kerasでKaggleのTitanicを予想する（kaggle⑦）

#はじめに
Kaggle（カグル）のコンペに参加してみたお話です。
[前回](https://qiita.com/sudominoru/items/3e48a2daa76c085c0777)までは、scikit-learnのモデルで Titanic の学習を進めていました。
今回は、kerasで学習してみたいと思います。

#目次
1.kerasのモデルについて
2.パーセプトロン
3.Kerasでグリッドサーチ
4.学習する
5.Kerasで学習
6.結果
7.まとめ
参考
履歴

#1.Kerasのモデル

[詳細ディープラーニング](https://www.amazon.co.jp/dp/4839962510)の本によると、Kerasのモデルは以下の3つに大別されます。

・パーセプトロン
・畳み込みニューラルネットワーク
・リカレントニューラルネットワーク

パーセプトロンは基本的なニューラルネットワークの1つです。
畳み込みニューラルネットワーク（CNN）は主に画像処理で利用します。
リカレントニューラルネットワーク（RNN）は時系列データで利用します。
今回は画像処理でもなく、時系列データでもないため、パーセプトロンで学習してみます。

#2.パーセプトロン

パーセプトロンは基本的なニューラルネットワークの1つで、よく見る以下のようなイメージのモデルです。

<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nkdkccmbr/20161006/20161006215155.png" width="75%">
引用元：[ニューラルネットワーク・DeepLearningなどの画像素材　プレゼン・ゼミなどに【WTFPL】](http://nkdkccmbr.hateblo.jp/entry/2016/10/06/222245)

kerasでのパーセプトロンの構築は比較的簡単です。
以下のように、Sequentialモデルに層を追加していくことで構築できます。

```python
##############################
# モデル構築 5層パーセプトロン
##############################
def create_model_5dim_layer_perceptron(activation="relu", optimizer="adam", out_dim=100, dropout=0.5):
    
    model = Sequential()

    # 入力層 - 隠れ層1
    model.add(Dense(input_dim=len(x_train.columns), units=out_dim))
    model.add(BatchNormalization())
    model.add(Activation(activation))
    model.add(Dropout(dropout))

    # 隠れ層1 - 隠れ層2
    model.add(Dense(units=out_dim))
    model.add(BatchNormalization())
    model.add(Activation(activation))
    model.add(Dropout(dropout))

    # 隠れ層2 - 隠れ層3
    model.add(Dense(units=out_dim))
    model.add(BatchNormalization())
    model.add(Activation(activation))
    model.add(Dropout(dropout))

    # 隠れ層3 - 出力層
    model.add(Dense(units=1))
    model.add(Activation("sigmoid"))

    model.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])
    
    return model
```

Denseで層を定義します。
最初のDenseはinput_dimで入力の次元を定義します。
input_dimは入力データの次元数と一致させる必要があります。
unitsで該当する層の次元数を指定します。
今回の出力層は次元が1つ(survival 1 or 0)のため、最後はunits=1にする必要があります。

Activationで活性化関数を定義します。
出力層以外で利用する活性化関数は何でもよいですが、出力層は二値分類ですので「sigmoid」を指定します。

そのほかにもDropoutで過学習を抑制したり、BatchNormalizationでミニバッチごとに正規化を行ったりしています。

#4.Kerasでグリッドサーチ

上の5層パーセプトロンの関数では、activation や optimizer などを引数で受け取るようにしています。
このようにkerasのパーセプトロンでも、パラメータを設定する必要があります。
[この記事](https://qiita.com/sudominoru/items/1c21cf4afaf67fda3fee)では scikit-learn でグリッドサーチを行い、パラメータを調査しましたが keras でも同じようにグリッドサーチを利用することができます。
KerasClassifier を利用します。

```python
from keras.wrappers.scikit_learn import KerasClassifier
model = KerasClassifier(build_fn=create_model_5dim_layer_perceptron(input_dim), verbose=0)
```

上記のように KerasClassifier を利用することで Keras のモデルを scikit-learn のモデルと同じように扱うことができます。
あとは scikit-learn と同様に GridSearchCV を利用できます。
以下のようなコードになります。

```python
# Define options for parameters
activation = ["tanh", "relu"]
optimizer = ["adam", "adagrad"]
out_dim = [234, 468, 702]
nb_epoch = [25, 50]
batch_size = [8, 16]
dropout = [0.2, 0.4, 0.5]

param_grid = dict(activation=activation, 
                  optimizer=optimizer, 
                  out_dim=out_dim, 
                  nb_epoch=nb_epoch, 
                  batch_size=batch_size,
                  dropout=dropout)
grid = GridSearchCV(estimator=model, param_grid=param_grid)

# Run grid search
grid_result = grid.fit(x_train, y_train)

print(grid_result.best_score_)
print(grid_result.best_params_)
```

best_score_、best_params_をプリントすると以下が出力されます。

```python
0.7814285721097673
{'activation': 'relu', 'batch_size': 16, 'dropout': 0.5, 'nb_epoch': 25, 'optimizer': 'adam', 'out_dim': 702}
acc:0.72
```

#5.Kerasで学習

パラメータが決まったら Keras で学習、結果の予測を行います。
全コードは以下です。
データの準備までは[前回](https://qiita.com/sudominoru/items/3e48a2daa76c085c0777)と同じです。

```python
import numpy
import pandas
import datetime

from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler

from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.layers.core import Dropout
from keras.layers.normalization import BatchNormalization

############################################################
# SibSp　を one hot エンコーディングする
# One hot encoding SibSp
############################################################
def get_dummies_sibSp(df_all, df, df_test) :
    
    categories = set(df_all['SibSp'].unique())
    df['SibSp'] = pandas.Categorical(df['SibSp'], categories=categories)
    df_test['SibSp'] = pandas.Categorical(df_test['SibSp'], categories=categories)
    
    df = pandas.get_dummies(df, columns=['SibSp'])
    df_test = pandas.get_dummies(df_test, columns=['SibSp'])

    return df, df_test

############################################################
# Parch　を one hot エンコーディングする
# One hot encoding SibSp
############################################################
def get_dummies_parch(df_all, df, df_test) :
    
    categories = set(df_all['Parch'].unique())
    df['Parch'] = pandas.Categorical(df['Parch'], categories=categories)
    df_test['Parch'] = pandas.Categorical(df_test['Parch'], categories=categories)
    
    df = pandas.get_dummies(df, columns=['Parch'])
    df_test = pandas.get_dummies(df_test, columns=['Parch'])
    
    return df, df_test

############################################################
# Ticket　を one hot エンコーディングする
# One hot encoding SibSp
############################################################
def get_dummies_ticket(df_all, df, df_test) :

    ticket_values = df_all['Ticket'].value_counts()
    ticket_values = ticket_values[ticket_values > 1]
    ticket_values = pandas.Series(ticket_values.index, name='Ticket')
    categories = set(ticket_values.tolist())
    df['Ticket'] = pandas.Categorical(df['Ticket'], categories=categories)
    df_test['Ticket'] = pandas.Categorical(df_test['Ticket'], categories=categories)
    
    df = pandas.get_dummies(df, columns=['Ticket'])
    df_test = pandas.get_dummies(df_test, columns=['Ticket'])

    return df, df_test

############################################################
# 標準化
# Standardization
############################################################
def standardization(df, df_test) :

    standard = StandardScaler()
    df_std = pandas.DataFrame(standard.fit_transform(df[['Pclass', 'Fare']].values), columns=['Pclass', 'Fare'])
    df.loc[:,'Pclass'] = df_std['Pclass']
    df.loc[:,'Fare'] = df_std['Fare']

    df_test_std = pandas.DataFrame(standard.transform(df_test[['Pclass', 'Fare']].values), columns=['Pclass', 'Fare'])
    df_test.loc[:,'Pclass'] = df_test_std['Pclass']
    df_test.loc[:,'Fare'] = df_test_std['Fare']

    return df, df_test

############################################################
# データ準備
# prepare Data
############################################################
def prepareData() :

    ##############################
    # データ前処理
    # 必要な項目を抽出する
    # Data preprocessing
    # Extract necessary items
    ##############################
    # gender_submission.csvを読み込む
    # Load gender_submission.csv
    df = pandas.read_csv('/kaggle/input/titanic/train.csv')
    df_test = pandas.read_csv('/kaggle/input/titanic/test.csv')
    
    df_all = pandas.concat([df, df_test], sort=False)
    
    df_test_index = df_test[['PassengerId']]

    df = df[['Survived', 'Pclass', 'Sex', 'SibSp', 'Parch', 'Ticket', 'Fare']]
    df_test = df_test[['Pclass', 'Sex', 'SibSp', 'Parch', 'Ticket', 'Fare']]
    
    ##############################
    # データ前処理
    # 欠損値を処理する
    # Data preprocessing
    # Fill or remove missing values
    ##############################
    ##############################
    df = df[df['Fare'] != 5].reset_index(drop=True)
    df = df[df['Fare'] != 0].reset_index(drop=True)

    ##############################
    # データ前処理
    # ラベル（名称）を数値化する
    # Data preprocessing
    # Digitize labels
    ##############################
    ##############################
    # 性別
    ##############################
    encoder_sex = LabelEncoder()
    df['Sex'] = encoder_sex.fit_transform(df['Sex'].values)
    df_test['Sex'] = encoder_sex.transform(df_test['Sex'].values)
    
    ##############################
    # データ前処理
    # One-Hot エンコーディング
    # Data preprocessing
    # One-Hot Encoding
    ##############################
    ##############################
    # SibSp
    ##############################
    df, df_test = get_dummies_sibSp(df_all, df, df_test)
    
    ##############################
    # Parch
    ##############################
    df, df_test = get_dummies_parch(df_all, df, df_test)
    
    ##############################
    # Ticket
    ##############################
    df, df_test = get_dummies_ticket(df_all, df, df_test)
 
    ##############################
    # データ前処理
    # 数値を標準化する
    # Data preprocessing
    # Standardize numbers
    ##############################
    df, df_test = standardization(df, df_test)

    ##############################
    # データ前処理
    # 欠損値を処理する
    # Data preprocessing
    # Fill or remove missing values
    ##############################
    df.fillna({'Fare':0}, inplace=True)
    df_test.fillna({'Fare':0}, inplace=True)
        
    ##############################
    # トレーニングデータとテストデータを分ける
    # Split training data and test data
    ##############################
    x = df.drop(columns='Survived')
    y = df[['Survived']]

    return x, y, df_test, df_test_index

##############################
# モデル構築 5層パーセプトロン
##############################
def create_model_5dim_layer_perceptron(input_dim, \
                                       activation="relu", \
                                       optimizer="adam", \
                                       out_dim=100, \
                                       dropout=0.5):
    
    model = Sequential()

    # 入力層 - 隠れ層1
    model.add(Dense(input_dim=input_dim, units=out_dim))
    model.add(BatchNormalization())
    model.add(Activation(activation))
    model.add(Dropout(dropout))

    # 隠れ層1 - 隠れ層2
    model.add(Dense(units=out_dim))
    model.add(BatchNormalization())
    model.add(Activation(activation))
    model.add(Dropout(dropout))

    # 隠れ層2 - 隠れ層3
    model.add(Dense(units=out_dim))
    model.add(BatchNormalization())
    model.add(Activation(activation))
    model.add(Dropout(dropout))

    # 隠れ層3 - 出力層
    model.add(Dense(units=1))
    model.add(Activation("sigmoid"))

    model.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])
    
    return model
    

if __name__ == "__main__":
    
    # データ準備
    x_train, y_train, x_test, y_test_index = prepareData()
    
    # モデルを構築する
    model = create_model_5dim_layer_perceptron(len(x_train.columns), \
                                               activation="relu", \
                                               optimizer="adam", \
                                               out_dim=702, \
                                               dropout=0.5)

    # 学習
    fit = model.fit(x_train, y_train, epochs=25, batch_size=16, verbose=2)
    
    # 予測
    y_test_proba = model.predict(x_test)
    y_test = numpy.round(y_test_proba).astype(int)
    
    # PassengerId のDataFrameと結果を結合する
    # Combine the data frame of PassengerId and the result
    df_output = pandas.concat([y_test_index, pandas.DataFrame(y_test, columns=['Survived'])], axis=1)

    # result.csvをカレントディレクトリに書き込む
    # Write result.csv to the current directory
    df_output.to_csv('result.csv', index=False)
```

#6.結果

結果は「0.79425」でした。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545343/92530584-c171-7b34-b5a4-890b66b4a788.png" width="75%">


#7.まとめ

Keras は scikit-learn と比較して、層の数やニューロン数など細かい設定ができる印象です。
ただし、畳み込みニューラルネットワーク（CNN）や、リカレントニューラルネットワーク（RNN）以外では、モデルの種類は少ないと思います。
状況によって scikit-learn と Keras を使い分けるのが良さそうです。

[次回](https://qiita.com/sudominoru/items/82dfde38a447143497c7)はR言語を使って学習してみたいと思います。

#参考
Keras with GridSearchCVでパラメータ最適化自動化
https://qiita.com/cvusk/items/285e2b02b0950537b65e

#履歴
2020/02/15 初版公開
2020/02/22 次回のリンク追加