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

前回 kaggle の Digit Recognizer というコンペティションに挑戦しましたが、今回は、ニューラルネットワークの構成を変えて、畳み込みニューラルネットワークで学習をしたいと思います。畳み込みニューラルネットワークについては、私が理解するのに参考にした動画やサイトを、この記事の最後にリンクしておきましたので、そちらを参照してください。

データ

データの準備は前回とほぼ同じなので、記載を適宜割愛しますが、畳み込みニューラルネットワークへの入力を 28 * 28 * 1 にしないといけないので、その部分を調整していきます。

import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Conv2D, MaxPooling2D, Flatten
from keras.utils import to_categorical
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
# データ読み込み
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[0])

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

# 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)
# print(y_train[0])

畳み込みニューラルネットワーク構築

それでは、畳み込みニューラルネットワークを構築していきます。

model = Sequential()
# 畳み込み層
model.add(Conv2D(filters=32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)))
# プーリング層
model.add(MaxPooling2D(pool_size=(2, 2)))
# 出力を1次元にする
model.add(Flatten())
# 出力層
model.add(Dense(10, activation='softmax'))

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 26, 26, 32)        320       
                                                                 
 max_pooling2d (MaxPooling2D  (None, 13, 13, 32)       0         
 )                                                               
                                                                 
 flatten (Flatten)           (None, 5408)              0         
                                                                 
 dense (Dense)               (None, 10)                54090     
                                                                 
=================================================================
Total params: 54,410
Trainable params: 54,410
Non-trainable params: 0
_________________________________________________________________

Conv2Dが畳み込み層になります。各引数の意味は下記の通りです。

  • filters: 出力空間の次元=出力フィルタの数
  • kernel_size: 2次元畳み込みウィンドウの大きさ
  • activation: 活性化関数
  • input_shape: 入力次元

MaxPooling2Dはプーリング層になります。引数 pool_size はプーリングするウィンドウの大きさです。

それでは学習していきます。

history = model.fit(x_train, y_train, epochs=5)

結果をグラフで表示します。

sns.lineplot(data=history.history["loss"])

sns.lineplot(data=history.history["accuracy"])

学習が進むにつれて、誤差が減少し、精度が増加していくことがわかります。具体的な誤差と精度の数値も求めておきます(結果は後述)。

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

設定を変える

上で紹介した畳み込みニューラルネットワークの構成をベースとして、いろいろと一つずつパラメータや構成を変えて誤差と精度がどう変わるのか試してみます。変えたパラメータや構成以外はベースと同じです。今回は下記のようにパラメータ・構成を変えてみました。

(1) Conv2D の kernel_size を (4, 4) にする
(2) Conv2D の kernel_size を (2, 2) にする
(3) MaxPooling2D の pool_size を (3, 3) にする
(4) epochs を 10 にする
(5) 全結合層を追加する
(6) Conv2D, MaxPooling2D の層を追加する

「(5) 全結合層を追加する」は、具体的に下記のように設定しています。

model = Sequential()
model.add(Conv2D(filters=32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)))
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()

「(6) Conv2D, MaxPooling2D の層を追加する」は、具体的に下記のように設定しています。

model = Sequential()
model.add(Conv2D(filters=32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))  # この行を追加
model.add(MaxPooling2D(pool_size=(2, 2)))  # この行を追加
model.add(Flatten())
model.add(Dense(10, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.summary()

それぞれの結果は下記の通りになりました。

loss accuracy
ベース 0.0354999378323555 0.9896904826164246
(1) Conv2D の kernel_size を (4, 4) にする 0.03126463294029236 0.9907143115997314
(2) Conv2D の kernel_size を (2, 2) にする 0.04609178006649017 0.9873571395874023
(3) MaxPooling2D の pool_size を (3, 3) にする 0.05607537552714348 0.9817143082618713
(4) epochs を 10 にする 0.015390428714454174 0.9960476160049438
(5) 全結合層を追加する 0.012460934929549694 0.9962618947029114
(6) Conv2D, MaxPooling2D の層を追加する 0.016651742160320282 0.9953095316886902

まず、畳み込み層の畳み込みウィンドウのサイズですが、学習する画像のサイズが28 × 28だと、3 × 3 よりも少し大きくした 4 × 4 の方が良いようです。学習回数は少なかったようで、epoch=10にしたら、良い結果が出ました。全結合層、Conv2D・MaxPooling2D の層を増やした方が良い結果が出ています。

この結果を踏まえて、ベースの設定から下記の設定を加えたもので再度学習し直してみます。

(1) Conv2D の kernel_size を (4, 4) にする
(4) epochs を 10 にする
(5) 全結合層を追加する
(6) Conv2D, MaxPooling2D の層を追加する

具体的には下記のような設定になります。

model = Sequential()
model.add(Conv2D(filters=32, kernel_size=(4, 4), activation='relu', input_shape=(28, 28, 1)))  # kernel_size を (4, 4) にする
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(filters=64, kernel_size=(4, 4), activation='relu'))  # この行を追加  kernel_size を (4, 4) にする
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()

history = model.fit(x_train, y_train, epochs=10)  # epochs を 10 にする

上記の設定の結果は下記のようになりました。今までで一番良い結果になりましたね。

loss accuracy
(1) + (4) + (5) + (6) 0.008217111229896545 0.9972618818283081

予測

それでは、上記構成で予測します。前回と同様のソースです。

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)

CSVが出力されたので kaggle に提出します。

おおおおおおおおおおおお!前回よりもスコアとランキングが上がりました!!

参考にしたサイト