【机器学习实践(2)】K近邻(KNN)模型

来源:互联网 发布:格灵深瞳 知乎 衰败 编辑:程序博客网 时间:2024/06/05 19:12

根据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 numpyimport 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