ソースコード
ステップ14 同じ変数を繰り返し使う
微分を累算して、同じ変数を使っても結果がおかしくならないようにする。
オリジナルのコードにある次のコード片をCommon Lispにどう翻訳するか少し悩んだ。そのままだと(@gradient x)
という式があちこちに出てくるし、aops:vectorize
マクロで書けないのである。
if x.grad is None: x.grad = gx else: x.grad = x.grad + gx
解決策として、UIOPというライブラリで定義されたuiop:if-let
というマクロを使うことにした。uiop:if-let
は判定に使う値に名前を付けて、フォームの中で使えるようにしてくれるマクロである。
ここで、UIOPはよくある処理に対するユーティリティを多数提供するライブラリである。Common Lisp処理系には、CのMakeにあたるASDFというビルドツールが付属しており、UIOPはASDFの部品として同梱されているので、準標準ライブラリとして使える。
(setf (@gradient x) (uiop:if-let ((agx (@gradient x))) (aops:vectorize (agx gx) (+ agx gx)) gx))
見直してみて気が付いたが、fletを使ってローカル関数を定義する、with-accessorマクロを使うことでも解決できそう。
微分をリセットするためのclear-gradient
メソッドも用意した。
(defmethod clear-gradient ((var <variable>)) (setf (@gradient var) nil))
ステップ15 複雑な計算グラフ(理論編)
説明だけなので省略。
ステップ16 複雑な計算グラフ(実装編)
関数や変数に世代を覚えさせることで、逆伝播を正しく計算できるようにする。
そのためにgeneration
というスロットを用意し、世代順にソートを行う。
Common Lispのsort関数は実践 Common Lispの著者がいうところのリサイクルな関数なので、必ず返り値を受け取ってsetfする。
実践 Common Lispは、載せられている事例が古びてきているものの、Common Lispを学ぶための最初の一冊として素晴らしいと思う。原書は無料公開されているので、読める人はぜひ。