三種超參數優化方法詳解,以及代碼實現


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

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(estimatorparam_grid*scoring=Nonen_jobs=Noneiid='deprecated'refit=Truecv=Noneverbose=0pre_dispatch='2*n_jobs'error_score=nanreturn_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可以顯着改善時間/得分

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM