KNN算法和kd树详解(例子+图示)

KNN算法和kd树详解(例子+图示)一 KNN 算法 KNN K NearestNeigh 算法既可以用于分类 也可用于回归 这里介绍他的分类用法 训练集 一堆拥有标签的 m 维数据 可以表示为 其中 是标签 即所属类别 目标 一个测试数据 x 预测其所属类别 算法 计算测试点 x 与训练集中每一个数据的 距离 将所求的距离进行升序排序 选择前 K 个 在上一步中所得到

一、KNN算法

KNN(K-NearestNeighbor)算法既可以用于分类,也可用于回归。这里介绍他的分类用法。

 

训练集:一堆拥有标签的m维数据,可以表示为:KNN算法和kd树详解(例子+图示)

               其中KNN算法和kd树详解(例子+图示) KNN算法和kd树详解(例子+图示) 是标签,即所属类别。

目标:一个测试数据x,预测其所属类别。

 

算法:

  1. 计算测试点x与训练集中每一个数据的“距离”
  2. 将所求的距离进行升序排序,选择前K个
  3. 在上一步中所得到的K个数据中,根据决策规则(如多数表决)决定x的类别预测结果

 

虽然KNN算法用短短的三步就能概括,但是大有文章可做

 

1、“距离”是啥距离?

话不多说,先摆个公式压压惊

                                                                         KNN算法和kd树详解(例子+图示)

上式表示的是:m维数据KNN算法和kd树详解(例子+图示)KNN算法和kd树详解(例子+图示)的距离KNN算法和kd树详解(例子+图示)就是这么求的。

当p=2时,这个距离就称为欧式距离,是不是很熟悉?

当p=1时,称为曼哈顿距离

根据数据特性的不同,我们可以选择不同的距离来度量。

 

2、K值如何选择

我们一直在谈KNN,那这个K我们该如何选择呢?

K值太小,预测结果会对近邻的训练数据十分敏感,模型过于复杂,易发生过拟合

K值太大,会导致分类结果模糊,模型过于简单。

 

对于k值的选择有这么几种方法:交叉验证、贝叶斯方法、bootstrap。

一般是取个较小值,采用交叉验证法来选取最优的K值。

 

3、决策规则是啥?

一般是选用多数表决规则,即在K个数据中,哪种类别出现的次数最多,这个类别就是x的预测类别。

 

二、kd树

以上说完,我们就要进入实现环节了。那么问题来了,我们上面说的是计算测试点和训练集中的每一个数据的距离,然后进行排序。数据量少的时候完全问题,可是当数据量大的时候,臣妾做不到啊!!!

 

这时候,我们聪明的前辈就提出了kd树(k-dimensional tree)。这是一颗什么样的树呢?

kd树可以帮助我们在很快地找到与测试点最邻近的K个训练点。不再需要计算测试点和训练集中的每一个数据的距离。

kd树是二叉树的一种,是对k维空间的一种分割,不断地用垂直于坐标轴的超平面将k维空间切分,形成k维超矩形区域,kd树的每一个结点对应于一个k维超矩形区域。

 

注意:这里的k维的k表示的是数据的维度,上文中KNN算法和kd树详解(例子+图示)我们称KNN算法和kd树详解(例子+图示)为m维数据。(不要理解为K个训练点的K,你看我甚至把训练点的K大写,维度的k小写

 

kd树的构造

 

首先我们需要构造kd数,构造方法如下:

  1. 选取KNN算法和kd树详解(例子+图示)为坐标轴,以训练集中的所有数据KNN算法和kd树详解(例子+图示)坐标中的中位数作为切分点,将超矩形区域切割成两个子区域。将该切分点作为根结点,由根结点生出深度为1的左右子结点,左节点对应KNN算法和kd树详解(例子+图示)坐标小于切分点,右结点对应KNN算法和kd树详解(例子+图示)坐标大于切分点
  2. 对深度为j的结点,选择KNN算法和kd树详解(例子+图示)为切分坐标轴,KNN算法和kd树详解(例子+图示),以该结点区域中训练数据KNN算法和kd树详解(例子+图示)坐标的中位数作为切分点,将区域分为两个子区域,且生成深度为j+1的左、右子结点。左节点对应KNN算法和kd树详解(例子+图示)坐标小于切分点,右结点对应KNN算法和kd树详解(例子+图示)坐标大于切分点
  3. 重复2,直到两个子区域没有数据时停止。

 

是不是现在还是懵懵懂懂的,甚至上面的构造方法只是一眼扫过。

不慌,有句话叫无图言X,接下来就是关门放图的时候。

我们用图像来走算法!

 

我们有二维数据集

KNN算法和kd树详解(例子+图示)

将他们在坐标系中表示如下:

                                         KNN算法和kd树详解(例子+图示)

 

开始:选择KNN算法和kd树详解(例子+图示)为坐标轴,中位数为6,即(6,5)为切分点,切分整个区域

                                                     KNN算法和kd树详解(例子+图示)          KNN算法和kd树详解(例子+图示)

 

再次划分区域

KNN算法和kd树详解(例子+图示)为坐标轴,选择中位数,可知左边区域为-3,右边区域为-12。所以左边区域切分点为(1,-3),右边区域切分点坐标为(17,-12)

 

                                             KNN算法和kd树详解(例子+图示)

                     KNN算法和kd树详解(例子+图示)

 

 

再次对区域进行切分,同上步,我们可以得到切分点,切分结果如下:

 

                                            KNN算法和kd树详解(例子+图示)

 

 

KNN算法和kd树详解(例子+图示)

 

最后分割的小区域内只剩下一个点或者没有点。我们得到最终的kd树如下图

 

KNN算法和kd树详解(例子+图示)

 

 

kd树完成K近邻的搜索

当我们完成了kd树的构造之后,我们就要想怎么利用kd树完成K近邻的搜索呢???

 

接下来,又是抛出算法的时候了

 

为了方便说明,我们采用二维数据的栗子。假设现在要寻找p点的K个近邻点(p点坐标为(a,b)),也就是离p点最近的K个点。设S是存放这K个点的一个容器。

 

新鲜的算法来了:

  1. 根据p的坐标和kd树的结点向下进行搜索(如果树的结点是以KNN算法和kd树详解(例子+图示)来切分的,那么如果p的KNN算法和kd树详解(例子+图示)坐标小于c,则走左子结点,否则走右子结点)
  2. 到达叶子结点时,将其标记为已访问。如果S中不足k个点,则将该结点加入到S中;如果S不空且当前结点与p点的距离小于S中最长的距离,则用当前结点替换S中离p最远的点
  3. 如果当前结点不是根节点,执行(a);否则,结束算法

(a)回退到当前结点的父结点,此时的结点为当前结点(回退之后的结点)。将当前结点标记为已访问,执行(b)和(c);如果当前结点已经被访过,再次执行(a)。

(b)如果此时S中不足k个点,则将当前结点加入到S中;如果S中已有k个点,且当前结点与p点的距离小于S中最长距离,则用当前结点替换S中距离最远的点。

(c)计算p点和当前结点切分线的距离。如果该距离大于等于S中距离p最远的距离并且S中已有k个点,执行3;如果该距离小于S中最远的距离或S中没有k个点,从当前结点的另一子节点开始执行1;如果当前结点没有另一子结点,执行3。

以上的1,2,3我们会称为算法中的1,算法中的2,算法中的3

老规矩,上图!

 

为了方便描述,我对结点进行了命名,如下图。

蓝色斜线表示该结点标记为已访问,红色下划线表示在此步确定的下一要访问的结点

 

我们现在就计算p(-1,-5)的3个邻近点。

 

                                         KNN算法和kd树详解(例子+图示)

 

KNN算法和kd树详解(例子+图示)

 

我们拿着(-1,-5)寻找kd树的叶子结点。

 

执行算法中的1。

  •  p点的-1与结点A的x轴坐标6比较,-1<6,向左走。
  •  p点的-5与结点B的y轴坐标-3比较,较小,往左走。
  •  因为结点C只有一个子结点,所以不需要进行比较,直接走到结点H。

 

进行算法中的2,标记结点H已访问,将结点H加入到S中。

此时                                                                          KNN算法和kd树详解(例子+图示)

                               KNN算法和kd树详解(例子+图示)KNN算法和kd树详解(例子+图示)

 

执行算法中的3,当前结点H不是根结点

  • 执行(a),回退到父结点C,我们将结点C标记为已访问
  • 执行(b),S中不足3个点,将结点C加入到S中
  • 执行(c)计算p点和结点C切分线的距离,可是结点C没有另一个分支,我们开始执行算法中的3。

                                                                KNN算法和kd树详解(例子+图示)

                                       KNN算法和kd树详解(例子+图示)KNN算法和kd树详解(例子+图示)

 

当前结点C不是根结点

  • 执行(a),回退到父结点B,我们将结点B标记为已访问
  • 执行(b),S中不足3个点,将结点B加入到S中
  • 执行(c)计算p点和结点B切分线的距离,两者距离为KNN算法和kd树详解(例子+图示)小于S中的最大距离。(S中的三个点与p的距离分别为KNN算法和kd树详解(例子+图示)KNN算法和kd树详解(例子+图示)KNN算法和kd树详解(例子+图示))。所以我们需要从结点B的另一子节点D开始算法中的1。

 

                                                                                        KNN算法和kd树详解(例子+图示)

 

                                         KNN算法和kd树详解(例子+图示)

                                          KNN算法和kd树详解(例子+图示)

 

KNN算法和kd树详解(例子+图示)

 

从结点D开始算法中的1

  • p点的-1与结点D的x轴坐标-2比较,-1 >  -2,向右走。
  • 找到了叶子结点J,标记为已访问。

开始算法中的2

  • S不空,计算当前结点J与p点的距离,为18.2,大于S中的最长距离
  • 所以我们不将结点J放入S中

                                                                                      KNN算法和kd树详解(例子+图示)

KNN算法和kd树详解(例子+图示)

 

执行算法中的3,当前结点J不为根结点

  • 执行(a),回退到父结点D,标记为已访问。
  • 执行(b),S中已经有3个点,当前结点D与p点距离为KNN算法和kd树详解(例子+图示),小于S中的最长距离(结点H与p点的距离),将结点D替换结点H。
  • 执行(c),计算p点和结点D切分线的距离,两者距离为1,小于S中最长距离,所以我们需要从结点D的另一子节点I开始算法中的1。

                                                                                          KNN算法和kd树详解(例子+图示)

KNN算法和kd树详解(例子+图示)

 

                            KNN算法和kd树详解(例子+图示)

                                          KNN算法和kd树详解(例子+图示)

 

从结点I开始算法中的1,结点I已经是叶子结点

直接进行到算法中的2

  • 标记结点I为已访问
  • 计算当前结点I和p点的距离为KNN算法和kd树详解(例子+图示),大于S中最长距离,不进行替换。

 

                                                                                           KNN算法和kd树详解(例子+图示)

 

KNN算法和kd树详解(例子+图示)

执行算法中的3.

当前结点I不是根结点

  • 执行(a),回退到父结点D,但当前结点D已经被访问过。
  • 再次执行(a),回退到结点D的父结点B,也标记为访问过
  • 再次执行(a),回退到结点B的父结点A,结点A未被访问过,标记为已访问。
  • 执行(b),结点A和p点的距离为KNN算法和kd树详解(例子+图示),大于S中的最长距离,不进行替换
  • 执行(c),p点和结点A切分线的距离为7,大于S中的最长距离,不进行替换

                                                                                      KNN算法和kd树详解(例子+图示)

KNN算法和kd树详解(例子+图示)

                                          KNN算法和kd树详解(例子+图示)

 

执行算法中的3,发现当前结点A是根结点,结束算法。

得到p点的3个邻近点,为(-6,-5)、(1,-3)、(-2,-1)

 

kd树就这么的完成了他的任务。

总的来说,就是以下几步

1、找到叶子结点,看能不能加入到S中

2、回退到父结点,看父结点能不能加入到S中

3、看目标点和回退到的父结点切分线的距离,判断另一子结点能不能加入到S中

 

有错误之处还请大家帮忙指正!

 

参考:

https://cloud.tencent.com/developer/news/

https://zhuanlan.zhihu.com/p/

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207790.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午1:06
下一篇 2026年3月19日 下午1:06


相关推荐

  • method_exists函数

    method_exists函数
    method_exists(mixed$object,string$method_name)—Checksiftheclassmethodexists
    确认$object类中是否存在$method_name的方法。如果存在返回TRUE;如果不存在返回FALSE.

    2022年7月15日
    22
  • leetcode – Populating Next Right Pointers in Each Node II

    leetcode – Populating Next Right Pointers in Each Node II

    2022年1月20日
    47
  • tar 打包的时候如何去掉目录前缀

    tar 打包的时候如何去掉目录前缀文章转载自:freefly的博客,对原作者表示感谢。问题:tarczfxx.tgz/xxx/xxx/A然后希望xx.tgz或xx.tar.gz里面就直接A这个目录不希望加前导xxx/xxx,我知道可以先cp这个目录到同一个目录再打包,不过想知道可以不可以不用另外cp到同一个目录 答案1:使用-C指定相对路径,如:tarczfx

    2022年5月6日
    83
  • 无法启动IIS服务解决办法

    无法启动IIS服务解决办法无法启动 IIS 服务启动 iis 报错如下图错误 nbsp 解决办法 nbsp 原因可能是 World nbsp Wide nbsp Web nbsp Publishing nbsp Service 服务停止了 需要开启服务 如下图 找到 World nbsp Wide nbsp Web nbsp Publishing nbsp Service 服务 右键启动 然后在重启 IIS 就 ok 了 nbsp 注 请使用 administrato 身份

    2026年3月18日
    2
  • js获取当前时间,返回字符格式「建议收藏」

    js获取当前时间,返回字符格式「建议收藏」js获取当前时间,返回字符格式

    2025年12月3日
    8
  • java高并发 pdf_Java高并发编程详解 PDF 下载

    java高并发 pdf_Java高并发编程详解 PDF 下载推荐序一推荐序二推荐序三推荐序四前言第一部分多线程基础第1章快速认识线程1.1线程的介绍1.2快速创建并启动一个线程1.3线程的生命周期详解1.4线程的start方法剖析:模板设计模式在Thread中的应用1.5Runnable接口的引入以及策略模式在Thread中的使用1.6本章总结第2章深入理解Thread构造函数2.1线程的命名2.2线程的父子关系2.3Thread与…

    2022年5月12日
    50

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号