kaggle の Digit Recognizer に挑戦する (3)

前回、kaggle の Digit Recognizer コンペティションで、畳み込みニューラルネットワークを利用しました。このコンペティションの Code で最も投票数を集めているのが Introduction to CNN Keras - 0.997 (top 6%) です。この中に、データを拡張して学習する方法が紹介されていますので、今回はこれを試していきたいと思います。

データ拡張

今回試すデータの拡張とは具体的に言うと、元々の手書き文字画像から、角度を少し変えてみたり、上下左右に少し動かしてみたり、大きさを少し変えてみたりした画像を生成して、それらの画像も学習に利用する、というものです。これで大幅に精度が上がったと書かれています。これらの画像の生成には、keras の ImageDataGenerator が利用できるので、それを使っていきます。

まずは、畳み込みニューラルネットワークコンペティションのことは一旦忘れて、単純に ImageDataGenerator を使って画像を生成してみます。データの下準備は、前回までとほぼ同じです。

from keras.models import Sequential
from keras.layers import Dense, Conv2D, MaxPooling2D, Flatten
from keras.utils import to_categorical
from keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
train = np.loadtxt('/root/practice/digit/csv/train.csv', skiprows=1, delimiter=',')
x_test = np.loadtxt('/root/practice/digit/csv/test.csv', skiprows=1, delimiter=',')
# print(train.shape)
# print(x_test.shape)

# 1列目と2〜785列目を分ける
y_train, x_train = np.split(train, [1], 1)
# print(x_train.shape)
# print(y_train.shape)

# 形状を枚数 × 28 × 28 × 1 に変形する
x_train = x_train.reshape(42000, 28, 28, 1)
x_test = x_test.reshape(28000, 28, 28, 1)
# print(x_train.shape)
# print(x_test.shape)

# 1枚分を取り出し表示させてみる
plt.imshow(x_train[3])

# ラベル確認
# print(y_train[3])

ここから ImageDataGenerator を使います。 今回はこの手書き数字「4」の画像を元画像として、これをコピーして3枚にし、これを使っていきます。

sample_images = np.array([x_train[3].copy(), x_train[3].copy(), x_train[3].copy()])

# -90 〜 90の範囲でランダムに回転
datagen = ImageDataGenerator(rotation_range=90)

# 3枚の画像を生成するので batch_size = 3  戻り値はイテレータ
g = datagen.flow(sample_images, batch_size=3)
batches = g.next()

#(枚数, 縦サイズ, 横サイズ, チャンネル数) --> (3, 28, 28, 1) になる
# print(batches.shape)

# 画像表示
plt.subplot(1, 3, 1)
plt.imshow(batches[0])
plt.subplot(1, 3, 2)
plt.imshow(batches[1])
plt.subplot(1, 3, 3)
plt.imshow(batches[2])

これで実際に画像を表示させてみると、「4」が回転している画像が3枚できたことがわかります。

学習

では、この ImageDataGenerator で、様々な画像データを生成して、学習していきます。CSVからデータを読み込み、訓練データと教師データを分ける処理などは上記と同じで、データの下準備と、ニューラルネットワークの構築部分は前回と同じです。

# 0 - 1 の間の数値にする
x_train  = x_train.astype('float32')
x_test   = x_test.astype('float32')
x_train /= 255
x_test  /= 255

# one-hot 表現にする
y_train = to_categorical(y_train, 10)

# モデルは前回の設定と同じ
model = Sequential()
model.add(Conv2D(filters=32, kernel_size=(4, 4), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(filters=64, kernel_size=(4, 4), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dense(10, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.summary()

それでは、ここから画像データを拡張していきます。

epochs = 30
batch_size = 86

datagen = ImageDataGenerator(
    rotation_range=10,      # ランダムに10度回転する
    zoom_range=0.1,         # ランダムに10%ズームする
    width_shift_range=0.1,  # ランダムに横方向に10%ずらす
    height_shift_range=0.1  # ランダムに縦方向に10%ずらす
)

# 第一引数は画像データ、第二引数はラベル
generator = datagen.flow(x_train, y_train, batch_size=batch_size)
# 第一引数はデータを生成する generator、引数 steps_per_epoch は1エポック当たり generator を呼び出す回数
history = model.fit_generator(
    generator,
    epochs=epochs,
    steps_per_epoch=(x_train.shape[0] // batch_size),
)

generator が生成するデータで学習をするときは、fit_generator メソッドを利用します。引数 steps_per_epoch は画像データ数をバッチサイズで割ったものを指定しています。これで1エポック当たりで、元々の画像データ数と同じ数のデータが生成されます。

学習が終わったら、誤差と精度を求めます。

loss, accuracy = model.evaluate(x_train, y_train)
print(loss)
print(accuracy)

前回(一つ前のブログ記事の (1) + (4) + (5) + (6))の結果と比較します。

loss accuracy
前回 0.008217111229896545 0.9972618818283081
今回 0.011472832411527634 0.9966190457344055

前回と比較して誤差・精度共に良い結果になりませんでした。
この学習後のニューラルネットワークで予測を行い、その結果を kaggle に提出してみます。

predicts = model.predict(x_test)

# 最も値の大きいものが予測された値なので、それを取り出す
predicts_label = np.argmax(predicts, axis=1)

# CSVを出力するためのデータを生成
df = pd.DataFrame({
    'imageId': list(range(1, len(predicts)+ 1)),
    'Label': predicts_label
})

# CSV出力
df.to_csv("/root/practice/digit/csv/predictions.csv", index=False)

すると、なんとスコアが更新されました!!!!!!!!!

訓練データに対しては前回の結果を上回ることはできませんでしたが、テストデータに対しては良い結果を得ることができたようです。