kaggle の Digit Recognizer に挑戦する(1)
今回は kaggle の Digit Recognizer というコンペティションに挑戦したいと思います。このコンペティションは簡単に言うと画像認識で、0〜9の数字を手で書いた画像があるので、それが何の数字なのかを予測する、というものです。なお、開発は Jupyter Notebook を利用し、環境は Docker tensorflow/tensorflow:latest-jupyter
イメージで構築しています。
MNIST のデータ
実は MNIST にも同じようなデータがあり、こちらの方が理解しやすいので、まずはこちらを説明します。手書きの数字画像は縦28×横28ピクセルの画像で、各ピクセルには色の濃淡を表す0〜255までの整数が入っています。この数値は、0だと真っ白で、255だと真っ黒を表します。MNIST のデータは解説しているサイトがたくさんありますので、例えばこちらのサイトを参照してもらえば、何を言っているのかすぐに理解してもらえると思います。
では、実際にダウンロードして確かめてみます。
import keras from keras.datasets import mnist from keras.models import Sequential from keras.layers import Dense, Dropout from keras.utils import to_categorical import matplotlib.pyplot as plt import seaborn as sns import numpy as np import pandas as pd
(xm_train, ym_train), (xm_test, ym_test) = mnist.load_data()
print(xm_train.shape)
(60000, 28, 28)
60000 × 28 × 28 の numpy.ndarray であることが確認できます。縦28×横28ピクセルの画像データが60000枚あるということです。matplotlib の imshow() を使うと、画像で表示することができます。
plt.imshow(xm_train[0])
対応するラベル y_train を確認します。
print(ym_train[0])
5
手書き数字画像と、ラベルが一致することが確認できました。
kaggle のデータ
kaggle のデータはMNISTのデータとはちょっと違います。実際にデータをダウンロードし、データを読み込んで中身を確認します。
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)
(42000, 785) (28000, 784)
訓練データは 42000 × 785 になっています。42000は手書き数字画像の枚数です。785は、1列目はラベル、即ち手書き画像の数値が何かを表したもので、2〜785列目は縦28×横28ピクセルの画像データを横一列に並べたものものです。28 × 28 = 784 なので数も合いますね。テストデータは、訓練データと比較して、ラベルの列が無いものになります。
実際にデータを加工して確かめてみます。
# 1列目と2〜785列目を分ける y_train, x_train = np.split(train, [1], 1) print(x_train.shape) print(y_train.shape)
(42000, 784) (42000, 1)
# 1枚分を取り出し 28 * 28 の形にして表示させてみる plt.imshow(x_train[0].reshape(28, 28))
# ラベル確認 print(y_train[0])
[1.]
画像が表示され、ラベルと一致することが確認できました。
データ準備
学習をする前にデータの下準備をしていきます。
# 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])
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
ラベルの方を one-hot 表現にしています。訓練データの最初の数値は1でしたから、(0から数えて)1を表すところが1になっていて、後は全て0になっていることが確認できます。
学習
それでは、ニューラルネットワークを構築して学習していきます。
# ニューラルネットワーク構築 model = Sequential() # [中間層] ユニット数:12, 活性化関数:relu, 入力次元数:784 model.add(Dense(units=12, activation='relu', input_dim=784)) # [出力層] ユニット数:10, 活性化関数:ソフトマックス model.add(Dense(10, activation='softmax')) # 損失関数:categorical_crossentropy, 最適化アルゴリズム:rmsprop model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy']) model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 12) 9420 dense_1 (Dense) (None, 10) 130 ================================================================= Total params: 9,550 Trainable params: 9,550 Non-trainable params: 0 _________________________________________________________________
各層の設定はソース内のコメントとして書きましたので、参照してください。
それでは学習していきます。
# バッチサイズ:128, 反復数:20 history = model.fit(x_train, y_train, batch_size=128, epochs=20, verbose=1)
結果をグラフで表示します。
sns.lineplot(data=history.history["loss"])
sns.lineplot(data=history.history["accuracy"])
学習が進むにつれ、誤差が減少し、精度が増加していくことがわかります。
設定を変える
最適化アルゴリズムを adam に変えて試してみます。具体的には下記の optimizer 引数を adam に変更します。他の設定は全く同じです。
# 損失関数:categorical_crossentropy, 最適化アルゴリズム:adam model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
誤差、精度ともに少し改善したようです。
中間層のユニット数を128に増やしてみます。最適化アルゴリズムは adam を利用し、その他設定は同じです。具体的な修正箇所は下記です。
# [中間層] ユニット数:128, 活性化関数:relu, 入力次元数:784 model.add(Dense(units=128, activation='relu', input_dim=784))
誤差、精度ともに大幅に改善されたようです。 evaluate メソッドで具体的な数値も比較してみます。
loss, accuracy = model.evaluate(x_train, y_train, verbose=1)
loss | accuracy | |
---|---|---|
rmsprop | 0.1851324439048767 | 0.9472380876541138 |
adam | 0.1607203185558319 | 0.9545952677726746 |
adam + Unit数128 | 0.00736998999491334 | 0.9989285469055176 |
数値を見ても改善されているのが確認できます。
予測
では、 adam + 中間層ユニット数128 の設定で、テストデータを予測したいと思います。
predicts = model.predict(x_test) # 0 - 9 の10列のうち、最も値の大きいものが予測された値なので、それを取り出す predicts_label = np.argmax(predicts, axis=1)
# CSVを出力するためのデータを生成 df = pd.DataFrame({ 'imageId': list(range(1, len(predicts)+ 1)), 'Label': predicts_label }) print(df.tail())
imageId | Label | |
---|---|---|
27995 | 27996 | 9 |
27996 | 27997 | 7 |
27997 | 27998 | 3 |
27998 | 27999 | 9 |
27999 | 28000 | 2 |
# CSV出力 df.to_csv("/root/practice/digit/csv/predictions.csv", index=False)
無事にCSVが出力されたので、kaggle に提出します。
おおおおおおおおおおお!これも下から数えた方が早いですが、自分の名前が Leaderboard に刻まれました!!