1. 概念
测量不同特征值之间的距离来进行分类
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高。
适用范围:数值型和标称型
工作原理:
存在一个样本数据合计,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似(最近邻)数据的分类标签。一般会选择样本数据集中前K个最相似的数据,K通常不大于20的整数,选择K个最相似数据中出现次数最多的分类,作为新数据的分类。
一般流程:
- 收集数据:任何方法
- 准备数据:距离计算所需要的数值,最好是结构化数据格式
- 分析数据:任何方法
- 训练算法:不适用
- 测试算法:计算错误率
- 使用算法:输入样本数据和结构化的输出结果,运行K近邻算法判断输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理
2. Python 实现
1. 数据输入
建立一个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
operator:运算符模块
createDataSet函数用于创建simple数据集和标签
在Console中
import KNN group,labels = KNN.createDataSet() group Out[69]: array([[ 1. , 1.1], [ 1. , 1. ], [ 0. , 0. ], [ 0. , 0.1]]) labels Out[70]: ['A', 'A', 'B', 'B']
通过KNN中的createDataSet函数创建变量group和labels
4个样本集在坐标轴上的位置:
2. 实施KNN分类算法
实现步骤:
- 计算已知类别数据集中的点与当前点之间的距离;
- 按照距离递增次序排序;
- 选取与当前点距离最小的k个点;
- 确定前k个点所在类别的出现频率;
- 返回前k个点出现频率最高的类别作为当前点的预测分类;
在KNN.py文件中创建classify0函数
1 def classify0(inX,dataSet,labels,k): 2 dataSetSize = dataSet.shape[0] 3 #距离计算 4 diffMat = tile(inX,(dataSetSize,1)) - dataSet 5 sqDiffMat = diffMat ** 2 6 sqDistances = sqDiffMat.sum(axis = 1) 7 distances = sqDistances ** 0.5 8 sortedDistIndicies = distances.argsort() 9 classCount = {} 10 for i in range(k): 11 voteIlabel = labels[sortedDistIndicies[i]] 12 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 13 sortedClassCount = sorted(classCount.iteritems(), 14 key=operator.itemgetter(1), 15 reverse=True) 16 return sortedClassCount[0][0]
参数:
- inX: 输入样本
- dataSet: 训练样本集
- labels:训练样本集标签
- k: 选取k个点
代码中使用的python方法:
- dataSet.shape():返回数据集维度(几行几列),这里需要返回有几条数据(几行) 可以用len(dataSet)替代?
- tile(inX,(dataSetSize,1)):将输入样本放入(并复制)到一个与数据集相同大小的矩阵
疑问:为什么不直接减?
tile(inX,(dataSetSize,1))- dataSet 与 inX - dataSet 结果一样
sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True):排序,reverse=True降序排列
计算距离公式
勾股定律:
重新装载KNN模块,python提示符中使用classify0函数来预测数据
1 reload(KNN) 2 Out[101]: <module 'KNN' from 'KNN.py'> 3 4 KNN.classify0([0,0],group,labels,3) 5 Out[102]: 'B'
3. 示例:使用K近邻算法改进约会网站的配对效果
示例说明:
样本集中包含三个特征值:
- 每年获得的飞行常客里程数
- 玩视频游戏所耗时间百分比
- 每周消费的冰激凌公升数
标签种类:
- 不喜欢的人
- 魅力一般的人
- 极具魅力的人
1. 从文本文件中解析数据
文本文件datingTestSet.txt
在KNN.py中增加文本转换为Numpy的解析函数
1 def file2matrix(filename): 2 fr = open(filename) 3 array0lines = fr.readlines() 4 numberOfLines = len(array0lines) 5 returnMat = np.zeros((numberOfLines,3)) 6 classLabelVector = [] 7 index = 0 8 for line in array0lines: 9 line = line.strip() 10 linstFromLine = line.split('\t') 11 returnMat[index,:] = linstFromLine[0:3] 12 classLabelVector.append(int(linstFromLine[-1])) 13 index +=1 14 return returnMat,classLabelVector
参数:
- filename:文件名
代码中使用的python方法:
- np.zeros((numberOfLines,3)) :创建以零填充的矩阵
- line.strip():去除回车字符
- line.split('\t'):以\t为分隔符分割字符
python命令提示符下输入命令:
1 reload(KNN) 2 Out[111]: <module 'KNN' from 'KNN.pyc'> 3 4 datingDataMat,datingLabels = KNN.file2matrix('datingTestSet2.txt') 5 6 datingDataMat 7 Out[113]: 8 array([[ 4.09200000e+04, 8.32697600e+00, 9.53952000e-01], 9 [ 1.44880000e+04, 7.15346900e+00, 1.67390400e+00], 10 [ 2.60520000e+04, 1.44187100e+00, 8.05124000e-01], 11 ..., 12 [ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01], 13 [ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01], 14 [ 4.37570000e+04, 7.88260100e+00, 1.33244600e+00]]) 15 16 datingLabels[:5] 17 Out[114]: [3, 2, 1, 1, 1]
2. 分析数据:使用Matplotlib创建散点图
1 import matplotlib 2 import matplotlib.pyplot as plt 3 fig = plt.figure() 4 ax = fig.add_subplot(111) 5 ax.scatter(datingDataMat[:,1],datingDataMat[:,2]) 6 fig
1 ax.scatter(datingDataMat[:,1],datingDataMat[:,2], 2 15.0*array(datingLabels),15.0*array(datingLabels)) 3 fig
1 ax.scatter(datingDataMat[:,0],datingDataMat[:,1], 2 15.0*array(datingLabels),15.0*array(datingLabels)) 3 fig
3. 准备数据:归一化数值
三个特征值计算样本距离:
每年获得飞行常客里程数远大于其他特征值需要转化为0-1区间內的值
newValue = (oldValue-min)/(max-min)
在KNN.py中增加归一化特征值函数
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
执行归一化函数
1 reload(KNN) 2 Out[145]: <module 'KNN' from 'KNN.py'> 3 4 normMat,ranges,minVals = KNN.autoNorm(datingDataMat) 5 6 normMat 7 Out[147]: 8 array([[ 0.44832535, 0.39805139, 0.56233353], 9 [ 0.15873259, 0.34195467, 0.98724416], 10 [ 0.28542943, 0.06892523, 0.47449629], 11 ..., 12 [ 0.29115949, 0.50910294, 0.51079493], 13 [ 0.52711097, 0.43665451, 0.4290048 ], 14 [ 0.47940793, 0.3768091 , 0.78571804]]) 15 16 ranges 17 Out[148]: array([ 9.12730000e+04, 2.09193490e+01, 1.69436100e+00]) 18 19 minVals 20 Out[149]: array([ 0. , 0. , 0.001156])
4. 测试算法:作为完整程序验证分类器
在KNN.py文件中添加测试函数datingClassTest
1 def datingClassTest(): 2 hoRatio = 0.1 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,:], 10 datingLabels[numTestVecs:m],3) 11 print "the classifier came back with: %d, the real answer is: %d" % (classifierResult,datingLabels[i]) 12 if (classifierResult != datingLabels[i]): 13 errorCount += 1.0 14 print "the total error rate is: %f" % (errorCount/float(numTestVecs))
在python命令提示符中运行datingClassTest()
1 reload(KNN) 2 Out[153]: <module 'KNN' from 'KNN.py'> 3 4 KNN.datingClassTest() 5 the classifier came back with: 3, the real answer is: 3 6 the classifier came back with: 2, the real answer is: 2 7 the classifier came back with: 1, the real answer is: 1 8 the classifier came back with: 3, the real answer is: 1 9 the total error rate is: 0.050000
错误率为5%
5. 使用算法:构建完整可用系统
在KNN.py中加入classifyPerson()函数:
1 def classifyPerson(): 2 resultList = ['not at all','insmall doses','in large doses'] 3 percentTats = float(raw_input("percentage of time spent playing video games?")) 4 ffMiles = float(raw_input("frequent flier miles earned per year?")) 5 iceCream = float(raw_input("liters of ice cream consumed per year?")) 6 datingDataMat,datingLabels = file2matrix('datingTestSet2.txt') 7 normMat , ranges, minVals = autoNorm(datingDataMat) 8 inArr = array([ffMiles,percentTats,iceCream]) 9 classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels,3) 10 print "You will probably like this person:",resultList[classifierResult - 1]
在python命令提示行执行classifyPerson函数
1 reload(KNN) 2 Out[155]: <module 'KNN' from 'KNN.py'> 3 4 KNN.classifyPerson() 5 6 percentage of time spent playing video games?10 7 8 frequent flier miles earned per year?10000 9 10 liters of ice cream consumed per year?0.5 11 You will probably like this person: insmall doses
4. 示例:手写数字识别系统
将图像转换为测试向量,将32*32的二进制图像矩阵转换为1*1024的向量
创建函数img2vector
1 def img2vector(filename): 2 returnVect = zeros(1,1024) #创建1*1024的矩阵 3 fr = open(filename) 4 for i in range(32): 5 lineStr = fr.readline() 6 for j in range(32): 7 returnVect[0,32*i+j] = int(lineStr[j])
测试算法:使用k近邻算法识别手写数字
1 def handwritingClassTest(): 2 hwLabels = [] 3 trainingFileList = listdir('trainingDigits') #获取目录内容 4 m = len(trainingFileList) 5 trainingMat = zeros((m,1024)) 6 #获取训练集向量和标签 7 for i in range(m): 8 #解析文件名 9 fileNameStr = trainingFileList[i] 10 fileStr = fileNameStr.split('.')[0] 11 classNumStr = int(fileStr.split('_')[0]) 12 hwLabels.append(classNumStr) 13 trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr) 14 testFileList = listdir('testDigits') 15 errorCount = 0 16 mTest = len(testFileList) 17 #获取测试集向量进行测试并将测试结果与正式结果比对 18 for i in range(mTest): 19 fileNameStr = testFileList[i] 20 fileStr = fileNameStr.split('.')[0] 21 classNumStr = int(fileStr.split('_')[0]) 22 vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) 23 classifierResult = KNN.classify0(vectorUnderTest,trainingMat,hwLabels,3) 24 print "the classifier came back with %d,the real answer is:%d" % (classifierResult,classNumStr) 25 if classifierResult != classNumStr: 26 errorCount += 1.0 27 print '\n the total number of errors is:%d' % errorCount 28 print '\n the total error rate is:%f' % (errorCount/float(mTest))
在python命令行运行handwritingClassTest()函数
handwritingClassTest() the classifier came back with 9,the real answer is:9 the classifier came back with 9,the real answer is:9 the classifier came back with 9,the real answer is:9 the total number of errors is:11 the total error rate is:0.011628
错误率1.1628%