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 距离度量方式
计算样本之间的距离时,有不同的距离计算方式,常用是欧式距离,也可以采用更一般的
Lp 距离:Lp(xi,xj)=(∑nk∣∣xki−xkj∣∣p)1p
欧式距离是
欧式距离:
L2(xi,xj)=∑nk∣∣xki−xkj∣∣2−−−−−−−−−−−√
另外,还有曼哈顿距离(也称之为街区距离),也是
曼哈顿距离:
L1(xi,xj)=∑nk∣∣xki−xkj∣∣
若
Lp(xi,xj)=(∑nk∣∣xki−xkj∣∣∞)1∞=maxk∣∣xki−xkj∣∣
选择不同的距离度量方式,最终结果也是不一样的,一般情况下都是选择欧式距离。
1.3 决策规则
决策规则就是指如何利用训练集中离新样本最近的k个样本决定。默认一般都是利用多数投票规则(即选择k个样本所属类别最多的类),因此很容易忽略决策规则的重要性。如果你愿意,你也可以选择其它的决策规则。
1.4 KNN算法求解
通过前面的解释,将KNN算法用代码解释如下:
问题描述
训练数据集(n个样本):
D={(x1,y1),(x2,y2),...,(xn,yn))}
新的样本:x 需要明确新样本
x 的类别
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的类别
根据上面的算法解释,如果训练样本数量很少时,是可行的,线性扫描一次计算消耗的计算量不算太大。但如果训练样本的数据量很大时呢?每个新样本,都需要和所有训练样本计算距离,计算距离后还需要排序,无论是时间复杂度还是空间复杂度都是很高,样本数量很大时,不能够接受这么高的复杂度。那么有没有更加高效的方式呢?这里我有一些个人的见解,比如最小堆(减少排序时间和排序所需要的空间)、训练数据排序等。
最小堆
在前面的算法流程中,保存了样本
对训练数据排序
最初步的想法是,对先对训练数据做排序,对于排好序的训练数据,每次检索时,就可以用二分查找法,就可以找到最近邻的样本,而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维(样本
构建kd树的流程
选择
x(1) 作为初始的划分坐标轴,计算所有训练样本在坐标轴x(1) 下的中位点(奇数个样本,则对应中间位置的数,偶数个样本,则对应中间两个数的平均值),通过该中位点,可以将所有训练样本划分为两部分,该中位点作为根节点,而这两部分则作为左右子节点的划分数据样本对于深度为
j 的节点,选择j(mod)k+1 (根节点的深度为0)作为划分的坐标轴,同样以中位点将数据划分为两部分。以此往复,直到没有样本可以划分了为止。
kd树的会保证每个样本都会占一个节点,即使两个点
2.2在kd树上搜索
kd树的构建是比较简单的,关键在于如何在kd树上搜索,找出k近邻的样本和最近邻的样本。
kd树搜索k近邻
对于新样本
x ,首先找到样本对应的叶节点(即使在kd树上搜索的过程,找了完全匹配的点,也要递归到叶节点)。若样本x 当前维的坐标小于结点的坐标,则移动到左子结点,否则移动到右子结点;将叶结点作为最近邻;
从叶结点开始回退,对回退路上的结点,判断结点和样本
x 的距离(如果还未凑齐k个近邻点,则加入;如果凑齐了,但比k近邻的点更近,那么更新k近邻结果)。
另外对于每个回退结点,还需要判断是否要进入该结点的另外一个子结点搜索,如何判断呢?在k近邻的k个点中,必然存在一个离样本点x 最远的点,那么以它们之间的距离作为判断准则,如果回退结点的切分坐标轴到样本x 的距离大于这个最远距离,那么就没有必要再探索了。一直回退到根节点为止。