1、同学的烦恼

前段时间有一个同学在QQ里面对我说,”老师,我还有1个月就毕业答辩了。我要做一个数字识别系统,怎么办,我着急死了。”

看到他的话,顿时觉得替他惋惜,真想说:“早干嘛去了,现在剩这点时间,写代码,写论文来得及吗。”

后来,我还是忍住了,告诉他别着急,只要知道原理,用合适的方法,做一个识别率很高的数字识别系统,只需要50行代码。

他听完觉得不可思议,想尽脑汁的问题,只要50行代码,就可以解决,是不是太神奇了。看看,我们的聊天吧。

好了,那让我们来看看本课的神奇吧。

2、手写数字识别需要完成的任务

手写数字识别就是将手写的阿拉伯数字识别出来,如下图是:

快递单上有很多数字,如电话号码,都是手写的,如果用机器学习的方法,将其识别出来,一定能提升效率,加快快递员收单的速度。

这些数字因为是不同人写的,笔锋、笔画千差万别,怎么准确的识别出来呢?

本节课,我们将带领大家来学习一下手写数字识别的技术,希望大家能喜欢。

3、手写数字识别的系统设计

我们可以把手写识别系统的处理流程画出,如下图所示:

  • 1、准备数据
  • 2、数据处理及分析
  • 3、使用knn进行模型训练
  • 4、验证训练的结果,对数字进行识别
  • 5、计算算法的准确性

4、准备数据

一般来说,如果是做一个输入法的程序,那么我们需要开发一个类似绘图板的界面,让用户在上面写数字,然后将这个数字保存为图片。进行图片分析,计算出这张图片的特征值。但是本课由于不会涉及太深的图像处理知识,所以我们这里忽略将图像转换为特征向量的具体步骤,如果感兴趣的同学,可以提问哦。

1、手写数字识别的原理

本章,我们要识别的数字样本用图形显示出来后,如下图所示:

将数字图片分割为8X8的矩阵,如下图:

这里每一个方块都是一种纯色,在颜色空间中,可以用数字来表示一种颜色。如可以用0到16的数字,表示鉴于黑色和白色中间的灰色。

我们可以将每一个方块的值,用数字来表示,从而组成一个二维数组,如下所示:

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

以上的矩阵就组成了一个样本数据,对成百上千的样本数据进行训练,就可以得到我们的模型了。

好了,下一节,来讲一讲怎么加载这些数据。

2、使用scikit learn加载数字数据

我们需要知道的是:不是每一种学习算法scikit learn都提供了我们需要的数据进行实验。例如:牛放屁和牛奶质量之间关系的数据,scikit learn就没有提供。

但是,幸运的是scikit learn 提供了手写数字识别的数据。其实前面几节课,我们已经简单的介绍了怎么加载数字数据。本节,我们复习一下:

看看下面的代码,是加载了一份数字数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
python
# -*- coding: utf8 -*-
import numpy as np
from sklearn.datasets import load_digits
 
 
# 设置打印完整的数组
np.set_printoptions(threshold='nan')
 
digits = load_digits()
 
print digits
 
# 写入文件中,在你的电脑上运行,请改到相应的路径
f = open(r'D:\ai\digits.txt','w')
print >> f,digits
f.close()

代码中,我们使用了load_digits函数加载了手写数字识别的数据。大家可以到前一课,看一下手写数字数据的相关内容,并了解一下每一个样本的格式,然后再回来学习哦。

5、使用KNN算法进行数字识别

得到了每一个样本的数据,我们先进行训练,这里使用KNN算法,进行训练,什么是KNN算法呢?我们接着聊。

1、KNN算法

KNN算法叫邻近算法,或者说K最近邻算法。英文是:kNN,k-NearestNeighbor,也就是最近的邻居的意思。

KNN分类算法是分类技术中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思。

如果要知道算法的原理,我们看一下下面这个图:

颜色是我们认识大千世界的一种工具,上图中,蓝色的方块代表一种分类,红色的三角形代表一种分类。绿色的圆形是不知道属于哪一个分类的?

这时,我们要对绿色的圆形用一次KNN算法,来决定绿色的圆形属于哪一个分类。

看一下图中的两个圆圈,这是理解KNN算法中K参数的关键。K表示离绿色样本距离由小到大有K个元素。

假设K=3的时候,就是图中的实线表示的范围中,总共有3个样本,这里的3就是K。其中红色的2个,蓝色的1个。因为红色的样本多于蓝色的,所以绿色的圆点将被分类到红色这一类。这就是KNN算法。

当K=5的时候,就是图中虚线的圆圈的范围,有3个蓝色样本,2个红色样本,这时候绿色的圆圈就被分类为蓝色这一类。

上面讲的就是KNN算法的基本原理了,是不是超级简单呢?什么你还不会,那么我只能告诉您,你再看一遍,多思考一下了。

2、怎么使用KNN算法来进行数字识别

怎么使用KNN算法来进行数字识别,首先我们需要大量的手写数字的样本,也就是0到9的数字的特征样本,最好有上万个,甚至更多,最好是不同人写的,这样可以避免算法只能识别少数几个人书写的数字。

如下图,是我们样本中的一些。当然,你可以请你的很多同学,分别写一些数字,作为样本。

这些样本必须知道属于哪一个类,也就是知道其表示的是哪一个数字。 这种知道样本结果然后训练的算法,我们一般叫做监督学习。如果大家想进一步学习其他监督学习算法,可以看我们后面的课程哦。那里有老师等着您来学习哦。

6、scikit learn中的KNN算法学习

在scikit learn中KNN算法使用KNeighborsClassifier类来实现。一看,这个单词就比较难写,它是K+Neighbors(邻居)+ Classifier(分类器)的连写。

大家可以打开pycharm,将本课的代码随便拖一个到pycharm中,并找到有KNeighborsClassifier类的文件,然后按住ctrl键,鼠标点击就可以进入这个类的构造函数了。

看看这个类的构造函数,如下:

1
2
3
4
def __init__(self, n_neighbors=5,
             weights='uniform', algorithm='auto', leaf_size=30,
             p=2, metric='minkowski', metric_params=None, n_jobs=1,
             **kwargs):

下面,我们队主要参数做一些说明:

n_neighbors: 默认是5 ,表示K的取值默认是5,你可以自己定义一个值,注意,你需要完全理解上面的关于K的意思,这样再自己定义,才能更好的驾驭KNN算法。一般来说不超过20。

weights : 可以是一个字符串或者一个回掉函数。python就是这么神奇,参数可以是任意类型,这里它即可以是字符串类型,又可以是函数类型。weights的取值可以取下面几种:

  • uniform:当weights为’uniform’时候,表示每一个样本在计算权重的时候,是平等的。
  • distance: 当weights为’weights’的时候,权重取距离的倒数,也就是越近的点,距离越小,那么它的倒数就越大。也就是越近的点比远的点权重要大一些。
  • callback: 当weights为一个函数的时候,它接受一个距离数组,并且返回一个权重数组。这个callback函数需要自己实现哦。

ok,大概这2个参数,就能够让我们灵活的使用KNN算法了,再进一步了解KNN算法之前,你不需要知道其他参数的意思了,保持默认值就可以了。知道得越多,可能就有越多的迷惑哦。哈哈。 所以,某些时候,还不如不知道。比如上厕所忘记带纸了。

最后,我们要定义一个KNeighborsClassifier类,可以如下代码:

1
2
3
4
from sklearn import neighbors
 
# 定义最邻近分类法算法类
knn = neighbors.KNeighborsClassifier()

现在就可以使用knn提供的函数进行模型训练了。

7、最终代码实现

好了,讲了这么多,我们来一起实现本章数字识别的代码吧,你将神奇的发现,它仅仅需要几十行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# -*- coding: utf8 -*-
import  numpy as np
from sklearn import datasets, neighbors
 
# 加载数字样本函数,
digits = datasets.load_digits()
# 样本数据
X_digits = digits.data
# 每一个样本属于哪一个类,也就是每一个样本表示的数字是几?
y_digits = digits.target
 
#
print 'X_digits',X_digits
print 'Y_digits',y_digits
 
# 样本的个数
n_samples = len(X_digits)
 
print '样本个数',n_samples,len(y_digits)
 
# 取前90%的样本作为训练集
X_train = X_digits[:int(.9 * n_samples)]
print '训练样本数',len(X_train)
 
# 前90%的样本的类别
y_train = y_digits[:int(.9 * n_samples)]
print 'y_train',len(y_train)
 
# 取后10%的样本,作为测试集合
X_test = X_digits[int(.9 * n_samples):]
y_test = y_digits[int(.9 * n_samples):]
 
# 定义最邻近分类法算法类
knn = neighbors.KNeighborsClassifier()
 
# 训练模型
model = knn.fit(X_train, y_train);
 
# 训练集中第79个样本的分类结果,以下2行打印出来的类别一样,则说明算法是正确的。
print model.predict(X_test[79])
# 第79个样本原本属于的类
print y_test[79]

第6行,是加载数据,前面已经说过了

第8到31行,是将数据分为2部分,一部分是训练集,一部分是测试集。显然训练集越多越好,至少要比测试集多吧。不然,怎么有信心让训练后的模型取检验测试集呢?

这里使用了一个python获取数组一部分数据的语法,我们叫做“冒号”语法,这个语法意义如下:

第37行,使用fit来进行训练,传入2个参数,fit的原型如下:

1
def fit(self, X, y):

参数:X是训练数据集 Y是训练数据集对应的分类。

第40行 ,predict表示预测的意思,将测试样本传入,就可以返回结果了。

8、小结

本章仅仅使用了40多行代码,就实现了一个简单的数字识别系统,是不是很兴奋呢?如果你现在一点兴奋都没有,那是为什么呢?是已经会了,还是觉得这很小儿科。哈哈,如果你还不够兴奋,那么我们的作业来了。有本事做完,然后把代码发给我们,帮你看看。

9、作业

利用本课的方法,能不能做汉子的识别,如果觉得汉子太多,可以先识别300个汉子,有兴趣吗?有兴趣的同学,可以联系我们,给你提供思路来做哦。

晚安,我亲爱的们。