自動機器學習超參數調整(貝葉斯優化)

【導讀】機器學習中,調參是一項繁瑣但相當重要的任務,由於它很大程度上影響了算法的性能。手動調參十分耗時,網格和隨機搜索不須要人力,但須要很長的運行時間。所以,誕生了許多自動調整超參數的方法。貝葉斯優化是一種用模型找到函數最小值方法,已經應用於機器學習問題中的超參數搜索,這種方法性能好,同時比隨機搜索省時。此外,如今有許多Python庫能夠實現貝葉斯超參數調整。本文將使用Hyperopt庫演示梯度提高機(Gradient Boosting Machine,GBM) 的貝葉斯超參數調整的完整示例。文章由貝葉斯優化方法、優化問題的四個部分、目標函數、域空間、優化過程、及結果展現幾個部分組成。python

 

貝葉斯優化方法

貝葉斯優化經過基於目標函數的過去評估結果創建替代函數(機率模型),來找到最小化目標函數的值。貝葉斯方法與隨機或網格搜索的不一樣之處在於,它在嘗試下一組超參數時,會參考以前的評估結果,所以能夠省去不少無用功。算法

超參數的評估代價很大,由於它要求使用待評估的超參數訓練一遍模型,而許多深度學習模型動則幾個小時幾天才能完成訓練,並評估模型,所以耗費巨大。貝葉斯調參發使用不斷更新的機率模型,經過推斷過去的結果來「集中」有但願的超參數。app

 

Python中的選擇

Python中有幾個貝葉斯優化庫,它們目標函數的替代函數不同。在本文中,咱們將使用Hyperopt,它使用Tree Parzen Estimator(TPE)。其餘Python庫包括Spearmint(高斯過程代理)和SMAC(隨機森林迴歸)。dom

 

優化問題的四個部分

貝葉斯優化問題有四個部分:機器學習

  • 目標函數:咱們想要最小化的內容,在這裏,目標函數是機器學習模型使用該組超參數在驗證集上的損失。函數

  • 域空間:要搜索的超參數的取值範圍性能

  • 優化算法:構造替代函數並選擇下一個超參數值進行評估的方法。學習

  • 結果歷史記錄:來自目標函數評估的存儲結果,包括超參數和驗證集上的損失。測試

 

數據集

在本例中,咱們將使用Caravan Insurance數據集,其目標是預測客戶是否購買保險單。 這是一個有監督分類問題,訓練集和測試集的大小分別爲5800和4000。評估性能的指標是AUC(曲線下面積)評估準則和ROC(receiver operating characteristic,以真陽率和假陽率爲座標軸的曲線圖)曲線,ROC AUC越高表示模型越好。 數據集以下所示:
優化

爲Hyperopt最小化目標函數,咱們的目標函數返回1-ROC AUC,從而提升ROC AUC。

 

梯度提高模型

梯度提高機(GBM)是一種基於使用弱學習器(如決策樹)組合成強學習器的模型。 GBM中有許多超參數控制整個集合和單個決策樹,如決策樹數量,決策樹深度等。簡單瞭解了GBM,接下來咱們介紹這個問題對應的優化模型的四個部分

 

目標函數

目標函數是須要咱們最小化的。 它的輸入爲一組超參數,輸出須要最小化的值(交叉驗證損失)。Hyperopt將目標函數視爲黑盒,只考慮它的輸入和輸出。 在這裏,目標函數定義爲:

1 def objective(hyperparameters):
2    '''Returns validation score from hyperparameters'''
3 
4    model = Classifier(hyperparameters)
5    validation_loss = cross_validation(model, training_data)
6    return validation_loss

咱們評估的是超參數在驗證集上的表現,但咱們不將數據集劃分紅固定的驗證集和訓練集,而是使用K折交叉驗證。使用10折交叉驗證和提早中止的梯度提高機的完整目標函數以下所示。

 1 import lightgbm as lgb
 2 from hyperopt import STATUS_OK
 3 
 4 N_FOLDS = 10
 5 
 6 # Create the dataset
 7 train_set = lgb.Dataset(train_features, train_labels)
 8 
 9 
10 def objective(params, n_folds=N_FOLDS):
11    '''Objective function for Gradient Boosting Machine Hyperparameter Tuning'''
12 
13    # Perform n_fold cross validation with hyperparameters
14    # Use early stopping and evalute based on ROC AUC
15    cv_results = lgb.cv(params, train_set, nfold=n_folds, num_boost_round=10000,
16                        early_stopping_rounds=100, metrics='auc', seed=50)
17 
18    # Extract the best score
19    best_score = max(cv_results['auc-mean'])
20 
21    # Loss must be minimized
22    loss = 1 - best_score
23 
24    # Dictionary with information for evaluation
25    return {'loss': loss, 'params': params, 'status': STATUS_OK}

關鍵點是cvresults = lgb.cv(...)。爲了實現提早中止的交叉驗證,咱們使用LightGBM函數cv,它輸入爲超參數,訓練集,用於交叉驗證的折數等。咱們將迭代次數(numboostround)設置爲10000,但實際上不會達到這個數字,由於咱們使用earlystopping_rounds來中止訓練,當連續100輪迭代效果都沒有提高時,則提早中止,並選擇模型。所以,迭代次數並非咱們須要設置的超參數。

一旦交叉驗證完成,咱們就會獲得最好的分數(ROC AUC),而後,由於咱們最小化目標函數,因此計算1- ROC AUC,而後返回這個值。

 

域空間

域空間表示咱們要爲每一個超參數計算的值的範圍。在搜索的每次迭代中,貝葉斯優化算法將從域空間爲每一個超參數選擇一個值。當咱們進行隨機或網格搜索時,域空間是一個網格。在貝葉斯優化中,想法是同樣的,可是不是按照順序(網格)或者隨機選擇一個超參數,而是按照每一個超參數的機率分佈選擇。

而肯定域空間是最困難的。若是咱們有機器學習方法的經驗,咱們能夠經過在咱們認爲最佳值的位置放置更大的機率來使用它來告知咱們對超參數分佈的選擇。然而,不一樣數據集之間最佳模型不同,而且具備高維度問題(許多超參數),超參數之間也會互相影響。在咱們不肯定最佳值的狀況下,咱們能夠將範圍設定的大一點,讓貝葉斯算法爲咱們作推理。

首先,咱們看看GBM中的全部超參數:

 1 import lgb
 2 # Default gradient boosting machine classifier
 3 model = lgb.LGBMClassifier()
 4 model
 5 LGBMClassifier(boosting_type='gbdt', n_estimators=100,
 6               class_weight=None, colsample_bytree=1.0,
 7               learning_rate=0.1, max_depth=-1,                      
 8               min_child_samples=20,
 9               min_child_weight=0.001, min_split_gain=0.0, 
10               n_jobs=-1, num_leaves=31, objective=None, 
11               random_state=None, reg_alpha=0.0, reg_lambda=0.0, 
12               silent=True, subsample=1.0, 
13               subsample_for_bin=200000, subsample_freq=1)

其中一些咱們不須要調整(例如objective和randomstate),咱們將使用提早中止來找到最好的n_estimators。 可是,咱們還有10個超參數要優化! 首次調整模型時,我一般會建立一個以默認值爲中心的寬域空間,而後在後續搜索中對其進行細化。

例如,讓咱們在Hyperopt中定義一個簡單的域,這是GBM中每棵樹中葉子數量的離散均勻分佈:

1 from hyperopt import hp
2 # Discrete uniform distribution
3 num_leaves = {'num_leaves': hp.quniform('num_leaves', 30, 150, 1)}

這裏選擇離散的均勻分佈,由於葉子的數量必須是整數(離散),而且域中的每一個值均可能(均勻)。

另外一種分佈選擇是對數均勻,它在對數標度上均勻分佈值。 咱們將使用對數統一(從0.005到0.2)來得到學習率,由於它在幾個數量級上變化:

1 # Learning rate log uniform distribution
2 learning_rate = {'learning_rate': hp.loguniform('learning_rate',
3                                                 np.log(0.005),
4                                                 np.log(0.2)}

下面分別繪製了均勻分佈和對數均勻分佈的圖。 這些是核密度估計圖,所以y軸是密度而不是計數!

如今,讓咱們定義整個域:

 1 # Define the search space
 2 space = {
 3    'class_weight': hp.choice('class_weight', [None, 'balanced']),
 4    'boosting_type': hp.choice('boosting_type', 
 5                               [{'boosting_type': 'gbdt', 
 6                                    'subsample': hp.uniform('gdbt_subsample', 0.5, 1)}, 
 7                                 {'boosting_type': 'dart', 
 8                                     'subsample': hp.uniform('dart_subsample', 0.5, 1)},
 9                                 {'boosting_type': 'goss'}]),
10    'num_leaves': hp.quniform('num_leaves', 30, 150, 1),
11    'learning_rate': hp.loguniform('learning_rate', np.log(0.01), np.log(0.2)),
12    'subsample_for_bin': hp.quniform('subsample_for_bin', 20000, 300000, 20000),
13    'min_child_samples': hp.quniform('min_child_samples', 20, 500, 5),
14    'reg_alpha': hp.uniform('reg_alpha', 0.0, 1.0),
15    'reg_lambda': hp.uniform('reg_lambda', 0.0, 1.0),
16    'colsample_bytree': hp.uniform('colsample_by_tree', 0.6, 1.0)
17 }

這裏咱們使用了許多不一樣的域分發類型:

 1 choice:類別變量
 2 quniform:離散均勻(整數間隔均勻)
 3 uniform:連續均勻(間隔爲一個浮點數)
 4 loguniform:連續對數均勻(對數下均勻分佈)
 5 # boosting type domain 
 6 boosting_type = {'boosting_type': hp.choice('boosting_type', 
 7                                            [{'boosting_type': 'gbdt', 
 8                                                  'subsample': hp.uniform('subsample', 0.5, 1)}, 
 9                                             {'boosting_type': 'dart', 
10                                                  'subsample': hp.uniform('subsample', 0.5, 1)},
11                                             {'boosting_type': 'goss',
12                                                  'subsample': 1.0}])}

這裏咱們使用條件域,這意味着一個超參數的值取決於另外一個超參數的值。 對於提高類型「goss」,gbm不能使用子採樣(僅選擇訓練觀察的子樣本部分以在每次迭代時使用)。 所以,若是提高類型是「goss」,則子採樣率設置爲1.0(無子採樣),不然爲0.5-1.0。 這是使用嵌套域實現的

定義域空間以後,咱們能夠從中採樣查看樣本。

 1 # Sample from the full space
 2 example = sample(space)
 3 
 4 # Dictionary get method with default
 5 subsample = example['boosting_type'].get('subsample', 1.0)
 6 
 7 # Assign top-level keys
 8 example['boosting_type'] = example['boosting_type']['boosting_type']
 9 example['subsample'] = subsample
10 
11 example
12 {'boosting_type': 'gbdt',
13 'class_weight': 'balanced',
14 'colsample_bytree': 0.8111305579351727,
15 'learning_rate': 0.16186471096789776,
16 'min_child_samples': 470.0,
17 'num_leaves': 88.0,
18 'reg_alpha': 0.6338327001528129,
19 'reg_lambda': 0.8554826167886239,
20 'subsample_for_bin': 280000.0,
21 'subsample': 0.6318665053932255}

 

優化算法

雖然這是貝葉斯優化中概念上最難的部分,但在Hyperopt中建立優化算法只需一行。 要使用Tree Parzen Estimator,代碼爲:

1 from hyperopt import tpe
2 # Algorithm
3 tpe_algorithm = tpe.suggest

在優化時,TPE算法根據過去的結果構建機率模型,並經過最大化預期的改進來決定下一組超參數以在目標函數中進行評估。

 

結果歷史

跟蹤結果並非絕對必要的,由於Hyperopt將在內部爲算法執行此操做。 可是,若是咱們想知道幕後發生了什麼,咱們可使用Trials對象來存儲基本的訓練信息,還可使用從目標函數返回的字典(包括損失和範圍)。 制建立Trials對象也只要一行代碼:

1 from hyperopt import Trials
2 # Trials object to track progress
3 bayes_trials = Trials()

爲了監控訓練運行進度,能夠將結果歷史寫入csv文件,防止程序意外中斷致使評估結果消失。

 1 import csv
 2 
 3 # File to save first results
 4 out_file = 'gbm_trials.csv'
 5 of_connection = open(out_file, 'w')
 6 writer = csv.writer(of_connection)
 7 
 8 # Write the headers to the file
 9 writer.writerow(['loss', 'params', 'iteration', 'estimators', 'train_time'])
10 of_connection.close()

而後在目標函數中咱們能夠在每次迭代時添加行寫入csv:

1 # Write to the csv file ('a' means append)
2 of_connection = open(out_file, 'a')
3 writer = csv.writer(of_connection)
4 writer.writerow([loss, params, iteration, n_estimators, run_time])
5 of_connection.close()

 

優化:

一旦咱們定義好了上述部分,就能夠用fmin運行優化:

1 from hyperopt import fmin
2 MAX_EVALS = 500
3 # Optimize
4 best = fmin(fn = objective, space = space, algo = tpe.suggest, 
5            max_evals = MAX_EVALS, trials = bayes_trials)

在每次迭代時,算法從代理函數中選擇新的超參數值,該代理函數基於先前的結果構建並在目標函數中評估這些值。 這繼續用於目標函數的MAX_EVALS評估,其中代理函數隨每一個新結果不斷更新。

 

結果:

從fmin返回的最佳對象包含在目標函數上產生最低損失的超參數:

 1 {'boosting_type': 'gbdt',
 2   'class_weight': 'balanced',
 3   'colsample_bytree': 0.7125187075392453,
 4   'learning_rate': 0.022592570862044956,
 5   'min_child_samples': 250,
 6   'num_leaves': 49,
 7   'reg_alpha': 0.2035211643104735,
 8   'reg_lambda': 0.6455131715928091,
 9   'subsample': 0.983566228071919,
10   'subsample_for_bin': 200000}

一旦咱們有了這些超參數,咱們就可使用它們來訓練完整訓練數據的模型,而後評估測試數據。 最終結果以下:

The best model scores 0.72506 AUC ROC on the test set.
The best cross validation score was 0.77101 AUC ROC.
This was achieved after 413 search iterations.

做爲參考,500次隨機搜索迭代返回了一個模型,該模型在測試集上評分爲0.7232 ROC AUC,在交叉驗證中評分爲0.76850。沒有優化的默認模型在測試集上評分爲0.7143 ROC AUC。

在查看結果時,請記住一些重要的注意事項:

最佳超參數是那些在交叉驗證方面表現最佳的參數,而不必定是那些在測試數據上作得最好的參數。當咱們使用交叉驗證時,咱們但願這些結果能夠推廣到測試數據。

即便使用10倍交叉驗證,超參數調整也會過分擬合訓練數據。交叉驗證的最佳分數顯著高於測試數據。

隨機搜索能夠經過純粹的運氣返回更好的超參數(從新運行筆記本能夠改變結果)。貝葉斯優化不能保證找到更好的超參數,而且可能陷入目標函數的局部最小值。

另外一個重點是超參數優化的效果將隨數據集的不一樣而不一樣。相對較小的數據集(訓練集大小爲6000),調整超參數,最終獲得的模型的提高並不大,但數據集更大時,效果會很明顯。

所以,經過貝葉斯機率來優化超參數,咱們能夠:在測試集上獲得更好的性能;調整超參數的迭代次數減小.

 

可視化結果:

繪製結果圖表是一種直觀的方式,能夠了解超參數搜索過程當中發生的狀況。此外,經過將貝葉斯優化與隨機搜索進行比較,能夠看出方法的不一樣之處。

首先,咱們能夠製做隨機搜索和貝葉斯優化中採樣的learning_rate的核密度估計圖。做爲參考,咱們還能夠顯示採樣分佈。垂直虛線表示學習率的最佳值(根據交叉驗證)。

 

咱們將學習率定義爲0.005到0.2之間的對數正態,貝葉斯優化結果看起來與採樣分佈相似。 這告訴咱們,咱們定義的分佈看起來適合於任務,儘管最佳值比咱們放置最大機率的值略高。 這可用於告訴域進一步搜索。

另外一個超參數是boosting_type,在隨機搜索和貝葉斯優化期間評估每種類型的條形圖。 因爲隨機搜索不關注過去的結果,咱們預計每種加強類型的使用次數大體相同。

 

根據貝葉斯算法,gdbt提高模型比dart或goss更有前途。 一樣,這能夠幫助進一步搜索,貝葉斯方法或網格搜索。 若是咱們想要進行更明智的網格搜索,咱們可使用這些結果來定義圍繞超參數最有但願的值的較小網格。

咱們再看下其餘參數的分佈,隨機搜索和貝葉斯優化中的全部數值型超參數。 垂直線再次表示每次搜索的超參數的最佳值:

在大多數狀況下(subsample_for_bin除外),貝葉斯優化搜索傾向於在超參數值附近集中(放置更多機率),從而產生交叉驗證中的最低損失。這顯示了使用貝葉斯方法進行超參數調整的基本思想:花費更多時間來評估有但願的超參數值。

此處還有一些有趣的結果可能會幫助咱們在未來定義要搜索的域空間時。僅舉一個例子,看起來regalpha和reglambda應該相互補充:若是一個是高(接近1.0),另外一個應該更低。不能保證這會解決問題,但經過研究結果,咱們能夠得到可能適用於將來機器學習問題的看法!

 

搜索的演變

隨着優化的進展,咱們指望貝葉斯方法關注超參數的更有但願的值:那些在交叉驗證中產生最低偏差的值。咱們能夠繪製超參數與迭代的值,以查看是否存在明顯的趨勢。

黑星表示最佳值。 colsample_bytree和learning_rate會隨着時間的推移而減小,這可能會指導咱們將來的搜索。

 

最後,若是貝葉斯優化工做正常,咱們預計平均驗證分數會隨着時間的推移而增長(相反,損失減小):

來自貝葉斯超參數優化的驗證集上的分數隨着時間的推移而增長,代表該方法正在嘗試「更好」的超參數值(應該注意,僅根據驗證集上的表現更好)。隨機搜索沒有顯示迭代的改進。

 

繼續搜索

若是咱們對模型的性能不滿意,咱們能夠繼續使用Hyperopt進行搜索。咱們只須要傳入相同的試驗對象,算法將繼續搜索。

隨着算法的進展,它會進行更多的挖掘 - 挑選過去表現良好的價值 , 而不是探索 - 挑選新價值。所以,開始徹底不一樣的搜索多是一個好主意,而不是從搜索中止的地方繼續開始。若是來自第一次搜索的最佳超參數確實是「最優的」,咱們但願後續搜索專一於相同的值。

通過另外500次訓練後,最終模型在測試集上得分爲0.72736 ROC AUC。 (咱們實際上不該該評估測試集上的第一個模型,而只依賴於驗證分數。理想狀況下,測試集應該只使用一次,以便在部署到新數據時得到算法性能的度量)。一樣,因爲數據集的小尺寸,這個問題可能致使進一步超參數優化的收益遞減,而且最終會出現驗證錯誤的平臺(因爲隱藏變量致使數據集上任何模型的性能存在固有限制未測量和噪聲數據,稱爲貝葉斯偏差)

 

結論

可使用貝葉斯優化來完成機器學習模型的超參數自動調整。與隨機或網格搜索相比,貝葉斯優化對目標函數的評估較少,測試集上的更好的泛化性能。

在本文中,咱們使用Hyperopt逐步完成了Python中的貝葉斯超參數優化。除了網格和隨機搜索以外,咱們還可以提升梯度提高樹的測試集性能,儘管咱們須要謹慎對待訓練數據的過分擬合。此外,咱們經過可視化結果表看到隨機搜索與貝葉斯優化的不一樣之處,這些圖表顯示貝葉斯方法對超參數值的機率更大,致使交叉驗證損失更低。

使用優化問題的四個部分,咱們可使用Hyperopt來解決各類各樣的問題。貝葉斯優化的基本部分也適用於Python中實現不一樣算法的許多庫。從手動切換到隨機或網格搜索只是一小步,但要將機器學習提高到新的水平,須要一些自動形式的超參數調整。貝葉斯優化是一種易於在Python中使用的方法,而且能夠比隨機搜索返回更好的結果。

 

參考自https://towardsdatascience.com/automated-machine-learning-hyperparameter-tuning-in-python-dfda59b72f8a

相關文章
相關標籤/搜索