zoukankan      html  css  js  c++  java
  • 【机器学习实践(2)】K近邻(KNN)模型

    根据machine learing in action 第二章改编

    machine learing in action 是一本介绍机器学习实例的书,书中大量使用了scipy系列库,像matlab一样使用python。对我们学习python科学计算和理解机器学习都有很大的帮助。

    本文根据其第二章内容改编,原作代码有一些问题,这里的代码都是作者重新写的。

    转载请注明出处: 本文来自数据火花 http://blog.csdn.net/dataspark

    1. 理论基础

    K近邻模型,也常被叫做K最邻近结点算法。它是直接拿已经标注的数据做模型的一种统计学习方法(即,不需要额外的训练过程)。

    对于待分类的样本点,在已经标注的数据集合中,找到与目标样本点最近的K个(K通常小于20)点,用K个点的标注类别来投票,得票最多的标注类别即作为目标点的分类结果。


    2. 用ptyhon实现简单的KNN


    2.1待处理数据及其格式

    这次实验,需要处理的数据可以在这里 获取http://pan.baidu.com/share/link?shareid=3079075397&uk=939810364
    数据的格式为:每一行为一个已经标注的样本,每个样本有3个特征,前三列是各维特征的值,第四列是样本的标注结果。

    2.2 python实现

    需要引入的库:

    from numpy import *
    import numpy
    import operator


    2.2.1 文件读取

    定义函数,读取数据文件:

    def read_data(fileName, dim):
        f = open(fileName, 'r')
        originalData = f.readlines()
        f.close()
        rows = len(originalData)
        index = 0
        labelVec = []
        multiArray = zeros((rows, dim))
      
        for line in originalData:
            line = line.strip()
            lineElems = line.split('	')
            multiArray[index, :] = lineElems[0:dim]
            labelVec.append(int(lineElems[-1]))   #without 'int', appended is str
            index += 1
        return multiArray, labelVec


    注释1.1:第13行需要注意,必须加上int强制类型转换。由于一行各元素是用字符串切割的方法得到的,元素本身是字符串形式的。

    注释1.2:其中函数 zeros((m,n))生成 m*n大小的数组,数组元素全为0;相似的有ones((m,n))

    注意函数参数是元组,zeros(m,n)是错误的。

    注释1.3:多维数组的元素操作,多维数组的元素操作:multiArray[index, :] ,取第index行的元素(该元素仍是个数组)。

    NumPy也允许你使用“点”像b[i,...]。 点(…)代表许多产生一个完整的索引元组必要的分号。如果x是秩为5的数组(即它有5个轴),那么: x[1,2,…] 等同于 x[1,2,:,:,:], x[…,3] 等同于 x[:,:,:,:,3] x[4,…,5,:] 等同 x[4,:,:,5,:]


    2.2.2 分类模型

    定义KNN分类函数:

    参数inX 表示输入的待分类样本点特征, dataSet是已经标注好的样本点特征集合, labelVec是标注的类别标签, k表示取多少个近邻。

    def knn_classify(inX, dataSet, labelVec, k):
        sampleCnt = dataSet.shape[0]
        expandX = tile(inX, (sampleCnt, 1))
        diffArray = expandX - dataSet
        sqArray = diffArray ** 2
        sumArray = sqArray.sum(axis = 1)
        distArray = sumArray ** 0.5
        sortedIndices = distArray.argsort()
        labelCnt = {}
        for i in range(k):
            label = labelVec[sortedIndices[i]]
            labelCnt[label] = labelCnt.get(label, 0) + 1
        sortedLabelCnt = sorted(labelCnt.iteritems(), key = operator.itemgetter(1), reverse=True)
        return sortedLabelCnt[0][0] 

    注2.1:tile函数的作用是对数组进行扩展,tile(a, (x,y)),将a横向复制y次,再纵向复制x次,使原数组的元素个数扩大到x*y倍

    注2.2:array的shape成员变量,值是tuple,反应了数组的结构(各维的大小)

    注2.3:sqArray.sum(), 不指定参数时,以每一个元素为单元,计算总和;指定axis,当axis=0时,按竖方向(以行为单元)求和;axis=1时,按横方向(以列为单元)求和。

    相似的有 array.min(), array.max()函数

    注2.4:array.argsort(),对数组本身不做排序,排序的结果是数组下标的集合

    注2.5:sorted()函数对字典进行排序,必要的参数是字典的迭代项 dict.iteritems(), key指定排序的依据。

    这个函数是典型的科学计算思维,有些地方和面向对象思维相悖:比如,样本点的特征和标注类别,没有放在一个类里,而是分成一个array和list,靠下标联系。这样做的主要原因是:为了更好的应用python科学计算中的数组操作。


    当有一个目标样本点inX输入时,调用该函数可以得到结果。

    dataSet, labelVec = read_data(dataFile, dim)
    l = knn_classify((0.3,0.4,0.5), dataSet, labelVec, 5)


    2.2.3 效果检验

    交叉验证KNN模型在测试集(测试集下载地址)上的效果:

    def cross_validate(crossTimes, dataFile, dim, k):
        dataSet, labelVec = read_data(dataFile, dim)
        dataSet, mins, ranges = autoNorm(dataSet)
        sampleCnt = dataSet.shape[0]
        testSetCnt = sampleCnt / crossTimes
        errorCnt = 0
        for i in range(crossTimes):
            testSet = dataSet[i * testSetCnt : i * testSetCnt + testSetCnt]
            trainSet =  zeros((sampleCnt - testSetCnt, dim))
            testLabelVec = labelVec[i * testSetCnt : i * testSetCnt + testSetCnt]
            trainLabelVec = labelVec[:i * testSetCnt] + labelVec[i * testSetCnt + testSetCnt:]
            trainSet[0: i * testSetCnt, :]  = dataSet[0: i * testSetCnt, :]
            trainSet[i * testSetCnt:] = dataSet[i * testSetCnt + testSetCnt:]
            t = array(list(dataSet[0: i * testSetCnt, :]) + list(dataSet[i * testSetCnt + testSetCnt:]))
            print t == trainSet
            for j in range(testSetCnt):
                l = knn_classify(testSet[j], trainSet, trainLabelVec, k)
                if l != testLabelVec[j]:
                    print "estimated:%d, real:%d" % (l, testLabelVec[j])
                    errorCnt += 1
        print "precision:%f" % ((sampleCnt - errorCnt * 1.0) / sampleCnt)
    

    注3.1:上面的函数中, trainSet是由dataSet分割后再组合而成,将两个数组组合到一起,可以通过强制转换array为list,再回转成array来做:

    def mergeArray(array1, array2):
        merged = array(list(array1) + list(array2))
        return merged


    用以下方式调用交叉验证函数时(9次交叉验证,K取值5):

    cross_validate(9, r"knn_data.txt", 3, 5)

    发现准确率只有 0.785000


    2.2.4 效果改进

    观察数据发现,各维特征的大小很大均衡,归一化能有效减小各维尺度差异带来的问题:

    def autoNorm(dataSet):
        minVals = dataSet.min(0)
        maxVals = dataSet.max(0)
        ranges = maxVals - minVals
        sampleCnt = dataSet.shape[0]
        expandMinus = tile(minVals, (sampleCnt, 1))
        expandDivider = tile(ranges, (sampleCnt, 1))
        normDataSet = (dataSet - expandMinus) / expandDivider
        return normDataSet, minVals, ranges


    在交叉验证函数中,加入归一化:

    def cross_validate(crossTimes, dataFile, dim, k):
        dataSet, labelVec = read_data(dataFile, dim)
        dataSet, mins, ranges = autoNorm(dataSet)
        sampleCnt = dataSet.shape[0]
        testSetCnt = sampleCnt / crossTimes
        errorCnt = 0
        for i in range(crossTimes):
            testSet = dataSet[i * testSetCnt : i * testSetCnt + testSetCnt]
            trainSet =  zeros((sampleCnt - testSetCnt, dim))
            testLabelVec = labelVec[i * testSetCnt : i * testSetCnt + testSetCnt]
            trainLabelVec = labelVec[:i * testSetCnt] + labelVec[i * testSetCnt + testSetCnt:]
            trainSet[0: i * testSetCnt, :]  = dataSet[0: i * testSetCnt, :]
            trainSet[i * testSetCnt:] = dataSet[i * testSetCnt + testSetCnt:]
            t = array(list(dataSet[0: i * testSetCnt, :]) + list(dataSet[i * testSetCnt + testSetCnt:]))
            print t == trainSet
            for j in range(testSetCnt):
                l = knn_classify(testSet[j], trainSet, trainLabelVec, k)
                if l != testLabelVec[j]:
                    print "estimated:%d, real:%d" % (l, testLabelVec[j])
                    errorCnt += 1
        print "precision:%f" % ((sampleCnt - errorCnt * 1.0) / sampleCnt)


    结果准确率达到了:

    precision:0.952000


  • 相关阅读:
    tyflow birth节点
    tyflow雨滴在物体上滑落测试
    【bootstrap】如何在js中动态修改提示冒泡(Tooltips)的显示内容
    解决office自动更新失败,错误代码0xc0000142
    hosts文件路径(Windows)
    【微信测试版】支持安卓平板和手机同时登录
    【javascript】canvas画布涂鸦及保存图片到本地
    【python】图片批量压缩(多线程)
    【python】提取pdf文件中的所有图片
    【python】计算程序运行所消耗的总时间
  • 原文地址:https://www.cnblogs.com/bbsno1/p/3258104.html
Copyright © 2011-2022 走看看