超參數調優方法:網格搜索,隨機搜索,貝葉斯優化等算法。
1、分別對幾種調有方法進行了實驗,實驗初始數據如下:
import numpy as np import pandas as pd from lightgbm.sklearn import LGBMRegressor from sklearn.metrics import mean_squared_error import warnings warnings.filterwarnings('ignore') from sklearn.datasets import load_diabetes from sklearn.model_selection import KFold, cross_val_score from sklearn.model_selection import train_test_split import timeit import os import psutil #在sklearn.datasets的糖尿病數據集上演示和比較不同的算法,加載它。 diabetes = load_diabetes() data = diabetes.data targets = diabetes.target n = data.shape[0] random_state=42 #時間占用 s start=timeit.default_timer() #內存占用 mb info_start = psutil.Process(os.getpid()).memory_info().rss/1024/1024 train_data, test_data, train_targets, test_targets = train_test_split(data, targets, test_size=0.20, shuffle=True, random_state=random_state) num_folds=2 kf = KFold(n_splits=num_folds, random_state=random_state) model = LGBMRegressor(random_state=random_state) score = -cross_val_score(model, train_data, train_targets, cv=kf, scoring="neg_mean_squared_error", n_jobs=-1).mean() print(score) end=timeit.default_timer() info_end = psutil.Process(os.getpid()).memory_info().rss/1024/1024 print('此程序運行占內存'+str(info_end-info_start)+'mB') print('Running time:%.5fs'%(end-start))
實驗結果為:
最小平方誤差:3532.0822189641976
此程序運行占內存:0.22265625mB
Running time:0.26576s
出於對比目的,我們將優化僅調整以下三個參數的模型:
n_estimators:從100到2000
max_depth:2到20
learning_rate:從10e-5到1
2、網格搜索:
網格搜索可能是最簡單,應用最廣泛的超參數搜索算法,他通過查找搜索范圍內的所以的點來確定最優值。如果采用較大的搜索范圍及較小的步長,網格搜索很大概率找到全局最優值。然而這種搜索方案十分消耗計算資源和時間,特別是需要調優的超參數比較多的時候。
因此在實際應用過程中,網格搜索法一般會先使用較廣的搜索范圍和較大的步長,來找到全局最優值可能的位置;然后再縮小搜索范圍和步長,來尋找更精確的最優值。這種操作方案可以降低所需的時間和計算量,但由於目標函數一般是非凸的,所以很可能會錯過全局最優值。
網格搜素對應於sklearn中的GridSearchCV模塊:sklearn.model_selection.
GridSearchCV
(estimator, param_grid, *, scoring=None, n_jobs=None, iid='deprecated', refit=True, cv=None, verbose=0, pre_dispatch='2*n_jobs', error_score=nan, return_train_score=False). 詳情見博客。
from sklearn.model_selection import GridSearchCV #時間占用 s start=timeit.default_timer() #內存占用 mb info_start = psutil.Process(os.getpid()).memory_info().rss/1024/1024 param_grid={'learning_rate': np.logspace(-3, -1, 3), 'max_depth': np.linspace(5,12,8,dtype = int), 'n_estimators': np.linspace(800,1200,5, dtype = int), 'random_state': [random_state]} gs=GridSearchCV(model, param_grid, scoring='neg_mean_squared_error', fit_params=None, n_jobs=-1, cv=kf, verbose=False) gs.fit(train_data, train_targets) gs_test_score=mean_squared_error(test_targets, gs.predict(test_data)) end=timeit.default_timer() info_end = psutil.Process(os.getpid()).memory_info().rss/1024/1024 print('此程序運行占內存'+str(info_end-info_start)+'mB') print('Running time:%.5fs'%(end-start)) print("Best MSE {:.3f} params {}".format(-gs.best_score_, gs.best_params_))
實驗結果:
此程序運行占內存9.265625mB
Running time:296.24025s
Best MSE 3320.630 params {'learning_rate': 0.01, 'max_depth': 5, 'n_estimators': 800, 'random_state': 42}
可視化解釋:
import matplotlib.pyplot as plt
gs_results_df=pd.DataFrame(np.transpose([-gs.cv_results_['mean_test_score'], gs.cv_results_['param_learning_rate'].data, gs.cv_results_['param_max_depth'].data, gs.cv_results_['param_n_estimators'].data]), columns=['score', 'learning_rate', 'max_depth', 'n_estimators']) gs_results_df.plot(subplots=True,figsize=(10, 10))
plt.show()
我們可以看到,例如max_depth是最不重要的參數,它不會顯着影響得分。 但是,我們正在搜索max_depth的8個不同值,並且在其他參數上搜索了任何固定值。 顯然浪費時間和資源。
3、隨機搜索
隨機搜索的思想與網絡搜索比較相似,只是不再測試上界和下界之間所有值,而是在搜索范圍內隨機選取樣本點。他的理論依據是,如果樣本點集足夠大,那么通過隨機采樣也能大概率地找到全局最優值或近似值。隨機搜索一般會比網絡搜索要快一些。我們在搜索超參數的時候,如果超參數個數較少(三四個或者更少),那么我們可以采用網格搜索,一種窮盡式的搜索方法。但是當超參數個數比較多的時候,我們仍然采用網格搜索,那么搜索所需時間將會指數級上升。
所以有人就提出了隨機搜索的方法,隨機在超參數空間中搜索幾十幾百個點,其中就有可能有比較小的值。這種做法比上面稀疏化網格的做法快,而且實驗證明,隨機搜索法結果比稀疏網格法稍好。RandomizedSearchCV使用方法和類GridSearchCV 很相似,但他不是嘗試所有可能的組合,而是通過選擇每一個超參數的一個隨機值的特定數量的隨機組合,這個方法有兩個優點:
- 如果你讓隨機搜索運行, 比如1000次,它會探索每個超參數的1000個不同的值(而不是像網格搜索那樣,只搜索每個超參數的幾個值)
- 你可以方便的通過設定搜索次數,控制超參數搜索的計算量。
RandomizedSearchCV的使用方法其實是和GridSearchCV一致的,但它以隨機在參數空間中采樣的方式代替了GridSearchCV對於參數的網格搜索,在對於有連續變量的參數時,RandomizedSearchCV會將其當做一個分布進行采樣進行這是網格搜索做不到的,它的搜索能力取決於設定的n_iter參數,同樣的給出代碼。
隨機搜索對於於sklearn中的sklearn.model_selection.
RandomizedSearchCV
(estimator,param_distributions,*,n_iter = 10,得分= None,n_jobs = None,iid ='deprecated',refit = True,cv = None,verbose = 0,pre_dispatch ='2 * n_jobs',random_state = None,error_score = nan,return_train_score = False )參數與GridSearchCV大致相同,但是多了一個n_iter,為迭代輪數。
from sklearn.model_selection import RandomizedSearchCV from scipy.stats import randint param_grid_rand={'learning_rate': np.logspace(-5, 0, 100), 'max_depth': randint(2,20), 'n_estimators': randint(100,2000), 'random_state': [random_state]} #時間占用 s start=timeit.default_timer() #內存占用 mb info_start = psutil.Process(os.getpid()).memory_info().rss/1024/1024 rs=RandomizedSearchCV(model, param_grid_rand, n_iter = 50, scoring='neg_mean_squared_error', fit_params=None, n_jobs=-1, cv=kf, verbose=False, random_state=random_state) rs.fit(train_data, train_targets) rs_test_score=mean_squared_error(test_targets, rs.predict(test_data)) end=timeit.default_timer() info_end = psutil.Process(os.getpid()).memory_info().rss/1024/1024 print('此程序運行占內存'+str(info_end-info_start)+'mB') print('Running time:%.5fs'%(end-start)) print("Best MSE {:.3f} params {}".format(-rs.best_score_, rs.best_params_))
實驗結果:
此程序運行占內存10.98046875mB
Running time:110.87318s
Best MSE 3200.924 params {'learning_rate': 0.0047508101621027985, 'max_depth': 19, 'n_estimators': 829, 'random_state': 42}
由此可見在運行50輪的時候效果已經比GridSearchCV效果要好了,而且用時更短。
3、貝葉斯優化算法
網格搜索速度慢,但在搜索整個搜索空間方面效果很好,而隨機搜索很快,但可能會錯過搜索空間中的重要點。幸運的是,還有第三種選擇:貝葉斯優化。本文我們將重點介紹貝葉斯優化的一個實現,一個名為hyperopt
的 Python 模塊。貝葉斯優化算法在尋找最優和最值參數時。采用了與網格搜索和隨機搜索完全不同的方法。網格搜素和隨機搜索在測試一個新點時,會忽略前一個點的信息,而貝葉斯優化算法則充分利用了之前的信息。貝葉斯優化算法通過對目標函數形狀進行學習,找到使目標函數向全局最優值提升的參數。
具體來說,學習目標函數的方法是,首先根據先驗分布,假設一個搜索函數;然后,每一次使用新的采樣點來測試目標函數時,利用這個信息來更新目標函數的先驗分布;最后,算法測試由后驗分布給出的全局最值可能出現的位置的點。對於貝葉斯優化算法,有一個需要注意的是,一旦找到可一個局部最優值,他會在該區域不斷采樣,所以很容易陷入局部最優值。為了彌補這個缺點,貝葉斯算法會在探索和利用之間找到一個平衡點,探索就是在還未取樣的區域獲取采樣點,而利用則根據后驗分布在最可能出現的全局最值區域進行采樣。
我們將使用hyperopt庫來處理此算法。 它是超參數優化最受歡迎的庫之一。詳細介紹看博客。
(1)TPE算法:
algo=tpe.suggest
TPE是Hyperopt的默認算法。 它使用貝葉斯方法進行優化。 它在每一步都試圖建立函數的概率模型,並為下一步選擇最有希望的參數。 這類算法的工作方式如下:
- 生成隨機初始點x
- 計算F(x)
- 利用試驗歷史嘗試建立條件概率模型P(F|x)
- 根據P(F|x)選擇xi最有可能導致更好的F(xi)
- 計算F(xi)的實際值
- 重復步驟3-5,直到滿足停止條件之一,例如i>max_evals
from hyperopt import fmin, tpe, hp, anneal, Trials def gb_mse_cv(params, random_state=random_state, cv=kf, X=train_data, y=train_targets): # the function gets a set of variable parameters in "param" params = {'n_estimators': int(params['n_estimators']), 'max_depth': int(params['max_depth']), 'learning_rate': params['learning_rate']} # we use this params to create a new LGBM Regressor model = LGBMRegressor(random_state=random_state, **params) # and then conduct the cross validation with the same folds as before score = -cross_val_score(model, X, y, cv=cv, scoring="neg_mean_squared_error", n_jobs=-1).mean() return score # 狀態空間,最小化函數的params的取值范圍 space={'n_estimators': hp.quniform('n_estimators', 100, 2000, 1), 'max_depth' : hp.quniform('max_depth', 2, 20, 1), 'learning_rate': hp.loguniform('learning_rate', -5, 0) } # trials 會記錄一些信息 trials = Trials() #時間占用 s start=timeit.default_timer() #內存占用 mb info_start = psutil.Process(os.getpid()).memory_info().rss/1024/1024 best=fmin(fn=gb_mse_cv, # function to optimize space=space, algo=tpe.suggest, # optimization algorithm, hyperotp will select its parameters automatically max_evals=50, # maximum number of iterations trials=trials, # logging rstate=np.random.RandomState(random_state) # fixing random state for the reproducibility ) # computing the score on the test set model = LGBMRegressor(random_state=random_state, n_estimators=int(best['n_estimators']), max_depth=int(best['max_depth']),learning_rate=best['learning_rate']) model.fit(train_data,train_targets) tpe_test_score=mean_squared_error(test_targets, model.predict(test_data)) end=timeit.default_timer() info_end = psutil.Process(os.getpid()).memory_info().rss/1024/1024 print('此程序運行占內存'+str(info_end-info_start)+'mB') print('Running time:%.5fs'%(end-start)) print("Best MSE {:.3f} params {}".format( gb_mse_cv(best), best))
實驗結果:
此程序運行占內存2.5859375mB
Running time:52.73683s
Best MSE 3186.791 params {'learning_rate': 0.026975706032324936, 'max_depth': 20.0, 'n_estimators': 168.0}
(2)Simulated Anneal模擬退火算法:
- 生成隨機初始點x
- 計算F(x)
- 在x的某個鄰域中隨機生成xi
- 計算F(xi)
- 根據規則更新x:
- 如果F(xi)<= F(x):x = xi 否則:x = xi的概率為p = exp((F(x)−F(xi))/Ti)Ti(稱為溫度)不斷降低的序列
- 重復步驟3-5,直到滿足停止條件之一:i> max_evals或Ti<Tmin
當Ti高時,即使F(xi)> F(x),更新x的概率也很高,該算法執行了許多探索步驟(類似於隨機搜索)
但是,當T降低時,算法將重點放在開發上-所有xi都接近於迄今為止找到的最佳解決方案之一。
algo=anneal.suggest
# possible values of parameters space={'n_estimators': hp.quniform('n_estimators', 100, 2000, 1), 'max_depth' : hp.quniform('max_depth', 2, 20, 1), 'learning_rate': hp.loguniform('learning_rate', -5, 0) } # trials will contain logging information trials = Trials() #時間占用 s start=timeit.default_timer() #內存占用 mb info_start = psutil.Process(os.getpid()).memory_info().rss/1024/1024 best=fmin(fn=gb_mse_cv, # function to optimize space=space, algo=anneal.suggest, # optimization algorithm, hyperotp will select its parameters automatically max_evals=50, # maximum number of iterations trials=trials, # logging rstate=np.random.RandomState(random_state) # fixing random state for the reproducibility ) # computing the score on the test set model = LGBMRegressor(random_state=random_state, n_estimators=int(best['n_estimators']), max_depth=int(best['max_depth']),learning_rate=best['learning_rate']) model.fit(train_data,train_targets) sa_test_score=mean_squared_error(test_targets, model.predict(test_data)) end=timeit.default_timer() info_end = psutil.Process(os.getpid()).memory_info().rss/1024/1024 print('此程序運行占內存'+str(info_end-info_start)+'mB') print('Running time:%.5fs'%(end-start)) print("Best MSE {:.3f} params {}".format( gb_mse_cv(best), best))
實驗結果:
此程序運行占內存3.54296875mB
Running time:50.46497s
Best MSE 3204.336 params {'learning_rate': 0.006780152596902742, 'max_depth': 5.0, 'n_estimators': 619.0}
4、結論
我們可以看到,即使在以后的步驟中,TPE和退火算法實際上仍會隨着時間的推移不斷改善搜索結果,而隨機搜索在開始時就隨機地找到了一個很好的解決方案,然后僅稍微改善了結果。 TPE和RandomizedSearch結果之間的當前差異很小,但是在某些具有超參數范圍更加多樣化的現實應用中,hyperopt可以顯着改善時間/得分