chainer1.9.1のMNISTのサンプルコードに、回帰およびCNNのネットワーク構造を追加し、引数で切り替えられるようにしてみた。
現時点での最新のChainerのバージョンは、1.12.0で、MNISTのサンプルコードは大分変っている。Chainer1.9.1は下記からダウンロードできる。
https://github.com/pfnet/chainer/releases
変更を加えたのは、net.pyとtrain_mnist.pyである。
まず、net.pyだが、下記のようにMnistRegressionとMnistCNNクラスを追加した。
MnistCNNに関しては、下記のブログを参考にした。
http://bunji2.hatenablog.com/entry/2016/05/22/213114
import chainer import chainer.functions as F import chainer.links as L class MnistMLP(chainer.Chain): """An example of multi-layer perceptron for MNIST dataset. This is a very simple implementation of an MLP. You can modify this code to build your own neural net. """ def __init__(self, n_in, n_units, n_out=10): super(MnistMLP, self).__init__( l1=L.Linear(n_in, n_units), l2=L.Linear(n_units, n_units), l3=L.Linear(n_units, n_out) ) def __call__(self, x): h1 = F.relu(self.l1(x)) h2 = F.relu(self.l2(h1)) return self.l3(h2) class MnistRegression(chainer.Chain): """An example of regression for MNIST dataset. """ def __init__(self, n_in, n_units, n_out=1): super(MnistRegression, self).__init__( l1=L.Linear(n_in, n_units), l2=L.Linear(n_units, n_units), l3=L.Linear(n_units, n_out) ) def __call__(self, x): h1 = F.dropout(F.relu(self.l1(x))) h2 = F.dropout(F.relu(self.l2(h1))) return self.l3(h2) class MnistCNN(chainer.Chain): """An example of convolutional neural network for MNIST dataset. """ 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(MnistCNN, 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) ) def __call__(self, x): # param x --- chainer.Variable of array x.data = x.data.reshape((len(x.data), 1, 28, 28)) 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))) y = self.l2(h) return y class MnistMLPParallel(chainer.Chain): """An example of model-parallel MLP. This chain combines four small MLPs on two different devices. """ def __init__(self, n_in, n_units, n_out): super(MnistMLPParallel, self).__init__( first0=MnistMLP(n_in, n_units // 2, n_units).to_gpu(0), first1=MnistMLP(n_in, n_units // 2, n_units).to_gpu(1), second0=MnistMLP(n_units, n_units // 2, n_out).to_gpu(0), second1=MnistMLP(n_units, n_units // 2, n_out).to_gpu(1), ) def __call__(self, x): # assume x is on GPU 0 x1 = F.copy(x, 1) z0 = self.first0(x) z1 = self.first1(x1) # sync h0 = z0 + F.copy(z1, 0) h1 = z1 + F.copy(z0, 1) y0 = self.second0(F.relu(h0)) y1 = self.second1(F.relu(h1)) # sync y = y0 + F.copy(y1, 0) return y
MnistRegressionは、MnistMLPと同じ3層のmulti-layer perceptron(MLP)であるが、出力層のユニット数がデフォルトで1つに設定されているところが異なる。また後述するが、loss関数がmean_squared_errorという点においても異なる。
一方、MnistCNNは、conv層が3層あり、その後full connected層が2層続いている。conv層の入力は画像なので、入力ベクトルx.dataを28x28の行列に変換している。
x.data = x.data.reshape((len(x.data), 1, 28, 28))
次に、train_mnist.pyは下記のように変更している。
#!/usr/bin/env python """Chainer example: train a multi-layer perceptron on MNIST This is a minimal example to write a feed-forward net. """ from __future__ import print_function import argparse import time import numpy as np import six import csv import chainer from chainer import computational_graph from chainer import cuda import chainer.links as L from chainer import optimizers from chainer import serializers import chainer.functions as F import data import net #--------------------------- # Setup parameters parser = argparse.ArgumentParser(description='Chainer example: MNIST') parser.add_argument('--initmodel', '-m', default='', help='Initialize the model from given file') parser.add_argument('--resume', '-r', default='', help='Resume the optimization from snapshot') parser.add_argument('--net', '-n', choices=('MLP', 'CNN', 'Regression', 'Parallel'), default='regression', help='Network type') parser.add_argument('--optimizer', '-o', choices=('SGD', 'MomentumSGD', 'NesterovAG', 'Adagrad','Adadelta','RMSprop','Adam'), default='SGD', help='optimization type') parser.add_argument('--gpu', '-g', default=-1, type=int, help='GPU ID (negative value indicates CPU)') parser.add_argument('--epoch', '-e', default=20, type=int, help='number of epochs to learn') parser.add_argument('--unit', '-u', default=1000, type=int, help='number of units') parser.add_argument('--batchsize', '-b', type=int, default=100, help='learning minibatch size') args = parser.parse_args() batchsize = args.batchsize n_epoch = args.epoch n_units = args.unit N = 60000 print('GPU: {}'.format(args.gpu)) print('# unit: {}'.format(args.unit)) print('# Minibatch-size: {}'.format(args.batchsize)) print('# epoch: {}'.format(args.epoch)) print('Network type: {}'.format(args.net)) print('') #--------------------------- #--------------------------- # Prepare dataset print('load MNIST dataset') mnist = data.load_mnist_data() mnist['data'] = mnist['data'].astype(np.float32) mnist['data'] /= 255 mnist['target'] = mnist['target'].astype(np.int32) #--------------------------- #--------------------------- # Prepare network defined in net.py if args.net == 'MLP': model = L.Classifier(net.MnistMLP(784, n_units, 10)) elif args.net == 'CNN': model = L.Classifier(net.MnistCNN()) elif args.net == 'Regression': mnist['target'] = mnist['target'].astype(np.float32).reshape(len(mnist['target']), 1) model = L.Classifier(net.MnistRegression(784,n_units,1),lossfun=F.mean_squared_error) model.compute_accuracy = False elif args.net == 'Parallel': cuda.check_cuda_available() model = L.Classifier(net.MnistMLPParallel(784, n_units, 10)) xp = cuda.cupy #--------------------------- #--------------------------- # Split data to train and test x_train, x_test = np.split(mnist['data'], [N]) y_train, y_test = np.split(mnist['target'], [N]) N_test = y_test.size #--------------------------- #--------------------------- # Setup gpu if args.net != 'parallel': if args.gpu >= 0: cuda.get_device(args.gpu).use() model.to_gpu() xp = np if args.gpu < 0 else cuda.cupy #--------------------------- #--------------------------- # Setup optimizer if args.optimizer == 'SGD': optimizer = optimizers.SGD() elif args.optimizer == 'MomentumSGD': optimizer = optimizers.MomentumSGD elif args.optimizer == 'NesterovAG': optimizer = optimizers.NesterovAG() elif args.optimizer == 'Adagrad': optimizer = optimizers.AdaGrad() elif args.optimizer == 'Adadelta': optimizer = optimizers.AdaDelta() elif args.optimizer == 'RMSprop': optimizer = optimizers.RMSprop() elif args.optimizer == 'Adam': optimizer = optimizers.Adam() optimizer.setup(model) #--------------------------- #--------------------------- # Init/Resume if args.initmodel: print('Load model from', args.initmodel) serializers.load_npz(args.initmodel, model) if args.resume: print('Load optimizer state from', args.resume) serializers.load_npz(args.resume, optimizer) #--------------------------- #--------------------------- # Training loop with epoch test_loss_accuracy=[] for epoch in six.moves.range(1, n_epoch + 1): print('epoch', epoch) # Training Loop with minibatch perm = np.random.permutation(N) sum_accuracy = 0 sum_loss = 0 start = time.time() for i in six.moves.range(0, N, batchsize): x = chainer.Variable(xp.asarray(x_train[perm[i:i + batchsize]])) t = chainer.Variable(xp.asarray(y_train[perm[i:i + batchsize]])) # Pass the loss function (Classifier defines it) and its arguments optimizer.update(model, x, t) #--------------------------- # Save network architecture if epoch == 1 and i == 0: with open('graph.dot', 'w') as o: variable_style = {'shape': 'octagon', 'fillcolor': '#E0E0E0', 'style': 'filled'} function_style = {'shape': 'record', 'fillcolor': '#6495ED', 'style': 'filled'} g = computational_graph.build_computational_graph( (model.loss, ), variable_style=variable_style, function_style=function_style) o.write(g.dump()) print('graph generated') #--------------------------- #--------------------------- # Compute loss and accuracy sum_loss += float(model.loss.data) * len(t.data) if (args.net == 'Regression'): sum_accuracy += float(np.sum(model.y.data.astype(np.int32) == t.data.astype(np.int32))) else: sum_accuracy += float(model.accuracy.data) * len(t.data) #--------------------------- #--------------------------- # Compute throughput end = time.time() elapsed_time = end - start throughput = N / elapsed_time #--------------------------- #--------------------------- # Printout print('train mean loss={}, accuracy={}, throughput={} images/sec'.format( sum_loss / N, sum_accuracy / N, throughput)) #--------------------------- #--------------------------- # Test loss and accuracy sum_accuracy = 0 sum_loss = 0 # Test Loop with minibatch for i in six.moves.range(0, N_test, batchsize): x = chainer.Variable(xp.asarray(x_test[i:i + batchsize]), volatile='on') t = chainer.Variable(xp.asarray(y_test[i:i + batchsize]), volatile='on') #--------------------- # Compute loss and accuracy loss = model(x, t) sum_loss += float(loss.data) * len(t.data) if (args.net == 'Regression'): accuracy = float(np.sum(model.y.data.astype(np.int32) == t.data.astype(np.int32))) else: accuracy = float(model.accuracy.data) * len(t.data) sum_accuracy += accuracy #--------------------- #--------------------- # Printout print('test mean loss={}, accuracy={}'.format( sum_loss / N_test, sum_accuracy / N_test)) #--------------------------- #--------------------------- # Record test loss and accuracy test_loss_accuracy.append([sum_loss/N_test,sum_accuracy/N_test]) #--------------------------- #--------------------------- # Save model, optimizer, loss and accuracy print('save the model') fname = args.net + '_' + args.optimizer + '.model' serializers.save_npz(fname, model) print('save the optimizer') fname = args.net + '_' + args.optimizer + '.state' serializers.save_npz(fname, optimizer) print('save the losses and accuracies') fname = args.net + '_' + args.optimizer + '_loss_accuracy.csv' f=open(fname,'w') writer=csv.writer(f,lineterminator='\n') writer.writerow(['loss','accuracy']) writer.writerows(test_loss_accuracy) f.close() #---------------------------
このtrain_mnist.pyでは、引数netにてMLP、CNNおよびRegressionを切り替えできるようになっている。また、引数optimizerにて、最適化の手法を、SGD, MomentumSGD, NesterovAG, Adagrad, Adadelta, RMSprop, およびAdamから選択することができる。
例えば、ネットワーク構造をMLP、最適化手法をSGDに設定して、GPUを用いて学習するためには、下記のように実行する。
> python train_mnist.py --gpu 0 --net=MLP --optimizer=SGD GPU: 0 # unit: 1000 # Minibatch-size: 100 # epoch: 20 Network type: MLP load MNIST dataset epoch 1 graph generated train mean loss=1.08769297053, accuracy=0.770116665624, throughput=3148.61461052 images/sec
また、ネットワークとしてregressionが選択された場合は、loss関数として、mean_squared_errorを用いて、教師ラベルが連続値をとるので、mnist['target']をnp.int32からnp.float32に変更し、さらには縦ベクトルに変更している。また、分類問題ではないのでデフォルトのaccuracyの計算ではエラーが出るので、compute_accuracyをFalseに設定してオフにしている。
mnist['target'] = mnist['target'].astype(np.float32).reshape(len(mnist['target']), 1) model = L.Classifier(net.MnistRegression(784,n_units,1),lossfun=F.mean_squared_error) model.compute_accuracy = False
ただし、他の分類問題とaccuracyを比較したいので、regressionの場合でも下記のように予測値yを四捨五入し真値tと比較して強引にaccuracyを計算している。
if (args.net == 'regression'): accuracy = float(np.sum(model.y.data.astype(np.int32) == t.data.astype(np.int32))) else: accuracy = float(model.accuracy.data) * len(t.data)
一方、MLPおよびCNNでは、lossfunとして、chainer.links.Classifierのデフォルトのsoftmax_cross_entropyが用いられている。
model = L.Classifier(net.MnistMLP(784, n_units, 10)) model = L.Classifier(net.MnistCNN())
最後に、Testのlossとaccuracyの値を、後々プロットすることを考えて、test_loss_accuracyという変数にappendで格納し、csvファイルに保存している。ファイル名は、「引数のnet名_引数のoptimizer名_loss_accuracy.csv」である。
#--------------------------- # Record test loss and accuracy test_loss_accuracy.append([sum_loss/N_test,sum_accuracy/N_test]) #--------------------------- #--------------------------- # Save model, optimizer, loss and accuracy print('save the model') fname = args.net + '_' + args.optimizer + '.model' serializers.save_npz(fname, model) print('save the optimizer') fname = args.net + '_' + args.optimizer + '.state' serializers.save_npz(fname, optimizer) print('save the losses and accuracies') fname = args.net + '_' + args.optimizer + '_loss_accuracy.csv' f=open(fname,'w') writer=csv.writer(f,lineterminator='\n') writer.writerow(['loss','accuracy']) writer.writerows(test_loss_accuracy) f.close() #---------------------------
下記のようなバッチファイル(run.bat)を作成して、コマンドプロンプトで実行すると、全組み合わせを一括で実行し、それぞれの結果をcsvファイルに保存することができる。
python train_mnist.py --gpu 0 --net=MLP --optimizer=SGD python train_mnist.py --gpu 0 --net=MLP --optimizer=MomentumSGD python train_mnist.py --gpu 0 --net=MLP --optimizer=Adagrad python train_mnist.py --gpu 0 --net=MLP --optimizer=Adadelta python train_mnist.py --gpu 0 --net=MLP --optimizer=RMSprop python train_mnist.py --gpu 0 --net=MLP --optimizer=Adam python train_mnist.py --gpu 0 --net=CNN --optimizer=SGD python train_mnist.py --gpu 0 --net=CNN --optimizer=MomentumSGD python train_mnist.py --gpu 0 --net=CNN --optimizer=Adagrad python train_mnist.py --gpu 0 --net=CNN --optimizer=Adadelta python train_mnist.py --gpu 0 --net=CNN --optimizer=RMSprop python train_mnist.py --gpu 0 --net=CNN --optimizer=Adam python train_mnist.py --gpu 0 --net=Regression --optimizer=SGD python train_mnist.py --gpu 0 --net=Regression --optimizer=MomentumSGD python train_mnist.py --gpu 0 --net=Regression --optimizer=Adagrad python train_mnist.py --gpu 0 --net=Regression --optimizer=Adadelta python train_mnist.py --gpu 0 --net=Regression --optimizer=RMSprop python train_mnist.py --gpu 0 --net=Regression --optimizer=Adam
ネットワーク構造としてMLPを用いた場合の、各最適化手法のaccuracyの推移は以下のようになった。
また、ネットワーク構造としてCNNを用いた場合の、各最適化手法のaccuracyの推移は以下のようになった。
どちらもAdadeltaとAdamが良い性能を出していた。RMSpropはMLPではAdagradと同程度の性能を出していたものの、CNNではaccuracyが最初から最後のepochまで、0.11台で横ばいで学習ができていなかった(accuracyが低すぎるためグラフに表れていない)。
一方、ネットワーク構造としてRegressionを用いた場合は、以下のようにlossは順調に下がっていくものの、連続の予測値を四捨五入して強引にクラスに割り振っているためか、accuracyは散々な結果になった。