Kaggle奮闘録(Titanic編3) -LightGBMによる分類-

# 1.はじめに

3回目の投稿です．今回は初めてLightGBMを使ってみたので，そのメモをこちらに記したいと思います．今までLightGBMは使ったことがなかったので，理論的(数理的)な背景は一切知らないですが，使い方は簡単かつ高精度だったので非常に便利に感じました．なので今回提出したコードをここに残します．



# 2.方法

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


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

[Titanic Tutorial](https://www.kaggle.com/code/alexisbcook/titanic-tutorial)
```python
# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# Input data files are available in the read-only "../input/" directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using "Save & Run All" 
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session
```


## パッケージインストール
```python
# Install other packages
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.model_selection import train_test_split
import optuna.integration.lightgbm as lgb
```
ハイパーパラメータチューニングなしにLightGBMを使うだけなら，"import lightgbm as lgb"でいいのですが，チューニングする場合は[Optuna](https://tech.preferred.jp/ja/blog/hyperparameter-tuning-with-optuna-integration-lightgbm-tuner/)というライブラリを使うのが簡単かつ高精度そうでした．詳しいアルゴリズムは調べていませんが，どうやらOptunaはベイズ最適化をベースにパラメータ最適化を行ってくれるそうです．


## データのインストール

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


## 欠損値の補間

```python
# for train
print(train.isnull().sum())   # Visualize the number of missing values
# fill Age column base on the sex and pclass of row
train['Age']= train.groupby(['Sex','Pclass'])['Age'].apply(lambda row : row.fillna(row.median()))

# for test
print(test.isnull().sum())
# fill Age column base on the sex and pclass of row
test['Age']= test.groupby(['Sex','Pclass'])['Age'].apply(lambda row : row.fillna(row.median()))
# fill Fare column base on the sex and pclass of row
test['Fare']= test.groupby(['Sex','Pclass'])['Fare'].apply(lambda row : row.fillna(row.median()))
```

欠損値の処理についてはこの間ちょうど社内のミーティングでもあがり，これとほぼ同じコードだったので，欠損値への対処法としては悪くないと思います．データの欠損値の数を見たければ".isnull().sum()"をつけてprintすることで，コラムごとの欠損値の数を見ることができます．
ちなみにapplyメソッドの中身は確か関数でないとダメなのでそこは気をつけなければいけません．今回はいちいちdefineして関数を作るほどではなかったので，無名関数lambdaを使い，１行で欠損値の補間を実装しています．


## 特徴量の選択

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


## カテゴリー変数の変換

```python
# Label encoding
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
# Normalization (Mean=0, Standard division=1)
standard = StandardScaler()

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


## 検証用データの用意

```python
# Divide training data into train and validation data
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)
```

これは大学院時代に学んだことですが，train_test_splitにより学習用データを分割する時には基本的にstratifyをちゃんと設定しておきましょう(デフォルトでは"stratify=None")．これを設定することにより，今回のケースでは検証用データにおいても元のクラス比率が維持されます(例えば元の学習データにおいて生存者:死亡者=1:2なら，"stratify=y_train"を設定することにより，検証用データにおいても生存者:死亡者=1:2となる)．


## LightGBM用のデータセットの作成

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

ここは正直あまり理解できていませんが，LightGBMにデータを学習させる際には，lgb.Datasetメソッドを使ってLightGBM特有のデータフレームに変更してから学習を行う必要があるようです．


## ハイパーパラメータの固定

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

ここでは，ハイパーパラメータチューニングにより変わってほしくない(変える必要が無い)パラメータを記述します．勾配ブースティング木(Gradient Boosting Decision Tree)については[こちら](https://nisshingeppo.com/ai/whats-gbdt/)が参考になりました．
他のパラメータについては特筆することはありませんが，'metric'のところはROC曲線の曲線化面積であるAUCなども指定することができます(しかしながら今回はaucを使用すると精度が低下してしまいました)．


## ハイパーパラメータチューニングとモデル選択

```python
model = lgb.train(params = params, 
                train_set = lgb_train,
                valid_sets = lgb_val,
                verbose_eval=100,
                early_stopping_rounds = 100)
```

そしてoptunaが提供するLightGBMにより学習を行うことにより，paramsで指定したパラメータ以外のパラメータについてベイズ最適化に基づいたチューニングを行ってくれます．そしてその結果最もよかったハイパーパラメータを自動的に選択し，そのハイパーパラメータに基づき構築されたモデルを与えてくれるみたいです．すごく便利です．


## 予測値の出力

```python
y_pred = model.predict(X_test, num_iteration=model.best_iteration)

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

構築したモデルにより予測した結果を出力します．


## 予測結果を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.結果
以上のモデルによる予測結果を提出したところ，予測精度は77.511%と，ニューラルネットワークの時より1.0%程度上がりました．
実は上記のコードに加えSMOTE(Synthetic Minority Oversampling Technique)によるデータ拡張なども行ってみましたが，LightGBMの予測精度が下がってしまったのでその部分については省きました．LightGBMは過学習を起こしやすいと言われているアーキテクチャなので，精度が上がるかなと思ったのですが...



# 4.おわりに
今回はkaggleで大人気のアーキテクチャLightGBMを用いてタイタニックデータにおける生存者・死亡者の2値分類に挑みました．やはり人気のアーキテクチャなだけあって他のアーキテクチャよりも高い精度で分類を行うことができました．
また今回のコードの実装にあたってはテッツォさんの[こちら](https://qiita.com/tetsuro731/items/a19a85fd296d4b87c367)の投稿が参考になりました．

次回はこのLightGBMとニューラルネットワークのアンサンブル学習により，分類精度8割越えを達成できるか検証したいと思います．