机器学习KNN算法之手写数字数字识别

算法简介

手写数字识别是KNN算法一个特别经典的实例,其数据源获取方式有两种,一种是来自MNIST数据集,另一种是从UCI欧文大学机器学习存储库中下载,本文基于后者讲解该例。
基本思想就是利用KNN算法推断出如下图一个32x32的二进制矩阵代表的数字是处于0-9之间哪一个数字。

在这里插入图片描述

数据集包括两部分,一部分是训练数据集,共有1934个数据;另一部分是测试数据集,共有946个数据。所有数据命名格式都是统一的,例如数字5的第56个样本——5_56.txt,这样做为了方便提取出样本的真实标签。
在这里插入图片描述

数据的格式也有两种,一种是像上图一样由0、1组成的文本文件;另一种则是手写数字图片,需要对图片做一些处理,转化成像上图一样的格式,下文皆有介绍。

算法步骤

  1. 收集数据:公开数据源
  2. 分析数据,构思如何处理数据
  3. 导入训练数据,转化为结构化的数据格式
  4. 计算距离(欧式距离)
  5. 导入测试数据,计算模型准确率
  6. 手写数字,实际应用模型

由于所有数据皆由0和1构成,所以不需要数据标准化和归一化这一步骤

算法实现

处理数据

在计算两个样本之间的距离时,每一个属性是一一对应的,所以这里将32x32的数字矩阵转化成1x1024数字矩阵,方便计算样本之间距离。

#处理文本文件
def img_deal(file):
    #创建一个1*1024的一维零矩阵
    the_matrix = np.zeros((1,1024))
    fb = open(file)
    for i in range(32):
        #逐行读取
        lineStr = fb.readline()
        for j in range(32):
            #将32*32=1024个元素赋值给一维零矩阵
            the_matrix[0,32*i+j] = int(lineStr[j])
    return the_matrix
计算欧式距离

numpy有一个tile方法,可以将一个一维矩阵横向复制若干次,纵向复制若干次,所以将一个测试数据经过tile方法处理后再减去训练数据,得到新矩阵后,再将该矩阵中每一条数据(横向)平方加和并开根号后即可得到测试数据与每一条训练数据之间的距离。

下一步将所有距离升序排列,取到前K个,并在这个范围里,每个数字类别的个数,并返回出现次数较多那个数字类别的标签。

def classify(test_data,train_data,label,k):
    Size = train_data.shape[0]
    #将测试数据每一行复制Size次减去训练数据,横向复制Size次,纵向复制1次
    the_matrix = np.tile(test_data,(Size,1)) - train_data
    #将相减得到的结果平方
    sq_the_matrix = the_matrix ** 2
    #平方加和,axis = 1 代表横向
    all_the_matrix = sq_the_matrix.sum(axis = 1)
    #结果开根号得到最终距离
    distance = all_the_matrix ** 0.5
    #将距离由小到大排序,给出结果为索引
    sort_distance = distance.argsort()
    dis_Dict = {}
    #取到前k个
    for i in range(k):
        #获取前K个标签
        the_label = label[sort_distance[i]]
        #将标签的key和value传入字典
        dis_Dict[the_label] = dis_Dict.get(the_label,0)+1
    #将字典按value值的大小排序,由大到小,即在K范围内,筛选出现次数最多几个标签
    sort_Count = sorted(dis_Dict.items(), key=operator.itemgetter(1), reverse=True)
    #返回出现次数最多的标签
    return sort_Count[0][0]
测试数据集应用

首先要对训练数据集处理,listdir方法是返回一个文件夹下所有的文件,随后生成一个行数为文件个数,列数为1024的训练数据矩阵,并且将训练数据集中每条数据的真实标签切割提取存入至labels列表中,即计算距离classify函数中传入的label

labels = []
#listdir方法是返回一个文件夹中包含的文件
    train_data = listdir('trainingDigits')
    #获取该文件夹中文件的个数
    m_train=len(train_data)
    #生成一个列数为train_matrix,行为1024的零矩阵
    train_matrix = np.zeros((m_train,1024))
    for i in range(m_train):
        file_name_str = train_data[i]
        file_str = file_name_str.split('.')[0]
        #切割出训练集中每个数据的真实标签
        file_num = int(file_str.split('_')[0])
        labels.append(file_num)
        #将所有训练数据集中的数据都传入到train_matrix中
        train_matrix[i,:] = img_deal('trainingDigits/%s'%file_name_str)

然后对测试训练数据集做与上述一样的处理,并将测试数据矩阵TestClassify训练数据矩阵train_matrix训练数据真实标签labelsK共4个参数传入计算距离classify函数中,最后计算出模型准确率并输出预测错误的数据。

error = []
 test_matrix = listdir('testDigits')
    correct = 0.0
    m_test = len(test_matrix)
    for i in range(m_test):
        file_name_str = test_matrix[i]
        file_str = file_name_str.split('.')[0]
        #测试数据集每个数据的真实结果
        file_num = int(file_str.split('_')[0])
        TestClassify = img_deal('testDigits/%s'%file_name_str)
        classify_result = classify(TestClassify,train_matrix,labels,3)
        print('预测结果:%s\t真实结果:%s'%(classify_result,file_num))
        if classify_result == file_num:
            correct += 1.0
        else:
            error.append((file_name_str,classify_result))
    print("正确率:{:.2f}%".format(correct / float(m_test) * 100))
    print(error)
    print(len(error))

代码运行部分截图如下

在这里插入图片描述

当K = 3时,准确率达到了98.94%,对于这个模型而言,准确率是十分可观的,但运行效率却比较低,接近30秒的运行时间。因为每个测试数据都要与近2000个训练数据进行距离计算,而每次计算又包含1024个维度浮点运算,高次数多维度的计算是导致模型运行效率低的主要原因。

K值

下图是K值与模型准确率的关系变化图,K = 3时,模型准确率达到峰值,随着K增大,准确率越来越小,所以这份数据的噪声还是比较小的。


在这里插入图片描述
手写数字测试

建模完成了,模型的准确率也不错,为何自己手写的数字测试一下呢?所以偶就手动写了几个数字


在这里插入图片描述

正常拍出的图片是RGB彩色图片,并且像素也各不相同,所以需要对图片做两项处理:转化成黑白图片、将像素转化为32x32,这样才符合我们上文算法的要求;对于像素点,数值一般位于0-255,255代表白、0代表黑,但因为手写数字像素点颜色并不规范,所以我们设置一个阈值用以判断黑白之分。
图片转文本代码如下:

def pic_txt():
    for i in range(0,10):
        img = Image.open('.\handwritten\%s.png'%i)
        #将图片像素更改为32X32
        img = img.resize((32,32))
        #将彩色图片变为黑白图片
        img = img.convert('L')
        #保存
        path = '.\handwritten\%s_new.jpg'%i
        img.save(path)
    for i in range(0,10):
        fb = open('.\hand_written\%s_handwritten.txt'%i,'w')
        new_img = Image.open('.\handwritten\%s_new.jpg'%i)
        #读取图片的宽和高
        width,height = new_img.size
        for i in range(height):
            for j in range(width):
                # 获取像素点
                color = new_img.getpixel((j, i))
                #像素点较高的为图片中的白色
                if color>170:
                    fb.write('0')
                else:
                    fb.write('1')
            fb.write('\n')
        fb.close()

整体代码运行截图如下:

在这里插入图片描述

正确率为70%,毕竟测试数据很少,10个数字中4、7、8三个数字预测错误,还算可观;由于光线问题,有几个数字左下角会有一些黑影,也会对测试结果产生一定的影响,若避免类似情况,并且多增加一些测试数据,正确率定会得到提升的。

公众号【奶糖猫】后台回复“手写数字”即可获取源码和数据供参考,感谢阅读。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容