🍷

【9日目】Optunaによるハイパーパラメーターチューニングをやってみる【2021アドベントカレンダー】

2021/12/08に公開

2021年1人アドベントカレンダー(機械学習)、9日目の記事になります。

https://qiita.com/advent-calendar/2021/solo_advent_calendar

テーマは Optuna によるハイパーパラメーターチューニング になります。

Colab のコードはこちら Open In Colab

optuna.integration.lightgbm によるハイパーパラメーターチューニング

lightGBM の場合は、

import lightgbm as lgb

import optuna.integration.lightgbm as lgb

にするだけでハイパーパラメーターチューニングできるのですごく便利です。

import optuna.integration.lightgbm as opt_lgb

ラベルエンコーディング等は割愛、詳細はColab参照。

y_train = y_train.reset_index(drop=True)

gkf = GroupKFold(n_splits=5)

groups = X_train_ce["Genre"]

params = {
          'task': 'train',              # タスクを訓練に設定
          'boosting_type': 'gbdt',      # GBDTを指定
          'objective': 'regression',    # 回帰を指定
          'metric': 'rmse',             # 回帰の損失(誤差)
          'learning_rate': 0.1,         # 学習率
          'seed': SEED                   # シード値
          }

best_params, history = {}, []

cv_result_opt = []

for fold, (train_index, test_index) in enumerate(gkf.split(X_train_ce, y_train, groups)):
    X_train_gkf, X_test_gkf = X_train_ce.iloc[train_index], X_train_ce.iloc[test_index]
    y_train_gkf, y_test_gkf = y_train.iloc[train_index], y_train.iloc[test_index]

    # 学習、推論
    lgb_train = opt_lgb.Dataset(X_train_gkf, y_train_gkf)
    lgb_test = opt_lgb.Dataset(X_test_gkf, y_test_gkf, reference=lgb_train)

    lgb_results = {}                                    # 学習の履歴を入れる入物

    model = opt_lgb.train(
                    params,                    # ハイパーパラメータをセット
                    lgb_train,              # 訓練データを訓練用にセット
                    valid_sets=[lgb_train, lgb_test], # 訓練データとテストデータをセット
                    valid_names=['Train', 'Test'],    # データセットの名前をそれぞれ設定
                    num_boost_round=100,              # 計算回数
                    early_stopping_rounds=50,         # アーリーストッピング設定
                    evals_result=lgb_results,
                    verbose_eval=-1,                  # ログを最後の1つだけ表示
                    )  
    
    best_params = model.params

    # 損失推移を表示
    loss_train = lgb_results['Train']['rmse']
    loss_test = lgb_results['Test']['rmse']   
    
    fig = plt.figure()
    
    plt.xlabel('Iteration')
    plt.ylabel('logloss')

    plt.title(f"fold:{fold}")
    plt.plot(loss_train, label='train loss')
    plt.plot(loss_test, label='test loss')
    
    plt.legend()
    plt.show()

    # 推論
    y_pred = model.predict(X_test_gkf, num_iteration=model.best_iteration)

    # 評価
    rmse = mean_squared_error(y_test_gkf, y_pred, squared=False)
    cv_result_opt.append(rmse)

print("RMSE:", cv_result_opt)
print("RMSE:", np.mean(cv_result_opt))

すごく便利ではあるのですが、通常の LightGBM で使えるカスタムメトリクスが使えないようです。
https://github.com/optuna/optuna/issues/861

Optuna によるハイパーパラメーターチューニング

そこで、コード量は増えますが、応用の効く通常のOptunaでのハイパーパラメーターチューニングを試します。

import optuna

ハイパーパラメーターチューニング用の関数を作る必要があります。

返り値を最小化するよう調整するようです。

回帰であることを踏まえ、返り値に RMSE を設定します。

パラメーターに trial.sugest_xxx を設定することで、引数に設定した値やデータを探索してくれるようです。

def objective(trial):

    learning_rate = trial.suggest_uniform('learning_rate', 0.1, 1.0)
    num_leaves =  trial.suggest_int("num_leaves", 5, 50)
    tree_learner = trial.suggest_categorical('tree_learner', ["serial", "feature", "data", "voting"])

    params = {
            'task': 'train',                 # タスクを訓練に設定
            'boosting_type': 'gbdt',         # GBDTを指定
            'objective': 'regression',       # 回帰を指定
            'metric': {'rmse'},              # 回帰の損失(誤差)
            'learning_rate': learning_rate,  # 学習率
            'num_leaves': num_leaves,
            'tree_learner': tree_learner,
            'seed': SEED                     # シード値
            }

    model = lgb.train(
                    params=params,                    # ハイパーパラメータをセット
                    train_set=lgb_train,              # 訓練データを訓練用にセット
                    valid_sets=[lgb_train, lgb_test], # 訓練データとテストデータをセット
                    valid_names=['Train', 'Test'],    # データセットの名前をそれぞれ設定
                    num_boost_round=100,              # 計算回数
                    early_stopping_rounds=50,         # アーリーストッピング設定
                    evals_result=lgb_results,
                    verbose_eval=-1,                  # ログを最後の1つだけ表示
                    )
    
    # 推論
    y_pred = model.predict(X_test_gkf)

    # 評価
    rmse = mean_squared_error(y_test_gkf, y_pred, squared=False)

    return rmse

https://lightgbm.readthedocs.io/en/latest/Parameters.html

https://optuna.readthedocs.io/en/stable/faq.html#objective-func-additional-args

https://qiita.com/studio_haneya/items/2dc3ba9d7cafa36ddffa

下記コードでハイパーパラメーターを実行します。

study = optuna.create_study()
study.optimize(objective, n_trials=100)

study.best_params で最も良かったハイパーパラメーター結果を辞書型で得ることができます。

ただし metric や objective の情報は失われてしまうようなので、study.best_params を変数に代入し、metric や objective を update で追加してやりました。

https://note.nkmk.me/python-dict-add-update/

y_train = y_train.reset_index(drop=True)

gkf = GroupKFold(n_splits=5)

groups = X_train_ce["Genre"]

params = {
          'task': 'train',              # タスクを訓練に設定
          'boosting_type': 'gbdt',      # GBDTを指定
          'objective': 'regression',    # 回帰を指定
          'metric': 'rmse',             # 回帰の損失(誤差)
          'learning_rate': 0.1,         # 学習率
          'seed': SEED                   # シード値
          }

cv_result_opt2 = []

for fold, (train_index, test_index) in enumerate(gkf.split(X_train_ce, y_train, groups)):
    X_train_gkf, X_test_gkf = X_train_ce.iloc[train_index], X_train_ce.iloc[test_index]
    y_train_gkf, y_test_gkf = y_train.iloc[train_index], y_train.iloc[test_index]

    # 学習、推論
    lgb_train = lgb.Dataset(X_train_gkf, y_train_gkf)
    lgb_test = lgb.Dataset(X_test_gkf, y_test_gkf, reference=lgb_train)

    lgb_results = {}                                    # 学習の履歴を入れる入物
    
    study = optuna.create_study()
    study.optimize(objective, n_trials=100)

    print(study.best_params)

    best_params = study.best_params

    add_params = {
        'objective':'regression', 
        'metric': 'rmse', 
        'task': 'train', 
        'seed': SEED
        }

    best_params.update(add_params)

    print(best_params)

    model = lgb.train(
                    params=best_params,               # ハイパーパラメータをセット
                    train_set=lgb_train,              # 訓練データを訓練用にセット
                    valid_sets=[lgb_train, lgb_test], # 訓練データとテストデータをセット
                    valid_names=['Train', 'Test'],    # データセットの名前をそれぞれ設定
                    num_boost_round=100,              # 計算回数
                    early_stopping_rounds=50,         # アーリーストッピング設定
                    evals_result=lgb_results,
                    verbose_eval=-1,                  # ログを最後の1つだけ表示
                    )

    # 損失推移を表示
    loss_train = lgb_results['Train']['rmse']
    loss_test = lgb_results['Test']['rmse']   
    
    fig = plt.figure()
    
    plt.xlabel('Iteration')
    plt.ylabel('logloss')

    plt.title(f"fold:{fold}")
    plt.plot(loss_train, label='train loss')
    plt.plot(loss_test, label='test loss')
    
    plt.legend()
    plt.show()

    # 推論
    y_pred = model.predict(X_test_gkf)

    # 評価
    rmse = mean_squared_error(y_test_gkf, y_pred, squared=False)
    cv_result_opt2.append(rmse)

print("RMSE:", cv_result_opt2)
print("RMSE:", np.mean(cv_result_opt2))
項目 指標 結果
ハイパラ調整なしのlightGBM RMSE: 0.199
optuna.integration.lightgbm
によるハイパラ調整
RMSE: 0.182
Optuna によるハイパラ調整 RMSE: 0.177

9日目は以上になります、最後までお読みいただきありがとうございました。

Discussion