PythonでXGBoostをちゃんと理解する(3) hyperoptでパラメーターチューニング

xgboostのハイパーパラメーターを調整するのに、何が良さ気かって調べると、結局「hyperopt」に落ち着きそう。
対抗馬はSpearmintになりそうだけど、遅いだとか、他のXGBoost以外のモデルで上手く調整できなかった例があるとかって情報もあって、時間の無い今はイマイチ踏み込む勇気はない。

Hyperparameter Optimization using Hyperopt - Otto Group Product Classification Challenge | Kaggle
Optimizing hyperparams with hyperopt - FastML

前回辺りにアルゴリズム振り返って、チューニングには特別気をつけなきゃいけないことも無さそうなので、ガリガリとコード書いて動かしてみます。

hyperoptはつまるところ最適化問題のソルバーで、目的関数を一定の条件下で最大or最小化するというもの。
もともとはベイズ的最適化のためにデザインされてたけど、今はランダムサーチとTree of Parzen Estimators (TPE)だけがインプリされてる状況。

hyperopt本体の挙動を視覚的に理解するには、下記の記事がオススメ。districtdatalabs.silvrback.com

で、id:puyokwさんも記事の中で挙げてらっしゃったけど、KaggleのOttoのコンペにXGBoostにhyperoptを使ったコードが公開されてました。

xgboost package のR とpython の違い - puyokwの日記
optimizing hyperparameters of an xgboost model on otto dataset · bamine/Kaggle-stuff@17f7828 · GitHub

で、もうやりたかったことこれだよね、ってpuyokwさんと丸かぶりなアイデアだったので参考と言うなのほぼ写経。ありがとうございます。
元のコードに交差検定加えたいなと思ったけど、xgb.CVは使い勝手悪いし、sklearnでやってます。

import numpy as np
import xgboost as xgb
import pandas as pd

from sklearn import datasets, cross_validation
from sklearn.cross_validation import train_test_split
from sklearn.metrics import confusion_matrix, log_loss
from sklearn.cross_validation import KFold
from sklearn import preprocessing

from hyperopt import fmin, tpe, hp, STATUS_OK, Trials

iris=datasets.load_iris()
X = iris.data
y = iris.target

#Split data set to train and test data
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.5, random_state=25)

np.random.seed(25)


def score(params):
    print "Training with params : "
    print params
    N_boost_round=[]
    Score=[]
    skf = cross_validation.StratifiedKFold(y_train, n_folds=10, shuffle=True,random_state=25)
    for train, test in skf:
        X_Train, X_Test, y_Train, y_Test = X_train[train], X_train[test], y_train[train], y_train[test]
        dtrain = xgb.DMatrix(X_Train, label=y_Train)
        dvalid = xgb.DMatrix(X_Test, label=y_Test)
        watchlist = [(dtrain, 'train'),(dvalid, 'eval')]
        model = xgb.train(params, dtrain, num_boost_round=150,evals=watchlist,early_stopping_rounds=10)
        predictions = model.predict(dvalid)
        N = model.best_iteration
        N_boost_round.append(N)
        score = model.best_score
        Score.append(score)
    Average_best_num_boost_round = np.average(N_boost_round)
    Average_best_score = np.average(Score)
    print "\tAverage of best iteration {0}\n".format(Average_best_num_boost_round)
    print "\tScore {0}\n\n".format(Average_best_score)
    return {'loss': Average_best_score, 'status': STATUS_OK}


def optimize(trials):
    space = {
        "objective": "multi:softprob",
        "eval_metric": "mlogloss",
        
        #Control complexity of model
        "eta" : hp.quniform("eta", 0.2, 0.6, 0.05),
        "max_depth" : hp.quniform("max_depth", 1, 10, 1),
        "min_child_weight" : hp.quniform('min_child_weight', 1, 10, 1),
        'gamma' : hp.quniform('gamma', 0, 1, 0.05),
        
        #Improve noise robustness 
        "subsample" : hp.quniform('subsample', 0.5, 1, 0.05),
        "colsample_bytree" : hp.quniform('colsample_bytree', 0.5, 1, 0.05),
        
        'num_class' : 3,
        'silent' : 1}
    best = fmin(score, space, algo=tpe.suggest, trials=trials, max_evals=250)
    print "best parameters",best

trials = Trials()
optimize(trials)

#出力
#best parameters {'colsample_bytree': 0.9500000000000001, 'min_child_weight': 1.0, #'subsample':0.9500000000000001, 'eta': 0.6000000000000001, 'max_depth': 3.0, 'gamma': 0.0}

#Adapt best params
params={'objective': 'multi:softprob',
        'eval_metric': 'mlogloss',
        'colsample_bytree': 0.9500000000000001, 
        'min_child_weight': 1.0, 
        'subsample': 0.9500000000000001, 
        'eta': 0.6000000000000001, 
        'max_depth': 3.0, 
        'gamma': 0.0,
        'num_class': 3
        }

score(params)

#出力
#Training with params : 
#{'subsample': 0.9500000000000001, 'eta': 0.6000000000000001, 'colsample_bytree': 0.9500000000000001, 'gamma': #0.0, 'eval_metric': 'mlogloss', 'objective': 'multi:softprob', 'num_class': 3, 'max_depth': 3.0, #'min_child_weight': 1.0}
#	Average of best iteration 37.7
#	Score 0.1092628

X_train = pd.DataFrame(X_train)
X_test = pd.DataFrame(X_test)
dtrain = xgb.DMatrix(X_train.as_matrix(),label=y_train.tolist())
dtest=xgb.DMatrix(X_test.as_matrix())

bst=xgb.train(params,dtrain,num_boost_round=38)
pred=bst.predict(dtest)
pred=pd.DataFrame(pred)
print confusion_matrix(y_test, pred.idxmax(axis=1))

#出力:Confusion_matrix
#[[26  0  0]
# [ 0 24  2]
# [ 0  2 21]]

ちなみに、パラメーターを全部デフォルトにした素うどんXGBoostでやってみても、結果は一緒でした。
あやめのデータだとこんなもんですかね。

params_Default={'objective': 'multi:softprob',
        'eval_metric': 'mlogloss',
        'eta': 0.3, 
        'max_depth': 6, 
        'min_child_weight': 1, 
        'subsample': 1, 
        'colsample_bytree': 1, 
        'num_class': 3,
        }
cv=xgb.cv(params_Default,dtrain,num_boost_round=200,nfold=10)
print(cv)

bst=xgb.train(params_Default,dtrain,num_boost_round=13)
pred=bst.predict(dtest)
pred=pd.DataFrame(pred)
print confusion_matrix(y_test, pred.idxmax(axis=1))

#出力:Confusion_matrix
#[[26  0  0]
# [ 0 24  2]
# [ 0  2 21]]

最近、やりたいことはすでにほかの人がやっちゃってるパターン多くて恥ずかしい限りですね。
本当はこの後、xgboostで得た情報をどう特徴選択などなどでどう使うかまでやろうかと思ったけど、そのネタはアンサンブルとして扱った方が良いのかなと思いxgboostはとりあえず一旦終了。

次は、Spermintとhyperoptの比較あたりやってみようかな。
ChainerとかLasagneとかのニューラルネット周りもやりたいけど。