バッチ規格化とバッチ再規格化の比較

ニューラルネットワークが複雑になり学習がうまくいかない場合にはバッチ規格化を行うといいです。
バッチ規格化を使うと途中の結果を整理しながら学習をすることができます。
またバッチ再規格化というものがあり、バッチ規格よりもよい性能を示すとのことなのでこの2つを比較してみます。

まずchainerよりMNISTデータを読み込みます。

1
2
3
4
import chainer.datasets as ds

# MNISTデータの読み込み
train, test = ds.get_mnist()

結果

次に読み込んだデータを訓練データとテストデータに分けます。
chainerにはconverくらすにconcat_examplesというメソッドがあり容易にデータの振り分けを行うことができます。

1
2
3
4
5
6
7
8
# 入力データとラベルデータの設定
import chainer.dataset.convert as con
# 訓練データとテストデータ分ける
xtrain, ttrain = con.concat_examples(train)
xtest, ttest = con.concat_examples(test)

print(xtrain.shape, ttrain.shape)
print(xtest.shape, ttest.shape)

結果

振り分けたデータの1つをmatplotlibを使って表示してみます。

1
2
3
4
5
6
7
8
import matplotlib.pyplot as plt

# データの形とMNIST originalの1列を表示する
Dtrain, N = xtrain.shape
plt.imshow(xtrain[0,:].reshape(28, 28))
plt.show()

print('ラベル(答え)=>', ttrain[0])

結果

本題のバッチ規格化を設定します。
まずニューラルネットワークを作成する際にbnorm1=L.BatchNormalization(400)(9行目)を引数に渡し、またニューラルネットワークの関数でNN.bnorm1関数(15行目)をはさみます。
バッチ再規格をする場合は、BatchNormalizationの代わりにBatchRenormalization(10行目)を指定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import chainer.optimizers as Opt
import chainer.functions as F
import chainer.links as L
from chainer import Variable, Chain, config

# バッチ規格化(途中の結果を整理整頓しながら学習する)
C = ttrain.max() + 1
NN = Chain(l1=L.Linear(N, 400), l2=L.Linear(400, C),
bnorm1=L.BatchNormalization(400))
# bnorm1=L.BatchRenormalization(400)) #バッチの再規格化(Chainer version4以降)

def model(x):
h = NN.l1(x)
h = F.relu(h)
h = NN.bnorm1(h) # バッチの規格化(途中結果の整理)
h = NN.l2(h)
return h

次に学習用関数を定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 学習用関数
def learning(model, optNN, data, T=50):
# 学習記録用エリア(返値)
train_loss = []
train_acc = []
test_loss = []
test_acc = []

for time in range(T):
# 学習
config.train = True
optNN.target.zerograds()
ytrain = model(data[0])
loss_train = F.softmax_cross_entropy(ytrain, data[2])
acc_train = F.accuracy(ytrain, data[2])
loss_train.backward()
optNN.update()

# テスト(検証)
config.train = False
ytest = model(data[1])
loss_test = F.softmax_cross_entropy(ytest, data[3])
acc_test = F.accuracy(ytest, data[3])

# 結果の記録
train_loss.append(loss_train.data)
train_acc.append(acc_train.data)
test_loss.append(loss_test.data)
test_acc.append(acc_test.data)
return train_loss, test_loss, train_acc, test_acc

グラフ表示用関数を定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# グラフ表示関数
def show_graph(result1, result2, title, xlabel, ylabel, ymin=0.0, ymax=1.0):
# 学習記録の表示(誤差)
Tall = len(result1)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), result1, label='train')
plt.plot(range(Tall), result2, label='test')
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.xlim([0, Tall])
plt.ylim(ymin, ymax)
plt.legend()
plt.show()

最適化手法を設定し、学習を実行したのち結果をグラフ表示します。

1
2
3
4
5
6
7
8
9
10
# 最適化手法の設定
optNN = Opt.MomentumSGD()
optNN.setup(NN)

# 学習を実行
train_loss, test_loss, train_acc, test_acc = learning(model, optNN, [xtrain, xtest, ttrain, ttest])

# 学習結果をグラフ表示
show_graph(train_loss, test_loss, 'loss function', 'step', 'lossfunction', 0.0, 4.0)
show_graph(train_acc, test_acc, 'accuracy', 'step', 'accuracy')

バッチ規格化とバッチ再規格化をそれぞれ実行した結果は下記の通りです。

バッチ規格化 バッチ再規格化
誤差 誤差
正解率 正解率

ほぼ同じ結果で明確な違いがないような気がします。。。

(Google Colaboratoryで動作確認しています。)