Batch Normalizationによる学習の効率化
Introduction
Batch Normalizationは各層でバッチごとの入力を正規化する手法である。これにより
学習速度があがる(学習係数の値を大きく設定可能)
初期値に依存しにくくなる
などの効果がある。今回は、MNISTを用いてその効果を確認した。 また、活性化関数についてSigmoidおよびReLu関数を用いた場合を比較した。
Implementation
Algorithm
$N$個のデータからなるミニバッチ$B = { x_{1}, \cdots ,x_{N} }$に対して、 平均$\mu_{B}$および分散を求めて正規化を行う。 さらに、パラメータを用いて正規化したデータのスケールとシフトを実行する。
Backpropagation
BatchNormの誤差逆伝搬は計算グラフを用いて導出できる。 詳細は文献[1]にゆずる。
順伝搬および逆伝搬からなるBatchNormクラスは以下の通り。
class BatchNorm: def __init__(self, dim): self.gamma = np.ones(np.prod(dim)).astype('float32') self.beta = np.zeros(np.prod(dim)).astype('float32') self.batch_size = None self.dgamma = None self.dbeta = None def forward(self, x): self.x_shape = x.shape if x.ndim != 2: x = x.reshape(x.shape[0], -1) mu = np.mean(x, 0) xc = x- mu var = np.mean(xc**2, 0) std = np.sqrt(var + 1e-6) xn = xc / std self.xc = xc self.xn = xn self.std = std self.batch_size = x.shape[0] out = self.gamma * xn + self.beta return out.reshape(self.x_shape) def backward(self, delta): if delta.ndim != 2: delta = delta.reshape(delta.shape[0], -1) dbeta = np.sum(delta, 0) dgamma = np.sum(self.xn * delta, 0) dxn = self.gamma * delta dxc1 = dxn / self.std dstd = -np.sum((self.xc * dxn) / (self.std * self.std), axis=0) dvar = 0.5 * dstd / self.std dxc2 = 2.0 * dvar / self.batch_size dx1 = dxc1 + dxc2 dx2 = -np.sum(dx1, axis=0) / self.batch_size dx = dx1 + dx2 self.dgamma = dgamma self.dbeta = dbeta return dx.reshape(self.x_shape)
Results
今回実験したモデルを以下の表にまとめた。いずれのモデルにおいても、入力層のユニット数は784個、隠れ層は300個で構成した。表のlrは学習係数(learning rate)を示している。
Name | Construction | Parameters |
---|---|---|
S3 | Input->Dense->Dense->Softmax->Output | Sigmoid, lr = 0.01 |
S4 | Input->Dense->Dense->Dense->Softmax->Output | Sigmoid, lr = 0.01 |
R6 | Input->Dense->Dense->Dense->Dense->Dense->Softmax->Output | ReLu, lr = 0.01 |
R6_lr | Input->Dense->Dense->Dense->Dense->Dense->Softmax->Output | Relu, lr = 0.1 |
S6_BN_lr | Input->Dense->Dense->Dense->Dense->Dense->Softmax->Output | Sigmoid, lr = 0.01 |
S6_BN | Input->Dense->Dense->Dense->Dense->Dense->Softmax->Output | Sigmoid, BatchNorm, lr = 0.5 |
R6_BN | Input->Dense->Dense->Dense->Dense->Dense->Softmax->Output | ReLu, BatchNorm, lr = 0.1 |
Batch normalization
BatchNormの有無による損失関数の変化を下の図に示す。まず、S3およびS4モデルより層を深くすることで学習が進まなくなっていることがわかった。S6_BN_lrより、層を深くしてもBatchNormを実行することで学習効率が大きく改善していることがわかった。さらに学習係数(lr)を0.01から0.5に大きくしても学習が進んでいることも分かった。
Activation function
ReLu関数を活性化関数として用いた場合の損失関数の変化を下図に示す。活性化関数にReLu関数を用いることで層を深くしても学習が進んでいることが確認できた(R6モデル)。活性化関数(ReLu)および学習係数(lr=0.1)を揃えた2つのモデル(R6_lr, R6_BN)を比較するとBatchNormを実行することでわずかながら学習効率の改善が見られた。
メモ
Batch Normalization
$(\gamma , \beta)=(1, 0)$とすると通常の正規化を実行することになる。したがって、$(\gamma , \beta)=(1, 0)$を初期値として与えるのが一般的であろう。学習パラメータとすることで、より学習が進むようなデータの分布を探すようになる。パラメータが増えることで過学習のリスクも増す?
Activation function
Sigmoid関数の傾きは最大で0.25である。したがって、層が深くなるにつれて浅い層に伝搬される誤差は非常に小さい値になる。これによって学習が進まなくなる。ReLu関数は$x>$0の領域で必ず傾きが1になるためSigmoid関数がもつ問題は解消される。
Unit value distribution per layer
各層(Affine, BatchNorm, Activation)のユニットが取る値の分布をS4およびS6_BN_lrモデルについて調べた。下図に順伝搬における出力の分布を示す。 S4モデルでは深い層ほどユニットが取る値が狭まってきている。一方、S6_BN_lrモデルでは層が深くなってもActivationのユニットが取る値は広がりを持っている。正規化によって関数の表現力が落ちていないことが確認できた。
- S4モデル(上:Affine, 下:Activation)
- S6_BN_lrモデル(上:Affine, 中央:BatchNorm, 下:Activation)
逆伝搬における出力の分布についても同様に調べた(下図)。S4モデルでは層が浅くなるにつれ伝搬される誤差が小さくなっていき、学習が進まない様子が確認できた。一方、S6_BN_lrモデルでは浅い層まで誤差が伝搬されていることがわかった。
- S4モデル(上:Activation, 下:Affine)
- S6_BN_lrモデル(上:Activation, 中央:BatchNorm, 下:Affine)
参考文献
[1] Frederik Kratzert “Understanding the backward pass through Batch Normalization Layer”
[2] 斎藤 康毅 「ゼロから作るDeep Learning」第6章, O'Reilly Japan, 2016.