ニューラルネットワークの最適化方法を比較

いくつかある最適化方法のうち下記の3つを比較します。

  • SGD
  • MomentumSGD
  • Adam

まずは必要なモジュールをインポートします。

1
2
3
4
5
6
7
8
import chainer.optimizers as Opt
import chainer.functions as F
import chainer.links as L
from chainer import Variable, Chain, config

import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds

アヤメデータを読み込み、訓練データと学習データに分けます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# アヤメデータを読み込む
Iris = ds.load_iris()
xdata = Iris.data.astype(np.float32)
tdata = Iris.target.astype(np.int32)

D, N = xdata.shape # D=150, N=4

# 訓練データとテストデータに分ける
Dtrain = D // 2
index = np.random.permutation(range(D))
xtrain = xdata[index[0:Dtrain],:]
ttrain = tdata[index[0:Dtrain]]
xtest = xdata[index[Dtrain:D],:]
ttest = tdata[index[Dtrain:D]]

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

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
# グラフ表示関数
def show_graph():
# 学習記録の表示(誤差)
Tall = len(train_loss)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), train_loss, label='train_loss')
plt.plot(range(Tall), test_loss, label='test_loss')
plt.title('loss function in training and test')
plt.xlabel('step')
plt.ylabel('loss function')
plt.xlim([0, Tall])
plt.ylim(0, 4)
plt.legend()
plt.show()

# 学習記録の表示(正解率)
plt.figure(figsize=(8, 6))
plt.plot(range(Tall), train_acc, label='train_acc')
plt.plot(range(Tall), test_acc, label='test_acc')
plt.title('accuracy in training and test')
plt.xlabel('step')
plt.ylabel('accuracy')
plt.xlim([0, Tall])
plt.ylim(0, 1.0)
plt.legend()
plt.show()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 学習用関数
def training(T):
for time in range(T):
config.train = True
optNN.target.zerograds()
ytrain = model(xtrain)
loss_train = F.softmax_cross_entropy(ytrain, ttrain)
acc_train = F.accuracy(ytrain, ttrain)
loss_train.backward()
optNN.update()

config.train = False
ytest = model(xtest)
loss_test = F.softmax_cross_entropy(ytest, ttest)
acc_test = F.accuracy(ytest, ttest)

train_loss.append(loss_train.data)
train_acc.append(acc_train.data)
test_loss.append(loss_test.data)
test_acc.append(acc_test.data)

最後に最適化方法を変えながら学習・結果表示を行います。

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
31
32
# 最適化方法を変えながら学習・結果表示を行う
for optNN in [Opt.SGD(), Opt.MomentumSGD(), Opt.Adam()]:
# 3層のニューラルネットワークを作成
C = np.max(tdata) + 1
NN = Chain(l1=L.Linear(N, 3), l2=L.Linear(3, 3), l3=L.Linear(3, C))

# 3層ニューラルネットワークの関数化
def model(x):
h = NN.l1(x)
h = F.sigmoid(h)
#h = F.relu(h)
h = NN.l2(h)
h = F.sigmoid(h)
#h = F.relu(h)
y = NN.l3(h)
return y

# 最適化手法を設定
# optNN = Opt.SGD()
# optNN = Opt.MomentumSGD()
# optNN = Opt.Adam()
optNN.setup(NN)

# 学習の記録用
train_loss = []
train_acc = []
test_loss = []
test_acc = []

training(3000) # 引数は学習回数

show_graph() # グラフ表示

実行結果は比較しやすいように表形式にまとめました。

SGD MomentumSGD Adam
初歩的な最適化 勢いをつけた最適化 適応的モーメント勾配法
誤差 誤差 誤差
正解率 正解率 正解率

(適応的勾配法は、勾配の大きさが小さくなってくると勾配に従って調整する大きさを多くします。)

非線形関数にReLUを使用すると実行結果が安定しなかった(すぐ最適化されたりまったくされなかったり・・・)のでシグモイドを使用しています。

今回のケースではAdamが一番はやく正解率が安定しています。
最適化方法によってだいぶ結果が変わるので、データやケースによって最適化方法をいろいろ試すしかなさそうです。。。

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