はじめに
titanic号の乗客のうち、生き残るのは誰かをXGBoostを使って予測した。
その際、ハイパーパラメータはoptunaで自動チューニングした。
titanic号の乗客のデータセットはKaggleからダウンロードしました。
実行環境
- python 3.6.6
- XGBoost 0.81
- optuna 0.6.0
実装
方針
- xgboostだけ。自分で決める必要があるパラメータは適当に決める
- optunaで(1)でやったパラメータをチューニングする
- パラメータを増やす
データは891人分あるので、(学習データ:テストデータ)=(791:100)でやる。
ライブラリ
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
データ前処理
def preprocess(df):
df['Fare'] = df ['Fare'].fillna(df['Fare'].mean())
df['Age'] = df['Age'].fillna(df['Age'].mean())
df['Embarked'] = df['Embarked'].fillna('Unknown')
df['Sex'] = df['Sex'].apply(lambda x: 1 if x == 'male' else 0)
df['Embarked'] = df['Embarked'].map( {'S':0,'C':1,'Q':2,'Unknown':3} ).astype(int)
df = df.drop(['Cabin','Name','PassengerId','Ticket'],axis=1)
return df
引数dfのデータセットを正規化して返す関数です。
学習に利用したデータは
- Survived (生存したかどうか)
- Pclass (乗客の階級)
- sex
- Age
- SibSp (兄弟、配偶者の数)
- Parch (両親、子供の数)
- Fare (乗船料金)
- Embarked (乗船した港)
です。
使わないデータはdropしています。
必要なデータの欠損データは平均で埋めて、型はINT型に直してます。
方針1(xgboostのみ)
学習(xgboost)
まずはxgboostだけのもの
def train(df,max_depth,learning_rate,num_round):
train_x = df.drop('Survived',axis=1)
train_y = df.Survived
dtrain = xgb.DMatrix(train_x,label=train_y)
param = { 'max_depth':max_depth,'learning_rate':learning_rate,'objective':'reg:logistic' }
bst = xgb.train(param,dtrain,num_round)
return bst
学習モデルbstを作成して返す関数です。
引数の説明
- df : 学習用データセット
- max_depth : ツリーの最大の深さ
- learning_rate : 学習率
- num_round : ブースティングを行う回数
Paramのobjectiveで最小化させる損失関数を指定します。今回は、reg:logistic(logistic回帰)を指定しています。
学習モデルの予測値を返す関数
def predict(bst,df):
return bst.predict(xgb.DMatrix(df))
名前の通りです。
main関数
def main():
df_original = pd.read_csv("/Users/pc1013/Desktop/first/XGBoost_optuna/df/train.csv")
df_test = preprocess(df_original.tail(100))
df_train = preprocess(df_original.head(791))
y = df_test['Survived']
df_test = df_test.drop('Survived',axis=1)
max_depth = 3
learning_rate = 0.6
round_num = 5
bst = train(df_train,max_depth,learning_rate,round_num)
answer = predict(bst,df_train.drop('Survived',axis=1)).round().astype(int)
print('train score :',accuracy_score(answer.round(),df_train['Survived']))
answer = predict(bst,df_test).round().astype(int)
xgb.plot_importance(bst)
print('test score :',accuracy_score(answer.round(),y))
main関数です。
とりあえず、ハイパーパラメータは
- max_depth = 3
- learning_rate = 0.6
- num_round = 5
としてみます。
方針2(xgboost+optuna)
optunaで自動チューニングする時に必要なのが、
- 目的関数を最適化するobjective()関数
- optunaの準備とobjective()の実行(main関数の変更)
objective()について
def objective(df, df_test, y,trial):
#目的関数
max_depth = trial.suggest_int('max_depth',1,30)
learning_rate = trial.suggest_uniform('learning_rate',0.0,1)
round_num = trial.suggest_int('round_num',1,30)
bst = train(df,max_depth,learning_rate,round_num,gamma,min_childe_weigh,subsample,colsample_bytree,alpha,lamb)
answer = predict(bst,df_test).round().astype(int)
score = accuracy_score(answer.round(),y)
return 1.0 - score
max_depthとround_numは[1,30]の整数値
learning_rateは[0.0,1.0]の少数値
と言う範囲でオートチューニングします。
評価は毎回テストデータで行うので、7行目predict(bst,df_test)
は引数にdf_testを入れてます。
main()
def main():
df_original = pd.read_csv("/Users/pc1013/Desktop/first/XGBoost_optuna/df/train.csv")
df_test = preprocess(df_original.tail(100))
df_train = preprocess(df_original.head(791))
y = df_test['Survived']
df_test = df_test.drop('Survived',axis=1)
#optunaの前処理
obj_f = partial(objective, df_train, df_test, y)
#セッション作成
study = optuna.create_study()
#回数
study.optimize(obj_f, n_trials=100)
max_depth = study.best_params['max_depth']
learning_rate = study.best_params['learning_rate']
round_num = study.best_params['round_num']
bst = train(df_train,max_depth,learning_rate,round_num)
print('\nparams :',study.best_params)
answer = predict(bst,df_train.drop('Survived',axis=1)).round().astype(int)
print('train score :',accuracy_score(answer.round(),df_train['Survived']))
answer = predict(bst,df_test).round().astype(int)
xgb.plot_importance(bst)
print('test score :',accuracy_score(answer.round(),y))
変更は8行目から。
obj_fではobjective()の引数を与えています。
n_trialはチューニングの試行回数で、
10から増やしいき50くらいから、増やしても変化があまりみられなかったので、今回は100回としてみました。
study.best_params[element]
でハイパーパラメータ(element)のチューニング結果を得られます。
これを使って、モデルを作成し、評価します。
方針3(optunaで沢山パラメータチューニング)
新たに、今までデフォルトだったパラメータたち
- alpha
- gamma
- min_childe_weigh
- colsample_bytree
をチューニングします。
それぞれのパラメータについては、調べてもなんとなくしかわかりませんでした。
subsampleとlambはか学習を防ぐためのパラメータですが、モデルが過学習しそうになかったため除外して
subsample = 1
lanb = 1
objectice()とmain()については、パラメータ増やすだけなので、コードは省略。
パラメータの範囲だけ⬇︎
- gamma - [0.0,1.0]
- subsample - [0.0,1.0]
- colsample_bytree - [0.0,1.0]
- min_childe_weigh - [0.0,1.0]
- alpha - [0.0,1.0]
実行結果
結果1(xgboostのみ)
精度は、85.0%
学習データ精度85.5%→過学習なし。
寄与率
結果2(xgboost+optuna)
精度は、89.0%
学習データ精度94.5%→過学習なし?
寄与率
結果3(xgboost+optuna+沢山パラメータ)
精度は、89.0%
学習データ精度88.2%→過学習なし。
寄与率
trial回数100
xgboostのアルゴリズムをbinary:logistic(ロジスティック2項分類)にして実行したところ、
精度97.5%が出ました。
9割超えた!
寄与率
まとめ
XGBoostが既にすごい。
optuna使うとちょっと精度上がる。
パラメータ増やすと安定する。
追記
過ちに気づいたので、追記.
本記事では、データを
「 データ → 学習データ + テストデータ 」
と分割し、テストデータを使った損失関数の出力から、パラメータをチューニングしていた.
しかし、これをテストデータでテストすると、本来より精度が高く出る可能性がある.ハイパーパラメータがオーバーフィットする可能性があるからだ.
よって、正しく精度を測るには
「 データ → 学習データ + 検証データ + テストデータ 」
というように分割し、パラメータチューニングは検証データで行い、チューニング後にテストデータで精度を測る必要がある.