1. 程式人生 > 實用技巧 >三種超引數優化方法詳解,以及程式碼實現

三種超引數優化方法詳解,以及程式碼實現

超引數調優方法:網格搜尋,隨機搜尋,貝葉斯優化等演算法。

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.RandomizedSearchCVestimatorparam_distributions*n_iter = 10得分= Nonen_jobs = Noneiid ='deprecated'refit = Truecv = Noneverbose = 0pre_dispatch ='2 * n_jobs'random_state = Noneerror_score = nanreturn_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可以顯著改善時間/得分