![TensorFlow 2.0卷积神经网络实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/133/29977133/b_29977133.jpg)
2.1 配角转成主角——从TensorFlow Eager Execution转正谈起
最早追溯到TensorFlow 1.10版本,那个时候TensorFlow由于强大的深度学习计算能力被众多的深度学习从业人员所使用。但是盛名之下还是有一些小的确定让人诟病,例如程序编写的困难,代码格式和其他深度学习框架有较大差异,运行时占用资源较多等。
TensorFlow开发组为了解决这些问题,在TensorFlow 1.10版本的时候就引入了一种新的程序运行机制——TensorFlow Eager Execution。其目的是为了解决程序开发人员使用TensorFlow作为深度学习框架时学习坡度不是很友善的缺点,同时也为了增加程序编写的方便,使用了一种新的简化的TensorFlow运行机制Eager Execution。结果一经推出就大受好评,使得很多原先使用别的机器学习框架的程序编写人员、机器学习爱好者转投TensorFlow的怀抱中。
TensorFlow Eager Execution(动态图)是一个命令式的编程环境,不建立图而是立即运算求值:运算返回具体值替换(以前)先构建运算图然后执行的机制。使得(使用)TensorFlow和调试模型变得简单,而且减少了多余(模板化、公式化操作)。
动态图是一个灵活的机器学习平台,用于研究和实验,提供以下功能:
●直观的接口:方便编码使用,基于Python数据结构。快速迭代小模型和小数据。
●调试简单:直接调用ops来检查运行模型和测试变更。使用标准Python调试工具进行即时错误报告。
●自然控制流:使用Python控制流替换图控制流,简化动态模型规范。
2.1.1 Eager简介与调用
TensorFlow的开发团队曾经表示,Eager Execution的主要优点如下:
●快速调试即刻的运行错误并通过Python工具进行整合。
●借助易于使用的Python控制流支持动态模型。
●为自定义和高阶梯度提供强大的支持。
●适用于几乎所有可用的TensorFlow运算。
1. Eager Execution的调用
Eager Execution的调用非常简单,可以直接使用代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P25_26985.jpg?sign=1738754351-GiwTx6HFHtI4yMPIGVLXEWeAvdPiUZ99-0-4e80ecf6aa639eee3e64ac2be218acf3)
这是因为在TensorFlow 2.0中,Eager Execution是默认开启的,因此直接引入TensorFlow即可。
而在TensorFlow 1.X版本中,Eager Execution需要手动开启,代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P26_26986.jpg?sign=1738754351-rULdwnYzfXJ4URVKbzZ79sR6UDfiw6RT-0-20ea99a874f12c6dda6316f0eeffa700)
这些代码是在1.X版本中开启Eager Execution的方法,首先第1行是引入TensorFlow,第2~3行是显示调用Eager模式,使之可以在本段代码中使用。
此外,更为常见的是,读者安装了TensorFlow 2.0或者更高版本,对于运行在1.X版本下编写的代码,可能会产生一些问题。因此需要重新引入TensorFlow 1.X的运行模式,在引入TensorFlow的时候需要修改代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P26_26987.jpg?sign=1738754351-PGhTElZoRXCejpXZ4aDrrRT0kgcDubqJ-0-1dc651ea61377d9f89d53c38d81a5454)
这样显式地调用TensorFlow 1.X版本的API使用。
2. Eager模式的使用
Eager Execution一个非常有意思的、作为宣传点的功能就是允许用户在不创建Graph(图)的情况下运行TensorFlow代码,代码如下:
【程序2-1】
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P26_26988.jpg?sign=1738754351-HCBSUntXDbIU1bw4DEvs1MkF6sqAIBqn-0-0b8d4b3fd80f968b6cdabbf31f904d3d)
这里默认启动了Eager模式,在使用TensorFlow读入一个序列后将其打印,结果如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P26_4237.jpg?sign=1738754351-JFdFAJ5qdWE0ohFOsHohTO3l0QCt7wVm-0-b3eecf51bf04170d774f3f6d543584fd)
可以看到,结果打印出了读入数据后的Tensor数据格式,即具体数值为[1,2],维度大小为2,数据类型为int32。
如果此时需要将这个数据的具体内容打印出来,代码可以改成如下:
【程序2-2】
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P26_26989.jpg?sign=1738754351-4gtwx1GrzjAD25IZMfnU55xeEErTlvM5-0-00a0aca6ae74661cfb2be305ce3456af)
打印结果如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P26_4261.jpg?sign=1738754351-JsBQA1k0UG41P74kDlxgamwquEg6dhVn-0-15eae80b458c4816b99845158396d6a2)
可以看到,此时由于加上了数据自带的numpy()函数,Tensor数据被显示转化为常用的NumPy数据格式,即常数格式。
这里顺带提一下,如果使用传统的TensorFlow编写模式,代码要修改为:
【程序2-3】
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P27_26990.jpg?sign=1738754351-QbtaYUCkFB6LB1PPPmQBFGLgODbyBQJ1-0-b9be23e1fa793388b02c6f40b4819aed)
打印结果如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P27_4300.jpg?sign=1738754351-1tauPPkRHYA1TzuJLzVfz4OoZoDRqdYz-0-20fd9eb1c8cb126a45469b3b2642092c)
可以看到,此时数据被读入到图中而不是被直接计算,因此打印出的结果并没有具体数据,而具体数据的计算请读者自行完成。
2.1.2 读取数据
TensorFlow 1.X数据的读取是采用占位符的形式,首先将数据读取到内存中,之后建立整体的TensorFlow图,在运行图以后读取数据并显示。
TensorFlow 2.0简化了数据读取,其相似度NumPy的数据迭代风格,只使用TensorFlow中自带的Dateset API即可完成数据的迭代,代码如下:
1. 第一步:生成数据
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P27_26991.jpg?sign=1738754351-eVZkRrCjVMgT7YqGGrI61KRrvyjqsmDq-0-3681f54740da30cba901cceaecb726ed)
首先是数据的生成,作者使用Numpy做了数据生成,产生了100个由0~99的数据并存储在arr_list中。
2. 第二步:使用Dataset API读取数据
下面使用Dataset读取API,代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P27_26992.jpg?sign=1738754351-k9V7BtGXp9BrmOErBRtsAJqLUbXtL2YN-0-489a94359b822031a8dba19fd2042179)
这里首先使用Dataset.from_tensor_slices读取数据,之后使用shuffle函数打乱顺序,最终将数据以每个batch为10输出。
3. 第三步:创建计算模型
创建计算模型是数据处理的关键,这里为了简化起见,作者创建了一个非常简单的模型,即使用TensorFlow将输入的数据乘以0.1并输出,代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P27_26993.jpg?sign=1738754351-IZ30IYdPbPfnOqEAWwXyP6C7v3vCmZmA-0-78bab3d860a5d4c292983b024a64ce39)
这里的model是一个简单函数的实现,有兴趣的读者可以往里添加更多的内容。
4. 第四步:数据的迭代输出
最后就是读取数据的迭代输出,前面已经做了说明,在Eager模式中,Dataset API是可以自动生成一个新的迭代器,将数据迭代出来。代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P28_26994.jpg?sign=1738754351-0dqe4dDQcIAelFNjJxpSPK2VTSHKxRoC-0-3c6a1ee185a1a9728b48f84ef8948398)
这样就构成了一个完整的使用Eager模型进行简单数据计算的模型,全部代码如下:
【程序2-4】
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P28_26996.jpg?sign=1738754351-1mq6HtPYhgpMGcndqgVVzGriXL3n2Zlz-0-987ac505babe5834e9ea20580d66f3f6)
最终打印结果如图2.2所示。
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P28_4472.jpg?sign=1738754351-fE91OGFrBjHwzjadIfUK7eDrJDDfXh0i-0-f8646b833db3419a882ce31aa2cc643d)
图2.2 打印结果
输出结果显然不符合在程序中既定的模型,即将数列中的数乘以0.1并显示,而这里的输出数据却都显示为0。
究其原因是在Numpy数据生成的时候,以int32格式为数据的基本生成格式,因此Eager在进行计算时无法隐式地将数据转化成float类型,从而造成计算失败。
解决的办法也很方便,将数据生成代码改成如下形式:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P29_4488.jpg?sign=1738754351-wBtZeyo1MV7v2jBoEyxv0DzKP1OhqqWs-0-c034d934e27958f2bd5f9bf4bada1e98)
具体内容请读者自行完成。
2.1.3 使用TensorFlow 2.0模式进行线性回归的一个简单的例子
下面我们就以一个线性回归的模型为例,介绍使用Eager模型进行机器学习计算的方法,其中涉及到模型参数的保存,以及读取已经保存的模型重新计算。
1. 第一步:模型的工具与数据的生成
首先是模型的定义,这里我们使用一个简单的一元函数模型作为待测定的模型基础,公式如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P29_4493.jpg?sign=1738754351-mwPNY5m0OyNtx9ZiHU5sNaVvpLJR33q2-0-e77384d93b45c95f6e5704cc11bb2706)
即3倍的输入值加上0.217作为输出值。
2. 第二步:模型的定义
在这里由于既定的模型是一个一元线性方程,因此在使用Eager模型时自定义一个类似的数据模型,代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P29_26998.jpg?sign=1738754351-7byDAqeX7XnUC8LrFvyVLs5P1JOj81Lo-0-ee49f507a2ecc601372bd8788aa17b80)
首先使用固定数据定义模型初始化参数weight和bias,之后一个线性回归模型在初始状态拟合了一元回归模型。
3. 第三步:损失函数的定义
对于使用机器学习进行数据拟合,一个非常重要的内容就是损失函数的编写,它往往决定着数据从空间中的哪个角度去拟合真实数据。
本例使用均方差(MSE)去计算拟合的数据与真实数据之间的误差,代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P29_4531.jpg?sign=1738754351-jE5NijlcLWXCl2JT9eZSr8P1WKAr45Nj-0-de72d3f7e5b7ad6e9add384c1d94774e)
这是使用TensorFlow自带损失函数计算MSE(均方差)的表示方法,当然也可以使用自定义的损失函数,代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P29_4538.jpg?sign=1738754351-3FYWioyvz7FnvMxEKzhZ3pBb3diFCiOQ-0-5ae2076f05af51a23b73eb55a153fe3a)
这两者是等效的,不过自定义的损失函数可以使程序编写者获得更大的自由度,对新手来说,还是使用定制的损失函数去计算较好,这一点请读者自行斟酌。
4. 第四步:梯度函数的更新计算
下面就是梯度的函数计算,这里可以直接调用TensorFlow的优化器,作者选择使用Adam优化器作为优化工具,代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P30_4556.jpg?sign=1738754351-kLsdBacq1kmwFeDIiZoJVD3NAxru93l2-0-f394b129747678abb7c61a7b7184c981)
opt对应的是TensorFlow优化器的对应写法。全部代码如下:
【程序2-5】
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P30_27005.jpg?sign=1738754351-q0AYDyQ2IsL1nM5MtuntPVdQlAlfY5nc-0-d5d6fcbf6d6df43b131827da377e9a5a)
打印结果如图2.3所示。
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P30_4679.jpg?sign=1738754351-9CaBTPgNmG6ImaiN9NIXaWe449weWsIC-0-74efc5a48099dd4de4195d9f587d8b02)
图2.3 打印结果
可以看到经过迭代计算以后,生成的weight值和bias值较好地拟合成预定的数据。有TensorFlow 1.X编程经验的读者可能会对这种数据更新的方式不习惯,但是需要记住这种写法:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P30_4687.jpg?sign=1738754351-AEzCCZaTcnsZlujbUW51qDlbPD01ra03-0-00ab6b0e72fbfbe4dcb653cd7ea30a57)
除此之外Keras对于梯度的更新采用回调的方式对权重进行更新,代码如下:
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P31_27008.jpg?sign=1738754351-C2NigMabxLC0YcUsfHC9AJWE2QEEEsEv-0-7e11e8c9586e1bb24d0ad23f692d6ffb)
全部代码如下(函数调用过于复杂,仅供参考):
【程序2-6】
![](https://epubservercos.yuewen.com/5F793F/16499866704817006/epubprivate/OEBPS/Images/Figure-P31_27009.jpg?sign=1738754351-FNLNBudc0SEQzLGBuelqHcALno8RvRJM-0-693f070f229180e3c7482a517a89288a)
在这里函数会直接调用,其内部多次用到回调函数,对Python有较多研究的读者可以尝试运行一下。