画像認識に向いているCNN

畳み込みニューラル・ネットワーク(CNN)は画像処理に強いディープ・ラーニングとのことです。
CNNを使って手書きの文字を入力し文字を認識させてみます。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from __future__ import print_function
import argparse

import chainer
import chainer.functions as F
import chainer.links as L
import chainer.initializers as I
from chainer import training
from chainer.training import extensions

class MLP(chainer.Chain):
def __init__(self, n_units, n_out):
w = I.Normal(scale=0.05) # モデルパラメータの初期化
super(MLP, self).__init__(
conv1=L.Convolution2D(1, 16, 5, 1, 0), # 1層目の畳み込み層(フィルタ数は16)
conv2=L.Convolution2D(16, 32, 5, 1, 0), # 2層目の畳み込み層(フィルタ数は32)
l3=L.Linear(None, n_out, initialW=w), #クラス分類用
)
def __call__(self, x):
h1 = F.max_pooling_2d(F.relu(self.conv1(x)), ksize=2, stride=2) # 最大値プーリングは2×2,活性化関数はReLU
h2 = F.max_pooling_2d(F.relu(self.conv2(h1)), ksize=2, stride=2)
y = self.l3(h2)
return y

def main():
parser = argparse.ArgumentParser(description='Chainer example: MNIST')
parser.add_argument('--batchsize', '-b', type=int, default=100, help='Number of images in each mini-batch')
parser.add_argument('--epoch', '-e', type=int, default=20, help='Number of sweeps over the dataset to train')
parser.add_argument('--gpu', '-g', type=int, default=-1, help='GPU ID (negative value indicates CPU)')
parser.add_argument('--out', '-o', default='result', help='Directory to output the result')
parser.add_argument('--resume', '-r', default='', help='Resume the training from snapshot')
parser.add_argument('--unit', '-u', type=int, default=1000, help='Number of units')
#args = parser.parse_args()
args = parser.parse_args(args=[]) # Jupyter用

print('GPU: {}'.format(args.gpu))
print('# unit: {}'.format(args.unit))
print('# Minibatch-size: {}'.format(args.batchsize))
print('# epoch: {}'.format(args.epoch))
print('')

train, test = chainer.datasets.get_mnist(ndim=3) # ndim=3を引数で与えるだけでOK
model = L.Classifier(MLP(args.unit, 10), lossfun=F.softmax_cross_entropy)
if args.gpu >= 0:
chainer.cuda.get_device(args.gpu).use()
model.to_gpu()
optimizer = chainer.optimizers.Adam()
optimizer.setup(model)
train_iter = chainer.iterators.SerialIterator(train, args.batchsize)
test_iter = chainer.iterators.SerialIterator(test, args.batchsize, repeat=False, shuffle=False)
updater = training.StandardUpdater(train_iter, optimizer, device=args.gpu)

trainer = training.Trainer(updater, (args.epoch, 'epoch'), out=args.out)
trainer.extend(extensions.Evaluator(test_iter, model, device=args.gpu))
trainer.extend(extensions.dump_graph('main/loss'))
trainer.extend(extensions.snapshot(), trigger=(args.epoch, 'epoch'))
trainer.extend(extensions.LogReport())
trainer.extend( extensions.PlotReport(['main/loss', 'validation/main/loss'], 'epoch', file_name='loss.png'))
trainer.extend( extensions.PlotReport(['main/accuracy', 'validation/main/accuracy'], 'epoch', file_name='accuracy.png'))
trainer.extend(extensions.PrintReport( ['epoch', 'main/loss', 'validation/main/loss', 'main/accuracy', 'validation/main/accuracy', 'elapsed_time']))
trainer.extend(extensions.ProgressBar())

if args.resume:
chainer.serializers.load_npz(args.resume, trainer)

trainer.run()
model.to_cpu()

modelname = args.out + "/MLP.model"
print('save the trained model: {}'.format(modelname))
chainer.serializers.save_npz(modelname, model)

if __name__ == '__main__':
main()

[実行結果]

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
GPU: -1
# unit: 1000
# Minibatch-size: 100
# epoch: 20

Downloading from http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz...
epoch main/loss validation/main/loss main/accuracy validation/main/accuracy elapsed_time
1 0.231759 0.0724735 0.935317 0.9783 92.431
2 0.0670751 0.0424591 0.980317 0.9862 244.515
3 0.0477893 0.0391225 0.98565 0.9865 396.184
4 0.0395797 0.0346431 0.987767 0.9892 547.208
5 0.0323916 0.035994 0.99 0.9883 698.513
6 0.0277908 0.032813 0.99155 0.9892 788.491
7 0.0233976 0.0334056 0.9929 0.9896 878.809
8 0.0202218 0.0281953 0.9938 0.9912 1029.3
9 0.0174586 0.0332045 0.994483 0.9901 1179.88
10 0.0150536 0.0278408 0.995333 0.9919 1269.85
11 0.0141825 0.0325213 0.995583 0.991 1360.04
12 0.0118445 0.0382767 0.996233 0.9893 1509.41
13 0.0107907 0.0331525 0.996683 0.9905 1659.55
14 0.00933434 0.0442279 0.9967 0.9885 1809.8
15 0.00836822 0.0357421 0.997333 0.9895 1932.74
16 0.00697497 0.0377525 0.9977 0.9896 2022.73
17 0.0075223 0.0438979 0.997383 0.9875 2174.04
18 0.00604851 0.0376632 0.99795 0.9914 2327.5
19 0.00420569 0.0373403 0.998817 0.9913 2492.95
20 0.00607973 0.0395273 0.99795 0.9905 2650.21
save the trained model: result/MLP.model

各項目の意味は下記の通りです。

名称 内容
epoch 学習回数
main/loss 出力と学習データの誤差
validation/main/loss 出力とテスト・データの誤差
main/accuracy 学習データの正答率
validation/main/accuracy テスト・データの正答率
elapsed_time 経過時間(秒)

最終的なテスト・データの正答率(validation/main/accuracy)は99.05%とのかなり優秀な結果となりました。
エポック数と正答率(main/accuracy、validation/main/accuracy)の関係は下記のグラフのようになります。
(validationがテストデータの方を表します。)
正答率

エポック数と誤差(validation/main/loss)の関係は下記のグラフのようになります。
誤差

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