KNN(K最近鄰)分類算法_糖潮麗子的博客

我們本篇博客來學習KNN算法的原理,超參數調整,以及KNN算法應用。
kNN算法:K最近鄰(kNN,k-NearestNeighbor)分類算法。
在這裏插入圖片描述

1、算法概述

鄰近算法,或者說K最近鄰(kNN,k-NearestNeighbor)分類算法是數據挖掘分類技術中最簡單的方法之一。所謂K最近鄰,就是k個最近的鄰居的意思,說的是每個樣本都可以用它最接近的k個鄰居來代表。KNN是一種分類(classification)算法,它輸入基於實例的學習(instance-based
learning),屬於懶惰學習(lazy
learning)即KNN沒有顯式的學習過程,也就是說沒有訓練階段,數據集事先已有了分類和特徵值,待收到新樣本後直接進行處理。與急切學習(eager
learning)相對應。

KNN是通過測量不同特徵值之間的距離進行分類。
思路:如果一個樣本在特徵空間中的k個最鄰近的樣本中的大多數屬於某一個類別,則該樣本也劃分爲這個類別。KNN算法中,所選擇的鄰居都是已經正確分類的對象。該方法在定類決策上只依據最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別。

目標:

  • 熟知KNN算法的基本原理
  • 能夠使用KNN算法實現分類與迴歸任務
  • 能夠調整算法的超參數
  • 熟知KD樹的構建與鄰居選擇

2、舉例

在講解KNN之前,我們來看如下的數據集:

語文 數學 學生
95 93
90 92
91 96
85 82
83 87
80 84
61 69
66 63
72 65
83 77

我們將以上數據映射到空間中,進行繪製。因爲數據具有兩個特徵,因此,每條數據對應二維空間中的一個點。

import numpy as np
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "SimHei"
plt.rcParams["axes.unicode_minus"] = False
plt.rcParams["font.size"] = 12

good = np.array([[95, 93], [90, 92], [91, 96]]) #good 好學生
medium = np.array([[85, 82], [83, 87], [80, 84]])  #中等學生
bad = np.array([[61, 69], [66, 63], [72, 65]]) #差學生
unknown = np.array([[83, 77]])  #未知

plt.scatter(good[:, 0], good[:, 1], color="r", label="優等生")
plt.scatter(medium[:, 0], medium[:, 1], color="g", label="中等生")
plt.scatter(bad[:, 0], bad[:, 1], color="b", label="差等生")
plt.scatter(unknown[:, 0], unknown[:, 1], color="orange", label="未知")
plt.legend()

在這裏插入圖片描述
上圖中橙色的點(樣本),最可能的類別是( B )。
A 優等生
B 中等生
C 差等生
D 三種可能概率均等

3、加深理解

我們要確定綠點屬於哪個顏色(紅色或者藍色),要做的就是選出距離目標點距離最近的k個點,看這k個點的大多數顏色是什麼顏色。當k取3的時候,我們可以看出距離最近的三個,分別是紅色、紅色、藍色,因此得到目標點爲紅色。
在這裏插入圖片描述

算法的描述:   
1)計算測試數據與各個訓練數據之間的距離;   
2)按照距離的遞增關係進行排序;   
3)選取距離最小的K個點;
4)確定前K個點所在類別的出現頻率;   
5)返回前K個點中出現頻率最高的類別作爲測試數據的預測分類

4、算法原理

從上例的運行結果中,我們發現,相似度較高的樣本,映射到 n 維空間後,其距離會比相似度較低的樣本在距離上更加接近,這正是KNN算法的核心思維。
KNN(K-Nearest Neighbor),即近鄰算法。
K近鄰就是K個最近的鄰居,當需要預測一個未知樣本的時候,就由與該樣本最接近的K個鄰居來決定。
KNN既可以用於分類問題,也可以用於迴歸問題。
當進行分類預測時,使用K個鄰居中,類別數量最多(或加權最多)者,作爲預測結果。
當進行迴歸預測時,使用K個鄰居的均值(或加權均值),作爲預測結果。
KNN算法的原理在於,樣本映射到多維空間時,相似度較高的樣本,其距離也會比較接近,反之,相似度較低的樣本,其距離也會比較疏遠。

5、算法超參數

超參數,是指我們在訓練模型之前,需要人爲指定的參數。 該參數不同於模型內部的參數,模型內部的參數是通過訓練數據,在訓練過程中計算得出的。超參數的不同,可能會對模型的效果產生很大影響。

5.1 K值

K值的選擇,會直接影響到預測結果。
當K值較小時,模型會較依賴於附近的鄰居樣本,具有較好敏感性,但是穩定性會較弱,容易導致過擬合。
當K值較大時,穩定性增加,但是敏感性會減弱,容易導致欠擬合。
通常情況下,我們可以通過交叉驗證的方式,選擇最合適的K值。

5.2 關於K的取值

在這裏插入圖片描述
當K=3時,在實心圈內綠色的圓圈預測爲紅色的三角。
當K=5時,在虛線圈內綠色的圓圈預測爲藍色的方塊。
K值取值的不同,會影響到預測結果的不同。

K:臨近數,即在預測目標點時取幾個臨近的點來預測。
K值得選取非常重要,因爲:

  • 如果當K的取值過小時,一旦有噪聲得成分存在們將會對預測產生比較大影響,例如取K值爲1時,一旦最近的一個點是噪聲,那麼就會出現偏差,K值的減小就意味着整體模型變得複雜,容易發生過擬合;
  • 如果K的值取的過大時,就相當於用較大鄰域中的訓練實例進行預測,學習的近似誤差會增大。這時與輸入目標點較遠實例也會對預測起作用,使預測發生錯誤。K值的增大就意味着整體的模型變得簡單;
  • 如果K==N的時候,那麼就是取全部的實例,即爲取實例中某分類下最多的點,就對預測沒有什麼實際的意義了;
  • K的取值儘量要取奇數,以保證在計算結果最後會產生一個較多的類別,如果取偶數可能會產生相等的情況,不利於預測。

K的取法:
常用的方法是從k=1開始,使用檢驗集估計分類器的誤差率。重複該過程,每次K增值1,允許增加一個近鄰。選取產生最小誤差率的K。
一般k的取值不超過20,上限是n的開方,隨着數據集的增大,K的值也要增大。

5.3 距離度量方式

在scikit-learn中,距離默認使用閔可夫斯基距離(minkowski),p 的值爲2。假設n維空間中的兩個點爲X與Y:
在這裏插入圖片描述
可知:
當p爲1時,距離就是曼哈頓距離,當p爲2時,距離就是歐幾里得距離(歐氏距離)。

5.4 權重計算方式

權重可以分爲兩種:

  • 統一權重:所有樣本的權重相同。
  • 距離加權權重:樣本的權重與待預測樣本的距離成反比。

(1)分類預測時:
在這裏插入圖片描述
在這裏插入圖片描述
(2)迴歸預測時:
在這裏插入圖片描述
注意:

  • 統一權重和距離加權權重算法不一樣。
  • 距離加權權重預測分類或者回歸預測時,都需提前計算權重。
  • 權重是距離的反比。
  • 權重之和等於1。
  • 規劃權重,每個樣本的權重等於該樣本權重除以所有樣本權重之和。

做個練習吧:

使用KNN預測未知樣本A,假設的值爲3,三個最近的鄰居是B,C 與D。A距離B,C ,D【】 【】【【】】【】的【【】】【】【】【[【】【】】【【】[【】【】【】【】[【】【】【】【】】【【】【】】【【】】【】【】距【【】】【】離分別是2,3,5。如果按照距離加權的方式計算權重,則B,C ,D 的權重分別是( D)。
在這裏插入圖片描述
解題步驟:

6、算法步驟

KNN算法的執行過程如下:

  1. 確定算法超參數。
  • 確定近鄰的數量。
  • 確定距離度量方式。
  • 確定權重計算方式。
  • 其他超參數。
  1. 從訓練集中選擇離待預測樣本A最近的K個樣本。
  2. 根據這K個樣本預測A。
  • 對於分類,使用個K樣本的類別(或加權類別)預測A。
  • 對於迴歸,使用個K樣本目標值( y )的均值(或加權均值)預測A。

練習:

給定待預測樣本 A,如果在訓練集中,存在兩個(或更多)不同的鄰居 N1與N2 (N1與 N2的標籤不 同),二者距離 A的距離相等,假設K 的值爲1,則此時選擇哪個鄰居較爲合理? (D
A 選擇 N1
B 選擇 N2
C 隨機選擇
D 根據 N1與 N2在訓練集中出現的先後順序選擇。
E C或D

解析:
算法一定要保證確定性,所以選擇D。

7、使用KNN實現分類

7.1 建模預測

我們以鳶尾花數據集爲例,通過KNN算法實現分類任務。同樣,爲了方便可視化,我們只取其中的兩個特徵。

from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

iris = load_iris()
X = iris.data[:, :2]
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
# n_neighbors:鄰居的數量。
# weights:權重計算方式。可選值爲uniform與distance。
#    uniform:所有樣本統一權重。
#    distance:樣本權重與距離成反比。
knn = KNeighborsClassifier(n_neighbors=3, weights="uniform")
knn.fit(X_train, y_train)
y_hat = knn.predict(X_test)
print(classification_report(y_test, y_hat))

代碼解析: KNeighborsClassifier 實現了K最近鄰居投票算法的分類器。 KNeighborsClassifier中的參數:
n_neighbors:鄰居的數量。
weights:權重計算方式。可選值爲uniform與distance
uniform:所有樣本統一權重。
distance:樣本權重與距離成反比。
KNeighborsRegressor 迴歸預測。

結果:
在這裏插入圖片描述

  • precision:精確度,預測爲正實際也爲正佔預測數的比率。
  • recall:召回率,實際爲正預測也爲證佔實際數的比率。
  • f1-score:一般形式爲fβ=(1+β2)⋅precision⋅recallβ2⋅precison+recall,所以f1=precision⋅recallprecison+recall
  • support:支持度,即實際類別個數。 accuracy:準確度。
  • macro avg與weighted avg:具體請查看這個鏈接

對分類模型評估不理解的可以參考這篇文章。這裏!!!

7.2 超參數對模型的影響

當然,不同的超參數值,會直接影響模型的分類效果。
我們可以從決策邊界中發現這一點。

from matplotlib.colors import ListedColormap

def plot_decision_boundary(model, X, y):
    color = ["r", "g", "b"]
    marker = ["o", "v", "x"]
    class_label = np.unique(y)
    cmap = ListedColormap(color[: len(class_label)])
    x1_min, x2_min = np.min(X, axis=0)
    x1_max, x2_max = np.max(X, axis=0)
    x1 = np.arange(x1_min - 1, x1_max + 1, 0.02)
    x2 = np.arange(x2_min - 1, x2_max + 1, 0.02)
    X1, X2 = np.meshgrid(x1, x2)
    Z = model.predict(np.c_[X1.ravel(), X2.ravel()])
    Z = Z.reshape(X1.shape)
    plt.contourf(X1, X2, Z, cmap=cmap, alpha=0.5)
    for i, class_ in enumerate(class_label):
        plt.scatter(x=X[y == class_, 0], y=X[y == class_, 1], 
                c=cmap.colors[i], label=class_, marker=marker[i])
    plt.legend()
from itertools import product
weights = ['uniform', 'distance']
ks = [2, 15]

plt.figure(figsize=(18, 10))
# 計算weights與ks的笛卡爾積組合。這樣就可以使用單層循環取代嵌套循環,
# 增加代碼可讀性與可理解性。
for i, (w, k) in enumerate(product(weights, ks), start=1):
    plt.subplot(2, 2, i)
    plt.title(f"K值:{k} 權重:{w}")
    knn = KNeighborsClassifier(n_neighbors=k, weights=w)
    knn.fit(X, y)
    plot_decision_boundary(knn, X_train, y_train)

結果:
在這裏插入圖片描述
通過決策邊界,我們可以得出如下結論:

  • K的值越小,模型敏感度越強(穩定性越弱),模型也就越複雜,容易過擬合
  • K的值越大,模型敏感度越弱(穩定性越強),模型也就越簡單,容易欠擬合

7.3 超參數調整

在實際應用中,我們很難單憑直覺,就能夠找出合適的超參數,通常,我們可以通過網格交叉驗證的方式,找出效果最好的超參數。

from sklearn.model_selection import GridSearchCV #網格搜索

knn = KNeighborsClassifier()
# 定義需要嘗試的超參數組合。
grid = {"n_neighbors": range(1, 11, 1), "weights": ['uniform', 'distance']}  # 20種超參數
# estimator:評估器,即對哪個模型調整超參數。
# param_grid:需要檢驗的超參數組合。從這些組合中,尋找效果最好的超參數組合。
# scoring:模型評估標準。
# n_jobs:併發數量。
# cv:交叉驗證折數。
# verbose:輸出冗餘信息,值越大,輸出的信息越多。
gs = GridSearchCV(estimator=knn, param_grid=grid, scoring="accuracy", n_jobs=-1, cv=5, verbose=10, iid=True) 
# cv=5每一種超參數都進行5折交叉驗證
gs.fit(X_train, y_train)

GridSearchCV,它存在的意義就是自動調參,只要把參數輸進去,就能給出最優化的結果和參數,這個方法適合於小數據集。
GridSearchCV參數說明:
estimator:評估器,即對哪個模型調整超參數。
param_grid:需要檢驗的超參數組合。從這些組合中,尋找效果最好的超參數組合,值爲字典或者列表。
coring:模型評估標準。
n_jobs:併發數量。
cv:交叉驗證折數。
verbose:輸出冗餘信息,值越大,輸出的信息越多。

結果:
在這裏插入圖片描述
在這裏插入圖片描述
當網格交叉驗證結束後,我們就可以通過GridSearchCV對象的相關屬性,來獲取最好的參數,分值與模型。

# 最好的分值。
print(gs.best_score_)
# 最好的超參數組合。
print(gs.best_params_)
# 使用最好的超參數訓練好的模型。
print(gs.best_estimator_)

在這裏插入圖片描述
最後,我們使用最好的模型在測試集上進行測試,實現最後的檢驗。

estimator = gs.best_estimator_
y_hat = estimator.predict(X_test)
print(classification_report(y_test, y_hat))

結果:
在這裏插入圖片描述
練習:

通過網格交叉驗證,可以幫助我們完成調參工作,因此,即使我們不太瞭解超參數的含義,也可以順利完成調參任務。這種說法正確嗎?(B
A 正確
B 不正確

8、使用KNN迴歸預測

8.1 建模預測

我們以波士頓房價數據集爲例,通過KNN算法實現迴歸預測。同時,我們使用線性迴歸算法進行對比。

from sklearn.datasets import load_boston
from sklearn.neighbors import KNeighborsRegressor
from sklearn.linear_model import LinearRegression

X, y = load_boston(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
knn = KNeighborsRegressor(n_neighbors=3, weights="uniform")
knn.fit(X_train, y_train)
print("KNN算法R^2值:", knn.score(X_test, y_test))
lr = LinearRegression()
lr.fit(X_train, y_train)
print("線性迴歸算法R^2值:", lr.score(X_test, y_test))

結果:
在這裏插入圖片描述
我們可以看到結果KNN算法小於線性迴歸算法的R²值。

8.2 數據標準化

我們發現,使用KNN算法進行迴歸,效果比線性迴歸要差很多,但這並不能證明KNN算法是不如線性迴歸的。
原因在於,線性迴歸在訓練參數時,不是基於距離進行計算的,因此,即使線性迴歸各個特徵的量綱(數量級)不同,也不影響最終的擬合效果(權重會不同)。
不過,KNN是基於距離計算的,如果特徵之間的量綱不同,在計算時,量綱較大的特徵就會佔據主導地位,從而算法會忽略量綱較小的特徵,這將會對模型性能造成較大的影響。
實際上,不只是KNN算法,很多算法對特徵的數量級都是敏感的,因此,在使用算法之前,我們最好將數據集中的特徵轉換成相同的量綱,從而消除不同量綱對算法造成的負面影響,我們將這個過程稱爲 數據標準化 實際上,即使特徵量綱相同,標準化也不會產生負面影響。

在scikit-learn中,常用的標準化方式爲均值標準差標準化(StandardScaler)與最小最大值標準化(MinMaxScaler)。

均值標準差標準化 =(每個特徵 - 特徵的平均值)/ 特徵的標準差【特徵變爲均值爲0標準差爲1】
最小最大值標準化 = (當前值 - 最小值)/ (最大值 - 最小值)【每個特徵縮放到(0,1)這個區間內】

from sklearn.preprocessing import StandardScaler, MinMaxScaler

scaler = [StandardScaler(), MinMaxScaler()]
desc = ["均值標準差標準化", "最小最大值標準化"]
for s, d in zip(scaler, desc):
    X_train_scale = s.fit_transform(X_train)
    X_test_scale = s.transform(X_test)
    knn = KNeighborsRegressor(n_neighbors=3, weights="uniform")
    knn.fit(X_train_scale, y_train)
    print(d, knn.score(X_test_scale, y_test))

StandardScaler 均值標準差標準化
MinMaxScaler 最小最大值標準化
結果:
在這裏插入圖片描述
我們發現,通過標準化處理之後,模型效果有了顯著的提升。

8.3 流水線

在上例中,我們使用標準化對訓練數據進行了轉換,然後使用KNN模型對象在轉換後的數據上進行擬合。可以說,這是兩個步驟。我們雖然可以分別去執行這兩個步驟,然而,當數據預處理的工作較多時,可能會涉及更多的步驟(例如多項式擴展,One-Hot編碼,特徵選擇等操作),此時分別執行每個步驟會顯得過於繁瑣。

流水線(Pipeline類)可以將每個評估器視爲一個步驟,然後將多個步驟作爲一個整體而依次執行,這樣,我們就無需分別執行每個步驟。 例如,在上例中,我們就可以將數據標準化與訓練模型兩個步驟視爲一個整體,一併執行。

流水線具有最後一個評估器的所有方法。 當通過流水線對象調用方法 f
時,會執行這樣的過程(假設流水線具有個評估器):

  • 如果 f 是fit方法,則會首先對前n - 1個評估器依次調用fit_transform方法,然後在最後一個評估器上調用 f(fit)方法。
  • 如果 f 是其他方法,則會首先對前 n - 1個評估器依次調用transform方法,然後在最後一個評估器上調用 f 方法。

在這裏插入圖片描述

例如,當在流水線上調用fit方法時,將會依次在每個評估器上調用fit_transform方法,前一個評估器將轉換之後的結果傳遞給下一個評估器,直到最後一個評估器調用fit方法爲止(最後一個評估器不會調用transform方法)。而當在流水線上調用predict方法時,則會依次在每個評估器上調用transform方法,在最後一個評估器上調用predict方法。

from sklearn.pipeline import Pipeline

X, y = load_boston(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
# 定義流水線的步驟。類型爲一個列表,列表中的每個元素是元組類型,
# 格式爲:[(步驟名1,評估器1), (步驟名2, 評估器2),……, (步驟名n, 評估器n)
steps = [("scaler", StandardScaler()), ("knn", KNeighborsRegressor())]
p = Pipeline(steps)
# 設置流水線的參數。所有可用的參數,可以通過get_params獲取。
p.set_params(knn__n_neighbors=3, knn__weights="uniform")
p.fit(X_train, y_train)
print(p.score(X_test, y_test))

解析:
knn__ :這裏knn兩個下劃線就是一種命名方法,給knn設置其參數。

結果:
在這裏插入圖片描述

9、KD樹

當樣本數量較少時,我們可以使用遍歷所有樣本的方式,找出最近的K的鄰居,然而,如果數據集龐大,這種方式會造成大量的時間開銷,此時,我們可以使用KD樹的方式來選擇K個鄰居。
KD樹算法中,首先是對訓練數據進行建模,構建KD樹,然後再根據建好的模型來獲取鄰近樣本數據。

10、總結

  • KNN算法原理與超參數。
  • 使用KNN實現分類與迴歸。
  • 通過網格交叉驗證調整超參數。
  • 數據標準化的作用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章