ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

Kaggler-ja in-classコンペ途中経過 〜Fashion MNISTをkerasで〜

現在、7月9日より開催されているkaggler-jaというslackグループのin-classコンペに参加しています。
今日はその途中経過を報告したいと思います。
また、このモデルを構築するまでに試行錯誤したことなど、最後にまとめてありますので良ければご参考になさってください。

コンペの内容

詳細は下記URLを参照していただければ分かると思いますが、簡単に言うとFashion MNISTのデータで学習モデルを作成し、その精度を競うコンペです。

Kaggler-ja In-class Competition 1

ソースコード

始めに、自分の提出したモデル構築のソースを載せておきます。

# ライブラリのimport
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import seaborn as sns
import itertools

from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from keras import initializers
from keras.utils.np_utils import to_categorical # convert to one-hot-encoding
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D, Activation, MaxPooling2D
from keras.optimizers import RMSprop, Adadelta
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ReduceLROnPlateau, EarlyStopping

# データの読み込み
train = pd.read_csv('train.csv')

# 正解ラベルと入力データに分割
Y_train = train['label']
X_train = train.drop(labels=['label'], axis=1)

# 正規化
X_train = X_train / 255.0

# スケールの変換
X_train = X_train.values.reshape(-1, 28, 28, 1)

# 正解ラベルをone-hot-vectorに変換
Y_train = to_categorical(Y_train, num_classes=10)

# 入力データを、訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X_train, Y_train, test_size = 0.1, random_state=2)

# コールバック関数の設定
# このコールバックは評価値を監視し,'patience'で指定されたエポック数の間改善が見られなかった場合,学習率を減らします.
learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc', 
                                            patience=3, 
                                            verbose=1, 
                                            factor=0.5, 
                                            min_lr=0.00001)
# 過学習の抑制
# patientce = 20とし、val_lossの最小値が20回更新されなければ計算ストップ
early_stopping = EarlyStopping(monitor='val_loss', patience=20 , verbose=1)

# 画像の水増し処理
datagen = ImageDataGenerator(
        featurewise_center=False,  # set input mean to 0 over the dataset
        samplewise_center=False,  # set each sample mean to 0
        featurewise_std_normalization=False,  # divide inputs by std of the dataset
        samplewise_std_normalization=False,  # divide each input by its std
        zca_whitening=False,  # apply ZCA whitening
        rotation_range=5,  # randomly rotate images in the range (degrees, 0 to 180)
        horizontal_flip=False,  # randomly flip images
        vertical_flip=False)  # randomly flip images

#  水増し画像を訓練用画像の形式に合わせる
datagen.fit(X_train)

# CNNのモデル定義
model = Sequential()

model.add(Conv2D(filters = 32, kernel_size = (3,3),padding = 'Same', 
                 activation ='relu', input_shape = (28,28,1)))
model.add(Conv2D(filters = 32, kernel_size = (3,3),padding = 'Same', 
                 activation ='relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Dropout(0.25))

model.add(Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', 
                 activation ='relu'))
model.add(Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', 
                 activation ='relu'))
model.add(MaxPool2D(pool_size=(2,2), strides=(2,2)))
model.add(Dropout(0.5))

model.add(Flatten())
model.add(Dense(256, activation = "relu"))
model.add(Dropout(0.5))
model.add(Dense(10, activation = "softmax"))

# オプティマイザ
optimizer =  Adadelta(lr=1.0, rho=0.95, epsilon=None, decay=0.0)

# コンパイル
model.compile(optimizer = optimizer , loss = "categorical_crossentropy", metrics=["accuracy"])

# エポック数とバッチサイズ設定
epochs = 100
batch_size = 100

# 学習
history = model.fit_generator(datagen.flow(X_train,y_train, batch_size=batch_size),
                              epochs = epochs, validation_data = (X_test,y_test),
                              verbose = 1, steps_per_epoch=X_train.shape[0] // batch_size
                              , callbacks=[learning_rate_reduction, early_stopping])

# 評価
score = model.evaluate(X_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
# =>Test loss: 0.16710302633792162
# =>Test accuracy: 0.94

94%の精度なので、まずまずかなぁと思いました。

精度と損失関数の遷移グラフも表示してみます。

#Accuracy
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

#loss
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

f:id:taxa_program:20180806204307p:plain

f:id:taxa_program:20180806204325p:plain

予測

このモデルを使用して予測し、その結果を提出しました。

# 予測
test = pd.read_csv('test.csv')

# テストデータも正規化を行う
test = test / 255.0

# テストデータのReshape
test = test.values.reshape(-1, 28, 28, 1)

# predict results
results = model.predict(test)

# select the indix with the maximum probability
results = np.argmax(results,axis = 1)
results = pd.Series(results,name="label")

submission = pd.concat([pd.Series(range(1,10001),name = "id"),results],axis = 1)
submission.to_csv("loss_16710_acc_94000.csv",index=False)

精度結果

結果がこちら f:id:taxa_program:20180806204536p:plain

94.9%の精度で、現在5位です。
DLのみで学習させた割には、良いスコアが出たかな?と言う印象です。

この結果に到るまでに試行錯誤したこと

参考にしたnotebook

まず、下記notebookを参考に、データをFashion MNISTに置き換えて学習させました。

Introduction to CNN Keras - 0.997 (top 6%) | Kaggle

この時点で、精度は90%弱でした。

以降、精度に影響を与えそうな部分のコードを修正しながら、トライアンドエラーを繰り返しました。

オプティマイザの検証

参考にしたnotebookだと、RMSprop をオプティマイザとして採用していましたが、今回のデータセットでは、違うオプティマイザの方が良い精度が出る可能性もあると考え、一通りのオプティマイザで実験しました。

kerasで使用できるオプティマイザ(最適化アルゴリズム)は、公式ドキュメントに載っています。

最適化 - Keras Documentation

今回作成したモデルは、この中で一番良い精度が出たAdadeltaを使用しました。

レイヤーの検証

次に、レイヤーの検証を行いました。
ここはレイヤー修正→検証→修正→検証・・・の繰り返しでした。
filterの数を変更してみたり、Dropoutを減らしたり増やしたり、BatchNormalizationレイヤーを追加してみたり・・・

結果的に冒頭で記述したレイヤーに落ち着きました。

この辺りで精度が93%程度まで向上しました。

画像の水増し処理

最後に、データの前処理部分で精度が向上しないか、検証しました。
参考にしたnotebookでは、MNISTデータを使用していました。
MNISTデータは、手書き数字の画像データですが、間違えやすいような数字(実際には人間でも読み間違えてしまうようで画像)も含まれており、データの前位処理部分で、画像の回転、画像の水平移動、上下移動、ズームなど、様々な方法で水増し処理が実装されています。

しかし、今回のデータを見てみると、そこまで変なデータ(人間でも間違えてしまうような、きわどいデータ)は含まれていないと判断し、水平移動や上下移動の前処理は省きました。

その結果、94%程度まで精度が向上しました。

まとめ

以上が、自分が試行錯誤した主たる部分です。

コンペ期間はまだ数日残っているため、もう少し精度を向上できるように、頑張ってみたいと思います。