Python 2.7
IDE Pycharm 5.0.3
numpy 1.11.0
matplotlib 1.5.1
建议先阅读:
1.(大)数据处理:从txt到数据可视化
2.机器学习之K-近邻算法(Python描述)基础
- 教程来自于《机器学习实战》第二章
- 代码及数据github@Mini-Python-Project中的DataSource文件夹下有个压缩包
前言
通过基础的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 operator
def 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的key
def 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.1
Please enter the k: 3
the classifier came back with:3,the real answer is 3
the total error rate is : 0.000000
the classifier came back with:2,the real answer is 2
the total error rate is : 0.000000
...
the classifier came back with:1,the real answer is 1
the total error rate is : 0.040000
the classifier came back with:3,the real answer is 1
the 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?10
frequent flier miles earned per year?40000
liters of ice cream consumed per year?1
You will probably like this person: in large doses
从上图可以看出,飞行距离为40000左右,玩游戏10%的很密集的红点也就是in large doses,符合
再来个例子
percentage of time spent playing video games?3
frequent flier miles earned per year?40000
liters of ice cream consumed per year?1
You 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 re
def 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 returnMat
def 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 classLabelVector
def 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 (%): 10
Please enter the k: 3
the classifier came back with:2,the real answer is 2
the total error rate is : 0.000000
the classifier came back with:2,the real answer is 2
the total error rate is : 0.000000
...
the classifier came back with:9,the real answer is 2
the total error rate is : 0.594542
the classifier came back with:9,the real answer is 2
the total error rate is : 0.595517
错误率达到了60%!!!!难道对于高维数据来说,knn是灾难,难道这个方法并不适合我的AVIRIS数据集?为什么能在约会匹配网络得到比较良好的误差呢?
分析解决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,classLabelVector
def 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 (%) : 10
Please enter the k: 3
Please enter the data path (.txt):C:\Users\MrLevo\Desktop\AL_Toolbox\datacombinlabel.txt
Label: 1.0 number: 1434
Label: 2.0 number: 834
Label: 3.0 number: 234
Label: 4.0 number: 497
Label: 5.0 number: 747
Label: 6.0 number: 489
Label: 7.0 number: 968
Label: 8.0 number: 2468
Label: 9.0 number: 614
Label: 10.0 number: 212
Label: 11.0 number: 1294
Label: 12.0 number: 380
Label: 13.0 number: 95
please 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,13
the classifier came back with:11,the real answer is 11
the total error rate is : 0.000000
the classifier came back with:8,the real answer is 8
the total error rate is : 0.000000
...
the classifier came back with:9,the real answer is 9
the total error rate is : 0.208577
当选择k=3,取样率百分之十,分类类别为第1,8,11类
...
the classifier came back with:11,the real answer is 11
the total error rate is : 0.073218
当选择k=3,取样百分之十,分类为第2,5,7,9
...
the classifier came back with:7,the real answer is 7
the total error rate is : 0.088608
从误差上来说,这个精度还算是不错的了,因为这组数据维度是200,数据集是10266组,类别13类,一般而言,有监督如果不上SVM的话,单一算法未改进的差不多也是这个准确度,原来真的是取样的问题!!
再来测试另一个高光谱数据KSC1,维度176个,样本数3784个
k=3,取百分之十做测试集
Please enter test set (%) : 10
Please enter the k: 3
Please enter the data path (.txt):C:\Users\MrLevo\Desktop\AL_Toolbox\testKSC1.txt
Label: 0.0 number: 761
Label: 1.0 number: 243
Label: 2.0 number: 256
Label: 3.0 number: 252
Label: 4.0 number: 161
Label: 5.0 number: 229
Label: 6.0 number: 105
Label: 7.0 number: 431
Label: 8.0 number: 419
Label: 9.0 number: 927
please select the labels from this !
please enter label you want to classify(use comma to separate):0,1,2,3,4,7
the classifier came back with:7,the real answer is 7
the total error rate is : 0.000000
...
the classifier came back with:0,the real answer is 0
the 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 1434
2.0 834
3.0 234
4.0 497
5.0 747
6.0 489
7.0 968
8.0 2468
9.0 614
10.0 212
11.0 1294
12.0 380
13.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