理论部分:
k近邻算法简单、直观:给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的k个实例,这k个实例多数属于某个类,就把输入实例分为这个类。这就是k近邻算法的直观描述。
k近邻算法中,当训练集、距离度量、k值和分类决策规则(例如:多数表决)确定后,对于任何一个新的输入实例,它所属的类唯一地确定。(注:k近邻算法的描述中标记了这四个要素的位置!)
训练集:输入的数据。
距离度量:可采用欧氏距离。
k值:适当选择。当选择较大的k值,就相当于用较大邻域中的训练实例进行预测,对噪音点不敏感,但是会将和次实例点不相关的点引入进来,可能导致预测错误;当选择较小的k值,只有与输入实例较近的实例点才会对预测结果起作用,但是有个缺点是对噪音点敏感,如果有噪音点的存在会导致错误的预测结果。(所以,k值的选择是个问题——一般通过交叉验证来选择,通常取值较小)
分类决策规则:多数表决
按照《统计学习方法》中的框架:模型、策略、算法的方式来对应k近邻算法,感觉有点勉强,就不对应了,详细参见《统计学习方法》。
简单实践:
纸上来的终觉浅!具体的例子实践下,以后有实践的例子会再补充上来。例子中的案例摘《机器学习实战》一书中的,代码例子是用python编写的(需要matplotlib和numpy库)。
背景描述:
海伦一直使用在线约会网站寻找合适自己的约会对象。尽管约会网站会推荐不同的人选,但她没有从中找到喜欢的人。经过一番总结,她发现曾交往过三种类型的人:1.不喜欢的人(以下简称1);2.魅力一般的人(以下简称2) ;3.极具魅力的人(以下简称3)
尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归入恰当的分类。她觉得可以在周一到周五约会哪些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴。海伦希望我们的分类软件可以更好的帮助她将匹配对象划分到确切的分类中。此外海伦还收集了一些约会网站未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。
我们先提取一下这个案例的目标:根据一些数据信息,对指定人选进行分类(1或2或3)。为了使用kNN算法达到这个目标,我们需要哪些信息?前面提到过,就是需要样本数据,这些样本数据就是“海伦还收集了一些约会网站未曾记录的数据信息”。
1.收集数据
海伦收集的数据是记录一个人的三个特征:每年获得的飞行常客里程数;玩视频游戏所消耗的时间百分比;每周消费的冰淇淋公升数。数据是txt格式文件,如下图,前三列依次是三个特征,第四列是分类(1:不喜欢的人,2:魅力一般的人,3:极具魅力的人),每一行代表一个人。
数据文档的下载链接是:http://pan.baidu.com/s/1jG7n4hS
----2.准备数据
何为准备数据?之前收集到了数据,放到了txt格式的文档中了,看起来也比较规整,但是计算机并不认识啊。计算机需要从txt文档中读取 数据,并把数据进行格式化,也就是说存到矩阵中,用矩阵来承装这些数据,这样才能使用计算机处理。
需要两个矩阵:一个承装三个特征数据,一个承装对应的分类。于是,我们定义一个函数,函数的输入时数据文档(txt格式),输出为两个矩阵。
代码如下:
1 def file2matrix(filename):
2 fr = open(filename)
3 numberOfLines = len(fr.readlines())
4 returnMat = zeros((numberOfLines, 3))
5 classLabelVector = []
6 fr = open(filename)
7 index = 0
8 for line in fr.readlines():
9 line = line.strip()
10 listFromLine = line.split('\t')
11 returnMat[index, :] = listFromLine[0:3]
12 classLabelVector.append(int(listFromLine[-1]))
13 index += 1
14 return returnMat, classLabelVector
简要解读代码:首先打开文件,读取文件的行数,然后初始化之后要返回的两个矩阵(returnMat、classLabelsVector),然后进入循环,将每行的数据各就各位分配给returnMat和classLabelsVector。
----3.设计算法分析数据
k-近邻算法的目的就是找到新数据的前k个邻居,然后根据邻居的分类来确定该数据的分类。
首先要解决的问题,就是什么是邻居?当然就是“距离”近的了,不同人的距离怎么确定?这个有点抽象,不过我们有每个人的3个特征数据。每个人可以使用这三个特征数据来代替这个人——三维点。比如样本的第一个人就可以用(40920, 8.326976, 0.953952)来代替,并且他的分类是3。那么此时的距离就是点的距离:
A点(x1, x2, x3),B点(y1, y2, y3),这两个点的距离就是:(x1-y1)^2+(x2-y2)^2+(x3-y3)^2的平方根。求出新数据与样本中每个点的距离,然后进行从小到大排序,前k位的就是k-近邻,然后看看这k位近邻中占得最多的分类是什么,也就获得了最终的答案。
这个处理过程也是放到一个函数里的,代码如下:
1 def classify0(inX, dataSet, labels, k):
2 dataSetSize = dataSet.shape[0]
3 diffMat = tile(inX, (dataSetSize,1)) - dataSet
4 sqDiffMat = diffMat**2
5 sqDistances = sqDiffMat.sum(axis=1)
6 distances = sqDistances**0.5
7 sortedDistIndicies = distances.argsort()
8 classCount={}
9 for i in range(k):
10 voteIlabel = labels[sortedDistIndicies[i]]
11 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
12 sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1), reverse=True)
13 return sortedClassCount[0][0]
简要解读代码:该函数的4个参数分别为新数据的三个特征inX、样本数据特征集(上一个函数的返回值)、样本数据分类(上一个函数的返回值)、k,函数返回位新数据的分类。第二行dataSetSize获取特征集矩阵的行数,第三行为新数据与样本各个数据的差值,第四行取差值去平方,之后就是再取和,然后平方根。代码中使用的排序函数都是python自带的。
好了,现在我们可以分析数据了,不过,有一点不知道大家有没有注意,我们回到那个数据集,第一列代表的特征数值远远大于其他两项特征,这样在求距离的公式中就会占很大的比重,致使两点的距离很大程度上取决于这个特征,这当然是不公平的,我们需要的三个特征都均平地决定距离,所以我们要对数据进行处理,希望处理之后既不影响相对大小又可以不失公平:
这种方法叫做,归一化数值,通过这种方法可以把每一列的取值范围划到0~1或-1~1:,处理的公式为:
newValue = (oldValue - min)/(max - min)
归一化数值的函数代码为:
1 def autoNorm(dataSet):
2 minVals = dataSet.min(0)
3 maxVals = dataSet.max(0)
4 ranges = maxVals - minVals
5 normDataSet = zeros(shape(dataSet))
6 m = dataSet.shape[0]
7 normDataSet = dataSet - tile(minVals, (m, 1))
8 normDataSet = normDataSet / tile(ranges, (m, 1))
9 return normDataSet, ranges, minVals
---4.测试算法
经过了格式化数据、归一化数值,同时我们也已经完成kNN核心算法的函数,现在可以测试了,测试代码为:
1 def datingClassTest():
2 hoRatio = 0.10
3 datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
4 normMat, ranges, minVals = autoNorm(datingDataMat)
5 m = normMat.shape[0]
6 numTestVecs = int(m * hoRatio)
7 errorCount = 0.0
8 for i in range(numTestVecs):
9 classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
10 print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
11 if (classifierResult != datingLabels[i]): errorCount += 1.0
12 print "the total error rate is: %f" % (errorCount / float(numTestVecs))
通过测试代码我们可以在回忆一下这个例子的整体过程:
- 读取txt文件,提取里面的数据到datingDataMat、datingLabels;
- 归一化数据,得到归一化的数据矩阵;
- 测试数据不止一个,这里需要一个循环,依次对每个测试数据进行分类。
代码中大家可能不太明白hoRatio是什么。注意,这里的测试数据并不是另外一批数据而是之前的数据集里的一部分,这样我们可以把算法得到的结果和原本的分类进行对比,查看算法的准确度。在这里,海伦提供的数据集又1000行,我们把前100行作为测试数据,后900行作为样本数据集,现在大家应该可以明白hoRatio是什么了吧。
整体的代码:
1 from numpy import *
2 import operator
3
4 def classify0(inX, dataSet, labels, k):
5 dataSetSize = dataSet.shape[0]
6 diffMat = tile(inX, (dataSetSize,1)) - dataSet
7 sqDiffMat = diffMat**2
8 sqDistances = sqDiffMat.sum(axis=1)
9 distances = sqDistances**0.5
10 sortedDistIndicies = distances.argsort()
11 classCount={}
12 for i in range(k):
13 voteIlabel = labels[sortedDistIndicies[i]]
14 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
15 sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1), reverse=True)
16 return sortedClassCount[0][0]
17
18 def file2matrix(filename):
19 fr = open(filename)
20 numberOfLines = len(fr.readlines())
21 returnMat = zeros((numberOfLines, 3))
22 classLabelVector = []
23 fr = open(filename)
24 index = 0
25 for line in fr.readlines():
26 line = line.strip()
27 listFromLine = line.split('\t')
28 returnMat[index, :] = listFromLine[0:3]
29 classLabelVector.append(int(listFromLine[-1]))
30 index += 1
31 return returnMat, classLabelVector
32
33 def autoNorm(dataSet):
34 minVals = dataSet.min(0)
35 maxVals = dataSet.max(0)
36 ranges = maxVals - minVals
37 normDataSet = zeros(shape(dataSet))
38 m = dataSet.shape[0]
39 normDataSet = dataSet - tile(minVals, (m, 1))
40 normDataSet = normDataSet / tile(ranges, (m, 1))
41 return normDataSet, ranges, minVals
42
43 def datingClassTest():
44 hoRatio = 0.10
45 datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
46 normMat, ranges, minVals = autoNorm(datingDataMat)
47 m = normMat.shape[0]
48 numTestVecs = int(m * hoRatio)
49 errorCount = 0.0
50 for i in range(numTestVecs):
51 classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
52 print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
53 if (classifierResult != datingLabels[i]): errorCount += 1.0
54 print "the total error rate is: %f" % (errorCount / float(numTestVecs))
运行一下代码,这里我使用的是ipython:
最后的错误率为0.05。
重要:
参考文献:
1.李航的《统计学习方法》
2.http://www.cnblogs.com/jtianwen2014/p/4249003.html
3.https://github.com/sangyongjia/machineLearning