勾配ブースティング決定木（XGBoost, LightGBM, CatBoost）でOptunaを使ってみた

* 製造業出身のデータサイエンティストがお送りする記事
* 今回は勾配ブースティング決定木（XGBoost, LightGBM, CatBoost）でOptunaを使ってみました。

##はじめに
勾配ブースティング木に関しては、過去に記事を書いておりますのでそちらを参照して頂けますと幸いです。

* [勾配ブースティング決定木（XGBoost, LightGBM, CatBoost）を実装してみた](https://qiita.com/DS27/items/1d0fe42fe2a406b7128a)
* [LightGBMのハイパーパラメータチューニング（Optuna）をしてみた](https://qiita.com/DS27/items/ec6a747977e57bb1837e)

##XGBoostの実装
まずは、XGBoostをOptunaでハイパラチューニングを実装してみます。
今回もUCI Machine Learning Repositoryで公開されているボストン住宅の価格データを用いて予測モデルを構築します。

```php
# ライブラリーのインポート
import os

import pandas as pd
import numpy as np

import seaborn as sns
import matplotlib.pyplot as plt
import plotly

%matplotlib inline

# ボストンの住宅価格データ
from sklearn.datasets import load_boston

# 前処理
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

# XGBoost
import xgboost as xgb

# Optuna
import optuna
from optuna.samplers import TPESampler

# 評価指標
from sklearn.metrics import r2_score
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error

import warnings

warnings.simplefilter(action="ignore", category=FutureWarning)
warnings.simplefilter(action="ignore", category=pd.core.common.SettingWithCopyWarning)

# データセットの読込み
boston = load_boston()

# 説明変数の格納
df = pd.DataFrame(boston.data, columns = boston.feature_names)
# 目的変数の追加
df['MEDV'] = boston.target

# データの中身を確認
df.head()
```

![スクリーンショット 2020-11-09 20.33.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/2ffa1a41-98dd-2798-1f6d-8f3385bf5f75.png)

次にoptunaの事前準備をします。

```php
# ランダムシード値
RANDOM_STATE = 10

# 学習データと評価データの割合
TEST_SIZE = 0.2

# 学習データと評価データを作成
x_train, x_test, y_train, y_test = train_test_split(
    df.iloc[:, 0 : df.shape[1] - 1],
    df.iloc[:, df.shape[1] - 1],
    test_size=TEST_SIZE,
    random_state=RANDOM_STATE,
)

# trainのデータセットの2割をモデル学習時のバリデーションデータとして利用する
x_train, x_valid, y_train, y_valid = train_test_split(
    x_train, y_train, test_size=TEST_SIZE, random_state=RANDOM_STATE
)

def objective(trial):

    param = {
        "eta": trial.suggest_loguniform("eta", 1e-8, 1.0),
        "gamma": trial.suggest_loguniform("gamma", 1e-8, 1.0),
        "max_depth": trial.suggest_int("max_depth", 3, 8),
        "min_child_weight": trial.suggest_loguniform("min_child_weight", 1, 40),
        "max_delta_step": trial.suggest_loguniform("max_delta_step", 1e-8, 1.0),
        "subsample": trial.suggest_uniform("subsample", 0.0, 1.0),
        "reg_lambda": trial.suggest_uniform("reg_lambda", 0.0, 1000.0),
        "reg_alpha": trial.suggest_uniform("reg_alpha", 0.0, 1000.0),
    }

    model = xgb.XGBRegressor(**param)

    model.fit(
        x_train,
        y_train,
        eval_set=[(x_valid, y_valid)],
        early_stopping_rounds=50,
        verbose=False,
    )

    preds = model.predict(x_valid)
    mae = mean_absolute_error(y_valid, preds)

    return mae
```

各パラメータについては、[公式文章](https://xgboost.readthedocs.io/en/latest/parameter.html)を参照してください。

次にoptunaで最適なハイパーパラメータを見つけます。

```php
# optunaで最適値を見つける
# create_studyメソッドの引数"sampler"にサンプラーと乱数シードを指定
study = optuna.create_study(direction='minimize', sampler=TPESampler(seed=RANDOM_STATE))
study.optimize(objective, n_trials=500)
```

あとは、チューニング結果のハイパーパラメータを使ってモデルの学習と予測を行います。

```php
# チューニングしたハイパーパラメーターをフィット
best_params = study.best_params
best_params["random_state"] = RANDOM_STATE

# チューニングしたハイパーパラメーターをフィット
optimised_model = xgb.XGBRegressor(**(best_params))
optimised_model.fit(x_train, y_train)

# XGBoost推論
y_pred = optimised_model.predict(x_test)

# 評価
def calculate_scores(true, pred):
    """全ての評価指標を計算する

    Parameters
    ----------
    true (np.array)       : 実測値
    pred (np.array)       : 予測値

    Returns
    -------
    scores (pd.DataFrame) : 各評価指標を纏めた結果

    """
    scores = {}
    scores = pd.DataFrame({'R2': r2_score(true, pred),
                          'MAE': mean_absolute_error(true, pred),
                          'MSE': mean_squared_error(true, pred),
                          'RMSE': np.sqrt(mean_squared_error(true, pred))},
                           index = ['scores'])
    return scores

scores = calculate_scores(y_test, y_pred)
print(scores)
```

出力結果は下記のようになります。

```
              R2       MAE       MSE      RMSE
scores  0.768409  3.302701  24.21993  4.921375
```

##LightGBMの実装
LightGBMは下記に実装サンプルがありますので、そちらの記事を参考にしてください。

* [LightGBMのハイパーパラメータチューニング（Optuna）をしてみた](https://qiita.com/DS27/items/ec6a747977e57bb1837e)

##CatBoostの実装
最後に、CatBoostを実装します。

```php
# ライブラリーのインポート
import os

import pandas as pd
import numpy as np

import seaborn as sns
import matplotlib.pyplot as plt
import plotly

%matplotlib inline

# ボストンの住宅価格データ
from sklearn.datasets import load_boston

# 前処理
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

# CatBoost
import catboost as cb
from catboost import CatBoost, Pool

# Optuna
import optuna
from optuna.samplers import TPESampler

# 評価指標
from sklearn.metrics import r2_score
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error

import warnings

warnings.simplefilter(action="ignore", category=FutureWarning)
warnings.simplefilter(action="ignore", category=pd.core.common.SettingWithCopyWarning)

# データセットの読込み
boston = load_boston()

# 説明変数の格納
df = pd.DataFrame(boston.data, columns = boston.feature_names)
# 目的変数の追加
df['MEDV'] = boston.target

# データの中身を確認
df.head()
```

![スクリーンショット 2020-11-09 20.33.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/2ffa1a41-98dd-2798-1f6d-8f3385bf5f75.png)

次にoptunaの事前準備をします。

```php
# ランダムシード値
RANDOM_STATE = 10

# 学習データと評価データの割合
TEST_SIZE = 0.2

# 学習データと評価データを作成
x_train, x_test, y_train, y_test = train_test_split(
    df.iloc[:, 0 : df.shape[1] - 1],
    df.iloc[:, df.shape[1] - 1],
    test_size=TEST_SIZE,
    random_state=RANDOM_STATE,
)

# trainのデータセットの2割をモデル学習時のバリデーションデータとして利用する
x_train, x_valid, y_train, y_valid = train_test_split(
    x_train, y_train, test_size=TEST_SIZE, random_state=RANDOM_STATE
)

def objective(trial):

    param = {
        "iterations": trial.suggest_int("iterations", 50, 300),
        "depth": trial.suggest_int("depth", 4, 10),
        "learning_rate": trial.suggest_loguniform("learning_rate", 0.01, 0.3),
        "random_strength": trial.suggest_int("random_strength", 0, 100),
        "bagging_temperature": trial.suggest_loguniform(
            "bagging_temperature", 0.01, 100.00
        ),
        "od_type": trial.suggest_categorical("od_type", ["IncToDec", "Iter"]),
        "od_wait": trial.suggest_int("od_wait", 10, 50),
    }

    model = cb.CatBoostRegressor(**param)

    model.fit(
        x_train,
        y_train,
        eval_set=[(x_valid, y_valid)],
        early_stopping_rounds=100,
        verbose=False,
    )

    preds = model.predict(x_valid)
    mae = mean_absolute_error(y_valid, preds)

    return mae
```

次にoptunaで最適なハイパーパラメータを見つけます。

```php
# optunaで最適値を見つける
# create_studyメソッドの引数"sampler"にサンプラーと乱数シードを指定
study = optuna.create_study(direction='minimize', sampler=TPESampler(seed=RANDOM_STATE))
study.optimize(objective, n_trials=50)
```

あとは、チューニング結果のハイパーパラメータを使ってモデルの学習と予測を行います。

```php
best_params = study.best_params
best_params["random_state"] = RANDOM_STATE

# チューニングしたハイパーパラメーターをフィット
optimised_model = cb.CatBoostRegressor(**(best_params))
optimised_model.fit(x_train, y_train)

# CatBoost推論
y_pred = optimised_model.predict(x_test)

# 評価
def calculate_scores(true, pred):
    """全ての評価指標を計算する

    Parameters
    ----------
    true (np.array)       : 実測値
    pred (np.array)       : 予測値

    Returns
    -------
    scores (pd.DataFrame) : 各評価指標を纏めた結果

    """
    scores = {}
    scores = pd.DataFrame({'R2': r2_score(true, pred),
                          'MAE': mean_absolute_error(true, pred),
                          'MSE': mean_squared_error(true, pred),
                          'RMSE': np.sqrt(mean_squared_error(true, pred))},
                           index = ['scores'])
    return scores

scores = calculate_scores(y_test, y_pred)
print(scores)
```

出力結果は下記のようになります。

```
              R2       MAE        MSE     RMSE
scores  0.863433  2.596167  14.282276  3.77919
```

##さいごに
最後まで読んで頂き、ありがとうございました。
今回は勾配ブースティング決定木の3つのアルゴリズム（XGBoost, LightGBM, CatBoost）でOptunaを使ってみました。

訂正要望がありましたら、ご連絡頂けますと幸いです。