【机器学习实践(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('\t')
        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


 

posted on 2013-08-14 19:10  bbsno  阅读(399)  评论(0编辑  收藏  举报

导航