首页 > 代码库 > 机器学习系列-K-NearestNeighbo

机器学习系列-K-NearestNeighbo


这是记录自学的过程,目前的理论基础就是:大学高等数学+线性代数+概率论。编程基础:C/C++,python
在观看机器学习实战这本书,慢慢介入。相信有读过以上三门课的人完全可以开始自学机器学习了,当然我上面这三门课学的一般,所以你只知道有这么一个公式或名词,不懂可以百度之深究之。在写这篇文章的时候作者机器学习还没学完,故文章中的错误还请不吝指出。再次声明,系列文章只是分享学习过程,学习点滴,不能保证文章的技术含量。后续再技术的不断完善中,我会重新再捋一遍这个学习过程,纠正其中错误。
目前的学习方法如下:
理论:斯坦福Andrew NG machine learning,系列课程。
代码实战:引用书籍 机器学习实战(python)。
真枪实战: 研究一个案例,建模选择合适的机器学习算法,分析数据。
演变:基于C/C++ 在linux下实现部分机器学习过程。将在学习中期阶段去实现。


一、K近邻算法的概念


 K近邻算法(kNN)属于一种分类的算法,与逻辑回归属于同一种。它的工作原理是:存在一组样本数据,它们的每一个样本的组成由若干个特征和一个既定的label与之对应,label也就是标明了这个样本点属于哪个分类,如果是简单的二元分类,那么label可以假设为1或0,以此类推。如果输入新的数据,而算法的目的就是输出新的数据的标签。这个新的数据将会和样本集合中的每个数据进行对比(求两个样本点之间的距离),对比的结果从小排到大,取前k个对比结果,当中出现次数最多的分类就是该新数据的分类。


近邻算法的优缺点:

1.优点:

精度高,对异常值不敏感,无数据输入的假定

2.缺点:

计算复杂度高,空间复杂度高。

3.使用数据范围:

数值型和标称型。

二、k近邻算法简单例子

伪代码:

1.计算样本集合中的已知点与当前点之间的距离
2.按照距离递增的顺序排序
3.选择排序中与当前点距离最近的前k个样本数据
4.计算出每个分类的样本个数并计算频率
5.返回k个点中出现频率最高的点的分类类别作为当前点的预测分类。

下面看一下这个简单的例子说明是如何求两点之间的欧氏距离的:

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize,1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    sortedDistIndicies = distances.argsort()     
    classCount={}          
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

==深入分析:==

1.dataSetSize = dataSet.shape[0].计算出dataSet 矩阵的行数。
2.diffMat = tile(inX, (dataSetSize,1)) - dataSet 这边主要是将输入数据分别与样本进行相减,这边pyth tile的用法我用自己的理解总结了一下:


上网查了下tile用法的教程,发现看的有点吃力,于是自己亲自研究了一下然后发现可以这理解,与大家分享一下,有什么错误的地方请大家指出:
1.tile的用法总结:

定义:tile(A,R),A和R都可以是数组或者普通单个数据。结果是将A重复R,具体有以下几种机制:

假设:
tile([a1,a2,…,an],[r1,r2,…,rn])=B
得到的结果B的一些属性是由R来决定的,B的第一维数就是r1,第二维数就是r2,…..,B的每一维由重复A数组rn遍的结果组成。当R的长度为1时(类似(1,2)长度2,(1,2,3)长度3,(3)长度为1),此时R控制维数。

例子1.
tile([1,2],2)=
array([1,2],
   [1,2]);

tile([1,2],4)=
array([1,2],
[1,2],
[1,2],
[1,2]);

tile([1,2],(1,2))= //1决定结果只是一维,2决定重复A(1,2)两遍
array([[1,2,1,2]])

tile([1,2],(3,3))= //3决定结果是三维,后面的三决定将重复(1,2)三遍
array([[1,2,1,2,1,2],
[1,2,1,2,1,2],
[1,2,1,2,1,2]])

tile([1,2],(2,2,2)) //第一个2决定结果将是一个2维,第二个2决定2为中的每一维都是一个2维,最后一个2决定最里面的维度的元素将是重复两遍A(1,2)的结果,层层嵌套。

结果:
array([[[1, 2, 1, 2],
    [1, 2, 1, 2]], //这里是维度分界线,注意中括号个数

   [[1, 2, 1, 2],
    [1, 2, 1, 2]]])

以此类推。


3.sqDiffMat = diffMat**2 //把刚才得到的结果求平方  
4.sqDistances = sqDiffMat.sum(axis=1)  
    distances = sqDistances**0.5  //这两句的意思是将求平方后的结果相加,然后结果开方  
5.sortedDistIndicies = distances.argsort()  //把求到的输入数据和每个点之间的距离进行递增排序,argsort返回的是每个数据的index。

6.for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1  
        //总体的作用选取距离最小的K个点。for循环中的第一句:sortedDistIndicies[i]记录着一排index比如(2,3,0,1)  
        //2代表dataSet中的index为2的数据距离输入数据距离最短,以此类推,1代表最长。由于dataSet和labels是对应  
        //的,所以当i从0>>k时,会找到最小的那几个数据所对应的lable值。第二行就进行对这些label进行分类计数,并  
        //保存在classCount中  

7.sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)  
//以上是对一个有多个类型的数据进行排序的函数,总之作用就是把刚才得到的保存有距离最短的k个数据中每一个label出现  
//个数的数组进行从大到小排序,最后结果被保存在sortedClassCount中,第一个元素就是前K个数据中频率最高的label  sortedClassCount[0][0]  ,关于sorted的用法详见附录1.

三、k近邻算法的实际应用-约会网站的预测

背景我就不细细描述了,总之目的就是输入一个数据,这个数据包含三个特征:1.每年获得的飞行常客里程数2.玩视频游戏所耗时间百分比3.每周消费的冰淇淋公升数。通过这三个特征对数据进行分类,当然原理还是计算这个数据和其他数据的距离,不同的是这次是三维空间中的距离了。

1.第一步:解析数据。
我们先看下数据的特点:

技术分享

第一列是飞行里程数,第二列是游戏时间所占百分比,第三列是吃冰淇淋的公升数。第四列是lable值,有三种:不喜欢,魅力一般,极具魅力。书中提供了解析数据的代码,目的就是把这些数据规律性的输入到一个数组中,方便后面处理,具体代码:


def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())         #get the number of lines in the file
    returnMat = zeros((numberOfLines,3))        #prepare matrix to return
    classLabelVector = []                       #prepare labels return   
    fr = open(filename)
    index = 0
    for line in fr.readlines():
        line = line.strip()
        listFromLine = line.split(‘\t‘)
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat,classLabelVector

上面代码运行过程如下:
输入:datingDataMat,dataingLabels = kNN.file2matrix(‘datingTestSet2.txt’) 记住这边参数是datingTestSet2.txt,书本那种会报错误:

==ValueError: invalid literal for int() with base 10: ‘largeDoses’ ==

然后在看看换算的结果:
datingDataMat:

技术分享

label的结果:

技术分享

这样子数据就从文件导成我们需要的格式了,后面就开始用算法来处理这些数据。

2.第二步:归一化数值:
如上图数据所示第一列数据与后面两列差了10的4次方级别,这会导致在求点和点的距离的时候,加减运算时,第二第三列的数据影响效果大大减小,甚至不起作用。所以这边要把所有数据之按照一定的计算方式归一化到0-1之间,这样数据之间的加减运算才会有意义。

下面的公式可以将数据转化到0-1之间:

newValue = http://www.mamicode.com/(oldValue - minValue)/(maxValue - minValue)>

其中minValue和maxValue分别是数据中的最小值和最大值。

用python代码实现如下:

def autoNorm(dataSet):
    minVals = dataSet.min(0) //获得dataSet中每列的最小值保存到minVals
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1)) //oldValue-minValue
    normDataSet = normDataSet/tile(ranges, (m,1))   #element wise divide
    return normDataSet, ranges, minVals

运行结果如下:
技术分享

3.第三步:测试算法

直接撸代码:

def datingClassTest():
    hoRatio = 0.10      #hold out 10%
    datingDataMat,datingLabels = file2matrix(‘datingTestSet2.txt‘)       #load data setfrom file
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print "the total error rate is: %f" % (errorCount/float(numTestVecs))

前面两句主要是从文件加载数据然后再进行归一化转换。m = normMat.shape[0] 算出来的是数据的总个数,后面取出的测试数据的个数就是由 numTestVecs = m*hoRatio ,这边hoRatio为0.1也就是说测试数据取10%。然后将测试数据和已有的数据还有label值分别输入分类器classfy0中,这个函数前面已经分析过。这边输入的第一个参数为待测试的数据:normMat[i,:],这个表达式的意思是取normMat的第i行数据,直到numTestVecs比如:

normMat[0,:]
array([ 0.44832535, 0.39805139, 0.56233353])

normMat[1,:]
array([ 0.15873259, 0.34195467, 0.98724416])

第二个参数:datingLabels[numTestVecs:m],表示一直数据从第numTestVecs行开始取知道结尾,前面的都是待测数据。

下面是运行的结果:

技术分享

可以看到最后的错误率是6.4%,这是一个不错的结果。在此,我们可以通改变hoRotio和变量k的值,来观察错误率的变化情况。

上述只是测试算法,数据直接从现有的数据中取出来的,下面构建完整的算法系统:

下面是完整代码:

def classifyPerson():
    resultList = [‘not at all‘,‘in small doses‘,‘in large doess‘]
    percentTats = float(raw_input("percentage of time spent playing video games?"))
    ffMiles = float(raw_input("frequent flier miles earned per year?"))
    iceCream = float(raw_input("liters of ice cream consumed per year?"))

    datingDataMat,datingLabels = file2matrix(‘datingTestSet2.txt‘)
    normMat,ranges,minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles,percentTats,iceCream])

    classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels,3)

    print "you will probably like this person:",resultList[classifierResult - 1]

第一行是标签数组化,有三个类别。
2-4行 分别要求输入玩游戏占用时间比、飞行里程数、消费冰淇淋公升数。

接下来两行就是将数据从文件中转化出来并归一化保存的过程。

最后就是输入分类器进行分类,返回分类结果。

下面是运行结果:

技术分享

附录1:
sorted
sorted函数
Python内置的排序函数sorted可以对list或者iterator进行排序,官网文档见:http://docs.python.org/2/library/functions.html?highlight=sorted#sorted,该函数原型为:

sorted(iterable[items[], cmp, key, reverse)

参数解释:

(1)iterable指定要排序的list或者iterable,不用多说;

(2)cmp为函数,指定排序时进行比较的函数,可以指定一个函数或者lambda函数,如:

  students为类对象的list,没个成员有三个域,用sorted进行比较时可以自己定cmp函数,例如这里要通过比较第三个数据成员来排序,代码可以这样写:
  students = [(‘john‘, ‘A‘, 15), (‘jane‘, ‘B‘, 12), (‘dave‘, ‘B‘, 10)]
  sorted(students, key=lambda student : student[2])

(3)key为函数,指定取待排序元素的哪一项进行排序,函数用上面的例子来说明,代码如下:
sorted(students, key=lambda student : student[2])

  key指定的lambda函数功能是去元素student的第三个域(即:student[2]),因此sorted排序时,会以students所有元素的第三个域来进行排序。

有了上面的operator.itemgetter函数,也可以用该函数来实现,例如要通过student的第三个域排序,可以这么写:
sorted(students, key=operator.itemgetter(2))
sorted函数也可以进行多级排序,例如要根据第二个域和第三个域进行排序,可以这么写:
sorted(students, key=operator.itemgetter(1,2))

即先跟句第二个域排序,再根据第三个域排序。
(4)reverse参数就不用多说了,是一个bool变量,表示升序还是降序排列,默认为false(升序排列),定义为True时将按降序排列。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    机器学习系列-K-NearestNeighbo