简介:K近邻(KNN)是最经典、最直观的基础机器学习算法。本文从非参数与惰性学习的本质讲起,系统拆解五大距离度量、K值选择规律与分类回归决策规则,给出含归一化处理的完整工程流程与可直接运行的代码实现,并梳理优缺点、适用场景及认证考试高频考点,助你一次构建从原理到落地的完整知识体系。
在机器学习的算法版图中,K近邻(K-Nearest Neighbors,KNN)占据着一个特殊位置:它几乎是每位学习者的“第一课”,原理极简,却常被低估其工程深度。它既能做分类也能做回归,不假设数据分布、不显式训练模型,仅凭“距离 + 决策”两个朴素动作即可完成预测。
“近朱者赤,近墨者黑”。想判断一个陌生人的品味,观察他最常来往的朋友圈往往比直接询问更可靠。KNN正是把这条社会经验数学化:面对一个未知样本,先在训练集中找出与它“最像”(距离最近)的K个样本,再让这K个“邻居”投票或取平均,得出最终结论。一句话概括:看你身边最亲近的K个样本是什么,你就是什么。
从学术视角看,KNN有三重身份标签,考试与面试中极易以此出题:
这三重属性决定了KNN“零训练成本、高预测成本”的结构特征,也解释了它为何是中小规模数据上快速搭建基线模型的首选。
KNN是少数同时覆盖两类任务的基础算法:
KNN的数学内核环环相扣,可拆解为三个问题:怎么算距离?取多少邻居?邻居如何共同决策?
距离越小,样本越相似,这是KNN一切判断的出发点。五种常用度量如下:
(1)欧氏距离(默认首选)
d(x, y) = √[ (x₁−y₁)² + (x₂−y₂)² + … + (xₙ−yₙ)² ]
多维空间中两点的直线距离。以二维点(3,4)与(0,0)为例:√(3²+4²)=5。适用于连续数值特征,是绝大多数场景的默认选择。
(2)曼哈顿距离(城市街区距离)
d(x, y) = |x₁−y₁| + |x₂−y₂| + … + |xₙ−yₙ|
各维度差值绝对值之和,如同在城市街区中只能横平竖直地行走。同一组点(3,4)与(0,0)的结果为3+4=7。对异常值的惩罚比欧氏距离更温和,适合网格型数据或离散特征。
(3)闵可夫斯基距离(通用形式)
d(x, y) = ( Σ|xᵢ−yᵢ|^p )^(1/p)
参数化的通用公式:p=2时退化为欧氏距离,p=1时退化为曼哈顿距离,p→∞时趋近切比雪夫距离。调节p即可控制对维度差异的敏感方式。
(4)切比雪夫距离
d(x, y) = max |xᵢ−yᵢ|
只看差异最大的维度,属于“一票否决”式度量,适合各维度必须同时接近的场景(如仓库坐标误差控制)。
(5)余弦相似度
sim(x, y) = x·y / (‖x‖·‖y‖)
衡量向量方向夹角而非绝对距离,取值范围[-1, 1]。文本向量、用户行为向量等高维稀疏场景首选——两篇文档即使用词频率差距很大,只要“用词方向”一致,相似度依然很高。
| 度量方式 | 核心特点 | 典型场景 |
|---|---|---|
| 欧氏距离 | 绝对直线距离 | 连续数值特征(默认) |
| 曼哈顿距离 | 各维差值求和 | 网格数据、离散特征 |
| 闵可夫斯基 | 通用参数化 | 需要调节敏感度 |
| 切比雪夫 | 只看最大差异 | 极值敏感场景 |
| 余弦相似度 | 比方向不比长度 | 文本、高维稀疏特征 |
注意:余弦相似度是“越大越相似”,与距离的“越小越相似”方向相反,混用时需做转换(例如取1−sim)。
K值是KNN唯一需要认真调优的旋钮,其行为遵循清晰的“偏差—方差”跷跷板规律:
工程经验总结:
分类任务:
回归任务:
第一步:数据探索。 确认特征类型(连续/离散/类别)、缺失情况与类别分布。
第二步:特征数值化。 KNN依赖距离计算,只接受数值输入,类别特征需先编码(如独热编码)。
第三步:归一化/标准化(必做,新手最易遗漏)。 距离对量纲高度敏感:若“收入”取值上万而“年龄”仅几十,未归一化时收入差异将完全主导距离,年龄特征形同虚设。常用手段:
第四步:确定距离度量与K值搜索空间。
第五步:交叉验证调参。 用网格搜索配合K折交叉验证选取最优K,切勿用测试集调参,避免信息泄露。
第六步:预测与评估。 存储训练数据,对待测样本执行“算距离 → 排序取K → 决策”流水线;分类用准确率/F1,回归用MAE/RMSE评估。
import mathfrom collections import Counterdef euclidean(a, b):"""欧氏距离"""return math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b)))def min_max_normalize(dataset):"""按列归一化到[0,1],消除量纲影响"""cols = list(zip(*dataset)) # 转置:每列一个特征norm_cols = [[(v - min(c)) / (max(c) - min(c) + 1e-12) for v in c]for c in cols]return [list(row) for row in zip(*norm_cols)] # 转回行结构def knn_classify(train_x, train_y, query, k=5, weighted=False):"""KNN分类:多数投票 / 距离加权投票"""# 1. 计算待测样本与全部训练样本的距离pairs = [(euclidean(query, tx), ty) for tx, ty in zip(train_x, train_y)]# 2. 距离升序排序,截取前K个近邻pairs.sort(key=lambda p: p[0])top_k = pairs[:k]# 3. 决策if weighted:votes = Counter()for dist, label in top_k:votes[label] += 1.0 / (dist + 1e-9) # 距离越近权重越大return votes.most_common(1)[0][0]return Counter(lb for _, lb in top_k).most_common(1)[0][0]# ---- 使用示例 ----train_x = [[18000, 35], [22000, 41], [3000, 26], [5000, 23]]train_y = ["高净值", "高净值", "普通", "普通"]train_x = min_max_normalize(train_x) # 归一化后再算距离query = min_max_normalize([[15000, 30]])[0]print(knn_classify(train_x, train_y, query, k=3))
代码刻意保留了KNN“惰性学习”的原貌:所谓训练就是持有数据,所有计算发生在预测函数内部。
朴素KNN每次预测需与全部N个样本计算d维距离,复杂度为O(N·d)。数据量增大后可借助空间索引结构加速:
另一个必须理解的概念是维数灾难:维度升高后,样本间距离趋于同质化,“最近”与“最远”的区分度锐减,KNN效果随之劣化。对策是先做特征选择或降维(如主成分分析),压缩维度后再建模。
优点:
缺点:
典型场景:中小规模表格数据的快速基线模型、推荐系统中的相似用户/相似物品召回、文本分类、基于相似样本的缺失值填补,以及“邻居很远即为异常”思路下的异常检测。
考点1:算法属性。 KNN是监督学习、非参数、惰性学习,三者常以组合判断题形式出现。
考点2:训练阶段做了什么。 答案是“几乎什么都没做,只是存储数据”,关键词是惰性学习。
考点3:K值与拟合程度。 K小→过拟合,K大→欠拟合,方向绝不能记反。
考点4:距离手算题。 给定两点计算欧氏/曼哈顿距离,如(3,4)与(0,0):欧氏为5、曼哈顿为7,注意开方与否。
考点5:归一化必要性。 量纲差异会导致大数值特征垄断距离计算。
考点6:K为何取奇数。 二分类下避免平票。
考点7:KNN与K-Means的区别(最大陷阱)。 KNN是监督的分类/回归算法,K是邻居数;K-Means是无监督聚类,K是簇数,两者仅名字相似、内核完全不同。
考点8:复杂度与加速。 预测阶段O(N·d),KD树/球树是标准加速手段。
易错点提醒:KNN“无显式模型”不等于“无超参数”,K就是超参数;加权投票中权重与距离成反比,方向别写反;余弦相似度与距离的相似方向相反,混用需转换。
KNN的价值不止于一个入门算法:它用最朴素的方式揭示了机器学习的一个本质——相似的输入往往对应相似的输出。理解了距离度量、K值权衡与决策规则这三大支柱,再掌握归一化、交叉验证与树结构加速这些工程细节,你不仅能从容应对认证考试与技术面试,也拥有了一把理解更复杂算法(如基于检索的推荐、向量相似度搜索)的钥匙。建议动手复现本文代码,用不同K值与距离度量反复实验,体会“参数—行为—结果”之间的因果链条,这才是真正吃透KNN的方式。