Kaggle超入門 ~タイタニック号の生存予測~(lightGBM編)

# 目的
前回、kaggleのタイタニック号生存予測のデータの確認を行なったので、今回は実際にモデルで訓練・予測を行っていきたいと思います。その過程で、モデルの特徴やモデル選択、モデル評価、パラメータ調整の手法など学んでいければと思います。
前回の記事はこちらhttps://qiita.com/tasuku303/items/becded4c6ec73d72ddbc
# モデル選択
モデル選択に関しては、まず教師あり学習か、教師なし学習かを理解する。今回は教師あり学習であるためカテゴリ予測である分類問題か、数値予測の回帰問題かで大まかに使うモデルを絞り込める。
今回は、生存しているかどうかを分類するので分類問題である。
モデル選択のやり方として、scikit-learnのドキュメントに以下のようなアルゴリズムチートシートというものがある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774002/b2749257-3905-2d2e-b4eb-a14f8c263b0f.png)
今回の例だと、まず、LinearSVCを試し、精度が良くなければK近傍法分類、その後カーネルSVCもしくはアンサンブルモデルを利用するという流れになる。
しかし、「Kaggleで勝つデータ分析の技術」という書籍では、使いやすさや計算速度等も考慮し、勾配ブースティング木モデル(以下GBDT)を最初に利用することを推奨している。これらを踏まえ、今回はlightgbmライブラリを利用したGBDTによる訓練・予測を行った。今回は、以下のサイトを参考に実装した。
https://banga-heavy.com/kaggle%E3%82%BF%E3%82%A4%E3%82%BF%E3%83%8B%E3%83%83%E3%82%AF%E3%83%87%E3%83%BC%E3%82%BF%E3%81%A781-100-lightgbmxoptunax%E4%BA%A4%E5%B7%AE%E6%A4%9C%E8%A8%BC/

# モデル訓練
## データの前処理
まずは、必要なライブラリをインポートする。
```
# ライブラリのインポート
from sklearn.model_selection import train_test_split
from sklearn import metrics
import lightgbm as lgb
from sklearn.model_selection import StratifiedKFold
import pandas as pd
import numpy as np
import optuna
```
データを読み込んで、objectはダミー変数化し、目的変数と説明変数に分けた。
```
# データの読み込み
train = pd.read_csv('train.csv')
train_p = train.copy()
feature_name = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"]
train = train[feature_name]
train = train.fillna(train.mean())
train = pd.get_dummies(train)
# 説明変数と目的変数に分離
X = train
y = train_p.iloc[:, 1:2]
```
## モデル訓練
```
kf=StratifiedKFold(n_splits=10, shuffle=True, random_state=0)
def objective(trial):
    params = {"metric": "auc",
             "objective": "binary",
             "max_depth": trial.suggest_int("max_depth", 10, 500),
             "num_leaves": trial.suggest_int("num_leaves", 10, 500),
             "min_child_samples":trial.suggest_int("min_child_samples",100,300),
              "learning_rate":trial.suggest_uniform("learning_rate",0.01,0.5),
              "feature_fraction":trial.suggest_uniform("feature_fraction",0,1),
              "bagging_fraction":trial.suggest_uniform("bagging_fraction",0,1)}
    
    scores = []
    for i, (train_, val_) in enumerate(kf.split(X, y)):
        x_train, x_val = X.iloc[train_], X.iloc[val_]
        y_train, y_val = y.iloc[train_], y.iloc[val_]
        lgb_train = lgb.Dataset(x_train, y_train)
        lgb_eval = lgb.Dataset(x_val, y_val)
        
        lgbm = lgb.train(params,
                                lgb_train,
                                valid_sets=lgb_eval,
                                num_boost_round=1000,
                                early_stopping_rounds=50,
                                verbose_eval=False)
        
        y_train_pred = np.round(lgbm.predict(x_train))
        y_val_pred = np.round(lgbm.predict(x_val))
        
        score_train = metrics.accuracy_score(y_train, y_train_pred)
        score_eval = metrics.accuracy_score(y_val, y_val_pred)
        
        scores.append(score_eval**4/score_train**3)
        cv_score = np.mean(scores)
        
        return cv_score
```
まず、交差検証の設定をした。StratifiedKFoldを利用して、層化K分割交差検証を実施した。分類問題では、この手法で交差検証を実施した方が、データの偏りがなく分割できるらしい。
今回は、パラメータ調整でoptunaを利用して最適なパラメータを算出し、それをもとに最終的な予測を行います。パラメータは、metric(評価関数)をauc、objective(モデルの種類)、max_depth(木の深さの最大値)、num_leaves(ノードの最大数)、min_child_samples(ノードに振られるデータ数)、learning_rate(学習率)、feature_fraction(特徴量の一部を削除するか)、bagging_fraction( baggingで選択されるサンプルの割合)である。ただ、今回bagging_fractionに関しては、bagging_freqを設定しないとバギングが実施されないらしいため、調整が行われていない可能性が高い。
lightGBMには、TrainingAPIとscikit-learnAPIの二つがあるらしく、今回はtrainingAPIを採用した(scikit-learnAPIはインスタンスを立てて、scikit-learnのように作れるみたい)。交差検証に関しては、引用と同様にforループで回したが、sklearnのcross_validationを使えないか、検討してみたい(見た感じ難しそう)。trainにはパラメータと訓練用データ、評価用データ、num_boost_round(epoch数と同義?)、さらにearly_stopping_roundsというモデルの性能が上がりにくくなったら学習を打ち切る設定をしました。そして、引用元ではパラメータ調整の工夫として、評価用データの正解率が高くなるようにハイパーパラメータを探索するために、（scoreval/scoretrain)の3乗を求めている。(この部分に関しては、再検討したい)。
## optunaによる最適化
```
study=optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=50)
```
optunaによる最適化です。最大値問題か最小値問題かを指定して、optimizeに目的関数と繰り返し数(n_trials)を設定する。求めた最適パラメータは以下のとおりです。
```
study.best_params
```
![スクリーンショット 2022-09-16 8.43.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774002/47b17246-599c-a39c-5063-3a1cd83c4d81.png)

## モデル予測
ここで、再度モデルを作って、テスト用データを最適パラメータで予測していきます。
```
#訓練用、テスト用に分割
x_train,x_val,t_train,t_val=train_test_split(X,y,test_size=0.25,stratify=y)
lgb_train=lgb.Dataset(x_train, t_train)
lgb_eval=lgb.Dataset(x_val, t_val)

#テストデータ読み込み
test = pd.read_csv('test.csv')
test_p = test.copy()
feature_name = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"]
test = test[feature_name]
test = pd.get_dummies(test)

#モデル予測
bestparams={"metric":"auc",
            "objective":"binary", 
            "max_depth":study.best_params["max_depth"],
            "num_leaves":study.best_params["num_leaves"],
            "min_child_samples":study.best_params["min_child_samples"],
            "learning_rate":study.best_params["learning_rate"],
            "feature_fraction":study.best_params["feature_fraction"],
            "bagging_fraction":study.best_params["bagging_fraction"]}

best_lgbm=lgb.train(bestparams,
                    lgb_train,
                    valid_sets=lgb_eval,
                    num_boost_round=1000,
                    early_stopping_rounds=50,
                    verbose_eval=50)

pred=np.round(best_lgbm.predict(test),3)
```
そして、訓練用と評価用の正解率がこちらです。
```
print(metrics.accuracy_score(t_train["Survived"],np.round(best_lgbm.predict(x_train)))) #訓練用
print(metrics.accuracy_score(t_val["Survived"],np.round(best_lgbm.predict(x_val))))　 #評価用
```
![スクリーンショット 2022-09-16 8.59.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774002/a1f172ff-5350-161d-ba0d-d7c149dfeb52.png)
そして、最終的にテストデータを提出した結果は、、、
![スクリーンショット 2022-09-16 9.00.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774002/8924c4e1-7f82-55b6-756a-e2a328c8f4e1.png)

なかなか低い、、、

ちなみに、上位は結構100%の数値を出しているので、まだまだ改善の余地がありそうです(100%だと逆に不安になりそうですけど)。

## まとめ
* 今回は、lightGBMを用いて最終的なスコアをも求めるところまで行った。
* 一連の流れを学んだので、他のコンペにもチャレンジしたいです。
* また、他のモデルや特徴量選択を再検討し、また挑戦したいです。