统计学习方法---KNN(K近邻)

来源:互联网 发布:人工智能武器 编辑:程序博客网 时间:2024/05/18 03:54

前言

k邻近算法(k-nearest)是一种判别模型,解决分类问题和回归问题,以分类问题为主,在此我们也主要介绍分类问题中的k近邻算法。

k近邻算法的输入为实例的特征向量,对应予特征空间中的点;输出为实例的类别,可以取多类,(前面我们介绍的三种方法主要是解决二分类问题)。

k近邻算法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其k个最近邻的训练实例的类别,通过多数表决等决策方法进行类别预测。因此,k近邻算法不具有显示的学习过程。

k近邻算法实际上利用训练数据集对特征向量空间进行划分,并作为其分类的“模型”。

k值的选择、距离度量以及分类决策规则是k近邻算法的三个基本要素。

算法概述

k近邻算法还是比较容易理解的,在此我们不做具体解释,后面我们只对一些细节进行说明。

直接贴图: 

这里写图片描述

模型

k近邻模型实质上是一个空间划分模型。根据训练样本自身的特征,通过距离公式计算,将训练数据集组成空间整体划分成M个字空间(M为类别数)。利用测试集进行测试评估模型的好快,以调整k的选择或者距离方法的选择。在此,经常使用交叉验证的方法。

距离度量

特征空间中两个实例点的距离是两个实例点相似程度的反映。在此我们介绍一些我们经常用到的一些距离公式:

直接上图: 

这里写图片描述

这里写图片描述

上图表明,不同的距离计算方法,对于同一个点(远点)计算得到的最近邻点是不同的,下面的例子也可以证明:

这里写图片描述

k值的选择

k值的选择会对k近邻算法的结果产生重大影响。

如果k值选择的较小,则会有:

1)优点:“学习”的近似误差会减小,只与输入实例较近的实例才会对预测结果起作用。2)缺点:“学习”的估计误差会增大,预测结果会对近邻的实例点非常敏感,因为k值较小,邻近点比较少(也就是说能够投票的人比较少),所以敏感(投票结果只倚重这几个人,如果这几个人都是一派人士,最终结果可想而知了)。如果邻近的实例点恰巧是噪音,预测就会出错。
  • 1
  • 2
  • 3

k值的减小意味着整体模型变得比较复杂(划分较多),容易发生过拟合。

如果选择的k值较大,则会有:

1)优点:可以减少学习的估计误差,因为能够投票的人数较多,更具有说服力,所以投票结果会比较符合实际情况(预测误差较小)。2)缺点:增大了近似误差。因为投票的人多了,每个人都会有投票误差,所以总误差就会增大。这时候,与输入实例比较远的(不相似的)训练实例也会对预测结果起作用,使得预测发生错误。
  • 1
  • 2
  • 3

k值的增大意味着模型变得简单。如果k=N,那么无论输入什么,都将简单的预测它属于在训练实例中最多的类,(此时,我想到一个词,人多势众)。这个时候,模型过于简单,完全忽略了实例中大量的有用信息,只考虑了类别数量,这是不可取的。

分类决策规则

k近邻算法中的分类决策规则往往是多数表决,即由输入实例的k个近邻的训练实例中的多数类决定输入实例的类别。

这里写图片描述

K近邻算法的实现---kd树

实现k近邻算法的时候,主要考虑的问题是如何对训练数据进行快速k近邻搜索,这点在特征空间的维数大以及训练数据容量大的时候,很重要!

k近邻算法最简单的实现方法是线性扫描(linear scan),这时候需要计算输入实例与每一个训练实例的距离,当训练集很大的时候,计算非常耗时,这种方法是不可行的。

为了提高k近邻搜索的效率,可以考虑使用特殊的结构来存储训练数据,以减少计算距离的次数,具体的方法很多,kd树便是其中的一种,其思路还是比较简单易理解的。我不打算在此复述。

kd树是二叉树,表示对k维空间的一个划分(partition),构造kd树相当于不断地使用垂直于坐标轴的超平面将k维空间切分,构造一系列的k维超矩形区域。kd树的每一个节点对应于一个k维超矩形区域。

直接贴图: 
这里写图片描述
这里写图片描述

给出实例,加深理解:

这里写图片描述
这里写图片描述

最终形成的kd树为: 

这里写图片描述

kd搜索

根据上一部分构造出的kd树,进行搜索获得k近邻节点,利用kd树可以省去对大部分数据点的搜索,不需要一一计算进行比较,从而减少计算量,提高算法效率。

以最近邻为例(k=1),给定一个目标点,搜索其最近邻。首先找到包含目标点的叶节点(最小的包含目标点的超矩形区域);然后从该叶节点出发,依次回退到父节点;不断查找与目标节点最邻近的节点,当确定不可能存在更近的节点时终止。这样搜索就被限制在空间的局部区域上,效率大为提高。

在此直接贴图: 
这里写图片描述
这里写图片描述

给出例子,加深理解:

这里写图片描述

    KNN算法实现

K近邻算法是一种思想极其简单,而分类效果比较优秀的分类算法,最重要的是该算法是很多高级机器学习算分基础,并且在后面我们将要学习的集成算法中,k近邻也经常被用来做基础分类器。它的基本思想我们已经在上节介绍过了,在此我们不在赘述,本节主要讲一下有关它的拓展知识以及实现。

模型:所有的空间划分,判别模型策略:距离最近的k个邻居方法:多数表决(注意,这里没有可计算的优化方法,可能我也没有说清楚,自己体会一下)训练过程:交叉验证的方法,来调整k值得选择,使得最终的预测估计误差最小。
  • 1
  • 2
  • 3
  • 4
  • 5

拓展

  KNN算法思想简单容易实现,但是它是一种没有优化(因为分类决策为多数投票)的暴力方法(线性搜索方法),所以当数据量比较大的时候,算法效率容易达到瓶颈。例如,样本个数为N,特征维数位D的时候,该算法的时间复杂度为O(D*N)。所以,通常情况下,KNN的实现会把训练数据建成Kd-tree(K-dimensional tree,kd-tree搜索方法),构建过程很快,甚至不用计算D为欧氏距离,而搜索速度高达O(D*log(N))。但是建立kd-tree搜索方法也有一个缺点是:当D维度过高的时候,会产生所谓的“维度灾难”,最终的效率会降低到与暴力法一样。 
kd树更适合用于训练实例树远大于空间维数时的k近邻搜索。当空间维数接近于训练实例数的时候,它的效率会迅速下降,几乎接近于线性扫描。

  当维度D>20以后,最好使用更高效率的ball-tree, 其时间复杂度为仍为O(D*log(N))。

  人们经过长期的实践发现,KNN算法适用于样本分类边界不规则的情况。由于KNN主要依靠周围有限的邻近样本,而不是靠判别类域的方法来确定所属类别,因此对于类域的交叉或重叠较多的待分样本集来说,KNN算法比其他方法要更有效。

  该算法在分类时有个主要的不足是,当样本不平衡的时候,如果一个类的样本容量很大,而其他类样本容量很小的时候,有可能导致当输入一个新样本的时候,该样本的K个邻居中大容量类的样本占多数。因此可以采用权值的方法(和该样本距离小的邻居取值大)来改进。 
  该方法的另一个不足之处是计算量比较大,因为对每一个待分类的文本都要计算它到全体已知样本的距离(当然了我们也提到过多次kd-tree的优化算法,可以避免这种情况),才能求得它的K个最近邻。目前常用的解决方法是事先对已知样本点进行剪辑,事先去除对分类作用不大的样本。该算法比较适用于样本容量较大的类域的自动分类,而那些样本容量较小的类域采用这种方法比较容易产生误分。

算法描述

总的来说就是我们已经存在了一个带标签的数据库,然后输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似(最近邻)的分类标签。一般来说,只选择样本数据库中前k个最相似的数据。最后,选择k个最相似数据中出现次数最多的分类。其算法描述如下:

1)计算已知类别数据集中的点与当前点之间的距离;2)按照距离递增次序排序;3)选取与当前点距离最小的k个点;4)确定前k个点所在类别的出现频率;5)返回前k个点出现频率最高的类别作为当前点的预测分类。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
#算法一 调用sklearn中的方法# -*- encoding:utf-8-*-'''调用sklearn中的方法,并且使用sklearn中自己的数据@author:Ada'''print __doc__import numpy as npfrom sklearn import neighbors,datasets#下载数据#64维 1797个样本datas=datasets.load_digits()totalNum=len(datas.data)#1797#print totalNum#print len(datas.data[0])#分割数据集#选出80%样本作为训练集,剩余的20%作为测试集trainNum=int(0.8*totalNum)trainX=datas.data[0:trainNum]trainY=datas.target[0:trainNum]testX=datas.data[trainNum:]testY=datas.target[trainNum:]#设置k值,一般情况下k<20n_neighbors=10#建立分类器clf=neighbors.KNeighborsClassifier(n_neighbors=n_neighbors,weights='uniform',algorithm='auto')#训练分类器clf.fit(trainX,trainY)#利用训练好的分类器进行预测answer=clf.predict(testX)print "误分率为:%.2f%%"%((1-np.sum(answer==testY)/float(len(testY)))*100)#说明:#KNeighborsClassifier可以设置3种算法:‘brute’,‘kd_tree’,‘ball_tree’。#如果不知道用哪个好,设置‘auto’让KNeighborsClassifier自己根据输入去决定。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

关于kd-tree和ball-tree基础参见这里

#方法二:自己实现一个线性KNN算法from numpy import *import operator#create a dataset which coantains 4 samples with 2 classesdef createDataSet():    #create a matrix:each row as a sample    group=array([[1.5,1.4],[1.6,1.5],[0.1,0.2],[0.0,0.1]])# each sample has two features    labels=['A','A','B','B']#four samples with two labels    return group,labels#calssify using KNNdef KNNClassify(newInput,dataSet,labels,k):    numSamples=dataSet.shape[0]#shape[0] stands for the num of row    #Step 1:calculate Euclidean distance    #tile(A,reps):construce an array by repeating A reps times    # eg:       #A=[[1.2,1.0]]       #diff =tile(A,(4,3))#也就是说把A看成一个整体,把A赋值成4行3列       # diff:       #[[ 1.2  1.   1.2  1.   1.2  1. ]       # [ 1.2  1.   1.2  1.   1.2  1. ]       # [ 1.2  1.   1.2  1.   1.2  1. ]       # [ 1.2  1.   1.2  1.   1.2  1. ]]    #the following copy numSamples rows for dataSet    diff =tile(newInput,(numSamples,1))-dataSet#计算测试集与每个训练集的差值    squareDiff=diff**2#差的平方    squareDist=sum(squareDiff,axis=1)#按行求和,也就是对每个样本进行操作计算,平方和    distance=squareDist**0.5    #Step 2: sort the distance by asce    #argsort() returns the distances that would sort an array in a ascending order    sortedDistance=argsort(distance)    classCount={}#定义一个字典(对应到每个样本)    for i in xrange(k):        #Step 3:choose the min k distance        voteLabel=labels[sortedDistance[i]]        # Step 4:count the times labels occur        # when the key voteLabel is not in dictionary classCount ,get()        # will return 0        classCount[voteLabel]=classCount.get(voteLabel,0)+1    # Step 5:the max voted class will return    maxCount=0    for key,value in classCount.items():        if value>maxCount:            maxCount=value            maxIndex=key    return maxIndexif __name__ == '__main__':    dataSet,labels=createDataSet()    testData=array([1.2,1.0])    k=3    predictlabel=KNNClassify(testData,dataSet,labels,k)    print '测试数据为:',testData,'预测结果类别为:',predictlabel    testData=array([0.1,0.3])    predictlabel=KNNClassify(testData,dataSet,labels,k)    print '测试数据为:',testData,'预测结果类别为:',predictlabel
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

实验结果为:

测试数据为: [ 1.2  1. ] 预测结果类别为: A测试数据为: [ 0.1  0.3] 预测结果类别为: B
  • 1
  • 2


原创粉丝点击