机器学习之K-近邻算法(Python描述)实战百维万组数据

来源:互联网 发布:小米网络音响均衡器 编辑:程序博客网 时间:2024/05/09 02:56

Python 2.7
IDE Pycharm 5.0.3
numpy 1.11.0
matplotlib 1.5.1

建议先阅读:
1.(大)数据处理:从txt到数据可视化
2.机器学习之K-近邻算法(Python描述)基础


前言

通过基础的knn学习,现在开始利用knn解决实际问题。

目的

将txt保存的数据进行分析,并能在给出数据时候根据knn算法进行分类,验证分类器精度,进行匹配等如有疑问亲先看基础部分@MrLevo520–机器学习之K-近邻算法(Python描述)基础


首先:将数据可视化

本来的数据图保存在txt中是这样的:

这里写图片描述

你只需要知道
每行的第一列数据是飞行里程,第二列是玩游戏所占百分比时间,第三列是每年吃的冰激凌消耗量,第四列是某个xx觉得这类人的适合约会的感兴趣程度,也就是说啦,他一年飞40920公里,有百分之八左右的时间在玩游戏,每年还要吃掉0.9公升哦,这个对象xx觉得好有魅力,非常想和它约会呢,就是这个意思!

详细的可见(大)数据处理:从txt到数据可视化,这里不做详细理解,这里po上一张图,至于怎么读出来的,请看上述链接

这里写图片描述


归一化特征值

一句话,就是把值拍扁,构成0~1之间的值,这样就是消去了数字差值对平方后的数据影响力,也就是说,大家数据能量等价,不偏不倚,当然,如果认为某个数值非常重要,可以适当增加权重,(默认归一化为权重一样),这个就是后话。放上添加的代码。

#归一化计算def autoNorm(dataSet):    minVals = dataSet.min(0) #求各列最小,返回一行,    maxVals = dataSet.max(0)    ranges = maxVals-minVals #最大最小差值,返回一行    normDataSet = zeros(shape(dataSet))    m = dataSet.shape[0] #求行数    normDataSet = dataSet -tile(minVals,(m,1))    normDataSet = normDataSet/tile(ranges,(m,1)) #最后返回的是一个矩阵    return normDataSet,ranges,minVals

这个处理之后出来的值也就是归一化后的值,可以进行下一步的处理,但是有些数据已经预处理之后,数据已经直接可用了,那就没有必要进行归一化,注意查看你自己的数据集。


验证分类器思想

所以得提前步骤准备妥当之后,可以来测试这个分类器的精度了。
步骤就是
1.把数据集分类测试集和训练集,当然,knn没有训练这个说法
2.测试集遮去标签,只输入数据,直接靠KNN的算法,进行预测判断标签
3.测试集本身自己的标签是正确的,只是暂时不用而已,用来当判断knn算法是否判断正确
4.错误率也就是=贴错的标签总数/总的测试样本数


验证分类器精度算法

# -*- coding: utf-8 -*-from numpy import *import operatordef classify0(inX,dataSet,labels,k): # inX用于需要分类的数据,dataSet输入训练集    ######输入与训练样本之间的距离计算######    dataSetSize = dataSet.shape[0] # 读取行数,shape[1]则为列数    diffMat = tile(inX,(dataSetSize,1))-dataSet # tile,重复inX数组的行(dataSize)次,列重复1    sqDiffMat = diffMat**2 #平方操作    sqDistances = sqDiffMat.sum(axis=1) # 每一个列向量相加,axis=0为行相加    distances = sqDistances**0.5    sortedDistIndicies = distances.argsort() # argsort函数返回的是数组值从小到大的索引值    #print sortedDistIndicies #产生的是一个排序号组成的矩阵    classCount={}    ######累计次数构成字典######    for i in range(k):        voteIlabel = labels[sortedDistIndicies[i]] #排名前k个贴标签        classCount[voteIlabel] = classCount.get(voteIlabel,0)+1 # 不断累加计数的过程,体现在字典的更新中        #get(key,default=None),就是造字典    ######找到出现次数最大的点######    sortedClassCount = sorted(classCount.iteritems(),key = operator.itemgetter(1),reverse=True)    #以value值大小进行排序,reverse=True降序    #key = operator.itemgetter(1),operator.itemgetter函数获取的不是值,而是定义了一个函数,通过该函数作用到对象上才能获取值    return sortedClassCount[0][0]    #返回出现次数最多的value的keydef file2matrix(filename):    fr = open(filename)    arrayOlines = fr.readlines()    numberOfLines = len(arrayOlines)    returnMat = zeros((numberOfLines,3)) #构造全零阵来存放数    classLabelVector = [] #开辟容器    index = 0    for line in arrayOlines:        #清洗数据        line = line.strip()        listFromLine = line.split('\t')        #存入数据到list        returnMat[index,:] = listFromLine[0:3] #三个特征分别存入一行的三个列        classLabelVector.append(int(listFromLine[-1])) #最后一行是类别标签        index +=1    return returnMat,classLabelVector#归一化计算def autoNorm(dataSet):    minVals = dataSet.min(0) #求各列最小,返回一行,    maxVals = dataSet.max(0)    ranges = maxVals-minVals #最大最小差值,返回一行    normDataSet = zeros(shape(dataSet))    m = dataSet.shape[0] #求行数    normDataSet = dataSet -tile(minVals,(m,1))    normDataSet = normDataSet/tile(ranges,(m,1)) #最后返回的是一个矩阵    return normDataSet,ranges,minVals#测试分类器精度def datingTest(HORATIO,K):    hoRatio = HORATIO #取百分之十作为测试数据    datingDataMat,datingLabels = file2matrix("C:\Users\MrLevo\Desktop\machine_learning_in_action\Ch02\datingTestSet2.txt")    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],K)        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))if __name__ == '__main__':    HORATIO = input("Please enter test set (%all): ")    K = input("Please enter the k: ")    datingTest(HORATIO,K)

IDE输入输出的结果:

Please enter test set (%all): 0.1Please enter the k: 3the classifier came back with:3,the real answer is 3the total error rate is : 0.000000the classifier came back with:2,the real answer is 2the total error rate is : 0.000000...the classifier came back with:1,the real answer is 1the total error rate is : 0.040000the classifier came back with:3,the real answer is 1the total error rate is : 0.050000

错误率为5%,可以接受的一个错误率。


构建一个能用于实际的系统

这里是约会网站的匹配:手动输入心目中的她大概是什么样的,比如飞行里程期望是多少公里,玩不玩游戏呢,还有吃冰激凌怎么看,这些都是用户自己输入的。

修改代码如下:

def classifyPerson():    resultList = ['not at all','in small doses','in large doses']    percentTats = input("percentage of time spent playing video games?")    ffMiles = input("frequent flier miles earned per year?")    iceCream = input("liters of ice cream consumed per year?")    datingDataMat,datingLabels = file2matrix("C:\Users\MrLevo\Desktop\machine_learning_in_action\Ch02\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]if __name__ == '__main__':    classifyPerson()

开始测试例子,先放个图,红色点是十分感兴趣,绿色一般,黑色不感兴趣

这里写图片描述

第一个例子,这是用户交互界面,10,40000,1等都是自己输入的,然后系统会根据算法,认为他是属于什么样的人。

percentage of time spent playing video games?10frequent flier miles earned per year?40000liters of ice cream consumed per year?1You will probably like this person:  in large doses

从上图可以看出,飞行距离为40000左右,玩游戏10%的很密集的红点也就是in large doses,符合

再来个例子

percentage of time spent playing video games?3frequent flier miles earned per year?40000liters of ice cream consumed per year?1You will probably like this person:  not at all

从图中可以看出玩游戏3%,飞行距离40000的,并不感兴趣,所以测试通过。具体的分析可以参考(大)数据处理:从txt到数据可视化中的分析例子,几乎是符合的。



What’s More!

当然,这是书本上的知识,拿来理解其中的算法和结构不错,对knn也有更深入的理解,但是,这还不够,所以,我作死的拿出了我研究课题的数据,AVIRIS数据,一个高光谱遥感图像的数据,简单说,就是放大了刚才的数据,维数从3维变成了200维(波段),数据从1000组变成了10266组,3类变成了13类,仅此而已啦。看看结构是怎样的。

这里写图片描述

这里只有matlab的.mat格式的,没事,先将它转为为txt保存。


从.mat到txt

如何从.mat到txt请看我单独列出来的一篇文章解决:将.mat文件保存到.txt不带有科学计数法e-0,这里放上写好之后的效果大概是这样的。

这里写图片描述

这些都处理好了之后,就可以用上述的第一个例子的算法了。开始!


拟构适用于AVIRIS的Knn算法(有致命bug,错误率高)

修改部分代码

sortedClassCount = sorted(classCount.iteritems(),key = lambda d:d[1],reverse=True)

这里是对同标签进行累加的过程,为了之后的排序求概率做准备。这里使用lambda比较好理解

增加file2matrix_Label函数,修改file2matrix函数
修改细则请见详细代码,注意构造zeros矩阵时候的大小设置,现在已经是200维,10266组数据了。

修改datingTest(HORATIO,K)
增加HORATIO,K参数,用来自定义设置测试集数量和K的参数


完整测试代码

# -*- coding: utf-8 -*-from numpy import *import redef file2matrix(filename):    fr = open(filename,'r')    arrayOlines = fr.readlines()    numberOfLines = len(arrayOlines) #行数    #numberOfColumn = shape(mat(arrayOlines))[0] #列数    returnMat = zeros((numberOfLines,200)) #构造全零阵来存放数    index = 0    for line in arrayOlines:        #清洗数据        line = line.strip()        line = re.sub(' +',' ',line)        line = re.sub('\t',' ',line)        listFromLine = line.split(' ')        #存入数据到list        #print listFromLine        returnMat[index,:] = listFromLine[0:200]        returnMat        index +=1    print returnMat    return returnMatdef file2matrix_Label(filename):    fr = open(filename,'r')    arrayOlines = fr.readlines()    numberOfLines = len(arrayOlines) #行数    returnLab = zeros((numberOfLines,1)) #构造全零阵来存放数    classLabelVector = [] #开辟容器    index = 0    for line in arrayOlines:        #清洗数据        line = line.strip()        line = re.sub('\t',' ',line)        line = re.sub(' +',' ',line)        listFromLine = line.split(' ')        #存入数据到list        #print listFromLine        returnLab[index,:] = listFromLine[0:1]         classLabelVector.append(int(listFromLine[0]))        index +=1    return classLabelVectordef classify0(inX,dataSet,labels,k): # inX用于需要分类的数据,dataSet输入训练集    ######输入与训练样本之间的距离计算######    dataSetSize = dataSet.shape[0] # 读取行数,shape[1]则为列数    diffMat = tile(inX,(dataSetSize,1))-dataSet # tile,重复inX数组的行(dataSize)次,列重复1    sqDiffMat = diffMat**2 #平方操作    sqDistances = sqDiffMat.sum(axis=1) # 每一个列向量相加,axis=0为行相加    distances = sqDistances**0.5    sortedDistIndicies = distances.argsort() # argsort函数返回的是数组值从小到大的索引值    #print sortedDistIndicies #产生的是一个从小到大排序后索引号的矩阵    classCount={}    ######累计次数构成字典######    for i in range(k):        voteIlabel = labels[sortedDistIndicies[i]] #排名前k个贴标签        classCount[voteIlabel] = classCount.get(voteIlabel,0)+1 # 不断累加计数的过程,体现在字典的更新中        #get(key,default=None),就是造字典    ######找到出现次数最大的点######    #sortedClassCount = sorted(classCount.iteritems(),key = operator.itemgetter(1),reverse=True)    sortedClassCount = sorted(classCount.iteritems(),key = lambda d:d[1],reverse=True)    #以value值大小进行排序,reverse=True降序    return sortedClassCount[0][0]    #返回出现次数最多的value的key#测试分类器精度def datingTest(HORATIO,K):    hoRatio = HORATIO*0.01 #取HORATIO作为测试数据%    datingDataMat = file2matrix('C:\\Users\\MrLevo\\Desktop\\AL_Toolbox\\data.txt')    datingLabels = file2matrix_Label('C:\\Users\\MrLevo\\Desktop\\AL_Toolbox\\label2.txt')    #datingDataMat,datingLabels = file2matrixComebin('C:\Users\MrLevo\Desktop\AL_Toolbox\datacombinlabel.txt')    m = datingDataMat.shape[0]    numTestVecs = int(m*hoRatio) #挑选出多少组测试数据    errorCount = 0.0    for i in range(numTestVecs):        classifierResult = classify0(datingDataMat[i,:],datingDataMat[numTestVecs:m,:],datingLabels[numTestVecs:m],K)        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))if __name__ == '__main__':    HORATIO = input("Please enter test set (%): ")    K = input("Please enter the k: ")    datingTest(HORATIO,K)

测试结果如下,选取百分之十作为测试集,k=3,进行计算

Please enter test set (%): 10Please enter the k: 3the classifier came back with:2,the real answer is 2the total error rate is : 0.000000the classifier came back with:2,the real answer is 2the total error rate is : 0.000000...the classifier came back with:9,the real answer is 2the total error rate is : 0.594542the classifier came back with:9,the real answer is 2the total error rate is : 0.595517

错误率达到了60%!!!!难道对于高维数据来说,knn是灾难,难道这个方法并不适合我的AVIRIS数据集?为什么能在约会匹配网络得到比较良好的误差呢?
mdzz


分析解决BUG

原因

数据的类别都堆在一起了!!这就导致取测试样本的时候一堆相同类别的数据,就像这样!
这里写图片描述

这怎么取样啊,第一个例子表现的是1,2,3类几乎是错开的,所以比较好取样,但是,对于我这数据而言,数据堆叠太严重了,导致错误率太高(高的离谱)

解决方案
使用random.shuffle(new_mat)方法,打乱列表数据,当然先要合并列表等等操作,所以重构def SelectLabel(numberOfLines,returnMatLabel,list_label,numberOfColumns):函数,第一个传入的是数组的行数,也就是样本个数,第二个参数表示将数组存入list后的列表,第三个则是选择需要的分类类别的列表形式,第四个是列总数包括维度和标签
整个函数如下

def SelectLabel(numberOfLines,returnMatLabel,list_label,numberOfColumns):    new_mat =[]    for i in range(numberOfLines):        if (returnMatLabel[:,-1])[i] in list_label: # 挑选选中标签            new_mat.append(returnMatLabel[i,:])    random.shuffle(new_mat)    classLabelVector = list(array(new_mat)[:,-1])    returnMat = array(new_mat)[:,0:numberOfColumns-1]    return returnMat,classLabelVector

重构适用于AVIRIS的Knn算法(支持多类别自主选择)

改进拓展
1.增加自由选择类别函数,可以自主选择所需要分类的类别,比如说,我想知道knn在第1,2,3,6类上的精度,直接可以输入1,2,3,6即可,增加的核心语句是

list_label = input("please enter label you want to classify(use comma to separate):")    list_label = list(list_label)

一个个input太麻烦了,所以我选择直接输入一组想分类的类别,然后构造list再传入下一个函数

2增加K值可选,测试样本可选参数,这样就可以自己设置k和测试样本百分比了,这样就可以验证自己的更多想法

3.增加自动化适应格式,只需要输入文件路径即可运行,格式要求,txt文件,且每行最后一个为标签即可。


完整代码

# -*- coding: utf-8 -*-#Author:哈士奇说喵#KNN算法from numpy import *#txt转成立于分析的格式def file2matrixComebin(filename):    fr = open(filename)    arrayOlines = fr.readlines()    numberOfLines = len(arrayOlines)    #计算列数(包括标签在内)    numberOfColumns = arrayOlines[0].split('\n')    numberOfColumns =(numberOfColumns[0].split('\t'))    numberOfColumns = len(numberOfColumns)    returnMatLabel = zeros((numberOfLines,numberOfColumns)) #构造全零阵来存放数据和标签    returnAllLabel = zeros((numberOfLines,1)) #存放标签    index = 0    for line in arrayOlines:        #清洗数据        line = line.strip()        listFromLine = line.split('\t')        #存入数据到list        returnMatLabel[index,:] = listFromLine[0:numberOfColumns]        returnAllLabel[index,:] = listFromLine[-1]        index +=1    #显示类别及各类别占个数    labelclass = set(list(array(returnAllLabel)[:,-1]))    for i in labelclass:        print 'Label:',i,'number:',list(array(returnAllLabel)[:,-1]).count(i)    print 'please select the labels from this ! '    list_label = input("please enter label you want to classify(use comma to separate):")    list_label = list(list_label)    #调用SelectLabel函数来选择分类的种类    returnMat,classLabelVector = SelectLabel(numberOfLines,returnMatLabel,list_label,numberOfColumns)    return returnMat,classLabelVector#SelectLabel函数,自由选择需要分类的类别及个数def SelectLabel(numberOfLines,returnMatLabel,list_label,numberOfColumns):    new_mat =[]    for i in range(numberOfLines):        if (returnMatLabel[:,-1])[i] in list_label: # 挑选选中标签            new_mat.append(returnMatLabel[i,:])    random.shuffle(new_mat)    classLabelVector = list(array(new_mat)[:,-1])    returnMat = array(new_mat)[:,0:numberOfColumns-1]    return returnMat,classLabelVectordef classify0(inX,dataSet,labels,k): # inX用于需要分类的数据,dataSet输入训练集    ######输入与训练样本之间的距离计算######    dataSetSize = dataSet.shape[0] # 读取行数,shape[1]则为列数    diffMat = tile(inX,(dataSetSize,1))-dataSet # tile,重复inX数组的行(dataSize)次,列重复1    sqDiffMat = diffMat**2 #平方操作    sqDistances = sqDiffMat.sum(axis=1) # 每一个列向量相加,axis=0为行相加    distances = sqDistances**0.5    sortedDistIndicies = distances.argsort() # argsort函数返回的是数组值从小到大的索引值    #print sortedDistIndicies #产生的是一个从小到大排序后索引号的矩阵    classCount={}    ######累计次数构成字典######    for i in range(k):        voteIlabel = labels[sortedDistIndicies[i]] #排名前k个贴标签        classCount[voteIlabel] = classCount.get(voteIlabel,0)+1 # 不断累加计数的过程,体现在字典的更新中        #get(key,default=None),就是造字典    ######找到出现次数最大的点######    sortedClassCount = sorted(classCount.iteritems(),key = lambda d:d[1],reverse=True)    #以value值大小进行排序,reverse=True降序    return sortedClassCount[0][0]    #返回出现次数最多的value的key#测试分类器精度def datingTest(HORATIO,K,Path):    hoRatio = HORATIO*0.01 #取HORATIO作为测试数据%    datingDataMat,datingLabels = file2matrixComebin(Path)    m = datingDataMat.shape[0]    numTestVecs = int(m*hoRatio) #挑选出多少组测试数据    errorCount = 0.0    for i in range(numTestVecs):        classifierResult = classify0(datingDataMat[i,:],datingDataMat[numTestVecs:m,:],datingLabels[numTestVecs:m],K)        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))if __name__ == '__main__':    HORATIO = input("Please enter test set (%) : ")    K = input("Please enter the k: ")    Path = raw_input("Please enter the data path (.txt):")    datingTest(HORATIO,K,Path)

进行测试,首先选取k=3,测试样本为百分之十,选择分类为全分类(13个类别)

Please enter test set (%) : 10Please enter the k: 3Please enter the data path (.txt):C:\Users\MrLevo\Desktop\AL_Toolbox\datacombinlabel.txtLabel: 1.0 number: 1434Label: 2.0 number: 834Label: 3.0 number: 234Label: 4.0 number: 497Label: 5.0 number: 747Label: 6.0 number: 489Label: 7.0 number: 968Label: 8.0 number: 2468Label: 9.0 number: 614Label: 10.0 number: 212Label: 11.0 number: 1294Label: 12.0 number: 380Label: 13.0 number: 95please select the labels from this ! please enter label you want to classify(use comma to separate):1,2,3,4,5,6,7,8,9,10,11,12,13the classifier came back with:11,the real answer is 11the total error rate is : 0.000000the classifier came back with:8,the real answer is 8the total error rate is : 0.000000...the classifier came back with:9,the real answer is 9the total error rate is : 0.208577

当选择k=3,取样率百分之十,分类类别为第1,8,11类

...the classifier came back with:11,the real answer is 11the total error rate is : 0.073218

当选择k=3,取样百分之十,分类为第2,5,7,9

...the classifier came back with:7,the real answer is 7the total error rate is : 0.088608

从误差上来说,这个精度还算是不错的了,因为这组数据维度是200,数据集是10266组,类别13类,一般而言,有监督如果不上SVM的话,单一算法未改进的差不多也是这个准确度,原来真的是取样的问题!!


*再来测试另一个高光谱数据KSC1,维度176个,样本数3784个
k=3,取百分之十做测试集*

Please enter test set (%) : 10Please enter the k: 3Please enter the data path (.txt):C:\Users\MrLevo\Desktop\AL_Toolbox\testKSC1.txtLabel: 0.0 number: 761Label: 1.0 number: 243Label: 2.0 number: 256Label: 3.0 number: 252Label: 4.0 number: 161Label: 5.0 number: 229Label: 6.0 number: 105Label: 7.0 number: 431Label: 8.0 number: 419Label: 9.0 number: 927please select the labels from this ! please enter label you want to classify(use comma to separate):0,1,2,3,4,7the classifier came back with:7,the real answer is 7the total error rate is : 0.000000...the classifier came back with:0,the real answer is 0the total error rate is : 0.061905

ok完美实现,其余的就不一一测试了。


Pay Attention

1.请尽量选择样本数相差不多的类别进行分类,不然分类精度上下浮动很大,比如事先,你可以查看一下自己的数据标签是多少个

labelclass = set(datingLabels)for i in labelclass:    print i,datingLabels.count(i)

查询可得我的标签样本为,如第一类有1434个样本。

1.0 14342.0 8343.0 2344.0 4975.0 7476.0 4897.0 9688.0 24689.0 61410.0 21211.0 129412.0 38013.0 95

2.融合数据和标签
在改进版代码中,我的样本和标签是融合在一份txt中的,所以和拟构那个分开的标签和样本集不同,因为要考虑到重新打乱顺序,所以需要样本和标签一一对应,之后再打乱顺序,再分开。至于怎么合并,直接选中excel的标签列,复制到样本的最后一列也就是第201列,粘贴就好了,就像这样,之后保存为txt即可

这里写图片描述


最后

打包成exe点击这里下载源码和打包文件方便没有装python环境的同学们学习knn算法,使用自己的数据集进行测试验证自己的idea,至于如何打包请看如何将py文件打包成exe

这里写图片描述

最后的最后

深刻理解knn算法的实现过程,期间出现太多问题,没有一一记录,但是真学到了非常多的东西,刚好又和研究课题结合起来,觉得非常值得这两天的不断推翻重构代码!


致谢

利用python进行数据分析.Wes McKinney
机器学习实战.Peter Harrington
@MrLevo520–机器学习之K-近邻算法(Python描述)基础
@MrLevo520–(大)数据处理:从txt到数据可视化
@MrLevo520–NumPy快速入门
@MrLevo520–解决:将.mat文件保存到.txt不带有科学计数法e-0
@MrLevo520–如何将py文件打包成exe

2 0