第2章
TensorFlow进阶
本章将介绍如何使用TensorFlow的关键组件,并串联起来创建一个简单的分类器,评估输出结果。阅读本章你会学到以下知识点:
- 计算图中的操作
- TensorFlow的嵌入Layer
- TensorFlow的多层Layer
- TensorFlow实现损失函数
- TensorFlow实现反向传播
- TensorFlow实现随机训练和批量训练
- TensorFlow实现创建分类器
- TensorFlow实现模型评估
2.1 简介
现在我们已经学习完TensorFlow如何创建张量,使用变量和占位符;下面将把这些对象组成一个计算图。基于此,创建一个简单的分类器,并看下性能如何。
本书的所有源代码可以在GitHub(https://github.com/nfmcclure/tensorflow_cookbook )下载。
2.2 计算图中的操作
现在可以把这些对象表示成计算图,下面介绍计算图中作用于对象的操作。
2.2.1 开始
导入TensorFlow,创建一个会话,开始一个计算图:
2.2.2 动手做
在这个例子中,我们将结合前面所学的知识,传入一个列表到计算图中的操作,并打印返回值:
1.首先,声明张量和占位符。这里,创建一个numpy数组,传入计算图操作:
上述代码的输出如下所示:
2.2.3 工作原理
首先,创建数据集和计算图操作,然后传入数据、打印返回值。下面展示计算图(见图2-1):
2.3 TensorFlow的嵌入Layer
在本节,我们将学习如何在同一个计算图中进行多个乘法操作。
2.3.1 开始
下面我们将用两个矩阵乘以占位符,然后做加法。传入两个矩阵(三维numpy数组):
2.3.2 动手做
知道数据在传入后是如何改变形状的也是非常重要的。我们将传入两个形状为3×5的numpy数组,然后每个矩阵乘以常量矩阵(形状为5×1),将返回一个形状为3×1的矩阵。紧接着再乘以1×1的矩阵,返回的结果矩阵仍然为3×1。最后,加上一个3×1的矩阵,示例如下:
1.首先,创建数据和占位符:
2.接着,创建矩阵乘法和加法中要用到的常量矩阵:
3.现在声明操作,表示成计算图:
4.最后,通过计算图传入数据:
2.3.3 工作原理
上面创建的计算图可以用Tensorboard可视化。Tensorboard是TensorFlow的功能,允许用户在图中可视化计算图和值。这些功能是原生的,不像其他机器学习框架。如果想知道这是如何做到的,可参见第11章。图2-2是分层的计算图。
2.3.4 延伸学习
在我们通过计算图运行数据之前要提前估计好声明数据的形状以及预估操作返回值的形状。由于预先不知道,或者维度在变化,情况也可能发生变化。为了实现目标,我们指明变化的维度,或者事先不知道的维度设为None。例如,占位符列数未知,使用方式如下:
上面虽然允许打破矩阵乘法规则,但仍然需要遵守——乘以常量矩阵返回值有一致的行数。在计算图中,也可以传入动态的x_data,或者更改形状的x_data,具体细节将在多批量传入数据时讲解。
尽管可以使用None调节变量在某个维度上的大小,但是建议读者尽量能够明确变量的形状,并在代码中明确。None维度主要应用在限制训练或者测试时的数据批量大小(即一次计算时多少个数据点参与运算)方面。
2.4 TensorFlow的多层Layer
目前,我们已经学完在同一个计算图中进行多个操作,接下来将讲述如何连接传播数据的多个层。
2.4.1 开始
本节中,将介绍如何更好地连接多层Layer,包括自定义Layer。这里给出一个例子(数据是生成随机图片数据),以更好地理解不同类型的操作和如何用内建层Layer进行计算。我们对2D图像进行滑动窗口平均,然后通过自定义操作层Layer返回结果。
在这节,我们将会看到TensorFlow的计算图太大,导致无法完整查看。为了解决此问题,将对各层Layer和操作进行层级命名管理。按照惯例,加载numpy和tensorf?low模块,创建计算图,代码如下:
2.4.2 动手做
1.首先,通过numpy创建2D图像,4×4像素图片。我们将创建成四维:第一维和最后一维大小为1。注意,TensorFlow的图像函数是处理四维图片的,这四维是:图片数量、高度、宽度和颜色通道。这里是一张图片,单颜色通道,所以设两个维度值为1:
2.下面在计算图中创建占位符。此例中占位符是用来传入图片的,代码如下:
3.为了创建过滤4×4像素图片的滑动窗口,我们将用TensorFlow内建函数conv2d()(常用来做图像处理)卷积2×2形状的常量窗口。conv2d()函数传入滑动窗口、过滤器和步长。本例将在滑动窗口四个方向上计算,所以在四个方向上都要指定步长。创建一个2×2的窗口,每个方向长度为2的步长。为了计算平均值,我们将用常量为0.25的向量与2×2的窗口卷积,代码如下:
可以使用函数中的name参数将层命名为Moring-Arg-Window。还可以使用公式:Output = (W - F + 2P)/S + 1计算卷积层的返回值形状。这里,W是输入形状,F是过滤器形状,P是padding的大小,S是步长形状。
4.现在定义一个自定义Layer,操作滑动窗口平均的2×2的返回值。自定义函数将输入张量乘以一个2×2的矩阵张量,然后每个元素加1。因为矩阵乘法只计算二维矩阵,所以剪裁图像的多余维度(大小为1)。TensorFlow通过内建函数squeeze()剪裁。下面是新定义的Layer:
5.现在把刚刚新定义的Layer加入到计算图中,并且用tf.name_scope()命名唯一的Layer名字,后续在计算图中可折叠/扩展Custom_Layer层,代码如下:
6.为占位符传入4×4像素图片,然后执行计算图,代码如下:
2.4.3 工作原理
已命名的层级Layer和操作的可视化图看起来更清晰,我们可以折叠和展开已命名的自定义层Layer。在图2-3中,我们可以在左边看到折叠的概略图,在右边看到展开的详细图:
2.5 TensorFlow实现损失函数
损失函数(loss function)对机器学习来讲是非常重要的。它们度量模型输出值与目标值(target)间的差值。本节会介绍TensorFlow中实现的各种损失函数。
2.5.1 开始
为了优化机器学习算法,我们需要评估机器学习模型训练输出结果。在TensorFlow中评估输出结果依赖损失函数。损失函数告诉TensorFlow,预测结果相比期望的结果是好是坏。在大部分场景下,我们会有算法模型训练的样本数据集和目标值。损失函数比较预测值与目标值,并给出两者之间的数值化的差值。
本节会介绍TensorFlow能实现的大部分损失函数。
为了比较不同损失函数的区别,我们将会在图表中绘制出来。先创建计算图,然后加载matplotlib(Python的绘图库),代码如下:
2.5.2 动手做
1.回归算法的损失函数。回归算法是预测连续因变量的。创建预测序列和目标序列作为张量,预测序列是-1到1之间的等差数列,代码如下:
2.L2正则损失函数(即欧拉损失函数)。L2正则损失函数是预测值与目标值差值的平方和。注意,上述例子中目标值为0。L2正则损失函数是非常有用的损失函数,因为它在目标值附近有更好的曲度,机器学习算法利用这点收敛,并且离目标越近收敛越慢,代码
如下:
TensorFlow有内建的L2正则形式,称为nn.l2_loss()。这个函数其实是实际L2正则的一半,换句话说,它是上面l2_y_vals的1/2。
3.L1正则损失函数(即绝对值损失函数)。与L2正则损失函数对差值求平方不同的是,L1正则损失函数对差值求绝对值,其优势在于当误差较大时不会变得更陡峭。L1正则在目标值附近不平滑,这会导致算法不能很好地收敛。代码如下:
4.Pseudo-Huber损失函数是Huber损失函数的连续、平滑估计,试图利用L1和L2正则削减极值处的陡峭,使得目标值附近连续。它的表达式依赖参数delta。我们将绘图来显示delta1 = 0.25和delta2 = 5的区别,代码如下:
现在我们转向用于分类问题的损失函数。分类损失函数是用来评估预测分类结果的。通常,模型对分类的输出为一个0~1之间的实数。然后我们选定一个截止点(通常为0.5),并根据输出是否高于此点进行分类。
5.重新给x_vals和target赋值,保存返回值并在下节绘制出来,代码如下:
6.Hinge损失函数主要用来评估支持向量机算法,但有时也用来评估神经网络算法。在本例中是计算两个目标类(-1,1)之间的损失。下面的代码中,使用目标值1,所以预测值离1越近,损失函数值越小:
7.两类交叉熵损失函数(Cross-entropy loss)有时也作为逻辑损失函数。比如,当预测两类目标0或者1时,希望度量预测值到真实分类值(0或者1)的距离,这个距离经常是0到1之间的实数。为了度量这个距离,我们可以使用信息论中的交叉熵,代码如下:
8.Sigmoid交叉熵损失函数(Sigmoid cross entropy loss)与上一个损失函数非常类似,有一点不同的是,它先把x_vals值通过sigmoid函数转换,再计算交叉熵损失,代码如下:
9.加权交叉熵损失函数(Weighted cross entropy loss)是Sigmoid交叉熵损失函数的加权,对正目标加权。举个例子,我们将正目标加权权重0.5,代码如下:
10.Softmax交叉熵损失函数(Softmax cross-entropy loss)是作用于非归一化的输出结果,只针对单个目标分类的计算损失。通过softmax函数将输出结果转化成概率分布,然后计算真值概率分布的损失,代码如下:
11.稀疏Softmax交叉熵损失函数(Sparse softmax cross-entropy loss)和上一个损失函数类似,它是把目标分类为true的转化成index,而Softmax交叉熵损失函数将目标转成概率分布。代码如下:
2.5.3 工作原理
这里用matplotlib绘制回归算法的损失函数(见图2-4):
下面是用matplotlib绘制各种分类算法损失函数(见图2-5):
2.5.4 延伸学习
下面总结一下前面描述的各种损失函数:
其他分类算法的损失函数都需要做交叉熵损失。Sigmoid交叉熵损失函数被用在非归一化逻辑操作,先计算sigmoid,再计算交叉熵。TensorFlow有很好的内建方法来处理数值边界问题。Softmax交叉熵和稀疏Softmax交叉熵都类似。
这里大部分描述的分类算法损失函数是针对二类分类预测,不过也可以通过对每个预测值/目标的交叉熵求和,扩展成多类分类。
也有一些其他指标来评价机器学习模型,这里给出一个列表。
2.6 TensorFlow实现反向传播
使用TensorFlow的一个优势是,它可以维护操作状态和基于反向传播自动地更新模型变量。本节将介绍如何使用这种优势来训练机器学习模型。
2.6.1 开始
现在开始介绍如何调节模型变量来最小化损失函数。前面已经学习了创建对象和操作,创建度量预测值和目标值之间差值的损失函数。这里将讲解TensorFlow是如何通过计算图来实现最小化损失函数的误差反向传播进而更新变量的。这步将通过声明优化函数(optimization function)来实现。一旦声明好优化函数,TensorFlow将通过它在所有的计算图中解决反向传播的项。当我们传入数据,最小化损失函数,TensorFlow会在计算图中根据状态相应的调节变量。
本节先举个简单的回归算法的例子。从均值为1、标准差为0.1的正态分布中抽样随机数,然后乘以变量A,目标值为10,损失函数为L2正则损失函数。理论上,A的最优值是10,因为生成的样例数据均值是1。
第二个例子是一个简单的二值分类算法。从两个正态分布(N (-1, 1)和N (3, 1))生成100个数。所有从正态分布N (-1, 1)生成的数据标为目标类0;从正态分布N (3, 1)生成的数据标为目标类1,模型算法通过sigmoid函数将这些生成的数据转换成目标类数据。换句话讲,模型算法是sigmoid (x + A),其中,A是要拟合的变量,理论上A = - 1。假设,两个正态分布的均值分别是m1和m2,则达到A的取值时,它们通过- (m1 + m2)/2转换成到0等距的值。后面将会在TensorFlow中见证怎样取到相应的值。
同时,指定一个合适的学习率对机器学习算法的收敛是有帮助的。优化器类型也需要指定,前面的两个例子使用标准梯度下降法,由TensorFlow中的GradientDescentOptimizer()函数实现。
2.6.2 动手做
这里是回归算法例子:
1.导入Python的数值计算模块,numpy和tensorf?low:
2.创建计算图会话:
3.生成数据,创建占位符和变量A:
4.增加乘法操作:
5.增加L2正则损失函数:
6.现在声明变量的优化器。大部分优化器算法需要知道每步迭代的步长,这距离是由学习率控制的。如果学习率太小,机器学习算法可能耗时很长才能收敛;如果学习率太大,机器学习算法可能会跳过最优点。相应地导致梯度消失和梯度爆炸问题。学习率对算法的收敛影响较大,我们会在本节结尾探讨。在本节中使用的是标准梯度下降算法,但实际情况应该因问题而异,不同的问题使用不同的优化器算法,具体见2.6.5节中Sebastian Ruder所写的文章。
7.在运行之前,需要初始化变量:
选取最优的学习率的理论很多,但真正解决机器学习算法的问题很难。2.6.5节列出了特定算法的学习率选取方法。
8.最后一步是训练算法。我们迭代101次,并且每25次迭代打印返回结果。选择一个随机的x和y,传入计算图中。TensorFlow将自动地计算损失,调整A偏差来最小化损失:
现在将介绍简单的分类算法例子。如果先重置一下前面的TensorFlow计算图,我们就可以使用相同的TensorFlow脚本继续分类算法的例子。我们试图找到一个优化的转换方式A,它可以把两个正态分布转换到原点,sigmoid函数将正态分布分割成不同的两类。
9.首先,重置计算图,并且重新初始化变量:
10.从正态分布(N (-1, 1),N (3, 1))生成数据。同时也生成目标标签,占位符和偏差变量A:
初始化变量A为10附近的值,远离理论值-1。这样可以清楚地显示算法是如何从10收敛为-1的。
11.增加转换操作。这里不必封装sigmoid函数,因为损失函数中会实现此功能:
12.由于指定的损失函数期望批量数据增加一个批量数的维度,这里使用expand_dims()函数增加维度。下节将讨论如何使用批量变量训练,这次还是一次使用一个随机数据:
13.初始化变量A:
14.声明损失函数,这里使用一个带非归一化logits的交叉熵的损失函数,同时会用sigmoid函数转换。TensorFlow的nn.sigmoid_cross_entropy_with_logits()函数实现所有这些功能,需要向它传入指定的维度,代码如下:
15.如前面回归算法的例子,增加一个优化器函数让TensorFlow知道如何更新和偏差变量:
16.最后,通过随机选择的数据迭代几百次,相应地更新变量A。每迭代200次打印出损失和变量A的返回值:
2.6.3 工作原理
作为概括,总结如下几点:
1.生成数据,所有样本均需要通过占位符进行加载。
2.初始化占位符和变量。这两个算法中,由于使用相同的数据,所以占位符相似,同时均有一个乘法变量A,但第二个分类算法多了一个偏差变量。
3.创建损失函数,对于回归问题使用L2损失函数,对于分类问题使用交叉熵损失
函数。
4.定义一个优化器算法,所有算法均使用梯度下降。
5.最后,通过随机数据样本进行迭代,更新变量。
2.6.4 延伸学习
前面涉及的优化器算法对学习率的选择较敏感。下面给出学习率选择总结:
有时,标准梯度下降算法会明显卡顿或者收敛变慢,特别是在梯度为0的附近的点。为了解决此问题,TensorFlow的MomentumOptimizer()函数增加了一项势能,前一次迭代过程的梯度下降值的倒数。
另外一个可以改变的是优化器的步长,理想情况下,对于变化小的变量使用大步长;而变化迅速的变量使用小步长。这里不会进行数学公式推导,但给出实现这种优点的常用算法:Adagrad算法。此算法考虑整个历史迭代的变量梯度,TensorFlow中相应功能的实现是AdagradOptimizer()函数。
有时,由于Adagrad算法计算整个历史迭代的梯度,导致梯度迅速变为0。解决这个局限性的是Adadelta算法,它限制使用的迭代次数。TensorFlow中相应功能的实现是AdadeltaOptimizer()函数。
还有一些其他的优化器算法实现,请阅读TensorFlow官方文档:https://www. tensorflow.org/api_guides/python/train 。
2.6.5 参考
2.7 TensorFlow实现批量训练和随机训练
根据上面描述的反向传播算法,TensorFlow更新模型变量。它能一次操作一个数据点,也可以一次操作大量数据。一个训练例子上的操作可能导致比较“古怪”的学习过程,但使用大批量的训练会造成计算成本昂贵。到底选用哪种训练类型对机器学习算法的收敛非常关键。
2.7.1 开始
为了TensorFlow计算变量梯度来让反向传播工作,我们必须度量一个或者多个样本的损失。与前一节所做的相似,随机训练会一次随机抽样训练数据和目标数据对完成训练。另外一个可选项是,一次大批量训练取平均损失来进行梯度计算,批量训练大小可以一次上扩到整个数据集。这里将显示如何扩展前面的回归算法的例子——使用随机训练和批量训练。
导入numpy、matplotlib和tensorf?low模块,开始一个计算图会话,代码如下:
2.7.2 动手做
1.开始声明批量大小。批量大小是指通过计算图一次传入多少训练数据:
2.接下来,声明模型的数据、占位符和变量。这里能做的是改变占位符的形状,占位符有两个维度:第一个维度为None,第二个维度是批量训练中的数据量。我们能显式地设置维度为20,也能设为None。如第1章所述,我们必须知道训练模型中的维度,这会阻止不合法的矩阵操作:
3.现在在计算图中增加矩阵乘法操作,切记矩阵乘法不满足交换律,所以在matmul()函数中的矩阵参数顺序要正确:
4.改变损失函数,因为批量训练时损失函数是每个数据点L2损失的平均值。在TensorFlow中通过reduce_mean()函数即可实现,代码如下:
5.声明优化器以及初始化模型变量,代码如下:
6.在训练中通过循环迭代优化模型算法。这部分代码与之前不同,因为我们想绘制损失值图与随机训练对比,所以这里初始化一个列表每间隔5次迭代保存损失函数:
7.迭代100次输出最终返回值。注意,A值现在是二维矩阵:
2.7.3 工作原理
批量训练和随机训练的不同之处在于它们的优化器方法和收敛过程。找到一个合适的批量大小是挺难的。为了展现两种训练方式收敛过程的不同,批量损失的绘图代码见下文。这里是存储随机损失的代码,接着上一节的代码:
绘制回归算法的随机训练损失和批量训练损失(见图2-6),代码如下:
2.7.4 延伸学习
2.8 TensorFlow实现创建分类器
在本节中,将结合前面所有的知识点创建一个iris数据集的分类器。
2.8.1 开始
iris数据集详细细节见第1章。加载样本数据集,实现一个简单的二值分类器来预测一朵花是否为山鸢尾。iris数据集有三类花,但这里仅预测是否是山鸢尾。导入iris数据集和工具库,相应的对原数据集进行转换。
2.8.2 动手做
1.导入相应的工具库,初始化计算图。注意,这里导入matplotlib模块是为了后续绘制结果:
2.导入iris数据集,根据目标数据是否为山鸢尾将其转换成1或者0。由于iris数据集将山鸢尾标记为0,我们将其从0置为1,同时把其他物种标记为0。本次训练只使用两种特征:花瓣长度和花瓣宽度,这两个特征在x-value的第三列和第四列:
3.声明批量训练大小、数据占位符和模型变量。注意,数据占位符的第一维度设为None:
注意,通过指定dtype = tf.f?loat32降低f?loat的字节数,可以提高算法的性能。
4.定义线性模型。线性模型的表达式为:x2 = x1A + b。如果找到的数据点在直线以上,则将数据点代入x2 - x1A - b计算出的结果大于0;同理找到的数据点在直线以下,则将数据点代入x2 - x1A - b计算出的结果小于0。将公式x2 - x1A - b传入sigmoid函数,然后预测结果1或者0。TensorFlow有内建的sigmoid损失函数,所以这里仅仅需要定义模型输出即可,代码如下:
5.增加TensorFlow的sigmoid交叉熵损失函数sigmoid_cross_entropy_with_logits(),代码如下:
6.声明优化器方法,最小化交叉熵损失。选择学习率为0.05,代码如下:
7.创建一个变量初始化操作,然后让TensorFlow执行它,代码如下:
8.现在迭代100次训练线性模型。传入三种数据:花瓣长度、花瓣宽度和目标变量。每200次迭代打印出变量值,代码如下:
9.下面的命令抽取模型变量并绘图,结果图在下一小节展示,代码如下:
2.8.3 工作原理
我们的目的是利用花瓣长度和花瓣宽度的特征在山鸢尾与其他物种间拟合一条直线。绘制所有的数据点和拟合结果,将会看到图2-7。
2.8.4 延伸学习
当前用一条直线分割两类目标并不是最好的模型。第4章将会介绍一种更好的方法来分割两类目标。
2.8.5 参考
关于iris数据集的介绍,可以看维基百科:https://en.wikipedia.org/wiki/Iris_flower_data_set 。或者Scikit Learn的iris数据集:http://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html 。
2.9 TensorFlow实现模型评估
学完如何使用TensorFlow训练回归算法和分类算法,我们需要评估模型预测值来评估训练的好坏。
2.9.1 开始
模型评估是非常重要的,每个模型都有很多模型评估方式。使用TensorFlow时,需要把模型评估加入到计算图中,然后在模型训练完后调用模型评估。
在训练模型过程中,模型评估能洞察模型算法,给出提示信息来调试、提高或者改变整个模型。但是在模型训练中并不是总需要模型评估,我们将展示如何在回归算法和分类算法中使用它。
训练模型之后,需要定量评估模型的性能如何。在理想情况下,评估模型需要一个训练数据集和测试数据集,有时甚至需要一个验证数据集。
想评估一个模型时就得使用大批量数据点。如果完成批量训练,我们可以重用模型来预测批量数据点。但是如果要完成随机训练,就不得不创建单独的评估器来处理批量数据点。
如果在损失函数中使用的模型输出结果经过转换操作,例如,sigmoid_cross_entropy_with_logits()函数,为了精确计算预测结果,别忘了在模型评估中也要进行转换操作。
另外一个重要方面是在评估前注意是回归模型还是分类模型。
回归算法模型用来预测连续数值型,其目标不是分类值而是数字。为了评估这些回归预测值是否与实际目标相符,我们需要度量两者间的距离。这里将重写本章上一小节的回归算法的例子,打印训练过程中的损失,最终评估模型损失。
分类算法模型基于数值型输入预测分类值,实际目标是1和0的序列。我们需要度量预测值与真实值之间的距离。分类算法模型的损失函数一般不容易解释模型好坏,所以通常情况是看下准确预测分类的结果的百分比。这次将使用本章上一小节的分类算法的例子。
2.9.2 动手做
首先,将展示如何评估简单的回归算法模型,其拟合常数乘法,目标值是10,步骤如下:
1.加载所需的编程库,创建计算图、数据集、变量和占位符。创建完数据后,将它们随机分割成训练数据集和测试数据集。不管算法模型预测的如何,我们都需要测试算法模型,这点相当重要。在训练数据和测试数据上都进行模型评估,以搞清楚模型是否过拟合:
2.声明算法模型、损失函数和优化器算法。初始化模型变量A,代码如下:
3.像以往一样迭代训练模型,代码如下:
4.现在,为了评估训练模型,将打印训练数据集和测试数据集训练的MSE损失函数值,代码如下:
对于分类模型的例子,与前面的例子类似。创建准确率函数(accuracy function),分别调用sigmoid来测试分类是否正确。
5.重新加载计算图,创建数据集、变量和占位符。记住,分割数据集和目标成为训练集和测试集,代码如下:
6.在计算图中,增加模型和损失函数,初始化变量,并创建优化器,代码如下:
7.现在进行迭代训练,代码如下:
8.为了评估训练模型,我们创建预测操作。用squeeze()函数封装预测操作,使得预测值和目标值有相同的维度。然后用equal()函数检测是否相等,把得到的true或false的boolean型张量转化成f?loat32型,再对其取平均值,得到一个准确度值。我们将用这个函数评估训练模型和测试模型,代码如下:
9.模型训练结果,比如准确度、MSE等,将帮助我们评估机器学习模型。因为这是一维模型,能很容易地绘制模型和数据点。用matplotlib绘制两个分开的直方图来可视化机器学习模型和数据点(见图2-8):
2.9.3 工作原理
图中的结果标明了两类直方图的最佳分割。