ニューラルネットワークをJavaScriptで実装。
ニューラルネットワークやバックプロパゲーションについてネットで検索して解説記事を読むと分かったような気になるけれど、実際の仕組みが分かってないので応用ができない。ネットで実装例を探してJavaとPythonのプログラムを動かす事が出来たので、それらを改造しつつ理解を深めて、JavaScript版を自作した。
関数 f(x)=x2 を学習させる。
サンプルでは単純な関数 f(x)=x2 を学習させる。入力ユニットに x (-1~1) を、出力ユニットに x2 を与えて学習させ、次に未知の x を与えて学習の成果をみる。
入力層 | 1個+バイアス1個 | 入力値がx |
---|---|---|
隠れ層 | 3個+バイアス1個 | |
出力層 | 1個 | 出力値がf(x) |
プログラム。
学習結果。
赤い点が正解(教師)データ。青い点が未知入力を与えて認識させた結果。赤(正解)はx軸方向に等間隔、青(未知)は乱数でばらつきを与えている。
隠れ層の値の成分も表示した。グレーの点はバイアス入力値と重みの積 x0 * w1j0 で直線、グレーの線は関数入力値と重みの積 x1 * w1j1 でシグモイド曲線、2つの和が各隠れユニットの値になる。そして各隠れユニット値に出力層との重み w2jk を掛けて足したものが出力値になる。単調な(変曲点が全て同じ点(0.5,0.5)にある)シグモイドの和なので表現力には限界があるようだ。例えばsin(2PI・x)のように何度も振動するカーブは追随が難しい。
学習率ETAを0.5ぐらいに上げると収束は速くなる。でもあまり大きく、2などにするとf(x)=sin(x)では激しく振動してしまう。
実装して動かしていろいろ修正してみて、ようやく仕組みがわかってきた。このサンプルはシンプルだけどパーセプトロンの理解に役立った。ニューラルネットがどんな関数でも表せるとすれば、「どんな関数でも sigmoid 関数の和で表せる」ということで、フーリエ変換の「どんな波形でも sin 波の和で表せる」と似ている。
ところでバックプロパゲーションの誤差を計算するときに微分は必要なのだろうか? 誤差を小さくするだけなら修正方向(プラスかマイナスか)さえ合っていれば効率はともかくいずれは収束するんじゃないか? 試しに
d1[j] = val.z[j] * (1 - val.z[j]) * temp; // sigmoid()の場合。のところを
d1[j] = Math.sign(val.z[j]) * temp;としてみたら全然収束しない。やっぱり微分が必要なのか。 (注:Math.sign()は疑似コード)
参考になったリンク。
-
ニューラルネットワーク
Javaによる実装。シンプルで完結しているので最初の実装で参考になった。
-
多層パーセプトロンによる関数近似
Pythonによる実装。解説や関連リンクが多く知識を広げる段階で役立った。