Going Faraway

渡辺遼遠の雑記帳。技術ネタと読んだ本の紹介。

【Deep Learning with Python】畳み込みニューラルネット(CNN)によるMNIST

Kerasの作者、フランソワ・ショレ氏によるディープラーニングとKerasの解説本 『Deep Learning with Python』の読書メモ。

第二部「Deep Learning in practice」では、実践的な問題にディープラーニングを適用する方法を扱う。5章は畳み込みニューラルネット(convolutional neural network; CNN, convnet) を用いたコンピュータビジョンに関する話。

理論的な話は後で出てくるので、とりあえずCNNのサンプルプログラムを動かす。最初にも出てきたMNISTデータセットを使って、画像の分類を行う。

from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))

Conv2D と MaxPooling の層が交互に置かれる形になる。

model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))

Conv2Dの最初の引数"32"はフィルタ(カーネル)の数を表し、出力の次元となる。次のタプル "(3, 3)" はフィルタ(カーネル)のサイズを表し、ここでは幅と高さが3x3のフィルタを用いることを意味している。

model.add(layers.MaxPooling2D((2, 2)))

MaxPooling層では出力をダウンスケールする。ここでは、前の畳み込み層の出力が幅と高さ方向にそれぞれ半分にされることを意味する。(畳み込み層とマックスプーリング層の処理の詳細は、後の節で解説があるので後述)

>>> model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 26, 26, 32)        320       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 11, 11, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 5, 5, 64)          0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 3, 3, 64)          36928     
=================================================================
Total params: 55,744
Trainable params: 55,744
Non-trainable params: 0
_________________________________________________________________

model.summary() 関数でモデルの概要を出力することができる。Output Shapeの列に注目すると、conv2dの層で「畳み込み」が行なわれ、max_pooling層で出力が半分に落とされていることが分かる。

# 予測用のレイヤー
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))

予測用のレイヤーを追加する。畳み込み&マックスプーリング層の出力は3x3x64次元のテンソルなので、layers.Flatten() を使って576個の要素を持つ1次元のベクトルに変換し、全結合層を使い、その出力から予測を行う。

# 訓練データの変換
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255

test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255

train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

訓練データをニューラルネットへの入力用に変換する。最初の全結合ネットワークを用いたサンプルでは、画像データを1次元ベクトルに変換した。

train_images = train_images.reshape((60000, 28, 28, 1))

今回の畳み込みニューラルネットでは、2次元のデータのまま入力する。 reshapeの引数のタプル "(60000, 28, 28, 1)" は、それぞれ、(データの数, 縦のピクセル数、横のピクセル数、チャネル) を表す。MNISTのデータは、0〜255の数値で表現されるグレースケールなので、今回のチャネル数は1となる。たとえば、RGBの3つの数で表現されるカラー画像の場合、チャネル数は3となるとか。

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

model.fit(train_images, train_labels, epochs=5, batch_size=64)

モデルをコンパイルして学習を行う。モデルがちょっと複雑になってきたためか、学習に多少時間を要する。GPUを使わない環境で実行したら、学習の1エポックに2〜3分程度掛かった。

>>> test_loss, test_acc = model.evaluate(test_images, test_labels)
>>> print(test_acc)

10000/10000 [==============================] - 3s 339us/step
0.9915

テスト誤差を測定してみると、99.15%だった。もっとカリカリにチューニングすれば99.5%程度まで向上させられるらしい。

予測と結果の表示

最初のサンプルと同様、結果を表示してみる。左上の青文字が正解のラベルで、赤字がニューラルネットの予測値 (誤答)

f:id:liaoyuan:20180224123428p:plain

最初の全結合ネットワークの結果よりも、更に乱雑で人間が見ても間違いそうなものが多い。それでも、8, 9の混同など、人間だったらこんな間違いはしないだろうという間違いも残っているようだ。