从锅炉工到AI专家(4)

手写数字识别问题

图像识别是深度学习众多主流应用之一,手写数字识别则是图像识别范畴简化版的入门学习经典案例。在TensorFlow的官方文档中,把手写数字识别“MNIST”案例称为机器学习项目的“Hello World”。从这个案例开始,咱们的连载才开始有了一些“人工智能”的感受。
问题的描述是这样:
有一批手写数字的图片,对应数字0-9。经过机器学习的算法,将这些图片对应到文本字符0-9。用通俗的话来讲,就是计算机认出了图片上面手写的数字。
从问题描述可见这个机器学习项目的“Hello World”对于入门者来说,既很实用,也存在一些门槛。下面以咱们的节奏,尽量把这个问题分解,中间插入一些机器学习的基本概念,让你们能够轻松入门。html

线性回归和逻辑回归

也有人从线性回归和非线性回归的角度来说,由于逻辑回归就是非线性回归的一种。
不要被这些专有名词吓倒,其实重要的是你理解这个概念,以便之后碰到复杂问题的时候帮助你选择更适合的算法。
线性回归是指数据集和结果都知足线性函数,也就是方程组是一次方的,不包含高次元。前面例子中的房价,虽然咱们例子中的参数不多,但即使参数大量增长,也基本符合线性回归机器学习的范畴。与此相似的还有基本股价预测、限定环境的金融分析等等。
逻辑回归主要使用数学sigmoid函数进行逻辑分类,在一般机器学习的概念中,这也是重要讲解的部分,TensorFlow中已经预先封装了对应的softmax函数,因此这里咱们也忽略掉具体的算法实现,只要记住逻辑回归主要对应分类的学习方式就能够了。好比本次MNIST的例子,就是把图片分类到0-9这十个类别之一。与此相似的应用领域还有:垃圾邮件分类、产品质量自动检测等。
(最下面参考连接中有相关公式的详细介绍,有兴趣的建议跳转去阅读。)python

监督学习和无监督学习

监督学习是指对于每个样本,若是有给定的正确结果做为学习依据,就属于监督学习。例如MNIST手写数字识别的每一副样本图片,咱们都有已知的标签指明这是哪一个数字,这是监督学习。
无监督学习则相反,对于样本咱们没有指定的结果。例如咱们去看画展,有几幅画看上去“很喜欢”,另外几幅看起来则“不喜欢”。若是是由机器学习作这个判断,则是典型的根据一些特征进行了分类,但这个分类并非有准确答案指导的,只是把“某一类”的画做放在一块儿,这就属于无监督学习。
了解这个概念的目的一样是帮你分解问题及选取合适的算法。git

人工智能的基本工做模式

结合上面两个概念,咱们已经能够得出比较常见的人工智能的工做模式,这也是咱们第一篇文章中概念的延伸。程序员

  1. 学习阶段:采集数据集->可能的认为标注(监督学习)->机器学习系统->完成补全的机器学习系统(方程求解)
  2. 生产阶段:生产数据集导入->完成的机器学习系统->分类后的结果(假设逻辑回归)

看起来结果很简单,只是一个分类。但这个分类在咱们作数学模型设计的时候,可能包含了全部须要的可能。好比本例中的字符0-9。
拿人工智能典型应用再举几个例子:算法

  • 自动驾驶:是输入各类摄像头、传感器检测到的路面和周边环境数据集,进行机器学习分类,最后获得加油门、减油门、左转、右转、刹车、倒车灯动做。
  • 语音识别:MIC采集到的声音样本,输出分类为全部可能的字符或者字母,中间根据语气可能插入几个有限的标点符号。
  • 广告推荐:根据采集到的我的偏好数据,推荐已经分类的有限内容。

固然人工智能的发展远不只仅这些,机器学习的算法也在不断的完善和发展过程当中。
整体上,只要能找到解决问题对应的算法,而后收集大量的数据集,并通过可能必要的标注,就可能把一个传统用程序没法解决的问题,转化为算法实现+数据处理的工程化问题,从而有了实现的可能。
反过来若是有人问:“机器学习能让电脑打扫花园吗?”,这一句话可能就带出来很复杂的状况,诸如“图像识别”、“自动驾驶”还有一系列机械工程的问题,对于这样复杂的问题,极可能是当前一个行业和领域单独所没法解决的。因此有的时候提及来简单,仔细思考后会发现,反而不必定容易实现。编程

数据规范化

数据规范化(Normalization)也称为数据归一化(Regularization),都是翻译的词汇,明白意思就好。前面第一个例子的时候已经讲过一些,这里须要再重点讲一下。
使用TensorFlow等机器学习框架以后,原先最复杂的部分,好比算法实现,都已经由框架帮你解决掉了,彻底不懂算法也能够靠抄样例的方式完成工做。事实上如今新的算法不断出现,你已经不太可能搞明白全部算法了,参考成熟算法完成工做已经成为常态。
可是原来算法实现的工做量虽然下降了,但数据从采集、规范化、输入给程序,到完成机器学习运算,再转换为合理的输出。这些工做难度不只没有下降,并且随着机器学习应用场景的普遍化变得更为复杂。
能够说不少项目的阻碍,都在于没法找到合理的数据规范化方法,从而没法将机器学习应用到场景中。
以MNIST为例,首先要作这几样事情,这也是一般图像识别都要通过的步骤:小程序

  • 准备手写数字的样本图片
  • 全部的样本中,手写数字的部分,在整个图片中所占的面积,基本相同
  • 手写的数字在图片中,应当都是正向的,不能有横、有竖甚至还有倾斜的(数字自身手写中应有的倾斜不算)
  • 全部样本图片,最终要使用彻底相同的分辨率,本例中统一使用28x28的点阵
  • 全部样本图片,要使用相同的图形格式,好比手写样本用单色就好
  • 这一条就是咱们前面说过的,一般单色图片没字节数据是0-255的整数,要统一按比例转换成0-1的浮点数

对于不一样的系统,规范化可能作不一样的工做,但大体原则是相似的。好比对于语音识别类的系统中:数组

  • 语音的采样频率必须是相同的
  • PCM声音采样整数数据,最后也要转换成0-1的浮点数
  • ...

关于取值0-1范围的浮点数的事情,也有一些例外的状况。好比天然语言处理(NLP)系统中的“单词向量化”问题,由于单词或者词语,长度是不一样的,规范化起见,咱们只能把单词转换成数字,好比1表明the/2表明is,这种状况下,这个数字是不能再规范化到0-1的浮点数的。缘由主要是这个整数通过运算后,结果必须仍然是精确的整数,不然即使差一点,单词就彻底是另一个了。因此具体状况仍是要具体分析。“单词向量化”的问题属于比较专业化的问题,我也不是专家,之后若是有机会咱们再分享。bash

数据预处理

由数据规范化带来的数据预处理问题每每很复杂,几乎每个机器学习系统中均可能有不一样的实现。而机器学习自己的内核翻来覆去不过就那几个算法,相似前两年一个神经网络就包打了天下。因此机器学习编程语言的能力,就有了很高的要求。
而python恰好是这样可塑性极好的一种语言,而且有丰富的第三方扩展库来实现各类各样的功能。好比图像处理的skimage / cv2(opencv) / pillow / matplotlib,声音处理的librosa / eyed3 / pydub / pyaudio等。有句行间逗比的话说“没有什么是一个python库解决不了的,若是有,那就是两个”。
因此至少当前,机器学习的重点转移到了数字化、大数据和算法,适应性如此之强的python就成了首先工具。
也由于数据预处理每每须要大量长期实际工做经验的积淀,因此实际上机器学习行业虽然是新兴学科,但仍然很须要大量经验丰富的程序员加入其中。不少刚毕业的学生,由于了解到人工智能的火爆,在学校冲刺学习了机器学习的知识,但碰到具体问题时候每每没法下手,所差的大多不是机器学习自己,而是在数据预处理方面经验不足。网络

样本集分组

科学是能够重复的,科学的研究须要科学的手段,很相似现代西医对药物临床试验的要求,对于“魔法师”通常神奇的人工智能,验证其有效性是很关键的环节。
对于收集到用于机器学习的样本,一般是要划分红三组:训练集、验证集和测试集。分别用于对算法模型进行训练、微调算法参数用验证集样本选择最优的算法以及对一个训练完成的模型使用测试集样本计算这个模型的正确率。
彻底独立的划分红三组数据的缘由比较复杂,这个缘由很相似药物的临时实验的需求,在这里也略去只说结果,一般会把数据集划分红60%:20%:20%的比例,固然具体状况也要具体看。
本例中由于不涉及算法调优及算法优选的工做,因此通常只须要划分红训练集和测试集两部分就能够。

MNIST的数据

很是幸运,毕竟TensorFlow官方文档只是为了介绍机器学习框架的工做,因此提供的数据样本是已经完成预处理和规范化的。为了便于你们之后解决相似的问题,在进入源代码以前,咱们先把样本数据展开作一个介绍,以便未来解决相似问题时候你们有一个参照。

如图所示,这就是一副样本图片数字化以后的样子:

  • 在灰度图中,每一个字节表明图中一个点,取值范围本来为0-255整数,这里已经转换成了0-1的浮点小数。
  • 图片的分辨率是28x28点阵灰度图,数字化以后是一个28x28的浮点数矩阵。在实际机器学习的计算中,这个图片会进一步展开为28x28=784的一维矩阵(向量)。
  • 灰度图已经预先经过修图、亮度对比度操做等,去掉了无心义的噪点干扰,好比看起来图片中除了手写以外的部分基本是0。(看到这里,老读者可能想起来本博中另外用OpenCV处理图片的文章,如今你应当知道那些文章真正的目的是什么了吧。)

这样的图片样本,本例数据集中一共是60000幅,保存在mnist.train.images之中,排列起来以下所示:

做为监督学习,每幅图片咱们都有一我的为标注,指明这幅图片是哪一个数字。咱们刚才讲过了,这实际是一种分类算法,计算的结果并非直接获得0-9数字,而是获得一个分类信息,本例就是分红10类。在这种表述方式中,数字n将表示成一个只有在第n维度(从0开始)数字为1的10维向量。好比,标签3将表示成([0,0,0,1,0,0,0,0,0,0,0])。所以, mnist.train.labels 是一个 [60000, 10] 的数字矩阵:

分类算法

分类在一般的机器学习中须要附加和屡次使用sigmoid公式(公式只能完成0、1两种分类,屡次使用达成分多类),TensorFlow则内置了softmax函数(能够完成多项分类)。
由于有这些内置的函数帮助,咱们已经不须要在具体算法上下太大的功夫,不过咱们这里仍然作一个简单的解释,更详细的说明其实这一部分的官方文档算说的比较明白,能够去参考。
回忆一下第一篇中的内容,机器学习最重要的假设就是,咱们认为一切问题都是能够用数学模型所描述的。引伸到MNIST案例中,由于咱们要分类10组,就能够列出一个包含10项的方程式。咱们简化一下,用一个只有3维的小方程来讲明,方程式看起来相似是这样子的:

这个方程式通过简化、推导、矩阵化以后,就成为了程序中所使用的语句:

y = tf.nn.softmax(tf.matmul(x,W) + b)

注意其中的x、W是矩阵,b则是向量。因此这一条语句,展开后实际上表明了10行、每行784个变量的方程式,若是没有IT技术支持,这样的方程式应当会哭死吧?
这也是较为通用的一个分类算法,不少机器学习的系统中,这简单的一行都是核心。
从直观上看,分类算法的功能就是把很是多维的数据,本例中是784个,处理成少许的输出,本例中是10个。事实上达成了降维的效果,因此也称为降维算法。
这很相似于人类利用自身的经验、感知和判断,经过获取多种数据后,抽丝剥茧的思考,最终在有限的可能手段中作出选择的过程。因此降维基本上是人工智能最经常使用的方法,同时也是机器学习算法“像”人的缘由。

看一眼咱们的数据

TensorFlow官方文档帮咱们简化了数据的规范化过程,这省了一大把力气。另一方面,原本很接地气的图像识别,变得看不见摸不着,就算看到了MNIST的源码和运行结果,不少人仍然感受不在掌握。归根结底,就是那些数据,不是咱们熟悉了的格式。因此额外的,咱们加一个小程序,用于把MNIST的样本数据,还原成肉眼可见的图片文件,而且在下面进入MNIST源代码讲解以前,先热身一下。

#!/usr/bin/env python
# -*- coding=UTF-8 -*-
#引入mnist数据预读准备库
import input_data
#tensorflow库
import tensorflow as tf
#引入绘图库
import matplotlib.pyplot as plt

#这里使用mnist数据预读准备库检查给定路径是已经有样本数据,
#没有的话去网上下载,并保存在指定目录
#已经下载了数据的话,将数据读入内存,保存到mnist对象中
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
#sess = tf.Session()
#使用交互模式初始化tf库
sess = tf.InteractiveSession()

def toImage(image,filename):
    #刚才咱们讲过了,样本数据是784长的向量数据,这里重定义成28x28的图片,每一个点1个数据
    x_image = tf.reshape(image, [28, 28, 1])

    #将规范化后0-1的浮点数,从新变成0-255的数据集
    x_image = 256 * x_image
    #取整
    x_image = tf.floor(x_image)
    #从浮点数转换成无符号8位二进制数,也就是1个字节
    y_image = tf.image.convert_image_dtype(x_image, tf.uint8)
    #转成jpeg图像格式
    im = tf.image.encode_jpeg(y_image)

    #打开图像文件并写出
    f = open(filename, "wb+")
    #注意这里就是tf运行的部分,交互模式下,可使用.eval的方式运行而不是一般的sess.run
    #f.write(im.eval(session = sess))
    f.write(im.eval())
    f.close()

#将样本数据测试集的前三个样本保存为图片    
babe = mnist.test.next_batch(1)
toImage(babe[0],"./digital1.jpeg")
babe = mnist.test.next_batch(1)
toImage(babe[0],"./digital2.jpeg")
babe = mnist.test.next_batch(1)
toImage(babe[0],"./digital3.jpeg")

这里咱们展现了python跟外界数据集互动的方式,但愿能够加深你对机器学习的理解。这个例子中,新用户在读取数据集那一行属于碰到问题最多,主要缘由是咱们在国内一个网络高度不稳定的环境下。个人办法是采用其它方式得到了数据文件,保存在指定目录,省去启动后再次下载数据。我这里四个数据文件的列表以下:

-rw-r--r--  1 andrew  staff   1.6M Jan  6  2017 t10k-images-idx3-ubyte.gz
-rw-r--r--  1 andrew  staff   4.4K Jan  6  2017 t10k-labels-idx1-ubyte.gz
-rw-r--r--  1 andrew  staff   9.5M Jan  6  2017 train-images-idx3-ubyte.gz
-rw-r--r--  1 andrew  staff    28K Jan  6  2017 train-labels-idx1-ubyte.gz

下载数据的方法能够参考input_data.py脚本,也能够用上面给出的文件名在网上搜索,有国内的下载点。
程序中使用了互动模式初始化TensorFlow,也就是这一行:

sess = tf.InteractiveSession()

官方文档中只是解释这种互动式的初始化通常用在交互方式,同前一个例子用的tf.Session()相比,Session()初始化必须在数学模型所有构建完成以后,交互模式能够一边构建模型,一边作一些运算好比插入一些图。
实际上官方开发人员在咨询问答中又给了更精确的一个解释,它们惟一的区别在于:tf.InteractiveSession()把它自身做为默认的session,tensor.eval()和operation.run()运行的时候,在后面不须要给出session的参数,直接使用默认session运行。而若是使用Session()初始化的话,上述两类函数的执行,必须在后面显示的给出使用哪个session执行该操做。上面源码中Session初始化及最后写出图像数据的两行,你能够用注释掉的内容自行测试一下就明白了。

程序生成的三幅图片文件跟本篇第一幅图片示例中左侧的原图样式彻底一致,这里就再也不贴图了。这个例子的根本目的仍是让你对tensorflow加深了解,而且更多的理解数据文件内容的前因后果。

初级mnist源码

#!/usr/bin/env python
# -*- coding=UTF-8 -*-

import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

import tensorflow as tf

#定义占位符,也就是tensorFlow的运行时参数,
#x占位符定义为28*28=784的数据集,None表示有多个这样的数据
x = tf.placeholder("float", [None, 784])
#前面介绍过了本例采用的方程算法:W*x+b,
#这里定义的就是方程中每一项的权重W(weight)
#上面的公式看起来简单,由于已经矩阵化,实际上
#W包含10个方程式、每一个方程式784个权重值
W = tf.Variable(tf.zeros([784,10]))
#变量b,b比较简单一些,表明10个方程式中,
#每一个方程最后的常数,b是bias的缩写
#b是参与运算和返回结果用的,不须要输入数据,所以是变量不是占位符
b = tf.Variable(tf.zeros([10]))
#定义核心数学模型
y = tf.nn.softmax(tf.matmul(x,W) + b)
#y_是监督学习中,对应x数据的标注分类标签
#每一个标签是10个元素的向量,含义见正文部分
y_ = tf.placeholder("float", [None,10])

#交叉熵代价函数,参考下面正文的解释
cross_entropy = -tf.reduce_sum(y_*tf.log(y))

#梯度降低法解方程,学习步长是0.01,交叉熵最小时候获得W和b的解
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

#循环进行1000个批次的训练
for i in range(1000):
  #随机抽取一个批次的训练数据,抽取算法参考input_data.py
  batch_xs, batch_ys = mnist.train.next_batch(100)
  #开始训练
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
    
#重点来了,在上一个例子中没有这部分
#训练结束后(1000个批次后),
#经过验证集数据验证咱们模型的正确率
#argmax是内置函数,用于将10个元素的分类表,取出最大的那一维的索引,
#等于将分类变回了0-9数字
#参考正文的解释,在样本或者计算结果的分类表中,
#某索引的值若是是1,表示分类到了这一类,算法决定了其他的必然都是0
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
#上面的结果是比较值,因此是true/false这样的一维数组,
#下面这个公式将bool转换成0、1数字,求平均值得出最后的正确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

#显示验证组标签信息
print mnist.test.labels
#执行验证组正确率运算
#在这个tensorflow任务执行前,必定要理解一个概念,
#就是这个任务,在同一个session中执行,跟上面学习过程的任务是接着的,
#因此实际上计算的核心是相同的一个公式
print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})

这个模型最后的识别正确率在91%左右,虽然并不高,可是几行代码就能获得这样的结果,仍是很不错的了,在不少机器学习应用中,这样的正确率已经达到应用水平。
上面的代码经过前面的铺垫和源码中的注解,应当很容易理解,额外说一下交叉熵的概念。
代价(cost)函数,也被称做损失(lost)函数,看名字应当能理解是一回事吧?咱们前面一个例子的代价函数使用了标签样本值与计算值相减的平方差,值越小表示越接近方程的正确解。这个模式简单易懂。不过这种算法有不少缺陷,在这个例子中引入了“交叉熵”的代价函数算法。
交叉熵是个复杂的概念,想详细了解的能够看参考引文中的softmax连接,其中有比较详细的解释。
简单介绍,就是交叉熵越小(跟平方差算法同样哈),变量的取值就越肯定,越肯定就表示咱们获得了肯定的结果。好比晓明考试10次才能及格1次,小王考试10次只会有1次不及格,小李50%的可能及格。那晓明和小王的交叉商就低,肯定性强,小李则最不肯定,你彻底无法判断他下一次考试是好是差。
在本例中的交叉熵不只仅是计算单一的一组数据,而是本批次100幅图片的交叉熵的总和。这样预测表现就比单一数据点计算能更好地描述咱们的求解是否趋近了收敛。

(待续...)

引文及参考

TensorFlow中文社区
手写字体样本数据下载
机器学习中训练集、验证集和测试集的做用
对线性回归、逻辑回归、各类回归的概念学习
个人机器学习笔记(一) - 监督学习vs 无监督学习
机器学习中训练集、验证集和测试集的做用
sigmoid函数
Softmax分类函数

相关文章
相关标签/搜索