ソースコード
ステップ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
マクロを呼ぶことにした。