KNN:近朱者赤
约 14 分钟
前面的模型都要「训练」——调参数、算梯度、长树。这一节讲一个几乎不需要训练的算法:K 近邻。
算法只有一句话
要预测一个新样本,就在训练集里找出离它最近的 个样本,看它们大多数属于哪一类。
回归任务就取这 个邻居的平均值。
没有参数要学,没有损失函数要优化。 训练阶段就是把数据存起来——所以它被称为惰性学习(lazy learning)。
三个必须做的决定
① K 取多少
| K | 效果 |
|---|---|
| K = 1 | 完全跟着最近邻走,对噪声极敏感,方差高 |
| K 很大 | 决策边界过于平滑,忽略局部结构,偏差高 |
| K 适中 | 平衡点,靠交叉验证选 |
经验起点:,然后用交叉验证微调。二分类时 K 取奇数,避免平票。
② 距离怎么算
| 距离 | 公式 | 适用 |
|---|---|---|
| 欧氏距离 | 最常用,连续特征 | |
| 曼哈顿距离 | $\sum | x_i - y_i |
| 余弦相似度 | 夹角 | 文本、推荐(只看方向不看长度) |
③ 要不要加权
默认每个邻居一票。更合理的做法是按距离加权——越近的邻居话语权越大(weights='distance')。
最大的坑:必须做特征缩放
这是 KNN 最容易翻车的地方。假设两个特征:
- 年龄:20–60(跨度 40)
- 年收入:30000–300000(跨度 270000)
算欧氏距离时,收入的差异会把年龄的差异彻底淹没——年龄差 40 岁贡献 ,收入差 1000 元就贡献 。模型实际上只在看收入。
所以用 KNN 之前必须归一化:
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
clf = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5))
用 Pipeline 包起来很重要:它保证缩放的统计量只从训练集算出,交叉验证时不会泄漏验证集信息。
对比一下:决策树完全不需要这一步(它只看阈值大小)。「要不要缩放」是区分各类模型的一个重要维度。
维度灾难:KNN 的天花板
在高维空间里,「最近」这个概念会失效。
直观理解:维度越高,空间越空旷,所有点之间的距离趋于相等——最近的邻居和最远的点差不了多少,「近朱者赤」的前提就没了。
所以 KNN 适合低维(几维到几十维),高维数据要先降维(下一节的 PCA 就是干这个的)或换模型。
代价:预测慢
训练几乎不花时间,但每预测一个新样本,都要和全部训练数据算距离。 个训练样本、 维,单次预测就是 。
工程上用 KD 树或 Ball 树加速(sklearn 会自动选),但高维时这些结构也会退化。
一句话总结 KNN 的取舍:把训练的成本推迟到了预测阶段。
练习:一个数据集有「身高(cm)」和「体重(kg)」两个特征,不做归一化直接跑 KNN,模型会主要依据哪个特征判断?为什么?
为什么 KNN 必须做特征缩放,而决策树不需要?什么是维度灾难对 KNN 的影响?
登录 后可看答案