KNN算法是機器學習中最好理解的算法之一,屬于惰性學習算法的典例。惰性指模型僅通過對訓練數據集的記憶功能進行預測,而不產生判別函數。
引申:參數化模型和非參數化模型
機器學習算法可以劃分為參數化模型和非參數化模型兩大類。使用參數化模型時,需要通過訓練估計參數,得到一個模式。常見的參數化模型諸如感知器、邏輯斯蒂回歸、SVM等。
非參數化模型無法通過一組固定的參數對樣本進行表征,參數的數量會隨著訓練集樣本數量的增加而增加。常見的非參數化模型有決策樹、Kernal SVM等。
KNN屬于非參數化模型的一個子類,可以被描述為基于實例的學習。這類模型的特點是對訓練數據進行記憶,KNN所屬的惰性學習是基于實例學習的一個特例。
sklearn中的KNN實現
KNN算法本身很簡單,歸納為如下幾步:
①選擇近鄰數量k和距離度量的方法
②找到待分類樣本的k個最近鄰
③根據最近鄰類標進行多數投票
sklearn中已經很好的實現了KNN算法分類器,位于sklearn.neighbors包下的KNeighborsClassifier類中。給出最基本的實現。
from sklearn.model_selection import train_test_split
import sklearn.datasets as dataset
from sklearn.neighbors import KNeighborsClassifier
'''加載數據'''
data = dataset.load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=666)
knn_clf = KNeighborsClassifier(n_neighbors=5)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
print('when k chose 5, fit score is : %s' % score)
選用sklearn.datasets的digits數據集,分類器的k近鄰數量選用5,這也是默認值,算是經驗上分類效果最佳的一個k值。獲得如下輸出:
when k chose 5, fit score is : 0.9866666666666667
在k選5的情況下,分類器的準確度為98%。
KNN算法的超參數
上面的實現屬于最簡單的實現,KNN算法因為不生成一個固定參數的模型,因此,在訓練過程中的調參的要求非常高。
(一)k值的選擇
k值的選擇是第一項,一般的根據經驗k=5是最能得到最佳效果的點,但在實際開發過程中需要進行驗證。
'''最好的k值'''
best_k = -1
best_score = 0.0
for k in range(1, 11):
knn_clf = KNeighborsClassifier(n_neighbors=k)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_score = score
best_k = k
print("best k is : %s, best_score is : %s\n" % (best_k, best_score))
在之前代碼中增加如下代碼,使用best_k、best_score記錄最佳的k和分類效果,k從1到10中進行選擇,使用for循環依次測試。獲得結果:
best k is : 5, best_score is : 0.9866666666666667
似乎和剛才是一樣的。強調一點,如果在1到10中求得最佳k值為10,那么有必要對10以上的值選擇區間再進行測試,因為可能含有效果更好的k值。
(二)考慮距離權重
之前的分類中,僅僅通過找到待分類樣本最近的k個樣本進行多數投票,但可能存在如下情況
如果按照投票的方式,應該分為藍色類別,但從距離上看,樣本距離紅色類別更近,劃為紅色似乎更加合理,這里就需要引入距離權重的概念。
在KNeighborsClassifier中有一個參數weight,不指定該參數的情況下默認為uniform也就是多數投票,也可以指定weight為distance,即可采用距離權重的方式進行分類。
'''考慮距離權重'''
best_method = ''
best_k = -1
best_score = 0.0
for method in ['uniform', 'distance']:
for k in range(1, 11):
knn_clf = KNeighborsClassifier(n_neighbors=k, weights=method)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_score = score
best_k = k
best_method = method
print('best method is : %s, best k is : %s, best score is : %s\n' % (best_method, best_k, best_score))
增加使用best_method對最佳分類采用的權重方法進行記錄。得到輸出:
best method is : uniform, best k is : 5, best score is : 0.9866666666666667
似乎又沒變。
(三)距離系數p
分類時候使用的距離是什么距離?距離的種類有很多,最常見的有歐氏距離ρ(A,B) =√ [ ∑( a[i] - b[i] )^2 ] (i = 1,2,…,n),此外還有曼哈頓距離ρ(A,B) =[ ∑( a[i] - b[i] ) ] (i = 1,2,…,n)
如圖給出的綠色的直線就是歐式距離,其他的線雖然走法不同但距離是一樣的,都是曼哈頓距離。仔細觀察下兩種距離的公式,可以進一步合并為ρ(A,B) =[ ∑( a[i] - b[i] )^p ]^(1/p) (i = 1,2,…,n)
當p為1時,代入發現是曼哈頓距離,p=2時則是歐氏距離。在sklearn的KNN分類器中,也有p參數,默認值為2即采用歐式距離,這個公式被稱為閔可夫斯基距離。訓練過程中,還可以實驗不同p值的分類效果
'''距離p值'''
best_score = 0.0
best_k = -1
best_p = -1
for k in range(1, 11):
for p in range(1, 6):
knn_clf = KNeighborsClassifier(n_neighbors=k, weight='distance', p=p)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_score = score
best_k = k
best_p = p
print('best k is : %s, best p is : %s, best score is : %s\n' % (best_k, best_p, best_score))
得到結果
best k is : 5, best p is : 2, best score is : 0.9866666666666667
好像歐式距離的效果最好。
網格搜索與KNN超參數
上面的部分中,我們不斷通過for循環和定義變量的方式記錄最佳分類效果的相應的超參數,sklearn中提供了更簡單的實現,位于sklearn.model_selection包下的GridSearchCV類就是對網格搜索的封裝,還是剛剛的尋找最佳分類的參數,使用GridSearchCV的方式就變為:
from sklearn.model_selection import train_test_split
import sklearn.datasets as dataset
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
'''加載數據'''
data = dataset.load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=666)
'''網格搜索獲取最佳參數'''
grid_params = [
{
'weights' : ['uniform'],
'n_neighbors' : [i for i in range(1, 11)]
},{
'weights' : ['distance'],
'n_neighbors' : [i for i in range(1, 11)],
'p' : [p for p in range(1, 6)]
}
]
通過對參數進行封裝,裝入到grid_params中,作為參數傳入到網格當中
'''創建一個KNN分類器,作為網格搜索使用的分類器參數'''
knn_clf = KNeighborsClassifier()
grid_search = GridSearchCV(knn_clf, param_grid=grid_params, n_jobs=-1, verbose=2)
grid_search.fit(X_train, y_train)
創建一個KNN分類器,作為網格搜索使用的分類器參數,再指定之前創建的grid_params,GridSearchCV就可以自動的尋找最佳分類方式。n_jobs是進行搜索時的任務數,可以指定為電腦的核數,-1就是使用全部的核進行搜索,verbose是在搜索過程中打印相關信息,值越高信息越詳細,一般2比較常用。
Fitting 3 folds for each of 60 candidates, totalling 180 fits
[CV] n_neighbors=1, weights=uniform ..................................
[CV] n_neighbors=1, weights=uniform ..................................
[CV] n_neighbors=1, weights=uniform ..................................
[CV] n_neighbors=2, weights=uniform ..................................
[CV] ................... n_neighbors=1, weights=uniform, total= 0.0s
[CV] ................... n_neighbors=1, weights=uniform, total= 0.1s
[CV] n_neighbors=2, weights=uniform ..................................
[CV] ................... n_neighbors=1, weights=uniform, total= 0.1s
......
verbose的作用就是在搜索過程中打印如下信息,注意第一行60 candidates,回頭看grid_params中weight選用uniform時,需要對k從1到10進行測試,而weights選用distance時,對k從1到10和p從1到5進行測試,一共需要進行110 + 510 = 60項測試,對應了candidates的值。
打印得到的最佳分類模式
print('best estimator is :')
print(grid_search.best_estimator_)
print('best score is %s:' % grid_search.best_score_)
# knn_clf = grid_search.best_estimator_
# score = knn_clf.score(X_test, y_test)
print("test by KNN Classifier's score() function, the score is : %s" % score)
輸出
best estimator is :
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=1, n_neighbors=5, p=3,
weights='distance')
best score is 0.9866369710467706:
test by KNN Classifier's score() function, the score is : 0.9822222222222222
可以看到best_estimator_其實是一個KNeighborsClassifier對象,這里得到的是使用距離權重,p值為3和n_neighbors為5的分類器,通過網格搜索測試的best_score_為0.9866369710467706,不如之前自己實現的0.9866666666666667,原因是使用GridSearchCV的打分機制和分類器的機制不一樣,更為復雜,因此分數也就不同。之后使用X_test和y_test測試待預測樣本的準確度為0.9822222222222222。
獲得的最佳KNeighborsClassifier中還有個屬性是metric,目前對應的值是minkowski就是之前講過的閔可夫斯基距離。此外還有些距離,作為擴展給出,可以參考sklearn手冊sklearn.neighbors包下的DistanceMetric。
KNN解回歸問題
在sklearn.neighbors包下還有KNeighborsRegressor用KNN算法解決回歸問題,其思路基本上和解分類問題一樣,但回歸問題是需要求出一個具體的值,求值可能采用最近k個點的值的均值、或者考慮上距離的權重等方式。API和KNeighborsClassifier非常相似,這里不做講解。
KNN算法的缺點
最大的缺點就是效率低,假如一個數據集有m個樣本n中特征,則預測一個樣本的時間復雜度為O(mn),即需要和m個樣本求距離并挑選前k個,每個特征維度都需要計算距離,故需要O(mn)。可以使用樹結構優化,如KD-Tree、Ball-Tree等。
缺點二:高度數據相關
缺點三:預測不具有可解釋性
缺點四:維數災難
隨著維數增加,看似很近的兩個點距離越來越大。
維數 坐標1 坐標2 距離
1 0 1 1
2 (0,0) (1, 1) 1.414
3 (0,0,0) (1,1,1) 1.73
...
維數災難可以用降維的方式進行優化。