GBDTの代表格!! Xgboost, LGBM, CatBoost,を紹介 実装編(Optuna パラメータチューニング付き)
はじめに
株式会社Elithでインターン生として働いている中村彰成です。今回は、あるプロジェクトで扱ったGBDT(勾配ブースティング決定木)を紹介します。GBDT系の機械学習アルゴリズムは、2024年現在でもkaggleなどのテーブルコンペで人気なアルゴリズムです。
この記事では、GBDTの概要、GBDTの代表格であるXgboost, LGBM, CatBoost, それぞれの実装方法について紹介します。
GBDTの概要や、Xgboost, LGBM, CatBoost,のアルゴリズムについて知りたい方は、以下の記事で紹介していますのでご覧ください。
実装方法
LGBM,XGBoost,CatBoostは基本的には同じように実装出来ます。一部、パラメータ設定で違いが生じます。今回はパラメータチューニングまで行うのでOptunaを用いた実装例を紹介します。
今回は「Breast Cancer Wisconsin (Diagnostic) データセット」を使用します。このデータセットでは、乳がん腫瘍を良性と悪性に分類します。
※以下sklearn APIに基づいて実装しています。
データとライブラリの読み込み
! pip install scikit-learn lightgbm xgboost catboost optuna
import numpy as np
import pandas as pd
import optuna
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import lightgbm as lgb
import xgboost as xgb
import catboost as cb
# データのロード
data = load_breast_cancer()
X = data.data
y = data.target
feature_names = data.feature_names
# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Optunaを用いたモデリング(パラメータチューニング込み)
それぞれのパラメータについて以下表形式でまとめました。
共通の主要なパラメータ
フレームワーク | パラメータ | 説明 | 取りうる範囲または典型的な値 |
---|---|---|---|
XGBoost | max_depth | 木の深さ | 正の整数(例: 3, 6, 10) |
learning_rate | 学習率 | 0から1の実数(例: 0.01, 0.1, 0.3) | |
n_estimators | 構築する木の数 | 正の整数(例: 100, 500, 1000) | |
subsample | トレーニングデータのサンプリング比率 | 0から1の実数(例: 0.5, 0.8, 1.0) | |
colsample_bytree | 木を構築する際に使用する特徴量の割合 | 0から1の実数(例: 0.6, 0.8, 1.0) | |
LightGBM | max_depth | 木の深さ | 正の整数または-1(無制限とする場合) |
learning_rate | 学習率 | 0から1の実数(例: 0.01, 0.1, 0.3) | |
n_estimators | 構築する木の数 | 正の整数(例: 100, 500, 1000) | |
num_leaves | 1つの木にある最大の葉の数 | 正の整数(例: 31, 127, 255) | |
feature_fraction | 各反復でランダムに選択される特徴量の割合 | 0から1の実数(例: 0.6, 0.8, 1.0) | |
CatBoost | depth | 木の深さ | 正の整数(例: 4, 6, 10) |
learning_rate | 学習率 | 0から1の実数(例: 0.01, 0.1, 0.3) | |
iterations | 構築する木の数 | 正の整数(例: 100, 500, 1000) | |
subsample | ブースティングのステップごとのデータのサンプル比率 | 0から1の実数(例: 0.5, 0.8, 1.0) | |
colsample_bylevel | 各深さのレベルで使用する特徴量の割合 | 0から1の実数(例: 0.6, 0.8, 1.0) |
各ライブラリ特有のパラメータ
フレームワーク | パラメータ | 説明 | 取りうる範囲または典型的な値 |
---|---|---|---|
XGBoost | gamma | リーフノードが分割を行うために必要な最小損失減少量 | 非負の実数(例: 0, 0.1, 0.5) |
min_child_weight | 子ノードを分割するために必要なインスタンスの重み合計の最小値 | 非負の実数(例: 1, 3, 10) | |
reg_alpha | L1正則化項の係数 | 非負の実数(例: 0, 0.01, 0.1) | |
reg_lambda | L2正則化項の係数 | 非負の実数(例: 1, 1.5, 2) | |
LightGBM | min_data_in_leaf | リーフに必要な最小データ数 | 正の整数(例: 20, 50, 100) |
max_bin | 使用するビンの最大数 | 正の整数(例: 255, 300, 1024) | |
bagging_fraction | バギングに使用するデータの割合 | 0から1の実数(例: 0.5, 0.8, 1.0) | |
bagging_freq | バギングを行う頻度(0はバギング無しを意味する) | 非負の整数(例: 0, 5, 10) | |
CatBoost | border_count | 数値特徴量の分割に使用するビンの数 | 正の整数(例: 32, 64, 255) |
l2_leaf_reg | リーフに対するL2正則化の強度 | 非負の実数(例: 3, 5, 9) | |
cat_features | カテゴリカル特徴量のインデックスリスト | インデックスのリスト(例: [0, 1, 2]) |
以下は、Optunaの公式ドキュメントを参考に共通の主要なパラメータのみ使用した実装例を示します。
# 関数定義
def objective_lgbm(trial):
param = {
'objective': 'binary',
'verbosity': -1,
'boosting_type': 'gbdt',
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2),
'num_leaves': trial.suggest_int('num_leaves', 20, 100),
'max_depth': trial.suggest_int('max_depth', 3, 10),
'min_data_in_leaf': trial.suggest_int('min_data_in_leaf', 20, 50),
'max_bin': trial.suggest_int('max_bin', 200, 300)
}
clf = lgb.LGBMClassifier(**param)
clf.fit(X_train, y_train)
preds = clf.predict(X_test)
accuracy = accuracy_score(y_test, preds)
return accuracy
def objective_xgb(trial):
param = {
'verbosity': 0,
'objective': 'binary:logistic',
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2),
'n_estimators': 100,
'max_depth': trial.suggest_int('max_depth', 3, 10),
'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),
'subsample': trial.suggest_float('subsample', 0.5, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),
}
clf = xgb.XGBClassifier(**param)
clf.fit(X_train, y_train)
preds = clf.predict(X_test)
accuracy = accuracy_score(y_test, preds)
return accuracy
def objective_cat(trial):
param = {
'iterations': 100,
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2),
'depth': trial.suggest_int('depth', 3, 10),
'l2_leaf_reg': trial.suggest_int('l2_leaf_reg', 1, 10),
'loss_function': 'Logloss',
'verbose': False
}
clf = cb.CatBoostClassifier(**param)
clf.fit(X_train, y_train)
preds = clf.predict(X_test)
accuracy = accuracy_score(y_test, preds)
return accuracy
study_lgbm = optuna.create_study(direction='maximize')
study_lgbm.optimize(objective_lgbm, n_trials=10)
lgbm_best_model = lgb.LGBMClassifier(**study_lgbm.best_params)
lgbm_best_model.fit(X_train, y_train)
lgbm_accuracy = accuracy_score(y_test, lgbm_best_model.predict(X_test))
study_xgb = optuna.create_study(direction='maximize')
study_xgb.optimize(objective_xgb, n_trials=10)
xgb_best_model = xgb.XGBClassifier(**study_xgb.best_params)
xgb_best_model.fit(X_train, y_train)
xgb_accuracy = accuracy_score(y_test, xgb_best_model.predict(X_test))
study_cat = optuna.create_study(direction='maximize')
study_cat.optimize(objective_cat, n_trials=10)
cat_best_model = cb.CatBoostClassifier(**study_cat.best_params)
cat_best_model.fit(X_train, y_train)
cat_accuracy = accuracy_score(y_test, cat_best_model.predict(X_test))
print('LightGBM Best Score:', study_lgbm.best_value)
print('LightGBM Best Params:', study_lgbm.best_params)
print('XGBoost Best Score:', study_xgb.best_value)
print('XGBoost Best Params:', study_xgb.best_params)
print('CatBoost Best Score:', study_cat.best_value)
print('CatBoost Best Params:', study_cat.best_params)
出力
LightGBM Best Score: 0.9736842105263158
LightGBM Best Params: {'learning_rate': 0.10324592460213763, 'num_leaves': 93, 'max_depth': 9, 'min_data_in_leaf': 29, 'max_bin': 243}
XGBoost Best Score: 0.9824561403508771
XGBoost Best Params: {'learning_rate': 0.13888056207608412, 'max_depth': 4, 'min_child_weight': 5, 'subsample': 0.9050510709546682, 'colsample_bytree': 0.5653522994626343}
CatBoost Best Score: 0.9736842105263158
CatBoost Best Params: {'learning_rate': 0.028469252699289897, 'depth': 9, 'l2_leaf_reg': 8}
上の予測結果より今回はXGBoostが最も良い精度を出していることが分かります。しかし、その性能は使用されるデータセットやハイパーパラメータの選択に大きく依存します。実際の運用においては、データの適切な前処理を行うこと、データセットの特性に合ったモデルを選択すること、そして効果的なハイパーパラメータと探索範囲を設定することが重要になります。
まとめ
この記事では、GBDTとその代表的な派生アルゴリズムであるXGBoost、LightGBM、CatBoostについて、Optunaを用いた実装方法について紹介しました。
最近は、LLM(Large Language Model)などの生成AIに世間の注目が集まっていますが、テーブルデータの分析やリソースが限られた環境では、GBDT系アルゴリズムが依然として非常に有効です。これらの技術を適切に活用することで、データから新たな洞察を引き出し、効果的な意思決定を支援することできると考えています。
おまけ
feature_importance
feature_importanceは、各特徴量がどれだけ重要であるかを示す指標です。決定木やランダムフォレスト、勾配ブースティングなどのモデルでは、ある特徴量を使って分割することで得られる情報利得(Information Gain)やジニ不純度(Gini Impurity)の減少量などを用いて、特徴量の重要度(特徴量がモデルに与える影響)を評価できます。feature_importanceを使用すれば、どの特徴量が予測に貢献しているかがわかり、モデルの解釈性を向上させ、モデルの精度向上のアプローチの参考になります。以下、実行例を示します。
import matplotlib.pyplot as plt
def plot_feature_importance(importance, names, model_type):
feature_importance = np.array(importance)
feature_names = np.array(names)
# 特徴重要度でソート
data={'feature_names':feature_names,'feature_importance':feature_importance}
fi_df = pd.DataFrame(data)
fi_df.sort_values(by=['feature_importance'], ascending=False,inplace=True)
# プロット
plt.figure(figsize=(10,8))
sns.barplot(x=fi_df['feature_importance'], y=fi_df['feature_names'])
plt.title(model_type + ' Feature Importance')
plt.xlabel('Feature Importance')
plt.ylabel('Feature Names')
plot_feature_importance(lgbm_best_model.feature_importances_, feature_names, 'LightGBM')
plot_feature_importance(xgb_best_model.feature_importances_, feature_names, 'XGBoost')
plot_feature_importance(cat_best_model.get_feature_importance(), feature_names, 'CatBoost')
plt.show()
Discussion