继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

KNN算法和KD树

萌萌小温柔
关注TA
已关注
手记 306
粉丝 56
获赞 401

KNN算法和KD树

KNN算法的思路非常简单,对于新的样本,找出距其最近的k个样本,再根据这k个样本的类别,通过多数投票的方式预测新样本的类别。k近邻算法没有学习或训练过程。但k近邻算法仍有很多值得关注的地方,比如超参数k值的选择、距离的度量方式、决策规则以及快速检索k近邻的算法(kd树等)。

1. KNN算法的三要素

KNN算法的流程非常简单,确定一个KNN算法,明确下来三个基本要素即可。即k值的选择、距离的度量方式和决策规则。

1.1 k值的选择

在KNN算法中k值即表示对于一个新的样本,从训练集中选择和新样本距离最近的k个样本,用这k个样本决定新样本的类别。k值作为一个超参数,很慢明确给出一个合适的值,k取的过大或过小都会导致算法误差增大。一般可以采用留取验证集的方式确定k值的大小。即选择使得验证集准确率最高的k值。

1.2 距离度量方式

计算样本之间的距离时,有不同的距离计算方式,常用是欧式距离,也可以采用更一般的LpLp距离。

LpLp距离:Lp(xi,xj)=(∑nk∣∣xki−xkj∣∣p)1pLp(xi,xj)=(kn|xikxjk|p)1p

欧式距离是LpLp距离的特殊情况,即p=2p=2时:

欧式距离:L2(xi,xj)=∑nk∣∣xki−xkj∣∣2−−−−−−−−−−−√L2(xi,xj)=kn|xikxjk|2

另外,还有曼哈顿距离(也称之为街区距离),也是LpLp距离的特殊情况,即p=1p=1时的情况。

曼哈顿距离:L1(xi,xj)=∑nk∣∣xki−xkj∣∣L1(xi,xj)=kn|xikxjk|

p=∞p=

Lp(xi,xj)=(∑nk∣∣xki−xkj∣∣∞)1∞=maxk∣∣xki−xkj∣∣Lp(xi,xj)=(kn|xikxjk|)1=maxk|xikxjk|

选择不同的距离度量方式,最终结果也是不一样的,一般情况下都是选择欧式距离。

1.3 决策规则

决策规则就是指如何利用训练集中离新样本最近的k个样本决定。默认一般都是利用多数投票规则(即选择k个样本所属类别最多的类),因此很容易忽略决策规则的重要性。如果你愿意,你也可以选择其它的决策规则。

1.4 KNN算法求解

通过前面的解释,将KNN算法用代码解释如下:

问题描述

训练数据集(n个样本):D={(x1,y1),(x2,y2),...,(xn,yn))}D={(x1,y1),(x2,y2),...,(xn,yn))}
  新的样本:xx

需要明确新样本xx的类别

train_data # 训练样本train_label #训练样本的标签类别,标签类别和样本一一对应,即trian_data[i]的标签为train_label[i]distances = []for i in range(0, len(train_data)):
    distances.append(distance(x,train_data[i])) #计算样本x和每个训练样本的距离nn = np.argsort(distances) #对distances进行排序,并返回排序后的索引值y = decision_rule(nn[0:k-1]) #用k近邻和响应的决策规则确定样本x的类别

根据上面的算法解释,如果训练样本数量很少时,是可行的,线性扫描一次计算消耗的计算量不算太大。但如果训练样本的数据量很大时呢?每个新样本,都需要和所有训练样本计算距离,计算距离后还需要排序,无论是时间复杂度还是空间复杂度都是很高,样本数量很大时,不能够接受这么高的复杂度。那么有没有更加高效的方式呢?这里我有一些个人的见解,比如最小堆(减少排序时间和排序所需要的空间)、训练数据排序等。

最小堆

在前面的算法流程中,保存了样本xx和所有训练样本的距离,然后再排序,再取前kk个训练样本做决策。但其实可以维护一个大小为kk的最小堆。对于每次距离的计算,只需要更新该最小堆就好了。即可已节约内存空间(不需要保存所有的距离),也可以减少计算量。

对训练数据排序

最初步的想法是,对先对训练数据做排序,对于排好序的训练数据,每次检索时,就可以用二分查找法,就可以找到最近邻的样本,而k近邻的,就在最近邻的附近。但其实仔细一想,这是没法操作的,依据什么来排序呢?难道根据训练数据到新样本的距离排序?Are you kidding me? 这不就是最原始的算法吗?naive了。

那么实际情况下,应该怎么做呢?常见的是KD树。

2. KD树

前面提了一些我自己的想法改进检索策略的思路,其实KNN算法有更加详细好用的搜索算法,即kd树,kd树表示k-dimensional tree。需要注意的是,kd树中的k和knn中的k含义完全不一样,kd树中的k如英文名中的含义,表示k维。

kd树的想法是先用训练数据构建一颗查找树,我前面对数据进行排序的想法其实也是想干这个,但是我自己想不到这么深。构建了树之后,再对树进行搜索,可以大大节约搜索的时间。用kd树解决KNN问题的主要两点就是:1)构建kd树;2)如何利用kd树搜索。

2.1 构建kd树

kd树的构建比较简单,从1~k维(样本xixi的维度为k)依次将数据集划分为左右两个节点,不断的递归进行划分,直到所有训练样本都被划分了为止。

构建kd树的流程
  1. 选择x(1)x(1)作为初始的划分坐标轴,计算所有训练样本在坐标轴x(1)x(1)下的中位点(奇数个样本,则对应中间位置的数,偶数个样本,则对应中间两个数的平均值),通过该中位点,可以将所有训练样本划分为两部分,该中位点作为根节点,而这两部分则作为左右子节点的划分数据样本

  2. 对于深度为jj的节点,选择j(mod)k+1j(mod)k+1(根节点的深度为0)作为划分的坐标轴,同样以中位点将数据划分为两部分。

  3. 以此往复,直到没有样本可以划分了为止。

kd树的会保证每个样本都会占一个节点,即使两个点x1(3,5),x2(3,10)x1(3,5),x2(3,10)会被同一个切分点划分?

2.2在kd树上搜索

kd树的构建是比较简单的,关键在于如何在kd树上搜索,找出k近邻的样本和最近邻的样本。

kd树搜索k近邻
  1. 对于新样本xx,首先找到样本对应的叶节点(即使在kd树上搜索的过程,找了完全匹配的点,也要递归到叶节点)。若样本xx当前维的坐标小于结点的坐标,则移动到左子结点,否则移动到右子结点;

  2. 将叶结点作为最近邻;

  3. 从叶结点开始回退,对回退路上的结点,判断结点和样本xx的距离(如果还未凑齐k个近邻点,则加入;如果凑齐了,但比k近邻的点更近,那么更新k近邻结果)。
     另外对于每个回退结点,还需要判断是否要进入该结点的另外一个子结点搜索,如何判断呢?在k近邻的k个点中,必然存在一个离样本点xx最远的点,那么以它们之间的距离作为判断准则,如果回退结点的切分坐标轴到样本xx的距离大于这个最远距离,那么就没有必要再探索了。

  4. 一直回退到根节点为止。

原文出处

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP