6.3 Batch Normalization

「ゼロから作るDeep Learning」 6章3節の内容を学習しながら、IvoryライブラリでBatch Normalizationの評価を行います。「ゼロから作るDeep Learning」では、次節から学習の効率化のためにTrainerクラスを導入しますが、ここでは一足先に活用していきます。

6.3.1 Batch Normalizationの評価

「ゼロから作るDeep Learning」のサンプルコードを参考にしながらIvoryライブラリでの評価を行います。「拡張版の全結合による多層ニューラルネットワーク」はcommon/multi_layer_net_extend.pyの中のMultiLayerNetExtendクラスで定義されています。また、実際の訓練は、ch06/batch_norm_test.pyで実行されます。

データセットを用意します。

from ivory.datasets.mnist import load_dataset

data = load_dataset(train_only=True)
data.length = 1000  # データセットの大きさを制限します。
data.batch_size = 100
data.epochs = 20
data.random = True
data

[1] 2019-06-12 17:36:38 (1.92s) python3 (1.92s)

mnist_train(batch_size=100, epochs=20, len=10, column=0, size=(1000,))

Batch Normalizationレイヤの有無を指定して、ネットワークを作り分ける関数を定義します。

def create_net(batch_norm):
    bn = ("batch_normalization",) if batch_norm else ()
    return [
        ("input", 784),
        (5, "affine", 100, *bn, "relu"),
        ("affine", 10, "softmax_cross_entropy"),
    ]

net = create_net(True)
net

[2] 2019-06-12 17:36:40 (31.3ms) python3 (1.95s)

[('input', 784),
 (5, 'affine', 100, 'batch_normalization', 'relu'),
 ('affine', 10, 'softmax_cross_entropy')]

次に導入するTrainerクラスは上述のようなレイヤ表現を引数にとるivory.common.trainerモジュールのsequential関数で作成できます。ちょうどLayerインスタンスのリストをivory.common.layerモジュールのsequential関数で作成していたのと同じです。

from ivory.core.trainer import sequential

trainer = sequential(net)
print(trainer.model.losses)
print(trainer.optimizer)

[3] 2019-06-12 17:36:40 (31.2ms) python3 (1.98s)

[<Loss('SoftmaxCrossEntropy.1.loss', ()) at 0x21a10f25b38>]
SGD(learning_rate=0.01, name='SGD')

Trainerインスタンスのレイヤパラメータを初期化するには、initメソッドを呼び出します。オプショナルのstdキーワード引数を指定すると、標準偏差を設定できます。

W = trainer.model.weights[0]
print(W.d.std())
trainer.init(std=100)
print(W.d.std())
trainer.init(std="he")
print(W.d.std())

[4] 2019-06-12 17:36:40 (31.3ms) python3 (2.02s)

0.050397325
100.40856
0.050782025

BatchNormalizationレイヤのtrain状態を見てみます。

bn = trainer.model.layers[1]
bn.train.d

[5] 2019-06-12 17:36:40 (15.7ms) python3 (2.03s)

True

Falseにした後初期化します。

bn.train.d = False
trainer.init(std="he")
bn.train.d

[6] 2019-06-12 17:36:40 (31.2ms) python3 (2.06s)

True

Trueに戻っています。

実際に学習してみます。initメソッドは自分自身を返すので、呼び出しをチェインできます。fitメソッドも同様に訓練データの設定をした後、自分自身を返します。

net = create_net(True)
trainer = sequential(net, metrics=["accuracy"]).init(std=0.1)
trainer = trainer.fit(data, epoch_data=data[:])
trainer

[7] 2019-06-12 17:36:40 (46.8ms) python3 (2.11s)

Trainer(inputs=[(784,), ()], optimizer='SGD', metrics=['accuracy'])

実際の訓練はイタレータを作って行います。

it = iter(trainer)
print(next(it))
print(next(it))
print(next(it))

[8] 2019-06-12 17:36:40 (188ms) python3 (2.30s)

(0, 0.111)
(1, 0.146)
(2, 0.24)

to_frameメソッドは訓練を行った後に結果をデータフレームで返します。

df = trainer.to_frame()
df.tail()

[9] 2019-06-12 17:36:41 (1.47s) python3 (3.77s)

epoch accuracy
16 16 0.861
17 17 0.868
18 18 0.881
19 19 0.886
20 20 0.890

重みの初期値の標準偏差、および、Batch Normalizationレイヤの有無をモデルのハイパーパラメータとします。これらをマトリックスにして学習の実験を行います。これには、ivory.core.trainerモジュールのproductメソッドを使います。productメソッドは、標準モジュールitertoolsproduct関数のように動作します。第1引数は、第2引数以降を受け取ってTrainerインスタンスを返す関数です。戻り値はProductクラスのインスタンスであり、通常のTrainerインスタンスと同様にfitメソッドを持ちます。

import numpy as np
from ivory.core.trainer import product

def trainer(std, batch_norm):
    net = create_net(batch_norm)
    return sequential(net, metrics=["accuracy"]).init(std=std)

stds = np.logspace(0, -4, num=16)
prod = product(trainer, stds, [True, False])
prod = prod.fit(data, epoch_data=data[:])
prod

[10] 2019-06-12 17:36:42 (31.3ms) python3 (3.80s)

Product(iterables=(<16>, <2>))
it = iter(prod)
print(next(it))
print(next(it))
print(next(it))

[11] 2019-06-12 17:36:42 (203ms) python3 (4.00s)

(1.0, True, 0, 0.159)
(1.0, True, 1, 0.159)
(1.0, True, 2, 0.164)

訓練を実行し、結果をデータフレームで返します。

df = prod.to_frame(columns=["std", "bn"])
df.tail()

[12] 2019-06-12 17:36:42 (36.5s) python3 (40.5s)

C:\Users\daizu\Documents\GitHub\ivory\ivory\layers\affine.py:11: RuntimeWarning: overflow encountered in matmul
  self.y.d = self.x.d @ self.W.d + self.b.d
C:\Users\daizu\Documents\GitHub\ivory\ivory\layers\affine.py:11: RuntimeWarning: invalid value encountered in matmul
  self.y.d = self.x.d @ self.W.d + self.b.d
C:\Users\daizu\Documents\GitHub\ivory\ivory\layers\activation.py:19: RuntimeWarning: invalid value encountered in less_equal
  self.mask = self.x.d <= 0
C:\Users\daizu\Documents\GitHub\ivory\ivory\layers\affine.py:16: RuntimeWarning: invalid value encountered in matmul
  self.W.g = self.x.d.T @ self.y.g + self.weight_decay.d * self.W.d
std bn epoch accuracy
667 0.0001 False 16 0.117
668 0.0001 False 17 0.117
669 0.0001 False 18 0.117
670 0.0001 False 19 0.117
671 0.0001 False 20 0.117

可視化します。

import altair as alt

def plot(std, df):
    y = alt.Y("accuracy", scale=alt.Scale(domain=[0, 1]))
    return (
        alt.Chart(df)
        .mark_line()
        .encode(x="epoch", y=y, color="bn")
        .properties(width=80, height=80, title=f"std={std:.05f}")
    )

charts = [plot(*x) for x in df.groupby("std")][::-1]
alt.vconcat(*(alt.hconcat(*charts[k : k + 4]) for k in range(0, 16, 4)))

[13] 2019-06-12 17:37:19 (297ms) python3 (40.8s)