KerasでDeep Learning:CNNを組んでみる
導入
前回はMNISTデータに対してネットワークを構築して、精度を見ました。
tekenuko.hatenablog.com
今回は、より画像処理に特化したネットワークを構築してみて、その精度検証をします。
参考
KerasのGithubにあるexampleのほぼ丸パクリです。
github.com
畳み込みニューラルネットワーク
畳み込みニューラルネットワーク(Convolutional Neural Network, 以下CNN)は、畳み込み層とプーリング層というもので構成されるネットワークです。CNNは画像データに対して威力を発揮します。このあたりの詳しい説明は講談社の書籍や、ネット上の解説で見ることができるので、省略します。
bookclub.kodansha.co.jp
postd.cc
ざっくりとは、ニューラルネットのリンク部分を全結合にしないで、うまく一部だけ結合させ、その一部の平均なり最大値なりをとるなどして画像の特徴をうまく抽出する手法になっています。このようなネットワークを何層も組むと、入力層に近い層では原始的な特徴(エッジ部分など)を、より深い層では抽象的な特徴(猫っぽいなど)が表現されるようです。
データ加工
今回も前回と同様、MNISTデータを使います。ただし、前回は28ピクセル×28ピクセルのデータを784次元のベクトルに変換して入力としましたが、今回は行列として入力します。
# 必要なライブラリのインポート import keras from keras.datasets import mnist from keras import backend as K #Kerasの関数でデータの読み込み。データをシャッフルして学習データと訓練データに分割 (x_train, y_train), (x_test, y_test) = mnist.load_data() # 行列として入力するための加工 batch_size = 128 num_classes = 10 epochs = 20 img_rows, img_cols = 28, 28 # 場合分け if K.image_data_format() == 'channels_first': x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols) x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols) input_shape = (1, img_rows, img_cols) else: x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1) x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1) input_shape = (img_rows, img_cols, 1)
ここで、引数の与え方に関して、注意点があります。それを確認するのに、以下のjsonファイルを見てみます。
$HOME/.keras/keras.json
私の環境では、中身は以下のようになっています。
{ "image_data_format": "channels_last", "epsilon": 1e-07, "floatx": "float32", "backend": "tensorflow" }
このindexの詳細は以下です(Kerasの公式ページより)。
- image_data_format: 文字列,"channels_last" か "channels_first" のいずれか.Kerasが従うデータのフォーマット規則を指定します. (keras.backend.image_data_format() がこれを返します.)
- 2次元データ (例えば画像) に対しては, "channels_last" は (rows, cols, channels) とみなし,"channels_first" は (channels, rows, cols)とみなします.
- 3次元データに対しては, "channels_last" は (conv_dim1, conv_dim2, conv_dim3, channels) とみなし, "channels_first" は (channels, conv_dim1, conv_dim2,conv_dim3) とみなします.
- epsilon: float,いくつかの操作で0除算を避けるために使う微小量定数.floatx: 文字列,"float16","float32",か "float64".デフォルトの浮動小数点精度.
- backend: 文字列,"tensorflow" か "theano" か "cntk".
この説明から、画像データの場合、"image_data_format"が"channels_first"か"channels_last"かによって指定する引数の順序が異なります。今回は"channels_last"だったので、先ほどのコードの後者の加工がされるようになっています。
最後に、入力データの正規化とラベルの処理を行っておきます。
# 入力データ x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 # ラベルはone-hot encodingを施す y_train = y_train.astype('int32') y_test = y_test.astype('int32') y_train = keras.utils.np_utils.to_categorical(y_train, num_classes) y_test = keras.utils.np_utils.to_categorical(y_test, num_classes)
ネットワーク構築
Kerasのexampleに従い、2回畳み込みをしてPoolingを行うようなネットワークを組んでみます。
# 必要なパッケージのインポート from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.layers import Conv2D, MaxPooling2D model = Sequential() model.add(Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=input_shape)) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(128, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(num_classes, activation='softmax'))
2次元画像データに対する畳み込みを行うということで、Conv2D()を使用します。kernel_sizeはフィルタリングの際に着目するピクセルのサイズになっています。プーリングに関しては、MaxPooling2D()を使用しています。pool_sizeで最大値を取る領域を指定しています。
この畳み込みとプーリングですが、明示的にオプションとして指定していないものがいくつかあります。つまり、Kerasではデフォルト設定でよしなにやってくれている操作がいくつかあります。このあたりはTensorflowを使用している人が見るととても気になる点のようです*1。私は、このあたりはモデルを作りこんでいく際に細かく見ていけばよい点かなと思っており、まずは見よう見まねでもネットワークを組んで試せるようになることが大事だと思っておりますので、細かいオプションについてはここでは述べないことにします。
ネットワークを組んだら、あとは前回までと同様にモデルをコンパイルして学習させればOKです。
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adadelta(), metrics=['accuracy']) history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_test, y_test)) score = model.evaluate(x_test, y_test, verbose=0) print('Test loss:', score[0]) print('Test accuracy:', score[1])
Train on 60000 samples, validate on 10000 samples Epoch 1/20 60000/60000 [==============================] - 60s - loss: 0.3299 - acc: 0.9012 - val_loss: 0.0805 - val_acc: 0.9744 Epoch 2/20 60000/60000 [==============================] - 60s - loss: 0.1168 - acc: 0.9653 - val_loss: 0.0548 - val_acc: 0.9819 Epoch 3/20 60000/60000 [==============================] - 60s - loss: 0.0889 - acc: 0.9730 - val_loss: 0.0445 - val_acc: 0.9853 Epoch 4/20 60000/60000 [==============================] - 60s - loss: 0.0732 - acc: 0.9785 - val_loss: 0.0382 - val_acc: 0.9870 Epoch 5/20 60000/60000 [==============================] - 60s - loss: 0.0617 - acc: 0.9816 - val_loss: 0.0343 - val_acc: 0.9884 Epoch 6/20 60000/60000 [==============================] - 60s - loss: 0.0553 - acc: 0.9831 - val_loss: 0.0315 - val_acc: 0.9890 Epoch 7/20 60000/60000 [==============================] - 60s - loss: 0.0515 - acc: 0.9850 - val_loss: 0.0301 - val_acc: 0.9899 Epoch 8/20 60000/60000 [==============================] - 60s - loss: 0.0477 - acc: 0.9856 - val_loss: 0.0310 - val_acc: 0.9894 Epoch 9/20 60000/60000 [==============================] - 60s - loss: 0.0437 - acc: 0.9869 - val_loss: 0.0289 - val_acc: 0.9900 Epoch 10/20 60000/60000 [==============================] - 60s - loss: 0.0417 - acc: 0.9874 - val_loss: 0.0261 - val_acc: 0.9917 Epoch 11/20 60000/60000 [==============================] - 60s - loss: 0.0376 - acc: 0.9888 - val_loss: 0.0271 - val_acc: 0.9913 Epoch 12/20 60000/60000 [==============================] - 60s - loss: 0.0382 - acc: 0.9882 - val_loss: 0.0274 - val_acc: 0.9914 Epoch 13/20 60000/60000 [==============================] - 60s - loss: 0.0364 - acc: 0.9892 - val_loss: 0.0253 - val_acc: 0.9918 Epoch 14/20 60000/60000 [==============================] - 60s - loss: 0.0343 - acc: 0.9898 - val_loss: 0.0266 - val_acc: 0.9914 Epoch 15/20 60000/60000 [==============================] - 60s - loss: 0.0346 - acc: 0.9897 - val_loss: 0.0262 - val_acc: 0.9915 Epoch 16/20 60000/60000 [==============================] - 60s - loss: 0.0319 - acc: 0.9905 - val_loss: 0.0274 - val_acc: 0.9916 Epoch 17/20 60000/60000 [==============================] - 60s - loss: 0.0330 - acc: 0.9898 - val_loss: 0.0263 - val_acc: 0.9918 Epoch 18/20 60000/60000 [==============================] - 60s - loss: 0.0320 - acc: 0.9903 - val_loss: 0.0284 - val_acc: 0.9906 Epoch 19/20 60000/60000 [==============================] - 60s - loss: 0.0312 - acc: 0.9907 - val_loss: 0.0285 - val_acc: 0.9915 Epoch 20/20 60000/60000 [==============================] - 60s - loss: 0.0309 - acc: 0.9905 - val_loss: 0.0268 - val_acc: 0.9906 Test loss: 0.0267664063053 Test accuracy: 0.9906
テストデータで約99%のAccuracyをたたき出しました。前回組んだネットワークよりも精度が上がっています。
epochごとにAccuracyとLossがどう変化していくかも確認しておきましょう。
#Accuracy plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show() #loss plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show()
train, validationともにかなり良い性能を持っていそうな振舞いをしています。Early stoppingとか気にしなくていっか、と思ってしまいました(笑)
まとめ
駆け足でしたが、今回はMNISTという画像データに対してCNNを構築して精度を見てみました。Kerasのexampleをまねただけですが、後はこれをベースにいろいろと試行錯誤していけばよいのかな、という感じです。
画像データに関する手法ですが、ベーシックな話はいったんここまでにします。次回以降は未定ですが、時系列に特化したネットワークの話や、画像に関して応用的な話などを候補として考えています。