chakokuのブログ(rev4)

テック・コミック・ごくまれにチャリ

【DLfS】俺NNの動作がおかしいのでデバッグ..

簡単なネットワークでは学習したが、なぜかBiasが修正されない。これはまぁ単純なミスだろうから後からデバッグするとして、、バッチ実行させたところ、出力段のソフトマックス関数がバッチに対応してないことが判明。これはDLfS版の関数、softmax(P69)をそのまま使っていたのだけど、このページではまだバッチの説明が行われておらず、バッチ対応版ではなかったため。この結果、母数の計算、np.sum(exp_a)がテストパターン全体の合計値となってしまい、本来は各パターンごとの合計で割り算すべきが、全パターンの合計で割っていた(認識結果を一つ選ぶだけの使い方なら、割り算の分母まちがっても結果は変わらないけど、降下法で誤差使う場合、誤差(傾きの値)がめちゃくちゃ小さくなって学習に時間がかかりすぎる問題になる)。自分なりの実装例は以下。Lispのように再帰で書いているけど。。再帰で実装して実行効率が良いかどうかは不明

def softmax(a):
   if a.ndim == 1:
      exp_a = np.exp(a - np.max(a))
      return exp_a / np.sum(exp_a)
   else:
      return np.array(list(map(softmax, a)))

バッチ対応版で動くようデバッグしたので、改めてバッチで学習させてみた。使ったデータは、x[[1,0],[0,1]] , t[[1,0],[0,1]]というこれまでと同じだけど、2パターンを1つのバッチデータとしてまとめて食わせて学習させた。
結果が左のグラフ。2パターンを1バッチとして学習しており、1回の学習で、内部的には、[1,0],[0,1]の誤差をそれぞれ計算して平均した上で降下させている。正解を出し始めたのは8000回目からであった。8000回って、(パターン1→パターン2)を学習させた場合と比べて倍になっているように思えるが。。左のグラフの横軸は学習回数で、最大が16000回であり、パターン1→パターン2を交互に学習させる場合と同じ回数。グラフの落ち方が1/2の速度になっているように思える。

■追記:Biasが0固定だった理由
なぜバイアス値が0のままで降下法で数値が変わらなかったのかを調べていた。結果、numpyで配列を初期化する際、0で埋めるとint型と覚えられてしまい、0.1232等を代入しても0に丸められるのが原因であった。小数点を使いたい場合は、初期値も0.0等にする必要があった。
以下は正しい初期化例(配列の要素の型はfloat)

    self.network['bias'][0] = np.array([0.0,0.0])
    self.network['bias'][1] = np.array([0.0,0.0])

以下は間違っていた時の例(後で小数点入れたいのに整数で初期化すると、要素の型がint型になってしまう)

    self.network['bias'][0] = np.array([0,0])
    self.network['bias'][1] = np.array([0,0])

バグが取れた俺NNに対して、前回と同じトレーニングデータを使って2万回学習させた結果、得られたWeightとBias値

=== W0 ===
[[-2.57039212 -2.53475398]
 [ 2.3728416   2.36319172]]
=== B0 ===
[-0.22755051 -0.24156227]
=== W1 ===
[[-2.64908234  2.67908234]
 [-2.5885513   2.6585513 ]]
=== B1 ===
[ 2.37069693 -2.37069693]

バイアスの修正が正しく働いた結果の学習傾向が左のグラフ、前回のバッチ学習と比べて収束が早くなっている。また、正しく正解となるのが学習、4200回目であり、正解に到達するのに半分の学習で済んでいることが分かる。

さらに、、これまではデバッグしやすいように初期値を0.01等で固定していたが、教科書【DLfS】通りの乱数と、学習率を使って、同じデータを学習させてみた。結果は左のグラフ。収束がめちゃくちゃ早い。学習200回目で正解を出すようになった。ゆえに、、初期値と学習率の適切な値が重要と理解