記録は作業の証

鉄道とコンピュータ

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

ソースコード

ステップ4 数値微分

Common Lispでは0.942のような浮動小数点数リテラルを読み取ると、デフォルトで単精度浮動小数点数型(single-float)として扱ってしまう。この挙動は

 (setf *read-default-float-format* 'double-float)

という行を挿入すれば変更できる。自分はそうする代わりに0.942d0のようにリテラルで型を明示する方針を取った。

オリジナルのPythonコードでは__call__メソッドを利用することで、関数とDeZeroの関数を同じように扱える。そのため普通の関数を使って合成関数を表すことができる。RubyでもMethod#callメソッドがあるため、callメソッドを実装しておけば同じ扱い方でルーチンを起動できる。しかしCommon Lispの関数ではそうするわけにはいかないため、合成関数を表現するための<composed-function>クラスを定義した。

numerical-diff関数の実装はaops:vectorizeマクロのおかげで分かりやすいものになった。ありがたい。

ステップ5

説明のみ。

ステップ6 手作業によるバックプロパゲーション

手作業でバックプロパゲーションを行う。

ステップ7 バックプロパゲーションの自動化

<variable>クラスにbackwardメソッドを実装する。 Pythonではメソッドがクラスの名前空間に閉じ込められており、同じ名前だがシグネチャが違うメソッドを定義できる。オリジナルのDeZeroではVariableクラスとFunctionクラスにbackwardメソッドを定義しているが、前者は無引数、後者は引数を一つ取る。 しかし、Common Lispの総称関数は違うクラスで異なるシグネチャを持つことができない。仕方がないので、<variable>クラスでは仮引数を記述するが、利用しないということにした。

(declare (ignore gy))

この行を<variable>クラスのbackwardメソッドの中に挿入しておくことで、Common Lispコンパイラからの警告を抑制する。

ステップ8 再帰からループへ

コールスタックを利用する代わりに、自分でスタックを作って管理する。 スタックはリストを使って表現することにし、pushマクロとpopマクロを呼ぶことにした。