記録は作業の証

鉄道とコンピュータ

ゼロから作るDeep LearningをCommon Lispで書き直す(ステップ9~10)

ソースコード

ステップ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さんの記事を参考にさせていただいた。