笔记:使用k-近邻算法构建手写识别系统

笔记:使用k-近邻算法构建手写识别系统

  • 示例:使用k-近邻算法构建手写识别系统
    • 1.准备数据:将图像转换为测试向量
    • 2.测试算法:使用k-近邻算法识别手写数字
    • 3.使用算法:构建完整可用的系统

笔记使用jupyter notebook完成,之后导出为markdown格式调整。环境:Anaconda3

示例:使用k-近邻算法构建手写识别系统

描述:
使用k-近邻分类器构造只能识别数字0-9的手写识别系统。
需要识别的数字已处理成宽高都是32像素的黑白图像,使用文本格式存储。

流程:

  • 收集数据:提供文本文件。
  • 准备数据:编写函数 img2vector(), 将图像格式化处理为向量格式
  • 分析数据:在 Python 命令提示符中检查数据,确保它符合要求
  • 训练算法:此步骤不适用于 KNN
  • 测试算法:编写函数使用提供的部分数据集作为测试样本,测试样本与非测试样本的区别在于测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误
  • 使用算法:从图像中提取数字,并完成数字识别,美国的邮件分拣系统就是一个实际运行的类似系统

1.准备数据:将图像转换为测试向量

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.]

2.测试算法:使用k-近邻算法识别手写数字

将数据输入到分类器,检测分类器的执行效果。对于手写数字,如果预测数字与实际数字不同,则出错次数加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

3.使用算法:构建完整可用的系统

使用画图工具写出4个数字4,放到了路径data/digits/image/下,手写图片如下:
44_14_24_3

通过简单的处理将手写的图片直接转换为向量形式,进行使用。通过下边的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-近邻算法的执行效率并不高,必须对数据集中的每个数据计算距离值。
必须有接近实际数据的训练样本数据。
必须保存全部数据集,如果训练数据集很大,必须使用大量的存储空间。
无法给出任何数据的基础结构信息

你可能感兴趣的:(机器学习实战)