最近想学python,因此开始看《机器学习实战》,笔记主要侧重代码的理解方面。
1 原理
首先讲述一下K近邻算法的原理:
准备阶段:给定训练数据
训练阶段:K近邻不需要训练过程
预测阶段:对于任何一个新的数据x,在训练数据中找到离x最近的k个样本点。在regression(回归)任务中,返回这k个样本点的平均值作为预测结果。在classification(分类)任务中,返回这k个样本点的中出现次数最多的那个类别。
2 样例--使用k近邻算法改进约会网站的配对效果
某个大型交友网站会推荐不同的人选,大龄女青年淋淋把这些人分成三大类:老娘并不care的人,老娘觉得还行的人,老娘爱到不行的人。那么淋淋是怎么将这些人分类的呢,她有她自己的特别的技巧:主要从这三个特征进行分析
□每年坐飞机的里程数
□玩视频游戏所占的时间比例
□每周吃掉的冰激凌公升数
至于为什么选择这些特征,在这里我不探究。总之淋淋把这些数据都记到了她的日记本里(文本文件datingTestSet.txt中)。淋淋收集了1000个男人的数据,每个样本数据占据一行,总共有1000行。(也是吸了好多人厚,淋淋只是想收集更多芭比娃娃)。
2.1 准备数据:从文本文件中解析数据
第一步工作就是要将这些记在文本中的数据提取出来,转换为算法可以处理的格式。下面这个函数file2matrix就是用来处理输入格式的问题。该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。
def file2matrix(filename): #输入数据是文件名
fr = open(filename)
numberOfLines = len(fr.readlines()) #get the number of lines in the file
returnMat = zeros((numberOfLines,3)) #prepare matrix to return
classLabelVector = [] #prepare labels return
fr = open(filename)
index = 0
for line in fr.readlines(): #遍历文件中的每一行
line = line.strip() #截取掉每一行的回车符
listFromLine = line.split('\t') #使用字符'\t'把行分割成一个元素列表
returnMat[index,:] = listFromLine[0:3] #前三个数是特征,存入returnMat中
classLabelVector.append(int(listFromLine[-1])) #最后一个数是label,存入classLabelVector中
index += 1
return returnMat,classLabelVector
其中关键性的几步单步调试结果如下,有助于理解。某一行最初的line数据如下
经过代码line = line.strip()后,去掉了这一行数据里的回车符
经过代码listFromLine = line.split('\t')后,利用制表符把这一行数据变成了如下元素列表
这段代码有个需要注意的点。1、python可以用索引值-1表示列表中的最后一个元素。2、在存储label的时候告诉interpreter列表中存储的元素值为整型。否则python语言会将这些元素当做字符串处理。从上面的代码可以看到,Python处理文本文件非常容易。
2.2 准备数据:归一化数据
数据归一化是机器学习中常用的预处理手段。它的主要功能是避免数据因为量纲不同而结果带来影响。例如本例中,每年坐飞机的里程数这个数据的值是很大,所以在计算的时候,该特征对计算结果的影响会远远大于其他两个特征。所以我们需要把所有的输入特征都归一化到[0,1]这个区间内。归一化的公式如下:
归一化的代码段如下:输入参数为原始数据集,输出为归一化后的数据集
def autoNorm(dataSet):
minVals = dataSet.min(0) #min(0)表示从选取每一列的最小值。
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))# 创建一个根dataSet一样大小的矩阵
m = dataSet.shape[0] # shape[0]表示求dataSet的行数
normDataSet = dataSet - tile(minVals, (m,1))
normDataSet = normDataSet/tile(ranges, (m,1)) #element wise divide
return normDataSet, ranges, minVals
这个函数比较简单,主要有两点注意的地方。1、tile函数的功能:重复某个数组,比如tile(A,n)就是将数组A重复n次,构成一个新的数组。同样用单步调试增加对该函数的理解
可以看出minVals这个矩阵,经过tile之后,在行数上重复了3次。在本代码中,特征矩阵有1000X3个值,而minVals和range的值都是1X3。实用tile函数将变量内容复制成与特征矩阵同样大小的矩阵。
2.3 测试算法:作为完成程序验证分类器
机器学习一个很重要的工作就是评估算法的正确率,通常我们用已有数据的90%作为训练样本来训练分类器,使用剩下的10%的数据去测试分类器,检测分类器的正确率。分类器针对约会网站的测试代码如下:
def datingClassTest():
hoRatio = 0.10 #hold out 10%
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt') #load data setfrom file
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],3)
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))
print errorCount
代码比较简单。它首先使用了file2matrix和autoNorm函数从文件中读取数据并将数据进行归一化。接着计算用于测试的数据的数量,这一步决定了norMat向量中那些数据用于测试,哪些数据用于当分类器的测试样本。最后调用函数classify0,也就是K近邻的核心算法部分。该函数的代码如下:
#函数输入:inX待分类的数据 dataSet给定的数据集 labels数据集中数据的类别标签 k选择最靠近的k个样本中的k的值
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] #得到dataSet的行数
# 距离计算 欧式距离
diffMat = tile(inX, (dataSetSize,1)) - dataSet #tile用于重复某个数组,例如这里就是将inX重复dataseSize行,1列
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)# 将sqDiffMat里面的数据按行相加,每行输出一个sum
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort() #从小到大排序,返回的是下标
classCount={} #classCount是字典
# 选择距离最小的k个点
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)#排序
return sortedClassCount[0][0]
python字典的iteritems方法作用:与items方法相比作用大致相同,只是它的返回值不是列表,而是一个迭代器。
从结果中可以看到,items()方法是将字典中的每个项分别做为元组,添加到一个列表中,形成了一个新的列表容器。如果有需要也可以将返回的结果赋值给新变量,这个新的变量就会是一个列表数据类型。
字典 iteritems()操作方法:
字典.iteritems()方法在需要迭代结果的时候使用最适合,而且它的工作效率非常的高。
参考文章
Python 字典items返回列表,iteritems返回迭代器 http://www.iplaypython.com/jinjie/items-iteritems.html
Python中的sorted函数以及operator.itemgetter函数 http://blog.csdn.net/dongtingzhizi/article/details/12068205
3 总结
k近邻算法是分类数据最简单最有效的方法,是基于实例的学习,使用算法时我们必须有接近实际数据的训练样本数据。k近邻算法必须保存全部数据集,如果训练数据集很大,必须使用大量的存储空间,此外,由于必须对数据集中的每一个数据计算距离值,实际使用时可能非常耗时。
k近邻算法的另一个缺陷是无法给出任何数据的基础结构信息,因此我们也无法知道平均实例样本和典型实例样本具有什么特征。但是下一个算法:决策树 可以解决这个问题。