【初心者】はじめての深層学習による手書き文字認識(MNIST)

スポンサーリンク
Python

今回は、機械学習を学び始めるなら、必ず通る道である「手書き文字認識」です。

手書き文字認識は、MNIST(Modified National Institute of Standards and Technology database)のデータセットで、scikit-learnや深層学習で使われるkeras、データ分析のコンペティションであるKaggleからデータを取り込むことができます。このデータセットは、0~9 の手書き数字の画像データセットです。

以前の過去記事ではK近傍法を使って、正解率97.2%でした。

はじめての手書き文字認識
今回は、機械学習を学び始めるなら、必ず通る道である「手書き文字認識」です。 手書き文字認識は、MNIST(Modified National Institute of Standards and Technolog...

深層学習ではどんな結果になるでしょうか。

今回はこの「手書き文字認識」について、深層学習を試していきましょう。解析はANACONDAからJupyter Labで行っていきます。

環境構築は以下の過去記事を参照してください。

【2021年最新】WindowsでAnacondaをインストールする方法、初心者がPythonの環境を構築する
Pythonの環境構築におすすめなのが、「anaconda」です。anacondaのなかにある、Jupyter Labはデータ解析や機械学習に非常に相性がいいです。理由は、コードを実行すると結果を返してくれます。その結果をみて、新たなコード
M1 mac でanacondaをインストールし、Pythonを動作確認
M1 mac miniを購入したので、anacondaのインストールし、Pythonの動作を確認しました。 以前はWindowsでのanacondaのインストール方法を提示しました。 macでのイ...

必要なライブラリをインポート

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D

手書き文字認識をscikit-learnからインポートしましょう。

# Scikit-learn(Sklearn)のインポート
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784',version=1)
X, y = mnist['data'],mnist['target']
print(X.shape)
print(y.shape)

出力:
(70000, 784)
(70000,)

データの形を整える

 

# すべでのデータを訓練用とテスト用に分割する
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25,
    random_state=1,
    stratify=y)

# 訓練データを訓練データと検証データに分割する
X_train, X_valid, y_train, y_valid = train_test_split(
    X_train, y_train, test_size=0.1,
    random_state=1,
    stratify=y_train)

データを確認します。

print("X_train.shape = {}".format(X_train.shape))
print("y_train.shape = {}".format(y_train.shape))
print("X_valid.shape = {}".format(X_valid.shape))
print("y_valid.shape = {}".format(y_valid.shape))
print("X_test.shape = {}".format(X_test.shape))
print("y_test.shape = {}".format(y_test.shape))

出力:
X_train.shape = (47250, 784)
y_train.shape = (47250,)
X_valid.shape = (5250, 784)
y_valid.shape = (5250,)
X_test.shape = (17500, 784)
y_test.shape = (17500,)

# 28x28x1のサイズへ変換し、数値を0~1に変換するために、255で割る
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1)/255.
X_valid = X_valid.reshape(X_valid.shape[0], 28, 28, 1)/255.
X_test = X_test.reshape(X_test.shape[0], 28, 28, 1)/255.

データを変換したので、確認してみましょう。

print("X_train.shape = {}".format(X_train.shape))
print("y_train.shape = {}".format(y_train.shape))
print("X_valid.shape = {}".format(X_valid.shape))
print("y_valid.shape = {}".format(y_valid.shape))
print("X_test.shape = {}".format(X_test.shape))
print("y_test.shape = {}".format(y_test.shape))

出力:
X_train.shape = (47250, 28, 28, 1)
y_train.shape = (47250,)
X_valid.shape = (5250, 28, 28, 1)
y_valid.shape = (5250,)
X_test.shape = (17500, 28, 28, 1)
y_test.shape = (17500,)

データのラベル(手書き文字がどの数字か)を確認してみましょう。

# 訓練データを確認してみる
for i in range(5):
    print(y_train[i])

出力:
4
4
9
4
6

データをダミー変数に変換する

from keras.utils import np_utils

# ターゲットとなる yをダミー変数(one-hot-encode)にする
y_train = np_utils.to_categorical(y_train, 10)
y_valid = np_utils.to_categorical(y_valid, 10)
y_test = np_utils.to_categorical(y_test, 10)

データを確認してみる

# ダミー変数化した訓練データを確認してみる
for i in range(5):
    print(y_train[i])

出力:
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]

1列目のダミー変数前は4でした。ダミー変数後は0から数えて4番目が1になっていますね。3列目のダミー変数前は9でした。ダミー変数後は0から数えて9番目が1になっています。これが、ダミー変数です。

深層学習(畳み込みニューラルネットワーク)を実行する

import keras

model = keras.models.Sequential([
    keras.layers.Conv2D(32, kernel_size=3, padding="same", activation="relu"),
    keras.layers.Conv2D(64, kernel_size=3, padding="same", activation="relu"),
    keras.layers.MaxPool2D(),
    keras.layers.Flatten(),
    keras.layers.Dropout(0.25),
    keras.layers.Dense(128, activation="relu"),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(64, activation="relu"),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(10, activation="softmax")
])

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

ディープラーニングを実行します。

%%time  # コードの実行時間を測定するためのコードです
# モデルの訓練(エポック 10)
model.fit(X_train, y_train,
          epochs=10,
          validation_data=(X_valid,y_valid))

出力:

深層学習による検証データでは99%以上です。なかなかの結果ですね。

学習するのにかかった時間は、26分以上でした。結構かかりましたね。

では、学習に使っていないデータであるテストデータで、正解率を確認してみましょう。

model.evaluate(X_test, y_test)

出力:

正解率は98.98%でしたね。なかなか99%以上にならないですね。Kaggleでは99%以上のコードがあるので、参考にしてみて下さい。

結果の確認

from sklearn.metrics import accuracy_score

y_pred = model.predict(X_test)

y_test = np.argmax(y_test, axis=1)
y_pred = np.argmax(y_pred, axis=1)

from sklearn.metrics import classification_report
print("Classification report for classifier %s:\n%s\n"
      % (model, classification_report(y_test, y_pred)))

出力:

もう少し分かりやすくしてみましょう。
縦軸が実際の結果で、横軸は深層学習モデルによる予測です。それらの正解率を表しています。

from sklearn.metrics import confusion_matrix

confusion_mtx = confusion_matrix(y_test, y_pred)

f, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(confusion_mtx, annot=True, linewidths=0.01,
            cmap="Greens",linecolor="gray", fmt= '.1f',ax=ax)

plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix")
plt.show()

出力:

自分で書いた手書き文字でモデルの性能を確認しましょう

from PIL import Image

im = Image.open('8.jpg')   # 手書き文字をJPGで保存したデータ(8.jpg)を取り込む
fig, ax = plt.subplots()   # axはAxesオブジェクト
ax.imshow(im)

出力:

明度を調整してます。

from PIL import ImageEnhance

im_enhanced = ImageEnhance.Brightness(im).enhance(1.5)  # Brightnessで明暗を調整する
fig, ax = plt.subplots()
ax.imshow(im_enhanced)

出力:

白黒に変換します。もともと白黒なので、分かりにくいですが。

im_gray = im_enhanced.convert(mode='L')   # グレースケールにする
fig, ax = plt.subplots()
ax.imshow(im_gray, cmap='gray')

出力:

画像を28✕28ピクセルに変換して、作成した深層学習モデルに当てはまるようにします。

from PIL import ImageOps

im_inverted = ImageOps.invert(im_28x28)
fig, ax = plt.subplots()
ax.imshow(im_inverted, cmap='gray')

出力:

データをモデルに予測できるように変換していきます。

X_im2d = numpy.asarray(im_inverted)  # 2次元のndarrayに変換
X_im2d  # 表示する

出力:

X_im1d = X_im2d.reshape(-1)  # 1次元のndarrayに変換する
X_im1d  # 表示する

出力:

このデータを変換する。

a = X_im1d.reshape(1,28, 28, 1)/255.
a.shape

出力:
(1, 28, 28, 1)

予測結果を確認する

np.set_printoptions(precision=3, suppress=True)
pred_a = model.predict(a)
pred_a

出力:
array([[0. , 0. , 0. , 0.001, 0. , 0. , 0. , 0. , 0.999, 0. ]], dtype=float32)

これは、0~9までの数字かどうかを割合で示しています。
つまり、3の確率は0.1%、8の確率は99.9%だと予測しています。

print("この数字は {} です。" .format(np.argmax(pred_a)))

出力:
この数字は 8 です。

こうすれば、分かりやすい結果になりますね。
入力した図を見れば分かりますが、8ですね。正解です。

コメント

タイトルとURLをコピーしました