描述:
使用k-近邻分类器构造只能识别数字0-9的手写识别系统。
需要识别的数字已处理成宽高都是32像素的黑白图像,使用文本格式存储。
流程:
trainingDigits文件夹中包含了大约 2000 个例子,每个数字大约有 200 个样本;
testDigits文件夹中包含了大约 900 个测试数据。
使用trainingDigits中的数据来训练分类器,使用testDigits中的数据来测试分类器的效果。两组数据没有覆盖。
编写函数,将图像转换为向量:
from numpy import *
def img2vector(filename):
"""
将图像转换为向量
:param filename:文件名
:return:numpy数组
"""
# 创建1*1024的numpy数组
returnVect = zeros((1, 1024))
# 打开指定文件
with open(filename) as fr:
# 循环每行
for i in range(32):
lineStr = fr.readline()
# 将每行的前32个字符值存储到numpy数组中
for j in range(32):
returnVect[0, 32 * i + j] = int(lineStr[j])
# 返回数组
return returnVect
运行测试效果:
filename = 'data/digits/testDigits/0_0.txt'
test_vector = img2vector(filename)
print(test_vector[0,0:31])
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0.]
将数据输入到分类器,检测分类器的执行效果。对于手写数字,如果预测数字与实际数字不同,则出错次数加1。
手写数字识别系统的测试分类器代码:
1.先分别获取训练数据的向量信息及分类信息(属于哪个数字,作为标签)
2.获取到测试数据的向量信息及真正的分类信息
3.将每一个测试向量通过kNN算法【通过计算和训练数据向量组成的矩阵数据集进行计算,从而获得前k个钟出现频率最高的那个分类】获得到预测的分类
4.将预测分类和真实分类进行比较,得到错误率
文件中的值在0和1之前,这里不用在进行归一化操作
import operator
from os import listdir
def classify0(inX, dataSet, labels, k):
"""
k-近邻算法
:param inX: 用于分类的输入向量
:param dataSet: 输入的训练样本集
:param labels: 标签向量
:param k: 表示用于选择最近邻居的数目
:return: 前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个最短距离, 选取这K个中最多的分类类别
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
def handwritingClassTest():
"""
手写数字识别系统的测试代码
:return:
"""
# 1. 导入训练数据
hwLabels = []
# 列出给定目录的文件名
trainingFileList = listdir('data/digits/trainingDigits')
m = len(trainingFileList)
# 初始化矩阵
trainingMat = zeros((m, 1024))
# hwLabels存储0~9对应的index位置, trainingMat存放的每个位置对应的图片向量
for i in range(m):
# 文件名称,如0_0.txt
fileNameStr = trainingFileList[i]
# 不带 .txt文件后缀的文件名
fileStr = fileNameStr.split('.')[0]
# 所表示的数字
classNumStr = int(fileStr.split('_')[0])
# 保存分类数字
hwLabels.append(classNumStr)
# 将 32*32的矩阵转化为1*1024的矩阵,每一行存储一个图像
trainingMat[i, :] = img2vector('data/digits/trainingDigits/%s' % fileNameStr)
# 2. 导入测试数据
testFileList = listdir('data/digits/testDigits')
# 错误次数
errorCount = 0.0
# 测试数据大小
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i]
# 不带 .txt文件后缀的文件名
fileStr = fileNameStr.split('.')[0]
# 实际所表示的数字
classNumStr = int(fileStr.split('_')[0])
# 测试数据的向量矩阵,每行数据表示一个图像向量
vectorUnderTest = img2vector('data/digits/testDigits/%s' % fileNameStr)
# kNN分类得到的结果
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
# 分类结果和实际结果不同
if (classifierResult != classNumStr):
errorCount += 1.0
print("kNN分类的结果得出的数字是: %d, 真实的数字是: %d" % (classifierResult, classNumStr))
print("\n总的分类错误的次数是: %d" % errorCount)
print("\n错误率为: %f" % (errorCount / float(mTest)))
测试函数的输出结果:
handwritingClassTest()
kNN分类的结果得出的数字是: 7, 真实的数字是: 1
kNN分类的结果得出的数字是: 9, 真实的数字是: 3
kNN分类的结果得出的数字是: 3, 真实的数字是: 5
kNN分类的结果得出的数字是: 6, 真实的数字是: 5
kNN分类的结果得出的数字是: 6, 真实的数字是: 8
kNN分类的结果得出的数字是: 3, 真实的数字是: 8
kNN分类的结果得出的数字是: 1, 真实的数字是: 8
kNN分类的结果得出的数字是: 1, 真实的数字是: 8
kNN分类的结果得出的数字是: 1, 真实的数字是: 9
kNN分类的结果得出的数字是: 7, 真实的数字是: 9
总的分类错误的次数是: 10
错误率为: 0.010571
使用画图工具写出4个数字4,放到了路径data/digits/image/
下,手写图片如下:
通过简单的处理将手写的图片直接转换为向量形式,进行使用。通过下边的classify_handwriting程序进行预测:
from PIL import Image
def image2vector(filename):
"""
将图片直接转换为向量
:param filename: 图片地址
:return: 图片向量
"""
with Image.open(filename) as image:
im=image.convert('L')
im.resize((32, 32))
im_array = array(im)
im_array=where(im_array<255,1,im_array)
im_array=where(im_array==255,0,im_array)
im_vector=im_array.ravel()
return im_vector
def classify_handwriting():
"""
手写数字分类预测
:return: 预测分类
"""
# 1. 导入训练数据
hwLabels = []
# 列出给定目录的文件名
trainingFileList = listdir('data/digits/trainingDigits')
m = len(trainingFileList)
# 初始化矩阵
trainingMat = zeros((m, 1024))
# hwLabels存储0~9对应的index位置, trainingMat存放的每个位置对应的图片向量
for i in range(m):
# 文件名称,如0_0.txt
fileNameStr = trainingFileList[i]
# 不带 .txt文件后缀的文件名
fileStr = fileNameStr.split('.')[0]
# 所表示的数字
classNumStr = int(fileStr.split('_')[0])
# 保存分类数字
hwLabels.append(classNumStr)
# 将 32*32的矩阵转化为1*1024的矩阵,每一行存储一个图像
trainingMat[i, :] = img2vector('data/digits/trainingDigits/%s' % fileNameStr)
imageFileList = listdir('data/digits/image')
mInput = len(imageFileList)
for i in range(mInput):
fileNameStr = imageFileList[i]
# 输入数据的向量矩阵
in_vector = image2vector('data/digits/image/%s' % fileNameStr)
# kNN分类得到的结果
classifierResult = classify0(in_vector, trainingMat, hwLabels, 3)
print(f"手写的数字是:{classifierResult}")
实际运行的效果如下:
classify_handwriting()
手写的数字是:4
手写的数字是:4
手写的数字是:4
手写的数字是:4
改变变量K的值、修改函数中随机选取的训练样本、改变训练样本的数目,都会对k-近邻算法的错误率产生影响。
k-近邻算法缺点:
k-近邻算法的执行效率并不高,必须对数据集中的每个数据计算距离值。
必须有接近实际数据的训练样本数据。
必须保存全部数据集,如果训练数据集很大,必须使用大量的存储空间。
无法给出任何数据的基础结构信息