一、k-近邻算法概述
简单来说,k-近邻算法采用测量不同特征值之间的距离方法进行分类。
k-近邻算法 |
优点:精度高、对异常值不敏感、无数据输入假定。 |
缺点:计算复杂度高、空间复杂度高。 |
适用数据范围:数值型和标称型。 |
其工作原理:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每一个特征与数据集中数据对应的特征进行比较,然后算法提取样本集中特征最相似的数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据。
k-近邻算法的一般流程:
(1)收集数据:可以使用任何方法。
(2)准备数据:距离计算所需要的数值,最好是结构化的数据格式。
(3)分析数据:可以使用任何方法。
(4)训练算法:此步骤不适用于k-近邻算法。
(5)测试算法:计算错误率。
(6)使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算机出的分类执行后续的处理。
1、准备:使用Python导入数据
首先,创建名为kNN.py的Python模块。在构造完整的k-近邻算法之前,我们还需要编写一些基本的通用函数,在kNN.py文件中增加下面的代码:
from numpy import *
import operator
def createDataSet() :
group = array([[1.0,1.1], [1.0,1.0],[0,0],[0,0.1]])
labels = ['A', 'A', 'B', 'B']
return group, labels
在上面的代码中,我们导入了两个模块:第一个是科学计算包NumPy;第二个是运算符模块,k-近邻算法执行排序操作时将使用这个模块提供的函数,后面我们将进一步介绍。
为了方便使用createDataSet ()函数,它创建数据集和标签。 然后依次执行以下步骤:保存kNN.py文件,改变当前路径到存储kNN.py文件的位置,打开Python开发环境。 无论是Linux、Mac OS还是Windows都需要打开终端,在命令提示符下完成上述操作。只要我们按照默认配置安装Python,在Linux/Mac OS终端内都可以直接输人python,而在Windows命令提示符下需要输入c:\Python2.6\python. exe,进人Python交互式开发环境。
进人Python开发环境之后,输人下列命令导人上面编辑的程序模块:
>>> import kNN
上述命令导人kNN模块。为了确保输人相同的数据集,kNN模块中定义了函数createDataSet,在Python命令提示符下输入下列命令:
>>> group,labels = kNN. createDataSet()
上述命令创建了变量group和labels,在Python命令提示符下,输人变量的名字以检验是否正确地定义变量:
>>> group
array([ [ 1. , 1.1],
[ 1. , 1. ],
[ 0. , 0. ],
[ 0. , 0.1] ])
>>> labels
['A','A', 'B', 'B']
这里有4组数据,每组数据有两个我们已知的属性或者特征值。上面的group矩阵每行包含一个不同的数据,我们可以把它想象为某个日志文件中不同的测量点或者人口。由于人类大脑的限制,我们通常只能可视化处理三维以下的事务。因此为了简单地实现数据可视化,对于每个数据点我们通常只使用两个特征。
向量labels包含了每个数据点的标签信息,labels包含的元素个数等于group矩阵行数。 这里我们将数据点(1, 1.1)定义为类A,数据点(0, 0.1)定义为类B。为了说明方便,例子中的数值是任意选择的,并没有给出轴标签,图1是带有类标签信息的四个数据点。
图1 k-近邻算法: 带有4个数据点的简单例子
2、实施kNN分类算法
使用以下代码运行kNN算法,为每组数据分类:
def classify0 (inX, dataSet, labels, k) :
dataSetSize = dataSet.shape[0]
#1、距离计算
diffMat = tile (inX, (dataSetSize,1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum (axis=1)
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort ()
classCount={}
for i in range(k) :
voteIlabel= labels [ sortedDistIndicies[i] ]
#2、选择距离最小的k个点
classCount [voteIlabel] = classCount.get (voteIlabel,0) + 1
#3、排序
sortedClassCount = sorted (classCount.iteritems(),
key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
该函数的功能是使用k近邻算法将每组数据划分到某个类中,其伪代码如下:
对未知类别属性的数据集中的每个点依次执行以下操作:
(1)计算已知类别数据集中的点与当前点之间的距离;
(2)按照距离递增次序排序;
(3)选取与当前点距离最小的k个点;
(4)确定前k个点所在类别的出现频率;
(5)返回前k个点出现频率最高的类别作为当前点的预测分类。
classify0()函数有4个输入参数:用于分类的输人向量是inX,输入的训练样本集为dataSet,标签向量为labels,最后的参数k表示用于选择最近邻居的数目,其中标签向量的元素数目和矩阵dataSet的行数相同。程序使用欧氏距离公式,计算两个向量点xA和xB之间的距离➊:
例如,点(0, 0)与(1, 2)之间的距离计算为:
如果数据集存在4个特征值,则点(1, 0,0, 1)与(7, 6, 9, 4)之间的距离计算为:
计算完所有点之间的距离后,可以对数据按照从小到大的次序排序。然后,确定前k个距底最小元素所在的主要分类❷,输人k总是正整数;最后,将classCount字典分解为元组列表,然后使用程序第二行导入运算符模块的itemgetter方法,按照第二个元素的次序对元组进行排序❸。此处的排序为逆序,即按照从最大到最小次序排序,最后返回发生频率最高的元素标签。
为了预测数据所在分类,在Python提示符中输人下列命令:
>>> kNN. classify0([0,0], group, labels, 3)
输出结果应该是B,大家也可以改变输入[0, 0]为其他值,测试程序的运行结果。
3、如何测试分类器
上文我们已经使用k近邻算法构造了第一个分类器,也可以检验分类器给出的答案是否符合我们的预期。读者可能会问:“分类器何种情况下会出错?”或者“答案是否总是正确的?”答案是否定的,分类器并不会得到百分百正确的结果,我们可以使用多种方法检测分类器的正确率。此外分类器的性能也会受到多种因素的影响,如分类器设置和数据集等。不同的算法在不同数据集上的表现可能完全不同。
为了测试分类器的效果,我们可以使用已知答案的数据,当然答案不能告诉分类器,检验分类器给出的结果是否符合预期结果。通过大量的测试数据,我们可以得到分类器的错误率——分类器给出错误结果的次数除以测试执行的总数。错误率是常用的评估方法,主要用于评估分类器在某个数据集上的执行效果。完美分类器的错误率为0,最差分类器的错误率是1.0,在这种情况下,分类器根本就无法找到一个正确答案。
下面将在现实世界中使用k近邻算法,我们将使用k近邻算法改进约会网站的效果。
二、示例:使用k-近邻算法改进约会网站的配对效果
我的朋友海伦一直使用在线约会网站 寻找适合自己的约会对象。尽管约会网站会推荐不同的 人选,但她并不是喜欢每一个人。 经过一番总结,她发现曾交往过三种类型的人:
1.不喜欢的人
2.魅力一般的人
3.极具魅力的人
尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归人恰当的类别。她觉得 可以在周一到周五约会那些魅力一般的人, 而周末则更喜欢与那些极具魅力的人为伴。海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外海伦还收集了一些约会网站未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。
示例:在约会网站上使用k-近邻算法 |
(1)收集数据:提供文本文件。 |
(2)准备数据:使用Python解析文本文件。 |
(3)分析数据:使用Matplotlib画二维扩散图。 |
(4)训练算法:此步骤不适用于人近邻算法。 |
(5)测试算法:使用海伦提供的部分数据作为测试样本。 测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。 |
(6)使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。 |
1、准备数据:从文本文件中解析数据
海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet2.txt中,每个样本数据占据一行,总共有1000行。海伦的样本主要包含以下3种特征:
1.每年获得的飞行常客里程数
2.玩视频游戏所耗时间百分比
3.每周消费的冰淇淋公升数
在将上述特征数据输人到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。在kNN.py中创建名为file2matrix的函数,以此来处理输入格式问题。该函数的输人为文件名字符串,输出为训练样本矩阵和类标签向量。
将下面的代码增加到kNN.py中。
#将文记录转换为NumPy的解析程序
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')
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(round(float(listFromLine[-1])))
index += 1
return returnMat,classLabelVector
在Python命令提示符下输人下面命令:
>>> reload (kNN)
>>> datingDataMat, datingLabels = kNN. file2matrix( 'datingTestSet2.txt')
使用函数f ile2matrix读取文件数据,必须确保文件dating TestSet.xt存储在我们的工作目录 中。此外在执行这个函数之前,我们重新加载了kNN.py模块, 以确保更新的内容可以生效,否则Python将继续使用上次加载的kNN模块。
成功导人datingTestSet.txt文件中的数据之后,可以简单检查一下数据内容。Python的输出结果大致如下:
>>> datingDataMat
array( [ [ 7.29170000e+04, 7.10627300e+00, 2.23600000e-01],
[ 1.42830000e+04, 2.44186700e+00, 1.90838000e-01],
[ 7.34750000e+04, 8.31018900e+00, 8.52795000e-01],
...,
[ 1.24290000e+04, 4.43233100e+00, 9.24649000e-01],
[ 2.52880000e+04, 1.31899030e+01, 1.05013800e+00],
[ 4.91800000e+03, 3.01112400e+00, 1.90663000e-01]])
>>> datingLabels[0:20]
[3,2,1,1,1,1,3,3,1,3,1,1,2,1,1,1,1,1,2,3]
2、分析数据:使用Matplotlib创建散点图
首先我们使用Matplotlib制作原始数据的散点图,在Python命令行环境中, 输人下列命令: import matplotlib
>>> import matplotlib.pyplot as plt
>>> fig = plt. figure ()
>>> ax = fig.add subplot (111)
>>> ax.scatter (dat ingDataMat[:,1], datingDataMat[:,21)
>>> plt. show()
输出效果如图2所示。散点图使用datingDataMat矩阵的第 二 、第三列数据,分别表示特征值“玩视频游戏所耗时间百分比”和“每周所消费的冰淇淋公升数”。
图2 散点图
3、准备数据:归一化数据
表1给出了提取的四组数据,如果想要计算样本3和样本4之间的距离,可以使用下面的方法:
表1 约会网站原始数据改进之后的样本数据
序号 | 玩视频游戏所耗时间百分比 | 每年获得的飞行常客里程数 | 每周消费的冰淇淋公斤数 | 样本分类 |
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 134000 | 0.9 | 3 |
3 | 0 | 20000 | 1.1 | 2 |
4 | 67 | 32000 | 0.1 | 2 |
归一化的公式:
其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。我们需要在文件kNN.py中增加一个新函数autoNorm(),该函数可以自动将数字特征值转化为0到1的区间。
函数autoNorm()的代码:
#归一化特征值
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)) #element wise divide
return normDataSet, ranges, minVals
在Python命令提示符下,重新加载kNN.py模块,执行autoNorm函数,检测函数的执行结果: >>> reload (kNN)
>>> normMat, ranges, minVals = kNN. autoNorm (dat ingDataMat)
>>> normMat
array([[ 0.33060119, 0. 58918886, 0. 69043973],
[ 0.49199139, 0. 50262471, 0. 13468257],
[ 0.34858782, 0.68886842, 0.59540619],
...,
[ 0.93077422, 0.52696233, 0.58885466],
[ 0. 76626481, 0.44109859, 0.88192528] ,
[0.0975718 , 0. 02096883, 0.02443895]])
>>> ranges
array([ 8. 78430000e+04, 2. 02823930e+01, 1. 69197100e+00] )
>>> minVals
array([ 0. , 0. , 0.001818])
4、测试算法:作为完整程序验证分类器
#分类器针对约会网站的测试代码
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 = round(float(m*hoRatio))
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
print ("分类器返回的结果是: %d, 真实结果是: %d" % (classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): errorCount += 1.0
print ("分类器处理约会数据集的错误率是: %f" % (errorCount/float(numTestVecs)))
在Python命令提示符下重新加载kNN模块,并输人kNN. datingClassTest(),执行分类器测试程序,我们将得到下面的输出结果:
>>> kNN. datingClassTest ()
the classifier came back with: 1,the real answer is: 1
the classifier came back with: 2,the real answer is: 2
.
.
the classifier came back with: 1,the real answer is: 1
the classifier came back with: 2,the real answer is: 2
the classifier came back with: 3,the real answer is: 3
the classifier came back with: 3,the real answer is: 1
the classifier came back with: 2,the real answer is: 2
the total error rate is: 0. 024000
5、使用算法:构建完整可用系统
将下列代码加人到kNN.py并重新载人kNN。
#约会网站预测函数
def classifyPerson():
resultList = ['不喜欢的人', '魅力一般的人', '极具魅力的人']
precentTats = float(input('玩视频游戏所耗时间百分比:'))
ffMiles = float(input('每年获得的飞行常客里程数:'))
iceCream = float(input('每周消费的冰淇淋公升数:'))
datingDatMat, datingLabels = fileMatrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDatMat)
inArr = array([precentTats, ffMiles, iceCream])
norminArr = (inArr-minVals)/ranges
classifierResult = classify(norminArr, normMat, datingLabels, 3)
print('这个人可能是你%'%(resultList[classifierResult-1]))
为了解程序的实际运行效果,输入如下命令:
>>> kNN. classifyPerson()
percentage of time spent playing video games?10
frequent flier miles earned per year?10000
liters of ice cream consumed per year?0.5
You will probably like this person: in small doses
三、实验结论
k-近邻算法是分类数据最简单最有序的算法。在使用K-近邻算法改进约会网站的配对效果的实例中,我体会到了KNN算法的思想,同时也感觉到了python语言的简便性和强大性,比如对于KNN算法的核心部分代码,计算距离远近,数组dataSet训练样本集可以兼容计算任意维度距离。但在使用算法时,我们必须有足够多接近实际数据的训练样本数据。