実装しながらメモ。メモ帳のかわり。備忘録。
最近ニューラルネットワークにはまってます。小さなノード群が数学的問題を脳のように解決するなんて、夢があるじゃないですか。
そんなわけでこのニューラルネットワーク(以下NN)のうち「多層パーセプトロン(Multi-layer perceptron, 以下MLP)」と呼ばれるタイプのNNをGauche Schemeで実装してみようと思い立ったのが数日前の話だが、なかなか実装が思い通りにいかない。階層状のデータ構造を扱うのはSchemeを筆頭としたLISP語族では得意な処理だけど、NNは「Neural」の語義通り網目状のデータ構造を持ってる。階層と言うより重みつきグラフのような代物だ。
NLPの動作原理
さて、ここで改めてMLPについて解説しようと思います。
MLPは入力層(Input layer)、不可視層(Hidden layer)、出力層の三層に分かれており、複数の計算ノードが不可視層と出力層に配されています。それぞれのノードは無限個の入力と唯一の出力を持ちます。ノードは入力の個数に対応した「重み」を保持しており、それぞれの入力に重みを掛けた数値の和、つまり入力と重みの内積(ドット積)から、さらにバイアス数値を引き、それを判断関数に通したものを出力として返します。バイアス数値はノードの判断の閾値を決定し、判断関数によってノードの解答は0から1までの数値に正規化されます。判断関数にはシグモイド関数がよく利用されるようです。
入力層には数値のリスト(場合によってはペア)が与えられます。不可視層のノードは入力層の数値を入力に取り、計算した値を出力します。同じように出力層のノードは不可視層のノードと接続されており、入力を計算して最終的な数値を出力します。
実装
まずノード単体のふるまいを関数にする。入力のリスト、重みのリスト、バイアス数値を受け取り、数値を返す関数を作成。
あとはどんどん下層から上層に向かってfoldしていけばいい(?)
実装上の課題
ここでいくつかの問題が浮上する。
- 多対多の網構造はGaucheでは扱いにくいので一対多の平坦な木構造に展開する必要がある。複雑な網だと莫大なメモリを消費する。
- 重みは固定だがノードの入力は下層ノードの計算を待たなければならない。これによってノードの構造の記述が静的でなくなる。できたらノードの構造記述と計算処理は分離したい。
この二つの問題が解決すべきものはおおよそ共通している。
まず第一の問題。網構造というのは暗黙のうちに参照先をメモ化しているはずだ。
木構造に展開したときの問題は、前もって下層の解答を用意しておき、それを後から参照させることで解決できる。
そういうわけで網構造を完全に分解して「このノードはこのノードを参照しているよー」「ノードの重みリストはこれだよー」というふうにしてしまう。これを動的にパースしていけば、求めている解を導出できる。
ついでに第二の問題も半分解決できる。まずグラフの構造をグラフィカルにS式で記述して、これをパーサにかけて先述の形式に変換する。これで明快にノードの構造を記述できる。
これによりマクロな分析は困難になるが、ミクロな方向に計算をシフトすることで問題は解決できた。
必要なもの(いまからつくる)
- マクロ形式からミクロ形式へのコンバータ関数
- ミクロ形式の構造をパースして計算ユニットに渡し、最終的な出力を導出する関数
- 単一ノードの計算を担う関数
- 入力パラメータをミクロ形式/マクロ形式に代入する関数
まとめ
このコーナーはまだ(当分)つづくと思う。