超參數:算法運行前需要決定的參數
模型參數:算法運行過程中學習的參數
我們常說的“調參工程師”調試的基本都是超參數,超參數選擇的好與壞在一定程度上決定了整個算法的好壞。
就拿KNN算法中的超參數K來說,雖然sklearn中對於KNN算法有默認的K=5,但這僅僅是在經驗中得到的較為理想的值,在實際應用中卻並不一定是這樣。
我們就拿KNN算法來對手寫數字數據集進行分類這件事來說,試圖從[1-20]之間找到最合適的那個K
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
digits_df = datasets.load_digits()
X = digits_df.data
y = digits_df.target
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.3, random_state=666)
best_K = 0
best_scope = -1
for k in range(1, 21):
KNN_cls = KNeighborsClassifier(n_neighbors=k)
KNN_cls.fit(X_train, y_train)
temp_scope = KNN_cls.score(X_test, y_test)
if temp_scope > best_scope:
best_scope = temp_scope
best_K = k
print(best_K)
print(best_scope)
最終的運行結果是 best_K = 3 best_scope = 0.9761526232114467 由此可見,算法中封裝的默認超參數並不一定是最好的。
有一個問題我們需要注意一下: 比如說我們從[1-20]之間來尋找K,如果最終結果顯示最優K為20,那么我們應該繼續向上拓展,繼續尋找,比如[20-30]
原因是:通常來講,不同的超參數決定不同的分類准確率,且這個變化是連續的,如果我們最終找到的超參數位於邊界,那么理論上還存有更優解我們仍未找到。
隱藏的超參數
就拿KNN算法來說,如果我們的K=3,且恰巧這個待分類樣本周圍最近的3個樣本卻分屬不同的三個分類(如下圖所示),那么就會造成平局。

所以說,只是考慮K這一個超參數是不穩妥的,如上圖所示,雖然待分類的小球最近的3個小球分屬不同的分類,但是它離沒一個小球的距離卻不一樣,我們可以認為,離得近的權重大,離得遠的權重小,這樣就很好的解決了平票的問題。

下邊我們考慮權重再來查找一次最優的K
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
digits_df = datasets.load_digits()
X = digits_df.data
y = digits_df.target
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.3, random_state=666)
best_K = 0
best_scope = -1
basic_weights = ''
for i in ['uniform', 'distance']:
for k in range(1, 21):
KNN_cls = KNeighborsClassifier(n_neighbors=k, weights=i)
KNN_cls.fit(X_train, y_train)
temp_scope = KNN_cls.score(X_test, y_test)
if temp_scope > best_scope:
best_scope = temp_scope
best_K = k
basic_weights = i
print(best_K)
print(best_scope)
print(basic_weights)
最終結果:best_K = 4 best_scope = 0.9785373608903021 basic_weights = 'distance'
從上邊我們根據是否考慮距離的權重計算出了更高的分類准確率,在這里我們不禁想到,不同的特征樣本之間的距離是怎樣算出來的呢?計算距離有多種方法,如歐拉距離、曼哈頓距離,不同的方法計算出的距離也不一樣,這樣就又引出了KNN算法中又一個超參數: KNeighborsClassifier類中的參數p

KNN算法中計算距離使用的是明可夫斯基距離,從上邊的推導式可以看出,不同的o值對應不同的公式,當p=1時,計算距離用的就是曼哈頓距離,當p=2時,用的就是歐拉距離,在KNN算法中默認p=2,使用的是歐拉距離。
接下來我們再根據不同的計算距離公式來找出最優的K
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
digits_df = datasets.load_digits()
X = digits_df.data
y = digits_df.target
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.2, random_state=666)
best_K = -1
best_scope = -1
best_p = -1
for i in range(1, 6):
for k in range(1, 21):
KNN_cls = KNeighborsClassifier(n_neighbors=k, weights='distance', p=i)
KNN_cls.fit(X_train, y_train)
temp_scope = KNN_cls.score(X_test, y_test)
if temp_scope > best_scope:
best_scope = temp_scope
best_K = k
best_p = i
print(best_K)
print(best_scope)
print(best_p)
我們看到 best_K=4 best_scope=0.9770514603616134 best_p=4
根據上邊的代碼 我們能看到,在參數p和參數k兩層循環下,我們相當於在x軸為k,y軸為p的平面中遍歷每一個點,來尋找其中的最優解,這中搜索策略其實有一個名字,叫做網格搜索。
上邊幾個例子都是我們來控制代碼尋找不同的超參數,實際上,sklearn為這種網格搜索方式封裝了一個專門的函數:Grid Search,下邊我們使用Grid Search來執行一邊函數就找到最優的幾個超參數解。
先來看這個參數,它是一個list格式的參數,里邊的每一個元素都是json類型,仔細看,這每一個json元素就是我們定義的一個針對超參數的搜索策略
param_grid = [
{
"weights":["uniform", "distinct"],
"n_neighbors":[ i for i in range(1, 20)]
},
{
"weights":["distinct"],
"n_neighbors":[ i for i in range(1, 20)],
"p":[ i for i in range(1, 20)]
}
]
下邊調用Grid Search函數來找出最優解
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
digits_df = datasets.load_digits()
X = digits_df.data
y = digits_df.target
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.4, random_state=666)
from sklearn.model_selection import GridSearchCV # 這個類用來進行網格搜索
knn_clf = KNeighborsClassifier() # 實例化KNN類
grid_search = GridSearchCV(knn_clf, param_grid) # 傳入兩個參數:模型的類,前邊制定的網格搜索策略
grid_search.fit(X_train, y_train) # 先讓這個類基於我們設定好的搜索策略從訓練數據種找出最優的參數
grid_search.best_estimator_ # 它用來返回找到的最優超參數
這是運行結果

我們可以看到,程序已經幫我們找到了 最優的n_neighbors=4, p=7, weights='distance',我們還可以通過調用grid_search.best_score_方法來看到使用這些超參數后的分類准確度
我們如何才能獲取到找到的這幾個超參數呢? 使用 grid_search.best_params_ 就可以獲取到通過網格搜索找到的這幾個超參數

像這幾個屬性:grid_search.best_estimator_
grid_search.best_score_
grid_search.best_params_
上邊這幾個幾個屬性都有一個下划線(這是一種代碼規則:如果某個參數是根據用戶傳入的參數計算出來的,那么就會帶個下划線)
上邊我們就已經計算出了幾個最優超參數,現在我們就用最優超參數來計算一下分類准確度吧
使用sklearn為我們封裝的GridSearchCV先進行網格搜索尋找最佳超參數,然后使用找到的超參數來計算進行分類並得到分類准確度:
from sklearn import datasets
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
digits_df = datasets.load_digits()
X = digits_df.data
y = digits_df.target
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.3, random_state=666)
knn_clf = KNeighborsClassifier()
params_grid = [
{
"weights":["uniform", "distance"],
"n_neighbors":[i for i in range(1, 20)]
},
{
"weights":["distance"],
"n_neighbors":[i for i in range(1, 20)],
"p":[j for j in range(1, 6)]
}
]
grid_search = GridSearchCV(estimator=knn_clf, param_grid=params_grid)
grid_search.fit(X_train, y_train)
knn_clf = grid_search.best_estimator_ # 它返回的就是一個使用了最佳超參數的KNN分類器,可以直接對測試數據集進行預測
print(knn_clf)
knn_clf.score(X_test, y_test) # 直接進行預測
這是運算結果

上邊我們使用了 GridSearchCV 這個類來使用網格搜索來尋找超參數,我們向這個類中傳遞了兩個參數estimator和param_grid,estimator標識你要使用的算法類,param_grid標識要使用的網格搜索策略,其實GridSearchCV類還有一些其他的參數也是比較有用的:

因為我們使用的網格搜索有多條策略,在尋找最優超參數時就需要一條策略一條策略的去計算,這無疑是比較慢的,n_jobs參數正式為了改善這一情況的,n_jobs參數默認為None,表示只用一個cpu核心去計算,你輸入數字幾,它就用幾核心,當n_jobs=-1時表示使用cpu的全部核心,這樣就加快了計算速度。
而且在進行網格搜索時,默認是沒有任何log打出的,這不利於我們觀察和監督,verbose=0參數用來控制log的打印長度,數字越大,log打印的越詳細
上邊我們使用sklearn的GridSearchCV類找到了KNN算法常用的超參數,但這不是全部,其實KNN算法還有好多超參數,如果我們不是用明可夫斯基距離計算樣本之間的距離,還有其他的一些選擇:

