2.1 TensorFlow的主要依赖包
TensorFlow依赖的两个最主要的工具包——Protocol Buffer和Bazel。
2.1.1 Protocol Buffer
Protocol Buffer是谷歌开发的处理结构化数据的工具。结构化数据指的是拥有多种属性的数据,比如:
当要将这些结构化的用户信息持久化或者进行网络传输时,就需要先将它们序列化。所谓序列化,是将结构化的数据变成数据流的格式,简单地说就是变为一个字符串。如何将结构化的数据序列化,并从序列化之后的数据流中还原出原来的结构化数据,统称为处理结构化数据,这就是Protocol Buffer解决的主要问题。
Protocol Buffer 序列化之后得到的数据不是可读的字符串,而是二进制流。
使用Protocol Buffer时需要先定义数据的格式(schema)。还原一个序列化之后的数据将需要使用到这个定义好的数据格式。
Protocol Buffer定义数据格式的文件一把一般保存在.proto文件中。每一个message代表了一类结构化的数据,比如这里的用户信息。
Protocol Buffer是TensorFlow系统中使用到的重要数据,TensorFlow中的数据基本都是通过Protocol Buffer来组织的。
分布式TensorFlow的通信协议gRPC也是以Protocol Buffer作为基础的。
2.1.2 Bazel
Bazel是从谷歌开源的自动化构建工具,谷歌内部绝大部分的应用都是通过它来编译的。
项目空间(workspace)是Bazel的一个基本概念。一个项目空间可以简单地理解为一个文件夹,在这个文件夹中包含了编译一个软件所需要的源代码以及输出编译结果的软连接(symbolic link)地址。
一个项目空间所对应的文件夹是这个项目的根目录,在这个根目录中需要有一个WORKSPACE文件,此文件定义了对外部资源的依赖关系。空文件也是一个合法的WORKSPACE文件。
2.3 TensorFlow测试样例
TensorFlow支持C、C++、Python三种语言,但是它对Python的支持是最全面的。
第三章 TensorFlow入门
本章将详细介绍TensorFlow的计算模型、数据模型和运行模型。通过这三个角度对TensorFlow的介绍,读者可以对TensorFlow的工作原理有一个大致的了解。在本章的最后一节中,将简单介绍神经网络的主要计算流程,并介绍如何通过TensorFlow实现这些计算。
3.1 TensorFlow计算模型——计算图
计算图是TensorFlow中最基本的一个概念,TensorFlow中所有的计算都会被转化为计算图上的节点。
3.1.1 计算图的概念
TensorFlow的名字中已经说明了它最重要的两个概念——Tensor和Flow。
Tensor就是张量。张量这个概念在数学或者物理中可以有不同的解释。在TensorFlow中,张量可以被简单地理解为多维数组。
如果说TensorFlow的第一个单词Tensor表明了它的数据结构,那么Flow则体现了它的计算模型。
Flow翻译成中文就是“流”,它直观表达了张量之间通过计算相互转化的过程。
TensorFlow是一个通过计算图的形式来表述计算的编程系统。TensorFlow中的每一个计算都是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系。
图3-1展示了TensorBoard(TensorBoard是TensorFlow的可视化工具,第九章将详细介绍之。)画出来的第2章中两个向量相加样例的计算图。
图3-1中的每一个节点都是一个运算,而每条边代表了计算之间的依赖关系。
所有TensorFlow的程序都可以通过类似图3-1所示的计算图的形式来表示,这就是TensorFlow的基本计算模型。
3.1.2 计算图的使用
TensorFlow程序一般分为两个阶段。在第一个阶段需要定义计算图中所有的计算;第二阶段为执行计算。
在TensorFlow程序中,系统会自动维护一个默认的计算图,通过tf.get default graph函数可以获取当前默认的计算图,以下代码示意了如何获取默认计算图以及如何查看一个运算所属的计算图。
除了使用默认的计算图,TensorFlow支持通过tf.Graph函数来生成新的计算图。不同计算图上的张量运算都不会共享。
有效地整理TensorFlow程序中的资源也是计算图的一个重要功能。在一个计算图中,可以通过集合(collection)来管理不同类别的资源。比如通过tf.add_to_collection函数将资源加入一个或多个集合中。
3.2 TensorFlow数据模型——张量
张量是 TensorFlow管理数据的形式。
接下来先介绍介绍张量的一些基本属性,然后介绍如何通过张量来保存和获取TensorFlow计算的结果。
3.2.1 张量的概念
张量是一个很重要的概念。在TensorFlow程序中,所有的数据都通过张量的形式来表示。
从功能的角度上看,张量可以被简单理解为多维数组(张量的类型也可以是字符串,不过多讨论)。其中零阶张量表示标量(scalar),也就是一个数;第一阶张量为向量(vector),也就是一个一维数组;第n阶张量可以理解为一个n维数组。但张量在TensorFlow中的实现并不是直接采用数组的形式,它只是对TensorFlow中运算结果的引用。在张量中并没有真正保存数字,它保存的是如何得到这些数字的计算过程。
以向量加法为例,当运行如下代码时,并不会得到加法的结果,而会得到对结果的一个引用。
可以看出TensorFlow中的张量和NumPy中的数组不同,TensorFlow计算的结果不是一个数字,而是一个张量的结构。从上面代码的运行结果可以看出,一个张量主要保存了3个属性:名字(name)、维度(shape)、类型(type)。
在3.2.1小节中介绍了TensorFlow的计算都可以通过计算图的模型来建立,而计算图上的每一个节点代表了一个计算,计算的结果就保存在张量之中。所以张量和计算图上的计算结果是对应的。这样张量的命名就可以通过“node.src_output”的形式来给出。其中node为节点的名称,src_output表示当前张量来自节点的第几个输出。比如上面代码打出来的“add:0”就说明了这个张量是计算节点“add”输出的第一个结果(编号从0开始)。
维度是张量一个很重要的属性,上例中shape=(2,)说明了张量result是一个一维数组,这个数组的长度为2。
每一个张量会有一个唯一的类型,TensorFlow会对所有参与运算的张量进行类型的检查。如果不指定类型,TensorFlow会给出默认的类型,比如不带小数点的数会被默认为int32,带小数点的会默认为float32。一般建议通过dtype来明确指出变量或者常量的类型。
TensorFlow支持14种不同的类型,主要包括了实数(tf.float32 tf.float64)、整数(tf.int8 tf.int16 tf.int32 tf.int64 tf.int128)、布尔型(tf.bool)、和复数(tf.complex64 tf.complex128)。
3.2.2 张量的使用
张量使用主要可以总结为两大类。
第一类用途是对中间计算结果的引用。通过张量来引用计算的中间结果可以使代码的可阅读性大大提升。同时通过张量来存储中间结果,这样可以方便获取中间结果。比如在卷积神经网络中,卷积层或者池化层有可能改变张量的维度,通过result.get_shape函数来获取结果张量的维度信息可以免去人工计算的麻烦.
第二类用途是当计算图构造完成后,张量可以用来获得计算结果,也就是得到真实的数字。虽然张量本身没有存储具体的数字,但是通过下面3.3小节中介绍的会话(session),就可以得到这些具体的数字。
3.3 TensorFlow运行模型——会话
本节介绍如何使用TensorFlow中的会话(session)来执行定义好的运算。
会话拥有并管理TensorFlow程序运行时的所有资源。当所有计算完成之后需要关闭会话来帮助系统回收资源,否则就可能出现资源泄露的问题。
TensorFlow中使用会话的模式一般有两种,第一种模式需要明确调用会话生成函数和关闭会话函数。使用这种模式时,在所有计算完成之后,需要明确调用Session.close函数来关闭会话并释放资源。然而当程序因为异常退出时,关闭会话的函数可能就不会被执行从而导致资源泄露。
为了解决异常退出时资源释放的问题,TensorFlow可以通过Python的上下文管理器来使用会话。
如3.1节,TensorFlow会自动生成一个默认的计算图,但不会自动生成默认的会话,而是需要手动指定。
当默认的会话被指定之后,可以通过tf.Tensor.eval函数来计算一个张量的取值。
TensorFlow提供了一种在交互式环境下直接构建默认会话的函数。这个函数就是tf.InteractiveSession。
还有ConfigProto Protocol Buffer;allow_soft_placement、log_device_placement这些布尔型参数,不再赘述,用时查阅。
(重要的技巧是懂得查看日志)
3.4 TensorFlow实现神经网络
这一节中,将结合神经网络的功能进一步介绍如何通过TensorFlow来实现神经网络。
3.4.1 TensorFlow游乐场及神经网络简介
TensorFlow游乐场(http://playground.tensorflow.org)是一个通过网页浏览器就可以训练的简单神经网络并实现了可视化训练过程的工具。如下图:
TensorFlow游乐场的左侧提供了4个不同的数据集来测试神经网络。默认的数据为左上角被框出来的那个。
(在机器学习中,所有用于描述实体的数字的组合就是一个实体的特征向量(feature vector))
第一章中介绍过,特征向量的提取对机器学习的效果至关重要。通过特征提取,就可以将实际问题中的实体转化为空间中的点。(在真实问题中,一般会从实体中抽取更多的特征,所以一个实体会被表示为高维空间中的点)
TensorFlow游乐场中FEATURES一栏对应了特征向量。特征向量是神经网络的输入。
下图给出了迭代训练100轮之后的情况:
先介绍一下如何解读TensorFlow游乐场的训练结果。
上图中,一个小格子代表神经网络中的一个节点,而边代表节点之间的连接。
每一个节点和边都被涂上了或深或浅的颜色,但边上的颜色和格子中的颜色含义有略微的区别。
每一条边代表了神经网络中的一个参数,它可以是任意实数。神经网络就是通过对参数的合理设置来解决分类或者回归问题的。当边的颜色越深时,这个参数取值的绝对值越大(黄色越深,表示负得越大;蓝色越深,表示正得越大);当边的颜色接近白色时,这个参数的取值接近于0。
每一个节点的颜色代表了这个节点的区分平面。和边类似,当节点的输出值的绝对值越大时,颜色越深(黄色越深,表示负得越大;蓝色越深,表示正得越大)。
综上所述,使用神经网络解决分类问题主要可以分为以下4个步骤:
1、提取问题中实体的特征向量作为神经网络的输入。不同的实体可以提取不同的特征向量。
2、定义神经网络的结构,并定义如何从神经网络的输入得到输出。这个过程就是神经网络的前向传播算法(3.4.2节将详述)。
3、通过训练数据来调整神经网络中参数的取值,这就是训练神经网络的过程(3.4.3)。
4、使用训练好的神经网络来预测未知的数据。这个过程和步骤2中的前向传播算法一致。
3.4.2 前向传播算法简介
不同的神经网络结构,前向传播的方式也不一样,本小节将介绍最简单的全连接网络结构的前向传播算法,并将展示如何通过TensorFlow实现这个算法。
神经元是构成一个神经网络的最小单元。神经网络中的神经元也可以称之为节点。
给定神经网络的输入,神经网络的结构以及边上权重,就可以通过前向传播算法来计算出神经网络的输出。
3.4.3 神经网络参数与TensorFlow变量
神经网络中的参数是神经网络实现分类或者回归问题中重要的部分。
本小节将更加具体地介绍TensorFlow是如何组织、保存以及使用神经网络中的参数的。
在TensorFlow中,变量(tf.Variable)的作用就是保存和更新神经网络中的参数。
和其他编程语言类似,TensorFlow中的变量也需要指定初始值。
下面代码给出了一种在TensorFlow中声明一个2*3矩阵变量的方法:
weights=tf.Variable(tf.random_normal([2,3],stddev=2))
这段代码调用了TensorFlow中变量的声明函数tf.Variable。TensorFlow中变量的初始值可以设置成随机数、常数或者是通过其他变量的初始值计算得到。在上面的样例中,tf.random_normal([2,3],stddev=2)会产生一个2*3的矩阵,矩阵中的元素是均值为0,标准差为2的随机数。tf.random_normal函数可以通过参数指定平均值,在没有指定时,默认为0。
通过满足正态分布的随机数来初始化神经网络中的参数是一个非常常用的方法。除了正态分布的随机数,TensorFlow还提供了一些其他的随机数生成器,图3-2列出了TensorFlow目前支持的所有随机数生成器。
TensorFlow也支持通过常数来初始化一个变量。图3-3给出了TensorFlow中常用的常量声明方法。
在神经网络中,偏置项(bias)通常会使用常数来设置初始值。以下代码给出一个样例:
biases=tf.Variable(tf.zeros([3]))
这段代码将会生成一个初始值为0且长度为3的变量。
在TensorFlow中,一个变量的值在被调用之前,这个变量的初始化过程需要被明确的调用。
以下样例介绍了如何通过变量实现神经网络的参数并实现前向传播的过程。
init_op=tf.initialize_all_variables()
sess.run(init_op)
通过tf.initialize_all_variables函数,就不需要将变量一个一个初始化了。这个函数也会自动处理变量之间的依赖关系。
在3.2节中介绍过TensorFlow的核心概念是张量(Tensor),所有的数据都是通过张量的形式来组织的,那么本小节介绍的变量和张量是什么关系呢?
在TensorFlow中,变量的声明函数tf.Variable是一个运算。这个运算的输出结果就是一个张量,这个张量也就是本节中介绍的变量。
所以,变量只是一种特殊的张量。
3.1.2小节介绍了TensorFlow中集合(collection)的概念,所有的变量都会被自动的加入GraphKeys.VARIABLES这个集合。通过tf.all_variables函数可以拿到当前计算图上所有的变量。拿到所有的变量有助于持久化整个计算图的运行状态。
当构建机器学习模型时,比如神经网络,可以通过变量声明函数中的trainable参数来区分需要优化 的参数(比如神经网络中的参数)和其他参数(比如迭代的轮数)。
如果声明变量时,trainable为True,那么这个变量将会被加入GraphKeys.TRAINABLE_VARIABLES集合。
在TensorFlow中可以通过tf.trainable.variables函数得到所有需要优化的参数。
TensorFlow中提供的神经网络优化算法会将GraphKeys.TRAINABLE_VARIABLES集合中的变量作为默认的优化对象。
类似张量,维度(shape)和类型(type)也是变量最重要的两个属性。
和大部分程序语言类似,变量的类型是不可改变的。一个变量在构建之后,它的类型就不能再改变了。
而维度在程序运行中是有可能改变的,但是需要通过设置参数validate_shape=False。这种用法在实践中比较罕见。下面给出了一段示范代码:
3.4.4 通过TensorFlow训练神经网络模型
这一小节将简单介绍使用监督学习的方式来更合理地设置参数取值,同时也将给出TensorFlow程序来完成这个过程。设置神经网络参数的过程就是神经网络的训练过程。只有经过有效训练的神经网络模型才可以真正的解决分类或者回归问题。
使用监督学习的方式设置神经网络参数需要有一个标注好的训练数据集。
监督学习最重要的思想就是,在已知答案的标注数据集上,模型给出的预测结果要尽量接近真实的答案。
通过调整神经网络中的参数对训练数据进行拟合,可以使得模型对未知的样本提供预测的能力。
TensorFlow游乐场有两种颜色,一种黄色(文中浅色部分),一种蓝色(文中深色部分)。任意一种颜色越深,都代表判断的信息越大。
在神经网络优化算法中,最常用的方法是反向传播算法(backpropagation)。
反向传播算法实现了一个迭代的过程。
在每次迭代开始前,首先需要选取一小部分训练数据,这一小部分数据叫做一个batch。
然后这个batch的样例会通过前向传播算法得到神经网络模型的预测结果。因为训练数据都是有正确答案标注的,所以可以计算出当前神经网络模型的预测答案与正确答案之间的差距。
最后,基于这预测值和真实值之间的差距,反向传播算法会相应更新神经网络参数的取值,使得在这个batch上神经网络模型的预测结果和真实答案更加接近。
通过TensorFlow实现反向传播算法的第一步是使用TensorFlow表达一个batch的数据。
一般来说,一个神经网络的训练过程需要经过几百万轮甚至几亿轮的迭代。
TensorFlow提供了placeholder机制用于提供输入数据。placeholder相当于定义了一个位置,这个位置中的数据在程序运行时再指定。
在placeholder定义时,这个位置上的数据类型是需要指定的。和其他张量一样,placeholder的类型也是不可以改变的。下面给出了通过placeholder实现前向传播算法的代码。
在得到一个batch的前向传播结果之后,需要定义一个损失函数来刻画当前的预测值和真实答案之间的差距。然后通过反向传播算法来调整神经网络参数的取值使得差距可以被缩小。
以下代码定义了一个简单的损失函数,并通过TensorFlow定义了反向传播的算法。
3.4.5 完整神经网络样例程序
下面给出一个完整的程序来训练神经网络解决二分类问题。
import tensorflow as tf #Numpy是一个科学计算的工具包。这里通过Numpy工具包生成模拟数据集。
from numpy.random import RandomState #定义训练数据batch的大小
batch_zize= #定义神经网络的参数,这里还是沿用3..2小节中给出的神经网络结构。
w1=tf.Variable(tf.random_normal([,],stddev=,seed=))
w2=tf.Variable(tf.random_normal([,],stddev=,seed=)) #在shape的一个维度上使用None可以方便使用不大的batch大小。在训练时需要把数据
#分成比较小的batch,但是在测试时,可以一次性使用全部的数据。当数据集比较小时,这#样比较方便测试,但数据集比较大时,将大量数据放入一个batch可能会导致内存溢出。
x=tf.placeholder(tf.float32,shape=(None,),name='x-input')
y_=tf.placeholder(tf.float32,shape=(None,),name='y-input') #定义神经网络前向传播的过程
a=tf.matmul(x,w1)
y=tf.matmul(a,w2) #定义损失函数和反向传播算法
cross_entropy=-tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-,1.0)))
train_step=tf.train.AdamOptimizer(0.001).minimize(cross_entropy) #通过随机数生成一个模拟数据集。
rdm=RandomState()
dataset_size=
X=rdm.rand(dataset_size,) #定义规则来给出样本的标签。在这里所有x1+x2<1的样例都被认为是正样本(比如零件合#格),而其他为负样本(比如零件不合格)。和TensorFlow游乐场中的表示法不大一样的#地方是在这里使用0来表示负样本,1来表示正样本。大部分解决分类问题的神经网络都会#采用0和1的表示方法。
Y=[[int(x1+x2<)] for (x1,x2) in X] #创建一个会话来运行TensorFlow程序。
with tf.Session() as sess:
init_op=tf.initialize_all_variables()
#初始化变量
sess.run(init_op)
print sess.run(w1)
print sess.run(w2)
'''
在训练之前神经网络参数的值:
w1=[[-0.81131822,1.48459876,0.06532937]
[-2.44270396,0.0992484,0.59122431]]
w2=[[-0.81131822] [1.48459876] [0.06532937]]
''' #设定训练的轮数
STEPS=
for i in range(STEPS):
#每次选取batch_size个样本进行训练。
start=(i*batch_size)%dataset_size
end=min(start+batch_size,dataset_size) #通过选取的样本训练神经网络并更新参数
sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})
if i%==:
#每隔一段时间计算在所有数据上的交叉熵并输出。
total_cross_entropy=sess.run(cross_entropy,feed_dict{x:X,y_:Y})
print("After %d training step(s),cross entropy on all data is %g" %
(i,total_cross_entropy))
'''
输出结果:
After training step(s),cross entropy on all data is 0.0674925
After training step(s),cross entropy on all data is 0.0163385
After training step(s),cross entropy on all data is 0.00907547
After training step(s),cross entropy on all data is 0.00714436
After training step(s),cross entropy on all data is 0.00578471 通过这个结果可以发现随着训练的进行,交叉熵是逐渐变小的。交叉熵越小说明
预测的结果和真实的结果差距越小。
''' print sess.run(w1)
print sess.run(w2)
'''
在训练之后神经网络参数的值:
w1=[[-1.9618274,2.58235407,1.68203783]
[-3.4681716,1.06982327,2.11788988]]
w2=[[-1.8247149] [2.68546653] [1.41819501]] 可以发现这两个参数的取值已经发生了变化,这个变化就是训练的结果。
它使得这个神经网络能更好的拟合提供的训练数据。
'''
上面的程序实现了训练神经网络的全部过程。从这段程序程序可以总结出训练神经网络的过程可以分为以下3个步骤:
1.定义神经网络的结构和前向传播的输出结果。
2.定义损失函数以及选择反向传播优化的算法。
3.生成会话(tf.session)并且在训练数据上反复运行反向传播算法。
无论神经网络的结构如何变化,这3个步骤是不变的。
第四章 深层神经网络
首先,介绍深度学习与深层神经网络的概念,并给出一个实际的样例来说明深层神经网络可以解决部分浅层神经网络解决不了的问题;
然后,介绍如何设定神经网络的优化目标。这个优化目标就是损失函数;
接着,将更加详细地介绍神经网络的反向传播算法,并给出一个TensorFlow框架来实现反向传播的过程;
最后,将介绍在神经网络优化中经常遇到的几个问题,并且给出解决这些问题的具体方法。
4.1 深度学习与深层神经网络
*对深度学习的精确定义为“一类通过多层非线性变换对高复杂性数据建模算法的集合”。
因为深层神经网络是实现“多层非线性变换”最常用的一种方法,所以在实际中基本上可以认为深度学习就是深层神经网络的代名词。
从*给出的定义可以看书,深度学习有两个非常重要的特性——多层和非线性。
4.1.1 线性模型的局限性
在线性模型中,模型的输出为输入的加权和。
假设一个模型的输出y输入xi满足以下关系,那么这个模型就是一个线性模型。
在线性可分问题中,线性模型就能很好区分不同颜色的点。因为线性模型就能解决线性可分问题,所以在深度学习的定义中特意强调它的目的为解决更加复杂的问题。所谓复杂问题,至少是无法通过直线(或者高维空间的平面)划分的。在现实世界中,绝大部分的问题都是无法线性分割的。
4.1.2 激活函数实现去线性化
在这一小节中,将详细介绍激活函数是如何工作的。
在3.4.2小节中介绍的神经元结构的输出为所有输入的加权和,这导致整个神经网络是一个线性模型,如果将每一个神经元(也就是神经网络中的节点)的输出通过一个非线性函数,那么整个神经网络的模型也就不再是线性的了。这个非线性函数就是激活函数!!!
图4-5显示了加入激活函数和偏置项之后的神经元结构。
下面公式给出了3.4.2小节中神经网络结构加上激活函数和偏置项之后的前向传播算法的数学定义:
相比3.4.2小节中的定义,上面的定义主要有两个改变。第一个改变是新的公式中增加了偏置项(bias),偏置项是神经网络中非常常用的一种结构。第二个改变就是每个节点的取值不再是单纯的加权和。每个节点的输出在加权和的基础上还做了一个非线性变换。
图4-6显示了几种常用的非线性激活函数的函数图像。
4.1.3 多层网络解决异或运算
上面的两个小节详细讲解了线性变换的问题。在这一小节中,将通过一个实际问题讲解深度学习的另外一个重要性质——多层变换。
在神经网络的发展史上,一个很重要的问题就是异或问题。
感知机可以简单地理解为单层的神经网络,图4-5中给出的神经元结构就是感知机的网络结构。
图4-8
异或运算直观来说就是如果两个输入的符号相同时(同时为正或者同时为负)则输出为0,否则(一个正一个负)输出为1。
4.2 损失函数的定义
本节将具体介绍如何刻画不同神经网络的效果。
神经网络模型的效果以及优化的目标是通过损失函数(loss function)来定义的。
在4.2.1小节中,将讲解适用于分类问题和回归问题的经典损失函数,并通过TensorFlow实现这些损失函数。然后在4.2.2小节中,将介绍如何根据具体问题定义损失函数,并通过具体样例来说明不同损失函数对训练结果的影响。
4.2.1 经典损失函数
通过神经网络解决多分类问题最常用的方法是设置n个输出节点,其中n为类别的个数。对于每一个样例,神经网络可以得到一个n维数组作为输出结果。数组中的每一个维度(也就是每一个输出节点)对应一个类别。
在理想情况下,如果一个样本属于类别k,那么这个类别所对应的输出节点的输出值应该为1,而其他节点的输出都为0。
在4.3节中将要介绍的手写体数字识别问题可以被归纳成一个十分类问题,以识别数字1为例,神经网络模型的输出结果越接近[0,1,0,0,0,0,0,0,0,0]越好。
那么判断一个输出向量和期望的向量有多接近呢?交叉熵(cross entropy)是常用的评判方法之一。交叉熵刻画了两个概率分布之间的距离,它是分类问题中使用比较广的一种损失函数。
交叉熵是一个信息论中的概念,它原本是用来估算平均编码长度的。这里不过多讨论它原本的意义。给定两个概率分布p和q,通过q来表示p的交叉熵为:
注意交叉熵刻画的是两个概率分布之间的距离,然而神经网络的输出却不一定是一个概率分布。
概率分布刻画了不同事件发生的概率。当事件总是有限的情况下,概率分布p(X=x)满足:
也就是说,任意事件发生的概率都在0和1之间,且总有某一个事件发生(概率的和为1)。
如果将分类问题中“一个样例属于某一个类别”看成一个概率事件,那么训练数据的正确答案就符合一个概率分布。因为事件“一个样例属于不正确的类别”的概率为0,而“一个样例属于正确的类别”的概率为1。
如何将神经网络前向传播得到的结果也变成概率分布呢?Softmax回归就是一个非常常用的方法。
Softmax回归本身可以作为一个学习算法来优化分类结果,但在TensorFlow中,Softmax回归的参数被去掉了,它只是一层额外的处理层,将神经网络的输出变成一个概率分布。图4-10展示了加上Softmax回归的神经网络结构图。
从以上公式可以看出,原始神经网络的输出被用作置信度来生成新的输出,而新的输出满足概率分布的所有要求。
这个新的输出可以理解为经过神经网络的推导,一个样例为不同类别的概率分别是多大。这样就把神经网络的输出也变成了一个概率分布,从而可以通过交叉熵来计算预测的概率分布和真实答案的概率分布之间的距离了。
从交叉熵的公式中可以看到交叉熵函数是不对称的(H(p,q)不等于H(q,p)),它(即交叉熵)刻画的是通过概率分布q来表达概率分布p的困难程度。
因为正确答案是希望得到的结果,所以当交叉熵作为神经网络的损失函数时,p代表的是正确答案,q代表的是预测值。交叉熵刻画的是两个概率分布的距离,也就是说交叉熵值越小,两个概率分布越接近。
下面将给出两个具体样例来直观说明通过交叉熵可以判断预测答案和真实答案之间的距离。假设有一个三分类问题,某个样例的正确答案是(1,0,0)。某模型经过Softmax回归之后之后的预测答案是(0.5,0.4,0.1),那么这个预测值和正确答案之间的交叉熵是:
如果另外一个模型的预测值是(0.8,0.1,0.1),那么这个预测值和真实值之间的交叉熵是:
从直观上可以很容易知道第二个预测答案要优于第一个。通过交叉熵计算得到的结果也是一致的(第二个交叉熵的值更小)。
在3.4.5小节中,已经通过TensorFlow实现过交叉熵,其代码实现如下:
cross_entropy=-tf.reduce_mean(
y_*tf.log(tf.clip_by_value(y,1e-10,1.0)))
其中y_代表正确结果,y代表预测结果。本小节将更加具体地讲解这个计算过程。
这一行代码包含了四个不同的TensorFlow运算。通过tf.clip_by_value函数可以将一个张量中的数值限制在一个范围之内,这样可以避免一些运算错误(比如log0是无效的)。下面给出了使用tf.clip_by_value的简单样例。
与分类问题不同,回归问题解决的是对具体数值的预测、销售预测等都是回归问题。这些问题需要预测的不是一个事先定义好的类别,而是一个任意函数,解决回归问题的神经网络一般只有一个输出节点,这个节点的输出值就是预测值。对于回归问题,最常用的损失函数是均方误差(MSE,mean squared error)(均方误差也是分类问题中常用的一种损失函数),它的定义如下:
4.2.2 自定义损失函数
4.3 神经网络优化算法
本节将更加具体地介绍如何通过反向传播算法(backpropagation)和梯度下降算法(gradient descent)调整神经网络中参数的取值。
梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法,从而使神经网络模型在训练数据上的损失函数尽可能小。
反向传播算法是训练神经网络的核心算法,它可以根据定义好的损失函数优化神经网络中参数的取值,从而使神经网络模型在训练集上的损失函数达到一个较小值。
神经网络中参数的优化过程直接决定了模型的质量,这是使用神经网络时非常重要的一步。
本节将给出一个具体的样例来解释使用梯度下降算法优化参数取值的过程。
假设用θ表示神经网络中的参数,J(θ)表示在给定的参数取值下,训练数据集上损失函数的大小,那么整个优化过程可以抽象为寻找一个参数θ,使得J(θ)最小。
因为目前没有一个通用的方法可以对任意损失函数直接求解最佳的参数取值,所以在实践中,梯度下降算法是最常用的神经网络优化算法。
梯度下降算法会迭代式更新参数θ,不断沿着梯度的反方向让参数朝着总损失更小的方向更新。图4-11展示了梯度下降算法的原理。
需要注意的是,梯度下降算法并不能保证被优化的函数达到全局最优解。如图4-12所示,图中给出的函数就有可能只能得到局部最优解而不是全局最优解。
在小黑点处。损失函数的偏导为0,于是参数就不会再进一步更新。
在这个样例中,若参数x的初始值落在右侧深色区间中,那么通过梯度下降得到的结果都会落在小黑点代表的局部最优解。只有当x的初始值落在左侧浅色的区间时梯度下降才能给出全局最优答案。
由此可见,在训练神经网络时,参数的初始值会很大程度影响最后得到的结果。只有当损失函数为凸函数时,梯度下降算法才能保证达到全局最优解。
除了不一定能达到全局最优外,梯度下降算法的另外一个问题就是计算时间太长。因为要在全部训练数据上最小化损失,所以损失函数J(θ)是在所有训练数据上的损失和。这样在每一轮迭代中都需要计算在全部训练数据上的损失函数。
在海量训练数据下,要计算所有训练数据的损失函数是非常消耗时间的。为了加速训练过程,可以使用随机梯度下降算法(stochastic gradient descent)。这个算法优化的不是在全部训练数据上的损失函数,而是在每一轮迭代中,随机优化某一条训练数据上的损失函数。这样每一轮参数更新的速度就大大加快了。因为随机梯度下降算法每次优化的只是某一条数据上的损失函数,所以它的问题也非常明显:
在某一条数据上损失函数更小并不代表在全部数据上损失函数更小,于是使用随机梯度下降优化得到的神经网络甚至可能无法达到局部最优。
为了综合梯度下降算法和随机梯度下降算法的优缺点,在实际应用中,一般采用这两个算法的折中——每次计算一小部分训练数据的损失函数。这一小部分数据被称之为一个batch。通过矩阵运算,每次在一个batch上优化神经网络的参数并不会比单个数据慢太多。另一方面,每次使用一个batch可以大大减小收敛所需要的迭代次数,同时可以使收敛到的结果更加接近梯度下降的效果。
以下代码给出了在TensorFlow中如何实现神经网络的训练过程。
4.4 神经网络进一步优化
首先介绍通过指数衰减的方法设置梯度下降算法中的学习率。通过指数衰减的学习率既可以让模型在训练的前期快速接近较优解,又可以保证模型在训练后期不会有太大的波动,从而更加接近局部最优。
然后介绍过拟合问题。在训练复杂神经网络时,过拟合是一个非常常见的问题。
最后介绍滑动平均模型,滑动平均模型会将每一轮迭代得到的模型综合起来,从而使得最终得到的模型更加健壮(robust)。
4.4.1 学习率的设置
上面这段代码中设定了初始学习率为0.1,因为指定了staircase=True,所以每训练100轮后学习率乘以0.96。一般来说,初始学习率、衰减系数和衰减速度都是根据经验设置的。并且损失函数下降的速度和迭代结束之后总损失的大小没有必然的联系。也就是说并不能通过前几轮损失函数的下降速度来比较不同神经网络的效果。
4.4.2 过拟合问题
在真实的应用中想要的并不是让模型尽量模拟训练数据的行为,而是希望通过训练出来的模型对未知的数据给出判断。
模型在训练数据上的表现并不一定代表了它在未知数据上的表现。过拟合问题就是可以导致这个差距的一个很重要的因素。
所谓过拟合,指的是当一个模型过于复杂之后,它可以很好的“记忆”每一个训练数据中随机噪音的部分而忘记了要去“学习”训练数据中通用的趋势。
为了避免过拟合问题,一个非常常用的方法是正则化(regularization)。
正则化的思想就是在损失函数中加入刻画模型复杂程度的指标。
假设用于刻画模型在训练数据上表现的损失函数为J(θ),那么在优化时不是直接优化J(θ),而是优化J(θ)+λR(w)。其中R(w)刻画的是模型的复杂程度,而λ表示模型复杂损失在总损失中的比例。注意这里θ表示的是一个神经网络中所有的参数,它包括边上的权重w和偏置项b。一般来说模型复杂度只由权重w决定。
常用的刻画模型复杂度的函数R(w)有两种,一种是L1正则化,计算公式是:
另一种是L2正则化,计算公式是:
无论是哪一种正则化方式,基本的思想都是希望通过限制权重的大小,使得模型不能任意拟合训练集中的随机噪音。
但这两种正则化的方法也有很大区别:
首先,L1正则化会让参数变得更稀疏,而L2正则化不会。
所谓参数变得更稀疏是指会有更多的参数变为0,这样可以达到类似特征选取的功能。之所以L2正则化不会让参数变得稀疏的原因是当参数很小时,比如0.001,这个参数的平方基本山就可以忽略了,于是模型不会进一步将这个参数调整为0。
其次,L1正则化的计算公式不可导,而L2正则化公式可导。
因为在优化时需要计算损失函数的偏导数,所以对含有L2正则化损失函数的优化要更加简洁。优化带L1正则化的损失函数要更加复杂,而且优化方法也有很多种。在实践中,也可以将L1正则化和L2正则化同时使用:
从上面的代码可以看出通过使用集合的方法在网络结构比较复杂的情况下的可读性更高。上面的代码给出的是一个只有5层的全连接网络,在更加复杂的网络结构中,使用这样的方式来计算损失函数将大大增加代码的可读性。
4.4.3 滑动平均模型
这一个小节将介绍另外一个可以使模型在测试数据上更健壮(robust)的方法——滑动平均模型。
在采用随机梯度下降算法训练神经网络时,使用平均滑动模型在很多应用中都可以在一定程度提高最终模型在测试数据集上的表现。
上面的代码给出了ExponentialMovingAverage的简单样例,在第5章将给出在真实应用中使用滑动平均的样例。
小结
第五章 MNIST数字识别问题
第4章介绍了训练神经网络模型时需要考虑的主要问题以及解决这些问题的常用方法。这一章将通过一个实际问题来验证第4章中介绍的解决方法。
5.1 MNIST数据处理
MNIST是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用作深度学习的入门样例。
本节中将大致讲解这个数据集的基本情况,并介绍TensorFlow对MNIST数据集做的封装。TensorFlow的封装让使用MNIST数据集变得更加方便。MNIST数据集是NIST数据集的一个子集,它包含了60000张图片作为训练数据,10000张图片作为测试数据。图片大小为28*28,且数字都会出现在图片的正中间。图5-1展示了一张数字图片及和它对应的像素矩阵。
注意:这里为了更清楚的显示,将右侧显示为14*14矩阵,真实的像素矩阵大小为28*28。
在Yann LeCun教授的网站中(http://yann.lecun.com/exdb/mnist)对MNIST数据集做了详细的介绍。MNIST数据集提供了4个下载文件。
5.2 神经网络模型训练及不同模型结果对比
5.2.1 TensorFlow训练神经网络
这一小节将给出一个完整的TensorFlow程序来解决MNIST手写体数字识别问题。这一小节中给出的程序实现了第4章中介绍的神经网络结构设计和训练优化的所有方法。
在给出具体的代码之前,先回顾一下第4章中提到的主要概念。
在神经网络的结构上,深度学习一方面需要使用激活函数实现神经网络模型的去线性化,另一方面需要使用一个或多个隐藏层使得神经网络的结构更深,以解决复杂问题。在训练神经网络时,第4章介绍了使用带指数衰减的学习率设置、使用正则化来避免过拟合,以及使用滑动平局模型来使得最终模型更加健壮。
以下代码给出了一个在MNIST数据集上实现这些功能的完整的TensorFlow程序。
从上面的结果可以看出,在训练初期,随着训练的进行,模型在验证数据集上的表现越来越好。从第4000轮开始,模型在验证数据集上的表现开始波动,这说明模型已经接近极小值了,所以迭代也就可以结束了。
5.2.2 使用验证数据集判断模型效果
当然,以上结论是针对MNIST这个数据集的,对于其他问题,还需要具体问题,具体分析。不同问题的数据分布不一样,如果验证数据分布不能很好地代表测试数据分布,那么模型在这两个数据集上的表现就有可能不一样。所以,验证数据的选取方法是非常重要的,一般来说选取的验证数据越接近测试数据分布,模型在验证数据上的表现越可以体现模型在测试数据集上的表现。
5.2.3 不同模型效果比较
本小节将通过MNIST数据集来比较第4章中提到的不同优化方法对神经网络模型正确率的影响。
在第4章中提到了设计神经网络时的5种优化方法。
在神经网络结构的设计上,需要使用激活函数和多层隐藏层。
在神经网络优化时,可以使用指数衰减的学习率、加入正则化的损失函数以及滑动平均模型。
5.3 变量管理
使用上面这段代码所示的方式,就不再需要将所有变量都作为参数传递到不同的函数中了。
当神经网络更加复杂、参数更多时,使用这种变量管理的方式将大大提高程序的可读性。
5.4 TensorFlow模型持久化
5.4.1 持久化代码实现
5.4.2 持久化原理及数据格式
5.5 TensorFlow最佳实践样例
第6章 图像识别与卷积神经网络
卷积神经网络的应用非常广泛,在自然语言处理、医药发现、灾难气候发现甚至围棋人工智能程序中都有应用。
本章将主要通过卷积神经网络在图像识别上的应用来讲解卷积神经网络的基本原理以及如何使用TensorFlow实现卷积神经网络。
首先介绍图像识别领域解决的问题以及图像识别领域中经典的数据集。
然后介绍卷积神经网络的主题思想和整体架构。
接着详细讲解卷积层和池化层的网络结构,以及TensorFlow对这些网络结构的支持。
还将通过两个经典的神经网络模型来介绍如何设计卷积神经网络的架构以及如何设置每一层神经网络的配置,将通过TensorFlow实现LeNet-5模型,并介绍TensorFlow-Slim来实现更加复杂的Inception-v3模块。
最后介绍如何通过TensorFlow实现卷积神经网络的迁移学习。
6.1 图像识别问题简介及经典数据集
视觉是人类认识世界非常重要的一种知觉。
图像识别问题希望借助计算机程序来处理、分析和理解图片中的内容,使得计算机可以从图片中自动识别各种不同模式的目标和对象。
图像识别问题作为人工智能的一个重要领域,在最近几年已经取得了很多突破性进展。本章将要介绍的卷积神经网络就是这些突破性进展背后的最主要技术支持。
(top-N正确率指的是图像识别算法给出前N个答案中有一个是正确的概率。在图像分类问题上,很多学术论文都将前N个答案的正确率作为比较的方法,其中N的取值一般为3或5。)
6.2 卷积神经网络简介
在前面的章节中所介绍的神经网络每两层之间的所有节点都是有边相连的。所以本书称这种网络结构为全连接层网络结构。为了将只包含全连接层的神经网络与卷积神经网络、循环神经网络区分开,本书将只包含全连接层的神经网络称之为全连接神经网络。
在这一节中将讲解卷积神经网络与全连接神经网络的差异,并介绍组成一个卷积神经网络的基本网络结构。
图6-6显示了全连接神经网络与卷积神经网络的结构对比图。
虽然图6-6中显示的全连接神经网络结构和卷积神经网络的结构直观上差异比较大,但实际上它们的整体架构是非常相似的。
从图6-6可以看出,卷积神经网络也是通过一层一层的节点组织起来的。和全连接神经网络一样,卷积神经网络中的每一个节点都是一个神经元。
在全连接神经网络中,每相邻两层之间的节点都有边相连,于是一般会将每一层全连接层中的节点组织成一列,这样方便显示连接结构。
而对于卷积神经网络,相邻两层之间,只有部分节点相连,为了展示每一层神经元的维度,一般会将每一层卷积层的节点组织成一个三维矩阵。
除了结构相似,卷积神经网络的输入输出以及训练流程与全连接神经网络也基本一致。
以图像分类为例,卷积神经网络的输入层就是图像的原始像素,而输出层中的每一个节点代表了不同类别的可信度,这和全连接神经网络的输入输出是一致的。类似的,第4章中介绍的损失函数以及参数的优化过程也都适用于卷积神经网络。
在后面的章节中会看到,在TensorFlow中训练一个卷积神经网络的流程和训练一个全连接神经网络没有任何差别。
卷积神经网络和全连接神经网络的唯一区别就在于神经网络中相邻两层的连接方式。
在进一步介绍卷积神经网络的连接结构之前,本节将先介绍为什么全连接神经网络无法很好地处理图像数据。
使用全连接神经网络处理图像的最大问题在于全连接层的参数太多。参数增多除了导致计算速度减慢,还容易导致过拟合问题。所以需要一个更加合理的神经网络结构来有效减少神经网络中参数个数。卷积神经网络就可以达到这个目的。
在卷积神经网络的前几层中,每一层的节点都被组织成一个三维矩阵。从上图可以看出,卷积神经网络中前几层中每一个节点只和上一层中部分的节点相连。
一个卷积神经网络主要由以下5种结构组成:
1 输入层
输入层是整个神经网络的输入,在处理图像的卷积神经网络中,它一般代表一张图片的像素矩阵。
比如在图6-7中,最左侧的三维矩阵就可以代表一张图片。其中三维矩阵的长和宽代表了图像的大小。而三维矩阵的深度代表了图像的色彩通道(channel)。比如黑白图片的深度为1,而在RGB(红绿蓝)色彩模式下,图像的深度为3。
(注:在RGB色彩模式下,一副完整的图像是由红色、绿色和蓝色3个通道组成的。因为每个通道在每个像素点上都有亮度值,所以整个图片就可以表示成一个三维矩阵)
从输入层开始,卷积神经网络通过不同的神经网络结构将上一层的三维矩阵转化为下一层的三维矩阵,直到最后的全连接层。
2 卷积层
从名字就可以看出,卷积层是一个卷积神经网络中最为重要的部分。和传统全连接层不同,卷积层中每一个节点的输入只是上一层神经网络的一小块,这个小块儿常用的大小有3*3或者5*5。卷积层试图将神经网络中的每一小块进行更加深入的分析从而得到抽象程度更高的特征。
一般来说,通过卷积层处理过的节点矩阵会变得更深,所以在图6-7中可以看到经过卷积层之后的节点矩阵的深度会增加。
3 池化层(Pooling)
池化层神经网络不会改变三维矩阵的深度,但是它可以缩小矩阵的大小。
池化操作可以认为是将一张分辨率较高的图片转化为分辨率较低的图片。通过池化层,可以进一步缩小最后全连接层中节点的个数,从而达到减少整个神经网络中参数的目的。
4 全连接层
在经过多轮卷积层和池化层的处理之后,在卷积神经网络的最后一般是由1到2个全连接层来给出最后的分类结果。经过几轮卷积层和池化层的处理之后,可以认为图像中的信息已经被抽象成了信息含量更高的特征。我们可以将卷积层和池化层看成自动图像特征提取的过程。在特征提取完成之后,仍然需要使用全连接层来完成分类任务。
5 Softmax层
同第4章介绍的一样,Softmax层主要用于分类问题。通过Softmax层,可以得到当前样例属于不同种类的概率分布情况。
在卷积神经网络中使用到的输入层、全连接层、Softmax层在第4章都有过详细的介绍,这里不再赘述。下面主要介绍卷积神经网络中特殊的两个网络结构——卷积层和池化层。
6.3 卷积神经网络常用结构
6.3.1 卷积层
本小节将详细介绍卷积层的结构以及其前向传播算法。
图6-8显示了卷积层神经网络结构中最重要的部分,这个部分被称之为过滤器(filter)或者内核(kernel)。因为TensorFlow文档中将这个结构称之为过滤器(filter),所以才本书中将统称这个结构为过滤器。
如图6-8所示,过滤器可以将当前层神经网络上的一个子节点矩阵转化为下一层神经网络上的一个单位节点矩阵。
单位节点矩阵指的是一个长和宽都为1,但深度不限的节点矩阵。
在一个卷积层中,过滤器所处理的节点矩阵的长和宽都是由人工指定的,这个节点矩阵的尺寸又被称之为过滤器的尺寸。常用的过滤器尺寸有3*3和5*5。
因为过滤器处理的矩阵深度和当前层神经网络节点矩阵的深度是一致的,所以虽然节点矩阵是三维的,但过滤器的尺寸只需要指定两个维度(即只需指定长和宽即可,因为深度是一致的,故无须指定)。
过滤器中另外一个需要人工指定的设置是处理得到的单位节点矩阵的深度,这个设置称为过滤器的深度。注意过滤器的尺寸指的是一个过滤器输入节点矩阵的大小,而深度指的是输出单位节点矩阵的深度。
如图6-8所示,左侧小矩阵的尺寸为过滤器的尺寸(即过滤器输入节点矩阵的大小),而右侧单位矩阵的深度为过滤器的深度(即输出单位节点矩阵的深度)。
6.4节将通过一些经典神经网络结构来了解如何设置每一层卷积层过滤器的尺寸和深度。
如图6-8所示,过滤器的前向传播过程就是通过左侧小矩阵中的节点计算出右侧单位矩阵中节点的过程。
在下面给出的样例中将展示如何通过过滤器将一个2*2*3的节点矩阵变化为一个1*1*5的单位节点矩阵。
一个过滤器的前向传播过程和全连接层相似,它总共需要2*2*3*5+5=65个参数。其中+5为偏置项个数的参数。
注:此处全0填充的方式和TensorFlow中实现的方式略有不同,但是原理是一样的。
6.3.2 池化层
从图6-7中可以看出,在卷积层之间往往会加入一个池化层(pooling layer)。
池化层可以非常有效地缩小矩阵的尺寸,从而减少最后全连接层中的参数。
使用池化层既可以加快计算速度,也有防止过拟合问题的作用。
池化层前向传播的过程也是通过移动一个类似过滤器的结构完成的。不过池化层过滤器中的计算不是节点的加权和,而是采用更加简单的最大值或者平均值计算。
使用最大值操作的池化层被称之为最大池化层(max pooling),这是被使用得最多的池化层结构。
使用平均值操作的池化层被称之为平均池化层(average pooling)。
与卷积层的过滤器类似,池化层的过滤器也需要人工设定过滤器的尺寸,是否使用全0填充以及过滤器移动的步长等设置,而且这些设置的意义也是一样的。
卷积层和池化层中过滤器移动的方式是相似的,唯一的区别在于卷积层使用的过滤器是横跨整个深度的,而池化层使用的过滤器只影响一个深度上的节点。所以池化层的过滤器除了在长和宽两个维度移动之外,它还需要在深度这个维度移动。
图6-14展示了一个最大池化层前向传播计算过程。
6.4 卷积神经网络模型
在6.3小节中介绍了卷积神经网络特有的两种网络结构——卷积层和池化层。然而,通过这些网络结构任意组合得到的神经网络有无限多种。
这一节将介绍一些经典的卷积神经网络的网络结构。通过这些经典的卷积神经网络的网络结构可以总结出卷积神经网络结构设计的一些模式。
6.4.1 LeNet-5 模型
LeNet-5 模型是Yann LeCun教授于1988年在论文Gradient-based learning applied to document recognition中提出的,它是第一个成功应用于数字识别问题的卷积神经网络。LeNet-5 模型共有7层。
6.4.2 Inception-v3 模型
Inception结构是一种和LeNet-5结构完全不同的卷积神经网络结构。
在LeNet-5模型中,不同卷积层通过串联的方式连接在一起,而Inception-v3 模型中的Inception结构是将不同的卷积层通过并联的方式结合在一起。
6.5 卷积神经网络迁移学习
首先讲解迁移学习的动机,并介绍如何将一个数据集上训练好的卷积神经网络模型快速转移到另外一个数据集上。
然后将给出一个具体的TensorFlow程序将ImageNet上训练好的Inception-v3模型转移到另外一个图像分类数据集上。
6.5.1 迁移学习介绍
6.5.2 TensorFlow实现迁移学习
第七章 图像数据处理
在第6章中详细介绍了卷积神经网络,并提到通过卷积神经网络给图像识别技术带来了突破性进展。
这一章将从另外一个维度来进一步提升图像识别的精度以及训练的速度。
本章将介绍如何对图像数据进行预处理使训练得到的神经网络模型尽可能小地被无关因素所影响。但与此同时,复杂的预处理过程可能导致训练效率的下降。
为了减小预处理对于训练速度的影响,在本章中也将详细地介绍TensorFlow中多线程处理输入数据的解决方案。
本章将根据数据预处理的先后顺序来组织不同的小节。
首先介绍如何统一输入数据的格式,使得在之后系统中可以更加方便地处理。来自实际问题的数据往往有很多格式和属性,这一节将介绍的TFRecord格式可以统一不同的原始数据格式,并更加有效地管理不同的属性。
接着介绍如何对图像数据进行预处理。
然后介绍多线程数据处理流程。
7.1 TFRecord输入数据格式
TensorFlow提供了一种统一的格式来存储数据,这个格式就是TFRecord。
第八章 循环神经网络
第九章 TensorBoard可视化
第十章 TensorFlow计算加速