Edited at

画像データからのMNIST


概要

この記事ではkerasのdatasetsを使わずにmnistの手書き文字画像の分類を行いたいと思います。

画像データはこちらのものを使用しています。

なお、機械学習初めて2週間くらいのクソザコなのでおかしな点はツッコミ入れて頂けるとありがたいです🙏


開発環境

MacOS Mojave 10.14

Python 3.6.3


環境構築

Pythonは入っている前提で始めます。

まずkeras単体では学習を行えないのでバックエンドエンジンとなるtensorflowをインストールします。

公式サイトの通りにやれば大丈夫です!

pip install tensorflow

次にkerasのインストールです、こちらも公式サイト通りにやりましょう。

sudo pip install keras


画像データの前処理

取ってきた画像データを解凍すると以下のような構造になっています。

mnistasjpg

|--testSample
|--testSet.tar.gz
|--trainingSample
|--trainingSet.tar.gz

今回はこの中のtestSet.tar.gz、trainingSet.tar.gzを使います。

まずはこの2つを解凍しましょう

tar -xzvf testSet.tar.gz

tar -xzvf trainingSet.tar.gz

展開されたtestSetとtrainingSetには0,1などのディレクトリが入っておりその中に各トレーニング用の手書き文字が入っています。

また次のようなディレクトリ構造を想定して話を進めて行きます。

mnist

├── data
| ├── testSet
| ├── trainingSet
└── mnist.py

さて、次にディレクトリ内に入っている画像を学習しやすいように変換していきます。


mnist.py

import numpy as np

from PIL import Image
import os

image_list = [] #トレーニングimage格納用list
label_list = [] #トレーニングlabel格納用list

train_path = "data/trainingSet"
test_path = "data/testSet"

for label in os.listdir(train_path):
dir = train_path + "/" + label #各ディレクトリ名がラベル名になっているのでそれをトレーニングラベルに利用する。
for filename in os.listdir(dir):
label_list.append(label)
image_path = dir + "/" + filename
image = np.array(Image.open(image_path).convert("L").resize((28,28))) #画像をグレースケールで28x28のサイズに変換
image_list.append(image/255.) # 255で割って正規化


上記のようにする事でimage_listにimageの情報が入った28x28の2次元numpy配列、image_labelにそのimageのラベルが入ります。


処理した画像をkerasで使えるようにする

このままkerasのmodelにリストを渡しても実行する事ができないので扱えるようにnumpy配列にする必要があります。


mnist.py

image_list = np.array(image_list) # リストをnumpy配列に変換する。

label_list = np.array(label_list) # リストをnumpy配列に変換する。
label_list = np_utils.to_categorical(label_list)

(train_data, test_data, train_label, test_label) = train_test_split(image_list, label_list, test_size=0.3, random_state=111)
train_data = train_data.reshape(-1, 28, 28, 1)
test_data = test_data.reshape(-1, 28, 28, 1)


label_list = np_utils.to_categoricalで何をやっているか?

http://may46onez.hatenablog.com/entry/2016/07/14/122047

によると


Kerasはラベルを数値ではなく、0or1を要素に持つベクトルで扱う


らしいのでlabel_listのnumpy配列を0,1の要素を持つベクトルに変換しているみたいですね。


CNNの実装

今回は下記のようなモデルを実装して学習を行いました!

各ハイパーパラメーターなどは割と適当です🙏

Layer (type)                 Output Shape              Param #

=================================================================
conv2d_1 (Conv2D) (None, 25, 25, 32) 544
_________________________________________________________________
conv2d_2 (Conv2D) (None, 22, 22, 64) 32832
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 11, 11, 64) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 11, 11, 64) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 8, 8, 64) 65600
_________________________________________________________________
conv2d_4 (Conv2D) (None, 5, 5, 64) 65600
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 2, 2, 64) 0
_________________________________________________________________
dropout_2 (Dropout) (None, 2, 2, 64) 0
_________________________________________________________________
dense_1 (Dense) (None, 2, 2, 64) 4160
_________________________________________________________________
flatten_1 (Flatten) (None, 256) 0
_________________________________________________________________
dense_2 (Dense) (None, 10) 2570
=================================================================

これをコードで書くと次のようになります。


mnist.py

batch_size = 128

epochs = 5
kernel_size = (4,4)
input_shape = train_data[0].shape

model = Sequential()

model.add(Conv2D(filters=32, kernel_size=kernel_size, input_shape=input_shape, activation="relu"))
model.add(Conv2D(filters=64, kernel_size=kernel_size, activation="relu"))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.2))
model.add(Conv2D(filters=64, kernel_size=kernel_size, activation="relu"))
model.add(Conv2D(filters=64, kernel_size=kernel_size, activation="relu"))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Dense(64, activation="relu"))
model.add(Flatten())
model.add(Dense(10, activation="softmax"))
model.summary()

model.compile(
optimizer='adadelta',
loss='categorical_crossentropy',
metrics=['categorical_accuracy']
)

model.fit(train_data, train_label, batch_size=batch_size, epochs=epochs) # 学習させる


上でやった画像データの変換処理を間違えると

expected activation_2 to have shape (None, 10) but got array with shape (3, 1)

などのエラーが出るのでこれが出た場合はmodelではなく画像変換処理周りを見直してください。

ここで結構詰まりました

これでほぼ完成です!


結果

実際に上記のモデルをテストデータで検証したスコアは次のようになりました。


mnist.py

scores = model.evaluate(test_data, test_label, verbose=1)

# モデル保存処理
json_string = model.to_json()
open('mnist.json', 'w').write(json_string)
model.save_weights('mnist.h5')

# スコアを標準出力に出力
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])


結果

Test loss: 0.04206473699009548

Test accuracy: 0.988015873015873

実際mnistはどれくらいの正解率がないといけない....とかはわかりませんが

良いほうではないでしょうか?

ソースコードを見たい方はこちらです

https://github.com/yasuno0327/MyMNIST

次は理科大のアドベントカレンダーでGolangで機械学習する系の記事を上げる予定なのでクリスマス暇な方はぜひ見てくださいね!笑

参考

http://twosquirrel.mints.ne.jp/?p=20515