自動化調整超參數方法介紹(使用python)

yuwei
Jacky’s blog
Published in
Sep 19, 2018

首先,要跟各位說明,這篇主要是參考William Koehrsen大神的這篇文章,也非常感謝他能對data science領域有這麼大的貢獻

在這一章我將延續上一個project裡面的在hyperparameter tuning的內容, 大家如果還記得我在那個專案的內容, 可以知道我是用random search 和grid search 來找出近似最佳解的hyperparameter, 但其實這兩個既耗費長時間在訓練模型也無法透過之前套參數來改善下一次對超參數的選擇,也就是說randomsearch和gridsearch浪費了很多時間和資源去評估一個爛的超參數,所以有了Bayesian Optimization(貝氏最優化)的出現,如果只用一段話來解釋這個概念

Bayesian Optimization : 建立一個含有目標函數的機率模型, 並且使用它去選擇最佳的超參數去評估正確的目標函數(類似條件機率)

超參數的最佳化可以用這個等式來表達

source

f(x)代表的是目標函數(類似指標), 而你需要將其最小化,以評估測試集,而x*則是hyperparameter能使f(x)最小化, 即產生最棒的分數在你用的metric上,

而如果你要處理這個目標函數你必須有4個部分

  1. 目標函數: 當然必須要有目標函數, 這是我們必須最小化的部分,降低驗證錯誤透過hyperparameter的tuning
  2. 範圍:我們必須明確確定參數的範圍,因為有可能A這個超參數只能為int,而B超參數可以為小數點(到很多小數)來表達,有些則因為是categorical data,所以只能有0,1來表示那個群體
  3. 最佳化演算法: 方法可以用來建立條件模型包含超參數和誤差項
  4. 結果:必須紀錄當你使用超參數和誤差項在目標函數的所有結果

我這裡就使用較簡單的LGBM的模型來針對hyporopt的調參數做一個簡單介紹,

lightGradientBoostingMachine: 與我們前兩篇所使用的GBC很相同,但訓練更快,更好的準確率,處理大量數據較為方便

1.要先定義目標函數

如題, 我們要先定義目標函數,藉由定義目標函數,還有評估你要用何種function和metric來看你的績效, 這都是我們必須判斷的

現在已經設定目標函數完成了,相信大家應該也跟我第一次看這個一樣懞懞懂懂的,沒關係,這些之後都會串在一起

2.範圍

代表我們想要評估的演算法的超參數範圍,在grid search和random search時,範圍就是一個網狀的形狀,從裡面random的選出要的參數,或每種組合都跑過一遍,而在Bayesian optimation裡,每個超參數的機率函數,所以,我們要先看LightGBM裡面有什麼超參數

LGBMClassifier(boosting_type='gbdt', class_weight=None, colsample_bytree=1.0,learning_rate=0.1, max_depth=-1, min_child_samples=20,min_child_weight=0.001,min_split_gain=0.0,n_estimators=100,n_jobs=-1, num_leaves=31, objective=None, random_state=None,reg_alpha=0.0, reg_lambda=0.0, silent=True, subsample=1.0,subsample_for_bin=200000, subsample_freq=0)

在這些參數裡面,我不會針對’objective’,’random_state'或’n_estimators’去做調整,所以還有10個超參數是我需要處理的,所以不急,讓我們慢慢來,先從透過將超參數分成不同的機率模型開始

舉例來說, ‘num_leaves’是其中一個都必須為整數的超參數,

from hyperopt import hp
#建立非連續行的uniform distribution
num_leaves={'num_leaves':hp.quniform('num_leaves',30,150,1)}

,而learning rate這個超參數則是採用log-均勻分配函數,這樣可以使Learning rate的比例主要瞄準在低-平均的間隔中,大家可以參考以下圖例

source
#建立log的uniform distribution
learning_rate={'learning_rate': hp.loguniform('learning_rate', np.log(0.01), np.log(0.2))}

既然出現了這麼多的uniform function(均勻分配函數),我們在這一篇會使用的hp的機率函數型態

choice:針對屬性資料(uniform)

quniform:針對非連續性資料(uniform)

uniform:針對連續性資料(uniform)

loguniform:針對連續性log 資料(uniform)

針對所有我們想調的超參數我都已經設定好了,接下來我會隨便取一個例子來看是否真的是我們所想要的超參數格式

x = sample(space)
subsample = x['boosting_type'].get('subsample', 1.0)
x['boosting_type'] = x['boosting_type']['boosting_type']
x['subsample'] = subsample
x

在這裡的boosting_type裡面的subsample因為是被包覆在裡面,為了將其表達出來額外拉出獨自的key所以使用get來將subsample提出,

3.最佳化演算法

這裡原本是最難的部分,因為涉及到演算法的應用,但在Hyperopt裡面很簡單,要使用Tree Parzen Estimator

from hyperopt import tpe
tpe_algorithm = tpe.suggest

就是被簡化了這麼多,hyperopt目前就只有Tree Parzen Estimator和 Random Search這兩種, 而TPE是藉由建立機率模型來最佳化

4.儲存歷史

在這一個小part, 我們將在最佳話的過程中會產生的每筆資料都紀錄下來而不是只找出極值, 最後回return 我們剛剛objective function裡面所define的dict(loss,params,迴圈,預測器,train的時間,狀態)最後都存在一個csv裡

from hyperopt import Trials
# Keep track of results
bayes_trials = Trials()

Trial會保留所有目標函數回傳的result, 我們再藉由建立csv來讓原本我們在objective function設定的csv append function能夠運作

#儲存結果的file 
out_file = 'gbm_trials.csv'
of_connection = open(out_file, 'w')
writer = csv.writer(of_connection)
# 寫入header的名稱
writer.writerow(['loss', 'params', 'iteration', 'estimators', 'train_time'])
of_connection.close()

好了,目標函數,超參數範圍,演算法和儲存歷史的套件都已經準備就位,現在我們將使用hyperopt下的fmin套件來optimize

這個max_eval=200 其實用電腦跑了也有一段時間,給大家看看跑流程時,loss的變化

results1=pd.read_csv('gbm_trials.csv')
plt.plot(results1['loss'])

不會像grid search不會了解資料的好壞和無意義的跑高loss的model

results = pd.read_csv('gbm_trials.csv')
#找出最低的loss 並排序
results.sort_values('loss', ascending = True, inplace = True)
results.reset_index(inplace = True, drop = True)
results.head()

返回第一個最低loss的params, 而因為其value為string,但是我們希望可以以dict表示這些hyperparameter, 所以我們要使用ast套件底下的literal_eval,可以幫助Python處理抽象的語法解析,判斷需要內容是不是合法的python資料型態

import ast
# Convert from a string to a dictionary
ast.literal_eval(results.loc[0, 'params'])

string的型態會被判斷成dict

找出roc auc score

最後,來找出交叉驗證後的分數, 確認我們是否有好的score,至少要比上一篇沒調參數前好吧!

lgbm_cv=cross_val_score(best_bayes_model,train_removed,labels,cv=20,scoring=score)
print(lgbm_cv)
print(np.mean(lgbm_cv))
print(np.std(lgbm_cv))

是有的!

結論:

又到了美好的結論時刻,我想跟各位說的是,不管是透過grid search 或 random search還是hyperopt, 你都必須經歷過一段了解hyperparameter的過程,所以我還繼續花費長時間的研究每個algorithm底下超參數的應用,希望這篇自動化調參數的文章可以幫助你在研究或工作上更進一步

如往常一樣,歡迎隨時寄email來跟我討論問題,我的email是jacky308082@gmail.com

謝謝各位!辛苦了!

--

--

yuwei
Jacky’s blog

Curious Data scientist. Strong Lebron James’s fan. #StriveForGreatness #JustAKidFromTaiwan https://www.linkedin.com/in/yu-wei-chung/