はじめに
こんにちは。船井総合研究所の平尾です。
とある案件においてXGBoostのパラメータチューニングをグリッドサーチによって行っていました。グリッドサーチによる計算実行時間は約1時間と長かったです。
しかし、先輩社員に「Optuna」というツールの存在を教えてもらい、Optunaを用いたところ実行が約30秒で終わって非常に感動しました。
そこで今回は、scikit-learnのdiabetes(糖尿病データ)に対するモデル構築において、グリッドサーチとOptunaを実際に使って実行時間、精度を比較してみました。
各ツールの概要
グリッドサーチ
グリッドサーチは、指定したハイパーパラメータの値の全ての組み合わせにおいてモデル構築を行う方法です。
以下は例です。
param_grid = {
'learning_rate':[0.01, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30],
'max_depth': [2, 4, 6, 8, 10],
'min_child_weight':[0, 1, 2, 3, 4],
'subsample':[0.2, 0.4, 0.6, 0.8, 1],
'colsample_bytree':[0.2, 0.4, 0.6, 0.8, 1],
'reg_lambda':[2, 4, 6, 8, 10],
}
組み合わせの総数は21875通りとなります。
実行時間が長くなるのも納得です。
変数として扱うパラメータの数が多いほど計算量が多くなり、実行時間も長くなります。
Optuna
指定したハイパーパラメータの範囲の中で最適と思われる数値の組み合わせを作成し、
モデル学習を行うという流れを指定回数繰り返します。
以下は例です。
param = {
'learning_rate':trial.suggest_float('learning_rate', 0.01, 0.30),
'max_depth': trial.suggest_int('max_depth', 2, 10),
'min_child_weight': trial.suggest_int('min_child_weight', 0, 4),
'subsample': trial.suggest_float('subsample', 0.2, 1),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.2, 1),
'reg_lambda': trial.suggest_int('reg_lambda', 2, 10),}
このように調整したいパラメータの範囲を指定します。
データの準備
まずはモデル学習できるようにデータを準備します。
ライブラリをインポートします。
import pandas as pd
import numpy as np
import seaborn as sns
from sklearn.datasets import load_diabetes
データフレームを作成します。
(参考:【Python】scikit-learnのdiabetes datasetを使って回帰分析をしてみる)
# データの読み込み
dia = load_diabetes()
# 目的変数
exp_data = pd.DataFrame(dia.data, columns=dia.feature_names)
# 説明変数
tar_data = pd.DataFrame(dia.target, columns=['target'])
# データを結合
df = pd.concat([exp_data, tar_data], axis=1)
df.rename({"s1":"tc", "s2":"ldl", "s3":"hdl", "s4":"tch", "s5":"ltg", "s6":"glu"}, axis=1, inplace=True)
df
10個の説明変数と1つの目的変数からできています。
これを用いて、XGBoostのパラメータチューニングを行います。
グリッドサーチの場合
コードは以下の通りです。(参考:機械学習のパラメータチューニングフレームワークを比較してみた!)
#必要なライブラリのインポート
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
from sklearn.metrics import root_mean_squared_error
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import r2_score
#目的変数と説明変数に分類し、トレーニングデータとテストデータに分割
X = df.iloc[:,:10]
y = df.iloc[:,10]
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
# 変数として扱うパラメータの値の設定
param_grid = {
'learning_rate':[0.01, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30],
'max_depth': [2, 4, 6, 8, 10],
'min_child_weight':[0, 1, 2, 3, 4],
'subsample':[0.2, 0.4, 0.6, 0.8, 1],
'colsample_bytree':[0.2, 0.4, 0.6, 0.8, 1],
'reg_lambda':[2, 4, 6, 8, 10],
}
# XGBoostの定数部分を指定。
base_model = xgb.XGBRegressor(
objective='reg:squarederror',
booster="gbtree",
n_estimators=100,
)
#各パラメータの組み合わせにおいて交差検証
cv = KFold(n_splits=6, shuffle=True, random_state=42)
grid_search = GridSearchCV(base_model, param_grid, cv=cv, scoring='neg_root_mean_squared_error')
grid_search.fit(X_train, y_train)
# 交差検定で最もスコアが良かったときのパラメータを用いてモデルを再学習
pred = grid_search.predict(X_test)
r2 = r2_score(y_test, pred)
#各結果の出力
print("最適パラメータ : " , grid_search.best_params_)
print("最適パラメータにおける交差検定でのRMSEの平均: " , -grid_search.best_score_)
print("最適パラメータにおける最終評価でのRMSE:", root_mean_squared_error(y_test, pred))
print(f"決定係数 (R^2): {r2}")
GridSearchCVを用いてグリッドサーチを行います。
各パラメータの組み合わせにおいて交差検定を行っています。
評価指標としてRMSEを用いています。
各パラメータの組み合わせにおける交差検定において
最も精度が良かった組み合わせを採用し、
テストデータを用いて最終的な精度を出力します。
Optunaの場合
コードは以下の通りです。
(参考:機械学習のパラメータチューニングフレームワークを比較してみた!)
#必要なライブラリのインポート
import optuna
import xgboost as xgb
from sklearn.base import clone
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score, KFold
from sklearn.metrics import root_mean_squared_error
from sklearn.metrics import r2_score
#目的変数と説明変数に分類し、トレーニングデータとテストデータに分割
X = df.iloc[:,:10]
y = df.iloc[:,10]
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
#XGBoostの定数部分を指定。
base_model = xgb.XGBRegressor(
objective='reg:squarederror',
booster="gbtree",
n_estimators=100
)
#チューニングにおける関数
def objective(trial):
#各変数の範囲を指定
param = {
'learning_rate':trial.suggest_float('learning_rate', 0.01, 0.30),
'max_depth': trial.suggest_int('max_depth', 2, 10),
'min_child_weight': trial.suggest_int('min_child_weight', 0, 4),
'subsample': trial.suggest_float('subsample', 0.2, 1),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.2, 1),
'reg_lambda': trial.suggest_int('reg_lambda', 2, 10),}
#base_modelに変数を組み込む
model = clone(base_model).set_params(**param)
#交差検証
cv = KFold(n_splits=6, shuffle=True, random_state=42)
score = cross_val_score(model, X_train, y_train, cv=cv, scoring='neg_root_mean_squared_error')
score = -score #scoreを正にする
mean_score = np.mean(score) #各回におけるscoreを平均
return mean_score #scoreの平均を出力
# objective関数を用いてパラメータチューニング
study = optuna.create_study(direction='minimize',sampler=optuna.samplers.TPESampler(seed=0)) #結果の再現性を担保
study.optimize(objective, n_trials=100)
best_parameters = study.best_params
best_score = study.best_value
# 交差検定で最もスコアが良かったときのパラメータを用いてモデルを再学習
model = clone(base_model).set_params(**best_parameters)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
test_score = root_mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
#各結果の出力
print('最適パラメータにおける交差検定でのRMSEの平均:',best_score)
print('最適パラメータ :',best_parameters)
print('最適パラメータにおける最終評価でのRMSE:',test_score)
print(f"決定係数 (R^2): {r2}")
指定したハイパーパラメータの範囲の中で最適と思われる数値の組み合わせを作成し、モデル学習を行うという流れを100回繰り返します。
Optunaの場合においても、各パラメータの組み合わせにおいて交差検定を行い、
最も精度が良かった組み合わせを採用し、テストデータを用いて最終的な精度を出力します。
また、以下のコード
study = optuna.create_study(direction='minimize',sampler=optuna.samplers.TPESampler(seed=0)) #結果の再現性を担保
により、各実行において出力結果にばらつきがでないようにしています。
(参考:Google ColaboratoryでOptunaを実行する方法(Studyの再現性を確保する))
精度・実行時間比較
グリッドサーチ | Optuna | |
---|---|---|
RMSE(交差検証) | 56.5 | 57.4 |
RMSE(テスト) | 54.1 | 52.6 |
決定係数 | 0.47 | 0.50 |
実行時間 | 50分40秒 | 9秒 |
RMSE(交差検証):各回の交差検証において最もRMSEが低くなったときのその値。
RMSE(テスト):各回の交差検証において最もRMSEが低くなったときのパラメータの組み合わせでモデルを再度作成し、そのモデルで測ったRMSEの値。
決定係数:再度作成したモデルが出力した予測データと、正解データを用いて計算した決定係数。
実行時間:上記コードを実行するのにかかった時間。
決定係数を見ればわかる通り、どちらの手法においても
良い精度は得られませんでした。
そもそもXGBoostとデータが合っていなかったのか、説明変数に
なにかしらの手を加えたほうが良かったのか、等々、
いろいろと考察した方が良さそうです。
実行時間に関しては、Optunaはグリッドサーチに比べて圧倒的に計算が早かったです。
Optunaでは100回試行、グリッドサーチでは約2万回試行なので、
それはそうであろうという結果でした。
パラメータ比較
グリッドサーチ | Optuna | |
---|---|---|
learning_rate | 0.1 | 0.068 |
max_depth | 2 | 2 |
min_child_weight | 2 | 4 |
subsample | 0.6 | 0.22 |
colsample_bytree | 0.2 | 0.85 |
reg_lambda | 4 | 7 |
learning_rateとmax_depthにおいては、
グリッドサーチとOptunaとで近しいor等しい数値となりました。
それ以外はバラバラの数値となりました。