跳转到内容

E.C.2 K 近邻

KNN 邻居投票图

KNN 会看离新样本最近的已标注样本,再让它们投票。它几乎没有训练成本,但预测时要比较距离,所以数据大时会变慢。

  • Python 3.10+
  • 当前稳定版 scikit-learnnumpy
Terminal window
python -m pip install -U scikit-learn numpy
  • K:参与投票的邻居数量。
  • 距离度量:如何计算“近”。
  • Lazy learning(惰性学习):训练时做得少,预测时做得多。
  • Scaling(缩放):特征范围不一致时必须做。

创建 knn_vote.py

import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
X = np.array([
[1, 1],
[2, 2],
[2, 1],
[8, 8],
[9, 9],
[8, 9],
])
y = np.array([0, 0, 0, 1, 1, 1])
model = make_pipeline(
StandardScaler(),
KNeighborsClassifier(n_neighbors=3),
)
model.fit(X, y)
pred = model.predict([[3, 3], [8.5, 8.2]])
print("predictions:", pred.tolist())

运行:

Terminal window
python knn_vote.py

预期输出:

Terminal window
predictions: [0, 1]

这个模型没有学复杂公式。它保存样本、缩放特征、计算距离,然后投票。

n_neighbors=3 改成 15。K 小时更敏感,K 大时决策更平滑。

适合尝试 KNN:

  1. 数据集较小。
  2. 特征距离有实际意义。
  3. 想快速做一个可解释 baseline。
  4. 预测延迟要求不严格。

如果数据量巨大,或者服务是高 QPS 实时预测,就不要默认用它。

复盘 KNN 时,不要只看最终标签。至少保留一个预测样本的最近邻、邻居标签和距离值。这样才能看出结果来自有意义的相似性,还是被某个尺度过大的特征带偏。

KNN 很容易解释,也很容易误用。如果缩放前后预测完全不同,或者数据变大后预测延迟明显上升,就把这个限制写进 baseline 结论。

交付检查时,用同一个测试点分别运行有缩放和无缩放版本,并记录最近邻变化。这个小对比能让你判断特征距离是否可信。若距离本身没有业务意义,继续调 K 往往只是调噪声。

再记录一次预测耗时或样本数量。KNN 的成本常常出现在预测阶段,这一点要在 baseline 结论里说清楚。

如果只是小数据离线分析,KNN 很适合做解释性 baseline;如果要实时服务,就要提前说明它的预测成本。

学完这一页,至少保留这张证据卡:

模型家族
SVM、KNN、朴素贝叶斯、LDA 或其他传统基线
数据视图
特征缩放、类别平衡、决策边界和训练/测试划分
指标
准确率/F1、混淆矩阵、边距、邻近行为或投影质量
失败检查
缩放、高维度、假设薄弱、泄漏或基线拟合差
期望产出
经典机器学习基线结果,以及一条局限性说明
  • 忘记缩放特征。
  • 以为 KNN 已经“训练好了”,忽略预测时才是主要成本。
  • 没检查特征是否表达相似性,就先调 K。

添加第三个特征,数值范围在 10000 左右。移除 StandardScaler(),观察距离投票如何被扭曲。

参考实现与讲解

没有缩放时,10000 左右的大尺度特征会主导欧氏距离。这样 KNN 可能几乎只按这个大尺度特征投票,即使原来的两个特征更能描述类别模式。

好的答案会比较有无 StandardScaler() 时的预测结果,并解释哪个特征控制了距离。核心结论是:KNN 很依赖特征尺度,因为它没有学到权重来自动修正糟糕的距离几何。