KerasでDeep Learning:KerasでMNISTデータを扱ってみる
導入
前回は人工データを用いたネットワーク構築について紹介しました。
tekenuko.hatenablog.com
今回は、異なるデータ(MNIST)に対してモデルを作成してみます。
MNIST
MNISTとは、「Mixed National Institute of Standards and Technology database」の略で、手書きの数字(0~9)に正解ラベルが与えられているデータセットです。
MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges
無料で入手かつ手軽に分析できるデータセットなので、機械学習(特に画像処理系)ではよく用いられます。
手書き文字は28ピクセル×28ピクセルの画像で与えられており、データ数は学習:テスト = 60000枚 : 10000枚です。
Kerasでは、MNISTをダウンロードするメソッドが実装されているので、それを利用して中身を少し覗いてみます。
# 必要なライブラリのインポート import keras from keras.datasets import mnist # Jupyter notebookを利用している際に、notebook内にplot結果を表示するようにする import matplotlib.pyplot as plt %matplotlib inline #Kerasの関数でデータの読み込み。データをシャッフルして学習データと訓練データに分割 (x_train, y_train), (x_test, y_test) = mnist.load_data() #MNISTデータの表示 fig = plt.figure(figsize=(9, 9)) fig.subplots_adjust(left=0, right=1, bottom=0, top=0.5, hspace=0.05, wspace=0.05) for i in range(81): ax = fig.add_subplot(9, 9, i + 1, xticks=[], yticks=[]) ax.imshow(x_train[i].reshape((28, 28)), cmap='gray')
なんというか、温かみのある文字たちが出現します。
データ加工
次に、MNISTデータを加工します。ここでは、28ピクセル×28ピクセルのデータを28×28=784次元のベクトルに変換し、ベクトルの成分を0~1の範囲に正規化します。
# 2次元データを数値に変換 x_train = x_train.reshape(60000, 784) x_test = x_test.reshape(10000, 784) # 型変換 x_train = x_train.astype('float32') x_test = x_test.astype('float32') # 255で割ったものを新たに変数とする x_train /= 255 x_test /= 255
Deep Learningによる画像データの解析では、畳み込みニューラルネットワーク(Convolutional Neural Network : CNN)がよく知られており、その場合は28ピクセル×28ピクセルのデータを行列として入力します。今回は、よりプリミティブなネットワークを作る予定なので、ベクトルデータに変換しています。
また、ベクトルの成分を0~1に変換していますが、これは必須かどうかちょっと判断に迷う操作です。正規化をしないで学習もしてみたのですが、今回は精度が全然出ない結果になりました。しかしながら、別のデータセットや(Kerasのバックエンドで動いているTensorflowをはじめとした)別フレームワークでは必ずしも精度が出ないというわけでもなかったからです。この挙動に関しては、まだ要因を分解できていないため、ここでは深く立ち入らず、Kerasのチュートリアルの通りにデータを正規化して話を進めます。
次に、目的変数(0~9の10種類のラベル)の加工を行います。Kerasでは、0と1が成分となっている配列でラベルを表示する必要があります。そのため、加工に必要なライブラリをインポートし、処理をします。
# one-hot encodingを施すためのメソッド from keras.utils.np_utils import to_categorical # クラス数は10 num_classes = 10 y_train = y_train.astype('int32') y_test = y_test.astype('int32') # one-hot encoding y_train = to_categorical(y_train, num_classes) y_test = to_categorical(y_test, num_classes)
ここで、one-hot encodingは以下のような変換を数値に対して施しています。
0 → [0,0,0,0,0,0,0,0,0,0] 1 → [0,1,0,0,0,0,0,0,0,0] 2 → [0,0,1,0,0,0,0,0,0,0] 3 → [0,0,0,1,0,0,0,0,0,0] ... 9 → [0,0,0,0,0,0,0,0,0,1]
また、num_classes = 10と明示的に指定しておくのが安全です。なぜなら、学習データとテストデータをランダムに分割しているので、分け方によってはどちらかのデータセットのクラス数が10にならない可能性があるためです。
これでデータの準備は完了です。
ネットワーク構築
前回紹介した手法の一つ、Sequentialモデルで作成します。
# 必要なライブラリのインポート、最適化手法はAdamを使う from keras.models import Sequential from keras.layers import Dense, Dropout from keras.optimizers import Adam # モデル作成 model = Sequential() model.add(Dense(512, activation='relu', input_shape=(784,))) model.add(Dropout(0.2)) model.add(Dense(512, activation='relu')) model.add(Dropout(0.2)) model.add(Dense(10, activation='softmax'))
基本は全結合で活性化関数はReLUですが、過学習防止のため、Dropoutという操作もしています。これは、学習の最中にネットワークのノードをランダムに削ることで、過学習が進みすぎるのを防止するような仕組みです。0.2という引数は20%削ることを意味しています。
model.summary()でサマリを見てみます。
model.summary()
Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 512) 401920 _________________________________________________________________ dropout_1 (Dropout) (None, 512) 0 _________________________________________________________________ dense_2 (Dense) (None, 512) 262656 _________________________________________________________________ dropout_2 (Dropout) (None, 512) 0 _________________________________________________________________ dense_3 (Dense) (None, 10) 5130 ================================================================= Total params: 669,706 Trainable params: 669,706 Non-trainable params: 0 _________________________________________________________________
上からネットワークの種類やノード数、パラメータ数などが順繰り表示されます。わかりやすい。
別の可視化
以下のように書いても、ネットワークの情報を表示できます。ただし、keras.utils.vis_utils を使用するためにはgraphvizというソフトウェアが別途必要なため、Macユーザは例えばbrew installで、Linux(Ubuntuとか)ユーザはapt-get installであらかじめ入れておいてください。
from IPython.display import SVG from keras.utils.vis_utils import model_to_dot SVG(model_to_dot(model).create(prog='dot', format='svg'))
学習、テスト
実際に学習をしてみましょう。学習結果はhistoryという変数で残しておきます。model.evaluate()でテストデータに対してAccuracy(正答率)を計算し、それも別途出力します。
# バッチサイズ、エポック数 batch_size = 128 epochs = 20 model.compile(loss='categorical_crossentropy', optimizer=Adam(), 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 [==============================] - 7s - loss: 0.2521 - acc: 0.9244 - val_loss: 0.1085 - val_acc: 0.9652 Epoch 2/20 60000/60000 [==============================] - 7s - loss: 0.1044 - acc: 0.9680 - val_loss: 0.0889 - val_acc: 0.9707 Epoch 3/20 60000/60000 [==============================] - 7s - loss: 0.0714 - acc: 0.9777 - val_loss: 0.0621 - val_acc: 0.9811 Epoch 4/20 60000/60000 [==============================] - 7s - loss: 0.0562 - acc: 0.9822 - val_loss: 0.0752 - val_acc: 0.9773 Epoch 5/20 60000/60000 [==============================] - 7s - loss: 0.0457 - acc: 0.9848 - val_loss: 0.0627 - val_acc: 0.9817 Epoch 6/20 60000/60000 [==============================] - 7s - loss: 0.0384 - acc: 0.9872 - val_loss: 0.0602 - val_acc: 0.9828 Epoch 7/20 60000/60000 [==============================] - 7s - loss: 0.0326 - acc: 0.9892 - val_loss: 0.0661 - val_acc: 0.9820 Epoch 8/20 60000/60000 [==============================] - 7s - loss: 0.0304 - acc: 0.9894 - val_loss: 0.0658 - val_acc: 0.9816 Epoch 9/20 60000/60000 [==============================] - 7s - loss: 0.0267 - acc: 0.9914 - val_loss: 0.0691 - val_acc: 0.9820 Epoch 10/20 60000/60000 [==============================] - 7s - loss: 0.0276 - acc: 0.9905 - val_loss: 0.0719 - val_acc: 0.9796 Epoch 11/20 60000/60000 [==============================] - 7s - loss: 0.0218 - acc: 0.9925 - val_loss: 0.0698 - val_acc: 0.9825 Epoch 12/20 60000/60000 [==============================] - 7s - loss: 0.0232 - acc: 0.9920 - val_loss: 0.0841 - val_acc: 0.9786 Epoch 13/20 60000/60000 [==============================] - 7s - loss: 0.0206 - acc: 0.9929 - val_loss: 0.0979 - val_acc: 0.9790 Epoch 14/20 60000/60000 [==============================] - 7s - loss: 0.0211 - acc: 0.9930 - val_loss: 0.0775 - val_acc: 0.9803 Epoch 15/20 60000/60000 [==============================] - 7s - loss: 0.0189 - acc: 0.9938 - val_loss: 0.0726 - val_acc: 0.9832 Epoch 16/20 60000/60000 [==============================] - 7s - loss: 0.0174 - acc: 0.9941 - val_loss: 0.0820 - val_acc: 0.9823 Epoch 17/20 60000/60000 [==============================] - 7s - loss: 0.0159 - acc: 0.9944 - val_loss: 0.0819 - val_acc: 0.9820 Epoch 18/20 60000/60000 [==============================] - 7s - loss: 0.0161 - acc: 0.9946 - val_loss: 0.0776 - val_acc: 0.9831 Epoch 19/20 60000/60000 [==============================] - 7s - loss: 0.0195 - acc: 0.9935 - val_loss: 0.0818 - val_acc: 0.9819 Epoch 20/20 60000/60000 [==============================] - 7s - loss: 0.0141 - acc: 0.9952 - val_loss: 0.0751 - val_acc: 0.9826 Test loss: 0.0751098209593 Test accuracy: 0.9826
テストデータのAccuracyが98%を超えるんですか。そんなに凝ったネットワークでもないのにすごいですね。
可視化
Accuracyとloss functionをepoch数でプロットしてみましょう。
#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()
学習データに関しては、学習を進めると精度がどんどん良くなっている様が見えます。一方で、テストデータに関しては、(十分良いのですが)あるepochからloss functionが微妙に増加するような振舞いをしています。過学習(今あるデータに特化しすぎて未知のデータに対する性能が落ちる)の傾向が見えているようです。
Early stopping
過学習を防ぐ方法の一つとして、予測性能が下がる前に学習を打ち切る、というものがあります。こういった方法をEarly stoppingといいます。検証用データ(validation data)を学習データの中からいくらか選び、学習した結果をその検証用データに適用し、精度やloss functionが劣化したら学習を打ち切ります(すぐに打ち切るか少し様子見をするかはオプションで指定できます)。
実際にコードを動かしてみます。
# 必要なライブラリのインポート from keras.callbacks import EarlyStopping # モデルの訓練 history_ES = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_split=0.1, callbacks=[early_stopping])
今回は、以下のように途中で学習が止まります。
Train on 54000 samples, validate on 6000 samples Epoch 1/20 54000/54000 [==============================] - 5s - loss: 0.0073 - acc: 0.9975 - val_loss: 0.0080 - val_acc: 0.9980 Epoch 2/20 54000/54000 [==============================] - 5s - loss: 0.0078 - acc: 0.9978 - val_loss: 0.0264 - val_acc: 0.9938 Epoch 00001: early stopping
Accuracyとloss functionをepoch数でプロットすると以下のようになります。
#Accuracy plt.plot(history_ES.history['acc']) plt.plot(history_ES.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_ES.history['loss']) plt.plot(history_ES.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show()
まとめとNext Step
今回はMNISTという画像データに対してネットワーク構築をしてみました。あまり複雑なネットワークでないにも関わらず、高い性能をもつものができたのでした。
次回は、より画像データの処理に強みを持ったネットワークの構築を目指していきます。やっとCNNの出番ですね。