k-近邻实现手写数字识别

1.k-近邻工作原理

简单地说,K近邻算法采用测量不同特征值之间的距离方法进行分类。该算法具有一下特点。

  • 优点:精度高、对异常值不敏感、无数据输入假定。
  • 缺点:计算复杂度高、空间复杂度高。

K近邻算法的工作原理是:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前 个最相似的数据,这就是K近邻算法中 的出处,通常 是不大于20的整数。最后,选择 个最相似数据中出现次数最多的分类,作为新数据的分类。

K近邻算法的一般流程

  • 1.收集数据:可以使用任何方法。
  • 2.准备数据:距离计算所需要的数值,最好是结构化的数据格式。
  • 3.分析数据:可以使用任何方法。
  • 4.训练算法:此步骤不只适用于K近邻算法。
  • 5.测试算法:计算错误率。
  • 6.使用算法:首先需要输入样本数据和结构化的输出结果,然后运行K近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。

2.准备数据集

下载数据集:手写数字数据集
k-近邻实现手写数字识别_第1张图片
可以看到该数据集已经切分好了训练集和测试集。其目录结构如下:

digits 目录下有两个文件夹,分别是:

  • trainingDigits:训练数据,1934个文件,每个数字大约200个文件。
  • testDigits:测试数据,946个文件,每个数字大约100个文件。

打开jupyter,查看一下样本格式。

# 查看一下文件内容
!cat ./data/digits/trainingDigits/0_1.txt  # 前面需要加上!,否则会报错

k-近邻实现手写数字识别_第2张图片
我们需要把一个32x32的二进制图像矩阵转换为1x1024的向量。

import numpy as np
def img2vector(filename):
    # 创建向量
    returnVect = np.zeros((1, 1024))
    # 打开数据文件,读取每行内容
    fr = open(filename)
    for i in range(32):
        # 读取每一行
        lineStr = fr.readline()
        # 将每行前 32 字符转成 int 存入向量
        for j in range(32):
            returnVect[0, 32*i+j] = int(lineStr[j])       
    return returnVect

接着测试一下该方法。

# 测试一下
vect = img2vector('./data/digits/trainingDigits/0_1.txt')
print(vect.shape)
print(vect)

k-近邻实现手写数字识别_第3张图片

3.计算距离

算法实现过程:

  • 1.计算已知类别数据集中的点与当前点之间的距离;
  • 2.按照距离递增次序排序;
  • 3.选取与当前点距离最小的k个点;
  • 4.确定前k个点所在类别的出现频率;
  • 5.返回前k个点出现频率最高的类别作为当前点的预测分类。

本次k-近邻使用欧式距离作为计算公式。

import operator

#分类器采用欧式距离
def classify0(inX, dataSet, labels, k):
    """
    参数: 
    - inX: 用于分类的输入向量
    - dataSet: 输入的训练样本集
    - labels: 样本数据的类标签向量
    - k: 用于选择最近邻居的数目
    """
    
    # 获取样本数据数量
    dataSetSize = dataSet.shape[0]
    # 矩阵运算,计算测试数据与每个样本数据对应数据项的差值
    # tile(a, (10, 1)) 表示构造了10行a这个向量
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet

    # sqDistances 上一步骤结果平方和
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)

    # 取平方根,得到距离向量
    distances = sqDistances**0.5

    # 按照距离从低到高排序
    sortedDistIndicies = distances.argsort()
    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 createDataSet():
    group = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels

group, labels = createDataSet()
classify0([0, 0], group, labels, 3)

k-近邻实现手写数字识别_第4张图片

4.手写数字识别

实验的步骤:

  • 1.读取训练数据到向量(手写图片数据),从数据文件名中提取类别标签列表(每个向量对应的真实的数字)
  • 2.读取测试数据到向量,从数据文件名中提取类别标签
  • 3.执行K 近邻算法对测试数据进行测试,得到分类结果
  • 4.与实际的类别标签进行对比,记录分类错误率
  • 5.打印每个数据文件的分类数据及错误率作为最终的结果
# 进行手写数字测试
from os import listdir

def handwritigCalssTest():
    hwLabels = []   # 样本数据的标签
    trainingFileList = listdir('./data/digits/trainingDigits') # 读取样本数据列表
    m = len(trainingFileList)  # 样本数据的实例数
    trainingMat = np.zeros((m, 1024)) # 初始化样本数据矩阵
    # 依次读取所有样本到数据矩阵中
    for i in range(m):
        # 提取文件名中的数字
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr) 
        
        # 将样本数据存入矩阵
        trainingMat[i, :] = img2vector(
            './data/digits/trainingDigits/%s' % fileNameStr)
    
    # 循环读取测试数据
    testFileList = listdir('./data/digits/testDigits')
    # 初始化错误率
    errorCount = 0.0
    mTest = len(testFileList)
    
    # 循环测试每个测试数据文件
    for i in range(mTest):
        # 提取文件名中的数字
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        
        # 提取数据向量
        vectorUnderTest = img2vector('./data/digits/testDigits/%s' % fileNameStr)
        # 对数据文件进行分类
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        
        # 打印 K 近邻算法分类结果和真实的分类
        print("测试样本 %d, 分类器预测: %d, 真实类别: %d" %
              (i+1, classifierResult, classNumStr))
        
        # 判断K 近邻算法结果是否准确
        if (classifierResult != classNumStr):
            errorCount += 1.0
            
    # 打印错误率
    print("\n错误分类计数: %d" % errorCount)
    print("\n错误分类比例: %f" % (errorCount/float(mTest)))
    print('\n分类器准确率: %f' % (1 - (errorCount/float(mTest))))

训练并测试分类器。

handwritigCalssTest()

k-近邻实现手写数字识别_第5张图片
可以看到我们实现的k-近临分类器在该数据集上的效果达到了98%。接着使用scikit-learn机器学习库自带的k-近临分类器来实现上述过程。

读取训练集

hwLabels = []   # 样本数据的标签
trainingFileList = listdir('./data/digits/trainingDigits') # 读取样本数据列表
m = len(trainingFileList)  # 样本数据的实例数
trainingMat = np.zeros((m, 1024)) # 初始化样本数据矩阵
# 依次读取所有样本到数据矩阵中
for i in range(m):
    # 提取文件名中的数字
    fileNameStr = trainingFileList[i]
    fileStr = fileNameStr.split('.')[0]
    classNumStr = int(fileStr.split('_')[0])
    hwLabels.append(classNumStr) 
        
    # 将样本数据存入矩阵
    trainingMat[i, :] = img2vector('./data/digits/trainingDigits/%s' % fileNameStr)

k-近邻实现手写数字识别_第6张图片

读取测试集

# 循环读取测试数据
testFileList = listdir('./data/digits/testDigits')
# 初始化错误率
testLabels = []
mTest = len(testFileList)
print(mTest)
vectorUnderTest = np.zeros((mTest, 1024)) # 初始化样本数据矩阵
print(vectorUnderTest.shape)
# 循环测试每个测试数据文件
for i in range(mTest):
    # 提取文件名中的数字
    fileNameStr = testFileList[i]
    fileStr = fileNameStr.split('.')[0]
    classNumStr = int(fileStr.split('_')[0])
    testLabels.append(classNumStr)  
    # 提取数据向量
    vectorUnderTest[i, :] = img2vector('./data/digits/testDigits/%s' % fileNameStr)

k-近邻实现手写数字识别_第7张图片

训练分类器并测试性能

from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier()
model = model.fit(trainingMat, hwLabels)

model.score(vectorUnderTest, testLabels)

k-近邻实现手写数字识别_第8张图片
到这里该demo案例基本完成了,可以看到分类效果基本相当,但是使用sklearn库进行机器学习要简便很多。

demo下载地址:demo案例源码

你可能感兴趣的:(机器学习与深度学习,python,机器学习,sklearn)