Batch Normalization:層ごとに、値の大きさをそろえる
第3章の前処理では、入力の値の大きさを0〜1などにそろえると、学習が安定することを学びました。ところが、ニューラルネットワークの中では、各層の出力が次の層の入力 になります。学習で重みが変わるたびに、次の層に渡る値の平均やばらつきも変わっていきます。深いネットワークでは、層ごとに値が大きくずれていき、学習が難しくなります。
そこで、各層で、ミニバッチごとに値をそろえ直す のが Batch Normalization(バッチ正規化)です。
手順
ミニバッチ(例えば32件)の中で、ニューロンごとに次の計算をします。
- 32件の値の平均 μ(ミュー)と、ばらつき(分散)σ2 を求める
- それぞれの値から平均を引き、標準偏差で割る(平均0、ばらつき1にそろう)
- そろえた値を、学習で決まる2つの数 γ(ガンマ)と β(ベータ)で、γ× 値 +β と調整する
z^=σ2+εz−μ,出力=γz^+β
ε(イプシロン)は、0で割らないように足す、ごく小さな数(0.00001など)です。2は、この章のレッスン2で、多項式の特徴量の大きさをそろえた「標準化」とまったく同じ計算です。3があるのは、「平均0、ばらつき1」が次の層にとって最適とは限らないからで、ちょうどよい平均とばらつきを、学習で決められるようにしています。
例:そろえる前と後
手書き数字のデータの32件を、ある層に通したときの値(3つのニューロン分)です。
ニューロンによって平均がばらばらだった値が、どれも平均0、標準偏差1にそろいました。
実験:Batch Normalizationで、深いネットワークが学習できるようになる
手書き数字のデータで、シグモイド関数の隠れ層を6層(各32個)重ねたネットワークを、ミニバッチ32件のSGD(学習率0.1)で20周学習させました。初期値は、前のレッスンのXavier法です。
- Batch Normalizationなし
- Batch Normalizationあり
周ごとの訓練データの損失。Batch Normalizationなしは、20周のあいだずっと約2.3のまま、ほとんど下がらない。Batch Normalizationありは、1周目の約1.77から順調に下がり、20周目には約0.09になる
Batch Normalizationなしでは、損失がまったく下がりませんでした。正解率0.096は、10個の数字からでたらめに選んだときの正解率(0.1)と同じくらいで、何も学習できていません。前のレッスンで学んだ勾配消失が起きているためです。Batch Normalizationを入れると、同じネットワークが学習できるようになり、正解率は0.964になりました。
各層の値を平均0、ばらつき1にそろえると、シグモイド関数に入る値が0の近く、つまり傾きが最も大きい(0.25に近い)ところに集まります。そのため、勾配が消えにくくなるのです。
学習するときと、使うとき(推論)の違い
使うときは、1件ずつ予測することもあり、ミニバッチの平均やばらつきを計算できません。そこで、学習の間に、ミニバッチごとの平均とばらつきを少しずつ記録しておき、使うときはその記録を使います。ドロップアウトと同じく、学習するときと使うときで動きが違う仕組みです。
注意:ミニバッチが小さすぎると、不安定になる
実は最初の実験では、訓練データ1347件を32件ずつに分けたとき、各周の最後に余る 3件だけのミニバッチ も使っていました。すると、損失は周ごとに大きく上下し、学習率0.1ではテストの正解率が0.46と0.61(乱数の種を2通り)にとどまりました。学習率0.2では、7周目に約0.33だった損失が、10周目に約3.9まで跳ね上がりました。わずか3件から計算した平均とばらつきは大きくぶれるため、そろえ方が毎回大きく変わってしまうのです。余りを使わないようにすると、上のグラフのとおり安定しました。
Batch Normalizationは、ミニバッチがある程度大きいこと(目安として数十件以上)を前提にした方法です。1件ずつのデータの中でそろえる Layer Normalization もあり、後の章で学ぶTransformer(大規模言語モデルのしくみ)では、こちらが使われています。ほかにも、画像1枚・チャネル1枚ごとにそろえる Instance Normalization、チャネルをいくつかの組に分けて組ごとにそろえる Group Normalization があり、どれも「何の範囲で平均とばらつきを求めるか」が違うだけです。
NumPyでBatch Normalizationを書く、よくある誤解と振り返り
学習するときの計算は、ミニバッチの平均とばらつきを、ニューロンごと(列ごと)に求めるだけです。
import numpy as np
z = np.array([[2.0, 5.0],
[4.0, 3.0],
[6.0, 4.0],
[8.0, 6.0]]) # ミニバッチ4件 × ニューロン2個
mu = z.mean(axis=0) # 列ごとの平均 → [5.0, 4.5]
var = z.var(axis=0) # 列ごとの分散(ばらつき)→ [5.0, 1.25]
z_hat = (z - mu) / np.sqrt(var + 1e-5) # 平均0、ばらつき1にそろえる
gamma, beta = 1.0, 0.0 # 学習で決まる数(最初は1と0)
out = gamma * z_hat + beta
print(z_hat.mean(axis=0), z_hat.std(axis=0)) # → ほぼ [0, 0] と [1, 1]
axis=0 は「縦の方向(件数の方向)に計算する」という意味で、列ごと(ニューロンごと)の平均になります。axis を付けないと、表全体の平均を1つ求めてしまいます
- 分散
var は、標準偏差の2乗です(第3章)
- PyTorchでは
nn.BatchNorm1d(ニューロンの数) や、CNN用の nn.BatchNorm2d(チャネルの数) を、層の間に入れるだけで使えます。学習するときと使うときの切り替えは、ドロップアウトと同じく model.train() と model.eval() です
よくある誤解
- 「Batch Normalizationは、入力データを一度そろえる前処理と同じ」: 前処理は学習の前に一度だけ行いますが、Batch Normalizationは、学習中に、毎回、各層の中で行います
- 「使うときも、そのときのミニバッチの平均を使う」: 使うときは、学習中に記録しておいた平均とばらつきを使います。そうしないと、同じ入力でも、一緒に予測するほかのデータによって結果が変わってしまいます
- 「ミニバッチの大きさに関係なく、同じように効く」: 3件のような小さなミニバッチでは、平均とばらつきがぶれて、学習が不安定になりました
振り返り
- Batch Normalization: 各層で、ミニバッチごとに値を平均0、ばらつき1にそろえ、学習で決まる γ、β で調整する
- 深いネットワークの学習を安定させ、勾配消失を防ぐ。シグモイド関数6層のネットワークが、なしでは何も学習できず、ありでは正解率0.964になった
- 使うときは、学習中に記録した平均とばらつきを使う。ミニバッチが小さすぎると不安定になる。1件ずつそろえるLayer Normalizationもある