0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kaggle】LightGBMとOptunaでTitanic【acc: 0.768】

Posted at

はじめに

前々回、LightGBMを使ってTitanicコンペに予測値を提出した。今回はOptunaを導入してハイパーパラメータのチューニングを行う。

できたこと

  • 機械学習モデル: LightGBM
  • 交差検証: Stratified K-Fold (5-Fold)
  • 評価指標: Accuracy
  • ハイパーパラメータチューニング: Optuna
    • max_bin: 255~500 → 302
    • num_leaves: 32~128 → 92
    • 試行回数: 40回
  • CVスコア(logloss): 0.406
  • パブリックスコア: 0.768

スクリーンショット 2024-11-20 213754.png

ライブラリ

import lightgbm as lgb
import numpy as np
import optuna
import pandas as pd

# Scikit-learn関連
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import log_loss
from sklearn.metrics import accuracy_score

import warnings
warnings.filterwarnings('ignore')

データ読み込み

csvファイルをあらかじめダウンロードしておく。pathは適当に設定。

path = '../input/titanic/'

train = pd.read_csv(f"{path}train.csv")
test = pd.read_csv(f"{path}test.csv")
sub = pd.read_csv(f"{path}gender_submission.csv")

前処理&特徴量エンジニアリング

Titanicなので、ここはあまりこだわり過ぎない。

# 結合
data = pd.concat([train, test], sort=False)

# maleを0, femaleを1に変換
data['Sex'].replace(['male', 'female'], [0, 1], inplace=True)
# Embarked列を最頻値のSで補完
# Sを0, Cを1, Qを2に変換
data['Embarked'].fillna(('S'), inplace=True)
data['Embarked'] = data['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
# Fare列を平均値で埋める
data['Fare'].fillna(np.mean(data['Fare']), inplace=True)
# Ageを中央値で埋める
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())
SEED = 1234

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

交差検証の準備

前回と同様にStratified K-Foldを使う。5-Foldにする。

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=SEED)

チューニング

まずはチューニングから。その前にカテゴリ変数の指定する。

categorical_features = ['Embarked', 'Pclass', 'Sex'] # カテゴリ変数

今回のメイン。Optunaで使ってハイパーパラメータを最適化する。今回、最適化するHパラメータはmax_binとnum_leavesの二つ。スコアにはloglossを使う。コンペに合わせてaccuracyをスコアにしてもよい気もする。

def objective(trial):
    params = {
        'objective': 'binary',
        'max_bin': trial.suggest_int('max_bin', 255, 500),
        'learning_rate': 0.05,
        'num_leaves': trial.suggest_int('num_leaves', 32, 128),
    }

    scores = []

    # 交差検証を実行
    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]

        # データセットを生成
        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=False
                    ),
                lgb.log_evaluation(10)
            ]
        )

    ##################################
    # logloss
    ##################################
        # 検証データで予測
        y_pred_val = model.predict(X_val, num_iteration=model.best_iteration)

        # loglossの計算
        score = log_loss(y_val, y_pred_val)
        scores.append(score)

    return np.mean(scores)  
    
    ##################################
    # Accuracy 
    ##################################
    #     # 検証データで予測
    #     y_pred_val = model.predict(X_val, num_iteration=model.best_iteration)
    #     y_pred_val_binary = np.round(y_pred_val) # 2値化

    #     # accuracyの計算
    #     accuracy = accuracy_score(y_val, y_pred_val_binary)
    #     scores.append(accuracy)
    
    # return np.mean(scores)

最適化を実行。試行回数は40回とする。

# Optunaによるハイパーパラメータ最適化の実行

###############################
# logloss 望小
###############################
study = optuna.create_study(
    sampler=optuna.samplers.RandomSampler(seed=SEED)
)
study.optimize(objective, n_trials=40)


###############################
# Accuracy 望大
###############################
# study = optuna.create_study(
# direction='maximize',
# sampler=optuna.samplers.RandomSampler(seed=SEED)
# )
# study.optimize(objective, n_trials=40)

最適化したHパラメータを表示する。max_binは302、num_leavesは92だった。

print(study.best_params)
{'max_bin': 302, 'num_leaves': 92}

最適化したHパラメータで再度学習

最適化したHパラメータで再度学習する。

params = {
    'objective': 'binary',
    'max_bin': study.best_params['max_bin'],
    'learning_rate': 0.05,
    'num_leaves': study.best_params['num_leaves']
}

y_preds_test = []
oof_train = np.zeros((len(X_train),))
models = []

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]

    # データセットを生成
    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=False
                ),
            lgb.log_evaluation(10)
        ]
    )

    # 検証データで予測
    oof_train[valid_index] = model.predict(X_val, num_iteration=model.best_iteration)
    y_pred_test = model.predict(X_test, num_iteration=model.best_iteration)
    
    # 予測値と学習済みモデルを格納
    y_preds_test.append(y_pred_test)
    models.append(model)

評価

各foldの検証データに対するスコア(logloss)を変数scoresに格納・表示する。0.366から0.438だった。前回は0.38から0.48だったので、ばらつきは小さくなった。

scores = [m.best_score['valid_1']['binary_logloss'] for m in models]
print(scores)
[0.37689105112604115, 0.43821552479915354, 0.36678411566241065, 0.4372929103068846, 0.41157843891030305]

各foldの「検証データに対する予測値」を平均して、最終的なCVスコアとする。結果は0.406。前回は0.413だったので、少し改善した。

# 各foldの検証データに対する予測値を平均して、最終的なスコアを生成
score = sum(scores) / len(scores)
print('===CV scores===')
print(score)
===CV scores===
0.4061524081609586

oof_train(訓練に使用されなかったfoldに対する予測値)を0.5で二値化し、正解率を算出する。結果は、0.830。前回は0.829だったので、わずかに増加。

y_pred_oof = (oof_train > 0.5).astype(int)
print(accuracy_score(y_train, y_pred_oof)) # 精度を計算
0.8305274971941639

提出

各foldでの「テストデータに対する予測値」の平均を取り、0.5で二値化する。

y_sub = sum(y_preds_test) / len(y_preds_test)
y_sub = (y_sub > 0.5).astype(int)
print(y_sub[:10])
[0 0 0 0 0 0 1 0 1 0]

提出用のファイルを作成する。

# 提出用ファイルの作成
sub['Survived'] = y_sub
sub.to_csv('submission.csv', index=False)
display(sub.head())

出力したcsvファイルをKaggleに提出する。結果は0.768くらい。前回が0.765くらいだったので、わずかにスコアアップ。

スクリーンショット 2024-11-20 213754.png

おわりに

Optunaを使ってハイパーパラメータチューニングを導入した結果、わずかにスコアアップした。よかった。

出典

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?