在机器学习,并不是所有模型都会先从训练数据中“学习出一组显式参数”。有一类方法的思路更直接:当遇到一个新样本时,先去训练集中找出与它最接近的若干已知样本,再根据这些邻居的情况来判断结果。K 近邻(K-Nearest Neighbors,KNN)正是这种思路的典型代表。
一、K 近邻的基本思想
K 近邻的核心思想是:如果两个样本在特征空间中足够接近,那么它们往往具有相近的输出结果。
对于一个新样本,K 近邻不会先建立一个显式公式,而是直接执行以下步骤:
• 在训练集中计算它与所有已知样本之间的距离
• 找出距离最近的 K 个邻居
• 根据这些邻居的标签或目标值给出预测
在分类问题中,K 近邻通常采用“多数表决”的方式:看这 K 个最近邻中哪一类出现得更多,就把新样本判为哪一类。
在回归问题中,K 近邻则通常对这些邻居的目标值做平均,或做按距离加权的平均,从而得到预测值。
从机器学习视角看,K 近邻完成的是这样一件事:
• 输入:一个待预测样本
• 依据:训练集中与它最相似的若干样本
• 输出:类别标签或连续数值
• 核心问题:怎样定义“近”
因此,K 近邻既是一种具体方法,也是一种理解“局部相似性决定预测结果”这一思想的重要入口。
图 1 K 近邻的基本预测过程
二、K 近邻的数学表达
1、距离度量
K 近邻并不通过一个显式的参数公式来预测,而是先定义样本之间的距离。
在最常见的二维或多维特征空间中,若两个样本分别记为:
那么最常见的欧氏距离(Euclidean Distance)可写为:
Scikit-learn 中近邻方法默认使用 Minkowski 距离;当 p = 2 时,它等价于标准欧氏距离;当 p = 1 时,则等价于曼哈顿距离。metric='minkowski' 是默认设置。
这说明,K 近邻最关键的前提之一是:样本之间的“几何接近程度”能够反映它们在任务上的相似性。
2、分类中的预测规则
设新样本为 x,其最近的 K 个邻居记为:
在最简单的分类情形中,预测类别可以理解为:
也就是说,在 K 个邻居中,哪一个类别出现次数最多,就把新样本判为哪一类。这就是“多数表决”思想。
3、回归中的预测规则
在回归问题中,K 近邻通常采用平均值作为预测结果:
也可以采用按距离加权的平均形式:
其中,wᵢ可由距离决定,距离越近,权重越大。
Scikit-learn 的 weights 参数支持 'uniform' 与 'distance' 两种常见方案:前者对邻居一视同仁,后者按距离倒数加权,使更近的邻居影响更大。
4、K 的意义
参数 K 决定“参考多少个邻居”。
• 当 K 很小,例如 K = 1,模型会非常依赖局部最近的单个样本
• 当 K 较大时,模型会综合更大的邻域信息,结果通常更平滑
• K 太小容易受噪声影响
• K 太大又可能把局部细节抹平
图 2 K 值与模型复杂度的关系
因此,K 近邻的关键问题之一,就是如何选择合适的 K。
三、K 近邻为什么有效
1、局部相似性假设
K 近邻之所以能够工作,依赖于一个基本假设:在特征空间中彼此接近的样本,往往具有相似的输出。
例如,在手写数字识别中,两个像素分布很接近的图像,通常也更可能代表同一个数字;在房价预测中,面积、地段、房龄等特征都相近的房屋,其价格往往也更接近。
2、K 近邻不是“先学参数,再预测”
K 近邻与线性回归、逻辑回归这类模型不同,它通常不在训练阶段学习出一组显式参数公式。它更像是一种“基于记忆”的方法:
• 训练阶段主要是保存训练数据
• 预测阶段才真正发生大量比较与计算
正因为如此,K 近邻常被看作一种惰性学习(lazy learning)方法:它把主要工作延后到了预测时。
3、决策边界来自邻域结构
在分类问题中,K 近邻的分类边界并不是由一个固定公式直接给出的,而是由训练样本在空间中的分布共同决定的。
这使得 K 近邻能够形成比较灵活的决策边界,而不必预先假设目标函数的具体形式。
这也是它的一大优势:当数据的类别分布比较复杂时,K 近邻往往比简单线性模型更有表达能力。
四、K 近邻中的关键参数
Scikit-learn 的 KNeighborsClassifier 实现的是基于近邻投票的分类器,而 KNeighborsRegressor 则基于近邻目标值的局部插值来完成回归。
1、n_neighbors
n_neighbors 表示默认要使用多少个邻居。它是 K 近邻中最关键的参数之一。
KNeighborsClassifier 与 KNeighborsRegressor 中的默认值都是 5。
从直观上看:
• K 较小:模型更灵活,但更容易对噪声敏感
• K 较大:模型更平滑,但可能忽略局部结构
2、weights
weights 用来决定邻居在预测时如何参与计算。
• uniform:所有邻居权重相同
• distance:距离越近,权重越大
当数据中邻居距离差异较明显时,distance 往往更合理,因为它强调“更近的样本更有参考价值”。
3、metric 与 p
metric 用于指定距离度量,默认是 'minkowski'。
若 p = 2,则是欧氏距离;若 p = 1,则是曼哈顿距离。
Scikit-learn 也支持其他距离度量,甚至支持预先给定距离矩阵。
这意味着,K 近邻的“近”并不只有一种定义。
不同任务中,距离定义本身就可能显著影响预测效果。
4、搜索算法
Scikit-learn 的 K 近邻还提供 algorithm 参数,可选 'auto'、'ball_tree'、'kd_tree'、'brute',用于控制近邻搜索的计算方式。
不同方法会影响查询速度和内存开销,但不会改变 K 近邻的基本原理。
五、K 近邻的结果如何解释
K 近邻虽然不像线性回归那样能直接给出一组系数,但它依然具有一定可解释性,只是这种解释方式更偏向“局部参照”。
1、分类结果的解释
在分类任务中,一个新样本之所以被分到某一类,是因为它周围最近的 K 个训练样本中,这一类占多数,或者加权后这一类贡献最大。
也就是说,K 近邻的分类解释可以表述为:这个样本周围最像它的训练样本,大多属于这一类,因此模型把它归入这一类。
2、回归结果的解释
在回归任务中,预测值来自邻居目标值的平均或加权平均。
因此,回归结果可理解为:这个样本附近若干相似样本的目标值大致是多少,当前预测值就是对这些邻近目标的综合估计。
3、解释性依赖局部邻域
K 近邻的可解释性并不体现在“全局公式”,而体现在“局部邻域依据”。
这意味着:它很适合解释单个样本为什么得到某个预测,但不擅长给出一个简洁的全局规律表达式。
六、Python 实现:K 近邻分类示例
下面用鸢尾花数据集演示 K 近邻分类的基本实现方式。
KNeighborsClassifier 用于基于近邻投票的分类。
# 导入所需的模块和函数from sklearn.datasets import load_iris # 加载鸢尾花数据集的函数from sklearn.model_selection import train_test_split # 划分训练集和测试集的函数from sklearn.neighbors import KNeighborsClassifier # K近邻分类器 # 1. 加载数据iris = load_iris() # 调用load_iris()加载鸢尾花数据集,返回一个类似字典的对象X = iris.data # 特征数据:一个150行4列的二维数组,每行代表一朵花,4列分别是花萼长度、花萼宽度、花瓣长度、花瓣宽度y = iris.target # 目标标签:一维数组,长度为150,每个元素是0、1、2,分别代表三种鸢尾花(山鸢尾、变色鸢尾、维吉尼亚鸢尾) # 2. 划分训练集和测试集# train_test_split函数将数据集随机划分为训练集和测试集# 参数说明:# X, y: 特征和标签# test_size=0.2: 测试集大小占总数据的20%(即30个样本),训练集占80%(120个样本)# random_state=42: 随机种子,保证每次运行代码时划分结果相同,便于复现X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42) # 3. 创建 K 近邻分类器# n_neighbors=5: 指定要参考的最近邻数量,即投票时考虑最近的5个邻居model = KNeighborsClassifier(n_neighbors=5) # 4. 训练模型# fit方法使用训练集数据训练模型:计算每个训练样本在特征空间中的位置,并保存起来用于后续预测model.fit(X_train, y_train) # 5. 预测# predict方法对测试集特征进行预测,返回每个测试样本所属类别的标签y_pred = model.predict(X_test) # 打印前10个预测结果,观察模型对部分测试样本的分类情况print("前 10 个预测结果:", y_pred[:10]) # 计算并打印模型在测试集上的准确率(score方法返回的是平均分类准确度)# 准确率 = 正确分类的样本数 / 测试集总样本数print("测试集得分:", model.score(X_test, y_test))这段代码展示了 K 近邻分类的基本工作流:
1、生成或加载数据
2、划分训练集与测试集
3、创建分类器
4、用 fit 保存训练数据并建立近邻查询结构
5、用 predict 输出预测类别
如果想进一步查看某个样本的邻居,还可以使用 kneighbors() 返回邻居索引与距离;Scikit-learn 的近邻类提供了这一接口。
七、Python 实现:K 近邻回归示例
下面再给出一个 K 近邻回归示例,帮助理解其“局部平均”思想。
KNeighborsRegressor 用于基于近邻目标值进行回归预测。
import numpy as npfrom sklearn.model_selection import train_test_splitfrom sklearn.neighbors import KNeighborsRegressor # 1. 构造一维非线性数据rng = np.random.RandomState(42) # 创建随机数生成器,固定种子42,保证结果可复现X = np.linspace(-3, 3, 120).reshape(-1, 1) # 生成120个在[-3, 3]上均匀分布的点,并转换为列向量(特征)# y = sin(x) + 噪声,模拟非线性关系# np.sin(X[:, 0]):对每个x计算正弦值# 0.3 * rng.normal(size=120):添加均值为0、标准差为0.3的高斯噪声,增加数据真实性y = np.sin(X[:, 0]) + 0.3 * rng.normal(size=120) # 2. 划分训练集与测试集# test_size=0.2:测试集占20%(24个样本),训练集占80%(96个样本)# random_state=42:固定划分方式,便于复现X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42) # 3. 创建 K 近邻回归器# n_neighbors=5:预测时考虑最近的5个训练样本# weights="distance":权重与距离成反比(距离越近的样本权重越大),默认"uniform"为所有邻居等权重model = KNeighborsRegressor(n_neighbors=5, weights="distance") # 4. 训练模型# 模型会存储训练样本的位置和对应值,不涉及显式的参数学习model.fit(X_train, y_train) # 5. 预测y_pred = model.predict(X_test) # 对测试集每个点,找5个最近邻,按权重加权平均得到预测值 # 打印前5个样本的真实值与预测值,保留3位小数,直观对比回归效果for i in range(5): print(f"真实值: {y_test[i]:.3f} 预测值: {y_pred[i]:.3f}")这个示例说明,在回归问题中,K 近邻并不是去拟合一条显式公式,而是根据邻域中的目标值做局部插值。Scikit-learn 文档对 KNeighborsRegressor 的描述正是“基于训练集中最近邻目标值的局部插值”。
八、K 近邻适用场景与主要局限
1、适用场景
K 近邻较适合以下情况:
• 样本之间的距离具有明确意义
• 局部相似性能够较好反映输出相似性
• 希望使用一个直观、易理解的基线模型
• 数据规模不太大,或近邻搜索代价可接受
• 类别边界较复杂,不易用简单线性模型表达
在很多教学或基线实验中,K 近邻都是很常见的起点模型。
2、主要局限
K 近邻虽然直观,但也并不是万能方法:
• 预测开销较大:它把大量计算放在预测阶段,样本很多时近邻搜索会更慢
• 对特征尺度敏感:如果各特征量纲差异很大,距离计算可能被某些特征主导,因此通常需要先做标准化
• 对无关特征敏感:无关或噪声特征会干扰距离计算
• 高维空间效果可能下降:维度升高后,“近”和“远”的区分会变得不明显
• 结果依赖 K 与距离度量:不同的 n_neighbors、weights、metric 组合可能导致明显不同的预测结果
📘 小结
K 近邻通过比较样本间距离,用最近的若干邻居来完成分类或回归。它直观、易实现,是理解“局部相似性决定预测结果”的重要入口。
“点赞有美意,赞赏是鼓励”