上一篇撸了一个 KNN 分类器,为了体现撸了半天的价值,所以搞一下手写数字识别。

原理

说起来原理也简单,一共分为以下几个步骤:

  1. 准备分类样本
  2. 对样本进行处理,即向量化,说的直白一点就是把图片处理成一个数组
  3. 输入数据向量化
  4. 对数据进行分类并得到输入数据的分类结果

以上就是手写数字识别的整体过程,至于识别的准确与否可以归结为两个条件

  1. 分类器是否优秀
  2. 特征提取是否优秀

说了这么多就不废话了,我的大刀早已饥渴难耐了!!!

准备工作

不用多说,样本。
为了方便起见,这里使用 sklearn 提供的样本数据。加载方式如下:

from sklearn import datasets
digits = datasets.load_digits()

这里的 digits 我们只需要关注两个属性就可以 data、target,其中 data 为一组手写数字的向量数据,图片大小统一为 8乘以8,最终向量是一个长度为64的数组。 target 是每一个向量的标注,即每一个向量代表哪个数字。

代码实现

好了不再废话了,直接贴代码:

#!/usr/bin/env python  
# encoding: utf-8  

"""
@version: v1.0 
@author: zhangyw
@site: http://blog.zhangyingwei.com
@software: PyCharm 
@file: knn_nums_app.py 
@time: 2017/11/22 17:43 
"""

from sklearn import datasets
from sklearn.cross_validation import train_test_split
import ml.classify.knn.knn as knn

digits = datasets.load_digits()
datas = digits.data
targets = digits.target

x_train,x_test,y_train,y_test = train_test_split(datas,targets,test_size=0.4)

model = knn.Knn()
model.fit(x_train,y_train)

err = 0
for index,item in enumerate(x_test):
    res = model.predict(item)
    if y_test[index] != res:
        err+=1
        print("实际是{0},识别结果是{1}".format(y_test[index],res))
print(1-err/len(x_test))

解释一下,这里首先把数据切分为 60% 的训练数据与 40% 的测试数据。然后统计识别结果,并打印出识别错误的数据,最终打印出识别准确率。我这里最终的正确率为 0.9554937413073713,所以准确率还是可以的。

进阶

上边一系列代码实现了手写数字识别。
但是,我特么除了看到一个识别准确率之外啥都没看到,是可忍孰不可忍。
接下来我们就自己写一个数字来识别一下,怎么也得对得起手写数字识别这个题目不是?
废话说了,拔剑吧。。。

准备数据

这里我在 ps 中准备了十个长宽都为80像素的数字图片,都是用鼠标写出来的,丑了点。

准备代码

首先需要把图片内容转为向量,这里需要两个库,分别是 PILnumpy

from PIL import Image
import numpy as np

def load_img(num = 0):
    img_path = "D:/work/code/zhangyingwei/python/python-demos/input/ml/classify/knn/{0}.jpg".format(num)
    img = Image.open(img_path)  # 加载图片
    img = img.convert("L") # 图片转为灰度图
    img = img.resize((8,8)) # 修改图片大小为 8乘8 
    img_arr = np.asarray(img) # 图片转为二维数组
    return np.abs(img_arr.flatten()-np.tile([255],len(img_arr))) # 转为一维数组并对数据进行处理

解释一下上边的代码。首先我们先使用 PIL 中的 Image 加载图片到内存中,然后将图片转为灰度图,因为我们的图片本来就是黑白图,所以严格来说这一步是多余的。因为训练样本中的图片大小是 8乘8 的,所以需要将图片大小改为 8乘8。这一步完成之后使用 numpy 将图片转为一个二维数组。
完成以上步骤之后基本上就完成大部分了,先观察下数据。
样本中的数据格式:

[  0.   0.   4.  14.  16.   5.   0.   0.   0.   4.  16.  16.  16.   8.   0.
   0.   0.  10.  15.   9.  16.   4.   0.   0.   0.   1.   2.  13.  14.   0.
   0.   0.   0.   0.   2.  16.   6.   0.   0.   0.   0.   0.   7.  16.   0.
   5.   7.   0.   0.   0.   8.  16.  13.  16.   6.   0.   0.   0.   2.  15.
  16.   6.   0.   0.]

我们得到的数据格式:

[[255 255 232  13 207 255 255 255]
 [255 255  30 254 189 129 255 255]
 [255 255 156  40 148 254 255 255]
 [255 255 255 255  16 255 255 255]
 [255 255 255 255  40 255 255 255]
 [255 255 255 246 227 255 255 255]
 [255 255 255  53 255 255 255 255]
 [255 255 255  71 255 255 255 255]]

可以发现两个问题:

  1. 我们得到的数据多了一个维度
  2. 我们的数据与样本中的数据是相反的

所以需要处理我们的数据: np.abs(img_arr.flatten()-np.tile([255],len(img_arr))) ,这句代码的意思就是先将数据转为一维数组,然后数组中的每一位与255求差,最后对每一位求绝对值。
然后我们得到的数据如下:

[  0   0 233  14 208   0   0   0   0   0  31 255 190 130   0   0   0   0
 157  41 149 255   0   0   0   0   0   0  17   0   0   0   0   0   0   0
  41   0   0   0   0   0   0 247 228   0   0   0   0   0   0  54   0   0
   0   0   0   0   0  72   0   0   0   0]

这样就像多了,就不再做进一步处理了,先这样吧。附上全部代码

#!/usr/bin/env python  
# encoding: utf-8  

"""
@version: v1.0 
@author: zhangyw
@site: http://blog.zhangyingwei.com
@software: PyCharm 
@file: knn_nums_app.py 
@time: 2017/11/22 17:43 
"""

from sklearn import datasets
from sklearn.cross_validation import train_test_split
import ml.classify.knn.knn as knn
from PIL import Image
import numpy as np

digits = datasets.load_digits()
datas = digits.data
targets = digits.target
target_name = digits.target_names

x_train,x_test,y_train,y_test = train_test_split(datas,targets,test_size=0.4)

model = knn.Knn()
model.fit(x_train,y_train)

def load_img(num = 1):
    img_path = "D:/work/code/zhangyingwei/python/python-demos/input/ml/classify/knn/{0}.jpg".format(num)
    print(img_path)
    img = Image.open(img_path)
    img = img.convert("L")
    img = img.resize((8,8))
    img_arr = np.asarray(img)
    # print(img_arr)
    img_arr = img_arr.flatten()
    return np.abs(img_arr-np.tile([255],len(img_arr)))

for i in range(10):
    img = load_img(num=i)**0.5
    # print(img)
    res = model.predict(img)
    print("image is {0},and result is {1}".format(i,res))

输出结果如下:

image is 0,and result is 0
image is 1,and result is 1
image is 2,and result is 2
image is 3,and result is 3
image is 4,and result is 4
image is 5,and result is 1
image is 6,and result is 6
image is 7,and result is 7
image is 8,and result is 8
image is 9,and result is 7

从结果可以看出,准确率并没有预测的百分之九十以上,这跟我们准备的图片与数据处理有关系,如果数据处理的再好一点准确率也许会有一些提升。
好吧,就先水这么多了。