前回は、ベイズ最適化の可視化を行いました。
今回は、アンサンブル学習(Voting)にベイズ最適化を適用します。
#Votingとは
アンサンブル学習といえばStackingが有名ですが、Votingは各分類器で多数決を
とって決める手法です。詳しくは参考資料のリンクを見て下さい。
図にすると以下のようになります。
ここでは、K近傍法、XGBoost、ランダムフォレスト、ニューラルネットの4つを
使っています。予測ラベルの決定は単純な多数決ではなく、それぞれの分類器に
重み係数kを掛けて投票させます。
この重み係数をベイズ最適化で求めるのが、本稿のテーマです。
式にすると、以下のようになります。
KNN + k_1XGB + k_2RF + k_3NN
ここでは、K近傍法の係数を1に固定しています。重み付きの投票で一番多かった
投票がラベルとして出力されます。
分類精度が最も高くなるように、k1,k2,k3をベイズ最適化で求めます。
#各分類器の用意
使うデータはお馴染みのMNISTです。
学習用と評価用のデータ数は以下のとおりです。
・学習データ:1000個
・評価データ:300個
import numpy as np
import pandas as pd
from keras.datasets import mnist
#MNIST データロード
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.reshape(60000, 784)
X_test = X_test.reshape(10000, 784)
x_train = X_train.astype('float32')
x_test = X_test.astype('float32')
x_train /= 255
x_test /= 255
x_train = x_train[:1000]
x_test = x_test[:300]
y_train = y_train[:1000]
y_test = y_test[:300]
まず、k近傍法を学習させます。
from sklearn import neighbors
#KNN
knn = neighbors.KNeighborsClassifier(5, weights = 'distance')
knn.fit(x_train,y_train)
XGBoostを学習させます。
import xgboost as xgb
#XGBoost
Xgb = xgb.XGBClassifier()
Xgb.fit(x_train,y_train)
ランダムフォレストを学習させます。
from sklearn.ensemble import RandomForestClassifier
#Random Forest
RF=RandomForestClassifier()
RF.fit(x_train,y_train)
最後にkerasを使って、ニューラルネットを学習させます。
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense, Activation
#NN
model = Sequential()
model.add(Dense(30, input_dim=x_train.shape[1]))
model.add(Activation('relu'))
model.add(Dense(10))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
Y_train = to_categorical(y_train, 10)
hist = model.fit(x_train, Y_train,
epochs=30, batch_size=100,verbose=0)
各分類器の精度を出してみます。
#それぞれの分類精度
from sklearn.metrics import accuracy_score
knn_predict = knn.predict(x_test)
print("KNN")
print(accuracy_score(y_test, knn_predict))
xgb_predict = Xgb.predict(x_test)
print("XGBoost")
print(accuracy_score(y_test, xgb_predict))
RF_predict = RF.predict(x_test)
print("Random Forest")
print(accuracy_score(y_test, RF_predict))
NN_predict = model.predict_classes(x_test, batch_size=1)
print("NN")
print(accuracy_score(y_test, NN_predict))
KNN
0.85
XGBoost
0.873
Random Forest
0.75
NN
0.863
意外にもXGBoostが一番でした。画像系に強いNNは少し届かず。
アンサンブルすることにより、XGBの87.3%をどれだけ高めることができるのかが、
焦点になってきます。
#アンサンブル学習
重み無しでVotingを行ってみます。
def Voting(x_voting,y_voting,k1=1,k2=1,k3=1):
knn_predict = knn.predict(x_voting)
xgb_predict = Xgb.predict(x_voting)
RF_predict = RF.predict(x_voting)
NN_predict = model.predict_classes(x_voting,batch_size=1)
knn_onehot = to_categorical(knn_predict, 10)
xgb_onehot = to_categorical(xgb_predict, 10)
RF_onehot = to_categorical(RF_predict, 10)
NN_onehot = to_categorical(NN_predict, 10)
voting_result = []
for i in range(len(x_voting)):
voting = np.argmax(knn_onehot[i] + k1*xgb_onehot[i] +
k2*RF_onehot[i] + k3*NN_onehot[i])
voting_result.append(voting)
result = accuracy_score(y_voting, voting_result)
print(result)
return result
print("Normal Voting")
_ = Voting(x_test,y_test)
Normal Voting
0.873
精度は、XGB単独と変わらずでした。
#アンサンブル学習の最適化
ここからが本題です。分類精度が良くなるように重み係数kをベイズ最適化で
探索します。前回と同じように、最適化する関数と重み係数の幅を定義します。
#ベイズ最適化
import GPy
import GPyOpt
import matplotlib.pyplot as plt
def f(x):
k = x[:,0]
kk = x[:,1]
kkk = x[:,2]
score = -Voting(x_train,y_train,k,kk,kkk)
print(-score)
return score
bounds = [{'name': 'k', 'type': 'continuous', 'domain': (0.5,1.5)},
{'name': 'kk', 'type': 'continuous', 'domain': (0.5,1.5)},
{'name': 'kkk', 'type': 'continuous', 'domain': (0.5,1.5)}]
あとは、学習データの予測結果を関数に渡して最適化させます。
myBopt = GPyOpt.methods.BayesianOptimization(f=f, domain=bounds)
myBopt.run_optimization(max_iter=10)
#結果出力
print("best [k1 k2 k3] =")
print(myBopt.x_opt)
print("Weighted Voting")
k_best = myBopt.x_opt
_ = Voting(x_test,y_test,k_best[0],k_best[1],k_best[2])
#探索履歴描画
result_z=-myBopt.Y
plt.figure()
plt.plot(result_z)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.show()
Weighted Voting
0.87
あれ? 重み無しよりも精度が下がってしまいました。
これは、「学習データの予測結果」を使って重み係数を最適化すると
簡単に精度100%が出てしまい、それ以降の探索を止めてしまっている
可能性があります。学習データでは、各分類器が優秀な分類結果を出して
いるため、いいかげんな重み係数を使っても精度100%が出てしまうわけです。
探索履歴をグラフ化するとこうなります。
従って、「学習データではない他のデータ」の予測結果を使って精度100%が
出にくい状況で、ベイズ最適化を行う必要があります。ここでは、学習データに
ノイズを加えたデータを用意しました。
x_train_n = x_train + np.random.normal(0, 0.1, (len(x_train), 784))
ベイズ最適化のコードは以下のように代えて、再度ベイズ最適化を行います。
score = -Voting(x_train_n,y_train,k,kk,kkk)
Weighted Voting
0.877
見事、重み無しのVotingよりも良い結果が出ました。
※今回は学習データにノイズを加えたものを用意しました。一見、「評価データ」を
使えば良いじゃん!と思うかもしれませんが、「評価データ」に手を出しては
いけません。評価データに手を出すとテストでカンニングするのと同じになります。
評価データは、最後の一回に評価用で使います。
重み付きのVotingが一番良い成績となりました。
#コツ
すんなりと最適化ができたように思えますが、実は2、3日ハマっていました。
以下にコツを示しますが、あくまでもここで試したコツです。使うデータと
データの個数とアンサンブルの組み合わせで、変わってくる可能性があります。
・前述したように、最適化する関数では「学習データの予測結果」を使ってはいけません。
精度が100%で飽和してしまうためです。本稿のように、ノイズを加えた学習データを
用意するか、もしくは学習データを分割して「各分類器用の学習データ」と
「ベイズ最適化用の学習データ」を用意すると良いかもしれません。
・ベイズ最適化のイタレーションは、あまり大きくとらない方が良いです。
大きくとると、ベイズ最適化用の学習データに過学習を起こす原因と
なります。ニューラルネットでepochsを大きくとるのと同じ現象です。
・重み係数の上限値は、あまり大きくとらない方が良いです。
重み係数が大きすぎると、これも過学習を起こす原因となります。
次回は、今度こそXGBoostのハイパーパラメータ探索にベイズ最適化を適用します。
#参考資料
・アンサンブル学習(Stacked generalization)のサンプルプログラムと実行例
https://qiita.com/TomHortons/items/2a05b72be180eb83a204
・KAGGLE ENSEMBLING GUIDE
https://mlwave.com/kaggle-ensembling-guide/