0. FPGAでニューラルネットワークする。まずC言語でやってみる

そもそもFPGAが好きなのは特別な回路を作って並列でできることを高速化できるからです。僕が興味のあるのは人工知能なので、せっかく勉強した初歩のニューラルネットワークを回路にしてみようと思います。

まずは比較用にC言語のプログラムを

とはいえ、「1秒間に1万回学習出来ました!」って言っても早いのか遅いのか分からないので比較用にまずC言語で書いてみることにします。誤差逆伝搬法の復習にもなるしね。
なので、今回はFPGAに興味が無い人でも読めるはず。Cでのニューラルネットワークの実装です。

ミッション

1次元関数の近似をさせてみたいと思います。y=x^2みたいなね。
そのためにこんな回路にしようと思います。Screen Shot 2015-01-23 at 18.28.05

 

第1レイヤー(以降Layer1)は入力された実数をそのまま出力するものです。
Layer2とLayer3は学習します。またLayer4である最終の+ってなってるやつも学習しますが出力はシグモイド関数でなく入力値の加算値を出力します。つまり重み付けが変更されるのは

Screen Shot 2015-01-23 at 18.28.33

このオレンジのところってことになります。
実際にプログラムする時はレイヤーの数とそれぞれのレイヤーに存在するノードの数を変更できるようにします。例えば1層目は10個、 2層目は6個、3層目は10個みたいプログラムの変更なしでできるようにね。

最後のレイヤーが合計値を出力したり、重み付けも考慮されていたりってのはどうなんだろうね。あとで考える。

早速作ってみた

ということで説明を飛ばして完成したのがこちら(3分クッキングみたいですね!)
https://github.com/yuki-sato/neural-network-c-language/tree/dev-1

ちょっと普通のニューラルネットワークと違う点として、評価関数を変更しています。
評価関数は普通
E=(y(理想値) – y (現実値))^2
という形をとっていますが、「絶対値にしたいだけじゃん」って思ってるので、
E=|y(理想値) – y(現実値)|
を評価関数として利用しています。それによって微分値が変わりますから重み付けの補正値が少し変わってます。

ちなみにそれぞれの重み付けは0~Nの範囲で乱数で初期化するようにしました。0だと若干ダメな気もしますが、とりあえず動かしてみましょう。

試しに動かす

さっきの画像と同じ3ノードがつながってる構造にしました。
関数はy=x-2で、x=3(y=9)を教師データとして、この教師データで100回誤差逆伝搬法で学習させてみました。
シグモイド関数のアルファ値は1(標準シグモイド関数ってことだね)。学習時に使うイプシロンは0.1としました。
結果はこんなの。

Screen Shot 2015-01-23 at 20.06.43

 

右下のError Improvementってのが誤差がどのぐらい小さくなっているかです。横軸が試行回数で、縦軸が誤差です。誤差が直線的に小さくなりある回数から定常状態になっていますね。
数値で見てみましょう。学習していない1回めの出力は

22.999849

でした。9が正しい結果ですからだいぶ大きいですね。
これを元に1回学習させた結果の出力は

22.699852

と、確かに補正されています。100回目の出力は

8.900009

で、かなり近くなっています。どうやら小さく振動しているみたいで99回目は

9.200006

だったのですが、98回目の出力は100回目の出力と同じになっています。

ちょっと考える

まず、何故振動したのかですが、まずイプシロンが少し大きかったんでしょう。floatの値の範囲の問題があります。重み付けの補正値がfloatで表現できないほど小さな値になってしまっています。これはイプシロンが大きいのにも少し関係しています。

誤差伝搬しない問題

ニューラルネットワークは層を多くしても誤差が後ろまで伝わらないという既知の問題があります。それはすごく当たり前の話で、ニューラルネットワークというのは1層だけでも数学的に完全なので(この辺で話したやつ)そうなっちゃいます。
今回どのぐらい”伝搬しなかったのか”を見てみましょう。

 

Screen Shot 2015-01-23 at 20.04.50

これが乱数で生成したそれぞれの重み付け係数の初期値です。
行がそれぞれのレイヤーです。なので3層目は最後の出力なので1つになっています。
また、それぞれ4こ数字がありますが、それが前のレイヤーとの接続の重み付け値になっています。1個多いのは閾値用です(このへんで話したやつ)。
どれがどれかは置いておいて、これがどのぐらい変わったかを見てみましょう。100回学習させた後はこうなりました。

Screen Shot 2015-01-23 at 20.04.57

 

驚くべきことにLayer1(入力値をそのまま出力するノード)からLayer2につながってる部分は全く学習されませんでした。floatのせいでしょうね。
Layer2-Layer3は1つのノードだけ値が変更されています。
そして、Layer3-Layer4の最後の加算するノードへ繋がってる部分の値だけやたらと変更されています。

次に向けて

ちょっと予想外ですね。ここまで伝搬しないもんなんですね。これで果たして関数を再現できるんでしょうか。最後のSUMノードへの重み付けは調整したらいけないもんなのか。それとも同じデータで何回もやるとこうなるってだけで違うデータも入れて学習させたら意外とちゃんと関数を再現してくれるのか。ノードの数が足りないのか。
次回もうちょっと実験してみましょう。
次へ