使用K近邻算法识别手写数字
这里构造的系统只能识别数字0-9,需要识别的数字已经使用图形处理软件处理成32*32大小的黑白图像,并将其转换为图像格式。
实际图像存储在两个子目录中:目录trainingDigits中,大约包含2000个例子,每个数字大约有200个样本,同一个数字有多种书写形态,数字样本被命名为:数字(0-9)_次序(0-xxx).txt文本的形式。testDigits中包含了大约900个测试数据,使用目录trainingDigits的数据来训练分类器,使用testDigits的数据来测试分类器的效果。
为使用前面的分类器,需要按照一行代表一个样本,每列一个特征的原则将图像格式化为一个向量。将32*32的二进制图像矩阵转化为一个1*1024的向量。首先编写函数img2vector:创建1*1024的Numpy数组,打开文件并循环读出文件的前32行,并将每行的头32个字符存储在Numpy数组中。源码如下:
#将32*32位的图像文本数据整合到1*1024的行向量中
def img2vector(filename):
returnVect = zeros((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])
return returnVect
遍历最里层的returnVect[0,32*i+j] = int(lineStr[j])语句实现将矩阵的元素赋予到行向量数组上的功能,前面的0代表该行向量的索引。
写入代码之前,需要将from os import listdir写入文件起始部分,它可以列出给定目录的文件名。以下是源码:
#对测试集进行测试并得到错误率
def handwritingClassTest():
hwLabels = []
#得到trainingDigits目录下的文件列表
trainingFileList = listdir('trainingDigits')
m = len(trainingFileList)
trainingMat = zeros((m,1024))
#遍历文件列表
for i in range(m):
fileNameStr = trainingFileList[i]
#去除文件名后缀
fileStr = fileNameStr.split('.')[0]
#根据文件名得到对应的数字即实际值
classNumStr = int(fileStr.split('_')[0])
#将实际值添加到结果集中
hwLabels.append(classNumStr)
#2维数组的行赋值,遍历结束得到训练集,每一行代表一个图形样本
trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
#得到测试集文件列表
testFileList = listdir('testDigits')
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = fileStr.split('_')[0]
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
#得到测试集的预估值
classiferResult = classify0(vectorUnderTest,trainingMat,hwLabels,3)
print("the classifier cameback with:%d,the real answer is %s"\
% (classiferResult,classNumStr))
#将预估值与实际值进行比较
if (classiferResult != int(classNumStr)):errorCount+=1.0
print("\nThe total number of errors is:%d" %errorCount)
print("\nThe total error rate is:%f" %(errorCount/float(mTest)))
listdir函数返回参数目录下的文件列表,通过遍历训练文件列表,提取文件名的数字项即样本的实际目标值,将实际目标值添加到标签列表中。然后通过字符串格式方法将文件路径传递到img2vector函数,并将返回的行向量赋给以i为索引的二维行向量中,遍历结束后是训练矩阵。再对测试集进行遍历,按照同样的方法得到测试数据的行向量,并将其作为参数传递给分类函数,得到预估值,并将预估值与实际目标值进行比较,得到错误计数,并计算错误率。
实际上这个算法的执行效率不高,每次测试数据都需要重新训练,适用于数据集小的情景,时间和存储花销较大。