0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

XGBoostにおける各種パラメータとその調整

0
Posted at

この記事の趣旨

業務において機械学習をかじることがあるのだが、大体XGBoostを使ってポイ!でそのまま成果物としている。実務上は精度が大抵あれば問題ないのだが、より良い精度を追求すべきだという気持ちが前々からあったため、まとめる機会として整理してみる。
どのようなパラメータが存在するかという整理と、それらを変更した際の影響についてを試行してみる

実行環境

GoogleColaboratory
Python

パラメータの一覧について

パラメータの一覧についてはこちらでまとめられている。
当該記事の内容を踏まえるに調整できるパラメータはBooster ParametersとCommand Line Parametersのnroundsのみとのこと。
Learning Parametesrのobjectiveとeval_metricも自身で設定するものなのでパラメータなのではないかと思ったが、
objectiveは「何を目的に学習するか」、eval_metricは「何を基準に良し悪しを判断するか」なのでパラメータではなさそうという理解。
そのため二値分類ならばobjective="binary:logistic"が必須のようだ。

実装・検証

kaggleのTitanicデータ(Titanic - Machine Learning from Disaster)を利用する。
XGBoostをそのまま実行するパターンとパラメータを変えたパターン、最後にグリットサーチを実行した結果と比較
検証用の指標はAUCを用いる

実装

今回検証したいことはパラメータの効果なので、特殊な特徴量や欠損値処理は行わないで最低限のマート作成を行う

train.csvのデータの概要は下記のとおり

カラム名 データ概要
PassengerId 乗客の識別ID
Survived 乗客の生存フラグ(1が生存・0が死亡)
Pclass 乗客の社会的地位
Name 乗客の名前
Sex 乗客の性別
Age 乗客の年齢
SibSp 同乗している兄弟・配偶者の数
Parch 同乗している親族(親・子供)の数
Ticket チケット番号
Fare 料金
Cabin 部屋番号
Embarked 乗船した港(3箇所)

検証

XGBoostに学習させるパラメータとしては
Pclass,Sex,Age,SibSp,Parch,Fare,Embarkedを採用する。
Sex,Embarkedについては元データではカテゴリ変数なのでフラグ値に変換を行う。
また欠損値については全てのカテゴリにおいて欠損のないレコードだけを対象とする。

前処理

import pandas as pd

# CSVファイルの読み込み
df = pd.read_csv('train.csv')

#欠損値を除いておく
use_cols = [
    'Survived',
    'Pclass',
    'Sex',
    'Age',
    'SibSp',
    'Parch',
    'Fare',
    'Embarked'
]

df = df.dropna(subset=use_cols).reset_index(drop=True)

#Embarked・Sexの前処理
train_oh = pd.get_dummies(
    df[['Embarked', 'Sex']],
    prefix=['Embarked', 'Sex'],
    prefix_sep='_'
).astype(int)

df_merge = pd.concat([df, train_oh], axis=1)

#不要な項目は削除
drop_cols = [
    'PassengerId',
    'Name',
    'Sex',
    'Ticket',
    'Cabin',
    'Embarked'
]

#分析用のマート作成
mart = df_merge.drop(columns=drop_cols)

特に難しい処理を書いているわけではないので説明は割愛。
この辺の記述をもっとスマートにできればと思っているがずっと課題・・・

実装編

そのまま実行するパターン、パラメータを適当に変えてみたパターン・グリッドサーチを行ってみたパターンの3つを検証する。

①基本設定

import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from xgboost import XGBClassifier

use_cols = [
    'Survived',
    'Pclass',
    'Age',
    'SibSp',
    'Parch',
    'Fare',
    'Embarked_C',
    'Embarked_Q',
    'Embarked_S',
    'Sex_female',
    'Sex_male'
]

df_merge = df_merge[use_cols]

X = df_merge.drop(columns='Survived')
y = df_merge['Survived']

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

まずはモデル作成のための下準備。
検証用データの作成について知ったのだが、stratify=yを設定することでテストデータと
モデル用データの正解データの割合を揃えられるらしい。
私の実務上そこまでやったことはなかったが、必要な設定だと感じた。

②まずは何も考えずに入れてみる

#モデル1、パラメータをそのまま
model = XGBClassifier(
)

model.fit(
    X_train,
    y_train,
    eval_set=[(X_test, y_test)],
    verbose=True
)

y_pred_proba = model.predict_proba(X_test)[:, 1]

auc = roc_auc_score(y_test, y_pred_proba)

print(auc)

上記を実行した結果のAUCとしては
0.8545638945233265となる。
全然手をつけていない現時点でもかなり高いAUCとなっている。

なお初期時点ではパラメータは何も設定してないが、
print(model.get_params()) だとpython側で設定していないだけで全てNoneと出る。
model.get_booster().save_config()の方から確認すると初期値は下記の通りとなっている。

パラメータ 実際の値 意味
booster gbtree 決定木ベースのモデルを使用
objective binary:logistic 2値分類
n_estimators 100 作成する木の本数
max_depth 6 1本の木の最大深さ
learning_rate 0.3 各木による予測の修正量を調整する学習率
min_child_weight 1 子ノードへの分割を制御するパラメータ
gamma 0 ノードを分割するために必要な最小の損失減少量
subsample 1 各木の学習に使用するデータの割合(100%)
colsample_bytree 1 各木の学習に使用する特徴量の割合(100%)
colsample_bylevel 1 各木の各レベルで使用する特徴量の割合(100%)
colsample_bynode 1 各ノードで使用する特徴量の割合(100%)
reg_alpha 0 L1正則化の強さ(正則化なし)
reg_lambda 1 L2正則化の強さ
max_delta_step 0 各木の更新量に対する制約(制約なし)
max_leaves 0 1本の木における葉の最大数(制限なし)
grow_policy depthwise 木を深さ方向に成長させる
max_bin 256 連続値を分割する際のビン数
tree_method auto 木の構築方法を自動選択

たくさんあって手動で変更するのはちょっと大変なので、
わかりやすいn_estimators、max_depth、learning_rate、subsample、colsample_bytree、max_leaves あたりをいじってみる

③諸々手作業でいじってみる

##①n_estimatorsを100→1000に
model_n_estimators = XGBClassifier(
    n_estimators=1000,
)

model_n_estimators.fit(
    X_train,
    y_train,
    eval_set=[(X_test, y_test)],
    verbose=True
)

y_pred_proba_n_estimators = model_n_estimators.predict_proba(X_test)[:, 1]

n_estimators_auc = roc_auc_score(y_test, y_pred_proba_n_estimators)

##②max_depthを6→60に
model_max_depth = XGBClassifier(
    max_depth=60,
)

model_max_depth.fit(
    X_train,
    y_train,
    eval_set=[(X_test, y_test)],
    verbose=True
)

y_pred_proba_max_depth = model_max_depth.predict_proba(X_test)[:, 1]

max_depth_auc = roc_auc_score(y_test, y_pred_proba_max_depth)

##③learning_rateを0.3→0.7に
model_learning_rate = XGBClassifier(
    learning_rate=0.7,
)

model_learning_rate.fit(
    X_train,
    y_train,
    eval_set=[(X_test, y_test)],
    verbose=True
)

y_pred_proba_learning_rate = model_learning_rate.predict_proba(X_test)[:, 1]

learning_rate_auc = roc_auc_score(y_test, y_pred_proba_learning_rate)


##④subsampleを1→0.5に
model_subsample = XGBClassifier(
    subsample=0.5,
)

model_subsample.fit(
    X_train,
    y_train,
    eval_set=[(X_test, y_test)],
    verbose=True
)

y_pred_proba_subsample = model_subsample.predict_proba(X_test)[:, 1]

subsample_auc = roc_auc_score(y_test, y_pred_proba_subsample)

##⑤colsample_bytreeを1→0.5に
model_colsample_bytree = XGBClassifier(
    colsample_bytree=0.5,
)

model_colsample_bytree.fit(
    X_train,
    y_train,
    eval_set=[(X_test, y_test)],
    verbose=True
)

y_pred_proba_colsample_bytree = model_colsample_bytree.predict_proba(X_test)[:, 1]

colsample_bytree_auc = roc_auc_score(y_test, y_pred_proba_colsample_bytree)

##⑥max_leavesを0→100に
model_max_leaves = XGBClassifier(
    max_leaves=100,
)

model_max_leaves.fit(
    X_train,
    y_train,
    eval_set=[(X_test, y_test)],
    verbose=True
)

y_pred_proba_max_leaves = model_max_leaves.predict_proba(X_test)[:, 1]

max_leaves_auc = roc_auc_score(y_test, y_pred_proba_max_leaves)

print(n_estimators_auc)
print(max_depth_auc)
print(learning_rate_auc)
print(subsample_auc)
print(colsample_bytree_auc)
print(max_leaves_auc)

実行結果(順に)

0.8532454361054767
0.8599391480730223
0.850608519269777
0.8621703853955375
0.8587221095334685
0.8545638945233265

パラメータをいじった結果、元が0.854→最大0.862程度まで改善することができた。
subsample_aucを調整することが精度の向上につながることは想像通りという感じ。
またn_estimatorsを変えると木が細かくなって精度が下がってしまうという形か。

④パラメータの最適化を図る

パラメータの最適化には諸々手法が存在する

・GridSearch
・RandomSearch
・Optuna

GridSearchは一定候補を用意してその中から選んでもらう、
RandomSearchは指定した範囲の中からランダムに繰り返して最適化、Optunaは同様に指定した範囲の中からランダムに取得するが実行した内容をもとに学習を繰り返す。
Optunaの方が計算量は大きくなる。

直感Optunaが一番良さそうなのでGPTにOptunaを実装してもらう

import optuna
import xgboost as xgb

from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import roc_auc_score
from xgboost import XGBClassifier


# =========================
# データ準備
# =========================

use_cols = [
    'Survived',
    'Pclass',
    'Age',
    'SibSp',
    'Parch',
    'Fare',
    'Embarked_C',
    'Embarked_Q',
    'Embarked_S',
    'Sex_female',
    'Sex_male'
]

df_merge = df_merge[use_cols]

X = df_merge.drop(columns='Survived')
y = df_merge['Survived']


# =========================
# Train / Test 分割
# =========================

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)


# =========================
# Optunaの目的関数
# =========================

def objective(trial):

    params = {
        'n_estimators': trial.suggest_int(
            'n_estimators', 50, 1000
        ),

        'max_depth': trial.suggest_int(
            'max_depth', 2, 10
        ),

        'learning_rate': trial.suggest_float(
            'learning_rate', 0.01, 0.3, log=True
        ),

        'subsample': trial.suggest_float(
            'subsample', 0.5, 1.0
        ),

        'colsample_bytree': trial.suggest_float(
            'colsample_bytree', 0.5, 1.0
        ),

        'max_leaves': trial.suggest_int(
            'max_leaves', 4, 64
        ),

        'objective': 'binary:logistic',
        'eval_metric': 'auc',
        'random_state': 42
    }

    model = XGBClassifier(**params)

    scores = cross_val_score(
        model,
        X_train,
        y_train,
        cv=5,
        scoring='roc_auc'
    )

    return scores.mean()


# =========================
# Optuna実行
# =========================

study = optuna.create_study(
    direction='maximize'
)

study.optimize(
    objective,
    n_trials=100
)


# =========================
# 結果
# =========================

print("Best AUC:")
print(study.best_value)

print("Best Parameters:")
print(study.best_params)

実行結果


Best AUC:
0.8647893842806429
Best Parameters:
{'n_estimators': 362, 'max_depth': 3, 'learning_rate': 0.013867610669919995, 'subsample': 0.6697163675186409, 'colsample_bytree': 0.9120620332901009, 'max_leaves': 51}

先ほど適当にパラメータをいじった時に最もよかったAUCでも
0.862だったので、0.864が現段階で最良のパラメータと言える

結論

諸々工夫はしてみたもののoptunaを使って最適化を図ったとして
思いつきで実行した時のAUCよりも微々たる改善しかできていないようだった。

optunaはかなり便利かつ有用な思想だと感じたものの、実際計算時間もそこそこかかるもので
他のパラメータとの兼ね合いを考えるともっとデータの中身を精査する方が良いのではないかと考えた次第。

ただパラメータは高いに越したことがないので実務上発生したらチャレンジしてみたい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?