数値微分と計算グラフのプログラムを考える 赤本5章の復習 その2

前回に引き続き、赤本の5章です。
今回はプログラムの観点から見ていきたいと思います。  

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装  

おさらいですが、 数値微分、計算グラフ、順伝播、逆伝播 この辺のキーワードでややこしくなりそうな点がいくつかあるので簡単にまとめておきます。
 

  • 計算グラフは順伝播のときでも、逆伝播のときでも使える
  • 比較しているもの
    • 数値微分と計算グラフを用いた計算→後者のほうが速い
    • 順伝播と逆伝播→後者のほうが速い

プログラムの観点から見てみる

前回の記事で数値計算のデメリットに「計算に時間がかかる」とありましたが、 実際にどこに時間がかかっているのでしょうか。
それを確かめてみましょう。  

ニューラルネットのモデル

  この本の5章にあるものと全く同じモデルを見ていきます。
以下の図の様なモデルです。

数値微分の場合

例として入力値xは2*2行列とします。
すると各レイヤは以下のような数式で表すことができます。

a_{1} = x \cdot w_{1} + b_{1}
z = \dfrac {1}{1+e^{-a_{1}}}
a_{2} = z \cdot w_{2} + b_{2}
y = \dfrac {e^{a_{2}}}{\sum ^{n}_{i=1} e ^{(a_{2})_{i}}}
loss = -\sum _{k}t_{k}\log y_{k}  

なので全て代入するとこんな式になります。
こんな大層な式に・・。
しかもこれ、xもwもbも行列なわけです。

loss = -\sum _{k}t_{k}\log (\dfrac {e^{\dfrac {1}{1+e^{-x \cdot w_{1} + b_{1}}} \cdot w_{2} + b_{2}}}{\sum ^{n}_{i=1} e ^({\dfrac {1}{1+e^{-x \cdot w_{1} + b_{1}}} \cdot w_{2} + b_{2})_{i}}})_{k}

これを以下のような中心差分微分の関数にかけて一発で微分させます。
引数のfには関数であるloss、 xには微分する値であるwやbを代入します。

【引用元】https://github.com/oreilly-japan/deep-learning-from-scratch/blob/master/common/gradient.py

numpy.nditerとは

この関数の中に出てくるnp.diterというのは何でしょうか。
少し見てみます。  
まず以下のような3*3の行列を用意します。

np.nditer()を使ってみます。

つまり、行列のインデックスを順々に返してくれます。
普通のforループと比べて何が嬉しいのかと言うと、 行列の次元が増えたり減ったりしても上記の部分に手を加えなくても良い点です。
op_flagsオプションはループした時に要素を書き換え可能にするか否かの設定をします。

詳しくはこのドキュメントや、teratailにあった質問などを参考にしてみてください。  

誤差逆伝播法の実装

 全く同じモデルをレイヤごとに計算グラフを作ります。



これらを参考にレイヤごとにクラスを作り、 下の図のように入力と出力はココとココになるような関数を定義します。

最も実装がシンプルなsigmoidレイヤのクラスは以下のようになります。

【引用元】deep-learning-from-scratch/layers.py at master · oreilly-japan/deep-learning-from-scratch  

順伝播と逆伝播を同時に定義し、実行時に順伝播→逆伝播の順で値を流します。
このような実装を計算グラフから導き、全レイヤに対してクラスを定義します。
つまり、この辺で計算ミスや実装ミスが起こる可能性があります。
しかも気づきにくいです。

最後に以下のようなコードで最初の入力を
\dfrac {\partial loss}{\partial loss}=1
とし、逆伝播で一気に勾配を求めます。

【引用元】deep-learning-from-scratch/two_layer_net.py at master · oreilly-japan/deep-learning-from-scratch  

これによって毎度毎度計算するのではなく、関数を呼び出すだけなので速く計算が終わります。

見たらおわかりの通り、xの値は入力する物の種類によって次元や要素数が増え、 レイヤを重ねるごとにwやbの次元も増えていきます。
すると計算回数も増えに増えていくので数値微分で計算するのと誤差逆伝播法で計算するのでは処理時間の差は広がっていくばかりです。

数値微分と誤差逆伝播法のがどれくらい計算に差が出るのか検証しているエントリーがありましたので参考にしてみてください。

【参考】
Deep Learningの Backpropagationはどのくらい高速か? – TIM Labs  
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装