《机器学习实战》读书笔记(二)K-近邻算法(下)(手写识别系统)

本文是《机器学习实战》读书笔记的第二部分,详细介绍了如何构建一个基于K-近邻算法的手写数字识别系统。通过解析digits.zip数据集,将图像转换为测试向量,然后使用K-近邻算法进行训练和测试,最终实现了一个错误率为1.06%的识别系统。讨论了k-近邻算法的特点,包括其简单有效性、数据保存需求以及计算复杂性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:本作者书写的博文中所包含的所有文件,读者均可从作者提供的链接中下载,创作不易,多谢理解!

2.3手写识别系统

本节我们将一步步地构建使用k-近邻分类器的手写识别系统,

老规矩,先附上文件下载链接https://download.csdn.net/download/qq_38172282/11232509

链接文件中的材料如下图所示:

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

digits.zip文件为压缩文件,里面包含testDigits+trainingDigits文件
testDigits文件为测试用的手写数字数据,包含大约900个测试数据,
trainingDigits文件为训练用的手写数字数据,包含大约2000个测试数据,每个数字约含有200个样本

上述文件中:文件名 a_b.txt 中,a代表的是文件中展示的数字,b代表的是数字的编号,内容是32*32的0和1组成的图案。

本节代码较为简单,首先创建一个1*1024的Numpy数组,然后打开给定的文件,循环读出文件的前32行,并将每行的头32个字符值存储在Numpy数组中,最后返回数组!

话不多说,可直接运行代码如下所示:

import numpy as np

#将32*32的二进制图像矩阵转换为1*1024的向量
def img2vector(filename):
    returnVect = np.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

testVector = img2vector('testDigits/0_13.txt')

print(testVector[0, 0:31])
print(testVector[0, 32:63])

运行结果如下:

 

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

1、对训练集,获取目录下的txt文件名字,组成列表。在for循环中,通过对文件名的字符操作可以得到Label(标签),然后打开文件,将32*32转成1*1024,最后得到训练集的特征矩阵和标签向量

2、对测试集,一样得到标签和特征向量,然后将特征向量、训练集特征矩阵和标签向量、k=3输入def classify0,返回预测标签,与实际标签比对。
在下面的代码中,为了读者能理解程序清单2-6,我特意添加了较为详细的备注!

可直接运行代码如下所示:

import numpy as np
import operator
from os import listdir		#用于获取文件夹下的文件名

# #对目标元素进行分类
# #程序清单2-1
# def classify0(inX, dataSet, labels, k):
#     dataSet_size = dataSet.shape[0]
#     # 在列方向上重复inX,1次,行方向上重复inX,dataSet_size次
#     diff_mat = np.tile(inX, (dataSet_size, 1)) - dataSet
#     # diff_mat:输入样本与每个训练样本的差值,然后对其每个x和y的差值进行平方运算
#     sq_diff_mat = diff_mat ** 2
#     # 按行进行累加,axis=1表示按行。
#     sq_distances = sq_diff_mat.sum(axis=1)
#     # 开方运算,求出距离
#     distances = sq_distances ** 0.5
#     # 返回distances中元素从小到大排序后的索引值
#     sorted_dist_indices = distances.argsort()
#     # 定一个字典:统计类别次数
#     class_count = {}
#     for i in range(k):
#         # 取出前k个元素的类别
#         vote_index_label = labels[sorted_dist_indices[i]]
#         # 统计类别次数
#         class_count[vote_index_label] = class_count.get(vote_index_label, 0) + 1
#         # 把分类结果进行降序排序,然后返回得票数最多的分类结果
#         sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
#         return sorted_class_count[0][0]

#与书中程序清单相同,不过是改进后的K-近邻算法
def classify0(X, dataset, labels, k):
    size = dataset.shape[0]
    dis = np.sqrt(np.sum(np.square(dataset-X), axis=1))		#差值后平方、求和、开方
    sort_index = np.argsort(dis)		#按照距离排序,返回的是排序后的索引
    label_count = {}
    for i in range(k):
        label = labels[sort_index[i]]
        label_count[label] = label_count.get(label, 0)+1
    sort_label = sorted(label_count.items(), key=lambda d: d[1], reverse=True)		#按照标签出现次数进行排序
    return sort_label[0][0]


#将32*32的二进制图像矩阵转换为1*1024的向量
def img2vector(filename):
    returnVect = np.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

def handwritingClassTest():
    # 初始化我们所需要的数据
    hwLabels = []
    # 这里需要我们提前导入os模块,listdir可以列出给定目录下的文件名
    trainingFileList = listdir('trainingDigits') # 加载训练集
    m = len(trainingFileList) # 获得训练样本数目
    trainingMat = np.zeros((m,1024)) # 构造m×1024的矩阵
    
    # 循环遍历训练集中的每个文件,生成每个数字的向量信息,保存在trainingMat中
    for i in range(m):
        fileNameStr = trainingFileList[i] # 获得文件名
        fileStr = fileNameStr.split('.')[0] # 分离.txt
        classNumStr = int(fileStr.split('_')[0]) # 获得该文件所代表的数字
        hwLabels.append(classNumStr) # 将文件所代表的数字其存放在类别标签中
        trainingMat[i,:] = img2vector('trainingDigits\\%s' % fileNameStr)
    
    # 遍历测试数据文件夹,使用KNN进行测试。
    testFileList = listdir('testDigits') # 遍历测试集
    errorCount = 0.0
    mTest = len(testFileList) # 获得测试样本数目
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]  # 分离.txt
        classNumStr = int(fileStr.split('_')[0]) # 获得该文件所代表的数字
        vectorUnderTest = img2vector('testDigits\\%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) # 分类
        print ("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount/float(mTest)))


print(handwritingClassTest())

运行结果如下图所示:

K-近邻算法识别手写数字数据集,错误率为1.06%,改变变量k的值、修改函数handwritingClassTest()随机选取训练样本、改变训练样本的数目,都会对k-近邻算法的错误率产生影响,感兴趣的话可以改变这些变量值,观察错误率的变化。

 

K-近邻算法小结

  • k-近邻算法是分类数据最简单最有效的算法。
  • k-近邻算法是基于实例的学习,使用算法时我们必须有接近实际数据的训练样本数据。
  • k-近邻算法必须保存全部数据集,如果训练数据集的很大,必须使用大量的存储空间。此外, 由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时
  • k-近邻算法的另一个缺陷是它无法给出任何数据的基础结构信息,因此我们也无法知晓平均实例样本和典型实例样本具有什么特征。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值