【Kaggle】LightGBMでTitanicコンペ【acc: 0.765】

# はじめに
　Kaggleの復習もやることにした。まずは定番のタイタニックから。

# できたこと
- 機械学習モデル: LightGBM
- 交差検証: Stratified K-Fold
- CVスコア(logloss): 0.418
- パブリックスコア: 0.765

![スクリーンショット 2024-11-08 155559.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/b5ab728b-6242-0a1f-4ee3-1f7d37b2371c.png)

https://github.com/grv2688/qt-kaggle-start-book/tree/main/241106_titanic_lightgbm

# ライブラリをインポート
```Python
# 関連ライブラリをインポート
import lightgbm as lgb
import numpy as np
#import os
import pandas as pd
import random
#import torch

# scikit-learn関連をインポート
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import accuracy_score

# warningを非表示
import warnings
warnings.filterwarnings('ignore')
```

乱数を固定する。
```Python
# 参考: 乱数固定
def seed_everything(seed=1234):
    random.seed(seed)
    np.random.seed(seed)
    #os.environ['PYTHONHASHSEED'] = str(seed)
    # torch.manual_seed(seed)
    # torch.cuda.manual_seed(seed)
    # torch.backends.cudnn.daterministic = True
seed_everything(1234)
```

# データ読み込み
コンペサイトからあらかじめデータをダウンロードし、読み込ませる。
```Python
# データ読み込み
train = pd.read_csv('../input/titanic/train.csv')
test = pd.read_csv('../input/titanic/test.csv')
gender_submission = pd.read_csv('../input/titanic/gender_submission.csv')
```

# 前処理・特徴量エンジニアリング
前処理と特徴量エンジニアリングを施す。
```Python
# データを連結（前処理効率化のため）
data = pd.concat([train, test], sort=False)

# 前処理
data['Sex'].replace(['male', 'female'], [0, 1], inplace=True)
data['Embarked'].fillna(('S'), inplace=True)
data['Embarked'] = data['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
data['Fare'].fillna(np.mean(data['Fare']), inplace=True)
data['Age'].fillna(data['Age'].median(), inplace=True)
data['FamilySize'] = data['Parch'] + data['SibSp'] + 1
data['IsAlone'] = 0
data.loc[data['FamilySize'] == 1, 'IsAlone'] = 1

# 使用しない列を削除
delete_columns = ['Name', 'PassengerId', 'Ticket', 'Cabin']
data.drop(delete_columns, axis=1, inplace=True)

# 訓練データとテストデータに戻す
train = data[:len(train)]
test = data[len(train):]

# 目的変数と説明変数に分割
y_train = train['Survived']
X_train = train.drop('Survived', axis=1)
X_test = test.drop('Survived', axis=1)

# 表示
print(f"X_train: {X_train.shape}")
display(X_train.head())
print(f"y_train: {y_train.shape}")
display(y_train.head())
print(f"X_test: {X_test.shape}")
display(X_test.head())
```

出力は以下のような感じになるはず（データフレームの表示は割愛）。
```
X_train: (891, 9)
y_train: (891,)
X_test: (418, 9)
```

# 交差検証の準備
交差検証を行うための準備を行う。Stratified K-Foldを行うためにインスタンスを生成。目的変数が均等に分割できているか確認。
```Python
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)

# 目的変数が均等に分割できているか確認
for fold_id, (train_index, valid_index) in enumerate(cv.split(X_train, y_train)):
    X_tr = X_train.loc[train_index, :]
    X_val = X_train.loc[valid_index, :]
    y_tr = y_train[train_index]
    y_val = y_train[valid_index]

    print(f'fold_id: {fold_id}')
    print(f'y_tr y==1 rate: {sum(y_tr)/len(y_tr)}')
    print(f'y_val y==1 rate: {sum(y_val)/len(y_val)}')
    print("--------------------------------")
```

出力は以下の通り。ちゃんと均等になっている。
```
fold_id: 0
y_tr y==1 rate: 0.38342696629213485
y_val y==1 rate: 0.3854748603351955
--------------------------------
fold_id: 1
y_tr y==1 rate: 0.38429172510518933
y_val y==1 rate: 0.38202247191011235
--------------------------------
fold_id: 2
y_tr y==1 rate: 0.38429172510518933
y_val y==1 rate: 0.38202247191011235
--------------------------------
fold_id: 3
y_tr y==1 rate: 0.38429172510518933
y_val y==1 rate: 0.38202247191011235
--------------------------------
fold_id: 4
y_tr y==1 rate: 0.38288920056100983
y_val y==1 rate: 0.38764044943820225
--------------------------------
```

# メインループ
訓練を行う。
```Python
y_preds = [] # 予測値を格納するリスト
models = [] # モデルを格納するリスト
oof_train = np.zeros((len(X_train),)) # 各分割でのoof(訓練に使用されなかったfold)に対する予測値
categorical_features = ['Embarked', 'Pclass', 'Sex'] # カテゴリ変数

# ハイパーパラメータ
params = {
    'objective': 'binary',
    'max_bin': 300,
    'learning_rate': 0.05,
    'num_leaves': 40
}
```

少し用語をメモ。Out-of-fold(oof)は、各foldで訓練に使われなかったfoldのこと（多分）。oof_trainは、各foldでoof(訓練に使用されなかったfold)に対する「予測値」のこと（多分）。

```Python
# メインループ
for fold_id, (train_index, valid_index) in enumerate(cv.split(X_train, y_train)):
    # 表示
    print('-------------------')
    print(f'Fold: {fold_id}')


    # 訓練データと検証データに分割
    X_tr = X_train.loc[train_index, :]
    X_val = X_train.loc[valid_index, :]
    y_tr = y_train[train_index]
    y_val = y_train[valid_index]

    # データセットを生成
    lgb_train = lgb.Dataset(X_tr, y_tr, categorical_feature=categorical_features)
    lgb_eval = lgb.Dataset(X_val, y_val, reference=lgb_train, categorical_feature=categorical_features)

    # 訓練
    model = lgb.train(params, lgb_train,
                      valid_sets=[lgb_train, lgb_eval],
                      num_boost_round=1000,
                      callbacks=[lgb.early_stopping(stopping_rounds=10,
                                                    verbose=True),
                                 lgb.log_evaluation(10)])

    # 検証データでの予測
    oof_train[valid_index] = model.predict(X_val, num_iteration=model.best_iteration)
    y_pred = model.predict(X_test, num_iteration=model.best_iteration)

    # 予測値と学習済みモデルを格納
    y_preds.append(y_pred)
    models.append(model)
```

# 評価
oof_train(訓練に使用されなかったfoldに対する予測値）をcsvで保存し、表示させる。
```Python
# oof_train(訓練に使用されなかったfoldに対する予測値)を保存・表示
pd.DataFrame(oof_train).to_csv('oof_train_skfold.csv', index=False)
print(oof_train[:10])
```
```
[0.10743915 0.9336148  0.24343427 0.97174844 0.23722008 0.05967737
 0.10523416 0.27028808 0.37330822 0.9413475 ]
```

次に各foldの検証データに対するスコアをscores変数に代入し、printする。
```Python
# 各foldの検証データに対するスコア（logloss）を格納・表示
scores = [m.best_score['valid_1']['binary_logloss'] for m in models]
print(scores)
```
```
[0.38009408187107163, 0.37924619589793596, 0.40508420679400237, 0.4450699329180633, 0.4825556025529494]
```

スコア(logloss)は0.38～0.48となった。ばらつきがある。スコアを平均して、最終的なスコア（Cross Validationスコア）を計算する。
```Python
# 各foldの検証データに対する予測値を平均して、最終的なスコアを生成
score = sum(scores) / len(scores)
print('===CV scores===')
print(score)
```
```
===CV scores===
0.41841000400680456
```

次に、oof_train(訓練に使用されなかったfoldに対する予測値)を閾値0.5で二値化する。その値と、訓練データの目的変数を使って、正解率を計算する。
```Python
# oof_train(訓練に使用されなかったfoldに対する予測値)を0.5で二値化
y_pred_oof = (oof_train > 0.5).astype(int)
print(accuracy_score(y_train, y_pred_oof)) # 精度を計算
```
```
0.8294051627384961
```

正解率は82%となった。

# Submit
提出するためにデータを作成する。今回は、各foldでの「テストデータに対する予測値」の平均をとる。

```Python
# 各foldでの「テストデータに対する予測値」の平均をとる
y_sub = sum(y_preds) / len(y_preds)
y_sub = (y_sub > 0.5).astype(int) # 閾値0.5で二値化
y_sub[:10]
```
```
array([0, 0, 0, 0, 0, 0, 1, 0, 1, 0])
```

提出用ファイルを作成する。サンプル用のデータ(gender_submission.csv)を読み込み、計算した予測値を代入する。
```Python
# 提出用ファイルを作成
sub = pd.read_csv('../input/titanic/gender_submission.csv')
sub['Survived'] = y_sub
sub.to_csv('submission_lightgbm_skfold.csv', index=False)

sub.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/0b5495ba-dd41-b051-4574-7b15123d8436.png)

# 結果確認
出力したcsvファイル（submission_lightgbm_skfold.csv）を提出する。パブリックスコアは76%だった。
![スクリーンショット 2024-11-08 155559.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689015/8ce58f2a-7b5a-6e35-c1bc-3ed0684ed74d.png)


# おわりに
今回は、タイタニックコンペにデータを提出してみた。
- 機械学習モデル: LightGBM
- 前処理,特徴量エンジニアリング: 適当に…
- 交差検証: Stratiried K-Fold (5 Fold)
- CVスコア: 82%
- パブリックスコア: 76%

# 出典
https://www.amazon.co.jp/%E5%AE%9F%E8%B7%B5Data-Science%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-Python%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8BKaggle%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%88%E3%83%96%E3%83%83%E3%82%AF-KS%E6%83%85%E5%A0%B1%E7%A7%91%E5%AD%A6%E5%B0%82%E9%96%80%E6%9B%B8-%E7%A5%A5%E5%A4%AA%E9%83%8E/dp/4065190061