CNNの学習に最高の性能を示す最適化手法はどれか
Adam, AdaGrad, AdaDelta, RMSpropGraves, SGD, MomentumSGDなど数ある最適化手法の中で、畳み込みニューラルネットワーク(CNN:Convolutional Neural Network)の学習には、どのOptimizerをつかうのが最も適しているのかということを実験しました。
最適化とは
最適化 (Optimization) とは、関数などを最適な状態に近づけることをいいます。ディープラーニングにおける最適化とは、モデルの予測値と実際の値との誤差から、パラメータ(重み)を更新することです。その最適化手法は以下のように様々なものがあります。パラメータを更新する際の数式がそれぞれ異なっています。
SGDとは
SGDとはStochastic Gradient Descentの略で日本語では確率的勾配降下法といいます。最も基本的なアルゴリズムです。確率的勾配降下法は,訓練データから1つずつデータをランダムに取り出して,そのデータ点における勾配を計算してパラメータを更新します。詳しくはこちらを参照。
MomentumSGDとは
MomentumSGDはSGDに慣性項を付与したものです。慣性項(Momentum:モメンタム)とは勾配降下法の式を拡張して、より素早く収束するように工夫するためのパラメータとして存在しています。
AdaGradとは
RMSpropGravesとは
NesterovAGとは
実験環境
使用ライブラリ | Chainer |
---|---|
データセット | MNIST手書き文字認識 |
パラメータ | デフォルト値 |
モデル | 5層CNN |
ミニバッチ数 | 100 |
評価手法 | 10分割交差検証 |
エポック数 | 300 |
実験環境は上記のようになっており、各最適化手法のパラメータはChainerデフォルトのものを利用しました。多分論文推奨の値にしてくれてるんじゃないかとおもう。評価手法として10分割-交差検証(クロスバリデーション)を利用することでモデルがどの程度安定して学習できるかを精密に計測できます。
実験結果
以下のグラフが実験の結果となります。
分散があってみにくいので、ガウシアンフィルタで平均化したものがこちら。
最大値のみをグラフにしたものがこちらになります。
結果を精度の順にならべると、
1 | RMSpropGraves | 0.9930 |
2 | MomentumSGD | 0.9928 |
3 | AdaDelta | 0.9927 |
4 | NesterovAG | 0.9927 |
5 | Adam | 0.9925 |
6 | SGD | 0.9919 |
7 | AdaGrad | 0.9875 |
となり、RMSpropGravesが5層CNNのMNIST認識における最も優れた最適化手法らしいということがわかりました。
ニューラルネットのモデルや、データセットの難しさに左右されることも考えられるので、このへんはまた今度検証してみたいとおもいます。
標準偏差の値もグラフ化したので載せておきます。
これを見ると、最も安定しているのはAdamということがわかります。
ソースコード
参考までに今回使ったソースコードを載せておきます。
レポジトリ: https://github.com/ttlg/cnn-lab
import numpy as np import chainer from chainer import cuda, Function, gradient_check, Variable, optimizers, serializers, utils from chainer import Link, Chain, ChainList import chainer.functions as F import chainer.links as L from sklearn.datasets import fetch_mldata xp = cuda.cupy if cuda.available else np class CNN(Chain): def __init__(self, channel=1, c1=16, c2=32, c3=64, f1=256, f2=512, filter_size1=3, filter_size2=3, filter_size3=3): super(CNN, self).__init__( conv1=L.Convolution2D(channel, c1, filter_size1), conv2=L.Convolution2D(c1, c2, filter_size2), conv3=L.Convolution2D(c2, c3, filter_size3), l1=L.Linear(f1, f2), l2=L.Linear(f2, 10) ) if cuda.available: cuda.get_device(0).use() self.to_gpu() def forward(self, x, train): h = F.relu(self.conv1(x)) h = F.max_pooling_2d(h, 2) h = F.relu(self.conv2(h)) h = F.max_pooling_2d(h, 2) h = F.relu(self.conv3(h)) h = F.max_pooling_2d(h, 2) h = F.dropout(F.relu(self.l1(h)), train=train) y = self.l2(h) return y def __call__(self, x, t, train=True): x = Variable(xp.asarray(x)) t = Variable(xp.asarray(t)) y = self.forward(x, train=train) loss = F.softmax_cross_entropy(y, t) self.loss = loss.data self.accuracy = F.accuracy(y, t).data return loss def __str__(self): return 'cnn' class MNIST: def __init__(self): self.mnist = fetch_mldata('MNIST original', data_home=".") np.random.seed(1) @staticmethod def unison_shuffle(a, b): assert len(a) == len(b) p = np.random.permutation(len(a)) return a[p], b[p] def _fold_i_split(self, n, i, x, t): cls_num = 10 test_size = len(t)/n cls_test_size = test_size/cls_num cls_size = len(t)/cls_num x_train = x_test = t_train = t_test = None for j in range(cls_num): cls_head = cls_size*j cls_tail = cls_size*(j+1) head_idx = cls_head + cls_test_size*i tail_idx = cls_head + cls_test_size*(i+1) if j == 0: x_test = x[head_idx:tail_idx] t_test = t[head_idx:tail_idx] x_train = np.concatenate((x[cls_head:head_idx], x[tail_idx:cls_tail])) t_train = np.concatenate((t[cls_head:head_idx], t[tail_idx:cls_tail])) else: x_test = np.concatenate((x_test, x[head_idx:tail_idx])) t_test = np.concatenate((t_test, t[head_idx:tail_idx])) x_train = np.concatenate((x_train, x[cls_head:head_idx], x[tail_idx:cls_tail])) t_train = np.concatenate((t_train, t[cls_head:head_idx], t[tail_idx:cls_tail])) x_test, t_test = self.unison_shuffle(x_test, t_test) x_train, t_train = self.unison_shuffle(x_train, t_train) x_train = x_train.reshape((len(x_train), 1, 28, 28)) x_test = x_test.reshape((len(x_test), 1, 28, 28)) return x_train, x_test, t_train, t_test def get_fold_i(self, n, i): x = self.mnist.data t = self.mnist.target x = x.astype(np.float32) t = t.astype(np.int32) x /= x.max() return self._fold_i_split(n, i, x, t) def validate_model(model, optimizer, x_train, x_test, t_train, t_test, batch, n_epoch): train_size = t_train.size test_size = t_test.size print train_size, test_size optimizer.setup(model) sum_accuracies = [] sum_accuracies_train = [] sum_losses = [] for epoch in range(1, n_epoch + 1): perm = np.random.permutation(train_size) sum_loss = 0 sum_accuracy_train = 0 for i in range(0, train_size, batch): x_batch = x_train[perm[i:i + batch]] y_batch = t_train[perm[i:i + batch]] optimizer.update(model, x_batch, y_batch) sum_loss += float(model.loss) * len(y_batch) sum_accuracy_train += float(model.accuracy) * len(y_batch) sum_losses.append(sum_loss / train_size) sum_accuracies_train.append(sum_accuracy_train/train_size) print 'loss:'+str(sum_loss/train_size) sum_accuracy = 0 for i in range(0, test_size, batch): x_batch = x_test[i:i + batch] y_batch = t_test[i:i + batch] model(x_batch, y_batch, train=False) sum_accuracy += float(model.accuracy) * len(y_batch) print 'accuracy:'+str(sum_accuracy/test_size) sum_accuracies.append(sum_accuracy / test_size) print "train mean loss: %f" % (sum_losses[n_epoch-1]) print "test accuracy: %f" % (sum_accuracies[n_epoch-1]) return sum_losses, sum_accuracies_train, sum_accuracies def k_fold_validation(k, model, optimizer=optimizers.Adam(), tag='', batch=100, n_epoch=300): loss = [] acc = [] acc_train = [] mnist = MNIST() print str(model) for i in range(k): x_train, x_test, t_train, t_test = mnist.get_fold_i(k, i) print 'fold:' + str(i) l, at, a = validate_model(copy.deepcopy(model), copy.deepcopy(optimizer), x_train, x_test, t_train, t_test, batch, n_epoch) loss.append(l) acc.append(a) acc_train.append(at) save_result('results/'+str(model)+'_'+tag+'_loss.txt', loss) save_result('results/'+str(model)+'_'+tag+'_accuracy.txt', acc) save_result('results/'+str(model)+'_'+tag+'_accuracy_train.txt', acc_train) def which_is_best_optimizer(k=10, model=CNN()): k_fold_validation(k, copy.deepcopy(model), optimizer=optimizers.Adam(), tag='Adam') k_fold_validation(k, copy.deepcopy(model), optimizer=optimizers.SGD(), tag='SGD') k_fold_validation(k, copy.deepcopy(model), optimizer=optimizers.RMSpropGraves(), tag='RMSpropGraves') k_fold_validation(k, copy.deepcopy(model), optimizer=optimizers.RMSprop(), tag='RMSprop') k_fold_validation(k, copy.deepcopy(model), optimizer=optimizers.AdaDelta(), tag='AdaDelta') k_fold_validation(k, copy.deepcopy(model), optimizer=optimizers.AdaGrad(), tag='AdaGrad') k_fold_validation(k, copy.deepcopy(model), optimizer=optimizers.MomentumSGD(), tag='MomentumSGD') k_fold_validation(k, copy.deepcopy(model), optimizer=optimizers.NesterovAG(), tag='NesterovAG') if __name__ == '__main__': which_is_best_optimizer(model=CNN())