Batch Normalization を実装する
最近よゆーのーてどよどよな感じのたかたかです.Batch Normalization ちう論文読んだらむしょーにプログラム書いて実験しとーなって,週末にちょこっとやってみました.
Deepなニューラルネットの学習がうまく進まない問題を解決する方法として,各層への入力(実際には活性化関数への入力)をミニバッチ単位で正規化する仕組みを組み込むことを提案してます.それ以上の説明は省略 (^^) 実験結果を見る限り,学習の進行を加速する(正確には,大きな学習係数でいけるようになる)効果は抜群のようです(理論的な面で少し気にかかるとこがありますが).論旨は明快でアルゴリズムも簡単なので,リンク先の論文を読めばすぐわかると思います.
ちうわけで,プログラムを書いて実験してみました.これまで同様に Theano を使ってます.ソースはこちら:
- https://gist.github.com/takatakamanbou/1af88ab0d7f10b842f58
- https://gist.github.com/takatakamanbou/2471f7b4de908a601646
やっつけ仕事で効率の悪い処理になってるとこもあるので注意です.まだ convolutional neural network で使えるようにしてへんし.
3層のネットワークでの実験
まずは3層のネットワークに MNIST の識別をさせてみる実験です.主な実験条件は次の通り:
- Input - ReLu - ReLu - softmax という3層,隠れ層ニューロン数は1000
- バッチサイズ 128 の stochastic gradient descent
- 学習係数 eta = 0.1, 慣性項の係数 mu = 0.9, weight decay なし,dropout なし
まず,従来のネットワークで学習するとこんなん:
$ time THEANO_FLAGS='device=gpu1' python ex151220.py Using gpu device 1: Tesla K20c # Layer 0 : Input ( 784 ) dropout = 1.0 # Layer 1 : ReLu ( 784 x 1000 ) dropout = 1.0 # Layer 2 : ReLu ( 1000 x 1000 ) dropout = 1.0 # Layer 3 : linear ( 1000 x 10 ) dropout = 1.0 ### training: NL = 50000 NV = 10000 batchsize = 128 # eta = 0.1 mu = 0.9 lam = 0.0 0 | 2.3023 90.29 | 2.3023 89.84 1 | 0.1007 3.06 | 0.1128 3.31 | eta = 0.1 2 | 0.0617 1.83 | 0.0901 2.75 | eta = 0.1 3 | 0.0428 1.29 | 0.0842 2.49 | eta = 0.1 4 | 0.0302 0.88 | 0.0861 2.19 | eta = 0.1 5 | 0.0237 0.68 | 0.0834 2.13 | eta = 0.1 6 | 0.0140 0.42 | 0.0775 1.95 | eta = 0.1 7 | 0.0137 0.43 | 0.0866 2.03 | eta = 0.1 8 | 0.0099 0.32 | 0.0869 2.11 | eta = 0.1 9 | 0.0049 0.15 | 0.0806 1.78 | eta = 0.1 10 | 0.0024 0.05 | 0.0798 1.73 | eta = 0.1 20 | 0.0001 0.00 | 0.0869 1.62 | eta = 0.1 30 | 0.0000 0.00 | 0.0905 1.61 | eta = 0.1 40 | 0.0000 0.00 | 0.0929 1.62 | eta = 0.1 # NT = 10000 50 | 0.0000 0.00 | 0.0929 1.62 | 0.0862 1.56
real 2m16.864s user 1m25.864s sys 0m48.664s
50epoch で学習,検査,テストデータの誤識別率はそれぞれ 0.00%,1.62%,1.56%となりました.
次は,Batch Normalization (BN) を使った場合:
$ time THEANO_FLAGS='device=gpu1' python ex151220bn.py Using gpu device 1: Tesla K20c # Layer 0 : Input ( 784 ) dropout = 1.0 # Layer 1 : ReLu ( 784 x 1000 ) dropout = 1.0 # Layer 2 : ReLu ( 1000 x 1000 ) dropout = 1.0 # Layer 3 : linear ( 1000 x 10 ) dropout = 1.0 ### training: NL = 50000 NV = 10000 batchsize = 128 # eta = 0.1 mu = 0.9 lam = 0.0 0 | 2.3023 90.29 | 2.3023 89.84 1 | 0.1206 3.75 | 0.1412 4.27 | eta = 0.1 2 | 0.0515 1.47 | 0.0815 2.47 | eta = 0.1 3 | 0.0348 0.98 | 0.0727 2.17 | eta = 0.1 4 | 0.0280 0.81 | 0.0748 2.25 | eta = 0.1 5 | 0.0190 0.54 | 0.0693 1.91 | eta = 0.1 6 | 0.0118 0.28 | 0.0591 1.65 | eta = 0.1 7 | 0.0097 0.26 | 0.0584 1.58 | eta = 0.1 8 | 0.0102 0.27 | 0.0640 1.89 | eta = 0.1 9 | 0.0050 0.10 | 0.0562 1.55 | eta = 0.1 10 | 0.0043 0.08 | 0.0568 1.55 | eta = 0.1 20 | 0.0009 0.02 | 0.0632 1.47 | eta = 0.1 30 | 0.0001 0.00 | 0.0510 1.20 | eta = 0.1 40 | 0.0001 0.00 | 0.0537 1.19 | eta = 0.1 # NT = 10000 50 | 0.0001 0.00 | 0.0537 1.19 | 0.0520 1.31 real 3m9.765s user 2m9.004s sys 0m57.476s
この条件では,学習の進み方に大きな違いは見えません.少しテスト識別率が下がってて,著者らが主張するように BN には regularization の効果もあるのかもです.それから,学習に要する時間が大きくなってますが,これはたかたかの実装が悪い(特に,バッチごとの平均と分散を使って推論時用の平均と分散を推定する処理の効率が悪い)せいだと思います.プログラムを見直せばたぶんもっと差は縮まるでしょう.
15層のネットワークでの実験
次は15層です.こんだけ deep にすると,従来のネットワークでは...
$ time THEANO_FLAGS='device=gpu1' python ex151220.py Using gpu device 1: Tesla K20c # Layer 0 : Input ( 784 ) dropout = 1.0 # Layer 1 : ReLu ( 784 x 1000 ) dropout = 1.0 # Layer 2 : ReLu ( 1000 x 1000 ) dropout = 1.0 (中略) # Layer 14 : ReLu ( 1000 x 1000 ) dropout = 1.0 # Layer 15 : linear ( 1000 x 10 ) dropout = 1.0 ### training: NL = 50000 NV = 10000 batchsize = 128 # eta = 0.1 mu = 0.9 lam = 0.0 0 | 2.3026 90.14 | 2.3026 90.09 1 | 2.3030 88.64 | 2.3046 89.36 | eta = 0.1 2 | 2.3028 90.06 | 2.3039 90.10 | eta = 0.1 (中略) 40 | 2.3032 88.64 | 2.3038 89.36 | eta = 0.1 # NT = 10000 50 | 2.3032 88.64 | 2.3038 89.36 | 2.3029 88.65 real 9m6.693s user 5m45.972s sys 3m10.120s
学習が進まなくなってしまいました.学習定数を大きくしたり小さくしたりしても変わりません.
一方,BNの場合...
$ time THEANO_FLAGS='device=gpu1' python ex151220bn.py Using gpu device 1: Tesla K20c # Layer 0 : Input ( 784 ) dropout = 1.0 # Layer 1 : ReLu ( 784 x 1000 ) dropout = 1.0 # Layer 2 : ReLu ( 1000 x 1000 ) dropout = 1.0 (中略) # Layer 14 : ReLu ( 1000 x 1000 ) dropout = 1.0 # Layer 15 : linear ( 1000 x 10 ) dropout = 1.0 ### training: NL = 50000 NV = 10000 batchsize = 128 # eta = 0.1 mu = 0.9 lam = 0.0 0 | 2.3026 90.14 | 2.3026 90.09 1 | 0.3051 5.57 | 0.3116 5.70 | eta = 0.1 2 | 0.0900 2.54 | 0.1193 3.34 | eta = 0.1 3 | 0.0621 1.75 | 0.0958 2.75 | eta = 0.1 4 | 0.0403 1.16 | 0.0829 2.15 | eta = 0.1 5 | 0.0429 1.12 | 0.0882 2.48 | eta = 0.1 6 | 0.0324 0.90 | 0.0865 2.34 | eta = 0.1 7 | 0.0283 0.72 | 0.0778 2.26 | eta = 0.1 8 | 0.0249 0.65 | 0.0811 2.09 | eta = 0.1 9 | 0.0164 0.42 | 0.0690 1.83 | eta = 0.1 10 | 0.0185 0.47 | 0.0785 2.16 | eta = 0.1 20 | 0.0046 0.07 | 0.0683 1.66 | eta = 0.1 30 | 0.0030 0.04 | 0.0626 1.45 | eta = 0.1 40 | 0.0016 0.03 | 0.0715 1.61 | eta = 0.1 # NT = 10000 50 | 0.0016 0.03 | 0.0715 1.61 | 0.0690 1.58 real 19m1.669s user 11m13.904s sys 7m23.208s
ちゃんと学習が進むようになってます.すばらしい.