KNN(K-Nearest Neighbors,K 近邻算法)是我面试机器学习岗位时最常拿来“送分”的一个问题,也是我在实际项目里几乎每次都会先跑一遍的 baseline 算法。它不需要训练梯度,不需要假设数据分布,甚至连“训练”这个动作都显得有些敷衍——模型只是把样本存下来,预测时现算距离,然后让邻居们投个票。但恰恰是这种“朴实无华”的设计,让 KNN 成了新手理解机器学习的一扇绝佳窗口:距离怎么度量、特征怎么缩放、超参数怎么选、缺失值怎么影响判断,这些贯穿整个机器学习生涯的核心问题,在这一个算法里几乎全都能碰到。
如果你正在入门机器学习,或者临近期末想复习 KNN 却不希望去啃一堆晦涩公式,这篇文章应该能帮到你。我会少绕弯子,直接从 KNN 的投票逻辑讲起,把距离度量、特征标准化、K 值选择这些关键点拆开说透,再用 sklearn 把“红酒分类”这个经典案例完整跑一遍,最后聊聊维度灾难和预测速度这些工程上容易踩的坑。看完之后,你对 KNN 的理解不会只停在“少数服从多数”这句话上。
1. “佛系训练”背后:KNN 只做投票,不做建模
1.1 一个手算就能懂的投票例子
很多人第一次看到 KNN 的直觉是“这不就是找几个离得近的样本投票吗?”,说实话,本质确实如此。我先给一个完全可以纸上手算的例子,你感受下它有多直白。
假设平面上有三条已知样本,都是二维特征:
- 点 A:(-2, -1),标签“甲类”
- 点 B:(-2, 1),标签“甲类”
- 点 C:(2, 0),标签“乙类”
现在来一个新样本 X = (0, 0),我们想判断它属于哪一类。用欧氏距离算一下:
- X 到 A 的距离是 sqrt((-2-0)^2 + (-1-0)^2) = sqrt(5),约等于 2.236
- X 到 B 的距离也是 sqrt((-2-0)^2 + (1-0)^2) = sqrt(5),约等于 2.236
- X 到 C 的距离是 sqrt((2-0)^2 + 0^2) = 2
如果取 K=1,最近的是 C 点,X 会被判成“乙类”;如果取 K=3,最近的三个点里 A、B 都投“甲类”,C 投“乙类”,于是变成 2:1,X 被判成“甲类”。同样的样本,不同的 K 值,结论完全变了。这就是为什么 KNN 调参的核心是 K,而不是什么隐藏层的权重。
从数学角度看,KNN 只依赖两个东西:一是特征空间中点与点的距离,二是 K 取多大。它没有显式的决策函数,没有损失函数,也不需要梯度下降。判断类别时,它把训练数据当成一本随时可以翻的“历史档案”,新样本来了就翻档案找相似案例,然后让历史案例投票。
1.2 分类、回归都能做:KNN 不只是“投票机”
很多人提到 KNN 只会想到分类,其实 KNN 做回归同样自然。新增一个样本时,找出它的 K 个近邻,分类任务让邻居们按照标签投票,回归任务则可以直接取这 K 个邻居目标值的平均数(或者距离加权平均数)。sklearn 里对应的类是 KNeighborsClassifier 和 KNeighborsRegressor,使用方式几乎一致。
这里有一个细节值得体会:KNN 在“训练”阶段几乎不做任何计算,只是把样本和标签原封不动存起来,所以它常被称为“惰性学习”(lazy learning)或者“基于实例的学习”(instance-based learning)。这意味着训练时间几乎为零,但预测阶段要老老实实把新样本跟所有历史样本算一次距离。这种“训练轻松、预测昂贵”的特性,决定了它在中小型数据集上很讨喜,但一旦参考样本量到百万级,预测效率会让人头大。后面我会专门展开聊这个问题。
1.3 “朴素”能成立,前提是特征真的能刻画相似性
KNN 的朴素假设可以概括成一句话:如果两个样本的特征足够接近,它们的标签也应该接近。这个假设在生活中很多场景都成立——判断一个人喜欢什么类型的电影,看他身边朋友喜欢什么类型的电影,往往八九不离十;判断一种葡萄酒像不像某个品种,比较它的化学成分是否接近,也说得通。
但“假设成立”不等于“自动成立”。如果给的数据里全是无关特征、噪声特征,比如用“用户 ID 的奇偶性”去判断用户是否会流失,那么距离算出来没有意义,邻居关系会被无关维度干扰。这也是新手最容易踩的坑:KNN 本身对特征的质量非常敏感,它不会像决策树那样自动挑特征,也不会像线性模型那样给特征赋予学出来的权重。做 KNN 之前,特征选择或降维往往是绕不开的一步。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 同样叫“近邻”,换种距离测法结论可能完全不同
2.1 欧氏距离只是默认项,不是唯一解
KNN 里的“近邻”到底怎么定义?绝大多数教材默认用欧氏距离,也就是我们从小到大最熟悉的直线距离。在二维坐标下,两个点 (x1, y1) 和 (x2, y2) 的欧氏距离是 sqrt((x1-x2)^2 + (y1-y2)^2);推广到 D 维特征,就是每个维度差值的平方和再开根号。
欧氏距离直观,但不该无脑用。常见的替代方案至少还有这几种:
- 曼哈顿距离:sum(|x
