學習器模型中一般有兩類參數,一類是可以從數據中學習估計得到,還有一類參數時無法從數據中估計,只能靠人的經驗進行設計指定,后者成為超參數。比如,支持向量機里面的C, Kernal, game;樸素貝葉斯里面的alpha等。
使用以下的方法獲得學習器模型的參數列表和當前取值, estimator.get_params()
參數空間的搜索有以下幾個部分構成:
- 一個estimator(回歸器 or 分類器)
- 一個參數空間
- 一個搜索或采樣方法來獲得候選參數集合
- 一個交叉驗證機制
- 一個評分函數
有些學習器模型有自己獨特的參數選擇優化方法。
Sklearn提供了兩種通用的超參數優化方法: 網格搜索 與 隨機采樣。
網格搜索交叉驗證(GridSearchCV): 以窮舉方法遍歷所有的可能的參數組合。
隨機采樣交叉驗證(RandomizedSearchCV): 依據某種分布對參數空間采樣,隨即得到參數組合方案。
模型 |
---|---
sklearn.model_selection.GridSearchCV |
sklearn.model_selection.RandomizedSearchCV |
sklearn.model_selection.ParameterGrid |
sklearn.model_selection.ParameterSampler |
sklearn.model_selection.fit_grid_point |
GridSearchCV
GridSearchCV提供了在參數網格上窮舉候選參數組合的方法。參數網格由參數param_grid來指定。比如,下面展示了舍子參數網格param_grid的一個例子。
param_grid = [
{'C': [1,10,100,1000], 'kernel': ['linear']},
{'C': [1,10,100,1000], 'gamma': [0.001,0.0001], 'kernel':['rbf']},
]
上面的參數指定了要搜索的兩個網格(每個網格是一個字典):第一個網格是線性核然后c
在[1, 10, 100, 1000]中取值;第二個參數時RFB核然后再是gamma的取值列表[0.001, 0.0001]以及C的取回列表[1, 10, 100, 1000]。第一個里面有4
個參數組合節點,第二個里面有4*2=8個參數組合節點。
from sklearn import svm,datasets
from sklearn.model_selection import GridSearchCV
iris = datasets.load_iris()
# 定義參數網格,2*3=6個參數組合
parameters = {'kernel': ('rbf', 'linear'), 'C': [1, 5, 10]}
svr = svm.SVC()
# 對所有的參數組合進行測試,選擇最好的一個組合
clf = GridSearchCV(svr, parameters)
clf.fit(iris.data, iris.target)
print(clf.best_estimator_)
RandomizedSearchCV
RandomizedSearchCV實現了一個在參數空間上進行隨機搜索到機制,起哄參數的選值是從某種概率分布中抽取的。這個概率分布描述了對應的參數的所有取值情況的可能性。這種隨機采樣機制與網格搜索窮舉搜索相比,有兩大優點:
- 相比于整體參數空間,可以選擇相對較少的參數組合數量。
- 添加參數節點不會影響性能,不會降低效率。
指定參數的采樣范圍和分布可以用一個字典來完成,跟網格搜索很像。另外,計算預算(總共要隨機采樣多少個參數組合或者迭代多少次)可以使用參數n_iter來指定,針對每一個參數范圍內的概率,既可以使用可能取值范圍內的概率分布,也可以指定一個離散的取值列表(會被均勻采樣)。
{'C':scipy.stats.expon(scale=100), 'gamma':scipy.stats.expon(scale=0.1),
'kernel':['rbf'], 'class_weight':['balanced', None]
}
這個例子使用了scipy.stats模塊,其中包含了很多有用的分布來產生參數采樣點,像expon, gamma, uniform or randint。原則上,任何一個函數都要可以傳遞進去,只要他提供了一個rvs(random variate sample)方法來返回采樣值,rvs函數的連續調用應該能保證產生獨立同分布的樣本值。
對于連續取值的參數,比如上面的C, 給他指定一個連續的分布非常重要,這樣可以保證充分利用隨機化帶來的好處,增加迭代次數,n_iter將會帶來非常精準的搜索。
import numpy as np
import time
from scipy.stats import randint
from sklearn.datasets import load_digits
from sklearn.model_selection import RandomizedSearchCV, GridSearchCV
from sklearn.ensemble import RandomForestClassifier
# 用于報告超參數搜索的最好結果的函數
def report(results, n_top=3):
for i in range(1, n_top + 1):
candidates = np.flatnonzero(results['rank_test_score'] == i)
for candidate in candidates:
print('Model with rank: {0}'.format(i))
print('Mean validation score: {0:.3f}(std: {1:.3f})'.format(
results['mean_test_score'][candidate],
results['std_test_score'][candidate]))
print('Parameters: {0}'.format(results['params'][candidate]))
print('')
# 取得數據,手寫字符分類
digits = load_digits()
X, y = digits.data, digits.target
# 構建一個隨機方森林分類器,有20棵數
clf = RandomForestClassifier(n_estimators=20)
# 設置想要優化的超參數以及他們的取值分布
param_dist = {
'max_depth': [3, None],
'max_features': randint(1,11),
'min_samples_split': randint(2,11),
'bootstrap': [True, False],
'criterion': ['gini', 'entropy']
}
# 開啟超參數空間的隨機搜索
n_iter_search = 20
random_search = RandomizedSearchCV(clf, param_distributions=param_dist, n_iter=n_iter_search)
start = time.time()
random_search.fit(X, y)
print('time use: {}'.format(time.time() - start))
report(random_search.cv_results_)
下面將這兩種方法進行比較
import numpy as np
import time
from scipy.stats import randint
from sklearn.datasets import load_digits
from sklearn.model_selection import RandomizedSearchCV, GridSearchCV
from sklearn.ensemble import RandomForestClassifier
# 用于報告超參數搜索的最好結果的函數
def report(results, n_top=3):
for i in range(1, n_top + 1):
candidates = np.flatnonzero(results['rank_test_score'] == i)
for candidate in candidates:
print('Model with rank: {0}'.format(i))
print('Mean validation score: {0:.3f}(std: {1:.3f})'.format(
results['mean_test_score'][candidate],
results['std_test_score'][candidate]))
print('Parameters: {0}'.format(results['params'][candidate]))
print('')
# 取得數據,手寫字符分類
digits = load_digits()
X, y = digits.data, digits.target
# 構建一個隨機方森林分類器,有20棵數
clf = RandomForestClassifier(n_estimators=20)
print('==========下面是RandomizedSearchCV結果==============')
# 設置想要優化的超參數以及他們的取值分布
param_dist = {
'max_depth': [3, None],
'max_features': randint(1,11),
'min_samples_split': randint(2,11),
'bootstrap': [True, False],
'criterion': ['gini', 'entropy']
}
# 開啟超參數空間的隨機搜索
n_iter_search = 20
random_search = RandomizedSearchCV(clf, param_distributions=param_dist, n_iter=n_iter_search)
start = time.time()
random_search.fit(X, y)
print('time use: {}'.format(time.time() - start))
report(random_search.cv_results_)
print('==========下面是GridSearchCV結果==============')
# 設置想要優化的超參數以及他們的取值分布
param_grid = {
'max_depth': [3, None],
'max_features': [1, 3, 10],
'min_samples_split': [2, 3, 10],
'bootstrap': [True, False],
'criterion': ['gini', 'entropy']
}
# 開啟超參數空間的隨機搜索
grid_search = GridSearchCV(clf, param_grid=param_grid)
start = time.time()
grid_search.fit(X, y)
print('time use: {}'.format(time.time() - start))
report(grid_search.cv_results_)
實驗結果:
以隨機森林分類器為優化對象,所有影響分類器學習的參數都被搜索了,除了樹的數量之外,隨機搜索和網格優化都在同一個超參數空間上對隨機森林分類器進行優化。雖然得到的超參數設置組合比較相似,但是隨機搜索運行的時間卻網格搜索的時間顯著減少,隨季節搜索得到的超參數組合的性能稍微差一點,但是這很大程度上是有噪聲引起的,在實踐中, 我們只是挑選幾個比較重要的參數進行隨機組合來進行優化。
超參空間搜索技巧
- 指定一個合適的目標測度對木星進行評估,默認情況下,參數搜索使用的是estimator的score函數來評價模型在某種參數配置下的性能。分類器使用的是sklearn.metrics.accuracy_score, 回歸器使用的是sklearn.metrics.r2_score。但是在某些情況下,其他的評分函數更加實用。
- 實用sklearn的Pineline將estimator和它們的參數空間組合起來
- 合理劃分數據集:開發集(用于GridSearchCV)+ 測試集(Test)使用model_selection.train_test_split()函數。
- 并行化:以上兩者在參數節點的計算上可以做到并行運算,這個通過'n_jobs'來指定。
- 提高在某些參數節點上發生錯誤時的魯莽性:在出錯節點上只是提示警告。設置參數error_score = 0(or np.NaN)來解決。