[翻訳] LightGBMのパラメーターチューニング

[Parameters Tuning — LightGBM 4\.5\.0\.99 documentation](https://lightgbm.readthedocs.io/en/latest/Parameters-Tuning.html)の翻訳です。

:::note warn
本書は著者が手動で翻訳したものであり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

## 有用な他のリンクのリスト

- [Parameters](https://lightgbm.readthedocs.io/en/latest/Parameters.html)
- [Python API](https://lightgbm.readthedocs.io/en/latest/Python-API.html)
- 自動ハイパーパラメータチューニングのための[FLAML](https://github.com/microsoft/FLAML)
- 自動ハイパーパラメータチューニングのための[Optuna](https://medium.com/optuna/lightgbm-tuner-new-optuna-integration-for-hyperparameter-optimization-8b7095e99258)

# Leaf-wise(Best-first)ツリーのパラメーターのチューニング

他の多くの人気のツールではdepth-widthツリー成長を使用していますが、LightGBMでは[leaf\-wise](https://lightgbm.readthedocs.io/en/latest/Features.html#leaf-wise-best-first-tree-growth)ツリー成長アルゴリズムを採用しています。depth-width成長と比べて、leaf-wiseアルゴリズムははるかに高速に収束することができます。しかし、適切なパラメーターを用いない場合にleaf-wise成長は過学習する場合があります。

leaf-wiseツリーを用いて優れた結果を得るには、いくつかの重要なパラメーターが存在します:

1. `num_leaves`。これはツリーモデルの複雑性を制御する主要なパラメーターです。理論的には、depth-wiseツリーと同じ数の葉を得るために、`num_leaves = 2^(max_depth)`を設定することができます。しかし、この簡単な変換は実際には良くありません。leaf-wiseツリーは通常、固定数の葉の数において、depth-wiseツリーよりもはるかに深くなります。深さに制限を設けないことで過学習を引き起こすことがあります。このため、`num_leaves`をチューニングしようとする際には、`2^(max_depth)`よりも小さくすべきです。例えば、`max_depth=7`の場合、depth-wiseツリーは優れた精度を達成できますが、`num_leaves`を`127`にすると過学習となり、`70`や`80`に設定することで、depth-wiseよりも優れた精度を得られる可能性があります。
1. `min_data_in_leaf`。これは、leaf-wiseツリーでの過学習を避けるための非常に重要なパラメーターです。この最適な値はトレーニングのサンプル数と`num_leaves`に依存します。大きな値に設定すると、あまりに深い木に成長させることを避けることができますが、学習不足に陥る場合があります。実際には、大規模データセットにおいては数百や数千に設定する事で十分です。
1. `max_depth`。また、ツリーの深さを明示的に制限するために`max_depth`を使うことができます。`max_depth`を設定した場合、`num_leaves`も`<= 2^max_depth`の値に明示的に設定します。

# 高速化のために

## さらなる計算資源を追加

利用するシステムにおいて、LightGBMは数多くのオペレーションを並列化するためにOpenMPを活用します。LightGBMによって使用されるスレッドの最大数は、パラメーター`num_threads`で制御することができます。デフォルトでは、これはOpenMPのデフォルトの挙動(実際のCPUコアあたり1つのスレッド、あるいは環境変数`OMP_NUM_THREADS`が設定されている場合にはその値)に従います。ベストなパフォーマンスを得るには、これを利用できる**実際の**CPUコア数に設定します。

より多くのCPUを持つマシンに移行することで、高速なトレーニングを達成できる可能性があります。

(複数マシンでの)分散トレーニングによって、トレーニング時間を削減することができます。詳細は[Distributed Learning Guide](https://lightgbm.readthedocs.io/en/latest/Parallel-Learning-Guide.html)をご覧ください。

## GPU有効化バージョンのLightGBMを使う

LightGBMのGPU有効化バージョンを用いることで、トレーニングを高速化できる可能性があります。詳細は[GPU Tutorial](https://lightgbm.readthedocs.io/en/latest/GPU-Tutorial.html)をご覧ください。

## より狭くツリーを成長させる

LightGBMの合計トレーニング時間は、ツリーのノードの合計数が増えれば増加します。LightGBMはツリーごとのノード数を制御するために活用できるいくつかのパラメーターを提供しています。

以下での提言はトレーニングを高速化しますが、トレーニングの精度を損なう場合があります。

### `max_depth`を減らす

このパラメーターは、それぞれのツリーのルートノードとリーフノード間の最大距離を制御する整数値です。トレーニング時間を削減するために`max_depth`を減らします。

### `num_leaves`を減らす

LightGBMは深さに関係なく、ノードを追加することによるゲインに基づいてツリーにノードを追加します。以下の[the feature documentation](https://lightgbm.readthedocs.io/en/latest/Features.html#leaf-wise-best-first-tree-growth)の図ではこのプロセスを説明しています。

![](https://lightgbm.readthedocs.io/en/latest/_images/leaf-wise.png)

この成長戦略のため、ツリーの複雑性を制御するためだけに`max_depth`を用いることは直感的ではありません。`num_leaves`パラメーターは、ツリーごとのノードの最大数を設定します。トレーニング時間を削減するには`num_leaves`を削減します。

### `min_gain_to_split`を増やす

新規ツリーノードを追加する際、LightGBMはより大きなゲインを得られる分割点を選択します。基本的にゲインは、分割点を追加することで得られたトレーニングのロスの削減となります。デフォルトでは、LightGBMは`min_gain_to_split`は0.0は設定し、これは「小さすぎる改善が認められない」を意味します。しかし、実際にはトレーニングロスにおける非常に小さい改善は、モデルの汎化エラーに意味のあるインパクトを与えないことに気づくかもしれません。トレーニング時間を削減するために`min_gain_to_split`を増やしましょう。

### `min_data_in_leaf`と`min_sum_hessian_in_leaf`を増やす

LightGBMでは、トレーニングデータのサイズと特徴量の分布に応じて、少数の観測値のみを説明するツリーノードを追加することができます。ほとんどの極端なケースでは、トレーニングデータの単一の観測値のみが属するツリーノードの追加を検討しましょう。これは、うまく汎化されることはほとんどなく、過学習のサインの可能性があります。

これは、`max_depth`と`num_leaves`のようなパラメーターで間接的に回避することは可能ですが、LightGBMではこれらの過度に特化したツリーノードの追加を直接回避する助けになるパラメーターを提供しています。

- `min_data_in_leaf`: 追加されるツリーノードに最低限含まれるべき観測値の数。
- `min_sum_hessian_in_leaf`: リーフにおける観測値に対するHessian(それぞれの観測値に対して評価される目的関数の二階導関数)の合計値の最小値。いくつかの回帰においては、これは単にそれぞれのノードに属すべき最小レコード数となります。分類においては、確率分布に対する合計値を表現します。このパラメータの値に対してどのように理由づけを行うのかに関する優れた説明に関しては、[こちらのStack Overflowの回答](https://stats.stackexchange.com/questions/317073/explanation-of-min-child-weight-in-xgboost-algorithm)をご覧ください。

## 成長させるツリーを減らす

### `num_iterations`を減らす

`num_iterations`パラメーターは実行されるブースティングラウンドの数を制御します。LightGBMは学習器として決定木を用いるので、これは「木の数」と考えることができます。

`num_iterations`を変更しようとする際には、`learning_rate`も変更しましょう。`learning_rate`はトレーニング時間に影響を与えません。一般的なルールとして、`num_iterations`を削減した際には、`learning_rate`も減らすべきです。

`num_iterations`と`learning_rate`の適切な値を選択することは、データと目標に大きく依存するので、多くの場合、ハイパーパラメーターチューニングを通じて取りうる値から選択することになります。

トレーニング時間を削減するためには、`num_iterations`を減らしましょう。

### Early Stoppingを使う

Early Stoppingを有効化すると、それぞれのブースティングラウンドの後に、トレーニングプロセスには含まれないデータを含む検証セットに対して、モデルのトレーニングの精度が評価されます。そして、その精度は以前のブースティングラウンドと比較されます。モデルの精度が以降のいくつかの連続するラウンドで改善しなかった場合、LightGBMはトレーニングプロセスを停止します。

この「連続するラウンドの数」は、パラメーター`early_stopping_round`によって制御されます。`early_stopping_round=1`は、「検証セットでの精度が改善しなくなった初めてのタイミングでトレーニングを停止する」と言うことです。

トレーニング時間を削減するために、`early_stopping_round`と検証セットを指定しましょう。

## 分割数の削減を検討する

以前のセクションで説明したパラメータは、幾つのツリーを構成するのか、ツリーごとに幾つのノードを構成するのかを制御します。モデルにツリーノードを追加する時間の送料を削減することで、トレーニング時間をさらに削減することができます。

以下の提案はトレーニングをスピードアップしますが、トレーニングの精度を損なう可能性があります。

### データセットを作成する際、特徴量の事前フィルタリングを有効化する

デフォルトでは、LightGBMの`Dataset`オブジェクトが構築された際、`min_data_in_leaf`の値に基づいていくつかの特徴料が除外されます。

シンプルな例として、`feature_1`という特徴量を持つ1000の観測値を持つデータセットを考えてみます。`feature_1`は二つの値のみを取ります: 25.0(995の観測値)と 50.0(5つの観測値)です。`min_data_in_leaf = 10`の場合、この特徴量は、リーフノードの少なくとも一つは観測値を5つしか持たない適切なスプリットになってしまうため分割されません。

この特徴量を再検討し、すべてのイテレーションで無視するのではなく、LightGBMはトレーニング前に`Dataset`を構築する際にこの特徴量を除外します。

このデフォルトの挙動は`feature_pre_filter=False`を設定することで上書きすることができますので、トレーニング時間を削減するためには`feature_pre_filter=True`を設定しましょう。

### データセットを作成する際、`max_bin`や`max_bin_by_feature`を減らす

LightGBMのトレーニングは、トレーニングスピードを改善し、メモリ要件を削減するために、[連続的な特徴量を離散的なビンに分割](https://lightgbm.readthedocs.io/en/latest/Features.html#optimization-in-speed-and-memory-usage)します。このビニングは`Dataset`構成時に一度行われます。ノード追加の際に検討される分割数は`O(#feature * #bin)`なので、特徴量ごとのビンの数を減らすことで、評価されるべきスプリットの数を削減することができます。

`max_bin`は、特徴量が分割されるビンの最大数を制御します。`max_bin_by_feature`を設定することで、特徴量ごとにこの最大値を設定することができます。

トレーニング時間を削減するために、`max_bin`や`max_bin_by_feature`を減らしましょう。

### データセットを作成する際、`min_data_in_bin`を増やす

候補となる分割ポイントとしてビンの境界を評価したとしても、最終的なモデルをそれほど変更しない可能性があるような少数の観測値を含むビンがいくつか存在する場合があります。`min_data_in_bin`を設定することで、ビンの粒度を制御することができます。

トレーニング時間を削減するには`min_data_in_bin`を増やしましょう。

### `feature_fraction`を減らす

デフォルトでは、LightGBMはトレーニングプロセスにおいて`Dataset`のすべての特徴量を考慮します。`feature_fraction`を`> 0`と`<= 1.0`の間の値に設定することで、この挙動を変更することができます。例えば、`feature_fraction`を`0.5`に設定することで、それぞれのツリーの構成の最初に、特徴量の`50%`をランダムに選択するようにLightGBMに指示することができます。これによって、それぞれのツリーノードを追加するために評価すべきスプリットの総数を削減することができます。

トレーニング時間を削減するために`feature_fraction`を減らしましょう。

### `max_cat_threshold`を減らす

LightGBMは、[カテゴリー特徴量の最適な分割ポイントを特定するためのカスタムのアプローチ](https://lightgbm.readthedocs.io/en/latest/Advanced-Topics.html#categorical-feature-support)を採用しています。このプロセスでは、LightGBMはカテゴリー特長量を二つのグループに分割する分割点を探索します。これらは時に「k-vs.-rest」スプリットと呼ばれます。`max_cat_threshold`の値を大きくすると、探索する分割点が多くなり、グループのサイズも大きくなります。

トレーニング時間を削減するために、`max_cat_threshold`を減らしましょう。

## データを減らす

### バギングを使う

デフォルトでは、LightGBMはそれぞれのイテレーションでトレーニングデータに含まれるすべての観測値を使用します。代わりに、LightGBMにトレーニングデータをランダムにサンプリングするように指示することができます。置き換えなしの複数のランダムサンプルに対するこのようなトレーニングプロセスは「バギング」と呼ばれます。

新規サンプルをどの程度の頻度で取得するのかを制御するために、`bagging_freq`を0より大きい整数値に設定しましょう。サンプルのサイズを制御するために、`bagging_fraction`を`> 0.0`と`< 1.0`の間で設定します。例えば、`{"bagging_freq": 5, "bagging_fraction": 0.75}`は、LightGBMに「5つのイテレーションごとに置き換えなしに再サンプリングを行い、トレーニングデータから75%のサンプルを抽出してください」ということを指示します。

トレーニング時間を削減するために、`bagging_fraction`を減らしましょう。

### 構築したデータセットを`save_binary`を指定して保存する

これはLightGBM CLIにのみ適用されます。パラメーター`save_binary`を引き渡すと、トレーニングデータセットとすべての検証セットは、LightGBMで解釈できるバイナリーフォーマットで保存されます。これによって、`Dataset`の構成の際に行われたビニングやその他の作業を再度実行する必要がないため、次回はスピードアップします。

# 精度改善のために

- 大きな`max_bin`を使う(遅くなる可能性あり)
- 大きな`num_iterations`と小さな`learning_rate`を使う
- 大きな`num_leaves`を使う(過学習の可能性あり)
- 大きなトレーニングデータを使う
- `dart`を試す

# 過学習に対応する

- 小さな`max_bin`を使う
- 小さな`num_leaves`を使う
- `min_data_in_leaf`と`min_sum_hessian_in_leaf`を使う
- `bagging_fraction`と`bagging_freq`を設定してバギングを使う
- `feature_fraction`を設定して、特徴量のサブサンプリングを使う
- 大きなトレーニングデータを使う
- 正則化のために`lambda_l1`、`lambda_l2`、`min_gain_to_split`を試す
- 深いツリーに成長しないように`max_depth`を試す
- `extra_trees`を試す
- `path_smooth`を増やしてみる