MNISTで手書き文字認識やってみた

お勉強記録第一弾です。
今回使用するMNISTとは、手書き数字(0~9)の画像とその画像に書かれた数字のラベルのデータセットです。AIドルの研修でも使用しましたが、DeepLearningのチュートリアルとしてよく使用されています。
今回はこのMNISTを使用し、手書き数字(0~9)の分類をするDeepLearningのモデルを構築してみようと思います。

実行環境・ライブラリのご紹介

Pythonの実行環境ですが、まずはGoogle Colaboratoryを使用することをオススメします。
Jupyter Notebookの環境構築って難しくないですか?私だけですか???
環境構築で躓いてはやる気も削がれるので、いますぐGoogleアカウント作ってサクッと始めちゃいましょう。
Colaboratoryはクラウド上で動くJupyter Notebookみたいな感じです。

使用するライブラリをインポートします。

%tensorflow_version 2.x
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

使用するライブラリをインポートします。
tensorflow: Googleの機械学習のためのライブラリです。機械学習でよく使用されるアルゴリズムがぎゅっと詰まってます。冒頭の一文は、tensorflowの2.xを使用するための魔法の呪文です。
numpy: 数値計算を高速で処理してくれるライブラリです。個人的には数式をnumpyに落とし込むのって難しく感じます。
(全然関係ないですが「なむぱい」と平仮名にすると一気にキュートな印象になりますよね)
matplotlib: グラフを描画してくれるライブラリです。
全ての文に共通して「 as〇〇」と語尾についていますが、読み込んだライブラリを以下のように略して呼びますよっていう意味です。ギャルがすぐ言葉を略すのと同じ感覚です。


データセットの中身をみてみよう

データを読み込みます。
実業務ですと、データセットをかき集め、成形し利用できるように加工する作業は非常に骨の折れる作業です。
ですが、tensorflowを利用すると以下のようにデータの読み込み、実行が簡単にできます。ありがたいですね。
(最初の一文は、「tensorflowに組み込まれているkerasの中にあるMNISTのデータセットを読み込むよ」という意味です。kerasは今ここで初めて出てきましたが、tensorflowと同じ機械学習用のライブラリです。独立しているkerasもありますが、tensorflowの中に同包されているものもあります。)

mnist = tf.keras.datasets.mnist.load_data()
train, test = mnist
(x_train, y_train),(x_test, y_test) = mnist

読み込んだデータの形を確認してみましょう

print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)

(結果)
(60000, 28, 28) # x_train: 28*28ピクセルの画像が60,000枚
(60000,)  #  y_train: 0~9までの正解データが60,000枚
(10000, 28, 28)  # testデータ: 28*28ピクセルの画像データが10,000枚
(10000,)  # 0~9までの正解データが10,000枚

試しにx_trainの1枚目のデータを見てみましょう

x_train[0]

(結果)
大量の数字が返ってくると思います。この数字の羅列は何なんですかね。
もう少し特徴を把握するために、最大値と最小値を見てみましょう。

np.max(x_train[0])
np.min(x_train[0])

(結果)
255
0

255というとピンとくる人もいるのではないでしょうか。
この大量の数字の正体は、0~255の値をとるRGBです。
カラー画像には1ピクセルあたりR(赤)、G(緑)、B(青)の3値(RGB値)を持っています。
とはいえ私たちはRGB値の羅列を見たとて、それがどんな画像か理解することはできませんので、matplotlibを使って可視化して見ましょう。

plt.imshow(x_train[0])

(結果)

スクリーンショット 2020-04-13 18.01.48

見える、、見えるぞ!画像だ、、!
(にしてもこの人字汚いですね。なんて書いてあるのかよくわかりません)

データの特徴を大まかに把握できました。
早速、モデルを構築したい所ですが、逸る気持ちをグッと堪え、もう一手間加えていきましょう。正規化と呼ばれる処理を施します。

x_train = x_train / 255.0
x_test = x_test / 255.0

正規化とは、データを0~1の範囲に収まるように加工することです。
このように処理することで学習の効率が上がるらしいです。(詳しくは知らんです)
ただし、一点ご注意を。何でもかんでも学習データを0~1に変換すれば言い訳ではありません。学習データの各成分の大きさを均等に扱っていいかわからない場合は、各成分ごと正規化するなど工夫する必要がある場合がありますよ。

モデルを構築してみよう

準備は整いました!モデルを作ってみましょう。

# 入力層の設定
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Input((28, 28))) # 入力層は28*28
model.add(tf.keras.layers.Flatten()) # 一次元配列に変換

1行目: モデルの箱を作ります。この箱の中にモデルの特徴を決める層をaddメソッドでポンポン追加していくイメージです。
(今回使用しているSequentialモデルはシンプルで初心者向きで使いやすいです)
2行目: 入力データは28*28だよって教えてあげます。
3行目: (28,28)二次元配列を(1,784)の一次元配列に変換してあげます。

# 中間層の設定
model.add(tf.keras.layers.Dense(128)) # 128個に全結合
model.add(tf.keras.layers.Activation(tf.keras.activations.relu)) # 中間層の活性化関数の設定
model.add(tf.keras.layers.Dropout(0.2)) # 20%ドロップアウトさせる

1行目: 784個の入力層の要素を128個に全結合します。128って数字の意味は分かりません。偉い人が128個に全結合するといい感じになるって見つけてくれたんですかね。
2行目: 中間層の活性化関数にReLu関数を適用させます。
レル関数とかランプ関数とかって読みます。
勾配消失問題を回避できるため、中間層でよく用いられます。
3行目: Dropoutとは、学習ごとにランダムにノード(道)を切ってしまう手法です。過学習を防ぐために行います。ここでは20%の道をカットしちゃいます。

model.add(tf.keras.layers.Dense(10)) # 出力層の設定
model.add(tf.keras.layers.Activation(tf.keras.activations.softmax)) # 出力層の活性化関数の設定

1行目: 0~9の数字に分類分けするため、出力層は10個です。
2行目: 出力層の活性化関数にSoftmax関数を適用させます。
Softmax関数は合計値を1にしてくれる関数です。つまり、全ての分類を合計した時100%になるように算出してくれます。
0である確率10%、1である確率5%、2である確率、、、と計算し、0~9のうちもっとも確率の高いものを予測結果として出してくれるわけです。

model.compile(
   optimizer=tf.keras.optimizers.Adam(),
   loss=tf.keras.losses.sparse_categorical_crossentropy,
   metrics=[tf.keras.metrics.sparse_categorical_accuracy]
)
model.fit(x_train, y_train, epochs=5)​

長い、長いですね。
ここではモデルを学習する方法を定義しています。
optimize: 逆伝播のモジュールです。ここの書き方はこういうもんだと思って受け入れましょう。わけわからん時は公式のドキュメントを読むと良いです。
losses: 誤差関数のモジュールです。
metrics: 評価関数(モデルの性能)のモジュールです。

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

epochsは、trainデータを何回学習させるかということです。
ここでは5回繰り返し学習させます。

model.evaluate(x_test, y_test)

(結果)
313/313 [==============================] - 0s 1ms/step - loss: 0.0750 - sparse_categorical_accuracy: 0.9771
[0.07500645518302917, 0.9771000146865845]
モデルの評価です。97%、優秀なモデルちゃんができました。
お次は、せっかく作った優秀なモデルちゃんをテストしてみましょう。

test_data = x_test[0]
plt.imshow(test_data)

(結果)

スクリーンショット 2020-04-18 22.26.16

この7の画像を投入してみてきちんと予測ができるか試してみます。

print(test_data.shape)
print(test_data[np.newaxis].shape)

(結果)
(28,28)
(1,28,28)
ここでは、model.predictに入力する形にarrayを変換します。
確かに、さっき使った学習データの形は2行目の形でしたよね。

pred = model.predict(test_data[np.newaxis])
np.argmax(pred)

(結果)
7
おおおおお見事!7と見事当てることができました。

自分で書いた数字を予測させよう

お次は自分で書いた数字を認識させてみましょう。
Google Drive を Colab にマウントします。

from google.colab import drive
drive.mount('/content/drive')

こちらを実行すると認証のURLが出てきます。リンク先でGoogleアカウントにログインし、トークンをColabにコピペするとマウント完了です。

認識させたい数字を書いた画像ファイルをGoogleドライブにアップしましょう。私はLineCameraを使って書きました。使うアプリはなんでもOKなので正方形の画像に数字を書きましょう。

スクリーンショット 2020-04-18 23.32.15

選ばれたのは8でした。やや左に傾いていてキュートです。
この子を予測させていきます。

from PIL import Image
img = Image.open('drive/My Drive/画像のファイル名').convert('L')

1行目: PILという画像処理ライブラリをインポートします。
2行目: 画像ファイルの読み込みです。

img.thumbnail((28, 28)) # 28*28に変換
img = np.array(img) # numpy arrayに変換
test_data[np.newaxis].shape 
plt.imshow(img)

(結果)

スクリーンショット 2020-04-18 23.44.12

28*28ピクセルの画像に変換し、データのarrayを整えます。
先ほどと同じような少し荒い画像に変換できましたね。
それでは、いざ予測です。

pred = model.predict(img[np.newaxis])
np.argmax(pred)

(結果)
8
優秀!うちの子優秀です!(親バカ)
自分で書いた数字を認識させると、モデルに対する愛着5割増になります。
あえて汚く書いた数字を予測させてモデルを弄んだりするのも楽しいのでおすすめです。皆さんも週末の暇潰しに、是非どうぞ。

以上、MNISTを使った手書き文字認識モデルの構築でした。
最後までお読みくださいまして、ありがとうございました!

この記事が気に入ったらサポートをしてみませんか?