TensorFlow-企业版实用指南-全-
TensorFlow 企业版实用指南(全)
原文:
annas-archive.org/md5/fc9b2aefad4448ff964cb34cb1cdff6f译者:飞龙
前言
TensorFlow 作为一个 机器学习 (ML) 库,已经发展成为一个成熟的生产就绪生态系统。本书通过实际示例,帮助你使用最佳设置构建和部署 TensorFlow 模型,确保长期支持,而无需担心库的弃用或在修复漏洞和解决问题时被落下。
本书首先展示了如何优化你的 TensorFlow 项目并为企业级部署做好准备。接下来,你将学习如何选择 TensorFlow 的版本。随着学习的深入,你将了解如何通过遵循 TensorFlow Enterprise 提供的推荐实践,在稳定和可靠的环境中构建和部署模型。本书还将教你如何更好地管理你的服务,并提升 人工智能 (AI) 应用的性能和可靠性。你将发现如何使用各种企业级服务来加速在 Google Cloud 上的 ML 和 AI 工作流。最后,你将学习如何扩展你的 ML 模型,并在 CPU、GPU 和云 TPUs 上处理大量工作负载。
在本书的学习结束时,你将掌握 TensorFlow Enterprise 模型开发、数据管道、训练和部署所需的模式。
本书的读者
本书面向数据科学家、机器学习开发者或工程师,以及希望从零开始学习和实现 TensorFlow Enterprise 提供的各种服务和功能的云计算从业者。具备基本的机器学习(ML)开发流程知识将非常有帮助。
本书内容
第一章,TensorFlow Enterprise 概述,介绍了如何在 Google Cloud Platform (GCP) 环境中设置和运行 TensorFlow Enterprise。这将让你获得初步的实践经验,了解 TensorFlow Enterprise 如何与 GCP 中的其他数据服务集成。
第二章,在 Google AI 平台上运行 TensorFlow Enterprise,介绍了如何使用 GCP 设置和运行 TensorFlow Enterprise。作为一个独特的 TensorFlow 发行版,TensorFlow Enterprise 可以在多个(但不是所有)GCP 平台上找到。为了确保正确的发行版得到配置,使用这些平台非常重要。
第三章,数据准备与处理技术,介绍了如何处理原始数据并将其格式化,使其能够独特地适应 TensorFlow 模型训练过程。我们将探讨一些关键的 TensorFlow Enterprise API,它们可以将原始数据转换为 Protobuf 格式以便高效流式传输,这是一种推荐的将数据输入训练过程的工作流。
第四章,可重用模型和可扩展数据管道,描述了 TensorFlow Enterprise 模型的构建或重用的不同方式。这些选项提供了灵活性,以适应构建、训练和部署 TensorFlow 模型的不同情境需求。掌握这些知识后,你将能够做出明智的选择,并理解不同模型开发策略之间的权衡。
第五章,大规模训练,阐述了使用 TensorFlow Enterprise 分布式训练策略将模型训练扩展到集群(无论是 GPU 还是 TPU)。这将使你能够构建一个稳健的模型开发和训练过程,并充分利用所有可用的硬件资源。
第六章,超参数调优,重点讲解了超参数调优,因为这是模型训练中必不可少的一部分,尤其是在构建自己的模型时。TensorFlow Enterprise 现在提供了高级 API 来支持先进的超参数空间搜索算法。通过本章内容,你将学习如何利用分布式计算能力,减少超参数调优所需的训练时间。
第七章,模型优化,探讨了你的模型是否足够精简高效。你的模型运行是否尽可能高效?如果你的使用场景需要模型在资源有限的情况下运行(如内存、模型大小或数据类型),例如在边缘设备或移动设备上的应用,那么就该考虑模型运行时优化了。本章讨论了通过 TensorFlow Lite 框架进行模型优化的最新方法。完成本章后,你将能够将训练好的 TensorFlow Enterprise 模型优化到尽可能轻量,以便进行推理。
第八章,模型训练和性能的最佳实践,聚焦于模型训练中两个普遍存在的方面:数据摄取和过拟合。首先,有必要建立一个数据摄取管道,无论训练数据的大小和复杂度如何,它都能正常工作。本章中,介绍和演示了使用 TensorFlow Enterprise 数据预处理管道的最佳实践和建议。其次,在处理过拟合时,讨论了正则化的标准做法,以及 TensorFlow 团队最近发布的一些正则化方法。
第九章,部署 TensorFlow 模型,介绍了将模型作为 Web 服务进行推理的基本知识。你将学习如何通过构建模型的 Docker 镜像,使用 TensorFlow Serving 来服务 TensorFlow 模型。在本章中,你将首先学习如何在本地环境中使用保存的模型。然后,你将以 TensorFlow Serving 为基础镜像,构建该模型的 Docker 镜像。最后,你将通过 Docker 容器暴露的 RESTful API,将此模型作为 Web 服务提供。
充分利用本书
拥有 Keras API 的基础知识和经验会非常有帮助,因为本书基于 2.x 以上版本的 TensorFlow,在该版本中,Keras API 已被官方支持并作为 tf.keras API 采用。此外,了解图像分类技术(卷积和多类分类)也会有所帮助,因为本书将图像分类问题作为介绍和解释 TensorFlow Enterprise 2 新功能的载体。另一个有用的工具是 GitHub。具备克隆 GitHub 仓库并浏览文件结构的基础经验,将有助于下载本书中的源代码。
从机器学习的角度来看,理解模型架构、特征工程过程和超参数优化的基本知识将非常有帮助。本书假设你已熟悉基本的 Python 数据结构,包括 NumPy 数组、元组和字典。
如果你使用的是本书的数字版,我们建议你自己输入代码,或者通过 GitHub 仓库访问代码(链接在下一部分提供)。这样做有助于避免由于复制/粘贴代码而产生的潜在错误。
下载示例代码文件
你可以通过 GitHub 下载本书的示例代码文件,地址:github.com/PacktPublishing/learn-tensorflow-enterprise/。如果代码有更新,将会在现有的 GitHub 仓库中进行更新。
我们还提供了来自我们丰富的书籍和视频目录的其他代码包,访问地址:github.com/PacktPublishing/。快来看看吧!
下载彩色图片
我们还提供了一个 PDF 文件,包含本书中使用的屏幕截图/图表的彩色图像。你可以从这里下载:static.packt-cdn.com/downloads/9781800209145_ColorImages.pdf
使用的约定
本书中使用了若干文本约定。
Code in text:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号。例如:‘就像 lxterminal 一样,我们也可以从这里运行 Linux 命令。’
代码块的设置如下:
p2 = Person()
p2.name = 'Jane'
p2.age = 20
print(p2.name)
print(p2.age)
任何命令行输入或输出都按如下方式写出:
sudo apt-get install xrdp -y
粗体:表示新术语、重要词汇或屏幕上显示的词语。例如,菜单或对话框中的词语会以这种方式出现在文本中。举个例子:“打开 Windows PC 上的远程桌面连接应用程序。”
提示或重要说明
以这种形式出现。
联系我们
我们始终欢迎读者的反馈。
customercare@packtpub.com。
勘误:虽然我们已经尽力确保内容的准确性,但错误是难以避免的。如果您在本书中发现了错误,欢迎向我们报告。请访问 www.packtpub.com/support/errata,选择您的书籍,点击“勘误提交表单”链接并输入详细信息。
copyright@packt.com,并附带链接到该资料。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且有兴趣撰写或参与编写书籍,请访问 authors.packtpub.com。
评论
请留下评论。当您阅读并使用完本书后,不妨在您购买书籍的站点上留下评论。潜在读者可以根据您的公正评价做出购买决定,Packt 也能了解您对我们产品的看法,我们的作者也能看到您对他们书籍的反馈。谢谢!
欲了解更多关于 Packt 的信息,请访问 packt.com。
第一章:TensorFlow 企业服务与功能
欢迎使用 TensorFlow 企业版!这是一个包含所有构建模块和 API 的 TensorFlow 发行版,支持更多优化、更好的监控、更高级的 API 以及长期支持。在本部分中,您将了解如何在 Google Cloud 原生平台和服务中开始使用 TensorFlow 企业版。
本节包括以下章节:
-
第一章,TensorFlow 企业版概述
-
第二章,在 Google AI 平台上运行 TensorFlow 企业版
第一章:
第二章:TensorFlow Enterprise 概览
在本章节的介绍中,您将学习如何在Google Cloud Platform(GCP)环境中设置和运行 TensorFlow Enterprise。这将帮助您获得一些关于 TensorFlow Enterprise 如何与 GCP 中的其他服务集成的初步实践经验。TensorFlow Enterprise 最重要的改进之一是与 Google Cloud 中的数据存储选项(如 Google Cloud Storage 和 BigQuery)的集成。
本章首先介绍如何完成云环境的一次性设置并启用必要的云服务 API。然后我们将看到如何在大规模下轻松地与这些数据存储系统进行协作。
在本章中,我们将讨论以下主题:
-
了解 TensorFlow Enterprise
-
配置 TensorFlow Enterprise 的云环境
-
访问数据源
了解 TensorFlow Enterprise
TensorFlow 已经成为一个包含众多有价值资产的生态系统。其受欢迎和多功能的核心在于一个全面的机器学习库和随着新特性和能力迅速发展的模型模板。受欢迎的背后是代价,这个代价表现为复杂性、复杂的依赖关系以及 API 更新或弃用时间表,这些都可能轻易地破坏那些不久前辛苦建立的模型和工作流。学习并在代码中使用最新改进以构建模型来实验您的想法和假设是一回事,但如果您的工作是为长期生产使用、维护和支持构建模型,那又是另外一回事。
与早期的 TensorFlow 相关的另一个问题是代码调试过程。在 TensorFlow 1 中,惰性执行使得测试或调试代码变得相当棘手,因为代码除非被包装在一个会话(即图形)中,否则不会执行。从 TensorFlow 2 开始,急切执行(eager execution)终于成为了一个一等公民。另外,TensorFlow 2 的另一个受欢迎的新增功能是采用了 Keras 高级 API。这使得编码、实验和维护模型变得更加容易,同时也提高了代码的可读性及训练流程。
对于企业采用来说,通常有三个主要的挑战需要关注:
-
第一个挑战是 规模。生产级模型必须使用大量数据进行训练,通常将其适配到单节点计算机的内存中并不现实或可能。这也可以被看作是另一个问题:如何将训练数据传递给模型?看起来,自然和本能的做法是声明并使用整个数据集作为类似 Python 结构的 NumPy 数组 或 pandas DataFrame,就像我们在许多开源示例中看到的那样。但如果数据量过大,那么使用另一种方式将数据传入模型实例似乎是合理的,这类似于 Python 迭代器。实际上,TensorFlow.io 和 TensorFlow 数据集库 专门为了解决这个问题。我们将在后续章节中看到它们如何将数据批量传递给模型训练过程。
-
第二个挑战通常是在考虑企业采用 TensorFlow 时出现的 可管理性 问题。TensorFlow 的向后兼容性不是其强项,因为 API 会非常快速地更新和发布新版本,从而替代或淘汰旧版本。这包括但不限于库版本、API 签名和使用风格的弃用。正如你现在可以想象的,这对于开发、调试和维护代码库来说是一个致命问题;它也不利于管理生产环境的稳定性和可复现性,以及其评分结果。这对于管理和控制机器学习开发基础设施以及企业项目中的标准实践的人来说,很容易变成一场噩梦。
-
第三个挑战是 API 改进、补丁发布和 bug 修复的工作。为了解决这个问题,TensorFlow 将这些工作纳入了 长期支持。通常,对于任何 TensorFlow 发布版本,Google 的 TensorFlow 团队承诺仅提供最多一年的修复。然而,对于企业来说,这对于他们从开发周期中获得适当的投资回报来说太短。因此,对于企业的关键任务性能,TensorFlow 发布的长期承诺是至关重要的。
TensorFlow Enterprise 是为了解决这些挑战而创建的。TensorFlow Enterprise 是 TensorFlow 的一个特殊发行版,仅通过 Google Cloud 的各项服务提供。TensorFlow Enterprise 可以通过以下方式获得:
-
Google Cloud AI Notebooks
-
Google Cloud AI 深度学习虚拟机
-
Google Cloud AI 深度学习容器
-
部分可用于 Google Cloud AI 训练
依赖项,如驱动程序和库版本兼容性,由 Google Cloud 管理。它还提供与其他 Google Cloud 服务(如 Cloud Storage 和数据仓库(BigQuery))的优化连接。目前,TensorFlow Enterprise 支持 Google Cloud 的版本 1.15、2.1 和 2.3,GCP 和 TensorFlow 团队将为其提供最长三年的长期支持,包括错误修复和更新。
除了这些独占服务和托管功能外,TensorFlow 团队还通过提供白手套服务将企业支持提升到另一个层次。这是与 Google Cloud Support 独立的服务。在这种情况下,Google 的 TensorFlow 工程师将与合格的企业客户合作,解决问题或提供前沿 AI 应用中的错误修复。
TensorFlow Enterprise 包
在撰写本书时,TensorFlow Enterprise 包括以下几个包:

图 1.1 – TensorFlow 包
我们将在第二章,在 Google AI Platform 上运行 TensorFlow Enterprise 中详细讲解如何启动 JupyterLab,但目前作为演示,可以在 JupyterLab 单元格中执行以下命令作为 CLI 命令。它将提供您实例中每个包的版本信息,确保版本一致性:
!pip list | grep tensorflow
这是输出结果:

图 1.2 – Google Cloud AI Platform JupyterLab 环境
我们确认环境正在运行 TensorFlow Enterprise 发行版及所有库版本。了解这些信息有助于今后的调试和协作工作。
配置 TensorFlow Enterprise 的云环境
假设您已经设置了 Google Cloud 账户并配置了计费方式,在开始使用 TensorFlow Enterprise 之前,您需要在 Google Cloud 中完成一些一次性设置步骤。这些设置包括以下步骤:
-
创建云项目并启用计费。
-
创建 Google Cloud Storage 存储桶。
-
启用必要的 API。
以下是这些步骤的一些快速说明。
设置云环境
现在我们将看看在开始使用 TensorFlow Enterprise 之前,需要在 Google Cloud 中进行哪些设置。这些设置是必要的,以便 Google Cloud 的关键服务能够无缝集成到用户租户中。例如,项目 ID 用于启用资源创建凭证和访问权限,在 TensorFlow 工作流中处理数据时访问不同的服务。通过项目 ID,您可以将数据读写到 Cloud Storage 和数据仓库中。
创建项目
这是第一步。此步骤是为了启用计费,以便您可以使用几乎所有的谷歌云资源。大多数资源会要求提供项目 ID。它还可以帮助您通过了解哪些服务贡献于每个工作负载,来组织和跟踪您的支出。让我们开始吧:
-
创建项目 ID 的页面 URL 是
console.cloud.google.com/cloud-resource-manager。在您登录 GCP 门户后,您将看到一个类似于此的面板:
![图 1.3 – 谷歌云项目创建面板]()
图 1.3 – 谷歌云项目创建面板
-
点击创建项目:
![图 1.4 – 创建新项目]()
图 1.4 – 创建新项目
-
然后提供一个项目名称,平台会立即为您生成一个项目 ID。您可以接受它或进行编辑。如果您已经有一些活动的项目,它可能会给您一个警告,提示您可以创建的项目数量:
![图 1.5 – 项目名称和项目 ID 分配]()
图 1.5 – 项目名称和项目 ID 分配
-
记下项目名称和项目 ID。将它们保存以供将来使用。点击创建,很快您将看到该项目的系统仪表板:

图 1.6 – 主要项目管理面板
项目 ID 在访问数据存储时会频繁使用。它也用于跟踪云租户中的资源消耗和分配。
创建谷歌云存储桶
谷歌 云存储桶是存储模型和模型资产的一种常见方式,尤其是来自模型训练工作的资产。创建存储桶非常简单。只需在左侧面板中找到存储并选择浏览器:

图 1.7 – 谷歌云存储选项
点击创建存储桶,并按照面板中的指示操作。在所有情况下,系统会为您选择默认选项:
-
选择数据存储位置。这是成本和可用性之间的权衡,按性能衡量。默认设置是多区域,以确保最高的可用性。
-
选择数据的默认存储类。此选项让您决定与检索操作相关的成本。默认设置是频繁访问数据的标准级别。
-
选择如何控制对对象的访问。这为存储桶提供了两种不同的访问级别。默认设置是对象级别权限(ACLs)以及存储桶级别权限(IAM)。
-
高级设置(可选)。在这里,您可以选择加密类型、存储桶保留策略以及任何存储桶标签。默认设置是谷歌托管的密钥,没有保留策略和标签:

图 1.8 – 存储桶创建过程和选择
启用 API
现在我们有了一个项目,但在开始使用 Google Cloud 服务之前,我们需要启用一些 API。这个过程只需要做一次,通常是在创建项目 ID 时完成:
-
现在,让我们为你选择的项目启用计算引擎 API:
![图 1.9 – 项目的 Google Cloud APIs 和服务]()
图 1.9 – 项目的 Google Cloud APIs 和服务
可选:然后选择启用 API 和服务。
你可以现在启用,或者在本书中的练习过程中启用。如果你第一次使用某个特定的云服务,你可以在进行时启用相应的 API:
![图 1.10 – 启用 API 和服务]()
图 1.10 – 启用 API 和服务
-
在搜索框中输入
Compute Engine API:![图 1.11 – 启用计算引擎 API]()
图 1.11 – 启用计算引擎 API
-
你将看到你项目中计算引擎 API的状态,如下图所示。如果没有启用,请启用它:

图 1.12 – Google Cloud 计算引擎 API
现在,这已经足够了。在你完成本书中的示例时,GCP 会要求你启用更多的 API,你可以在相关时启用它们。
如果你愿意,你可以重复前面的步骤来启用其他几个 API:具体来说,BigQuery API、BigQuery 数据传输 API、BigQuery 连接 API、服务使用 API、Cloud Storage 和 存储传输 API。
接下来,我们来看一下如何将存储桶中的数据移动到 BigQuery 数据仓库中的表格。
创建数据仓库
我们将使用一个简单的例子,将存储在 Google Cloud 存储桶中的数据放入一个可以通过 BigQuery 查询的表格中。最简单的方法是使用 BigQuery 用户界面。确保它处于正确的项目中。我们将使用这个例子来创建一个包含一个表格的数据集。
你可以通过在 GCP 门户的搜索栏中搜索 BigQuery 来导航到它,如下图所示:

图 1.13 – 搜索 BigQuery
你将看到BigQuery被推荐。点击它,它会带你进入 BigQuery 门户:

图 1.14 – BigQuery 和数据仓库查询门户
下面是创建 BigQuery 数据仓库中持久表格的步骤:
-
选择创建数据集:
![图 1.15 – 为项目创建数据集]()
图 1.15 – 为项目创建数据集
-
确保你位于刚才创建的数据集中。现在点击创建表格:
![图 1.16 – 为数据集创建表格]()
图 1.16 – 为数据集创建表格
在 源 部分,在 创建表格自 部分,选择 Google Cloud Storage:
![图 1.17 – 通过指定数据源填充表格]()
图 1.17 – 通过指定数据源填充表格
-
然后它将过渡到另一个对话框。你可以输入文件名或使用 浏览 选项查找存储在存储桶中的文件。在这种情况下,我的 Google Cloud Storage 存储桶中已经放置了一个 CSV 文件。你可以将自己的 CSV 文件上传到存储桶,或者从
data.mendeley.com/datasets/7xwsksdpy3/1下载我使用的那个文件。另外,输入列名和数据类型作为模式:![图 1.18 – 使用存储在存储桶中的现有 CSV 文件填充表格的示例]()
图 1.18 – 使用存储在存储桶中的现有 CSV 文件填充表格的示例
-
在 模式 部分,使用 自动检测,在 高级选项 中,由于第一行是列名数组,我们需要告诉它跳过第一行:
![图 1.19 – 处理表格的列名]()
图 1.19 – 处理表格的列名
-
一旦表格创建完成,你可以点击 查询表格 来更新 SQL 查询语法,或者直接输入这个查询:
SELECT * FROM `project1-190517.myworkdataset.iris` LIMIT 1000 -
执行前面的查询并点击 运行:

图 1.20 – 运行查询以检查表格
有许多不同的数据源类型,也有许多不同的方式将原始数据创建为数据仓库。这只是结构化数据的简单示例。如需有关其他数据源和类型的更多信息,请参考 BigQuery 文档:cloud.google.com/bigquery/docs/loading-data-cloud-storage-csv#console。
现在你已经学会了如何使用存储桶中的原始数据,在 BigQuery 数据仓库中创建持久表格。
我们使用了一个 CSV 文件作为示例,并将其作为表格添加到 BigQuery。在接下来的章节中,我们将看到如何将 TensorFlow 连接到存储在 BigQuery 和 Cloud Storage 存储桶中的数据。现在我们准备启动一个在 AI Platform 上运行的 TensorFlow Enterprise 实例。
在 AI Platform 中使用 TensorFlow Enterprise
在本节中,我们将亲自体验如何轻松访问存储在 Google Cloud Storage 选项中的数据,例如存储桶或 BigQuery。为此,我们需要配置一个环境来执行一些示例 TensorFlow API 代码和命令行工具。本节中使用 TensorFlow Enterprise 的最简单方法是通过 Google Cloud 中的 AI Platform Notebook:
-
在 GCP 门户中,搜索
AI Platform。 -
然后选择NEW INSTANCE,选择TensorFlow Enterprise 2.3并选择Without GPUs。然后点击OPEN JUPYTERLAB:
![图 1.21 – Google Cloud AI Platform 与实例创建]()
图 1.21 – Google Cloud AI Platform 与实例创建
-
点击Python 3,它将提供一个新的笔记本,以执行本章余下的示例:

图 1.22 – 由 AI Platform 托管的 JupyterLab 环境
一个运行在 AI Platform 上的 TensorFlow Enterprise 实例现在已准备好使用。接下来,我们将使用该平台进行一些数据 I/O 操作。
访问数据源
TensorFlow Enterprise 可以轻松访问 Google Cloud Storage 和 BigQuery 中的数据源。这些数据源可以轻松存储从几 GB 到 TB 级别的数据。然而,以这种规模读取训练数据到 JupyterLab 运行时是不可行的。因此,使用流式数据批次进行训练是处理数据导入的方式。tf.data API 是构建数据导入管道的方式,能够从分布式系统中的文件中聚合数据。经过这一步后,数据对象可以通过变换步骤,演化成一个新的训练数据对象。
在本节中,我们将学习以下任务的基本编码模式:
-
从 Cloud Storage 存储桶中读取数据
-
从 BigQuery 表中读取数据
-
将数据写入 Cloud Storage 存储桶
-
将数据写入 BigQuery 表
在这之后,你将能够很好地掌握如何读取和写入 Google Cloud Storage 选项中的数据,并持久化你的数据或 TensorFlow 运行时所生成的对象。
Cloud Storage 读取器
tf.data,因此一个tf.data对象可以轻松地访问 Google Cloud Storage 中的数据。例如,下面的代码片段演示了如何读取一个tfrecord数据集:
my_train_dataset = tf.data.TFRecordDataset('gs://<BUCKET_NAME>/<FILE_NAME>*.tfrecord')
my_train_dataset = my_train_dataset.repeat()
my_train_dataset = my_train_dataset.batch()
…
model.fit(my_train_dataset, …)
在前面的示例中,存储在存储桶中的文件被序列化为tfrecord,它是原始数据的二进制格式。这是一种非常常见的存储和序列化大量数据或文件的方式,适用于 TensorFlow 在云中使用。这种格式可以提高通过网络流式传输数据时的读取效率。我们将在未来的章节中更详细地讨论tfrecord。
BigQuery 读取器
同样,BigQuery 读取器也集成在 TensorFlow Enterprise 环境中,因此存储在 BigQuery 中的训练数据或衍生数据集可以被 TensorFlow Enterprise 消费。
有三种常用的方法可以读取存储在 BigQuery 数据仓库中的表。第一种方法是使用%%bigquery 魔法命令。第二种方法是在常规 Python 运行时中使用 BigQuery API,第三种方法是使用 TensorFlow I/O。每种方法都有其优点。
BigQuery 魔法命令
这种方法非常适合在 JupyterLab 单元中直接运行 SQL 语句。这等同于切换单元的命令解释器。%%bigquery 解释器执行标准 SQL 查询,并将结果作为 pandas DataFrame 返回。
以下代码片段展示了如何使用 %%bigquery 解释器,并为结果分配一个 pandas DataFrame 名称。每一步都是一个 JupyterLab 单元:
-
指定一个项目 ID。此 JupyterLab 单元使用默认解释器。因此,这里是一个 Python 变量。如果 BigQuery 表与您当前运行的项目相同,那么无需指定项目 ID:
project_id = '<PROJECT-XXXXX>' -
调用
%%bigquery魔法命令,并为结果指定项目 ID 和 DataFrame 名称:%%bigquery --project $project_id mydataframe SELECT * from `bigquery-public-data.covid19_jhu_csse.summary` limit 5如果表与您当前运行的项目相同,则无需 --project 参数。
-
验证结果是一个 pandas DataFrame:
type(mydataframe) -
显示 DataFrame:
mydataframe
本示例的完整代码片段如下:

me integration
这里是关键要点:
-
必须提供项目 ID 才能使用 BigQuery API。
-
您可以将像项目 ID 这样的 Python 变量作为值传递给运行
%%bigquery解释器的单元,使用$前缀。 -
为了便于 Python 预处理功能或 TensorFlow 使用,您需要为保存查询结果的 DataFrame 指定一个名称。
Python BigQuery API
我们可以通过 Google Cloud 的 BigQuery 客户端调用 BigQuery API。这将直接访问数据,执行查询,并立即返回结果。此方法不需要用户了解表的架构。实际上,它仅仅是通过库调用将 SQL 语句包装在 BigQuery 客户端中。
这个代码片段演示了如何调用 BigQuery API,并使用它将结果返回为 pandas DataFrame:
from google.cloud import bigquery
project_id ='project-xxxxx'
client = bigquery.Client(project=project_id)
sample_count = 1000
row_count = client.query('''
SELECT
COUNT(*) as total
FROM `bigquery-public-data.covid19_jhu_csse.summary`''').to_dataframe().total[0]
df = client.query('''
SELECT
*
FROM
`bigquery-public-data.covid19_jhu_csse.summary`
WHERE RAND() < %d/%d
''' % (sample_count, row_count)).to_dataframe()
print('Full dataset has %d rows' % row_count)
上述代码的输出如下:

图 1.24 – 代码输出
让我们仔细看看上述代码:
-
需要导入 BigQuery 库来创建 BigQuery 客户端。
-
使用该 API 创建 BigQuery 客户端时需要提供项目 ID。
-
该客户端包装了一个 SQL 语句并执行它。
-
返回的数据可以立即轻松地转换为 pandas DataFrame。
BigQuery 表的 pandas DataFrame 版本包含以下列:

图 1.25 – BigQuery 表的 pandas DataFrame 版本
现在可以进一步使用。它现在是一个 pandas DataFrame,占用了您 Python 运行时的内存空间。
这个方法非常直接,因为它可以帮助你探索数据架构并进行简单的聚合和过滤,而且由于它本质上是一个 SQL 语句包装器,你可以非常轻松地将数据从仓库中取出并开始使用。你不需要了解表格架构就能做到这一点。
然而,这种方法的问题在于当表格足够大以至于超出了内存容量时。TensorFlow I/O 可以帮助解决这个问题。
TensorFlow I/O
对于 TensorFlow 使用 BigQuery 数据,最好使用 TensorFlow I/O 来调用 BigQuery API。因为 TensorFlow I/O 会提供一个代表查询结果的数据集对象,而不是像之前的方法那样提供整个结果。数据集对象是训练期间流式传输训练数据的方式。因此,不必一次性将所有训练数据加载到内存中。这与小批量训练互为补充,而小批量训练无疑是深度学习中最常用的梯度下降优化实现。然而,这比前面的方法稍微复杂一些,它要求你了解表格的架构。本示例使用的是 Google Cloud 托管的公共数据集。
我们需要从表格中感兴趣的列开始。我们可以使用之前的方法来检查列名和数据类型,并创建会话定义:
-
按如下方式加载所需的库并设置变量:
import tensorflow as tf from tensorflow_io.bigquery import BigQueryClient PROJECT_ID = 'project-xxxxx' # This is from what you created in your Google Cloud Account. DATASET_GCP_PROJECT_ID = 'bigquery-public-data' DATASET_ID = 'covid19_jhu_csse' TABLE_ID = 'summary' -
实例化一个 BigQuery 客户端并指定批次大小:
batch_size = 2048 client = BigQueryClient() -
使用客户端创建一个读取会话并指定感兴趣的列和数据类型。注意,在使用 BigQuery 客户端时,你需要知道正确的列名及其相应的数据类型:
read_session = client.read_session( 'projects/' + PROJECT_ID, DATASET_GCP_PROJECT_ID, TABLE_ID, DATASET_ID, ['province_state', 'country_region', 'confirmed', 'deaths', 'date', 'recovered' ], [tf.string, tf.string, tf.int64, tf.int64, tf.int32, tf.int64], requested_streams=10 ) -
现在我们可以使用创建的会话对象来执行读取操作:
dataset = read_session.parallel_read_rows(sloppy=True).batch(batch_size) -
让我们用
type()来查看数据集:type(dataset)这是输出结果:
![图 1.26 – 输出]()
图 1.26 – 输出
-
为了实际查看数据,我们需要将数据集操作转换为 Python 迭代器,并使用
next()查看第一批次的内容:itr = tf.compat.v1.data.make_one_shot_iterator( dataset ) next(itr)
前面的命令输出显示它被组织为一个有序字典,其中键是列名,值是张量:

图 1.27 – 原始数据作为迭代器
这里是关键的要点:
-
TensorFlow I/O 的 BigQuery 客户端需要设置读取会话,读取会话由你感兴趣的表格的列名组成。
-
然后这个客户端执行一个读取操作,包含数据批次处理。
-
读取操作的输出是 TensorFlow 操作。
-
这些操作可能会被转换为 Python 迭代器,这样它就可以输出操作读取的实际数据。
-
这提高了训练过程中内存使用的效率,因为数据是按批次发送进行训练的。
在 BigQuery 中持久化数据
我们已经了解了如何读取存储在 Google Storage 解决方案中的数据,例如 Cloud Storage 存储桶或 BigQuery 数据仓库,并如何使 AI Platform 的 TensorFlow Enterprise 实例可以在 JupyterLab 中使用这些数据。现在让我们来看一些将数据写回,或将工作数据持久化到云存储中的方法。
我们的第一个示例涉及写入存储在 JupyterLab 运行时目录中的文件(在一些 TensorFlow Enterprise 文档中,这也称为本地文件)。一般过程如下:
-
为了方便起见,执行一个 BigQuery SQL
read命令,从公共数据集中查询一个表。 -
将结果本地保存为逗号分隔文件(CSV)。
-
将 CSV 文件写入我们 BigQuery 数据集中的一个表。
每个步骤都是一个代码单元。以下逐步代码片段适用于任何三个 AI 平台的 JupyterLab(AI Notebook,AI Deep Learning VM 和 Deep Learning Container):
-
指定项目 ID:
project_id = 'project1-190517' -
执行 BigQuery SQL 命令并将结果分配给 pandas DataFrame:
%%bigquery --project $project_id mydataframe SELECT * from `bigquery-public-data.covid19_jhu_csse.summary`BigQuery 的查询结果默认返回为 pandas DataFrame。在这种情况下,我们将 DataFrame 的名称指定为
mydataframe。 -
将 pandas DataFrame 写入本地目录中的 CSV 文件。在这种情况下,我们使用了 JupyterLab 运行时的
/home目录:import pandas as pd mydataframe.to_csv('my_new_data.csv') -
指定数据集名称:
dataset_id = 'my_new_dataset' -
使用 BigQuery 命令行工具在此项目的数据集中创建一个空表。该命令以
!bq开头:!bq --location=US mk --dataset $dataset_id该命令创建了一个新的数据集。这个数据集还没有任何表格。我们将在下一步将一个新表写入该数据集。
-
将本地 CSV 文件写入新表:
'my_new_data.csv' will suffice. Otherwise, a full path is required. Also, {dataset_id}.my_new_data_table indicates that we want to write the CSV file into this particular dataset and the table name. -
现在你可以导航到 BigQuery 门户,你会看到数据集和表格:图 1.28 – BigQuery 门户和导航到数据集
图 1.28 – BigQuery 门户和导航到数据集
在这种情况下,我们有一个数据集,包含一个表。
-
然后,执行一个简单的查询,如下所示:

图 1.29 – 用于检查表的查询
这是一个非常简单的查询,我们只是想显示 1,000 条随机选择的行。现在您可以执行此查询,输出将如下面的截图所示。
以下查询输出显示了我们刚刚创建的 BigQuery 表中的数据:

图 1.30 – 示例表格输出
以下是关键要点:
-
在 AI Platform 的 JupyterLab 运行时中生成的 TensorFlow 工作流数据,可以无缝地作为表持久化到 BigQuery 中。
-
将数据以结构化格式(如 pandas DataFrame 或 CSV 文件)持久化到 BigQuery 中,可以轻松使用 BigQuery 命令行工具完成。
-
当你需要在 JupyterLab 运行时和 BigQuery 之间移动数据对象(如表格)时,使用 BigQuery 命令行工具和
!bq可以节省时间和精力。
在存储桶中持久化数据
在前面的 在 BigQuery 中持久化数据 部分,我们看到如何将 CSV 文件或 pandas DataFrame 等结构化数据源持久化为 BigQuery 数据集中的表。在本节中,我们将看到如何将工作数据(如 NumPy 数组)持久化。在这种情况下,适合的目标存储是 Google Cloud Storage 存储桶。
本演示的工作流如下:
-
为了方便,从
tf.keras.dataset中读取 NumPy 数组。 -
将 NumPy 数组保存为 pickle (
pkl) 文件。(仅供参考:虽然 pickle 文件格式方便且易于用于序列化 Python 对象,但也有其缺点。首先,它可能很慢,并且生成的对象比原始对象大。其次,pickle 文件可能包含漏洞或安全风险,任何打开它的进程都有可能受到影响。这里只是为了方便使用。) -
使用
!gsutil存储命令行工具将文件从 JupyterLab 的/home目录(在一些文档中,这被称为本地目录)传输到存储桶。 -
使用
!gsutil将存储桶中的内容传输回 JupyterLab 运行时。由于我们将使用 Python 与!gsutil,因此需要将内容保持在不同的单元格中。
按照以下步骤完成工作流:
-
我们使用 IMDB 数据集,因为它已经以 NumPy 格式提供:
import tensorflow as tf import pickle as pkl (x_train, y_train), (x_test, y_test) = tf.keras.datasets.imdb.load_data( path='imdb.npz', num_words=None, skip_top=0, maxlen=None, seed=113, start_char=1, oov_char=2, index_from=3 ) with open('/home/jupyter/x_train.pkl','wb') as f: pkl.dump(x_train, f)x_train、y_train、x_test和y_test将作为 NumPy 数组返回。我们将使用x_train进行本演示。x_train数组将作为pkl文件保存在 JupyterLab 运行时中。上述代码打开了作为 TensorFlow 一部分分发的 IMDB 电影评论数据集。该数据集的格式为 NumPy 数组元组,并分为训练和测试分区。接下来,我们将
x_train数组保存为运行时/home目录下的 pickle 文件。然后,在下一步中,该 pickle 文件将被持久化到存储桶中。 -
为新的存储桶指定一个名称:
bucket_name = 'ai-platform-bucket' -
使用指定的名称创建一个新的存储桶:
!gsutil mb gs://{bucket_name}/使用
!gsutil将pkl文件从运行时移动到存储桶:!gsutil cp /home/jupyter/x_train.pkl gs://{bucket_name}/ -
读取
pkl文件:!gsutil cp gs://{bucket_name}/x_train.pkl /home/jupyter/x_train_readback.pkl -
现在我们来查看 Cloud Storage 存储桶:

图 1.31 – 从 AI 平台的工作流中将对象序列化到存储桶中
这里是关键要点:
-
在 TensorFlow 工作流中生成的工作数据可以作为序列化对象持久化到存储桶中。
-
Google AI Platform 的 JupyterLab 环境提供了 TensorFlow 运行时与 Cloud Storage 命令行工具
gsutil之间的无缝集成。 -
当你需要在 Google Cloud Storage 和 AI Platform 之间传输内容时,使用
!gsutil命令行工具。
总结
本章概述了由 Google Cloud AI Platform 托管的 TensorFlow 企业环境。我们还看到该平台如何无缝集成特定工具,例如命令行 API,以促进数据或对象在 JupyterLab 环境和我们的存储解决方案之间的轻松传输。这些工具使得访问存储在 BigQuery 或存储桶中的数据变得简单,这两个数据源是 TensorFlow 中最常用的。
在下一章,我们将更详细地了解 AI Platform 中使用 TensorFlow 企业版的三种方式:Notebook、深度学习虚拟机(Deep Learning VM)和深度学习容器(Deep Learning Containers)。
第二章:
第三章:在 Google AI 平台上运行 TensorFlow Enterprise
当前,TensorFlow Enterprise 发行版仅通过 Google Cloud AI 平台提供。本章将演示如何启动 AI 平台并将其用于 TensorFlow Enterprise。在 AI 平台中,TensorFlow Enterprise 可以通过各自的命令行工具以及简单的 API 与 Cloud Storage 和 BigQuery 进行交互,从而加载源数据。本章将展示如何启动 AI 平台,并且演示开始使用 TensorFlow Enterprise 发行版的简便方法。
我们将涵盖以下主要内容:
-
设置笔记本环境
-
从 BigQuery 轻松提取参数化数据
设置笔记本环境
TensorFlow Enterprise 仅在由 Google Cloud 托管的 JupyterLab 环境中提供。使用此 TensorFlow 发行版,有三种方式可以使用 JupyterLab:Google Cloud AI 平台笔记本,Google Cloud 深度学习虚拟机镜像(DLVM),以及在本地计算机上运行的Google Cloud 深度学习容器(Docker 镜像)。无论您选择哪种方式,您将看到标准 JupyterLab 环境的相同界面,如下所示:

图 2.1 – JupyterLab 门户
那么,让我们来看看如何开始。
AI 平台笔记本
这是开始使用 TensorFlow Enterprise 并使其在 Google Cloud 上运行的最简单、最不复杂的方式:
-
只需进入 Google Cloud 门户,选择左侧面板中的AI 平台,然后选择笔记本选项:
![图 2.2 – AI 平台启动门户]()
图 2.2 – AI 平台启动门户
-
然后点击新实例,您将获得 TensorFlow Enterprise 的选择,可以选择1.15、2.1或2.3版本。您还可以选择使用一个Tesla K4 GPU:
![图 2.3 – 在 AI 平台上创建新的笔记本实例]()
图 2.3 – 在 AI 平台上创建新的笔记本实例
对于本章中的示例,我们不需要使用 GPU。选择无 GPU即可。
-
然后点击创建以接受默认节点选择,或点击自定义查看所有可用的设置选项:
![图 2.4 – 自定义计算实例]()
图 2.4 – 自定义计算实例
在 AI 平台使用笔记本选项时,以下是可用的机器配置选择:
![图 2.5 – 可用的机器实例选项]()
图 2.5 – 可用的机器实例选项
点击创建后,几分钟内笔记本实例将可用:
![图 2.6 – 实例上线并准备就绪]()
图 2.6 – 实例上线并准备就绪
-
当实例准备就绪时,打开 JUPYTERLAB 将被激活,您可以点击它。点击后会进入 JupyterLab 笔记本:

图 2.7 – JupyterLab 环境
我们将使用 Python 3 来展示所有示例。
深度学习虚拟机镜像
如果你希望有更多选项,例如不同的 GPU 选择,那么 DLVM 是更好的选择。你可能会发现以下参考资料有帮助:
-
cloud.google.com/ai-platform/deep-learning-vm/docs/quickstart-cli -
cloud.google.com/ai-platform/deep-learning-vm/docs/quickstart-marketplace
按照以下步骤选择 DLVM:
-
点击左侧面板中的 市场:
![图 2.8 – Google Cloud Platform 市场]()
图 2.8 – Google Cloud Platform 市场
-
在查询框中搜索
Deep Learning VM,你将看到如下内容:![图 2.9 – 启用 DLVM]()
图 2.9 – 启用 DLVM
这里是你可以启动 DLVM 部署的地方。
-
点击 启动,你将看到许多可用选项,包括 机器类型、GPU 类型 和 GPU 数量:
![图 2.10 – DLVM 配置门户]()
图 2.10 – DLVM 配置门户
此外,DLVM 除了 TensorFlow Enterprise 之外,还支持更多框架:
![图 2.11 – DLVM 和框架选项]()
图 2.11 – DLVM 和框架选项
-
如果你选择其中一个 TensorFlow Enterprise 框架,然后点击
创建,你将能够像之前一样访问 JupyterLab:

图 2.12 – JupyterLab 入口点
这是一个建议。为了最小化成本,完成工作后,停止实例非常重要。查看正在运行的实例的最快方式是选择左侧面板中的 计算引擎,然后选择 VM 实例:

图 2.13 – 订阅中的计算实例
从那里你将看到所有已创建的实例。完成后停止它们:

图 2.14 – 列出 VM 实例并管理其使用情况
用户有责任关注正在运行的实例。作为良好的实践,完成工作后,下载或提交你的笔记本以保存工作,并在不使用时删除实例。
深度学习容器 (DLC)
这是使用 TensorFlow Enterprise 的一种相对复杂的方式。使用此方法的一个重要原因是,在数据不存储在 Google Cloud 的情况下,你希望在本地或本地机器上运行 TensorFlow Enterprise。另一个原因是,对于企业使用,你可能希望将 DLC 作为基础 Docker 镜像来构建你自己的 Docker 镜像,以便为特定用途或在团队之间分发使用。这是运行 TensorFlow Enterprise 的方式,超出了 Google Cloud 的范围。由于它是一个 Docker 镜像,因此需要安装 Docker 引擎并运行守护进程。对 Docker 有一些基本了解将非常有帮助。你可以在console.cloud.google.com/gcr/images/deeplearning-platform-release找到当前可用的 DLC 的完整列表。
目标是运行 TensorFlow Enterprise JupyterLab。但由于它是在本地计算机上运行,JupyterLab 的 URL 格式如下:
localhost:<LOCAL_PORT>
以下是我们可以如何实现这一目标(参考:cloud.google.com/ai-platform/deep-learning-containers/docs/getting-started-local):
-
假设 Docker 守护进程正在运行,我们将执行以下命令来运行 TensorFlow Enterprise 容器:
docker run -d -p <LOCAL_PORT>:8080 -v <LOCAL_DIR>:/home <CONTAINER_REGISTRY>让我们通过以下表格了解前面命令的各个部分:
![图 2.15 – 解释运行 TensorFlow Enterprise 容器命令中的各个对象]()
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/lrn-tf-entp/img/Figure_2.15.jpg)
图 2.15 – 解释运行 TensorFlow Enterprise 容器命令中的各个对象
在前面的表格中,注意以下几点:
-
<LOCAL_PORT>指的是本地计算机上用于托管该 Docker 镜像实例的端口号。如果8080已被其他程序或进程占用,可以使用任何其他可用的端口号。 -
<LOCAL_DIR>是指存放训练数据和资产的顶级目录的路径。对于 Windows 机器,它可能是C:\Users\XXXX\Documents。对于 Linux 或 Mac 机器,它可能是/home/XXXX/Documents。 -
<CONTAINER_REGISTRY>是指 Docker 镜像在互联网上的位置,对于我们关心的 Docker 容器,它位于gcr.io/deeplearning-platform-release/tf2-cpu.2-1。
-
-
将这些组合在一起形成一个命令,并从本地计算机的终端(如 Windows 命令提示符)运行:
8080 inside the Docker image. Notice that the preceding command maps local port 8080 to the Docker image's port 8080. The first 8080 is your local port, the second 8080 is the port number used by JupyterLab inside the Docker image environment. Again, the local port number doesn't have to be 8080. -
现在你可以通过浏览器访问本地端口:
localhost:8080然后你将看到 JupyterLab 作为 Docker 容器运行,如下所示:
![图 2.16 – JupyterLab 在本地或本地环境中作为 Docker 镜像运行]()
图 2.16 – JupyterLab 在本地或本地环境中作为 Docker 镜像运行
首先让我们看一下左侧面板。左侧面板显示了你指定为
<LOCAL_DIR>的所有本地文件和文件夹。在这种情况下,它是/temp/chapter2-v(或--volume)选项将本地目录映射到 Docker 容器实例的/home目录。这就是本地内容如何访问 Docker 容器的方式。 -
你可以点击
/home:![图 2.17 – JupyterLab 读取本地数据的 Docker 镜像]()
图 2.17 – JupyterLab 读取本地数据的 Docker 镜像
-
你还可以将数据写入本地目录:
![图 2.18 – Docker 镜像中的 JupyterLab 写入本地数据]()
iris.to_csv('/home/iris-write-from-docker.csv', index=False)由于你将
/home与本地目录映射,因此你也可以在本地文件资源管理器中找到该文件:![图 2.19 – JupyterLab 在 Docker 镜像中运行时写入的本地数据]()
图 2.19 – JupyterLab 在 Docker 镜像中运行时写入的本地数据
-
完成后,为了关闭 Docker 镜像,你需要知道本地 Docker 守护进程为此实例分配的容器 ID。命令如下:
docker ps它将返回类似于以下的输出:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 553cfd198067 gcr.io/deeplearning-platform-release/tf2-cpu.2-1 '/entrypoint.sh /run…' 44 minutes ago Up 44 minutes 0.0.0.0:8080->8080/tcp intelligent_goldwasser -
记下
CONTAINER ID的值。然后使用以下命令来关闭它:docker stop 553cfd198067
选择工作区的建议
前一节讨论的三种方法将你引导到运行 TensorFlow Enterprise 的 JupyterLab。每种方法都有一些差异和需要考虑的后果:
-
本地运行的 Docker 镜像更适合进行本地数据访问。
-
DLC 可以作为创建新的企业特定镜像的基础镜像。
在本地运行的 Docker 镜像的优势在于它能直接访问本地环境或数据源。我们已经看到它如何轻松地在本地节点上读取和写入数据。显然,这在 AI 平台环境中无法轻松实现。因此,如果训练数据和输出需要保留在本地或本地环境中,那么这是最明智的选择。此方法的缺点是设置和管理自己的 Docker 环境的开销。使用 DLC 的另一个原因是,大型企业通常需要可定制的环境。它们可能希望在 DLC 基础上创建自己的 Docker 容器,并随后要求公司中的每个人都使用该容器与 Cloud AI Platform Notebook。Notebook 支持自定义容器模式,只要该容器是基于 DLC 的。
-
如果你想自定义计算实例核心、内存和磁盘资源,请使用 DLVM。
如果你想为工作负载配置 CPU、GPU、内存或磁盘资源,那么 DLVM 是首选方法。
-
对于大多数一般需求,使用默认的笔记本环境。
使用 AI 平台时,笔记本环境显然可以直接访问云存储,如存储桶容器或 BigQuery 表。如果不需要特别选择 CPU 或 GPU 配置,AI 平台的笔记本环境完全足够。
到目前为止,我们已经了解了三种不同的环境,供用户开始使用 Google 的 AI 平台并消费 TensorFlow Enterprise 发行版。总的来说,这些方法都提供了一致的用户体验和 TensorFlow Enterprise 库的运行时分发。选择方法的理由主要基于数据访问和计算资源配置的需求。如果训练数据存储在本地或本地磁盘上,那么 Docker 镜像是首选方法。如果计算资源和速度是主要关注点,那么 DLVM 是首选。
现在,已经进入 AI 平台及其笔记本环境,作为入门,我们将仔细查看一个常见的示例,演示如何使用 AI 平台访问 BigQuery 中的数据,并构建自己的训练数据。
从 BigQuery 中轻松提取参数化数据
很多时候,您的企业数据仓库包含了用于构建自己训练数据的来源,而简单的 SQL 查询命令就能满足行列选择和特征转换的需求。所以让我们看看一种方便、灵活、快速的方式,通过 SQL 查询选择和操作原始数据,其中查询结果是一个 pandas DataFrame。我们已经看到如何使用%%bigquery解释器执行查询并将结果返回为 pandas DataFrame。接下来,我们将学习如何传入查询参数,以便用户可以探索和选择适合模型训练的数据。以下示例使用的是一个公共数据集covid19_juh_csse及其summary表。
该表具有以下结构:

图 2.20 – 使用 BigQuery 显示的表结构
在前面提到的任何三种方法提供的 JupyterLab 环境中,您可以执行以下步骤来进行参数化查询:
-
定义一组 JSON 兼容格式的参数,即键值对,如同 Python 字典:
params = {'min_death': 1000, 'topn': 10} -
构建查询并通过名称将其分配给 DataFrame。注意查询中每个参数键如何通过前缀
@来引用:%%bigquery myderiveddata --params $params SELECT country_region, MAX(confirmed) as total_confirmed, MAX(deaths) AS total_deaths FROM `bigquery-public-data.covid19_jhu_csse.summary` GROUP BY country_region HAVING (total_deaths > @min_death) ORDER BY total_deaths DESC LIMIT @topn -
检查数据:
myderiveddata
以下是聚合命令的输出,显示了按国家汇总的结果:

图 2.21 – 从笔记本中聚合命令的输出
我们可以使用 Python 的type命令来确认数据对象的结构:
type(myderiveddata)
并确认该对象为 pandas DataFrame:

图 2.22 – type(myderiveddata)的输出
DataFrame 可以被序列化为 pickle 文件以供未来使用。一旦转换为 pickle 格式,你可以将其持久化存储在云存储中,如上一章所示。
以下是关键要点:
-
参数化查询使得数据选择和操作变得快速简便,适用于构建训练数据作为 pandas DataFrame。
-
参数被封装在 Python 字典中,并可以在执行过程中传递到查询字符串中。
-
查询字符串可以通过
@运算符引用参数。
汇总
以下是我们刚刚处理的快速示例的完整代码片段:
params = {'min_death': 1000,
'topn': 10}
%%bigquery myderiveddata --params $params
SELECT country_region, MAX(confirmed) as total_confirmed, MAX(deaths) AS total_deaths
FROM `bigquery-public-data.covid19_jhu_csse.summary`
GROUP BY country_region
HAVING (total_deaths > @min_death)
ORDER BY total_deaths DESC
LIMIT @topn
print(myderiveddata)
输出如 图 2.23 所示:

图 2.23 – 来自 BigQuery 的输出及与 pandas DataFrame 格式的兼容性
如前面步骤所示,笔记本环境与 BigQuery 紧密集成。因此,使用 SQL 执行的内联查询将生成一个准备好在 Python 中使用的 DataFrame。这进一步展示了 Google Cloud AI Platform Notebook 环境的灵活性。
总结
在本章中,你已经学习了如何启动 JupyterLab 环境来运行 TensorFlow Enterprise。TensorFlow Enterprise 以三种不同的形式提供:AI Platform Notebook、DLVM 和 Docker 容器。这些方法使用的计算资源可以在 Google Cloud Compute Engine 面板中找到。这些计算节点不会自动关闭,因此在使用完毕后,务必停止或删除它们。
BigQuery 命令工具与 TensorFlow Enterprise 环境无缝集成。通过使用 SQL 查询字符串进行参数化数据提取,可以快速便捷地创建派生数据集并进行特征选择。
即使数据尚未存储在 Google Cloud 中,TensorFlow Enterprise 仍然可以工作。通过拉取并运行 TensorFlow Enterprise Docker 容器,你可以将其与本地或本地数据源一起使用。
现在你已经了解了如何利用数据的可用性和可访问性进行 TensorFlow Enterprise 的使用,接下来我们将探讨一些常见的数据转换、序列化和存储技术,这些技术经过优化,适用于 TensorFlow Enterprise 的使用和模型训练流程。
第二部分 – 数据预处理与建模
在本部分,你将学习如何预处理和设置原始数据,以便高效地供 TensorFlow 使用,并且你还将学习如何使用 TensorFlow 企业版 API 构建几种不同的模型。我们还将讨论如何构建自定义模型,并如何利用 TensorFlow Hub 中的预构建模型。
本部分包括以下章节:
-
第三章,数据准备与处理技巧
-
第四章,可重用模型与可扩展数据管道
第三章:
第四章:数据准备和处理技术
在本章中,您将学习如何将两种常见的数据类型转换为适合摄取管道的结构——将结构化的 CSV 或 pandas DataFrame 转换为数据集,以及将图像等非结构化数据转换为 TFRecord。
在此过程中,我们还将介绍一些在许多情况下都能重复使用的小技巧和实用功能。您还将理解转换过程的基本原理。
如前一章所示,TensorFlow Enterprise 利用 Google Cloud AI 平台提供的灵活性来访问训练数据。一旦解决了对训练数据的访问问题,我们的下一步任务是开发一个工作流,让模型高效地消耗数据。在本章中,我们将学习如何检查和处理常用的数据结构。
虽然 TensorFlow 可以直接处理 Pythonic 数据结构,如 pandas 或 numpy,但为了提高资源吞吐量和摄取效率,TensorFlow 构建了数据集 API,用于将数据从其原生 Pythonic 结构转换为 TensorFlow 特定的结构。数据集 API 可以处理和解析许多常用的数据类型。例如,具有定义模式的结构化或表格数据通常呈现为 pandas DataFrame。数据集 API 将这种数据结构转换为 TensorFlow 数据集。图像数据通常呈现为 numpy 数组。在 TensorFlow 中,建议将其转换为 TFRecord。
在处理这些数据结构时,确保转换过程正确且数据可验证非常重要。本章将演示一些有助于确保数据结构转换正确的技术;例如,解码字节流为图像。通常,将这些数据结构解码为可读格式有助于快速检查数据质量。
我们将从应用于结构化数据的 TensorFlow 数据集开始。特别是,我们将涵盖以下主要内容:
-
将表格数据转换为 TensorFlow 数据集
-
将分布式 CSV 文件转换为 TensorFlow 数据集
-
处理图像数据以供输入管道使用
-
解码
TFRecord并重建图像 -
处理大规模图像数据
将表格数据转换为 TensorFlow 数据集
表格 或 逗号分隔值(CSV) 数据通常具有固定的模式和数据类型,这类数据常常会遇到。我们通常会将它转化为 pandas DataFrame。我们在上一章中看到,当数据托管在 BigQuery 表 中时,如何通过 BigQuery 的魔法命令将查询结果直接返回为 pandas DataFrame。
让我们来看一下如何处理可以适应内存的数据。在这个例子中,我们将使用 BigQuery 魔法命令读取一个公共数据集,这样我们就可以轻松地将数据导入 pandas DataFrame。然后,我们将把它转换为 TensorFlow 数据集。TensorFlow 数据集是一种数据结构,用于按批次流式传输训练数据,而不会占用计算节点的运行内存。
将 BigQuery 表格转换为 TensorFlow 数据集
以下每一步都会在单元格中执行。再次提醒,使用任何你喜欢的 AI 平台(AI Notebook、深度学习 VM、深度学习容器)。AI notebook 是最简单和最便宜的选择:
注意
本示例中的表格仅用于演示目的。我们将把daily_deaths视为机器学习模型训练的目标。虽然我们将其视为训练数据(换句话说,包含特征列和目标列),但在实际的数据工程实践中,还会涉及其他步骤,如特征工程、聚合和归一化。
-
让我们查看来自 BigQuery 的数据,以便确认其数据结构和每列的数据类型,然后预览表格:
![图 3.1 – 使用 BigQuery 检查数据结构]()
图 3.1 – 使用 BigQuery 检查数据结构
一旦运行上述查询,你将看到如下所示的输出:
![图 3.2 – 表格预览]()
图 3.2 – 表格预览
-
加载库、定义变量,并仅在你运行在不同项目中时定义项目 ID:
PROJECT_ID = '<PROJECT_ID>' import tensorflow as tf import pandas as pd -
使用 BigQuery 魔法命令将表格读取到 pandas DataFrame(
train_raw_df)中:%%bigquery train_raw_df SELECT countries_and_territories, geo_id, country_territory_code, year, month, day, confirmed_cases, daily_deaths, pop_data_2019 FROM bigquery-public-data.covid19_ecdc.covid_19_geographic_distribution_worldwide -
看几个样本行:
train_raw_df.sample(n=5)以下是输出结果:
![图 3.3 – 表格中几行数据的输出]()
图 3.3 – 表格中几行数据的输出
-
一些列是类别型的。我们需要将它们编码为整数。首先,我们将该列指定为 pandas 类别特征:
train_raw_df['countries_and_territories'] = pd.Categorical(train_raw_df['countries_and_territories']) -
然后我们将列内容替换为类别编码:
train_raw_df['countries_and_territories'] = train_raw_df.countries_and_territories.cat.codes -
然后,我们对其他类别列重复此过程:
train_raw_df['geo_id'] = pd.Categorical(train_raw_df['geo_id']) train_raw_df['geo_id'] = train_raw_df.geo_id.cat.codes train_raw_df['country_territory_code'] = pd.Categorical(train_raw_df['country_territory_code']) train_raw_df['country_territory_code'] = train_raw_df.country_territory_code.cat.codes -
按数据类型创建列表来存放列名。这样做的原因是确保数据集能够将我们 DataFrame 中的列转换为正确的 TensorFlow 数据类型:
int32_features = ['confirmed_cases'] float32_features = ['pop_data_2019'] int16_features = ['year', 'month', 'day'] categorical_features = ['countries_and_territories', 'geo_id', 'country_territory_code'] int32_target = ['daily_deaths'] -
从 pandas DataFrame 创建数据集时,我们需要指定正确的列名和数据类型。列名根据其数据类型存放在相应的列表中:
training_dataset = tf.data.Dataset.from_tensor_slices( ( tf.cast(train_raw_df[int32_features].values, tf.int32), tf.cast(train_raw_df[float32_features]. values, tf.float32), tf.cast(train_raw_df[int16_features].values, tf.int16), tf.cast(train_raw_df[categorical_features]. values, tf.int32), tf.cast(train_raw_df[int32_target].values, tf.int32) ) ) -
查看数据集的结构,确保其元数据与前一步创建过程中指定的内容一致:
training_dataset输出结果如下:
<TensorSliceDataset shapes: ((1,), (1,), (3,), (3,), (1,)), types: (tf.int32, tf.float32, tf.int16, tf.int32, tf.int32)>张量的形状和数据类型与前一步中所示的顺序完全一致。
现在你已经从 pandas DataFrame 创建了一个数据集。这个数据集现在是输入管道的一部分。如果这个数据集的特征和目标已正确归一化和选择(例如,执行了归一化操作如最小-最大缩放,或标准化操作如 Z 分数转换,如果可以假定列数据的分布为高斯分布),那么它可以直接用于模型训练。
到目前为止,通过这次练习,你已经学到了以下几点:
-
尽可能使用 BigQuery 来首先检查数据模式和数据类型。
-
对于可以适应内存的数据,利用 BigQuery 魔法命令输出 pandas DataFrame。
-
根据数据类型对列名进行分组,以便清晰和组织。
-
将类别特征编码为整数,以便它们可以转换为与 TensorFlow 数据集兼容的 TensorFlow 数据类型。
将分布式 CSV 文件转换为 TensorFlow 数据集
如果你不确定数据的大小,或者不确定它是否能完全适应 Python 运行时的内存,那么将数据读取到 pandas DataFrame 中就不是一个可行的选项。在这种情况下,我们可以使用 TF 数据集 来直接访问数据,而不需要打开它。
通常,当数据以部分形式存储在存储桶中时,命名约定遵循一般模式。这个模式类似于 *。
在将分布式文件存储到 Google Cloud Storage 桶时,文件名的常见模式如下:
<FILE_NAME>-<pattern>-001.csv
…
<FILE_NAME>-<pattern>-00n.csv
或者,存在以下模式:
<FILE_NAME>-<pattern>-aa.csv
…
<FILE_NAME>-<pattern>-zz.csv
文件名中总是有一个模式。TensorFlow 模块 tf.io.gfile.glob 是一个方便的 API,它可以在分布式文件系统中编码此类文件名模式。这对于推断存储在存储桶中的分布式文件至关重要。在这一部分,我们将使用这个 API 来推断我们分布在存储桶中的结构化数据(多个 CSV 文件)。推断后,我们将使用 tf.data.experimental.make_csv_dataset 将其转换为数据集。
准备一个示例 CSV
由于我们需要多个相同模式的 CSV 文件进行演示,我们可以使用开源的 CSV 数据集,如 皮马印第安人糖尿病 数据集(CSV)作为数据来源。该 CSV 文件托管在 raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv。
你可以在本地系统上运行以下命令(你已经下载了上述文件):
wget https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv
再次强调,为了演示的目的,我们需要将这些数据拆分为多个较小的 CSV 文件,然后将这些 CSV 文件上传到 Google Cloud Storage 桶。
该文件的列名如下:
['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigree', 'Age', 'Outcome']
由于 CSV 文件中未包含列名,因此我们可以在不提取标题行的情况下将文件拆分为多个部分。让我们从以下步骤开始:
-
将文件拆分为多个部分。
下载 CSV 文件后,你可以使用以下
awk命令将其拆分为多个部分。这个命令会在每 200 行处将文件拆分成多个 CSV 部分:awk '{filename = 'pima_indian_diabetes_data_part0' int((NR-1)/200) '.csv'; print >> filename}' pima-indians-diabetes.data.csv生成了以下 CSV 文件:
-rw-r--r-- 1 mbp16 staff 6043 Jul 21 16:25 pima_indian_diabetes_data_part00.csv -rw-r--r-- 1 mbp16 staff 6085 Jul 21 16:25 pima_indian_diabetes_data_part01.csv -rw-r--r-- 1 mbp16 staff 6039 Jul 21 16:25 pima_indian_diabetes_data_part02.csv -rw-r--r-- 1 mbp16 staff 5112 Jul 21 16:25 pima_indian_diabetes_data_part03.csv -
将文件上传到存储。下载完文件并生成多个 CSV 文件后,你可以将这些文件上传到 Google Cloud Storage 桶中:

图 3.4 – 上传 CSV 文件到 Cloud Storage 桶
所有文件都在这里:

图 3.5 – Cloud Storage 桶中的多部分 CSV 文件
使用 TensorFlow I/O 构建文件名模式
文件上传完成后,接下来我们进入 AI 平台笔记本环境,执行以下代码行:
import tensorflow as tf
distributed_files_pattern = 'gs://myworkdataset/pima_indian_diabetes_data_part*'
filenames = tf.io.gfile.glob(distributed_files_pattern)
Tf.io.gfile.glob接收一个文件模式字符串作为输入,并创建一个filenames列表:
['gs://myworkdataset/pima_indian_diabetes_data_part00.csv',
'gs://myworkdataset/pima_indian_diabetes_data_part01.csv',
'gs://myworkdataset/pima_indian_diabetes_data_part02.csv',
'gs://myworkdataset/pima_indian_diabetes_data_part03.csv']
现在我们已经有了匹配模式的文件名列表,我们可以准备将这些文件转换为数据集了。
从 CSV 文件创建数据集
通常,多个 CSV 文件要么没有表头,要么全部都有表头。在这种情况下,没有表头。我们需要在将 CSV 转换为数据集之前准备列名:
COLUMN_NAMES = ['Pregnancies', 'Glucose', 'BloodPressure',
'SkinThickness', 'Insulin', 'BMI',
'DiabetesPedigree', 'Age', 'Outcome']
这是列名的来源:data.world/data-society/pima-indians-diabetes-database。
然后我们需要指定这些文件的第一行不是表头,因为我们要将 CSV 文件转换为数据集:
ds = tf.data.experimental.make_csv_dataset(
filenames,
header = False,
column_names = COLUMN_NAMES,
batch_size=5, # Intentionally make it small for
# convenience.
label_name='Outcome',
num_epochs=1,
ignore_errors=True)
在make_csv_dataset中,我们使用文件名列表作为输入,并指定没有表头,然后我们分配COLUMN_NAMES,制作小批量结果展示,选择一列作为目标列('Outcome'),并将训练周期数设置为1,因为我们此时并不会用它训练模型。
检查数据集
现在我们可以验证数据集的内容了。回想一下,由于我们指定了一列作为标签,这意味着其余列是特征。输出将是一个包含特征和目标的元组。
让我们以数据集的第一批数据为例,其中包含五个观测值,并打印出特征列和目标列的数据。在数据集中,数据以数组的形式存储,每一列现在是一个键值对。在features中是另一级别的键值对,表示每个特征:
for features, target in ds.take(1):
print(''Outcome': {}'.format(target))
print(''Features:'')
for k, v in features.items():
print(' {!r:20s}: {}'.format(k, v))
输出如下:
'Outcome': [1 0 0 0 0]
'Features:'
'Pregnancies' : [ 7 12 1 0 2]
'Glucose' : [129 88 128 93 96]
'BloodPressure' : [ 68 74 82 100 68]
'SkinThickness' : [49 40 17 39 13]
'Insulin' : [125 54 183 72 49]
'BMI' : [38.5 35.3 27.5 43.4 21.1]
'DiabetesPedigree' : [0.439 0.378 0.115 1.021 0.647]
'Age' : [43 48 22 35 26]
在训练过程中,数据将以批处理的形式传递给训练过程,而不是作为单个文件打开,可能会消耗大量运行时内存。在前面的示例中,我们看到作为一种最佳实践,存储在云存储中的分布式文件遵循一定的命名模式。tf.io.gfile.glob API 可以轻松推断分布在云存储桶中的多个文件。我们可以轻松使用tf.data.experimental.make_csv_dataset从gfile实例创建数据集实例。总体而言,tf.io和tf.data API 一起使得构建数据输入管道成为可能,而无需显式地将数据读取到内存中。
处理输入管道的图像数据
尽管有许多类型的非结构化数据,但图像可能是最常见的一种类型。TensorFlow 提供了TFRecord作为图像数据的一种数据集类型。在本节中,我们将学习如何将云存储中的图像数据转换为TFRecord对象,以供输入管道使用。
在 TensorFlow 管道中处理图像数据时,原始图像通常会转换为TFRecord对象,这与 CSV 或 DataFrame 相同。与原始 numpy 数组相比,TFRecord对象是图像集合的更高效且可扩展的表示形式。将原始图像转换为TFRecord对象不是一个简单的过程。在TFRecord中,数据以二进制字符串的形式存储。在本节中,我们将逐步展示如何实现这一过程。
让我们从将原始图像转换为TFRecord对象的过程开始。随时将您自己的图像上传到 JupyterLab 实例:
-
上传您选择的图像到 JupyterLab 运行时。为我们将要上传的图像创建一个文件夹。给该文件夹命名,这就是图像将被上传的文件夹:
![图 3.6 – 在笔记本运行时创建文件夹]()
图 3.6 – 在笔记本运行时创建文件夹
现在文件夹已经命名,您可以继续执行下一步。
-
双击您刚才命名的文件夹。现在您已进入该文件夹。在此示例中,我将该文件夹命名为
image-ai-platform-examle。然后,在这个文件夹内,我创建了另一个名为maldives的文件夹。一旦进入该文件夹,您可以点击上传按钮,将您自己的几张图像上传到此文件夹:![图 3.7 – 将项目上传到 JupyterLab 运行时]()
图 3.7 – 将项目上传到 JupyterLab 运行时
这里,我上传了一张名为
maldives-1.jpg的图像。 -
您可以通过右键点击图像文件来获取该图像的路径:
![图 3.8 – 查找上传到笔记本的图像路径]()
图 3.8 – 查找上传到笔记本的图像路径
您可以将文件路径粘贴到记事本或编辑器中,便于下一步的快速参考。
-
选择
images-ai-platform-example/maldives/maldives-1.jpg: -
显示图像以进行验证:
import IPython.display as display my_image = 'images-ai-platform-example/maldives/maldives-1.jpg' display.display(display.Image(filename=my_image))这是输出结果:
![图 3.9 – 显示图像]()
图 3.9 – 显示图像
-
创建一个字典,将文件名与标签映射。我们可以使用
my_image别名作为键,并且可以验证此字典:image_labels = { my_image : 0 } image_labels.items()输出应如下所示:
dict_items([('images-ai-platform-example/maldives/maldives-1.jpg', 0)])
构建 protobuf 消息
现在我们有了 image_labels,它将图像文件映射到其标签。接下来,我们需要做的是将此图像转换为 tf.Example image_label,tf.Example 消息由键值对组成。键值对是图像的元数据,包括三个维度及其相应的值、标签及其值,最后是图像本身的字节数组格式。值表示为 tf.Tensor。现在让我们构建这个 protobuf 消息。
此时,tf.Example protobuf 消息只能接受三种类型的 tf.Tensor。它们如下所示:
-
tf.train.ByteList可以处理string和byte。 -
tf.train.FloatList可以处理float (float32)和double (float64)。 -
tf.train.Int64List可以处理bool、enum、int32、uint32、int64和uint64。
根据 TensorFlow 文档,其他大多数通用数据类型可以强制转换为这三种类型之一,相关文档请参见:www.tensorflow.org/tutorials/load_data/tfrecord#tftrainexample:
-
首先,我们可以使用 TensorFlow 文档中提供的这些函数。这些函数可以将值转换为与
tf.Example兼容的类型:def _bytes_feature(value): '''Returns a bytes_list from a string / byte.''' if isinstance(value, type(tf.constant(0))): value = value.numpy() # BytesList won't unpack a # string from an EagerTensor. return tf.train.Feature(bytes_list= tf.train.BytesList(value=[value])) def _float_feature(value): '''Returns a float_list from a float / double.''' return tf.train.Feature(float_list= tf.train.FloatList(value=[value])) def _int64_feature(value): '''Returns an int64_list from a bool / enum / int / uint.''' return tf.train.Feature(int64_list= tf.train.Int64List(value=[value]))一般来说,从前述函数的模式中,我们可以看到,数据中的原始值首先被强制转换为三种可接受类型之一,然后再转换为
feature。 -
然后,我们可以将图像作为字节串打开并提取其尺寸:
image_string = open(my_image, 'rb').read() image_shape = tf.image.decode_jpeg(image_string).shape image_shape -
现在我们构建一个字典,将这些键值对组合在一起:
label = image_labels[my_image] feature_dictionary = { 'height': _int64_feature(image_shape[0]), 'width': _int64_feature(image_shape[1]), 'depth': _int64_feature(image_shape[2]), 'label': _int64_feature(label), 'image_raw': _bytes_feature(image_string), }请注意,特征字典由元数据的键值对组成,其中值是
tf.Example的三种强制转换数据类型之一。 -
然后我们将此字典转换为
tf.Train.Features:features_msg = tf.train.Features(feature=feature_dictionary) -
将
tf.Featuresprotobuf 消息转换为tf.Exampleprotobuf 消息:example_msg = tf.train.Example(features=features_msg) -
现在,创建一个存储
tfrecords的目录:!mkdir tfrecords-collection -
指定目标名称,然后执行写操作:
record_file = 'tfrecords-collection/maldives-1.tfrecord' with tf.io.TFRecordWriter(record_file) as writer: writer.write(example_msg.SerializeToString())该图像现在已写入 protobuf 消息,它是一个键值对集合,用于存储其尺寸、标签和原始图像(图像值以字节串形式存储)。
解码 TFRecord 并重建图像
在上一节中,我们学习了如何将.jpg图像写入TFRecord数据集。现在我们将看到如何将其读回并显示。一个重要的要求是,你必须知道TFRecord protobuf 的特征结构,这是通过其键来指示的。特征结构与上一节中用于构建TFRecord的特征描述是相同的。换句话说,就像原始图像被结构化为具有定义特征描述的tf.Example protobuf 一样,我们可以使用该特征描述来解析或重建图像,使用存储在特征描述中的相同知识:
-
从存储路径中读取
TFRecord:read_back_tfrecord = tf.data.TFRecordDataset('tfrecords-collection/maldives-1.tfrecord') -
创建一个字典来指定
TFRecord中的键和值,并使用它来解析TFRecord数据集中的所有元素:# Create a dictionary describing the features. image_feature_description = { 'height': tf.io.FixedLenFeature([], tf.int64), 'width': tf.io.FixedLenFeature([], tf.int64), 'depth': tf.io.FixedLenFeature([], tf.int64), 'label': tf.io.FixedLenFeature([], tf.int64), 'image_raw': tf.io.FixedLenFeature([], tf.string), } def _parse_image_function(example_proto): # Parse the input tf.Example proto using the dictionary # above. return tf.io.parse_single_example(example_proto, image_feature_description) parsed_image_dataset = read_back_tfrecord.map(_parse_image_function)在前面的代码中,
_parse_image_function使用image_feature_description来解析TFRecordprotobuf。我们使用map函数将_parse_image_function应用于read_back_tfrecord中的每个图像。 -
接下来,我们将使用以下代码来显示图像:
for image_features in parsed_image_dataset: image_raw = image_features['image_raw'].numpy() display.display(display.Image(data=image_raw))这是输出结果:

图 3.10 – 将数据集显示为图像
在本节中,你学会了如何将原始数据(图像)转换为TFRecord格式,并通过读取TFRecord并将其显示为图像来验证转换是否正确。从这个例子中,我们还可以看到,为了解码和检查TFRecord数据,我们需要在编码过程中使用的特征字典。在使用TFRecord时,记住这一点非常重要。
大规模处理图像数据
处理数据及其相应标签很简单,如果一切数据都可以加载到 Python 引擎的运行时内存中。然而,在构建数据管道以供模型训练工作流使用时,我们希望以批量的方式摄取或流式传输数据,以便不依赖于运行时内存来保存所有训练数据。在这种情况下,必须保持数据(图像)与标签之间的一对一关系。接下来我们将看到如何使用TFRecord来实现这一点。我们已经看到如何将一张图像转换为TFRecord。对于多张图像,转换过程对于每张图像都是完全相同的。
让我们来看一下如何重用和重构上一节中的代码,以应用于一批图像。由于你已经看到如何处理单个图像,所以在理解此处的代码和原理时,你应该几乎没有问题。
通常,在处理分类图像时,我们会按照以下目录结构组织图像,从一个基本目录开始(换句话说,项目名称)。下一级目录为train、validation和test。在这三个目录中,每个目录下都有图像类别目录。换句话说,标签是最底层的目录名称。例如,目录可以组织为以下结构:
/home/<user_name>/Documents/<project_name>
然后,在此级别下,我们将拥有以下内容:
/home/<user_name>/Documents/<project_name>train
/home/<user_name>/Documents/<project_name>train/<class_1_dir>
/home/<user_name>/Documents/<project_name>train/<class_2_dir>
/home/<user_name>/Documents/<project_name>train/<class_n_dir>
/home/<user_name>/Documents/<project_name>validation
/home/<user_name>/Documents/<project_name>/validation/<class_1_dir>
/home/<user_name>/Documents/<project_name>/validation/<class_2_dir>
/home/<user_name>/Documents/<project_name>/validation/<class_n_dir>
/home/<user_name>/Documents/<project_name>test
/home/<user_name>/Documents/<project_name> /test /<class_1_dir>
/home/<user_name>/Documents/<project_name> test/<class_2_dir>
/home/<user_name>/Documents/<project_name> /test/<class_n_dir>
另一种展示图像按类别组织的方式如下:
-base_dir
-train_dir
-class_1_dir
-class_2_dir
-class_n_dir
-validation_dir
-class_1_dir
-class_2_dir
-class_n_dir
-test
-class_1_dir
-class_2_dir
-class_n_dir
图像根据其类别放置在相应的目录中。在本节中,示例简化为云存储中的以下结构:
-bucket
-badlands (Badlands national park)
-kistefos (Kistefos Museum)
-maldives (Maldives beaches)
你可以在以下链接找到示例的 jpg 图片:github.com/PacktPublishing/learn-tensorflow-enterprise/tree/master/chapter_03/from_gs
每个文件夹名称对应图像的标签。一般步骤如下:
-
将存储在云存储桶中的图像复制到 JupyterLab 运行时。
-
将图像文件名映射到它们各自的标签。
-
将每个图像的尺寸、标签和字节数组写入
tf.Exampleprotobuf。 -
将多个 protobuf 文件一起存储在一个
TFRecord中。
执行步骤
下面是每个单元格中需要运行的详细步骤:
-
将图像从存储桶复制到笔记本运行时:
!mkdir from_gs !gsutil cp -r gs://image-collection from_gs在这一步骤中,创建了一个文件夹
from_gs,并将image-collection桶复制到其中。将基本目录视为
/from_gs/image-collection:![图 3.11 – 基本目录]()
图 3.11 – 基本目录
-
由于此示例用于演示如何创建
TFRecordDataset,而不是将数据划分为训练、验证和测试,因此我们可以直接进入图像类别目录级别,如下截图所示:![图 3.12 – 图像类别目录级别]()
图 3.12 – 图像类别目录级别
检查其中一个图像类别目录后,我们可以看到图像文件:
![图 3.13 – 图像文件]()
图 3.13 – 图像文件
-
导入库并将标签名称指定为
CLASS_NAMES:import tensorflow as tf import numpy as np import IPython.display as display import pathlib data_dir = pathlib.Path('from_gs/image-collection') data_dir = pathlib.Path(data_dir) CLASS_NAMES = np.array([item.name for item in data_dir.glob('*')]) CLASS_NAMES并且
CLASS_NAMES被正确捕获,如下所示:array(['kistefos', 'badlands', 'maldives'], dtype='<U8') -
现在,我们需要构建一个字典,将文件名映射到它们对应的标签(来自
CLASS_NAMES)。我们可以使用glob来编码目录和文件名模式。创建几个空列表,以便我们可以递归遍历目录,并将路径到文件名添加到文件名列表中,将标签(由目录名表示)添加到类别列表中:import glob file_name_list = [] class_list = [] for name in glob.glob('from_gs/image-collection/*/*.jpg', recursive=True): file_name_list.append(name) # label is next to the last substring before the file # name. class_str = name.split('/')[-2] idx_tuple = np.where(CLASS_NAMES == class_str) idx = int(idx_tuple[0]) # first element of the idx # tuple is the index class_list.append(idx) -
一旦两个列表按准确顺序填充,我们可以将这些列表组合在一起并将结果编码为键值对(字典):
image_label_dict = dict(zip(file_name_list, class_list)) image_label_dict should look similar to: {'from_gs/image-collection/kistefos/kistefos-1.jpg': 0, 'from_gs/image-collection/kistefos/kistefos-3.jpg': 0, 'from_gs/image-collection/kistefos/kistefos-2.jpg': 0, 'from_gs/image-collection/badlands/badlands-1.jpg': 1, 'from_gs/image-collection/badlands/badlands-2.jpg': 1, 'from_gs/image-collection/maldives/maldives-2.jpg': 2, 'from_gs/image-collection/maldives/maldives-1.jpg': 2}如所示,这是一个字典,键是文件路径,值编码相应的标签(图像类别)。
-
我们想要将数据转换为
tf.Exampleprotobuf 消息,这也是TFRecord的前身格式。tf.Example要求我们在图像中指定特征(如图像宽度像素数、高度像素数,或者以numpy数组形式表示的小数值等数据)。tf.Example指定的三种数据类型是tf.train.BytesList、tf.train.FloatList和tf.train.Int64List。因此,常见的 Python 数据类型需要强制转换为这三种类型中的一种。这是每个tf.Example数据类型可以接受并强制转换的内容:-
tf.train.BytesList:string,byte -
tf.train.FloatList:float(float32,float64) -
tf.train.Int64List:bool,enum,int32,uint32,int64,uint64为了将常见的数据类型强制转换为相应的兼容
tf.Example数据类型,TensorFlow 团队提供了以下辅助函数:如果我们想要将一串文本(字节字符串)转换成
tf.train.ByteList类型的特征,下面的函数首先将文本(它是一个急切张量)转换成numpy数组,因为tf.train.BytesList目前只能将numpy格式解包成字节列表。在将一个 protobuf 消息的值转换为ByteList类型之后,它会被转换为一个带有ByteList数据类型的特征对象:def _bytes_feature(value): if not tf.is_tensor(value): value = tf.convert_to_tensor(value) value = value.numpy() bytes_list_msg = tf.train.BytesList(value = [value]) coerced_list = tf.train.Feature(bytes_list = bytes_list_msg) return coerced_list如果我们需要将浮点数转换为
tf.train.FloatList类型的特征,下面的函数可以完成这个任务:def _float_feature(value): float_list_msg = tf.train.FloatList(value=[value]) coerced_list = tf.train.Feature(float_list = float_list_msg) return coerced_list
-
-
最后,对于生成
tf.train.Int64List类型的特征,可以按照以下方式完成:def _int64_feature(value): int64_list_msg = tf.train.Int64List(value=[value]) coerced_list = tf.train.Feature(int64_list = int64_list_msg) return coerced_list注意事项
tf.train.Feature一次只能接受一个特征。这些函数处理的是一次转换和强制转换一个数据特征。这个函数不同于tf.train.Features,后者接受一个包含多个特征的字典。在下一步中,我们将使用tf.train.Features。 -
将创建
tf.Exampleprotobuf 消息的工作流整合到一个包装函数中。这个函数接受两个输入:一个表示图像的字节字符串,以及该图像的对应标签。在这个函数内部,首先通过
decode_jpeg的输出指定图像形状,decode_jpeg将字节数组转换为jpeg格式。维度值存储在image_shape中,作为numpy数组,我们可以将这些值传入特征字典。在feature字典内,指定了键,并且从前面步骤中的辅助函数中得到了相应的值并进行了类型转换。特征字典随后被用来指定特征的 schema 到featuresprotobuf 中。然后,featureprotobuf 被转换为一个示例 protobuf,这是最终的格式,将被序列化为TFRecord:def image_example(image_str, label): image_shape = tf.image.decode_jpeg(image_string).shape feature = { 'height': _int64_feature(image_shape[0]), 'width': _int64_feature(image_shape[1]), 'depth': _int64_feature(image_shape[2]), 'label': _int64_feature(label), 'image_raw': _bytes_feature(image_string), } features_msg = tf.train.Features(feature=feature) example_msg = tf.train.Example(features=features_msg) return example_msg -
通过遍历
image_label_dict将多个图像文件写入TFRecords:record_file = 'image-collection.tfrecords' with tf.io.TFRecordWriter(record_file) as writer: for filename, label in image_image_label_dict.items(): image_string = open(filename, 'rb').read() tf_example = image_example(image_string, label) writer.write(tf_example.SerializeToString())
在前面的步骤中,我们将所有七张图像按三类写入了一个 TFRecord。
读取 TFRecord 并将其显示为图像
为了确保 TFRecord 格式呈现的图像数据无误,如果我们能读取回这些数据并显示出来,确认一切都被正确格式化,那将是非常有帮助的。现在,让我们读取TFRecord并将其显示为图像:
-
使用与前一节相同的 API 读取
tfrecords:image_collection_dataset = tf.data.TFRecordDataset('image-collection.tfrecords') -
定义数据集的规格:
feature_specs = { 'height': tf.io.FixedLenFeature([], tf.int64), 'width': tf.io.FixedLenFeature([], tf.int64), 'depth': tf.io.FixedLenFeature([], tf.int64), 'label': tf.io.FixedLenFeature([], tf.int64), 'image_raw': tf.io.FixedLenFeature([], tf.string), } -
解析 protobuf。这与前一节所示的完全相同:
def parse_image(example): return tf.io.parse_single_example(example, feature_specs) parsed_image_dataset = image_collection_dataset.map(parse_image) -
使用以下代码帮助显示图像:
import IPython.display as display for image_features in parsed_image_dataset: image_raw = image_features['image_raw'].numpy() display.display(display.Image(data=image_raw))
你应该能看到这个 protobuf 消息中包含的所有图像。为了简便起见,我们只展示两张图像,并注意图 3.14 和图 3.15 的尺寸不同,protobuf 能够正确保留和恢复这些尺寸。
这是第一张图像:

图 3.14 – 马尔代夫类别图像 (1)
这是第二张图像:

图 3.15 – 马尔代夫类别图像 (2)
关于在一个 TFRecord 中包含多个图像的几点说明
你已经看到,无论是单张图像还是多张图像,一切都可以写入一个单一的TFRecord。没有绝对的对错,哪种方式更优,取决于内存和 I/O 带宽等因素。一个经验法则是,如果图像数量足够多,应将训练图像分布到至少 32 - 128 个分片(每个分片是一个TFRecord),以便在 I/O 过程中保持文件级并行性。
摘要
本章提供了关于如何处理常见的结构化和非结构化数据的解释和示例。我们首先介绍了如何读取和格式化 pandas DataFrame 或 CSV 类型的数据结构,并将其转换为高效的数据摄取管道数据集。然后,在处理非结构化数据时,我们以图像文件为例。在处理图像数据时,我们必须以层次化的方式组织这些图像文件,使得标签可以轻松地映射到每个图像文件。TFRecord是处理图像数据的首选格式,它将图像的尺寸、标签和原始图像字节封装在一个被称为tf.Example的格式中。
在下一章中,我们将查看可重用的模型和模式,它们可以处理我们在这里学到的数据结构。
第四章:
第五章:可重用模型与可扩展数据管道
在本章中,你将学习如何使用 TensorFlow Enterprise 高级 API 中的预制模型元素,构建可扩展的数据摄取管道。这些选项提供了灵活性,适应不同的需求或风格,以便构建、训练和部署模型。掌握这些知识后,你将能够做出明智的选择,并理解不同模型开发方法之间的取舍。三种主要的方法是 TensorFlow Hub、TensorFlow Estimators API 和 TensorFlow Keras API。
TensorFlow Hub 是一个开源机器学习模型库。TensorFlow Estimators 和 tf.keras API 是包装器,可以视为可以配置和重用的高级元素,作为模型的构建块。从所需的代码量来看,TensorFlow Hub 模型需要最少的额外代码,而 Estimator 和 Keras API 属于较低级的构建块,因此使用 Estimator 或 Keras API 时需要更多的编码。但是无论如何,这三种方法都使 TensorFlow 更加易于学习和使用。接下来几节我们将学习这些方法如何与云存储中的可扩展数据摄取管道一起使用。
借助一个示例,我们将学习如何使用 TensorFlow 数据集 和 TensorFlow I/O 来摄取大量数据,而无需将其读取到 JupyterLab 的运行时内存中。本章将涵盖以下内容:
-
使用 TensorFlow Hub
-
应用 TensorFlow Hub 中的模型
-
利用 TensorFlow Keras API
-
使用 TensorFlow Estimators
使用 TensorFlow Hub
在这三种方法中(TensorFlow Hub、Estimators API 和 Keras API),TensorFlow Hub 相较于其他两者脱颖而出。它是一个开源机器学习模型库。TensorFlow Hub 的主要目的是通过迁移学习实现模型的重用。迁移学习是深度学习建模开发中非常实用且便捷的技术。其假设是,经过精心设计的模型(通过同行评审并由出版物推广)在训练过程中学习到了特征中的模式,这些模式可以被泛化,并应用于新的数据。因此,当我们有新的训练数据时,不需要重新训练模型。
以人类视觉为例,我们所看到的内容可以从简单到复杂的模式分解,顺序为线条、边缘、形状、层次,最终形成一个模式。事实证明,这也是计算机视觉模型识别人脸的方式。如果我们想象一个多层感知器模型,最开始,层次学习的是线条模式,然后是形状,随着深入到更深层次,我们可以看到学习到的模式是面部特征。
由于相同模式的层级结构可以用于分类其他图像,我们可以重用模型的架构(例如来自 TensorFlow Hub 的模型),并为我们自己的目的添加一个最终的分类层。在本章中,我们将利用这种迁移学习的方法。
应用来自 TensorFlow Hub 的模型
TensorFlow Hub 包含了许多可重用的模型。例如,在图像分类任务中,提供了诸如 Inception V3、不同版本的 ResNet 等预训练模型,以及可用的特征向量。在本章中,我们将学习如何加载和使用 ResNet 特征向量模型来进行我们自己图像的分类。这些图像包括五种花卉:雏菊、蒲公英、玫瑰、向日葵和郁金香。我们将使用tf.keras API 来获取这些图像:
-
你可以使用 Google Cloud AI 平台的 JupyterLab 环境进行这项工作。一旦进入 AI 平台的 JupyterLab 环境,你可以通过导入必要的模块并下载图像来开始:
import tensorflow as tf import tensorflow_hub as hub import matplotlib.pyplot as plt import numpy as np data_dir = tf.keras.utils.get_file( 'flower_photos', 'https://storage.googleapis.com/download.tensorflow. org/example_images/flower_photos.tgz', untar=True) print(data_dir)你可以在你的 JupyterLab 运行时实例中找到这些花卉图像,路径为
/home/jupyter/.keras/datasets/flower_photos。 -
在一个新的单元格中,我们可以运行以下命令来查看数据的目录结构:
!ls -lrt {data_dir}前面的命令将返回以下结构:
-rw-r----- 1 jupyter jupyter 418049 Feb 9 2016 LICENSE.txt drwx------ 2 jupyter jupyter 45056 Feb 10 2016 tulips drwx------ 2 jupyter jupyter 36864 Feb 10 2016 sunflowers drwx------ 2 jupyter jupyter 36864 Feb 10 2016 roses drwx------ 2 jupyter jupyter 45056 Feb 10 2016 dandelion drwx------ 2 jupyter jupyter 36864 Feb 10 2016 daisy每个文件夹中,你会找到不同宽度和高度的彩色
.jpg格式图像。在使用任何预构建模型之前,了解模型输入端所要求的数据形状非常重要。 -
我们将使用在 imagenet 上预训练的 ResNet V2 特征向量作为我们的基础模型。该模型的 URL 是
tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4。该文档指出,图像在模型入口点处的期望高度和宽度为
224。让我们继续指定这些参数以及训练的批量大小:pixels =224 BATCH_SIZE = 32 IMAGE_SIZE = (pixels, pixels)
现在我们已经了解了作为模型输入所期望的图像维度,接下来我们将处理下一步,即如何获取我们的训练图像。
创建一个生成器以大规模地提供图像数据
一种方便的将数据输入模型的方法是使用生成器。Python 生成器是一个迭代器,它会遍历数据目录并将数据批次传递给模型。当我们使用生成器遍历训练数据时,无需一次性加载整个图像集合,也不必担心计算节点的内存限制。相反,我们一次发送一批图像。因此,使用 Python 生成器比将所有数据作为一个巨大的 NumPy 数组传递要更加高效。
TensorFlow 提供了 API 和工作流,用于创建专门为 TensorFlow 模型消费设计的生成器。从高层次来看,它遵循以下过程:
-
它通过
ImageDataGenerator函数创建了一个对象。 -
它使用这个对象调用
flow_from_directory函数来创建一个 TensorFlow 生成器。
结果是,这个生成器知道训练数据存储的目录。
在处理图像时,我们需要为生成器指定一些关于数据的参数。输入图像的颜色值应在0和1之间。因此,我们必须通过将图像除以一个重新缩放因子255来对图像进行归一化,这是.jpg格式 RGB 图像中的最大像素值。我们还可以保留 20%的数据作为验证集。这被称为验证拆分因子。我们还需要指定符合 ResNet 标准的图像大小,选择一种插值方法将任何大小的图像转换为该大小,并指定批次中的数据量(批次大小)。必要的步骤如下:
-
将这些因素组织成元组。这些因素作为输入关键字指定给
ImageDataGenerator或flow_from_directory。我们可以将这些参数及其值作为元组传递给这些函数。在执行函数时,元组将被解包。这些参数存储在这些字典中:datagen_kwargs = dict(rescale=1./255, validation_split=.20) dataflow_kwargs = dict(target_size=IMAGE_SIZE, batch_size=BATCH_SIZE, interpolation='bilinear')如前述代码行所示,
datagen_kwargs传递给ImageDataGenerator,而dataflow_kwargs传递给flow_from_directory。 -
将这些元组传递给
ImageGenerator。这些元组封装了所有这些因素。现在,我们将把这些元组传递给生成器,如下代码所示:valid_datagen = tf.keras.preprocessing.image.ImageDataGenerator( **datagen_kwargs) valid_generator = valid_datagen.flow_from_directory( data_dir, subset='validation', shuffle=False, **dataflow_kwargs)你将看到交叉验证数据中图像和类别数量的输出:
Found 731 images belonging to 5 classes. -
对于训练数据,如果你愿意,可以考虑使用数据增强选项。如果是这样,我们可以在
ImageDataGenerator中设置这些参数:rotation_range horizontal_flip Width_shift_range height_shift_range Shear_range Zoom_range这些参数有助于将原始图像转换为不同的方向。这是一种典型的技术,用于增加更多训练数据以提高准确性。
-
目前,我们暂时不需要处理这个问题,因此我们将
do_data_augmentation = False,如下代码所示。如果你愿意,也可以将其设置为True。这里提供了建议的增强参数:do_data_augmentation = False if do_data_augmentation: train_datagen = tf.keras.preprocessing.image.ImageDataGenerator( rotation_range=40, horizontal_flip=True, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, **datagen_kwargs) else: train_datagen = valid_datagen train_generator = train_datagen.flow_from_directory( data_dir, subset='training', shuffle=True, **dataflow_kwargs)执行上述代码后,你将看到以下输出:
Found 731 images belonging to 5 classes. Found 2939 images belonging to 5 classes.我们的验证数据和训练数据生成器正确地识别了目录,并能够识别类别数量。
-
和所有分类任务一样,标签会转换为整数索引。生成器使用
train_generator.class_indices来映射标签:labels_idx = (train_generator.class_indices) -
我们可以通过创建一个反向查找,轻松地将索引映射回标签,反向查找的形式也是一个字典。这可以通过反转
labels_idx中的键值对来完成,其中键是索引,值是花卉类型:idx_labels = dict((v,k) for k,v in labels_idx.items()) print(idx_labels) {0: 'daisy', 1: 'dandelion', 2: 'roses', 3: 'sunflowers', 4: 'tulips'}
在本节中,我们学习了如何实现用于训练和验证数据的ImageGenerator。我们利用可选的输入参数对图像进行重新缩放和归一化。我们还学习了如何获取真实标签映射,以便解码模型的预测结果。
接下来,我们将学习通过重用 ResNet 特征向量来实现迁移学习,以完成我们自己的图像分类任务。
重用预训练的 ResNet 特征向量
现在我们准备构建模型。我们将使用 tf.keras.sequential API。它由三层组成——输入层、ResNet 层和一个全连接层——作为分类输出。我们还可以选择对 ResNet 进行微调或重新训练(这需要更长的训练时间)。定义模型架构的代码如下:
-
我们将首先定义参数,如下所示:
FINE_TUNING_CHOICE = True NUM_CLASSES = len(idx_labels) -
接下来,我们将使用以下代码来构建模型:
mdl = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)), hub.KerasLayer('https://tfhub.dev/google/imagenet/ resnet_v2_50/feature_vector/4', trainable = FINE_TUNING_CHOICE), tf.keras.layers.Dense(NUM_CLASSES, activation='softmax', name = 'custom_class') ]) -
现在,让我们使用以下代码行来构建模型:
mdl.build([None, 224, 224, 3])ResNet 需要将 RGB 层作为第三维分开。因此,我们需要添加一个输入层,接受形状为
[224, 224, 3]的input_shape。此外,由于我们有五种花卉类型,这是一个多类分类问题。我们需要一个带有 softmax 激活的全连接层来为每个标签输出概率。 -
我们可以通过以下代码行来确认模型架构:
mdl.summary()执行前面的代码行后,您将看到三个层的顺序及其预期的输出形状:
Model: 'sequential_1' _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= keras_layer_1 (KerasLayer) (None, 2048) 23564800 _________________________________________________________________ custom_class (Dense) (None, 5) 10245 ================================================================= Total params: 23,575,045 Trainable params: 23,529,605 Non-trainable params: 45,440 _________________________________________________________________这表明模型的结构非常简单。它由我们从 TensorFlow Hub 下载的 ResNet 特征向量层组成,后面是一个包含五个节点的分类头(我们图像集合中有五种花卉类别)。
编译模型
现在我们已经用适当的输入和输出层封装了 ResNet 特征向量,我们准备好设置训练工作流了。首先,我们需要编译模型,在其中指定优化器(在此案例中,我们选择 loss 函数)。优化器使用梯度下降算法不断寻找权重和偏置,以最小化 loss 函数。由于这是一个多类分类问题,因此需要使用分类交叉熵。
如需更深入的讨论,请参阅 TensorFlow 2.0 快速入门指南,作者 Tony Holroyd,由 Packt Publishing 出版。您可以参考 第四章 使用 TensorFlow 2 的监督机器学习,以及名为 逻辑回归 的部分,讨论有关损失函数和优化器的内容。这就是我们定义优化器的方法:
my_optimizer = tf.keras.optimizers.SGD(lr=0.005, momentum=0.9)
由于我们希望为每个类别输出概率,因此我们设置 from_logits = True。同时,我们希望模型不要变得过于自信,因此我们将 label_smoothing = 0.1 作为正则化项,以惩罚极高的概率。我们可以按如下方式定义 loss 函数:
my_loss_function = tf.keras.losses.CategoricalCrossentropy(from_logits=True, label_smoothing=0.1)
我们需要配置模型以进行训练。这是通过在模型的训练过程中定义 loss 函数和优化器来完成的,因为训练过程需要知道 loss 函数用于优化的目标是什么,以及使用什么优化器。要编译模型并指定优化器和 loss 函数,请执行以下代码:
mdl.compile(
optimizer=my_optimizer,
loss=my_loss_function,
metrics=['accuracy'])
结果是一个准备好用于训练的模型架构。
训练模型
对于模型训练,我们将使用tf.keras.fit函数。我们只会训练五个周期:
steps_per_epoch = train_generator.samples // train_generator.batch_size
validation_steps = valid_generator.samples // valid_generator.batch_size
hist = mdl.fit(
train_generator,
epochs=5, steps_per_epoch=steps_per_epoch,
validation_data=valid_generator,
validation_steps=validation_steps).history
训练结果应类似于此:
Epoch 1/5
91/91 [==============================] - 404s 4s/step - loss: 1.4899 - accuracy: 0.7348 - val_loss: 1.3749 - val_accuracy: 0.8565
Epoch 2/5
91/91 [==============================] - 404s 4s/step - loss: 1.3083 - accuracy: 0.9309 - val_loss: 1.3359 - val_accuracy: 0.8963
Epoch 3/5
91/91 [==============================] - 405s 4s/step - loss: 1.2723 - accuracy: 0.9704 - val_loss: 1.3282 - val_accuracy: 0.9077
Epoch 4/5
91/91 [==============================] - 1259s 14s/step - loss: 1.2554 - accuracy: 0.9869 - val_loss: 1.3302 - val_accuracy: 0.9020
Epoch 5/5
91/91 [==============================] - 403s 4s/step - loss: 1.2487 - accuracy: 0.9935 - val_loss: 1.3307 - val_accuracy: 0.8963
在每个训练周期中,loss函数值和训练数据的准确度都会显示出来。由于我们提供了交叉验证数据,模型在每个训练周期结束时也会使用验证数据集进行测试。loss函数和准确度测量通过 Fit API 在每个周期提供。这是每次训练运行的标准输出。
还值得一提的是,当在 AI Notebook 中使用 Nvidia Tesla T4 图形处理单元(GPU)和基本驱动节点(4 个 CPU,15GB 内存)执行上述代码时,总训练时间仅略超过 2 分钟,而如果在没有 GPU 的相同驱动节点中执行此训练过程,则可能需要超过 30 分钟才能完成训练。
GPU 非常适合深度学习模型的训练,因为它可以并行处理多个计算。GPU 通过大量核心实现并行处理。这意味着它拥有大带宽的内存和比 CPU 更快的梯度计算能力,可以处理深度学习架构中的所有可训练参数。
使用测试图像进行评分
现在,我们可以使用测试(保留)图像来测试模型。在这个例子中,我上传了五张花卉图像,我们需要将它们全部转换为[224, 224]的形状,并将像素值归一化到[0, 1]。按照惯例,测试图像会与训练和交叉验证图像分开存储。因此,通常会有一个不同的文件路径来存储测试图像:
-
我们将为这些花卉类型下载一些测试图像。图像已经在以下链接中划分为训练、验证和测试图像:
dataverse.harvard.edu/api/access/datafile/4159750 -
所以,在下一个单元格中,你可以使用
wget将其下载到你的笔记本中:!wget https://dataverse.harvard.edu/api/access/datafile/4159750 -
接着,解压它:
/flower_photos/small_test directory available in the left panel of your notebook instance. -
创建一个数据生成器实例用于测试数据。由于我们的
train_datagen已经知道如何实现这一点,我们可以重用该对象。确保你指定working_dir目录作为测试图像所在文件路径:working_dir = ‘flower_photos/small_test’ test_generator = train_datagen.flow_from_directory (directory=working_dir, batch_size = 5, target_size = [224, 224], shuffle=False, classes = list(labels_idx)) -
让我们记录下标签的索引:
print(test_generator.class_indices)输出结果表示每个标签在概率数组中的相对位置:
{'daisy': 0, 'dandelion': 1, 'roses': 2, 'sunflowers': 3, 'tulips': 4} -
我们还定义了一个辅助函数来绘制图像:
def plotImages(images_arr): fig, axes = plt.subplots(1, 5, figsize=(10,10)) axes = axes.flatten() for img, ax in zip( images_arr, axes): ax.imshow(img) ax.axis('off') plt.tight_layout() plt.show() -
现在,我们来看一下测试图像及其对应的标签(真实标签):
sample_test_images, ground_truth_labels = next(test_generator) print(ground_truth_labels)测试图像的输出结果如下所示。在前三行中,one-hot 编码在第一位置为
1,根据test_generator.class_indices,这对应于雏菊,而在最后两行中,1位于最后位置,表示最后两张图像是郁金香:[[1\. 0\. 0\. 0\. 0.] [1\. 0\. 0\. 0\. 0.] [1\. 0\. 0\. 0\. 0.] [0\. 0\. 0\. 0\. 1.] [0\. 0\. 0\. 0\. 1.]] -
然后,我们可以绘制这些图像:
plotImages(sample_test_images[:5])![图 4.1 – 测试图像示例;前三个是雏菊,最后两个是郁金香]()
图 4.1 – 测试图像示例;前三个是雏菊,最后两个是郁金香
-
要让模型对这些图像进行预测,请执行以下代码:
prediction = mdl.predict(sample_test_images[:5])预测的输出结果如下:
array([[9.9985600e-01, 3.2907694e-05, 2.3326173e-05, 6.8752386e-05, 1.8940274e-05], [9.9998152e-01, 7.6931758e-07, 9.4449973e-07, 1.6520202e-05, 2.8859478e-07], [9.9977893e-01, 2.0959340e-05, 6.2238797e-07, 1.8358800e-04, 1.6017557e-05], [6.7357789e-04, 5.8116650e-05, 3.0710772e-04, 6.2863214e-04, 9.9833256e-01], [1.9417066e-04, 1.3316995e-04, 6.2624150e-04, 1.4169540e-04, 9.9890471e-01]], dtype=float32)
该输出是每张图像每个类别的概率的 NumPy 数组。每一行对应一张图像,包含五个类别的概率。前三行的最大概率出现在第一列,后两行的最大概率出现在最后一列。这意味着根据test_generator.class_indices提供的映射,模型预测前三张图像为雏菊,最后两张图像为郁金香。
如果我们能将这些结果输出为更易读的格式,比如 CSV 文件,其中包含测试图像的文件名及其相应的预测结果,那就更有帮助了。
-
让我们将概率大小与位置关联,并定义一个标签参考:
labelings = tf.math.argmax(prediction, axis = -1) label_reference = np.asarray(list(labels_idx)) -
编写一个辅助函数,将位置映射到实际标签:
def find_label(idx): return label_reference[idx] -
现在我们可以映射每个观测值的最高概率位置:
predicted_idx = tf.math.argmax(prediction, axis = -1) -
我们可以查看
predicted_idx:<tf.Tensor: shape=(5,), dtype=int64, numpy=array([0, 0, 0, 4, 4])>这意味着在前三张图像中,最大概率出现在位置
0,根据test_generator.class_indices该位置对应daisy(雏菊)。同样,后两张图像的最大概率出现在位置4,该位置对应tulips(郁金香)。 -
然后,将辅助函数应用于预测输出的每一行,并将测试图像的文件名(
test_generator.filenames)与预测结果一起插入到格式良好的 pandas DataFrame 中:import pandas as pd predicted_label = list(map(find_label, predicted_idx)) file_name = test_generator.filenames results=pd.DataFrame({'File':file_name, 'Prediction':predicted_label}) results结果应该类似于以下图示。现在,您可以将 pandas DataFrame 保存为任何您选择的格式,例如 CSV 文件或 pickle 文件:

图 4.2 – 以 DataFrame 格式显示的预测输出
这完成了使用来自 TensorFlow Hub 的预训练模型的演示,将其应用到我们自己的数据,重新训练模型并进行预测。我们还展示了如何利用生成器批量地将训练数据输入模型。
TensorFlow Hub 位于模型重用性的最高层次。在那里,你可以找到许多已经构建好的开源模型,可以通过一种称为迁移学习的技术进行使用。在本章中,我们使用 tf.keras API 构建了一个回归模型。以这种方式(自定义)构建模型实际上并不是一件简单的事。通常,你需要花费大量时间实验不同的模型参数和架构。如果你的需求涉及分类或回归问题,并且与预构建的开源模型兼容,那么 TensorFlow Hub 是寻找适合你数据的分类或回归模型的一站式商店。然而,对于这些预构建的模型,你仍然需要调查输入层所需的数据结构,并为你的目的提供最终的输出层。然而,重用 TensorFlow Hub 中的这些预构建模型将节省你在构建和调试自己模型架构上的时间。
在接下来的章节中,我们将看到 TensorFlow Keras API,它是最新的高级 API,提供了许多可重用的模型。
利用 TensorFlow Keras API
Keras 是一个深度学习 API,封装了 TensorFlow、Theano 和微软认知工具包(也称为 CNTK)等机器学习库。作为一个独立的 API,Keras 之所以受欢迎,源于它简洁的模型构建过程。从 2018 年起,TensorFlow 将 Keras 作为未来的高级 API 添加进来,现在被称为 tf.keras。自 2019 年发布 TensorFlow 2.0 版本以来,tf.keras 已成为官方的高级 API。
tf.keras 擅长建模复杂的深度学习架构,包含 tf.keras 密集层,用于构建一个回归模型,处理来自 BigQuery 的表格数据。
数据获取
我们将使用 Google Cloud 上的一个公开数据集作为本示例的工作数据:
-
这是我们感兴趣的表格:
DATASET_GCP_PROJECT_ID = 'bigquery-public-data' DATASET_ID = 'covid19_geotab_mobility_impact' TABLE_ID = 'us_border_volumes'你可以在 BigQuery 中找到它:
![图 4.3 – BigQuery 门户和表格选择]()
图 4.3 – BigQuery 门户和表格选择
-
让我们通过运行以下查询来查看数据:
SELECT * FROM `bigquery-public-data.covid19_geotab_mobility_impact.us_border_volumes` ORDER BY RAND() LIMIT 1000上面的查询帮助我们检索了表格
data.covid19_geotab_mobility_impact.us_border_volumes中的 1,000 行随机数据。
这是输出结果:

图 4.4 – us_border_volumes 表格的列
使用 us_border_volumes 表格解决数据科学问题
输出由从查询的表格中随机选出的行组成。你的输出将包含不同的值。
在us_border_volumes表中,每条记录表示一辆卡车在美国边境口岸的进出。每条记录中的属性有trip_direction、day_type、day_of_week、date、avg_crossing_duration、percent_of_normal_volume、avg_crossing_duration_truck和percent_of_nortal_volume_truck。我们希望构建一个模型,根据这些特征预测卡车通过边境所需的时间。
选择特征和目标进行模型训练
这是一个示例问题,我们将使用它来演示如何利用 TensorFlow I/O 管道为模型提供训练数据。
让我们将这个问题设置为一个回归问题,使用这些数据。我们将构建一个回归模型来预测卡车通过边境的平均时间(avg_crossing_duration_truck)。其他列(除了date)为特征。
流式训练数据
在接下来的例子中,我们将使用 Google AI 平台的 JupyterLab 笔记本,配备 TensorFlow Enterprise 2.1 发行版。你可以重新使用第二章中的项目 ID,在 Google AI 平台上运行 TensorFlow Enterprise。
确定数据源后,我们将构建一个流式工作流程,将训练数据传输到模型中。这不同于在 Python 运行时将表读取为 pandas DataFrame。我们希望通过批次流式传输训练数据,而不是耗尽为 Python 运行时分配的所有内存。因此,我们将使用 TensorFlow I/O 从 BigQuery 流式传输训练数据:
-
我们将从以下代码开始,导入必要的库并设置环境变量:
import tensorflow as tf from tensorflow import feature_column from tensorflow_io.bigquery import BigQueryClient import numpy as np from google.cloud import bigquery client = BigQueryClient() PROJECT_ID = 'project1-XXXXX' # A project ID in your GCP subscription. DATASET_GCP_PROJECT_ID = 'bigquery-public-data' DATASET_ID = 'covid19_geotab_mobility_impact' TABLE_ID = 'us_border_volumes' -
创建一个会话来读取 BigQuery:
read_session3 = client.read_session( 'projects/' + PROJECT_ID, DATASET_GCP_PROJECT_ID, TABLE_ID, DATASET_ID, ['trip_direction', 'day_type', 'day_of_week', 'avg_crossing_duration', 'percent_of_normal_volume', 'avg_crossing_duration_truck', 'percent_of_normal_volume_truck' ], [tf.string, tf.string, tf.int64, tf.double, tf.int64, tf.double, tf.int64 ], requested_streams=10 ) dataset3 = read_session3.parallel_read_rows() -
我们刚刚从 BigQuery 中的表中选择了感兴趣的字段。现在,表已经作为数据集被读取,我们需要将每一列指定为特征或目标。让我们使用这个辅助函数:
def transfrom_row(row_dict): # Identify column names for features. feature_dict = { column: (tf.strings.strip(tensor) if tensor.dtype == 'string' else tensor) for (column,tensor) in row_dict.items() } # Remove target column from data target = feature_dict.pop ('avg_crossing_duration_truck') # Return a tuple of features and target return (feature_dict, target) -
现在我们将这个函数应用于训练数据集的每一行。这本质上是对数据集的一次转换,因为我们应用了一个函数,它将数据集拆分为两个字典的元组——特征和目标:
transformed_ds = dataset3.map(transfrom_row) -
现在我们将对数据集进行洗牌并分批处理:
BATCH_SIZE = 32 SHUFFLE_BUFFER = 1024 training_dataset3 = transformed_ds.shuffle (SHUFFLE_BUFFER).batch(BATCH_SIZE)
在这一部分,我们从 BigQuery 中识别了一个表,确定了特征和目标列,将表转换为 TensorFlow 数据集,对其进行了洗牌并分批。这是当你不确定数据量是否会导致内存使用问题时常用的技术。
在下一部分,我们将查看tf.keras API 以及如何使用它来构建和训练模型。
模型的输入
到目前为止,我们已经处理了在训练数据集中指定特征和目标的问题。现在,我们需要将每个特征指定为类别型或数值型。这需要我们设置 TensorFlow 的feature_columns对象。feature_columns对象是模型的输入:
-
对于每个类别列,我们需要跟踪可能的类别。通过以下辅助函数来实现:
def get_categorical_feature_values(column): query = 'SELECT DISTINCT TRIM({}) FROM `{}`.{}.{}'. format(column, DATASET_GCP_PROJECT_ID, DATASET_ID, TABLE_ID) client = bigquery.Client(project=PROJECT_ID) dataset_ref = client.dataset(DATASET_ID) job_config = bigquery.QueryJobConfig() query_job = client.query(query, job_config=job_config) result = query_job.to_dataframe() return result.values[:,0] -
然后,我们可以使用以下代码片段创建
feature_columns对象(实际上是一个 Python 列表):feature_columns = [] # Numeric columns for header in ['day_of_week', 'avg_crossing_duration', 'percent_of_normal_volume', 'percent_of_normal_volume_truck']: feature_columns.append (feature_column.numeric_column(header)) # Categorical columns for header in ['trip_direction', 'day_type']: categorical_feature = feature_column.categorical_column_with_vocabulary_list( header, get_categorical_feature_values(header)) categorical_feature_one_hot = feature_column.indicator_column(categorical_feature) feature_columns.append(categorical_feature_one_hot)请注意,目标列不在
feature_columns中。 -
现在,我们只需创建一个层来作为模型的输入。第一个层是特征列输入模型的层,这是一个多层感知机,由一系列可重用的
Dense层定义:feature_layer = tf.keras.layers.DenseFeatures(feature_columns) Dense = tf.keras.layers.Dense model = tf.keras.Sequential( [ feature_layer, Dense(100, activation=tf.nn.relu, kernel_initializer='uniform'), Dense(75, activation=tf.nn.relu), Dense(50, activation=tf.nn.relu), Dense(25, activation=tf.nn.relu), Dense(1) ])
在本节中,我们创建了一个流程,将数据集导入到模型的特征层。在此过程中,对于类别列,我们必须进行独热编码,因为这些列不是数值型的。接着,我们使用tf.keras API 构建了一个模型架构。
接下来,我们将编译这个模型并启动训练过程。
模型训练
在模型可用之前,我们需要对其进行编译。由于这是一个回归模型,我们可以指定loss函数,对于训练指标,我们将跟踪 MSE 以及平均绝对误差(MAE):
-
使用适当的
loss函数和回归任务中使用的指标来编译模型:model.compile( loss='mse', metrics=['mae', 'mse']) -
训练模型:
model.fit(training_dataset3, epochs=5) -
一旦模型训练完成,我们可以创建一个包含两个观测值的样本测试数据集。测试数据必须采用字典格式:
test_samples = { 'trip_direction' : np.array(['Mexico to US', 'US to Canada']), 'day_type' : np.array(['Weekdays', 'Weekends']), 'day_of_week' : np.array([4, 7]), 'avg_crossing_duration' : np.array([32.8, 10.4]), 'percent_of_normal_volume' : np.array([102, 89]), 'percent_of_normal_volume_truck' : np.array([106, 84]) } -
为了对这个测试样本进行评分,执行以下代码:
model.predict(test_samples)上述代码的输出如下:
array([[29.453201], [10.395596]], dtype=float32)这表示预测的卡车过境平均等待时间(
avg_crossing_duration_truck)。
我们刚刚学习了如何重用tf.keras的全连接层和顺序 API,并将其与一个由数据集流驱动的数据输入管道进行整合,使用feature_column对象进行特征编码。
tf.keras是一个高级 API,提供了一组专门用于深度学习问题的可重用元素。如果你的解决方案需要深度学习技术,那么tf.keras是推荐的起点。
在接下来的部分,我们将查看另一个高级 API——TensorFlow Estimators。在tf.keras API 成为 TensorFlow 的核心 API 之前,以及在 1.x 版本的 TensorFlow 中,TensorFlow Estimators 是唯一可用的高级 API。
所以,在接下来的部分,我们将查看它是如何工作的。
使用 TensorFlow Estimators
TensorFlow 估算器也是可复用的组件。估算器是更高层的 API,允许用户构建、训练和部署机器学习模型。它有几个预制的模型,可以节省用户创建计算图或会话的麻烦。这使得用户能够在有限的代码修改下快速尝试不同的模型架构。估算器并不像tf.keras那样专门面向深度学习模型。因此,你不会找到很多预制的深度学习模型。如果你需要使用深度学习框架,tf.keras API 是入门的正确选择。
在这个例子中,我们将设置相同的回归问题并构建一个回归模型。数据来源是我们在流式训练数据中使用的相同数据,通过 Google Cloud 的 BigQuery 提供:
DATASET_GCP_PROJECT_ID = 'bigquery-public-data'
DATASET_ID = 'covid19_geotab_mobility_impact'
TABLE_ID = 'us_border_volumes'
这就是我们在tf.keras部分使用的同一个 BigQuery 表(图 4.4)。请参阅图 4.4,其中展示了从该表随机提取的几行数据。
就像我们在上一节中使用tf.keras API 一样,这里我们希望使用 TensorFlow Estimators 构建一个线性回归模型,来预测卡车过境的平均时间(avg_crossing_duration_truck)。其他列(除date外)是特征。
使用 TensorFlow Estimators API 构建和训练模型的模式如下。
通过调用估算器(即对于预制的估算器,如线性回归器)并指定feature_columns对象来创建estimator对象,这样模型就知道在特征数据中应该期待哪些数据类型。
使用estimator对象调用.train()并传递一个输入函数给它。这个输入函数负责解析训练数据和标签。由于我们正在设置一个回归问题,接下来以预制的线性回归估算器为例。这是训练过程的常见模式:
linear_est = tf.estimator.LinearRegressor(feature_columns=feature_columns, model_dir=MODEL_DIR)
linear_est.train(input_fn)
从前面的代码中,可以观察到以下内容:
-
首先,创建一个线性回归器实例
linear_est,并使用feature_columns对象。这个对象提供关于每个特征的注释(数字或类别数据类型)。model_dir是保存模型检查点的指定目录。 -
接下来的代码是
linear_est.train(input_fn)。这个实例调用train()方法来启动训练过程。train()方法接收一个函数input_fn,它负责将训练数据按批次流式传送并格式化到模型中。接下来我们会了解如何构建input_fn。换句话说,TensorFlow Estimators 将数据注解与训练工作流中的数据摄取过程分开。
TensorFlow Estimators 的数据管道
像tf.keras一样,TensorFlow Estimators 在 TensorFlow 企业环境中运行时,可以利用流式数据管道,例如在 Google Cloud AI 平台上。在本节中,作为示例,我们将看到如何将训练数据(来自 BigQuery 中的一个表)流式传输到 TensorFlow Estimator 模型中。
以下是为 TensorFlow Estimator 构建 BigQuery 数据管道的步骤。
-
通常,我们从所需库的
import操作开始:import tensorflow as tf from tensorflow_io.bigquery import BigQueryClient from tensorflow import feature_column from google.cloud import bigquery import pandas as pd import numpy as np import datetime, os import itertools -
现在我们为 BigQuery 中的目标表指定一些参数。确保指定你自己的
PROJECT_ID:PROJECT_ID = '<YOUR_PROJECT_ID>' DATASET_GCP_PROJECT_ID = 'bigquery-public-data' DATASET_ID = 'covid19_geotab_mobility_impact' TABLE_ID = 'us_border_volumes' -
接下来,我们将指定训练过程的输入函数。这个输入函数将通过
transform_row函数处理读取操作、数据注解、转换,以及将目标与特征分离。这些操作与之前在利用 TensorFlow Keras API部分中描述的tf.keras示例完全相同。唯一的区别是我们现在将所有代码封装如下:def input_fn(): PROJECT_ID = 'project1-190517' # This is from what you created in your Google Cloud Account. DATASET_GCP_PROJECT_ID = 'bigquery-public-data' TABLE_ID = 'us_border_volumes' DATASET_ID = 'covid19_geotab_mobility_impact' client = BigQueryClient() read_session = client.read_session( 'projects/' + PROJECT_ID, DATASET_GCP_PROJECT_ID, TABLE_ID, DATASET_ID, ['trip_direction', 'day_type', 'day_of_week', 'avg_crossing_duration', 'percent_of_normal_volume', 'avg_crossing_duration_truck', 'percent_of_normal_volume_truck' ], [tf.string, tf.string, tf.int64, tf.double, tf.int64, tf.double, tf.int64 ], requested_streams=10 ) dataset = read_session.parallel_read_rows()我们仍然在
input_fn内部。继续处理input_fn: -
我们还重新组织了如何在数据中指定特征和目标,通过
input_fn内部的transform_row函数。def transform_row(row_dict): # Trim all string tensors feature_dict = { column: (tf.strings.strip(tensor) if tensor.dtype == 'string' else tensor) for (column,tensor) in row_dict.items() } # Extract target from features target = feature_dict.pop( 'avg_crossing_duration_truck') # return a tuple of features and target return (feature_dict, target) transformed_ds = dataset.map(transfrom_row) transformed_ds = transformed_ds.batch(32) return transformed_ds这就是整个
input_fn。此时,input_fn返回从us_border_volumes读取的数据集。 -
就像我们在利用 TensorFlow Keras API部分中讨论的
tf.keras示例一样,我们也需要构建一个feature_columns对象来进行特征注解。我们可以重用相同的代码:feature_columns = [] # Numeric columns for header in ['day_of_week', 'avg_crossing_duration', 'percent_of_normal_volume', 'percent_of_normal_volume_truck']: feature_columns.append( feature_column.numeric_column(header)) # Categorical columns for header in ['trip_direction', 'day_type']: categorical_feature = feature_column.categorical_column_with_vocabulary_list( header, get_categorical_feature_values(header)) categorical_feature_one_hot = feature_column.indicator_column(categorical_feature) feature_columns.append(categorical_feature_one_hot) -
现在,让我们设置一个目录来保存模型的检查点:
MODEL_DIR = os.path.join('models', datetime.datetime.now().strftime('%Y%m%d-%H%M%S')) -
使用以下命令创建目录:
%mkdir models %mkdir {MODEL_DIR} -
启动训练过程:
linear_est = tf.estimator.LinearRegressor(feature_columns=feature_columns, model_dir=MODEL_DIR) linear_est.train(input_fn)
这完成了模型训练过程。
由于estimator模型期望输入为一个函数,为了使用estimator进行评分,我必须传入一个函数,该函数接收测试数据,格式化它,并像训练数据一样将其输入到模型中。
训练的输入函数基本上做了两件事:
-
它查询了表格并获取了表格的数据集表示。
-
它通过将目标与特征分离来转换数据。
关于这里的评分情况,我们不需要担心。我们只需要获取测试数据的数据集表示:
-
我们可以重用模型训练部分中展示的相同测试数据:
test_samples = { 'trip_direction' : np.array(['Mexico to US', 'US to Canada']), 'day_type' : np.array(['Weekdays', 'Weekends']), 'day_of_week' : np.array([4, 7]), 'avg_crossing_duration' : np.array([32.8, 10.4]), 'percent_of_normal_volume' : np.array([102, 89]), 'percent_of_normal_volume_truck' : np.array([106, 84]) } -
创建一个辅助函数,通过以下代码将
test_samples转换为数据集:def scoring_input_fn(): return tf.data.Dataset.from_tensor_slices(test_samples).batch(2) -
下一步是使用以下代码行对测试数据进行评分:
y = linear_est.predict( input_fn=scoring_input_fn) -
最后,让我们打印预测结果,如下所示:
predictions = list(p['predictions'] for p in itertools.islice(y, 2)) print('Predictions: {}'.format(str(predictions))) Above code prints the output: Predictions: [array([23.875168], dtype=float32), array([13.621282], dtype=float32)]在前面的代码中,我们遍历了模型的输出,该输出是一个字典。为了引用与模型输出相关的值,我们需要通过名为
prediction的键来访问它。为了提高可读性,我们将其转换为列表并以字符串的形式打印出来。它显示了第一辆卡车预计在23.87分钟内通过边境,而第二辆卡车预计在13.62分钟内通过边境。
在 tf.keras 成为 TensorFlow 发布的正式部分之前,TensorFlow Estimators 是唯一的高层 API。尽管它包含了许多预构建的模块,如线性回归器和不同版本的分类器,但它缺乏对一些常见深度学习模块的支持,包括 CNN、LSTM 和 GRU。但是,如果你的需求可以通过非深度学习回归器或分类器来解决,那么 TensorFlow Estimators 是一个不错的起点。它也可以与数据摄取管道进行集成。
概述
在本章中,你已经看到了三大可重用模型元素源如何与可扩展的数据管道集成。通过 TensorFlow 数据集和 TensorFlow I/O API,训练数据被流式传输到模型训练过程中。这使得模型能够在无需处理计算节点内存的情况下进行训练。
TensorFlow Hub 位于模型可重用性的最高层级。在这里,你将找到许多已经构建好的开源模型,这些模型可以通过一种称为迁移学习的技术进行使用。在本章中,我们使用 tf.keras API 构建了一个回归模型。以这种方式(自定义)构建模型实际上并不是一项简单的任务。通常,你需要花费大量时间来尝试不同的模型参数和架构。如果你的需求可以通过预构建的开源模型来满足,那么 TensorFlow Hub 就是你理想的选择。然而,对于这些预构建的模型,你仍然需要调查输入层所需的数据结构,并为你的目的提供最终的输出层。尽管如此,在 TensorFlow Hub 中重用这些预构建的模型将节省构建和调试自己模型架构的时间。
tf.keras 是一个高层次的 API,专门为深度学习问题提供一组可重用的元素。如果你的解决方案需要深度学习技术,那么 tf.keras 是推荐的起点。在一个示例的帮助下,我们已经看到了如何使用 tf.keras API 快速构建一个多层感知机,并与 TensorFlow I/O 模块结合,后者负责流式传输训练数据。
在下一章中,我们将继续探讨在这里学到的关于 tf.keras 和 TensorFlow Hub 的内容,并利用 Google Cloud AI Platform 将我们的模型训练流程作为云端训练任务来运行。
第三部分 – 扩展和调优机器学习工作
在通过多种方式进行 TensorFlow 企业模型开发设置训练任务后,接下来是通过使用 GPU 或 TPU 集群来扩展训练过程。你将学习如何利用分布式训练策略,并实施超参数调优,以扩展并改进你的模型训练实验。
在这一部分,你将学习如何在 GCP 环境中设置 GPU 和 TPU,以便提交模型训练任务。你还将了解最新的超参数调优 API,并利用 GCP 资源进行大规模运行。
本节包括以下章节:
-
第五章,大规模训练
-
第六章,超参数调优
第五章:
第六章:大规模训练
当我们构建和训练更复杂的模型,或在数据摄取管道中使用大量数据时,我们自然希望更高效地利用我们所拥有的所有计算时间和内存资源。这就是本章的主要目的,我们将把前几章学到的内容与在计算节点集群中运行的分布式训练技术结合起来。
TensorFlow 已经为分布式训练开发了一个高级 API。此外,这个 API 与 Keras API 的集成非常良好。事实证明,Keras API 现在已经成为 TensorFlow 生态系统中的一等公民。与估算器 API 相比,在分布式训练策略方面,Keras 获得了最多的支持。因此,本章将主要集中在如何使用 Keras API 和分布式训练策略。我们将利用 Google Cloud 资源,演示如何对我们已经熟悉的 Keras API 代码进行最小改动,并将其与分布式训练策略集成。
本章中,我们将学习如何利用 Google Cloud 的 AI Platform,并将 TFRecordDataset 融入模型训练工作流,指定用于 TPU 和 GPU 加速器的分布式训练策略。本章的所有代码都可以在 github.com/PacktPublishing/learn-tensorflow-enterprise/tree/master/chapter_05 找到。
本章将涵盖以下主题:
-
通过 AI Platform 使用 Cloud TPU
-
通过 AI Platform 使用 Cloud GPU
通过 AI Platform 使用 Cloud TPU
在我们开始之前,让我们简要讨论一下你可能会在Google Cloud Platform(GCP)上产生的费用。这里的所有脚本和示例都是为了教学目的而设定的。因此,训练周期通常会设置为合理的最小值。考虑到这一点,仍然值得注意的是,当我们开始利用云资源时,我们需要牢记计算集群的成本。你可以在这里找到关于 AI Platform 训练费用的更多信息:cloud.google.com/ai-platform/training/pricing#examples_calculate_training_cost_using_price_per_hour。
本书中的示例通常使用预定义的规模层级。在前述链接中的预定义规模层级列表中,你将看到不同层级的每小时价格。例如,BASIC_TPU 的价格远高于 BASIC_GPU。我们将在本章中同时使用这两者,因为我们将学习如何向 TPU 或 GPU 提交训练任务。根据我的经验,本书中的每个示例通常会在 20 到 60 分钟内完成运行,所使用的参数在书中或 GitHub 仓库中已有说明。你的体验可能会有所不同,这取决于你的区域和计算资源的可用性。
该费用不包括云存储的费用,云存储用于读取和写入数据或模型文件。请记得在不使用时删除云存储。供参考,与本书相关的内容和工作所需的云存储费用仅占整体费用的一小部分。
提示
有时候,当 GPU 需求量很大时,你可能想使用 TPU,TPU 是 GCP 提供的最快集群。它可能显著减少训练时间,从而减少你的开支。
如果你还没有这样做,请立即克隆这个仓库:
git clone **https://github.com/PacktPublishing/learn-tensorflow-enterprise.git**
正如我们在前几章中所看到的,Google 的 AI 平台提供了一个便捷的开发环境,称为 JupyterLab。它可以通过 SDK 与其他 Google Cloud 服务(如 BigQuery 或云存储桶)集成。在本节中,我们将利用 Google Cloud 的 TPU 进行分布式训练工作负载。
TPU 是根据 Google 的规格和设计定制的 ASIC 加速器。它是专门优化用于处理深度学习计算和算法的加速器。因此,TPU 非常适合训练复杂的神经网络和机器学习模型,能够处理几乎无限量的训练数据。它可以在几分钟内完成训练过程,而在单节点机器上可能需要几个小时。
当前,有四种 TPU 配置:V2、V2 Pod、V3 和 V3 Pod。更多细节,请参考官方链接,其中包含了 Google Cloud 对 Cloud TPU 的优势描述:cloud.google.com/tpu/?_ga=2.138028336.-1825888872.1592693180。对于运行 TensorFlow Enterprise 2.1 或更高版本的 AI Platform 实例,V3 是首选配置。无论是 V2 还是 V3,TPU pod都由多个 TPU 组成。Pod 基本上是 TPU 集群。有关 TPU 和 TPU pod 的更多信息,请参考以下链接,描述了不同版本 TPU pod 的配置及其在不同机器学习训练任务中的运行时:cloud.google.com/tpu/docs/system-architecture#configurations。每个 Pod,无论是 V2 还是 V3,都能达到 100 petaFLOPS 的性能。此性能数据可以在此链接中查看:techcrunch.com/2019/05/07/googles-newest-cloud-tpu-pods-feature-over-1000-tpus/。
使用 Pod 相较于单个 TPU 的好处在于训练速度和你在训练流程中可用的内存。与 V2 Pod(512 个核心 = 每个 TPU 8 个核心 × 64 个 TPU)相比,每个 V2 TPU 由 8 个核心组成,每个核心是训练数据并行性的基本单元。在核心级别上,执行 TensorFlow 分布式训练策略。为了演示和教学目的,本节中的所有示例都会在 TPU 内部的 8 个核心之间分配训练策略。tf.distribute.TPUStrategy API 是在 TPU 中分配训练的手段。该策略实现了同步分布式训练,并结合了 TPU 在多个 TPU 核心之间的全归约操作。
我们将使用 Cloud TPU 并提交一个训练任务。在这个例子中,我们将展示如何使用 tfrecord 格式提交训练任务,图像保持原始尺寸。tfrecord 格式的图像存储在 Google Cloud 存储桶中(假设你的 tfrecord 已准备好;从原始图像生成 tfrecord 格式数据不在本章范围内)。
训练工作流将在训练完成时生成检查点并保存模型工件。这些项也会保存在存储桶中。因此,我们必须授予 TPU 对工作存储桶的读写权限。
在开始使用 TPU 之前,需要处理 Google Cloud 中的一些管理事项。让我们开始吧。
安装 Cloud SDK
要在客户端节点上安装 Cloud SDK,请下载并安装 Google Cloud SDK。Google Cloud SDK 提供了关于如何在不同操作系统(如 Mac、Linux 或 Windows)上安装的详细说明页面。强烈建议你按照此链接中的说明安装 Google Cloud SDK:cloud.google.com/sdk/docs#install_the_latest_cloud_sdk_version。安装完成后,你可以使用以下命令进行验证:
gcloud --help
上述命令将返回以下输出:

图 5.1 – gcloud SDK 验证
图 5.1 显示了 gcloud 命令的一般格式。使用 Ctrl + C 退出此模式并恢复命令提示符。
授予 Cloud TPU 访问你的项目权限
从这里开始,设置说明来自 Google Cloud 自己的文档网站,网址是:cloud.google.com/ai-platform/training/docs/using-tpus#tpu-runtime-versions:
-
在此步骤中,我们将根据我们的项目 ID 检索云 TPU 服务账户名称。可以使用以下命令:
curl -H 'Authorization: Bearer $(gcloud auth print-access-token)' \ https://ml.googleapis.com/v1/projects/<your-project-id>:getConfig上述命令将返回以下输出:
![图 5.2 – TPU 服务账户检索]()
图 5.2 – TPU 服务账户检索
-
记下
serviceAccountProject和tpuServiceAccount的详细信息。 -
一旦我们知道 TPU 服务账户的信息,我们需要根据以下命令进行初始化:
curl -H 'Authorization: Bearer $(gcloud auth print-access-token)' \ -H 'Content-Type: application/json' -d '{}' \ https://serviceusage.googleapis.com/v1beta1/projects/<serviceAccountProject>/services/tpu.googleapis.com:generateServiceIdentity
前述命令会为你生成一个 Cloud TPU 服务账户。确保在 URL 中填写 <serviceAccountProject> 的详细信息。
将 TPU 服务账户添加为项目成员
我们使用的项目也必须知道 TPU 服务账户。在上一节的 第 3 步 中,我们将项目的 Bearer Token 传递给 TPU 服务账户,以便 TPU 可以访问我们的项目。基本上,这就像是将另一个成员添加到该项目中,在这种情况下,新的成员是 TPU 服务账户:
-
我们可以使用 Google Cloud Console 来实现这一点,如 图 5.3 所示:
![图 5.3 – IAM & 管理入口]()
图 5.3 – IAM & 管理入口
-
在 IAM 屏幕上,点击 添加 按钮,将 TPU 添加到该项目,如 图 5.4 所示:
![图 5.4 – 向项目中添加成员]()
图 5.4 – 向项目中添加成员
-
然后,在 新成员 框中填写 TPU 服务账户的详细信息。在 选择角色 下,找到 服务代理角色,然后找到 Cloud ML 服务代理。如 图 5.5 所示:
![图 5.5 – 为 TPU 服务账户分配 Cloud ML 服务代理角色]()
图 5.5 – 为 TPU 服务账户分配 Cloud ML 服务代理角色
我们还没有完成 TPU 服务账户的配置。我们还需要让它访问我们的训练数据,并将训练结果(如检查点和模型资产)写入存储。这意味着我们需要为 TPU 服务账户添加几个新的角色。
-
我们点击 添加另一个角色,然后继续寻找 项目,如 图 5.6 所示:
![图 5.6 – 为 TPU 服务账户分配项目查看者角色]()
图 5.6 – 为 TPU 服务账户分配项目查看者角色
-
同样,我们还需要添加 Cloud Storage Admin 角色,如 图 5.7 所示:
![图 5.7 – 为 TPU 服务账户分配存储管理员角色]()
图 5.7 – 为 TPU 服务账户分配存储管理员角色
-
一旦设置好所有三个角色,点击 保存。
白名单访问以读取训练数据并写入工件(替代方案)
前述方法赋予 TPU 服务较为广泛的权限,允许 TPU 拥有对所有存储桶的管理员权限。如果你希望将 TPU 服务的权限限制为仅限某些存储桶,可以将 TPU 服务账户放入每个存储桶的 访问控制列表(ACL)中。你可以为训练数据存储桶执行此操作,如果希望训练任务将结果写入另一个存储桶,则也可以为该存储桶执行相同操作:
-
我们可以从编辑存储桶权限开始,如 图 5.8 所示。选择 权限 标签页:
![图 5.8 – 编辑存储桶权限]()
图 5.8 – 编辑存储桶权限
-
然后,点击 添加,如图 5.9所示:
![图 5.9 – 将 TPU 服务账户添加到存储桶 ACL]()
图 5.9 – 将 TPU 服务账户添加到存储桶 ACL
-
然后,通过填写服务账户名称,向 TPU 服务账户添加两个新角色,如图 5.10所示。在这个例子中,我们将使用相同的存储桶来托管训练数据和写入训练产物。因此,我们需要添加两个来自 Cloud Storage Legacy 的角色:Storage Legacy Bucket Reader 和 Storage Legacy Bucket Writer:
![图 5.10 – 将 TPU 服务账户列入存储桶的两个角色白名单]()
图 5.10 – 将 TPU 服务账户列入存储桶的两个角色白名单
-
一旦添加了这些角色,点击 保存。
我们已经完成了使用 Cloud TPU 进行模型训练工作流所需的最基本管理工作。在接下来的部分,我们将看到如何重构代码并设置 TPU 的分布式训练策略。
执行命令及格式
AI 平台还提供作为服务的模型训练功能。它允许用户从本地环境的命令行提交训练任务。该任务将在 Google Cloud 的计算集群上运行(提供不同定价层级的 CPU、TPU 或 GPU 选项)。如果你不熟悉作为服务的训练概念,可以参考此链接了解更多详情:cloud.google.com/ai-platform/training/docs/training-jobs。
除了云端训练任务,AI 平台还可以执行云推理任务。我们即将运行的命令用于提交云端训练任务。你可以在跟随本章节的练习时保留这个链接作为参考:cloud.google.com/sdk/gcloud/reference/ai-platform/jobs/submit/training。由于方法是将训练脚本保存在客户端节点(即安装了 Google Cloud SDK 的本地计算机)中,我们需要在执行 gcloud ai-platform jobs submit training 时,让 Google Cloud 运行时知道所有训练脚本的位置。此外,因为脚本中会导入一些库,我们还需要在名为 setup.py 的文件中指定信息,例如库的版本、名称等。为此,必须在工作目录中创建一个小的 setup.py 文件:
-
在你的工作目录中的命令终端(对于 Mac OS X 或 Linux)中,输入以下内容:
setup.py. In install_requires, you will see a Python list that contains TensorFlow datasets or tensorflow_hub. This is where dependencies are added to the runtime in Google Cloud AI Platform. -
现在我们准备设置用于 Cloud TPU 的分布式训练命令,让我们首先看看命令及执行格式。回想一下,我们之前提到过,这个任务将在客户端运行的 Cloud SDK 中执行。一般来说,客户端节点将使用输入标志发出
gcloud命令,格式如下:gcloud ai-platform jobs submit training cloudtpu \ --staging-bucket=gs://ai-tpu-experiment \ --package-path=python \ --module-name=python.ScriptProject.traincloudtpu_resnet_cache \ --runtime-version=2.2 \ --python-version=3.7 \ --scale-tier=BASIC_TPU \ --region=us-central1 \ -- \ --distribution_strategy=tpu \ --model_dir=gs://ai-tpu-experiment/traincloudtpu_tfkd_resnet_cache \ --train_epochs=10 \ --data_dir=gs://ai-tpu-experiment/tfrecord-flowers
有比这里显示的更多标志(用户输入参数)。有关所有可能的输入参数的详细描述,请参阅 TensorFlow Google Cloud AI 平台参考文档(cloud.google.com/sdk/gcloud/reference/ai-platform/jobs/submit/training)和 Cloud SDK 文档(cloud.google.com/sdk/gcloud/reference/ai-platform)。
云命令参数
示例命令(在本节后面讨论),说明了如图5.11所示的目录结构:

图 5.11 – 本地客户端示例训练运行的目录结构和文件组织
在前述图中的某些文件夹名称是个人选择:vs_code、python、ScriptProject。您可以根据自己的喜好命名这些文件夹。名为traincloudtpu_resnet_cache的训练脚本也是个人选择。
让我们看看这个示例命令。这个示例命令可以基于-- \分为两部分。命令的第一部分包括以下内容:
gcloud ai-platform jobs submit training cloudtpu \
--staging-bucket=gs://ai-tpu-experiment \
--package-path=python \
--module-name=python.ScriptProject.traincloudtpu_resnet_cache \
--runtime-version=2.1 \
--python-version=3.7 \
--scale-tier=BASIC_TPU \
--region=us-central1 \
这个命令在图 5.11中显示的vs_code目录中执行。在这个目录中,您会找到setup.py。这个文件告诉gcloud运行时有关训练脚本所需的依赖项或包。cloudtpu只是我们为这次训练运行提供的一个名称。我们还需要指定一个临时存储桶(云存储桶),用于在训练期间和训练后序列化模型工件。
package-path是组织项目的文件夹。在这种情况下,在此包中,我们有兴趣执行一个训练脚本,traincloudtpu_resnet_cache.py。为了让gcloud运行时找到它,我们需要指定以下内容:
module-name=python.ScriptProject.traincloudtpu_resnet_cache
接下来,我们指定 TensorFlow Enterprise 版本为 2.1,Python 解释器版本为 3.7,这个示例应该足够使用BASIC_TPU规模级别。我们还将区域设置为us-central1。BASIC_TPU规模级别为我们提供了一个主 VM 和一个具有八个 TPU V2 核心的 TPU VM。
正如前面所述,-- \将gcloud系统标志与指定的任何其他用户定义的标志分隔开,并作为训练脚本的输入参数。这种分隔是必要且设计良好的。请勿混合系统标志和用户定义的标志。有关位置参数的详细信息,请参阅 SDK 参考文档(cloud.google.com/sdk/gcloud/reference/ai-platform/jobs/submit/training)。
现在,让我们来看看这个命令的后半部分,其中包含用户定义的标志:
--distribution_strategy=tpu \
--model_dir=gs://ai-tpu-experiment/traincloudtpu_tfkd_resnet_cache \
--train_epochs=10 \--data_dir=gs://ai-tpu-experiment/tfrecord-flowers
我们将 distribution_strategy=tpu 指定为用户定义的标志,因为我们可能会在条件逻辑中使用这个值来选择适当的分发策略。我们还指定了 model_dir,这是一个云存储路径,我们授予 TPU 服务写权限以序列化检查点和模型资源。然后,对于剩余的标志,我们指定了在 train_epochs 中进行训练的时期数,并指定了 data_dir 指示的训练数据路径,这也是一个云存储路径,我们授予 TPU 服务读权限。TPU 的分布式训练策略(www.tensorflow.org/guide/distributed_training#tpustrategy)实现了跨多个核心的所有必要操作。
组织训练脚本
本示例的训练脚本的一般结构采用了极简风格。在实践中,将 Python 代码组织到多个文件和模块中是很常见的。因此,我们将所有需要的内容放在一个 Python 脚本 train.py 中。其伪代码如下:
def run( input parameters ):
# specify distribute strategy (https://cloud.google.com/
ai-platform/training/docs/using-tpus)
import tensorflow as tf
if distribution_strategy==TPU:
resolver = tf.distribute.cluster_resolver. TPUClusterResolver()
tf.config.experimental_connect_to_cluster(resolver)
tf.tpu.experimental.initialize_tpu_system(resolver)
strategy = tf.distribute.experimental.TPUStrategy(resolver)
# build data streaming pipeline with tf.io and tf.data.TFRecordDataset
# build model
# train model
# save results
def main():
run(input parameters)
if __name__ == '__main__'
app.run(main)
一旦运行了 main() 程序,它将调用 run(),在其中定义训练策略,然后构建数据流水线,接着构建和训练模型,最后将结果保存到云存储。
接下来,我们将深入研究 train.py 的实际代码。让我们从数据流水线开始。
数据流水线
当使用 Google Cloud AI Platform 时,目前唯一的数据流方式是通过 tf.io 和 tf.dataTFRecordDataset。
我们的数据集(TFRecord)已经在一个存储桶中。其组织结构如 图 5.12 所示:

图 5.12 – 用于花卉图像分类数据集的云存储
-
在我们训练脚本的
run函数中,我们需要指定训练数据的云存储路径。我们可以利用tf.io.gfile对多个部分的文件名模式进行编码。接着,我们使用tf.data.TFRecordDataset实例化一个数据集对象:root_dir = flags_obj.data_dir # this is gs://<bucket>/folder where tfrecord are stored train_file_pattern = '{}/image_classification_builder-train*.tfrecord*'.format(root_dir) val_file_pattern = '{}/image_classification_builder-validation*.tfrecord*'.format(root_dir) train_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(train_file_pattern)) val_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(val_file_pattern)) train_all_ds = tf.data.TFRecordDataset(train_all_files,num_parallel_reads=tf.data.experimental.AUTOTUNE) val_all_ds = tf.data.TFRecordDataset(val_all_files,num_parallel_reads=tf.data.experimental.AUTOTUNE)正如前面的代码所示,在编码数据集名称模式后,我们使用
tf.data.Dataset.list_files来编码符合模式的所有文件名列表。然后,tf.data.TFRecordDataset实例化一个数据集读取器对象。一旦在运行时执行了这些行,它有效地建立了 TPU 与云存储之间的连接。数据集对象在训练工作流中向模型流送数据。为什么我们不使用
tf.keras生成器模式,比如ImageDataGenerator或flow_from_directory?实际上,这是因为gcloud ai-platform jobs submit training命令目前还不支持这种模式。这个模式在数据挂载或直接存储在文件系统中时非常方便,且通过用户输入的可选参数,能够轻松处理分类问题中的标签一热编码、图像归一化和标准化等过程。 -
我们必须通过编写自己的函数来处理图像标准化(调整为不同的高度和宽度)。以下是一个在
TFRecordDataset上执行这些操作的函数:def decode_and_resize(serialized_example): # resized image should be [224, 224, 3] and have value range [0, 255] # label is integer index of class. parsed_features = tf.io.parse_single_example( serialized_example, features = { 'image/channels' : tf.io.FixedLenFeature([], tf.int64), 'image/class/label' : tf.io.FixedLenFeature([], tf.int64), 'image/class/text' : tf.io.FixedLenFeature([], tf.string), 'image/colorspace' : tf.io.FixedLenFeature([], tf.string), 'image/encoded' : tf.io.FixedLenFeature([], tf.string), 'image/filename' : tf.io.FixedLenFeature([], tf.string), 'image/format' : tf.io.FixedLenFeature([], tf.string), 'image/height' : tf.io.FixedLenFeature([], tf.int64), 'image/width' : tf.io.FixedLenFeature([], tf.int64) }) image = tf.io.decode_jpeg(parsed_features[ 'image/encoded'], channels=3) label = tf.cast(parsed_features[ 'image/class/label'], tf.int32) label_txt = tf.cast(parsed_features[ 'image/class/text'], tf.string) label_one_hot = tf.one_hot(label, depth = 5) resized_image = tf.image.resize(image, [224, 224], method='nearest') return resized_image, label_one_hot这个
decode_and_resize函数解析数据集中的 JPEG 图像,并调整相应的颜色值范围,然后解析标签,对图像进行一热编码,并使用最近邻方法对图像进行调整大小,以将其标准化为 224×224 像素,适应我们选择的模型(ResNet)。该函数还提供了不同的方式返回标签,无论是纯文本还是整数。如果需要,你可以通过将感兴趣的标注添加到return元组中,返回不同格式和风格的标签:return resized_image, label_one_hot, label_txt, label然后,根据返回元组在调用函数中的位置(如前述
return语句所示的顺序)解包返回值。 -
既然我们已经准备好了
decode_and_resize函数,接下来就是如何将它应用到dataset对象中的每个元素:dataset = train_all_ds.map(decode_and_resize) val_dataset = val_all_ds.map(decode_and_resize) -
然后,我们将每张图像的像素值重新缩放或归一化到
[0, 1]范围内,以便所有图像的像素值范围一致,适合进行训练。让我们来创建一个normalize函数:def normalize(image, label): #Convert `image` from [0, 255] -> [0, 1.0] floats image = tf.cast(image, tf.float32) / 255\. + 0.5 return image, label我们需要通过应用批处理操作来准备训练数据。我们使用以下函数来实现这一点:
def prepare_for_training(ds, cache=True, shuffle_buffer_size=1000): if cache: if isinstance(cache, str): ds = ds.cache(cache) else: ds = ds.cache() ds = ds.shuffle(buffer_size=shuffle_buffer_size) ds = ds.repeat() ds = ds.batch(BATCH_SIZE) AUTOTUNE = tf.data.experimental.AUTOTUNE ds = ds.prefetch(buffer_size=AUTOTUNE) return ds上述函数接受一个数据集,然后根据全局变量
BATCH_SIZE对其进行洗牌和批处理,并为训练管道预取数据。我们再次使用
map方法,将normalize操作应用到我们的训练集和验证集:AUTOTUNE = tf.data.experimental.AUTOTUNE BATCH_SIZE = flags_obj.train_batch_size VALIDATION_BATCH_SIZE = flags_obj.validation_batch_size train_dataset = train_dataset.map(normalize, num_parallel_calls=AUTOTUNE) val_dataset = val_dataset.map(normalize, num_parallel_calls=AUTOTUNE) val_ds = val_dataset.batch(VALIDATION_BATCH_SIZE) train_ds = prepare_for_training(train_dataset)这是
run函数的数据管道部分。我们还没有完成run函数。 -
接下来,我们将设置模型并进行训练。我们将利用流行的迁移学习技术,在我们的训练数据集上应用并训练一个预构建的模型。这里感兴趣的预构建模型是 ResNet-50 图像分类模型。记得我们之前已经为训练指定了基于 TPU 的分布式策略吗?我们可以在这里简单地将模型定义和优化器选择与该策略结合:
with strategy.scope(): base_model = tf.keras.applications.ResNet50( input_shape=(224,224,3), include_top=False, weights='imagenet') model = tf.keras.Sequential( [base_model, tf.keras.layers.GlobalAveragePooling2D(), tf.keras.layers.Dense(5, activation='softmax', name = 'custom_class') ]) lr_schedule = \ tf.keras.optimizers.schedules.ExponentialDecay( 0.05, decay_steps=100000, decay_rate=0.96) optimizer = tf.keras.optimizers.SGD( learning_rate=lr_schedule) model.compile(optimizer=optimizer, loss=tf.keras.losses.CategoricalCrossentropy( from_logits=True, label_smoothing=0.1), metrics=['accuracy'])上述代码描述了模型架构,指定了训练的优化策略,并编译了模型。我们使用 ResNet-50 特征向量作为分类五种花卉类型的基础模型。
-
然后,我们通过以下代码来设置检查点和回调函数:
checkpoint_prefix = os.path.join(flags_obj.model_dir, 'ckpt_{epoch}') callbacks = [ tf.keras.callbacks.ModelCheckpoint (filepath=checkpoint_prefix, save_weights_only=True)]回调函数将在每个 epoch 训练过程中分别保存模型的权重和偏置作为检查点。
-
接下来,我们需要在每个 epoch 设置训练和交叉验证的样本大小:
train_sample_size=0 for raw_record in train_all_ds: train_sample_size += 1 print('TRAIN_SAMPLE_SIZE = ', train_sample_size) validation_sample_size=0 for raw_record in val_all_ds: validation_sample_size += 1 print('VALIDATION_SAMPLE_SIZE = ', validation_sample_size) steps_per_epoch = train_sample_size // BATCH_SIZE validation_steps = validation_sample_size // VALIDATION_BATCH_SIZE -
最后,这里是训练过程的代码:
hist = model.fit( train_ds, epochs=flags_obj.train_epochs, steps_per_epoch=steps_per_epoch, validation_data=val_ds, validation_steps=validation_steps, callbacks=callbacks) model_save_dir = os.path.join(flags_obj.model_dir, 'save_model') model.save(model_save_dir)这就是
run函数的结束。这个函数比较长,请确保注意所有正确的缩进标识。这只是 Google Cloud AI Platform 的一个最小示例。它包含了一个可扩展数据管道、分布式训练工作流和 TPU 使用的所有必要代码和模式。在实际应用中,您可以根据需要组织和重构代码,以提高代码的清晰性和可维护性。
提交训练脚本
现在是提交我们训练脚本的时候了。我们根据 图 5.11 中提到的本地目录结构,从 vs_code 目录提交它。Cloud AI Platform 中的 TensorFlow 运行时版本不一定是最新的,跟 Cloud AI Notebook 中的 TensorFlow 稳定版本相比。如我们所知,目前 Cloud Notebook 中的稳定版本是 TFE 2.3\,然而在 Cloud AI Platform 中,最新的版本是 2.2\。因此我们使用 --runtime-version=2.2。
您可以通过以下链接查看最新的运行时版本:cloud.google.com/ai-platform/prediction/docs/runtime-version-list。
以下是命令和终端输出:
vs_code % gcloud ai-platform jobs submit training traincloudtpu_tfk_resnet50 \
--staging-bucket=gs://ai-tpu-experiment \
--package-path=python \
--module-name=python.ScriptProject.trainer \
--runtime-version=2.2 \
--python-version=3.7 \
--scale-tier=BASIC_TPU \
--region=us-central1 \
-- \
--distribution_strategy=tpu \
--model_dir=gs://ai-tpu-experiment/traincloudtpu_tfk_resnet50 \
--train_epochs=10 \
--data_dir=gs://ai-tpu-experiment/tfrecord-flowers
Job [traincloudtpu_tfk_resnet50] submitted successfully.
您的作业仍然处于活动状态。您可以使用以下命令查看作业的状态:
$ gcloud ai-platform jobs describe traincloudtpu_tfk_resnet50
或者,您可以使用以下命令继续流式传输日志:
$ gcloud ai-platform jobs stream-logs traincloudtpu_tfk_resnet50
jobId: traincloudtpu_tfk_resnet50
state: QUEUED
前面的命令将一段训练代码提交到 Cloud TPU。从当前目录(vs_code)来看,有一个子文件夹(python),其中包含一个 ScriptProject 模块。在 ScriptProject 中,有一部分脚本名为 trainer.py。您可以在 GitHub 仓库中查看 trainer.py 的内容,链接为:github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_05/cnn_on_tpu/custom_model_on_tpu/trainer.py。
我们还必须指定以下参数,这些参数在 trainer.py 中使用(github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_05/cnn_on_tpu/custom_model_on_tpu/trainer.py):
Job name: traincloudtpu_tfk_resnet50
Staging bucket is gs://ai-tpu-experiment
Bucket to save the model is gs://ai-tpu-experiment/traincloudtpu_tfk_resnet50
Training data is in gs://tfrecord-dataset/flowers
一旦我们提交前面的命令,它将进入您的 Cloud AI Platform 实例队列中等待执行。要查看我们可以在哪里监控训练过程,可以运行gcloud ai-platform jobs describe traincloudtpu_tfk_resnet50 来获取正在运行日志的 URL:
vs_code % gcloud ai-platform jobs describe traincloudtpu_tfk_resnet50
createTime: ‚2020-08-09T20:59:16Z'
etag: QMhh5Jz_KMU=
jobId: traincloudtpu_tfk_resnet50
state: PREPARING
trainingInput:
args:
- --distribution_strategy=tpu
- --model_dir=gs://ai-tpu-experiment/traincloudtpu_tfk_resnet50
- --train_epochs=10
- --data_dir=gs://ai-tpu-experiment/tfrecord-flowers
packageUris:
- gs://ai-tpu-experiment/traincloudtpu_tfk_resnet50/XXXXXX/official-0.0.0.tar.gz
pythonModule: python.ScriptProject.trainer
pythonVersion: '3.7'
region: us-central1
runtimeVersion: '2.2'
scaleTier: BASIC_TPU
trainingOutput: {}
你可以在 Cloud Console 中查看作业 console.cloud.google.com/mlengine/jobs/traincloudtpu_tfk_resnet50?project=project1-190517。
根据前面的代码和高亮输出,我们可以在浏览器中访问日志 URL,并观察训练进度。以下是一些示例摘录(参见 图 5.13 和 图 5.14):

图 5.13 – Google Cloud AI Platform TPU 训练日志示例摘录 1
这是一个较长的日志,直到训练作业完成才会停止。接近训练结束时,日志看起来像这样:

图 5.14 – Google Cloud AI Platform TPU 训练日志示例摘录 2
在存储桶中,我们看到由训练工作流创建的文件夹(图 5.15):

图 5.15 – TPU 训练工作流创建的文件夹
在这个存储桶里,我们看到了检查点和模型资产(图 5.16):

图 5.16 – TPU 完成训练工作流后的模型检查点和资产
它与在本地独立机器上训练时完全相同。完整的 trainer.py 文件可在 github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_05/cnn_on_tpu/custom_model_on_tpu/trainer.py 获取。
接下来,我们将查看如何重用我们在这里学到的内容。事实证明,如果我们想要使用 TensorFlow Hub 中可用的模型,我们可以重用训练模式、文件组织和工作流。但是,稍微需要进行一些调整。这是因为目前 TPU 无法直接访问 TensorFlow Hub 的模块 URL。
在 TensorFlow Hub 中使用模型
TensorFlow Hub 托管了大量预训练模型。然而,要使用这些模型,用户或客户端代码必须能够连接到 Hub 并通过 RESTful API 下载模型到客户端的 TensorFlow 运行时。目前,TPU 无法直接进行此操作。因此,我们必须先从 TensorFlow Hub 下载我们感兴趣的模型到本地计算机,然后将其上传到云存储,以便 TPU 可以访问。通常情况下,以下是使用 tf.keras API 从 TensorFlow Hub 实现预训练模型的步骤:
m = tf.keras.Sequential([
hub.KerasLayer('https://tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4', trainable=False),
tf.keras.layers.Dense(num_classes, activation='softmax')
])
m.build([None, 224, 224, 3]) # Batch input shape.
如前述代码所示,预训练模型的 URL 会传递给 KerasLayer。然而,目前,运行在 Cloud AI Platform 上的 TPU 无法直接访问 TensorFlow Hub 的 URL。为了下载模型,请按照 TensorFlow Hub 网站上的简单说明操作,如图 5.17所示:

图 5.17 – 从 TensorFlow Hub 下载预训练模型
模型已被压缩。解压后,您将看到如图 5.18所示的内容:

图 5.18 – 从 TensorFlow Hub 下载的预训练模型
下载并解压模型后,让我们将其上传到 TPU 服务帐户可以访问的存储桶,如图 5.19所示:

图 5.19 – 将预训练模型上传至云存储
请注意,我们在存储桶中创建了一个model-cache-dir文件夹,然后选择了上传文件夹,现在该模型文件夹已可供 TPU 使用。
然后,在 run 函数内部,我们需要利用环境变量来告诉 TPU 在哪里找到这个模型:
os.environ['TFHUB_CACHE_DIR'] = 'gs://ai-tpu-experiment/model-cache-dir/imagenet_resnet_v2_50_feature_vector_4'
这行代码可以插入到 run 函数中的模型定义之前。在模型定义中,我们将像往常一样使用 hub.KerasLayer 来指定模型架构:
with strategy.scope():
model = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)),
hub.KerasLayer('https://tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4',
trainable=flags_obj.fine_tuning_choice),
tf.keras.layers.Dense(5, activation='softmax',
name = 'custom_class')
])
由于我们已经在环境变量中定义了TFHUB_CACHE_DIR,并指定了云存储名称和路径,因此当 TPU 执行模型架构代码中的hub.KerasLayer部分时,TPU 运行时会首先从TFHUB_CACHE_DIR查找模型,而不是尝试通过 RESTful API 调用来获取模型。对训练脚本进行这些小的修改后,我们可以将其重命名为trainer_hub.py。训练工作可以通过类似的调用方式启动:
vs_code % gcloud ai-platform jobs submit training traincloudtpu_tfhub_resnet50 \
--staging-bucket=gs://ai-tpu-experiment \
--package-path=python \
--module-name=python.ScriptProject.trainer_hub \
--runtime-version=2.2 \
--python-version=3.7 \
--scale-tier=BASIC_TPU \
--region=us-central1 \
-- \
--distribution_strategy=tpu \
--model_dir=gs://ai-tpu-experiment/traincloudtpu_tfhub_resnet50 \
--train_epochs=10 \
--data_dir=gs://ai-tpu-experiment/tfrecord-flowers
Job [traincloudtpu_tfhub_resnet50] submitted successfully.
您的任务仍在进行中。您可以使用以下命令查看任务的状态
$ gcloud ai-platform jobs describe traincloudtpu_tfhub_resnet50
或继续使用命令流式传输日志
$ gcloud ai-platform jobs stream-logs traincloudtpu_tfhub_resnet50
jobId: traincloudtpu_tfhub_resnet50
state: QUEUED
完整的 trainer_hub.py 代码可在 github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_05/tfhub_on_tpu/tfhub_resnet_fv_on_tpu/trainer_hub.py 查阅。
接下来,我们将看看如何使用gcloud ai-platform命令利用 GPU 进行类似的训练工作。
通过 AI Platform 使用 Google Cloud GPU
在完成了上一节中关于使用 AI Platform 的 Cloud TPU 的部分后,我们准备好使用 GPU 进行同样的操作。事实证明,训练脚本和调用命令的格式非常相似。除了增加一些参数和在分布式策略定义上有些许差异外,其他一切都保持不变。
目前有几种分布式策略(www.tensorflow.org/guide/distributed_training#types_of_strategies)可供选择。对于 Google AI Platform 中的 TensorFlow Enterprise 版本,MirroredStrategy和TPUStrategy是唯一完全支持的策略。其他策略都处于实验阶段。因此,在本节的示例中,我们将使用MirroredStrategy。该策略在每个 GPU 上创建模型中所有变量的副本。由于这些变量在每次梯度下降步骤时都会更新,值的更新会同步复制到每个 GPU。默认情况下,该策略使用NVIDIA NCCL的全归约实现。现在,我们将从以下步骤开始:
-
我们可以从对上一节使用的 TPU 训练脚本进行小的修改开始。让我们实现一个条件,根据选择 TPU 或 GPU 来选择分布式策略:
if flags_obj.distribution_strategy == 'tpu': resolver = tf.distribute.cluster_resolver.TPUClusterResolver() tf.config.experimental_connect_to_cluster(resolver) tf.tpu.experimental.initialize_tpu_system(resolver) strategy = tf.distribute.experimental.TPUStrategy(resolver) strategy_scope = strategy.scope() print('All devices: ', tf.config.list_logical_devices('TPU')) elif flags_obj.distribution_strategy == 'gpu': devices = ['device:GPU:%d' % i for i in range(flags_obj.num_gpus)] strategy = tf.distribute.MirroredStrategy(device=devices) strategy_scope = strategy.scope()使用
MirroredStrategy与 GPU 一起使用的训练脚本的完整实现可以在github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_05/tfhub_on_gpu/tfhub_resnet_fv_on_gpu/trainer_hub_gpu.py找到。对于
MirroredStrategy,我们将scale-tier设置为BASIC_GPU。这将为我们提供一个包含一块 NVIDIA Tesla K80 GPU 的单个工作实例。使用trainer_hub_gpu.py启动训练的命令如下:vs_code % gcloud ai-platform jobs submit training traincloudgpu_tfhub_resnet_gpu_1 \ --staging-bucket=gs://ai-tpu-experiment \ --package-path=python \ --module-name=python.ScriptProject.trainer_hub \ --runtime-version=2.2 \ --python-version=3.7 \ --scale-tier=BASIC_GPU \ --region=us-central1 \ -- \ --distribution_strategy=gpu \ --model_dir=gs://ai-tpu-experiment/traincloudgpu_tfhub_resnet_gpu_1 \ --train_epochs=10 \ --data_dir=gs://ai-tpu-experiment/tfrecord-flowers Job [traincloudtpu_tfhub_resnet_gpu_1] submitted successfully.
您的工作仍在进行中。您可以使用命令查看工作状态
$ gcloud ai-platform jobs describe traincloudgpu_tfhub_resnet_gpu_1
或者使用命令继续流式传输日志
$ gcloud ai-platform jobs stream-logs traincloudtpu_tfhub_resnet_gpu_1
jobId: traincloudgpu_tfhub_resnet_gpu_1
state: QUEUED
注意到我们将scale-tier更改为BASIC_GPU。我们将特定于脚本的distribution_strategy标志设置为gpu。这就是我们指定所需计算实例和分发策略的方式。
总结
从我们在本章中涵盖的所有示例中,我们学会了如何通过 AI Platform 利用 TPU 和 GPU 的分布式训练策略,该平台运行在 TensorFlow Enterprise 2.2 版本上。AI Platform 是一个封装 TPU 或 GPU 加速硬件的服务,并管理您的训练作业的配置和设置。
目前,在 Google AI Platform 中,数据摄取管道依赖于TFRecordDataset将训练数据批量流式传输到模型训练工作流中。我们还学习了如何通过使用TFHUB_CACHE_DIR环境变量来利用从 TensorFlow Hub 下载的预构建模型。这也是将自己保存的模型从离线环境导入到 Google AI Platform 的方式。总体来说,在该平台上,我们使用 TensorFlow Enterprise 2.2 发行版来实现可扩展的数据流和在 Google Cloud 的 TPU 或 GPU 上进行分布式训练,并将所有模型检查点和资产序列化并回传至云存储。
在下一章,我们将使用 Cloud AI Platform 提交一个超参数调优作业。超参数调优通常非常耗时且资源密集。我们将看到如何利用 Cloud TPU 的计算能力来加速这个过程。
第六章:
第七章:超参数调优
本章我们将首先介绍三种不同的超参数调优算法——Hyperband、贝叶斯优化和随机搜索。这些算法已在tf.keras API 中实现,使它们相对容易理解。通过这个 API,你现在可以使用简化的接口来调用这些我们在本章中会遇到的复杂和先进的算法。我们将学习如何实现这些算法,并使用我们能找到的最佳超参数来构建和训练图像分类模型。我们还将学习其学习过程的细节,以便知道需要搜索和优化哪些超参数。我们将从获取和准备数据开始,然后将算法应用于数据。在此过程中,我们还将尝试理解关键原理和逻辑,以便将用户选择的这些算法作为输入,同时我们将学习如何在 GCP Cloud TPU 中提交调优和训练作业的模板。
本章我们将涵盖以下主题:
-
划分超参数类型
-
理解 Keras Tuner 的语法和使用方法
-
划分超参数搜索算法
-
在本地环境中提交调优作业
-
在 Google 的 AI 平台上提交调优作业
技术要求
本章的完整代码库可以在以下 GitHub 仓库中找到,请克隆到你的环境中:
github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_06/
这可以通过命令行环境来完成:
git clone https://github.com/PacktPublishing/learn-tensorflow-enterprise.git
划分超参数类型
在我们开发模型及其训练过程时,我们定义变量并设置其值,以决定训练流程和模型结构。这些值(例如多层感知器中某层的隐藏节点数,或者优化器和损失函数的选择)被称为超参数。这些参数由模型创建者指定。机器学习模型的性能通常依赖于模型架构和在训练过程中选择的超参数。为模型找到一组最优的超参数并非易事。完成这一任务的最简单方法是网格搜索,也就是在搜索空间内构建所有可能的超参数值组合,并比较这些组合的评估指标。虽然这种方法直接且全面,但它是一个繁琐的过程。我们将看到新版本的tf.keras API 是如何实现三种不同的搜索算法的。
在模型训练的背景下,有两种类型的超参数:
-
模型超参数:这些参数与模型层的结构直接相关,比如层中节点的数量。
-
算法超参数:这些参数是执行学习算法所必需的,例如在梯度下降过程中使用的损失函数中的学习率,或者损失函数的选择。
如果你想使用网格搜索技术,扫描两种类型的超参数所需的代码修改将非常复杂。在这种情况下,一个更高效、更全面的框架对于超参数调优非常有帮助。
在本章中,我们将看到 TensorFlow 生态系统中最新的超参数调优框架——Keras Tuner。顾名思义,它是为使用 TensorFlow 2.x Keras API 开发的模型而设计的。此框架的最低要求是 TensorFlow 2.0+ 和 Python 3.6。它作为 TensorFlow 2.3 发行版的一部分发布。如果你还没有使用 TensorFlow 2.3,那么只要你使用的是 TensorFlow 2.x,Keras Tuner 就可以通过以下命令安装:
pip install keras-tuner
一旦安装了 Keras Tuner,你可以在你的 Python 代码中加载它。
注意
在导入 keras-tuner 时,你不需要在其中加上破折号 -。它将这样导入:import kerastuner as kt。
Keras Tuner 是一个可分发的超参数优化框架,有助于定义超参数集合的搜索空间。它还包括以下三种内置算法,帮助找到最佳超参数:
-
Hyperband
-
贝叶斯优化
-
随机搜索
对于 Keras Tuner,无论是模型超参数还是算法超参数,这些超参数的语法或定义方式没有区别。因此,你在选择调优哪些超参数时具有极大的灵活性,无需复杂的编码模式或循环。
Keras Tuner 框架使我们可以轻松修改训练脚本。虽然涉及到一些更改和重构,但 API 格式和逻辑流程与 Keras 风格和实现保持高度一致。在进入示例之前,我们先花一些时间了解这个框架,并看看如何扩展我们的训练代码来适应它。
理解 Keras Tuner 的语法和用法
就 Keras Tuner 而言,超参数通常可以通过以下三种数据类型来描述:整数、浮动点和从离散值或对象列表中选择的选项。在接下来的子章节中,我们将更详细地了解如何在模型架构和训练工作流的不同部分中使用这些数据类型定义超参数。
使用 hp.Int 定义超参数
Keras Tuner 以非常简单直观的方式定义了一个搜索空间。要定义给定层中可能的节点数量,通常你会像这样定义一个层:
tf.keras.layers.Dense(units = hp_units, activation = 'relu')
在前面的代码行中,hp_units 是该层的节点数量。如果您希望将 hp_units 作为超参数进行搜索,那么您只需定义该超参数搜索空间的定义。下面是一个示例:
hp = kt.HyperParameters()
hp_units = hp.Int('units', min_value = 64, max_value = 256, step = 16)
hp 是表示 kerastuner 实例的对象。
这仅仅是一个在 64 和 256 之间以 16 为增量的整数数组。当应用于 Dense 层时,它变成了 hp_units 的搜索空间中的可能值数组。
使用 hp.Choice 进行超参数定义
如果您有一组预定值,并且这些值不是增量的,您可以像下面的代码行一样指定一个值列表:
hp_units = hp.Choice('units', values = [64, 80, 90])
hp_Choice 是一种灵活的超参数类型。它也可以用来定义算法的超参数,如激活函数。所需的只是可能激活函数的名称。不同激活函数的搜索空间可能如下所示:
hp_activation = hp.Choice('dense_activation', values=['relu', 'tanh', 'sigmoid'])
然后,使用该超参数的层的定义将是:
tf.keras.layers.Dense(units = hp_units, activation = hp_activation)
hp.Choice 可能应用的另一个地方是当您想尝试不同的优化器时:
hp_optimizer = hp.Choice('selected_optimizer', ['sgd', 'adam'])
然后,在模型编译步骤中,指定优化器时,您只需将 optimizer 定义为 hp_optimizer:
model.compile(optimizer = hp_optimizer, loss = …, metrics = …)
在前面的示例中,我们将 hp_optimizer 传递到模型编译步骤中,作为我们在训练过程中使用的优化器的选择。
使用 hp.Float 进行超参数定义
浮动点数通常出现在训练工作流中的参数,例如优化器的学习率。这里是一个示例,展示了如何在优化器的学习率作为超参数的情况下进行定义:
hp_learning_rate = hp.Float('learning_rate', min_value = 1e-4, max_value = 1e-2, step = 1e-3)
optimizer=tf.keras.optimizers.SGD(lr=hp_learning_rate, momentum=0.5)
在前面的代码中,我们为优化器的学习率定义了一个搜索空间。然后,我们将 hp_learning_rate 对象传递到优化器定义中。
作为示例,我创建了一个 model_builder 函数。此函数接受定义超参数搜索空间的 hp 对象,然后将 hp 对象 传递到模型架构中。该函数返回完成的模型。下面是 model_builder 函数:
def model_builder(hp):
hp_units = hp.Int('units', min_value = 64, max_value = 256,
step = 64)
hp_activation = hp.Choice('dense_activation',
values=['relu', 'tanh', 'sigmoid'])
IMAGE_SIZE = (224, 224)
model = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)),
hub.KerasLayer('https://tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4', trainable=False),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(units = hp_units,
activation = hp_activation,
kernel_initializer='glorot_uniform'),
tf.keras.layers.Dense(5, activation='softmax',
name = 'custom_class')
])
model.build([None, 224, 224, 3])
model.compile(
optimizer=tf.keras.optimizers.SGD(lr=1e-2,
momentum=0.5),
loss=tf.keras.losses.CategoricalCrossentropy(
from_logits=True, label_smoothing=0.1),
metrics=['accuracy'])
return model
使用 Keras Tuner API,搜索空间格式和在模型层或训练算法中引用搜索空间的方式非常直观,并且提供了很大的灵活性。所做的只是定义一个搜索空间,然后将包含搜索空间的对象传递到模型定义中。如果按照网格搜索方法处理条件逻辑,将会是一项艰巨的任务。
接下来,我们将看看如何使用 Keras Tuner 类来指定以下三种不同的搜索算法:
-
Hyperband
-
贝叶斯优化
-
随机搜索
描述超参数搜索算法
在这一部分,我们将更深入地了解三种遍历超参数搜索空间的算法。这些算法是通过 tf.keras API 实现的。
Hyperband
超参数搜索本身是一个繁琐的过程,需要一个预算 B 来测试有限数量的超参数配置 n。在这个过程中,预算只是表示计算时间(由 epoch 和训练数据子集大小来指示)。Hyperband 算法利用早停和连续减半的策略,使其能够在给定的时间和硬件资源下评估更多的超参数配置。早停有助于在投入过多训练时间之前,剔除那些性能差的配置。
连续减半方法非常直观:对于一组超参数配置,将它们通过相同的预算(即:epoch、内存和训练数据子集大小)运行。然后我们对这些配置的性能进行排名,丢弃性能最差的一半配置。这个过程会一直重复,直到只剩下一个配置。这类似于淘汰赛制,在每一轮比赛中,半数配置被淘汰,直到只剩下一个。
Hyperband 算法中有两个 for 循环:
-
内部循环执行连续减半,丢弃一部分超参数配置,从而减少搜索空间。
-
外部循环,遍历不同的
B和n的组合。
在早期迭代中,有许多候选配置。每个候选配置都会分配一定的预算 B 来进行训练,早停策略确保在浪费过多训练时间之前,提前丢弃这些配置中的一半(即,性能最差的一半)。随着连续减半,比赛轮次逐渐减少,剩下的候选配置越来越少,因此每个候选配置获得更多预算 B 的可能性更大。这个过程会一直持续,直到最后一个超参数配置剩下。因此,你可以将 Hyperband 算法看作是一种通过尽早丢弃低性能配置来选择最佳超参数配置的方法。
以下是 Hyperband 算法的更多信息参考:openreview.net/pdf?id=ry18Ww5ee。
现在让我们来看一下如何定义一个使用 Hyperband 算法的调优器实例(有关 API 及其参数的详细说明,请参见 keras-team.github.io/keras-tuner/documentation/tuners/)。下面是一个示例:
import kerastuner as kt
import tensorflow_hub as hub
import tensorflow as tf
from absl import flags
flags_obj = flags.FLAGS
strategy = tf.distribute.MirroredStrategy()
tuner = kt.Hyperband(
hypermodel = model_builder,
objective = 'val_accuracy',
max_epochs = 3,
factor = 2,
distribution_strategy=strategy,
directory = flags_obj.model_dir,
project_name = 'hp_tune_hb',
overwrite = True)
以下是参数描述:
-
hypermodel:构建模型架构的类函数。 -
objective:评估的性能指标。 -
max_epoch:训练模型的最大 epoch 数。 -
factor:每轮比赛中 epoch 数和模型数量的减少比例。它选择排名在所有配置中前 1/factor的配置。factor值越大,剪枝越多,因此搜索过程越快,可以识别出最佳表现的配置。 -
distribution_strategy:如果硬件可用于分布式训练,则使用此选项。 -
directory:目标目录或路径,用于保存搜索结果。 -
project_name:用于作为调优器保存文件的前缀名称。 -
overwrite:这是一个布尔值。如果为True,则超参数搜索将从头开始。让我们稍微详细解释一下这个 API。在此情况下,kt是调优器对象。在 Hyperband 定义中,hypermodel指定了构建模型架构的函数。
在这个示例中,我们将定义模型架构中间 Dense 层节点数(hp_units)的搜索空间,以及该层激活函数(hp_activation)的搜索空间。在这些定义之后,我们构建模型架构,将这些 hp 对象传入目标层,编译模型并返回模型。
注意函数签名中的 hp。它表示这是模型结构定义的入口函数,其中指定了超参数。在此示例中,有两个超参数:
hp_units = hp.Int('units', min_value = 64, max_value = 256, step = 64)
hp_activation = hp.Choice('dense_activation', values=['relu', 'tanh', 'sigmoid'])
在模型的顺序 API 定义中,您将会在某个 Dense 层中找到这些超参数:
tf.keras.layers.Dense(units = hp_units, activation = hp_activation, kernel_initializer='glorot_uniform'),
在退出此函数之前,您需要编译模型并将模型返回给调优器实例。现在让我们开始 Hyperband 超参数的训练:
-
现在已经定义了调优器和其搜索算法,以下是您如何设置搜索的方法:
tuner.search(train_ds, steps_per_epoch=STEPS_PER_EPOCHS, validation_data=val_ds, validation_steps=VALIDATION_STEPS, epochs=30, callbacks=[tf.keras.callbacks.EarlyStopping( 'val_accuracy')])在此示例中,
train_ds是训练数据集,而val_ds是交叉验证数据集。其余参数与典型的训练流程相同。 -
搜索完成后,您可以通过一个对象检索最佳的超参数配置:
best_hps = tuner.get_best_hyperparameters(num_trials = 1)[0] print(f''' The hyperparameter search is done. The best number of nodes in the dense layer is {best_hps.get('units')}. The best activation function in mid dense layer is {best_hps.get('dense_activation')}. ''')默认情况下,
num_trials = 1表示将返回最佳模型。由于这是一个列表对象,我们通过列表的第一个索引(即0)来检索它。print语句展示了如何引用best_hps中的项目。 -
建议在获得
best_hps后,您应使用这些参数重新训练您的模型。我们将从使用best_hps初始化的tuner对象开始:model = tuner.hypermodel.build(best_hps) -
然后我们可以为正式训练定义检查点和回调函数:
checkpoint_prefix = os.path.join(flags_obj.model_dir, 'best_hp_train_ckpt_{epoch}') callbacks = [ tf.keras.callbacks.ModelCheckpoint( filepath=checkpoint_prefix, save_weights_only=True)] -
现在让我们调用
fit函数,开始使用最佳超参数配置进行训练:model.fit( train_ds, epochs=30, steps_per_epoch=STEPS_PER_EPOCHS, validation_data=val_ds, validation_steps=VALIDATION_STEPS, callbacks=callbacks) -
一旦训练完成,保存训练好的模型:
model_save_dir = os.path.join(flags_obj.model_dir, 'best_save_model') model.save(model_save_dir)现在,通过 Hyperband 超参数搜索训练得到的模型已保存在
model_save_dir指定的文件路径中。接下来,我们将介绍另一种超参数搜索算法:贝叶斯优化。
贝叶斯优化
该方法利用从初始训练样本中学到的知识,并推动超参数值朝着搜索空间的有利方向变化。实际上,从初始训练样本中学到的是一个概率函数,它模拟了我们目标函数的值。这个概率函数,也叫做代理函数,以高斯过程的形式模拟了我们目标(即验证损失)的分布。有了代理函数,接下来的超参数配置候选值将被选择,使其最有可能改善(即最小化,如果目标是验证损失)代理函数。
tuner实例以直接的方式调用该算法。以下是一个示例:
tuner = kt.BayesianOptimization(
hypermodel = model_builder,
objective ='val_accuracy',
max_trials = 50,
directory = flags_obj.model_dir,
project_name = 'hp_tune_bo',
overwrite = True
)
这一行代码定义了一个tuner对象,我设置它使用贝叶斯优化算法作为超参数优化的方法。与 Hyperband 类似,它需要一个hypermodel的函数定义。在这种情况下,再次使用 Hyperband 中的model_builder。优化标准是验证准确度。最大试验次数设置为50,我们将在作业提交过程中指定保存模型的目录。model_dir的用户输入通过flags_obj.model_dir传递。
如BayesianOptimization API 所示,与 Hyperband 相比,函数签名没有太大差异。max_trials是要尝试的最大超参数配置数。如果搜索空间耗尽,此值可能会被预先占用或忽略。
下一步与在Hyperband部分中看到的启动搜索过程相同:
tuner.search(train_ds,
steps_per_epoch=STEPS_PER_EPOCHS,
validation_data=val_ds,
validation_steps=VALIDATION_STEPS,
epochs=30,
callbacks=[tf.keras.callbacks.EarlyStopping(
'val_accuracy')])
其余部分,例如检索最佳超参数配置并使用此配置训练模型,与Hyperband部分完全相同。
随机搜索
随机搜索简单来说就是在超参数配置搜索空间中随机选择。以下是一个示例定义:
tuner = kt.RandomSearch(
hypermodel = model_builder,
objective='val_accuracy',
max_trials = 5,
directory = flags_obj.model_dir,
project_name = 'hp_tune_rs',
overwrite = True)
在前面的代码中的RandomSearch API 中,我们将model_builder函数定义为hypermodel。该函数包含我们的超参数对象,这些对象保存超参数名称和搜索空间的定义。hypermodel指定了我们的函数名称,该函数将接受搜索找到的最佳超参数,并使用这些值构建模型。我们的目标是找到最大化验证准确度的最佳超参数集合,并将max_trials设置为5。保存模型的目录由用户输入提供。model_dir的用户输入由flags_obj.model_dir对象捕获。
关于directory的简短说明
directory参数在所有三种算法类型中都是必需的。它是搜索结果存储的目标。这个参数接受一个文本字符串,使用非常灵活。这个文本字符串可以表示输入标志传递的文本(例如flags_obj.model_dir),当代码以脚本形式运行时。或者,如果你使用的是笔记本环境,这个字符串可能是一个文件路径或云存储桶路径。
在本地环境中提交调优任务
由于超参数调优过程本质上是耗时的,最好从脚本中运行它,而不是在笔记本环境中运行。此外,虽然在某种意义上,超参数调优过程包括多个模型训练任务,但调优 API 和搜索工作流要求特定的代码重构风格。最明显的一点是,我们必须将模型结构包装成一个函数(在我们的示例中,命名为model_builder),其函数签名表明预计会在模型结构中引用超参数数组。
你可以在 GitHub 仓库中找到代码和说明:github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_06/localtuningwork
在以下代码的帮助下,我们将设置用户输入或标志,并在必要时为这些标志分配默认值。我们来快速回顾一下如何在 Python script.absl库中处理和定义用户输入,以及常用的 API 来处理用户输入:
-
导入
absl库和相关的 API:from absl import flags from absl import logging from absl import app -
接下来,我们将使用以下代码行来指示用户输入或标志:
tf.compat.v1.flags.DEFINE_string('model_dir', 'default_model_dir', 'Directory or bucket for storing checkpoint model.') tf.compat.v1.flags.DEFINE_bool('fine_tuning_choice', False, 'Retrain base parameters') tf.compat.v1.flags.DEFINE_integer('train_batch_size', 32, 'Number of samples in a training batch') tf.compat.v1.flags.DEFINE_integer('validation_batch_size', 40, 'Number of samples in a validation batch') tf.compat.v1.flags.DEFINE_float('learning_rate', 0.01, 'Initial learning rate.') tf.compat.v1.flags.DEFINE_string('tuner_type', 'Hyperband', 'Type of tuner. Default is hyperband')第一个参数是输入标志的名称,接着是其默认值,然后是说明。前面的示例展示了常用的类型转换:
string、Boolean、integer和float。 -
在代码中,我们如何引用并使用这些标志呢?原来我们需要在使用输入标志的函数中使用一个
flags.FLAGS对象。这个函数可以是main()或任何其他函数。为了方便和提高可读性,通常我们会将这个对象赋值给一个变量:flags_obj = flags.FLAGS -
现在,为了引用
model_dir,我们只需要执行以下操作:flags_obj.model_dir这实际上将
model_dir属性解码为一个文本字符串。 -
现在让我们看看一个示例脚本。我们将从
import语句开始,将所有需要的库引入到作用域中:import kerastuner as kt import tensorflow as tf import tensorflow_hub as hub import tensorflow_datasets as tfds import os import IPython from kerastuner import HyperParameters from absl import flags from absl import logging from absl import app -
定义用户输入参数的名称、默认值和简短说明:
tf.compat.v1.flags.DEFINE_string('model_dir', 'default_model_dir', 'Directory or bucket for storing checkpoint model.') tf.compat.v1.flags.DEFINE_bool('fine_tuning_choice', False, 'Retrain base parameters') tf.compat.v1.flags.DEFINE_integer('train_batch_size', 32, 'Number of samples in a training batch') tf.compat.v1.flags.DEFINE_integer('validation_batch_size', 40, 'Number of samples in a validation batch') -
定义一个加载工作数据的函数。在这个例子中,为了方便,我们将直接从 TensorFlow 加载数据:
def get_builtin_data(): data_dir = tf.keras.utils.get_file( 'flower_photos', 'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz', untar=True) return data_dir这个函数调用
tf.kerasAPI 来获取 TensorFlow 自带的图像数据。它托管在 Google 的公共存储中。由于它是压缩格式的,所以我们需要将untar设置为True。 -
我们还创建了一个名为
make_generators的函数。这是一个我们将用来制作数据生成器并将图像数据流入模型训练过程的函数:def make_generators(data_dir, flags_obj): BATCH_SIZE = flags_obj.train_batch_size IMAGE_SIZE = (224, 224) datagen_kwargs = dict(rescale=1./255, validation_split=.20) dataflow_kwargs = dict(target_size=IMAGE_SIZE, batch_size=BATCH_SIZE, interpolation='bilinear') valid_datagen = tf.keras.preprocessing.image.ImageDataGenerator( **datagen_kwargs) valid_generator = valid_datagen.flow_from_directory( data_dir, subset='validation', shuffle=False, **dataflow_kwargs)上述代码中的函数接受数据路径和用户输入。
train_batch_size是用户输入之一。这个值用于定义该函数中的BATCH_SIZE。首先创建验证生成器。我们可能有不同的训练数据偏好,比如数据增强的选项。 -
继续进行
make_generators函数。在这个例子中,默认情况下我们不会对训练数据进行数据增强。在该函数的最后,train_generator与valid_generator一起返回:do_data_augmentation = False if do_data_augmentation: train_datagen = tf.keras.preprocessing.image.ImageDataGenerator( rotation_range=40, horizontal_flip=True, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, **datagen_kwargs) else: train_datagen = valid_datagen train_generator = train_datagen.flow_from_directory( data_dir, subset='training', shuffle=True, **dataflow_kwargs) return train_generator, valid_generator该函数将创建两个生成器:一个用于训练数据,另一个用于交叉验证数据。有关更多详情,请参见第四章,可重用模型和可扩展数据管道,以及在大规模中创建图像数据生成器部分。
-
接下来,我们定义一个函数来检索索引到标签的映射。由于模型输出的是预测,预测的形式是
0到4之间的整数。每个整数对应花卉类别的名称:def map_labels(train_generator): labels_idx = (train_generator.class_indices) idx_labels = dict((v,k) for k,v in labels_idx.items()) return idx_labels上述代码中的函数遍历花卉类型索引和相应的花卉类型名称,并创建一个字典作为查找表。现在让我们继续构建模型架构。
-
以下函数构建了模型架构,如Hyperband部分所述:
def model_builder(hp): os.environ['TFHUB_CACHE_DIR'] = '/Users/XXXXX/Downloads/imagenet_resnet_v2_50_feature_vector_4' hp_units = hp.Int('units', min_value = 64, max_value = 256, step = 64) IMAGE_SIZE = (224, 224) model = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)), hub.KerasLayer('https://tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4', trainable=False), tf.keras.layers.Flatten(), tf.keras.layers.Dense(units = hp_units, activation = 'relu', kernel_initializer='glorot_uniform'), tf.keras.layers.Dense(5, activation='softmax', name = 'custom_class') ]) model.build([None, 224, 224, 3]) hp_learning_rate = hp.Choice('learning_rate', values = [1e-2, 1e-4]) model.compile( optimizer=tf.keras.optimizers.SGD( lr=hp_learning_rate, momentum=0.5), loss=tf.keras.losses.CategoricalCrossentropy( from_logits=True, label_smoothing=0.1), metrics=['accuracy']) return model -
定义一个对象来清除屏幕,以便在超参数搜索在搜索空间中移动时:
class ClearTrainingOutput(tf.keras.callbacks.Callback): def on_train_end(*args, **kwargs): IPython.display.clear_output(wait = True)这将有助于在搜索过程中清除一些打印输出。它被传递到回调中。
-
这是训练脚本的主要驱动程序:
def main(_): flags_obj = flags.FLAGS strategy = tf.distribute.MirroredStrategy() data_dir = get_builtin_data() train_gtr, validation_gtr = make_generators(data_dir, flags_obj) idx_labels = map_labels(train_gtr)在上述代码中,我们设置了分布式训练策略,定义了数据源,并创建了训练和验证数据生成器。同时,获取了标签映射。
在下面的条件代码逻辑块中,我们处理超参数搜索算法的选择。所有三种选择都已列出:贝叶斯优化、随机搜索和 Hyperband。默认选择是 Hyperband。在每个选择中,都有一个hypermodel属性。该属性指定将采用最佳超参数来构建模型的函数名称:
'''Runs the hyperparameter search.'''
if(flags_obj.tuner_type.lower() == 'BayesianOptimization'.lower()):
tuner = kt.BayesianOptimization(
hypermodel = model_builder,
objective ='val_accuracy',
tune_new_entries = True,
allow_new_entries = True,
max_trials = 5,
directory = flags_obj.model_dir,
project_name = 'hp_tune_bo',
overwrite = True
)
elif (flags_obj.tuner_type.lower() == 'RandomSearch'.lower()):
tuner = kt.RandomSearch(
hypermodel = model_builder,
objective='val_accuracy',
tune_new_entries = True,
allow_new_entries = True,
max_trials = 5,
directory = flags_obj.model_dir,
project_name = 'hp_tune_rs',
overwrite = True)
除非通过输入指定使用贝叶斯优化或随机搜索,否则默认选择是 Hyperband。这在下面代码的else块中有所指示:
else:
# Default choice for tuning algorithm is hyperband.
tuner = kt.Hyperband(
hypermodel = model_builder,
objective = 'val_accuracy',
max_epochs = 3,
factor = 2,
distribution_strategy=strategy,
directory = flags_obj.model_dir,
project_name = 'hp_tune_hb',
overwrite = True)
现在根据前面的代码逻辑执行搜索算法;我们需要传递最佳超参数。为了自己的方便,我们可以使用get_gest_hyperparameters API 来打印出最佳超参数。我们将通过以下代码获得最优超参数:
best_hps = tuner.get_best_hyperparameters(num_trials = 1)[0]
print(f'''
The hyperparameter search is done.
The best number of nodes in the dense layer is {best_hps.get('units')}.
The optimal learning rate for the optimizer is {best_hps.get('learning_rate')}.
''')
现在我们可以将这些最佳超参数best_hp传递给模型,并使用这些值训练模型。tuner.hypermodel.build API 负责将这些值传递给模型。
在以下代码中,我们将设置训练和验证数据批次,创建回调对象,并使用fit API 开始训练:
# Build the model with the optimal hyperparameters and train it on the data
model = tuner.hypermodel.build(best_hps)
checkpoint_prefix = os.path.join(flags_obj.model_dir, 'best_hp_train_ckpt_{epoch}')
callbacks = [
tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_prefix,
save_weights_only=True)]
steps_per_epoch = train_gtr.samples // train_gtr.batch_size
validation_steps = validation_gtr.samples // validation_gtr.batch_size
model.fit(
train_gtr,
epochs=3, steps_per_epoch=steps_per_epoch,
validation_data=validation_gtr,
validation_steps=validation_steps,
callbacks=callbacks)
在这里,我们将目标目录中保存的模型的输出日志显示在屏幕上:
logging.info('INSIDE MAIN FUNCTION user input model_dir %s', flags_obj.model_dir)
# Save model trained with chosen HP in user specified bucket location
model_save_dir = os.path.join(flags_obj.model_dir,
'best_save_model')
model.save(model_save_dir)
if __name__ == '__main__':
app.run(main)
要将其作为脚本运行(hp_kt_resnet_local.py),你可以通过以下命令简单地调用它:
python3 hp_kt_resnet_local_pub.py \
--model_dir=resnet_local_hb_output \
--train_epoch_best=2 \
--tuner_type=hyperband
在前面的命令中,我们调用python3运行时来执行训练脚本hp_kt_resnet_local.py。model_dir是我们希望保存模型的地方。Tuner_type指定选择的超参数搜索算法。你可以尝试的其他算法选择包括贝叶斯优化和随机搜索。
在 Google 的 AI 平台提交调优任务
现在我们准备使用 Google 的 AI 平台进行超参数训练。你可以从本章的 GitHub 仓库下载所有需要的内容。对于本节中的 AI 平台代码,你可以参考 GitHub 仓库中本章文件夹中的gcptuningwork文件。
在云端,我们可以访问强大的计算机,这些计算机能加速我们的搜索过程。总体而言,我们将采用的方法与前一节提交本地 Python 脚本训练任务时看到的方法非常相似。我们将使用tf.compat.v1.flag方法来处理用户输入或标志。其余的脚本结构类似,唯一不同的是数据处理部分,因为我们将使用TFRecord代替ImageGenerator,并使用条件标志来指定分布式训练策略。
由于调优任务是从远程节点(即你的本地计算环境)提交到 AI 平台的,因此需要满足一些先决条件(请参见第五章,大规模训练):
-
在将要调用调优任务的目录中,需要更新
setup.py以包含keras-tuner。同时,我们也将添加 IPython。所以,请按以下方式编辑setup.py文件:from setuptools import find_packages from setuptools import setup setup( name='official', install_requires=['IPython', 'keras-tuner', 'tensorflow-datasets~=3.1', 'tensorflow_hub>=0.6.0'], packages=find_packages() )这是
setup.py的全部内容。在这个文件中,我们指定了训练任务所需的库,并通过find_packages函数指示运行时找到这些库。 -
现在你已经准备好提交调优任务。在以下命令中,任务被提交到 Cloud TPU 并在分布式训练策略下运行:
gcloud ai-platform jobs submit training hp_kt_resnet_tpu_hb_test \ --staging-bucket=gs://ai-tpu-experiment \ --package-path=tfk \ --module-name=tfk.tuner.hp_kt_resnet_tpu_act \ --runtime-version=2.2 \ --python-version=3.7 \ --scale-tier=BASIC_TPU \ --region=us-central1 \ --use-chief-in-tf-config='true' \ -- \ --distribution_strategy=tpu \ --data_dir=gs://ai-tpu-experiment/tfrecord-flowers \ --model_dir=gs://ai-tpu-experiment/hp_kt_resnet_tpu_hb_test \ --tuner_type=hyperband
请注意前面的代码中-- \的分隔符,在-- \之前,它们是 Google Cloud 特有的参数。我们从我们的环境提交训练脚本到 Cloud TPU。对于训练脚本,我们需要指定包路径和模块名称。Python 版本和 TensorFlow 运行时也需要选择。我们将使用BASIC_TPU,并选择位于us-central1区域的 Cloud TPU。
在 -- \ 后面是训练脚本的自定义参数。这些参数是为训练脚本定义并使用的。我们为分布式训练策略选择了 tpu 作为值。此外,训练数据的位置由 data_dir 指定。一旦训练作业完成,模型将保存在 model_dir 中。最后,我们选择 HYPERBAND 作为超参数调优算法的 tuner_type。
并且从当前目录(前述命令被调用的目录)中,训练脚本存储在 /python/ScriptProject/hp_kt_resnet_tpu_act.py 文件夹中。
这个训练脚本执行了两个超参数的搜索:我们图像分类模型中间 Dense 层的单元数量和激活函数。添加的 tuner_type 标志允许用户选择算法:Hyperband、贝叶斯优化或随机搜索。一旦搜索完成,它将使用最佳超参数配置训练模型,并将模型保存到存储桶中。
注意
代码较长,因此你可以在以下 GitHub 仓库中找到完整的代码和说明:github.com/PacktPublishing/learn-tensorflow-enterprise/tree/master/chapter_06/gcptuningwork。
训练的主驱动脚本可以在 github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_06/gcptuningwork/tfk/tuner/hp_kt_resnet_tpu_act.py 中找到。
训练完成后,你将在由 model_dir 指定的云存储中看到输出,如图 6.1 所示:

图 6.1 – 超参数调优和训练作业输出
在存储桶中,有使用最佳超参数配置进行训练的模型资产,保存在 best_save_model 文件夹中。此外,我们还可以看到,超参数调优工作流的每次试验也保存在 hp_tune_hb 文件夹中。
在所有搜索算法中,Hyperband 是最新的方法,它基于一种开发-探索策略,提供了高效且有效的搜索体验。它通常是最快收敛到最佳配置的算法。从硬件选择的角度来看,对于这个例子,Cloud TPU 提供了最短的运行时间。然而,由于超参数搜索本身是一个耗时的过程,数据规模和数据 I/O 也是影响搜索速度的重要因素。有时候,最好从较小的数据集或较小的搜索空间开始,以从进一步分析中排除一些选择。
摘要
在本章中,我们学习了如何在 Google Cloud AI Platform 上使用 Keras Tuner。我们学习了如何运行超参数搜索,并了解了如何使用最佳超参数配置训练模型。我们还看到,采用典型的 Keras 风格,将 Keras Tuner 集成到我们现有的模型训练工作流中是非常简单的,特别是将超参数作为某种数据类型的数组来简单处理。这真正扩展了超参数的选择范围,而且我们不需要实现搜索逻辑或复杂的条件循环来跟踪结果。
在下一章中,我们将看到最新的模型优化技术,这些技术可以减小模型的体积。这样,我们的模型可以变得更加精简和紧凑。
第四部分 – 模型优化与部署
本部分介绍了提高模型及其管道效率和速度的方法。我们将从模型运行时的概念开始,然后是模型优化,接着使用 TensorFlow Serving 通过 RESTful API 将模型作为 Docker 容器提供服务。
本节包括以下章节:
-
第七章,模型优化
-
第八章,模型训练与性能的最佳实践
-
第九章,服务化 TensorFlow 模型
第七章:
第八章:模型优化
在本章中,我们将通过量化技术了解模型优化的概念。这一点很重要,因为即使在云环境中,计算和内存等容量问题不那么突出,但延迟和吞吐量始终是影响模型输出质量和数量的因素。因此,优化模型以减少延迟和最大化吞吐量有助于降低计算成本。在边缘环境中,许多约束与内存、计算、电力消耗和带宽等资源相关。
在本章中,你将学习如何使你的模型尽可能精简且高效,同时确保模型精度的变化在可接受范围内。换句话说,我们将减少模型的大小,以便让模型在更少的电力和计算资源下运行,而不会对其性能产生过大影响。在本章中,我们将探讨最近的进展和一种适用于 TensorFlow 的方法:TFLite 量化。
在本章中,我们将讨论以下主题:
-
理解量化概念
-
为评分准备完整的原始模型
-
将完整模型转换为减少的 float16 模型
-
将完整模型转换为减少的混合量化模型
-
将完整模型转换为整数量化模型
技术要求
你可以在github.com/PacktPublishing/learn-tensorflow-enterprise.git找到所有源代码。
你可以在命令终端使用git命令克隆它:
git clone https://github.com/PacktPublishing/learn-tensorflow-enterprise.git
本章的所有资源都可以在 GitHub 链接中的chapter_07文件夹中找到。
理解量化概念
量化是一种将模型大小减小并因此提高其效率的技术。这项技术对于构建适用于移动设备或边缘部署的模型非常有用,因为这些场景下计算资源或电源供应是有限的。由于我们的目标是使模型尽可能高效运行,我们也接受这样一个事实:模型必须变得更小,因此精度也会比原始模型低。这意味着我们正在将模型转化为其原始版本的轻量化形式,而转化后的模型是原始模型的近似值。
量化可以应用于已训练的模型。这称为后训练量化 API。在这种类型的量化中,有三种方法:
-
将
float 32 位操作转换为float 16操作。 -
8 位,同时保持偏差和激活为32 位操作。 -
8 位,而偏差和激活可能是8或16 位。
前述方法适用于使用传统手段构建和训练的 TensorFlow 模型。另一种方法是在执行优化的同时训练模型。这被称为量化感知训练,在此过程中,我们应用 API 来模拟在深度学习训练的前向传播阶段进行的量化操作。
结果模型包含量化值。这是相对较新的,且目前只有整数量化 API 可用。量化感知训练目前只适用于自定义构建的模型,而不适用于来自 TensorFlow Hub 的模型(这些模型是预训练的)。如果你希望使用这些著名的预训练模型的量化版本,可以在此处找到这些模型:www.tensorflow.org/lite/guide/hosted_models。
训练基准模型
让我们开始训练一个包含五类花卉的图像分类模型。我们将利用 TensorFlow Hub 上托管的预训练 ResNet 特征向量(tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4),你可以从这里下载 TFRecord 格式的花卉图像:dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/1ECTVN。
或者,如果你克隆了本书的代码库,训练基准模型的源代码和 TFRecord 数据集可以在github.com/PacktPublishing/learn-tensorflow-enterprise/tree/master/chapter_07/train_base_model找到。
以下是一个训练脚本default_trainer.py文件,用于使用 TFRecord 数据集训练此模型:
-
我们通过一个
import语句开始这个训练脚本,导入我们所需的所有库:import tensorflow as tf import tensorflow_hub as hub import tensorflow_datasets as tfds import os import IPython import time from absl import flags from absl import logging from absl import appAbsl是一个有用的库。在这个库中,flagsAPI 用于定义用户输入。这特别方便,因为我们通过用户命令调用此脚本,而不是以笔记本的形式运行它。 -
配备了
flagsAPI 的import语句后,我们将为flags.FLAGS定义一个简短的别名,并定义一系列我们将传递给脚本的用户输入。这是通过tf.compat.v1.flagsAPI 实现的。请注意,我们可以为用户输入定义数据类型,并提供默认值,以便用户无需指定每个输入:FLAGS = flags.FLAGS # flag name, default value, explanation/help. tf.compat.v1.flags.DEFINE_string('model_dir', 'default_model_dir', 'Directory or bucket for storing checkpoint model.') tf.compat.v1.flags.DEFINE_bool('fine_tuning_choice', False, 'Retrain base parameters') tf.compat.v1.flags.DEFINE_integer('train_batch_size', 32, 'Number of samples in a training batch') tf.compat.v1.flags.DEFINE_integer('validation_batch_size', 40, 'Number of samples in a validation batch') tf.compat.v1.flags.DEFINE_string('distribution_strategy', 'tpu', 'Distribution strategy for training.') tf.compat.v1.flags.DEFINE_integer('train_epochs', 3, 'Number of epochs for training') tf.compat.v1.flags.DEFINE_string('data_dir', 'tf_datasets/flower_photos', 'training data path') tf.compat.v1.flags.DEFINE_integer('num_gpus', 4, 'Number of GPU per worker') tf.compat.v1.flags.DEFINE_string('cache_dir', '../imagenet_resnet_v2_50_feature_vector_4' , 'Location of cached model')有一些用户标志值得进一步解释。
data-dir标志定义了训练数据的文件路径。在这种情况下,它指向当前目录train_base_model下的文件夹路径tf_datasets/flower_photos。另一个用户标志是cache_dir,它是我们下载的 ResNet 特征向量模型的路径。尽管我们可以通过互联网直接访问 TensorFlow Hub,但在某些情况下,网络连接可能会出现问题。因此,下载模型并将其放在本地环境中是个不错的选择。 -
我们可能会在以下函数中封装模型架构和编译过程:
def model_default(): flags_obj = flags.FLAGS os.environ['TFHUB_CACHE_DIR'] = flags_obj.cache_dir IMAGE_SIZE = (224, 224) model = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)), hub.KerasLayer('https://tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4', trainable=flags_obj.fine_tuning_choice), tf.keras.layers.Flatten(), tf.keras.layers.Dense(units = 64, activation = 'relu', kernel_initializer='glorot_uniform'), tf.keras.layers.Dense(5, activation='softmax', name = 'custom_class') ]) model.build([None, 224, 224, 3]) model.compile( optimizer=tf.keras.optimizers.SGD(lr=1e-2, momentum=0.5), loss=tf.keras.losses.CategoricalCrossentropy( from_logits=True, label_smoothing=0.1), metrics=['accuracy']) return model这个函数负责构建模型并使用合适的
optimizer和loss函数进行编译。它返回一个用于训练的模型对象。 -
至于图像数据的输入管道,这个管道需要处理数据解析。可以通过以下函数来实现:
def decode_and_resize(serialized_example): # resized image should be [224, 224, 3] and normalized to value range [0, 255] # label is integer index of class. parsed_features = tf.io.parse_single_example( serialized_example, features = { 'image/channels' : tf.io.FixedLenFeature([], tf.int64), 'image/class/label' : tf.io.FixedLenFeature([], tf.int64), 'image/class/text' : tf.io.FixedLenFeature([], tf.string), 'image/colorspace' : tf.io.FixedLenFeature([], tf.string), 'image/encoded' : tf.io.FixedLenFeature([], tf.string), 'image/filename' : tf.io.FixedLenFeature([], tf.string), 'image/format' : tf.io.FixedLenFeature([], tf.string), 'image/height' : tf.io.FixedLenFeature([], tf.int64), 'image/width' : tf.io.FixedLenFeature([], tf.int64) }) image = tf.io.decode_jpeg(parsed_features[ 'image/encoded'], channels=3) label = tf.cast(parsed_features['image/class/label'], tf.int32) label_txt = tf.cast(parsed_features[ 'image/class/text'], tf.string) label_one_hot = tf.one_hot(label, depth = 5) resized_image = tf.image.resize(image, [224, 224], method='nearest') return resized_image, label_one_hot正如函数名所示,这个函数接收一个存储在 TFRecord 中的样本。它通过特征描述解析,并且样本图像(这是一个
byte string)会解码为 JPEG 图像。至于图像标签,函数也会解析标签名称(image/class/text),并将其转换为独热向量。JPEG 图像会被调整为224x224的大小。因此,这个函数返回一个元组。该元组包含一个调整大小后的图像及其标签。 -
我们还需要将图像像素值归一化到[0, 1.0]的范围内。可以通过以下函数来实现:
def normalize(image, label): #Convert `image` from [0, 255] -> [0, 1.0] floats image = tf.cast(image, tf.float32) / 255. return image, label在
normalize函数中,一个表示为 NumPy 数组的 JPEG 图像会被归一化到[0, 1.0]的范围。同时,尽管我们没有对标签做任何处理,但最好将标签与图像一起传递,并将它们作为元组返回,以便你能够将图像和标签一起追踪。 -
然后我们在以下函数中对训练数据应用洗牌和批处理操作:
def prepare_for_training(ds, cache=True, shuffle_buffer_size=1000): # This is a small dataset, only load it once, and keep it in memory. # use `.cache(filename)` to cache preprocessing work for datasets that don't # fit in memory. flags_obj = flags.FLAGS if cache: if isinstance(cache, str): ds = ds.cache(cache) else: ds = ds.cache() ds = ds.shuffle(buffer_size=shuffle_buffer_size) # Repeat forever ds = ds.repeat() ds = ds.batch(flags_obj.train_batch_size) # `prefetch` lets the dataset fetch batches in the background while the model # is training. AUTOTUNE = tf.data.experimental.AUTOTUNE ds = ds.prefetch(buffer_size=AUTOTUNE) return ds这个函数返回一个附带了洗牌、重复、批处理和预取操作的数据集。这是为训练准备数据集的标准方法。
-
现在我们来到了这段代码的主要驱动部分:
def main(_): flags_obj = flags.FLAGS if flags_obj.distribution_strategy == 'tpu': resolver = tf.distribute.cluster_resolver.TPUClusterResolver() tf.config.experimental_connect_to_cluster(resolver) tf.tpu.experimental.initialize_tpu_system(resolver) strategy = tf.distribute.experimental.TPUStrategy(resolver) strategy_scope = strategy.scope() print('All devices: ', tf.config.list_logical_devices('TPU')) elif flags_obj.distribution_strategy == 'gpu': strategy = tf.distribute.MirroredStrategy() strategy_scope = strategy.scope() devices = ['device:GPU:%d' % i for i in range(flags_obj.num_gpus)] else: strategy = tf.distribute.MirroredStrategy() strategy_scope = strategy.scope() print('NUMBER OF DEVICES: ', strategy.num_replicas_in_sync)在
main()函数的这一部分,我们提供了分布式训练策略的逻辑。 -
接着在
main()中,数据路径通过tf.dataAPI 进行识别和处理:## identify data paths and sources root_dir = flags_obj.data_dir # this is gs://<bucket>/folder or file path where tfrecord is found file_pattern = '{}/image_classification_builder-train*.tfrecord*'.format(root_dir) val_file_pattern = '{}/image_classification_builder-validation*.tfrecord*'.format(root_dir) file_list = tf.io.gfile.glob(file_pattern) all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(file_pattern)) val_file_list = tf.io.gfile.glob(val_file_pattern) val_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(val_file_pattern)) train_all_ds = tf.data.TFRecordDataset(all_files, num_parallel_reads=tf.data.experimental.AUTOTUNE) val_all_ds = tf.data.TFRecordDataset(val_all_files, num_parallel_reads=tf.data.experimental.AUTOTUNE)在前面的代码中,所有三个数据源——训练、验证和测试——都被标识并引用。回想一下,文件名模式中的通配符符号
*帮助这个管道具有可扩展性。无论你有多少个 TFRecord 数据部分,这个管道都能处理。 -
继续在
main()函数中,现在我们需要对训练和验证数据集中的每个样本应用特征工程和归一化函数。通过map函数来完成:# perform data engineering dataset = train_all_ds.map(decode_and_resize) val_dataset = val_all_ds.map(decode_and_resize) # Create dataset for training run BATCH_SIZE = flags_obj.train_batch_size VALIDATION_BATCH_SIZE = flags_obj.validation_batch_size dataset = dataset.map(normalize, num_parallel_calls=tf.data.experimental.AUTOTUNE) val_dataset = val_dataset.map(normalize, num_parallel_calls=tf.data.experimental.AUTOTUNE) val_ds = val_dataset.batch(VALIDATION_BATCH_SIZE) AUTOTUNE = tf.data.experimental.AUTOTUNE train_ds = prepare_for_training(dataset)根据相应的用户标志,训练和验证数据集会被批处理。如果没有给定标志,则使用默认值。
-
现在我们需要为训练和验证设置一些参数:
NUM_CLASSES = 5 IMAGE_SIZE = (224, 224) train_sample_size=0 for raw_record in train_all_ds: train_sample_size += 1 print('TRAIN_SAMPLE_SIZE = ', train_sample_size) validation_sample_size=0 for raw_record in val_all_ds: validation_sample_size += 1 print('VALIDATION_SAMPLE_SIZE = ', validation_sample_size) STEPS_PER_EPOCHS = train_sample_size // BATCH_SIZE VALIDATION_STEPS = validation_sample_size // VALIDATION_BATCH_SIZE在前面的代码中,我们将类别数和图像大小设置为变量,并将其传递到模型训练过程中。然后我们确定每个训练周期和交叉验证的步骤数。
-
继续使用
main(),我们现在可以通过调用model_default函数来创建模型:model = model_default() checkpoint_prefix = os.path.join(flags_obj.model_dir, 'train_ckpt_{epoch}') callbacks = [ tf.keras.callbacks.TensorBoard(log_dir=os.path.join(flags_obj.model_dir, 'tensorboard_logs')), tf.keras.callbacks.ModelCheckpoint( filepath=checkpoint_prefix, save_weights_only=True)]在前面的代码中,我们调用
model_default函数来构建和编译我们的模型。我们还为训练检查点设置了回调。 -
继续使用
main(),我们现在可以启动训练过程:model.fit( train_ds, epochs=flags_obj.train_epochs, steps_per_epoch=STEPS_PER_EPOCHS, validation_data=val_ds, validation_steps=VALIDATION_STEPS, callbacks=callbacks)在前面的代码中,我们将训练集和验证集传递给
fit函数。训练周期数由用户输入决定。如果没有提供,则使用默认值。 -
继续使用
main(),我们可以在执行此脚本的终端中将输出记录为STDOUT:logging.info('INSIDE MAIN FUNCTION user input model_dir %s', flags_obj.model_dir) timestr = time.strftime('%Y%m%d-%H%M%S') output_folder = flags_obj.model_dir + '-' + timestr if not os.path.exists(output_folder): os.mkdir(output_folder) print('Directory ' , output_folder , ' Created ') else: print('Directory ' , output_folder , ' already exists') model_save_dir = os.path.join(output_folder, 'save_model') model.save(model_save_dir) if __name__ == '__main__': app.run(main)在前面的代码中,我们还利用了时间戳值来构建文件夹名称,其中每次构建的模型可能会根据训练完成的时间保存。
这标志着main()的结束。模型保存在model_save_dir中。要调用此脚本,你只需在你的 Python 环境中运行以下命令:
python3 default_trainer.py \
--distribution_strategy=default \
--fine_tuning_choice=False \
--train_batch_size=32 \
--validation_batch_size=40 \
--train_epochs=5 \
--data_dir=tf_datasets/flower_photos \
--model_dir=trained_resnet_vector
从存储此脚本的目录中,你会找到一个以trained_resnet_vector为前缀的子文件夹,后面跟着一个日期和时间戳,如20200910-213303。该子文件夹包含保存的模型。我们将使用此模型作为我们的基准模型。训练完成后,你会在以下目录中找到保存的模型:
trained_resnet_vector-20200910-213303/save_model/assets
这个保存的模型与default_trainer.py文件存储在同一目录下。现在我们有了一个训练好的 TensorFlow 模型,在下一节中,我们将用训练好的模型对测试数据进行评分。
准备一个完整的原始模型进行评分
完成完整模型的训练后,我们将在本仓库中使用一个Scoring Jupyter 笔记本演示如何使用完整模型进行评分。这个笔记本可以在github.com/PacktPublishing/learn-tensorflow-enterprise/blob/master/chapter_07/train_base_model/Scoring.ipynb找到。
对于原始模型,它以savedModel Protobuf 格式存储。我们需要按照以下方式加载它:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageOps
import IPython.display as display
path_saved_model = 'trained_resnet_vector-unquantized/save_model'
trained_model = tf.saved_model.load(path_saved_model)
我们刚刚训练的完整模型现在已加载到 Jupyter 笔记本的运行时中,命名为trained_model。对于评分,还需要做一些额外的步骤。我们必须找到用于预测的模型签名:
signature_list = list(trained_model.signatures.keys())
signature_list
它显示此列表中只有一个签名:
['serving_default']
我们将创建一个infer包装函数并将签名传递给它:
infer = trained_model.signatures[signature_list[0]]
这里,signature_list[0]等同于serving_default。现在让我们打印输出:
print(infer.structured_outputs)
让我们看看前面函数的输出:
{'custom_class': TensorSpec(shape=(None, 5), dtype=tf.float32, name='custom_class')}
输出是一个shape=(None, 5)的 NumPy 数组。这个数组将保存模型预测的类别概率。
现在让我们处理测试数据。此处提供的测试数据是 TFRecord 格式。我们将其转换为形状为 [None, 224, 224, 3] 的 NumPy 数组形式的图像批次。
准备测试数据
这部分与我们在第六章中看到的非常相似,超参数调优,在那时我们广泛使用 TFRecord 作为模型训练的输入格式。这里使用的 TFRecord 可以在github.com/PacktPublishing/learn-tensorflow-enterprise/tree/master/chapter_07/train_base_model/tf_datasets/flower_photos找到。
加载测试数据
让我们从加载 TFRecord 数据开始:
root_dir = ' tf_datasets/flower_photos'
test_pattern = '{}/image_classification_builder-test.tfrecord*'.format(root_dir)
test_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(test_pattern))
test_all_ds = tf.data.TFRecordDataset(test_all_files,
num_parallel_reads=tf.data.experimental.AUTOTUNE)
我们将使用以下代码检查图像的样本大小:
sample_size = 0
for raw_record in test_all_ds:
sample_size += 1
print('Sample size: ', sample_size)
这是输出结果:
Sample size: 50
这表明我们在测试数据中有 50 个样本。
这个测试数据集中有 50 张图像。我们将重用来自第六章的辅助函数,超参数调优,来解码 TFRecord 及其中的元数据,然后对像素值进行归一化处理:
def decode_and_resize(serialized_example):
# resized image should be [224, 224, 3] and normalized to value range [0, 255]
# label is integer index of class.
parsed_features = tf.io.parse_single_example(
serialized_example,
features = {
'image/channels' : tf.io.FixedLenFeature([], tf.int64),
'image/class/label' : tf.io.FixedLenFeature([], tf.int64),
'image/class/text' : tf.io.FixedLenFeature([], tf.string),
'image/colorspace' : tf.io.FixedLenFeature([], tf.string),
'image/encoded' : tf.io.FixedLenFeature([], tf.string),
'image/filename' : tf.io.FixedLenFeature([], tf.string),
'image/format' : tf.io.FixedLenFeature([], tf.string),
'image/height' : tf.io.FixedLenFeature([], tf.int64),
'image/width' : tf.io.FixedLenFeature([], tf.int64)
})
image = tf.io.decode_jpeg(parsed_features['image/encoded'], channels=3)
label = tf.cast(parsed_features['image/class/label'], tf.int32)
label_txt = tf.cast(parsed_features['image/class/text'], tf.string)
label_one_hot = tf.one_hot(label, depth = 5)
resized_image = tf.image.resize(image, [224, 224], method='nearest')
return resized_image, label_one_hot
def normalize(image, label):
#Convert `image` from [0, 255] -> [0, 1.0] floats
image = tf.cast(image, tf.float32) / 255\.
return image, label
decode_and_resize 函数解析图像,将其调整为 224 x 224 像素,同时对图像的标签进行一热编码。decode_and_resize 然后返回图像和对应标签作为一个元组,确保图像和标签始终一起处理。
normalize 函数通过将图像像素值除以 255,将像素范围归一化到 [0, 1.0]。尽管标签本身没有做任何处理,但必须将图像和标签一起跟踪,确保它们始终一起存在。
现在我们可以应用前述的辅助函数来解码、标准化和归一化 TFRecord 数据集中的图像:
decoded = test_all_ds.map(decode_and_resize)
normed = decoded.map(normalize)
请注意,我们通过 np.expand_dims 在第一个维度引入了一个额外的维度。这个额外的维度是为了变量批大小的需要:
np_img_holder = np.empty((0, 224, 224,3), float)
np_lbl_holder = np.empty((0, 5), int)
for img, lbl in normed:
r = img.numpy() # image value extracted
rx = np.expand_dims(r, axis=0)
lx = np.expand_dims(lbl, axis=0)
np_img_holder = np.append(np_img_holder, rx, axis=0)
np_lbl_holder = np.append(np_lbl_holder, lx, axis=0)
现在测试数据已经是 NumPy 格式,具有标准化的维度,像素值在 0 到 1 之间,并且进行了批处理,标签也是如此。
我们现在将检查这些图像。为此,我们可以使用以下代码,在图 7.1 中将 NumPy 数组 np_img_holder 作为图像显示:
%matplotlib inline
plt.figure()
for i in range(len(np_img_holder)):
plt.subplot(10, 5, i+1)
plt.axis('off')
plt.imshow(np.asarray(np_img_holder[i]))
在之前的代码片段中,我们遍历图像数组,并将每张图像放置在子图中的一个位置。共有 50 张图像(10 行,每行 5 个子图),如下图所示:

图 7.1 – 测试数据集中五种花卉类别的 50 张图像
使用完整模型评分单张图像
现在我们来看一下测试数据的形状,并理解将测试数据转换为模型所需的形状的过程:
-
我们将首先使用一张图像测试我们的评分流程。就像我们通过在第一维添加一个新维度来创建图像批次一样,我们也将通过相同的方式创建一个样本大小为
1的图像批次:x = np.expand_dims(np_img_holder[0], axis=0) x.shape (1, 224, 224, 3) -
现在维度是正确的,这是一个包含一张图像的批次。我们将其转换为
float32类型的张量:xf = tf.dtypes.cast(x, tf.float32)然后,将此传递给
infer函数进行评分:prediction = infer(xf)你会记得,我们模型的最后一层是一个名为
custom_class的全连接层,包含五个节点,并且每个节点的激活函数都是 softmax,这样我们就可以得到五个类别的概率。 -
现在我们将检查预测的内容:
prediction.get('custom_class')输出结果应类似于以下内容:
<tf.Tensor: shape=(1, 5), dtype=float32, numpy= array([[1.5271275e-04, 2.0515859e-05, 1.0230409e-06, 2.9591745e-06, 9.9982280e-01]], dtype=float32)>数组中的这些值代表概率。数组中的每个位置代表一种花卉类型的类别。如你所见,最高的概率位于数组的最后一个位置;与这个位置对应的索引是
4。我们需要将4映射到纯文本名称。现在我们将其转换为 NumPy 数组,以便我们可以找到预测最大概率的位置:
predicted_prob_array = prediction.get('custom_class').numpy() idx = np.argmax(predicted_prob_array) print(idx) -
第四个位置的索引是预测最大概率的位置。现在我们需要通过将此索引映射到标签来了解它代表的是什么。我们需要创建一个反向查找字典,将概率映射回标签。我们刚刚找到了最大概率所在的索引。接下来的步骤是将
idx映射到正确的花卉类型。为此,我们需要从 TFRecord 中提取该信息:feature_description = { 'image/channels' : tf.io.FixedLenFeature([], tf.int64), 'image/class/label' : tf.io.FixedLenFeature([], tf.int64), 'image/class/text' : tf.io.FixedLenFeature([], tf.string), 'image/colorspace' : tf.io.FixedLenFeature([], tf.string), 'image/encoded' : tf.io.FixedLenFeature([], tf.string), 'image/filename' : tf.io.FixedLenFeature([], tf.string), 'image/format' : tf.io.FixedLenFeature([], tf.string), 'image/height' : tf.io.FixedLenFeature([], tf.int64), 'image/width' : tf.io.FixedLenFeature([], tf.int64) } def _parse_function(example_proto): return tf.io.parse_single_example(example_proto, feature_description) parsd_ds = test_all_ds.map(_parse_function) val_label_map = {} # getting label mapping for image_features in parsd_ds.take(50): label_idx = image_features[ 'image/class/label'].numpy() label_str = image_features[ 'image/class/text'].numpy().decode() if label_idx not in val_label_map: val_label_map[label_idx] = label_str在前面的代码中,我们使用了相同的特征描述符(
feature_description)来解析test_all_ds。一旦通过_parse_function进行解析,我们遍历整个测试数据集。我们需要的信息位于image/class/label和image/class/text中。 -
我们简单地创建一个字典,其中键是
label_idx,值是label_str。结果是val_label_map。如果我们按如下方式检查它:val_label_map输出结果如下:
{4: 'tulips', 3: 'dandelion', 1: 'sunflowers', 2: 'daisy', 0: 'roses'}然后我们评估
idx:print(val_label_map.get(idx))这是输出结果:
tulip这将我们的图像映射到
tulip类别。
使用完整模型对批量图像进行评分
在上一节中,我们查看了如何对单张图像进行评分。现在我们想对一批图像进行评分。在我们的测试数据中,有 50 张图像:
-
在上一节中,我们创建了正确形状的图像批次,形状为
[50, 224, 224, 3]。现在可以进行评分了:batched_input = tf.dtypes.cast(np_img_holder, tf.float32) batch_predicted_prob_array = infer(batched_input)让我们创建一个函数,帮助在给定 NumPy 数组和查找字典时查找标签名称:
def lookup(np_entry, dictionary): class_key = np.argmax(np_entry) return dictionary.get(class_key)该函数接受一个 NumPy 数组,映射出最大值的位置,然后通过字典将该位置映射到标签。
-
这是一个包含我们真实标签的列表,如
np_lbl_holder所示:actual = [] for i in range(len(np_lbl_holder)): plain_text_label = lookup(np_lbl_holder[i], val_label_map) actual.append(plain_text_label)actual包含所有 50 个测试样本的真实标签(纯文本格式)。 -
这是我们如何获得包含预测标签的列表:
predicted_label = [] for i in range(sample_size): batch_prediction = batch_predicted_prob_array.get('custom_class').numpy() plain_text_label = lookup(batch_prediction[i], val_label_map) predicted_label.append(plain_text_label)predicted_label包含所有 50 个测试样本的预测结果,以纯文本形式存储,因为我们利用lookup函数将概率映射到花卉类型名称。 -
我们将比较
predicted_label和actual来计算模型的准确率:from sklearn.metrics import accuracy_score accuracy=accuracy_score(actual, predicted_label) print(accuracy) 0.82
这表明我们完整模型的准确率为 82%。这是通过使用sklearn中的accuracy_score API,简单地将actual与predicted_label进行比较得出的。
注意
预计你的模型准确率会略有不同于这里打印的名义值。每次训练基础模型时,模型的准确率都不会完全相同。然而,它不应该与名义值相差太多。影响模型准确率重现性的另一个因素是训练时使用的迭代次数;在本例中,为了演示和教学目的,仅进行了五个 epoch。更多的训练 epoch 将使你的模型准确率更好,方差更小。
将完整模型转换为减少版的float16模型
在本节中,我们将加载我们刚刚训练的模型,并将其量化为一个减少版的float16模型。为了方便逐步讲解和提升你的学习体验,建议你使用 JupyterLab 或 Jupyter Notebook 跟随本文的说明:
-
让我们首先加载训练好的模型:
import tensorflow as tf import pathlib import os import numpy as np from matplotlib.pyplot import imshow import matplotlib.pyplot as plt root_dir = '../train_base_model' model_dir = ' trained_resnet_vector-unquantized/save_model' saved_model_dir = os.path.join(root_dir, model_dir) trained_model = tf.saved_model.load(saved_model_dir)tf.saved_model.loadAPI 帮助我们加载我们已构建并训练好的保存模型。 -
然后,我们将创建一个
converter对象,使用以下代码来引用savedModel目录:converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)对于
converter对象,我们将选择DEFAULT优化策略,以最佳方式提高模型的大小和延迟:converter.optimizations = [tf.lite.Optimize.DEFAULT]替代方案包括
OPTIMIZE_FOR_LATENCY或OPTIMIZE_FOR_SIZE。有关详细信息,请参阅www.tensorflow.org/api_docs/python/tf/lite/Optimize。 -
接下来,我们将设置
float16作为模型参数的目标类型,并开始转换过程:converter.target_spec.supported_types = [tf.float16] tflite_model = converter.convert()我们将使用以下代码设置目录路径来保存量化后的模型:
root_dir = '' tflite_model_dir = trained_resnet_vector-unquantized to_save_tflite_model_dir = os.path.join(root_dir, tflite_model_dir) -
现在,我们将创建一个
pathlib对象来表示我们想要保存量化模型的目录:saved_tflite_model_dir = pathlib.Path( to_save_tflite_model_dir)让我们创建一个目录来保存量化后的模型:
saved_tflite_model_dir.mkdir(exist_ok=True, parents=True) -
我们现在将创建一个
pathlib对象tgt来表示量化模型文件:tgt = pathlib.Path(tflite_models_dir, 'converted_model_reduced.tflite')我们现在将使用
pathlib对象tgt来写入量化后的模型:tgt.write_bytes(tflite_model)这将显示以字节为单位写入的输出大小:
47487392使用最后一条命令,你将看到量化后的模型大小略大于
47MB,具体为47,487,392字节。请前往以下目录:../trained_resnet_vector-unquantized/save_model/variables.这显示了原始模型的权重和偏差文件稍微超过 95 MB(结果可能有所不同,如果你重新训练,它可能不会完全相同;但是,它应该非常接近 95 MB),如以下图所示:

图 7.2 – 原始模型的权重和偏差文件大小
量化模型的大小约为原始模型的一半。这是预期中的结果,因为该模型是从 float32 格式转换为 float16 格式的。接下来,我们将使用降低为 float16 的模型对测试数据进行评分。
准备减小的 float16 模型进行评分
在本节中,我们将使用量化后的模型(降低为float16)来对上一节中使用的相同测试数据集进行评分。我们将通过 TensorFlow Lite 解释器接口执行评分(推理):
-
我们将从由
tflite_models_dir表示的文件路径加载量化模型。在上一节中,我们创建了一个pathlib对象tgt来表示量化模型文件:tgt = pathlib.Path(tflite_models_dir, 'converted_model_reduced.tflite') -
接着我们需要获取
input_details和output_details张量:input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() -
从这些张量中,我们将检查输入和输出中 NumPy 数组的形状:
input_details[0]['shape'] array([ 1, 224, 224, 3], dtype=int32) output_details[0]['shape'] array([1, 5], dtype=int32)
我们验证了模型的输入和输出应该是一个批量,因为这些张量中有四个维度。接下来,我们将通过对测试数据进行评分来查看这个模型的表现如何。
使用量化模型对单张图像进行评分
现在我们可以开始使用 TFLite 量化模型进行评分。在接下来的步骤中,我们首先扩展样本,包含一个批量维度,将输入数据传递给解释器,进行输入数据的评分,然后获取预测结果的输出:
input_data = np.array(np.expand_dims(np_img_holder[0], axis=0), dtype=np.float32)
interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()
output_data = interpreter.get_tensor(output_details[0]['index'])
print(output_data)
输出如下:
[[1.5181543e-04 2.0090181e-05 1.0022727e-06 2.8991076e-06 9.9982423e-01]]
要将output_data映射回原始标签,请执行以下命令:
lookup(output_data, val_label_map)
输出如下:
'tulips'
使用量化模型对批量图像进行评分
当前,TFLite 模型的批量评分是通过单张图像的迭代评分过程来实现的。对于我们包含 50 张测试图像的示例,我们可以创建一个辅助函数来封装整个单张图像评分过程:
-
这是一个处理批量评分的函数:
def batch_predict(input_raw, input_tensor, output_tensor, dictionary): input_data = np.array(np.expand_dims(input_raw, axis=0), dtype=np.float32) interpreter.set_tensor(input_tensor[0]['index'], input_data) interpreter.invoke() interpreter_output = interpreter.get_tensor( output_tensor[0]['index']) plain_text_label = lookup(interpreter_output, dictionary) return plain_text_label这个函数将原始图像维度扩展为批量,然后将批量图像传递给解释器进行评分。解释器的输出通过
lookup函数映射为明文名称,返回的明文即为预测标签。 -
接下来,我们将遍历测试数据,调用
batch_predict:batch_quantized_prediction = [] for i in range(sample_size): plain_text_label = batch_predict(np_img_holder[i], input_details, output_details, val_label_map) batch_quantized_prediction.append(plain_text_label)结果存储在
batch_quantized_prediction列表中。 -
就像我们衡量原始模型预测准确性一样,我们也可以使用
accuracy_score来获取 TFLite 量化模型的准确性:quantized_accuracy = accuracy_score(actual, batched_quantized_prediiction) print(quantized_accuracy)输出如下:
0.82这里的输出显示也是 82%。如果你重新训练了模型,结果可能会有所不同,但根据我的经验,它与基础模型的准确性完全相同。
注意
预计你的模型精度会与此处打印的名义值略有不同。每次训练基础模型时,模型精度不会完全相同。然而,它不应该与名义值相差太大。另一个影响模型精度可重复性的因素是训练时使用的周期数;在这种情况下,仅使用了五个周期作为演示和教学目的。更多的训练周期会为你提供更好、更精确的模型精度。
到目前为止开发的函数、例程和工作流程将在本章剩余部分中用于演示模型优化的过程和结果。我们已经学会了如何对原始模型进行评分,将原始模型转换为 TFLite 量化模型并对量化模型进行评分。接下来,我们将使用相同的转换和评估过程将原始模型转换为不同的格式。
将完整模型转换为减少的混合量化模型
在上一部分中,我们将一个完整模型转换为一个减少的float16 TFLite 模型,并演示了其评分和评估过程。现在我们将尝试第二种支持的量化类型,即混合方法。
混合量化通过将模型转换为 8 位整数权重、32 位浮动偏置和激活来优化模型。由于它同时包含整数和浮点计算,因此被称为混合量化。这是为了在准确性和优化之间做出权衡。
对于混合量化,我们需要做一个小的修改。只有一行的区别,如下所述。在上一部分中,这是我们如何将完整模型量化为一个减少的float16 TFLite 模型:
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_model = converter.convert()
对于混合量化,我们只需删除关于supported_types的中间一行:
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
其他部分基本保持不变。以下是混合量化和评分的完整笔记本:
-
和往常一样,我们将指定必要的库和模型路径:
import tensorflow as tf import pathlib import os import numpy as np from matplotlib.pyplot import imshow import matplotlib.pyplot as plt root_dir = '' model_dir = 'trained_resnet_vector-unquantized/save_model' saved_model_dir = os.path.join(root_dir, model_dir)现在,模型的路径已在
saved_model_dir中指定。 -
然后我们为
saved_model_dir创建一个converter对象,并用它来转换我们的模型:converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir) converter.optimizations = [tf.lite.Optimize.DEFAULT] tflite_model = converter.convert()现在,转换器将完整模型转换为混合量化模型。
-
现在我们将保存我们的混合量化模型:
root_dir = '' tflite_models_dir = 'trained_resnet_vector-unquantized/tflite_hybrid_model' to_save_tflite_model_dir = os.path.join(root_dir, tflite_models_dir) saved_tflite_models_dir = pathlib.Path(to_save_tflite_model_dir) saved_tflite_models_dir.mkdir(exist_ok=True, parents=True tgt = pathlib.Path(to_save_tflite_model_dir, 'converted_model_reduced.tflite') tgt.write_bytes(tflite_model)输出显示模型的大小,以字节为单位:
24050608
这比原始基础模型的 95MB 小得多。接下来,让我们看看这个更小的混合量化模型在测试数据上的表现如何。
准备测试数据进行评分
我们将像处理减少的float16模型一样开始加载测试数据:
-
我们可以加载 TFRecord 数据,就像我们之前做的那样:
root_dir = '../train_base_model/tf_datasets/flower_photos' test_pattern = '{}/image_classification_builder-test.tfrecord*'.format(root_dir) test_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(test_pattern)) test_all_ds = tf.data.TFRecordDataset(test_all_files, num_parallel_reads=tf.data.experimental.AUTOTUNE)现在,
test_all_ds表示指向我们测试数据路径的数据集对象。 -
我们可以通过遍历数据集并跟踪样本数量来确定样本大小:
sample_size = 0 for raw_record in test_all_ds: sample_size += 1 print('Sample size: ', sample_size)这将显示样本大小为
50。 -
我们使用在简化的
float16模型部分中看到的相同辅助函数来标准化图像大小和像素值:def decode_and_resize(serialized_example): # resized image should be [224, 224, 3] and normalized to value range [0, 255] # label is integer index of class. parsed_features = tf.io.parse_single_example( serialized_example, features = { 'image/channels' : tf.io.FixedLenFeature([], tf.int64), 'image/class/label' : tf.io.FixedLenFeature([], tf.int64), 'image/class/text' : tf.io.FixedLenFeature([], tf.string), 'image/colorspace' : tf.io.FixedLenFeature([], tf.string), 'image/encoded' : tf.io.FixedLenFeature([], tf.string), 'image/filename' : tf.io.FixedLenFeature([], tf.string), 'image/format' : tf.io.FixedLenFeature([], tf.string), 'image/height' : tf.io.FixedLenFeature([], tf.int64), 'image/width' : tf.io.FixedLenFeature([], tf.int64) }) image = tf.io.decode_jpeg(parsed_features[ 'image/encoded'], channels=3) label = tf.cast(parsed_features['image/class/label'], tf.int32) label_txt = tf.cast(parsed_features[ 'image/class/text'], tf.string) label_one_hot = tf.one_hot(label, depth = 5) resized_image = tf.image.resize(image, [224, 224], method='nearest') return resized_image, label_one_hot def normalize(image, label): #Convert `image` from [0, 255] -> [0, 1.0] floats image = tf.cast(image, tf.float32) / 255\. return image, labeldecode_and_resize函数解析一张图像,将其调整为224x224像素,并同时对图像的标签进行独热编码。decode_and_resize然后返回图像和对应标签作为一个元组,以确保图像和标签始终一起保存。normalize函数将图像像素值除以255,以将像素范围调整为[0, 1.0]。尽管标签本身没有做任何处理,但仍有必要将图像和标签作为元组跟踪,以确保它们始终一起保存。 -
接下来,我们将使用以下辅助函数应用转换:
decoded = test_all_ds.map(decode_and_resize) normed = decoded.map(normalize) -
让我们将 TFRecord 转换为 NumPy 数组进行评分:
np_img_holder = np.empty((0, 224, 224,3), float) np_lbl_holder = np.empty((0, 5), int) for img, lbl in normed: r = img.numpy() # image value extracted rx = np.expand_dims(r, axis=0) lx = np.expand_dims(lbl, axis=0) np_img_holder = np.append(np_img_holder, rx, axis=0) np_lbl_holder = np.append(np_lbl_holder, lx, axis=0)现在,所有测试图像都已是 NumPy 格式,具有标准的
(224, 224, 3)维度,像素值介于0和1之间,图像是批处理的,标签也以批次形式存在。 -
我们现在需要提取真实标签,以便我们能够衡量预测的准确性:
actual = [] for i in range(len(np_lbl_holder)): class_key = np.argmax(np_lbl_holder[i]) actual.append(val_label_map.get(class_key))在前面的代码中,
actual是一个包含每个测试图像类名的列表。 -
我们可以使用以下代码检查 NumPy 数组
np_img_holder中的图像,产生如 图 7.1 中所示的图像:%matplotlib inline plt.figure() for i in range(len(np_img_holder)): plt.subplot(10, 5, i+1) plt.axis('off') imshow(np.asarray(np_img_holder[i]))在前面的代码片段中,我们遍历图像数组,将每张图像放入子图中,同时有 50 张图像(10 行,每行 5 个子图)。输出图像应以 10 行显示,每行有 5 张图像,正如 图 7.1 中所示。
对于单个测试文件评分,我们需要为样本添加一个维度。由于给定的图像形状为
(224, 224, 3),我们需要将其转换为(1, 224, 224, 3),以便模型能够接受并进行评分。这就是我们在将 TFRecord 转换为 NumPy 时使用np.expand_dim的原因。由于模型是为批量评分设计的,因此它期望四个维度,第一个维度是样本大小。
将预测映射到类别名称
从 TFRecord 中,我们需要创建一个反向查找字典,将概率映射回标签。换句话说,我们需要找到数组中最大概率所对应的索引。然后,我们将这个位置索引映射到花卉种类。
为了创建查找字典,我们将解析带有特征描述的 TFRecord 文件,提取标签索引和名称,如下代码所示:
feature_description = {
'image/channels' : tf.io.FixedLenFeature([], tf.int64),
'image/class/label' : tf.io.FixedLenFeature([], tf.int64),
'image/class/text' : tf.io.FixedLenFeature([], tf.string),
'image/colorspace' : tf.io.FixedLenFeature([], tf.string),
'image/encoded' : tf.io.FixedLenFeature([], tf.string),
'image/filename' : tf.io.FixedLenFeature([], tf.string),
'image/format' : tf.io.FixedLenFeature([], tf.string),
'image/height' : tf.io.FixedLenFeature([], tf.int64),
'image/width' : tf.io.FixedLenFeature([], tf.int64)
}
def _parse_function(example_proto):
return tf.io.parse_single_example(example_proto,
feature_description)
parsd_ds = test_all_ds.map(_parse_function)
val_label_map = {}
# getting label mapping
for image_features in parsd_ds.take(50):
label_idx = image_features['image/class/label'].numpy()
label_str = image_features['image/class/text'].numpy().decode()
if label_idx not in val_label_map:
val_label_map[label_idx] = label_str
在前面的代码中,我们使用 feature_description 解析 test_all_ds。一旦通过 _parse_function 解析,我们就遍历整个测试数据集。我们想要的信息可以在 image/class/label 和 image/class/text 中找到。
我们还可以检查 val_label_map:
{4: 'tulips', 3: 'dandelion', 1: 'sunflowers', 2: 'daisy', 0: 'roses'}
这是一个查找表,用于将索引映射到明文名称。
使用混合量化模型进行评分
与减少的float16模型一样,我们希望查看混合量化模型在测试数据上的表现。现在我们可以开始使用混合量化模型对测试图像进行评分:
-
我们将按常规步骤加载模型并分配张量,代码如下:
interpreter = tf.lite.Interpreter(model_path=str(tgt)) interpreter.allocate_tensors()现在,混合量化模型已加载。
-
为了确定模型操作的张量的输入和输出形状,我们可以通过以下方式获取输入和输出张量:
input_details = interpreter.get_input_details() output_details = interpreter.get_output_details()在前面的代码中,
get_input_details和get_output_details方法将检索这些张量的详细信息,例如name、shape和数据类型,并分别将其存储在input_details和output_details中。
评分单张图像
我们将通过扩展图像的维度来评分单张图像,就像这是一批只有一张图像一样,将其传递给 TFLite 解释器,然后获取输出:
-
我们可以开始处理图像数组,并扩展其维度以形成批次:
input_data = np.array(np.expand_dims(np_img_holder[0], axis=0), dtype=np.float32) interpreter.set_tensor(input_details[0]['index'], input_data) interpreter.invoke() output_data = interpreter.get_tensor(output_details[0]['index']) print(output_data)前面的代码将图像扩展为批次维度,然后将其传递给解释器进行预测。前面代码的输出如下:
[[1.1874483e-04 1.3445899e-05 8.4869811e-07 2.8064751e-06 9.9986410e-01]]这些是每种花卉类型的概率。我们需要将概率最高的位置映射到其纯文本名称。这就是我们再次使用
lookup函数的地方。 -
我们使用一个辅助函数(
lookup)将概率转换为最可能的类别名称:def lookup(np_entry, dictionary): class_key = np.argmax(np_entry) return dictionary.get(class_key) lookup(output_data, val_label_map)输出如下:
'tulips'在
lookup函数中,NumPy 数组np_entry是我们模型的输出。它包含每个类别的概率。我们希望将数组中概率最高的位置索引映射到类别名称。为此,函数通过键将其映射到字典。在这个例子中,概率数组中具有最高概率的是最后一个位置(即位置4)。4被映射到tulips。
批量图像评分
目前,TFLite 模型中的批量评分是通过对单张图像的迭代评分过程来实现的。对于我们 50 张测试图像的示例,我们可以创建一个辅助函数来封装整个单图像评分过程,就像我们在前面“评分单张图像”部分中使用混合量化模型所做的那样:
-
我们将通过以下代码迭代整个数据集来评分批次:
def batch_predict(input_raw, input_tensor, output_tensor, dictionary): input_data = np.array(np.expand_dims(input_raw, axis=0), dtype=np.float32) interpreter.set_tensor(input_tensor[0]['index'], input_data) interpreter.invoke() interpreter_output = interpreter.get_tensor( output_tensor[0]['index']) plain_text_label = lookup(interpreter_output, dictionary) return plain_text_labelbatch_predict()函数将原始图像维度扩展为批次,然后将批量图像传递给解释器进行评分。解释器的输出将通过lookup函数映射为纯文本名称,并返回该纯文本作为预测标签。 -
然后,我们需要迭代我们的测试数据,以调用
batch_predict:batch_quantized_prediction = [] for i in range(sample_size): plain_text_label = batch_predict(np_img_holder[i], input_details, output_details, val_label_map) batch_quantized_prediction.append(plain_text_label)我们可以使用
accuracy_score函数在 sklearn 库中评估模型的准确性:accuracy=accuracy_score(actual, batch_quantized_prediction) print(accuracy)它的输出如下:
0.82这里的输出显示准确率也是 82%。如果重新训练模型,结果可能会有所不同,但根据我的经验,它与基础模型的准确度是相同的。
注意
预计你的模型准确度会略有不同于此处显示的名义值。每次训练基本模型时,模型的准确度可能不会完全相同。然而,它不应与名义值相差太远。影响模型准确度再现性的另一个因素是训练过程中使用的 epoch 数量;在本示例中,仅使用了五个 epoch 进行演示和教学目的。更多的训练 epoch 将使模型准确度的方差更小,结果更稳定。
到目前为止,我们已经学习了两种后训练量化技术,即减少float16量化和混合量化。两种技术都使 TFLite 模型比原始模型小得多。当模型部署在边缘设备或计算能力或功耗资源较低的设备时,这一点尤为重要。
在这两种策略中,我们量化了中间层,并保留了输入和输出层不变。因此,输入和输出层没有被量化,仍保持原始数据类型。然而,在一些专为加速和轻量化优化的设备上,如边缘 TPU 或只能处理整数操作的设备上,我们需要将输入和输出层量化为整数类型。
在接下来的部分中,我们将学习第三种量化策略——整数量化,它将精确地完成此任务。
将完整模型转换为整数量化模型
这种策略需要int8表示。这种量化策略将尝试为所有操作或算子使用int8表示。若无法使用int8,则这些操作将保持原始精度(换句话说,即float32)。
这种量化策略需要一些代表性数据。此数据代表了模型通常期望的数据类型,具体表现为数值范围。换句话说,我们需要为整数量化过程提供一些训练或验证数据。这可能是已使用的数据,如训练或验证数据的一个子集。通常,建议提供大约 100 个样本。我们将使用 80 个验证数据样本,因为在此情况下足够。
在这一部分,我们将构建一个使用预训练 ResNet 特征向量的模型,该特征向量来自 TensorFlow Hub。训练完成后,我们将再次使用交叉验证数据作为代表性数据集。这个数据集将帮助模型调整输入和输出层的参数为整数。
训练完整模型
我们将使用与github.com/PacktPublishing/learn-tensorflow-enterprise/tree/master/chapter_07/train_base_model/tf_datasets相同的花卉数据集。
这是你用于减少float16和混合量化的相同数据集。让我们开始吧:
-
和往常一样,我们首先导入库并加载数据集:
import tensorflow as tf import tensorflow_hub as hub import numpy as np import os import pathlib root_dir = '../train_base_model/tf_datasets/flower_photos' file_pattern = '{}/image_classification_builder-train*.tfrecord*'.format(root_dir) val_file_pattern = '{}/image_classification_builder-validation*.tfrecord*'.format(root_dir) file_list = tf.io.gfile.glob(file_pattern) all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(file_pattern)) val_file_list = tf.io.gfile.glob(val_file_pattern) val_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(val_file_pattern)) train_all_ds = tf.data.TFRecordDataset(all_files, num_parallel_reads=tf.data.experimental.AUTOTUNE) val_all_ds = tf.data.TFRecordDataset(val_all_files, num_parallel_reads=tf.data.experimental.AUTOTUNE)在前面的代码中,我们使用
tf.ioAPI 来封装文件路径和我们将使用的所有文件名,包括训练、验证和测试数据。一旦我们将文件路径进行编码,我们就使用tf.data.TFRecordDataset来引用这些文件。此过程适用于训练数据,通过train_all_ds引用,和验证数据,通过val_all_ds引用。 -
然后我们将需要以下辅助函数来解码和标准化图像,规范化像素值,并设置训练数据集:
def decode_and_resize(serialized_example): # resized image should be [224, 224, 3] and normalized to value range [0, 255] # label is integer index of class. parsed_features = tf.io.parse_single_example( serialized_example, features = { 'image/channels' : tf.io.FixedLenFeature([], tf.int64), 'image/class/label' : tf.io.FixedLenFeature([], tf.int64), 'image/class/text' : tf.io.FixedLenFeature([], tf.string), 'image/colorspace' : tf.io.FixedLenFeature([], tf.string), 'image/encoded' : tf.io.FixedLenFeature([], tf.string), 'image/filename' : tf.io.FixedLenFeature([], tf.string), 'image/format' : tf.io.FixedLenFeature([], tf.string), 'image/height' : tf.io.FixedLenFeature([], tf.int64), 'image/width' : tf.io.FixedLenFeature([], tf.int64) }) image = tf.io.decode_jpeg(parsed_features[ 'image/encoded'], channels=3) label = tf.cast(parsed_features['image/class/label'], tf.int32) label_txt = tf.cast(parsed_features[ 'image/class/text'], tf.string) label_one_hot = tf.one_hot(label, depth = 5) resized_image = tf.image.resize(image, [224, 224], method='nearest') return resized_image, label_one_hot def normalize(image, label): #Convert `image` from [0, 255] -> [0, 1.0] floats image = tf.cast(image, tf.float32) / 255\. + 0.5 return image, labeldecode_and_resize函数解析图像,将其调整为224x224像素,同时对图像的标签进行独热编码。decode_and_resize然后返回图像和相应的标签作为一个元组,这样图像和标签始终保持在一起。normalize函数通过255来除以图像像素值,从而将像素范围调整为[0, 1.0]。尽管对标签没有进行任何处理,但有必要将图像和标签保持为元组,以确保它们始终在一起。 -
现在我们需要定义一个函数来打乱和获取训练数据集。以下是实现此功能的函数:
def prepare_for_training(ds, cache=True, shuffle_buffer_size=1000): # This is a small dataset, only load it once, and # keep it in memory. # use `.cache(filename)` to cache preprocessing work # for datasets that don't fit in memory. if cache: if isinstance(cache, str): ds = ds.cache(cache) else: ds = ds.cache() ds = ds.shuffle(buffer_size=shuffle_buffer_size) # Repeat forever ds = ds.repeat() ds = ds.batch(32) # `prefetch` lets the dataset fetch batches in the # background while the model is training. AUTOTUNE = tf.data.experimental.AUTOTUNE ds = ds.prefetch(buffer_size=AUTOTUNE) return ds此函数返回一个附带有洗牌、重复、批量和预取操作的数据集。这是一种准备数据集以进行训练的标准方法。
-
现在我们可以对训练数据集中的每个元素应用以下步骤:
# perform data engineering dataset = train_all_ds.map(decode_and_resize) val_dataset = val_all_ds.map(decode_and_resize)所以现在,
decode_and_resize被应用到train_all_ds和val_all_ds中的每张图像。得到的数据集分别为dataset和val_dataset。 -
我们还需要对验证数据集进行标准化,并最终确定训练数据集,以便进行训练过程:
# Create dataset for training run BATCH_SIZE = 32 VALIDATION_BATCH_SIZE = 40 dataset = dataset.map(normalize, num_parallel_calls=tf.data.experimental.AUTOTUNE) val_dataset = val_dataset.map(normalize, num_parallel_calls=tf.data.experimental.AUTOTUNE) val_ds = val_dataset.batch(VALIDATION_BATCH_SIZE) AUTOTUNE = tf.data.experimental.AUTOTUNE train_ds = prepare_for_training(dataset)在前面的代码中,我们使用
map函数将decode_and_resize函数应用于数据集中的每张图片。对于训练数据集,我们还应用prepare_for_training来预取数据集并用于数据处理。 -
现在我们将设置交叉验证的参数:
NUM_CLASSES = 5 IMAGE_SIZE = (224, 224) train_sample_size=0 for raw_record in train_all_ds: train_sample_size += 1 print('TRAIN_SAMPLE_SIZE = ', train_sample_size) validation_sample_size=0 for raw_record in val_all_ds: validation_sample_size += 1 print('VALIDATION_SAMPLE_SIZE = ', validation_sample_size) STEPS_PER_EPOCHS = train_sample_size // BATCH_SIZE VALIDATION_STEPS = validation_sample_size // VALIDATION_BATCH_SIZE在前面的代码中,我们将类别数量和图像大小设置为变量,并传递给模型训练过程。然后我们确定每个训练轮次和交叉验证的步数。
输出应如下所示:
TRAIN_SAMPLE_SIZE = 3540 VALIDATION_SAMPLE_SIZE = 80这表明我们有一个训练数据样本大小为
3540,而交叉验证数据样本大小为80。 -
现在我们将使用以下代码构建模型:
model = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)), hub.KerasLayer( https://tfhub.dev/google/imagenet/resnet_v1_101/feature_vector/4', trainable=False), tf.keras.layers.Dense(NUM_CLASSES, activation='softmax', name = 'custom_class') ]) model.build([None, 224, 224, 3]) model.compile( optimizer=tf.keras.optimizers.SGD(lr=0.005, momentum=0.9), loss=tf.keras.losses.CategoricalCrossentropy( from_logits=True, label_smoothing=0.1), metrics=['accuracy']) model.summary()在前面的代码中,我们使用 TensorFlow Hub 的 ResNet 特征向量作为中间层来构建和编译模型,输出是一个分类层,由一个具有五个输出的全连接层表示,每个输出节点提供一种五种花卉类型的概率。
这是模型摘要,它由一个来自resnet_v1_101特征向量的层组成,后面跟着一个分类头,如图 7.3所示:
![图 7.3 – 花卉类型分类模型摘要]()
图 7.3 – 用于花卉类型分类的模型总结
然后,我们将使用
fitAPI 来训练这个模型,利用提供的训练数据和交叉验证数据。 -
模型的权重和偏置结果保存在
checkpoint_prefix目录中。这是我们开始训练模型以识别五种不同花卉图像的方式:checkpoint_prefix = os.path.join('trained_resnet_vector', 'train_ckpt_{epoch}') callbacks = [ tf.keras.callbacks.ModelCheckpoint( filepath=checkpoint_prefix, save_weights_only=True)] model.fit( train_ds, epochs=3, steps_per_epoch=STEPS_PER_EPOCHS, validation_data=val_ds, validation_steps=VALIDATION_STEPS, callbacks=callbacks)在前面的代码中,调用了
fitAPI 来训练模型。train_ds和val_ds分别是训练数据和交叉验证数据。在每个 epoch 结束时,权重和偏置会作为检查点进行存储。这是通过回调函数来指定的。为了节省训练时间,我们将只训练三轮。 -
接下来,我们将使用以下代码行来保存模型:
saved_model_path = os.path.join(root_dir, 'custom_cnn/full_resnet_vector_saved_model') tf.saved_model.save(model, saved_model_path)我们可以使用以下命令检查权重矩阵文件,以了解模型的大小:
SavedModel format. In order to properly quantize the input and output layers, we need to provide some typical data. We will use the validation data, which contains 80 samples of 5 classes of flower images: -
让我们对验证图像进行标准化和归一化处理:
decoded = val_all_ds.map(decode_and_resize) normed = decoded.map(normalize) -
接下来,我们通过一个维度扩展来批处理图像。这个额外的维度是为可变批次大小设计的:
np_img_holder = np.empty((0, 224, 224,3), float) np_lbl_holder = np.empty((0, 5), int) for img, lbl in normed: r = img.numpy() rx = np.expand_dims(r, axis=0) lx = np.expand_dims(lbl, axis=0) np_img_holder = np.append(np_img_holder, rx, axis=0) np_lbl_holder = np.append(np_lbl_holder, lx, axis=0)图像现在通过一个维度扩展,表示第一个维度保存的是图像的数量,即图像批次的大小,接着对标准化后的图像进行迭代。每当我们遍历一张图像时,我们将图像值作为 NumPy 数组和相应标签一起捕捉,并分别附加到
np_img_holder和np_lbl_holder。 -
现在我们已经将图像转化为 NumPy 数组,接下来我们需要构建一个生成器,将这些代表性数据输入到转换过程中:
def data_generator(): for input_tensor in tf.data.Dataset.from_tensor_slices(np_img_holder.astype(np.float32)).batch(1).take(sample_size): yield [input_tensor]我们需要指定一个生成器函数,在转换过程中流式传输代表性数据。这个过程通过
data_generator函数来完成。该函数调用了一个生成器,用于流式传输 NumPy 数组。 -
让我们确认我们的样本大小:
sample_size = 0 for raw_record in val_all_ds: sample_size += 1 print('Sample size: ', sample_size)上面
print语句的输出如下:Sample size: 80上面的代码会遍历一个验证数据集,并在
for循环中跟踪样本计数。每次遇到一张图像时,计数器(sample_size,初始化为0)会增加 1。目前,这是了解数据集中样本大小的唯一方法。我们刚刚确认了验证数据集中有 80 个样本。 -
现在我们可以开始转换过程了:
converter = tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations = [tf.lite.Optimize.DEFAULT] converter.representative_dataset = data_generator在前面的代码中,我们像在混合量化中一样设置了转换器实例和优化器,然后为代表性数据集设置了一个数据生成器对象。
-
如果有任何操作未能量化,我们还希望抛出错误标志:
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]在前面的代码中,我们为模型设置的支持数据类型是 8 位整数(
INT8)。 -
现在,我们指定输入和输出张量的数据类型为
INT8:converter.inference_input_type = tf.uint8 converter.inference_output_type = tf.uint8 tflite_model_quant = converter.convert()现在,模型已经转换为整数量化模型。该模型期望输入数据类型为 8 位整数(
INT8),并将输出 8 位整数(INT8)的数据类型。 -
一旦前面的代码执行完毕,我们可以检查并验证现在与输入和输出层关联的数据类型,应该为无符号
INT8:interpreter = tf.lite.Interpreter(model_content=tflite_model_quant) input_type = interpreter.get_input_details()[0]['dtype'] print('input: ', input_type) output_type = interpreter.get_output_details()[0]['dtype'] print('output: ', output_type)在前面的代码中,我们首先需要获取 TFLite 模型的解释器接口。解释器对象是 TFLite 模型中执行推理的组件。它有像
get_input_details和get_output_details这样的方法,帮助我们查看模型在推理过程中预期的数据类型。以下是前面代码的输出:
input: <class 'numpy.uint8'> output: <class 'numpy.uint8'>该模型期望输入数据类型为 8 位整数(
INT8),并将输出数据类型设为 8 位整数(INT8)。 -
现在我们可以保存量化后的模型:
tflite_models_dir = 'quantized_resnet_vector/tflite_int8_model' to_save_tflite_model_dir = os.path.join(root_dir, tflite_models_dir) saved_tflite_models_dir = pathlib.Path(to_save_tflite_model_dir) saved_tflite_models_dir.mkdir(exist_ok=True, parents=True) tgt = pathlib.Path(to_save_tflite_model_dir, 'converted_model_reduced.tflite') tgt.write_bytes(tflite_model_quant)现在,借助前面的代码,我们设置了一个目录路径并将路径编码为一个
string。这个字符串表示我们将要写入整数量化模型的路径。最后,write_bytesAPI 完成写入过程,并将我们的整数量化模型保存到字符串tflite_models_dir所定义的路径中。这显示模型的大小如下:
44526000前面的输出显示我们的整数量化模型大约为 44 MB。
接下来,我们将通过对测试数据进行评分,看看这个模型的表现如何。
使用整数量化模型进行评分
对于评分,我们需要准备测试数据集和一个查找表,将模型输出映射到类名。我们的测试数据集包含作为索引编码的标签及其相应的类名。因此,我们将使用测试数据集中的标签和类名作为实际值。然后,将其与模型的预测结果进行比较。
准备用于评分的测试数据集
正如我们对减少的 float16 和混合量化模型所做的那样,我们希望看看整数量化模型在测试数据上的表现。现在我们可以开始使用整数量化模型对测试图像进行评分的过程:
-
我们将通过加载 TFRecord 测试数据集来进行:
test_pattern = '{}/image_classification_builder-test.tfrecord*'.format(root_dir) test_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(test_pattern)) test_all_ds = tf.data.TFRecordDataset(test_all_files, num_parallel_reads=tf.data.experimental.AUTOTUNE)在前面的代码中,我们使用
tf.ioAPI 封装文件路径和我们将使用的所有文件名,也就是测试数据。获取文件路径编码后,我们使用tf.data.TFRecordDatasedt来引用数据。这个过程是针对测试数据进行的,数据通过test_all_ds来引用。 -
接下来,我们可以验证样本大小:
sample_size = 0 for raw_record in test_all_ds: sample_size += 1 print('Sample size: ', sample_size)这将显示样本大小为
50。前面的代码通过遍历验证数据集,并在for循环中跟踪样本数量。每当遇到一个图像时,计数器(sample_size,初始化为0)就会增加 1。目前,这是了解数据集中样本大小的唯一方法。我们刚刚确认验证数据集中有 80 个样本。 -
由于我们的模型已经量化为处理整数运算,我们不希望将像素值标准化为浮点数值。我们只需要标准化图像的大小:
decoded = test_all_ds.map(decode_and_resize)然后,我们将 TFRecord 转换为图像数据和标签的 NumPy 数组。
-
我们还需要扩展数据的维度,以处理图像的批量数据:
np_img_holder = np.empty((0, 224, 224,3), float) np_lbl_holder = np.empty((0, 5), int) for img, lbl in decoded: r = img.numpy() rx = np.expand_dims(r, axis=0) lx = np.expand_dims(lbl, axis=0) np_img_holder = np.append(np_img_holder, rx, axis=0) np_lbl_holder = np.append(np_lbl_holder, lx, axis=0)现在,图像的维度被扩展了一个,表示第一维存储的是图像的数量,即图像批次的大小,然后对标准化后的图像进行迭代。每当我们遍历每一张图像时,我们将图像的值捕捉为 NumPy 数组及其对应的标签,并分别附加到
np_img_holder和np_lbl_holder中。 -
为了创建一个查找字典,将标签索引映射到类别名称,我们可以遍历 TFRecord 数据集来创建一个字典
val_label_map,但首先,我们需要了解如何解析 TFRecord 数据集。这意味着我们需要正确捕捉 TFRecord 数据集中的张量。因此,我们需要使用以下的feature_description:feature_description = { 'image/channels' : tf.io.FixedLenFeature([], tf.int64), 'image/class/label' : tf.io.FixedLenFeature([], tf.int64), 'image/class/text' : tf.io.FixedLenFeature([], tf.string), 'image/colorspace' : tf.io.FixedLenFeature([], tf.string), 'image/encoded' : tf.io.FixedLenFeature([], tf.string), 'image/filename' : tf.io.FixedLenFeature([], tf.string), 'image/format' : tf.io.FixedLenFeature([], tf.string), 'image/height' : tf.io.FixedLenFeature([], tf.int64), 'image/width' : tf.io.FixedLenFeature([], tf.int64) }Thefeature_description在前面的代码中是一个键值对集合。每一对键值对定义了一段表示为张量的元数据:def _parse_function(example_proto): return tf.io.parse_single_example(example_proto, feature_description) parsd_ds = test_all_ds.map(_parse_function)前面的代码展示了如何使用提供的
feature_description解析test_all_ds。结果是一个已解析的数据集(parsd_ds),其中包含所有定义和解析的必要张量:val_label_map = {} # getting label mapping for image_features in parsd_ds.take(30): label_idx = image_features[ 'image/class/label'].numpy() label_str = image_features[ 'image/class/text'].numpy().decode() if label_idx not in val_label_map: val_label_map[label_idx] = label_str我们现在需要找出数据集如何为类别标签分配索引。做到这一点的一种方式是遍历整个数据集或其部分。在每次迭代时,我们捕捉标签索引和对应的明文标签,并将其更新为字典中的键值对,例如
val_label_map。这在前面的代码中有展示。 -
我们可以通过在笔记本单元中输入
val_label_map来检查字典:val_label_map你可能会发现
val_label_map是一个类似于以下内容的字典:{0: 'roses', 1: 'sunflowers', 2: 'daisy', 3: 'dandelion', 4: 'tulips'}键是花卉类别的索引,值是花卉类别的明文名称。
-
我们将创建一个帮助函数来处理查找:
def lookup(np_entry, dictionary): class_key = np.argmax(np_entry) return dictionary.get(class_key)在
lookup函数中,NumPy 数组np_entry是我们模型的输出。它包含每个类别的概率。我们希望将数组中概率最高的位置索引映射到类别名称。为此,函数通过键将其映射到字典中。 -
接下来,我们创建一个包含真实花卉类别名称的列表:
actual = [] for i in range(len(np_lbl_holder)): class_key = np.argmax(np_lbl_holder[i]) actual.append(val_label_map.get(class_key))我们可以创建一个表,将标签的整数值映射到对应的明文名称。在前面的代码中,我们首先设置一个空列表
actual,然后使用for循环遍历整个标签持有者np_lbl_holder。接下来的步骤是找到该记录中最大值的位置,并将其赋值给class_key。class_key是用于查找val_label_map的索引,它将该键映射到相应的明文名称。然后,明文名称被添加到actual中。然后,for循环将继续处理np_lbl_holder中找到的下一个记录。
对批量图像进行评分
批量评分需要一个辅助函数。这与我们在混合型和减少的float16量化模型中使用的类似。唯一的区别在于 NumPy 数组维度扩展的数据类型。由于我们使用的是整数量化构建的模型,因此需要将数据类型转换为无符号 8 位整数(uint8):
-
这是一个将输入 NumPy 数组视为无符号 8 位整数(
uint8)的batch_predict函数:def batch_predict(input_raw, input_tensor, output_tensor, dictionary): input_data = np.array(np.expand_dims(input_raw, axis=0), dtype=np.uint8) interpreter.set_tensor(input_tensor['index'], input_data) interpreter.invoke() interpreter_output = interpreter.get_tensor( output_tensor['index']) plain_text_label = lookup(interpreter_output, dictionary) return plain_text_label这就结束了
batch_predict函数。该函数接收input_raw数组,并使用我们的解释器对其进行评分。解释器的输出随后通过lookup函数映射到一个明文标签。 -
现在让我们加载整数量化模型,并设置输入和输出张量:
interpreter = tf.lite.Interpreter(model_path=str(tgt)) interpreter.allocate_tensors() # Get input and output tensors. input_details = interpreter.get_input_details()[0] output_details = interpreter.get_output_details()[0]在前面的代码中,我们初始化了量化模型并为输入张量分配了内存,按照该模型的要求。
get_input_details和get_output_details方法将获取这些张量的详细信息,例如名称、形状和数据类型。 -
然后,我们可以进行批量预测:
batch_quantized_prediction = [] for i in range(sample_size): plain_text_label = batch_predict(np_img_holder[i], input_details, output_details, val_label_map) batch_quantized_prediction.append(plain_text_label)在前面的代码中,我们遍历了测试图像,对其进行评分,并将结果存储在名为
batch_quantized_prediction的列表中。 -
我们可以使用
sklearn的accuracy_score来计算准确度:from sklearn.metrics import accuracy_score accuracy_score(actual, batch_quantized_prediction)上述函数基本上是将
actual列表与batch_quantized_prediciton列表进行比较。在这个特定情况下,准确度如下:
0.86注意
预计您的模型准确性将略有不同于此处打印的名义值。每次训练基础模型时,模型的准确性都不会完全相同。然而,它不应该与名义值相差太多。影响模型准确性可重复性的另一个因素是训练时使用的 epochs 数量;在此示例中,仅使用了五个 epochs,目的是为了演示和教学。如果增加训练 epochs,您将获得更好、更稳定的模型准确性。
如果您重新训练了整个模型,结果可能会有所不同,但不应该与此值相差太多。此外,根据我对这些数据的经验,整数量化模型的性能与原始完整模型相当。前面的代码表明,我们的 TFLite 模型的表现与原始模型一样好。通过量化减少模型大小时,我们仍然能够保持模型的准确性。在这个例子中,模型的准确性不会因为模型变得更加紧凑而受到影响。
总结
在这一章中,我们学习了如何通过使训练后的模型变得更小、更紧凑来优化模型。因此,当我们将这些模型部署到各种硬件或资源受限的环境时,我们有了更多的灵活性。优化对于在资源受限的环境中部署模型非常重要,例如在计算、内存或电力资源有限的边缘设备上。我们通过量化实现了模型优化,在量化过程中,我们通过改变权重、偏置和激活层的数值类型,减少了模型的存储占用。
我们学习了三种量化策略:减少的float16、混合量化和整数量化。在这三种策略中,整数量化目前需要升级到 TensorFlow 2.3 版本。
选择量化策略取决于多个因素,如目标计算能力、资源、模型大小限制和模型精度。此外,还需要考虑目标硬件是否只支持整数运算(换句话说,是否支持 TPU)。如果是,那么整数量化显然是最佳选择。在所有的示例中,我们了解到模型优化策略不会影响模型的精度。量化后,模型的大小是原来的一个小分数。这证明了模型优化的价值,特别是在部署场景需要高效利用计算和电力资源时。
在下一章,我们将更详细地探讨模型构建过程中的一些常见实践。这些实践包括数据摄取管道设计以及如何避免模型过拟合。
第八章:
第九章:模型训练和性能的最佳实践
为了使一个监督式机器学习模型得到良好的训练,需要大量的训练数据。在本章中,我们将查看处理输入数据的一些常见示例和模式。我们将特别学习如何访问训练数据,不论其大小,并使用这些数据训练模型。之后,我们将看看有助于防止过拟合的正则化技术。拥有大量的训练数据并不能保证得到一个良好的训练模型。为了防止过拟合,我们可能需要在训练过程中应用各种正则化技术。我们将逐步探讨这些技术,从典型的 Lasso(L1)、Ridge(L2)和弹性网正则化开始,然后深入到一种现代的正则化技术——对抗正则化。通过这些技术的应用,我们可以在减少训练过程中的过拟合方面处于有利地位。
在谈到正则化时,并没有一种简单的方法来确定哪种方法最有效。这肯定取决于其他因素,例如特征的分布或稀疏性以及数据的体量。本章的目的是提供各种示例,并为您在自己的模型训练过程中提供多种选择。在本章中,我们将涵盖以下主题:
-
加载数据的输入处理
-
正则化以减少过拟合
加载数据的输入处理
我们通常看到的许多常见示例往往侧重于建模方面,比如如何使用 TensorFlow 构建一个深度学习模型,并结合不同的层次和模式。在这些示例中,所使用的数据几乎总是直接加载到运行时内存中。只要训练数据足够小,这没问题。但如果数据远远超过了您的运行时内存处理能力怎么办呢?解决方案是数据流技术。我们在前几章中已经使用了这种技术将数据输入到模型中,接下来我们将更详细地探讨数据流,并将其推广到更多的数据类型。
数据流技术非常类似于 Python 生成器。数据是按批次输入到模型训练过程中,也就是说,所有数据并不会一次性发送。在本章中,我们将使用一个花卉图像数据的示例。尽管这些数据本身并不大,但它在教学和学习方面是一个非常方便的工具。该数据集是多类别的,并包含不同大小的图像。这反映了我们在实际应用中通常需要处理的情况,即可用的训练图像可能是众包的,或提供的尺度和尺寸各不相同。此外,模型训练过程的前端还需要一个高效的数据摄取工作流。
使用生成器
在生成器方面,TensorFlow 现在提供了一个非常方便的 ImageDataGenerator API,极大简化并加速了代码开发过程。根据我们在使用预训练模型进行图像分类的经验,我们发现通常需要对图像尺寸(以像素数衡量的高度和宽度)进行标准化,并将图像像素值归一化到特定范围内(从 [0,255] 到 [0,1])。
ImageDataGenerator API 提供了可选的输入参数,使得这些任务几乎成为常规操作,并减少了编写自定义函数来执行标准化和归一化的工作。那么,接下来我们来看看如何使用这个 API:
-
组织原始图像。让我们首先设置我们的图像集合。为了方便起见,我们将直接使用
tf.kerasAPI 提供的花卉图像:import tensorflow as tf import tensorflow_hub as hub data_dir = tf.keras.utils.get_file( 'flower_photos', 'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz', untar=True)在前面的代码中,我们使用
tf.kerasAPI 下载五种花卉类型的图像。 -
接下来,我们将使用
flow_from_directory设置ImageDataGenerator和流对象。在这个步骤中,我们定义了几个操作:a. 图像像素强度被缩放到 [
0,255] 范围内,并且伴随有交叉验证分数。ImageDataGeneratorAPI 提供了可选的输入参数,包括重新缩放和validation_split。这些参数需要以字典格式提供。因此,我们可以将重缩放(归一化)因子和交叉验证的比例一起组织到datagen_kwargs中。b. 图像的高度和宽度都被重新格式化为
224像素。flow_from_directoryAPI 包含可选的target_size、batch_size和interpolation参数。这些参数以字典格式设计。我们可以使用这些输入参数来设置图像尺寸标准化、批量大小和重采样插值算法在dataflow_kwargs中。c. 前述设置传递给生成器实例。然后,我们将这些设置传递给
ImageDataGenerator和flow_from_directory:pixels =224 BATCH_SIZE = 32 IMAGE_SIZE = (pixels, pixels) NUM_CLASSES = 5 datagen_kwargs = dict(rescale=1./255, validation_split=.20) dataflow_kwargs = dict(target_size=IMAGE_SIZE, batch_size=BATCH_SIZE, interpolation="bilinear") valid_datagen = tf.keras.preprocessing.image.ImageDataGenerator( **datagen_kwargs) valid_generator = valid_datagen.flow_from_directory( data_dir, subset="validation", shuffle=False, **dataflow_kwargs) train_datagen = valid_datagen train_generator = train_datagen.flow_from_directory( data_dir, subset="training", shuffle=True, **dataflow_kwargs)上述代码展示了创建图像生成器的典型工作流,将训练数据传递到模型中。定义了两个字典并存储我们需要的参数。接着调用了
ImageDataGeneratorAPI,随后调用flow_from_directoryAPI。对于训练数据,流程也会重复进行。最后,我们通过train_generator和valid_generator设置了一个用于训练和交叉验证数据的摄取工作流。 -
检索标签映射。由于我们使用
ImageDataGenerator创建训练数据管道,我们也可以使用它来检索图像标签:labels_idx = (train_generator.class_indices) idx_labels = dict((v,k) for k,v in labels_idx.items()) print(idx_labels)在前面的代码中,
idx_labels是一个字典,将分类模型的输出(即索引)映射到flower类。以下是idx_labels:{0: 'daisy', 1: 'dandelion', 2: 'roses', 3: 'sunflowers', 4: 'tulips'}由于这是一个多类分类问题,我们的模型预测将是一个包含五个概率的数组。因此,我们需要获取概率最高类别的位置,然后使用
idx_labels将该位置映射到相应类别的名称。 -
构建并训练模型。这个步骤与我们在前一章第七章中执行的相同,模型优化,在其中我们将通过迁移学习构建一个模型。选择的模型是 ResNet 特征向量,最终的分类层是一个包含五个节点的密集层(
NUM_CLASSES定义为5,如步骤 2所示),这五个节点输出每个类别的概率:mdl = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)), hub.KerasLayer("https://tfhub.dev/google/imagenet/resnet_v1_101/feature_vector/4", trainable=False), tf.keras.layers.Dense(NUM_CLASSES, activation='softmax', name = 'custom_class') ]) mdl.build([None, 224, 224, 3]) mdl.compile( optimizer=tf.keras.optimizers.SGD(lr=0.005, momentum=0.9), loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True, label_smoothing=0.1), metrics=['accuracy']) steps_per_epoch = train_generator.samples // train_generator.batch_size validation_steps = valid_generator.samples // valid_generator.batch_size mdl.fit( train_generator, epochs=5, steps_per_epoch=steps_per_epoch, validation_data=valid_generator, validation_steps=validation_steps)上述代码展示了通过训练设置模型架构的基本流程。我们首先使用
tf.keras的顺序 API 构建了一个mdl模型。一旦指定了loss函数和优化器,就编译了模型。由于我们希望将交叉验证作为训练流程的一部分,我们需要设置step_per_epoch,即生成器为每个 epoch 生成的总数据批次数。这个过程会为交叉验证数据重复执行。然后,我们调用 Fit API 启动五个 epoch 的训练过程。
上述步骤展示了如何从ImageDataGenerator开始,构建一个将图像数据从图像目录通过flow_from_directory流入的管道,我们还能够处理图像标准化和归一化过程,作为输入参数。
TFRecord 数据集 – 数据摄取管道
训练过程中,另一种将训练数据流入模型的方式是通过 TFRecord 数据集。TFRecord 是一种协议缓冲格式。以这种格式存储的数据可以在Python、Java和C++中使用。在企业或生产系统中,这种格式可能提供灵活性,并促进数据在不同应用程序中的重用。TFRecord 的另一个注意事项是,如果你希望使用 TPU 作为计算目标,并希望使用管道摄取训练数据,那么 TFRecord 就是实现这一目标的方式。目前,TPU 不支持生成器。因此,通过管道流式传输数据的唯一方法是使用 TFRecord。再次强调,实际上该数据集的大小并不需要 TFRecord,这仅用于学习目的。
我们将从已经准备好的 TFRecord 数据集开始。它包含了与前一节中看到的相同的花卉图像和类别。此外,这个 TFRecord 数据集已被划分为训练、验证和测试数据集。该 TFRecord 数据集可以在本书的 GitHub 仓库中找到。你可以使用以下命令克隆该仓库:
git clone https://github.com/PacktPublishing/learn-tensorflow-enterprise.git
一旦该命令完成,请进入以下路径:
learn-tensorflow-enterprise/tree/master/chapter_07/train_base_model/tf_datasets/flower_photos
你将看到以下 TFRecord 数据集:
image_classification_builder-train.tfrecord-00000-of-00002
image_classification_builder-train.tfrecord-00001-of-00002
image_classification_builder-validation.tfrecord-00000-of-00001
image_classification_builder-test.tfrecord-00000-of-00001
记下这些数据集存储的文件路径。
我们将把这个路径称为<PATH_TO_TFRECORD>。这可以是你本地系统中的路径,或者是你上传并挂载这些 TFRecord 文件的任何云端笔记本环境中的路径:
-
设置文件路径。如你所见,在这个 TFRecord 集合中,有多个部分(两个)
train.tfrecord。我们将使用通配符(*)符号来表示遵循相同命名模式的多个文件名。我们可以使用glob来跟踪模式,将其传递给list_files以创建文件列表,然后让TFRecordDataset创建数据集对象。 -
识别并编码文件名约定。我们希望拥有一个能够处理数据导入过程的管道。因此,我们必须创建变量来保存文件路径和命名约定:
import tensorflow as tf import tensorflow_hub as hub import tensorflow_datasets as tfds root_dir = '<PATH_TO_TFRECORD>' train_file_pattern = "{}/image_classification_builder-train*.tfrecord*".format(root_dir) val_file_pattern = "{}/image_classification_builder-validation*.tfrecord*".format(root_dir) test_file_pattern = "{}/image_classification_builder-test*.tfrecord*".format(root_dir)在这里,我们将训练、验证和测试数据的文件路径以文本字符串的形式编码到
train_file_pattern、val_file_pattern和test_file_pattern变量中。注意,我们使用了通配符操作符*来处理多个文件部分(如果有的话)。这是实现数据导入管道可扩展性的一个重要方法。无论有多少个文件,因为现在你有了一种通过路径模式找到所有文件的方法。 -
创建文件列表。为了创建一个能够处理多个部分 TFRecord 文件的对象,我们将使用
list_files来跟踪这些文件:train_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(train_file_pattern)) val_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(val_file_pattern)) test_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(test_file_pattern))在前面的代码中,我们使用
tf.ioAPI 引用了训练、验证和测试文件。这些文件的路径由train_file_pattern、val_file_pattern和test_file_pattern定义。 -
创建数据集对象。我们将使用
TFRecordDataset从训练、验证和测试列表对象创建数据集对象:train_all_ds = tf.data.TFRecordDataset(train_all_files, num_parallel_reads = AUTOTUNE) val_all_ds = tf.data.TFRecordDataset(val_all_files, num_parallel_reads = AUTOTUNE) test_all_ds = tf.data.TFRecordDataset(test_all_files, num_parallel_reads = AUTOTUNE)TFRecordDatasetAPI 读取由文件路径变量引用的TFRecord文件。 -
检查样本大小。到目前为止,尚无快速方法来确定每个 TFRecord 中的样本大小。唯一的方法是通过迭代它:
print("Sample size for training: {0}".format(sum(1 for _ in tf.data.TFRecordDataset(train_all_files))) ,'\n', "Sample size for validation: {0}".format(sum(1 for _ in tf.data.TFRecordDataset(val_all_files))) ,'\n', "Sample size for test: {0}".format(sum(1 for _ in tf.data.TFRecordDataset(test_all_files))))上面的代码打印并验证了我们每个 TFRecord 数据集中的样本大小。
输出应如下所示:
Sample size for training: 3540 Sample size for validation: 80 Sample size for test: 50由于我们能够统计 TFRecord 数据集中的样本数,我们知道我们的 TFRecord 数据管道已经正确设置。
TFRecord 数据集 - 特征工程与训练
当我们使用生成器作为数据导入管道时,生成器在训练过程中会负责批处理和数据与标签的匹配。然而,与生成器不同,为了使用 TFRecord 数据集,我们必须自己解析它并执行一些必要的特征工程任务,例如归一化和标准化。TFRecord 的创建者必须提供一个特征描述字典作为模板来解析样本。在这种情况下,提供了以下特征字典:
features = {
'image/channels' : tf.io.FixedLenFeature([], tf.int64),
'image/class/label' : tf.io.FixedLenFeature([], tf.int64),
'image/class/text' : tf.io.FixedLenFeature([], tf.string),
'image/colorspace' : tf.io.FixedLenFeature([], tf.string),
'image/encoded' : tf.io.FixedLenFeature([], tf.string),
'image/filename' : tf.io.FixedLenFeature([], tf.string),
'image/format' : tf.io.FixedLenFeature([], tf.string),
'image/height' : tf.io.FixedLenFeature([], tf.int64),
'image/width' : tf.io.FixedLenFeature([], tf.int64)
})
我们将通过以下步骤来解析数据集,执行特征工程任务,并提交数据集进行训练。这些步骤是在完成TFRecord 数据集 - 导入管道部分后进行的:
-
解析 TFRecord 并调整图像大小。我们将使用前面的字典来解析 TFRecord,以提取单张图像作为 NumPy 数组及其对应的标签。我们将定义一个
decode_and_resize函数来执行此操作:def decode_and_resize(serialized_example): # resized image should be [224, 224, 3] and # normalized to value range [0, 255] # label is integer index of class. parsed_features = tf.io.parse_single_example( serialized_example, features = { 'image/channels' : tf.io.FixedLenFeature([], tf.int64), 'image/class/label' : tf.io.FixedLenFeature([], tf.int64), 'image/class/text' : tf.io.FixedLenFeature([], tf.string), 'image/colorspace' : tf.io.FixedLenFeature([], tf.string), 'image/encoded' : tf.io.FixedLenFeature([], tf.string), 'image/filename' : tf.io.FixedLenFeature([], tf.string), 'image/format' : tf.io.FixedLenFeature([], tf.string), 'image/height' : tf.io.FixedLenFeature([], tf.int64), 'image/width' : tf.io.FixedLenFeature([], tf.int64) }) image = tf.io.decode_jpeg(parsed_features[ 'image/encoded'], channels=3) label = tf.cast(parsed_features[ 'image/class/label'], tf.int32) label_txt = tf.cast(parsed_features ['image/class/text'], tf.string) label_one_hot = tf.one_hot(label, depth = 5) resized_image = tf.image.resize(image, [224, 224], method='nearest') return resized_image, label_one_hotdecode_and_resize函数接受 TFRecord 格式的数据集,解析它,提取元数据和实际图像,然后返回图像及其标签。在该函数的更详细层面内,TFRecord 数据集使用
parsed_feature进行解析。这是我们从数据集中提取不同元数据的方法。图像通过decode_jpegAPI 进行解码,并调整为 224 x 224 像素。至于标签,它被提取并进行独热编码。最后,函数返回调整后的图像和相应的独热标签。 -
标准化像素值。我们还需要将像素值标准化到[
0,255]范围内。这里,我们定义了一个normalize函数来实现这一目标:def normalize(image, label): #Convert `image` from [0, 255] -> [0, 1.0] floats image = tf.cast(image, tf.float32) / 255. return image, label在这里,图像按像素进行重新缩放,范围为[
0,1.0],方法是将每个像素值除以255。结果被转换为float32,以表示浮动点值。该函数返回重新缩放后的图像及其标签。 -
执行这些函数。这些函数(
decode_and_resize和normalize)被设计为应用于 TFRecord 中的每个样本。我们使用map来完成这一任务:resized_train_ds = train_all_ds.map(decode_and_resize, num_parallel_calls=AUTOTUNE) resized_val_ds = val_all_ds.map(decode_and_resize, num_parallel_calls=AUTOTUNE) resized_test_ds = test_all_ds.map(decode_and_resize, num_parallel_calls=AUTOTUNE) resized_normalized_train_ds = resized_train_ds.map(normalize, num_parallel_calls=AUTOTUNE) resized_normalized_val_ds = resized_val_ds.map(normalize, num_parallel_calls=AUTOTUNE) resized_normalized_test_ds = resized_test_ds.map(normalize, num_parallel_calls=AUTOTUNE)在这里,我们对所有数据集应用
decode_and_resize,然后在像素级别对数据集进行标准化。 -
批量处理用于训练过程的数据集。对 TFRecord 数据集执行的最后一步是批量处理。我们将为此定义一些变量,并定义一个函数
prepare_for_model来进行批量处理:pixels =224 IMAGE_SIZE = (pixels, pixels) TRAIN_BATCH_SIZE = 32 # Validation and test data are small. Use all in a batch. VAL_BATCH_SIZE = sum(1 for _ in tf.data.TFRecordDataset(val_all_files)) TEST_BATCH_SIZE = sum(1 for _ in tf.data.TFRecordDataset(test_all_files)) def prepare_for_model(ds, BATCH_SIZE, cache=True, TRAINING_DATA=True, shuffle_buffer_size=1000): if cache: if isinstance(cache, str): ds = ds.cache(cache) else: ds = ds.cache() ds = ds.shuffle(buffer_size=shuffle_buffer_size) if TRAINING_DATA: # Repeat forever ds = ds.repeat() ds = ds.batch(BATCH_SIZE) ds = ds.prefetch(buffer_size=AUTOTUNE) return ds交叉验证和测试数据没有被分成批次。因此,整个交叉验证数据集是一个单独的批次,测试数据集也是如此。
prepare_for_model函数接受一个数据集,然后将其缓存在内存中并进行预取。如果此函数应用于训练数据,它还会无限次重复数据,以确保在训练过程中不会用完数据。 -
执行批量处理。使用
map函数来应用batching函数:NUM_EPOCHS = 5 SHUFFLE_BUFFER_SIZE = 1000 prepped_test_ds = prepare_for_model(resized_normalized_test_ds, TEST_BATCH_SIZE, False, False) prepped_train_ds = resized_normalized_train_ds.repeat(100).shuffle(buffer_size=SHUFFLE_BUFFER_SIZE) prepped_train_ds = prepped_train_ds.batch(TRAIN_BATCH_SIZE) prepped_train_ds = prepped_train_ds.prefetch(buffer_size = AUTOTUNE) prepped_val_ds = resized_normalized_val_ds.repeat(NUM_EPOCHS).shuffle(buffer_size=SHUFFLE_BUFFER_SIZE) prepped_val_ds = prepped_val_ds.batch(80) prepped_val_ds = prepped_val_ds.prefetch(buffer_size = AUTOTUNE)前面的代码设置了训练、验证和测试数据的批次。这些数据已经准备好可以输入到训练过程中。我们现在已经完成了数据输入管道。
-
构建和训练模型。这部分与前面的部分没有区别。我们将构建并训练一个与生成器中看到的架构相同的模型:
FINE_TUNING_CHOICE = False NUM_CLASSES = 5 IMAGE_SIZE = (224, 224) mdl = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,), name='input_layer'), hub.KerasLayer("https://tfhub.dev/google/imagenet/resnet_v1_101/feature_vector/4", trainable=FINE_TUNING_CHOICE, name = 'resnet_fv'), tf.keras.layers.Dense(NUM_CLASSES, activation='softmax', name = 'custom_class') ]) mdl.build([None, 224, 224, 3]) mdl.compile( optimizer=tf.keras.optimizers.SGD(lr=0.005, momentum=0.9), loss=tf.keras.losses.CategoricalCrossentropy( from_logits=True, label_smoothing=0.1), metrics=['accuracy']) mdl.fit( prepped_train_ds, epochs=5, steps_per_epoch=100, validation_data=prepped_val_ds, validation_steps=1)请注意,训练数据集和验证数据集分别作为
prepped_train_ds和prepped_val_ds传递给模型。在这方面,它与我们在训练时将生成器传递给模型的方式没有区别。然而,相较于生成器,我们在解析、标准化和规范化这些数据集方面所做的额外工作要复杂得多。
TFRecord 的好处在于,如果您有大型数据集,那么将其分成多个部分并将其存储为 TFRecord,将比使用生成器更快地将数据流入模型。此外,如果您的计算目标是 TPU,那么您不能使用生成器来流式传输训练数据;您将必须使用 TFRecord 数据集来流式传输训练数据到模型进行训练。
正则化
在训练过程中,模型正在学习找到最佳的权重和偏置集合,以最小化 loss 函数。随着模型架构变得更复杂或者简单地开始增加更多层次,模型正在装配更多的参数。尽管这可能有助于在训练期间产生更好的拟合,但使用更多的参数也可能导致过拟合。
在本节中,我们将深入探讨一些可以在 tf.keras API 中直接实现的正则化技术。
L1 和 L2 正则化
传统方法解决过拟合问题涉及在 loss 函数中引入惩罚项。这称为正则化。惩罚项直接与模型复杂性相关,主要由非零权重的数量决定。更具体地说,机器学习中通常使用三种传统的正则化类型:
loss函数与 L1 正则化:![]()
它使用权重 w 的绝对值之和,乘以用户定义的惩罚值 λ,来衡量复杂性(即模型拟合的参数数量表明其复杂性)。其思想是,使用的参数或权重越多,施加的惩罚就越高。我们希望用尽可能少的参数得到最佳模型。
loss函数与 L2 正则化:

它使用权重 w 的平方和,乘以用户定义的惩罚值 λ,来衡量复杂性。
loss函数与 L1 和 L2 正则化:

它使用 L1 和 L2 的组合来衡量复杂性。每个正则化项都有自己的惩罚因子。
(参考:pp. 38-39,Antonio Gulli 和 Sujit Pal,Deep Learning with Keras,Packt 2017,www.tensorflow.org/api_docs/python/tf/keras/regularizers/)
这些是模型层定义的关键字输入参数,包括密集或卷积层,例如 Conv1D、Conv2D 和 Conv3D:
-
kernel_regularizer:应用于权重矩阵的正则化器 -
bias_regularizer:应用于偏置向量的正则化器 -
activity_regularizer:应用于层输出的正则化器
(参考:p. 63,Antonio Gulli 和 Sujit Pal,Deep Learning with Keras,Packt 2017,www.tensorflow.org/api_docs/python/tf/keras/regularizers/Regularizer)
现在我们将看看如何实现其中一些参数。例如,我们将利用前一节中构建的模型架构,即 ResNet 特征向量层,后跟密集层作为分类头部:
KERNEL_REGULARIZER = tf.keras.regularizers.l2(l=0.1)
ACTIVITY_REGULARIZER = tf.keras.regularizers.L1L2(l1=0.1,l2=0.1)
mdl = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)),
hub.KerasLayer("https://tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4",trainable=FINE_TUNING_CHOICE),
tf.keras.layers.Dense(NUM_CLASSES
,activation='softmax'
,kernel_regularizer=KERNEL_REGULARIZER
,activity_regularizer =
ACTIVITY_REGULARIZER
,name = 'custom_class')
])
mdl.build([None, 224, 224, 3])
请注意,我们使用别名来定义我们感兴趣的正则化器。这将使我们能够调整决定正则化项如何惩罚潜在过度拟合的超参数(l1,l2):
KERNEL_REGULARIZER = tf.keras.regularizers.l2(l=0.1)
ACTIVITY_REGULARIZER = tf.keras.regularizers.L1L2(l1=0.1,l2=0.1)
然后在密集层定义中添加这些regularizer定义:
tf.keras.layers.Dense(NUM_CLASSES
,activation='softmax'
,kernel_regularizer=KERNEL_REGULARIZER
,activity_regularizer =
ACTIVITY_REGULARIZER
,name = 'custom_class')
这些是在前一节使用的代码中所需的唯一更改。
对抗正则化
一个有趣的技术被称为对抗学习于 2014 年出现(如果感兴趣,请阅读Goodfellow et al., 2014发表的开创性论文)。这个想法源于这样一个事实,即如果输入比预期稍微嘈杂,机器学习模型的准确性可能会大大降低,从而产生错误预测。这种噪声称为对抗扰动。因此,如果训练数据集增加了一些数据的随机变化,我们可以利用这种技术使我们的模型更加健壮。
TensorFlow 的AdversarialRegularization API 旨在补充tf.keras API 并简化模型构建和训练过程。我们将重新使用下载的 TFRecord 数据集作为原始训练数据。然后我们将对该数据集应用数据增强技术,最后我们将训练模型。为此,请按照以下步骤操作:
-
下载并解压训练数据(如果您在本章开头没有这样做)。您需要下载 flower_tfrecords.zip,即我们将从 Harvard Dataverse 使用的 TFRecord 数据集(
dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/1ECTVN)。将其放置在您打算使用的计算节点上。它可以是您的本地计算环境或云计算环境,如 Google AI 平台的 JupyterLab 或 Google Colab。下载后解压文件,并记下其路径。我们将称此路径为<PATH_TO_TFRECORD>。在此路径中,您将看到这些 TFRecord 数据集:image_classification_builder-train.tfrecord-00000-of-00002 image_classification_builder-train.tfrecord-00001-of-00002 image_classification_builder-validation.tfrecord-00000-of-00001 image_classification_builder-test.tfrecord-00000-of-00001 -
安装库。我们需要确保神经结构化学习模块在我们的环境中可用。如果您还没有这样做,应使用以下
pip命令安装此模块:!pip install --quiet neural-structured-learning -
为数据管道创建文件模式对象。有多个文件(两个)。因此,在数据摄取过程中,我们可以利用文件命名约定和通配符
*限定符:import tensorflow as tf import neural_structured_learning as nsl import tensorflow_hub as hub import tensorflow_datasets as tfds AUTOTUNE = tf.data.experimental.AUTOTUNE root_dir = './tfrecord-dataset/flowers' train_file_pattern = "{}/image_classification_builder-train*.tfrecord*".format(root_dir) val_file_pattern = "{}/image_classification_builder-validation*.tfrecord*".format(root_dir) test_file_pattern = "{}/image_classification_builder-test*.tfrecord*".format(root_dir)为了方便起见,TFRecord 文件的路径被指定为以下变量:
train_file_pattern、val_file_pattern和test_file_pattern。这些路径以文本字符串的形式表示。通配符*用于处理多个文件部分,以防有多个文件。 -
清点所有文件名。我们可以使用
globAPI 创建一个数据集对象,用于追踪文件的所有部分:train_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(train_file_pattern)) val_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(val_file_pattern)) test_all_files = tf.data.Dataset.list_files( tf.io.gfile.glob(test_file_pattern))在这里,我们使用
tf.ioAPI 引用前一步中指示的文件路径。tf.io的globAPI 所引用的文件名将通过tf.data的list_filesAPI 编码成文件名列表。 -
建立加载管道。现在我们可以通过
TFRecordDataset建立与数据源的引用:train_all_ds = tf.data.TFRecordDataset(train_all_files, num_parallel_reads = AUTOTUNE) val_all_ds = tf.data.TFRecordDataset(val_all_files, num_parallel_reads = AUTOTUNE) test_all_ds = tf.data.TFRecordDataset(test_all_files, num_parallel_reads = AUTOTUNE)在这里,我们使用
TFRecordDatasetAPI 从源数据创建各自的数据集。 -
为了检查我们是否完全看到了数据,我们将统计每个数据集中的样本大小:
train_sample_size = sum(1 for _ in tf.data.TFRecordDataset(train_all_files)) validation_sample_size = sum(1 for _ in tf.data.TFRecordDataset(val_all_files)) test_sample_size = sum(1 for _ in tf.data.TFRecordDataset(test_all_files)) print("Sample size for training: {0}".format(train_sample_size) ,'\n', "Sample size for validation: {0}".format(validation_sample_size) ,'\n', "Sample size for test: {0}".format(test_sample_size))目前,了解 TFRecord 文件中有多少样本的方法是通过遍历文件。在代码中:
sum(1 for _ in tf.data.TFRecordDataset(train_all_files))我们使用
for循环遍历数据集,并通过累加迭代次数来获得最终的样本大小。此编码模式也用于确定验证集和测试集的样本大小。然后,这些数据集的大小将存储为变量。上述代码的输出将如下所示:
Sample size for training: 3540 Sample size for validation: 80 Sample size for test: 50 -
关于数据转换,我们需要将所有图像转换为相同的大小,即高度为
224像素,宽度为224像素。每个像素的强度值应在[0,1]的范围内。因此,我们需要将每个像素的值除以255。我们需要以下两个函数来进行这些转换操作:def decode_and_resize(serialized_example): # resized image should be [224, 224, 3] and # normalized to value range [0, 255] # label is integer index of class. parsed_features = tf.io.parse_single_example( serialized_example, features = { 'image/channels' : tf.io.FixedLenFeature([], tf.int64), 'image/class/label' : tf.io.FixedLenFeature([], tf.int64), 'image/class/text' : tf.io.FixedLenFeature([], tf.string), 'image/colorspace' : tf.io.FixedLenFeature([], tf.string), 'image/encoded' : tf.io.FixedLenFeature([], tf.string), 'image/filename' : tf.io.FixedLenFeature([], tf.string), 'image/format' : tf.io.FixedLenFeature([], tf.string), 'image/height' : tf.io.FixedLenFeature([], tf.int64), 'image/width' : tf.io.FixedLenFeature([], tf.int64) }) image = tf.io.decode_jpeg(parsed_features[ 'image/encoded'], channels=3) label = tf.cast(parsed_features['image/class/label'], tf.int32) label_txt = tf.cast(parsed_features[ 'image/class/text'], tf.string) label_one_hot = tf.one_hot(label, depth = 5) resized_image = tf.image.resize(image, [224, 224], method='nearest') return resized_image, label_one_hotdecode_and_resize函数接受 TFRecord 格式的数据集,解析数据,提取元数据和实际图像,并返回图像及其标签。更具体地,在该函数内部,TFRecord 数据集通过parsed_feature进行解析。通过这种方式,我们提取数据集中的不同元数据。图像通过decode_jpegAPI 解码,并调整大小为224×224像素。至于标签,它会被提取并进行独热编码。 -
最后,函数返回调整大小后的图像及其对应的独热标签。
def normalize(image, label): #Convert `image` from [0, 255] -> [0, 1.0] floats image = tf.cast(image, tf.float32) / 255. return image, label这个函数接受一张 JPEG 图像,并将像素值(将每个像素值除以
255)归一化到[0,1.0]范围内,并将其转换为tf.float32表示浮动值。它返回归一化后的图像及其对应的标签。 -
执行数据转换。我们将使用
map函数将之前的转换操作应用到数据集中的每个元素:resized_train_ds = train_all_ds.map(decode_and_resize, num_parallel_calls=AUTOTUNE) resized_val_ds = val_all_ds.map(decode_and_resize, num_parallel_calls=AUTOTUNE) resized_test_ds = test_all_ds.map(decode_and_resize, num_parallel_calls=AUTOTUNE) resized_normalized_train_ds = resized_train_ds.map(normalize, num_parallel_calls=AUTOTUNE) resized_normalized_val_ds = resized_val_ds.map(normalize, num_parallel_calls=AUTOTUNE) resized_normalized_test_ds = resized_test_ds.map(normalize, num_parallel_calls=AUTOTUNE)在之前的代码中,我们将
decode_and_resize应用于每个数据集,然后通过对每个像素应用normalize函数来重新缩放数据集。 -
定义训练参数。我们需要指定数据集的批量大小,以及定义周期的参数:
pixels =224 IMAGE_SIZE = (pixels, pixels) TRAIN_BATCH_SIZE = 32 VAL_BATCH_SIZE = validation_sample_size TEST_BATCH_SIZE = test_sample_size NUM_EPOCHS = 5 SHUFFLE_BUFFER_SIZE = 1000 FINE_TUNING_CHOICE = False NUM_CLASSES = 5 prepped_test_ds = resized_normalized_test_ds.batch(TEST_BATCH_SIZE).prefetch(buffer_size = AUTOTUNE) prepped_train_ds = resized_normalized_train_ds.repeat(100).shuffle(buffer_size=SHUFFLE_BUFFER_SIZE) prepped_train_ds = prepped_train_ds.batch(TRAIN_BATCH_SIZE) prepped_train_ds = prepped_train_ds.prefetch(buffer_size = AUTOTUNE) prepped_val_ds = resized_normalized_val_ds.repeat(NUM_EPOCHS).shuffle(buffer_size=SHUFFLE_BUFFER_SIZE) prepped_val_ds = prepped_val_ds.batch(80) prepped_val_ds = prepped_val_ds.prefetch(buffer_size = AUTOTUNE)在前面的代码中,我们定义了设置训练过程所需的参数。数据集也会被批量化并提取出来以供使用。
现在我们已经构建了数据集管道,每次都会获取一批数据并输入到模型训练过程中。
-
构建你的模型。我们将使用 ResNet 特征向量来构建一个图像分类模型:
mdl = tf.keras.Sequential([ tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)), hub.KerasLayer("https://tfhub.dev/google/imagenet/resnet_v2_50/feature_vector/4",trainable=FINE_TUNING_CHOICE), tf.keras.layers.Dense(NUM_CLASSES, activation='softmax', name = 'custom_class') ]) mdl.build([None, 224, 224, 3])我们使用
tf.keras的顺序 API 来构建一个图像分类模型。首先使用输入层接受训练数据,尺寸为224乘224乘3像素。接着我们利用ResNet_V2_50的特征向量作为中间层。我们将其保持原样(trainable=FINE_TUNING_CHOICE。FINE_TUNING_CHOICE在前一步已设为False,如果你愿意,也可以将其设置为True,但这样会显著增加训练时间)。最后,输出层由一个具有五个节点的全连接层表示(NUM_CLASSES = 5)。每个节点表示各类花卉类型的概率值。到目前为止,还没有涉及到对抗正则化。从下一步开始,我们将通过构建一个配置对象来指定对抗训练数据并启动训练过程。
-
将训练样本转换为字典。对抗正则化的一个特殊要求是将训练数据和标签合并为一个字典,并将其流式传输到训练过程中。这可以通过以下函数轻松实现:
def examples_to_dict(image, label): return {'image_input': image, 'label_output': label}该函数接受图像和对应的标签,然后将其重新格式化为字典中的键值对。
-
将数据和标签集合转换为字典。对于批量数据集,我们可以再次使用
map函数,将examples_to_dict应用于数据集中的每个元素:train_set_for_adv_model = prepped_train_ds.map(examples_to_dict) val_set_for_adv_model = prepped_val_ds.map(examples_to_dict) test_set_for_adv_model = prepped_test_ds.map(examples_to_dict)在这段代码中,数据集中的每个样本也会被转换为字典。这是通过
map函数实现的。map函数将examples_to_dict函数应用到数据集中的每个元素(样本)上。 -
创建对抗正则化对象。现在我们准备创建一个指定对抗配置的
adv_config对象。然后我们将前一步创建的mdl基础模型与adv_config进行包装:adv_config = nsl.configs.make_adv_reg_config() adv_mdl = nsl.keras.AdversarialRegularization(mdl, label_keys=['label_output'], adv_config=adv_config)现在我们有了一个模型,
adv_mdl,它包含了由mdl定义的基础模型结构。adv_mdl包括了对抗配置adv_config的知识,该配置将在训练过程中用于生成对抗图像。 -
编译并训练模型。这部分与之前我们做的类似,训练基础模型时也是如此,唯一的不同在于输入数据集:
adv_mdl.compile(optimizer=tf.keras.optimizers.SGD(lr=0.005, momentum=0.9), loss=tf.keras.losses.CategoricalCrossentropy( from_logits=True, label_smoothing=0.1), metrics=['accuracy']) adv_mdl.fit( train_set_for_adv_model, epochs=5, steps_per_epoch=100, validation_data=val_set_for_adv_model, validation_steps=1)请注意,现在传递给
fit函数的训练输入是train_set_for_adv_model和val_set_for_adv_model,这两个数据集会将每个样本作为字典流式传输到训练过程中。
使用tf.keras和对抗性正则化 API 设置对抗性正则化并不需要很多工作。基本上,只需要额外一步将样本和标签重新格式化为字典。然后,我们使用nsl.keras.AdversarialRegularization API 包装我们的模型,该 API 封装了模型架构和对抗性正则化对象。这使得实现这种类型的正则化变得非常简单。
总结
本章介绍了一些增强和改善模型构建与训练过程的常见实践。在处理训练数据时,最常见的问题之一是以高效和可扩展的方式流式传输或获取训练数据。在本章中,你已经看到两种方法帮助你构建这样的数据获取管道:生成器和数据集。每种方法都有其优点和用途。生成器能够很好地管理数据转换和批处理,而数据集 API 则是为 TPU 目标而设计的。
我们还学习了如何使用传统的 L1 和 L2 正则化技术,以及一种现代的正则化技术——对抗性正则化,它适用于图像分类。对抗性正则化还会为你处理数据转换和增强,省去你生成噪声图像的麻烦。这些新的 API 和功能增强了 TensorFlow Enterprise 的用户体验,并帮助节省开发时间。
在下一章,我们将学习如何使用 TensorFlow Serving 服务 TensorFlow 模型。
第九章:
第十章:提供 TensorFlow 模型
现在,在学习了前面所有章节之后,您已经看到了TensorFlow Enterprise (TFE)模型构建过程的许多方面。现在是时候总结我们所做的工作,并看看如何服务我们构建的模型了。在本章中,我们将研究通过本地主机中的 RESTful API 提供 TensorFlow 模型的基础知识。开始的最简单方式是使用TensorFlow Serving (TFS)。TFS 是一个用于提供使用 TensorFlow 构建的机器学习模型的系统,尽管它目前尚未得到 TFE 的官方支持,但您会发现它适用于由 TFE 2 构建的模型。它可以作为服务器运行,也可以作为 Docker 容器运行。为了我们的方便起见,我们将使用 Docker 容器,因为这确实是开始使用 TFS 的最简单方式,无论您的本地环境如何,只要您有可用的 Docker 引擎。在本章中,我们将涵盖以下主题:
-
运行本地服务
-
了解使用 Docker 的 TFS
-
下载 TFS Docker 镜像
技术要求
要按照本章进行操作,并尝试此处的示例代码:https://github.com/PacktPublishing/learn-tensorflow-enterprise,您需要克隆此书的 GitHub 存储库,并导航到chapter_09文件夹。您可以使用以下命令克隆存储库:
git clone https://github.com/PacktPublishing/learn-tensorflow-enterprise.git
我们将从名为chapter_09的文件夹开始工作。在此文件夹中,有一个包含源代码的 Jupyter 笔记本。您还会在flowerclassifier/001目录中找到一个saved_model.pb文件,准备好供您使用。在raw_images目录中,您会找到一些用于测试的原始 JPG 图像。
运行本地服务
服务模型的先决条件是对模型结构及其资产(如权重和偏差矩阵)进行序列化。训练过的 TensorFlow 模型通常以SavedModel格式保存。SavedModel格式包含具有权重、偏差和计算操作的完整 TensorFlow 程序。这是通过低级tf.saved_model API 完成的。
通常,在使用 Fit 进行模型训练过程时,您最终会得到类似这样的东西:
mdl.fit(
train_dataset,
epochs=5, steps_per_epoch=steps_per_epoch,
validation_data=valid_dataset,
validation_steps=validation_steps)
在执行了上述代码之后,您将拥有一个名为mdl的模型对象,可以通过以下语法保存:
saved_model_path = ''
tf.saved_model.save(mdl, saved_model_path)
如果您查看当前目录,您会在那里找到一个saved_model.pb文件。
为了方便起见,本练习提供了一个saved_model文件。在flowerclassifier/001目录中,您会找到以下输出:
-rw-r--r-- 1 2405393 Oct 12 22:02 saved_model.pb
drwxr-xr-x@ 2 64 Oct 12 22:02 assets
drwxr-xr-x@ 4 128 Oct 12 22:02 variables
请注意,save_model_path被定义为null。这表示模型将保存在当前目录中。如果您有另一个要使用的目录,您需要为该目录指定完整或相对路径。
saved_model.pb是模型结构的 Protobuf 格式。assets文件夹包含词汇表或任何查找表等对象,这些是模型执行所必需的。如果没有创建或需要这些对象,它可能为空。variables文件夹包含训练结果的权重和偏差值。这些项目构成了SavedModel。我们将看看如何调用SavedModel对测试数据进行评分。现在,让我们关注本章 GitHub 仓库中的 Jupyter notebook:
-
如果你只是想使用 Python 脚本调用
SavedModel,这非常简单。你需要做的就是按照以下方式加载模型:path_saved_model = 'flowerclassifier/001' working_model = tf.saved_model.load(path_saved_model) -
每个
SavedModel都有一个默认的模型签名,用于描述模型的输入和输出结构。该签名还关联了一个名称。我们需要找出这个名称是什么:print(list(working_model.signatures.keys()))由于在保存过程中没有指定签名名称,签名名称的输出如下:
['serving_default'] -
接下来,我们需要创建一个推理对象
infer,然后找到模型输出的名称及其形状,这些在使用模型对测试数据进行评分时是必需的:infer = working_model.signatures['serving_default'] print(infer.structured_outputs)这将输出以下内容:
{'custom_class': TensorSpec(shape=(None, 5), dtype=tf.float32, name='custom_class')}输出的名称为
custom_class,它是一个形状为shape=(None, 5)的浮点型 NumPy 数组张量。这表示输出是一个五种花卉类型的概率数组。数组中概率最高的索引位置是我们需要映射到花卉类型的地方。我们在第七章《模型优化》中看到了这个映射,在那里我们学习了如何处理 TFRecord 来构建和训练这个模型。这个映射如下:{4: 'tulips', 3: 'dandelion', 1: 'sunflowers', 2: 'daisy', 0: 'roses'}如果
custom_class输出数组中最高的概率在第一个位置,则预测结果映射为roses。如果它在第五个位置,则预测结果映射为tulips。 -
另一个我们需要确认的是模型期望的输入形状。我们可以使用
save_model_cli来获取这些信息。我们可以在 Jupyter notebook 单元格中执行以下内联命令:!saved_model_cli show --dir {path_saved_model} --all你将看到该命令的输出包括以下内容:
signature_def['serving_default']: The given SavedModel SignatureDef contains the following input(s): inputs['input_4'] tensor_info: dtype: DT_FLOAT shape: (-1, 224, 224, 3)注意
shape的要求。我们知道(224,224,3)指的是图像的尺寸。第一维中的-1表示此输入设置为处理多个(批次)(224,224,3)图像数组。因此,如果我们想对一张图像进行评分,我们需要通过添加一个维度来扩展该图像。 -
我们使用
raw_image目录中的一张测试图像,并通过nvision库的imread读取图像:jpg1 = 'raw_images2440874162_27a7030402_n.jpg' img1_np = nv.imread(jpg1, resize=(224,224),normalize=True) img1_np = nv.expand_dims(img1_np,axis=0)注意,我们只需要提供图像的高度和宽度,以便将图像调整到每个维度的正确像素数。
-
使用
infer对象对该图像进行评分:prediction = infer(tf.constant(img1_np))这会为每个五种花卉类型生成预测结果,给定
img1_np:prediction['custom_class'].numpy()这将生成以下输出:
array([[2.4262092e-06, 5.6151916e-06, 1.8000206e-05, 1.4342861e-05, 9.9995959e-01]], dtype=float32)在第五个位置,最高的概率值为
9.9995959e-01。因此,基于步骤 3中提到的映射,这张图片被映射为tulips。
我们已经看过如何使用SavedModel进行推理。这要求我们在 Python 运行时中加载模型、读取图片,并将其传递给模型进行评分。然而,在生产或应用环境中,通常是通过 TFS 来调用模型。在下一节中,我们将了解如何让这个模型通过这种方式工作。
使用 Docker 了解 TensorFlow Serving
TFS 的核心实际上是一个运行模型 Protobuf 文件的 TensorFlow 模型服务器。安装模型服务器并不简单,因为它有很多依赖项。为了方便起见,TensorFlow 团队还提供了一个包含所有必要依赖项(即库或模块)并在隔离环境中运行的 Docker 容器平台,这个平台在操作系统级别使用虚拟化。
因此,部署 TensorFlow SavedModel最简单的方式是通过 TFS 和 Docker 容器。要安装 Docker,你可以参考 Docker 网站的安装说明(docs.docker.com/install/),包括 Mac、Windows 或 Linux 安装的具体指导。对于本章内容,社区版已经足够。我们将使用在 macOS Catalina 10.15.6 上运行的 Docker Desktop 2.4,具体配置如图 9.1所示:

图 9.1 – 本章使用的 Docker 版本
假设你已经正确安装并运行了 Docker Desktop。在高层次上,我们将下载一个 TFS Docker 镜像,添加我们的模型,并在基础镜像 TFS 之上构建一个新的 Docker 镜像。最终的镜像通过 TCP/IP 端口暴露,用于处理来自客户端的 RESTful API 调用。
下载 TensorFlow Serving Docker 镜像
一旦 Docker 引擎启动并运行,你就可以执行以下步骤:
-
你可以使用以下 Docker 命令拉取最新的 TFS Docker 镜像:
docker pull tensorflow/serving -
这就是我们当前的基础镜像。为了在这个镜像上添加我们的模型,我们需要先运行这个基础镜像:
docker run -d --name serv_base_img tensorflow/serving
在前面的命令中,我们调用了tensorflow/serving镜像,并且现在它作为一个 Docker 容器在运行。我们还将这个容器命名为serv_base_img。
创建一个包含模型的新镜像并提供服务
现在我们来看一下这里的文件目录。对于这个示例,目录结构如下面的图所示:

图 9.2 – 创建自定义 Docker 容器的目录结构
我们将在与Tensorflow_Serving.ipynb相同的目录下执行以下命令。
在将 TFS 基础 Docker 镜像启动并运行为容器后,我们准备将我们自己的SavedModel放入该容器:
-
基本上,我们需要将模型复制到 TFS 容器的
model文件夹中:flowerclassifier is the directory name two levels up from the saved_model.pb file. In between the two, you will notice that there is a directory, 001. This hierarchy is required by TFS, and so is the naming convention for the middle directory, which has to be an integer. It doesn't have to be 001, as long as it is all integers.The preceding command copies our model into the base image's `/model` directory. -
现在我们将更改提交到基础镜像,并为容器命名,使其与我们的模型目录匹配:
docker commit --change "ENV MODEL_NAME flowermodel" serv_base_img flowermodel -
我们不再需要运行基础镜像。现在,我们可以直接停止它:
flowermodel, which is deployed in a TFS container. Once we launch the TFS container, it brings our model up for serving. -
为了服务图像并对我们的测试图像进行评分,我们将运行以下命令:
8501, and map it to the Docker container's port, 8501. If your local port 8501 is not available, you may try another local port, say 8502. Then the command would take on -p 8502:8501. The source of our model is in the current directory (as indicated by the inline `$PWD` command) and followed by `flowerclassifier`. This folder also defines an environment variable, `MODEL_NAME`. `-t tensorflow/serving` indicates we want the container to be ready for `STDIN` from `tensorflow/serving`.
在您的命令终端,您将看到如下图所示的输出:


图 9.3 – Docker 容器运行自定义构建的模型
请注意前面的截图中的以下内容:成功加载可服务版本 {name: flowerclassifier version: 1}。
这一行表示容器成功找到了我们的模型flowerclassifier,以及命名层级中的下一个目录001。这是 TFS 所需的一种通用模式,以便与您构建的自定义模型一起使用 TFS。
现在模型已被服务。在接下来的部分,我们将看到如何构建调用该模型的客户端,使用 Python 的 JSON 库。
通过 RESTful API 进行评分
现在让我们返回到 Jupyter 环境。我们将看到如何从我们在本地服务中所做的工作继续进行。回想一下,我们使用nvision库来规范化和标准化我们的测试图像。我们还需要扩展图像的维度,因为模型期望在输入中有一个批次维度。完成这些步骤后,正如在本地服务中一样,我们将得到一个正确形状的 NumPy 数组img1_np。现在让我们将这个数组构建成一个 JSON 负载,并通过 RESTful API 将负载传递给我们的模型,按照以下步骤进行:
-
我们将使用以下命令构建 JSON 负载:
data = json.dumps({ "instances": img1_np.tolist() }) headers = {"content-type": "application/json"}在前面的代码中,我们将测试图像转换为 JSON 负载格式,并为我们的 RESTful API 调用定义了一个头部,指示负载是 JSON 格式,供应用程序使用。
根据 TFS 的要求,负载必须通过实例的键名对和要转换为列表的 NumPy 数组来编码评分数据作为输入。我们还需要为 JSON 负载定义一个头部。
-
我们将用以下命令为这个数据评分,使用我们的测试图像
img1_np:response = requests.post('http://localhost:8501/v1/models/flowerclassifier:predict', data=data, headers=headers)上面的命令会从 TFS 容器返回一个
response负载。 -
我们将使用以下命令检查
response负载:response.json()以下是前面命令的输出:
{'predictions': [[2.42621149e-06, 5.61519164e-06, 1.80002226e-05, 1.43428879e-05, 0.999959588]]}这是一个字典,包含键
prediction以及每种花卉类型的概率值。这些值与我们在本地服务中看到的相同。因此,我们知道该模型已经通过 TFS 在 Docker 容器中正确服务。
总结
在这一章中,你学习了如何部署 TensorFlow SavedModel。这绝不是企业部署中最常用的方法。在企业部署场景中,许多因素决定了部署管道应该如何构建,并且根据用例的不同,从那里开始,部署模式和选择可能会迅速发生变化。例如,一些组织使用 AirFlow 作为其编排工具,而有些可能更喜欢 KubeFlow,另外还有很多组织仍然使用 Jenkins。
本书的目标是从数据科学家/机器学习模型构建者的角度,向你展示如何利用最新且最可靠的 TensorFlow Enterprise 实现。
从这里开始,依据你的兴趣或优先事项,你可以根据本书中的学习内容,继续探索其他主题,如 MLOps、模型编排、漂移监控和重新部署。这些是任何企业机器学习讨论中,从用例角度来看,一些重要的议题。用例、IT 基础设施和业务考量通常决定了一个模型如何实际提供服务。进一步的考量包括服务管道必须满足什么样的服务水平协议,以及与身份验证和模型安全相关的安全性和合规性问题。
第十一章:其他您可能喜欢的书籍
如果您喜欢本书,您可能对 Packt 出版的其他书籍感兴趣:
使用 TensorFlow 2 和 Keras 的高级深度学习
Rowel Atienza
ISBN: 978-1-83882-165-4
-
使用互信息最大化技术进行无监督学习
-
使用分割技术识别图像中每个物体的像素级类别
-
使用目标检测技术识别图像中的物体的边界框和类别
-
学习高级技术的构建模块——MLP、CNN 和 RNN 理解深度神经网络——包括 ResNet 和 DenseNet
-
理解并构建自回归模型——自编码器、变分自编码器(VAE)和生成对抗网络(GAN)
-
发现并实现深度强化学习方法
Google Cloud Platform 上的人工智能实践
Anand Deshpande, Manish Kumar 和 Vikram Chaudhari
ISBN: 978-1-78953-846-5
-
理解云计算基础并探索 GCP 组件
-
使用 GCP 中的机器学习数据摄取和预处理技术
-
使用 Google Cloud AutoML 实现机器学习算法
-
使用 Google Cloud TPU 优化 TensorFlow 机器学习
-
掌握在 GCP 上实现 AI 的技巧
-
使用 Cloud Storage、Cloud Dataflow 和 Cloud Datalab 构建端到端的机器学习管道
-
使用 BigQuery ML 从 PB 级别的结构化和半结构化数据构建模型
留下评论——让其他读者了解您的看法
请通过在您购买书籍的网站上留下评论,分享您对本书的看法。如果您是在 Amazon 上购买的这本书,请在该书的 Amazon 页面上留下真实的评价。这对其他潜在读者非常重要,能帮助他们根据您的公正意见做出购买决策,帮助我们了解客户对我们产品的看法,同时也让我们的作者看到您对他们与 Packt 合作创作的书籍的反馈。这将只占用您几分钟的时间,但对其他潜在客户、我们的作者和 Packt 都非常宝贵。谢谢!
目录
-
学习 TensorFlow 企业版
-
为什么订阅?
-
贡献者
-
关于作者
-
前言
-
本书适合谁阅读
-
本书内容
-
如何最大化利用本书
-
下载示例代码文件
-
下载彩色图像
-
使用的约定
-
联系我们
-
-
第一节 – TensorFlow 企业服务与功能
-
第一章:
-
TensorFlow 企业概述
-
理解 TensorFlow 企业
- TensorFlow 企业套件
-
为 TensorFlow 企业配置云环境
-
设置云环境
-
创建 Google Cloud Storage 存储桶
-
启用 API
-
-
创建数据仓库
-
在 AI 平台中使用 TensorFlow 企业
-
访问数据源
-
云存储读取器
-
BigQuery 读取器
-
在 BigQuery 中持久化数据
-
在存储桶中持久化数据
-
-
摘要
-
-
第二章:
-
在 Google AI 平台上运行 TensorFlow 企业
-
设置笔记本环境
-
AI 平台笔记本
-
深度学习虚拟机映像
-
深度学习容器(DLC)
-
选择工作空间的建议
-
-
从 BigQuery 轻松参数化数据提取
- 综合运用
-
摘要
-
-
第二节 – 数据预处理与建模
-
第三章:
-
数据准备与操作技术
-
将表格数据转换为 TensorFlow 数据集
- 将 BigQuery 表转换为 TensorFlow 数据集
-
将分布式 CSV 文件转换为 TensorFlow 数据集
-
准备示例 CSV
-
使用 TensorFlow I/O 构建文件名模式
-
从 CSV 文件创建数据集
-
检查数据集
-
-
处理输入管道的图像数据
- 构建 protobuf 消息
-
解码 TFRecord 和重建图像
-
批量处理图像数据
-
执行步骤
-
读取 TFRecord 并显示为图像
-
-
摘要
-
-
第四章:
-
可重用模型和可扩展数据管道
-
使用 TensorFlow Hub
-
应用 TensorFlow Hub 的模型
-
创建生成器以批量处理图像数据
-
重用预训练的 ResNet 特征向量
-
编译模型
-
训练模型
-
使用测试图像进行评分
-
-
利用 TensorFlow Keras API
-
数据采集
-
使用 us_border_volumes 表解决数据科学问题
-
选择模型训练的特征和目标
-
流式训练数据
-
模型的输入
-
模型训练
-
-
使用 TensorFlow Estimators
- TensorFlow Estimators 的数据管道输入
-
摘要
-
-
第三节 – 扩展和调优 ML 工作
-
第五章:
-
大规模训练
-
通过 AI Platform 使用 Cloud TPU
-
安装 Cloud SDK
-
为读取训练数据和写入结果设置白名单(备选方案)
-
执行命令和格式
-
Cloud 命令参数
-
组织训练脚本
-
数据流管道
-
提交训练脚本
-
在 TensorFlow Hub 中使用模型
-
-
通过 AI Platform 使用 Google Cloud GPU
-
总结
-
-
第六章:
-
超参数调优
-
技术要求
-
划定超参数类型
-
理解 Keras Tuner 的语法和使用方法
-
使用 hp.Int 进行超参数定义
-
使用 hp.Choice 进行超参数定义
-
使用 hp.Float 进行超参数定义
-
-
划定超参数搜索算法
-
Hyperband
-
贝叶斯优化
-
随机搜索
-
-
在本地环境中提交调优任务
-
在 Google 的 AI Platform 上提交调优任务
-
总结
-
-
第四章 – 模型优化与部署
-
第七章:
-
模型优化
-
技术要求
-
理解量化概念
- 训练基准模型
-
为评分准备完整的原始模型
-
准备测试数据
-
加载测试数据
-
使用完整模型为单个图像评分
-
使用完整模型为一批图像评分
-
-
将完整模型转换为精简的 float16 模型
-
为评分准备精简的 float16 模型
-
使用量化模型为单个图像评分
-
使用量化模型为一批图像评分
-
-
将完整模型转换为减少的混合量化模型
-
为评分准备测试数据
-
将预测映射到类别名称
-
使用混合量化模型评分
-
评分单张图像
-
批量图像评分
-
-
将完整模型转换为整数量化模型
-
训练完整模型
-
使用整数量化模型评分
-
为评分准备测试数据集
-
批量图像评分
-
-
总结
-
-
第八章:
-
模型训练与性能的最佳实践
-
加载数据的输入处理
-
与生成器一起工作
-
TFRecord 数据集 – 输入管道
-
TFRecord 数据集 – 特征工程与训练
-
-
正则化
-
L1 和 L2 正则化
-
对抗正则化
-
-
总结
-
-
第九章:
-
为 TensorFlow 模型提供服务
-
技术要求
-
运行本地服务
-
理解 TensorFlow Serving 与 Docker
-
下载 TensorFlow Serving Docker 镜像
-
创建模型并服务新图像
-
通过 RESTful API 评分
-
-
总结
-
-
您可能喜欢的其他书籍
- 留下评论 - 让其他读者知道您的想法
地标
-
封面
-
目录




















































浙公网安备 33010602011771号