Kaggle奮闘録(Titanic編4) -アンサンブル学習-

# 1.はじめに

引き続きkaggleの"Titanic - Machine Learning from Disaster"に挑戦していきます．ここ最近になって，このタイタニックの2値分類において精度8割越えがかなり難易度高いことに気づいて少し絶望していますが，もっと勉強してなんとか達成してみたいと思います．
今回は[前回](https://qiita.com/bazz0907/items/cd489ca054961cac11bb)の最後で述べたように，ニューラルネットワークモデルとLightGBMのアンサンブル学習を実践してみましたところ，前回よりもさらに精度が向上したのでそれを記載しておきます．



# 2.方法

以下，提出したコードです．


## Titanic Tutorialに載っていたデータインストールのために必要なコード(コピペ)

[Titanic Tutorial](https://www.kaggle.com/code/alexisbcook/titanic-tutorial)

```python
import numpy as np
import pandas as pd
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))
```


## パッケージインストール

```python
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder, StandardScaler
from imblearn.over_sampling import SMOTE
from sklearn.model_selection import train_test_split
import optuna.integration.lightgbm as lgb
import keras
```


## ニューラルネットワークモデルの定義

```python
def build_model(unit1=64, unit2=128, unit3=256, dropout=0):
  model = keras.models.Sequential()
  model.add(keras.layers.Dense(unit1, activation='relu', kernel_initializer=keras.initializers.he_normal(seed=0)))   # He Weight Initialization
  model.add(keras.layers.Dense(unit2, activation='relu', kernel_initializer=keras.initializers.he_normal(seed=0)))   # He Weight Initialization
  model.add(keras.layers.Dense(unit3, activation='relu', kernel_initializer=keras.initializers.he_normal(seed=0)))   # He Weight Initialization
  if dropout > 0:
    model.add(keras.layers.Dropout(dropout))
  model.add(keras.layers.Dense(1, activation='sigmoid', kernel_initializer=keras.initializers.glorot_uniform(seed=0)))   # Xavier Weight Initialization
  model.compile(optimizer='adam', loss='binary_crossentropy', metrics=["accuracy"])
  return model
```

今回は[Titanic編2](https://qiita.com/bazz0907/items/2d3fa4d5555ddd21f46b)の時のニューラルネットワークに少し変更を加え，中間層を1層増やした3層のニューラルネットワークを使用しました．2層の中間層のニューラルネットワークの学習曲線ではtrain_accuracy < validation_accuracyとなっており，過学習が考えられたためです．
またハイパーパラメータの初期値は[Titanic編2](https://qiita.com/bazz0907/items/2d3fa4d5555ddd21f46b)と同様の方法で行い，その結果unit1=64, unit2=128, unit3=256, dropout=0という値が得られたのでそちらを使用していますが，今回はハイパーパラメータチューニングの部分のコードは割愛しています．


## データインストール&欠損値処理

```python
train = pd.read_csv("/kaggle/input/titanic/train.csv")
test = pd.read_csv("/kaggle/input/titanic/test.csv")

train['Age']= train.groupby(['Sex','Pclass'])['Age'].apply(lambda row : row.fillna(row.median()))
test['Age']= test.groupby(['Sex','Pclass'])['Age'].apply(lambda row : row.fillna(row.median()))
test['Fare']= test.groupby(['Sex','Pclass'])['Fare'].apply(lambda row : row.fillna(row.median()))
```

この辺も[前回](https://qiita.com/bazz0907/items/cd489ca054961cac11bb)と全く同じ，中央値による補間です．


## 特徴量選択とカテゴリー変数の変換

```python
names = ['Pclass','Sex','Age','SibSp','Parch','Fare']
X_train = train[names]
X_test = test[names]
y_train = train['Survived']

sex_le = LabelEncoder()
X_train['Sex'] = sex_le.fit_transform(X_train['Sex'])
X_test['Sex'] = sex_le.fit_transform(X_test['Sex'])
X_train.head()
```


## 標準化&検証データの用意

```python
standard = StandardScaler()
standard.fit(X_train)
X_train = standard.transform(X_train)
X_test = standard.transform(X_test)

X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size = 0.2, stratify = y_train, random_state = 0)
```

[前回](https://qiita.com/bazz0907/items/cd489ca054961cac11bb)触れていませんでしたが，random_stateを指定することによって，学習データの分割方法が何回やっても同じになるように固定することができます(乱数シードの固定)．
全体の実装が終わってから思いましたが，ここをホールドアウト法ではなく，交差検証法に変えてアンサンブル学習を行えばもっと上手くいくのでは？と思ったので次回以降で試してみようと思います(予定)．


## LightGBMモデルの構築

```python
# making dataset for lightGBM
lgb_train = lgb.Dataset(X_train, y_train)
lgb_val = lgb.Dataset(X_val, y_val)

# fix parameters
params = {
    'boosting': 'gbdt',   # Gradient Boosting Decision Tree
    'objective':'binary',   # Method of task     
    'metric':'binary_error',   # Evaluation criteria
    "verbosity": -1,   # Not visualize progress
}

# hyperparameter tuning & build lightGBM model with optimal parameters
model1 = lgb.train(params = params, 
                train_set = lgb_train,
                valid_sets = lgb_val,
                verbose_eval = 100,
                early_stopping_rounds = 100)
```

[前回](https://qiita.com/bazz0907/items/cd489ca054961cac11bb)と同じ．


## ニューラルネットワークモデルの構築

```python
# build model
model2 = build_model()

# set callbacks
early_stopping = keras.callbacks.EarlyStopping(
    monitor='val_loss',
    patience=10,
)

# train model
history = model2.fit(
X_train,
y_train,
validation_data=(X_val, y_val),
epochs=50,
batch_size=32,
callbacks=early_stopping
)
```

[前々回](https://qiita.com/bazz0907/items/2d3fa4d5555ddd21f46b)と同じ．


## アンサンブル学習

```python
y_pred1 = model1.predict(X_test, num_iteration=model1.best_iteration)
y_pred2 = np.squeeze(model2.predict(X_test))

y_pred = (y_pred1 + y_pred2) / 2

y_pred[(y_pred >= 0.5)] = 1
y_pred[(y_pred < 0.5)] = 0
y_pred = np.array(y_pred, dtype='int')
```

LightGBMにより予測された(ラベル1である)確率をy_pred1，Neural Networkにより予測された確率をy_pred2に格納し，それぞれの平均値をとったものを最終予測確率としました．これをソフトなアンサンブル学習というようです．ちなみに分類器が奇数個存在する場合には，多数決により予測ラベルを決定するアンサンブル学習が可能であり，これをハードなアンサンブル学習というようです．詳しくはkoshian2さんの[こちら](https://qiita.com/koshian2/items/d569cd71b0e082111962)の記事をご参照ください．


## 予測結果をCSVファイルに変換

```python
output = pd.DataFrame({'PassengerId': test.PassengerId, 'Survived': y_pred})
output.to_csv('submission.csv', index=False)
print("Your submission was successfully saved!")
```



# 3.結果

以上のモデルによる予測結果を提出したところ，予測精度は78.708%と，過去最高の分類精度となりました．分類精度をあと1.3%あげることができれば目標達成です．
この予測結果をsubmitし，Leaderboardで自分の順位を確認したところ，16408チーム中1568位だったので，上位10%以内の比較的精度の高い分類器であることがわかります．



# 4.おわりに

今回は前回に引き続きLightGBMとNeural Networkのアンサンブル学習により，タイタニックデータの生存者・死亡者の2値分類に挑みました．その結果大きく精度が向上したのでモチベーションの向上につながりました．

次回は交差検証法により今回の分類器を複数生成し，アンサンブル学習を行う予定です．
あとはもう少し解説要素を増やせるよう，統計学等を勉強します．