近邻法(
-nearest neighbor,
-NN)是一种基本的分类和回归方法;
输入为实例的特征向量,对应特征空间的点;
输出为实例的类别,可以取多类。
近邻法假设给定一个训练数据集,其中实例类别已定。分类时,对新的实例,根据其
个最近邻的训练实例的类别,通过多数表决等方式进行预测。
近邻法不具有显示学习过程,
近邻法实际上利用训练数据集对特征向量空间进行划分,并作为其分类的“模型”。
值的选择,距离度量及分类决策规则是
近邻法的三个基本要素。
1 k近邻算法
算法1(近邻法)
输入:训练数据集
其中,为实例的特征向量,
为实例的类别,
;实例特征向量
输出:实例所属的类
.
- 根据给定的距离度量,在训练集
中找出与
最邻近的
个点,涵盖这
个点的
的邻域记作
;
- 在
中根据分类觉此规则(如多数表决)决定
的类别
:
为指示函数,当
时,
为1,否则
为0;
邻近法的特殊情况是
情形,称为最近邻算法。对于输入
,最近邻法将训练数据集中与
最近邻点的类作为
的类。
2 K近邻模型
近邻算法对应的模型实际上就是对特征空间的划分;
由三个基本要素:
距离度量,值选择,分类决策规则;
2.1 模型
训练实例集完成空间的划分;

2.2 距离度量
特征空间中两个实例点的距离是两个实例点相似程度的反映。
近邻模型的特征空间一般是
维实数向量空间
。使用的距离为欧氏距离,但也可以是其他距离。
各种距离介绍:
设特征空间是
维实数向量空间
,
,
,
,
的
距离定义为:
当时,称为欧氏距离(Euclidean distance),即:
当时,称为曼哈顿距离(Manhattan distance),即:
当时,为坐标距离的最大值,即:
2.3 k值的选择
值较小,学习近似误差会减小,估计误差会增大,容易出现过拟合;
值较大,学习近似误差会减大,估计误差会减小,模型过于简单;
- 近似误差:approximation error
- 估计误差:estimation error
2.4 分类决策规则
略;
3 k近邻法的实现:kd树
算法实现时,主要考虑的问题是如何对训练数据进行快速
近邻搜索,这点在特征空间的维数大及训练数据容量大时尤为重要;
最简单的实现方法:线性扫描(linear scan),计算输入实例与每一个训练实例的距离,训练集大时,计算非常耗时;
为提高效率,考虑使用特殊的结构存储训练数据,以减少计算距离的次数;树属于这种方法;
3.1 构造树算法描述
算法2:(构造平衡树)
输入:维空间数据集
输出:树
-
开始:构造根节点,根节点对应于包含
的
维空间的超矩形区域;
选择作为坐标轴,以
中所有实例的
坐标的中位数为切分点,将根结点对应的超矩形区域切分为两个子区域。切分由通过切分点并与坐标轴
垂直的超平面实现。
由根结点生成深度为1的左、右子结点:左子结点对应坐标小于切分点的子区域,右子结点对应于坐标
大于切分点的子区域。
将落在切分超平面上的实例点保存到根结点; -
重复:对深度为
的结点,选择
为切分的坐标轴,
,以该结点的区域中所有实例的
坐标的中位数为切分点,将该结点对应的巨型区域切分为两个子区域,切分由通过切分点并与坐标轴
垂直的超平面实现。
由该结点生成深度为的左,右子结点:左子结点对应坐标
小于切分点的子区域,右子结点对应坐标
大于切分点的子区域。
将落在七分超平面上的实例点保存在该节点; - 直到连个子区域没有实例存在时停止。从而形成
树区域划分。
3.2 构造树算法实现

#kd树算法举例
#the input
a<-data.frame(x=c(2,5,9,4,8,7),y=c(3,4,6,7,1,2))
attach(a)
#the output
t_list<-list()
#the initial varables
j<-0
k<-2
#the median cacuation is different w/ the standard function
my_median=function(v){
t<-median(v)
v_n<-sort(v)
for(i in v_n) {
if(i>=t){
r<-i
return(r)
break
}
}
}
#the kd tree function
kdtree=function(d,j) {
dim <-j%%k+1 #dimension
bl <-numeric()
bg <-numeric()
x <-d[,dim]
m <-my_median(x)
t_lst<-list()
for(i in 1:length(x)){
if(x[i]==m){
t_lst[[length(t_lst)+1]]<-d[i,]
}
else if(x[i]<m){
bl<-append(bl,i)
}
else {
bg<-append(bg,i)
}
}
if(length(x)==1){
return(t_lst)
}
bl_df<-d[bl,]
bg_df<-d[bg,]
#cat(j,m,"bl=","\n")
#print(bl_df)
#cat(j,m,"bg=","\n")
#print(bg_df)
#cat("\n")
j<-j+1
dim<-dim+1
if(length(bg)==0 & length(bl)==0) {
return(t_lst)
}
else if(length(bg)==0) {
bl_lst<-kdtree(bl_df,j)
bg_lst<-list()
}
else if(length(bl)==0) {
bl_lst<-list()
bg_lst<-kdtree(bg_df,j)
}
else {
bl_lst<-kdtree(bl_df,j)
bg_lst<-kdtree(bg_df,j)
}
t_lst[[length(t_lst)+1]]<-bl_lst
t_lst[[length(t_lst)+1]]<-bg_lst
return(t_lst)
}
res<-kdtree(a,0)
res
代码存储的数据结构如下:
- 每一个树结构都分为结点+左侧树+右侧树;
- 每一侧树又分为结点+左侧树+右侧树;
- 最后一级只有一个结点;

3.3 搜索树算法描述
算法3:(用kd树的最近邻搜索)
输入:已构造的树;目标点
;
输出:的最近邻。
- 在
树中找到包含目标点
的叶节点:从根节点出发,递归地向下访问
树,若目标点
当前维的坐标小于切分点的坐标,则移动到左子节点,否则为右,直到子结点为叶节点为止。
- 以此叶节点为“当前最近点”。
- 递归地往上回退,在每个结点进行如下操作:
- 如果该结点保存的实例点比当前最近点距离目标点更近,则以该实例点为“当前最近点”。
- 当前最近点一定存在与该结点一个子结点对应的区域,检查该子结点的父结点的另一个子节点对应的区域是否有更近的点。(检查另一子结点对应的区域是否与目标点为球心,以目标点与“当前最近点”间距离为半径的超球体相交)
- 相交:移动到另一结点,递归进行最近邻搜索;
- 不交:向上回退。
- 当回退到根结点时,搜索结束。最后的“当前最近点”即为
的最近邻点。
树平均计算复杂度是
,适合训练实例数远大于空间维数的搜索,当维数与训练实例数接近时,效率迅速下降,几乎接近线性扫描;
3.4 搜索算法实现
编码略去了(累了。。。。。),思路如下:
- 寻找最近点(直接找到了更好,如相同点)。
- 根据最近点回退,更新最近点,直到更新到根结点结束;