《TensorFlow:实战Google深度学习框架(第二版)》笔记【1-6章】

本书PDF 密码: uj6t
代码:https://github.com/caicloud/tensorflow-tutorialnode

第一章:深度学习简介

在大部分状况下,在训练数据达到必定数量以前,越多的训练数据可使逻辑回归算法对未知邮件作出的判断越精准。之因此说在大部分状况下,是由于逻辑回归算法的效果除了依赖于训练数据,也依赖于从数据中提取的特征。假设从邮件中抽取的特征只有邮件发送的时间,那么即便有再多的训练数据,逻辑回归算法也没法很好地利用。这是由于邮件发送的时间和邮件是否为垃圾邮件之间的关联不大,而逻辑回归算法没法从数据中习得更好的特征表达。这也是不少传统机器学习算法的一个共同问题。git

如何从实体中提取特征,对于不少传统机器学习算法的性能有巨大影响。下图展现了一个简单的例子。经过笛卡尔坐标系和极角坐标系表示数据。一样的数据使用不一样的表达方式会极大地影响解决问题的难度。一旦解决了数据表达和特征提取,不少人工智能任务也就解决了90%。github

既然人工的方式没法很好地抽取实体中的特征,那么是否有自动的方式呢?深度学习解决的核心问题之一就是自动的将简单地特征组合成更加复杂的特征,并使用这些组合特征解决问题。下图展现了深度学习和传统机器学习在流程上的差别。深度学习算法能够从数据中学习更加复杂的特征表达,使得最后一步权重学习变得更加简单且有效。web

神经科学家们发现,若是将小白鼠的视觉神经链接到听觉中枢,一段时间以后小白鼠能够习得使用听觉中枢“看”世界。这说明虽然哺乳动物大脑分为了不少区域,但这些区域的学习机制倒是类似的。正则表达式

深度学习的发展历程

第一个阶段:模仿大脑的学习机理算法

感知机模型能够根据样例数据学习特征权重。
不足:感知机没法解决线性不可分问题,且计算能力不足以训练多层神经网络编程

第二个阶段:分布式知识表达和反向传播算法
分布式知识表达大大增强了模型的表达能力,让神经网络从宽度的方向走向了深度的方向。
反向传播算法大幅下降了训练神经网络所须要的时间。
不足:计算资源不足以训练深层神经网络;数据量没法知足训练须要。json

第三个阶段:云计算、GPU和海量数据
解决了计算力和数据的问题,深度学习迎来高潮。数组

深度学习的应用

  1. 计算机视觉:分类,识别,无人驾驶,图像搜索等
  2. 语音识别:siri,同声传译等
  3. 天然语言处理:语言模型、机器翻译、词性标注、实体识别、情感分析等
    核心技术:单词向量word embedding
  4. 人机博弈:AlphaGo系列

深度学习工具

第二章:TensorFlow环境搭建

  1. 依赖的工具包
  2. 安装方式
  3. 样例程序(略)

TensorFlow的主要依赖包

Protocol Buffer和Bazel浏览器

Protocol Buffer

Protocol Buffer的做用是将结构化的数据序列化,并从序列化以后的数据流中还原出原来的结构化数据。

与XML、Json等其余工具相比:

  • 序列化为二进制流,而不是字符串
  • XML、Json格式信息包含在数据流中,Protocol Buffer须要先定义数据的格式
  • Protocol Buffer数据流比XML小3-10倍,解析时间快20-100倍

结构化数据:

Protocol Buffer数据格式示例:

Protocol Buffer数据格式通常保存在.proto文件中。

TensorFlow中的数据基本都是经过Protocol Buffer来组织的。

Bazel

Bazel是从Google开源的自动化构建工具,相比传统的makefile,Ant或者Maven,Bazel在速度、可伸缩性、灵活性以及对不一样程序语言和平台的支持上都要更加出色。Tensorflow自己以及Google给出的不少官方样例都是经过Bazel来编译的。

  • WORKSPACE文件:定义了对外部资源的依赖关系
  • BUILD文件:找到须要编译的目标

在编译出来的结果中,bazel-bin目录下存放了编译产生的二进制文件以及运行该二进制文件所须要的全部依赖关系。

Tensorflow安装方式

  • Docker:可移植性最强,但对GPU支持有限,且对本地开发环境的支持不够友好
  • Pip:最方便,但没法修改Tensorflow自己
  • 源码:最灵活,但比较繁琐,通常只有修改tensorflow护着须要支持特殊GPU才会被用到

第三章 Tensorflow入门

本章介绍Tensorflow的计算模型、数据模型和运行模型,以了解Tensorflow的工做原理。并用Tensorflow实现一个神经网络的计算流程。

Tensorflow计算模型:计算图

计算图是Tensorflow中最基本的一个概念,Tensorflow中的全部计算都被被转化为计算图上的节点。

Tensorflow是一个经过计算图的形式来描述计算的编程系统。Tensor即张量,Flow指计算图。Tensorflow中的每个计算都是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系。

为了建模方便,tf将常量转化成一种永远输出固定值的运算。

tf自动维护一个默认的计算图,若是没有特地指定,tf会将定义的计算自动转化为默认计算图上的节点。

除了默认的计算图,也能够用tf.Graph生成新的计算图,不一样计算图的张量和运算不会共享。

计算图能够经过tf.Graph.device函数指定运行计算的设备。

有效的整理tf程序中的资源也是计算图的一个重要功能。能够经过集合来管理不一样类别的资源。体乳经过tf.add_to_collection将资源加入一个或多个集合中,而后经过tf.get_collection获取一个集合里面的全部资源。这里的资源能够是张量、变量或者运行tf程序所须要的队列资源等。tf自动管理了一些最经常使用的集合:

Tensorflow数据模型:张量

张量是tf管理数据的形式。

tf全部的数据都经过张量的形式来表示,张量能够简单理解为多维数组。但张量在tf的实现并非直接采用数组的形式,它只是对tf中运算结果的引用。在张量中并无真正保存数字,它保存的是如何获得这些数字的计算过程。

执行上面的代码,并不会获得加法的结果,而是对结果的一个引用。
tf的计算均可以经过计算图的模型来创建,而计算图上的每个节点表明了一个计算,计算的结果保存在张量之中。张量对应了计算图上节点表明的计算结果。

一个张量主要保存三个属性:名字(name),维度(shape)和类型(type)。

  • 张量的命名形式能够是“node:src_output"。其中node为节点的名称,src_output表示来自节点的第几个输出。
  • 维度是张量一个很重要的属性,围绕张量的维度tf给出了不少有用的运算。
  • 每一个张量都有一个惟一的类型,tf会对参与运算的全部张量进行类型检查,当发现类型不匹配会报错。如

报错:

若是将a指定为实数类型”a=tf.constant([1,2],name=“a”,dtype=tf.float32)",就不会报错了。不指定类型,tf会给出默认的类型,容易致使类型不匹配问题。tf支持14中不一样的类型,主要包括实数(tf.float32,tf.float64)、整数(tf.int8,tf.int16,tf.int64,tf.uinit8)、布尔型(tf.bool)和复数(tf.complex64/tf.conplex128)。

Tensorflow运行模型:会话

计算图和张量分别组织运算和数据,而会话(session)用来执行定义好的运算。会话拥有并管理tf程序运行时的全部资源,当全部计算完成以后须要关闭会话帮助系统回收资源,不然可能出现资源泄露的问题。tf中使用会话的模式通常有两种。

使用这种模式须要明确调用Session.close关闭会话并释放资源。当程序由于异常而退出时,关闭函数可能不会执行致使资源泄露。

经过Python上下文管理器机制,退出时自动释放全部资源。

会话和计算图有相似的机制,但tf不会自动生成默认的会话,而是须要手动指定。默认的会话被指定以后能够经过tf.Tensor.eval函数来计算一个张量的取值。

或者

在交互式环境(好比Python脚本或者Jupyter的编辑器)下,经过设置默认会话的方式来获取张量的取值更加方便。因此tf提供了一种直接构建默认会话的函数tf.InteractiveSession。

不管使用哪一种方法均可以经过ConfigProto Protocol Buffer来配置须要生成的会话。

经过ConfigProto能够配置相似并行的线程数、GPU分配策略,运算超时时间等参数。在这些参数中,最常使用的有两个。

  • allow_soft_placement:默认值为False。设为True时,能够在如下状况发生时,把GPU的运算放到CPU上。
  1. 运算没法在GPU上执行
  2. 没有GPU资源(好比指定在第二个GPU,但只有一个GPU)
  3. 运算输入包含对CPU结果的引用
  • log_device_placement:True时日志中会记录每一个节点被安排在了哪一个设备上以方便调试。在生产环境设为False能够减小日志量。

Tensorflow实现神经网络

Tensorflow游乐场是一个经过网页浏览器就能够训练的简单神经网络并实现了可视化训练的工具。

使用这个工具能够看到神经网络解决分类问题主要分为如下步骤:

  1. 提取问题中实体的特征向量做为神经网络的输入。
  2. 定义神经网络的结构,并定义如何从输入获得输出
  3. 经过训练数据来调整神经网络中参数的取值
  4. 使用训练好的神经网络预测未知的数据

前向传播算法简介

本小节用最简单的全链接网络(区别于卷积网络、LSTM等结构)的前向传播算法介绍。首先须要了解神经元(也称为节点)的结构。

一个节点有多个输入和一个输出,最简单的节点的输出就是对全部输入的加权和,不一样输入的权重就是节点的参数。而神经网络的优化过程就是优化节点中参数的值的过程(反向传播算法)。下面给出了一个简单的判断零件是否合格的三层全链接神经网络(全链接神经网络是指相邻两层之间全部节点之间都有链接)。

计算神经网络的前向传播结果须要三部分信息。

  1. 神经网络的输入:从实体中提取的特征向量。上图中的零件长度x1和零件质量x2。
  2. 神经网络的链接结构:神经网络由节点组成,神经网络的结构就是不一样节点之间输入输出的链接关系。
  3. 神经元的参数:用W表示神经元的参数。W的上标代表神经网络的层数,下标代表链接节点的编号。如W1,2(1)表示链接x1和a12节点的权重。

有了输入、结构和参数,就能够经过前向传播算法计算神经网络的输出,如图。

其中a11的计算过程

输出y的计算过程

前向传播算法能够表示为矩阵乘法,将输入x1,x2表示为1x2的矩阵x=[x1,x2],W(1)表示为2x3的矩阵

这样经过矩阵乘法就能够获得隐藏层三个节点的向量取值:

相似的输出层:

tf中矩阵乘法的实现很是简单。

以后的章节中会继续介绍偏置(bias)、激活函数(activation function)等更复杂的神经元结构,还有卷积神经网络、LSTM等更复杂的神经网络结构。

神经网络参数与Tensorflow变量

本小节介绍tf是如何组织、保存和使用神经网络中的参数的。在tf中,变量的做用是保存和更新神经网络中的参数。tf中的变量须要指定初始值,其中随机初始值最多见。

这段代码调用变量声明函数tf.Variable,在函数中给出了初始化方法。初始值能够是随机数、常数或者经过其余变量的初始值计算获得。tf.random_normal([2,3],stddev=2)会产生一个2x3的矩阵,矩阵的元素是均值为0,标准差为2的随机数。下表列出了tf目前支持的全部随机数生成器。

常量声明方法(好比偏置项:biases=tf.Variable(tf.zeros([3])))

经过其余变量的初始值初始化

如下样例介绍了如何经过变量实现神经网络的参数并实现前向传播的过程。

也可使用tf.initialize_all_variables函数实现初始化全部变量的过程。

tf中,变量声明函数tf.Variable是一个运算,输出结果是一个张量,因此变量只是一种特殊的张量。下面经过上个案例中计算图中关于w1的操做的可视化结果说明tf.Variable操做在tf中底层是如何实现的。

能够看到w1是一个Variable运算。w1经过一个read操做将值提供给了一个乘法运算(tf.matmul(x,w1)),Assign这个节点的输入为随机数生成函数的输出,输出赋给了变量w1,这样就完成了变量初始化。

全部的变量都会被自动的加入GraphKeys.VARIABLES这个集合,经过tf.all_variables函数能够拿到当前计算图上全部的变量。拿到全部变量有助于持久化计算图的运行状态。能够经过变量声明函数中的trainable参数来区分须要优化的参数(好比神经元参数)和其余参数(好比迭代次数),若是trainable为True,那么这个变量会被加入到GraphKeys.TRAINABLE_VARIABLES集合。能够经过tf.trainable_variables函数获得全部须要优化的参数。tf提供的神经网络优化算法会将GraphKeys.TRAINABLE_VARIABLES集合中的变量做为默认的优化对象。

相似张量,维度和类型也是变量最重要的两个属性。变量类型是不能够改变的,但维度是可能改变的,须要设置参数validate_shape=False,可是更改维度的作法在tf中比较罕见。

经过Tensorflow训练神经网络模型

在神经网络优化算法中,最经常使用的方法是反向传播算法,本小节主要介绍训练神经网络的总体流程以及Tensorflow对于这个流程的支持。

使用tf实现反向传播算法的第一步是表达一个batch的数据。以前的例子中曾使用常量表达过一个样本,但若是每次迭代都使用一个常量,那么计算图中的常量节点会很是多且利用率很低。为了不这个问题,tf提供了placeholder机制用于提供输入数据。placeholder至关于定义了一个位置,这个位置中的数据在程序运行时再指定。定义placeholder时,须要指定类型,可是不必定须要维度,由于能够根据提供的数据推导出来。

batch数据例子:

获得前向传播结果后,经过定义好的损失函数计算损失并经过反向传播算法更新神经网络参数。损失函数和反向传播算法将在第四章详细介绍。

完整神经网络样例程序

ipynb github代码

第四章 深层神经网络

本章进一步介绍如何设计和优化神经网络,内容包括深度学习的概念、损失函数、反向传播算法、tf实现反向传播的过程以及神经网络优化中常见的问题。

深度学习与深层神经网络

维基百科对深度学习的定义为“一类经过多层非线性变换对高复杂性数据建模算法的合集”。基本上深度学习就是深层神经网络的代名词。

从定义中能够看出深度学习有两个很是重要的特征:多层和非线性。

线性模型的局限性

线性模型中,模型的输出为输入的加权和。

前面介绍的前向传播算法实现的就是一个线性模型。虽然那个神经网络有两层,可是和单层神经网络的表达能力没有区别,由于它们只有线性变换。然而线性模型解决的问题是有限的,也是为何深度学习要强调非线性。

咱们使用Tensorflow游乐场试验一下,使用Activation为Linear的线性模型获得的效果是:

能够看到模型不能很好的区分不一样颜色的点,只能经过直线来划分平面。

使用Activation为Relu的非线性模型的效果是:

可见,对于复杂问题(没法经过直线或者高维平面划分的),非线性模型能够很好的解决。在现实世界中,绝大部分问题都是非线性的。

对于线性可分问题,线性模型就能够很好的区分:

激活函数实现非线性化

上一节提到了Activation(激活函数),若是将前面提到的加权和节点后经过一个非线性函数,那么神经网络模型就是非线性的了。这个非线性函数就是激活函数。

新的结构中加入了偏置项(输出永远为1的节点)和非线性变换。

激活函数的图像都不是一条直线:

新的神经网络结构图:

输出层计算公式

目前tf提供7中不一样的非线性激活函数,好比tf.nn.relu,tf.sigmoid和tf.tanh。tf也支持使用自定义的激活函数。如下代码展现了tf实现上图中的前向传播算法:

多层网络解决异或运算

神经网络发展史上,一个重要的问题就是异或问题。感知机模型的提出从数学上完成了对神经网络的建模,感知机能够简单的理解为单层的神经网络,其网络结构就是图4-5。然而感知机被证实没法解决异或运算(若是两个输入的符号相同输出0,不然输出1):

加入隐藏层后,异或问题能够很好地解决。

并且,隐藏层的四个节点中,每一个节点都有一个角是黑的。这个节点表明了从输入特征中抽取的更高维的特征,好比第一个节点大体表明两个输入的逻辑与操做的结果。深度神经网络的组合特征提取的功能,对于解决不易提取特征向量的问题(图片识别、语音识别等)有很大帮助。

损失函数定义

神经网络模型的效果以及优化的目标是经过损失函数来定义的,本节讲解适用于分类和回归问题的经典损失函数、自定义损失函数。

经典损失函数

分类问题:交叉熵函数
经过神经网络解决多分类问题最经常使用的方法是设置n个输出节点,n是分类的个数。理想状况下,若是一个样本属于类别k,那么这个类别对应的输出节点为1,其余节点为0,好比[0,1,0,0,0,0]。怎么判断一个输出向量和指望的向量有多接近呢?交叉熵(cross entropy)是经常使用的评判方法之一。交叉熵刻画了两个几率分布之间的距离,它是分类问题中使用比较多的损失函数。

给定两个几率分布p和q,经过q来表示p的交叉熵为

交叉熵刻画的是两个几率分布的距离,然而神经网络的输出不必定是一个几率分布(任意事件发生的几率都在0和1之间,且几率总和为1)。Softmax回归是一个经常使用的将前向传播结果变成几率分布的方法。

交叉熵函数不是对称的,H(p,q)!=H(q,p)

Softmax回归自己能够做为一个学习算法来优化分类结果,但在tf中,Softmax回归的参数被去掉了,只做为一个额外的处理层,将输出变成一个几率分布。

交叉熵值越小,两个几率分布越接近。以前已经用tf实现过交叉熵的计算:

由于交叉熵通常与Softmax回归一块儿使用,tf提供了tf.nn.softmax_cross_entropy_with_logits函数:

其中y表明输出值,y_表明标准答案。
若是分类只有一个正确答案,还可使用tf.nn.sparse_softmax_cross_entropy_with_logits函数加速计算过程。

回归问题:均方偏差函数
回归问题须要预测的不是一个实现定义好的类别,而是一个任意实数,好比房价、销量等。解决回归问题的神经网络通常只有一个输出节点,最经常使用的损失函数是均方偏差(MSE)

tf实现:

均方偏差也是分类问题经常使用的损失函数

自定义损失函数

为了让神经网络优化的结果更加接近实际问题,咱们须要自定义损失函数。

好比商品销量问题,若是预测值较大(大于真实销量),商家损失生产商品的成本。若是预测值较小,损失商品的利润。由于通常成本和利润不相同,使用均方偏差不可以很好地最大化利润。好比成本是1元,利润是10元,那么模型应该偏向预测值较大。下面的公式给出了预测值多于或少于真实值时有不一样系数的损失函数,经过这个损失函数,模型可能最大化收益。

tf实现:

示例代码github

神经网络优化算法

本节介绍如何经过反向传播算法和梯度降低算法调整神经网络中参数的取值。梯度降低算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在全部参数上使用梯度降低算法。反向传播算法是训练神经网络的核心算法,能够根据定义好的损失函数优化参数的取值,从而使模型在训练数据集上的损失函数达到一个较小值。神经网络模型参数的优化过程直接决定了模型的质量。

本笔记不对梯度降低和反向传播算法作记录,详情可参考原书或其余资源。

几个概念:

  • 学习率
  • 局部最优
  • 随机梯度降低

在tf中实现神经网络的训练过程大体以下:

神经网络进一步优化

介绍神经网络优化过程当中可能遇到的一些问题,以及解决的经常使用方法。好比指数衰减学习率、过拟合和滑动平均模型。

学习率的设置

tf提供了一种灵活的学习率设置方法——指数衰减法,tf.train.exponential_decay。先使用较大的学习率快速获得一个比较优的解,随着迭代的继续逐步减小学习率,使得模型在训练后期更加稳定。它实现了如下代码的功能:

decayed_learning_rate为每一轮优化时使用的学习率,learning_rate为设定的初始学习率,decay_rate为衰减系数,decay_steps为衰减速度。tf.train.exponential_decay能够经过设置参数staircase选择不一样的衰减方式,默认为False,此时学习率衰减趋势以下图灰色曲线。当设置为True时,global_step/decay_steps会被转化成整数,使得学习率成为一个阶梯函数,如黑色曲线。在这样的设置下,decay_steps一般表明完整使用一遍训练数据所须要的迭代轮数,也就是总训练数据/一个batch的样本数。这样可使得总训练数据中的每个batch都使用相同的学习率,对模型产生相等的做用。当完整过完一遍训练数据时,学习率就减小一次。

tf代码实现:

初始学习率、衰减系数和衰减速度都是根据经验设置

示例代码github

过拟合问题

正则化的思想是在损失函数中加入刻画模型复杂程度的指标,通常来讲模型复杂度只由权重w决定,和偏置b无关。经常使用的刻画模型复杂度的函数R(w)有两种,L1正则

和L2正则

L1正则化会让参数变得更稀疏,而L2正则不会。其次,L1正则的计算公式不可导,L2正则可导。由于优化时须要计算损失函数的偏导数,因此对L2正则损失函数的优化要更加简洁。优化L1正则更加复杂,并且优化方法也有不少种。也能够将L1和L2一块儿使用

tf实现带L2正则的损失函数定义

相似的,L1为

当网络结构复杂 以后,定义网络结构的部分和计算损失函数的部分可能不在同一个函数中,这样经过变量这种方式计算损失函数就不方便了。为了解决这个问题,咱们可使用tf的集合。

滑动平均模型

滑动平均模型是可让模型在测试数据上更鲁棒的方法。tf提供了tf.train.ExponentialMovingAverage来实现滑动平均模型。初始化ExponentialMovingAverage时,须要提供一个衰减率(decay)。ExponentialMovingAverage对每个变量都会维护一个影子变量(shadow variable),影子变量的初始值就是相应变量的值,而每次运行变量更新时,影子变量的值都会更新为:

decay决定了模型更新的速度,decay越大模型越趋于稳定。实际应用中,decay通常设成很是接近1的数(好比0.999或0.9999)。为了模型在训练前期能够更新的更快,ExponentialMovingAverage还提供了num_updates来动态设置decay的大小:

滑动平均实例代码

第5章会给出在真实应用中使用滑动平均的样例。

第五章 MNIST数字识别问题

第四章介绍了训练神经网络模型须要考虑的主要问题和经常使用解决方法,这一章将经过一个实际问题验证。并介绍Tensorflow变量重用和命名空间、模型持久化问题。

MNIST数据处理

MNIST是一个很是有名的手写体数字识别数据集。Tensorflow的封装让使用MNIST数据集变得更加方便,tf提供了一个类来处理MNIST数据,这个类会自动下载并转化MNIST数据的格式,将数据从原始的数据包中解析成训练和测试神经网络时使用的格式。

神经网络模型训练及不一样模型结果对比

首先给出一个tf程序解决MNIST问题,而后在验证数据集上评价表现,使用第四章介绍的优化方法改进模型。

Tensorflow训练神经网络

使用第四章介绍的训练和优化方法(指数衰减、正则、滑动平均)实现的代码:

从上面的结果能够看出,在训练初期,模型在验证数据集上的表现愈来愈好。从第4000轮开始,模型的表现开始波动,说明模型已经接近极小值。

程序开始设置了初始学习率、学习率衰减率、隐藏层节点数量、迭代轮数等7种不一样的参数。通常从验证数据集评判不一样参数取值下模型的表现。交叉验证的方式会花费大量时间,因此在海量数据的状况下,通常会更多地采用验证数据集的形式。

不一样模型效果比较

本小节使用神经网络模型在MNIST测试数据集上的正确率做为评价不一样优化方法的标准。

从上图能够看到,神经网络的结构对模型的效果有本质性的影响。而滑动平均、指数衰减和正则化带来的提高并非特别明显。由于滑动平均和指数衰减在必定程度上都是限制神经网络中参数更新的速度,而在MNSIT数据上,模型收敛的速度很快,因此这两种优化影响不大。

能够看到,从4000轮以后,由于梯度自己较小,参数的改变也就缓慢了。因而滑动平均或者指数衰减的做用没有那么突出了。然而,当问题更加复杂时,迭代不会那么快收敛,好比Cifar-10数据集上,使用滑动平均模型能够将错误率下降11%,使用指数衰减能够将错误率下降7%。

相比滑动平均和指数衰减,正则化给模型带来的提高相对显著。下图对比了两个使用不一样损失函数的神经网络模型。实线给出了正确率的变化趋势,虚线给出了当前训练batch的交叉熵损失。

能够看到,只优化交叉熵的模型在训练数据上的交叉熵损失要比优化总损失的模型更小。然而在测试数据上,优化总损失的模型却要好于只优化交叉熵的模型。这个缘由就是过拟合问题。下图显示了不一样模型的损失函数的变化趋势。

左侧随着迭代的进行,正则化损失是在不断加大的,由于MNIST问题相对比较简单,迭代后期的梯度很小,因此正则化损失的增加也不快。若是问题更复杂,总损失会呈现出一个U字型。右侧的正则化损失也能够随着迭代的进行愈来愈小,从而使得总体的损失呈现一个逐步递减的趋势。

变量管理

在以前计算神经网络前向传播结果的过程抽象成了一个函数,经过这种方式在训练和测试的过程当中能够统一调用同一个函数来获得模型的前向传播结果。

def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):

这个函数的参数中包括了神经网络中的全部参数,当神经网络的结构更加复杂,参数更多时,就须要一个更好的方式来传递和管理神经网络中的参数了。tf提供了经过变量名称建立或者获取一个变量的机制,在不一样函数中能够直接经过变量名字来使用变量,而不须要将变量经过参数的形式处处传递。

第四章中介绍了经过tf.Variable函数来建立一个变量。除了tf.Variable,tf还提供了tf.get_variable来建立或者获取变量。tf.get_variable建立变量的功能和tf.Variable基本相同。

其中tf提供的initializer函数和第三章中介绍的随机数以及常量生成函数大部分是一一对应的。

tf.get_variable的变量名称参数是必须的,若是名称已存在,建立变量的程序会报错。

tf.get_variable也能够获取一个已经建立过的变量,须要经过tf.variable_scope生成一个上下文管理器。

当tf.variable_scope使用参数reuse=True时,这个上下文管理器内全部的tf.get_variable函数会直接获取已经建立的变量。若是变量不存在则报错。reuse=False或者None,tf.get_variable将建立新变量。tf.variable_scope是能够嵌套的。

tf.variable_scope也会建立一个tf中的命名空间,在其中建立的变量都会带上这个命名空间做为前缀。

将前面的函数改进一下:

当神经网络更加复杂时,使用这种变量管理方式将大大提升程序的可读性。

Tensorflow模型持久化

持久化即保存训练好的模型,并能够从文件中还原。

持久化代码实现

tf提供了一个很是简单的API保存和还原一个神经网络模型:tf.train.Saver类。

保存计算图:

tf模型通常会存在后缀为.ckpt的文件中,且目录下会出现三个文件,由于tf会将计算图的结构和图上参数取值分开保存。model.ckpt.meta保存计算图的结构,model.ckpt保存tf每个变量的取值,checkpoint保存目录下全部的模型文件列表。三个文件的具体内容在下小节介绍。

加载模型:

加载模型和保存模型的代码基本相同,惟一的不一样是加载模型的代码中没有运行变量的初始化过程,而是将变量的值经过已经保存的模型加载进来。若是不但愿重复定义图上的运算,也能够直接加载图。

也能够保存或加载部分变量,在声明tf.train.Saver类时能够提供一个列表来指定。好比saver=tf.train.Saver([v1]),只有v1被加载进来。另外还能够在保存或加载时给变量重命名:

tf经过字典将模型保存时的变量名和须要加载的变量联系起来。

这样作的主要目的之一是方便使用变量的滑动平均值。若是在加载模型时直接将影子变量映射到变量自身,那么在使用训练好的模型时就不须要再调用函数来获取变量的滑动平均值了。

经过重命名读取滑动平均值:

为了方便加载时重命名滑动平均变量,tf.train.ExponentialMovingAverage提供了variables_to_restore函数来生成重命名字典。

使用tf.train.Saver会保存运行tf程序所须要的所有信息,然而在测试或者预测时,只须要知道前向传播获得输出便可,不须要相似于变量初始化、模型保存等辅助节点的信息。tf提供了convert_variables_to_constants函数,将计算图中的变量及其取值经过常量的方式保存,这样整个计算图能够统一存放在同一个文件中。

张量的名称后面有:0,表示某个计算节点的第一个输出。而计算节点自己的名称后是没有:0的。

加载模型并直接获得结果:

持久化原理及数据格式

上小节介绍了当调用saver.save函数时,tf会生成3个文件。这小节将详细介绍这3个文件保存的内容和数据格式。

model.ckpt.meta
tf经过元图(MetaGraph)保存计算图中节点的信息以及元数据。元图是由MetaGraphDef Protocol Buffer定义的。上小节保存的文件就是model.ckpt.meta。

元图中主要记录了5类信息,.meta文件是二进制,没法直接查看。tf提供了export_meta_graph函数,以json格式导出MetaGraphDef Protocol Buffer。

  • meta_info_def
    meta_info_def是经过MetaInfoDef定义的,它记录了tf计算图中的元数据以及tf程序中全部使用到的运算方法的信息。

元数据包括计算图的版本号以及用户指定的一些标签(tags),没有特殊指定默认为空。只有stripped_op_list属性是不为空的。stripped_op_list记录了tf计算图上使用到的全部运算方法的信息。每一个方法只会出现一次。
- OpList
stripped_op_list的类型是OpList,OpList是一个OpDef类型的列表。

name定义了运算的名称,也是一个运算的惟一标识符。下面介绍的GraphDef将经过name来引用不一样的运算。input_arg和output_arg定义了输入和输出列表(repeated),attr给出了其余的运算参数信息。model.ckpt.meta.json文件中的一个Op为:

名称为Add的运算,有2个输入和1个输出,输入输出都指定了type_attr,值为T。在attr属性中,必须出现name为T的属性。这个样例中,这个属性制定了运算输入输出容许的参数类型(allowed_values)。

  • graph_def
    graph_def记录了tf计算图的节点信息,每个节点对应了一个运算。meta_info_def包含了运算的具体信息,graph_def关注运算的链接结构。graph_def是经过GraphDef Protocol Buffer定义的,主要包含了一个NodeDef类型的列表。

versions属性存储了tf版本号。NodeDef名称name是节点惟一标识符。tf程序中能够经过节点的名称来获取相应的节点。op属性是使用的运算方法的名称,经过这个名称在meta_info_def属性中找到该运算。
input是字符串列表,取值格式为node:src_output,当src_output为0时可省略。好比node:0也能够写做node。(node的第一个输出)
device指定了处理这个运算的设备。第10章具体介绍如何指定运行设备。device为空时,tf会自动选取一个合适的设备。attr指定了和当前运算相关的配置信息。model.ckpt.meta.json文件一些计算节点以下:

第一个节点是变量定义的运算,名称为v1,运算方法为Variable,attr属性指定了变量的维度和类型。
第二个节点是加法运算。输入为v1/read,v2/read。v1/read(:0)表明的节点能够读取变量v1的值。save/control_dependency是系统在完成模型持久化过程当中自动生成的一个运算。versions是tf版本号。

  • saver_def
    saver_def记录了持久化模型须要的一些参数,好比保存文件名、保存和加载操做的名称以及保存频率、清理历史记录等。saver_def类型为SaverDef。

model.ckpt.meta.json文件的内容:

filename_tensor_name给出了保存文件名的张量名称,就是节点save/Const的第一个输出。save_tensor_name是持久化tf模型的运算对应的节点名称,就是graph_def的save/control_dependency节点。加载tf模型的运算名称是restore_op_name。max_to_keep和keep_checkpoint_every_n_hours属性设定了tf.train.Saver类清理以前模型的策略。max_to_keep为5的时候,第6次调用saver.save时,第一次保存的模型就会被删除。keep_checkpoint_every_n_hours,每n小时能够在max_to_keep的基础上多保存一个模型。

  • collection_def
    tf计算图能够维护不一样集合,底层实现就是collection_def属性。collection_def是一个从集合名称到集合内容的映射,名称为字符串,内容为CollectionDef Protocol Buffer。

tf能够维护4类不一样的集合。NodeList维护节点集合,ByteList维护字符串或者序列化以后的Protocol Buffer集合,好比张量是Protocol Buffer表示,张量集合就是ByteList。Int64List维护整数集合,FloatList维护实数集合。model.ckpt.meta.json文件的内容:

样例程序中维护了两个集合,一个是全部变量的集合,一个是可训练变量的集合。元素都是v1和v2。他们都是系统自动维护的。

model.ckpt

model.ckpt保存了全部变量的取值。这个文件是经过SSTable格式存储的,能够大体理解为就是一个(key,value)列表。
model.ckpt列表的第一行描述了文件的元信息,好比这个文件中存储的变量列表。剩下的每一行保存了一个变量的片断,片断信息经过SavedSlice Protocol Buffer定义,保存了变量名称、当前片断信息以及变量取值。tf提供了tf.train.NewCheckpointReader类查看model.ckpt文件。

checkpoint

这个文件是tf.train.Saver自动生成且维护的。checkpoint文件维护了全部tf模型文件的文件名,当某个模型被删除时,也会从checkpoint文件中删除。checkpoint内容格式为CheckpointState Protocol Buffer。

model_checkpoint_path保存了最新的模型文件,all_model_checkpoint_paths列出了当前全部模型文件。
样例文件:

Tensorflow最佳实践样例程序

本章前面已经给出了一个完整的tf程序解决MNIST问题,然而这个程序的可扩展性并很差。好比,前向传播须要传入全部变量,可读性不好;没有持久化训练好的模型,且须要每隔一段时间保存一次模型训练的中间结果(防止程序退出致使结果丢失)。

结合前面介绍的变量管理和持久化机制,本节介绍一个tf训练神经网络模型的最佳实践。将训练和测试分为两个独立的程序,每个组件更加灵活。训练神经网络的程序能够持续输出训练好的模型,而测试程序能够每隔一段时间检验最新模型的正确率。将前向传播的过程抽离成一个单独的库函数,方便且保持训练和测试使用的前向传播方法是一致的。

重构以后的代码分为三个程序,mnist_inference.py定义了前向传播的过程以及神经网络中的参数。mnist_train.py定义了神经网络的训练过程。mnist_eval.py定义了测试过程。

mnist_inference.py

mnist_train.py

运行程序获得结果:

新的训练代码中,再也不将训练和测试跑在一块儿。训练过程当中,没1000轮输出一次损失函数的大小评估训练的效果。每1000轮保存一次训练好的模型,这样能够经过一个单独的测试程序,更加方便的在滑动平均模型上作测试。

mnist_eval.py

测试程序每10秒运行一次,每次运行都是读取最新保存的模型,并在验证集上计算正确率。运行程序获得结果以下,注意由于训练程序不必定每10秒输出一个新模型,因此有些模型可能被测试屡次。通常解决真实问题时,不会这么频繁地运行评测程序。

github code

第六章 图像识别与卷积神经网络

卷积神经网络(CNN)的应用很是普遍,在天然语言处理、医药发现、灾难气候发现甚至围棋人工智能程序中都有应用。本章主要经过卷积神经网络在图像识别上的应用讲解卷积神经网络的基本原理以及如何使用Tensorflow实现。内容包括图像识别领域解决的问题以及经典数据集、卷积神经网络的主体架构、tf实现两个经典的CNN模型以及实现CNN的迁移学习。

图像识别问题简介及经典数据集

图像识别问题但愿借助计算机程序处理、分析和理解图片中的内容,使得计算机能够从图片中自动识别不一样模式的目标和对象。第五章介绍的MNIST数据集就是识别图片中的手写体数字。下图显示了图像识别的主流技术在MNIST数据集上的错误率随着年份的发展趋势图。

其余经典数据集还有Cifar、ImageNet等。在这些数据集上,使用CNN的算法都超过了人类的表现,给图像识别问题带来了质的飞跃。

卷积神经网络简介

前面的章节介绍的神经网络每两层之间的全部节点都是有边相连的,称为全链接神经网络。卷积神经网络、循环神经网络等是非全链接神经网络。

全链接神经网络通常会将每一层的节点组织成一列,方便显示链接结构。卷积神经网络相邻两层之间只有部分节点相连,为了展现每一层神经元的维度,通常会将每一层卷积层的节点组织成一个三维矩阵。

卷积神经网络和全链接神经网络的惟一区别在于神经网络中相邻两层的链接方式,输入输出、损失函数和训练流程基本一致。

全链接神经网络没法很好地处理图像识别问题的主要缘由是参数太多,致使速度减慢,还很容易致使过拟合问题。而卷积神经网络很好的避免了这个问题。

卷积神经网络架构图:

一个卷积神经网络主要由如下5中结构组成:

  1. 输入层
    在处理图像的CNN中,它通常表明了一张图片的像素矩阵,好比32323。从输入层开始,CNN经过不一样的神经网络结构将上一层的三维矩阵转化为下一层的三维矩阵,直到最后的全链接层。
  2. 卷积层
    卷积层中每个节点的输入只是上一层神经网络的一小块,这小块经常使用的大小是33,55等。CNN将每一小块进行深刻地分析从而获得抽象程度更高的特征。通常来讲,经过卷积层处理过的节点矩阵会变得更深。
  3. 池化层
    池化层不会改变三维矩阵的额深度,可是能够缩小矩阵的大小。能够认为是将一张高分辨率图片转化为分辨率较低的图片。能够进一步减小参数的数量。
  4. 全链接层
    通过几轮卷积层和池化层的处理以后,能够认为图像中的信息已经被抽象成了信息含量更高的特征。在特征提取以后,仍然须要全链接层来完成分类任务。
  5. Softmax层
    和第四章介绍的做用相同,用于获得不一样种类的几率分布。

下面将详细介绍卷积神经网络特殊的两个结构:卷积层和池化层。

卷积神经网络经常使用结构

卷积层

下图是卷积层神经网络结构中最重要的部分,称为过滤器(filter)或者核(kernel)。过滤器能够将当前层神经网络上的一个子节点矩阵转化为下一层神经网络上的一个单位节点矩阵。单位节点矩阵指的是一个长和宽都是1,深度不限的节点矩阵。

过滤器处理的节点矩阵的尺寸也称之为过滤器的尺寸,其长和宽是人工指定的。经常使用的有33和55。由于过滤器处理的矩阵深度和当前神经网络节点矩阵的深度一致,因此虽然节点矩阵是三维的,过滤器的尺寸只须要指定长和宽。过滤器中还要指定的一个维度是单位节点矩阵的深度,也称为过滤器的深度(或者过滤器的个数)。

下面展现过滤器的前向传播过程,以一个223的节点矩阵变化为一个115的单位节点矩阵为例。总共须要223*5+5=65个参数,+5为偏置项的个数。单位矩阵中第i个节点的取值g(i)为:

其中,g(0)的计算过程:

卷积层结构的前向传播过程就是经过一个过滤器从神经网络当前层的左上角移动到右下角,而且在移动中计算每个对应的单位矩阵获得的。

本笔记没有完整记录过滤器的前向传播过程,具体参考原书或其余资料。几个概念:same padding,valid padding,步长(step)。

在卷积神经网络中,每个卷积层中使用的过滤器中的参数都是同样的。共享过滤器的参数可使得图像上的内容不受位置的影响。同时大幅度减小参数数量。加入输入层维度为32×32×3,第一层卷积层使用55316的过滤器,那么参数个数为55316+16=1216个。而使用500个隐藏节点的全链接层有1.5百万个参数。另外,卷积层的参数个数和图片的大小无关,它只和过滤器的尺寸、深度以及当前层节点矩阵的深度有关,使得CNN能够很好的扩展到更大的图像数据上。

使用same padding、步长为2的卷积层前向传播流程:

左上角的格子计算过程为:

tf对CNN提供了很是好的支持,下面的程序实现了一个卷积层的前向传播过程。

池化层

和卷积层相似,池化层前向传播的过程也是经过移动一个相似过滤器的结构完成的。过滤器中的计算不是节点的加权和,而是最大值或者平均值运算,分别称为 max pooling和average pooling。同时,池化层的过滤器也须要人工设定过滤器的尺寸、padding方式以及步长等,且这些设置的意义相同。和卷积层移动方式不一样的是,池化层的过滤器只影响一个深度上的节点,因此除了在长和宽方向上移动,还须要在深度这个方向上移动。如图所示:

tf实现:

其中ksize(过滤器尺寸)和strides(步长)的数组的第一个和最后一个元素必须为1,由于不能影响节点矩阵的深度。过滤器尺寸经常使用的有[1,2,2,1]和[1,3,3,1]。tf的另外一个池化方式tf.nn.avg_pool使用方式是一致的。

经典卷积网络模型

经过上小节介绍的卷积层和池化层能够组合任意结构的CNN。但怎样结构的CNN才更好的解决真实图像问题呢?本小节介绍一些经典的CNN网络结构:LeNet-5和Inception。

LeNet-5模型

LeNet-5是Yann LeCun提出的,是第一个成功应用于数字识别问题的卷积神经网络。总共有7层:

subsampling即pooling,池化

  • 第一层:卷积层
    输入是原始的图像像素,输入层大小32321。第一个卷积层过滤器尺寸为55,深度6。输出2828*6的feature map。
  • 第二层:池化层
    过滤器大小为2*2,输出为14×14×6。
  • 第三层:卷积层
    过滤器大小55,深度16,输出1010*16
  • 第四层:池化层
    过滤器大小22,输出55*16
  • 第五层:全链接层
    输出节点120
  • 第六层:全链接层
    输出节点84
  • 第七层:全链接层
    输出节点10

tf实现LeNet-5解决MNIST问题:训练过程能够复用第五章中的mnist_train.py,不过由于CNN的输入层是一个三维矩阵,因此须要调整一下输入数据的格式:

修改mnist_inference.py实现LeNet-5模型的前向传播过程:

几个要点:

  1. 训练过程当中加入dropout避免过拟合,dropout通常只在全链接层而不是卷积层或者池化层中使用
  2. 只有全链接层的权重须要加入正则化

运行mnist_train.py,获得输出:

相比第五章98.4%的准确率,使用CNN能够达到99.4%。

一种CNN架构不能解决全部问题,好比LeNet-5就没法很好地处理相似ImageNet这样比较大的图像数据集。那么如何设计CNN的架构呢?能够用下面的正则表达式公式总结经典的图片分类问题的CNN架构:

输入层 ->( 卷积层+ -> 池化层? )+ -> 全链接层+

“卷积层+”表示一层或多层卷积层,通常连续使用最多3层。“池化层?”表示没有或者一层池化层,部分论文中发现能够直接经过调整卷积层步长起到和池化层相同的效果。CNN输出以前通常会通过1~2个全链接层。按照这个公式,LeNet-5的架构为:

输入层->卷积层->池化层->卷积层->池化层->全链接层->全链接层->输出层

除此以外,AlexNet、ZF Net、VGGNet等都知足上面的公式,其中VGG论文中介绍的做者尝试过的不一样CNN架构以下:

convX-Y表示过滤器边长为X,深度为Y。VGG中过滤器边长通常为3和1,每通过一次池化层以后,过滤器深度都会乘2。卷积层的步长通常为1,最大池化比较经常使用,池化层的过滤器边长通常为2或者3,步长通常为2或3.

Inception-v3模型

本小节介绍Inception结构以及Inception-v3卷积神经网络模型。在LeNet-5中,不一样卷积层经过串联的方式链接在一块儿,而Inception结构将不一样卷积层并联。

前面提到了一个卷积层可使用边长为一、3或5的过滤器,如何在这些边长中选呢。Inception同时使用全部不一样尺寸的过滤器,而后将获得的输出矩阵拼接起来。

上图Inception使用了1/3/5三种过滤器,不一样矩阵表明不一样计算路径。全部过滤器使用Same Padding且步长为1,获得的输出矩阵大小一致,因此能够拼接成更深的矩阵。Inception-v3架构以下:

详细架构参考论文
Rethinking the Inception Architecture for Computer Vision

Inception-v3总共有46层,由11个Inception模块组成,上图中方框标注的就是一个Inception模块。

Tensorflow-Slim是一个简洁的实现卷积层的工具,相比直接使用tf,代码量减小不少:

下面用tf-slim实现方框中的Inception模块:

卷积神经网络迁移学习

介绍迁移学习的概念以及如何经过tf实现。

迁移学习介绍

迁移学习是将一个问题上训练好的模型经过简单的调整使其适用于一个新的问题。根据论文DeCAF: A Deep Convolutional Activation Feature for Generic Visual Recognition
中的结论,能够保留训练好的Inception-v3模型中全部卷积层的参数,只是替换最后一层全链接层。最后这一层全链接层以前的网络层称之为瓶颈层(bottleneck)。有理由认为瓶颈层输出的节点向量能够被做为任何图像的一个更加精简且表达能力更强的特征向量。因而,在新数据集上,能够直接利用这个训练好的模型进行特征提取,再将这个特征向量做为输入来训练一个新的单层全链接神经网络处理新的分类问题。
通常来讲,数据量足够的状况下,迁移学习的效果不如彻底从新训练。可是迁移学习所须要的训练时间和训练样本都远远小于完整训练的模型。

Tensorflow实现迁移学习

首先下载数据集

解压以后的文件夹包含5个子文件夹,每一个子文件夹的名称为一种花的名称。

下载Google提供的Inception-v3模型

实现代码:

运行以后的结果:

效果还不错。