Pynote

Python、機械学習、画像処理について

TensorFlow/Keras - Fashion-MNIST で TensorFlow の基本を学ぶ。

概要

Fashion-MNIST を使った画像分類問題を TensorFlow で作成するチュートリアル

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow import keras

Fashion-MNIST

Fashion-MNIST は、60000サンプルの学習データと10000サンプルのテストデータからなる衣類品画像のデータセットである。
keras.datasets に含まれているので、keras.datasets.fashion_mnist.load_data で読み込む。

# データセットを読み込む。
(train_images, train_labels), (test_images, test_labels) = keras.datasets.fashion_mnist.load_data()

返り値は画像及びラベルを表す以下の numpy 配列である。

  • train_images: (60000, 28, 28) の uint8 型の numpy 配列。学習用の画像。
  • train_labels: (60000,) の uint8 型の numpy 配列。の学習用のラベル。
  • test_images: (10000, 28, 28) の uint8 型の numpy 配列。テスト用の画像
  • test_labels: (10000,) の uint8 型の numpy 配列。テスト用のラベル。

ラベルは画像に対応する 0, 1, \cdots, 9 のクラス IDで表され、次を意味する。

クラス ID クラス名
0 T-shirt/top
1 Trouser
2 Pullover
3 Dress
4 Coat
5 Sandal
6 Shirt
7 Sneaker
8 Bag
9 Ankle boot

# クラス ID とクラス名の対応
class_names = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
]

各画像は大きさが (28, 28) の 8bit グレースケール形式なので、matplotlib で可視化できる。
学習データの先頭25サンプルを表示する。

fig = plt.figure(figsize=(10, 10), facecolor="w")

for i in range(25):
    img, label = train_images[i], class_names[train_labels[i]]
    
    ax = fig.add_subplot(5, 5, i + 1)
    ax.imshow(img, cmap="gray")
    ax.set_xlabel(label)
    # 目盛を無くす。
    ax.set_xticks([])
    ax.set_yticks([])
    
plt.show()

画像の画素値は [0, 255] の範囲なので、255で割ることによって、[0, 1] に正規化する。

train_images = train_images / 255
test_images = test_images / 255

モデルを作成する。

全結合層型ニューラルネットワークを作成する。
入力が1チャンネルの画像なので、入力層には (N, 28, 28) のテンソルが渡される。
全結合層は (N, M) のテンソルの入力を期待しているので、まずFlatten 層で (N, 28 * 28) に配列の形状を変更する。
そのあと、出力数が128の全結合層に渡す。
今回はクラス分類問題を解くためにモデルが表す関数は10クラスの確率値を出力するようにしたいので、出力数が10、活性化関数が softmax の全結合層を出力層とする。

model = keras.Sequential(
    [
        keras.layers.Flatten(input_shape=(28, 28)),
        keras.layers.Dense(128, activation="relu"),
        keras.layers.Dense(10, activation="softmax"),
    ]
)

モデルをコンパイルする。

最適化手法は Adam を指定する。
損失関数は多クラス分類問題でラベルは 0, 1, \cdots, 9 の sparse 形式で与えられるので、sparse_categorical_crossentropy を指定する。
指標はクラス分類問題なので、損失関数の値の他に精度も確認したいので accuracy を指定する。

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

学習する。

model.fit(train_images, train_labels, epochs=5)
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 2s 42us/sample - loss: 0.4948 - accuracy: 0.8238
Epoch 2/5
60000/60000 [==============================] - 3s 44us/sample - loss: 0.3745 - accuracy: 0.8649
Epoch 3/5
60000/60000 [==============================] - 3s 55us/sample - loss: 0.3354 - accuracy: 0.8770
Epoch 4/5
60000/60000 [==============================] - 4s 70us/sample - loss: 0.3149 - accuracy: 0.8840
Epoch 5/5
60000/60000 [==============================] - 3s 44us/sample - loss: 0.2938 - accuracy: 0.8925

5エポックで約89%の精度が出た。

テストデータに対する性能を確認する。

test_loss, test_acc = model.evaluate(test_images, test_labels)

print(f"test loss: {test_loss:.2f}, test accuracy: {test_acc:.2%}")
# test loss: 0.36, test accuracy: 86.93%

学習データより若干性能は落ちるが、87%の精度が出ていることがわかる。

テストデータを推論する。

テストデータの画像をモデルに入力して、モデルの予測結果を受け取る。

predictions = model.predict(test_images)

print("predictions.shape", predictions.shape)  # predictions.shape (10000, 10)

predictions[i] は画像 test_images[i] の予測結果に対応しており、10クラスの確率値が入っている。

# test_images[0] の予測結果
pred = predictions[0]

for name, score in zip(class_names, pred):
    print(f"{name}: {score:.2%}")
# T-shirt/top: 0.00%
# Trouser: 0.00%
# Pullover: 0.00%
# Dress: 0.00%
# Coat: 0.00%
# Sandal: 0.03%
# Shirt: 0.00%
# Sneaker: 1.49%
# Bag: 0.00%
# Ankle boot: 98.48%

# 確率値なので和は1になる。
print(predictions[0].sum())  # 1.0000001

matplotlib で可視化する。

def plot_prediction(img, prediction, label):
    pred_label = np.argmax(prediction)

    fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(10, 5), facecolor="w")

    ax1.imshow(img, cmap="gray")
    ax1.set_xticks([])
    ax1.set_yticks([])
    ax1.set_xlabel(
        f"{class_names[pred_label]} {prediction[pred_label]:.2%} ({class_names[label]})",
        fontsize=15,
    )

    bar_xs = np.arange(10)  # 棒の位置
    ax2.bar(bar_xs, prediction)
    ax2.set_xticks(bar_xs)
    ax2.set_xticklabels(class_names, rotation="vertical", fontsize=15)


plot_prediction(test_images[0], predictions[0], test_labels[0])


画像1枚だけを推論する。

前項では複数の画像を一度に推論したが、今回は1枚だけ推論する。
注意点としては、モデルは (N, 28, 28) の入力を期待しているので、1枚しか画像がない場合でも、(1, 28, 28) と形状を変更してからモデルに渡す。
predict の返り値は (N, 10) なので、(1, 10) になる。

# インデックス3のサンプルを取り出す。
img = test_images[3]
label = test_labels[3]
print("img.shape", img.shape)  # img.shape (28, 28)

# バッチの次元を追加する。
x = np.expand_dims(img, axis=0)
print("img.shape", x.shape)  # img.shape (1, 28, 28)

predictions = model.predict(x)
prediction = predictions[0]

plot_prediction(img, prediction, label)