KNN算法实现手写数字识别

KNN 最邻近分类算法:

(近邻取样)
邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法是机器学习分类技术中最简单的方法之一。
所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。

属于监督学习,有类别标记,且KNN是惰性学习。叫做Memory-based learning、也叫instance-based learning. 他没有明显的前期训练过程,在程序运行之后,把数据加载到内存后,不需要进行训练就可以分类。

算法实现:
1、计算出每一个样本点与测试点的距离
2、选取距离最近的K个样本,并获取他们的标签 label
3、然后找出K个样本中数量最多的标签,返回该标签

KNN的本质是基于一种数据统计的方法。

下面的是KNN案例的应用:手写数字识别。
我这里的案例是文本格式。没有图片转换的步骤。
素材模型:(源码+素材最后会贴上githup的链接)

在这里插入图片描述

KNN 手写数字识别

实现思路:

  • 将测试数据转换成只有一列的0-1矩阵形式 将所有(L个)训练数据也都用上方法转换成只有一列的0-1矩阵形式

  • 把L个单列数据存入新矩阵A中——矩阵A每一列存储一个字的所有信息

  • 用测试数据与矩阵A中的每一列求距离,求得的L个距离存入距离数组中

  • 从距离数组中取出最小的K个距离所对应的训练集的索引 拥有最多索引的值就是预测值

第一步:
导入模块:

import os,time,operator             #导入os内置库来读取文件名     导入time来测试效率
import pandas as pd                 #导入数据处理库pandas        安装方法pip install pandas
import numpy as np                  #导入科学计算库numpy         安装方法pip install numpy
import matplotlib.pyplot as plt     #导入绘图库matplotlib        安装方法pip install matplotlib

第二步:
引入文件,定义一个读取数据的转换数据的函数

## print(len(tarining))      #1934个训练集                     ## print(len(test))   #945个测试集
trainingDigits =r'D:\work\日常任务6机器学习\day2手写数字识别\trainingDigits'
testDigits = r'D:\work\日常任务6机器学习\day2手写数字识别\testDigits'
                                                      ## ↑数据路径
tarining = (os.listdir(trainingDigits))                 ## 读取训练集
test = (os.listdir(testDigits))                        ## 读取测试集
def read_file(doc_name):                             ## 定义一个把32x32格式转为1行的函数
    data=np.zeros((1,1024))                         ## 创建1个zero数组
    f=open(doc_name)                               ## 打开文件
    for i in range(32):                          ## 已知每个文件中有32行32列
        hang=f.readline()                       ## 取行
        for j in range(32):                   ## 取每行中的每一列
            data[0,32*i+j]=int(hang[j])      ## 给data值
    # print(pd.DataFrame(data))             ## 不要在这里转换成DataFrame。
    return data                           ## 否则测试集效率会降低7倍
                                         ## 读取训练集效率会降低12倍

第三步:
定义一个字段转列表的函数,后面会用。
因为我为了提高效率没有使用pandas里面的Dataframe来操作数据。

def dict_list(dic:dict):               ## 定义函数将字典转化为列表
    keys = dic.keys()                 ## dic.keys()就是字典的k
    values = dic.values()            ## dic.values()就是字典的V
    lst = [(key,val) for  key,val in zip(keys, values)] ## for k,v in zip(k,v)
    return lst                        ## zip是一个可迭代对象
                                        ## 返回一个列表

第四步:
定义相似度函数:

def xiangsidu(tests,xunlians,labels,k):    ## tests:测试集 # xulians:训练样本集 # labels:标签 # k: 邻近的个数
    data_hang=xunlians.shape[0]              ## 获取训练集的行数data_hang
    zu=np.tile(tests,(data_hang,1))-xunlians   ## 用tile把测试集tests重构成一个 data_hang行、1列的1维数组
    q=np.sqrt((zu**2).sum(axis=1)).argsort()     ## 计算完距离后从低到高排序,argsort返回的是索引
    my_dict = {}                                   ## 设置一个dict
    for i in range(k):                              ## 根据我们的k来统计出现频率,样本类别
        votelabel=labels[q[i]]                         ## q[i]是索引值,通过labels来获取对应标签
        my_dict[votelabel] = my_dict.get(votelabel,0)+1   ## 统计每个标签的次数
    sortclasscount=sorted(dict_list(my_dict),key=operator.itemgetter(1),reverse=True)
                                                         ## 获取votelabel键对应的值,无返回默认
    return sortclasscount[0][0]                        ## 返回出现频次最高的类别

第五步:
编写识别函数:

def shibie():                                        ## 定义一个识别手写数字的函数
    label_list = []                                    ## 将训练集存储到一个矩阵并存储他的标签
    train_length = len(tarining)                        ## 直接一次获取训练集长度
    train_zero = np.zeros((train_length,1024))           ## 创建(训练集长度,1024)维度的zeros数组
    for i in range(train_length):                         ## 通过遍历训练集长度
        doc_name = tarining[i]                              ## 获取所有的文件名
        file_label = int(doc_name[0])                         ## 取文件名第一位文件的标签
        label_list.append(file_label)                           ## 将标签添加至handlabel中
        train_zero[i,:] = read_file(r'%s\%s'%(trainingDigits,doc_name))## 转成1024的数组
                                                                    ## 下面是测试集
    errornum = 0                                                  ## 记录error的初值
    testnum = len(test)                                         ## 同上 获取测试集的长度
    errfile = []                                              ## 定义一个空列表
    for i in range(testnum):                               ## 将每一个测试样本放入训练集中使用KNN进行测试
        testdoc_name = test[i]                           ## 通过i当作下标来获取测试集里面的文件
        test_label = int(testdoc_name[0])              ## 拿到测试文件的名字 拿到我们的数字标签
        testdataor = read_file(r'%s\%s' %(testDigits,testdoc_name)) ## 调用read_file操作测试集
        result = xiangsidu(testdataor, train_zero, label_list, 3)  ## 调用xiangsidu返回了result
        print("正在测试 %d, 内容是 %d" % (test_label,result))    ## 输出result和标签
        if (result != test_label):                               ## 判断标签是否等于测试名
            errornum += 1                                       ## 不是则+1 记录次数
            errfile.append(testdoc_name)                       ## 并把错误的文件名加入错误列表
    print("错误数量有 :%d" % errornum)                       ## 输出错误的数量
    print("错误的有 :%s"%[i for i in errfile])             ## 输出错误的列表中的名字
    print("准确率 %.2f%%" % ((1 - (errornum / float(testnum))) * 100)) ## 计算准确率

最后调用:

if __name__ == '__main__':                                        ## 声明主函数
    a = time.time()                                              ## 设置起始时间
    shibie()                                                   ## 调用测试函数
    b= time.time() - a                                       ## 计算运行时间
    print("运行时间:",b)                                   ## 输出运行时间

没啥太多好说的,为了改进效率在中间进行了很多技巧性的操作,虽然还是一堆for循环。
但是每个步骤的注释都写的很清楚,相信大家是可以看懂的,如有不懂请留言。

Github完整链接: https://github.com/lixi5338619/KNN_Distinguish/tree/master
在这里插入图片描述

点赞
  1. Pingback: go

发表回复