【XGboost】ハイパラメータを自動で最適化

## はじめに

東京23区の賃料を予測する回帰モデルをXGboostで作成しています。
モデルを作成するときにパラメータのチューニングで苦戦して色々と調べたので、この記事にまとめます。

機械学習に関して特別知識があるわけではないため、間違いを含んでいるかもしれません。その際はコメント欄で指摘していただけると助かります。

チューニングの方法として有名なものでグリッドサーチやベイズの最適化という手法があるのですが、精度や即効性の観点から今回はOptunaというハイパラメータ最適化ツールを利用して最適化してみました。
Optunaはベイズ最適化アルゴリズムの一種を利用したハイパーパラメータチューニングのフレームワークです

[Google「ベイズの最適化」](https://www.google.com/search?q=%E3%83%99%E3%82%A4%E3%82%BA%E3%81%AE%E6%9C%80%E9%81%A9%E5%8C%96&oq=%E3%83%99%E3%82%A4%E3%82%BA%E3%81%AE%E6%9C%80%E9%81%A9%E5%8C%96&aqs=chrome..69i57.2943j0j4&sourceid=chrome&ie=UTF-8)


## チューニングすべきとされるハイパラメータ
そもそもどの値をチューニングすべきなのか？

XGboostのモデル作成においてチューニングすべきとされるハイパラメータの一覧です。

|パラメータ|デフォルト|内容|よく取る値|
|---|---|---|---|
|eta	|0.3	|学習率を調整。小さくするとより頑健になる	|0.01~0.2|
|max_depth	|6	|木の深さの最大値。過学習を制御する。	|3,4,5, ... 10|
|min_child_weight	|1	|子ノードにおいて観察されるデータの重み付けの合計値の最小の値|	
|subsample	|1	|各木においてランダムに抽出される列の割合	|0.5~1|
|colsample_bytree	|1	|各レベル単位での、分割における列のsubsample比率	|0.5~1|
|lambda	|1	|重みに関するL2正則化項|	
|alpha	|0	|重みに関するL1正則化項|

## Optunaでチューニングしてみる
今回は5つのパラメータ`n_estimators`、`max_depth`、`min_child_weight`、`subsample`、`colsample_bytree`をOptunaを用いてチューニングしていきます。

### 方針
1. 最適化したいパラメータを選択。
この時の注意点としてはパラメータを増やすことによって処理に必要な時間が指数関数的に増える。そのため、できるだけ少ないパラメータを選択する。

2. 最小化したい目的関数を定義
今回は回帰タスクなので、MSE(平均二乗誤差)を目的関数として最小化を行う。
Optunaは目的関数の最小化しかできないので、確率など最大化したいものは1-xや1/xなどを目的関数とする。
3. 最適化の実行
4. 結果を出力
最適化されたパラメータの値や、最小化された目的関数の値を確認することができる。

### 実際のコード
Optunaのインストール

```
$ pip install optuna
```
Optunaのインポート

```python
import optuna
```
ハイパラメータの指定と目的関数の作成
具体的なパラメータの指定方法は新しい項目を設けて記している。

```python
def opt(trial):
    # パラメータの指定
    n_estimators = trial.suggest_int('n_estimators', 0, 1000)
    max_depth = trial.suggest_int('max_depth', 1, 20)
    min_child_weight = trial.suggest_int('min_child_weight', 1, 20)
    subsample = trial.suggest_discrete_uniform('subsample', 0.5, 0.9, 0.1)
    colsample_bytree = trial.suggest_discrete_uniform('colsample_bytree', 0.5, 0.9, 0.1)
    # XGBostのインスタンス生成
    xgboost_tuna = XGBRegressor(
        random_state=42,
        n_estimators = n_estimators,
        max_depth = max_depth,
        min_child_weight = min_child_weight,
        subsample = subsample,
        colsample_bytree = colsample_bytree,
    )
    # 学習
    xgboost_tuna.fit(X_train,y_train)
    # 予測
    tuna_pred_test = xgboost_tuna.predict(X_test)
    # MSEを返す
    return np.sqrt(mean_squared_error(y_test, tuna_pred_test))
```
最適化
最適化を実行する時に試行回数を指定できる。

```python
# studyオブジェクトの作成
study = optuna.create_study()
# 最適化の実行
study.optimize(opt, n_trials=50)
```
結果の確認

```python
# 最適パラメータの表示
print(study.best_params)
# 最小化された目的関数を表示
print(study.study.best_value)
```
最適化の続行
先ほど試行を50回行い、今回50回行うので合計で100回試行を行ったことになる。

```python
study.optimize(objective, n_trials=50)
```


### パラメータの指定方法
Optunaが対応しているデータ型は5つ。データ型によってパラメータの指定方法が異なるので1つずつみていく。

1. Categorical parameter：カテゴリー型パラメーター
    optimizer = trial.suggest_categorical('optimizer', ['MomentumSGD', 'Adam'])
第一引数でパラメーターの名前、第二引数でリスト型で値を指定する。

2. Int parameter：整数値型パラメーター
    num_layers = trial.suggest_int('num_layers', 1, 3)
第一引数でパラメーターの名前、第二・第三引数に指定した数値の間でパラメーターをチューニング。

3. Uniform parameter：浮動小数点型パラメーター
    dropout_rate = trial.suggest_uniform('dropout_rate', 0.0, 1.0)
第一引数でパラメーターの名前、第二・第三引数に指定した数値の間でパラメーターをチューニング。


4. Loguniform parameter：対数型パラメーター
    learning_rate = trial.suggest_loguniform('learning_rate', 1e-5, 1e-2)
第一引数でパラメーターの名前、第二・第三引数に指定した数値の間でパラメーターをチューニング。

5. Discrete-uniform parameter：離散値型パラメーター
    drop_path_rate = trial.suggest_discrete_uniform('drop_path_rate', 0.0, 1.0, 0.1)
第一引数でパラメーターの名前、第二引数以降で値を指定する。






## 参考サイト
[Optuna公式ドキュメント](https://optuna.readthedocs.io/en/stable/index.html)
[新米データサイエンティストのFXブログ](http://www.algo-fx-blog.com/xgboost-optuna-hyperparameter-tuning/)