一、KNN算法介紹
K最近鄰(K-Nearest Neighbor, KNN)算法是機器學習中最簡單、最直觀的分類算法之一。它既可以用于分類問題,也可以用于回歸問題。KNN是一種基于實例的學習(instance-based learning)或懶惰學習(lazy learning)算法,因為它不會從訓練數據中學習一個明確的模型,而是直接使用訓練數據本身進行預測。
1.1 KNN算法原理
KNN算法的核心思想可以概括為:"物以類聚,人以群分"。對于一個待分類的樣本,算法會在訓練集中找到與之最相似的K個樣本(即"最近鄰"),然后根據這K個樣本的類別來決定待分類樣本的類別。
具體步驟如下:
-
計算待分類樣本與訓練集中每個樣本的距離(通常使用歐氏距離)
-
選取距離最近的K個訓練樣本
-
統計這K個樣本中每個類別出現的頻率
-
將頻率最高的類別作為待分類樣本的預測類別
1.2 KNN算法的特點
優點:
-
簡單直觀,易于理解和實現
-
無需訓練過程,新數據可以直接加入訓練集
-
對數據分布沒有假設,適用于各種形狀的數據分布
缺點:
-
計算復雜度高,預測時需要計算與所有訓練樣本的距離
-
對高維數據效果不佳(維度災難)
-
對不平衡數據敏感
-
需要選擇合適的K值和距離度量方式
二、Scikit-learn中的KNN實現
Scikit-learn提供了KNeighborsClassifier和KNeighborsRegressor兩個類分別用于KNN分類和回歸。下面我們重點介紹KNeighborsClassifier。
2.1 KNeighborsClassifier API詳解
class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, # K值,默認5weights='uniform', # 權重函數algorithm='auto', # 計算最近鄰的算法leaf_size=30, # KD樹或球樹的葉子節點大小p=2, # 距離度量參數(1:曼哈頓距離,2:歐氏距離)metric='minkowski', # 距離度量類型metric_params=None, # 距離度量的額外參數n_jobs=None # 并行計算數
)
主要參數說明:
-
n_neighbors (int, default=5)
-
K值,即考慮的最近鄰的數量
-
較小的K值會使模型對噪聲更敏感,較大的K值會使決策邊界更平滑
-
通常通過交叉驗證來選擇最佳K值
-
-
weights ({'uniform', 'distance'} or callable, default='uniform')
-
'uniform': 所有鄰居的權重相同
-
'distance': 權重與距離成反比,距離越近的鄰居影響越大
-
也可以自定義權重函數
-
-
algorithm ({'auto', 'ball_tree', 'kd_tree', 'brute'}, default='auto')
-
計算最近鄰的算法:
-
'brute': 暴力搜索,計算所有樣本的距離
-
'kd_tree': KD樹,適用于低維數據
-
'ball_tree': 球樹,適用于高維數據
-
'auto': 自動選擇最合適的算法
-
-
-
leaf_size (int, default=30)
-
KD樹或球樹的葉子節點大小
-
影響樹的構建和查詢速度
-
-
p (int, default=2)
-
距離度量的參數:
-
p=1: 曼哈頓距離
-
p=2: 歐氏距離
-
-
僅當metric='minkowski'時有效
-
-
metric (str or callable, default='minkowski')
-
距離度量類型,可以是:
-
'euclidean': 歐氏距離
-
'manhattan': 曼哈頓距離
-
'chebyshev': 切比雪夫距離
-
'minkowski': 閔可夫斯基距離
-
或自定義距離函數
-
-
-
n_jobs (int, default=None)
-
并行計算數
-
-1表示使用所有處理器
-
2.2 常用方法
-
fit(X, y)
: 擬合模型,只需要存儲訓練數據 -
predict(X)
: 預測X的類別 -
predict_proba(X)
: 返回X屬于各類別的概率 -
kneighbors([X, n_neighbors, return_distance])
: 查找點的K近鄰 -
score(X, y)
: 返回給定測試數據和標簽的平均準確率
三、KNN分類實戰示例
3.1 基礎示例:鳶尾花分類
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix# 加載數據集
iris = load_iris()
X = iris.data # 特征 (150, 4)
y = iris.target # 標簽 (150,)# 劃分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 創建KNN分類器
knn = KNeighborsClassifier(n_neighbors=5, # 使用5個最近鄰weights='uniform', # 均勻權重algorithm='auto', # 自動選擇算法p=2 # 歐氏距離
)# 訓練模型(實際上只是存儲數據)
knn.fit(X_train, y_train)# 預測測試集
y_pred = knn.predict(X_test)# 評估模型
print("分類報告:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
print("\n混淆矩陣:")
print(confusion_matrix(y_test, y_pred))
3.2 進階示例:手寫數字識別?
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import matplotlib.pyplot as plt
import numpy as np# 加載手寫數字數據集
digits = load_digits()
X = digits.data # (1797, 64)
y = digits.target # (1797,)# 可視化一些樣本
fig, axes = plt.subplots(2, 5, figsize=(10, 5))
for i, ax in enumerate(axes.flat):ax.imshow(X[i].reshape(8, 8), cmap='gray')ax.set_title(f"Label: {y[i]}")ax.axis('off')
plt.show()# 劃分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 創建管道:先標準化數據,再應用KNN
pipe = Pipeline([('scaler', StandardScaler()), # 標準化特征('knn', KNeighborsClassifier()) # KNN分類器
])# 設置參數網格進行網格搜索
param_grid = {'knn__n_neighbors': [3, 5, 7, 9], # 不同的K值'knn__weights': ['uniform', 'distance'], # 兩種權重方式'knn__p': [1, 2] # 曼哈頓距離和歐氏距離
}# 創建網格搜索對象
grid = GridSearchCV(pipe, param_grid, cv=5, # 5折交叉驗證scoring='accuracy', # 評估指標n_jobs=-1 # 使用所有CPU核心
)# 執行網格搜索
grid.fit(X_train, y_train)# 輸出最佳參數和得分
print(f"最佳參數: {grid.best_params_}")
print(f"最佳交叉驗證準確率: {grid.best_score_:.4f}")# 在測試集上評估最佳模型
best_model = grid.best_estimator_
test_score = best_model.score(X_test, y_test)
print(f"測試集準確率: {test_score:.4f}")# 可視化一些預測結果
sample_indices = np.random.choice(len(X_test), 10, replace=False)
sample_images = X_test[sample_indices]
sample_labels = y_test[sample_indices]
predicted_labels = best_model.predict(sample_images)plt.figure(figsize=(12, 3))
for i, (image, true_label, pred_label) in enumerate(zip(sample_images, sample_labels, predicted_labels)):plt.subplot(1, 10, i+1)plt.imshow(image.reshape(8, 8), cmap='gray')plt.title(f"True: {true_label}\nPred: {pred_label}", fontsize=8)plt.axis('off')
plt.tight_layout()
plt.show()
3.3 自定義距離度量示例?
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import make_classification
import numpy as np# 自定義距離函數:余弦相似度
def cosine_distance(x, y):return 1 - np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))# 創建模擬數據
X, y = make_classification(n_samples=1000, n_features=20, n_classes=3, random_state=42
)# 使用自定義距離度量的KNN
knn_custom = KNeighborsClassifier(n_neighbors=5,metric=cosine_distance, # 使用自定義距離algorithm='brute' # 自定義距離需要暴力搜索
)# 使用標準KNN作為對比
knn_standard = KNeighborsClassifier(n_neighbors=5)# 劃分訓練集和測試集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 訓練并評估兩個模型
knn_custom.fit(X_train, y_train)
knn_standard.fit(X_train, y_train)print(f"自定義距離KNN準確率: {knn_custom.score(X_test, y_test):.4f}")
print(f"標準KNN準確率: {knn_standard.score(X_test, y_test):.4f}")
四、KNN回歸實戰示例
總結:一句話區分核心差異
- 分類:回答 “這是什么” 的問題,輸出離散類別(如 “是垃圾郵件”);
- 回歸:回答 “這有多少” 的問題,輸出連續數值(如 “房價 300 萬”)。
雖然本文主要介紹分類問題,但KNN也可以用于回歸。下面是一個簡單的回歸示例:
from sklearn.neighbors import KNeighborsRegressor
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt# 創建回歸數據集
X, y = make_regression(n_samples=200, n_features=1, noise=10, random_state=42
)# 劃分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 創建KNN回歸器
knn_reg = KNeighborsRegressor(n_neighbors=5,weights='distance', # 距離加權p=1 # 曼哈頓距離
)# 訓練模型
knn_reg.fit(X_train, y_train)# 預測
y_pred = knn_reg.predict(X_test)# 評估
mse = mean_squared_error(y_test, y_pred)
print(f"均方誤差(MSE): {mse:.2f}")# 可視化結果
plt.scatter(X_test, y_test, color='blue', label='Actual')
plt.scatter(X_test, y_pred, color='red', label='Predicted')
plt.title('KNN Regression')
plt.xlabel('Feature')
plt.ylabel('Target')
plt.legend()
plt.show()
五、KNN調優技巧
5.1 K值選擇
K值的選擇對KNN性能有很大影響:
-
K值太小:模型對噪聲敏感,容易過擬合
-
K值太大:模型過于簡單,可能欠擬合
常用方法:
-
使用交叉驗證選擇最佳K值
-
經驗法則:K通常取3-10之間的奇數(避免平票)
5.2 數據預處理
KNN對數據尺度敏感,通常需要:
-
標準化:將特征縮放到均值為0,方差為1
-
歸一化:將特征縮放到[0,1]范圍
5.3 維度災難
高維空間中,所有點都趨向于遠離彼此,導致距離度量失效。解決方法:
-
特征選擇:選擇最相關的特征
-
降維:使用PCA等方法降低維度
5.4 距離度量選擇
不同距離度量適用于不同場景:
-
歐氏距離:適用于連續變量
-
曼哈頓距離:適用于高維數據或稀疏數據
-
余弦相似度:適用于文本數據
六、總結
KNN是一種簡單但強大的機器學習算法,特別適合小規模數據集和低維問題。通過Scikit-learn的KNeighborsClassifier,我們可以方便地實現KNN算法,并通過調整各種參數來優化模型性能。
關鍵點回顧:
-
KNN是一種懶惰學習算法,沒有顯式的訓練過程
-
K值和距離度量的選擇對模型性能至關重要
-
數據預處理(特別是標準化)對KNN非常重要
-
高維數據中KNN可能表現不佳,需要考慮降維
希望本教程能幫助你理解和應用KNN算法。在實際應用中,記得結合交叉驗證和網格搜索來找到最佳參數組合。
?
?
?
?
?
?
?
?