实战一:kNN手写识别系统
本文将一步步地构造使用K-近邻分类器的手写识别系统。由于能力有限,这里构造的系统只能识别0-9。需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小:32像素*32像素的黑白图像。
当前使用文本格式存储图像,即使不能有效的利用空间,但是为了方便理解,还是将图像转换成文本格式。
示例:使用k-近邻算法的手写识别系统
(1)收集数据:提供文本文件。
(2)处理数据:编写img2vector()函数,将图像格式转换成分类器使用的向量格式。
(3)分析数据:在Python命令提示符中检查数据,确保它符合要求。
(4)训练算法:此步骤不适用于k-近邻算法。
(5)测试算法:编写函数使用提供的部分数据集作为测试样本,对学习算法进行测试。
(6)使用算法:本例没有完成此步骤
准备数据:将图像转换为测试向量
我们所使用的两个文件trainingDigits中包含了大约2000个例子,每个数字大约有200个样本;测试文件testDigits中包含了大约900个测试数据。两组数据没有重叠。为了使用kNN算法分类器必须将一个
32*32的二进制矩阵转换为1*1024的向量,以便我们使用分类器处理数字图像信息。
首先我们定义img2vector()函数,将32*32的二进制矩阵转换成1*1024的矩阵并返回:
def img2vector(filename): returnVector = zeros((1,1024)) with open(filename) as fr: for i in range(32): lineStr = fr.readline() for j in range(32): returnVector[0,32*i+j] = lineStr[j] return returnVector
执行下述代码:
testVector = img2vector("testDigits/0_13.txt") print(testVector[0,0:31]) print(testVector[0,32:61])
得到结果:
[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.] [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.]
k-近邻算法
k-近邻算法的一般流程
- 收集数据:可使用任何方法
- 准备数据:距离计算所需要的数值,最好是结构化的数据格式。
- 分析数据:可以使用任何方法。
- 训练算法:此步骤不适用于K-近邻算法
- 使用算法:首先需要输入样本数据和节后话的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理
#kNN分类器 def classify0(inX, dataSet, labels, k): dataSetSize = dataSet.shape[0] #得到数据总量 diffMat = tile(inX,(dataSetSize,1)) - dataSet #将输入数据扩充成与数据集同样大小的矩阵并作差 sqDiffMat = diffMat**2 sqDistances = sqDiffMat.sum(axis=1) #axis = 1 参数是维度参数等于1在此处表示将一个矩阵的每一行向量相加 distances = sqDistances** 0.5 sortedDistancesIndicies = distances .argsort() #将列表值进行对比返回一个按照数值升序的下标值 classCount={} for i in range(k): voteIlabel = labels[sortedDistancesIndicies[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0)+1 #dict.get("key") 返回value dict.get("key",default= None)如果能找到就返回对应的value找不到返回默认值 sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse=True) #sorted 返回一个list operator.itemgetter(x,y)表示根据x+1维度的第y+1维度 return sortedClassCount[0][0]
测试算法:使用k-近邻算法识别手写数字
现在我们得到处理完成的数据还有分类算法,现在我们需要构造handwritingClassTest()函数进行分类器测试。为了处理大量的文本文件我们需要from os import listdir用于列出指定目录的文件名,读取多个
数字文本文件。
def handwritingClassTest(): hwLabels = [] #训练数据真实值数组 trainingFileList = listdir("trainingDigits") #获取trainingDigits文件子目录的列表 m = len(trainingFileList) #获得训练数据总数 trainingMat = zeros((m,1024)) #初始化训练数据矩阵 for i in range(m): #循环将trainingDigits文件下的训练数据文本文件放入矩阵traningMat中,真实值放入hwLabels中 fileNameStr = trainingFileList[i] #获取该次循环的文件名字符串 fileStr = fileNameStr.split('.')[0] #将获得的字符串按分隔符'.'分隔并取第一个即去拓展名的文件名 classNumber = int(fileStr.split('_')[0]) #获取训练数据的真实值 非numpy数据需要指定数据类型int hwLabels.append(classNumber) #将得到的单个真实值按顺序加入到真实值列表hwLabels中 trainingMat[i,:] = img2vector("trainingDigits/%s"%fileNameStr) #把32*32的二进制文本文件转换成1*1024矩阵并按行存储到训练数据总矩阵中 testFileList = listdir("testDigits") errorCount = 0.0 #错误预测计数器 mTest = len(testFileList) #测试数据总量 for i in range(mTest): fileNameStr = testFileList[i] fileStr = fileNameStr.split('.')[0] classNumber = int(fileStr.split('_')[0]) vectorUnderTest = img2vector("testDigits/%s"%fileNameStr) classifierResult = classify0(vectorUnderTest,trainingMat,hwLabels,3) #用kNN分类算法分类 if(classifierResult != classNumber) : #判断预测是否正确,不正确计数器+1打印错误预测 errorCount +=1.0 print("预测值为:%d ,真实值为:%d " % (classifierResult, classNumber)) print("测试总数:%d,预测错误总数:%d ,错误率为:%f"%(mTest,errorCount,errorCount/float(mTest))) handwritingClassTest()
执行效果:
预测值为:7 ,真实值为:1 预测值为:9 ,真实值为:3 预测值为:3 ,真实值为:5 预测值为:6 ,真实值为:5 预测值为:6 ,真实值为:8 预测值为:3 ,真实值为:8 预测值为:1 ,真实值为:8 预测值为:1 ,真实值为:8 预测值为:1 ,真实值为:9 预测值为:7 ,真实值为:9 测试总数:946,预测错误总数:10 ,错误率为:0.010571
总结
k-近邻算法识别手写数据集,错误率为1%。改变kNN分类函数中的k值、修改训练样本的内容和数目都会对错误率产生影响,可以改变这些数值观察错误率的变化。实际使用这个算法的时候,算法的执行
效率并不高。我们需要进行2000次距离计算,每个距离计算包括了1024个维度的浮点数,总计执行900次,此外,我们还要为测试向量准备存储空间。期待有更好的算法能够改进。