ソースコード
ステップ9 関数をより便利に
まず、呼び出し側が毎回gradient
に全要素が1の配列を設定しなければならないのを直す。
ユーティリティ関数として次のようなものを定義する。
(defun full-like (array fill-value) (let ((dims (array-dimensions array))) (make-array dims :initial-element fill-value))) (defun ones-like (array) (full-like array 1))
あとは次の行を書いておけば、gradient
が未設定の時、初期値を設定できる。
(unless (@gradient var) (setf (@gradient var) (ones-like (@data var))))
defclassの:initform
オプションを使ってもよかったかもしれない。
次に、<variable>
クラスに好き勝手な値を設定できないようにする。これにはcheck-typeを使うことで実行時に型を検査できる。インスタンスを作るときの挙動を変更するにはinitialize-instance総称関数をオーバーライドすればよい。
(defmethod initialize-instance :after ((var <variable>) &key) (check-type (@data var) array))
渡された値が数値なら配列に包み、配列であればそのまま返す関数も用意する。
(defun ensure-array (x) (if (numberp x) (vector x) x))
ステップ10 テストを行う
Common Lispではテストフレームワークがいくつか知られており、特にFiveAMが最も代表的なものとされている。今回はroveを試すことにした。
数値微分とバックプロパゲーションの結果を比較し、計算が正しいか検証するため勾配確認という方法を使う。オリジナルではNumPyのallclose
関数を使うことでほぼ等しいか判定できていたがCommon Lispにはそういうものはない。そこで次のような関数を定義することにした。
(defun all-close (x y) (every (lambda (x) (<= x 1d-08)) (aops:flatten (aops:vectorize (x y) (/ (abs (- x y)) (abs x))))))
配列の各要素について差を求め、1次元配列につぶし、差が定数以下であればほぼ等しいと判定する。効率は良くないかもしれないが、今のところはこれで十分だろう。この辺りはmsyksphinzさんの記事を参考にさせていただいた。