Python
ディープラーニング
TensorFlow
MNIST

自分の手書きデータをTensorFlowで予測する

はじめに

TenorFlowのチュートリアルで、MNISTを使って学習すると90%以上の精度がでます。そこで誰しも思うのが、「自分で書いた手書きデータも当然予測できるよね?」だと思います。
私も以前、同じことを思って自分の手書きデータを予測させてみたのですが、精度は50%程度だったのを覚えてます。90%以上の精度がでるはずなのに、なぜ!?
今回はようやく解決できたので記事にしました。キーとなるのは前処理でした。

MNISTの前処理

MNISTは、THE MNIST DATABASE of handwritten digitsで公開されている手書きの数字データです。公開サイトを見ると、ちゃんと前処理について書いてあります。

The original black and white (bilevel) images from NIST were size normalized to fit in a 20x20 pixel box while preserving their aspect ratio. The resulting images contain grey levels as a result of the anti-aliasing technique used by the normalization algorithm. the images were centered in a 28x28 image by computing the center of mass of the pixels, and translating the image so as to position this point at the center of the 28x28 field.

MNISTは20×20ピクセルに変換された画像(ただしアスペクト比は保ったまま)の重心を、28×28ピクセルの画像の中心に合わせた画像となってます。
当初、私の予測させるための画像は、28×28ピクセルに数字を書いただけでした。
では実際に前処理の有無で精度がどう変わるか見ていきましょう。

使用するモデル

TensorFlowで以下のネットワークを組みました。KerasのAPIを使っています。
200エポック回して、MNISTのテストデータで精度が97%程度でした。
(※コードは抜粋です。今後も基本的にコードは抜粋です。)

learn.py
# Define model
model = tf.keras.Sequential([
    tf.keras.layers.Dense(100, activation="relu", input_shape=(28 * 28,)),
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(10, activation="softmax")
])

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

前処理前のデータで予測

最初は、28×28ピクセルの画像に数字を書いた前処理前のデータで予想しました。
以下の画像になります。

0.png 1.png 2.png 3.png 4.png 5.png 6.png 7.png 8.png 9.png

結果は下記の通りで、60%の精度でした。
(0から順に9まで予測した結果です。)

predict.py
# Predict raw data
images = []

for i in range(10):
    im = Image.open('data/{}.png'.format(i)).convert('L')
    im = np.array(im)
    im = im.reshape((28 * 28))
    im = np.true_divide(im, 255)
    images.append(im)
pred = model.predict_classes(np.array(images))
score = metrics.accuracy_score([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], pred)

print(pred)
print('Accuracy is {}%'.format(score * 100))


[3 1 2 3 4 5 5 3 3 9]
Accuracy is 60.0%

前処理後のデータで予測

次は前処理を行います。以下のコードで20×20ピクセルにリサイズしてから、重心を28×28ピクセルの画像の中心に貼り付けます。

predict.py
# Define preprocess
def preprocess(image):
    # Resize to 20 * 20
    im = image.resize((20, 20))

    # Pasete to 28 * 28
    im_ret = Image.new('L', (28, 28))
    im_ret.paste(im, (4, 4))

    # Compute the center of mass and translate the image
    # to the point at the center of 28 * 28
    y_center, x_center = center_of_mass(np.array(im_ret))
    x_move = x_center - 14
    y_move = y_center - 14
    im_ret = im_ret.transform(size=(28, 28), method=Image.AFFINE,
                              data=(1, 0, x_move, 0, 1, y_move))
    return im_ret

前処理を行った画像は以下になります。

conv0.png conv1.png conv2.png conv3.png conv4.png conv5.png conv6.png conv7.png conv8.png conv9.png

結果は下記の通り、90%の精度になりました。
(0から順に9まで予測した結果です。)

predict.py
# Predict proprocessed data
images = []

for i in range(10):
    im = Image.open('data/{}.png'.format(i)).convert('L')
    im = preprocess(im)
    im.save('data/conv{}.png'.format(i))
    im = np.array(im)
    im = im.reshape((28 * 28))
    im = np.true_divide(im, 255)
    images.append(im)
pred = model.predict_classes(np.array(images))
score = metrics.accuracy_score([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], pred)

print(pred)
print('Accuracy is {}%'.format(score * 100))

[0 1 2 3 4 5 6 7 2 9]
Accuracy is 90.0%

位置をずらして予測

追加の実験で数字の7で、位置を上下左右にずらして予測させてみました。予測に使った画像は以下の通りになります。

conv7t.png conv7b.png conv7l.png conv7r.png

結果は下記の通り、ずらした画像は4つのうち1つしか当たりませんでした。
(比較のため、ずらしていない画像の予測も行っています。一番最初の結果がずらしていない画像になります。そのあとは上下左右の順に予測しています。)

predict.py
# Predict shifted data
images = []

for i in ['', 't', 'b', 'l', 'r']:
    im = Image.open('data/conv7{}.png'.format(i)).convert('L')
    im = np.array(im)
    im = im.reshape((28 * 28))
    im = np.true_divide(im, 255)
    images.append(im)
pred = model.predict_classes(np.array(images))
score = metrics.accuracy_score([7, 7, 7, 7, 7], pred)

print(pred)
print('Accuracy is {}%'.format(score * 100))

[7 3 4 7 5]
Accuracy is 40.0%

まとめ

今回はMNISTで学習したモデルで自分の手書きデータを予測してみました。0から9の数字のデータなので、簡単に予測できるのかと思いきや、前処理の有無で結果がずいぶんと違っていました。
データ分析の本等を読むとデータの前処理の重要性が必ずと言っていいほど書かれています。今回はそれを実感しました。

今回使ったソースコードはgithubに公開していますので、処理の詳細を確認したい方は参照して下さい。

ちなみに初めてTensorFlowを触ったときに書いた記事でも、手書きデータでの予測をやっていました。読み返すと50%や60%の精度となっています。こちらの記事も更新しておきました。

Cloud9でTensorFlowのチュートリアル(初心者のためのMNIST)をやってみた~手書き画像の分類~
Cloud9でTensorFlowのチュートリアルで畳み込みニューラルネットワーク(CNN)をやってみた~手書き画像の分類~