第4章 Scrapy爬虫入门实例


本章利用Scrapy架构,实现一个爬虫任务,从一个网站爬取一些教师信息(如教师姓名、职称、简介),然后把爬取的信息分别存放到cvs文件、MySQL。
环境为python3.6

4.1 Scrapy 框架入门简介

4.1.1 Scrapy 框架

Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。Scrapy 使用了 Twisted'twɪstɪd异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。

Scrapy架构图(其中绿线是数据流向)

Scrapy Engine(引擎):
负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
Scheduler(调度器):
它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。

Downloader(下载器):
负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
Spider(爬虫):
它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),
Item Pipeline(管道):
它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.
Downloader Middlewares(下载中间件):
你可以当作是一个可以自定义扩展下载功能的组件。
Spider Middlewares(Spider中间件):
你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)

4.1.2 Scrapy 流程

Scrapy运行流程大概如下:
引擎从调度器中取出一个链接(URL)用于接下来的抓取
引擎把URL封装成一个请求(Request)传给下载器
下载器把资源下载下来,并封装成应答包(Response)
爬虫解析Response
解析出实体(Item),则交给实体管道进行进一步的处理
解析出的是链接(URL),则把URL交给调度器等待抓取
详细流程如下:
1 引擎:Hi!Spider, 你要处理哪一个网站?
2 Spider:老大要我处理xxxx.com。
3 引擎:你把第一个需要处理的URL给我吧。
4 Spider:给你,第一个URL是xxxxxxx.com。
5 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
6 调度器:好的,正在处理你等一下。
7 引擎:Hi!调度器,把你处理好的request请求给我。
8 调度器:给你,这是我处理好的request
9 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
10 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
11 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
12 Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
13 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
14 管道  调度器:好的,现在就做!

4.1.3 Scrapy 开发一般步骤

1、新建项目 (scrapy startproject xxx):新建一个新的爬虫项目
2、明确目标 (编写items.py):明确你想要抓取的目标
3、制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
4、存储内容 (pipelines.py):设计管道存储爬取内容
本实例的流程如下图

4.1.4.Scrapy安装

验证安装是否成功

4.2入门案例

主要步骤:
1、创建一个Scrapy项目
2、定义提取的结构化数据(Item)
3、编写爬取网站的 Spider 并提取出结构化数据(Item)
4、编写 Item Pipelines 来存储提取到的Item(即结构化数据)

4.2.1 创建项目

新建项目(scrapy my_project)
在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中,运行下列命令:

其中, my_project 为项目名称,可以看到将会创建一个 my_project 文件夹,目录结构大致如下:

下面来简单介绍一下各个主要文件的作用:
scrapy.cfg 是爬虫执行的入口文件。当输入“scrapy crawl”命令让爬虫开始工作时,首先会读取该文件中的配置项内容。
my_project/items.py 文件定义了爬虫抓取下来的数据,是以何种组织方式存储信息的。比如爬虫抓取的结果可以是标题字符串,也可以是结构化的JSON对象,或者是一张图片对应的字节流,items.py就是用来定义结构化对象中的属性名。
my_project/pipelines.py文件定义了信息的保存方式。爬虫抓取的内容,存放在内存对象中,如何保存这些信息,用户可以定义多种方式,比如写入文件、存入DB或者直接在控制台输出。Scrapy会采用管道(pipeline)方式,把内存中的信息依次交给每个管道文件。
my_project/settings.py 文件保存了爬虫运行时所依赖的配置信息。比如用户定义了两个pipelines.py文件,希望把抓取的内容先写入DB,再输出到控制台,那么就可以在setttings.py文件中定义ITEM_PIPELINES属性,其值分别给出了两个管道文件的文件名。比如:

my_project/spiders/ 是用来存放具体爬虫文件的目录。爬虫文件需要用户手动创建,在该目录下可以同时存在多个爬虫文件。

【备注】为保证输出格式为json文件也支持中文,在settings.py添加如下语句:

4.2.2 明确目标

我们打算抓取:http://www.itcast.cn/channel/teacher.shtml 网站里的所有讲师的姓名、职称和个人信息。打开my_project目录下的items.py
Item 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误。
可以通过创建一个 scrapy.Item 类, 并且定义类型为
scrapy.Field的类属性来定义一个Item。接下来,创建一个ItcastItem 类,和构建item模型(model)。

4.2.3 制作爬虫

主要编辑spiders/itcastSpider.py
爬虫功能要分两步:
1. 爬数据
在当前目录下输入命令,将在my_project /spider目录下创建一个名为itcast的爬虫,并指定爬取域的范围:

打开 my_project /spider目录里的 itcast.py,默认增加了下列代码:

其实也可以由我们自行创建itcast.py并编写上面的代码,只不过使用命令可以免去编写固定代码的麻烦要建立一个Spider, 你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性 和 一个方法。
name = "" :这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。
allow_domains = [] 是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。
start_urls = () :爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
parse(self, response) :解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下:
负责解析返回的网页数据(response.body),提取结构化数据(生成item)
生成需要下一页的URL请求。
将start_urls的值修改为需要爬取的第一个url

修改parse()方法

然后运行一下看看,在my_project目录下(含文件scrapy.cfg所在的目录)执行:

2. 取数据
爬取整个网页完毕,接下来的就是的取过程了,首先观察页面源码:

是不是一目了然?直接上XPath开始提取数据吧。
我们之前在my_project /items.py 里定义了一个ItcastItem类。 这里引入进来

然后将我们得到的数据封装到一个 ItcastItem 对象中,可以保存每个老师的属性,修改itcast.py文件如下。

【备注】这里补充一点xpath有关内容,更多内容大家可以通过google或百度查询
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下面列出了最有用的路径表达式:

3. 运行
在scrapy.cfg 所在文件的目录下,运行如下命令

运行完成后,在当前目录下,将生成一个文件:teachers.csv

查看该文件:

4、修改settles.py文件
一些变量可存放在settles.py文件中,把数据库的连接信息存放在该文件,便于以后维护管理

5、利用pipeline,把数据导入mysql数据库
pipeline.py文件定义数据存储的方式,此处定义数据存储的逻辑,可以将数据存储加载到MySQL数据库,MongoDB数据库,文件,CSV,Excel等存储介质中,如下以存储载CSV为例。
编辑pipeline.py文件

6、修改settles.py文件
为说明通过pipeline导入数据程序,在文件settings.py后,添加如下一行:

7、运行以下命令

8、查询结果
csv文件内容:

查看数据库对应表部分记录:

第3章 Python 爬虫入门实例


如果你对爬虫感兴趣,但没有相关基础,这个实例就比较适合你。本实例非常简单,特别适合入门。本实例主要步骤如下本实例爬取一个有关教学研究人员网站 WISE(http://www.wise.xmu.edu.cn/people/faculty),提取这个页面上所有老师的姓名和个人主页的链接,然后简单解析相关标签(tag),然后把解析出来的内容存放在文件和数据库中。
讲完这个实例后面还有一个更高级的爬虫实例,使用Scrapy架构爬取网站内容,这个架构分布式、平行处理等我们就无需做过多配置,系统自动为我们配置好。
接下来,我们还是从一个简单实例开始吧。

3.1下载网页

使用requests下载网页,它是第三方库

把需要的网页内容提取出来:

到这一步我们已经获取了网页的源代码。具体源 代码是什么样的呢?右键,点击“查看源文件”或者“查看源”就可以看到。

3.2 解析网页

从源代码里面找。找到的我们需要的信息如下:

解析使用bs4模块,该模块有最常用的几个BeautifulSoup对象的方法(method)。我们使用的这几个方法,主要是通过HTML的标签和标签里面的参数来定位,然后用特定方法(method)提取数据。

使用了BeautifulSoup对象的find方法。这个方法的意思是找到带有‘div’这个标签并且参数包含" class = 'people_list' "的HTML代码。如果有多个的话,find方法就取第一个。那么如果有多个呢?可以使用find_all,现在我们要取出所有的“a”标签里面的内容:

显示结果
'助理教授'

这里我们使用find_all方法取出所有标签为“a”并且参数包含“ target = ‘_blank‘ ”的代码,返回一个列表。“a”标签里面的“href”参数是我们需要的老师个人主页的信息,而标签里面的文字是老师的姓名

运行结果:

3.3 拓展一

通过源码分析,可知教师的姓名,职位,邮箱等信息包含在td标签内,接下来我们解析源码并把解析结果保存到cvs文件中。具体步骤如下:
1、在td标签里,包含教师的姓名,职位,邮箱等信息

打印结果为
327
查看前几行数据

['敖萌幪',
'助理教授',
'mengmengao@xmu.edu.cn',
'Bowers, Roslyn',
'英语教师',
'bowers.roslyn@yahoo.com',
'Brown, Katherine',
'英语教师',
'kbrownanne@yahoo.com']
把数据保存到cvs文件中

3.4 拓展二

取出所以的td的内容,并按姓名,职位,邮箱格式写入数据库,采用mysql数据库,连接用pymysql,具体内容如下:

数据表创建成功
数据导入成功
查看数据库部分记录:

本文参考:https://zhuanlan.zhihu.com/p/21377121

第26 章 自动驾驶实例---交通标志识别

26.1交通标志识别简介

26.1.1 交通标记易识特性

由于交通标志采用特定的文字、图形和符号,在一定范围内具有标准、醒目、信息明确的特性,一直是图像识别技术在交通领域应用的首选。从图像识别技术诞生之日起,交通标志识别的算法和模型就一直受到广泛的关注,也让这一技术发展相对成熟,并应用到自动驾驶领域中来。

图1 交通标志识别技术
我国的交通标志一共有一百余种,按类别可分为黄底黑边的警告标志、白底红圈的禁令标志、蓝底白字的指示标志,形状上以三角形、圆形和矩形为主。明确的形状和颜色区分、有限的标志数量,都为图像识别提供了一个相对稳定的应用环境。

图2 我国的交通标志

26.1.2交通标志识别技术的原理

利用图像识别技术的交通标志识别系统一般分为以下几个工作步骤:

图3 图像识别的步骤
1. 图像预处理:
在实际的交通场景中,由于运动抖动、自然光、天气条件等各种因素的影响,不可避免的会在采集的图像中引入一定程度的干扰和噪声,所以首先需要将这些不利因素消除,对采集到的图像进行一些预处理。通过图像均衡、图像增强和图像去噪等算法,将图像的光线均衡,突出关键信息。这一步基本和美图秀秀中的那些工具类似,都是将图像变得清晰、易辨认。

图4 对图像进行预处理,去除噪声、提高亮度和对比度
2. 交通标志分割:
预处理后的图像仍然包含很多信息,交通标志在其中只有很小的一个区域,为了减小处理的数据量,加快处理速度,一般都会先将交通标志的区域检测出来,再去判断这个区域中的交通标志的具体含义。交通标志在颜色和形状上都有一定的特殊性,并可按照下图进行一定程度的分类,所以一般通过这两个特征去检测交通标志。

图5 交通标志按颜色和形状分类
2.1颜色分割:
颜色按照国际标准可划分为RGB、HSV、HSI等颜色空间,并进行量化分析,以RGB空间为例,将颜色按照红色、绿色、蓝色三种颜色进行分割,通过给定交通标志牌中常用的蓝色、黄色、红色的色度坐标范围,即可过滤掉与之不相关的颜色信息,快速检测到交通标志牌。

图 6 通过RGB色彩空间处理,快速定位红色区域
2.2形状分割:
仅仅检测颜色显然又是不够的,由于光照、背景色的影响和干扰,还需要在颜色检测结果的基础上对相应区域进行形状检测。交通标志具有边缘清晰、形状简单易辨认的特点。这些特征在排除颜色影响后的灰度图像中更加明显,因此通过一定的边缘检测算子去判断图像像素中出现的灰度阶跃变化,一般就能较为准确的检测出交通标志的形状和轮廓特征。

图7 对检测区域进行灰度处理,再通过灰度阶跃检测其形状边界
3. 交通标志特征提取
在图像检测完成以后,图像中基本就只剩下了交通标志的关键信息,这些信息简单直观,但计算机依然不会知道这些信息的具体含义,这时候需要再进一步对这些图像特征进行提取和比对,才能对具体的信息进行识别。
图像的关键特征,是识别具体信息的关键因素,特征的好坏直接决定了识别的准确度。一般说来这些关键特征需要具有可区分性、简约性和抗干扰等几个要素,可区分性即不同标志的特征要具有足够的差异性,简约性是在保证可区分性的前提下用尽量少的数据表示图像特征,这可以保证检测的速度和效率,抗干扰度即图像特征信息要保证尽量少的会被噪声、自然光和图像畸变影响。
在交通标志识别上,一般会提取颜色特征、线条变化特征、矩特征、灰度直方图统计特征等等,并会在事先维护一个足够样本数量的特征库,包含现有交通标志的图像特征信息。在识别的时候将采集到的图像的特征提取出来与数据库中的条件进行比对,即可判断出该交通标志的实际意义。
4. 识别结果匹配
目前有多种方法实现图像特征与特征库数据的比对,最为简单直接的方式是模板匹配:即在特征库中将不同交通标志的特征参数规定为某些特定的参数,当所采集图像的特征参数在某个范围内,就判断是这个交通标志信息。但由于图像在采集的时候难免发生形状畸变、颜色失真等误差,在实际使用场景中用模板匹配来识别的成功率和准确度并不是特别高,即便优化了图像处理算法,也还有很多局限。

图 8 通过匹配特征库信息识别标志
近些年机器学习技术的发展,让图像识别也有了很大的变化,通过设定一些简单的判断条件,并在特征库中加入各种形态和场景下的学习样本,让系统不断加深交通标志识别的认知和识别概率。机器学习让识别不再依靠具体固定的参数,而是通过一系列的条件判断让系统找到概率最大的目标,以此提升识别的准确度和灵活性。这一技术在目前成为研究的热点,并有效提高了图像识别的准确率。

图 9 机器学习在图像识别中的应用

26.1.3 总结

交通标志识别是图像识别技术在自动驾驶领域较为成功的应用,其情景相对简单固定,识别准确度和成功率都让人满意。现在自动驾驶中摄像头识别车辆、行人、自行车、车道线等其他目标的工作原理基本和交通标志识别类似,只是针对不同的对象所用的的算法和模型也会进行一定的调整和优化,并维护一个更为多样的样本学习库。Mobileye在自动驾驶摄像头领域已经耕耘了17年,其算法集成优化、样本库丰富度、识别精确度都处于绝对领先,也为自动驾驶的普及带来了巨大的福音。

26.2 交通标志识别常用模型

1、LeNet(X)
是交通标志识别中非常经典的算法结构,其结构如下:

但是要实现交通标志识别还需要对该算法的初始结构进行调整。比如输出层的分类数调整为43;Subsampling layers 转为max pooling layers;增加Dropout 层,初始设置keep_prob=0.9;激活函数采用RELU。
改进后的架构流程如下表所示:

2、AlexNet
AlexNet是2012年发表的一个经典网络,在当年取得了ImageNet的最好成绩。
这是AlexNet的网络结构图:

网络共有8层,其中前5层是卷积层,后边3层是全连接层,在每一个卷积层中包含了激励函数RELU以及局部响应归一化(LRN)处理,然后再经过池化(max pooling),最后的一个全连接层的输出是具有1000个输出的softmax层,最后的优化目标是最大化平均的多元逻辑回归。
3、其它多层神经网络
36.3 实例1(使用多层神经网络)
这个Project的目的是利用神经卷积网路(CNN)来分类(classify)常见的交通标志。 CNN 在读图领域已经全面超过了传统的机器学习方法(SVC, OpenCV)。大量的资料是深度学习准确性的保证, 在资料不够的情况下也可以人为的对原有资料进行小改动从而来提高识别的准确度。汇入必要的软体包(pickle, numpy, cv2, matplotlib, sklearn, tensorflow, Keras)
这里使用python3.6,TensorFlow1.6 等,使用GPU比使用CPU快约100倍!
1)导入需要的模块或库

【注意】如果环境已安装keras,可以直接使用,导入方式可改为:

资料来源: 和大部分的机器学习的要求一样, CNN需要大量有label的资料,German Traffic Sign Dataset提供了对于这个project的研究所需的数据,本章数据集下载

2)导入数据

打印结果
Number of training examples = 39209
Number of testing examples = 12630
Image data shape = (32, 32, 3)
Number of classes = 43

3)探索数据
从上面我们可以看到有39209个用作训练的影象 和 12630个testing data。 39209张照片对于训练CNN来说是不够的(100000张以上是比较理想的资料量), 所以之后要加入data augment 的模组来人为增加资料。每张影象的大小是是32×32 并且有3个通道。总共有43个不同的label。我们也可以把每个label对应的图片随机选择一张画出来。

4)查看各种标志的分布情况

从上图可以看到这43个类别的资料量的分配是很不均匀的。这个会给CNN带来bias(偏见):CNN会更倾向于预测在training data里出现频率多的那些分类。

5)数据预处理
资料前期处理 根据这篇论文[Sermanet, LeCun], 把RGB的照片转化成YUV(除了RGB模型外,还有一种广泛采用的模型,称为YUV模型,又被称为亮度-色度模型(Luma-ChromaModel)。它是通过数学转换,将RGB三通道转换为一个代表亮度的通道(Y,又称为Luma),和两个代表色度的通道(UV,并称为Chroma)来记录图像的模型) 然后只选择Y通道的图片可以在不影响精度的同时减少资料计算量。然后每张图片都转化成以0为平均值, 以1为标准差的阵列。

人为添加数据(data augment) 对于图片来时, 一定程度的旋转, 上下左右移动, 放大或者缩小都应该不会影响它的标签。 虽然图片数据已经完全不一样了, 我们肉眼还是能够识别的出来, 这能够在增加数据量的同时帮助CNN 总结(generalize). Keras 有个很方便的函数ImageDataGenerator(rotation_range=15., zoom_range=0.2, width_shift_range=0.1, height_shift_range=0.1) 可以实现这个, 这个函数可以设置 旋转的角度rotation_range, 放大或缩小的倍数zoom_range, 左右移动的比例width_shift_range 和上下移动的比例height_shift_range , 随机在区间内改动原来的照片并产生无数新的照片, 下面我选择一张作为示范:

6)搭建神经网络
搭建CNN 每一层的神经网络都加了dropout 来防止overfitting。这个CNN的特点是把两层conv的output做了一个合成:fc0 = tf.concat([flatten(drop1), flatten(drop2)],1 ) 然后再连接到fully_connected layer 和output layer(43 classes)。文献中说这样做的好处是“the classifier is explicitly provided both the local “motifs” (learned by conv1) and the more “global” shapes and structure (learned by conv2) found in the features.” 我的理解是: CNN 能够在图片的局部和整体都能作为判断的依据,从而提高准确率

【备注】如果lr大小,如小于0.0001可能需要花费更多训练时间,如果太大,如0.1可能很难提高精度,所以选择合适lr很重要。
定义超参数

【备注】大家可以根据情况,增加迭代次数,批量大小,批量一般不宜过大,否则将迅速增加训练时间。
7)训练CNN模型

【备注】keep_prob参数控制dropout的比例,这个参数很重要,大家可以修改该参数,看它对精度的影响。
Training...

EPOCH 1 ...
Training Accuracy = 0.224

EPOCH 2 ...
Training Accuracy = 0.339

EPOCH 3 ...
Training Accuracy = 0.400

EPOCH 4 ...
Training Accuracy = 0.452

EPOCH 5 ...
Training Accuracy = 0.508

EPOCH 6 ...
Training Accuracy = 0.548

EPOCH 7 ...
Training Accuracy = 0.587

EPOCH 8 ...
Training Accuracy = 0.609

EPOCH 9 ...
Training Accuracy = 0.658

.......................

EPOCH 90 ...
Training Accuracy = 0.971

EPOCH 91 ...
Training Accuracy = 0.968

EPOCH 92 ...
Training Accuracy = 0.971

EPOCH 93 ...
Training Accuracy = 0.975

EPOCH 94 ...
Training Accuracy = 0.965

EPOCH 95 ...
Training Accuracy = 0.977

EPOCH 96 ...
Training Accuracy = 0.967

EPOCH 97 ...
Training Accuracy = 0.976

EPOCH 98 ...
Training Accuracy = 0.976

EPOCH 99 ...
Training Accuracy = 0.976

EPOCH 100 ...
Training Accuracy = 0.977
【说明】因时间关系,我这里只训练100次,看来精度也还可以,如果增加迭代次数,可以达到99%左右
8)用测试数据验证模型

26.3 实例2(使用LeNet神经网络)

待续

第25章 自动驾驶汽车实例--使汽车在两条白线间行驶

本文将介绍如何使用 Keras + Tensorflow1.6,python为3.6 创建卷积神经网络,并对其进行训练,以使得车辆保持在两条白线之间,如下图:

下载数据

备注:由于下载经常出现超时情况,大家可以先下载该文件,然后放到指定目录。

步骤1:获取驾驶数据

数据集由 ~7900 个图像和手动开车时收集的转向角组成。大约三分之二的图像与线之间的汽车。另外三分之一的车开始偏离航线,并且驶回线路之间。
数据集由 2 个 pickled 数组组成。X 是图像阵列,Y 是相应转向角度的阵列。

打印结果
X.shape: (7892, 120, 160, 3)
Y.shape: (7892,)

步骤2:拆分数据

在这里,对数据进行洗牌(shuffle),并将数据分成三部分。训练数据将用于训练我们的驾驶模型,使用验证数据避免过度拟合模型,测试数据用于测试我们的模型。

打印结果
7892

打印结果
7892

步骤3:增强训练数据

为了加倍我们的训练数据并防止转向偏差,我们翻转每个图像和转向角并将其添加到数据集中。还有其他的方法来增加使用翻译和假阴影驾驶数据,但我没有使用这些自动驾驶仪。

打印结果
12626

步骤4:创建驾驶模式

这种驾驶模式将是一个端到端的神经网络,接受图像阵列作为输入,并输出-90(左)和90(右)之间的转向角。 要做到这一点,我们将使用一个完全连接图层的3层卷积网络。该模型基于 Otavio 的 Carputer,但不产生油门值输出,不使用过去的转向值作为模型的输入,并且使用较少的卷积层。

使用 TensorFlow 后端。
查看目前image数据格式,是tensorflow格式(channels_last) 还是theano格式(channels_first)

'channels_last'

如果不是tensorflow格式,可以通过K.set_image_data_format('channels_last')进行修改,或修改~/.keras/keras.json文件。 前者只对当前会话有效,后者修改,将永久有效。

打印结果
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
img_in (InputLayer) (None, 120, 160, 3) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 118, 158, 8) 224
_________________________________________________________________
activation_1 (Activation) (None, 118, 158, 8) 0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 59, 79, 8) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 57, 77, 16) 1168
_________________________________________________________________
activation_2 (Activation) (None, 57, 77, 16) 0
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 28, 38, 16) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 26, 36, 32) 4640
_________________________________________________________________
activation_3 (Activation) (None, 26, 36, 32) 0
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 13, 18, 32) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 7488) 0
_________________________________________________________________
dense_1 (Dense) (None, 256) 1917184
_________________________________________________________________
activation_4 (Activation) (None, 256) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 256) 0
_________________________________________________________________
angle_out (Dense) (None, 1) 257
=================================================================
Total params: 1,923,473
Trainable params: 1,923,473
Non-trainable params: 0

步骤5:训练模型

我们已经学会了很难的方法,即使这一切都是完美的,如果你没有正确地训练,你的自动驾驶仪将无法工作。我遇到的最大的问题是过度适应模型,以至于在很少的情况下都不能正常工作。 这里是 2 个 Keras回调,将节省您的时间。 警告 - 如果仅使用CPU,则需要较长时间,这里我采用GPU进行训练

因时间关系,这里只进行 10 次迭代(epochs),训练时间2分钟左右,比较快。

运行结果
Train on 12626 samples, validate on 789 samples
Epoch 1/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 41288.4184
Epoch 00001: val_loss improved from inf to 656.89127, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 22s 2ms/step - loss: 41230.2161 - val_loss: 656.8913

Epoch 2/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 550.5565
Epoch 00002: val_loss improved from 656.89127 to 543.14232, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 550.1622 - val_loss: 543.1423

Epoch 3/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 476.8377
Epoch 00003: val_loss improved from 543.14232 to 417.43844, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 476.6054 - val_loss: 417.4384

Epoch 4/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 420.1108
Epoch 00004: val_loss improved from 417.43844 to 416.97928, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 420.0584 - val_loss: 416.9793

Epoch 5/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 399.4089
Epoch 00005: val_loss did not improve
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 399.0109 - val_loss: 420.7120

Epoch 6/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 373.5299
Epoch 00006: val_loss improved from 416.97928 to 411.31800, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 373.7999 - val_loss: 411.3180

Epoch 7/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 356.3707
Epoch 00007: val_loss improved from 411.31800 to 384.65238, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 356.4031 - val_loss: 384.6524

Epoch 8/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 338.6780
Epoch 00008: val_loss did not improve
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 338.4380 - val_loss: 435.1508

Epoch 9/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 325.1715
Epoch 00009: val_loss improved from 384.65238 to 348.49219, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 325.0649 - val_loss: 348.4922

Epoch 10/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 304.1439
Epoch 00010: val_loss did not improve
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 304.2825 - val_loss: 349.3760

步骤6:评估性能

我们可以通过绘制预测值和实际值来检查我们的模型预测是否合理。第一个图表显示我们的测试数据中存在一个学习关系(在训练期间,部分测试数据我们的模型没有注意到)。

第二张图,使用包含训练数据的非混洗(unshuffled)数据,来显示预测角度紧跟实际转向角度。

后续可进一步完善

改善模型,这个模型是纯粹(navie)的,因为它不使用过去的值来帮助预测未来。我们可以通过将过去的转向角度作为模型的输入来进行试验,添加一个递归层,或者只是改变卷积层的结构。
添加更多数据,随着我们添加更多驾驶数据,此模型将会得到改进。 预测油门,输出目前自动驾驶仪只能转向并保持恒定的速度。一个更复杂的模型将加速在直路上,并在路缘之前放缓。

第4章 概率与信息论


机器学习、深度学习有三块基石:线性代数、概率与信息论、数值分析。线性代数上章已介绍,数值分析后续将介绍,三块基石缺一块都会地动山摇。本章讨论概率和信息论,概率是用于表示不确定性陈述的数学框架,即它是对事物不确定性的度量,而信息论主要研究信号或随机变量所包含的信息量。
在人工智能领域,概率法则告诉我们AI系统应该如何推理,概率和统计从理论上分析我们提出的AI系统的行为。
计算机科学的许多分支处理的对象都是完全确定的实体,但机器学习却大量使用概率。如果你了解机器学习的工作原理,或许你有更深的体会。因为机器学习大部分时候处理的都是不确定量或随机量。
概率论和信息论是众多学科的基础,也是机器学习、深度学习的重要基础。
如果你对概率论和信息论很熟悉了,可以跳过这章。如果你觉得本章内容还不够,希望进一步了解更多,可以参考相关专业教材。本章主要内容包括:

  • 为何要学概率与信息论
  • 样本空间与随机变量
  • 概率分布
  • 边缘概率
  • 条件概率
  • 期望、方差及协方差
  • 贝叶斯定理
  • 信息论

4.1为何要学概率、信息论

机器学习、深度学习需要借助概率、信息论?
概率研究对象不是预先知道或确定的事情,而是预先不确定或随机的事件,研究这些不确定或随机事件背后的规律或规则。或许有人会说,这些不确定或随机事件有啥好研究?他们本来就不确定或随机的,飘忽不定、不可捉摸。表面上看似如此,有句话说得好:偶然中有必然,必然中有偶然。就拿我们比较熟悉微积分来说吧,如果单看有限的几步,很多问题都显得杂乱无章,毫无规律可言,而且还很难处理,但是一旦加上一个无穷大(∞)这个“照妖镜”,其背后规律立显,原来难处理的也好处理了。如大数定律、各种分布等带给我们这样的认识。
信息论主要研究对一个信号包含信息的多少进行量化。它的基本思想是一个不太可能的事件居然发生了,其提供的信息量要比一个非常可能发生的事件更多。这中情况也似乎与我们的直觉相矛盾。
机器学习、深度学习与概率、信息论有哪些内在关联呢?
(1)被建模系统内在的随机性。例如一个假想的纸牌游戏,在这个游戏中我们假设纸牌被真正混洗成了随机顺序。
(2不完全观测。即使是确定的系统,当我们不能观测到所有驱动系统行为的所有变量或因素时,该系统也会呈现随机性。
(3)不完全建模。假设我们制作了一个机器人,它可以准确观察周围每一个对象的位置。在对这些对象将来的位置进行预测时,如果机器人采用的是离散化的空间,那么离散化的方法将使得机器人无法确定对象们的精确位置:因为每个对象都可能处于它被观测到的离散单元的任何一个角落。也就是说,当不完全建模时,我们不能明确的确定结果,这个时候的不确定,就需要借助概率来处理。
由此看来,概率、信息论很重要,机器学习、深度学习确实很需要它们。后续我们可以看到很多实例,见证概率、信息论在机器学习、深度学习中是如何发挥它们作用的。

4.2样本空间与随机变量

随机试验中,每一个可能的结果,在试验中发生与否,都带有随机性,所以称为随机事件。而所有可能结果构成的全体,称为样本空间。随机变量、样本空间这两个概念非常重要,以下就这两个概念作进一步说明。
样本空间
样本空间是一个实验或随机试验所有可能结果的集合,随机试验中的每个可能结果称为样本点。例如,如果抛掷一枚硬币,那么样本空间就是集合{正面,反面}。如果投掷一个骰子,那么样本空间就是{1,2,3,4,5,6}。
随机变量
随机变量,顾名思义,就是“其值随机而定”的变量,一个随机试验有许多可能结果,到底出现哪个预先是不知道的,其结果只有等到试验完成后,才能确定。如掷骰子,掷出的点数X是一个随机变量,它可以取1,2,3,4,5,6中的任何一个,到底是哪一个,要等掷了骰子以后才知道。因此,随机变量又是试验结果的函数,它为每一个试验结果分配一个值。比如,在一次扔硬币事件中,如果把获得的背面的次数作为随机变量X,则X可以取两个值,分别是0和1。如果随机变量X的取值是有限的或者是可数无穷尽的值,如:
则称 X为离散随机变量。如果X由全部实数或者由一部分区间组成,如:
X={x| a≤x≤b},其中a<b,它们都为实数。
则称 X为连续随机变量,连续随机变量的取值是不可数及无穷尽的。
有些随机现象需要同时用多个随机变量来描述。例如对地面目标射击,弹着点的位置需要两个坐标(X,Y)才能确定,X,Y都是随机变量,而(X,Y)称为一个二维随机变量或二维随机向量,多维随机向量含义依次类推。

4.3概率分布

概率分布用来描述随机变量(含随机向量)在每一个可能状态的可能性大小。概率分布有不同方式,这取决于随机变量是离散的还是连续的。
对于随机变量X,其概率分布通常记为P(X=x),或X ~P(x),表示X服从概率分布P(x)。
概率分布描述了取单点值的可能性或概率,但在实际应用中,我们并不关心取某一值的概率,特别是对连续型随机变量,它在某点的概率都是0,这个后续章节将介绍。因此,我们通常比较关心随机变量落在某一区间的概率,为此,引入分布函数的概念。

4.3.1 离散型随机变量

设x_1,x_2,…,x_n是随机变量X的所有可能取值,对每个取值x_i,X = x_i是其样本空间S上的一个事件,为描述随机变量X,还需知道这些事件发生的可能性(概率)。
设离散型随机变量X的所有可能取值为x_i (i=1,2,…,n)。
P(X = x_i) = P_i,i= 1,2,...n
称之为X的概率分布或分布律,也称概率函数。
常用表格形式来表示X的概率分布:


例1:某篮球运动员投中篮圈的概率是0.8,求他两次独立投篮投中次数X的概率分布。

且P(X=0)+P(X=1)+P(X=2)=0.04+0.32+0.64=1
于是随机变量X的概率分布可表示为:
X         0      1         2
P_i 0.04    0.32    0.64
若已知一个离散型随机变量X的概率分布:

例如,设X的概率分布由例1给出,则
F(2)=P(X≤2)=P(X=0)+P(X=l)=0.04+0.32=0.36
常见的离散随机变量的分布有:
(1)两点分布
若随机变量X只可能取0和1两个值,且它的分布列为P(X=1)=p,P(X = 0) = l − P其中(0 < P < 1),则称X服从参数为p的两点分布,记作X~B(1, p)。其分布函数为

(2)二项分布
二项分布是重要的离散概率分布之一,由瑞士数学家雅各布•伯努利(Jokab Bernoulli)提出。一般用二项分布来计算概率的前提是,每次抽出样品后再放回去,并且只能有两种试验结果,比如黑球或红球,正品或次品等。二项分布指出,假设某样品在随机一次试验出现的概率为p,那么在n次试验中出现k次的概率为:

假设随机变量X满足二项分布,且知道n,p,k等参数,我们如何求出各种情况的概率值呢?方法比较多,这里介绍一种比较简单的方法,利用scipy库的统计接口stats即可,具体如下:

运行后的二项分布图如图4-1所示。

图4-1 二项分布图
(3)泊松(Poisson)分布
若随机变量X所有可能取值为0,1,2,…,它取各个值的概率为:

这里介绍了离散型随机变量的分布情况,如果X是连续型随机变量,其分布函数通常通过密度函数来描述,具体请看下一节。

4.3.2 连续型随机变量

与离散型随机变量不同,连续型随机变量采用概率密度函数来描述变量的概率分布。如果一个函数f(x)是密度函数,满足以下三个性质,我们就称f(x)为概率密度函数。
(1)f(x)≥0,注意这里不要求f(x)≤1。

图4-2 概率密度函数
对连续型随机变量在任意一点的概率处处为0。

最常见的正态分布的密度函数为:

这个连续分布被称之为正态分布,或者高斯分布。其密度函数的曲线呈对称钟形,因此又被称之为钟形曲线,其中μ是平均值,σ是标准差(何为平均值、标准差后续我们会介绍)。正态分布是一种理想分布。
正态分布如何用Python实现呢?同样,我们可以借助其scipy库中stats来实现,非常方便。

sigmal系统与正态分布如图4-3所示。


图4-3 sigmal系统与正态分布
正态分布的取值可以从负无穷到正无穷。这里我们为便于可视化,只取把X数据定义在[-6,6]之间,用stats.norm.pdf得到正态分布的概率密度函数。另外从图形可以看出,上面两图的均值u都是0,只是标准差(σ)不同,这就导致图像的离散程度不同,标准差大的更分散,个中原因,我们在介绍随机变量的数字特征时将进一步说明。

4.4边缘概率

对于多维随机变量,如二维随机变量(X,Y),假设其联合概率分布为F(x,y),我们经常遇到求其中一个随机变量的概率分布的情况。这种定义在子集上的概率分布称为边缘概率分布。
例如,假设有两个离散的随机变量X,Y,且知道P(X,Y),那么我们可以通过下面求和的方法,得到边缘概率P(X):

边缘概率如何计算呢?我们通过一个实例来说明。假设有两个离散型随机变量X,Y,其联合分布概率如表4-1所示。
表4-1:X与Y的联合分布

如果我们要求P(Y=0)的边缘概率,根据式(4.7)可得:
P(Y=0)=P(X=1,Y=0)+P(X=2,Y=0)=0.05+0.28=0.33

4.5条件概率

上一节我们介绍了边缘概率,它是多维随机变量一个子集(或分量)上的概率分布。对于含多个随机变量的事件中,经常遇到求某个事件在其他事件发生的概率,例如,在表4-1的分布中,假设我们要求当Y=0的条件下,求X=1的概率?这种概率叫作条件概率。条件概率如何求?我们先看一般情况。
设有两个随机变量X,Y,我们将把X=x,Y=y发生的条件概率记为P(Y=y|X=x),那么这个条件概率可以通过以下公式计算:

其中P(Y=0)是一个边缘概率,其值为:P(X=1,Y=0)+P(X=2,Y=0)=0.05+0.28=0.33
而P(X=1,Y=0)=0.05.故P(X=1|Y=0)=0.05/0.33=5/33
式(4.10)为离散型随机变量的条件概率,对连续型随机变量也有类似公式。假设(X,Y)为二维连续型随机变量,它们的密度函数为f(x,y),关于Y的边缘概率密度函数为f_Y (y),且满足f_Y (y)>0,假设

在X=x的条件下,关于Y的条件分布函数为:

4.6条件概率的链式法则

条件概率的链式法则,又称为乘法法则,把式(4.10)变形,可得到条件概率的乘法法则:
P(X,Y)=P(X)×P(Y|X)                                                                   (4.16)
根据式(4.16)可以推广到多维随机变量,如:
P(X,Y,Z)=P(Y,Z) ×P(X|Y,Z)
而P(Y,Z)=P(Z) ×P(Y|Z)
由此可得:P(X,Y,Z)=P(X|Y,Z) ×P(Y|Z) ×P(Z)                          (4.17)
推广到n维随机变量的情况,可得:

4.7独立性及条件独立性

两个随机变量X,Y,如果它们的概率分布可以表示为两个因子的乘积,且一个因子只含x,另一个因子只含y,那么我们就称这两个随机变量互相独立。这句话可能不好理解,我们换一种方式的来表达。或许更好理解。
如果对∀x∈X,y∈Y,P(X=x,Y=y)=P(X=x)P(Y=y) 成立,那么随机变量X,Y互相独立。
在机器学习中,随机变量为互相独立的情况非常普遍,一旦互相独立,联合分布的计算就变得非常简单。
这是不带条件的随机变量的独立性定义,如果两个随机变量带有条件,如P(X,Y|Z),它的独立性如何定义呢?这个与上面的定义类似。具体定义如下:
如果对∀x∈X,y∈Y,z∈Z,P(X=x,Y=y|Z=z)=P(X=x|Z=z)P(Y=y|Z=z) 成立
那么随机变量X,Y在给定随机变量Z时是条件独立的。
为便于表达,如果随机变量X,Y互相独立,又可记为X⊥Y,如果随机变量X,Y在给定时互相独立,则可记为X⊥Y|Z。
以上主要介绍离散型随机变量的独立性和条件独立性,如果是连续型随机变量,我们只要把概率换成随机变量的密度函数即可。

4.8期望、方差及协方差

在机器学习、深度学习中经常需要分析随机变量的数据特征及随机变量间的关系等,对于这些指标的衡量在概率统计中有相关的内容,如用来衡量随机变量的取值大小的期望(Expectation)值或平均值、衡量随机变量数据离散程度的方差(Variance)、揭示随机向量间关系的协调方差(Convariance)等。这些衡量指标的定义及公式就是本节主要内容。
首先我们看随机变量的数学期望的定义:
对离散型随机变量X,设其分布律为:

如果是随机变量函数,如随机变量X的g(x)的期望,公式与式(4.21)或式(4.22)类似,只要把x换成g(x)即可,即随机变量函数g(x)的期望为:

期望有一些重要性质,具体如下:
设a,b为一个常数,X和Y是两个随机变量。则有:
(1)E(a)=a
(2)E(aX)=aE(X)
(3)E(aX+bY)=aE(X)+bE(Y) (4.25)
(4)当X和Y相互独立时,则有:
E(XY)=E(X)E(Y) (4.26)
数学期望也常称为均值,即随机变量取值的平均值之意,当然这个平均,是指以概率为权的加权平均。期望值可大致描述数据的大小,但无法描述数据的离散程度,这里我们介绍一种刻画随机变量在其中心位置附近离散程度的数字特征,即方差。如何定义方差?
假设随机向量X有均值E(X)=a。试验中,X取的值当然不一定恰好是a,可能会有所偏离。偏离的量X-a本身也是一个随机变量。如果我们用X-a来刻画随机变量X的离散程度,当然不能取X-a的均值,因E(X-a)=0 ,说明正负偏离抵消了,当然我们可以取|X-a|这样可以防止正负抵消的情况,但绝对值在实际运算时很不方便。人们就考虑另一种方法,先对
方差的平方根被称为标准差。
对于多维随机向量,如二维随机向量(X,Y)如何刻画这些分量间的关系?显然均值、方差都无能为力。这里我们引入协方差的定义,我们知道方差是X-EX乘以X-EX的均值,如果我们把其中一个换成Y-EY,就得到E(X-EX)(Y-EY),其形式接近方差,又有X,Y两者的参与,由此得出协方差的定义,随机变量X,Y的协方差,记为:Cov(X,Y)
Cov(X,Y) =E(X-EX)(Y-EY)                                    (4.28)
协方差的另一种表达方式:
Cov(X,Y) =E(XY)-EX×EY                                       (4.29)
方差可以用来衡量随机变量与均值的偏离程度或随机变量取值的离散度,而协方差则可衡量随机变量间的相关性强度,如果X与Y独立,那么它们的协方差为0。反之,并不一定成立,独立性比协方差为0的条件更强。不过如果随机变量X、Y都是正态分布,此时独立和协方差为0是一个概念。
当协方差为正时,表示随机变量X、Y为正相关;如果协方差为负,表示随机变量X、Y为负相关。
为了更好的衡量随机变量间的相关性,我们一般使用相关系数来衡量,相关系数将每个变量的贡献进行归一化,使其只衡量变量的相关性而不受各变量尺寸大小的影响,相关系统的计算公式如下:
ρ_xy=(Cov(X,Y))/(√(Var(X)) √(Var(Y)))                                             (4.30)
由式(4.30)可知,相关系统是在协方差的基础上进行了正则化,从而把相关系数的值限制在[-1,1]之间。如果ρ_xy=1,说明随机变量X、Y是线性相关的,即可表示为Y=kX+b,其中k,b为任意实数,且k>0;如果ρ_xy=-1,说明随机变量X、Y是负线性相关的,即可表示为Y=-kX+b,其中k>0。
上面我们主要以两个随机变量为例,实际上协方差可以推广到n个随机变量的情况或n维的随机向量。对n维的随机向量,我们可以的第一个nxn的协方差矩阵,而且满足:

求随机变量的方差、协方差、相关系统等,使用Python的numpy相关的函数,如用numpy.var求方差,numpy.cov求协方差,使用numpy.corrcoef求相关系数,比较简单,这里就不展开来说。
在机器学习中多维随机向量,通常以矩阵的方式出现,所以求随机变量间的线性相关性,就转换为求矩阵中列或行的线性相关性。这里我们举一个简单实例,来说明如果分析向量间的线性相关性并可视化结果。这个例子中使用的随机向量(或特征值)共有三个,一个是气温(temp),一个体感温度(atemp),一个是标签(label)说明共享单车每日出租量,以下是这三个特征的部分数据:
表4-2 共享单车示例数据

这里使用Python中数据分析库pandas及画图库matplotlib、sns等。

从图4-4可以看出,特征temp与atemp是线性相关的,其分布接近正态分布。

图4-4 特征分布及相关性

4.9贝叶斯定理

贝叶斯定理是概率论中的一个定理,它跟随机变量的条件概率以及边缘概率分布有关。在有些关于概率的解释中,贝叶斯定理(贝叶斯公式)能够告知我们如何利用新证据修改已有的看法。这个名称来自于托马斯•贝叶斯。
通常,事件A在事件B(发生)的条件下的概率,与事件B在事件A(发生)的条件下的概率是不一样的;然而,这两者是有确定的关系的,贝叶斯定理就是这种关系的陈述。贝叶斯公式的一个用途在于通过已知的三个概率函数推出第四个。
贝叶斯公式为:
P(B|A)=(P(B)P(A|B))/(P(A))                                                               (4.31)
在贝叶斯定理中,每项都有约定俗成的名称:

  • P(B|A)是已知A发生后B的条件概率,也由于得自A的取值而被称作B的后验概率。
  • P(B)是B的先验概率(或边缘概率)。之所以称为"先验"是因为它不考虑任何A方面的因素。
  • P(A|B)是已知B发生后A的条件概率,也称为似然(likelihood),也由于得自B的取值而被称作A的后验概率。
  • P(A)是A的先验概率或边缘概率。

4.10信息论

信息论是应用数学的一个分支,主要研究的是对信号所含信息的多少进行量化。它的基本想法是一个不太可能的事件居然发生了,要比一个非常可能的事件发生能提供更多的信息。本节主要介绍度量信息的几种常用指标,如信息量、信息熵、条件熵、互信息、交叉熵等。
1.信息量
1948年克劳德•香农(Claude Shannon)发表的论文“通信的数学理论”是世界上首次将通讯过程建立了数学模型的论文,这篇论文和1949年发表的另一篇论文一起奠定了现代信息论的基础。信息量是信息论中度量信息多少的一个物理量,它从量上反应具有确定概率的事件发生时所传递的信息。香农把信息看作是“一种消除不确定性”的量,而概率正好是表示随机事件发生的可能性大小的一个量,因此,可以用概率来定量地描述信息。
在实际运用中,信息量常用概率的负对数来表示,即,。为此,可能有人会问,为何要用对数,前面还要带上负号?
用对数表示是为了计算方便。因为直接用概率表示,在求多条信息总共包含的信息量时,要用乘法,而对数可以变求积为求和。另外,随机事件的概率总是小于1,而真实小于1的对数为负的,概率的对数之前冠以负号,其值便成为正数。所以通过消去不确定性,获取的信息量总是正的。
2.信息熵
信息熵(entropy)又简称为熵,是对随机变量不确定性的度量。熵的概念由鲁道夫•克劳修斯(Rudolf Clausius)于1850年提出,并应用在热力学中。1948年,克劳德•艾尔伍德•香农(Claude Elwood Shannon)第一次将熵的概念引入信息论中,因此它又称为香农熵。
用熵来评价整个随机变量X平均的信息量,而平均最好的量度就是随机变量的期望,即熵的定义如下:

信息熵越大,包含的信息就越多,那么随机变量的不确定性就越大。
以下我们通过一个实例进一步说明这个关系。
假设随机变量X服从0-1分布,其概率分布为:
P(X=1)=p,P(X=0)=1-p
这时,X的熵为:
H(X)=-p log_2(p)-(1-p) log_2(1-p)
我们利用Python具体实现以下概率p与H(X)的关系:

图4-5 概率与信息熵
从这个图形可以看出,当概率为0或1时,H(X)为0,说明此时随机变量没有不确定性,当p=0.5时,随机变量的不确定性最大,即信息量最大。H(X)此时取最大值。
3.条件熵
设二维随机变量(X,Y),其联合概率分布为:

注意,这个条件熵,不是指随机变量X在给定某个数的情况下,另一个变量的熵是多少,变量的不确定性是多少?而是期望!因为条件熵中X也是一个变量,意思是在一个变量X的条件下(变量X的每个值都会取),另一个变量Y熵对X的期望。
条件熵比熵多了一些背景知识,按理说条件熵的不确定性小于熵的不确定,即H(Y|X)≤H(Y),事实也是如此,下面这个定理有力地说明了这一点。
定理:对二维随机变量(X,Y),条件熵H(Y|X)和信息熵H(Y)满足如下关系:
H(Y|X)≤H(Y)                                                                                      (4.36)
4.互信息
互信息(mutual information)又称为信息增益,用来评价一个事件的出现对于另一个事件的出现所贡献的信息量。记为:
I(X,Y)=H(Y)-H(Y|X)                                                                               (2.37)
在决策树的特征选择中,信息增益为主要依据。在给定训练数据集D,假设数据集由n维特征构成,构建决策树时,一个核心问题就是选择哪个特征来划分数据集,使得划分后的纯度最大,一般而言,信息增益越大,意味着使用某属性a来划分所得“纯度提升”越大。因此,我们常用信息增益来构建决策树划分属性。
5.相对熵
相对熵(relative entropy),所谓相对,一般是在两个随机变量之间来说,又被称为KL散度(Kullback–Leibler divergence,KLD),这里我们假设 p(x) 和 q(x) 是 X 取值的两个概率分布,如p(x)表示X的真实分布,q(x)表示X的训练分布或预测分布,则 p 对 q 的相对熵为:

相对熵有些重要性质:
(1)相对熵不是传统意义上的距离,它没有对称性,即
KL(p(x)||q(x))≠KL(q(x)||p(x))
(2)当预测分布q(x)与真实分布p(x)完全相等时,相对熵为0;
(3)如果两个分别差异越大,那么相对熵也越大;反之,如果两个分布差异越小,相对熵也越小。
(4)相对熵满足非负性,即 KL(p(x)||q(x))≥0
6.交叉熵
交叉熵可在神经网络(机器学习)中作为代价函数,p表示真实标记的分布,q则为训练后的模型的预测标记分布,交叉熵代价函数可以衡量p与q的相似性。交叉熵作为代价函数还有一个好处是使用sigmoid函数在梯度下降时能避免均方误差代价函数学习速率降低的问题,因为学习速率可以被输出的误差所控制。

4.11 小结

概率与信息论是机器学习的重要基础及重要理论依据。本章介绍了概率论、信息论的一些基本概念,如样本空间、随机变量等。根据随机变量取值不同,又可分为有离型和连续型随机变量;根据随机变量的维度又可分为一维或多维随机变量。概率分布、边缘分布是刻画随机变量的重要特征,而期望、方差及协方差是随机变量三个常用统计量。信息论是刻画随机变量的另一种方式,信息论在深度学习、人工智能中应用非常广泛,后续章节也经常会出现。

下载本章代码及数据

第3章 线性代数


机器学习、深度学习的基础除了编程语言外,还有一个就是应用数学。它一般包括线性代数、概率与信息论、概率图、数值计算与最优化等。其中线性代数又是基础的基础。线性代数是数学的一个重要分支,广泛应用于科学和工程领域。大数据、人工智能的源数据在模型训练前,都需要转换为向量或矩阵,而这些运算正是线性代数的主要内容。
如在深度学习的图像处理中,如果1张图由28*28像素点构成,那这28*28就是一个矩阵。在深度学习的神经网络中,权重一般都是矩阵,我们经常把权重矩阵W与输入X相乘,输入X一般是向量,这就涉及矩阵与向量相乘的问题。诸如此类,向量或矩阵之间的运算在深度学习中非常普遍,也非常重要。
本章主要介绍如下内容:
标量、向量、矩阵和张量
矩阵和向量运算
特殊矩阵与向量
线性相关性及向量空间

3.1标量、向量、矩阵和张量

在机器学习、深度学习中,首先遇到的就是数据,如果按类别来划分,我们通常会遇到以下4种类型的数据。
1.标量(scalar)
一个标量就是一个单独的数,一般用小写的变量名称表示,如a,x等。
2.向量(vector)
向量就是一列数或一个一维数组,这些数是有序排列的。通过次序中的索引,我们可以确定向量中每个单独的数。通常我们赋予向量粗体的小写变量名称,如x、y等。一个向量一般有很多元素,这些元素如何表示?我们一般通过带脚标的斜体表示,如x_1 表示向量x中的第一个元素,x_2表示第二元素,依次类推。
当需要明确表示向量中的元素时,我们一般将元素排列成一个方括号包围的纵列:

我们可以把向量看作空间中的点,每个元素是不同的坐标轴上的坐标。
向量可以这样表示,那我们如何用编程语言如python来实现呢?如何表示一个向量?如何获取向量中每个元素呢?请看如下实例:

打印结果如下:
5
1 2 4 8

这说明向量元素个数为5,向量中索引一般从0开始,如a[0]表示第一个元素1,a[1]
表示第二个元素2,a[2]表示第三个元素4,依次类推。这是从左到右的排列顺序,如果从右到左,我们可用负数来表示,如a[-1]表示第1个元素(注:从右到左),a[-2]表示第2个元素,依次类推。
3.矩阵(matrix)
矩阵是二维数组,其中的每一个元素被两个索引而非一个所确定。我们通常会赋予矩阵粗体的大写变量名称,比如A。如果一个实数矩阵高度为m,宽度为n,那么我们说A∈R^mxn。
与向量类似,可以通过给定行和列的下标表示矩阵中元素,下标用逗号分隔,如A_1,1表示A左上的元素,A_1,2表示第一行第二列对应的元素,依次类推;这是表示单个元素,如果我们想表示1列或1行,该如何表示呢?我们可以引入冒号":"来表示,如第1行,可用A1,:表示,第2行,用A2,:表示,第1列用A:,1表示,第n列用A:,n表示。
如何用Python来表示或创建矩阵呢?如果希望获取其中某个元素,该如何实现呢?请看如下实例:

打印结果:
[[1 2 3]
[4 5 6]]
6
(2, 3)
1 2 5
[4 5 6]

矩阵可以用嵌套向量生成,和向量一样,在Numpy中,矩阵元素的下标索引也是从0开始的。
4.张量(tensor)
几何代数中定义的张量是向量和矩阵的推广,通俗一点理解的话,我们可以将标量视为零阶张量,向量视为一阶张量,那么矩阵就是二阶张量,三阶的就称为三阶张量,以此类推。在机器学习、深度学习中经常遇到多维矩阵,如一张彩色图片就是一个三阶张量,三个维度分别是图片的高度、宽度和色彩数据。
张量(tensor)也是深度学习框架TensorFlow的重要概念。TensorFlow由tensor(张量)+flow(流)构成。
同样我们可以用Python来生成张量及获取其中某个元素或部分元素,请看实例:

打印结果如下:
[[[ 0 1 2 3]
[ 4 5 6 7]]

[[ 8 9 10 11]
[12 13 14 15]]]
16
(2, 2, 4)
0 1 5
[4 5 6 7]
5.转置(transpose)
转置以主对角线(左上到右下)为轴进行镜像操作,通俗一点来说就是行列互换。将矩

向量可以看作只有一列的矩阵,把(3.1)式中向量x进行转置,得到下式。

用Numpy如何实现张量的转置?很简单,利用张量的T属性即可,示例如下:

打印结果如下:
[[1 2 3]
[4 5 6]]
[[1 4]
[2 5]
[3 6]]

3.2矩阵和向量运算

矩阵加法和乘法是矩阵运算中最常用的操作之一,两个矩阵相加,需要它们的形状相同,进行对应元素的相加,如:C=A+B,其中C_(i,j)=A_(i,j)+B_(i,j)。矩阵也可以和向量相加,只要它们的列数相同,相加的结果是矩阵每行与向量相加,这种隐式地复制向量b到很多位置的方式称为广播(broadcasting),以下我们通过一个代码实例来说明。

打印结果为:
[[11 22 33]
[14 25 36]]

两个矩阵相加,要求它们的形状相同,如果两个矩阵相乘,如A和B相乘,结果为矩阵C,矩阵A和B需要什么条件呢?条件比较简单,只要矩阵A的列数和矩阵B的行数相同即可。如果矩阵A的形状为m×n,矩阵B的形状为n×p,那么矩阵C的形状就是m×p,例如:
C=AB,则它们的具体乘法操作定义为:

矩阵乘积有很多重要性质,如满足分配律A(B+C)=AB+AC 和结合律,A(BC)=(AB)C。大家思考一下是否满足交换律?

3.3特殊矩阵与向量

上一节我们介绍了一般矩阵的运算,实际上在机器学习或深度学习中,我们还经常遇到一些特殊类型的矩阵,如可逆矩阵、对称矩阵、对角矩阵、单位矩阵、正交矩阵等等。这些特殊矩阵有特殊属性,下面我们逐一进行说明。
1.可逆矩阵
先简单介绍一下可逆矩阵,因后续需要用到。在(3.3)式中,假设矩阵W已知,向量b已知,如何求向量x?为求解向量x,我们需要引入一个称为逆矩阵的概念。而为了求逆矩阵,又牵涉到单位矩阵,何为单位矩阵?单位矩阵的结构很简单,就是所有沿主对角线上的元素都是1,而其他位置的元素都是0的方阵(行数等于列数的矩阵),一般记为I_n,如:


对此后续我们有更详细的讨论及代码实现。
2.对角矩阵
对角矩阵只有在主对角线上才有非零元素,其余都是0。从形式上来看,如果A为对角矩阵,当且仅当对所有i≠j,A_(i,j)=0。对角矩阵可以是方阵(行数等于列数)也可以不是方阵,如下矩阵,就是一个对角矩阵。

对角矩阵有非常好的性质,这些性质使很多计算非常高效,在机器学习、深度学习中经常会遇到对角矩阵。
对于对角矩阵为方阵的情况,我们可以把对角矩阵简单记为:

从上面两个式子可以看到对角矩阵的简洁高效。
(3)对称矩阵
对称矩阵,对于任意一个n阶方阵A,若A满足:A=A^T成立,则称方阵A为对称矩阵。
(4)单位向量

3.4线性相关性及向量空间

前面我们介绍了向量、矩阵等概念,接下来我们将介绍向量组、线性组合、线性相关性、秩等重要概念。
由多个同维度的列向量构成的集合称为向量组,矩阵可以看成是由行向量或列向量构成的向量组。
1.线性组合

2.线性相关

秩是一个重要概念,运用非常广泛,实际上矩阵我们可以看成是一个向量组。如果把矩阵看成是由所有行向量构成的向量组,这样矩阵的行秩就等于行向量组的秩;如果把矩阵看成是由所有列向量构成的向量组,这样矩阵的列秩就等于列向量组的秩。矩阵的行秩与列秩相等,因此,把矩阵的行秩和列秩统称为矩阵的秩。

3.5范数

数有大小,向量也有大小,向量的大小我们通过范数(Norm)来衡量。范数在机器学习、深度学习中运用非常广泛,特别在限制模型复杂度、提升模型的泛化能力方面效果不错。p范数的定义如下:


前面主要介绍了利用范数来度量向量的大小,矩阵的大小如何度量呢?我们可以用类似的方法。在深度学习中,常用Frobenius范数来描述,即:

其中θ表示x与y之间的夹角。
以上说了向量一种度量方式,即通过范数来度量向量或矩阵的大小,并有具体公式,在实际编程中如何计算向量的范数呢?这里我们还是以Python为例进行说明。

打印结果如下:
[ 0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]
4.5
1.68819430161
0.9

由此看出利用Python求向量的范数还是很方便的。

3.6特征值分解

许多数学对象可以分解成多个组成部分。特征分解就是使用最广的矩阵分解之一,即将矩阵分解成一组特征向量和特征值。本节讨论的矩阵都是方阵。
我们先介绍特征值、特征向量的概念。
设A是一个n阶方阵,如果存在实数⋋和n维的非零向量x,满足:
Ax=⋋x                                            (3.14)
那么把数⋋称为方阵A的特征值,向量x称为矩阵A对应特征值⋋的特征向量。
假设矩阵A有n个线性无关的特征向量{ν^1,ν^2,⋯,ν^n},它们对应的特征值为{⋋_1,⋋_2,⋯,⋋_n}
把这n个线性无关的特征向量组合成一个新方阵,每一列是一个特征向量。
V=[ν^1,ν^2,⋯,ν^n]
用特征值构成一个n阶对角矩阵,对角线的元素都是特征值。
〖〖diag(λ)=[⋋〗_1,⋋_2,⋯,⋋_n]〗^T
那么,A的特征分解可表示为:
A=Vdiag(λ)V^(-1)                                   (3.15)
注意,并不是所有方阵都能进行特征值分解,一个n阶方阵A能进行特征值分解的充分必要条件是它含有n个线性无关的特征向量。
这里我们介绍了给定一个方阵,如何求该方阵的特征向量和特征值?如何用编程语言实现?这些问题有了Python的帮忙,实现起来都非常简单,具体请看如下示例:

打印结果:
[-0.37228132 5.37228132]
[-0.37228132 5.37228132]
[[-0.82456484 -0.41597356]
[ 0.56576746 -0.90937671]]
【说明】
在numpy.linalg模块中:
eigvals() 计算矩阵的特征值
eig() 返回包含特征值和对应特征向量的元组

3.7奇异值分解

上节我们介绍了方阵的一种分解方式,如果矩阵不是方阵,是否能分解?如果能,该如何分解?这节我们介绍一种一般矩阵的分解方法,称为奇异值分解,这种方法应用非常广泛,如降维、推荐系统、数据压缩等等。
矩阵非常重要,所以其分解方法也非常重要,方法也比较多,除了特征分解法,还有一种分解矩阵的方法,被称为奇异值分解(SVD)。将矩阵分解为奇异向量和奇异值。通过奇异分解,我们会得到一些类似于特征分解的信息。然而,奇异分解有更广泛的应用。
每个实数矩阵都有一个奇异值分解,但不一定都有特征分解。例如,非方阵的矩阵就没有特征分解,这时我们只能使用奇异值分解。
奇异分解与特征分解类似,只不过这回我们将矩阵A分解成三个矩阵的乘积:
A=UDV^T                                      (3.16)
假设A是一个m×n矩阵,那么U是一个m×m矩阵,D是一个m×n矩阵,V是一个n×n矩阵。这些矩阵每一个都拥有特殊的结构,其中U和V都是正交矩阵,D是对角矩阵(注意,D不一定是方阵)。对角矩阵D对角线上的元素被称为矩阵A的奇异值。矩阵U的列向量被称为左奇异向量,矩阵V 的列向量被称右奇异向量。
SVD最有用的一个性质可能是拓展矩阵求逆到非方矩阵上。奇异值分解,看起来很复杂,如果用python来实现,却非常简单,具体请看如下示例:

打印结果:
[ 1.09824632e+01 8.79229347e+00 1.03974857e+00 1.18321522e-15
2.13044868e-32]
[[ 10.98246322 0. 0. ]
[ 0. 8.79229347 0. ]
[ 0. 0. 1.03974857]]

3.8迹运算

迹运算返回的是矩阵对角元素的和:

迹运算在某些场合非常有用。若不使用求和符号,有些矩阵运算很难描述,而通过矩阵乘法和迹运算符号可以清楚地表示。例如,迹运算提供了另一种描述矩阵Frobenius 范数的方式:

对迹运算的表达式,我们可以使用很多等式来表示。例如,迹运算在转置运算下是不变的:

多个矩阵相乘得到的方阵的迹,和将这些矩阵中的最后一个挪到最前面之后相乘的迹是相同的。当然,我们需要考虑挪动之后矩阵乘积依然有定义:
Tr(ABC)=Tr(CAB)=Tr(BCA)
利用Python的Numpy对矩阵求迹同样方便。请看以下示例。

打印结果:
15
15
171
171

3.9实例:Python实现主成分分析

主成分分析(Principal Component Analysis,PCA)是一种统计方法。通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。
在许多机器学习、深度学习的应用中,往往需要处理大量样本或大的矩阵,多变量大样本无疑会为研究和应用提供丰富的信息,但也在一定程度上增加了数据采集的工作量。更重要的是在多数情况下,许多变量之间可能存在相关性,从而增加了问题分析的复杂性,同时对分析带来不便。如果分别对每个指标进行分析,分析往往是孤立的,而不是综合的。而盲目减少指标又会损失很多信息,且容易产生错误的结论。
因此需要找到一个合理有效的方法,在减少需要分析的指标或维度的同时,尽量减少原指标所含信息的损失,以达到对所收集数据进行全面分析的目的。由于各变量间存在一定的相关关系,因此有可能用较少的综合指标分别存在变量的各类信息。主成分分析就属于这类降维的方法。
如何实现以上目标呢?这里我们简要说明一下原理,然后使用Python来实现,至于详细的推导过程,大家可参考相关书籍或网上资料。
问题:设在n维空间中有m个样本点:{x^1,x^2,…,x^m},假设m比较大,需要对这些点进行压缩,使其投影到k为空间中,其中k<n,同时使损失的信息最小。
该如何实现呢?以下简要说明一下思路。

要使信息损失最小,一种合理的设想就是重构后的点X^*与原来的数据点之间距离最小,据此,PCA可转换为求带约束的最优化问题:

最后对(3.22)式两端对w求导,并令导数为0,化简后就可得到:
XX^T W=⋋W                                                                                                        (3.23)
由(3.23)式可知,W是由协方差矩阵XX^T的特征向量构成的特征矩阵,利用特征值分解的方法就可求出W。
以下我们Python具体实现一个PCA实例。以iris为数据集,该数据集可以通过load_iris自动下载。
1)iris数据集简介:
Iris数据集是常用的分类实验数据集,由Fisher, 1936收集整理。Iris也称鸢尾花卉数据集。数据集包含150个数据集,分为3类,每类50个数据,每个数据包含4个属性。可通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三类中的哪一类。
2)算法主要步骤为:
(1)对向量X进行去中心化
(2)计算向量X的协方差矩阵,自由度可以选择0或者1
(3)计算协方差矩阵的特征值和特征向量
(4)选取最大的k个特征值及其特征向量
(5)用X与特征向量相乘
3)代码实现

各特征值的贡献率如图3-1所示,可以看出,前2个特征值的方差贡献率超过95%,所以k取2有其合理性。

图3-1 各特征值的贡献率示意图

3.10小结

本章主要介绍线性代数中矩阵及向量有关概念,以及相关规则和运算等。线性代数是机器学习、深度学习的重要基础,与之相当的还有概率与信息论,我们将在下一章介绍。

本章代码下载

第2章 Theano基础


第1章我们介绍了NumPy,它是数据计算的基础,更是深度学习框架的基石。但如果直接使用NumPy计算大数据,其性能已成为一个瓶颈。
随着数据爆炸式增长,尤其是图像数据、音频数据等数据的快速增长,迫切需要突破这个瓶颈。需求就是强大动力!通过大咖们的不懈努力,在很多方面取得可喜进展,如硬件有GPU,软件有Theano、Keras、TensorFlow,算法有卷积神经网络、循环神经网络等等。
Theano是Python的一个库,为开源项目,在2008年,由Yoshua Bengio领导的加拿大蒙特利尔理工学院LISA实验室开发。对于解决大量数据的问题,使用Theano可能获得与手工用C实现差不多的性能。另外通过利用GPU,它能获得比CPU上的快很多数量级的性能。Theano开发者在2010年公布的测试报告中指出:在CPU上执行程序时,Theano程序性能是NumPy的1.8倍,而在GPU上是NumPy的11倍。这还是2010的测试结果,近些年无论是Theano还是GPU,性能都有显著提高。
这里我们把Theano作为基础来讲,除了其性能方面的跨越外,它还是“符合计算图”的开创者,当前很多优秀的开源工具,如TensorFlow、Keras等,都派生于或借鉴了Theano的底层设计。所以了解Theano的使用,将有助于我们更好学习TensorFlow、Keras等其他开源工具。
至于Theano是如何实现性能方面的跨越?如何用“符号计算图”来运算等,本章都将有所涉猎,但限于篇幅无法深入分析,只做一些基础性的介绍。涵盖的主要内容:
 如何安装Theano
 符号变量是什么
 如何设计符号计算图
 函数的功能
 共享变量的妙用

2.1安装

这里主要介绍Linux+Anaconda+theano环境的安装说明,Linux为CentOS或Ubuntu都可以,安装Python、NumPy、SciPy等,建议使用Anaconda来安装,当然也可用pip进行安装。最好使用工具来安装,这样可以避免很多程序依赖的麻烦,而且日后的软件升级维护也很方便。
Theano支持CPU、GPU,如果使用GPU还需要安装其驱动程序如CUDA等,限于篇幅这里只介绍CPU的(TensorFlow将介绍基于GPU的安装),有关GPU的安装,大家可参考:http://www.deeplearning.net/software/theano/install.html
以下为安装主要步骤:
(1)安装anaconda
从anaconda官网:https://www.anaconda.com/download/下载linux环境最新的软件包,Python版本建议选择3系列的,2系列后续将不再维护。下载文件为一个sh程序包:如:
Anaconda3-4.3.1-Linux-x86_64.sh,然后在下载目录下运行如下命令:

bash Anaconda3-4.3.1-Linux-x86_64.sh

安装过程中按enter或y即可,安装完成后,程序提示是否把anaconda的binary加入到.bashrc配置文件中,加入后运行python、ipython时将自动使用新安装的Python环境。
安装完成后,你可用conda list命令查看已安装的库:

conda list

安装成功的话,应该能看到numpy、scipy、matplotlib、conda等库。
(2)安装theano
利用conda 来安装或更新程序

conda install theano

(3)测试
先启动python,然后导入theano模块,如果不报错,说明安装成功。

$ python
Python 3.6.0 |Anaconda custom (64-bit)| (default, Dec 23 2016, 12:22:00)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import theano
>>>

2.2符号变量

存储数据需要用到各种变量,Theano如何使用变量的呢?Theano用一种变量类型称为符号变量来表示变量,用TensorVariable表示,又称为张量(Tensor),张量是Theano的核心元素(也是TensorFlow的核心元素),它是Theano表达式和运算操作的基本单位。张量可以是标量(scalar)、向量(vector)、矩阵(matrix)等的统称。具体来说,标量就是我们通常看到的0阶的张量,如12,a等,而向量和矩阵分别为1阶张量和2阶的张量。
如果通过这些概念,你还不很清楚,没有关系,可以结合以下一个实例,来直观感受一下。
首先定义三个标量:一个代表输入x、一个代表权重w、一个表示偏移量b,然后计算这些标量运算结果z=x*w+b,Theano代码实现如下:

打印结果
net_input: 6.500000

通过以上实例我们不难看出,Theano本身是一个通用的符号计算框架,与非符号架构的框架不同,它先使用tensor variable初始化变量,然后将复杂的符号表达式编译成为函数模型,最后运行时传入实际数据进行计算。整个过程涉及三个步骤:定义符号变量,编译代码,执行代码。这节主要介绍第一步如何定义符号变量,其他步骤将在后续小节介绍。
如何定义符号变量?或定义符号变量有哪些方式?在Theano中定义符号变量有大致三种:使用内置的变量类型、自定义变量类型、转换其他的变量类型,具体如下:
(1)使用内置的变量类型创建
目前Theano支持7种内置的变量类型,分别是标量(scalar)、向量(vector)、行(row)、列(col)、矩阵(matrix)、tensor3、tensor4等。其中标量是0阶张量,向量为1阶张量,矩阵为二阶张量等,以下为创建内置变量的实例:

其中,name指定变量名字,dtype指变量的数据类型。
(2)自定义变量类型
内置的变量类型只能处理4维及以下的变量,如果需要处理更高维的数据时,我们可以使用Theano的自定义变量类型,具体通过TensorType方法来实现:

其中broadcastable是True或False的布尔类型元组,元组的大小等于变量的维度,如果为True,表示变量在对应维度上的数据可以进行广播,否则数据不能广播。
广播机制(broadcast)是一种重要机制,有了这种机制,就可以方便对不同维的张量进行运算,否则,就要手工把低维数据变成高维,利用广播机制系统自动利用复制等方法把低维数据补齐,numpy也有这种机制。以下我们通过图1-2所示的一个实例来说明广播机制原理:

图2-1 广播机制

图2-1中矩阵与向量相加的具体代码如下:

(3) 将Python类型变量或者Numpy类型变量转化为Theano共享变量
共享变量是Theano实现变量更新的重要机制,后面我们会详细讲解。要创建一个共享变量,只要把一个Python对象或NumPy对象传递给shared函数即可,如下示例:

2.3符号计算图模型

符号变量定义后,需要说明这些变量间的运算关系,如何描述变量间的运算关系? Theano实际采用符号计算图模型来实现。首先创建表达式所需的变量,然后通过操作符(op)把这些变量结合在一起。如图2-1。
Theano处理符号表达式时通过把符号表达式转换为一个计算图(graph)来处理(TensorFlow也使用了这种方法,等到我们介绍TensorFlow时,大家可对比一下),符号计算图的节点有:variable、type、apply和op。
variable节点:即符号的变量节点,符号变量是符号表达式存放信息的数据结构,可以分为输入符号和输出符号。
type节点:当定义了一种具体的变量类型以及变量的数据类型时,Theano为其指定数据存储的限制条件。
apply节点:把某一种类型的符号操作符应用到具体的符号变量中,与variable不同,apply节点无须由用户指定,一个apply节点包括3个字段:op、inputs、outputs。
op节点:即操作符节点,定义了一种符号变量间的运算,如+、-、sum()、tanh()等。
Theano是将符号表达式的计算表示成graphs。这些graphs是由Apply 和 Variable将节点连接而组成,它们分别与函数的应用和数据相连接。 操作由 Op 实例表示,而数据类型由 Type 实例表示。下面这段代码和图2-2,说明了这些代码所构建的结构。借助这个图或许有助于您进一步理解,如何将这些内容拟合在一起:

图2-2 符号计算图
图2-2中箭头表示指向python对象的引用。这里的蓝色盒子是一个 Apply 节点,红色盒子是 Variable 节点,绿色圆圈是Ops,紫色盒子是 Types。
在创建 Variables 之后,应用 Apply Ops得到更多的变量,这些变量仅仅是一个占位符,在function中作为输入。变量指向 Apply 节点的过程是用来表示函数通过它们的owner 域来生成它们 。这些Apply节点是通过它们的inputs和outputs域来得到它们的输入和输出变量。
x 和 y 的owner 域的指向都是None,是因为它们不是另一个计算的结果。如果它们中的一个是另一个计算的结果,那么owner域将会指向另一个蓝色盒。

2.4函数

上节我们介绍了如何把一个符号表达式转化为符号计算图,这节我们介绍函数的功能,函数是Theano的一个核心设计模块,它提供一个接口,把函数计算图编译为可调用的函数对象。前面介绍了如何定义自变量x(不需要赋值),这节介绍如何编写函数方程。
(1)函数定义的格式
theano.function(inputs, outputs, mode=None, updates=None, givens=None, no_default_updates=False, accept_inplace=False, name=None,rebuild_strict=True, allow_input_downcast=None, profile=None, on_unused_input='raise')。
这里参数看起来很多,但常用的一般只用到三个,inputs表示自变量、outputs表示函数的因变量(也就是函数的返回值),还有另外一个比较常用的是updates这个参数,这个一般用于神经网络共享变量参数更新,通常以字典或元组列表的形式指定;givens是一个字典或元组列表,记为[(var1,var2)],表示在每一次函数调用时,在符号计算图中,把符号变量var1节点替换为var2节点,该参数常用来指定训练数据集的batch大小。
我们看一个有多个自变量、同时又有多个因变量的函数定义例子:

打印结果
[array(5.0, dtype=float32), array(6.0, dtype=float32)]
在执行theano.function()时,Theano进行了编译优化,得到一个end-to-end的函数,传入数据调用f(2,3)时,执行的是优化后保存在图结构中的模型,而不是我们写的那行z=x+y,尽管二者结果一样。这样的好处是Theano可以对函数f进行优化,提升速度;坏处是不方便开发和调试,由于实际执行的代码不是我们写的代码,所以无法设置断点进行调试,也无法直接观察执行时中间变量的值。
(2)自动求导
Theano有了符号计算图2-2,自动计算导数就很容易了。tensor.grad()唯一需要做的就是从outputs逆向遍历到输入节点。对于每个Op,它都定义了怎么根据输入计算出偏导数。使用链式法则就可以计算出梯度了。利用Theano求导时非常方便,可以直接利用函数theano.grad(),比如求S函数的导数:

以下代码实现当x=3的时候,求s函数的导数:

打印结果
0.045176658779382706
(3)更新共享变量参数
在深度学习中通常需要迭代多次,每次迭代都需要更新参数。Theano如何更新参数呢?在theano.function函数中,有个非常重要的参数updates,updates是一个包含两个元素的列表或tuple,updates=[old_w,new_w],当函数被调用的时候,这个会用new_w替换old_w,具体看下面这个例子。

打印结果:1、4
在求梯度下降的时候,经常用到updates这个参数。比如updates=[w,w-α*(dT/dw)],其中dT/dw就是我们梯度下降的时候,代价函数对参数w的偏导数,α是学习速率。为便于大家的更全面的了解Theano函数一些使用,下面我们通过一个逻辑回归的完整实例来说明:

2.5条件与循环

编写函数需要经常用到条件或循环,这节我们就简单介绍Theano如何实现条件判断或逻辑循环。 (1)条件判断 Theano是一种符号语言,条件判断不能直接使用Python的if语句。在Theano可以用ifelse 和 Switch来表示判定。这两个判决语句有何区别呢? switch对每个输出变量进行操作,ifelse只对一个满足条件的变量操作,比如: 对语句: switch(cond, ift, iff) 如果满足条件,则switch既执行ift也执行iff。对语句: if cond then ift else iff ifelse只执行ift或者只执行iff 以下通过一个示例进一步说明

打印结果
time spent evaluating both values 0.005268 sec
time spent evaluating one value 0.007501 sec

(2)循环语句
scan是theano中构建循环Graph的方法,scan是个灵活复杂的函数,任何用循环、递归或者跟序列有关的计算,都可以用scan完成。其格式如下:
theano.scan(fn, sequences=None, outputs_info=None, non_sequences=None, n_steps=None, truncate_gradient=-1, go_backwards=False, mode=None, name=None, profile=False, allow_gc=None, strict=False)
参数说明:
fn:函数类型,scan的一步执行。除了outputs_info,fn可以返回sequences变量的更新updates。fn的输入变量顺序为sequences中的变量,outputs_info的变量,non_sequences中的变量。如果使用了taps,则按照taps给fn喂变量。taps的详细介绍会在后面的例子中给出。
sequences:scan进行迭代的变量,scan会在T.arange()生成的list上遍历,例如下面的polynomial 例子。
outputs_info:初始化fn的输出变量,和输出的shape一致。如果初始化值设为None表示这个变量不需要初始值。
non_sequences:fn函数用到的其他变量,迭代过程中不可改变(unchange)。
n_steps:fn的迭代次数。
下面通过一个例子解释scan函数的具体使用方法。
代码实现思路是:先定义函数one_step,它就是scan里的fn,其任务就是计算多项式的一项,scan函数返回的result里会保存多项式每一项的值,然后我们对result求和,就得到了多项式的值。

打印结果
多项式各项的值: [ 2 30 400 6000 50000]
多项式和的值: 56432

2.6共享变量

共享变量(shared variable)是实现机器学习算法参数更新的重要机制。shared函数会返回共享变量。这种变量的值在多个函数可直接共享。可以用符号变量的地方都可以用共享变量。但不同的是,共享变量有一个内部状态的值,这个值可以被多个函数共享。它可以存储在显存中,利用GPU提高性能。我们可以使用get_value和set_value方法来读取或者修改共享变量的值,使用共享变量实现累加操作。

这里state是一个共享 变量,初始化为0,每次调用accumulator(),state都会加上inc。共享变量可以像普通张量一样用于符号表达式,另外,他还有自己的值,可以直接用.get_value()和.set_value()方法来访问和修改。
上述代码引入了函数中updates参数。updates参数是一个list,其中每个元素是一个元组(tuple),这个tuple的第一个元素是一个共享变量,第二个元素是一个新的表达式。updatas中的共享变量会在函数返回后更新自己的值。updates的作用在于执行效率,updates多数时候可以用原地(in-place)算法快速实现,在GPU上,Theano可以更好地控制何时何地给共享变量分配空间,带来性能提升。最常见的神经网络权值更新,一般会用update实现。

2.7小结

Theano基于NumPy,但性能方面又高于NumPy。因Theano采用了张量(Tensor)这个核心元素,在计算方面采用符号计算模型,而且采用共享变量、自动求导、利用GPU等适合于大数据、深度学习的方法,其他很多开发项目也深受这些技术和框架影响。本章主要为后续介绍TensorFlow做个铺垫。

本章代码下载

第1章 NumPy常用操作

NumPy是Python基础,更是数据科学的通用语言,而且与TensorFlow关系密切,所以我们把它列为第一章。
NumPy为何如此重要?实际上Python本身含有列表(list)和数组(array),但对于大数据来说,这些结构有很多不足。因列表的元素可以是任何对象,因此列表中所保存的是对象的指针。这样为了保存一个简单的[1,2,3],都需要有3个指针和三个整数对象。对于数值运算来说这种结构显然比较浪费内存和CPU计算时间。 至于array对象,它直接保存数值,和C语言的一维数组比较类似。但是由于它不支持多维,也没有各种运算函数,因此也不适合做数值运算。
NumPy(Numerical Python 的简称)的诞生弥补了这些不足,NumPy提供了两种基本的对象:ndarray(N-dimensional array object)和 ufunc(universal function object)。ndarray是存储单一数据类型的多维数组,而ufunc则是能够对数组进行处理的函数。
NumPy的主要特点:
 ndarray,快速和节省空间的多维数组,提供数组化的算术运算和高级的广播功能。
 使用标准数学函数对整个数组的数据进行快速运算,而不需要编写循环。
 读取/写入磁盘上的阵列数据和操作存储器映像文件的工具。
 线性代数,随机数生成,和傅里叶变换的能力。
 集成C,C++,Fortran代码的工具。
在使用 NumPy 之前,需要先导入该模块:

本章主要内容如下:
 如何生成NumPy的ndarray的几种方式
 如何存取元素
 如何操作矩阵
 如何合并或拆分数据
 简介NumPy的通用函数
 简介NumPy的广播机制

1.1生成ndarray的几种方式

NumPy封装了一个新的数据类型ndarray(n-dimensional array),一个多维数组对象,该对象封装了许多常用的数学运算函数,方便我们做数据处理,以及数据分析,如何生成ndarray呢?这里我们介绍生成ndarray的几种方式,如从已有数据中创建;利用random创建;创建特殊多维数组;使用arange函数等。
1.从已有数据中创建
直接对python的基础数据类型(如列表、元组等)进行转换来生成ndarray。
(1)将列表变换成ndarray

打印结果
[ 3.14 2.17 0. 1. 2. ]
<class 'numpy.ndarray'>
(2)嵌套列表可以转换成多维ndarray

打印结果
[[ 3.14 2.17 0. 1. 2. ]
[ 1. 2. 3. 4. 5. ]]
<class 'numpy.ndarray'>
如果把(1)和(2)中的列表换成元组也同样适合。
2.利用random模块生成ndarray
在深度学习中,我们经常需要对一些变量进行初始化,适当的初始化能提高模型的性能。通常我们用随机数生成模块random来生成,当然random模块又分为多种函数:
random生成0到1之间的随机数;uniform生成均匀分布随机数;randn生成标准正态的随机数;normal生成正态分布;shuffle随机打乱顺序;seed设置随机数种子等等,以下我们列举几个简单示例。

打印结果
[[ 0.88900951 0.47818541 0.91813526]
[ 0.48329167 0.63730656 0.14301479]
[ 0.9843789 0.99257093 0.24003961]]
<class 'numpy.ndarray'>
生成一个随机种子,对生成的随机数打乱。

打印结果
[[-1.0856306 0.99734545 0.2829785 ]
[-1.50629471 -0.57860025 1.65143654]]
随机打乱后数据
[[-1.50629471 -0.57860025 1.65143654]
[-1.0856306 0.99734545 0.2829785 ]]
<class 'numpy.ndarray'>
3. 创建特定形状的多维数组
数据初始化时,有时需要生成一些特殊矩阵,如0或1的数组或矩阵,这时我们可以利用np.zeros,np.ones,np.diag来实现,以下我们通过几个示例来说明。

我们还可以把生成的数据保存到磁盘,然后从磁盘读取。

4.利用arange函数
arange是numpy模块中的函数,其格式为:arange([start,] stop[, step,], dtype=None)
根据start与stop指定的范围,以及step设定的步长,生成一个 ndarray,其中start缺省值为0,步长step可为小数。

1.2存取元素

上节我们介绍了生成ndarray的几种方法,数据生成后,如何读取我们需要的数据?这节我们介绍几种读取数据的方法。

如果你对上面这些获取方式还不是很清楚,没关系,下面我们通过图形的方式说明如何获取多维数组中的元素,如图1-1所示,左边的为表达式,右边为对应获取元素。

图1-1 获取多维数组中的元素
获取数组中的部分元素除通过指定索引标签外,还可以使用一些函数来实现,如通过random.choice函数可以从指定的样本中进行随机抽取数据。

打印结果:
随机可重复抽取
[[ 7. 22. 19. 21.]
[ 7. 5. 5. 5.]
[ 7. 9. 22. 12.]]
随机但不重复抽取
[[ 21. 9. 15. 4.]
[ 23. 2. 3. 7.]
[ 13. 5. 6. 1.]]
随机但按制度概率抽取
[[ 15. 19. 24. 8.]
[ 5. 22. 5. 14.]
[ 3. 22. 13. 17.]]

1.3矩阵操作

深度学习中经常涉及多维数组或矩阵的运算,正好NumPy模块提供了许多相关的计算方法,下面介绍一些常用的方法。

上面介绍的几种方法是numpy.linalg模块中的函数,numpy.linalg模块中的函数是满足行业标准级的Fortran库,具体请看表1-1。

表1-1 numpy.linalg中常用函数

1.4数据合并与展平

在机器学习或深度学习中,经常遇到需要把多个向量或矩阵按某轴方向进行合并,展平也经常使用,如在卷积或循环神经网络中,在全连接层之前,需要把矩阵展平。这节介绍几种数据合并和展平的方法。
(1)合并一维数组

打印结果:
[1 2 3 4 5 6]
[1 2 3 4 5 6]
(2)多维数组的合并

打印结果
[[0 1]
[2 3]
[0 1]
[2 3]]
合并后数据维度 (4, 2)
按列合并结果:
[[0 1 0 1]
[2 3 2 3]]
合并后数据维度 (2, 4)
(3)矩阵展平

打印结果
[[0 1 2]
[3 4 5]]
按列优先,展平
[0 3 1 4 2 5]
按行优先,展平
[0 1 2 3 4 5]

1.5通用函数

NumPy提供了两种基本的对象,即ndarray和ufunc对象。前面我们对ndarray作了简单介绍,本节将介绍它的另一个对象ufunc,ufunc(通用函数)是universal function的缩写,它是一种能对数组的每个元素进行操作的函数。许多ufunc函数都是在c语言级别实现的,因此它们的计算速度非常快。此外,功能比math模块中函数更灵活。math模块的输入一般是标量,但NumPy中函数可以是向量或矩阵,而利用向量或矩阵可以避免循环语句,这点在机器学习、深度学习中经常使用。表1-2为NumPy常用几个通用函数。
表1-2 NumPy几个常用通用函数

(1)使用math与numpy函数性能比较:

打印结果
math.sin: 0.5169950000000005
numpy.sin: 0.05381199999999886
由此可见,numpy.sin比math.sin快近10倍。

(2)使用循环与向量运算比较:
充分使用python的numpy库中的内建函数(built-in function),实现计算的向量化,可大大提高运行速度。Python的NumPy库中的内建函数使用了SIMD指令。例如下面所示在Python中使用向量化要比使用循环计算速度快得多。如果使用GPU,其性能将更强大,不过NumPy不提供GPU支持。TensorFlow提供GPU,第8章将介绍TensorFlow如何使用GPU来加速算法。

打印结果
dot = 250215.601995
for loop----- Computation time = 798.3389819999998ms
dot = 250215.601995
verctor version---- Computation time = 1.885051999999554ms
从程序运行结果上来看,该例子使用for循环运行时间是使用向量运算运行时间的约400倍。因此,深度学习算法中,一般都使用向量化矩阵运算。

1.6 广播机制

广播机制(Broadcasting)的功能是为了方便不同shape的array(numpy库的核心数据结构)进行数学运算。广播提供了一种向量化数组操作的方法,以便在C中而不是在Python中进行循环,这通常会带来更高效的算法实现。广播的兼容原则为:
(1)对齐尾部维度
(2)shape相等or其中shape元素中有一个为1
以下通过实例来具体说明。

打印结果
[ 0 2 4 6 8 10 12 14 16 18]
[ 3 4 5 6 7 8 9 10 11 12]
[ 0 1 4 9 16 25 36 49 64 81]
[[ 0 2]
[ 2 4]
[ 4 6]
[ 6 8]
[ 8 10]]

图1-2 NumPy多维数组相加
有时为了保证矩阵运算正确,我们可以使用reshape()函数来变更矩阵的维度。

1.7小结

本章简单介绍了NumPy模块的两个基本对象ndarray、ufunc,介绍了ndarray对象的几种生成方法,及如何存取其元素、如何操作矩阵或多维数组、如何进行数据合并与展平等。最后说明了通用函数及广播机制。如果想进一步了解NumPy,大家可参考:http://www.numpy.org/

本章代码下载

Python深度学习----TensorFlow卷

目录
第一部分 Python及应用数学基础 12
第1章 NumPy常用操作
1.1生成ndarray的几种方式 12
1.2存取元素 15
1.3矩阵操作 17
1.4数据合并与展平 18
1.5通用函数 20
1.6 广播机制 22
1.7小结 23
第2章 Theano基础
2.1安装 24
2.2符号变量 25
2.3符号计算图模型 28
2.4函数 29
2.5条件与循环 32
2.6共享变量 35
2.7小结 36
第3章线性代数
3.1标量、向量、矩阵和张量 37
3.2矩阵和向量运算 40
3.3特殊矩阵与向量 41
3.4线性相关性及向量空间 42
3.5范数 43
3.6特征值分解 44
3.7奇异值分解 45
3.8迹运算 46
3.9实例:Python实现主成分分析 47
3.10小结 51
第4章概率与信息论
4.1为何要学概率、信息论 52
4.2样本空间与随机变量 53
4.3概率分布 53
4.3.1 离散型随机变量 54
4.3.2 连续型随机变量 56
4.4边缘概率 58
4.5条件概率 59
4.6条件概率的链式法则 60
4.7独立性及条件独立性 60
4.8期望、方差及协方差 61
4.9贝叶斯定理 63
4.10信息论 64
4.11 小结 67
第5章概率图模型 68
5.1为何要引入概率图 68
5.2使用图描述模型结构 68
5.3贝叶斯网络 70
5.3.1隐马尔可夫模型简介 70
5.3.2隐马尔可夫模型三要素 71
5.3.3隐马尔可夫模型三个基本问题 72
5.3.4隐马尔可夫模型简单实例 72
5.4马尔可夫网络 74
5.4.1 马尔可夫随机场 75
5.4.2条件随机场 76
5.4.3实例:用Tensorflow实现条件随机场 77
5.5小结 80
第二部分深度学习理论与应用 81
第6章机器学习基础 81
6.1监督学习 81
6.1.1 线性模型 82
6.1.2 SVM 85
6.1.3贝叶斯分类器 87
6.1.4 集成学习 89
6.2无监督学习 91
6.2.1主成分分析 91
6.2.2k-means聚类 92
6.3梯度下降与优化 93
6.3.1梯度下降简介 93
6.3.2梯度下降与数据集大小 94
6.3.3传统梯度优化的不足 96
6.3.4动量算法 97
6.3.5 自适应算法 99
6.3.6有约束最优化 102
6.4前馈神经网络 104
6.4.1神经元结构 104
6.4.2感知机的局限 105
6.4.4多层神经网络 106
6.4.5 实例:用TensorFlow实现XOR 109
6.4.6反向传播算法 111
6.5 实例:用keras构建深度学习架构 116
6.6小结 117
第7章深度学习挑战与策略 118
7.1 正则化 118
7.1.1正则化参数 118
7.1.2增加数据量 123
7.1.3梯度裁剪 123
7.1.4提前终止 124
7.1.5共享参数 124
7.1.6 Dropout 125
7.2 预处理 127
7.2.1 初始化 127
7.2.2 归一化 128
7.3批量化 128
7.3.1随机梯度下降法 128
7.3.2批标准化 129
7.4并行化 131
7.4.1TensorFlow利用GPU加速 131
7.4.2深度学习并行模式 132
7.5选择合适激活函数 134
7.6选择合适代价函数 135
7.7选择合适优化算法 137
7.8小结 137
第8章安装TensorFlow 138
8.1 TensorFlow CPU版的安装 138
8.2 TensorFlow GPU版的安装 139
8.3配置Jupyter Notebook 143
8.4实例:比较CPU与GPU性能 144
8.5 实例:单GPU与多GPU性能 145
8.6小结 147
第9章TensorFlow基础 148
9.1 TensorFlow系统架构 148
9.2数据流图 149
9.3 TensorFlow基本概念 150
9.3.1张量 151
9.3.2算子 152
9.3.3计算图 152
9.3.4会话 153
9.3.5常量 155
9.3.6变量 155
9.3.7占位符 160
9.3.8实例:比较constant 、variable和 placeholder 161
9.4TensorFlow实现数据流图 163
9.5可视化数据流图 164
9.6TensorFlow分布式 166
9.7小结 168
第10章TensorFlow图像处理 169
10.1加载图像 169
10.2图像格式 170
10.3把图像转换为TFRecord文件 171
10.4 读取TFRecord文件 172
10.5图像处理实例 174
10.6 全新的数据读取方式Dataset API 177
10.6.1 Dataset API 架构 177
10.6.2 构建Dataset 178
10.6.3创建迭代器 182
10.6.4从迭代器中获取数据 182
10.6.5读入输入数据 183
10.6.6预处理数据 183
10.6.7批处理数据集元素 184
10.6.8使用高级API 185
10.7 小结 186
第11章TensorFlow神经元函数 187
11.1激活函数 187
11.1.1sigmoid函数 187
11.1.2tanh函数 188
11.1.3relu函数 188
11.1.4softplus函数 190
11.1.5dropout函数 190
11.2代价函数 190
11.2.1sigmoid_cross_entropy_with_logits函数 191
11.2.2softmax_cross_entropy_with_logits函数 192
11.2.3sparse_softmax_cross_entropy_with_logits函数   193
11.2.4weighted_cross_entropy_with_logits函数 193
11.3小结 194
第12章 TensorFlow自编码器 195
12.1自编码简介 195
12.2降噪自编码 197
12.3 实例:TensorFlow实现自编码 197
12.4实例:用自编码预测信用卡欺诈 200
12.5小结 208
第 13章 TensorFlow 实现Word2Vec 209
13.1词向量及其表达 209
13.2Word2Vec原理 210
13.2.1 CBOW模型 210
13.2.2 Skim-gram模型 211
13.3实例:TensorFlow实现Word2Vec 212
13.4小结 218
第14章 TensorFlow 卷积神经网络 219
14.1 卷积神经网络简介 219
14.2卷积层 220
14.2.1 卷积核 221
14.2.2步幅 222
14.2.3 填充 223
14.2.4 多通道上的卷积 224
14.2.5激活函数 225
14.2.6卷积函数 225
14.3池化层 227
14.4归一化层 228
14.5TensorFlow实现简单卷积神经网络 229
14.6TensorFlow实现进阶卷积神经网络 230
14.7几种经典卷积神经网络 235
14.8小结 236
第15章TensorFlow 循环神经网络 237
15.1 循环神经网络简介 237
15.2前向传播与随时间反向传播 239
15.3 梯度消失或爆炸 242
15.4LSTM算法 242
15.5 RNN其他变种 245
15.6 RNN应用场景 247
15.7 实例:用LSTM实现分类 248
15.8小结 252
第 16章 TensorFlow高层封装 253
16.1 TensorFlow高层封装简介 253
16.2 Estimator简介 254
16.3 实例:使用Estimator预定义模型 255
16.4 实例:使用Estimator自定义模型 258
16.5 Keras简介 264
16.6实例:Keras实现序列式模型 265
16.7 TFLearn简介 267
16.7.1利用TFLearn解决线性回归问题 268
16.7.2利用TFLearn进行深度学习 269
16.8小结 270
第17章情感分析 271
17.1深度学习与自然语言处理 271
17.2词向量简介 272
17.3 循环神经网络 273
17.4 迁移学习简介 273
17.5实例:TensorFlow实现情感分析 274
17.5.1 导入数据 274
17.5.2定义辅助函数 280
17.5.3构建RNN模型 280
17.5.4调优超参数 283
17.5.5训练模型 283
17.6小结 286
第18章利用TensorFlow预测乳腺癌 287
18.1 数据说明 287
18.2数据预处理 288
18.3 探索数据 290
18.4 构建神经网络 294
18.5 训练并评估模型 296
18.6 小结 298
第19章聊天机器人 299
19.1 聊天机器人原理 299
19.2 带注意力的框架 300
19.3用TensorFlow实现聊天机器人 304
19.3.1 接口参数说明 305
19.3.2 训练模型 308
19.4 小结 321
第20 章人脸识别 322
20.1人脸识别简介 322
20.2项目概况 325
20.3实施步骤 325
20.3.1数据准备 326
20.3.2 预处理数据 326
20.3.3 训练模型 329
20.3.4 测试模型 334
20.4小结 337
第三部分扩展篇 338
第21章强化学习基础 338
21.1强化学习简介 338
21.2强化学习常用算法 340
21.2.1 Q-Learning算法 340
21.2.2 Sarsa算法 342
21.2.3 DQN算法 342
21.3小结 343
第22章生成式对抗网络 344
22.1 GAN简介 344
22.2GAN的改进版本 346
22.3小结 348
第23章语音识别基础 349
23.1 语言识别系统的架构 349
23.2 语音识别的原理 350
23.3 语音识别发展历程 354
23.4小结 355

下载本书代码及数据【暂未开放,书上架时将开放】

语音识别基础

第24章 语音识别基础

语音作为最自然便捷的交流方式,一直是人机通信和交互最重要的研究领域之一。自动语音识别(Automatic Speech Recognition,ASR)是实现人机交互尤为关键的技术,其所要解决的问题是让计算机能够“听懂”人类的语音,将语音中传化为文本,或其它语言的语音(如同声翻译)等。
自动语音识别技术起源于上世纪五十年代,经过几十年的发展已经取得了显著的成效。尤其是近些年,把深度学习融入其中,使ASR取得突飞猛进的发展。越来越多的语音识别智能软件和应用走入了人们的日常生活,比较典型的有苹果的Siri、亚马逊的Alexa、微软的小娜、科大讯飞的语音输入法、叮咚智能音箱等。

本章主要介绍语音识别的基本概念、发展轨迹及主要技术和原理等。包括:
 语音识别系统架构
 语音识别基础
 语音识别发展轨迹
 语音识别未来方向

24.1 语言识别系统的架构

图24.1为语言识别系统的典型结构,其中关键技术涉及信号处理、特征提取、解码器及声学模型和语言模型的建立等。

图24.1 语音识别系统的架构
 信号处理及特征提取
该模块的主要任务是从输入信号中提取特征,供声学模型处理。同时,它一般也包括了一些信号处理技术,以尽可能降低环境噪声、信道、说话人等因素对特征造成的影响。
 声学模型
声学模型充分利用了声学、语音学、环境特性以及说话人性别口音等信息,对语音进行建模。目前的语音识别系统往往采用隐含马尔科夫模型(Hidden Markov Model,HMM)建模,表示某一语音特征矢量序列对某一状态序列的后验概率。隐含马尔科夫模型是一种概率图模型,可以用来表示序列之间的相关关系,常常被用来对时序数据建模。
 词典
词典包含系统所能处理的词汇集及其发音。词典实际提供了声学模型建模单元与语言模型建模单元间的映射。
 语言模型
语言模型对系统所针对的语言进行建模。理论上,包括正则语言,上下文无关文法在内的各种语言模型都可以作为语言模型,但目前各种系统常用的还是基于统计的N 元文法(N-Gram),即统计前后 N 个字出现的概率。
 解码器
解码器是语音识别系统的核心之一,其任务是对输入的信号,根据声学、语言模型及词典,寻找能够以最大概率输出该信号的词序列。
这节我们介绍了语音识别系统的主要架构,接下了我们从基本概念入手,进一步分析语音识别系统如果把语音或声音一步步变成文字或语句的。

24.2 语音识别的原理

上节介绍了语音识别系统的架构,这节我们对上节内容进行细化或具体化,为了大家对语音识别系统有直观认识,主要介绍系统涉及的一般基本概念及其原理。
语音识别系统的输入是何种形式的文件?
语音识别系统的输入自然是语音或声音,我们知道声音实际上是一种波。常见的MP3等格式都是压缩格式,必须转成非压缩的纯波形文件来处理,比如通常看到wav文件。wav文件里存储的除了一个文件头以外,就是声音波形的一个个点了。下图24.2是一个波形的示例

图24.2 声音波形示意图
所以输入文件一般是wav文件。有了输入文件后,接下就需要文件进行预处理,一般需要做哪些预处理呢?
(1)去首尾静音
利用信号处理的一些技术,切除首尾端的静音,这种操作一般称为VAD,以降低对后续步骤造成的干扰。
(2)分帧
对声音分帧,也就是把声音切成一小段一小段,每小段称为一帧。分帧操作一般不是简单的切开,而是使用移动窗口函数来实现。帧与帧之间一般有交叠,如图24.3

图24.3 分帧示意图
图24.3中,每帧的长度为25毫秒,每两帧之间有25-10=15毫秒的交叠。我们称为以帧长25ms、帧移动10ms分帧。
(3)特征提取
通过分帧后,语音变成了很多小段。但这些小段波形计算机还是无法处理,因此需要将波形作变换,常用的一般变换方法是提取MFCC特征,依据人耳的生理特性,把每一帧波形变成一个多维向量,这个向量包含了这帧语音的内容信息,这个过程称为声学特征提取。
这样声音就成了12行(假设声学特征为12维)、N列的一个矩阵(N是总帧数),称为观察序列。观察序列如图24.4,每一帧都用一个12维的向量表示,色块的颜色深浅表示向量值的大小。至此,准备工作基本完成,接下就是训练模型了。

图24.4 观察序列
(4)训练模型
在开始训练模型前,我们先介绍两个基本概念:
音素:单词的发音由音素构成。对英语,一种常用的音素集是卡内基梅隆大学的一套 由39个音素构成的音素集。汉语一般直接用全部声母和韵母作为音素集,另外汉语识 别还分有调无调。
状态:这里理解成比音素更细致的语音单位就可。通常把一个音素划分成3个状态。
具体训练过程可分为三步来实现:
第一步,把帧识别成状态(难点);
第二步,把状态组合成音素;
第三步,把音素组合成单词。
首先看第一步,如何把帧识别成状态?如图24.5所示


图24.5 音素的合作过程
在图24.5中,每个小竖条代表一帧,若干帧语音对应一个状态,每三个状态组合成一个音素,若干个音素组合成一个单词。也就是说,只要知道每帧语音对应哪个状态,语音识别的结果也就出来了。如何由帧音素来确定对应的状态呢?有个容易想到的办法,看某帧对应哪个状态的概率最大,那这帧就属于哪个状态。如图24.6所示,这帧在状态S3上的条件概率最大,因此就猜这帧属于状态S3。

图24.6 由音素确定其对应状态的过程
图24.6中的那些用到的概率从哪里读取呢?有个叫“声学模型”的东西,里面存了一大堆参数,通过这些参数,就可以知道帧和状态对应的概率。获取这一大堆参数的方法叫做“训练”,需要使用巨大数量的语音数据,训练的方法比较繁琐,这里不讲。
但这样做有一个问题:每一帧都会得到一个状态号,最后整个语音就会得到一堆乱七八糟的状态号,相邻两帧间的状态号基本都不相同。假设语音有1000帧,每帧对应1个状态,每3个状态组合成一个音素,那么大概会组合成300个音素,但这段语音其实根本没有这么多音素。如果真这么做,得到的状态号可能根本无法组合成音素。实际上,相邻帧的状态应该大多数都是相同的才合理,因为每帧很短。这里就涉及第二步,如何由状态合成音素的问题。
解决这个问题的常用方法就是使用隐马尔可夫模型(Hidden Markov Model,HMM)。这东西听起来好像很高深的样子,实际上用起来很简单,主要由以下两步来实现:
第一步,构建一个状态网络;
第二步,从状态网络中寻找与声音最匹配的路径。
这样就把结果限制在预先设定的网络中,避免了刚才说到的问题。当然也带来一个局限,比如你设定的网络里只包含了“今天晴天”和“今天下雨”两个句子的状态路径,那么不管说些什么,识别出的结果必然是这两个句子中的一句。
如果想识别任意文本呢?只要把这个网络搭得足够大,包含任意文本的路径就可以了。但这个网络越大,想要达到比较好的识别准确率就越难。所以要根据实际任务的需求,合理选择网络大小和结构。
如何由音素合成单词呢?首先搭建状态网络,它是由单词级网络展开成音素网络,再展开成状态网络,其中涉及到HMM、词典以及语言模型等。具体如图24.7所示。语音识别过程其实就是在状态网络中搜索一条最佳路径,语音对应这条路径的概率最大,这称之为“解码”。路径搜索的算法是一种动态规划剪枝的算法,称之为Viterbi算法,用于寻找全局最优路径。

图24.7 状态网络示意图
这里所说的概率,由三部分构成,分别是:
观察概率:每帧和每个状态对应的概率
转移概率:每个状态转移到自身或转移到下个状态的概率
语言概率:根据语言统计规律得到的概率
其中,前两种概率从声学模型中获取,最后一种概率从语言模型中获取。语言模型是使用大量的文本训练出来的,可以利用某门语言本身的统计规律来帮助提升识别正确率。语言模型很重要,如果不使用语言模型,当状态网络较大时,识别出的结果基本是一团乱麻。
至此,语音识别主要过程就完成了,这就是语音识别技术的原理。
【备注】本节内容主要参考知乎,作者:张俊博,链接:https://www.zhihu.com/question/20398418/answer/18080841

24.3 语音识别发展历程

语音识别与上世纪50年代就开始了,当时主要尝试对单个的孤立词进行识别;
60-70年代人们开始开始探索连续语言识别工作,但进展比较缓慢;
80年代得到开始发展,得益于两个关键技术的应用:隐马尔科夫模型(HMM)的理论和N-gram语言模型;
90年代是语音识别基本成熟的时期,主要进展是语音识别声学模型的区分性训练准则和模型自适应方法的提出。这个时期剑桥语音识别组推出的HTK工具包对于促进语音识别的发展起到了很大的推动作用。此后语音识别发展很缓慢,主流的框架GMM-HMM趋于稳定,但是识别效果离实用化还相差甚远,语音识别的研究陷入了瓶颈;
2006年。这一年辛顿(Hinton)提出深度置信网络(DBN),促使了深度神经网络(Deep Neural Network,DNN)研究的复苏,掀起了深度学习的热潮,从此语音识别开始了新篇章,基于GMM-HMM的语音识别框架被打破,大量研究人员开始转向基于DNN-HMM的语音识别系统的研究。用DNN替换GMM,主要优势有:
(1)使用DNN估计HMM的状态的后验概率分布不需要对语音数据分布进行假设;
(2)DNN的输入特征可以是多种特征的融合,包括离散或者连续的;
(3) DNN可以利用相邻的语音帧所包含的结构信息。
图24.8为基于深度神经网络的语音识别系统框架。

图24.8 基于深度神经网络的语音识别系统框架
不过这种框架中DNN对语音信号的长时相关性解决不很理想,于是,循环神经网络(Recurrent Neural Network,RNN)近年来逐渐替代传统的DNN成为主流的语音识别建模方案。如图24.9。


图24.9 基于RNN和CTC的主流语音识别系统框架
循环神经网络在隐层上增加了一个反馈连接,这就很好解决长时相关性问题,再加上序列短时分类(Connectionist Temporal Classification,CTC)输出层,使整个训练更加高效,实现有效的“端对端”训练。
近几年,随着大数据、深度学习及云计算的进一步发展和融合,语音识别性能得到进一步提升,现在已有很多商业化应用,并且正在不断拓展其应用的广度和深度。