ML:K 近邻的基本原理与实现
在机器学习并不是所有模型都会先从训练数据中“学习出一组显式参数”。有一类方法的思路更直接当遇到一个新样本时先去训练集中找出与它最接近的若干已知样本再根据这些邻居的情况来判断结果。K 近邻K-Nearest NeighborsKNN正是这种思路的典型代表。一、K 近邻的基本思想K 近邻的核心思想是如果两个样本在特征空间中足够接近那么它们往往具有相近的输出结果。对于一个新样本K 近邻不会先建立一个显式公式而是直接执行以下步骤• 在训练集中计算它与所有已知样本之间的距离• 找出距离最近的 K 个邻居• 根据这些邻居的标签或目标值给出预测在分类问题中K 近邻通常采用“多数表决”的方式看这 K 个最近邻中哪一类出现得更多就把新样本判为哪一类。在回归问题中K 近邻则通常对这些邻居的目标值做平均或做按距离加权的平均从而得到预测值。从机器学习视角看K 近邻完成的是这样一件事• 输入一个待预测样本• 依据训练集中与它最相似的若干样本• 输出类别标签或连续数值• 核心问题怎样定义“近”因此K 近邻既是一种具体方法也是一种理解“局部相似性决定预测结果”这一思想的重要入口。图 1 K 近邻的基本预测过程二、K 近邻的数学表达1、距离度量K 近邻并不通过一个显式的参数公式来预测而是先定义样本之间的距离。在最常见的二维或多维特征空间中若两个样本分别记为那么最常见的欧氏距离Euclidean Distance可写为Scikit-learn 中近邻方法默认使用 Minkowski 距离当 p 2 时它等价于标准欧氏距离当 p 1 时则等价于曼哈顿距离。metricminkowski 是默认设置。这说明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_neighborsn_neighbors 表示默认要使用多少个邻居。它是 K 近邻中最关键的参数之一。KNeighborsClassifier 与 KNeighborsRegressor 中的默认值都是 5。从直观上看• K 较小模型更灵活但更容易对噪声敏感• K 较大模型更平滑但可能忽略局部结构2、weightsweights 用来决定邻居在预测时如何参与计算。• uniform所有邻居权重相同• distance距离越近权重越大当数据中邻居距离差异较明显时distance 往往更合理因为它强调“更近的样本更有参考价值”。3、metric 与 pmetric 用于指定距离度量默认是 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_size0.2: 测试集大小占总数据的20%即30个样本训练集占80%120个样本# random_state42: 随机种子保证每次运行代码时划分结果相同便于复现X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42) # 3. 创建 K 近邻分类器# n_neighbors5: 指定要参考的最近邻数量即投票时考虑最近的5个邻居model KNeighborsClassifier(n_neighbors5) # 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(size120)添加均值为0、标准差为0.3的高斯噪声增加数据真实性y np.sin(X[:, 0]) 0.3 * rng.normal(size120) # 2. 划分训练集与测试集# test_size0.2测试集占20%24个样本训练集占80%96个样本# random_state42固定划分方式便于复现X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42) # 3. 创建 K 近邻回归器# n_neighbors5预测时考虑最近的5个训练样本# weightsdistance权重与距离成反比距离越近的样本权重越大默认uniform为所有邻居等权重model KNeighborsRegressor(n_neighbors5, weightsdistance) # 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 近邻通过比较样本间距离用最近的若干邻居来完成分类或回归。它直观、易实现是理解“局部相似性决定预测结果”的重要入口。“点赞有美意赞赏是鼓励”