机器学习之KNN(K近邻)

来源:互联网 发布:在手机淘宝上怎么退货 编辑:程序博客网 时间:2024/06/05 09:45

简介

k近邻法(K-NN)是一种基本的分类与回归方法。分类时,对新的实例,根据k个最近邻的训练实例的类别,通过多数表决等方式进行预测。k近邻的三要素:k值的选择、距离度量以及分类决策规则。k近邻算法简单、直观,给定一个训练数据集,对于输入的实例,在训练数据集中找到与该实例最近邻的k个实例,这k个实例多数属于某个类,就把该输入实例氛围这个类。


k-近邻模型

k值的选择

k值减小以为这整体模型变得复杂,容易发生过拟合;
k值增大意味着模型变得简单,当k=N的时候,模型过于简单,完全忽略训练实例中的大量有用信息,不可取
k值一般去一个比较小的数值

距离度量:

通用(()^p+()^p+()^p)^(1/p)
当p=2是欧式距离
p=1是曼哈顿距离
p=无穷大,各个坐标距离的最大值

分类决策规则:

误分类概率P(y!=f(x))=1-P(y=f(x))


KNN应用

概述:

优点:精度高,对异常不敏感,无数据输入假定
缺点:计算复杂度高,空间复杂度高

举例:

kd tree:

数据集T=(x1,x2,x3,....xN)
其中xi=(a1,a2,a3....ak)
kd树构造:
1、开始构造根节点,根节点对应包含数据集的k维空间超矩形区域
      选择a1作为坐标轴,以数据集中所有实例的a1坐标的中位数作为切分点,将根节点对应的超矩形区域切分为两个子区域。切分由通过且分店并与坐标轴x垂直的超平面实现;由根节点生成深度为1的左右子节点,左子节点对应坐标a1小于切分点的子区域,右子节点对应坐标a1大于切分点的子区域。将落在切分超平面上的实例点保存在根节点;
2、重复:对于深度为j的节点,选择al作为切分的坐标轴,l= j mod k +1,以数据集中所有实例的al坐标的中位数为切分点,将该节点对应的超巨型区域分为两个子区域,切分有通过且分店并与坐标轴al垂直的超平面实现
      

3、直到两个子区域没有实例存在时停止

    先以一个简单直观的实例来介绍k-d树算法。假设有6个二维数据点{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},数据点位于二维空间内(如图2中黑点所示)。k-d树算法就是要确定图2中这些分割空间的分割线(多维空间即为分割平面,一般为超平面)。下面就要通过一步步展示k-d树是如何确定这些分割线的。


  1. 算法:构建k-d树(createKDTree)  
  2. 输入:数据点集Data-set和其所在的空间Range  
  3. 输出:Kd,类型为k-d tree  
  4. 1.If Data-set为空,则返回空的k-d tree  
  5. 2.调用节点生成程序:  
  6.   (1)确定split域:对于所有描述子数据(特征矢量),统计它们在每个维上的数据方差。假设每条数据记录为64维,可计算64个方差。挑选出最大值,对应的维就是split域的值。数据方差大表明沿该坐标轴方向上的数据分散得比较开,在这个方向上进行数据分割有较好的分辨率;  
  7.   (2)确定Node-data域:数据点集Data-set按其第split域的值排序。位于正中间的那个数据点被选为Node-data。此时新的Data-set' = Data-set \ Node-data(除去其中Node-data这一点)。  
  8. 3.dataleft = {d属于Data-set' && d[split] ≤ Node-data[split]}  
  9.    Left_Range = {Range && dataleft}  
  10.   dataright = {d属于Data-set' && d[split] > Node-data[split]}  
  11.    Right_Range = {Range && dataright}  
  12. 4.left = 由(dataleft,Left_Range)建立的k-d tree,即递归调用createKDTree(dataleft,Left_Range)。并设置left的parent域为Kd;  
  13.    right = 由(dataright,Right_Range)建立的k-d tree,即调用createKDTree(dataleft,Left_Range)。并设置right的parent域为Kd。

  以上述举的实例来看,过程如下:

        由于此例简单,数据维度只有2维,所以可以简单地给x,y两个方向轴编号为0,1,也即split={0,1}。

        (1)确定split域的首先该取的值。分别计算x,y方向上数据的方差得知x方向上的方差最大,所以split域值首先取0,也就是x轴方向;

         (2)确定Node-data的域值。根据x轴方向的值2,5,9,4,8,7排序选出中值为7,所以Node-data = (7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于split = 0(x轴)的直线x = 7;

         (3)确定左子空间和右子空间。分割超平面x = 7将整个空间分为两部分,如下图所示。x <= 7的部分为左子空间,包含3个节点{(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点{(9,6),(8,1)}。


         如算法所述,k-d树的构建是一个递归的过程。然后对左子空间和右子空间内的数据重复根节点的过程就可以得到下一级子节点(5,4)和(9,6)(也就是左右子空间的'根'节点),同时将空间和数据集进一步细分。如此反复直到空间中只包含一个数据点,如下图所示。最后生成的k-d树如下图所示。

搜索kdtree

   在k-d树中进行数据的查找也是特征匹配的重要环节,其目的是检索在k-d树中与查询点距离最近的数据点。这里先以一个简单的实例来描述最邻近查找的基本思路。

        星号表示要查询的点(2.1,3.1)。通过二叉搜索,顺着搜索路径很快就能找到最邻近的近似点,也就是叶子节点(2,3)。而找到的叶子节点并不一定就是最邻近的,最邻近肯定距离查询点更近,应该位于以查询点为圆心且通过叶子节点的圆域内。为了找到真正的最近邻,还需要进行'回溯'操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。此例中先从(7,2)点开始进行二叉查找,然后到达(5,4),最后到达(2,3),此时搜索路径中的节点为小于(7,2)和(5,4),大于(2,3),首先以(2,3)作为当前最近邻点,计算其到查询点(2.1,3.1)的距离为0.1414,然后回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查询点更近的数据点。以(2.1,3.1)为圆心,以0.1414为半径画圆,如下图所示。发现该圆并不和超平面y = 4交割,因此不用进入(5,4)节点右子空间中去搜索。


         再回溯到(7,2),以(2.1,3.1)为圆心,以0.1414为半径的圆更不会与x = 7超平面交割,因此不用进入(7,2)右子空间进行查找。至此,搜索路径中的节点已经全部回溯完,结束整个搜索,返回最近邻点(2,3),最近距离为0.1414。

          一个复杂点了例子如查找点为(2,4.5)。同样先进行二叉查找,先从(7,2)查找到(5,4)节点,在进行查找时是由y = 4为分割超平面的,由于查找点为y值为4.5,因此进入右子空间查找到(4,7),形成搜索路径<(7,2),(5,4),(4,7)>,取(4,7)为当前最近邻点,计算其与目标查找点的距离为3.202。然后回溯到(5,4),计算其与查找点之间的距离为3.041。以(2,4.5)为圆心,以3.041为半径作圆,如下图左所示。可见该圆和y = 4超平面交割,所以需要进入(5,4)左子空间进行查找。此时需将(2,3)节点加入搜索路径中得<(7,2),(2,3)>。回溯至(2,3)叶子节点,(2,3)距离(2,4.5)比(5,4)要近,所以最近邻点更新为(2,3),最近距离更新为1.5。回溯至(7,2),以(2,4.5)为圆心1.5为半径作圆,并不和x = 7分割超平面交割,如下图右所示。至此,搜索路径回溯完。返回最近邻点(2,3),最近距离1.5。


  1. 算法:k-d树最邻近查找  
  2. 输入:Kd,    //k-d tree类型  
  3.      target  //查询数据点  
  4. 输出:nearest, //最邻近数据点  
  5.      dist      //最邻近数据点和查询点间的距离  
  6. 1. If Kd为NULL,则设dist为infinite并返回  
  7. 2. //进行二叉查找,生成搜索路径  
  8.    Kd_point = &Kd;                   //Kd-point中保存k-d tree根节点地址  
  9.    nearest = Kd_point -> Node-data;  //初始化最近邻点  
  10.    while(Kd_point)  
  11.      push(Kd_point)到search_path中; //search_path是一个堆栈结构,存储着搜索路径节点指针  
  12.  /*** If Dist(nearest,target) > Dist(Kd_point -> Node-data,target) 
  13.        nearest  = Kd_point -> Node-data;    //更新最近邻点 
  14.        Max_dist = Dist(Kd_point,target);  //更新最近邻点与查询点间的距离  ***/  
  15.      s = Kd_point -> split;                       //确定待分割的方向  
  16.      If target[s] <= Kd_point -> Node-data[s]     //进行二叉查找  
  17.        Kd_point = Kd_point -> left;  
  18.      else  
  19.        Kd_point = Kd_point ->right;  
  20.    nearest = search_path中最后一个叶子节点; //注意:二叉搜索时不比计算选择搜索路径中的最邻近点,这部分已被注释  
  21.    Max_dist = Dist(nearest,target);    //直接取最后叶子节点作为回溯前的初始最近邻点  
  22.   
  23. 3. //回溯查找  
  24.    while(search_path != NULL)  
  25.      back_point = 从search_path取出一个节点指针;   //从search_path堆栈弹栈  
  26.      s = back_point -> split;                   //确定分割方向  
  27.      If Dist(target[s],back_point -> Node-data[s]) < Max_dist   //判断还需进入的子空间  
  28.        If target[s] <= back_point -> Node-data[s]  
  29.          Kd_point = back_point -> right;  //如果target位于左子空间,就应进入右子空间  
  30.        else  
  31.          Kd_point = back_point -> left;    //如果target位于右子空间,就应进入左子空间  
  32.        将Kd_point压入search_path堆栈;  
  33.      If Dist(nearest,target) > Dist(Kd_Point -> Node-data,target)  
  34.        nearest  = Kd_point -> Node-data;                 //更新最近邻点  
  35.        Min_dist = Dist(Kd_point -> Node-data,target);  //更新最近邻点与查询点间的距离 


0 0
原创粉丝点击