K近邻:从理论到实践

K近邻:从理论到实践

文章目录

  • K近邻:从理论到实践
    • 1. 核心思想
    • 2. 距离度量
    • 3. k的选择与误差分析
      • 3.1 近似误差
      • 3.2 估计误差
      • 3.3 总误差
    • 4. kd树的构造与搜索
      • 4.1 kd树的构造
      • 4.2 kd树的搜索
    • 5. 总结
    • 6. K近邻用于iris数据集分类
      • 6.1加载数据
      • 6.2加载模型并可视化

1. 核心思想

K近邻(KNN)是一种基于实例的监督学习方法。其基本思想是:
对于一个待分类样本,根据训练集中与其“距离”最近的 kk 个邻居的类别,通过投票或加权投票的方式决定该样本的类别。

数学表达:
设训练集为

D={(x1,y1),(x2,y2),…,(xn,yn)},xi∈Rd,yi∈{1,2,…,C}{D} = \{ (x_1,y_1), (x_2,y_2), \dots, (x_n,y_n) \}, \quad x_i \in \mathbb{R}^d, \; y_i \in \{1,2,\dots,C\}D={(x1,y1),(x2,y2),,(xn,yn)},xiRd,yi{1,2,,C}

给定测试样本x,找到其最近的 kk 个邻居集合Nk(x){N}_k(x)Nk(x)
预测类别为:

y^(x)=arg⁡max⁡c∈{1,…,C}∑(xi,yi)∈Nk(x)1(yi=c)\hat{y}(x) = \arg\max_{c \in \{1,\dots,C\}} \sum_{(x_i,y_i) \in \mathcal{N}_k(x)} \mathbf{1}(y_i = c)y^(x)=argc{1,,C}max(xi,yi)Nk(x)1(yi=c)

其中,1(⋅){1}(\cdot)1() 是指示函数。

如果采用加权投票(考虑距离远近),则为:

y^(x)=arg⁡max⁡c∈{1,…,C}∑(xi,yi)∈Nk(x)1∥x−xi∥⋅1(yi=c)\hat{y}(x) = \arg\max_{c \in \{1,\dots,C\}} \sum_{(x_i,y_i) \in \mathcal{N}_k(x)} \frac{1}{\|x - x_i\|} \cdot \mathbf{1}(y_i = c)y^(x)=argc{1,,C}max(xi,yi)Nk(x)xxi11(yi=c)


2. 距离度量

KNN 依赖距离来衡量样本相似度。常见的度量方式有:

  • 欧氏距离:

d(xi,xj)=∑l=1d(xi(l)−xj(l))2d(x_i, x_j) = \sqrt{\sum_{l=1}^d (x_i^{(l)} - x_j^{(l)})^2}d(xi,xj)=l=1d(xi(l)xj(l))2

  • 曼哈顿距离:

d(xi,xj)=∑l=1d∣xi(l)−xj(l)∣d(x_i, x_j) = \sum_{l=1}^d |x_i^{(l)} - x_j^{(l)}|d(xi,xj)=l=1dxi(l)xj(l)

  • 闵可夫斯基距离(推广形式):

d(xi,xj)=(∑l=1d∣xi(l)−xj(l)∣p)1/pd(x_i, x_j) = \left( \sum_{l=1}^d |x_i^{(l)} - x_j^{(l)}|^p \right)^{1/p}d(xi,xj)=(l=1dxi(l)xj(l)p)1/p


3. k的选择与误差分析

KNN 的性能对 k 值选择敏感,体现了 近似误差估计误差 的权衡。

3.1 近似误差

  • 定义:模型表达能力不足,导致预测结果无法逼近真实分布。
  • k 较大时:决策边界过于平滑,难以捕捉复杂模式 → 近似误差大
  • k 较小时:决策边界灵活,可以更好地拟合真实模式 → 近似误差小

数学上,假设真实函数为 f(x),KNN 的期望预测为:

f^(x)=ED[y^(x)]\hat{f}(x) = \mathbb{E}_{\mathcal{D}}[\hat{y}(x)]f^(x)=ED[y^(x)]

则近似误差为:

Bias2(x)=(ED[y^(x)]−f(x))2\text{Bias}^2(x) = \big( \mathbb{E}_{\mathcal{D}}[\hat{y}(x)] - f(x) \big)^2Bias2(x)=(ED[y^(x)]f(x))2

3.2 估计误差

  • 定义:模型对有限训练数据过于依赖,泛化性差,导致预测不稳定。
  • k 较小时:极易受噪声点影响,估计误差大。
  • k 较大时:结果受单个点波动影响小,估计误差小。

其数学形式为:

Var(x)=ED[(y^(x)−ED[y^(x)])2]\text{Var}(x) = \mathbb{E}_{\mathcal{D}}\big[(\hat{y}(x) - \mathbb{E}_{\mathcal{D}}[\hat{y}(x)])^2\big]Var(x)=ED[(y^(x)ED[y^(x)])2]

3.3 总误差

textMSE(x)=Bias2(x)+Var(x)+σ2text{MSE}(x) = \text{Bias}^2(x) + \text{Var}(x) + \sigma^2textMSE(x)=Bias2(x)+Var(x)+σ2

其中,σ2\sigma^2σ2 是不可约误差。
因此,选择合适的 k 值非常重要。


4. kd树的构造与搜索

由于 KNN 需要计算测试点与所有训练点的距离,时间复杂度为O(n)。为了加速,可以用 kd树进行近邻搜索。

4.1 kd树的构造

  • kd树是一种对数据进行递归二分的空间划分结构。
  • 每次选择一个维度(通常是方差最大的维度),按照该维度的中位数划分数据。
  • 构造过程:
    1. 从根节点开始,选择一个维度作为切分轴;
    2. 找到该维度的中位数,作为节点存储值;
    3. 左子树存储小于该值的样本,右子树存储大于该值的样本;
    4. 递归进行直到样本数过少或树深度达到限制。

伪代码:

function build_kd_tree(points, depth):
if points is empty:
return None
axis = depth mod d
sort points by axis
median = len(points) // 2
node = new Node(points[median])
node.left = build_kd_tree(points[:median], depth+1)
node.right = build_kd_tree(points[median+1:], depth+1)
return node


4.2 kd树的搜索

kd树搜索遵循“回溯+剪枝”原则:

  1. 从根节点开始,递归到叶子节点,找到测试点所属的区域;
  2. 以该叶子节点为“当前最近邻”;
  3. 回溯检查父节点和另一子树,若另一子树中可能存在更近邻,则递归进入;
  4. 维护一个大小为 kk 的优先队列,存储当前最近的 kk 个邻居;
  5. 搜索结束时队列中的点即为近邻结果。

伪代码:

function knn_search(node, target, k, depth):
if node is None:
return
axis = depth mod d
if target[axis] < node.point[axis]:
next = node.left
other = node.right
else:
next = node.right
other = node.left

function knn_search(node, target, k, depth):if node is None:returnaxis = depth mod dif target[axis] < node.point[axis]:next = node.leftother = node.rightelse:next = node.rightother = node.leftknn_search(next, target, k, depth+1)update priority queue with node.pointif |target[axis] - node.point[axis]| < current_max_distance_in_queue:knn_search(other, target, k, depth+1)

5. 总结

  • 核心思想:KNN 通过寻找最近的 kk 个邻居来分类或回归。
  • k 的选择:小 kk → 近似误差小、估计误差大(过拟合);大 kk → 近似误差大、估计误差小(欠拟合)。
  • kd树:通过空间划分加速近邻搜索,提升算法效率。

最终,KNN 的关键在于 合适的 k 值选择高效的搜索结构


6. K近邻用于iris数据集分类

6.1加载数据

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_splitiris = load_iris(as_frame=True)
X = iris.data[["sepal length (cm)", "sepal width (cm)"]]
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=0)

鸢尾花数据集,as_frame=True 表示返回 pandas DataFrame 而不是 numpy 数组,方便做列选择。

这个数据集有 150 条样本4 个特征sepal length, sepal width, petal length, petal width。目标变量 target 有三类 (0=setosa, 1=versicolor, 2=virginica)。

6.2加载模型并可视化

import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.inspection import DecisionBoundaryDisplay
import pandas as pd
import time# 1. 载入数据
iris = load_iris(as_frame=True)
X = iris.data[["sepal length (cm)", "sepal width (cm)"]]
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=0
)# 2. 构建 pipeline:标准化 + KNN
clf = Pipeline(steps=[("scaler", StandardScaler()),("knn", KNeighborsClassifier(n_neighbors=11))]
)# 3. 不同的 weights 和 algorithm 组合
weights_list = ["uniform", "distance"]
algorithms = ["auto", "ball_tree", "kd_tree"]# 定义结果存储表
results = []# 4. 画图:每行一个 weights,每列一个 algorithm
fig, axs = plt.subplots(nrows=len(weights_list), ncols=len(algorithms), figsize=(18, 10)
)for i, weights in enumerate(weights_list):for j, algo in enumerate(algorithms):ax = axs[i, j]# 设置参数并拟合start_train = time.time()clf.set_params(knn__weights=weights, knn__algorithm=algo).fit(X_train, y_train)end_train = time.time()start_pred = time.time()clf.predict(X_test)end_pred = time.time()acc = clf.score(X_test, y_test)results.append({"weights": weights,"algorithm": algo,"accuracy": acc,"train_time (s)": end_train - start_train,"predict_time (s)": end_pred - start_pred})# 决策边界disp = DecisionBoundaryDisplay.from_estimator(clf,X_test,response_method="predict",plot_method="pcolormesh",xlabel=iris.feature_names[0],ylabel=iris.feature_names[1],shading="auto",alpha=0.5,ax=ax,)# 训练样本点scatter = disp.ax_.scatter(X.iloc[:, 0], X.iloc[:, 1], c=y, edgecolors="k")# 图例disp.ax_.legend(scatter.legend_elements()[0],iris.target_names,loc="lower left",title="Classes",)# 子图标题ax.set_title(f"k={clf[-1].n_neighbors}, weights={weights}, algo={algo}")plt.tight_layout()
plt.show()
df_results = pd.DataFrame(results)
print(df_results)

image-20250917183120303

 weights  algorithm  accuracy  train_time (s)  predict_time (s)
0   uniform       auto  0.710526        0.003293          0.004401
1   uniform  ball_tree  0.710526        0.004864          0.006618
2   uniform    kd_tree  0.710526        0.003537          0.004044
3  distance       auto  0.631579        0.003269          0.001961
4  distance  ball_tree  0.631579        0.003211          0.001694
5  distance    kd_tree  0.631579        0.003055          0.001578

不同 algorithm 的表现

  • autoball_treekd_tree 在相同权重下的 准确率完全一致,训练预测速度不同,这说明 搜索算法仅影响计算效率,不会改变最终分类结果
  • 这和理论一致:算法只是用不同的数据结构加速邻居查找,不会影响邻居集合本身。

不同 weights 的表现

  • uniform 权重下,测试集准确率为 71.05%
  • distance 权重下,测试集准确率为 63.16%
  • 在本实验中,uniform 明显优于 distance
  • 这表明在鸢尾花数据的 前两个特征(花萼长、宽) 上,等权投票比加权投票更适合。可能原因是:
    • 特征维度少,距离加权放大了噪声点或边界点的影响;
    • 类别边界本身不完全线性,用距离权重反而削弱了多数邻居的稳定性。

结合可视化

  • 从决策边界图上可以看到:

    • uniform 的边界相对平滑,更符合数据整体分布;
      eights 的表现**
  • uniform 权重下,测试集准确率为 71.05%

  • distance 权重下,测试集准确率为 63.16%

  • 在本实验中,uniform 明显优于 distance

  • 这表明在鸢尾花数据的 前两个特征(花萼长、宽) 上,等权投票比加权投票更适合。可能原因是:

    • 特征维度少,距离加权放大了噪声点或边界点的影响;
    • 类别边界本身不完全线性,用距离权重反而削弱了多数邻居的稳定性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/98079.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/98079.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dokcer的安装(ubuntu-20.04.6):

Dokcer的安装(ubuntu-20.04.6)&#xff1a; 1.添加Docker仓库 #更新本地软件包索引&#xff0c;获取最新的软件包信息 sudo apt-get update #安装依赖包 sudo apt-get install -y \ ca-certificates \ curl \ gnupg \ lsb-release #创建密钥存储目录 sudo mkdir -p /etc/apt/…

CT图像重建原理

一、CT到底测了什么&#xff1f;硬件动作X 射线源与探测器阵列对置&#xff0c;围着物体旋转。每转到一个角度 θ&#xff08;也叫一个视角 / view&#xff09;&#xff0c;源发射扇形/平行的射线束&#xff0c;探测器阵列上有很多“通道/像素/bin”&#xff08;记作索引 n&…

【pycharm】 ubuntu24.04 搭建uv环境

通过uv配置python环境 一直是conda环境 现在有个开源项目说用uv更快更好 所以在pycharm搞起。 一开始在在一个conda项目的里面某个项目里搞 发现会被conda 环境影响。 导致deepseed 安装不了。 python 环境不对 # NOTE: We must explicitly request them as `dependencies` abo…

从软件工程角度谈企业管理

从软件工程角度谈企业管理企业管理&#xff0c;本质上是人与人之间的博弈。 管理的最大难题&#xff0c;不是定目标、不是写流程&#xff0c;而是&#xff1a;如何让个体的利益最大化路径&#xff0c;与组织的整体目标一致&#xff1f; 这就是经济学里的“激励相容”。 在互联网…

vue3 实现前端生成水印效果

vue3 实现前端生成水印效果首先一点哈&#xff0c;就是单纯web前端生成水印只能作为警示使用&#xff0c;如果享彻底防住几乎是不可能的&#xff0c;有无数种方式去掉web前端生成的水印&#xff0c;所以这种方式只当是一个君子协议吧。编写水印组件 首先直接把这部分封装成一个…

Armonia Mall超级数字生态WEB3商城的引领者

Armonia Mall是一个基于Web3技术的超级数字生态商城&#xff0c;旨在打造全球首家Web3数字普惠商城&#xff0c;帮助千万行销人实现数字生态创业&#xff0c;让全球一亿家庭共享数字经济红利。 Armonia Mall商城创始人&#xff1a;石玉华Armonia Mall七大超级机制&#xff08;模…

Axios与Java Spring构建RESTful API服务集成指南

1 前后端分离时代的技术选择 现在的Web开发&#xff0c;前后端分离已经不是什么新鲜事了。前端用什么&#xff1f;很多团队选择Axios。后端呢&#xff1f;Java Spring依然是企业级应用的首选。 Axios这个JavaScript库确实好用&#xff0c;Promise-based的设计让异步请求变得简单…

Django ORM多对多关系实战指南

一、Django 多对多关系的原理 在关系型数据库中&#xff0c;多对多关系通常需要 第三张中间表 来维护两张表之间的对应关系。 在 Django 中&#xff0c;你只需要定义 ManyToManyField&#xff0c;Django 会自动帮你创建这张中间表。 特点&#xff1a; 可以双向查询&#xff08;…

STM32 单片机开发 - TIM 定时器(PWM)

一、硬件定时器高级控制定时器 Advanced Control Timers (TIM1/TIM8)通用定时器 General Purpose Timers (TIM2/TIM3/TIM4/TIM5)通用定时器 General Purpose Timers (TIM15/TIM16/TIM17)基本定时器 Basic Timers (TIM6/TIM7)表 1 定时器种类二、TIM 中 PWM 概念PWM 的基本原理就…

OpenCV内置分类器实现简单的人脸识别

引言 人脸检测是计算机视觉领域的基础任务之一&#xff0c;广泛应用于安防监控、人机交互、图像美化等场景。今天我们将通过一段简洁的Python代码&#xff0c;使用OpenCV库实现实时摄像头人脸检测功能。无论你是计算机视觉新手还是有经验的开发者&#xff0c;这篇文章都能帮你理…

Tomcat 性能优化与高并发调优

Tomcat 性能优化与高并发调优1. 引言 经过前几篇文章的学习&#xff0c;我们已经掌握了 Tomcat 的核心原理&#xff1a; Connector 连接器容器体系&#xff08;Engine → Host → Context → Wrapper&#xff09;Servlet 执行链路线程模型&#xff08;Executor Worker&#xf…

MacOS M1安装face_recognition

MacOS M1安装face_recognition一致失败&#xff0c;尝试网上各种方法还是失败&#xff0c;遂分享自己安装成功的经历。 conda虚拟环境python版本&#xff1a;3.9.23准备工作确保 Homebrew 已安装 Homebrew 是 macOS 的包管理器&#xff0c;用于安装依赖项。如果尚未安装&#x…

动态库和静态库的链接加载

静态库的链接与加载静态库&#xff08;如.a或.lib文件&#xff09;在编译时直接链接到可执行文件中。编译器会将静态库中实际用到的代码复制到最终的可执行文件&#xff0c;生成独立的二进制文件。优点是不依赖外部库文件&#xff0c;但会导致可执行文件体积较大。生成静态库的…

如何处理在pytorch环境中已经安装的matplotlib无法使用的问题

1 问题已经安装好的matplotlib包无法在pytorch环境中使用。2 方法方法一&#xff1a;用命令安装matplotlib &#xff1a;方法二&#xff1a;打开cmd&#xff0c;使用conda install matplotlib命令安装matplotlib库#输入以下代码段&#xff0c;查询当前执行路径import osos.sys.…

Linux基础命令汇总

系统基础指令 ls:列出目录内容 ls -a:显示所有文件(包括隐藏文件) ls -l:显示详细文件信息 ls /etc:列出 /etc 目录内容 示例: cat:查看文件内容 cat /etc/os-release:查看系统版本信息 cat file1:显示文件内容 cat file1 file2 > merged.txt:合并文件并输出到新…

一场史诗级的冒险——Docker命令大航海!

各位亲爱的开发者、运维勇士、以及所有对现代化软件部署充满好奇的小伙伴们&#xff01;今天&#xff0c;我们将开启一场史诗级的冒险——Docker命令大航海&#xff01;我们将乘坐“Docker号”巨轮&#xff0c;驶向容器化技术的星辰大海。 这不是一篇枯燥的说明书&#xff0c;而…

告别依赖混乱:Spring IoC 容器与 DI 依赖注入入门精讲

目录 什么是 IoC IoC 介绍 传统开发思路 解决方法 IoC 优势 DI IoC & DI 使用 IoC 详解 Bean 的存储 Controller&#xff08;控制器存储&#xff09; 获取 bean 对象的其他方法 bean 命名 面试题之 ApplicationContext pk BeanFactory Service&#xff08;服…

视频理解学习笔记

目录 VideoRefer VideoPrism 核心解密&#xff1a;通用视频编码器的力量 VideoRefer VideoRefer 是由浙江大学和阿里达摩院联合推出的视频对象感知与推理技术&#xff0c;增强视频大型语言模型&#xff08;Video LLMs&#xff09;的空间-时间理解能力。简单一点来说就是可以…

P1198题解

题目链接 开题第一件事看数据范围.这里的范围是二十万,支持O(nlogn). 这是一个RMQ问题,同时要加点,我们因此考虑ST表或者线段树.这里用线段树是核弹打蚊子,没有意义,我们因此考虑ST表.我们注意到如果加点操作需要改动ST表原来的东西ST表就会炸掉,我们就要考虑更高级的数据结构…

使用yolov8对视频进行目标检测

使用 Ultralytics 的 YOLO 模型对视频进行逐帧目标检测非常简单&#xff0c;以下是完整的实现方法&#xff1a; 我们的输入视频是这样的 视频目标检测输入视频这里是天津市和平区天津大学附近&#xff0c;感兴趣的小伙伴来天津玩哈&#xff01;&#xff01; 1. 安装依赖 确保已…