前回、ReLUと重みの初期化で勾配消失が解消されたという話を書きました。ただ、勾配が通るようになっただけでは十分ではなくて、深いネットワークには「学習が不安定になる」問題と「訓練データを丸暗記してしまう」問題がまだ残っています。
今回はこの2つに対処するバッチ正規化とドロップアウトについて書きます。
バッチ正規化:各層の入力分布を揃える
深いネットワークを学習させると、パラメータの更新に伴って各層への入力の分布が変わり続けます。前の層の出力が変わるので、次の層から見れば「入力のルールが毎回変わる」ような状態です。内部共変量シフトと呼ばれるこの現象が、学習を不安定にします。
バッチ正規化(Batch Normalization)は、ミニバッチ内のデータの平均を0、分散を1に正規化する処理です。
\[ \hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} \]\( \mu_B \) がミニバッチの平均、\( \sigma_B^2 \) が分散。これを活性化関数の前か後に挟みます。入力のスケールが一定に保たれるので、勾配の爆発や消失が起きにくくなり、学習率も大きめに設定できます。結果として学習が速くなる。
実装は nn.BatchNorm1d や nn.BatchNorm2d を層の間に入れるだけです。効果が大きいので、現在ではほぼ標準的に使われています。
ドロップアウト:ニューロンをランダムに無効化する
過学習の問題は前々回にも触れましたが、パラメータが多い深いネットワークでは特に起きやすいです。訓練データのノイズまで覚えてしまい、未知のデータで性能が出ない。
ドロップアウト(Dropout)は、学習の各ステップでランダムに一部のニューロンを無効化して、残りだけで学習を進める手法です。特定のニューロン同士が強く結びついて決まったパターンに依存するのを防げます。毎回違う組み合わせで学習するので、個々のニューロンがより汎用的な特徴を学ぶようになる。理論的には、毎回異なるサブネットワークを学習してアンサンブルしているのに近い効果があるとされています。
ニューロンを消して性能が上がるというのは直感に反する気もしますが、実際にドロップアウトを入れるだけで検証データの精度が改善するケースは多いです。
学習時と推論時で挙動が違う
バッチ正規化もドロップアウトも、学習時と推論時で動作が変わります。
バッチ正規化は学習時にミニバッチの統計量を使い、推論時は学習中に計算した移動平均を使います。ドロップアウトは学習時にニューロンを消し、推論時は全ニューロンを使って出力をスケーリングします。
PyTorchだと model.train() と model.eval() の切り替えがこれに対応していて、切り替え忘れると推論時にドロップアウトがかかったままになったりします。学習はうまくいっているのに推論の結果がおかしいときは、だいたいここが原因です。自分も何度かやりました。
まとめ
バッチ正規化で各層の入力分布を安定させ、ドロップアウトで特定パターンへの依存を防ぐ。どちらも実装が簡単で効果が大きく、深層学習ではほぼ必ず使われます。
ここまでで、全結合型のニューラルネットワークに必要な道具は一通り揃いました。次回は、画像のような空間的な構造を持つデータに特化した畳み込みニューラルネットワーク(CNN)について書きます。