😊

GBDTの代表格!! Xgboost, LGBM, CatBoost,を紹介 実装編(Optuna パラメータチューニング付き)

2024/05/15に公開

はじめに

株式会社Elithでインターン生として働いている中村彰成です。今回は、あるプロジェクトで扱ったGBDT(勾配ブースティング決定木)を紹介します。GBDT系の機械学習アルゴリズムは、2024年現在でもkaggleなどのテーブルコンペで人気なアルゴリズムです。

この記事では、GBDTの概要、GBDTの代表格であるXgboost, LGBM, CatBoost, それぞれの実装方法について紹介します。

GBDTの概要や、Xgboost, LGBM, CatBoost,のアルゴリズムについて知りたい方は、以下の記事で紹介していますのでご覧ください。
https://zenn.dev/elith/articles/f8ad80f937330a

実装方法

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()



参考文献

株式会社Elith

Discussion