KNN 算法详解
目录
1. 什么是 KNN
K 近邻算法(K-Nearest Neighbors, KNN)由 Thomas Cover 和 Peter Hart 于 1967 年在论文 "Nearest Neighbor Pattern Classification" 中正式提出。这是机器学习中最直观的算法之一:给定一个未知样本,找到训练集中与它最近的 K 个邻居,用这些邻居的标签来投票决定预测结果。
KNN 被称为"惰性学习器"(Lazy Learner)。为什么叫"惰性"?因为 KNN 在训练阶段完全不做任何计算 — 它只是把训练数据存储起来,所有的计算(距离计算、排序、投票)全部推迟到预测时才执行。这与"急切学习器"(Eager Learner,如逻辑回归、SVM)形成对比,后者在训练阶段就建立了显式的模型参数。
2. KNN 工作原理
KNN 的工作流程可以分解为以下步骤,每一步都有明确的数学原因:
- 存储训练数据:将所有训练样本 (x_i, y_i) 存入内存。为什么不做任何预处理?因为 KNN 假设预测时的数据分布与训练数据相同,保留原始数据可以最大程度保留信息。
- 计算距离:给定查询点 x_q,计算它与每个训练样本 x_i 的距离 d(x_q, x_i)。为什么选择距离作为相似性度量?因为在特征空间中,相似的样本在几何上应该彼此靠近 — 这是 KNN 的核心假设(局部一致性假设)。
- 排序选邻居:按距离从小到大排序,选出距离最近的 K 个样本。为什么不只选最近的 1 个?因为单个样本容易受噪声影响(高方差),多个邻居的投票可以平滑噪声。
- 投票/平均:分类任务:K 个邻居中出现最多的类别作为预测结果(多数投票);回归任务:K 个邻居的目标值取平均。为什么用多数投票?因为在局部区域内,最常出现的类别最可能是真实类别(最大似然原理的直觉版本)。
- 输出预测:返回投票结果或平均值。
3. 距离度量
距离度量的选择直接决定了"谁是邻居",因此对 KNN 的性能至关重要。不同的距离度量适用于不同的数据特性,每种度量的存在都有其数学和实践原因。
3.1 欧氏距离(Euclidean Distance, L2)
为什么是默认选择?欧氏距离是人类直觉中"直线距离"的数学形式化。它具有旋转不变性(rotational invariance) — 无论坐标系如何旋转,两点之间的欧氏距离不变。这意味着它不偏好任何特定方向,对各维度一视同仁。当特征在同一尺度上、且各维度同等重要时,欧氏距离是最自然的选择。
提出者:基于欧几里得几何(公元前 300 年),现代 KNN 中由 Cover & Hart(1967)默认采用。
适用场景:连续数值特征、特征已标准化、低到中维度数据。
3.2 曼哈顿距离(Manhattan Distance, L1)
为什么存在?在高维稀疏数据中,欧氏距离会出现"距离集中"现象 — 所有点之间的距离趋于相等,区分度消失。Aggarwal、Hinneburg 和 Keim 在 2001 年论文 "On the Surprising Behavior of Distance Metrics in High Dimensional Space" 中证明:随着维度增加,L1(曼哈顿)距离比 L2(欧氏)距离保留了更好的区分度。数学原因是 L1 对各维度差异的敏感度更均匀,不会像 L2 那样被少数大差异主导(平方放大效应)。
适用场景:高维稀疏数据、网格状路径问题、特征值差异大且不希望被平方放大的场景。
3.3 闵可夫斯基距离(Minkowski Distance, Lp)
为什么存在?闵可夫斯基距离是 L1 和 L2 的统一推广,由 Hermann Minkowski(1896)提出。参数 p 提供了在 L1 和 L2 行为之间连续调节的能力。较小的 p 值对各维度差异更"民主"(每个维度贡献类似),较大的 p 值让最大差异维度主导结果。通过交叉验证选择最优 p 值,可以让距离度量更适配具体数据分布。
适用场景:当你不确定 L1 还是 L2 更好时,将 p 作为超参数进行调优。
3.4 余弦距离(Cosine Distance)
为什么存在?在文本分类(TF-IDF 向量)和推荐系统中,我们关心的是两个向量的方向而非大小。例如,一篇文章提到"机器学习"10 次和另一篇提到 100 次,内容主题可能相同,只是长度不同。余弦距离忽略向量的模长(magnitude-invariant),只比较方向,因此能捕捉"主题相似性"而非"数量相似性"。这就是为什么在 NLP 中余弦距离是标准选择。
提出者:由信息检索领域发展,Gerard Salton(1971)在向量空间模型中推广使用。
适用场景:文本分类、文档相似度、推荐系统、高维稀疏向量。
3.5 距离度量对比表
| 距离度量 | 公式特性 | 最佳场景 | 弱点 |
|---|---|---|---|
| 欧氏 (L2) | 旋转不变、受大差异维度影响 | 连续特征、低维、标准化后的数据 | 高维空间区分度下降 |
| 曼哈顿 (L1) | 各维度均匀贡献、对异常值更鲁棒 | 高维稀疏数据、离散特征多 | 不具旋转不变性 |
| 闵可夫斯基 (Lp) | 可调 p 参数在 L1/L2 间平衡 | 需要调优距离度量的场景 | 额外超参数增加复杂度 |
| 余弦 | 只关注方向、忽略模长 | 文本/NLP、推荐系统 | 不适合数值大小有意义的数据 |
4. 如何选择 K 值
K 值的选择是 KNN 最关键的超参数决策,直接控制着偏差-方差权衡(bias-variance tradeoff)。
4.1 偏差-方差权衡的数学解释
小 K(如 K=1)— 低偏差/高方差:决策边界紧密贴合训练数据的每个点,能捕捉细微的局部模式。为什么低偏差?因为模型几乎完美拟合训练数据(1-NN 的训练误差为 0)。为什么高方差?因为微小的数据扰动(增加或移除一个样本)会显著改变决策边界。这意味着模型对噪声过度敏感,容易过拟合。
大 K(如 K=n)— 高偏差/低方差:决策边界极其平滑。为什么高偏差?因为模型过度平均化,忽略了局部结构(极端情况下 K=n 时,所有查询点都预测同一类别 — 多数类)。为什么低方差?因为大量邻居的投票使预测非常稳定,不受个别样本的影响。
4.2 为什么二分类用奇数 K
在二分类任务中,如果 K 为偶数(如 K=4),可能出现 2 票对 2 票的平局,此时算法必须引入额外的打破规则(如随机选择、选距离最近的类别),这引入了不确定性。使用奇数 K 可以从数学上保证不会出现平局(因为奇数无法被 2 整除),使预测结果是确定性的。
注意:多分类问题中,即使 K 为奇数也可能出现平局(如 3 个类别各得 1 票),因此奇数 K 的建议主要针对二分类。
4.3 交叉验证选 K
实践中最可靠的方法是通过 K 折交叉验证(k-fold cross-validation,注意这里的小 k 不同于 KNN 的大 K)来选择最优 K 值。常见做法是测试 K = 1, 3, 5, 7, ..., sqrt(n) 范围内的奇数值,选择验证集上准确率最高的 K。
经验法则:sqrt(n) 是 K 的一个合理上界(其中 n 是训练样本数)。为什么?因为如果 K 远大于 sqrt(n),邻域范围会过大,覆盖数据空间的大部分,失去局部性。
5. 维度诅咒
维度诅咒(Curse of Dimensionality)是 KNN 在高维数据上失效的核心原因。这个术语由 Richard Bellman 于 1961 年在其著作 "Adaptive Control Processes" 中首次提出。
5.1 为什么 KNN 在高维空间失效
核心问题:随着维度 d 增加,数据点之间的距离趋于相等,"最近邻"和"最远邻"之间的距离差异变得微不足道。为什么会这样?
数学解释:考虑 n 个均匀分布在 d 维单位超立方体 [0,1]^d 中的点。两个随机点之间的欧氏距离期望值为 E[d] ~ sqrt(d/6),而距离的标准差为 Std[d] ~ O(1)。因此:
当最近邻和最远邻的距离几乎相同时,基于距离的"邻居"概念失去了意义 — KNN 的核心假设(近邻标签相似)不再成立。
5.2 Hughes 现象
Gordon Hughes(1968)在论文 "On the Mean Accuracy of Statistical Pattern Recognizers" 中发现了一个反直觉的现象:在训练样本数固定的情况下,随着特征维度增加,分类器的性能先上升后下降。为什么?
增加维度最初提供更多信息,但当维度超过某个临界点后,高维空间变得极其稀疏(数据点无法充分覆盖特征空间),模型开始拟合噪声维度而非有用信号。需要的训练样本数随维度呈指数增长 — 要在 d 维空间中保持相同的数据密度,样本数需要是低维的 n^(d/d_0) 倍。
5.3 解决方案:降维后再用 KNN
PCA(主成分分析)+ KNN 是最经典的组合策略。为什么 PCA 有效?PCA 通过线性变换将数据投影到方差最大的方向上,消除冗余维度和噪声维度,保留最有信息量的成分。降维后数据密度增加,距离度量重新变得有意义。
其他降维方法:t-SNE(可视化用,不适合用于 KNN 预测)、UMAP(更快且保持全局结构)、特征选择(如互信息、L1 正则化)。
6. 特征缩放
特征缩放对 KNN 来说不是可选项,而是必须的预处理步骤。为什么?
核心原因:KNN 基于距离计算,而不同特征的量纲差异会导致某些特征不公平地主导距离计算。例如,"年龄"范围 [0, 100] 而"年收入"范围 [0, 1000000] — 在未缩放的情况下,欧氏距离几乎完全由"年收入"决定,"年龄"的影响可以忽略不计。
缩放方法对比
| 方法 | 公式 | 适用场景 | 为什么选择它 |
|---|---|---|---|
| StandardScaler | (x - mean) / std | 正态分布数据 | 保持分布形状,中心化到均值 0、标准差 1 |
| MinMaxScaler | (x - min) / (max - min) | 有界数据 | 将所有特征映射到 [0,1],适合不含异常值的数据 |
| RobustScaler | (x - median) / IQR | 含异常值的数据 | 基于中位数和四分位距,不受异常值影响 |
7. Python 实现
7.1 从零实现 KNN(20 行)
以下代码展示了 KNN 分类器的核心逻辑,帮助你深入理解算法的每一步:
7.2 使用 sklearn
8. 加权 KNN
S.A. Dudani 于 1976 年在论文 "The Distance-Weighted k-Nearest-Neighbor Rule" 中提出了距离加权 KNN。
为什么需要加权?标准 KNN 中,K 个邻居的投票权重相等 — 距离 0.1 的邻居和距离 9.9 的邻居对预测结果的影响完全相同。这在直觉上不合理:更近的邻居应该更可信(因为距离越近,标签相同的概率越高)。
加权方案:最常用的权重函数是距离的倒数 w_i = 1/d(x_q, x_i)。这样,距离为 0.1 的邻居权重是距离为 10 的邻居的 100 倍。
加权 KNN 的好处:(1) 对 K 值的选择更鲁棒 — 即使包含了一些较远的邻居,它们的权重也很小,不会显著影响结果;(2) 决策边界更平滑;(3) 在 sklearn 中只需设置 weights='distance'。
9. 何时使用/不使用 KNN
| 条件 | 推荐使用 KNN? | 原因 |
|---|---|---|
| 小数据集(<10K 样本) | 是 | 预测时间可接受,KNN 在小数据集上竞争力强 |
| 低维特征(<20 维) | 是 | 距离度量有效,避免维度诅咒 |
| 非线性决策边界 | 是 | KNN 自动适应任意形状的决策边界 |
| 需要快速原型验证 | 是 | 无需训练,立即可用 |
| 数据不断更新 | 是 | 新数据直接追加到训练集,不需重新训练 |
| 大数据集(>100K 样本) | 否 | 预测时间 O(n*d) 太慢(除非使用 KD-Tree/Ball Tree) |
| 高维特征(>50 维) | 否 | 维度诅咒使距离失效 |
| 需要模型可解释性 | 否 | KNN 无法输出特征重要性或规则 |
| 需要在线/实时预测 | 否 | 每次预测都需遍历训练集 |
| 特征含大量噪声 | 否 | 噪声维度污染距离计算 |
KNN vs 其他算法对比
| 对比维度 | KNN | SVM | 决策树 | 逻辑回归 |
|---|---|---|---|---|
| 训练时间 | O(1)(无训练) | O(n^2) ~ O(n^3) | O(n*d*log n) | O(n*d*迭代次数) |
| 预测时间 | O(n*d) | O(sv*d) | O(树深度) | O(d) |
| 决策边界 | 任意非线性 | 核函数决定 | 轴对齐矩形 | 线性 |
| 需要缩放 | 是(必须) | 是 | 否 | 推荐 |
| 可解释性 | 低 | 低 | 高 | 高 |
| 高维表现 | 差 | 好(核技巧) | 中等 | 好 |
10. 相关工具与延伸阅读
11. 常见问题
可以。KNN 回归的原理完全相同 — 找到 K 个最近邻后,不是多数投票,而是取邻居目标值的平均(或距离加权平均)。sklearn 提供了 KNeighborsRegressor 类。KNN 回归适合目标值与特征空间局部关系强的数据,但对异常值敏感。
能。暴力搜索的时间复杂度为 O(n*d),但可以通过空间索引结构加速:KD-Tree(Bentley, 1975)在低维(d < 20)时将查询降到 O(d*log n);Ball Tree(Omohundro, 1989)在中高维也表现较好。sklearn 的 algorithm='auto' 会自动选择最优方法。对于超大规模数据,可使用近似最近邻方法如 FAISS(Facebook)或 Annoy(Spotify)。
类别不平衡时,多数类的样本更可能出现在邻域中,导致少数类被淹没。解决方案:(1) 使用距离加权(weights='distance'),让近邻影响力更大;(2) 对少数类进行过采样(SMOTE);(3) 调小 K 值,使模型更关注局部结构;(4) 修改投票规则为加权比例而非绝对数量。
虽然名字都有 K,但它们是完全不同的算法。KNN 是监督学习(分类/回归),K 表示邻居数量,不需要训练;K-Means 是无监督学习(聚类),K 表示簇的数量,通过迭代优化簇中心。KNN 的 K 是超参数(预测时使用),K-Means 的 K 也是超参数(训练时使用)。
当 K=1 时,每个训练样本的最近邻就是它自己(距离为 0),因此预测标签总是等于真实标签,训练误差恒为 0。这也是为什么训练误差不是评估 KNN 的好指标 — 它总是乐观地偏低。必须使用交叉验证或独立测试集来评估真实性能。