第2章 k-近邻算法
http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default">>
KNN 概述
k-近邻(kNN, k-NearestNeighbor)算法主要是用来进行分类的.
KNN 场景
电影可以按照题材分类,那么如何区分动作片和爱情片呢?
动作片:打斗次数更多
爱情片:亲吻次数更多
基于电影中的亲吻、打斗出现的次数,使用 k-近邻算法构造程序,就可以自动划分电影的题材类型。
现在根据上面我们得到的样本集中所有电影与未知电影的距离,按照距离递增排序,可以找到 k 个距离最近的电影。
假定 k=3,则三个最靠近的电影依次是, He's Not Really into Dudes 、 Beautiful Woman 和 California Man。
knn 算法按照距离最近的三部电影的类型,决定未知电影的类型,而这三部电影全是爱情片,因此我们判定未知电影是爱情片。
KNN 原理
KNN 工作原理
假设有一个带有标签的样本数据集(训练样本集),其中包含每条数据与所属分类的对应关系。
输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较。
计算新数据与样本数据集中每条数据的距离。
对求得的所有距离进行排序(从小到大,越小表示越相似)。
取前 k (k 一般小于等于 20 )个样本数据对应的分类标签。
求 k 个数据中出现次数最多的分类标签作为新数据的分类。
KNN 开发流程
收集数据:任何方法
准备数据:距离计算所需要的数值,最好是结构化的数据格式
分析数据:任何方法
训练算法:此步骤不适用于 k-近邻算法
测试算法:计算错误率
使用算法:输入样本数据和结构化的输出结果,然后运行 k-近邻算法判断输入数据分类属于哪个分类,最后对计算出的分类执行后续处理
KNN 算法特点
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型
KNN 项目案例
项目案例1: 优化约会网站的配对效果
项目概述
海伦使用约会网站寻找约会对象。经过一段时间之后,她发现曾交往过三种类型的人:
不喜欢的人
魅力一般的人
极具魅力的人
她希望:
工作日与魅力一般的人约会
周末与极具魅力的人约会
不喜欢的人则直接排除掉
现在她收集到了一些约会网站未曾记录的数据信息,这更有助于匹配对象的归类。
开发流程
收集数据:提供文本文件
准备数据:使用 Python 解析文本文件
分析数据:使用 Matplotlib 画二维散点图
训练算法:此步骤不适用于 k-近邻算法
测试算法:使用海伦提供的部分数据作为测试样本。
测试样本和非测试样本的区别在于:
测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。
使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。
收集数据:提供文本文件
海伦把这些约会对象的数据存放在文本文件datingTestSet2.txt中,总共有 1000 行。海伦约会的对象主要包含以下 3 种特征:
每年获得的飞行常客里程数
玩视频游戏所耗时间百分比
每周消费的冰淇淋公升数
文本文件数据格式如下:
40920 8.326976 0.953952 3
14488 7.153469 1.673904 2
26052 1.441871 0.805124 1
75136 13.147394 0.428964 1
38344 1.669788 0.134296 1
准备数据:使用 Python 解析文本文件
将文本记录转换为 NumPy 的解析程序
deffile2matrix(filename):"""Desc:导入训练数据parameters:filename: 数据文件路径return:数据矩阵 returnMat 和对应的类别 classLabelVector"""fr=open(filename)#获得文件中的数据行的行数numberOfLines=len(fr.readlines())#生成对应的空矩阵#例如:zeros(2,3)就是生成一个 2*3的矩阵,各个位置上全是 0returnMat=zeros((numberOfLines,3))#prepare matrix to returnclassLabelVector=[]#prepare labels returnfr=open(filename) index=0forlineinfr.readlines():#str.strip([chars]) --返回移除字符串头尾指定的字符生成的新字符串line=line.strip()#以 '\t' 切割字符串listFromLine=line.split('\t')#每列的属性数据returnMat[index, :]=listFromLine[0:3]#每列的类别数据,就是 label 标签数据classLabelVector.append(int(listFromLine[-1])) index+=1#返回数据矩阵returnMat和对应的类别classLabelVectorreturnreturnMat, classLabelVector
分析数据:使用 Matplotlib 画二维散点图
importmatplotlibimportmatplotlib.pyplotaspltfig=plt.figure()ax=fig.add_subplot(111)ax.scatter(datingDataMat[:,1], datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))plt.show()
下图中采用矩阵的第一和第三列属性得到很好的展示效果,清晰地标识了三个不同的样本分类区域,具有不同爱好的人其类别区域也不同。
归一化数据 (归一化是一个让权重变为统一的过程,更多细节请参考:https://www.zhihu.com/question/19951858)
序号玩视频游戏所耗时间百分比每年获得的飞行常客里程数每周消费的冰淇淋公升数样本分类
10.84000.51
212134 0000.93
3020 0001.12
46732 0000.12
样本3和样本4的距离: $$\sqrt{(0-67)^2 + (20000-32000)^2 + (1.1-0.1)^2 }$$
归一化特征值,消除特征之间量级不同导致的影响
defautoNorm(dataSet):"""Desc:归一化特征值,消除特征之间量级不同导致的影响parameter:dataSet: 数据集return:归一化后的数据集 normDataSet. ranges和minVals即最小值与范围,并没有用到归一化公式:Y = (X-Xmin)/(Xmax-Xmin)其中的 min 和 max 分别是数据集中的最小特征值和最大特征值。该函数可以自动将数字特征值转化为0到1的区间。"""#计算每种属性的最大值、最小值、范围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 dividereturnnormDataSet, ranges, minVals
训练算法:此步骤不适用于 k-近邻算法
因为测试数据每一次都要与全量的训练数据进行比较,所以这个过程是没有必要的。
测试算法:使用海伦提供的部分数据作为测试样本。如果预测分类与实际类别不同,则标记为一个错误。
kNN 分类器针对约会网站的测试代码
defdatingClassTest():"""Desc:对约会网站的测试方法parameters:nonereturn:错误数"""#设置测试数据的的一个比例(训练数据集比例=1-hoRatio)hoRatio=0.1#测试范围,一部分测试一部分作为样本#从文件中加载数据datingDataMat, datingLabels=file2matrix('input/2.KNN/datingTestSet2.txt')#load data setfrom file#归一化数据normMat, ranges, minVals=autoNorm(datingDataMat)#m 表示数据的行数,即矩阵的第一维m=normMat.shape[0]#设置测试的样本数量, numTestVecs:m表示训练样本的数量numTestVecs=int(m*hoRatio)print'numTestVecs=', numTestVecs errorCount=0.0foriinrange(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.0print"the total error rate is:%f"%(errorCount/float(numTestVecs))printerrorCount
使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。
约会网站预测函数
defclasdifyPerson(): resultList=['not at all','in small doses','in large doses'] percentTats=float(raw_input("percentage of time spent playing video games ?")) ffMiles=float(raw_input("frequent filer miles earned per year?")) iceCream=float(raw_input("liters of ice cream consumed per year?")) datingDataMat, datingLabels=file2matrix('datingTestSet2.txt') normMat, ranges, minVals=autoNorm(datingDataMat) inArr=array([ffMils, percentTats, iceCream]) classifierResult=classify0((inArr-minVals)/ranges,normMat,datingLabels,3)print"You will probably like this person:", resultList[classifierResult-1]
实际运行效果如下:
>>>kNN.classifyPerson()percentage of time spent playing video games?10frequent flier miles earned per year?10000liters of ice cream consumed per year?0.5You will probably like this person:insmall doses
完整代码地址:https://github.com/apachecn/MachineLearning/blob/master/src/python/2.KNN/kNN.py
项目案例2: 手写数字识别系统
项目概述
构造一个能识别数字 0 到 9 的基于 KNN 分类器的手写数字识别系统。
需要识别的数字是存储在文本文件中的具有相同的色彩和大小:宽高是 32 像素 * 32 像素的黑白图像。
开发流程
收集数据:提供文本文件。
准备数据:编写函数 img2vector(), 将图像格式转换为分类器使用的向量格式
分析数据:在 Python 命令提示符中检查数据,确保它符合要求
训练算法:此步骤不适用于 KNN
测试算法:编写函数使用提供的部分数据集作为测试样本,测试样本与非测试样本的
区别在于测试样本是已经完成分类的数据,如果预测分类与实际类别不同,
则标记为一个错误
使用算法:本例没有完成此步骤,若你感兴趣可以构建完整的应用程序,从图像中提取
数字,并完成数字识别,美国的邮件分拣系统就是一个实际运行的类似系统
收集数据: 提供文本文件
目录trainingDigits中包含了大约 2000 个例子,每个例子内容如下图所示,每个数字大约有 200 个样本;目录testDigits中包含了大约 900 个测试数据。
准备数据: 编写函数 img2vector(), 将图像文本数据转换为分类器使用的向量
将图像文本数据转换为向量
defimg2vector(filename): returnVect=zeros((1,1024)) fr=open(filename)foriinrange(32): lineStr=fr.readLine()forjinrange(32): returnVect[0,32*i+j]=int(lineStr[j])returnreturnVect
分析数据:在 Python 命令提示符中检查数据,确保它符合要求
在 Python 命令行中输入下列命令测试 img2vector 函数,然后与文本编辑器打开的文件进行比较:
>>>testVector=kNN.img2vector('testDigits/0_13.txt')>>>testVector[0,0:31]array([0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,1.,1.,1.,1.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.])>>>testVector[0,31:63]array([0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,1.,1.,1.,1.,1.,1.,1.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.])
训练算法:此步骤不适用于 KNN
因为测试数据每一次都要与全量的训练数据进行比较,所以这个过程是没有必要的。
测试算法:编写函数使用提供的部分数据集作为测试样本,如果预测分类与实际类别不同,则标记为一个错误
defhandwritingClassTest():#1. 导入训练数据hwLabels=[] trainingFileList=listdir('input/2.KNN/trainingDigits')#load the training setm=len(trainingFileList) trainingMat=zeros((m,1024))#hwLabels存储0~9对应的index位置, trainingMat存放的每个位置对应的图片向量foriinrange(m): fileNameStr=trainingFileList[i] fileStr=fileNameStr.split('.')[0]#take off .txtclassNumStr=int(fileStr.split('_')[0]) hwLabels.append(classNumStr)#将 32*32的矩阵->1*1024的矩阵trainingMat[i, :]=img2vector('input/2.KNN/trainingDigits/%s'%fileNameStr)#2. 导入测试数据testFileList=listdir('input/2.KNN/testDigits')#iterate through the test seterrorCount=0.0mTest=len(testFileList)foriinrange(mTest): fileNameStr=testFileList[i] fileStr=fileNameStr.split('.')[0]#take off .txtclassNumStr=int(fileStr.split('_')[0]) vectorUnderTest=img2vector('input/2.KNN/testDigits/%s'%fileNameStr) classifierResult=classify0(vectorUnderTest, trainingMat, hwLabels,3)print"the classifier came back with:%d, the real answer is:%d"%(classifierResult, classNumStr)if(classifierResult!=classNumStr): errorCount+=1.0print"\nthe total number of errors is:%d"%errorCountprint"\nthe total error rate is:%f"%(errorCount/float(mTest))
使用算法:本例没有完成此步骤,若你感兴趣可以构建完整的应用程序,从图像中提取数字,并完成数字识别,美国的邮件分拣系统就是一个实际运行的类似系统
完整代码地址:https://github.com/apachecn/MachineLearning/blob/master/src/python/2.KNN/kNN.py
作者:羊三小瑶
GitHub地址:https://github.com/apachecn/MachineLearning
版权声明:欢迎转载学习 => 请标注信息来源于ApacheCN