概要
有名なMNISTのデータを使用して手書き文字認識をする。
https://www.kaggle.com/c/digit-recognizer
データについて
https://www.kaggle.com/c/house-prices-advanced-regression-techniques/data
MNISTのデータだが用意されたcsvを使用する。
フォーマットが若干違う。
testデータが一部マスクされている。
決してtestデータを持ってきてそれを学習しようなどと考えてはいけない。
データ読み込み
import pandas as pd
import numpy as np
train = pd.read_csv("./data/digit-recognizer/train.csv")
print(train.shape)
test= pd.read_csv("./data/digit-recognizer/test.csv")
print(test.shape)
X_train = (train.iloc[:,1:].values).astype('float32') # all pixel values
y_train = train.iloc[:,0].values.astype('int32') # only labels i.e targets digits
X_test = test.values.astype('float32')
train.head()
np.random.seed(666)
データ可視化
import matplotlib.pyplot as plt
%matplotlib inline
X_train = X_train.reshape(X_train.shape[0], 28, 28)
fig = plt.figure(figsize=(9, 9))
fig.subplots_adjust(left=0, right=1, bottom=0, top=1.5, hspace=0.05, wspace=0.05)
index = 0 # 100*n
for i in range(0, 100):
ax = fig.add_subplot(10, 10, i + 1, xticks=[], yticks=[])
ax.imshow(X_train[i+index], cmap='gray')
plt.title(str(i+index)+", "+str(y_train[i+index]));

以下ざっとテストデータを確認して判別が困難と思われるもの。数値は0から始まるインデックス。

2, 7, 7, 4, 0, 6, 7, 8, 4, 7
5, 7, 8, 9, 0, 2, 4, 7, 5, 2
ではないかと思われる。
1と区別するため?7に横棒をつける文化があるらしい。ない場合1と7の判別がつかないデータがある。
8の下が見切れてしまっている。人間なら判別は難しくないが、訓練データによっては厳しいかもしれない。
そのほか4と9, 5と6 などで判別しにくい文字がある。
むしろこの手のは機械に任せてしまった方がいい感じに分類してくれるんだろうか?
学習用にフォーマット修正
ライブラリに合わせる。
y_trainをone-hotラベル化。
形状を(データ数, width, height, 1)に変更。
画素の値を0.0~1.0にする。
y_train= to_categorical(y_train)
# 学習データのフォーマットを修正
X_train = X_train.reshape(X_train.shape[0], img_rows, img_cols, 1)
X_train = X_train.astype('float32')
X_train /= 255
モデルの作成
例の如く適当。
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
from keras.utils.np_utils import to_categorical
from keras.preprocessing.image import ImageDataGenerator
batch_size = 128
# 学習回数
epochs = 3
img_rows, img_cols = 28, 28
input_shape = (img_rows, img_cols, 1)
# 最終的に出力される分類数 0~9 の10通り
num_classes = y_train.shape[1]
# モデルを作成
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
activation='relu',
input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
# 学習のためのモデルを設定
model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.Adadelta(),
metrics=['accuracy'])
# 学習
model.fit(X_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1)
スコアは0.98600。
上記で挙げた判別困難だと思ったデータは意外と正解していた。
人間と機械では判断基準が異なるので何が苦手なのかは確認しておいた方がいいかも。
実際に誤認識していたのは以下。
[3, 128, 132, 165, 409, 460, 511]

データを拡張して学習
KerasのImageDataGenerator関数を使う。
https://keras.io/ja/preprocessing/image/
# データ拡張 +-8°, 縦横+-8%, 拡大縮小+-8%
gen =ImageDataGenerator(rotation_range=8, width_shift_range=0.08,
height_shift_range=0.08, zoom_range=0.08)
batches = gen.flow(X_train, y_train, batch_size=batch_size)
# val_batches = gen.flow(X_val, y_val, batch_size=64)
# 学習開始
history=model.fit_generator(generator=batches,
steps_per_epoch=batches.n,
epochs=epochs,
verbose=1)
shear_rangeは斜め方向に引っ張ったような画像になる。
多くのケースで指定しない方がいいかもしれない。
生憎細かい調整が効かない。
Scoreは0.9971 になる。
画像認識系はレベルを上げて物理で殴る(データをいっぱい増やす)だけでそこそこ精度が出るので楽。
Epochを後2回くらい増やすともう少し精度を出せそう。
ただしPCスペックにもよるが1回あたり3時間くらいかかるので注意。
ファイルへ出力
# 学習データにフォーマットを合わせる
X_test = X_test.reshape(X_test.shape[0], img_rows, img_cols, 1)
X_test = X_test.astype('float32')
X_test /= 255
predictions = model.predict_classes(X_test, verbose=0)
submissions=pd.DataFrame({"ImageId": list(range(1,len(predictions)+1)),
"Label": predictions})
submissions.to_csv("DR.csv", index=False, header=True)
全体のソースコード
https://github.com/ninomae-makoto/kaggle/blob/master/digit-recognizer02.ipynb
kaggleカーネルの方に持ってくるべきだろうか。
残るビギナー向けコンペは
House Prices: Advanced Regression Techniques
https://www.kaggle.com/c/house-prices-advanced-regression-techniques
のみだが
ここまでやったらそれなりに戦えるようになるんだろうか?あまりそんな気がしない。