Lambdaカクテル

Common LISPが好きなWeb屋さんです 自宅サーバやフロントエンドもできます

ニューラルネットワーク(多層パーセプトロン)その1

実装しながらメモ。メモ帳のかわり。備忘録。

最近ニューラルネットワークにはまってます。小さなノード群が数学的問題を脳のように解決するなんて、夢があるじゃないですか。

そんなわけでこのニューラルネットワーク(以下NN)のうち「多層パーセプトロン(Multi-layer perceptron, 以下MLP)」と呼ばれるタイプのNNをGauche Schemeで実装してみようと思い立ったのが数日前の話だが、なかなか実装が思い通りにいかない。階層状のデータ構造を扱うのはSchemeを筆頭としたLISP語族では得意な処理だけど、NNは「Neural」の語義通り網目状のデータ構造を持ってる。階層と言うより重みつきグラフのような代物だ。

NLPの動作原理

さて、ここで改めてMLPについて解説しようと思います。

MLPは入力層(Input layer)、不可視層(Hidden layer)、出力層の三層に分かれており、複数の計算ノードが不可視層と出力層に配されています。それぞれのノードは無限個の入力と唯一の出力を持ちます。ノードは入力の個数に対応した「重み」を保持しており、それぞれの入力に重みを掛けた数値の和、つまり入力と重みの内積(ドット積)から、さらにバイアス数値を引き、それを判断関数に通したものを出力として返します。バイアス数値はノードの判断の閾値を決定し、判断関数によってノードの解答は0から1までの数値に正規化されます。判断関数にはシグモイド関数がよく利用されるようです。

入力層には数値のリスト(場合によってはペア)が与えられます。不可視層のノードは入力層の数値を入力に取り、計算した値を出力します。同じように出力層のノードは不可視層のノードと接続されており、入力を計算して最終的な数値を出力します。

実装

まずノード単体のふるまいを関数にする。入力のリスト、重みのリスト、バイアス数値を受け取り、数値を返す関数を作成。

あとはどんどん下層から上層に向かってfoldしていけばいい(?)

実装上の課題

ここでいくつかの問題が浮上する。

  • 多対多の網構造はGaucheでは扱いにくいので一対多の平坦な木構造に展開する必要がある。複雑な網だと莫大なメモリを消費する。
  • 重みは固定だがノードの入力は下層ノードの計算を待たなければならない。これによってノードの構造の記述が静的でなくなる。できたらノードの構造記述と計算処理は分離したい。

この二つの問題が解決すべきものはおおよそ共通している。

まず第一の問題。網構造というのは暗黙のうちに参照先をメモ化しているはずだ。
木構造に展開したときの問題は、前もって下層の解答を用意しておき、それを後から参照させることで解決できる。
そういうわけで網構造を完全に分解して「このノードはこのノードを参照しているよー」「ノードの重みリストはこれだよー」というふうにしてしまう。これを動的にパースしていけば、求めている解を導出できる。

ついでに第二の問題も半分解決できる。まずグラフの構造をグラフィカルにS式で記述して、これをパーサにかけて先述の形式に変換する。これで明快にノードの構造を記述できる。

これによりマクロな分析は困難になるが、ミクロな方向に計算をシフトすることで問題は解決できた。

必要なもの(いまからつくる)

  • マクロ形式からミクロ形式へのコンバータ関数
  • ミクロ形式の構造をパースして計算ユニットに渡し、最終的な出力を導出する関数
  • 単一ノードの計算を担う関数
  • 入力パラメータをミクロ形式/マクロ形式に代入する関数

まとめ

このコーナーはまだ(当分)つづくと思う。