みかんのゆるふわ技術ブログ

Raspberry PiやIoT関係のことを書き残していきます

Tensorflow2で手書き文字認識(MNIST)

前回の記事でTensorflow2をインストールしました。 早速これを使って何かしてみましょう✊

初心者向けチュートリアル

Tensorflowの公式マニュアルを見ると、初心者向けのチュートリアルがあります。

www.tensorflow.org

最初は、"Hello, world!"代わりに、画像を分類するニューラルネットワークを構築して学習させ、正解率の評価を行うようです。 題材は有名なMNIST。手書き文字認識のデータセットですね。 学習用の手書き文字画像が6万枚、テスト用の画像が1万枚も入っています。縦横28ピクセルのグレースケール画像です。

早速やってみよう

といっても、公式のソースをそのまま写すだけです😅

import tensorflow as tf

mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train, x_test = x_train / 255.0, x_test / 255.0
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10, activation='softmax')
])

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(x_train, y_train, epochs=5)

チュートリアルですし、深くはないニューラルネットワークを使うようです。 入力層は画像の画素の28x28=784ノード、出力層は0~9までの数字をあらわす10ノードになっていますね。

これを実行するとどうなるのでしょうか。 上記ソースをmnist_test.pyというファイルに保存して実行します。

> venv\Scripts\python.exe mnist_test.py
(略)
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 3s 57us/sample - loss: 0.2966 - accuracy: 0.9125
Epoch 2/5
60000/60000 [==============================] - 3s 49us/sample - loss: 0.1426 - accuracy: 0.9582
Epoch 3/5
60000/60000 [==============================] - 3s 49us/sample - loss: 0.1089 - accuracy: 0.9678
Epoch 4/5
60000/60000 [==============================] - 3s 49us/sample - loss: 0.0887 - accuracy: 0.9722
Epoch 5/5
60000/60000 [==============================] - 3s 49us/sample - loss: 0.0758 - accuracy: 0.9756
10000/10000 - 0s - loss: 0.0844 - accuracy: 0.9751

MNISTのデータセットをダウンロードしていなくても、初回実行時に自動でダウンロードしてくれます。至れり尽くせりなフレームワークですね☺️

30秒もしないうちに学習が終わり、評価結果が出ました。 6万枚ある学習用画像を5周分学習し、その後1万枚のテスト画像を分類した結果、accuracyが0.9751、すなわち約97%で正解する分類器が作れたことになります🎉

公式のチュートリアルはここで一区切りし、次のチュートリアルに移ります。

………。

…何かあっさりしていますね。本当にニューラルネットワークが学習したのか実感がわかない…🤔

ということで、もう少しいじってみることにしました。

データを見てみる

そもそもどんな画像が入っているのか見てみましょう。

import tensorflow as tf
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)
print(x_train[0])
print(y_train[0])

上記スクリプトmnist_shape.pyという名前で保存して実行してみます。

> .\venv\Script\python.exe mnist_shape.py
(60000, 28, 28)
(60000,)
(10000, 28, 28)
(10000,)
[
(中略)
 [  0   0   0   0   0   0  18 171 219 253 253 253 253 195  80   9   0   0
    0   0   0   0   0   0   0   0   0   0]
 [  0   0   0   0  55 172 226 253 253 253 253 244 133  11   0   0   0   0
    0   0   0   0   0   0   0   0   0   0]
(中略)
]
5

画像のほうは28x28ピクセルの画像がそれぞれ6万枚、1万枚、それに対応する教師データは、正解の数字がそのまま入っています。 一番最初の学習用画像の正解は、5のようです。 画像データは背景の部分は0、何か書かれている部分は1~255までの数字で表されています。

画像データを実際に表示させてみましょう。 次のチュートリアルで出てきますが、matplotlibを使うと簡単に可視化👀できます。pipで簡単にインストールできます。

> .\venv\Script\pip.exe matplotlib

そして、先ほどのmnist_shape.pyの最後に次のスクリプトを付け加えます。

import matplotlib.pylab as plt

plt.figure(figsize=(10, 9))
plt.subplots_adjust(hspace=0.5)
for n in range(30):
  plt.subplot(6, 5, n+1)
  plt.imshow(x_train[n], cmap="Greys")
  plt.title(y_train[n])
  plt.axis('off')
plt.show()

学習用画像の最初30個を表示します。こんな画像が出てきました。

f:id:kimura_khs:20200504184949p:plain:w400

なんか、えらく下手な?文字も混ざっているように見えますが😅ともかく手書き文字です。

自分の手書き文字も認識させたい!

ここまで来たら、自分の手書き文字も認識させたいですよね😆早速やってみましょう。

データ作成

私はWindows10に付属のペイントで、マウスを使って28x28ピクセルの画像データを作成しました。

f:id:kimura_khs:20200504185955p:plain

注意点としては、MNISTのデータは背景が0、文字の部分が0以外になっていますので、ペイントで描く際は黒背景に白文字で書くと良いでしょう。 あとでグレースケール画像に変換すると、MNISTデータと同じようになります。

画像はpng形式で保存し、ファイル名は0.pngなどとします。

学習させたモデルの保存

その前に、学習したニューラルネットワークを保存しておきましょう。 いろんな画像を試すのに、毎回学習していては不便です。

最初に作成した、mnist_test.pyの最後に、次の1行を付け加えて実行します。

model.save("mnist_model.h5")

これで、学習したニューラルネットワークmnist_model.h5という名前で保存されました。 認識するときは、そのデータを読み込めばよいというわけです。

認識させてみよう

`0.png9.pngまで用意し、次のスクリプトを実行します。

import tensorflow as tf
import numpy as np

def load_imgset(filename):
    img = tf.io.decode_image(tf.io.read_file(filename))
    img = tf.image.rgb_to_grayscale(img)
    img = tf.image.resize(img, [28, 28])
    img = np.reshape(img, (28, 28))
    img / 255.0
    img_set = np.expand_dims(img,0) # Create dataset from one image
    return img_set

model = tf.keras.models.load_model("mnist_model.h5")

for i in range(10):
  img = load_imgset("%d.png" % i)
  pred = model.predict(img)
  pred_num = pred.argmax()
  print(i, "-->", pred_num)

実行結果は次の通りです。

0 --> 0
1 --> 1
2 --> 2
3 --> 3
4 --> 4
5 --> 3
6 --> 8
7 --> 7
8 --> 3
9 --> 9

左側が正解、右側が認識結果ですが…5,6,8が正しく認識できていないです。 正解率約97%のはずが、なぜかこんな結果に。実質2値画像だからでしょうか🤔??

ともあれ、ひとまずここまでで一区切りとします😌