iMind Developers Blog

iMind開発者ブログ

TensorflowでMNISTの手書き文字をRegression

概要

Tensorflowを使ってMNISTの手書き文字をClassificationではなくRegressionで予測するという特に意味のない行為。

バージョン情報

  • tensorflow==1.3.1

学習処理

よくあるCNNでの手書き文字認識のモデル。

変わっているところはlayersの最後が Dense(1) になっているところとoptimizerがRMSPropOptimizerになっているところ。

TensorflowのチュートリアルにあるようなDence(10)にして10個のクラスを予測するのではなくDense(1)で1つの数値を予測する形になっている。

またlossにはmse(mean squared error)を使っている。

import tensorflow as tf
from tensorflow.keras import layers
from sklearn import model_selection

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape([60000, 28, 28, 1])
x_test = x_test.reshape([10000, 28, 28, 1])

# testデータの1/10をvalidationデータに使う
x_test, x_val, y_test, y_val = model_selection.train_test_split(
    x_test, y_test, test_size=0.1, stratify=y_test)

model = tf.keras.models.Sequential([
    layers.Convolution2D(64, (4, 4), activation='relu', input_shape=(28, 28, 1)),
    layers.MaxPooling2D((2, 2)),
    layers.Dropout(0.1),
    layers.Convolution2D(64, (4, 4), activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Dropout(0.3),
    layers.Flatten(),
    layers.Dense(256, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(64, activation='relu'),
    layers.BatchNormalization(),
    layers.Dense(1)
])

optimizer = tf.train.RMSPropOptimizer(0.001)
model.compile(loss='mse',
              optimizer=optimizer,
              metrics=['mae'])

tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir="logs")
history = model.fit(
    x_train,
    y_train,
    epochs=200,
    validation_split = 0.2,
    verbose=0,
    callbacks=[tensorboard_callback])

tensor boardで学習状況の確認

lossにはmseの値が入っている。

f:id:mwsoft:20200327062309p:plain

metricsで指定したmae(mean absolute error)の値。

f:id:mwsoft:20200327061323p:plain

val_lossはイマイチなことになっている。

f:id:mwsoft:20200327062910p:plain

historyの確認

historyのラストの値を確認。

pd.DataFrame(history.history).tail(1)
loss mean_absolute_error val_loss val_mean_absolute_error
199 0.375688 0.476193 0.682941 0.711386

テストデータでモデルを評価

model.evaluateで評価してみる。

import numpy as np
loss, mae = model.evaluate(x_test, y_test, verbose=2)

    #=>  - 0s - loss: 0.6494 - mean_absolute_error: 0.6806

validation dataでの結果とだいたい同じ値。

テストデータでplot

実際の画像と推定結果を並べて表示してみる。

from matplotlib import pylab as plt
f, ax_list = plt.subplots(5, 5, figsize=(10, 10))
for i, p in enumerate(model.predict(x_test[:25])):
    test_image = x_test[i].reshape(28, 28)
    ax = ax_list[int(i / 5)][i % 5]
    ax.imshow(test_image, cmap='gray', vmin=0, vmax=255)
    ax.set_title(str(p))
    ax.axis('off')

f:id:mwsoft:20200327073632p:plain

そこまで大きくはずれた結果になっている画像は見当たらない。

実際に差がいくつになっているか1%刻みでパーセンタイルで表示してみる。

percentile = np.percentile(np.abs(y_pred.reshape(len(y_test)) - y_test), q=[i for i in range(101)])
for i, p in enumerate(percentile):
    print('%d%% : %f   ' % (i, p), end='')
    if i % 5 == 4:
        print('')

    #=> 0% : 0.000015   1% : 0.010779   2% : 0.020681   3% : 0.028143   4% : 0.033981   
    #=> 5% : 0.040912   6% : 0.047804   7% : 0.055319   8% : 0.066414   9% : 0.162870   
    #=> 10% : 0.204350   11% : 0.222296   12% : 0.235256   13% : 0.246413   14% : 0.258070   
    #=> 15% : 0.268439   16% : 0.279743   17% : 0.292645   18% : 0.310170   19% : 0.324281   
    #=> 20% : 0.336159   21% : 0.346213   22% : 0.354617   23% : 0.363465   24% : 0.369943   
    #=> 25% : 0.375881   26% : 0.382982   27% : 0.391841   28% : 0.403090   29% : 0.421755   
    #=> 30% : 0.440723   31% : 0.455754   32% : 0.469524   33% : 0.482156   34% : 0.492203   
    #=> 35% : 0.502215   36% : 0.510913   37% : 0.520904   38% : 0.531517   39% : 0.542054   
    #=> 40% : 0.551697   41% : 0.562289   42% : 0.572216   43% : 0.584174   44% : 0.595658   
    #=> 45% : 0.609094   46% : 0.622925   47% : 0.644110   48% : 0.663562   49% : 0.687310   
    #=> 50% : 0.705858   51% : 0.723974   52% : 0.736585   53% : 0.749156   54% : 0.759072   
    #=> 55% : 0.770889   56% : 0.779675   57% : 0.787372   58% : 0.795873   59% : 0.805422   
    #=> 60% : 0.814253   61% : 0.821961   62% : 0.829439   63% : 0.837790   64% : 0.845326   
    #=> 65% : 0.853941   66% : 0.860545   67% : 0.866964   68% : 0.875309   69% : 0.883531   
    #=> 70% : 0.892430   71% : 0.900244   72% : 0.908062   73% : 0.915675   74% : 0.923948   
    #=> 75% : 0.931080   76% : 0.937270   77% : 0.946038   78% : 0.954966   79% : 0.965360   
    #=> 80% : 0.974450   81% : 0.984057   82% : 0.996121   83% : 1.007325   84% : 1.019098   
    #=> 85% : 1.030877   86% : 1.042961   87% : 1.056136   88% : 1.068431   89% : 1.084204   
    #=> 90% : 1.096824   91% : 1.112234   92% : 1.132662   93% : 1.153828   94% : 1.177927   
    #=> 95% : 1.206677   96% : 1.238960   97% : 1.287427   98% : 1.363786   99% : 1.635719   
    #=> 100% : 6.303422   

99%の予測は1.63の差に収まり、最もハズレている結果は6.3の差が出ている。

35%の時点で0.5を超えてしまうのでroundした結果を正とした場合、accuracyはたったの34%になる。酷い数字だ。

np.mean(np.round(y_pred).reshape(len(y_test)) == y_test)
    #=> 0.3472222222222222

類似のモデルを調べてみるとDense(64)からDense(1)に一気にやっているところがどうもイマイチなようで、下記のように16→4を挟むことでaccuracyが97%まで改善した。

model = tf.keras.models.Sequential([
    layers.Convolution2D(64, (4, 4), activation='relu', input_shape=(28, 28, 1)),
    layers.MaxPooling2D((2, 2)),
    layers.Dropout(0.1),
    layers.Convolution2D(64, (4, 4), activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Dropout(0.3),
    layers.Flatten(),
    layers.Dense(256, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(64, activation='relu'),
    layers.BatchNormalization(),
    layers.Dropout(0.5),
    layers.Dense(16, activation='relu'),
    layers.Dense(4, activation='relu'),
    layers.Dense(1)
])
loss mean_absolute_error val_loss val_mean_absolute_error
499 0.778615 0.513578 0.18739 | 0.182132
y_pred = model.predict(x_test)
np.mean(np.round(y_pred).reshape(len(y_test)) == y_test)

    #=> 0.973

普通にやったら99%以上の精度が出るものなので97%出てもだからなんだという感じではあるけど。

改定履歴

Author: Masato Watanabe, Date: 2020-03-28, 記事投稿