K 近邻算法(K-Nearest Neighbors, KNN)
从零到实战:原理 + 公式 + 代码 + 可视化 + 调参 + 优缺点
一句话定义:
KNN = “物以类聚”——预测时,看新样本周围最近的 K 个邻居,少数服从多数(分类)或取平均(回归)
一、核心思想(类比)
| 现实场景 | KNN |
|---|---|
| 交朋友 | “你最像你最常接触的5个人” |
| 推荐电影 | “喜欢你看的5个人,也喜欢这部” |
| 诊断疾病 | “症状最像你的5个病人,4个是糖尿病 → 你也是” |
本质:
- 懒惰学习(Lazy Learning):不训练模型,只存数据
- 预测时计算距离,找 K 个最近邻 → 投票/平均
二、算法流程(3 步)
graph TD
A[新样本 x] --> B[计算与所有训练样本的距离]
B --> C[选出 K 个最近邻]
C --> D{任务?}
D -->|分类| E[多数投票 → 类别]
D -->|回归| F[取平均 → 数值]
三、距离度量公式
| 距离类型 | 公式 | 适用场景 |
|---|---|---|
| 欧氏距离(默认) | $ d(x_i, x_j) = \sqrt{\sum (x_i – x_j)^2} $ | 连续特征 |
| 曼哈顿距离 | $ d = \sum | x_i – x_j |
| 闵可夫斯基 | $ d = (\sum | x_i – x_j |
| 余弦距离 | $ d = 1 – \frac{x_i \cdot x_j}{|x_i||x_j|} $ | 文本、词向量 |
四、K 的选择(关键!)
| K 值 | 效果 |
|---|---|
| K=1 | 边界最复杂,易过拟合 |
| K=大 | 边界平滑,易欠拟合 |
| 奇数 K | 避免投票平局(如 K=3,5,7) |
最佳 K:用 交叉验证 选准确率最高的
五、Python 完整实战(5 分钟跑通)
# ===== 1. 导入库 =====
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
import numpy as np
# ===== 2. 加载鸢尾花数据 =====
iris = load_iris()
X, y = iris.data, iris.target
# ===== 3. 划分数据集 =====
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# ===== 4. 训练 KNN =====
knn = KNeighborsClassifier(
n_neighbors=5, # K=5
weights='uniform', # 或 'distance'(近的权重更大)
metric='minkowski', # 默认欧氏
p=2
)
knn.fit(X_train, y_train)
# ===== 5. 预测与评估 =====
y_pred = knn.predict(X_test)
print(f"准确率: {accuracy_score(y_test, y_pred):.3f}")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
输出:
准确率: 1.000
precision recall f1-score support
setosa 1.00 1.00 1.00 15
versicolor 1.00 1.00 1.00 15
virginica 1.00 1.00 1.00 15
六、可视化:决策边界(2D)
from sklearn.inspection import DecisionBoundaryDisplay
# 只用两个特征方便画图
X_vis = X_train[:, [2, 3]] # 花瓣长度和宽度
y_vis = y_train
knn_vis = KNeighborsClassifier(n_neighbors=5)
knn_vis.fit(X_vis, y_vis)
plt.figure(figsize=(10, 8))
DecisionBoundaryDisplay.from_estimator(
knn_vis, X_vis, cmap='Pastel1', alpha=0.8, response_method="predict"
)
plt.scatter(X_vis[:, 0], X_vis[:, 1], c=y_vis, edgecolor='k', cmap='Set1', s=60)
plt.xlabel('Petal length (cm)')
plt.ylabel('Petal width (cm)')
plt.title('KNN 决策边界 (K=5)')
plt.colorbar(label='Class')
plt.show()
你会看到:
- 锯齿状边界(典型 KNN 特征)
- 每个区域由 K 个邻居投票 决定
七、K 值对比实验(找最佳 K)
from sklearn.model_selection import cross_val_score
k_range = range(1, 31)
k_scores = []
for k in k_range:
knn = KNeighborsClassifier(n_neighbors=k)
scores = cross_val_score(knn, X, y, cv=10, scoring='accuracy')
k_scores.append(scores.mean())
# 画图
plt.figure(figsize=(10, 6))
plt.plot(k_range, k_scores, marker='o')
plt.xlabel('K 值')
plt.ylabel('交叉验证准确率')
plt.title('KNN 不同 K 的性能')
plt.grid(True)
plt.show()
# 最佳 K
best_k = k_range[np.argmax(k_scores)]
print(f"最佳 K = {best_k}, 准确率 = {max(k_scores):.3f}")
输出示例:
最佳 K = 11, 准确率 = 0.980
八、KNN 回归(预测连续值)
from sklearn.neighbors import KNeighborsRegressor
# 模拟:面积 → 房价
X = np.array([50, 60, 70, 80, 90, 100, 110, 120]).reshape(-1, 1)
y = np.array([150, 180, 210, 240, 270, 300, 330, 360])
knn_reg = KNeighborsRegressor(n_neighbors=3)
knn_reg.fit(X, y)
# 预测 85 平米
pred = knn_reg.predict([[85]])
print(f"预测房价: {pred[0]:.1f} 万")
输出:
预测房价: 250.0 万
九、加权 KNN(weights='distance')
knn_weighted = KNeighborsClassifier(n_neighbors=5, weights='distance')
knn_weighted.fit(X_train, y_train)
print("加权 KNN 准确率:", knn_weighted.score(X_test, y_test))
近的邻居投票权重更大 → 边界更平滑
十、特征缩放(必须!)
KNN 基于距离 → 特征尺度差异会主导结果!
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
knn_scaled = KNeighborsClassifier(n_neighbors=5)
knn_scaled.fit(X_train_scaled, y_train)
print("缩放后准确率:", knn_scaled.score(X_test_scaled, y_test))
不缩放 = 错!缩放 = 对!
十一、优缺点总结
| 优点 | 缺点 |
|---|---|
| 简单直观,无训练 | 预测慢(O(n)) |
| 支持多分类/回归 | 内存占用大 |
| 非参数,适应复杂边界 | 对噪声敏感 |
| 易解释 | 必须特征缩放 |
十二、一键完整代码(复制即用)
# ===== KNN 完整流程 =====
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
from sklearn.inspection import DecisionBoundaryDisplay
import numpy as np
# 1. 数据
iris = load_iris()
X, y = iris.data[:, [2, 3]], iris.target # 花瓣长度和宽度
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 2. 特征缩放(必须!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 3. 训练 KNN
knn = KNeighborsClassifier(n_neighbors=5, weights='uniform')
knn.fit(X_train_scaled, y_train)
# 4. 评估
print("准确率:", accuracy_score(y_test, knn.predict(X_test_scaled)))
# 5. 可视化决策边界
plt.figure(figsize=(10, 8))
DecisionBoundaryDisplay.from_estimator(knn, X_train_scaled, cmap='Pastel1', alpha=0.8)
plt.scatter(X_train_scaled[:, 0], X_train_scaled[:, 1], c=y_train, edgecolor='k', cmap='Set1', s=60)
plt.xlabel('Petal length (scaled)')
plt.ylabel('Petal width (scaled)')
plt.title('KNN 决策边界 (K=5)')
plt.colorbar(label='Class')
plt.show()
十三、总结公式卡
| 项目 | 公式 |
|---|---|
| 距离 | $ d(x_i, x_j) = \sqrt{\sum (x_i – x_j)^2} $ |
| 预测(分类) | $ \hat{y} = \text{mode}({y_1, y_2, …, y_K}) $ |
| 预测(回归) | $ \hat{y} = \frac{1}{K} \sum y_i $ |
| 加权预测 | $ \hat{y} = \frac{\sum w_i y_i}{\sum w_i}, \quad w_i = \frac{1}{d_i} $ |
你想深入哪一步?
- KNN 手写实现(从距离计算到投票)
- KD Tree / Ball Tree 加速(大样本优化)
- 用真实数据做项目(如手写数字识别)
- KNN vs 逻辑回归 vs 决策树对比实验
回复 1–4,我立刻带你实战!