TensorFlow 2.0 的新增功能(一)
零、前言
TensorFlow 是最受欢迎的机器学习框架之一,其新版本 TensorFlow 2.0 改善了其简单性和易用性。 本书将帮助您了解和利用最新的 TensorFlow 功能。
《TensorFlow 2.0 的新增功能》首先关注高级概念,例如新的 TensorFlow Keras API,急切执行和高效的分发策略,这些策略可帮助您在多个 GPU 和 TPU 上运行机器学习模型。 本书随后将引导您完成构建数据摄取和训练管道的过程,并提供建议和最佳实践,以将数据提供给使用新tf.keras API 创建的模型。 您将探索使用 TensorFlow 服务和其他多平台部署构建推理管道的过程,然后再继续探索新发布的 AIY(本质上是自己动手的 AI)。 本书深入研究了核心 API,可帮助您构建统一的卷积层和循环层,并使用 TensorBoard 通过假设分析来可视化深度学习模型。
到这本书的结尾,您将了解 TensorFlow 2.0 和 TensorFlow 1.x 之间的兼容性,并将能够平稳地迁移到 TensorFlow 2.0。
这本书是给谁的
如果您是数据科学家,机器学习从业人员,深度学习研究人员或 AI 爱好者,希望将代码迁移到并探索 TensorFlow 2.0 的最新功能,则适合您。 要了解本书所涵盖的概念,必须具有 TensorFlow 和 Python 编程的经验。
充分利用这本书
读者需要具有 Python 和 TensorFlow 的基础知识。
下载示例代码文件
您可以从 www.packt.com 的帐户中下载本书的示例代码文件。 如果您在其他地方购买了此书,则可以访问 www.packt.com/support 并注册以将文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
- 登录或注册 www.packt.com 。
- 选择支持选项卡。
- 单击代码下载和勘误。
- 在搜索框中输入书籍的名称,然后按照屏幕上的说明进行操作。
下载文件后,请确保使用以下最新版本解压缩或解压缩文件夹:
- Windows 的 WinRAR/7-Zip
- Mac 版 Zipeg/iZip/UnRarX
- 适用于 Linux 的 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上。
下载彩色图像
我们还提供了 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。 您可以在此处下载。
使用约定
本书中使用了许多文本约定。
CodeInText:指示文本,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄中的代码字。 这是一个示例:“将下载的WebStorm-10*.dmg磁盘镜像文件安装为系统中的另一个磁盘。”
代码块设置如下:
layer_name = tf.keras.Input( shape=None, batch_size=None, name=None, dtype=None, sparse=False, tensor=None, **kwargs)
任何命令行输入或输出的编写方式如下:
python3 -m pip --help
粗体:表示新术语,重要单词或您在屏幕上看到的单词。 例如,菜单或对话框中的单词会出现在这样的文本中。 这是一个...
一、TensorFlow 2.0 入门
本书旨在使读者熟悉 TensorFlow 2.0(TF 2.0)中引入的新功能,并在构建机器学习应用时使您发挥其潜力。 本章概述了 TF 2.0 中新的架构和 API 级别的变化。 我们将介绍 TF 2.0 的安装和设置,并将比较有关 TensorFlow 1.x(TF 1.x)的更改,例如 Keras API 和 Layer API。 我们还将涵盖丰富的扩展,例如 TensorFlow 概率,Tensor2Tensor,参差不齐的 Tensors,以及新的针对损失函数的自定义训练逻辑。 本章还总结了对层 API 和其他 API 的更改。
本章将涵盖以下主题:
- TF 2.0 的主要变化
- 适用于 TF 2.0 的推荐技术
- 使代码 TF 2.0 原生
- 常见问题
- TF 2.0 的未来
技术要求
在开始执行前人脸分中描述的步骤之前,您需要具备以下条件:
- Python 3.4 或更高版本
- 具有 Ubuntu 16.04 或更高版本的计算机(对于大多数基于 *NIX 的系统,例如 macOS 或其他 Linux 变体,说明仍然相似)
什么是新增的?
TF 2.0 的理念基于简单性和易用性。 主要更新包括使用tf.keras轻松构建模型并急切执行,可在任何平台上进行生产和商业使用的强大模型部署,强大的实验技术和研究工具,以及用于简化 API 的 API 简化。
下图简化了 TF 2.0 的新组织:
上图着重于使用 Python API 进行训练和部署; 但是,其他受支持的语言(包括 Julia,JavaScript 和 R)也遵循相同的过程。TF 2.0 的流程是...
来自 TF 1.x 的更改
TF 1.x 和 TF 2.0 之间的第一个主要区别是 API 的组织。 TF 2.0 减少了 API 结构中的冗余。 主要更改包括删除tf.app,tf.flags和tf.logging,以支持其他 Python 模块,例如absl-py和内置的日志记录功能。
tf.contrib库现在也已从主要 TensorFlow 存储库中删除。 该库中实现的代码已移至其他位置或已移至 TensorFlow 附加库。 这样做的原因是contrib模块已经超出了单个存储库中可以维护的范围。
其他更改包括删除QueueRunner模块以支持使用tf.data,删除图集合以及更改如何处理变量。 QueueRunner模块是一种向模型提供数据以进行训练的方法,但是它比tf.data复杂且难于使用,后者现在是将数据提供给模型的默认方法。 在第 3 章“设计和构造输入数据管道”中,说明了将tf.data用于数据管道的其他好处。
TF 2.0 的另一个主要变化是没有更多的全局变量。 在 TF 1.x 中,使用tf.Variable创建的变量将被放在默认图中,并且仍可以通过其名称恢复。 TF 1.x 具有各种机制,旨在帮助用户恢复变量,例如变量作用域,全局集合以及诸如tf.get_global_step和tf.global_variables_initializer之类的辅助方法。 对于 TF 中的默认变量行为,所有这些都已在 TF 2.0 中删除。
TF 2.0 安装和设置
本节介绍了使用不同方法和不同系统配置在系统上安装 TF 2.0 所需的步骤。 建议入门级用户从基于pip和virtualenv的方法开始。 对于 GPU 版本的用户,推荐使用docker。
安装和使用 PIP
对于初学者来说,pip是 Python 社区中流行的包管理系统。 如果您的系统上未安装此软件,请先安装它,然后再继续进行。 在许多 Linux 安装中,默认情况下安装了 Python 和pip。 您可以通过键入以下命令来检查是否已安装pip:
python3 -m pip --help
如果看到blurb描述pip支持的不同命令,则说明pip已安装在系统上。 如果未安装pip,您将看到一条错误消息,类似于No module named pip 。
隔离开发环境通常是一个好主意。 这极大地简化了依赖项管理并简化了软件开发过程。 我们可以使用 Python 中名为virtualenv的工具来实现环境隔离。 此步骤是可选的,但强烈建议:
>>mkdir .venv
>>virtualenv --python=python3.6 .venv/
>>source .venv.bin/activate
您可以使用pip安装 TensorFlow,如以下命令所示:
pip3 install tensorflow==version_tag
例如,如果要安装版本2.0.0-beta1,则命令应如下所示:
pip3 install tensorflow==2.0.0-beta1
最新包更新的完整列表可在这个页面中找到。
您可以通过运行以下命令来测试安装:
python3 -c "import tensorflow as tf; a = tf.constant(1); print(tf.math.add(a, a))"
使用 Docker
如果您想将 TensorFlow 安装与系统的其余部分隔离开来,则可能要考虑使用 Docker 镜像进行安装。 这将要求您在系统上安装 Docker。 可在这个页面上获得安装说明。
为了在 Linux 系统上使用不带sudo的 Docker,请执行以下安装后步骤。
TensorFlow 团队正式支持 Docker 镜像作为安装方式。 对于用户而言,这意味着可以在这里下载更新的 Docker 镜像。
使用以下命令在本地下载 Docker 镜像:...
GPU 安装
TensorFlow 的 GPU 版本的安装与 CPU 版本的过程稍有不同。 可以使用pip和 Docker 进行安装。 安装过程的选择归结为最终目标。 基于 Docker 的过程更容易,因为它涉及到安装更少的附加组件。 它还有助于避免库冲突。 但是,这可能会带来管理容器环境的额外开销。 基于pip的版本涉及安装更多的其他组件,但具有更高的灵活性和效率。 它使结果安装无需任何虚拟化即可直接在本地主机上运行。
要继续进行操作,假设您已经设置了必要的硬件,则至少需要以下软件。 NVIDIA GPU 驱动程序的链接中提供了详细的安装说明。
使用 Docker 安装
在撰写本书时,此选项仅适用于在 Linux 主机上运行的 NVIDIA GPU。 如果您遇到平台限制,那么这是一个很好的选择,因为它可以大大简化流程。 通过利用预构建的容器,还可以最大程度地减少需要安装的其他软件组件的数量。 要继续,我们需要安装nvidia-docker。 请参考以下链接以获取更多详细信息:
完成上述链接中描述的步骤后,请执行以下步骤:
- 测试 GPU 是否可用:...
使用 PIP 安装
如果您想将 TensorFlow 与 NVIDIA GPU 一起使用,则需要在系统上安装以下其他软件。 共享的链接中提供了详细的安装说明:
- CUDA 工具包:TensorFlow 支持 CUDA 10.0 )
- CUPTI 随附 CUDA 工具包
- cuDNN SDK(版本 7.4.1 或更高版本)
- (可选)TensorRT 5.0 可以改善某些模型上的推理延迟和吞吐量
一旦安装了所有先前的组件,这是一个相当简单的过程。
使用pip安装 TensorFlow:
pip3 install tensorflow-gpu==version_tag
例如,如果要安装tensorflow-2.0:alpha,则必须输入以下命令:
pip3 install tensorflow-gpu==2.0.0-alpha0
有关最新包更新的完整列表,请访问这里。
您可以通过运行以下命令来测试安装:
python3 -c "import tensorflow as tf; a = tf.constant(1); print(tf.math.add(a, a))"
使用 TF 2.0
TF 2.0 可以通过两种主要方式使用-使用低级 API 和使用高级 API。 为了在 TF 2.0 中使用低级 API,需要实现诸如tf.GradientTape和tf.function之类的 API。
编写低级代码的代码流程是定义函数内部的前向传递,该函数将输入数据作为参数。 然后使用tf.function装饰器对该函数进行注解,以便在图模式下运行它及其所有优点。 为了记录和获得前向通过的梯度,装饰器函数和损失函数都在tf.GradientTape上下文管理器中运行,可以从中计算梯度并将其应用于模型变量。
训练代码也可以使用低级 API 编写,用于...
丰富的扩展
丰富的扩展功能是 TensorFlow 中引入的一组功能,可提高用户的工作效率并扩展功能。 在本节中,我们将介绍参差不齐的张量以及如何使用它们,并且还将介绍 TF 2.0 中引入的新模块。
参差不齐的张量
当训练和服务于机器学习模型时,可变大小的数据很常见。 在不同的基础媒体类型和模型架构中,此问题始终存在。 当代的解决方案是使用最大记录的大小,对较小的记录使用填充。 这不仅效率低下,不仅在内存或存储方面,而且在计算效率方面也是如此; 例如,当处理循环模型的输入时。
参差不齐的张量有助于解决此问题。 在非常高的水平上,参差不齐的张量可以被认为是变长链表的 TensorFlow 模拟。 这里要注意的一个重要事实是,这种可变性也可以存在于嵌套大小中。 这意味着有可能...
真正的参差不齐的张量是什么?
参差不齐的张量也可以定义为具有一个或多个参差不齐的大小的张量。 换句话说,具有可变长度切片的大小。 由于最常见的用例涉及处理有限数量的记录,因此参差不齐的张量要求最外面的维度是统一的,换句话说,该维度的所有切片都应具有相同的长度。 最外部大小之前的大小可以既参差不齐,也可以统一。 总结一下这些要点,我们可以指出,参差不齐的张量的形状目前仅限于以下形式:
- 单个统一大小
- 后跟一个或多个参差不齐的大小
- 后跟零个或更多个统一大小
构造参差不齐的张量
TF 2.0 提供了大量可用于创建或返回锯齿张量的方法。 最简单的方法之一是tf.ragged.constant()。 让我们用它来创建大小为[num_sentences,(num_words)的参差不齐的张量。 请注意,我们使用圆括号来指示参差不齐的大小:
sentences = tf.ragged.constant([ ["Hello", "World", "!"], ["We", "are", "testing", "tf.ragged.constant", "."] ])print(sentences)
您应该会看到以下内容:
<tf.RaggedTensor [[b'Hello', b'World', b'!'], [b'We', b'are', b'testing', b'tf.ragged.constant', b'.']]>
也可以从带有填充元素的旧式张量或 Python 列表中创建参差不齐的张量。 这可能非常...
参差不齐的张量的基本操作
在许多情况下,参差不齐的张量可以类似于常规张量的方式使用。 TensorFlow 提供了超过 100 个支持参差不齐的张量的运算符。 这些运算符大致可分为基本数学运算符,数组运算符或字符串运算符。
以下代码块显示了添加两个锯齿张量的过程:
x = tf.ragged.constant([
[1, 2, 3, 4],
[1, 2]
])
y = tf.ragged.constant([
[4, 3, 2, 1],
[5, 6]
])
print(tf.add(x, y))
结果为以下输出:
<tf.RaggedTensor [[5, 5, 5, 5], [6, 8]]>
另一个有趣的功能是为参差不齐的张量定义了运算符重载。 这意味着程序员可以像使用其他张量一样直观地使用+, -, *, //, /, %, **, &, |, ^和>=等运算符。
以下代码块显示了使用重载运算符的参差张量的乘法:
x = tf.ragged.constant([
[1, 2, 3, 4],
[1, 2]
])
print(x * 2) # Multiply a ragged tensor with a scalar
print(x * x) # Multiply a ragged tensor with another ragged tensor
结果输出如下:
<tf.RaggedTensor [[2, 4, 6, 8], [2, 4]]>
<tf.RaggedTensor [[1, 4, 9, 16], [1, 4]]>
此外,tf.ragged包中定义了各种特定于参差不齐的张量的运算符。 可能有必要查看包的文档以了解更多信息。 请参阅以下链接以获取有关此文档的详细文档:
新的重要包
TF 2.0 的到来还伴随着 TensorFlow 下更多有趣且有用的包的到来,这些包可以单独安装。 其中一些包包括 TensorFlow 数据集,TensorFlow 插件,TensorFlow 文本和 TensorFlow 概率。
TensorFlow 数据集是一个 Python 模块,可轻松访问 100 多个数据集,从音频到自然语言再到图像。 这些数据集可以通过以下代码轻松下载并用于模型中:
import tensorflow_datasets as tfdsdataset = tfds.load(name="mnist", split=tfds.Split.TRAIN)dataset = dataset.shuffle(1024).batch(32).prefetch(tf.data.experimental.AUTOTUNE)
从该库中获取的数据集是tf.data.Dataset对象,这些对象...
总结
TF 2.0 包含许多主要更改,例如 API 清理,热切执行和面向对象的哲学。 API 清理包括弃用具有等效标准 Python 库的冗余模块,以及删除tf.contrib模块并将其重新组织到主要 API 和 TensorFlow Addons 包中。 急切的执行和面向对象的 API 使调试更加有效和直接,并且导致变量被视为普通的 Python 变量。 这意味着不再需要变量集合和其他专用于处理全局变量的 API,因此在 TF 2.0 中已将其删除。
TF 2.0 还将默认的高级 API 从 TF 1.x 中的估计器转移到 TF 2.0 中的tf.keras,以简化和扩展。 tf.keras API 具有三种不同的编程类型,每种提供不同级别的抽象和可定制性。 可以使用tf.GradientTape编写低级 TF 2.0 代码,以处理操作的梯度,而使用tf.function编写基于图的执行。
本章还介绍了安装 TF 2.0 的不同方法,包括通过pip和 Docker 进行安装,以及 GPU 版本的安装。 有许多与 TF 2.0 兼容并与之一起发布的模块,这些模块进一步增强和扩展了基本 API 的可能性。 其中包括 TensorFlow 数据集,TensorFlow 插件,TensorFlow 文本和 TensorFlow 概率。
本章还包括参差不齐的张量,这对于存储具有可变长度和形状以及分层输入的数据很有用。 这意味着参差不齐的张量对于存储语言和序列数据很有用。
在下一章中,我们将了解 Keras 的默认集成和急切执行的知识。
二、Keras 默认集成和急切执行
本章涵盖了两个高级 TensorFlow 2.0(TF 2.0)API,即 Keras 和估计器。 本章重点关注惰性求值和急切执行的概念,重点介绍如何在 TensorFlow 1.x(TF 1.x)和 TF 2.0 中求值基础计算图之间的差异 。 本章还提供了有关使用诸如 Keras 之类的高级 API 构建自定义模型(使用自定义低级操作)的详细指南。
本章将涵盖以下主题:
- TF 2.0 中的新抽象
- 深入了解 Keras API
- 估计器
- 求值 TensorFlow 图
技术要求
为了运行本章中给出的代码摘录,您将需要以下硬件和软件:
- TF 2.0 或更高版本(足够使用 CPU 或 GPU 版本)
- Python 3.4+(目前,TensorFlow 支持的最高 Python 版本是 3.6)
- NumPy(如果不是由 TensorFlow 自动安装)
可在这个页面中获得本章的代码文件。
TF 2.0 中的新抽象
抽象是在编程和软件开发过程中使用的非常流行的工具。 从非常高级的意义上讲,抽象指的是隔离和描述特定任务或一组任务的中心思想而不必指定物理,空间或时间细节的过程。 正确完成后,抽象可以显着减少针对特定任务需要编写的代码量。 它还提高了现有代码的可重用性,并使其与 TF 2.0 兼容。
在使用机器学习系统时,有一些常见的高级任务,例如训练数据,建模,模型评估,预测,模型存储和模型加载,这是常见的...
深入了解 Keras API
TF 2.0 与 Keras 的结合比以前紧密,特别是对于高级 API。 如果您刚开始在 TensorFlow 中构建基于神经网络的模型,则建议您从 Keras 开始。 简而言之,Keras 公开了用户友好的 API,用于执行常见任务,例如加载数据,构建模型,训练模型,评估模型,运行模型以及加载和保存以前的模型。 影响其灵活性的一个重要因素是,它允许您在不同的抽象级别上无缝运行。
什么是 Keras?
Keras 是用于构建和训练深度学习模型的流行的高级 API。 Keras 的核心是高级神经网络 API 规范。 它在机器学习社区中被研究人员,爱好者和软件工程师广泛使用。 它的开发着眼于实现快速实验。 它具有多种机器学习平台和编程语言的实现,例如 TensorFlow,MXNet,TypeScript,JavaScript,CNTK,Theano,PlaidML,Python,Scala 和 CoreML。 TF 2.0 包含 Keras API 规范的完整实现以及 TensorFlow 特定的增强功能和优化功能。 在tf.keras模块中可用。
Keras 是用明确的...
构建模型
机器学习从根本上讲是一系列统计计算,这些统计计算可以实现最终目的。 这些核心统计组件可以封装为模型。 此外,一些标准计算可被视为与此核心的交互。 从程序员的角度来看,将模型看成一个包含大量数学方程的黑匣子可能会很有用。 然后,其他动作可以描述为与此黑匣子的一组交互。
例如,给定一组输入记录,可以将训练模型理解为计算模型参数(或权重)的过程。 推理可以看作是一个过程,使用数学核心和学习到的参数来生成给定输入集的预测。
Keras 大致采用了我们刚刚讨论的抽象范式,以帮助用户使用基于神经网络的模型轻松地构建,训练和预测。 在随后的小节中,我们将详细介绍 Keras 为上述任务中的每一项提供的选项。 我们还将探讨使 Keras 成为不可忽视的强大力量的其他辅助功能。
在 Keras 中,模型是通过组合层来构建的。 每个 Keras 层大致对应于神经网络架构中的层。 模型也可以看作是层的组合。 Keras 提供了多种选择来组合这些层以形成基于神经网络的模型。 接下来的两个小节重点介绍 Keras 为构建模型而公开的两种最流行的 API,也称为数学和统计核心。
Keras 层 API
在用于模型构建的高级 Keras API 中,Keras 层是基本构建块。 模型通常定义为这些层的某种图形。 这些层也可以被编程为彼此交互。 由于这些是基本的构建块,因此我们可以在训练和推理阶段定义和自定义层的行为。 换句话说,我们具有在前进和后退过程中定义层行为的能力(如果适用)。 从程序员的角度来看,可以将一层视为封装状态和逻辑的数据结构,以从给定的一组输入生成特定的输出。
层...
使用顺序 API 建立简单模型
Sequential API 是 Keras 为构建模型公开的非常简单但功能强大的抽象。 如果刚开始使用 Keras,建议您使用此功能。 如果要使用单输入级模型,这也是推荐的选择。
该 API 的主要组件是tf.keras.Sequential。
这对于简单,连续的层组合很有用。 假设您有一个n层神经网络。 假设这些层定义为[layer_1, layer_2, …. , layer_n]。
请注意,这些层中的每一层都是 Keras 层,如前所述。 对于我们的实现,这意味着该层对象将是tf.keras.layers中公开的层之一,或者是对基础 Keras 层实现进行子类化的用户定义层。
可以使用tf.keras.Sequential实例的add()方法合并组成层。
按顺序组合它们的一般形式如下:
my_model = tf.keras.Sequential()
my_model.add(layer_1)
.
.
my_model.add(layer_n)
假设您要建立一个描述全连接神经网络的模型(也称为多层感知器(MLP)),以对具有五个属性的一维记录进行二分类。 我们的模型包括四个全连接层。 纯粹出于说明目的,我们假设每个全连接层包含 10 个节点或神经元。 这些层中的每一层都使用整流线性单元(ReLU)激活函数。 最终输出通过softmax层获取。 可以在相应层的构造器中定义特定于层的自定义。 实现此模型的代码如下:
model = tf.keras.Sequential()
# Adds a densely-connected layer with 10 units and rectified linear unit activations
# Accepts multiple input tensors of size 5 from user
model.add(layers.Dense(10, activation='relu', input_shape=(5,)))
# Add layer 2 with 10 units and relu activations:
model.add(layers.Dense(10, activation='relu'))
# Add layer 3 with 10 units and relu activations:
model.add(layers.Dense(10, activation='relu'))
# Add layer 4 with 10 units and relu activations:
model.add(layers.Dense(10, activation='relu'))
# Add a softmax layer with 2 output units:
model.add(layers.Dense(2, activation='softmax'))
使用Sequential API 的另一种方法是提供列表中的所有层,或者通常提供某种迭代器。 这些可以在初始化模型对象时传递给Sequential()构造器。 这在分隔层描述和模型创建任务时特别有用。 让我们看下面的示例,以更好地理解这一点。
考虑一下尝试从以下这些层的列表中生成模型的示例:layer_list =[layer_1, layer_2, …. , layer_n]。 现在可以通过将layer_list对象直接传递给构造器来创建模型,如下所示:
new_model = tf.keras.Sequential(layer_list)
值得注意的是,前面的语句等同于下面的语句:
new_model = tf.keras.Sequential(layers=layer_list)
这也可以用其他方式使用。 一个示例是将层规范和模型创建过程分开。 让我们进一步探讨这个想法。 假设您有一个用例,其中模型需要多个仅在运行时可用的层。
一种简单的方法是编写一个用于创建层的函数。 让我们编写一个示例函数get_layers(n),它使用整数值n并一个接一个地返回许多Dense层。 为了说明 API 的灵活性,让我们使用 Python 生成器实现该函数:
def get_layers(n):
while n > 0:
yield tf.keras.Dense(10, activation='relu')
n -= 1
如果您不熟悉 Python 生成器,请在继续操作之前参阅这里。
前一个代码块中定义的函数接受n的正整数值并返回generator对象。 此生成器生成的每个元素都是一个层。 以下代码段显示了如何使用此函数创建模型:
model_using_generator = tf.keras.Sequential(layers=get_layers(10))
使用函数式 API 建立高级模型
随着机器学习任务的日益成熟,具有多阶段输入和输出的模型变得越来越普遍。 大量实际使用案例涉及具有多阶段输入和输出的模型。 具有多个输入的真实世界模型的一个示例是文本分类模型,该模型可以查看输入文本中的单词和字符序列。
尽管Sequential API 在以串行方式组合层方面做得非常好,但是它不能用于描述基础层的并行组成。 通常,它不能用于构建不具有线性拓扑的层图。 在需要利用特定层的情况下,其实用性也受到限制。
训练模型
训练模型指的是为不同网络组件学习权重的过程,这些过程在给定的一组示例中将损失函数降至最低。 简而言之,训练神经网络意味着找到网络值的最佳组合。 如您所知,训练过程也与评估和预测过程紧密相关。 借助抽象的强大功能,Keras 提供了强大的高级接口来实现和管理端到端的训练过程。 让我们看一下它为使用顺序和函数式 API 创建的模型提供的训练 API。 它为此阶段提供的一些函数如下:
model.compile():此函数用于配置训练过程。 用户指定详细信息,例如优化器的类型(以及超参数(如果有的话)),损失函数的类型以及要评估的指标。 这些也是可以使用 TensorBoard 可视化的指标。 下面的示例代码片段描述了一个带有随机梯度下降(SGD)优化器,CategoricalCrossentropy损失函数和记录Accuracy指标的样本训练配置:
model.compile(
# Optimizer
optimizer=tf.keras.optimizers.SGD(),
# Loss function to minimize
loss=keras.losses.CategoricalCrossentropy(),
# List of metrics to monitor
metrics=[
keras.metrics.SparseCategoricalAccuracy()
]
)
model.fit():此方法用于提供训练数据并控制实际训练过程。 此方法中的一些重要参数和参数是训练记录,训练标签,训练周期数和训练批量大小。 以下样本片段描述了一个样本训练过程,该过程用于在训练记录(train_x)和训练标签(train_y)上以批号32训练30周期的预定义模型:
model.fit(
x=train_x,
y=train_y,
epochs=30,
batch_size=32
)
model.evaluate():如前所述,训练和评估过程是相互联系的,并且紧密相连。 训练神经网络需要经常更新权重,以找到最佳的权重集。 为此,有必要在当前阶段计算某种类型的网络状态。 此过程称为评估。 更具体地说,评估是针对给定数据集在当前阶段计算网络的损失和其他指标的过程。 请记住,此方法执行的计算是分批执行的。 该函数返回与损失函数相对应的标量。 它还返回与model.compile()阶段中提供的任何度量对应的值。 以下代码段描述了一个评估函数,该函数以批量大小32来计算记录(test_x)和标签(test_y)上的度量:
results = model.evaluate(
test_x,
test_y,
batch_size=32
)
保存和加载模型
训练后,保存模型以备后用可能非常有用。 在许多用例中,将训练和推理管道分离是一个好主意。 从开发人员的角度来看,模型可以抽象为一个黑匣子,该黑匣子接受一组输入并返回一些输出。 这样,保存模型只不过是导出表示该黑匣子的工件。 然后,还原或加载模型成为使用此黑匣子执行一些实际工作的过程。 这也可以理解为序列化和反序列化模型黑匣子的过程。
TF 2.0 支持以多种模式保存和恢复模型:
- 仅模型架构(Keras)
- 仅模型权重(Keras)
- 整个模型:...
分别加载和保存架构和权重
在某些用例中,将模型创建和模型初始化步骤分离是有意义的。 在这种情况下,模型序列化将需要使用单独的过程来加载和保存架构和模型权重。 Keras 为用户提供支持,以独立使用架构和权重。
加载和保存架构
在tf.Keras Python API 中,架构交换的基本单元是 Python dict。 Keras 模型使用get_config()方法从现有模型生成此dict。 然后可以使用标准的 Python 序列化和反序列化方法(例如 Pickle 或 HD5)将此dict保存到磁盘或任何其他存储介质中。 您也可以将 Python dict直接写入磁盘上的文件。
假设您要将 Keras 模型的架构my_model保存到磁盘。 以下代码段说明了如何执行此操作:
my_model_architecture = my_model.get_config()
现在,您可以使用选择的方法将此 Python dict保存到磁盘。
对于从配置对象生成模型的逆用例,...
加载和保存权重
在 Python API 中,tensorflow.keras使用 NumPy 数组作为权重交换的单元。 这与用于加载和保存架构的 API 非常相似。 这些 NumPy 数组也可以使用原生 Python 技术保存到磁盘中。 get_weights()和set_weights()方法大致类似于get_config()和from_config()。 前者返回对应于模型中不同层的 NumPy 数组列表。 后者接受 NumPy 数组列表并更新内存中的模型。
以下代码段说明了如何获取现有模型的权重:
my_model_weights = my_model.get_weights()
给定一组权重和一个模型副本,可以按以下步骤执行更新内存中模型权重的逆操作:
replica_my_model.set_weights(my_model_weights)
如我们所见,可以使用get_config()或get_weights()与from_config或set_weights()的组合存储整个模型。 但是,此过程的局限性在于它不存储有关训练过程的任何信息。
为了更好地理解这一点,让我们看一个例子。 考虑一个具有一个输入层,一个隐藏层和一个输出层的简单模型。 然后,我们将仅使用上一节中讨论的方法来创建此模型的副本。 步骤如下:
- 首先,让我们使用
functionalAPI 定义此模型:
# Define layer chain
input_layer = tf.keras.layers.Input(shape=(5,))
hidden_layer = tf.keras.layers.Dense(10)(input_layer)
output_layer = tf.keras.layers.Dense(5, activation='softmax')(hidden_layer)
# Define Model
my_model = tf.keras.Model(inputs=input_layer, outputs=output_layer)
- 这里的目标是创建先前模型的副本。 为此,让我们创建模型架构和模型权重的副本:
# Save architecture
my_model_arch = my_model.get_config()
# Save weights
my_model_weights = my_model.get_weights()
- 要创建我们的副本模型,我们必须创建一个 Keras 模型,其架构与源模型相同:
# Create replica using saved architecture
my_model_replica = tf.keras.Model.from_config(my_model_arch)
- 接下来,我们将权重从源模型复制到模型副本:
# Copy saved weights
my_model_replica.set_weights(
my_model_weights
)
如您所见,我们已经使用save API 成功创建了源模型的副本。 此外,我们已经通过使用前面介绍的加载 API 将其重新加载到单独的对象内存中进行了测试。 换句话说,我们已经使用load和save API 创建了模型的副本。
保存和加载整个模型
上一节中描述的过程的主要限制之一是它不包括训练过程。 这可能是用例中的主要障碍,这些用例涉及训练过程中某个时刻的检查点。 为了克服它,TensorFlow 可以完整保存模型。 这主要可以通过两种方式实现-使用 Keras API 或使用SavedModel API。
在以下各节中,我们将简要讨论方法及其语法。 我们还提供了有关何时使用它们的见解。
使用 Keras
可以将使用Sequential API 或functional API 构建的模型保存在单个文件中。 也可以从此文件中加载此模型,而与构建模型所用的代码无关。
该文件包括以下内容:
- 模型的架构
- 模型的权重值(如果适用,还包括训练中获得的权重)
- 优化器及其状态(如果有的话)(可用于从特定点恢复训练)
- 模型的训练配置(已传递来编译)(如果有)
使用Sequential或functional API 创建的 Keras 模型可以直接保存到磁盘。 使用 Keras 的本机 HDF5 文件格式保存文件。 实现此目的的代码的一般形式如下:
model.save('file_name.h5')
可以使用简单的 Python 单一代码将该模型重新加载到内存中。 通用格式如下:
loaded_model = tf.keras.models.load_model(
'path_to_model.h5'
)
这是一种非常直接的方法,在 Python API 中交换模型时效果很好。
使用SavedModel API
SavedModel是在 TensorFlow 生态系统中存储对象的默认方式。 由于这种标准化的性质,它可以用于在不同的 TensorFlow 实现之间交换模型。 使用SavedModel保存的模型除包含模型架构和权重外,还包含实际的 TensorFlow 代码。 SavedModel文件的确切内容可以列出如下:
- 一个包含模型权重的 TensorFlow 检查点
- 包含底层 TensorFlow 图的
SavedModel原型:- 默认情况下,为预测阶段保存了单独的图(训练和评估阶段也分别在适用时存储)
- 模型的架构配置(如果有)
在 Python API 中,与SavedModel ...进行交互
其它功能
除了非常强大的 API 规范外,TensorFlow 的tf.keras Keras 实现还附带了许多附加组件。 在以下各节中,我们将简要讨论其中最相关的两个。
keras.applications模块
keras.applications模块包含具有流行模型权重的预构建架构。 这些可以直接用于进行预测。 用户还可以使用它们来创建其他网络的输入特征。 该包中突出的预建实现包括:
densenet module:Keras 的 DenseNet 模型inception_resnet_v2:Keras 的 Inception-ResNet V2 模型inception_v3:适用于 Keras 的 Inception V3 模型mobilenet:Keras 的 MobileNet v1 模型mobilenet_v2:Keras 的 MobileNet v2 模型nasnet:Keras 的 NASNet-A 模型resnet50:用于 Keras 的 ResNet50 模型vgg16:适用于 Keras 的 VGG16 模型vgg19:适用于 Keras 的 VGG19 模型xception:适用于 Keras 的 Xception V1 模型
每个...
keras.datasets模块
keras.datasets模块包括自动化功能,可以从文件中解析某些流行数据集的数据。 如果本地没有这些文件,它还包括自动通过互联网下载这些文件的功能。 这使用户可以更轻松,更快捷地试验和评估不同的模型。 对于某些用例,此模块可以代替整个数据处理阶段! Keras 随附的各种数据集模块包括以下内容:
boston_housing:波士顿房屋价格回归数据集cifar10:CIFAR10 小图像分类数据集cifar100:CIFAR100 小图像分类数据集fashion_mnist:Fashion-MNIST 数据集imdb:IMDB 情感分类数据集mnist:MNIST 手写数字数据集reuters:路透社主题分类数据集
列出的每个数据集都是一个 Python 模块。 可在这个页面中找到其组件的详细列表。
端到端顺序示例
现在,让我们使用上一节中讨论的 Keras API 的组件来完成一个小的实际任务。 让我们使用Sequential API 构建神经网络,以对 MNIST 数据集中的手写数字进行分类。 步骤如下:
- 在开始编写任何函数代码之前,我们需要将
tensorflow和keras导入内存:
import tensorflow as tfimport tensorflow.keras as keras
- 然后,让我们开始将数据集加载到内存中。 为此,请使用前面章节中讨论的
keras.datasets模块:
# Load Data(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
- 在前面的代码段中,数据作为
numpy数组加载到内存中。 ...
估计器
从头开始构建机器学习模型时,从业人员通常会经历多个高级阶段。 其中包括训练,评估,预测和装运,以供大规模使用(或导出)。 到目前为止,开发人员必须编写自定义代码才能实现这些步骤中的每个步骤。 在所有应用中,运行这些过程所需的许多样板代码都保持不变。 更糟的是,此代码很容易需要在低抽象级别上进行操作。 这些问题放在一起,可能会在开发过程中造成极大的效率低下。
TensorFlow 团队尝试通过引入 Estimators 来解决此问题,Estimators 是一个高级 API,旨在抽象出在上述阶段执行不同任务时产生的许多复杂性。 具体来说,估计器是用于封装以下类别任务的高级 API:
- 训练
- 评价
- 预测
- 模型共享(导出和运输模型)
用户可以从一组预先构建的估计器中进行选择,甚至可以实现自己的估计器。 标准库中提供了针对各种常用机器学习和深度学习算法的估计器的实现。
估计器具有以下优点:
- 基于估计器的模型与硬件和环境无关:
- 程序员不必担心 Estimator 是在本地计算机上运行还是在远程计算网格上运行。
- 程序员可以在 CPU,GPU 或 TPU 上运行基于 Estimator 的模型,而无需重新编码他们的模型。
- 估计器简化了团队中不同开发人员之间或使用不同环境或栈的团队之间的共享实现。
- 程序员可以使用高级直观代码来开发高性能和前沿模型。 换句话说,程序员不必在管理低级 TensorFlow API 的复杂性上浪费时间。
- 估计器建立在
tf.keras.layers本身上,从而简化了自定义。 - 估计器为您构建图。
- 估计器提供了一个安全分布的训练循环,该循环控制如何以及何时执行以下操作:
- 建立图
- 初始化变量
- 加载数据
- 处理异常
- 创建检查点文件并从故障中恢复
- 为 TensorBoard 保存摘要
- 使用 Estimators 编写应用时,程序员可以灵活地将数据输入管道与模型分开。 通过这种分离,可以轻松地尝试使用不同的数据集和不同的数据源。
在 TF 2.0 中,Keras 已经提供了 Estimators 公开的许多功能。 如果您只是入门,那么 Keras 是一个更容易学习的 API。 建议初学者在评估器上使用 Keras API。 一旦用例需要使用 Estimators,就可以查找并了解更多信息。 有关详细指南,请访问这里。
求值 TensorFlow 图
TensorFlow 的中心思想是,要求程序员创建计算图以指定需要执行的操作才能获得所需的结果。 然后,程序员指定了硬件和其他环境参数,以针对给定的一组输入来计算此计算图的输出。 这意味着在程序员明确计算图之前,值和变量没有任何值。 当程序员真正想要的只是数量的值时,这增加了程序员创建和管理会话的开销。
TF 2.0 旨在通过更改求值和计算基础计算图的方式来解决此问题。 用一个句子,TF ...
延迟加载与急切执行
延迟加载是一种编程范例,其中直到实际需要数量才计算数量的值。 换句话说,在没有明确请求之前,不会初始化对象。 这样做的主要好处是,当按需计算数量值时,无需使用额外的内存来存储计算结果。 如果正确使用,这将导致非常有效的内存使用并提高速度。
急切执行可以理解为与延迟加载相反。 在此,数量的值一定义就立即计算,而不必等到它被调用。 这意味着当实际请求数量时,该值从内存中返回,而不是从头开始计算。 这有助于最小化返回查询结果所需的时间,因为用户不必等待计算值所花费的时间。
可以通过添加两个常量的简单操作来说明两者之间的区别:a和b。 首先,让我们看一下 2.0 之前的 TensorFlow 版本。 这些要求用户定义一个计算图,然后使用会话来运行和求值该图。 这可以理解为延迟加载的示例。 让我们看一下以下代码片段,以获得更好的主意:
# Define constants
a = tf.constant(10)
b = tf.constant(32)
# Define add operation
c = a + b
print(f"Value outside session: {c}”)
这给出以下输出:
Outside session: Tensor("add_1:0", shape=(), dtype=int32)
在此阶段,我们可以看到c的值(即add对两个常量进行运算的结果)实际上是张量,没有实际数值。 因此,我们可以看到该图已构建但尚未求值。 为了获得加法运算的实际数值结果,我们必须定义一个会话来运行和求值基础图:
# Create a session and run graph in it
with tf.Session() as sess:
print(f"Value inside Session: {c}”)
您将看到以下输出:
Value inside Session: 42
这表明添加操作仅在会话中运行后才进行求值。
现在,让我们尝试使用 TF 2.0 及更高版本的相同示例。 我们用相同的变量名称和值定义两个常量。 我们还定义了第三个变量来保存加法的结果。 然后,我们在紧接之后打印加法的值:
# Define constants
a = tf.constant(10)
b = tf.constant(32)
#Define add operation
c = a + b
print(f"Value outside session: {c}")
结果输出如下:
Value outside session: 42
如我们所见,此阶段的输出在 TensorFlow 2.0+ 和 <2.0 版本之间有所不同。 在这种情况下,c变量已经包含加法运算的值。 无需程序员求值任何计算图即可进行计算。 换句话说,加法操作急切地执行。 这是 2.0 及更高版本与旧版本之间的主要区别。
TF 2.0 与 Python 编程语言紧密集成。 急切的执行使张量可以无缝用作本机 Python 对象,而不必担心求值计算图以及管理会话或基础硬件。 好处不止于此。 急切的执行使程序员能够利用宿主编程语言的强大控制流结构。 TensorFlow 代码现在与平台的其余部分更加直观地集成,这为开发人员带来了巨大的价值,因为它不再需要特殊的流控制结构。 这也为实验,调试和笔记本环境增加了重要价值。
总结
在本章中,我们了解了 TF 2.0 中可用于模型构建,训练,保存和加载的高级抽象。 深入研究 Keras API,我们了解了如何通过使用Sequential和functional API 组合层来构建模型。 我们还了解了如何利用 Keras API 的高级抽象来训练模型。 本章还研究了在各种配置和模式下加载和保存模型的复杂性。 我们已经了解了保存模型,架构和权重的不同方法,本章对每种方法进行了深入的说明,并描述了何时应该选择一种方法。
将讨论的所有概念放在一起...
三、设计和构建输入数据管道
本章将概述如何构建复杂的输入数据管道,以使用由TFRecords组成的tf.data API 来以最常见的格式(例如 CSV 文件,图像,文本等)提取大型训练/推理数据集。 和tf.data.Dataset方法。 您还将获得有关协议缓冲区,协议消息以及如何使用 TensorFlow 2.0(TF 2.0)中的TFRecords和tf.Example方法实现的一般概念。 本章还说明了在数据的混洗,批量和预取方面使用tf.data.Dataset方法的最佳实践,并针对 TF 2.0 提供了建议。 最后,我们将讨论内置的 TensorFlow 数据集...
技术要求
您应该了解标准数据格式,例如 CSV 文件,图像(PNG 和 JPG)和 ASCII 文本格式。 不用说,本书的大多数章节都假定您了解基本的机器学习概念,Python 编程,numpy Python 模块,并且您已使用 TensorFlow 创建了一些机器学习模型。 尽管不是必需的,但熟悉 TensorFlow 1.x(TF 1.x)版本的tf.data API 会有所帮助。 即使您没有tf.data API 的先验知识,您也应该发现本章可以自学以了解它们。
本章中的某些主题需要 Python 模块,例如argparse和tqdm,这些模块已在本书的 GitHub 存储库中列出。 可在这个页面中获得本章的代码。
设计和构建数据管道
训练机器学习(ML)模型和深度神经网络(DNN)时,最重要的要求之一,是在给定的样本空间中具有同分布的大型训练数据集(通常是未知的,我们在 ML 或 DNN 训练中了解到),以便 ML 模型和 DNN 可以从给定的训练数据中学习,并很好地推广到看不见的未来或分离出来的测试数据。 此外,通常与训练集分布来自同一来源的验证数据集对于微调模型超参数至关重要。 在许多情况下,开发人员会从可用的数据(无论是少量还是大量)入手,以训练机器学习模型,包括大容量的深度学习...
原始数据
用于训练 ML 模型的原始数据可以是文本文件,CSV 文件,图像,视频或自定义格式的文件。 原始数据甚至可以是这些文件类型的组合。 原始数据也可以是有序数据,例如时间序列数据,或者,它甚至可以是文本的向量表示,例如单词嵌入。 重要的是要确保在将原始输入数据输入模型之前对其进行管理,因为它会影响运行时模型训练的效率。
在许多情况下,原始数据可以存储在数据库中,例如 MySQL,MS SQL,MongoDB 等。 就本书而言,假设甚至表格数据,SQL 或 NoSQL 数据都是原始数据,并且出于机器/深度学习模型的目的,需要将其拆分并转换为TFRecords。 解释 SQL 和 NoSQL 数据库超出了本书的范围。
将数据拆分为训练,验证和测试数据
ML 模型训练的数据准备的关键特征之一是能够将现有数据分为训练,验证和测试集。 训练数据是已看到并用于拟合或训练模型的数据; 例如,神经网络的学习权重和偏置。 验证数据(有时称为开发数据)用于微调模型的超参数,例如学习率,要使用的优化程序等等。 模型会经常查看此数据(例如,在每次迭代或新周期之后)并评估模型。
请注意,验证数据仅可帮助您微调模型。 它不会更新权重和偏置。
最后,测试数据是...
创建TFRecords
TFRecords 的创建是输入数据管道的核心,因此您可以创建tf.data.Dataset对象。 值得注意的是,您可以直接使用原始数据创建数据集,而无需创建TFRecords(将在下一部分中进行说明)。 但是,推荐的方法是首先从原始(拆分)数据创建TFRecords,然后将其用于数据集管道。 这是 TF 2.0 输入数据管道设计的关键部分。 下图显示了TFRecords的创建流程:
TFRecords通过将数据序列化到磁盘来帮助我们有效地读取数据,并且可以存储在一组TFRecords文件中。 每个文件的建议大小为 100 MB 到 200 MB。 应该注意的是TFRecord是可以存储任何类型数据的二进制格式。 由于是二进制格式,因此它占用的磁盘空间更少,并且从磁盘存储进行复制或读取所需的时间也更少。 当训练数据太大而无法存储在内存服务器,GPU 和/或 TPU 中时,还需要TFRecords。 使用带有数据集的TFRecords,可以按批形式从磁盘按需加载数据(将在本章稍后的批量中对此进行解释) 部分)。
TFRecords有四个重要组成部分:
TFRecord格式,用于存储二进制记录或数据序列。- 协议缓冲区是跨平台的,并且具有跨语言库,用于以协议消息的形式对结构化数据进行有效的序列化。
- 协议消息是信息的小型逻辑记录,其中包含一系列名称/值对。
tf.Example是一种灵活的协议消息(也称为protobuf),旨在与 TensorFlow 一起使用。 TensorFlow 扩展(TFX)是 TF 2.0 中的另一个重要功能,用于部署生产级 ML 管道,我们将在第 5 章,“模型推理管道–多平台部署”中进行学习。
请注意,在 TF 2.0 中,tf.Examples已在诸如 TFX 的所有 TensorFlow 高级 API 中使用。。
现在,让我们看看如何将数据存储在TFRecords中。 如前所述,任何转换为TFRecords格式的数据都存储为二进制字符串序列。 您可能会猜到,必须先指定数据结构,然后才能从tfrecord文件读取或写入数据。 为了读取和写入tfrecords文件,我们需要使用tf.Example协议消息。 请注意,数据中包含的每条小信息都必须使用Etf.Example进行存储。 此外,为了将信息写入磁盘,使用了tf.io.TFRecordWriter。 要从磁盘读回信息,您可以使用tf.io.TFRecordReader。
TensorFlow 协议消息 - tf.Example
tf.Example是{'string':tf.train.Feature}映射(Python 词典),其中'string'可以是任何名称; 例如'image','features'或'label'。
tf.train.Feature可以是以下三种类型之一:
tf.train.BytesList:用于string或byte信息tf.train.FloatList:用于float或double信息tf.train.Int64List:用于bool,enum和所有整数,例如int32,uint32,int64,uint64等
通过使用以下快捷函数转换标准 TensorFlow 类型,可以将tf.Example消息序列化,写入和读取到tfrecords文件中:
以下代码块中的函数可用于将值转换为...
tf.data数据集对象创建
如我们前面提到的,tf.data API 集提供了从原始数据构建复杂而有效的输入数据管道的工具。 例如,输入管道可以从分布式文件系统的图像文件构建。 如果您使用的是自然语言处理(NLP)模块,也可以从原始文本数据构建它。 下图显示了tf.dataset对象创建的流程:
tf.data.Dataset是tf.data API 集的主要类,代表一系列元素,其中每个元素包含一个或多个张量对象。 数据集有四种主要类型,如下图所示:
在本章中,为简单起见,所有四种类型的数据集都称为数据集和/或tf.data.Dataset。 在需要时将引用显式类型。
从定义上讲,tf.data.Dataset是一个或多个张量对象的元素序列,称为分量; 数据集中的每个元素都具有相同的结构。 要检查数据集的类型和形状,开发人员可以使用两个 Python API tf.data.Dataset.output_types和tf.data.Dataset.output_shapes,如以下代码块所示:
# Check type and shape of Dataset
dataset = tf.data.Dataset.from_tensor_slices(...)
print(dataset.output_types)
print(dataset.output_shapes)
前面的代码是构建图像数据管道的示例。 数据集的元素可以是单张训练数据,由一对图像和标签张量组成。
在 TF 2.0 中,数据集对象是 Python iterables,这与 TF 1.x 版本的关键区别在于 TF 1.x 版本需要tf.data.Iterator来遍历数据集对象。 以下代码显示了在 TF 1.x 和 TF 2.0 中迭代数据集对象之间的区别:
# The following code shows difference in iterating Dataset objects
# in TensorFlow 1.x and TensorFlow 2.0
dataset = tf.data.Dataset.from_tensor_slices(...)
dataset = dataset.shuffle(...)
dataset = dataset.map(...)
dataset = dataset.batch(...)
# TensorFlow 1.x (using one shot iterator, get_next)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
with tf.Session() as sess:
for _ in range(...):
element = sess.run(next_element)
...
# TensorFlow 2.0 (extremely simple where Datasets are Python iterables)
for element in dataset:
...
正如您在前面的代码块中看到的那样,现在遍历数据集对象非常简单。
创建数据集对象
可以使用两种主要方法创建数据集对象:
- 从源创建:
- 来自内存中的
numpy/tensorflow对象 - 使用
TFRecords来自磁盘
- 来自内存中的
- 将转换应用于现有数据集:
- 从一个或多个数据集构造一个数据集。 这将在“数据集转换”部分中更详细地说明。
由于建议使用TFRecords创建一个tf.data.Dataset,让我们看看它是如何工作的。 然后,我们将介绍从其他类型的输入创建数据集的方法。
使用 TFRecords 创建数据集
创建一些TFRecords后,我们可以直接使用tf.data.Dataset API 读取它们。 以下是使用TFRecords创建数据集的框图:
您可以使用以下代码从数据集中读取tfrecords文件:
# You can read tfrecord files as below
dataset = tf.data.TFRecordDataset(tfrecords_file_names)
使用内存中的对象和张量创建数据集
从内存中对象创建tf.data.Dataset的最简单方法是使用from_tensor_slices()方法,该方法相对于数据中的第一个索引对数组进行切片。 我们将在此处引用tf.data.Dataset.map() API,该 API 在“数据集转换”部分中详细定义。 就目前而言,map(...)仅表示正在基于应用于数据集对象的每个元素的某些函数来修改(转换)数据集。
您可以使用两种 API 从内存中的张量创建数据集:
tf.data.Dataset.from_tensors()tf.data.Dataset.from_tensor_slices()。
您可以在这个页面中查看示例代码。
不使用 TFRecords 直接使用其他格式创建数据集
如前所述,您可以使用所有不同的文件格式直接创建tf.data.Dataset。 我们还解释了创建TFRecords的推荐方法。 但是,如果您想直接创建数据集而不经过TFRecords,那也是可能的。 以下是一些直接从原始数据创建tf.data.Dataset的示例:
- 使用 CSV 文件:
您可以使用td.data.experimental.make_csv_dataset(...) API 查找.csv文件。 您可以如下定义.csv文件和batch_size中可用的列。 完整的代码可以在这个页面中找到:
csv_file = "./curated_data/train.csv"
csv_columns = ['square_ft', 'house_type', 'price']
dataset = tf.data.experimental.make_csv_dataset(csv_file, column_names=csv_columns, batch_size=8)
如果需要从 CSV 文件中选择几列,则可以使用select_columns参数来完成。 有关更详细的概述,请参阅 tensorflow.org 。
- 使用文本数据:
tf.data.TextLineDataset(...) API 旨在从文本文件创建数据集。 这主要用于文本数据,其中每一行包含一个数据样本。 一些示例包括日志消息,问题答案等。 我们将使用与上一节相同的示例向您展示如何使用文本数据创建tf.data.Dataset。 完整代码可在这个页面中找到:
def train_decode_line(row): cols = tf.io.decode_csv(row, record_defaults=[[0.], ['house'], [0.]]) myfeatures = {'sq_footage':cols[0], 'type':cols[1]} mylabel = cols[2] #price
return myfeatures, mylabel
def predict_decode_line(row):
cols = tf.decode_csv(row, record_defaults=[[0.], ['house']])
myfeatures = {'sq_footage':cols[0], 'type':cols[1]}
return myfeatures
line_dataset = tf.data.TextLineDataset('./curated_data/train.csv')
train_dataset = line_dataset.map(train_decode_line)
- 使用图像:
最常见的输入数据管道之一是图像,可以是.jpeg或.png格式。 您的数据集中可能有成千上万的图像。 由于硬件内存(CPU 内存或 GPU 内存)的限制,我们无法将所有图像存储到内存中。 tf.data.Dataset提供了构建此管道的有效方法。
在以下示例中,我们有几个.jpeg/.jpg文件,我们将使用它们全部创建tf.data.Dataset。 您可以在这个页面中找到更多详细信息:
# Get images files
file_pattern = ["./curated_daimg/*.jpeg", "./curated_daimg/*.jpg"]
image_files = tf.io.gfile.glob(file_pattern)
# Get labels
labels = []
for img_path in image_files:
labels.append(get_label(img_path))
# preprocess images
def preprocess_image(img_path, label):
img_data = tf.io.read_file(img_path)
feat = tf.image.decode_jpeg(img_data, channels=3)
feat = tf.image.convert_image_dtype(feat, tf.float32)
return feat, label, img_path
# Create dataset of all image files
image_path_dataset = tf.data.Dataset.from_tensor_slices((image_files, labels))
# Convert to image dataset
image_dataset = image_path_dataset.map(preprocess_image)
- 使用多个数据集:
我们还可以使用tf.data.Dataset.map(),tf.data.Dataset.zip()和tf.data.Dataset.concatenate() API 从现有数据集中创建数据集。 这些将在下一节中解释,我们将在其中讨论数据集的转换。
转换数据集
创建数据集对象后,需要根据模型要求对其进行转换。 下图显示了数据集转换的流程:
一些最重要的转换如下:
- 数据重排:选择部分数据而不是获取整个数据集可能需要这些。 它们对于使用数据子集进行实验很有用。
- 数据清除:这些非常重要。 就像清除日期格式(例如从
YYYY/MM/DD到MM-DD-YYYY)或删除具有缺失值或错误数字的数据一样简单。
map函数
此转换 API 在数据集的每个元素上执行map_func输入。 对于那些使用 PandasDataframe.apply(...)的人来说,map(...)与之非常相似。 作为map(...) API 的自变量,它采用了一个应用于数据集每个元素的函数。 该函数继而从输入数据集中获取表示单个元素的tf.Tensor对象,并返回新转换的tf.Tensor对象。 请注意,输出中元素的顺序与输入数据集的顺序相同:
ds = tf.data.Dataset.range(1, 6) # [1, 2, 3, 4, 5]
ds.map(lambda x: x + 1)
根据数据集中每个元素的结构,正确定义map_func的输入签名非常重要:
a = [1, 2, 3, 4, 5]
ds = tf.data.Dataset.from_tensor_slices(a)
result = a.map(lambda x: ...)
b = [(2, 1), (3, 5), (6, 6)]
ds = tf.data.Dataset.from_tensor_slices(b)
def map_func(input):
output1 = input[0] + 1
output2 = input[1] + 2
return output1, output2
ds=ds.map(map_func)
flat_map函数
此转换将map_func输入映射到输入数据集并展平结果。 这用于确保数据集的顺序保持不变。 map_func必须在此处返回数据集:
a = Dataset.from_tensor_slices([ [1, 2, 3], [4, 5, 6], [7, 8, 9] ])a.flat_map(lambda x: Dataset.from_tensor_slices(x + 1)) # ==># [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
zip函数
该 API 与 Python 的内置zip(...)函数相似。 Python 的zip(...)函数和tf.data.Dataset.zip(...)函数之间的区别在于,后者可以采用数据集的嵌套结构:
a = Dataset.range(1, 4) # ==> [ 1, 2, 3 ]
b = Dataset.range(4, 7) # ==> [ 4, 5, 6 ]
c = Dataset.range(7, 13).batch(2) # ==> [ [7, 8], [9, 10], [11, 12] ]
d = Dataset.range(13, 15) # ==> [ 13, 14 ]
# The nested structure of the `datasets` argument determines the
# structure of elements in the resulting dataset.
Dataset.zip((a, b)) # ==> [ (1, 4), (2, 5), (3, 6) ]
Dataset.zip((b, a)) # ==> [ (4, 1), (5, 2), (6, 3) ]
# The `datasets` argument may contain an arbitrary number of
# datasets.
Dataset.zip((a, b, c)) # ==> [ (1, 4, [7, 8]),
# (2, 5, [9, 10]),
# (3, 6, [11, 12]) ]
# The number of elements in the resulting dataset is the same as
# the size of the smallest dataset in `datasets`.
Dataset.zip((a, d)) # ==> [ (1, 13), (2, 14) ]
concatenate函数
此转换 API 通过将输入数据集与此数据集连接来创建新的数据集:
a = tf.data.Dataset.range(1, 4) # ==> [ 1, 2, 3 ]
b = tf.data.Dataset.range(4, 8) # ==> [ 4, 5, 6, 7 ]
c = a.concatenate(b) # ==> [ 1, 2, 3, 4, 5, 6, 7 ]
interleave函数
该 API 使用map_func转换数据集的每个元素,并交织结果。 例如,您可以使用Dataset.interleave()同时处理许多输入文件:
# Preprocess 4 files concurrently, and interleave blocks of 16 records from
# each file.
filenames = ["/var/data/file1.txt", "/var/data/file2.txt", ...]
dataset = (Dataset.from_tensor_slices(filenames)
.interleave(lambda x:
TextLineDataset(x).map(parse_fn, num_parallel_calls=1),
cycle_length=4, block_length=16))
cycle_length和block_length参数控制元素生成的顺序。 cycle_length控制同时处理的输入元素的数量。 例如,如果将cycle_length设置为 1,则此转换将一次处理一个输入元素,并将产生与tf.data.Dataset.flat_map相同的结果。 通常,此转换会将map_func应用于cycle_length输入元素,在返回的数据集对象上打开迭代器,并对其进行循环,从每个迭代器生成block_length连续元素,然后在每次到达迭代器的末尾时就使用下一个输入元素:
a = Dataset.range(1, 6) # ==> [ 1, 2, 3, 4, 5 ]
# NOTE: New lines indicate "block" boundaries.
a.interleave(lambda x: Dataset.from_tensors(x).repeat(6),
cycle_length=2, block_length=4)
# ==> [1, 1, 1, 1,
# 2, 2, 2, 2,
# 1, 1,
# 2, 2,
# 3, 3, 3, 3,
# 4, 4, 4, 4,
# 3, 3,
# 4, 4,
# 5, 5, 5, 5,
# 5, 5]
只要map_func是纯函数,此变换产生的元素的顺序就是确定性的。 如果map_func包含任何有状态操作,则该状态的访问顺序不确定。
take(count)函数
take(count)函数使用当前数据集中的计数最多的元素创建一个新数据集。 通常,这可用于减少数据集的大小,以用于调试或简化目的。 此外,如果将计数指定为-1,或者如果计数大于数据集的大小,则新数据集将包含先前数据集的所有元素。
filter函数
此 API 根据条件谓词函数过滤当前数据集:
ds = tf.data.Dataset.from_tensor_slices([1, 2, 3])
ds = ds.filter(lambda x: x > 3) # ==> [1, 2]
打乱和重复tf.data.Dataset
机器学习模型必须从训练,验证和测试步骤的总体分布中合理地表示数据。 通常,原始数据可以按特定顺序存储,例如相对于每个类一起存储,或者数据可以一起存储在特定源中。 必须对原始数据进行混洗,以确保训练,验证和测试数据分布在整个数据分布中。 另外,建议在每个周期之后对数据进行混洗。 下图显示了打乱和重复使用tf.data.Dataset的流程:
良好的随机播放还有助于减少数据的差异,该数据用于模型...
批量
梯度下降与反向传播相结合是最近机器学习或深度神经网络系统中最流行的学习算法。 梯度下降有三种:
- 批量梯度下降,其中所有数据都呈现给模型以供学习
- 小批量梯度下降,其中将一批数据提供给模型以供学习
- 随机梯度下降,其中提供随机采样的数据以训练模型
在这里,由于大型数据集的硬件内存限制,在大多数情况下批量梯度下降是不实际的。 而且,由于模型一次从一个数据中学习,因此随机梯度下降可能会很慢。 由于这些原因,小批量梯度下降法是使用最广泛的算法。 下图显示了批量的流程:
此外,最近的机器学习算法和深度神经网络在 GPU,TPU 和大量 CPU 上进行了分布式训练。 这些 GPU 或 TPU 中的每一个都有自己的内存限制(例如,NVIDIA 的 1080Ti GPU 具有 11 GB 的可用内存,而 Tesla V100 GPU 具有 16 GB 的可用内存)。 由于基于反向传播的梯度下降用于训练和学习 ML 模型的权重和偏差,因此开发人员使用小批量梯度下降; 因此,重要的是要有足够的批量大小,以确保可用的 GPU(或 TPU)不会耗尽内存。
tf.data.Dataset提供了一种以高效且无缝的方式创建一批样本的好方法,如以下代码块所示:
dataset = tf.data.TFRecordsDataset(...)
dataset = dataset.shuffle(buffer_size, seed=None, reshuffle_each_iteration=None)
dataset = dataset.repeat(count = None)
dataset = dataset.batch(batch_size, drop_remainder=True)
batch(...) API 将此数据集的连续元素合并为批量。 batch_size是传递给此 API 的超参数。 在第 4 章,“模型训练和 TensorBoard 的使用”中,我们将讨论并提供批量建议。
预取
批量完成后,建议使用prefetch(...) API。 该 API 将输入数据集转换为新数据集,该数据集可从输入数据集中预提取元素。 该 API 之所以重要,是因为它收集了在模型为当前批量提供服务时将加载到输入管道中的下一个批量:
dataset = tf.data.TFRecordsDataset(...)dataset = dataset.shuffle(buffer_size, seed=None, reshuffle_each_iteration=None)dataset = dataset.repeat(count = None)dataset = dataset.batch(batch_size, drop_remainder=True)dataset = dataset.prefetch(buffer_size)
通常,为prefetch(...)函数指定的buffer_size参数应与为batch(...) ...指定的batch_size参数一样大。
在将数据管道输出输入模型之前,先对其进行验证
到目前为止,我们已经学习了使用几种提取和转换数据的方法和技术来构建输入数据管道。 作为建议,在将输入数据管道输入模型之前,验证输入数据管道是否正在提取和转换正确的数据非常有用。 在 TF 2.0 中,这样做非常简单,因为数据集对象现在是 Python 可迭代的。 您可以如下遍历创建的数据集以获取数据的打印值:
ds = tf.data.Dataset.from_tensor_slices([1, 2, 3])
for data in ds:
print(data)
将创建的数据集馈入模型
一旦创建,转换和打乱数据集对象并完成批量,就需要将其馈入模型(从本章开头记住 ETL 的 L)。 此步骤在 TF 2.0 中进行了重大更改。
TF 2.0 中创建输入数据管道的一个主要区别在于其简单性。 TF 1.x 需要一个迭代器才能将数据集提供给模型。 为了做到这一点,有几个迭代器可以迭代一批数据。 一种是通过使用数据集对象中的tf.data.Iterator API。 TF 1.x 中有一个一次性的,可初始化的,可重新初始化的和可填充的迭代器。 尽管这些迭代器功能非常强大,但它们也增加了大量的复杂性,无论从术语上还是...
完整的端到端数据管道示例
到目前为止,我们已经介绍了数据集对象的创建以及如何创建批量数据以馈入模型。 在本节中,我们将看一个端到端输入数据管道和模型训练的示例。 我们将使用 CIFAR10 数据构建图像分类器。
为了运行基于 CIFAR10 的端到端示例,您需要从这里下载必要的数据。 该数据集摘自《从微小图像中学习多层特征》。 该数据集包含以下信息:
- 50,000 张带有标签的图像用于训练
- 10,000 张带有标签的图像用于测试
- 10 个类标签
下载并解压缩数据集后,您将看到一个名为cifar-10-batches-py的文件夹,其中包含以下文件:
batches.metadata_batch_2data_batch_4readme.htmldata_batch_1data_batch_3data_batch_5test_batch
data_batch_*文件包含训练数据,而test_batch文件包含测试数据。 这些文件为 Python pickle格式。 在此端到端示例中,我们将从 pickle 文件中创建tfrecords。 完整的代码文件以及README.md文件可在本书的 GitHub 存储库中找到,两者均可在这里找到。
使用 Pickle 文件创建 TFRecords
出于说明目的,我们将使用data_batch_*文件之一作为验证数据。 我们将其余的用于训练。 例如,如果我们选择data_batch_4作为验证数据,则data_batch_1,data_batch_2,data_batch_3和data_batch_5将用作训练数据。
- 让我们使用 CIFAR10 数据创建 TFRecords:
def create_tfrecords(cifar10_data_folder, validation_data_idx): """ function to generate tfrecords Creates three sub-folders, train, eval, test and put resp tfr files """ batch_files = _get_file_names(validation_data_idx) tfrecords_outdir = './tfrecords' for data_type in ['train', 'eval', 'test']: input_files = [os.path.join(cifar10_data_folder, i) \ for i in batch_files[data_type]] ...
TF 2.0 中数据管道的最佳实践和性能优化
这是在 TF 2.0 中建立有效的输入数据管道时应遵循的最佳实践的摘要:
- 建议在重复转换之前使用打乱(
shuffle)API。 - 使用预取转换可以重叠生产者(获取下一批数据)和使用者(使用当前数据进行训练)的工作。 另外,非常重要的一点是要注意,在对数据管道进行打乱(打乱),重复(重复)和批量(批量)之后,应将预取转换添加到输入管道的末尾。 看起来应该像这样:
# buffer_size could be either 1 or 2 which represents 1 or 2 batches of data
dataset = dataset.shuffle(count).repeat().batch(batch_size).prefetch(buffer_size)
- 强烈建议通过启用
num_parallel_calls参数来并行化映射 API。 - 对于远程存储的数据集,建议使用
interleave(...)转换来并行读取来自不同文件的数据。
TF 2.0 中的内置数据集
TF 2.0 还提供了可与 TensorFlow 一起使用的数据集的集合。 它负责下载,准备数据,甚至自行构建tf.data.Dataset,然后可以将其直接输入模型中。
请按照以下步骤使用这些内置数据集:
- 安装 TensorFlow 数据集:
pip3 install tensorflow-datasets
请注意,tensorflow-datasets希望您正确且完整地安装 TF 2.0。
- 安装
tensorflow-datasets后,可以使用以下代码查看可用数据集的列表:
import tensorflow_datasets as tfdstfds.list_builders()
这将给出以下输出:
['abstract_reasoning', 'bair_robot_pushing_small', 'caltech101', ...
总结
本章以简单而富于启发性的方式展示了使用 TF 2.0 API 设计和构建输入数据管道的总体方法。 它提供了数据管道的不同组件的构建块,并提供了构建管道所需的 API 的详细信息。 提供了 TF 1.x API 和 TF 2.0 API 之间的比较。
总体流程可以概括为两个主要过程:原始数据管理和数据集处理。 原始数据管理处理原始数据; 将数据分为训练,验证和测试集; 并创建 TFRecords。 通常,这是一个一次性过程,其中还可以包括脱机数据转换。 数据集操作是一个在线转换过程,该过程创建数据集对象,应用转换,对数据进行混洗,然后重复进行此操作并通过预取创建一批数据; 稍后将它们输入模型。
无论模型训练/推理的训练数据大小和生命周期如何,始终建议使用输入数据管道。 由于数据集对象在 2.0 版中是 Python 可迭代的,因此将它们馈送到模型中非常简单。
在下一章中,我们将学习有关模型训练和使用 TensorBoard 的知识。
进一步阅读
尽管本章试图捕获有关如何构建输入数据管道的最新信息,但 TensorFlow 是一个快速变化的平台。 开发人员每天都在增加新功能。 社区中还有成千上万的开源贡献者,他们正在迅速添加功能。 强烈建议尽可能参考这里,以了解正确的 API 使用和/或更改。
四、TensorBoard 的模型训练和使用
本章详细介绍了机器学习训练管道,以构建,训练和验证包括深度神经网络在内的最新机器学习模型。 它描述了如何集成输入数据管道,创建基于tf.keras的模型,以分布式方式进行训练以及运行验证以微调模型的超参数。 它还涉及有关如何导出和保存 TensorFlow 模型以进行部署和推理的各种概念。 模型调试和可视化是用于调试和提高模型准确率和表现的关键工具。 本章还概述了 TensorBoard 的用法,在 TF 2.0 中的更改以及如何使用 TensorBoard 进行模型调试以及对模型的速度和性能进行性能分析。
TensorFlow 1.x 版本强烈支持低级和中级 API,以构建机器学习模型。 它还具有 Estimator API,包括预制的估计器,例如LinearClassifier和DNNRegressor,以及用作高级 TF API 的定制估计器。 TF 1.x 中对估计器的支持是提供高级 API,与低级和中级 TF API 相比,它们更易于构建。 从 TensorFlow 2.0 开始,主要变化之一是采用 Keras API 标准作为高级 API 而不是 Estimators。 对于 TensorFlow 开发团队而言,这非常有意义,因为 Keras API 是迄今为止机器学习社区中采用的最大 API 集,并且 Keras 的创建者 Francois Chollet 也是一位出色的人工智能(AI)研究人员,现在已经加入 TensorFlow 开发团队的成员。 TensorFlow 1.x 版本已经提供对tf.keras的初始支持; 但是,在 TF 2.0 版本中可以获得tf.keras的完整而完整的体验。
在本章中,我们将浏览tf.keras API,包括 API 的顺序,函数式和模型子类类型。 您将学习如何使用tf.data.Dataset将输入数据流水线馈入模型流水线,以及特征列的可能分类结构。 我们还将介绍如何定义损失函数,最常见的优化器,基于 TensorBoard 的数据,模型调试,可视化和性能分析等。 从 TensorFlow 2.0 开始,tf.keras API 已紧密集成到 TensorFlow 生态系统中,其中包括对tf.data的改进支持和最新可用的分发策略,可用于跨多种 GPU 和 TPU 进行分布式训练。 tf.keras还无缝支持导出训练有素的模型,这些模型可以使用 TensorFlow 服务和其他技术在 TensorFlow Lite 的移动和嵌入式设备上进行服务和部署。
我们将在本章介绍以下主题:
- 比较 Keras 和
tf.keras - 使用
tf.keras2.0 创建模型 - 模型编译与训练
- 自定义训练逻辑
- 分布式训练
- TensorBoard
技术要求
假定本章和本书的读者都知道机器学习,神经网络和深度神经网络的基础知识。 另外,作为前提条件,假设读者知道 TensorFlow 1.x API。 此外,还需要对深度神经网络中的卷积层,循环层和前馈层有基本的了解。
比较 Keras 和tf.keras
tf.keras是 TensorFlow 对 Keras API 规范的实现。 这是用于构建和训练模型的高级 API,其中包括对 TensorFlow 特定功能的一流支持,例如急切执行,tf.data管道和估计器。 tf.keras使 TensorFlow 易于使用,而不会牺牲灵活性和表现。
Keras(定义 Keras API 标准的原始网站)是一个开源项目,由于其简单和强大而受到 ML 工程师和数据科学家的极大关注。 最初,Keras 的默认后端引擎(请记住,Keras 是一组 API)是 Theano; 但是,最近它发生了变化,现在 TensorFlow 作为其默认后端引擎。 您还可以将默认后端引擎设置为 MXNet,CNTK 等。 Keras API 非常易于使用,模块化且可组合。 此外,还可以轻松扩展您的特定需求。 TensorFlow 采用了 Keras API 标准,从那时起,使用 TensorFlow 核心功能的tf.keras开发就如火如荼地进行。 现在,随着 TF 2.0 的发布,TF 开发团队为tf.keras高级 API 提供了紧密而有效的支持。 另外,值得一提的是 Keras 和tf.keras是两个完全不同的包,作为 TF 2.0 的一部分,应使用tf.keras。 在版本方面,在 TensorFlow 2.0 中,TensorFlow 和tf.keras的版本号仍然存在差异,您可以尝试使用tf.__version__和tf.keras.__version__查看此版本。
比较估计器和tf.keras
TensorFlow 1.x 已建议为其高级 API 集使用tf.estimator API,该 API 集具有内置模型(例如LinearRegressor和DNNClassifier)可用的预制估计器。 此外,对于更细化和定制的模型,TF 1.x 具有定制的估计器。 从 TF 2.0 开始,建议仅使用与线性分类器,DNN 分类器,组合 DNN 线性分类器和梯度提升树打包在一起的丰富的预制估计器 API 集。 这些模型已准备就绪,可以广泛使用。 对于任何自定义模型,建议直接使用tf.keras而不是tf.estimator API。 另外,值得注意的是,与tf.keras, ...有更好的协同作用
机器学习分类法和 TF 支持的快速回顾
可以使用三种主要的机器学习技术来解决大多数学习问题:
- 监督学习借助标签数据预测标签
- 无监督学习,对没有标签的数据进行分组和聚类
- 强化学习,其中存在一种环境,智能体可以通过该环境通过采取行动并从环境中获取反馈(奖励)来学习实现预期目标
生成模型和判别模型可以与这三种机器学习技术一起使用。 生成模型尝试从具有未知分布的给定数据集中凭经验学习模式和分布,并可能使用学习的模型来生成新数据,就好像它来自同一分布。 一些流行的生成模型是高斯混合模型,隐马尔可夫模型,贝叶斯网络(例如朴素贝叶斯)等。 生成对抗模型是 2014 年非常流行的生成模型,由于其强大的成功和潜力而备受关注。 除了仅学习可用于无监督学习的分布之外,生成模型还可以用于执行分类或预测任务(有监督学习),该任务使用样本x的条件概率, 通过使用朴素贝叶斯定理计算概率P(y | x),属于y类。 与生成模型相反,判别模型用于直接学习条件概率P(y | x),用于回归,分类和其他类型的监督学习问题。 深度神经网络可用于构建生成模型或判别模型。
TensorFlow 提供了丰富的 API 集来构建上述生成模型和判别模型。 此外,在 TF 2.0 中,通过引入急切的执行(在第 2 章, “Keras 默认集成和急切执行”中进行了解释),创建这些模型的理念发生了整体变化, 这使得tf.keras的使用非常简单且易于调试。 此外,TensorFlow 2.0 中的tf.keras API 丰富了 TF 在 TF 1.x 版本中可以执行的全部功能。 在本书中,除非另有说明,否则我们主要使用tf.keras API 来构建,训练和预测神经网络模型,并且不会讨论低级或中级 TF API。
TensorFlow 建立深度学习模型并对其进行训练时的理念是,首先定义神经网络层(也称为构建由节点和边组成的计算图); 定义损失函数,准确率度量和适当的优化器; 然后训练模型以更新梯度。 这三个步骤在使用构建,编译和拟合的tf.keras API 中得到了体现,如下图所示:
在以下部分中,我们将首先了解如何使用tf.keras 2.0 API 构建模型,该 API 将详细介绍计算图节点和边的创建。 然后,我们将介绍编译和拟合,包括损失和准确率函数的定义。
使用 tf.keras 2.0 创建模型
在本节中,我们将学习tf.keras API 的三种主要类型,以定义神经网络层,即:
- 顺序 API :这些基于堆叠的 NN 层,可以是密集(前馈)层,卷积层或循环层)
- 函数式 API :这些有助于构建复杂的模型
- 模型子类 API :这些是完全可自定义的模型; 这些 API 灵活,需要谨慎编写
下图显示了用于构建tf.keras.Model的这三个 API 的 Python 类层次结构:
让我们创建一个相对简单的神经网络来构建手写识别分类器...
顺序 API
顺序 API 是创建 TF 模型并提供大约 70-75% 模型类型的最简单方法。 您需要创建一个tf.keras.models.Sequential(...) Python 类并将所需的层顺序添加到模型中-这也称为层栈。 这些层可能是密集,卷积甚至是循环层。 您可能需要提供第一层的输入形状。 以下是使用顺序 API 创建 TF 模型的步骤:
- 创建一个
Sequential模型类:
model = tf.keras.models.Sequential()
num_filters = 32
kernel_size = (5, 5)
pool_size = (2, 2)
num_classes = 10
- 首先通过调用
build()或fit()和一些数据来构建模型,或者在第一层中指定input_shape参数以进行自动构建。
(可选)第一层可以接收input_shape参数:
model.add(tf.keras.layers.Conv2D(filters=num_filters,
kernel_size=kernel_size,
padding='valid', activation='relu',
input_shape=input_shape))
- 另一个
Conv2D层:
model.add(tf.keras.layers.Conv2D(filters=num_filters,
kernel_size=kernel_size,
padding='same', activation='relu'))
- 添加最大池化层:
model.add(tf.keras.layers.MaxPooling2D(pool_size=pool_size))
- 并添加一个
Dropout层:
model.add(tf.keras.layers.Dropout(0.5))
- 另外,添加
Flatten层:
model.add(tf.keras.layers.Flatten())
- 添加具有 10 个输出单元的
softmax层:
model.add(tf.keras.layers.Dense(units=num_classes,
activation='softmax'))
请注意,使用tf.keras.layers代替tf.layers。 TensorFlow 2.0 明确建议使用tf.keras.layers。 使用tf.keras.layers,您可以指定权重,偏差,初始值设定项和正则化项。 使用tf.layers和tf.keras.layers时,权重初始化的方式以及获得确切的 API 定义的方式可能会有一些差异。 建议在各个部分中查看。
函数式 API
函数式 API 比顺序 API 可以构建更高级的模型。 例如,如果您需要一个具有多个输入和多个输出的模型,则无法使用顺序 API。 函数式 API 提供了这种灵活性。 另外,使用函数式 API,您可以定义具有共享层的模型。 此外,只能使用函数式 API 定义具有剩余连接的模型。
使用函数式 API 的神经网络层的创建是通过 Python 可调用对象(可调用的 Python 对象)进行的。 作为构建深度学习模型的一部分,深度学习模型通常是分层的,与顺序 API 相反,在顺序 API 中,您首先创建tf.keras.Sequential模型,然后在函数式 API 中逐层添加层...
模型子类化 API
模型子类化 API 通过对tf.keras.Model类对象进行子类化(派生)来构建完全自定义的模型。 这是通过在派生类的构造器__init__(...)中创建层栈并将其设置为该类的属性来实现的。 此外,您可以在call(...)函数中实现前向通过图。
让我们使用以下类构建模型子类:
class MyModel(tf.keras.Model):
def __init__(self):
super(MyModel, self).__init__()
self.num_filters = 32
self.kernel_size = (5, 5)
self.pool_size = (2, 2)
self.num_classes = 10
self.my_input_shape = (28, 28, 1)
现在定义层:
# first conv layer
self.conv1_layer = tf.keras.layers.Conv2D(filters=self.num_filters,
kernel_size=self.kernel_size, padding='valid', activation='relu',
input_shape=self.my_input_shape)
# Another conv2d layer
self.conv2_layer = tf.keras.layers.Conv2D(filters=self.num_filters,
kernel_size=self.kernel_size, padding='same', activation='relu')
添加最大池化层:
self.mp_layer = tf.keras.layers.MaxPooling2D(pool_size=self.pool_size)
并添加一个丢弃:
self.do_layer = tf.keras.layers.Dropout(0.5)
展平层:
self.ft_layer = tf.keras.layers.Flatten()
添加一个带有 10 个输出单元的 softmax 层:
self.outputs_layer = tf.keras.layers.Dense(self.num_classes, activation='softmax')
def call(self, inputs, training=False):
conv1 = self.conv1_layer(inputs)
conv2 = self.conv2_layer(conv1)
mp = self.mp_layer(conv2)
do = tf.keras.layers.Dropout(0.5)(mp)
ft = tf.keras.layers.Flatten()(do)
outputs = self.outputs_layer(ft)
return outputs
使用任何 API 创建模型后,最好使用model.summary()和/或tf.keras.utils.plot_model(...)查看模型详细信息。
模型编译与训练
神经网络对复杂的非线性函数建模,例如sin(x),x ** 2和x ** 3,仅举几个简单的函数, 由层的网络(栈)组成。 这些层可以是卷积层,循环层或简单的前馈层的混合。 每层由神经元组成。 神经元有两种模型化非线性的成分:前一层的加权总和,然后是激活函数。 神经网络试图以迭代方式学习给定训练数据的分布。 一旦通过指定激活函数以层栈的形式构建了神经网络,就需要定义一个目标函数(也称为损失函数)以使用适当的模型来改善模型权重。
compile() API
tf.keras.Model.compile(...) API 有助于定义loss函数和优化器,如下所示:
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
可以使用mse或categorical_crossentropy之类的字符串或通过指定tf.keras.losses.CategoricalCrossentropy来简单地定义损失,如以下代码块所示。 优化器也是如此。 但是,为了为优化器指定明确的学习率,您必须使用 Python 优化器类,例如tf.keras.optimizers.Adam,如下所示:
# Specify the training configuration (optimizer, loss, metrics)
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
# Loss function to minimize
loss=tf.keras.losses.CategoricalCrossentropy(),
# List of metrics to monitor
metrics=[tf.keras.metrics.Accuracy()])
fit() API
tf.keras.Model.fit(...)是训练模型的主要 API。 它以输入训练数据以及(可选)批量大小,回调等作为输入:
model.fit(train_dataset, epochs=10, callbacks=[tensorboard_callback], validation_data=val_dataset)
回调是模型训练期间特定工具的挂钩。 它们被传递给fit(...)函数以自定义和扩展训练过程中模型的行为。 有很多有用的内置回调-以下是其中一些:
tf.keras.callbacks.ModelCheckpoint:定期保存模型的检查点tf.keras.callbacks.LearningRateScheduler:动态更改学习率tf.keras.callbacks.EarlyStopping:发生以下情况时中断训练
保存和还原模型
监视训练进度非常重要,并且在每次迭代或训练步骤中都能查看模型对于调试模型的表现同样重要。 此外,训练结束后,需要加载模型以进行推理和部署。 为了能够做到这一点,需要保存模型的训练权重和参数以备将来使用。
TF 2.0 提供了支持,可以轻松完成此操作,因为可以在训练期间和训练后保存模型。 这为用户提供了灵活性,允许从先前的检查点恢复训练,并且避免完全重新启动模型的训练以减少较长的训练时间。 此外,这些保存的模型可以在团队之间共享以进行进一步的工作。 在本节中,我们将主要讨论保存tf.keras.Models。
TF 提供了仅保存模型权重或保存整个模型的灵活性,包括模型权重,配置和优化器详细信息,等等。
在训练同时保存检查点
可以使用tf.keras.callbacks轻松实现保存检查点,如下所示:
# Create checkpoint callbackcp_callback = tf.keras.callbacks.ModelCheckpoint(checkpoint_path, save_weights_only=True, verbose=1)model.fit(train_dataset, epochs=10, callbacks=[cp_callback], validation_data=val_dataset)
之前的回调会创建多个 TensorFlow 检查点文件,这些文件会在每次训练完成后进行更新。 此外,要使用这些检查点,请使用与保存检查点的原始模型完全相同的架构来重新创建模型,构建模型,然后使用tf.keras.Model.load_weight(...) API 从任何检查点加载权重并将其用于评估:
model.load_weights(checkpoint_path) ...
手动保存和恢复权重
模型权重也可以保存在检查点文件中。 这可以用来保存训练后的权重,以便将来进行进一步的训练:
# Save the weights
model.save_weights('./checkpoints/my_checkpoint')
# Restore the weights
model = create_model()
model.load_weights('./checkpoints/my_checkpoint')
loss,acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
保存和还原整个模型
TF 还可以保存和恢复整个模型,包括权重,变量,参数和模型的配置。 这提供了加载整个模型的灵活性,而无需使用训练模型的原始代码。 整个模型可以使用tf.keras.experimental.export_saved_model以 HDF5 文件格式或即将发布的 TF 内部格式存储。 在这一点上,后者仍处于试验阶段,因此我们将不再描述:
model = create_model()model.fit(train_images, train_labels, epochs=5)
将整个模型保存到 HDF5 文件中:
model.save('my_model.h5')
重新创建完全相同的模型,包括权重和优化器:
new_model = keras.models.load_model('my_model.h5') ...
自定义训练逻辑
如前所述,TF 2.0 带来了默认的紧急执行,这意味着基于图的代码流的传统 TF 1.x 自定义训练逻辑实现现在已过时。 为了在 TF 2.0 中实现有关急切执行的自定义训练逻辑,可以使用tf.GradientTape。 tf.GradientTape的目的是记录用于自动微分的运算,或者用于计算运算或计算相对于其输入变量的梯度。 这可以通过使用tf.GradientTape作为上下文管理器来完成。 TensorFlow 将在tf.GradientTape上下文中执行的所有操作记录到磁带上,然后将其与梯度一起与那些操作关联,以使用反向模式微分计算记录的操作的梯度。
例如,一个简单的立方体操作的梯度可以如下计算:
x = tf.constant(2.0)
with tf.GradientTape() as tape:
tape.watch(x)
y = x ** 3
dy_dx = tape.gradient(y, x) # 12.0
tf.GradientTape记录所有涉及监视张量的操作,例如上例中的x。 会自动监视tf.GradientTape上下文中出现的所有可训练变量,并将其记录在磁带上。 可以通过将watch_accessed_variables设置为False来禁用此功能,以便仅记录程序员专门监视的变量。
通过将上下文管理器相互堆叠并计算相对于前一阶导数的梯度,也可以使用tf.GradientTape计算高阶导数。
tf.GradientTape还允许使用更多自定义训练逻辑,因为它提供了在使用优化程序之前操纵梯度的选项。 与内置的tf.keras.Model.fit相比,它提供了一种替代的,更加复杂且功能强大的深度学习模型训练方法。 为此,所有前向通过操作都记录在磁带上,并且为了计算这些操作的梯度,将磁带向后播放然后丢弃。 这里要注意的重要一点是,特定的tf.GradientTape模型只能计算一个梯度。
要首先使用tf.GradientTape实现模型的简单训练,请在tf.GradentTape上下文管理器内部的输入张量上调用前向传递,然后计算loss函数。 这样可以确保将所有计算结果记录在梯度磁带上。 然后,针对模型中的所有可训练变量计算梯度。 一旦计算出梯度,就可以在将其传递给优化器以将其应用于模型变量之前执行任何所需的梯度截断,归一化或变换。 看下面的例子:
NUM_EXAMPLES = 2000
input_x = tf.random.normal([NUM_EXAMPLES])
noise = tf.random.normal([NUM_EXAMPLES])
input_y = input_x * 5 + 2 + noise
def loss_fn(model, inputs, targets):
error = model(inputs) - targets
return tf.reduce_mean(tf.square(error))
def gradients(model, inputs, targets):
with tf.GradientTape() as tape:
loss_value = loss_fn(model, inputs, targets)
return tape.gradient(loss_value, model.trainable_variables)
model = tf.keras.Sequential(tf.keras.layers.Dense(1))
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)
print("Initial loss: {:.3f}".format(loss_fn(model, input_x, input_y)))
for i in range(500):
grads = gradients(model, input_x, input_y)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
if i % 20 == 0:
print("Loss at step {:03d}: {:.3f}".format(i, loss_fn(model, input_x, input_y)))
print("Final loss: {:.3f}".format(loss(model, input_x, input_y)))
print("W = {}, B = {}".format(*model.trainable_variables))
TF 2.0 中添加的另一个功能是tf.function装饰器。 用tf.function注解函数时,它仍然像任何其他 Python 函数一样工作,但是将被编译成图,这提供了诸如执行速度更快,GPU 和 TPU 加速之类的好处,并且可以轻松导出到SavedModel。
并非所有函数都需要使用tf.function进行注解,因为在带注解的函数内部调用的任何函数也将在图模式下运行。 对于具有多个较小操作的图,此类函数速度更快,但对于其他具有较昂贵操作(例如卷积)的图,改进效果会较小。
tf.function装饰器还可以绘制 Python 控制流图,例如if,while,for,break,continue和return。 运行这些功能可实现更快的求值和硬件加速。
tf.function也可以在tf.keras模型和训练循环中使用。 tf.function装饰器通常用于模型的call方法上,以提供图模型来求值。 另一种更常见的做法是将tf.function用于一个训练循环,因为它仅控制流程。 这样,训练过程的更多计算可以带入 TensorFlow 内,并将受益于优化的操作。
以下代码段是tf.keras中tf.function的示例:
class CustomModel(tf.keras.models.Model):
@tf.function
def call(self, input_data):
if tf.reduce_mean(input_data) > 0:
return input_data
else:
return input_data // 2
以下代码段是训练中的tf.function示例:
compute_loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
compute_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
def train_one_step(model, optimizer, x, y):
with tf.GradientTape() as tape:
logits = model(x)
loss = compute_loss(y, logits)
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
compute_accuracy(y, logits)
return loss
@tf.function
def train(model, optimizer):
train_ds = mnist_dataset()
step = 0
loss = 0.0
accuracy = 0.0
for x, y in train_ds:
step += 1
loss = train_one_step(model, optimizer, x, y)
if tf.equal(step % 10, 0):
tf.print('Step', step, ': loss', loss, '; accuracy', compute_accuracy.result())
return step, loss, accuracy
TF 2.0 还提供了一种创建自定义梯度以覆盖默认梯度计算的方法。 这是通过使用tf.custom_gradient装饰器完成的。 使用自定义梯度的一个常见原因是为一系列操作提供数值稳定的梯度,并且它们也可以用于限制梯度的范数。
要使用tf.custom_gradient装饰器,我们必须定义一个函数,该函数既返回所需的计算结果,又返回计算的梯度。 一个示例是在反向传播过程中实现梯度裁剪:
@tf.custom_gradient
def clip_gradient_by_norm(x, norm):
y = tf.identity(x)
def grad_fn(dresult):
return [tf.clip_by_norm(dresult, norm), None]
return y, grad_fn
正如我们在前面的示例中看到的那样,该函数不仅返回输入张量的副本,而且还返回以默认梯度作为参数并返回裁剪后的梯度的函数。
tf.custom_gradient装饰器的主要用途是允许在一系列操作的梯度上进行细粒度设置,并可用于创建一系列操作的更有效,更稳定的实现。
有关如何使用tf.custom_gradient的更多示例,请参见这里。
分布式训练
TF 2.0 的优点之一是能够在多个 GPU 和 TPU 上以分布式方式训练和推断模型,而无需编写大量代码。 使用分发策略 API tf.distribute.Strategy(...)可以简化此过程,该 API 随时可用。 “fit() API”部分介绍了tf.keras.Model.fit(...),该部分说明了如何使用此功能训练模型。 在本节中,我们将展示如何使用分布策略跨多个 GPU 和 TPU 训练基于tf.keras的模型。 值得注意的是,tf.distribute.Strategy(...)可与tf.keras和tf.estimator等高级 API 一起使用,并支持自定义训练循环或...中的任何计算。
TensorBoard
TensorBoard 是 TensorFlow 平台最重要的优势之一,而有了 TF 2.0,TensorBoard 进入了一个新的高度。 在机器学习中,要改善模型权重,通常需要能够对其进行度量。 TensorBoard 是用于提供机器学习工作流程期间所需的测量和可视化的工具。 它可以跟踪实验指标,例如损失和准确率,可视化模型图,将嵌入投影到较低维度的空间等。 与 TF 1.x 相比,TF 2.0 提供了一种非常简单的方法来使用回调来集成和调用 TensorBoard,在“fit() API”部分中对此进行了解释。 TensorBoard 还提供了一些技巧来测量和可视化您的数据和模型图,并且具有假设分析和分析工具。 它还扩展了自身以进行调试。
使用回调和调用连接 TensorBoard
TensorBoard 可以在 TF 2.0 中以两种主要方式使用。 一种方法是在使用tf.keras.Model.fit()训练模型时将其用作回调,另一种方法是将tf.summary用于使用tf.GradientTape的较低级模型。
要在 Keras 模型训练中使用 TensorBoard,我们需要指定一个 TensorBoard 回调,该回调以logdir作为参数。 TensorBoard 回调的其他参数包括histogram_freq,write_graph,write_images和update_freq。 histogram_freq允许用户指定应该多久计算一次激活和权重直方图,并需要指定验证数据。 write_graph指定是否要在 TensorBoard 中可视化模型的图,...
可视化标量,度量,张量和图像数据
TensorBoard 还提供了可视化自定义标量和图像数据的功能。 这是前面描述的度量可视化的补充。 自定义标量日志记录可用于记录动态学习率。 为此,请使用以下步骤:
- 使用
tf.summary.create_file_writer()创建文件编写器:
logdir = "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S")
file_writer = tf.summary.create_file_writer(logdir + "/metrics")
file_writer.set_as_default()
- 然后,定义一个自定义学习率函数,然后将其传递给 Keras
LearningRateScheduler回调并将自定义学习率记录在该函数内:
def lr_schedule(epoch):
"""
Returns a custom learning rate that decreases as epochs progress.
"""
learning_rate = 0.2
if epoch > 10:
learning_rate = 0.02
if epoch > 20:
learning_rate = 0.01
if epoch > 50:
learning_rate = 0.005
tf.summary.scalar('learning rate', data=learning_rate, step=epoch)
return learning_rate
lr_callback = keras.callbacks.LearningRateScheduler(lr_schedule)
tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir)
model = keras.models.Sequential([
keras.layers.Dense(16, input_dim=1),
keras.layers.Dense(1),
])
model.compile(
loss='mse', # keras.losses.mean_squared_error
optimizer=keras.optimizers.SGD(),
)
- 最后,将
LearningRateScheduler与 TensorBoard 回调一起传递给model.fit回调:
training_history = model.fit(
x_train, # input
y_train, # output
batch_size=train_size,
epochs=100,
validation_data=(x_test, y_test),
callbacks=[tensorboard_callback, lr_callback],
)
一般来说,要记录自定义标量,我们需要将tf.summary.scalar()与文件编写器一起使用,该文件编写器负责将运行数据写入特定目录并被隐式使用。
在 TensorBoard 中写入用于可视化的图像数据时,也使用文件编写器。 TensorFlow 图像摘要 API 可用于轻松记录张量和任意图像并在 TensorBoard 中查看它们。 这有助于采样和检查输入数据,并可视化模型权重和生成的图像。
为了可视化图像,在文件编写器的上下文中调用tf.summary.image()记录一个或多个图像。 此功能采用(批量,高度,宽度,通道)形式的 4 级张量,因此任何非该格式的图像都必须在将其记录到 TensorBoard 之前进行重塑。 只要将其转换为张量,该 API 还可用于记录任何种类的任意图像数据,例如 Matplotlib 图形。
以下代码段是如何将单个图像记录到 TensorBoard 的示例:
img = np.reshape(train_images[0], (-1, 28, 28, 1))
# Sets up a timestamped log directory.
logdir = "logs/train_data/" + datetime.now().strftime("%Y%m%d-%H%M%S")
# Creates a file writer for the log directory.
file_writer = tf.summary.create_file_writer(logdir)
# Using the file writer, log the reshaped image.
with file_writer.as_default():
tf.summary.image("Training data", img, step=0)
图形仪表板
TensorBoard 的图形仪表板提供可视化和检查 TensorFlow 模型的功能。 我们可以使用它来快速查看模型结构的概念图以验证其设计或查看操作级图以了解 TensorFlow 如何理解和执行程序。 检查操作级图还可以深入了解如何重新设计模型以获得更佳的运行时间。
在 TF 2.0 中,按照以下步骤查看操作级图非常简单:
- 将 TensorBoard 回调添加到
Model.fit以确保图数据记录在 TensorBoard 中。 - 运行后,打开 TensorBoard 并导航到顶部栏上的“图”选项卡以查看图。 默认情况下,TensorBoard 显示操作级别图,该图显示...
超参数调优
建立良好的深度学习模型最重要的部分之一就是选择最佳超参数来训练模型本身。 超参数是工程师在模型训练之前设置的参数。 一些常见的超参数包括丢弃率,学习率和所用优化器的类型。 超参数的优化是一个耗时的过程,其中涉及对具有不同超参数的模型进行多次训练以找到最佳模型,因为目前尚无关于如何选择超参数的见解。
这样,TF 2.0 提供了一种智能执行超参数调优的工具,它可以协助确定执行最佳实验和尝试最有前途的超参数的过程。
为此,请使用以下步骤:
- 列出要为特定超参数尝试的值,并将实验配置记录到 TensorBoard。 然后,修改 TensorFlow 模型以在模型构建中包括超参数。 完成此操作后,将
hp.KerasCallback回调添加到model.fit函数中:
def train_test_model(hparams):
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(hparams[HP_NUM_UNITS], activation=tf.nn.relu),
tf.keras.layers.Dropout(hparams[HP_DROPOUT]),
tf.keras.layers.Dense(10, activation=tf.nn.softmax),
])
model.compile(
optimizer=hparams[HP_OPTIMIZER],
loss='sparse_categorical_crossentropy',
metrics=['accuracy'],
)
model.fit(
...,
callbacks=[
tf.keras.callbacks.TensorBoard(logdir), # log metrics
hp.KerasCallback(logdir, hparams), # log hparams
],
)
_, accuracy = model.evaluate(x_test, y_test)
return accuracy
- 定义模型后,下一步就是定义一种算法来循环遍历所有可能的超参数,例如网格搜索。 这将遍历离散超参数的所有值以及实值超参数的上限和下限:
session_num = 0
for num_units in HP_NUM_UNITS.domain.values:
for dropout_rate in (HP_DROPOUT.domain.min_value, HP_DROPOUT.domain.max_value):
for optimizer in HP_OPTIMIZER.domain.values:
hparams = {
HP_NUM_UNITS: num_units,
HP_DROPOUT: dropout_rate,
HP_OPTIMIZER: optimizer,
}
run_name = "run-%d" % session_num
print('--- Starting trial: %s' % run_name)
print({h.name: hparams[h] for h in hparams})
run('logs/hparam_tuning/' + run_name, hparams)
session_num += 1
对于更复杂的超参数调优,随机搜索更加有效。 可以通过随机选择每个超参数并运行实验来进行,这可以比网格搜索快得多地探索超参数空间。 也可以使用其他更复杂的算法。
- 最后,可以通过在
logdir上运行 TensorBoard 来查看超参数日志,日志在其中写入:
仪表板的左窗格允许用户按超参数过滤日志,以方便访问和使用。 超参数仪表板具有三个视图-表视图,并行坐标视图和“散点图矩阵视图”-每个视图都提供了一种不同的方式来可视化结果。 TABLE VIEW列出运行和超参数并显示指标。 并行坐标视图将每个运行显示为一条穿过每个超参数和指标的轴的线,可用于查看哪个超参数更重要。 SCATTER PLOT MATRIX VIEW显示比较每个超参数和度量的图,并有助于识别相关性。
该工具可轻松调整超参数和详细的日志,并在 TensorBoard 中直观显示结果。
What-If 工具
TensorFlow 2.0 引入了一个非常强大的工具,即 What-If 工具(WIT),该工具可在 TensorBoard 仪表板内部提供易于使用的界面。 但是,仅当使用 TensorFlow 服务为模型提供服务时,才可以使用 WIT。 在第 5 章,“模型推理管道 – 多平台部署”中解释了 TensorFlow 服务。 另外,为了使用 WIT,推理数据集必须为TFRecords格式。
WIT 的某些功能是可以将具有相同工作流程的多个模型进行比较,推理结果的可视化,基于相似度的数据排列以及通过编辑数据点执行模型的敏感性分析的能力。
分析工具
使用 TF 2.0 随附的 TensorBoard 时,如果您使用tf.keras API 构建和训练模型,则已经有一个 PROFILE 仪表板选项卡,可用于查看模型所花费的各种训练时间:
总结
本章详细介绍了如何使用 TF 2.0 tf.keras API 构建训练管道,以及如何使用分布策略在 GPU 上以分布方式在 GPU 上使用各种可用的损失函数,优化器和超参数查看构建,编译和拟合模型。 。 它还详细介绍了如何在训练时保存,恢复模型以进行将来的训练以及进行推断。 TensorBoard 是 TF 2.0 的主要优势之一,我们提供了有关如何有效地使用它来监视训练表现损失和准确率以及如何调试和分析它的详细信息。
在下一章中,我们将学习模型推理管道并将其部署在多平台上。
问题
我应该使用tf.keras API 还是 TF 的低级和中级 API?
查看本章,然后尝试找到答案。
我应何时使用tf.keras顺序和函数式 API? 为什么需要模型子类化?
通常,对于更简单的模型,应使用tf.keras顺序。 大部分模型可以使用顺序 API 编写。 但是,对于那些需要多个输入和输出以及某些特定连接(例如残差)的模型,应使用函数式 API。 对于真正定制的模型,可以使用模型子类化。
进一步阅读
鼓励用户阅读这里的迁移学习指南,该指南重用了预训练的模型权重和变量,并将学习表示迁移到另一个数据集。
五、模型推理管道 - 多平台部署
训练完模型后您会怎么做? 用它? 如果答案是肯定的,那么您将如何使用它? 您正在寻找的答案是推理。 简而言之,推理过程是确保机器学习模型可用于满足实际用户需求的基础。 正式地说,推理是有效地计算经过训练的机器学习模型以满足用户需求的过程。 可以在各种硬件类型上进行推断,包括服务器以及最终用户设备(例如电话和 Web 浏览器)。 根据用户要求,它也可以在不同的操作系统上执行。
前几章重点介绍了如何...
技术要求
为了运行本章中给出的代码摘录,您将需要以下硬件和软件:
- TensorFlow 2.0(TF 2.0)或更高版本(CPU 或 GPU 版本都足够)
- Python 3.4+(当前,TensorFlow 支持的最高 Python 版本是 3.6)
- NumPy(如果不是由 TensorFlow 自动安装)
- Docker(请参阅第 1 章和 “TensorFlow 2.0 入门”,有关如何安装 Docker 的详细信息)
curl- 具有命令行界面的 Linux 计算机
本章中的每个 Python 代码段均假定已安装 TF 2.0,并且已将其导入到名称空间中。 这意味着在执行任何代码块之前,请先输入以下行:
import tensorflow as tf
可在这个页面中获得本章的代码文件。
机器学习工作流程 - 推理阶段
机器学习应用的最常见子集之一遵循构建一次,并多次使用范式。 这种类型的应用涉及所谓的推理阶段。 在推断阶段,开发人员必须专注于运行模型以满足用户需求。 满足用户需求可能涉及从用户那里接受输入并对其进行处理以返回适当的输出。 下图描述了典型的高级机器学习应用工作流程:
从上图,我们可以看到推理过程如何适应整体情况。 在随后的应用中...
从推理的角度理解模型
实现基于机器学习的应用的开发人员可以依靠的一件事是使生活变得轻松,无论所服务模型中的实际计算如何,向用户提供模型的过程或多或少都是相同的。 这意味着,如果实现正确,工程师可能不必在每次数据科学家更新模型时都重新构建部署管道。 这可以通过利用抽象的力量来实现。 这里的一个关键抽象是模型存储和加载的格式。 通过引入标准化格式,TF 2.0 使得在一个环境中训练模型然后在各个平台上使用它变得容易。 在 TF 2.0 中,执行此操作的标准方法是通过SavedModel格式。 这种标准化格式类似于软件开发管道中的构建工件。 读者可以将模型工件视为快照,可用于重新创建模型而无需访问创建模型的实际代码。
实际上,在推理时,模型被简化为一个黑盒子,它具有一组预定义的输入和输出以及一个与底层模型进行交互的统一接口。 开发人员现在要做的就是建立在给定环境中实现和执行黑匣子所需的基础结构。 在以下各节中,我们将学习如何构建管道以服务于各种流行的软件和硬件环境中的模型。
模型工件 – SavedModel 格式
SavedModel格式是 TensorFlow 使用的默认模型序列化和反序列化格式。 用外行的术语来说,这可以理解为一个容器,它容纳了在不访问创建模型的原始代码的情况下从头开始重现模型的所有内容。 我们可以使用SavedModel将训练后的模型从训练阶段转移到推理阶段,甚至在训练过程的不同部分之间转移状态。 简而言之,可以说SavedModel包含完整的 TensorFlow 程序以及模型权重和所描述的各种计算操作的描述。 使用 TF 2.0 的 Python API 时,现在可以导出某些本机...
了解核心数据流模型
在我们研究SavedModel格式的细微差别之前,重要的是要首先了解 TensorFlow 模型的真正含义。 对于初学者,TensorFlow 实现数据流编程范例。 在这种范式下,程序被建模为在不同计算操作之间流动的数据的有向图。 这意味着每个节点代表一个操作(或计算),边代表数据。 输入边缘将代表该节点的输入,而输出边缘将对应于计算节点产生的输出。 为了说明这个想法,让我们看一下tf.add()操作的(粗略)数据流表示形式。 如下图所示,输入边对应于 x 和 y 的输入。 输出边缘z(x + y)对应于节点的输出,在这种特定情况下,该输出恰好是输入的总和:
使用数据流范例可以使 TensorFlow 在执行用户代码时利用某些好处:
- 并行性:将模型表示为有向图可以使 TensorFlow 识别哪些操作相互依赖,哪些不依赖。 这样,可以并行执行独立的操作,从而加快基础计算图的执行速度。
- 分布式执行:并行性的一个相关好处是,并行执行可以在同一台物理计算机上执行,也可以在另一台物理计算机上完成。 TensorFlow 还负责这些节点之间的通信。
- 编译:TensorFlow 的 XLA 编译器旨在利用数据流图中的信息,通过一系列优化来生成更快的代码。
- 可移植性:数据流图是模型中代码的语言无关表示形式。 这使得可以在 Python 中构建数据流图并以较低级别的语言(例如 C 或 Java)将其还原以进行低延迟推理。
我们已经看到了如何使用数据流范例来表示一个简单的操作。 实际的 TensorFlow 程序或模型将由许多这样的简单操作组成。 这意味着此类程序的数据流表示形式将由许多此类简单表示形式组成,每个操作通常具有一个或多个节点。 SavedModel格式可以理解为该基础数据流图的序列化。 这里有趣地提到了诸如 Keras 和 Estimators 之类的高级 API 的角色。 实际上,他们从用户那里抽象出了该数据流图的细节,以至于用户甚至不必考虑它。 它们为用户提供了一组高级操作,以供实现,然后将其转换为 TensorFlow 可以执行的数据流图。 这意味着,最终,在 TensorFlow 中创建的任何模型,无论其创建方式如何,都将转换为统一的计算图。 这样就可以使用一个统一的格式保存和加载所有模型。
tf.function API
正如我们在第 1 章中看到的那样,第 2 章“TensorFlow 2.0 入门”, “Keras 默认集成和急切执行”,默认情况下启用急切执行是 TF 2.0 中引入的主要更改之一。 第 1 章和 “TensorFlow 2.0 入门”还简要提到了 TF 2.0 与 Python 编程语言更紧密地结合在一起。 此更改的核心是低级tf.function API。 实际上,这是通过使用户能够从 Python 函数创建 TensorFlow 图而将 TensorFlow 1.x 的功能与急切执行的优点相结合。 它既可以用作可调用函数,也可以用作装饰器。 在本节中,我们将简要介绍一下如何在每个人中使用它。
tf.autograph函数
到目前为止,我们已经看到了如何从 Python 函数创建 TensorFlow 图的代码。 TF 2.0 将 Python-TensorFlow 耦合提升到了一个全新的水平。 新引入的 AutoGraph(tf.autograph)函数使用户可以使用本机 Python 语法编写图的代码。
当前,此功能仅支持 Python 语法的有限子集。 这个页面中提供了当前支持的语法元素的详细列表。
这样做的主要优点是,它使开发人员可以编写直观的 Python 代码来完成特定任务,然后自动将其转换为高性能的 TensorFlow 图代码。 这意味着开发人员可以以直观的 Python 形式描述基本的编程语言结构(例如循环和条件),而不是 TensorFlow 等效形式,并且具有可比的性能。
在 TF 2.0 中,调用tf.function时会自动调用 AutoGraph。 用户不需要单独调用它。 tf.autograph模块包含低级模块。 初学者或中级用户几乎不必直接使用它们,现在可以安全地忽略详细信息。
让我们看看执行此操作的示例。 考虑一个计算给定张量中所有值之和的函数。 让我们完全使用 Pythonic 语法实现它,然后使用tf.function将其转换为本地 TensorFlow 计算图代码:
@tf.function
def sum_of_cubes(numbers):
_sum = 0
for number in numbers:
_sum += number ** 3
return _sum
为了测试到目前为止已经编写的代码,让我们创建一个介于 1 到 5 之间(包括两端)的整数张量。 然后,将它们传递给我们的函数:
input_values = tf.constant([1, 2, 3, 4, 5])
result = sum_of_cubes(input_values)
print(type(result))
print(result)
这将导致以下输出:
<class 'tensorflow.python.framework.ops.EagerTensor'>
tf.Tensor(225, shape=(), dtype=int32)
正如我们在提取的输出中看到的那样,我们已经编写的纯 Python 函数现在被转换为 TensorFlow 图。 函数现在返回张量而不是单个数字的事实证明了这一点。 输出值与预期值相同。 有效地,我们已经证明了特定于 Python 的语法结构(例如for循环和幂运算符)已成功转换为 TensorFlow 图的代码。 这是tf.function和 AutoGraph 的真正功能。 由于我们现在已经有效地将本机 Python 代码转换为 TensorFlow 计算图,因此可以使用SavedModel格式在环境之间共享此图。
导出自己的 SavedModel 模型
如前所述,SavedModel格式用于生成当前计算图(数据流图)的可再现表示。 此表示独立于用于创建计算图的特定代码。 它也独立于用于构造该图的特定过程。 例如,SavedModel格式没有积极地区分使用本机 TensorFlow 操作,Keras 甚至tf.function创建的计算图。 尽管我们可以互换地将此计算图称为模型,但从技术上讲,它也可以被认为是训练有素的数学模型和围绕它编写的一些其他代码的组合,以执行支持...
使用tf.function API
如前所述,tf.function API 使我们能够使用简单的 Python 编写 TensorFlow 图和模型。 让我们从构建一个简单模型开始,该模型接受一个数字或一个数字列表并返回列表中值的平方。 然后,我们将由此创建的模型导出为SavedModel格式。 这是本章以下大部分内容的重要步骤。 我们将几乎在所有地方都使用SavedModel工件。
首先,让我们首先编写一个简单的 Python 函数来计算平方。 然后,我们可以从那里向后退:
def compute_square(number):
return number ** 2
如我们所见,前面的 Python 方法接受一个数字作为输入并返回其平方。 我们的最终目标是构建用于执行此计算的 TensorFlow 图。 利用我们从前面的部分中学到的知识,我们知道一种实现方法是使用tf.function。 我们选择使用tf.function的装饰器形式。 如果仔细观察我们刚刚编写的代码段,您将意识到我们假设传递给number变量的值是一个数值。 在现实世界中,情况未必一定如此。 为了解决这个问题,我们可以在装饰器中指定此方法可以接受的值的类型。 这是通过在装饰器中固定输入签名来完成的。 我们将其固定为包含 32 位浮点数的一维张量。 任何不符合此标准的输入将被自动丢弃。 我们修改后的代码片段(带有实现错误检查的功能)现在看起来像这样:
@tf.function(input_signature=[tf.TensorSpec(shape=None, dtype=tf.float32)])
def compute_square(number):
return number ** 2
到目前为止,我们已经成功实现了一个 TensorFlow 计算图,该图可以计算给定一维张量的平方。 现在唯一要做的就是以SavedModel格式将此图导出到磁盘。 您可能还记得,tf.saved_model模块中提供了用于SavedModel的 API。 在阅读该模块的文档时,我们发现save方法可能会对我们有所帮助。 一个粗糙的边缘是tf.saved_model.save方法仅适用于Trackable类型的对象,而我们所拥有的是tf.function()对象(属于Trackable类型或其子类)。 为了克服这个问题,我们只需将代码包装在实现Trackable接口的类中:
class Square(tf.Module):
@tf.function(
input_signature=[
tf.TensorSpec(shape=None, dtype=tf.float32)
]
)
def compute_square(self, number):
return number ** 2
现在,我们将逻辑封装在[HTG0]方法支持的表示形式中。 最后,我们创建一个Square类的对象(继承自Trackable)并将其传递给save方法:
sos = Square()
tf.saved_model.save(sos, './square/1')
现在,您将看到模型已成功导出到./square/1目录。 可以通过列出前面目录的内容来验证。 打开终端并输入以下内容:
cd <directory-containing-your-code>
ls -ax ./square/1
您将看到如下内容:
. .. assets saved_model.pb variables
在接下来的分析SavedModel工件的部分中,我们将研究这些文件中的每个文件所包含的内容以及它们在保存模型的过程中所起的作用。
分析 SavedModel 工件
在本小节中,我们将详细研究SavedModel如何序列化和反序列化 TensorFlow 图。 我们还将看看SavedModel命令行界面,这是一个功能强大的工具,可以分析磁盘上SavedModel的内容,甚至可以在本地运行SavedModel!
SavedModel格式本质上描述了一种在磁盘上存储 TensorFlow 图的方法。 在较低的级别上,其工作的一部分是编纂一种用于在文件中表示该图的格式。 按照这种格式,每个图都使用组成的底层函数及其状态的组合表示。 用 TensorFlow 的话来说,这些组成函数用名称标识,并称为签名或命名签名。 这些...
SavedModel 命令行界面
SavedModel 命令行界面(CLI)是非常方便的工具,可用于分析各种SavedModel实例并运行它们。 它在调试磁盘上的模型时非常有用,并且可以在不读取,编写或修改任何代码的情况下使用。 在本节中,我们将简要介绍如何安装此工具,使用它分析图的不同组件并运行计算图。
该工具与 TensorFlow 二进制文件捆绑在一起。 如果您通过从源代码构建 TensorFlow 来安装它,则必须单独安装它。 有关安装说明,请参见这里。
这里值得简要讨论的两个命令是show和run。 前者可用于列出 MetaGraph 信息,而后者可用于通过命令行在一组输入上执行图。 通过使用-h参数运行工具,可以在每个步骤中获取详细说明:
saved_model_cli -h
可以通过在命令名称后调用-h参数来获取特定命令的说明。 例如,如果您想要有关run命令的详细说明,请键入以下内容:
saved_model_cli run -h
为了亲身体验此工具,让我们回到在tf.function API 的较早部分中构建和训练的模型。 您可能还记得,模型接受任何维数的张量,并返回包含原始元素平方的相同形状的张量。 首先让我们看一下模型中存在的元图数量。 为此,请在“终端”窗口中键入以下内容:
saved_model_cli show --dir <path-to-model-dir>
对于我们计算平方的模型,您应该看到以下内容:
The given SavedModel contains the following tag-sets:
serve
如前所述,使用标记集来标识元图。 在这里,我们可以看到只有一个名为serve的标签集。 我们可能还想看一下构成该元图的组成函数。 要查看构成此标签集的SignatureDefs(有关详细信息,请参阅 这里,您可以键入以下命令:
saved_model_cli show \
--dir <path-to-model-dir> \
--tag_set serve
对于我们计算平方的模型,您应该看到以下内容:
The given SavedModel MetaGraphDef contains SignatureDefs with the following keys:
SignatureDef key: "__saved_model_init_op"
SignatureDef key: "serving_default"
现在,让我们看看如何使用run函数与直接使用命令行通过SavedModel保存的 TensorFlow 计算图进行交互,而无需编写任何代码。 从上一阶段的输出中可以看到,有两个组件函数。 其中,我们选择使用serving_default SignatureDef。 现在,我们可以通过提供所需的输入并获得所需的结果,通过命令行运行它。 为此,我们需要将路径传递给模型,标签集,输入值以及要运行的组件的名称。 为了该测试的目的,我们要计算的张量由[1, 2 , 3]给出。 确切的命令如下:
saved_model_cli run \
--dir <path-to-model> \
--tag_set serve \
--input_exprs "number"="[1, 2, 3]" \
--signature_def serving_default
以下是输出:
Result for output key output_0:
[1\. 4\. 9.]
从上一阶段的输出中,我们可以观察到以下内容:
- 输出张量与输入张量具有相同的形状
- 输出张量中的值对应于我们输入张量中的值的平方
这些观察结果都确认SavedModel工作正常。
在随后的部分中,我们将探讨在各种硬件和软件环境中服务于此模型的方法。
后端服务器上的推理
在当今世界,分布式系统无处不在。 从我们浏览的网站到我们在手机上使用的应用范围,当我们不使用分布式系统时几乎没有一天。 鉴于这种无所不在的性质,将这种范例用于构建机器学习系统显然是一个选择。 构建分布式系统的典型模式是在后端服务器上执行资源密集型(和数据敏感型)计算,同时将较轻(且相对独立)的计算任务推向用户设备。 机器学习应用的很大一部分属于资源密集型类别。 此外,机器学习模型是使用数据构建的。 在现实世界中的很大一部分...
TensorFlow 服务
TensorFlow 服务是 TensorFlow 扩展(TFX)平台的组成部分。 顾名思义,它旨在用于服务于机器学习模型。 简而言之,它是专为生产环境设计的高性能服务系统。 TensorFlow 服务的一个重要特征是它向下游用户公开了一致的 API,而与所服务模型的实际内容无关。 这使得快速进行实验和重新部署变得容易,而无需对其余软件栈进行任何其他更改。 它附带对 TensorFlow 模型的内置支持,并且可以扩展为服务于其他类型的模型。
在本节中,我们将详细介绍 TensorFlow 服务。 从基本的安装和设置开始,以下小节通过一系列动手示例描述如何设置服务器来为SavedModel服务。 我们还将简要介绍 TensorFlow 服务提供的一些关键 API。
设置 TensorFlow 服务
与 TensorFlow 平台的大多数其他组件一样,TensorFlow 服务也可以通过多种方式安装。 这里推荐通过 Docker 镜像使用它,因为它相对简单。
如果容器镜像对您不起作用,请在这个页面上获取其他安装 TensorFlow 服务方法的摘要。
使用 Docker 设置 TensorFlow 服务涉及一个简单的步骤。 但是,此步骤需要将 Docker 安装在主机上。 有关设置 Docker 的说明,请参阅第 1 章, “TensorFlow 2.0 入门”或本章的“技术要求”部分。 您需要做的就是拉相关的 Docker 镜像以...
设置并运行推理服务器
现在我们已经设置了 TensorFlow 服务,让我们使用它来执行一些实际任务。 我们可以看看如何设置后端服务器以服务于前面几节中构建的SavedModel格式。 我们可以使用上一节中下载的 Docker 镜像来运行SavedModel格式。 为此,我们需要做两件事:
- 将本地主机上包含模型的位置绑定到容器内的目录(
/models/<your-model_name>) - 绑定网络端口 TensorFlow 服务正在监听主机上的网络端口
该命令的一般形式如下:
docker run -t --rm \
-p <port-on-host>:8501 \
-v <path-to-model-on-host>:/models/<model_name> \
-e MODEL_NAME=<model_name> \
tensorflow/serving&
现在,模型服务器应该在您的主机上<port-on-host>中指定的端口上运行。
现在让我们通过发送一些推断数据来测试我们的模型。 我们可以通过 RESTful API 与模型进行交互。 我们应该将带有输入值的 HTTP POST请求发送到服务器。 为此,请在“终端”窗口中键入以下命令:
curl -X POST \
http://localhost:<port-on-host>/v1/models/square:predict \
-H 'Content-Type: application/json' \
-d '{"instances": [1.0, 2.0, 3.0, 4.0]}'
您应该看到以下输出:
{
"predictions": [1.0, 4.0, 9.0, 16.0]
}
现在我们已经看到了如何使用 TensorFlow 服务在后端服务器上提供SavedModel。 可通过 gRPC 和 RESTful API 访问此模型。 有关这些的详细信息,请参见以下链接:
请记住,每次调用docker run时,主机上都会启动一个新的 Docker 容器。 即使您已停止与该容器进行交互甚至关闭了“终端”窗口,该容器也可能会在后台继续刷新并运行。 这会导致大量的隐藏内存消耗。 需要有意识地停止容器。 为此,请执行以下步骤:
找出刚启动的容器的名称或 ID。 在“终端”窗口中键入以下内容:
docker ps
如您在前面的命令的输出中看到的,每个容器都有一个名称和 ID。 这些中的任何一个都可以用来唯一地标识容器。 我们需要使用它来停止我们启动的容器。 可以按照以下步骤进行:
docker stop <container-name>
您还可以使用以下内容:
docker stop <container-id>
现在,您可以放心,容器已停止并且没有占用计算机的任何内存。
当 TensorFlow.js 与 Node.js 相遇时
TensorFlow.js 的引入使在 JavaScript 环境中运行 TensorFlow 模型成为可能。 您可能已经知道,Node.js 是一个跨平台的运行时环境,可以在浏览器外部执行 JavaScript 代码。 这样就可以使用 JavaScript 代码编写后端服务。 将 Node.js 与 TensorFlow.js 集成在一起,就可以从 JavaScript 环境在后端服务器上提供机器学习服务。 请参阅这个页面上有关如何执行此操作的文档。
浏览器中的推断
您可能还记得,在前面的部分中,我们简要讨论了分布式系统。 在那里,我们讨论了主要在主机服务器上执行基于机器学习的计算的场景。 在这里,我们将研究在浏览器中在用户端执行这些计算的场景。 这样做的两个重要优点如下:
- 计算被推送到用户端。 主机不必担心为执行计算而管理服务器。
- 将模型推送到用户端意味着不必将用户数据发送到主机。 对于使用敏感或私有用户数据的应用来说,这是一个巨大的优势。 因此,浏览器中的推理成为对隐私至关重要的机器学习应用的绝佳选择:
上图中描述的工作流说明了从头构建模型然后允许最终用户在其 Web 浏览器中运行它的端到端管道。 我们看到该过程分为两个主要阶段:训练和推理。 在训练阶段,数据科学家和其他机器学习从业者聚在一起,建立和训练模型。 现在,该模型以SavedModel格式导出。 但是,TensorFlow.js 尚不直接支持SavedModel格式。 因此,有必要将模型转换为 TensorFlow.js 支持的格式。
有关如何执行转换的详细信息,请参见这里。
现在,通过任何其他 JavaScript 代码,都可以通过 Web 服务器将转换后的模型提供给用户。 用户为模型提供必要的输入。 TensorFlow.js 模型在用户浏览器中处理这些输入并返回适当的输出。
Detailed resources for getting started with TensorFlow.js are available at the following links:
移动和物联网设备上的推理
在过去几年中,智能手机的使用呈指数增长,并且以不减缓的方式持续增长。 其他物联网设备在我们的日常生活中也变得越来越普遍。 使用率的这些上升趋势对机器学习系统产生了有趣的影响。 与普通主机相比,这些平台通常资源有限。 结果,需要其他优化来在此类设备上进行推理。 TensorFlow 平台支持构建机器学习和基于深度学习的应用,这些应用可以在不同类型的边缘设备(例如手机和其他 IoT 设备)上运行。 实现此目的的主要工具是...
总结
在本章中,我们详细介绍了推理阶段。 首先,通过对端到端机器学习工作流的外观有了基本了解,我们了解了每个阶段涉及的主要步骤。 我们还了解了将模型从训练阶段转移到推理阶段时所起作用的不同抽象。 详细了解SavedModel格式和基础数据流模型,我们了解了可用于构建和导出模型的不同选项。 我们还了解了tf.function和tf.autograph等出色功能,使我们能够使用本地 Python 代码构建 TensorFlow 图。 在本章的后半部分,我们学习了如何构建推理管道,以便在后端服务器,Web 浏览器甚至边缘设备等不同环境中运行 TensorFlow 模型。
在下一章中,我们将了解有关 AIY 项目和 TensorFlow Lite 的更多信息。
六、AIY 项目和 TensorFlow Lite
本章详细介绍如何在低功耗嵌入式系统(例如边缘设备,移动系统(例如 Android,iOS 和 Raspberry Pi),Edge TPU 和 NVIDIA Jetson Nano 上部署经过训练的 TensorFlow 2.0(TF2.0)模型。 本章还介绍了自己动手工具包的训练和部署模型,例如 Google 自己做人工智能(AIY)工具包。 本章涵盖的其他主题是如何将经过训练的 TensorFlow(TF)模型转换为 TensorFlow Lite(TFLite)模型,他们之间的主要区别,以及两者的优势。
本章与前几章略有不同,从某种意义上说,它只是对 TF2.0 的更广泛关注的介绍。 也就是说,硬件领域...
TFLite 简介
TFLite 是一组工具,可帮助开发人员在二进制大小较小且延迟较低的设备上运行 TF 模型。 TFLite 由两个主要组件组成:TFLite 解释器(tf.lite.Interpreter)和 TFLite 转换器(tf.lite.TFLiteConverter)。 TFLite 解释器实际上是在低功耗设备(例如手机,嵌入式 Linux 设备和微控制器)上运行 TFLite 模型的。 另一方面,TFLite 转换器在可用于训练 TF 模型的强大设备上运行,并将训练后的 TF 模型转换为解释器的有效形式。
TFLite 旨在简化在设备上执行机器学习的过程,而无需通过网络连接发送任何数据。 这样可以改善延迟时间(因为没有通过网络传输数据),提高了隐私性(因为没有数据会离开设备)和脱机功能(因为不需要互联网连接就可以在任何地方发送数据)。
TFLite 的一些关键功能包括针对设备的经过优化的优化解释器(它支持在二进制大小较小的设备上优化的一组核心操作),针对多种语言(例如 Swift,C,C++,Java 和 Python 的 API),预训练的模型和教程(新手可以在低功耗设备上轻松部署机器学习模型)。 TFLite 旨在通过硬件加速以及预融合的激活和偏差进行高效和优化。
TFLite 的基本开发工作流程是选择模型,转换模型,将其部署到所需的设备并优化模型。 该模型可以是任何东西,从tf.keras自定义训练模型到从 TF 本身获取的预训练模型。
TFLite 入门
使用 TFLite 的第一步是选择要转换和使用的模型。 这包括使用预训练的模型,定制训练的模型或微调的模型。 TFLite 团队提供了一组预训练和预转换的模型,可以解决各种机器学习问题。 这些包括图像分类,对象检测,智能回复,姿势估计和分割。 使用经过微调的模型或经过定制训练的模型需要另一步骤,将它们转换为 TFLite 格式。
TFLite 旨在在设备上高效地执行模型,而这种效率的某些内在原因来自用于存储模型的特殊格式。 TF 模型必须先转换为这种格式,然后才能使用...
在移动设备上运行 TFLite
在本节中,我们将介绍如何在两种主要的移动操作系统(Android 和 iOS)上运行 TFLite。
Android 上的 TFLite
在 Android 上使用 TFLite 就像在 Android Studio 的build.gradle文件中的dependencies字段中添加 TFLite 并将其导入 Android Studio 一样容易:
dependencies { implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'}import org.tensorflow.lite.Interpreter;
一旦完成,下一步就是创建解释器的实例并加载模型。 可以使用 GitHub 上 TFLite 示例中的getModelPath函数getModelPath和loadModelFile加载转换后的 TFLite 文件来实现。 现在,要运行模型,只需使用解释器类的.run方法并为其提供所需的输入数据,如本例所示:
tflite.run(inp,out);
inp参数是输入数据,它将...
iOS 上的 TFLite
在 iOS 上使用 TFLite 的过程与此类似,其中包括安装 TFLite 解释器,加载模型并运行它。 再次按照“TFLite 入门”部分中的步骤操作,以创建和转换机器学习模型以在智能手机上使用。 我们将使用以下步骤在 iOS 上实现 TFLite:
- 通过将 TFLite 添加到项目的
root目录中的pod文件中来安装它:
use_frameworks!
pod 'TensorFlowLiteSwift'
通过运行pod install来安装包,这将安装pod文件中包括的所有包,包括新添加的TFLite包。 安装后,可以通过在swift文件顶部附近添加import TensorFlowLite来导入包。
- 要运行
interpreter,首先为张量分配内存:
let outputTensor: Tensor
do {
try interpreter.allocateTensors()
let inputTensor = try interpreter.input(at: 0)
- 然后,从图像缓冲区中删除
alpha组件以获取rgbData变量:
guard let rgbData = rgbDataFromBuffer(
thumbnailPixelBuffer,
byteCount: batchSize * inputWidth * inputHeight * inputChannels,
isModelQuantized: inputTensor.dataType == .uInt8
) else {
print("Failed to convert the image buffer to RGB data.")
return
}
- 接下来,将
rgbData变量复制到Tensor输入模型中:
try interpreter.copy(rgbData, toInputAt: 0)
- 通过调用
interpreter函数运行推理:
try interpreter.invoke()
- 获取
outputTensor函数以处理推理结果:
outputTensor = try interpreter.output(at: 0)
} catch let error {
print("Failed to invoke the interpreter with error: \(error.localizedDescription)")
return
}
然后可以处理结果并将其显示在应用中。
在低功率机器上运行 TFLite
TFLite 能够在低功耗和低二进制计算机上运行的能力使其在嵌入式 Linux 计算机上运行时非常强大。 TFLite 可以在许多流行的嵌入式 Linux 机器以及 Coral Dev Board 上运行。 在本节中,我们将介绍在三个设备上 TFLite 的构建,编译和运行。 涵盖的第一个设备是带有 Edge TPU 处理器的 Coral Dev Board,第二个设备是 NVIDIA Jetson Nano,最后一个是 Raspberry Pi。 NVIDIA Jetson Nano 是 NVIDIA 的小型而强大的计算机,可在图像分类,目标检测,分割和语音等应用中并行运行多个神经网络。
在 Edge TPU 处理器上运行 TFLite
Edge TPU 是一种小型处理器,能够执行深度前馈网络,例如卷积神经网络。 但是,它仅支持量化的 TFLite 模型。 量化是一种优化技术,可将所有 32 位浮点数转换为最接近的 8 位定点数。 这使模型更小,更快,尽管精度和准确率有所降低。
TF 支持两种类型的量化。 第一种量化方式是训练后量化。 通过将模型优化属性设置为带有tf.lite.Optimize.OPTIMIZE_FOR_SIZE的列表,可以在将 TF 模型转换为 TFLite 模型时完成此操作。 这导致权重被转换为 8 位精度,从而将延迟增加了多达 3 倍。 网络中其他更多计算密集型操作将转换为具有定点操作但具有浮点内存的混合操作。
另一种量化类型是量化感知训练,它使用伪造的量化节点来模拟前向和后向模型中量化的效果; 该量化是直接估计。 这是 Edge TPU 支持的唯一量化,并允许在其上运行 TFLite 模型。
Edge TPU 有两种可用方式:
- Coral 开发板,其中包含 TPU 以及预安装的所有必需软件和 API
- Edge TPU USB 扩展器,可在所需计算机上添加另一个处理器
USB 加速器与任何具有运行 Debian 的 USB 端口的 Linux 计算机兼容。 要设置 USB 加速器,请从这里下载.tar文件,然后解压缩并运行install.sh。
这里要注意的一点是,在安装过程中,安装程序将要求启用最大工作频率,这将大大加快推理时间,但也会使 TPU 摸起来很烫。
下图显示了将 TF 模型转换为 Edge TPU 模型并在其上运行的过程:
但是,Edge TPU 有两个约束。 如前所述,必须使用量化感知训练对Tensor参数进行量化。 张量大小必须恒定(这样就不能有动态大小); 模型参数必须恒定; 张量必须是一维,二维或三维张量,或者是三个最里面的大小大于 3 维的张量,并且只能包含 Edge TPU 支持的那些操作。 如果不满足这些要求,那么将仅编译某些模型。 模型图中发生不支持的操作的第一点是编译器将图分为两部分:一部分包含 Edge TPU 可以计算的所有操作,另一部分包含它不能计算的操作,这些部分将运行在 CPU 上:
一旦 TFLite 模型已编译并准备好运行,就可以使用 Edge TPU 运行时和 API 库执行该模型。 Edge TPU API 具有三个用于推理的关键 API:
ClassificationEngineAPI,执行图像分类。 要使用它,请通过指定模型来创建实例,然后将图像传递到该实例的ClassifyWithImage()方法,该方法返回标签和分数列表。DetectionEngineAPI,用于执行对象检测。 与先前的 API 一样,通过指定模型文件来创建实例,然后运行DetectWithImage()方法,该方法返回检测候选对象的列表,每个候选对象包含一个标签,一个得分和该对象的坐标。- 最终的关键 API 是允许压印的 API:一种迁移学习算法,可以进行模型重新训练而无需反向传播,并且可以在 Edge TPU 上运行。 要运行此 API,必须遵循三个步骤:
- 首先,确定嵌入张量,它是最后一个分类层的输入张量。
- 然后,切断最后一个分类层。
- 最后,完成嵌入提取器。
Edge TPU 的性能远远优于许多最强大的 CPU。 当在带或不带 USB 加速器的 IntelXeon®3.60 GHz 处理器上测试模型时,单个 Edge TPU 能够以每秒 2 瓦的功率每秒执行 4 万亿次操作; 嵌入式 1.5 GHz CPU; 和珊瑚开发委员会。 在运行 DeepLab 网络时,英特尔至强花费了 301 毫秒,带加速器的英特尔至强花费了 35 毫秒,嵌入式 CPU 花费了 1,210 毫秒,而珊瑚开发板花费了 156 毫秒。 显然,Edge TPU 对模型的延迟具有重大影响。
在 NVIDIA Jetson Nano 上运行 TF
NVIDIA Jetson Nano 是另一种嵌入式设备,可为机器学习应用提供强大的计算能力。 Jetson Nano 的前提与 Edge TPU 不同,因为 Jetson Nano 是一款小型而功能强大的 GPU 计算机。 Jetson Nano 可以像配置用于深度学习的任何机器一样使用,并且可以轻松安装 GPU 版本的 TF。 也不需要安装 CUDA 和 cuDNN,因为它已预先安装在系统上。
比较 TFLite 和 TF
如前所述,TFLite 模型与普通 TF 模型有很大不同。 TFLite 模型更快,更小且计算量更少。 这种区别来自 TFLite 模型的特殊存储和解释方式。
速度的首次提高来自模型存储的基本格式。.tflite模型文件以FlatBuffer格式存储,其中包含模型的简化形式和二进制形式。 FlatBuffer是适用于多种流行语言的高效跨平台序列化库,由 Google 创建,用于游戏开发和其他对性能至关重要的应用。 FlatBuffer格式在有效序列化模型数据并提供对这些数据的快速访问,同时保持较小的二进制大小方面起着至关重要的作用。 由于大量的数字数据,这对于模型存储很有用,这通常会在读取操作中产生很多延迟。 通过使用FlatBuffers,TFLite 可以绕过许多传统的文件解析和非解析操作,这在计算上非常昂贵。
TFLite 模型优化也一直延伸到设备上的硬件。 这是因为,由于电话处理器和嵌入式 CPU 的限制,必须以超高效标准使用所有处理器。 在 Android 上运行 TFLite 时,可访问 Android 神经网络 API,该接口可访问 Android 中的硬件加速推理操作,并且已接口,以利用有利的硬件加速来使用所使用的设备。 TFLite 还可以在电话和其他设备中使用内置的 GPU,从而使具有过多可并行化操作和量化敏感精度的模型的速度提高了近 7 倍。
如前所述,量化是另一种非常有影响力的优化技术。 量化被视为 TF 中的一种压缩技术。 神经网络中的权重和激活趋向于具有分布在相对较小范围内的三个值,因此可以有效地使用量化来压缩这些值。 由于神经网络往往对权重噪声具有鲁棒性,因此量化和舍入加到参数上的噪声对模型的整体准确率影响很小。 量化模型的好处在于,它可以有效地表示任意范围的范围,它们的线性扩展使乘法简单明了,而量化权重具有对称范围,可以实现下游硬件优化,而 32 位浮点数则无法实现。
如下图所示,将模型从 TF 转换为量化的 TFLite 模型会大大减少模型的推理时间和延迟:
AIY
Google 为语音和视觉应用发布了自己的制造商套件,称为 AIY。 这些套件随附了所有必需的零件和组件,以及在线易于理解的教程。 AIY 当前提供两种套件-语音套件和视觉套件。
语音套件
语音工具包提供了构建自然语言处理器并将其连接到 Google Assistant 或 Cloud Speech-to-Text 服务的功能。 该套件随附 Raspberry Pi Zero,以及定制设计的语音引擎盖和用于音频功能的扬声器。 该套件还随附可插入 Pi 的 SD 卡,以及用于许多最常见应用的大量演示,示例和摘要。 它还带有一个在设备上运行 Google Assistant 并将其转变为智能家居设备的应用。
要开始使用语音工具包,请按照这个页面上的说明构建设备。 该设备设计合理,易于组装和设置。 要设置设备,可以使用计算机或手机。 该套件的设置非常简单,可以通过安全外壳(SSH)或 HDMI 连接来完成。 完成后,可以运行许多演示来进一步了解和探索该工具包,例如前面提到的 Google Assistant 应用。
语音工具包可以完成的一些事情包括创建自定义语音用户界面和使用助手控制 IoT 设备。
可以使用套件中demo文件夹中包含的 Google Cloud 语音转文本 API 和 AIY API 在语音工具包上创建自定义语音用户界面。 该 API 增加了使用 Cloud Speech API,语音转文本以及控制 Vision Bonnet 上的 GPIO 引脚的功能。
要使用语音工具包和助手来控制 IoT 设备,您可以使用几种强大的技术。 都使用了用于物联网项目的 Wi-Fi 开发套件 Particle Photon 和用于创建对话界面的DialogFlow。 语音套件中包含的演示提供了打开和关闭连接到 Photon 的 LED 的代码。
视觉套件
视觉套件提供了构建智能相机的功能,该相机可以使用机器学习来查看和识别对象,甚至可以在其上运行自定义 TF 模型。 与语音工具包一样,该工具包还附带 Raspberry Pi Zero,定制设计的 Vision Bonnet,压电蜂鸣器和 Raspberry Pi Camera V2。 该套件随附一个预先存储有 AIY 系统图像的 SD 卡,其中包括针对多种计算机视觉应用的演示,例如图像分类,物体检测,人脸检测,食物分类和自动拍照。
和以前一样,可以按照这里。 该设备有一个简单的...
总结
TFLite 是 TF2.0 的一项功能,它采用 TF 模型并对其进行压缩和优化,以在嵌入式 Linux 设备或低功耗和低二进制设备上运行。 可以通过三种方式将 TF 模型转换为 TFLite 模型:从已保存的模型,tf.keras模型或具体函数。 转换模型后,将创建一个.tflite文件,然后可以将其传输到所需的设备并使用 TFLite 解释器运行。 该模型经过优化以使用硬件加速,并以FlatBuffer格式存储,以提高读取速度。 可以将其他优化技术应用于该模型,例如量化,以最小的精度权衡将 32 位浮点数转换为 8 位定点数。 可以在 TFLite 上运行的某些设备是 Edge TPU,NVIDIA Jetson Nano 和 Raspberry Pi。 Google 还提供了两个工具包,可为用户提供创建与视觉和语音相关的机器学习应用所需的硬件。
在下一章中,我们将学习如何从 TF1.x 迁移到 TF2.0。
七、从 TensorFlow 1.x 迁移到 2.0
本章将介绍如何将 TensorFlow 1.x(TF 1.x)代码转换为 TensorFlow 2.0(TF 2.0) 代码有两种方式。 第一种方法是使用更新脚本,该脚本会更改大多数 TF 1.x 代码,以便可以在 TF 2.0 中运行。 但是,这仅将所有tf.x API 调用转换为tf.compat.v1.x格式。 另一种方法是,考虑到对库所做的核心更改,将 TF 1.x 代码转换为惯用的 TF2.0 代码。 我们将讨论 TF 1.x 和 TF 2.0 之间的概念差异,它们之间的兼容性标准以及我们在语法和语义上进行迁移的方式。 我们还将展示从 TF 1.x 到 TF 2.0 的语法和语义迁移的几个示例,我们将通过它们提供参考和将来的信息。
本章将涵盖以下主题:
- TF 2.0 的主要变化
- 适用于 TF 2.0 的推荐技术
- 使代码 TF 2.0 原生
- 常见问题
- TF 2.0 的未来
TF 2.0 的主要变化
从 TF 1.x 迁移到 TF 2.0 时,您将遇到的主要变化涉及 API 清理。
TF 2.0 中的许多 API 都已被删除或移动。 主要更改包括删除tf.app,tf.flags和tf.logging,以支持其他 Python 模块,例如absl-py和内置的日志记录系统。
TF 2.0 在代码方面所做的最大更改之一就是急切执行。 TF 1.x 要求用户使用tf.*调用来手工拼接抽象语法树,以构建计算图,该图将与session.run()一起运行。 这意味着 TF 2.0 代码逐行运行,因此不再需要tf.control_dependancies()。
TF 1.x 中的session.run()调用与...非常相似。
适用于 TF 2.0 的推荐技术
第一条建议涉及在 TF 2.0 中处理常规代码工作流。 TF 1.x 中常见的工作流程是使用瀑布策略,其中所有计算都布置在默认图上。 然后,使用session.run()运行选定的张量。 在 TF 2.0 中,应将代码重构为较小的函数,这些函数将在需要时调用。 这些函数可以是普通的 Python 函数,但如果在另一个以tf.function注解的函数中调用它们,则仍可以在图模式下运行。 这意味着tf.function仅应用于注解高级计算,例如模型的前向传递或单个训练步骤。
以前,模型和训练循环所需的所有计算都将预先确定并编写,并使用session.run()执行。 这使得 TF 1.x 代码对于大多数编码人员来说很难遵循,因为模型的流程可能与图的编码方式完全不同,因为该图是在最后运行的。 急切执行和tf.function专门用于简化 TensorFlow 代码动态过程,并使其他开发人员更容易理解预编写的代码。
管理和跟踪变量是 TF 1.x 中另一个复杂的过程。 使用了许多方法来控制和访问这些变量,这为线性代码增加了更多的维度。 TF 2.0 更加强调使用tf.keras层和tf.estimator模型来管理模型中的变量。
这与手动滚动神经网络层和手动创建变量形成对比。 在以下示例中,必须跟踪权重和偏差变量,其形状的定义应远离模型的创建。 这使得难以更改模型并使模型适应不同的架构和数据集:
def dense(x, W, b):
return tf.nn.sigmoid(tf.matmul(x, W) + b)
@tf.function
def multilayer_perceptron(x, w0, b0, w1, b1, w2, b2 ...):
x = dense(x, w0, b0)
x = dense(x, w1, b1)
x = dense(x, w2, b2)
...
此代码的tf.keras实现非常简单明了,并确保开发人员不必担心变量和变量名的组织和管理。 它还可以轻松访问模型中的可训练变量:
layers = [tf.keras.layers.Dense(hidden_size, activation=tf.nn.sigmoid) for _ in range(n)]
perceptron = tf.keras.Sequential(layers)
# layers[3].trainable_variables => returns [w3, b3]
# perceptron.trainable_variables => returns [w0, b0, ...]
tf.keras模型还继承了tf.train.Checkpointable模型的方法,并与tf.function集成在一起,因此可以将它们直接保存到检查点并导出到SavedModels。
以下是迁移学习实现的示例,并显示tf.keras如何使收集相关值的子集,计算其梯度以及基于梯度对其进行调整变得容易:
trunk = tf.keras.Sequential([...])
head1 = tf.keras.Sequential([...])
head2 = tf.keras.Sequential([...])
path1 = tf.keras.Sequential([trunk, head1])
path2 = tf.keras.Sequential([trunk, head2])
# Train on primary dataset
for x, y in main_dataset:
with tf.GradientTape() as tape:
prediction = path1(x)
loss = loss_fn_head1(prediction, y)
# Simultaneously optimize trunk and head1 weights.
gradients = tape.gradient(loss, path1.trainable_variables)
optimizer.apply_gradients(zip(gradients, path1.trainable_variables))
# Fine-tune second head, reusing the trunk
for x, y in small_dataset:
with tf.GradientTape() as tape:
prediction = path2(x)
loss = loss_fn_head2(prediction, y)
# Only optimize head2 weights, not trunk weights
gradients = tape.gradient(loss, head2.trainable_variables)
optimizer.apply_gradients(zip(gradients, head2.trainable_variables))
# You can publish just the trunk computation for other people to reuse.
tf.saved_model.save(trunk, output_path)
所有尚未存储在内存中的数据集都应使用tf.dataset进行存储和流传输。 数据集在 TF 2.0 中是可迭代的,因此在急切的执行模式下,它们可以像任何其他 Python 可迭代的一样使用,例如列表和元组。 您还可以通过使用tf.function包装数据集迭代来利用数据集异步预取和流传输功能,该迭代将 Python 交互转换为与 AutoGraph 等效的图操作。 正如我们在本书前面所提到的,AutoGraph 采用默认的 Python 流并将其转换为基于图的代码。 例如,诸如if...else块之类的控制流将转换为tf.condition语句。 以下代码块向您展示了如何使用for块训练模型:
@tf.function
def train(model, dataset, optimizer):
for x, y in dataset:
with tf.GradientTape() as tape:
prediction = model(x)
loss = loss_fn(prediction, y)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
但是,如果您正在使用 Keras 的model.fit,则不必担心。 要使用model.fit在数据集上训练模型,只需将数据集传递给方法。 它将处理其他所有事项:
model.compile(optimizer=optimizer, loss=loss_fn)
model.fit(dataset)
使代码 TF 2.0 原生
使 TF 1.x 代码与 TF 2.0 代码兼容的最简单方法是运行系统上安装的更新脚本以及 TF 2.0 安装。 更新脚本使用tf.compat.v1模块。
为了向 TF 1.x 编写的代码提供向后兼容性,在 TF 2.0 中引入了tf.compat.v1模块。 tf.compat.v1模块替换了所有 TF 1.x 符号,例如tf.foo和tf.compat.v1.foo。 此模块允许转换为 TF 1.x 编写的大多数代码,以便可以在 TF 2.0 中运行。
作为简化此过程的一种方式,TensorFlow 提供了tf_upgrade_v2工具,该工具有助于尽可能简化转换。 该工具已预装...
转换 TF 1.x 模型
第一步是将所有tf.Session.run()调用替换为 Python 函数。 这意味着将tf.placeholder和feed_dict转换为函数参数。 这些成为函数的返回值。 此更改意味着与 TF 1.x 不同,可以使用标准的 Python 工具(例如pdb)来逐步调试该功能。 构建函数后,可以添加tf.function注解以在图模式下运行该函数,以及 TF 1.x 中等效的tf.Session.run调用的效率。
使用tf.layers API 创建的 TF 1.x 模型可以相对容易地转换为 TF 2.0。 tf.layers模块用于包含依赖于tf.variable_scope定义和重用变量的层函数。
以下代码块是使用tf.layers API 编写的 TF 1.x 中小型卷积神经网络的实现:
def model(x, training, scope='model'):
with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
x = tf.layers.conv2d(x, 32, 3, activation=tf.nn.relu,
kernel_regularizer=tf.contrib.layers.l2_regularizer(0.04))
x = tf.layers.max_pooling2d(x, (2, 2), 1)
x = tf.layers.flatten(x)
x = tf.layers.dropout(x, 0.1, training=training)
x = tf.layers.dense(x, 64, activation=tf.nn.relu)
x = tf.layers.batch_normalization(x, training=training)
x = tf.layers.dense(x, 10, activation=tf.nn.softmax)
return x
train_out = model(train_data, training=True)
test_out = model(test_data, training=False)
将模型转换为 TF 2.0 的最简单方法是使用tf.keras.Sequential,因为该模型由线性层组成。 从tf.layers到tf.keras.layers有一对一的转换,但有一些区别。 在 TF 2.0 代码中,训练参数不再传递给每个层,因为模型会自动处理该参数。
这是 TF 2.0 中的代码:
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(32, 3, activation='relu',
kernel_regularizer=tf.keras.regularizers.l2(0.04),
input_shape=(28, 28, 1)),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Flatten(),
tf.keras.layers.Dropout(0.1),
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Dense(10, activation='softmax')
])
train_data = tf.ones(shape=(1, 28, 28, 1))
test_data = tf.ones(shape=(1, 28, 28, 1))
train_out = model(train_data)
test_out = model(test_data, training=False)
如我们所见,tf.variable_scope没有用于组织为模型创建的变量。 在 TF 1.x 中,该范围将用于从模型中恢复变量。 在 TF 2.0 中,可以使用model.trainable_variables列出模型变量。
尽管从tf.layers到tf.keras.layers的转换相对简单,但是由于代码流的差异,转换变得更加复杂。
TF 1.x 中的低级 API 的一些示例包括使用变量作用域来控制重用,使用tf.get_variable创建变量,使用tf.placeholder和session.run定期访问集合以及手动初始化变量。 由于引入了系统范围内的急切执行,这些技术和策略中的许多现在已过时,因此以低级 API 编写的代码比以高级 API 编写的代码(例如tf.keras和tf.layers)需要更大的更改。 。
以下是使用 TF 1.x 的低级 API 编写的一些代码的示例:
in_a = tf.placeholder(dtype=tf.float32, shape=(2))
in_b = tf.placeholder(dtype=tf.float32, shape=(2))
def forward(x):
with tf.variable_scope("matmul", reuse=tf.AUTO_REUSE):
W = tf.get_variable("W", initializer=tf.ones(shape=(2,2)),
regularizer=tf.contrib.layers.l2_regularizer(0.04))
b = tf.get_variable("b", initializer=tf.zeros(shape=(2)))
return W * x + b
out_a = forward(in_a)
out_b = forward(in_b)
reg_loss = tf.losses.get_regularization_loss(scope="matmul")
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
outs = sess.run([out_a, out_b, reg_loss],
feed_dict={in_a: [1, 0], in_b: [0, 1]})
可以通过将前向函数更改为用tf.function注解的函数进行基于图的计算,删除session.run函数和变量范围并添加简单的函数调用来转换此代码。 将不会在W变量上全局调用正则化; 相反,它将被手动调用,而无需引用全局集合:
W = tf.Variable(tf.ones(shape=(2,2)), name="W")
b = tf.Variable(tf.zeros(shape=(2)), name="b")
@tf.function
def forward(x):
return W * x + b
out_a = forward([1,0])
out_b = forward([0,1])
regularizer = tf.keras.regularizers.l2(0.04)
reg_loss = regularizer(W)
正如我们所看到的,TF 2.0 代码比以前的 TF 1.x 代码更加 Python 化和简洁。
使用tf.placeholder的好处之一是可以控制图输入的形状,如果输入与预定形状不匹配,则会返回错误。 在 TF 2.0 中,仍然可以通过使用 Python 内置的assert命令来完成此操作。 这可以用来断言该函数的输入自变量的形状与输入自变量所期望的形状匹配。
现有的 TF 1.x 代码通常同时包含较低级别的 TF 1.x 变量和具有较高级别tf.layers的操作。 这意味着上述示例都不足以转换 TF 1.x 代码,并且需要tf.keras编程的更复杂形式,称为模型或层子类。
以下是在 TF 1.x 中使用tf.get_variable和tf.layers编写的原始代码:
def model(x, training, scope='model'):
with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
W = tf.get_variable(
"W", dtype=tf.float32,
initializer=tf.ones(shape=x.shape),
regularizer=tf.contrib.layers.l2_regularizer(0.04),
trainable=True)
if training:
x = x + W
else:
x = x + W * 0.5
x = tf.layers.conv2d(x, 32, 3, activation=tf.nn.relu)
x = tf.layers.max_pooling2d(x, (2, 2), 1)
x = tf.layers.flatten(x)
return x
train_out = model(train_data, training=True)
test_out = model(test_data, training=False)
通过将所有低层操作和变量包装在自定义创建的 Keras 层中,可以转换此代码。 这可以通过创建一个从tf.keras.layers.Layer类继承的类来完成:
# Create a custom layer for part of the model
class CustomLayer(tf.keras.layers.Layer):
def __init__(self, *args, **kwargs):
super(CustomLayer, self).__init__(*args, **kwargs)
def build(self, input_shape):
self.w = self.add_weight(
shape=input_shape[1:],
dtype=tf.float32,
initializer=tf.keras.initializers.ones(),
regularizer=tf.keras.regularizers.l2(0.02),
trainable=True)
# Call method will sometimes get used in graph mode,
# training will get turned into a tensor
@tf.function
def call(self, inputs, training=None):
if training:
return inputs + self.w
else:
return inputs + self.w * 0.5
前面的代码创建了一个名为CustomLayer的类,该类继承了tf.keras.layers.Layer类的属性。 此技术允许在tf.keras模型内部使用任何类型的低级代码,而不管它是使用Sequential API 还是functional API 的模型。 此类中有两种方法:
build():此方法修改继承的类的默认生成方法。 在这种方法中,应该创建模型所需的所有变量。 尽管可以在模型的the __init__()方法中完成此操作,但建议使用build(),以便在正确的最佳时间构建变量。 可以使用self.add_weight函数完成此操作,以使 Keras 跟踪变量和正则化损失。call():在输入张量上调用模型时,将运行此方法。 此方法通常采用两个参数:inputs和training。 尽管inputs参数是不言自明的,但training参数可能不会一直使用,但是对于在该层中使用批量规范化和丢弃的情况而言是必不可少的。 该功能由tf.function装饰器注解,以实现签名,基于图的优点以及自动控件的依赖关系。
写入此自定义层后,即可在tf.keras模块中的任何位置使用它。 对于此转换,将使用Sequential API:
train_data = tf.ones(shape=(1, 28, 28, 1))
test_data = tf.ones(shape=(1, 28, 28, 1))
# Build the model including the custom layer
model = tf.keras.Sequential([
CustomLayer(input_shape=(28, 28, 1)),
tf.keras.layers.Conv2D(32, 3, activation='relu'),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Flatten(),
])
train_out = model(train_data, training=True)
test_out = model(test_data, training=False)
升级训练循环
将 TF 1.x 代码转换为惯用的 TF 2.0 代码的第二步是升级训练管道。 TF 1.x 训练管道涉及对优化器,损失和预测的多个tf.Session.run()调用。 这样的训练循环还涉及样板代码,该样板代码被编写为将训练结果记录到控制台以方便监督。
在 TF 2.0 中,可以使用三种类型的训练循环。 这些循环中的每一个都有不同的优点和缺点,并且难度,API 级别和复杂性各不相同。 它们如下:
- 第一种训练循环是
tf.keras.Model.fit()。 这是一个内置的训练循环,可处理训练的所有方面,并为各种 Keras 提供统一的接口...
转换时要注意的其他事项
从 TF 1.x 迁移到 TF 2.0 时,还需要进行其他几个主要转换。 比起我们先前描述的对话,要困难得多的对话是将以 TF-Slim 编写的代码转换为 TF 2.0。
由于 TF-Slim 打包在tf.contrib.layers库下,因此即使在兼容性模块中,它也无法在 TF 2.0 中使用。 这意味着要将 TF-Slim 代码转换为 TF 2.0 格式,通常需要更改整个代码动态。
这包括从代码中删除参数范围,因为所有参数在 TF 2.0 中都应明确。 normalizer_fn和activation_fn函数应分为各自的层。 请注意,TF-Slim 层的参数名称和默认值与tf.keras层不同。
将 TF-Slim 模型转换为 TF 2.0 的最简单方法是将其转换为 TF 1.x 中的tf.layers API,然后将其转换为tf.keras.layers。
另一个需要注意的转换细节是,在 TF 2.0 中,所有指标都是具有三种主要方法的对象:update_state()(添加新的观察值),result()(获取指标的当前结果)和reset_states()( 清除所有观察结果。
度量对象也是可调用的,并且在新观察值上调用时,它们会累加值并返回最新结果。
以下示例向我们展示了如何在自定义训练循环中使用指标:
- 创建度量标准对象,该度量标准对象在每次调用时都会累积度量标准数据:
loss_metric = tf.keras.metrics.Mean(name='train_loss')
accuracy_metric = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')
@tf.function
def train_step(inputs, labels):
with tf.GradientTape() as tape:
predictions = model(inputs, training=True)
regularization_loss = tf.math.add_n(model.losses)
pred_loss = loss_fn(labels, predictions)
total_loss = pred_loss + regularization_loss
gradients = tape.gradient(total_loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
- 更新指标:
loss_metric.update_state(total_loss)
accuracy_metric.update_state(labels, predictions)
for epoch in range(NUM_EPOCHS):
- 重置指标:
loss_metric.reset_states()
accuracy_metric.reset_states()
for inputs, labels in train_data:
train_step(inputs, labels)
- 获取度量结果:
mean_loss = loss_metric.result()
mean_accuracy = accuracy_metric.result()
print('Epoch: ', epoch)
print(' loss: {:.3f}'.format(mean_loss))
print(' accuracy: {:.3f}'.format(mean_accuracy))
常见问题
在本节中,将解决有关从 TF 1.x 迁移到 TF 2.0 的一些常见问题。
用 TF 2.0 编写的代码的速度是否与基于图的 TF 1.x 代码相同?
是的,使用tf.function或tf.keras在 TF 2.0 中编写的代码将具有与 TF 1.x 相同的速度和最优性。 正如我们在本章前面提到的那样,使用tf.function注解主要功能允许模型以图模式运行,并且该功能中的所有计算和逻辑都将编译为一个计算图。 使用tf.keras定义和训练 TensorFlow 模型也是如此。 使用model.fit方法还将在图模式下训练模型,并具有所有优点和优化功能,这些优点和优点包括:
TF 2.0 的未来
TF 2.0 目前处于 beta 版本,因此仍在开发中。 即将出现的一些关键功能包括对包的修改,例如 TensorBoard,TensorFlow Lite,TensorFlow.js,用于 TensorFlow 的 Swift 和 TensorFlow Extended,以及对基本 API 的微小更改。 TensorBoard 将看到增强功能,例如改进的超参数调优功能,引入托管功能以使共享仪表板变得容易,并使插件能够使用不同的前端技术,例如 ReactJS。 TensorFlow Lite 将扩大支持的操作范围,将 TF 2.0 模型更轻松地转换为 TFLite,并扩展对 Edge TPU 和 AIY 板的支持。 TensorFlow.js 和用于 TensorFlow 的 Swift 都将看到速度和性能方面的改进,并且很快将包含一组丰富的示例和带有端到端教程的入门指南。 TF Extended 即将与 TF 2.0 基本 API 完全集成,并将包括完全协调的端到端工作流程和训练函数。
TF 2.0 基本 API 将包括针对任务的更多预制估计器,例如增强树,随机森林,最近邻搜索和 k 均值聚类。 tf.distribute.Strategy模型将扩展其对 Keras 子模型,TPU 和多节点训练的支持,以在多个处理器上实现更优化和更快的训练。
当前正在开发的另一个主要附加功能是tf-agents模块。 该模块将核心强化学习算法实现为智能体,该算法定义了与环境进行交互的策略并从集体经验中训练了该策略。 TF-agents与 OpenAI Gym 框架一起实现,并抽象了许多用于开发的关键强化学习算法。 该模块当前处于预发布状态,但将于今年晚些时候发布。
可看的更多资源
可以在 TensorFlow Beta 网站上找到教程和许多其他资源,其中包含有关创建和训练机器学习模型的关键因素的信息。 该页面还为该领域的许多重要技术提供了许多有用的端到端教程。
可以在网站上找到 TF 2.0 的官方文档,以及该模块中每个 API 的详细文档。 该站点还具有指向其他 TensorFlow 模块和功能的链接。
TensorFlow Medium 博客还提供有关 TensorFlow 库和服务状态的许多更新,并且源源不断的有用新闻和...
总结
本章介绍了两种将 TF 1.x 代码转换为 TF 2.0 代码的方法。 第一种方法是使用随附的升级脚本,该脚本会将所有 API 调用从tf.x更改为tf.compat.v1.x。 这允许 TF 1.x 代码在 TF 2.0 中运行,但不会从 TF 2.0 中带来的升级中受益。 第二种方法是将 TF 1.x 更改为惯用的 TF 2.0 代码,这涉及两个步骤。 第一步是将所有模型创建代码更改为 TF 2.0 代码,这涉及使用对函数的sess.run调用,以及将占位符和字典馈入函数的参数来更改张量。 使用tf.layers API 创建的模型与tf.keras.layers具有一对一的比较。 第二步是通过使用tf.keras.Model.fit或带有tf.GradientTape的自定义训练循环来升级训练管道。
TF 2.0 改变了 TensorFlow 代码的编写和组织方式。 TF 2.0 中的一些主要更改是对主模块中 API 的重组和清理。 这包括删除tf.contrib模块。 其他更改包括增加了代码范围内的急切执行,以简化调试和使用范围。 由于急切执行,因此在 TF 2.0 中创建的变量的行为类似于普通的 Python 变量。 这意味着用于处理全局变量的 TF 1.x API 已过时,因此已在 TF 2.0 中删除。 这使我们到书的结尾!