第 3 部分:TensorFlow 2.0 - 模型推断和部署以及 AIY
如果您使用过 TensorFlow 1.x,则本部分将重点介绍迁移到 TensorFlow 2.0 所需的总体概念更改。 它还将教您使用 TensorFlow 可以进行的各种 AIY 项目。 最后,本节向您展示如何将 TensorFlow Lite 与跨多个平台的低功耗设备一起使用。
本节包含以下章节:
- 第 5 章,“模型推理管道 – 多平台部署”
- 第 6 章,“AIY 项目和 TensorFlow Lite”
五、模型推理管道 - 多平台部署
训练完模型后您会怎么做? 用它? 如果答案是肯定的,那么您将如何使用它? 您正在寻找的答案是推理。 简而言之,推理过程是确保机器学习模型可用于满足实际用户需求的基础。 正式地说,推理是有效地计算经过训练的机器学习模型以满足用户需求的过程。 可以在各种硬件类型上进行推断,包括服务器以及最终用户设备(例如电话和 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
可在这个页面中获得本章的代码文件。
机器学习工作流程 - 推理阶段
机器学习应用的最常见子集之一遵循构建一次,并多次使用范式。 这种类型的应用涉及所谓的推理阶段。 在推断阶段,开发人员必须专注于运行模型以满足用户需求。 满足用户需求可能涉及从用户那里接受输入并对其进行处理以返回适当的输出。 下图描述了典型的高级机器学习应用工作流程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CvP0nnKr-1681704017943)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/whats-new-tf2/img/4dba2f20-e39c-4cce-98ba-9f2d018a4f3e.png)]
从上图,我们可以看到推理过程如何适应整体情况。 在随后的应用中…
从推理的角度理解模型
实现基于机器学习的应用的开发人员可以依靠的一件事是使生活变得轻松,无论所服务模型中的实际计算如何,向用户提供模型的过程或多或少都是相同的。 这意味着,如果实现正确,工程师可能不必在每次数据科学家更新模型时都重新构建部署管道。 这可以通过利用抽象的力量来实现。 这里的一个关键抽象是模型存储和加载的格式。 通过引入标准化格式,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)
对应于节点的输出,在这种特定情况下,该输出恰好是输入的总和:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9eLDsaC-1681704017944)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/whats-new-tf2/img/2e09fd48-ab43-4546-8eab-1df5c1dae380.png)]
使用数据流范例可以使 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 访问此模型。 有关这些的详细信息,请参见以下链接:
- https://www.tensorflow.org/tfx/serving/api_rest
- https://github.com/tensorflow/serving/blob/master/tensorflow_serving/apis/prediction_service.proto
请记住,每次调用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 环境在后端服务器上提供机器学习服务。 请参阅这个页面上有关如何执行此操作的文档。
浏览器中的推断
您可能还记得,在前面的部分中,我们简要讨论了分布式系统。 在那里,我们讨论了主要在主机服务器上执行基于机器学习的计算的场景。 在这里,我们将研究在浏览器中在用户端执行这些计算的场景。 这样做的两个重要优点如下:
- 计算被推送到用户端。 主机不必担心为执行计算而管理服务器。
- 将模型推送到用户端意味着不必将用户数据发送到主机。 对于使用敏感或私有用户数据的应用来说,这是一个巨大的优势。 因此,浏览器中的推理成为对隐私至关重要的机器学习应用的绝佳选择:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rhsf8SIv-1681704017944)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/whats-new-tf2/img/ed83cc9d-b1bb-4e26-b251-f69247244b1b.png)]
上图中描述的工作流说明了从头构建模型然后允许最终用户在其 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:
- https://www.tensorflow.org/js/guide
- https://www.tensorflow.org/js/tutorials
- https://www.tensorflow.org/js/demos
移动和物联网设备上的推理
在过去几年中,智能手机的使用呈指数增长,并且以不减缓的方式持续增长。 其他物联网设备在我们的日常生活中也变得越来越普遍。 使用率的这些上升趋势对机器学习系统产生了有趣的影响。 与普通主机相比,这些平台通常资源有限。 结果,需要其他优化来在此类设备上进行推理。 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 模型并在其上运行的过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lCTiwxkG-1681704017945)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/whats-new-tf2/img/ee7dff88-2f0b-4a1a-86b9-966f0c8ce1d7.png)]
但是,Edge TPU 有两个约束。 如前所述,必须使用量化感知训练对Tensor
参数进行量化。 张量大小必须恒定(这样就不能有动态大小); 模型参数必须恒定; 张量必须是一维,二维或三维张量,或者是三个最里面的大小大于 3 维的张量,并且只能包含 Edge TPU 支持的那些操作。 如果不满足这些要求,那么将仅编译某些模型。 模型图中发生不支持的操作的第一点是编译器将图分为两部分:一部分包含 Edge TPU 可以计算的所有操作,另一部分包含它不能计算的操作,这些部分将运行在 CPU 上:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4OjpxEr-1681704017945)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/whats-new-tf2/img/aedc0f8e-e06f-4917-ad0a-41abe07ad00b.png)]
一旦 TFLite 模型已编译并准备好运行,就可以使用 Edge TPU 运行时和 API 库执行该模型。 Edge TPU API 具有三个用于推理的关键 API:
ClassificationEngine
API,执行图像分类。 要使用它,请通过指定模型来创建实例,然后将图像传递到该实例的ClassifyWithImage()
方法,该方法返回标签和分数列表。DetectionEngine
API,用于执行对象检测。 与先前的 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 模型会大大减少模型的推理时间和延迟:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hPM7g7vJ-1681704017945)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/whats-new-tf2/img/6b0e98ac-64e7-4b03-ad69-abbde7fee70d.png)]
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。
第 4 部分:TensorFlow 2.0 - 迁移,总结
本书的本部分将以高级方式总结 TensorFlow 2.0(TF 2.0)的使用,以及与以前版本相比 TF 2.0 的兼容性差异。 如果您使用过 TensorFlow 1.x(TF 1.x),则本书的这一部分将重点介绍如何迁移到 TF 2.0。 尽管有一个将 TF 1.x 代码转换为 TF 2.0 的迁移 API,但它只是进行语法到语法的转换。 本部分还将深入指导您完成从语义上将 TF 1.x 代码转换为 TF 2.0 所需的代码级更改。
本节包含以下章节:
- 第 7 章“从 TensorFlow 1.x 迁移到 2.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 中删除。 这使我们到书的结尾!