表計算でニューラルネットワークを動かしてみる

先日、社内の勉強会でニューラルネットワークの発表がありました。私は大学時代に人工知能関係のゼミでニューラルネットワークも勉強したのですが、色々と忘れていたのでこの際改めて調べなおしてみました。
調べてみると、ニューラルネットワークの解説ページは色々あったのですが計算式の解説がほとんどで、いまいち仕組みというか動きを説明したものが少ないような感じでした。
そこで、思い出しがてらに私なりにニューラルネットワークの仕組みを紹介してみたいと思います。

仕組みを説明するにあたって、実際に動くものがあった方がわかりやすいと思ったので、Googleスプレッドシートで簡単な階層型ニューラルネットワークを使ってみました。

https://docs.google.com/spreadsheets/d/1yYFnFOZoE_B42ypoZJu8LBX1iiIIHT4l_4GMze63H4Y/edit

Googleのアカウントがあればご自身のGoogleDriveにファイルをコピーして触ってみてください。


問題定義
----

まずは学習させたい問題ですが、
**３×３の白黒画像が「０」〜「９」のどの数字を表しているかを判定する**
ということを考えたいと思います。
（ですが、実際には数字の「４」のパターンのみで説明していきます）

ニューラルネットワークの構成
----

次に、この問題を解くニューラルネットワークの構成を考えます。
今回は、入力ノード（９個）×中間ノード（４個）×出力ノード（１０個）の構成にしたいと思います。
入力ノードは、画像の各マスに１つずつ対応させて、黒なら1、白なら-1の値を出力することとします。
一方、出力ノードは、数字の各文字に１つずつ対応させ、それぞれの文字の確からしさ（1.0であるほどその数字である）を出力することとします。この値が最も高いノードの数字を、入力画像に対する「答え」として採用します。

ちなみに、この入力ノードや出力ノードの定義は、必ずこうすると決まっているわけではないので、良さそうな方法を使います。
例えば、出力ノードを１つだけにして出力値の整数部分を画像が意味する数字とする（出力値が4.2なら数字の「４」）、と定めることもできますが、この方法はうまくいかなさそうな気がします。
このような、問題をどのようにモデル化するのかということも、実際に学習手法を活用するときには考える必要があります。

ニューラルネットワークの構造
-----

この構成に沿って実装したものが先に紹介したGoogleスプレッドシートということになります。

このシートの構造は以下のようになっています。

| セル | 内容 |
|---| --- |
| B15:D17 | 入力となる３×３の画像。各マスが黒なら1、白なら-1としています |
| B20:B28 | 入力ノードからの出力値。１ノード（１セル）が画像の１ドットに対応しています。(ノード０が座標(0,0)、ノード１が座標(1,0)、...) |
| D20:G28 | 入力ノードと中間ノードをつなぐエッジの重み。入力ノードB20:B28と中間ノードD42:G42の交差する位置がそれらのノードをつなぐエッジに対応します。初期値はランダムな値にしておきます。|
| D42:G42 | 中間ノードからの出力値。入力ノードの出力とエッジの重みを元に算出されます。 |
| D30:G39 | 中間ノードと出力ノードをつなぐエッジの重み。初期値はランダムな値にしておきます。|
| K30:K39 | 出力ノードからの出力値。１ノードが１つの数字の確からしさを意味します。1.0に近い出力であるほど、そのノードが受け持つ数字であると判断します。|
| L30:L39 | 入力された画像に対する判定結果の期待値。今回は、数字の４であることを想定しているので、そのセルだけが1.0で他が0.0であることになります |
| N40 | 出力ノードの出力値と期待値を比較した上での誤差の評価値です。この値を0に近ずけるのが学習の目標となります |
| Q20:T28, Q30:T39 | 学習として誤差を最小化させるための、ノード間のエッジの重さの変化量です。この値を各重みに加算することで学習が進みます |
| G15 | 学習時の重みの変化量に対する倍率。この値を大きくすることで学習を早めることができます。ただし、大きすぎると過学習になりがちになります |
| V20:Y28, V35:Y44 | 現在の重みにQ20:T28, Q30:T39の変化量を足したあとの新しい重みの値。 |

なお、このシートでは自動で学習が進むわけではなく、手動で１ステップごとに進めていく必要があります。
V20:Y39の領域の値をD20:G39に[編集]>[特殊貼り付け]>[値のみ貼り付け]を行うと１ターンの学習が進みます。１ターンごとの動作を確かめながら進めてみるとネットワークの学習の様子がわかるかと思います。


ニューラルネットワークの学習の流れ
-----

それでは、ニューラルネットワークの学習の流れを順番に説明していきます。
基本的に、「動き」をメインに説明していますので、計算式の詳細については、今回参考にさせてもらった以下のページを参照ください。
https://www.sist.ac.jp/~suganuma/kougi/other_lecture/SE/net/net.htm


**Step.1**
入力された画像に応じて入力層の各ノードの値が決まります(B20:B28)

**Step.2**
入力ノード群(B20:B28)の値と、入力ノードと中間ノードをつなぐエッジの重み(D20:G28)の値を用いて中間ノードの値(D42:G42)を計算します。
中間ノード(D42)を例にとると、この値は入力ノード(B20:B28)とエッジ(D20:D28)の値をそれぞれ１組ずつ掛けた結果(B20 * D20、D21 * D21、...)を合計した値(D41)を、シグモイド関数と呼ばれる関数によって-1〜1の範囲の値に正規化しています。

この計算の意図をわかりやすくするために、[学習後]シートに極端な例をつくってみました。
例えば、このシートの中間ノード(D30)の値が1.0に近くなるのはどういう時かを考えます。入力ノードとのエッジの重み(D8:D16)が正の方向に大きい場合は、入力ノードの値が1(=黒)であるほど中間ノードが1.0になることに貢献します。一方、重みが負の方向の値であれば、入力が-1(=白)であるほど貢献します。重みが0に近い場合は入力値の影響が小さくなります。
つまり、この状態ではC37:E39に示したような画像パターンであるほど1.0に近い値を出力します。ニューロン的に言えば「強く発火する」状態と言えます。これにより中間ノード(D30)はこのような画像パターンを判定する役割を持っていると言えます。


**Step.3**
中間ノードの計算ができたところで、次は中間ノード群(D32:G32)と、中間ノードと出力ノードをつなぐエッジの重み(D30:G39)で同様の計算を行って出力ノードの値(K30:K39)を計算します。

Step.2で説明したように、個々の中間ノードが入力ノードのパターンを判定していますので、出力ノードではそれらのパターンの組み合わせにて画像が表現する数字を判定することになります。

**Step.4**
ここまでで、ニューラルネットワークによるパターンの判定が完了です。
出力ノードのうちで最大値のノードの担当する数字が判定結果ということになります。

**Step.5**
入力された画像パターンに対する出力の期待値(L30:L39)と実際の出力値を比較します。
ノードごとの期待値と実際の値との二乗誤差の合計値(N40)がこのネットワークの評価値となります。この値が一定値より小さくなると学習が完了したとみなすことができます。

**Step.6**
ここからは学習のフェーズになります。
出力ノードごとの誤差値(M30:M39)と学習係数(G15)、中間ノードの値(D42:G42)の値から、中間ノードと出力ノード間のエッジの重さの変化量を算出します。
この計算は、最急降下法と言われる最適化問題の解法の１つです。計算式の詳細は他のサイトに任せまします。

**Step.7**
次に、入力ノードと中間ノードをつなぐエッジの重みの変化量(Q20:T28)を算出します。

**Step.8**
最後に実際に変化量をエッジに加算して新しいエッジの重みを算出し(V20:Y30)、この値でエッジの重みを更新すれば１ターンの学習が完了です。

**Step.9**
今回の例では、入力パターンが固定ですが、実際にはこの後に入力パターンを変えてStep.1から処理を繰り返します。


まとめ
----

かなり大雑把ですが、ニューラルネットワークの学習の仕組みを説明してみました。
プログラムで書いてみるのとは別に、表計算でやってみるとどういう計算でなりたっているのかが掴みやすいのではないかと思います。