LoginSignup
29
30

More than 5 years have passed since last update.

XGBoostをOptunaでチューニング(コード解説付き)

Last updated at Posted at 2019-03-19

はじめに

この記事では前回の記事(TFLearn使ってみた。Tensorflowと比較。)で使ったデータを今回はXGBoost + Optunaで使って結果を比較してみます。ついでにkeras + Optunaでもやります。

目次
1. Optunaとは
2. XGBoost + Optunaの実装
3. 局所のコード解説
4. 結果 (XGBoost)
5. keras + Optunaの実装
6. 結果 (Keras)
7. 参考文献

Optunaとは

一言でいえばパラメーターチューニングを効率良く自動化したフレームワーク。ハイパーパラメータの値を試行錯誤しながら、そのモデルに最適な値を探すことを可能とします。

(ハイパーパラメータとは機械学習アルゴリズムの挙動を制御するためにプログラマが付けるパラメータです。例としてバッチ数や学習率が挙げられます。単なるパラメータとの違いを挙げるとすれば、パラメータがfunction(parameters)など既に決まっている値に対し、ハイパーパラメータはプログラマ自身が色々と試行錯誤しながら決めていく値という感覚で大丈夫です)

Optunaの実装

import pandas as pd
import numpy as np
from sklearn.model_selection import KFold
from sklearn.metrics import roc_auc_score, accuracy_score
from sklearn.model_selection import train_test_split
import xgboost as xgb
import optuna

#Objective関数の設定
def objective(trial):
    data = pd.read_csv('a.csv')
    x, y = pre_processing(data)

    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)

    params = {
        'objective': 'binary:logistic',
        'max_depth': trial.suggest_int('max_depth', 1, 9),
        'n_estimators': trial.suggest_int('n_estimators', 10, 1000),
        'learning_rate': trial.suggest_loguniform('learning_rate', 1e-8, 1.0)
    }

    model = xgb.XGBClassifier(**params)
    model.fit(x_train, y_train)

    pred = model.predict(x_test)

    accuracy = accuracy_score(y_test, pred)
    return (1-accuracy)

if __name__ == '__main__':

    study = optuna.create_study()
    study.optimize(objective, n_trials=300)

    print(study.best_params)
    print(study.best_value)
    print(study.best_trial)

(簡略化するためにpre_processing()の実装は省いています。詳しくは前記事を参照してください)

それでは公式読まずにさっさとコードを理解したいという方に抽象から具体の流れで説明していきます。

直観的に理解する全体の流れ

XGBoostを一定回数訓練する。その一回一回の訓練中に違うパラメーターを試してみて一番性能を発揮したパラメータを最後にユーザーへ出力する。

具体的に説明

study.optimize()の引数のn_trials回だけXGBoostを訓練する。そして一回一回の訓練中にparams{}の中のtrial.suggest()等と設定されているパラメーターを試す。その後(今まで試したパラメータの履歴も参照しながら)次に試すパラメータを、有望な領域から定める。これをn_trials回分繰り返し、完了したあとに最も最適だったパラメータをユーザーに出力する。

この有望な領域というのは、Tree-structured Parzen Estimator というベイズ最適化アルゴリズムの一種を用いて出しています。
(参考:https://research.preferred.jp/2018/12/optuna-release/

(注:一回一回でパラメータを変えるとありますが、変えないときもあります。説明の簡略化のために省きました)

全体の流れをステップで表すとこんな感じ。
1. studyオブジェクト生成。
2. optimize()実行
3. trial.suggest_〇〇からパラメータを決める。
4. 訓練開始
5. returnで1-accurayを返す(できるだけ1-accuray=0に近づけたい)
6. returnの結果も見つつ次のパラメーター候補を定める。(詳しくはベイズ最適化参照)
6. 1~4をn_trials回繰り返す。

それでは局所のコード解説に入ります。

まずobjective()の引数にtrialというのがありますが、

def objective(trial):

これはobjective関数が呼ばれるたびに内部で自動的に生成されるTrialというクラスのインスタンスです。このインスタンスはtrial.suggest_〇〇〇()等からわかるようにハイパーパラメータの値をサンプリングする役割を果たします。

study = optuna.create_study()
study.optimize(objective, n_trials=300)

ここではcreate_studyという関数が使われています。これは文字通りstudyオブジェクトを生成しています。studyオブジェクトとは最適化を担う(扱う)オブジェクトです。

params = {
    'objective': 'binary:logistic',
    'max_depth': trial.suggest_int('max_depth', 1, 9),
    'n_estimators': trial.suggest_int('n_estimators', 10, 1000),
    'learning_rate': trial.suggest_loguniform('learning_rate', 1e-8, 1.0)
    }

実際のハイパーパラメータの設定です。例えばこのコード内のmax_depthは「1から9までの整数内でパラメータを決めてくれ」と言っています。

ご存知かと思いますがsuggestは「提案する」という意味なので覚えやすいですね。

return (1-accuracy)

最後に(1-accuracy)を返します。study.optimize(objective, n_trials) は objective関数を最適化するための関数ですが、これはobjective関数内のaccuracyをできる限り1.0に近づけて、(1-accuracy) = 0にしたいという意味です。

結果 (XGBoost)

 'objective': 'binary:logistic',
 'max_depth': 5,
 'n_estimators' : 242,
 'learning_rate': 0.099326350132149599

実際にkfold=5のXGBoostで試してみるとAccuracyは0.869ぐらい。パラメーターチューニングの回もあってかTensorflowより若干良かった。

Kerasでの実装

from keras.models import Sequential
from keras.layers import Dense, Activation
from keras import optimizers
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import optuna

def objective(trial):

    data = pd.read_csv('a.csv')

    x_train, y_train = pre_processing(data)

    x_train = scaling(x_train)

    units  = trial.suggest_int("units", 3, 65)
    activation = trial.suggest_categorical("activation", ["relu", "tanh", "sigmoid"])
    optimizer = trial.suggest_categorical("optimizer", ["sgd", "adam", "rmsprop"])

    model = Sequential()

    model.add(Dense(units=units, input_shape=(11,), activation=activation))
    model.add(Dense(units=units, activation=activation))
    model.add(Dense(units=1, activation='sigmoid'))

    model.compile(optimizer=optimizer, loss='binary_crossentropy', metrics=['accuracy'])

    history = model.fit(x_train, y_train, batch_size=10, verbose=0, validation_split=0.2, epochs=5)

    return (1 - history.history["val_acc"][-1])

if __name__ == '__main__':
    study = optuna.create_study()
    study.optimize(objective, n_trials=100)

    print(study.best_params)    
    print(study.best_value)
    print(study.best_trial)

(簡略化するためにpre_processing()とscaling()の実装は省いています。詳しくは前記事を参照してください)

肝心の結果ですが手違いで紛失しました(笑)。ただ前の記事のハイパーパラメータとほぼ同じ結果になりました。学習用のデータセットを使ったからだと思われます。

注意点としてepoch数を増やすとそれだけ時間がかかるためGCPで実行することをお勧めします。また筆者のようにepoch数を減らすと過学習を起こすのでお勧めはしません。例えば10エポックと指定した場合、10エポック時点での最適なパラメータを探してしまうので10エポックでの結果がベストでも100エポックしたときにベストとは限りません。またエポック自体をOptunaに自動で探索してもらうのもありでしたが、大分重くなりそうなので止めました。

参考文献:

https://optuna.readthedocs.io/en/latest/index.html
https://optuna.readthedocs.io/en/latest/reference/trial.html#optuna.trial.Trial
https://optuna.readthedocs.io/en/latest/reference/study.html#optuna.study.Study
https://www.hellocybernetics.tech/entry/2018/12/22/160349
https://research.preferred.jp/2018/12/optuna-release/

29
30
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
29
30