カテゴリ値を含むデータでのLightGBMを使った分析（３本目：ハイパーパラメータ・チューニング編）

# はじめに

過去の投稿[^1][^2]では、Kaggleのデータセット「Adult Census Income[^3]」を使って二値分類問題にチャレンジしました。
その際、カテゴリ値の取扱い方法としては、**Label Encoding**と**One-hot Encoding**を採用した場合を紹介しました。

が、**ハイパーパラメータ・チューニング**には取り組んでいませんでした。

そこで本稿では、**Optuna**を利用したLightGBMの**ハイパーパラメータ・チューニング**にチャレンジします（問題設定は過去の投稿に同じ）。

## 本稿で紹介すること

- ハイパーパラメータ・チューニング
- 乱数Seedを指定した試行の自動化

:::note info
過去の投稿からの**変更部分を中心**に、説明します。
が、紹介するPythonコードは**リファクタリング未済**のため、PEP8[^4]には則っていません！
:::

## 本稿で紹介しないこと

- データの収集および整形
- 特徴量設計
- 予測モデルの作成
- 予測精度の確認
- 特徴量重要度の確認（可視化）

# 分析環境の各種情報

:::note info
筆者は、Windows 11のホスト上でJupyterを起動しています。（コンテナは不使用）
:::

- Python 3.9.13
- pip 23.0.1
- pandas 1.5.3
- numpy 1.24.2
- scikit-learn 1.2.1
- lightgbm 3.3.5
- optuna 3.1.0
- matplotlib 3.6.3

# ハイパーパラメータ・チューニング

こちらのサイト[^5]で公開されているPythonコードを参考にしました。
LightGBMTunerのインスタンス化、学習（≒探索）、最適モデル（およびパラメータ）取得などの流れは同じです。

:::note info
ポイントは、optuna.integration.lightgbm.LightGBMTuner[^6]を利用することです。
:::

```python
# LightGBMのハイパーパラメータ
params = {
    # 勾配ブースティング木(GBDT:Gradient Boosting Decision Tree)
    'boosting_type': 'gbdt',
    # 二値分類問題
    'objective': 'binary',
    # AUCの最大化を目指す
    'metric': 'auc',
    # 正答率の最大化を目指す
    #'metric': 'binary_error',
    # Fatal の場合出力
    'verbosity': -1,
    # 学習率
    'learning_rate': 0.02,
    'deterministic': True, # 再現性確保用のパラメータ
    'force_row_wise': True  # 再現性確保用のパラメータ
}

booster = lgb.LightGBMTuner(
    params = params,
    train_set = lgb_train,
    valid_sets = lgb_valid,
    verbose_eval = 50,  # 50イテレーション毎に学習結果出力
    num_boost_round = 10000,  # 最大イテレーション回数指定
    early_stopping_rounds = 100,
    categorical_feature = columns_cat, # categorical_featureを設定
    optuna_seed = 31, # 再現性確保用のパラメータ
)

# 上記のパラメータでモデルを学習する
booster.run()
```

:::note info
↓↓↓ココまでの成果↓↓↓
:::

https://github.com/Blaster36/Script/blob/main/Kaggle/pred50k_uci_label-categoricalfeature-optuna-1-baseline.ipynb

https://github.com/Blaster36/Script/blob/main/Kaggle/pred50k_uci_onehot-optuna-1-baseline.ipynb

# 乱数Seedを指定した試行の自動化

データ分析も数値実験の扱いなので、筆者的は手放しで実行させました。
結果をファイル出力しておき、後にハイパーパラメータや精度指標を確認できるようにしました。

:::note warn
予測モデルを丸ごとpickleで保管・再実行することも考えたのですが、探索範囲が広いとDisk領域もそこそこ必要だし、乱数Seedさえ手元に残せば再現可能なので、筆者はこの方式としています。
forループを抜けてから一括でCSV出力としていますが、慎重に進めたい場合はforループの中で**追記モード**のCSV出力とするのが良いかと。
:::

```python
# 乱数Seedを指定した試行
# LightGBMTunerのインスタンス化、学習（≒探索）、最適モデル（およびパラメータ）取得などの流れは前節に同じ
# 更にモデル精度指標算出を追加
def autotuning(seed=31):
    booster = lgb.LightGBMTuner(
        params = params,
        train_set = lgb_train,
        valid_sets = lgb_valid,
        verbose_eval = 50,  # 50イテレーション毎に学習結果出力
        num_boost_round = 10000,  # 最大イテレーション回数指定
        early_stopping_rounds = 100,
        categorical_feature = columns_cat, # categorical_featureを設定
        optuna_seed = seed, # 再現性確保用のパラメータ
    )

    # 上記のパラメータでモデルを学習する
    booster.run()

    # 最適なパラメータの表示
    #print("Best params:", booster.best_params)

    # 最適なモデル（Boosterオブジェクト）を取得する
    model = booster.get_best_booster()

    # 最適なパラメータの表示
    best_params = model.params
    print("Best params:", best_params)

    # テストデータを予測する
    y_pred = model.predict(X_test, num_iteration=model.best_iteration)

    # AUCを計算
    fpr, tpr, thresholds = roc_curve(np.asarray(y_test), y_pred)
    #print("AUC", auc(fpr, tpr))

    metrics = {
        "seed": seed,
        "auc": auc(fpr, tpr),
        "accuracy": accuracy_score(np.asarray(y_test), np.round(y_pred)),
        "precision": precision_score(np.asarray(y_test), np.round(y_pred)),
    }

    # Tupleとして戻り値に
    return (best_params, metrics)

# 乱数シードを指定（from 素数を取得するワンライナー）
seeds = (lambda n:[x for x in range(2, n) if not 0 in map(lambda z: x%z, range(2, x))])(200)

# 各試行での情報を保存
listParams = []
listMetrics = []

# 乱数Seedを指定した試行を自動化
for s in seeds:
    print("### seed:%3s ###" %s)
    tunedparam, metrics = autotuning(s)
    tunedparam["seed"] = s
    listParams.append(tunedparam)
    listMetrics.append(metrics)

# DataFrameからCSVファイルを生成
# encoding="SJIS"だとJupyterLab（CSVTable）上で表示不可なことに注意
df_params = pd.DataFrame(listParams)
df_params.to_csv('lightgbm_label_params.csv', index=False, header=True, encoding='UTF-8', sep=',', quotechar='"', quoting=csv.QUOTE_ALL)
df_metrics = pd.DataFrame(listMetrics)
df_metrics.to_csv('lightgbm_label_metrics.csv', index=False, header=True, encoding='UTF-8', sep=',', quotechar='"', quoting=csv.QUOTE_ALL)
```

:::note info
↓↓↓ココまでの成果↓↓↓
:::

https://github.com/Blaster36/Script/blob/main/Kaggle/pred50k_uci_label-categoricalfeature-optuna-2-auto.ipynb

https://github.com/Blaster36/Script/blob/main/Kaggle/pred50k_uci_onehot-optuna-2-auto.ipynb

# 実行結果例

200未満の素数を乱数Seedに指定して実行すると、こんな感じ。
Label Encodingの時は、**乱数Seed:=127**で最も良い結果となりました。
一方、One-hot Encodingの時は、**乱数Seed:=67**で最も良い結果となりました。

Label Encoding(w/Categorical_Feature)の方かなと予想していましたが、One-hot Encodingに軍配が上がりました。
以下、CSV出力（モデルの精度指標のみ）の結果です。

<details><summary>Label Encoding</summary>
<table class='t'>
<tr>
<td>seed</td>
<td>auc</td>
<td>accuracy</td>
<td>precision</td>
</tr>
<tr>
<td>2</td>
<td>0.8938513791435835</td>
<td>0.85513011768606</td>
<td>0.6797814207650273</td>
</tr>
<tr>
<td>3</td>
<td>0.8944455631108564</td>
<td>0.8549643626719708</td>
<td>0.6798245614035088</td>
</tr>
<tr>
<td>5</td>
<td>0.8943227679593286</td>
<td>0.8567876678269518</td>
<td>0.6885428253615128</td>
</tr>
<tr>
<td>7</td>
<td>0.8948486272242101</td>
<td>0.8554616277142384</td>
<td>0.6853303471444568</td>
</tr>
<tr>
<td>11</td>
<td>0.8945356587826498</td>
<td>0.8556273827283275</td>
<td>0.6836283185840708</td>
</tr>
<tr>
<td>13</td>
<td>0.8944940099716203</td>
<td>0.8556273827283275</td>
<td>0.6828193832599119</td>
</tr>
<tr>
<td>17</td>
<td>0.8941711456348777</td>
<td>0.85513011768606</td>
<td>0.6829810901001112</td>
</tr>
<tr>
<td>19</td>
<td>0.8946069952792277</td>
<td>0.856124647770595</td>
<td>0.6871508379888268</td>
</tr>
<tr>
<td>23</td>
<td>0.8942950594528173</td>
<td>0.8572849328692193</td>
<td>0.6887417218543046</td>
</tr>
<tr>
<td>29</td>
<td>0.8941614218422198</td>
<td>0.8554616277142384</td>
<td>0.6849162011173184</td>
</tr>
<tr>
<td>31</td>
<td>0.8941373274887316</td>
<td>0.8554616277142384</td>
<td>0.6861642294713161</td>
</tr>
<tr>
<td>37</td>
<td>0.8946421902455731</td>
<td>0.8556273827283275</td>
<td>0.6840354767184036</td>
</tr>
<tr>
<td>41</td>
<td>0.8940544601229845</td>
<td>0.8556273827283275</td>
<td>0.6840354767184036</td>
</tr>
<tr>
<td>43</td>
<td>0.893948961275211</td>
<td>0.8556273827283275</td>
<td>0.6816192560175055</td>
</tr>
<tr>
<td>47</td>
<td>0.8944086471192618</td>
<td>0.8554616277142384</td>
<td>0.6828729281767956</td>
</tr>
<tr>
<td>53</td>
<td>0.8946586260367027</td>
<td>0.8549643626719708</td>
<td>0.6826280623608018</td>
</tr>
<tr>
<td>59</td>
<td>0.8944940099716203</td>
<td>0.8557931377424167</td>
<td>0.6835722160970231</td>
</tr>
<tr>
<td>61</td>
<td>0.8938493999645468</td>
<td>0.8557931377424167</td>
<td>0.6831683168316832</td>
</tr>
<tr>
<td>67</td>
<td>0.8945473617543442</td>
<td>0.8559588927565058</td>
<td>0.6847345132743363</td>
</tr>
<tr>
<td>71</td>
<td>0.8946022624597925</td>
<td>0.8554616277142384</td>
<td>0.6824696802646086</td>
</tr>
<tr>
<td>73</td>
<td>0.8945383263717863</td>
<td>0.85513011768606</td>
<td>0.680968096809681</td>
</tr>
<tr>
<td>79</td>
<td>0.8944894492547101</td>
<td>0.8556273827283275</td>
<td>0.6836283185840708</td>
</tr>
<tr>
<td>83</td>
<td>0.8943404945193951</td>
<td>0.8538040775733466</td>
<td>0.6797312430011199</td>
</tr>
<tr>
<td>89</td>
<td>0.8941464489225521</td>
<td>0.8554616277142384</td>
<td>0.6828729281767956</td>
</tr>
<tr>
<td>97</td>
<td>0.8938825297005931</td>
<td>0.8543013426156141</td>
<td>0.6784140969162996</td>
</tr>
<tr>
<td>101</td>
<td>0.8944909121261718</td>
<td>0.8559588927565058</td>
<td>0.6835164835164835</td>
</tr>
<tr>
<td>103</td>
<td>0.8945502014460055</td>
<td>0.8554616277142384</td>
<td>0.6845039018952063</td>
</tr>
<tr>
<td>107</td>
<td>0.8944299878323516</td>
<td>0.8546328526437925</td>
<td>0.6827354260089686</td>
</tr>
<tr>
<td>109</td>
<td>0.8942384377221198</td>
<td>0.8547986076578816</td>
<td>0.678298800436205</td>
</tr>
<tr>
<td>113</td>
<td>0.8944550287497268</td>
<td>0.8543013426156141</td>
<td>0.6820224719101123</td>
</tr>
<tr>
<td>127</td>
<td>0.894999819292349</td>
<td>0.8554616277142384</td>
<td>0.683684794672586</td>
</tr>
<tr>
<td>131</td>
<td>0.8943819712279</td>
<td>0.8562904027846843</td>
<td>0.6850220264317181</td>
</tr>
<tr>
<td>137</td>
<td>0.8943342988284981</td>
<td>0.8562904027846843</td>
<td>0.6854304635761589</td>
</tr>
<tr>
<td>139</td>
<td>0.894206340601223</td>
<td>0.8547986076578816</td>
<td>0.6826815642458101</td>
</tr>
<tr>
<td>149</td>
<td>0.8943289636502257</td>
<td>0.856124647770595</td>
<td>0.683461117196057</td>
</tr>
<tr>
<td>151</td>
<td>0.894111856315044</td>
<td>0.8544670976297033</td>
<td>0.6823793490460157</td>
</tr>
<tr>
<td>157</td>
<td>0.8944799836158397</td>
<td>0.8562904027846843</td>
<td>0.6846153846153846</td>
</tr>
<tr>
<td>163</td>
<td>0.8942234648024521</td>
<td>0.856124647770595</td>
<td>0.6838638858397366</td>
</tr>
<tr>
<td>167</td>
<td>0.8941788041972364</td>
<td>0.8564561577987734</td>
<td>0.6870144284128746</td>
</tr>
<tr>
<td>173</td>
<td>0.8941963586547778</td>
<td>0.8547986076578816</td>
<td>0.681465038845727</td>
</tr>
<tr>
<td>179</td>
<td>0.8942485917710898</td>
<td>0.8554616277142384</td>
<td>0.6828729281767956</td>
</tr>
<tr>
<td>181</td>
<td>0.8941140075966054</td>
<td>0.8556273827283275</td>
<td>0.6832229580573952</td>
</tr>
<tr>
<td>191</td>
<td>0.8944329996265374</td>
<td>0.8559588927565058</td>
<td>0.683920704845815</td>
</tr>
<tr>
<td>193</td>
<td>0.8943032343227507</td>
<td>0.8566219128128626</td>
<td>0.6840958605664488</td>
</tr>
<tr>
<td>197</td>
<td>0.8944410023939461</td>
<td>0.8544670976297033</td>
<td>0.6823793490460157</td>
</tr>
<tr>
<td>199</td>
<td>0.8940823407320209</td>
<td>0.85513011768606</td>
<td>0.6829810901001112</td>
</tr>
</table>
</details>

<details><summary>One-hot Encoding</summary>
<table class='t'>
<tr>
<td>seed</td>
<td>auc</td>
<td>accuracy</td>
<td>precision</td>
</tr>
<tr>
<td>2</td>
<td>0.8944794673082648</td>
<td>0.8534725675451682</td>
<td>0.6786114221724524</td>
</tr>
<tr>
<td>3</td>
<td>0.8941294968238479</td>
<td>0.8554616277142384</td>
<td>0.6853303471444568</td>
</tr>
<tr>
<td>5</td>
<td>0.8943444528774681</td>
<td>0.8557931377424167</td>
<td>0.6831683168316832</td>
</tr>
<tr>
<td>7</td>
<td>0.894542973139959</td>
<td>0.8556273827283275</td>
<td>0.6816192560175055</td>
</tr>
<tr>
<td>11</td>
<td>0.8951063507552719</td>
<td>0.8562904027846843</td>
<td>0.6842105263157895</td>
</tr>
<tr>
<td>13</td>
<td>0.8944831675125506</td>
<td>0.8559588927565058</td>
<td>0.6815217391304348</td>
</tr>
<tr>
<td>17</td>
<td>0.894911014389492</td>
<td>0.8549643626719708</td>
<td>0.6786492374727668</td>
</tr>
<tr>
<td>19</td>
<td>0.8946778154682308</td>
<td>0.8546328526437925</td>
<td>0.6831460674157304</td>
</tr>
<tr>
<td>23</td>
<td>0.8945323888346766</td>
<td>0.8546328526437925</td>
<td>0.6803097345132744</td>
</tr>
<tr>
<td>29</td>
<td>0.8944440141881322</td>
<td>0.8552958727001492</td>
<td>0.6797385620915033</td>
</tr>
<tr>
<td>31</td>
<td>0.8946890881836128</td>
<td>0.8546328526437925</td>
<td>0.6803097345132744</td>
</tr>
<tr>
<td>37</td>
<td>0.8942121060358075</td>
<td>0.8562904027846843</td>
<td>0.6854304635761589</td>
</tr>
<tr>
<td>41</td>
<td>0.8944607941843113</td>
<td>0.8552958727001492</td>
<td>0.6829268292682927</td>
</tr>
<tr>
<td>43</td>
<td>0.8940910319095292</td>
<td>0.8536383225592574</td>
<td>0.6769911504424779</td>
</tr>
<tr>
<td>47</td>
<td>0.8942133968047445</td>
<td>0.8549643626719708</td>
<td>0.6846846846846847</td>
</tr>
<tr>
<td>53</td>
<td>0.8945319585783642</td>
<td>0.8538040775733466</td>
<td>0.6773480662983425</td>
</tr>
<tr>
<td>59</td>
<td>0.8943666541031823</td>
<td>0.8566219128128626</td>
<td>0.6894618834080718</td>
</tr>
<tr>
<td>61</td>
<td>0.8942538408980999</td>
<td>0.8552958727001492</td>
<td>0.6817180616740088</td>
</tr>
<tr>
<td>67</td>
<td>0.8951266588532122</td>
<td>0.8556273827283275</td>
<td>0.6832229580573952</td>
</tr>
<tr>
<td>71</td>
<td>0.8937288421458431</td>
<td>0.8544670976297033</td>
<td>0.6795580110497238</td>
</tr>
<tr>
<td>73</td>
<td>0.8944449607520192</td>
<td>0.8547986076578816</td>
<td>0.6822742474916388</td>
</tr>
<tr>
<td>79</td>
<td>0.8939726253723869</td>
<td>0.8552958727001492</td>
<td>0.6825221238938053</td>
</tr>
<tr>
<td>83</td>
<td>0.8946227426602575</td>
<td>0.8543013426156141</td>
<td>0.675704989154013</td>
</tr>
<tr>
<td>89</td>
<td>0.8947935544162368</td>
<td>0.8569534228410409</td>
<td>0.6914414414414415</td>
</tr>
<tr>
<td>97</td>
<td>0.8942900684795947</td>
<td>0.8543013426156141</td>
<td>0.6796008869179601</td>
</tr>
<tr>
<td>101</td>
<td>0.8941228708766386</td>
<td>0.8556273827283275</td>
<td>0.6836283185840708</td>
</tr>
<tr>
<td>103</td>
<td>0.8943894576877338</td>
<td>0.8554616277142384</td>
<td>0.6845039018952063</td>
</tr>
<tr>
<td>107</td>
<td>0.8943374827252092</td>
<td>0.8534725675451682</td>
<td>0.6766334440753046</td>
</tr>
<tr>
<td>109</td>
<td>0.8947576710397919</td>
<td>0.856124647770595</td>
<td>0.6863181312569522</td>
</tr>
<tr>
<td>113</td>
<td>0.894577565747467</td>
<td>0.8549643626719708</td>
<td>0.6778741865509761</td>
</tr>
<tr>
<td>127</td>
<td>0.8937288421458431</td>
<td>0.8544670976297033</td>
<td>0.6795580110497238</td>
</tr>
<tr>
<td>131</td>
<td>0.8945638835967362</td>
<td>0.85513011768606</td>
<td>0.6789989118607181</td>
</tr>
<tr>
<td>137</td>
<td>0.8943548650802254</td>
<td>0.8533068125310791</td>
<td>0.6774553571428571</td>
</tr>
<tr>
<td>139</td>
<td>0.8944597615691621</td>
<td>0.8543013426156141</td>
<td>0.6780219780219781</td>
</tr>
<tr>
<td>149</td>
<td>0.8940351846401939</td>
<td>0.8546328526437925</td>
<td>0.6791208791208792</td>
</tr>
<tr>
<td>151</td>
<td>0.8939071403616562</td>
<td>0.8557931377424167</td>
<td>0.6823658269441402</td>
</tr>
<tr>
<td>157</td>
<td>0.8947278973029814</td>
<td>0.8546328526437925</td>
<td>0.6799116997792495</td>
</tr>
<tr>
<td>163</td>
<td>0.8943990093778665</td>
<td>0.8549643626719708</td>
<td>0.6818181818181818</td>
</tr>
<tr>
<td>167</td>
<td>0.8943369664176344</td>
<td>0.8549643626719708</td>
<td>0.6822222222222222</td>
</tr>
<tr>
<td>173</td>
<td>0.8948788312173328</td>
<td>0.85513011768606</td>
<td>0.6817679558011049</td>
</tr>
<tr>
<td>179</td>
<td>0.8938926837495634</td>
<td>0.8557931377424167</td>
<td>0.6839779005524862</td>
</tr>
<tr>
<td>181</td>
<td>0.894712063870689</td>
<td>0.8539698325874358</td>
<td>0.676923076923077</td>
</tr>
<tr>
<td>191</td>
<td>0.8942296604933492</td>
<td>0.8544670976297033</td>
<td>0.6803551609322974</td>
</tr>
<tr>
<td>193</td>
<td>0.8946532908584303</td>
<td>0.8571191778551301</td>
<td>0.6871569703622393</td>
</tr>
<tr>
<td>197</td>
<td>0.8946267870695931</td>
<td>0.8544670976297033</td>
<td>0.679162072767365</td>
</tr>
<tr>
<td>199</td>
<td>0.8947976848768349</td>
<td>0.8543013426156141</td>
<td>0.6792035398230089</td>
</tr>
</table>
</details>

**Label Encoding**の方式で最も良い精度のモデルを獲得できる乱数Seedで、改めて試行し精度や特徴量重要度を確認しました。
乱数シードの違いでAUCは最大約1%の差が出ましたが、特徴量重要度の上位ランクインした特徴量に差はありませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/a654d42e-15ed-e68c-abe8-0bb004dc20dd.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/43ad877a-4464-da54-c0ed-f8d1668a775b.png)

:::note info
↓↓↓ココまでの成果↓↓↓
:::

https://github.com/Blaster36/Script/blob/main/Kaggle/pred50k_uci_label-categoricalfeature-optuna-3-rerun.ipynb

https://github.com/Blaster36/Script/blob/main/Kaggle/pred50k_uci_onehot-optuna-3-rerun.ipynb

# Notebook（Pythonコード）

GitHubで公開しています。このタイミングですが、参考サイトの方々に感謝申し上げます。

https://github.com/Blaster36/Script/tree/main/Kaggle

# まとめ

Optunaを利用してLightGBMのハイパーパラメータ・チューニングをする方法を紹介しました。
AutoML、例えばTPOT[^7]やPyCaret[^8]を利用したら、モデル構築は更に省エネ化できそうだなと実感しました。。。


[^1]: カテゴリ値を含むデータでのLightGBMを使った分析 | https://qiita.com/Blaster36/items/d0a99f9d4f6b12a2dd83
[^2]: カテゴリ値を含むデータでのLightGBMを使った分析（２本目：One-hot Encoding編） | https://qiita.com/Blaster36/items/8461ec0dca662750eb71
[^3]: Adult Census Incomeh | https://www.kaggle.com/datasets/uciml/adult-census-income
[^4]: PEP 8 – Style Guide for Python Code | https://peps.python.org/pep-0008/
[^5]: LightGBMでOptunaを使用するときの再現性確保について | https://book-read-yoshi.hatenablog.com/entry/2021/03/22/lightgbm_optuna_deterministic
[^6]: optuna.integration.lightgbm.LightGBMTuner | https://optuna.readthedocs.io/en/stable/reference/generated/optuna.integration.lightgbm.LightGBMTuner.html
[^7]: TPOT | http://automl.info/tpot/
[^8]: PyCaret | https://pycaret.org/