赞
踩
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型
存在一个样本数据集合(训练样本集),并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。
输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据的分类标签,即最近邻的分类标签。
一般来说,我们只选取样本数据集中前K个最相似的数据,通常K是不大于20的整数。
最后,选择K个最相似数据中出现次数最多的分类,作为数据的分类。
(1)收集数据:数据是数值型,并带有分类标签
(2)准备数据:通过python从文本文件中解析数据,并归一化特征值
(3)分析数据:使用matplotlib画二维散点图
(4)测试算法:作为对分类器的验证,计算它的错误率和错误个数
(5)使用算法:输入特征数值,来对未知类别的数据进行分类
(1)KNN分类算法的classify0函数有4个参数,其中的inX需要注意,它是输入向量,即待测试数据。它用到了tile函数,这个我在下文的代码解析会提到。
(2)标签向量labels的元素数目和矩阵dataset的行数相同
(3)读取文件数据,必须确保txt等文档存储在当前的工作目录
(4)创建散点图,需要确保环境有matplotlib(这点针对零基础的学习者)
(5)归一化特征值是很有必要的,因为这样能避免较大的特征数据对计算的影响,使得每个特征值对计算结果的影响是一样的。
(6)可以通过改变datingClassTest函数的hoRatio和K值,使得错误率发生变化,错误率是随着它们的变化而增加。
(7)书中“2-5约会网站预测函数”的raw_input函数是python2中的,python3环境会报错,直接换成input函数即可。
(8)同样,书中“2-1 k-近邻算法”,用到的iteritems也是python2中的,在python3环境下,应该换成items。
这一章节书中共有9个函数代码,下面我会逐个展示代码及注释,并做一些相关的解析。
这里,我将对classify0函数和createDataSet函数进行解析。
首先,createDataSet函数是一个很基础的函数,它用来创建数据集和标签,并能返回数据集和标签的值。
而classify0函数,则是这章的精髓。它的功能是将每组数据划分到某个类中。
函数包含4个参数,其中inX是用于分类的输入向量,dataSet是输入的训练样本集,labels是标签向量,K是用于选择最近邻居的数目。
而函数的大体思路可以概括为5个步骤:
(1)计算已知数据集的点与当前点之间的距离
(2)按照距离递增次序排序
(3)选取与当前点距离最小的K个点
(4)确定前K个点所在类别的出现频率
(5)返回前K个点出现频率最高的类别
而这些步骤的解释,我在下面的代码片里有所提及,可以详见注释。
相关代码及注释:
#KNN分类算法,inX是用于分类的输入向量,dataSet是输入的训练样本集,labels是标签向量,K是用于选择最近邻居的数目 def classify0(inX, dataSet, labels, k): # 以下是第一步:计算已知数据集的点与当前点之间的距离 dataSetSize = dataSet.shape[0] #获取数据集的宽 diffMat = tile(inX, (dataSetSize,1)) - dataSet #使用欧式距离度量,故将输入向量和数据集中各向量相减 sqDiffMat = diffMat**2 #进行平方 sqDistances = sqDiffMat.sum(axis=1) #计算输入向量和数据集中各向量之间的差的平方和 distances = sqDistances**0.5 #计算欧式距离 # 以下是第二步:按照距离递增次序排序 sortedDistIndicies = distances.argsort() #取得输入向量和数据集中各向量欧式距离的从小到大排序的下标值,返回排序后的索引值 # 以下是第三步:选取与当前点距离最小的K个点 classCount={} #定义一个空字典 for i in range(k): #取计算欧氏距离排序后最小的前k个值 voteIlabel = labels[sortedDistIndicies[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #若字典中存在label键,则对应的值+1,如果不存在,则添加这个键设置为0并+1 # 以下是第四步:确定前K个点所在类别的出现频率 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) #按照字典中的值对字典进行降序排列 # 最后是第五步,返回前K个点出现频率最高的类别 return sortedClassCount[0][0] #创建数据集和标签 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
补充:
因为函数里有用到tile函数,这个函数将inX向量进行相应的变换,使得它与dataSet矩阵为相似矩阵,并进行相应的操作。
而tile函数的形式是: tile(A,rep),
A: Array类的都可以
rep:A沿着各个维度重复的次数(由外至内)
它的功能是:重复A的各个维度
如果对tile函数感兴趣,可以参照这个Python tile函数详细解释
这里,我将对“准备数据”的相关代码函数进行解析,即file2matrix函数和 autoNorm函数
file2matrix函数的功能是从文本文件中解析数据,输入的参数为文件名字,返回的结果为训练样本矩阵和类标签向量。
file2matrix函数的大体思路可以概况为3个步骤:
(1)得到文件行数
(2)创建返回的Numpy矩阵
(3)解析文件数据到列表
同样,这些步骤的解释,我在下面的代码片里有所提及,可以详见注释。
autoNorm函数的功能是归一化特征值,输入的参数为数据集数据,返回的结果为归一化矩阵,取值范围以及最小值。
至于归一化特征值的作用,我在上文有所提及,可以详见“1.3 注意事项”的第5点。
相关代码及注释:
#从文本文件中解析数据,输入为文件名字,输出为训练样本矩阵和类标签向量 def file2matrix(filename): # 以下是第一步:得到文件行数 fr = open(filename) #打开文件 numberOfLines = len(fr.readlines()) #计算该文件共有多少行(即共有多少个样本) # 以下是第二步:创建返回的Numpy矩阵 returnMat = zeros((numberOfLines,3)) #创建返回的Numpy矩阵 classLabelVector = [] #定义下面需要返回的标签 fr = open(filename) #需要再次打开文件,否则会报错 index = 0 # 以下是第三步:解析文件数据到列表 for line in fr.readlines(): #解析文件数据到列表 line = line.strip() #去掉首尾空白符 listFromLine = line.split('\t') #利用空格符分离字符串 returnMat[index,:] = listFromLine[0:3] #将每行样本数据的前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 #返回归一化矩阵,取值范围以及最小值
这里,我将对创建散点图的相关代码进行解析。
散点图使用矩阵的第二、第三列数据,分别表示特征值。
而它的scatter函数个性化标记散点图上的点。它利用变量datingLabels存储的类标签属性,在散点图上绘制了色彩不等、尺寸不同的点。它还利用datingDatamat矩阵的第二列和第三列属性来展示数据。
相关代码:
# 创建散点图,散点图使用矩阵的第二、第三列数据,分别表示特征值
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDatamat[:,1], datingDatamat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
matplotlib.pyplot.show()
“测试算法”对应datingClassTest函数,“使用算法”对应classifyperson函数。这里我将对它们的相关代码进行解析。
datingClassTest函数的功能是计算分类器的错误率和错误个数,它输出计算得到的错误率和错误个数。
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
同样,这些函数的解释,我在下面的代码片里有所提及,可以详见注释。
相关代码及注释:
#计算分类器的错误率和错误个数 def datingClassTest(): hoRatio = 0.50 #取测试样本占数据集样本的10% datingDataMat,datingLabels = file2matrix('datingTestSet2(1).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],3) #通过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))) #计算错误率,并输出 print(errorCount) #输出错误的个数 #约会网站的对象判断 def classifyperson(): resultList = ['not at all', 'in small doses', 'in large doses'] #喜欢程度的预测值列表 #输入约会对象的一些特征 percentTats = float(input("percentage of time spent playing video games?")) ffMiles = float(input("frequent flier miles earned per year?")) iceCream = float(input("liters of ice cream consumed per year?")) datingDatamat,datingLabels = file2matrix('datingTestSet2(1).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])
手写识别系统包含2个部分,即“准备数据”和“测试算法”。
“准备数据”对应img2vector函数,“测试算法”对应handwritingClassTest函数。这里我将对它们的相关代码进行解析。
img2vector函数的功能是将图像转换为测试向量,它最后返回测试向量,这个向量本身也完全是由01构成,相当于将原来的矩阵每一行首尾相连。
handwritingClassTest函数的功能是使用K-近邻算法识别手写数字,并计算错误率和错误个数。
同样,这些函数的解释,我在下面的代码片里有所提及,可以详见注释。
相关代码及注释:
#将图像转换为测试向量 def img2vector(filename): returnVect = zeros((1,1024)) #返回一个1*1024的向量 fr = open(filename) for i in range(32): lineStr = fr.readline() #每次读一行 for j in range(32): returnVect[0,32*i+j] = int(lineStr[j]) #这个向量本身也完全是由01构成,相当于将原来的矩阵每一行首尾相连 return returnVect #识别手写数字 def handwritingClassTest(): hwLabels = [] trainingFileList = listdir('trainingDigits') #导入训练集,‘trainingDigits’是一个文件夹 m = len(trainingFileList) #计算训练样本个数 trainingMat = zeros((m,1024)) #初始化数据集,将所有训练数据用一个m行,1024列的矩阵表示 for i in range(m): fileNameStr = trainingFileList[i] #获得所有文件名,文件名格式‘x_y.txt’,x表示这个手写数字实际表示的数字(label) fileStr = fileNameStr.split('.')[0] #去除 .txt classNumStr = int(fileStr.split('_')[0]) #classnumber为每个样本的分类,用‘_’分割,取得label hwLabels.append(classNumStr) #将所有标签都存进hwLables[] trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) #将文件转化为向量后存入trainingMat[],这里展现了灵活的文件操作 testFileList = listdir('testDigits') #迭代测试集 errorCount = 0.0 mTest = len(testFileList) for i in range(mTest): fileNameStr = testFileList[i] fileStr = fileNameStr.split('.')[0] #去除 .txt classNumStr = int(fileStr.split('_')[0]) vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) #这部分针对测试集的预处理和前面基本相同 classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) #使用算法预测样本所属类别,调用了前面写的classify0()函数 print ("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr)) if (classifierResult != classNumStr): errorCount += 1.0 #算法结果与样本的实际分类做对比 print ("\nthe total number of errors is: %d" % errorCount) print ("\nthe total error rate is: %f" % (errorCount/float(mTest)))
from numpy import * #导入科学计算包numpy模块 import operator #导入运算符模块 from os import listdir #用于获取目录中的文件名 import matplotlib #用于创建散点图 import matplotlib.pyplot as plt #将pyplot命名为plt #KNN分类算法,inX是用于分类的输入向量,dataSet是输入的训练样本集,labels是标签向量,K是用于选择最近邻居的数目 def classify0(inX, dataSet, labels, k): # 以下是第一步:计算已知数据集的点与当前点之间的距离 dataSetSize = dataSet.shape[0] #获取数据集的宽 diffMat = tile(inX, (dataSetSize,1)) - dataSet #使用欧式距离度量,故将输入向量和数据集中各向量相减 sqDiffMat = diffMat**2 #进行平方 sqDistances = sqDiffMat.sum(axis=1) #计算输入向量和数据集中各向量之间的差的平方和 distances = sqDistances**0.5 #计算欧式距离 # 以下是第二步:按照距离递增次序排序 sortedDistIndicies = distances.argsort() #取得输入向量和数据集中各向量欧式距离的从小到大排序的下标值,返回排序后的索引值 # 以下是第三步:选取与当前点距离最小的K个点 classCount={} #定义一个空字典 for i in range(k): #取计算欧氏距离排序后最小的前k个值 voteIlabel = labels[sortedDistIndicies[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #若字典中存在label键,则对应的值+1,如果不存在,则添加这个键设置为0并+1 # 以下是第四步:确定前K个点所在类别的出现频率 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) #按照字典中的值对字典进行降序排列 # 最后是第五步,返回前K个点出现频率最高的类别 return sortedClassCount[0][0] #创建数据集和标签 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 #从文本文件中解析数据,输入为文件名字,输出为训练样本矩阵和类标签向量 def file2matrix(filename): # 以下是第一步:得到文件行数 fr = open(filename) #打开文件 numberOfLines = len(fr.readlines()) #计算该文件共有多少行(即共有多少个样本) # 以下是第二步:创建返回的Numpy矩阵 returnMat = zeros((numberOfLines,3)) #创建返回的Numpy矩阵 classLabelVector = [] #定义下面需要返回的标签 fr = open(filename) #需要再次打开文件,否则会报错 index = 0 # 以下是第三步:解析文件数据到列表 for line in fr.readlines(): #解析文件数据到列表 line = line.strip() #去掉首尾空白符 listFromLine = line.split('\t') #利用空格符分离字符串 returnMat[index,:] = listFromLine[0:3] #将每行样本数据的前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 datingClassTest(): hoRatio = 0.50 #取测试样本占数据集样本的10% datingDataMat,datingLabels = file2matrix('datingTestSet2(1).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],3) #通过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))) #计算错误率,并输出 print(errorCount) #输出错误的个数 #约会网站的对象判断 def classifyperson(): resultList = ['not at all', 'in small doses', 'in large doses'] #喜欢程度的预测值列表 #输入约会对象的一些特征 percentTats = float(input("percentage of time spent playing video games?")) ffMiles = float(input("frequent flier miles earned per year?")) iceCream = float(input("liters of ice cream consumed per year?")) datingDatamat,datingLabels = file2matrix('datingTestSet2(1).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]) #将图像转换为测试向量 def img2vector(filename): returnVect = zeros((1,1024)) #返回一个1*1024的向量 fr = open(filename) for i in range(32): lineStr = fr.readline() #每次读一行 for j in range(32): returnVect[0,32*i+j] = int(lineStr[j]) #这个向量本身也完全是由01构成,相当于将原来的矩阵每一行首尾相连 return returnVect #识别手写数字 def handwritingClassTest(): hwLabels = [] trainingFileList = listdir('trainingDigits') #导入训练集,‘trainingDigits’是一个文件夹 m = len(trainingFileList) #计算训练样本个数 trainingMat = zeros((m,1024)) #初始化数据集,将所有训练数据用一个m行,1024列的矩阵表示 for i in range(m): fileNameStr = trainingFileList[i] #获得所有文件名,文件名格式‘x_y.txt’,x表示这个手写数字实际表示的数字(label) fileStr = fileNameStr.split('.')[0] #去除 .txt classNumStr = int(fileStr.split('_')[0]) #classnumber为每个样本的分类,用‘_’分割,取得label hwLabels.append(classNumStr) #将所有标签都存进hwLables[] trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) #将文件转化为向量后存入trainingMat[],这里展现了灵活的文件操作 testFileList = listdir('testDigits') #迭代测试集 errorCount = 0.0 mTest = len(testFileList) for i in range(mTest): fileNameStr = testFileList[i] fileStr = fileNameStr.split('.')[0] #去除 .txt classNumStr = int(fileStr.split('_')[0]) vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) #这部分针对测试集的预处理和前面基本相同 classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) #使用算法预测样本所属类别,调用了前面写的classify0()函数 print ("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr)) if (classifierResult != classNumStr): errorCount += 1.0 #算法结果与样本的实际分类做对比 print ("\nthe total number of errors is: %d" % errorCount) print ("\nthe total error rate is: %f" % (errorCount/float(mTest))) if __name__ == "__main__": # 当程序执行时 # 调用基础函数 group,labels = createDataSet() a = classify0([0,0], group, labels, 3) print(a) # 从文件中解析数据 f = open("datingTestSet2(1).txt", "r") datingDatamat,datingLabels = file2matrix('datingTestSet2(1).txt') print(datingDatamat) print(datingLabels[0:20]) # 创建散点图,散点图使用矩阵的第二、第三列数据,分别表示特征值 fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(datingDatamat[:,1], datingDatamat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels)) matplotlib.pyplot.show() # 归一化特征值 normMat, ranges, minVals = autoNorm(datingDatamat) print(normMat) print(ranges) print(minVals) # 计算分类器的错误率和错误个数 datingClassTest() # 约会网站的对象判断 classifyperson() # 将图像转换为测试向量 testVector = img2vector('testDigits/0_13.txt') print(testVector[0,0:31]) print(testVector[0,32:63]) # 计算分类器的错误率和错误个数 handwritingClassTest()
以上是我整理的k-近邻算法的相关知识和代码解析,望对大家有用。由于初写,如有纰漏或者错误,欢迎指出。
以下是对K-近邻算法的评价:
1.算法执行效率低:算法需要为每个测试向量做大约2000次距离计算,每个距离计算包括了1024个维度浮点计算,总共要执行900次。此外,还需要为测试向量准备一定的存储空间。所以这个算法不仅耗时,而且需要不少的存储空间。
2.k-临近算法的另一个缺陷就是它无法给出任何数据的基础结构信息,因此我们无法得知平均实例样本和典型实例样本具体有什么特征。
3.k-近邻算法是分类数据最简单最有效的算法,并且精度高、对异常值不敏感、无数据输入假定。
4.K决策树就是k-近邻算法的优化版,可以节省大量的计算开销。
[1]: 《机器学习实战》[美] Peter Harrington 著 李锐 李鹏 曲亚东 王斌 译
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。