机器学习流水线构建指南-全-

机器学习流水线构建指南(全)

原文:zh.annas-archive.org/md5/6898f6e243535d55cdd12be4ea2244f4

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

每个人都在谈论机器学习。它已经从学术学科转变为最令人兴奋的技术之一。从理解自动驾驶汽车中的视频反馈到个性化药物,它在每个行业中都变得非常重要。虽然模型架构和概念受到了广泛关注,但机器学习还没有像软件行业在过去二十年中经历的过程标准化那样。在本书中,我们想向您展示如何构建一个标准化的、自动化的机器学习系统,产生可重复的模型。

什么是机器学习流水线?

在过去几年中,机器学习领域的发展令人惊讶。随着图形处理单元(GPUs)的广泛普及以及诸如BERT这样的新深度学习概念的兴起,或者像深度卷积 GAN 这样的生成对抗网络(GANs),AI 项目的数量激增。AI 初创公司的数量庞大。组织越来越多地将最新的机器学习概念应用于各种业务问题中。在追求最高效的机器学习解决方案的过程中,我们注意到一些未被重视的事情。我们发现数据科学家和机器学习工程师缺乏用于加速、重用、管理和部署其开发的概念和工具的良好信息来源。需要的是机器学习流水线的标准化。

机器学习流水线实现和规范化了加速、重用、管理和部署机器学习模型的过程。大约十年前,软件工程经历了类似的变革,引入了持续集成(CI)和持续部署(CD)。从前,测试和部署 Web 应用是一个漫长的过程。如今,通过一些工具和概念,这些过程已经大大简化。以前,Web 应用的部署需要 DevOps 工程师和软件开发人员之间的协作。今天,应用程序可以在几分钟内可靠地测试和部署。数据科学家和机器学习工程师可以从软件工程中学到很多关于工作流的知识。我们的目的是通过本书帮助读者理解整个机器学习流水线的标准化过程。

根据我们的个人经验,大多数旨在将模型部署到生产环境的数据科学项目并没有一个庞大的团队。这使得在内部从头开始构建整个流水线变得困难。这可能意味着机器学习项目会变成一次性的努力,在时间过去后性能会下降,数据科学家会花费大量时间在基础数据发生变化时修复错误,或者模型未被广泛使用。一个自动化、可重复的流水线可以减少部署模型所需的工作量。该流水线应包括以下步骤:

  • 有效地对数据进行版本控制,并启动新的模型训练运行

  • 验证接收到的数据并检查数据漂移情况

  • 有效地预处理数据用于模型训练和验证

  • 有效地训练你的机器学习模型

  • 追踪你的模型训练

  • 分析和验证你训练和调优的模型

  • 部署经过验证的模型

  • 扩展部署的模型

  • 使用反馈循环捕获新的训练数据和模型性能指标

这个列表遗漏了一个重要的点:选择模型架构。我们假设你已经对这一步骤有了良好的工作知识。如果你刚开始接触机器学习或深度学习,以下资源是熟悉机器学习的绝佳起点:

  • 《深度学习基础:设计下一代机器智能算法》第一版,作者 Nikhil Buduma 和 Nicholas Locascio(O'Reilly)

  • 《Scikit-Learn、Keras 和 TensorFlow 实战》第二版,作者 Aurélien Géron(O'Reilly)

这本书适合谁?

本书的主要受众是数据科学家和机器学习工程师,他们希望不仅仅是训练一次性的机器学习模型,而是成功地将其数据科学项目产品化。你应该对基本的机器学习概念感到舒适,并且熟悉至少一种机器学习框架(例如 PyTorch、TensorFlow、Keras)。本书中的机器学习示例基于 TensorFlow 和 Keras,但核心概念可以应用于任何框架。

本书的次要受众是数据科学项目的经理、软件开发人员或 DevOps 工程师,他们希望帮助组织加速其数据科学项目。如果您有兴趣更好地理解自动化机器学习生命周期及其如何使您的组织受益,本书将介绍一个工具链来实现这一目标。

为什么选择 TensorFlow 和 TensorFlow Extended?

在本书中,我们所有的流水线示例将使用 TensorFlow 生态系统中的工具,特别是 TensorFlow Extended(TFX)。我们选择这一框架背后有多个原因:

  • TensorFlow 生态系统在撰写本文时是最广泛可用的机器学习生态系统。除了其核心焦点外,它还包括多个有用的项目和支持库,例如 TensorFlow Privacy 和 TensorFlow Probability。

  • 它在小型和大型生产设置中都很受欢迎和广泛使用,并且有一个积极的感兴趣用户社区。

  • 支持的用例涵盖从学术研究到生产中的机器学习。TFX 与核心 TensorFlow 平台紧密集成,用于生产用例。

  • TensorFlow 和 TFX 都是开源工具,使用没有限制。

然而,我们在本书中描述的所有原则也适用于其他工具和框架。

章节概览

每章中,我们将介绍构建机器学习流水线的具体步骤,并演示这些步骤如何与示例项目配合使用。

第一章:介绍了机器学习流水线的概述,讨论了何时应该使用它们,并描述了构成流水线的所有步骤。我们还介绍了本书中将用作示例项目的实例项目。

第二章:介绍了 TensorFlow Extended,介绍了 TFX 生态系统,解释了任务之间如何通信,描述了 TFX 组件在内部工作的方式。我们还深入了解了 ML MetadataStore 在 TFX 上下文中的使用方式,以及 Apache Beam 如何在幕后运行 TFX 组件。

第三章:数据摄取讨论了如何以一致的方式将数据引入我们的流水线,并涵盖了数据版本控制的概念。

第四章:数据验证解释了如何使用 TensorFlow 数据验证有效地验证流入流水线的数据。这将在新数据与先前数据在可能影响模型性能的方式上发生显著变化时提醒您。

第五章:数据预处理侧重于使用 TensorFlow Transform 对数据进行预处理(特征工程),将原始数据转换为适合训练机器学习模型的特征。

第六章:模型训练讨论了如何在机器学习流水线中训练模型。我们还解释了模型调优的概念。

第七章:模型分析与验证介绍了在生产中理解模型的有用指标,包括可能帮助您发现模型预测中的偏差的指标,并讨论解释模型预测的方法。“TFX 中的分析与验证” 解释了当新版本改进指标时如何控制模型的版本。流水线中的模型可以自动更新到新版本。

第八章:使用 TensorFlow Serving 进行模型部署专注于如何高效地部署您的机器学习模型。我们从一个简单的 Flask 实现开始,突出了这种自定义模型应用的局限性。我们将介绍 TensorFlow Serving 以及如何配置您的服务实例。我们还讨论了其批处理功能,并指导您设置客户端以请求模型预测。

第九章:使用 TensorFlow Serving 进行高级模型部署讨论了如何优化您的模型部署以及如何监控它们。我们涵盖了优化 TensorFlow 模型以提高性能的策略。我们还指导您通过 Kubernetes 进行基本的部署设置。

第十章:高级 TensorFlow Extended 引入了为您的机器学习管道定制组件的概念,使您不受 TFX 标准组件的限制。无论您是想添加额外的数据摄取步骤,还是将导出的模型转换为 TensorFlow Lite(TFLite),我们都将指导您完成创建这些组件所需的步骤。

第十一章:管道第一部分:Apache Beam 和 Apache Airflow 将前几章的所有内容联系起来。我们讨论如何将您的组件转换为管道,以及如何配置它们以适配您选择的编排平台。我们还将指导您如何在 Apache Beam 和 Apache Airflow 上运行整个端到端管道。

第十二章:管道第二部分:Kubeflow Pipelines 从上一章继续,介绍了如何使用 Kubeflow Pipelines 和 Google 的 AI 平台进行端到端管道。

第十三章:反馈回路讨论了如何将您的模型管道转变为可以通过最终产品用户的反馈来改进的循环。我们将讨论捕获哪些类型的数据以改进未来版本的模型,以及如何将数据反馈到管道中。

第十四章:机器学习的数据隐私介绍了快速增长的隐私保护机器学习领域,并讨论了三种重要方法:差分隐私、联邦学习和加密机器学习。

第十五章:管道的未来和下一步展望了将影响未来机器学习管道的技术,并讨论了我们将如何思考未来几年的机器学习工程问题。

附录 A:机器学习基础设施简介简要介绍了 Docker 和 Kubernetes。

附录 B:在 Google Cloud 上设置 Kubernetes 集群提供了有关在 Google Cloud 上设置 Kubernetes 的补充材料。

附录 C:操作 Kubeflow Pipelines 的技巧提供了一些有关操作 Kubeflow Pipelines 设置的实用提示,包括 TFX 命令行界面概述。

本书使用的惯例

本书使用以下排版惯例:

Italic

指示新术语、URL、电子邮件地址、文件名和文件扩展名。

Constant width

用于程序清单,以及在段落中引用程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。

Constant width bold

显示用户应该直接输入的命令或其他文本。

Constant width italic

显示应由用户提供值或由上下文确定值的文本。

提示

此元素表示提示或建议。

注意

此元素表示一般说明。

警告

此元素表示警告或注意事项。

使用代码示例

可以从oreil.ly/bmlp-git下载补充材料(例如代码示例)。

如果您在使用代码示例时有技术问题或问题,请发送电子邮件至 bookquestions@oreilly.com 或 buildingmlpipelines@gmail.com。

本书旨在帮助您完成工作。通常情况下,如果本书提供了示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分,否则无需联系我们请求许可。例如,编写一个使用本书多个代码片段的程序不需要许可。销售或分发 O’Reilly 图书中的示例代码需要许可。通过引用本书回答问题并引用示例代码不需要许可。将本书大量示例代码整合到您产品的文档中需要许可。

我们感谢但不要求署名。署名通常包括标题、作者、出版社和 ISBN。例如:“《Building Machine Learning Pipelines》由 Hannes Hapke 和 Catherine Nelson(O’Reilly)著作。2020 年版权所有 Hannes Hapke 和 Catherine Nelson,978-1-492-05319-4。”

如果您认为使用代码示例超出了合理使用范围或上述许可,请随时通过 permissions@oreilly.com 与我们联系。

致谢

在撰写本书的整个过程中,我们得到了许多人的大力支持。非常感谢所有帮助使其成为现实的人!特别感谢以下人员。

O’Reilly 的每个人在整本书的整个生命周期中都非常出色。感谢我们的编辑 Melissa Potter,Nicole Taché和 Amelia Blevins,感谢他们的出色支持、持续鼓励和深思熟虑的反馈。同时也感谢 Katie Tozer 和 Jonathan Hassell 在路上的支持。

感谢 Aurélien Géron、Robert Crowe、Margaret Maynard-Reid、Sergii Khomenko 和 Vikram Tiwari,他们审阅了整本书并提供了许多有益的建议和深刻的评论。您的审阅使最终稿变成了一本更好的书。感谢您花费的时间对书籍进行如此详细的审阅。

感谢 Yann Dupis、Jason Mancuso 和 Morten Dahl 对机器学习隐私章节的彻底审查和深入分析。

我们在 Google 有很多出色的支持者。感谢你们帮助我们找到和修复 bug,以及使这些工具作为开源包发布!除了提到的 Google 员工外,特别感谢 Amy Unruh、Anusha Ramesh、Christina Greer、Clemens Mewald、David Zats、Edd Wilder-James、Irene Giannoumis、Jarek Wilkiewicz、Jiayi Zhao、Jiri Simsa、Konstantinos Katsiapis、Lak Lakshmanan、Mike Dreves、Paige Bailey、Pedram Pejman、Sara Robinson、Soonson Kwon、Thea Lamkin、Tris Warkentin、Varshaa Naganathan、Zhitao Li 和 Zohar Yahav。

感谢 TensorFlow 和 Google Developer Expert 社区及其出色的成员们。我们对社区深表感激。感谢你们支持这一努力。

感谢其他在不同阶段帮助过我的贡献者们:Barbara Fusinska、Hamel Husain、Michał Jastrzębski 和 Ian Hensel。

感谢 Concur Labs(过去和现在)以及 SAP Concur 其他地方的人们,为书籍提供了有益的讨论和建议。特别感谢 John Dietz 和 Richard Puckett 对这本书的极大支持。

Hannes

我想要感谢我的伟大搭档惠特尼,在写作这本书的过程中给予我巨大的支持。感谢你的持续鼓励和反馈,以及忍受我长时间写作的陪伴。感谢我的家人,特别是我的父母,让我能够追随我的梦想走遍世界。

没有了伟大的朋友,这本书就不可能问世。感谢 Cole Howard 成为我出色的朋友和导师。我们当初的合作开启了这本书的出版,也启发了我对机器学习流水线的思考。对我的朋友 Timo Metzger 和 Amanda Wright,感谢你们教会我语言的力量。同时也感谢 Eva 和 Kilian Rambach,以及 Deb 和 David Hackleman。没有你们的帮助,我不会一路走到俄勒冈。

我要感谢像 Cambia Health、Caravel 和 Talentpair 这样的前雇主,让我能够将这本书中的概念应用到生产环境中,尽管这些概念是新颖的。

这本书的问世离不开我的合著者 Catherine。感谢你的友谊、鼓励和无穷的耐心。很高兴我们因生活的偶然性而相遇。我们一起完成这本出版物,我感到非常开心。

Catherine

我在这本书中写了很多字,但没有足够的言语来表达我对丈夫 Mike 的支持之深。感谢你的鼓励、做饭、有益的讨论、讽刺和深刻的反馈。感谢我的父母很久以前就给我种下编程的种子,虽然它花了一段时间才生根发芽,但你们一直都是对的!

感谢我有幸参与的所有美好社区。通过西雅图 PyLadies、数据科学女性和更广泛的 Python 社区,我结识了很多优秀的人。我非常感谢你们的鼓励。

特别感谢汉内斯邀请我一同走过这段旅程!没有你,这一切都不可能发生!你的专业知识、注重细节和坚持不懈使整个项目取得了成功。而且,这一切也非常有趣!

第一章:引言

在本书的第一章中,我们将介绍机器学习流水线,并概述构建流水线所需的所有步骤。我们将解释如何将您的机器学习模型从实验推进到稳健的生产系统。我们还将介绍我们将在本书余下部分使用的示例项目,以演示我们所描述原则的应用。

为什么要使用机器学习流水线?

机器学习流水线的关键优势在于自动化模型生命周期的步骤。当新的训练数据变得可用时,应该触发包括数据验证、预处理、模型训练、分析和部署的工作流程。我们观察到许多数据科学团队手动完成这些步骤,这既昂贵又容易出错。让我们详细讨论一下机器学习流水线的好处:

能够专注于新模型,而不是维护现有模型

自动化机器学习流水线将使数据科学家摆脱维护现有模型的工作。我们观察到许多数据科学家整天都在维护先前开发的模型。他们手动运行脚本预处理训练数据,编写一次性部署脚本,或手动调整模型。自动化流水线允许数据科学家开发新模型,这是他们工作中有趣的部分。最终,这将提高工作满意度,并在竞争激烈的就业市场中提高留任率。

防止错误

自动化流水线可以防止错误。正如我们将在后面的章节中看到的那样,新创建的模型将与一组版本化的数据和预处理步骤相关联。这意味着如果收集到新数据,将生成一个新模型。如果更新了预处理步骤,训练数据将变得无效,并生成一个新模型。在手动机器学习工作流程中,错误的常见来源是模型训练后预处理步骤的更改。在这种情况下,我们可能会部署带有不同处理说明的模型,这与我们训练模型时的处理方式不同。由于模型推断仍然可能存在,这些错误可能非常难以调试,但却是明显的不正确。通过自动化工作流程,可以预防这些错误。

有用的文件记录

实验跟踪和模型发布管理生成模型变更的文件记录。实验将记录模型超参数的变化、使用的数据集以及生成的模型指标(例如损失或准确度)。模型发布管理将跟踪最终选择和部署的模型。如果数据科学团队需要重新创建模型或跟踪模型的性能,这些文件记录尤为宝贵。

标准化

标准化的机器学习管道提高了数据科学团队的体验。由于标准化的设置,数据科学家可以快速上手或者在团队间流动,并找到相同的开发环境。这提高了效率并减少了在新项目上设置的时间。设置机器学习管道的时间投资也可能导致提高的保留率。

管道的商业案例

自动化机器学习管道的实施将对数据科学团队产生三个关键影响:

  • 为新模型提供更多的开发时间

  • 更新现有模型的简化流程

  • 减少重复模型的时间

所有这些方面将减少数据科学项目的成本。而且,自动化的机器学习管道将:

  • 帮助检测数据集或训练模型中的潜在偏见。发现偏见可以防止对与模型交互的人造成伤害。例如,亚马逊的机器学习驱动简历筛选器发现存在性别偏见。

  • 创建纸质记录(通过实验跟踪和模型发布管理),以帮助解决关于数据保护法律的问题,如欧洲的通用数据保护条例(GDPR)。

  • 释放数据科学家的开发时间,提高他们的工作满意度。

何时考虑机器学习管道

机器学习管道提供了各种优势,但并非每个数据科学项目都需要管道。有时候数据科学家只是想尝试一个新模型,调查一个新的模型架构,或者重现最近的出版物。在这些情况下,管道并不实用。然而,一旦模型有了用户(例如,它被用于应用程序中),它将需要持续更新和微调。在这些情况下,我们回到了我们早些时候讨论的持续更新模型和减少数据科学家任务负担的情景。

机器学习项目的规模扩大后,管道变得更加重要。如果数据集或资源需求较大,我们讨论的方法可以实现基础设施的轻松扩展。如果重复性很重要,这是通过机器学习管道的自动化和审计跟踪来提供的。

机器学习管道步骤的概述

机器学习管道从新训练数据的摄取开始,以接收新训练模型的表现反馈结束。这个反馈可以是生产性能指标或产品用户的反馈。管道包括各种步骤,包括数据预处理、模型训练和模型分析,以及模型的部署。你可以想象手动完成这些步骤是繁琐且容易出错的。在本书的过程中,我们将介绍自动化机器学习管道的工具和解决方案。

正如您在图 1-1 中看到的那样,管道实际上是一个循环周期。数据可以持续收集,因此可以更新机器学习模型。更多的数据通常意味着改进的模型。由于数据不断涌入,自动化变得至关重要。在实际应用中,您希望经常重新训练您的模型。如果不这样做,在许多情况下,准确性会降低,因为训练数据与模型正在预测的新数据不同。如果重新训练是一个手动过程,需要手动验证新的训练数据或分析更新的模型,那么数据科学家或机器学习工程师将没有时间为完全不同的业务问题开发新模型。

图 1-1. 模型生命周期

机器学习管道通常包括以下部分中的步骤。

数据摄入和数据版本控制

数据摄入,正如我们在第三章中描述的那样,是每个机器学习管道的开始。在这个管道步骤中,我们将数据处理成后续组件可以消化的格式。数据摄入步骤不执行任何特征工程(这发生在数据验证步骤之后)。这也是将传入数据版本化的良好时机,以将数据快照与管道末端的训练模型连接起来。

数据验证

在训练新模型版本之前,我们需要验证新数据。数据验证(第四章)的重点是检查新数据的统计数据是否符合预期(例如范围、类别数量和类别分布)。如果检测到任何异常,它还会提醒数据科学家。例如,如果您正在训练一个二分类模型,您的训练数据可能包含 50%的 X 类样本和 50%的 Y 类样本。数据验证工具将在这些类别之间的分割发生变化时提供警报,例如新收集的数据在这两个类别之间的分割变为 70/30。如果模型在这样一个不平衡的训练集上进行训练,并且数据科学家没有调整模型的损失函数,或者过度/欠采样类别 X 或 Y,那么模型的预测可能会偏向于主导类别。

常见的数据验证工具还允许您比较不同的数据集。如果您有一个带有主导标签的数据集,并且将数据集分割为训练集和验证集,您需要确保标签在这两个数据集之间的分割大致相同。数据验证工具将允许您比较数据集并突出显示异常。

如果验证发现任何异常情况,可以在此停止流水线并通知数据科学家。如果检测到数据的变化,数据科学家或机器学习工程师可以改变各个类别的抽样(例如,只从每个类别中选择相同数量的示例),或者改变模型的损失函数,启动新的模型构建流水线,并重新启动生命周期。

数据预处理

你几乎无法直接使用新收集的数据直接训练机器学习模型。在几乎所有情况下,您需要预处理数据以用于训练运行。标签通常需要转换为单热或多热向量。1 对模型输入也适用相同的情况。如果从文本数据训练模型,您希望将文本的字符转换为索引或文本标记转换为单词向量。由于预处理仅在模型训练之前需要,而不是每个训练时期都需要,因此在训练模型之前单独运行预处理是最合理的生命周期步骤。

数据预处理工具可以从简单的 Python 脚本到复杂的图形模型。虽然大多数数据科学家专注于他们偏爱的工具的处理能力,但同样重要的是,预处理步骤的修改能够与处理后的数据相链接。这意味着如果有人修改了一个处理步骤(例如,在一个独热向量转换中允许额外的标签),那么先前的训练数据应该无效,强制更新整个流水线。我们在第五章中描述了这个流水线步骤。

模型训练和调整

模型训练步骤(第六章)是机器学习流水线的核心。在这一步骤中,我们训练一个模型以尽可能低的误差来预测输出。对于较大的模型,特别是大训练集,这一步骤很快就会变得难以管理。由于内存通常是我们计算的有限资源,模型训练的有效分布至关重要。

模型调整近来受到了极大关注,因为它可以显著提升性能并提供竞争优势。根据您的机器学习项目,您可以选择在开始考虑机器学习流水线之前调整模型,或者作为流水线的一部分调整它。由于我们的管道具有可伸缩性,得益于其底层架构,我们可以并行或顺序地启动大量模型。这使我们能够挑选出最终生产模型的最佳模型超参数。

模型分析

通常情况下,我们会使用准确率或损失来确定模型参数的最佳设定。但一旦我们确认了模型的最终版本,深入分析模型性能会非常有用(见第七章描述)。这可能包括计算其他指标,如精度、召回率和 AUC(曲线下面积),或在比训练时使用的验证集更大的数据集上计算性能。

进行深入模型分析的另一个原因是检查模型的预测是否公平。除非对数据集进行切片并计算每个切片的性能,否则无法确定模型在不同用户组中的表现。我们还可以调查模型对训练中使用的特征的依赖性,并探索如果更改单个训练示例的特征,模型预测会如何改变。

类似于模型调整步骤和最佳执行模型的最终选择,此工作流步骤需要由数据科学家审查。但我们将展示如何仅通过人工进行最终审查,即可实现整个分析的自动化。自动化将确保模型分析的一致性,并与其他分析进行比较。

模型版本控制

模型版本控制和验证步骤的目的是跟踪已被选择为下一个部署版本的模型、超参数集和数据集。

在软件工程中的语义化版本控制要求在 API 发生不兼容更改或添加重大功能时增加主版本号。否则,应增加次版本号。模型发布管理还有另一自由度:数据集。有些情况下,通过为训练过程提供显著更多和/或更好的数据,可以实现模型性能的显著差异,而无需更改单个模型参数或架构描述。这种性能提升是否值得进行主版本升级?

尽管每个数据科学团队对这个问题的答案可能不同,但记录所有输入到新模型版本(超参数、数据集、架构)并跟踪它们作为此发布步骤的一部分是至关重要的。

模型部署

一旦您已经训练、调优和分析了您的模型,它就准备好了。不幸的是,许多模型是以一次性实现方式部署的,这使得更新模型变得脆弱的过程。

现代模型服务器允许您在不编写 Web 应用程序代码的情况下部署模型。通常,它们提供多种 API 接口,如表现状态转移(REST)或远程过程调用(RPC)协议,并允许同时托管同一模型的多个版本。同时托管多个版本将使您能够对模型运行 A/B 测试,并提供有关模型改进的宝贵反馈。

模型服务器还允许您更新模型版本,而无需重新部署应用程序,这将减少应用程序的停机时间并减少应用程序开发与机器学习团队之间的通信。我们在第八章和第九章描述了模型部署。

反馈回路

机器学习流水线的最后一步经常被遗忘,但对数据科学项目的成功至关重要。我们需要闭环。然后,我们可以衡量新部署模型的有效性和性能。在此步骤中,我们可以获取有关模型性能的宝贵信息。在某些情况下,我们还可以捕获新的训练数据以增加我们的数据集并更新我们的模型。这可能涉及人在回路中,也可能是自动的。我们在第十三章讨论了反馈回路。

除了两个手动审查步骤(模型分析步骤和反馈步骤),我们可以自动化整个流水线。数据科学家应专注于开发新模型,而不是更新和维护现有模型。

数据隐私

在撰写本文时,数据隐私考虑超出了标准的机器学习流水线范围。随着消费者对其数据使用的关注增加以及新法律的实施限制个人数据的使用,我们预计这种情况将发生变化。这将导致隐私保护方法被整合到构建机器学习流水线工具中。

我们讨论了增强机器学习模型隐私的几种当前选项,详情请参阅第十四章:

  • 差分隐私,数学上保证模型预测不会暴露用户数据

  • 联合学习,其中原始数据不会离开用户设备

  • 加密机器学习,其中整个训练过程可以在加密空间中运行,或者对原始数据进行训练的模型可以进行加密

流水线编排

在前一节中描述的机器学习流水线的所有组件都需要被执行,或者说,被编排,以确保组件按正确顺序执行。在执行组件之前必须计算组件的输入。这些步骤的编排由诸如 Apache Beam、Apache Airflow(详见第十一章)或用于 Kubernetes 基础设施的 Kubeflow Pipelines(详见第十二章)等工具执行。

虽然数据流水线工具协调机器学习流水线步骤,但像 TensorFlow ML MetadataStore 这样的流水线工件存储捕获了各个过程的输出。在第二章,我们将概述 TFX 的 MetadataStore 并深入探讨 TFX 及其流水线组件的内部工作。

为什么需要流水线编排?

在 2015 年,谷歌的一群机器学习工程师总结出,机器学习项目经常失败的一个原因是大多数项目都使用定制代码来弥合机器学习流水线步骤之间的差距 2。然而,这些定制代码并不容易从一个项目转移到下一个。研究人员在论文《机器学习系统中隐藏的技术债务》3 中总结了他们的发现。作者在论文中认为,流水线步骤之间的粘合代码通常很脆弱,并且定制脚本在特定案例之外不具备扩展性。随着时间的推移,诸如 Apache Beam、Apache Airflow 或 Kubeflow Pipelines 等工具得以开发。这些工具可用于管理机器学习流水线任务,它们允许标准化的编排以及在任务之间的粘合代码的抽象化。

虽然一开始学习新工具(如 Beam 或 Airflow)或新框架(如 Kubeflow)并设置额外的机器学习基础设施(如 Kubernetes)可能显得繁琐,但这种时间投资很快就会得到回报。如果不采用标准化的机器学习流水线,数据科学团队将面临独特的项目设置、任意的日志文件位置、独特的调试步骤等问题。这些复杂性问题可能无穷无尽。

有向无环图

像 Apache Beam、Apache Airflow 和 Kubeflow Pipelines 这样的流水线工具通过任务依赖关系的图形表示来管理任务的流动。

如图 1-2 中的示例图所示,流水线步骤是有向的。这意味着一个流水线从任务 A 开始,以任务 E 结束,保证了执行路径由任务依赖明确定义。有向图避免了某些任务在所有依赖项完全计算之前就开始执行的情况。由于我们知道在训练模型之前必须预处理训练数据,有向图的表示防止了在完成预处理步骤之前执行训练任务。

图 1-2. 示例有向无环图

流水线图还必须是无环的,这意味着图形不链接到先前完成的任务。这将意味着流水线可能无休止地运行,因此无法完成工作流程。

由于这两个条件(有向和无环),流水线图被称为有向无环图(DAGs)。您会发现 DAG 是大多数工作流工具背后的核心概念。我们将在第十一章和第十二章讨论这些图是如何执行的更多细节。

我们的示例项目

要跟随本书的内容,我们创建了一个使用开放数据的示例项目。数据集是关于美国消费者对金融产品的投诉集合,包含结构化数据(分类/数值数据)和非结构化数据(文本)。数据来自消费者金融保护局

图 1-3 显示了该数据集的一个样本。

图 1-3. 数据样本

机器学习问题是,根据投诉的数据预测消费者是否对投诉有异议。在这个数据集中,30%的投诉有异议,所以数据集不平衡。

项目结构

我们提供了我们的示例项目作为GitHub 仓库,您可以使用以下命令正常克隆它:

$ git clone https://github.com/Building-ML-Pipelines/``\ building-machine-learning-pipelines.git

PYTHON 包版本

为了构建我们的示例项目,我们使用了 Python 3.6 至 3.8 版本。我们使用了 TensorFlow 版本 2.2.0 和 TFX 版本 0.22.0。我们将尽力更新我们的 GitHub 仓库到未来的版本,但不能保证项目能够与其他语言或包的版本兼容。

我们的示例项目包含以下内容:

  • 包含独立章节示例笔记本的 chapters 文件夹,包括第 3、4、7 和 14 章

  • 包含常见组件代码(如模型定义)的 components 文件夹

  • 完整的交互式流水线

  • 一个机器学习实验示例,这是流水线的起点

  • 由 Apache Beam、Apache Airflow 和 Kubeflow Pipelines 协同管理的完整示例流水线

  • 包含下载数据脚本的 utility 文件夹

在接下来的章节中,我们将指导您完成将示例机器学习实验(在我们的案例中是一个带有 Keras 模型架构的 Jupyter 笔记本)转变为完整端到端机器学习流水线的必要步骤。

我们的机器学习模型

我们示例深度学习项目的核心是由示例项目的components/module.py脚本中的get_model函数生成的模型。该模型预测消费者是否对投诉有异议,使用以下特征:

  • 金融产品

  • 子产品

  • 公司对投诉的回应

  • 消费者投诉的问题

  • 美国的州

  • 邮政编码

  • 投诉的文本(叙述)

为了构建机器学习流水线,我们假设模型架构设计已完成,我们不会修改模型。我们将在第六章更详细地讨论模型架构。但对于本书而言,模型架构是一个非常小的点。本书关注的是一旦您拥有模型,您可以用它做什么。

示例项目的目标

在本书的过程中,我们将展示连续训练示例机器学习模型所需的框架、组件和基础设施元素。我们将使用架构图中显示的堆栈,见图 1-4。

图 1-4. 我们示例项目的机器学习管道架构

我们尝试实现一个通用的机器学习问题,可以轻松替换为您特定的机器学习问题。机器学习管道的结构和基本设置保持不变,可以转移到您的用例中。每个组件都需要一些定制(例如,从何处摄取数据),但正如我们将讨论的那样,定制需求将是有限的。

摘要

在本章中,我们介绍了机器学习管道的概念,并解释了各个步骤。我们还展示了自动化此过程的好处。此外,我们为接下来的章节做了铺垫,并简要概述了每一章以及我们示例项目的介绍。在下一章中,我们将开始构建我们的管道!

1   在带有多个类输出的监督分类问题中,通常需要将从类别转换为向量,例如(0,1,0),这是一个独热向量,或者从类别列表转换为向量,例如(1,1,0),这是一个多热向量。

2   Google 于 2007 年启动了一个名为 Sibyl 的内部项目,用于管理内部的机器学习生产管道。然而,2015 年,D. Sculley 等人发表了关于机器学习管道的学习成果,“机器学习系统中的隐藏技术债务”,“Hidden Technical Debt in Machine Learning Systems”,这个主题引起了广泛关注。

3   D. Sculley 等人,“机器学习系统中的隐藏技术债务”,Google,Inc.(2015 年)。

第二章:TensorFlow Extended 简介

在上一章中,我们介绍了机器学习流水线的概念,并讨论了构成流水线的各个组件。在本章中,我们介绍 TensorFlow Extended(TFX)。TFX 库提供了我们机器学习流水线所需的所有组件。我们使用 TFX 定义我们的流水线任务,然后可以使用流水线编排器(如 Airflow 或 Kubeflow Pipelines)执行这些任务。图 2-1 概述了流水线步骤及不同工具如何配合使用。

图 2-1. TFX 作为 ML 流水线的一部分

在本章中,我们将指导您安装 TFX,并解释将为后续章节铺垫的基本概念和术语。在这些章节中,我们深入探讨了构成我们流水线的各个组件。在本章中,我们还介绍了Apache Beam。Beam 是一个用于定义和执行数据处理作业的开源工具。在 TFX 流水线中,它有两个用途:首先,它在许多 TFX 组件的幕后运行,执行诸如数据验证或数据预处理之类的处理步骤。其次,它可以用作流水线编排器,正如我们在第一章中讨论的那样。我们在这里介绍 Beam,因为它将帮助您理解 TFX 组件,并且如果您希望编写自定义组件(正如我们在第十章中讨论的那样),它是必不可少的。

什么是 TFX?

机器学习流水线可能变得非常复杂,并消耗大量开销来管理任务依赖关系。同时,机器学习流水线可以包括各种任务,包括数据验证、预处理、模型训练以及任何训练后任务。正如我们在第一章中讨论的那样,任务之间的连接通常很脆弱,导致流水线失败。这些连接也被称为来自于出版物“机器学习系统中的隐藏技术债务”的胶水代码。连接脆弱最终意味着生产模型更新不频繁,数据科学家和机器学习工程师厌恶更新过时模型。流水线还需要良好管理的分布式处理,这就是为什么 TFX 利用 Apache Beam。对于大型工作负载来说,这一点尤为重要。

Google 在内部面临相同问题,并决定开发一个平台来简化流水线定义,减少编写任务样板代码的工作量。Google 内部 ML 流水线框架的开源版本是 TFX。

图 2-2 展示了带有 TFX 的通用流水线架构。流水线编排工具是执行我们任务的基础。除了编排工具外,我们还需要一个数据存储来跟踪中间流水线结果。各个组件通过数据存储进行通信以获取其输入,并将结果返回给数据存储。这些结果随后可以作为后续任务的输入。TFX 提供了将所有这些工具组合在一起的层,并提供了主要流水线任务的各个组件。

图 2-2. 机器学习流水线架构

最初,Google 将一些流水线功能作为开源 TensorFlow 库发布(例如,在第八章中讨论了 TensorFlow Serving),并纳入了 TFX 库的范畴。2019 年,Google 发布了包含所有必要流水线组件的开源粘合代码,用于将这些库连接在一起,并自动创建流水线定义,以供 Apache Airflow、Apache Beam 和 Kubeflow Pipelines 等编排工具使用。

TFX 提供了多种流水线组件,涵盖了许多使用案例。在编写本文时,以下组件可用:

  • 使用 ExampleGen 进行数据摄取

  • 使用 StatisticsGenSchemaGenExampleValidator 进行数据验证

  • 使用 Transform 进行数据预处理

  • 使用 Trainer 进行模型训练

  • 使用 ResolverNode 检查先前训练的模型

  • 使用 Evaluator 进行模型分析和验证

  • 使用 Pusher 进行模型部署

图 2-3 展示了流水线组件和库的结合方式。

图 2-3. TFX 组件和库

我们将在接下来的章节中详细讨论组件和库。如果需要一些非标准功能,在第十章中我们将讨论如何创建自定义流水线组件。

TFX 的稳定版本

在编写本章时,尚未发布稳定的 TFX 1.X 版本。本书中提到的 TFX API 可能会在未来更新中进行更新。据我们所知,本书中的所有示例将与 TFX 版本 0.22.0 兼容。

安装 TFX

使用以下 Python 安装程序命令可轻松安装 TFX:

$ $ pip install tfx

tfx 软件包附带多种自动安装的依赖项。它不仅安装各个 TFX Python 软件包(例如 TensorFlow 数据验证),还包括诸如 Apache Beam 等的依赖项。

安装完 TFX 后,您可以导入各个 Python 软件包。如果想使用单独的 TFX 软件包(例如,使用 TensorFlow 数据验证验证数据集,请参见第四章)推荐采用这种方式:

import``tensorflow_data_validation``as``tfdv``import``tensorflow_transform``as``tft``import``tensorflow_transform.beam``as``tft_beam``...

或者,您可以导入相应的 TFX 组件(如果在管道上下文中使用组件):

from``tfx.components``import``ExampleValidator``from``tfx.components``import``Evaluator``from``tfx.components``import``Transform``...

TFX 组件概述

一个组件处理的过程比仅执行单个任务更复杂。所有机器学习管道组件从通道读取以获取来自元数据存储的输入工件。然后从元数据存储提供的路径加载数据并进行处理。组件的输出,即处理后的数据,然后提供给下一个管道组件。组件的通用内部始终是:

  • 接收一些输入

  • 执行一个操作

  • 存储最终结果

在 TFX 术语中,组件的三个内部部分分别称为驱动程序、执行程序和发布者。驱动程序处理元数据存储的查询。执行程序执行组件的操作。发布者管理将输出元数据保存到 MetadataStore 中。驱动程序和发布者不会移动任何数据。相反,它们从 MetadataStore 中读取和写入引用。图 2-4 显示了 TFX 组件的结构。

图 2-4. 组件概述

组件的输入和输出称为工件。工件的示例包括原始输入数据、预处理数据和训练模型。每个工件与存储在 MetadataStore 中的元数据相关联。工件元数据包括工件类型和工件属性。这种工件设置确保组件可以有效地交换数据。TFX 目前提供了十种不同类型的工件,我们将在接下来的章节中进行审查。

什么是 ML Metadata?

TFX 的组件“通过元数据进行通信”;而不是直接在管道组件之间传递工件,组件使用和发布管道工件的引用。例如,一个工件可以是原始数据集、转换图或导出模型。因此,元数据是我们 TFX 管道的支柱。通过在组件之间传递元数据而不是直接传递工件的一个优点是信息可以集中存储。

在实践中,工作流程如下:当我们执行一个组件时,它使用 ML Metadata(MLMD)API 来保存与运行相对应的元数据。例如,组件驱动从元数据存储中接收原始数据集的引用。在组件执行之后,组件发布者将组件输出的引用存储到元数据存储中。MLMD 基于存储后端一致地保存元数据到 MetadataStore 中。目前,MLMD 支持三种类型的后端:

  • 内存数据库(通过 SQLite)

  • SQLite

  • MySQL

由于 TFX 组件始终如一地跟踪,ML Metadata 提供了多种有用的功能。例如,我们可以在第七章讨论模型验证时进行比较。在这种特定情况下,TFX 比较当前运行的模型分析结果与先前运行的结果。该元数据还可以用于确定所有基于另一个先前创建的工件的工件。这为我们的机器学习流水线创建了一种审计跟踪方式。

图 2-5 显示每个组件与 MetadataStore 交互,而 MetadataStore 则将元数据存储在提供的数据库后端上。

图 2-5. 使用 MLMD 存储元数据

交互式流水线

设计和实现机器学习流水线有时可能令人沮丧。例如,有时在流水线中调试组件是具有挑战性的。这就是为什么围绕交互式流水线的 TFX 功能如此有益。事实上,在接下来的章节中,我们将逐步实现一个机器学习流水线,并通过交互式流水线演示其实现。该流水线在 Jupyter Notebook 中运行,并且可以立即查看组件的工件。一旦确认了流水线的全部功能,在第十一章和第十二章,我们将讨论如何将交互式流水线转换为生产就绪的流水线,例如在 Apache Airflow 上执行。

任何交互式流水线都是在 Jupyter Notebook 或 Google Colab 会话的环境中编程的。与我们将在第十一章和第十二章讨论的编排工具相比,交互式流水线由用户编排和执行。

通过导入所需的包可以启动交互式流水线:

import``tensorflow``as``tf``from``tfx.orchestration.experimental.interactive.interactive_context``import \ InteractiveContext

导入要求后,可以创建context对象。context对象处理组件执行并显示组件的工件。此时,InteractiveContext还设置了一个简单的内存中 ML MetadataStore:

context``=``InteractiveContext``()

在设置流水线组件(例如StatisticsGen)之后,您可以通过context对象的run函数执行每个组件对象,如下例所示:

from``tfx.components``import``StatisticsGen``statistics_gen``=``StatisticsGen``(``examples``=``example_gen``.``outputs``[``'examples'``])``context``.``run``(``statistics_gen``)

组件本身将前一组件的输出(在我们的案例中是数据摄取组件ExampleGen)作为实例化参数接收。在执行组件的任务后,组件会自动将输出工件的元数据写入元数据存储。一些组件的输出可以在你的笔记本中显示。结果和可视化的即时可用性非常方便。例如,你可以使用StatisticsGen组件来检查数据集的特征:

context``.``show``(``statistics_gen``.``outputs``[``'statistics'``])

在运行前面的context函数后,你可以在你的笔记本中看到数据集统计的视觉概述,如图 2-6 所示。

有时候,通过程序检查组件的输出工件可能是有利的。在组件对象执行后,我们可以访问工件属性,如以下示例所示。属性取决于特定的工件:

for``artifact``in``statistics_gen``.``outputs``[``'statistics'``]``.``get``():``print``(``artifact``.``uri``)

这给出了以下结果:

'/tmp/tfx-interactive-2020-05-15T04_50_16.251447/StatisticsGen/statistics/2'

图 2-6. 交互式流水线允许我们直观地检查数据集

在接下来的章节中,我们将展示如何在交互式环境中运行每个组件。然后在第 11 和 12 章中,我们将展示完整的流水线以及如何通过 Airflow 和 Kubeflow 进行编排。

TFX 的替代方案

在我们深入了解接下来的 TFX 组件之前,让我们花点时间看看 TFX 的替代方案。在过去几年中,机器学习流水线的编排一直是一个重要的工程挑战,毫无疑问,许多主要的硅谷公司都开发了自己的流水线框架。在以下表格中,你可以找到一些流行的框架:

- 公司 框架 链接
- AirBnb AeroSolve github.com/airbnb/aerosolve
- 条纹 铁路场 stripe.com/blog/railyard-training-models
- Spotify Luigi github.com/spotify/luigi
- Uber Michelangelo eng.uber.com/michelangelo-machine-learning-platform/
- Netflix Metaflow metaflow.org/

由于这些框架起源于各大公司,它们设计时考虑了特定的工程堆栈。例如,AirBnB 的 AeroSolve 专注于基于 Java 的推断代码,而 Spotify 的 Luigi 则专注于高效的编排。TFX 在这方面也不例外。此时,TFX 的架构和数据结构假设你正在使用 TensorFlow(或 Keras)作为机器学习框架。某些 TFX 组件可以与其他机器学习框架结合使用。例如,可以使用 TensorFlow Data Validation 分析数据,然后由 scikit-learn 模型使用。然而,TFX 框架与 TensorFlow 或 Keras 模型密切相关。由于 TFX 由 TensorFlow 社区支持,并且像 Spotify 这样的公司正在采用 TFX,我们相信它是一个稳定且成熟的框架,最终将被更广泛的机器学习工程师采纳。

Apache Beam 简介

多种 TFX 组件和库(例如 TensorFlow Transform)依赖于 Apache Beam 来高效处理管道数据。由于在 TFX 生态系统中的重要性,我们想简要介绍 Apache Beam 在 TFX 组件背后的工作原理。在第十一章中,我们将讨论如何将 Apache Beam 用于第二个目的:作为管道编排工具。

Apache Beam 为你提供了一种开源、供应商无关的方式来描述数据处理步骤,然后可以在各种环境中执行。由于其极大的灵活性,Apache Beam 可以用于描述批处理过程、流处理操作和数据管道。事实上,TFX 依赖于 Apache Beam,并在幕后使用它来支持各种组件(例如 TensorFlow Transform 或 TensorFlow Data Validation)。我们将在第四章中讨论 Apache Beam 在 TFX 生态系统中的具体用法,以及在第五章中讨论 TensorFlow Transform。

虽然 Apache Beam 将数据处理逻辑与支持运行时工具分离,但它可以在多个分布式处理运行时环境上执行。这意味着你可以在 Apache Spark 或 Google Cloud Dataflow 上运行相同的数据管道,而不需要改变管道描述。此外,Apache Beam 不仅仅是为了描述批处理过程,还能无缝支持流处理操作。

设置

安装 Apache Beam 非常简单。你可以通过以下命令安装最新版本:

$ pip install apache-beam

如果你计划在 Google Cloud Platform 的背景下使用 Apache Beam,例如处理来自 Google BigQuery 的数据或在 Google Cloud Dataflow 上运行我们的数据管道(如“使用 GCP 处理大型数据集”中描述的),你应该按以下方式安装 Apache Beam:

$ pip install 'apache-beam[gcp]'

如果您计划在 Amazon Web Services(AWS)的上下文中使用 Apache Beam(例如,如果您想要从 S3 存储桶加载数据),则应按以下方式安装 Apache Beam:

$ pip install 'apache-beam[boto]'

如果您使用 Python 包管理器pip安装 TFX,Apache Beam 将自动安装。

基本数据流水线

Apache Beam 的抽象基于两个概念:集合和转换。一方面,Beam 的集合描述了从给定文件或流中读取或写入数据的操作。另一方面,Beam 的转换描述了如何操作数据。所有的集合和转换都在流水线的上下文中执行(在 Python 中通过上下文管理器命令with表达)。当我们在下面的示例中定义我们的集合或转换时,并没有实际加载或转换任何数据。这只有在流水线在运行时环境(例如 Apache Beam 的 DirectRunner、Apache Spark、Apache Flink 或 Google Cloud Dataflow)的上下文中执行时才会发生。

基本集合示例

数据流水线通常从读取或写入数据开始和结束,在 Apache Beam 中通过集合(通常称为PCollections)处理。然后对集合进行转换,并且最终结果可以再次表达为集合并写入文件系统。

以下示例展示了如何读取文本文件并返回所有行:

import``apache_beam``as``beam``with``beam``.``Pipeline``()``as``p``:lines``=``p``|``beam``.``io``.``ReadFromText``(``input_file``)

使用上下文管理器来定义流水线。

将文本读入PCollection

类似于ReadFromText操作,Apache Beam 提供了将集合写入文本文件的函数(例如WriteToText)。写操作通常在所有转换执行后进行:

with``beam``.``Pipeline``()``as``p``:``...``output``|``beam``.``io``.``WriteToText``(``output_file``)

output写入文件output_file

基本转换示例

在 Apache Beam 中,数据通过转换进行操作。正如我们在本例中看到的,并且稍后在第五章中看到的,可以通过使用管道操作符|将转换链接在一起。如果您链式地应用同一类型的多个转换,则必须为操作提供一个名称,由管道操作符和尖括号之间的字符串标识符表示。在以下示例中,我们将所有转换顺序应用于从文本文件中提取的行:

counts``=``(``lines``|``'Split'``>>``beam``.``FlatMap``(``lambda``x``:``re``.``findall``(``r``'[A-Za-z``\'``]+'``,``x``))``|``'PairWithOne'``>>``beam``.``Map``(``lambda``x``:``(``x``,``1``))``|``'GroupAndSum'``>>``beam``.``CombinePerKey``(``sum``))

让我们详细地分析这段代码。例如,我们将采用短语“Hello, how do you do?”和“I am well, thank you.”作为示例。

Split 转换使用 re.findall 将每行分割成一个标记列表,结果如下:

[``"Hello"``,``"how"``,``"do"``,``"you"``,``"do"``]``[``"I"``,``"am"``,``"well"``,``"thank"``,``"you"``]

beam.FlatMap 将结果映射到 PCollection

"Hello" "how" "do" "you" "do" "I" "am" "well" "thank" "you"

接下来,PairWithOne 转换使用 beam.Map 来创建每个标记和计数(每个结果为 1)的元组:

"Hello", 1 "how", 1 "do", 1 "you", 1 "do", 1 "I", 1 "am", 1 "well", 1 "thank", 1 "you"

最后,GroupAndSum 转换会对每个标记的所有单独元组进行求和:

"Hello", 1 "how", 1 "do", 2 "you", 2 "I", 1 "am", 1 "well", 1 "thank", 1

您也可以将 Python 函数应用作为转换的一部分。下面的例子展示了如何将函数 format_result 应用于先前生成的求和结果。该函数将生成的元组转换为字符串,然后可以写入文本文件:

def``format_result``(``word_count``):``"""将元组(标记,计数)转换为字符串"""``(``word``,``count``)``=``word_count``return``"{}: {}"``.``format``(``word``,``count``)``output``=``counts``|``'Format'``>>``beam``.``Map``(``format_result``)

Apache Beam 提供了各种预定义的转换。然而,如果您的首选操作不可用,可以通过使用 Map 操作符来编写自己的转换。只需记住,操作应该能够以分布式方式运行,以充分利用运行时环境的能力。

将所有内容整合在一起

在讨论了 Apache Beam 流水线的各个概念之后,让我们通过一个例子将它们整合起来。前面的片段和接下来的示例是修改版的Apache Beam 介绍。为了可读性,例子已经简化到最基本的 Apache Beam 代码:

import``re``import``apache_beam``as``beam``from``apache_beam.io``import``ReadFromText``from``apache_beam.io``import``WriteToText``from``apache_beam.options.pipeline_options``import``PipelineOptions``from``apache_beam.options.pipeline_options``import``SetupOptions``input_file``=``"gs://dataflow-samples/shakespeare/kinglear.txt"output_file``=``"/tmp/output.txt"``# 定义管道选项对象。``pipeline_options``=``PipelineOptions``()``with``beam``.``Pipeline``(``options``=``pipeline_options``)``as``p``:# 将文本文件或文件模式读取到 PCollection 中。``lines``=``p``|``ReadFromText``(``input_file``)# 计算每个单词的出现次数。``counts``=``(`lines|'Split'>>beam.FlatMap(lambdax:re.findall(r'[A-Za-z\']+',x))|'PairWithOne'>>beam.Map(lambdax:(x,1))|'GroupAndSum'>>beam.CombinePerKey(sum))# 将计数格式化为字符串的 PCollection。defformat_result(word_count):(word,count)=word_countreturn"{}: {}".format(word,count)output=counts|'Format'>>beam.Map(format_result)# 使用具有副作用的“Write”转换写入输出。output|WriteToText(output_file)

文本存储在 Google Cloud Storage 存储桶中。

设置 Apache Beam 管道。

通过读取文本文件创建数据集。

对集合执行转换。

示例管道下载了莎士比亚的《李尔王》,并在整个语料库上执行了标记计数管道。然后将结果写入位于 /tmp/output.txt 的文本文件。

执行您的基本管道

例如,您可以通过执行以下命令(假设前面的示例代码已保存为 basic_pipeline.py)使用 Apache Beam 的 DirectRunner 运行管道。如果要在不同的 Apache Beam 运行程序(如 Apache Spark 或 Apache Flink)上执行此管道,则需要通过 pipeline_options 对象设置管道配置:

python basic_pipeline.py

可在指定的文本文件中找到转换的结果:

$``head``/``tmp``/``output``.``txt``*``KING``:``243``LEAR``:``236``DRAMATIS``:``1``PERSONAE``:``1``king``:``65``...

摘要

在本章中,我们提供了 TFX 的高级概述,并讨论了元数据存储的重要性以及 TFX 组件的一般内部。我们还介绍了 Apache Beam,并向您展示了如何使用 Beam 进行简单的数据转换。

本章讨论的所有内容都 这一章讨论的所有内容都将对你阅读第 3–7 章节,特别是管道组件和管道编排(详见第 11 和 12 章节)时有所帮助。第一步是将你的数据送入管道,我们将在[第 _split_008.html#filepos156116)中详细介绍。

第三章:数据摄取

通过基本的 TFX 设置和 ML MetadataStore,本章重点介绍了如何将您的数据集摄取到流水线中,以便在各个组件中使用,如图 3-1 所示。

图 3-1. 作为 ML 流水线一部分的数据摄取

TFX 为我们提供了从文件或服务摄取数据的组件。在本章中,我们概述了底层概念,解释了如何将数据集拆分为训练和评估子集,并演示了如何将多个数据导出组合成一个全面的数据集。然后,我们讨论了摄取不同形式数据(结构化数据、文本和图像)的一些策略,这些策略在之前的使用案例中被证明是有帮助的。

数据摄取的概念

在我们的流水线的这一步骤中,我们从外部服务(例如 Google Cloud BigQuery)读取数据文件或请求数据以运行我们的流水线。在将摄取的数据传递给下一个组件之前,我们将可用数据分成单独的数据集(例如训练数据集和验证数据集),然后将数据集转换为包含以 tf.Example 数据结构表示的数据的 TFRecord 文件。

TFRECORD

TFRecord 是一种针对流式传输大型数据集进行优化的轻量级格式。实际上,大多数 TensorFlow 用户在 TFRecord 文件中存储序列化的示例 Protocol Buffers,但 TFRecord 文件格式实际上支持任何二进制数据,如下所示:

import``tensorflow``as``tf``with``tf``.``io``.``TFRecordWriter``(``"test.tfrecord"``)``as``w``:``w``.``write``(``b``"First record"``)``w``.``write``(``b``"Second record"``)``for``record``in``tf``.``data``.``TFRecordDataset``(``"test.tfrecord"``):``print``(``record``)``tf``.``Tensor``(``b``'First record'``,``shape``=``(),``dtype``=``string``)``tf``.``Tensor``(``b``'Second record'``,``shape``=``(),``dtype``=``string``)

如果 TFRecord 文件包含 tf.Example 记录,每个记录都包含一个或多个特征,这些特征代表我们数据中的列。然后,数据以可以高效消化的二进制文件形式存储。如果您对 TFRecord 文件的内部机制感兴趣,我们建议参阅TensorFlow 文档

将数据存储为 TFRecord 和 tf.Examples 提供了一些好处:

  1. 该数据结构与系统无关,因为它依赖于 Protocol Buffers,这是一个跨平台、跨语言的库,用于序列化数据。

  2. TFRecord 优化了快速下载或写入大量数据的能力。

  3. tf.Example,在 TFRecord 中代表每个数据行的数据结构,也是 TensorFlow 生态系统中的默认数据结构,因此在所有 TFX 组件中都被使用。

摄取、拆分和转换数据集的过程由 ExampleGen 组件执行。正如我们在下面的示例中看到的那样,数据集可以从本地和远程文件夹中读取,也可以从像 Google Cloud BigQuery 这样的数据服务中请求。

摄取本地数据文件

ExampleGen 组件可以摄取几种数据结构,包括逗号分隔值文件(CSV)、预计算的 TFRecord 文件以及 Apache Avro 和 Apache Parquet 的序列化输出。

将逗号分隔数据转换为 tf.Example

结构化数据或文本数据的数据集通常存储在 CSV 文件中。TFX 提供功能来读取和转换这些文件为 tf.Example 数据结构。以下代码示例演示了如何摄取包含我们示例项目 CSV 数据的文件夹:

import``os``from``tfx.components``import``CsvExampleGen``from``tfx.utils.dsl_utils``import``external_input``base_dir``=``os``.``getcwd``()``data_dir``=``os``.``path``.``join``(``os``.``pardir``,``"data"``)``examples``=``external_input``(``os``.``path``.``join``(``base_dir``,``data_dir``))example_gen``=``CsvExampleGen``(``input``=``examples``)context``.``run``(``example_gen``)

定义数据路径。

实例化管道组件。

执行组件的交互操作。

如果您将组件作为交互式管道的一部分执行,则运行的元数据将显示在 Jupyter Notebook 中。组件的输出显示在 图 3-2 中,突出显示了训练和评估数据集的存储位置。

图 3-2. ExampleGen 组件输出

文件夹结构

预期 ExampleGen 的输入路径仅包含数据文件。组件尝试消耗路径级别内的所有现有文件。任何额外的文件(例如元数据文件)无法被组件消耗,并导致组件步骤失败。该组件也不会遍历现有的子目录,除非配置为输入模式。

导入现有的 TFRecord 文件

有时我们的数据无法有效表示为 CSV(例如,当我们想要加载用于计算机视觉问题的图像或用于自然语言处理问题的大语料库时)。在这些情况下,建议将数据集转换为 TFRecord 数据结构,然后使用 ImportExampleGen 组件加载保存的 TFRecord 文件。如果您希望作为管道的一部分将数据转换为 TFRecord 文件,请参阅 第十章,我们将讨论自定义 TFX 组件的开发,包括数据摄取组件。TFRecord 文件可以如以下示例所示进行摄取:

import``os``from``tfx.components``import``ImportExampleGen``from``tfx.utils.dsl_utils``import``external_input``base_dir``=``os``.``getcwd``()``data_dir``=``os``.``path``.``join``(``os``.``pardir``,``"tfrecord_data"``)``examples``=``external_input``(``os``.``path``.``join``(``base_dir``,``data_dir``))``example_gen``=``ImportExampleGen``(``input``=``examples``)``context``.``run``(``example_gen``)

由于数据集已经以 TFRecord 文件中的tf.Example记录的形式存储,因此可以导入而无需转换。ImportExampleGen组件处理此导入步骤。

将 Parquet 序列化数据转换为 tf.Example

在第二章中,我们讨论了 TFX 组件的内部架构和组件的行为,其驱动力是其executor。如果我们想要将新的文件类型加载到我们的流水线中,我们可以覆盖executor_class,而不是编写全新的组件。

TFX 包括用于加载不同文件类型的executor类,包括 Parquet 序列化数据。以下示例显示了如何覆盖executor_class以更改加载行为。而不是使用CsvExampleGenImportExampleGen组件,我们将使用通用文件加载器组件FileBasedExampleGen,允许覆盖executor_class

from``tfx.components``import``FileBasedExampleGenfrom``tfx.components.example_gen.custom_executors``import``parquet_executorfrom``tfx.utils.dsl_utils``import``external_input``examples``=``external_input``(``parquet_dir_path``)``example_gen``=``FileBasedExampleGen``(``input``=``examples``,``executor_class``=``parquet_executor``.``Executor``)

导入通用文件加载器组件。

导入特定于 Parquet 的执行器。

覆盖执行器。

将 Avro 序列化数据转换为 tf.Example

覆盖executor_class的概念当然可以扩展到几乎任何其他文件类型。TFX 提供了额外的类,如下例所示,用于加载 Avro 序列化数据:

from``tfx.components``import``FileBasedExampleGenfrom``tfx.components.example_gen.custom_executors``import``avro_executorfrom``tfx.utils.dsl_utils``import``external_input``examples``=``external_input``(``avro_dir_path``)``example_gen``=``FileBasedExampleGen``(``input``=``examples``,``executor_class``=``avro_executor``.``Executor``)

导入通用文件加载器组件。

导入特定于 Avro 的执行器。

覆盖执行器。

如果我们想要加载不同的文件类型,可以编写特定于我们文件类型的自定义执行器,并应用之前覆盖执行器的相同概念。在第十章中,我们将向您介绍如何编写自己的自定义数据摄入组件和执行器的两个示例。

将自定义数据转换为 TFRecord 数据结构

有时,将现有数据集转换为 TFRecord 数据结构,然后如我们在“导入现有 TFRecord 文件”中讨论的那样使用ImportExampleGen组件导入,比较简单。如果我们的数据不通过允许高效数据流的数据平台可用,这种方法尤其有用。例如,如果我们正在训练计算机视觉模型并将大量图像加载到管道中,我们首先必须将图像转换为 TFRecord 数据结构(稍后在“用于计算机视觉问题的图像数据”一节中详细介绍)。

在以下示例中,我们将结构化数据转换为 TFRecord 数据结构。想象一下,我们的数据不是以 CSV 格式提供的,只能使用 JSON 或 XML 格式。在使用ImportExampleGen组件将这些数据导入管道之前,可以使用以下示例(稍作修改)转换这些数据格式。

要将任何类型的数据转换为 TFRecord 文件,我们需要为数据集中的每个数据记录创建一个tf.Example结构。tf.Example是一个简单但非常灵活的数据结构,它是一个键值映射:

{``"string"``:``value``}

对于 TFRecord 数据结构,tf.Example期望一个tf.Features对象,该对象接受包含键值对的特征字典。键始终是表示特征列的字符串标识符,值是tf.train.Feature对象。

示例 3-1. TFRecord 数据结构

Record 1: tf.Example     tf.Features         'column A': tf.train.Feature         'column B': tf.train.Feature         'column C': tf.train.Feature

tf.train.Feature允许三种数据类型:

  • tf.train.BytesList

  • tf.train.FloatList

  • tf.train.Int64List

为了减少代码冗余,我们将定义辅助函数来帮助将数据记录转换为tf.Example所使用的正确数据结构:

import``tensorflow``as``tf``def``_bytes_feature``(``value``):``return``tf``.``train``.``Feature``(``bytes_list``=``tf``.``train``.``BytesList``(``value``=``[``value``]))``def``_float_feature``(``value``):``return``tf``.``train``.``Feature``(``float_list``=``tf``.``train``.``FloatList``(``value``=``[``value``]))``def``_int64_feature``(``value``):``return``tf``.``train``.``Feature``(``int64_list``=``tf``.``train``.``Int64List``(``value``=``[``value``]))

在放置了辅助函数之后,让我们看看如何将演示数据集转换为包含 TFRecord 数据结构的文件。首先,我们需要读取原始数据文件,并将每个数据记录转换为tf.Example数据结构,然后将所有记录保存在 TFRecord 文件中。以下代码示例是简略版本。完整示例可以在书的GitHub 存储库的章节/data_ingestion 中找到。

import``csv``import``tensorflow``as``tf``original_data_file``=``os``.``path``.``join``(``os``.``pardir``,``os``.``pardir``,``"data"``,``"consumer-complaints.csv"``)``tfrecord_filename``=``"consumer-complaints.tfrecord"``tf_record_writer``=``tf``.``io``.``TFRecordWriter``(``tfrecord_filename``)with``open``(``original_data_file``)``as``csv_file``:``reader``=``csv``.``DictReader``(``csv_file``,``delimiter``=``","``,``quotechar``=``'"'``)``for``row``in``reader``:``example``=``tf``.``train``.``Example``(``features``=``tf``.``train``.``Features``(``feature``=``{"product"``:``_bytes_feature``(``row``[``"product"``]),``"sub_product"``:``_bytes_feature``(``row``[``"sub_product"``]),``"issue"``:``_bytes_feature``(``row``[``"issue"``]),``"sub_issue"``:``_bytes_feature``(``row``[``"sub_issue"``]),``"state"``:``_bytes_feature``(``row``[``"state"``]),``"zip_code"``:``_int64_feature``(``int``(``float``(``row``[``"zip_code"``]))),``"company"``:``_bytes_feature``(``row``[``"company"``]),``"company_response"``:``_bytes_feature``(``row``[``"company_response"``]),``"consumer_complaint_narrative"``: \ _bytes_feature``(``row``[``"consumer_complaint_narrative"``]),``"timely_response"``:``_bytes_feature``(``row``[``"timely_response"``]),``"consumer_disputed"``:``_bytes_feature``(``row``[``"consumer_disputed"``]),``}))``tf_record_writer``.``write``(``example``.``SerializeToString``())`tf_record_writer.close``()

创建了一个 TFRecordWriter 对象,保存在 tfrecord_filename 指定的路径中。

tf.train.Example 适用于每个数据记录

序列化数据结构

生成的 TFRecord 文件 consumer-complaints.tfrecord 现在可以使用 ImportExampleGen 组件导入。

从远程数据文件中摄取

ExampleGen 组件可以从像 Google Cloud Storage 或 AWS Simple Storage Service (S3) 这样的远程云存储桶中读取文件。1 TFX 用户可以像下面的例子一样向 external_input 函数提供存储桶路径:

examples``=``external_input``(``"gs://example_compliance_data/"``)``example_gen``=``CsvExampleGen``(``input``=``examples``)

访问私有云存储桶需要设置云服务提供商的凭证。设置是特定于提供商的。AWS 通过用户特定的访问密钥和访问密钥进行用户身份验证。要访问私有 AWS S3 存储桶,需要创建用户访问密钥和密钥。2 相反,Google Cloud Platform (GCP) 通过服务帐号对用户进行身份验证。要访问私有 GCP 存储桶,需要创建具有访问存储桶权限的服务帐号文件。3

直接从数据库摄取数据

TFX 提供了两个组件,直接从数据库摄取数据集。在以下部分,我们介绍了 BigQueryExampleGen 组件用于查询 BigQuery 表中的数据,以及 PrestoExampleGen 组件用于查询 Presto 数据库中的数据。

Google Cloud BigQuery

TFX 提供了一个组件,用于从 Google Cloud 的 BigQuery 表中摄取数据。如果我们在 GCP 生态系统中执行机器学习流水线,这是摄取结构化数据非常高效的方式。

GOOGLE CLOUD 凭证

执行 BigQueryExampleGen 组件需要在本地环境中设置必要的 Google Cloud 凭证。我们需要创建一个带有所需角色(至少是 BigQuery 数据查看器和 BigQuery 作业用户)的服务账号。如果在交互式环境中使用 Apache Beam 或 Apache Airflow 执行组件,则必须通过环境变量 GOOGLE_APPLICATION_CREDENTIALS 指定服务账号凭证文件的路径,如下面的代码片段所示。如果通过 Kubeflow Pipelines 执行组件,可以通过引入的 OpFunc 函数提供服务账号信息,详见“OpFunc 函数”。

您可以在 Python 中使用以下方式实现:

import``os``os``.``environ``[``"GOOGLE_APPLICATION_CREDENTIALS"``]``=``"/path/to/credential_file.json"

有关更多详细信息,请参阅Google Cloud 文档

下面的示例展示了查询我们的 BigQuery 表的最简单方法:

from``tfx.components``import``BigQueryExampleGen``query``=``"""```     SELECT * FROM <project_id>..<table_name> ```"""``example_gen``=``BigQueryExampleGen``(``query``=``query``)

当然,我们可以创建更复杂的查询来选择我们的数据,例如,连接多个表。

BIGQUERYEXAMPLEGEN 组件的更改

在 TFX 版本大于 0.22.0 时,需要从 tfx.extensions.google_cloud_big_query 导入 BigQueryExampleGen 组件:

from``tfx.extensions.google_cloud_big_query.example_gen \ import``component``as``big_query_example_gen_component``big_query_example_gen_component``.``BigQueryExampleGen``(``query``=``query``)

Presto 数据库

如果我们想要从 Presto 数据库摄取数据,可以使用 PrestoExampleGen。其用法与 BigQueryExampleGen 非常类似,我们定义了一个数据库查询,然后执行该查询。PrestoExampleGen 组件需要额外的配置来指定数据库的连接详细信息:

from``proto``import``presto_config_pb2``from``presto_component.component``import``PrestoExampleGen``query``=``"""```     SELECT * FROM <project_id>..<table_name> ```"""``presto_config``=``presto_config_pb2``.``PrestoConnConfig``(``host``=``'localhost'``,``port``=``8080``)``example_gen``=``PrestoExampleGen``(``presto_config``,``query``=``query``)

PRESTOEXAMPLEGEN 需要单独安装

自 TFX 版本 0.22 以来,PrestoExampleGen 需要单独的安装过程。安装protoc编译器后,4 您可以按以下步骤从源代码安装组件:

$ git clone git@github.com:tensorflow/tfx.git &&``cd tfx/ $ git checkout v0.22.0 $ cd tfx/examples/custom_components/presto_example_gen $ pip install -e .

安装完成后,您将能够导入 PrestoExampleGen 组件及其协议缓冲区定义。

数据准备

每个引入的ExampleGen组件都允许我们配置数据集的输入设置(input_config)和输出设置(output_config)。如果我们想增量摄入数据集,可以将一个时间跨度定义为输入配置。同时,我们可以配置数据应如何拆分。通常,我们希望生成一个训练集以及一个评估和测试集。我们可以通过输出配置定义详细信息。

分割数据集

在我们的管道中稍后,我们希望在训练期间评估我们的机器学习模型,并在模型分析步骤中测试它。因此,将数据集拆分为所需的子集是有益的。

将一个数据集拆分成子集

以下示例显示了如何通过需要三向拆分来扩展我们的数据摄入:训练、评估和测试集,比例为 6:2:2。比例设置通过hash_buckets定义:

from``tfx.components``import``CsvExampleGen``from``tfx.proto``import``example_gen_pb2``from``tfx.utils.dsl_utils``import``external_input``base_dir``=``os``.``getcwd``()``data_dir``=``os``.``path``.``join``(``os``.``pardir``,``"data"``)``output``=``example_gen_pb2``.``Output``(``split_config``=``example_gen_pb2``.``SplitConfig``(``splits``=```![example_gen_pb2.SplitConfig.Split(name='train',hash_buckets=6),`![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00075.jpg)`example_gen_pb2.SplitConfig.Split(name='eval',hash_buckets=2),example_gen_pb2.SplitConfig.Split(name='test',hash_buckets=2)]))examples=external_input(os.path.join(base_dir,data_dir))example_gen=CsvExampleGen(input=examples,output_config=output)`![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00064.jpg)`context.run(example_gen)`

定义首选拆分。

指定比例。

添加output_config参数。

在执行example_gen对象后,我们可以通过打印生成的艺术品列表来检查生成的艺术品:

for``artifact``in``example_gen``.``outputs``[``'examples'``]``.``get``():``print``(``artifact``)``Artifact``(``type_name``:``ExamplesPath``,``uri``:``/``path``/``to``/``CsvExampleGen``/``examples``/``1``/``train``/``,``split``:``train``,``id``:``2``)``Artifact``(``type_name``:``ExamplesPath``,``uri``:``/``path``/``to``/``CsvExampleGen``/``examples``/``1``/``eval``/``,``split``:``eval``,``id``:``3``)``Artifact``(``type_name``:``ExamplesPath``,``uri``:``/``path``/``to``/``CsvExampleGen``/``examples``/``1``/``test``/``,``split``:``test``,``id``:``4``)

在接下来的章节中,我们将讨论如何调查我们数据管道中产生的数据集。

默认拆分

如果我们没有指定任何输出配置,ExampleGen 组件默认将数据集拆分为训练集和评估集,比例为 2:1。

保留现有的拆分

在某些情况下,我们已经在外部生成了数据集的子集,并且在摄取数据集时希望保留这些拆分。我们可以通过提供输入配置来实现这一点。

对于以下配置,让我们假设我们的数据集已经被外部拆分并保存在子目录中:

└── data    ├── train     │   └─ 20k-consumer-complaints-training.csv     ├── eval │   └─ 4k-consumer-complaints-eval.csv     └── test └─ 2k-consumer-complaints-test.csv

我们可以通过定义以下输入配置来保留现有的输入拆分:

import``os``from``tfx.components``import``CsvExampleGen``from``tfx.proto``import``example_gen_pb2``from``tfx.utils.dsl_utils``import``external_input``base_dir``=``os``.``getcwd``()``data_dir``=``os``.``path``.``join``(``os``.``pardir``,``"data"``)``input``=``example_gen_pb2``.``Input``(``splits``=````example_gen_pb2``.``Input``.``Split``(``name``=``'train'``,``pattern``=``'train/*'``),![example_gen_pb2``.``Input``.``Split``(``name``=``'eval'``,``pattern``=``'eval/*'``),``example_gen_pb2``.``Input``.``Split``(``name``=``'test'``,``pattern``=``'test/*'``)``])``examples``=``external_input``(``os``.``path``.``join``(``base_dir``,``data_dir``))``example_gen``=``CsvExampleGen``(``input``=``examples``,``input_config``=``input``)

设置现有子目录。

添加 input_config 参数。

定义了输入配置后,我们可以通过定义 input_config 参数将设置传递给 ExampleGen 组件。

数据集跨度

机器学习流水线的一个重要用例是当新数据可用时,我们可以更新我们的机器学习模型。对于这种情况,ExampleGen 组件允许我们使用 spans。将 span 视为数据的快照。每小时、每天或每周,批量提取、转换、加载 (ETL) 过程可以生成这样的数据快照并创建一个新的 span。

跨度可以复制现有的数据记录。如下所示,export-1包含了前一个export-0的数据,以及自export-0导出后新增的记录:

└── data     ├── export``-0     │   └─ 20k-consumer-complaints.csv     ├── export``-1     │   └─ 24k-consumer-complaints.csv     └── export``-2         └─ 26k-consumer-complaints.csv

现在,我们可以指定跨度的模式。输入配置接受一个{SPAN}占位符,表示在我们的文件夹结构中显示的数字(0、1、2 等)。通过输入配置,ExampleGen组件现在选择“最新”的跨度。在我们的示例中,这将是文件夹export-2下的可用数据:

from``tfx.components``import``CsvExampleGen``from``tfx.proto``import``example_gen_pb2``from``tfx.utils.dsl_utils``import``external_input``base_dir``=``os``.``getcwd``()``data_dir``=``os``.``path``.``join``(``os``.``pardir``,``"data"``)``input``=``example_gen_pb2``.``Input``(``splits``=``[``example_gen_pb2``.``Input``.``Split``(``pattern``=``'export-{SPAN}/*'``)``])``examples``=``external_input``(``os``.``path``.``join``(``base_dir``,``data_dir``))``example_gen``=``CsvExampleGen``(``input``=``examples``,``input_config``=``input``)``context``.``run``(``example_gen``)

当然,如果数据已经分割,输入定义也可以定义子目录:

input``=``example_gen_pb2``.``Input``(``splits``=``[``example_gen_pb2``.``Input``.``Split``(``name``=``'train'``,``pattern``=``'export-{SPAN}/train/*'``),``example_gen_pb2``.``Input``.``Split``(``name``=``'eval'``,``pattern``=``'export-{SPAN}/eval/*'``)``])

数据集版本化

在机器学习流水线中,我们希望跟踪生成的模型以及用于训练机器学习模型的数据集。为了做到这一点,版本化我们的数据集非常有用。

数据版本控制允许我们更详细地跟踪摄入的数据。这意味着我们不仅在 ML MetadataStore 中存储摄入数据的文件名和路径(因为当前由 TFX 组件支持),还跟踪关于原始数据集的更多元信息,例如摄入数据的哈希值。这样的版本跟踪可以确保在训练期间使用的数据集在稍后的时间点仍然是相同的数据集。这种功能对端到端 ML 可重现性至关重要。

然而,TFX 的ExampleGen组件目前不支持此功能。如果您希望对数据集进行版本化,可以使用第三方数据版本控制工具,并在数据集摄入流水线之前对数据进行版本化。不幸的是,目前没有可用的工具会直接将元数据信息写入 TFX 的 ML MetadataStore 中。

如果您希望对数据集进行版本控制,可以使用以下其中之一的工具:

数据版本控制(DVC)

DVC 是一个用于机器学习项目的开源版本控制系统。它允许您提交数据集的哈希而不是整个数据集本身。因此,数据集的状态被跟踪(例如通过git),但存储库不会因整个数据集而杂乱。

厚皮象

Pachyderm 是一个运行在 Kubernetes 上的开源机器学习平台。它最初是基于数据版本控制概念而起的(“数据的 Git”),但现在已扩展为包括基于数据版本的流水线编排在内的整个数据平台。

注入策略

到目前为止,我们已经讨论了多种将数据注入到机器学习流水线中的方法。如果您从头开始一个全新的项目,选择合适的数据注入策略可能会让人感到不知所措。在接下来的章节中,我们将为三种数据类型(结构化数据、文本数据和图像数据)提供一些建议。

结构化数据

结构化数据通常存储在数据库中或以支持表格数据的文件格式存储在磁盘上。如果数据存在于数据库中,我们可以将其导出为 CSV,或直接使用PrestoExampleGenBigQueryExampleGen组件(如果服务可用)消耗数据。

存储在支持表格数据的文件格式中的磁盘上的数据应转换为 CSV,并使用CsvExampleGen组件将其注入到流水线中。如果数据量超过几百兆字节,应考虑将数据转换为 TFRecord 文件或使用 Apache Parquet 存储数据。

自然语言问题的文本数据

文本语料库的大小可能会迅速增加。为了有效地注入这些数据集,我们建议将数据集转换为 TFRecord 或 Apache Parquet 表示形式。使用性能良好的数据文件类型可以实现语料库文档的高效和增量加载。也可以从数据库中注入语料库,但建议考虑网络流量成本和瓶颈问题。

计算机视觉问题的图像数据

推荐将图像数据集从图像文件转换为 TFRecord 文件,但不要解码图像。对高度压缩的图像进行解码只会增加存储中间tf.Example记录所需的磁盘空间。压缩图像可以作为字节字符串存储在tf.Example记录中:

import``tensorflow``as``tf``base_path``=``"/path/to/images"``filenames``=``os``.``listdir``(``base_path``)``def``generate_label_from_path``(``image_path``):``...``return``label``def``_bytes_feature``(``value``):``return``tf``.``train``.``Feature``(``bytes_list``=``tf``.``train``.``BytesList``(``value``=``[``value``]))``def``_int64_feature``(``value``):``return``tf``.``train``.``Feature``(``int64_list``=``tf``.``train``.``Int64List``(``value``=``[``value``]))``tfrecord_filename``=``'data/image_dataset.tfrecord'``with``tf``.``io``.``TFRecordWriter``(``tfrecord_filename``)``as``writer``:``for``img_path``in``filenames``:``image_path``=``os``.``path``.``join``(``base_path``,``img_path``)``try``:``raw_file``=``tf``.``io``.``read_file``(``image_path``)``except``FileNotFoundError``:``print``(``"File {} could not be found"``.``format``(``image_path``))``continue``example``=``tf``.``train``.``Example``(``features``=``tf``.``train``.``Features``(``feature``=``{``'image_raw'``:``_bytes_feature``(``raw_file``.``numpy``()),``'label'``:``_int64_feature``(``generate_label_from_path``(``image_path``))``}))``writer``.``write``(``example``.``SerializeToString``())

示例代码从提供的路径 /path/to/images 读取图像,并将图像存储为字节字符串放入 tf.Example 中。我们当前阶段不对图像进行预处理。尽管这样可能节省大量磁盘空间,但我们希望稍后在管道中执行这些任务。当前阶段避免预处理有助于避免后续训练/服务偏差的错误。

我们在 tf.Examples 中存储原始图像及其标签。我们通过示例中的 generate_label_from_path 函数从文件名中派生每个图像的标签。由于标签生成是特定于数据集的,因此我们没有在此示例中包含它。

将图像转换为 TFRecord 文件后,我们可以使用 ImportExampleGen 组件高效地消费数据集,并应用我们在“导入现有 TFRecord 文件”中讨论的相同策略。

摘要

在本章中,我们讨论了将数据输入到机器学习管道中的各种方法。我们强调了从磁盘和数据库中存储的数据集的消费。在此过程中,我们还讨论了转换为 tf.Example(存储在 TFRecord 文件中)的摄取数据记录,以便下游组件消费。

在接下来的章节中,我们将看看如何在管道的数据验证步骤中消费生成的 tf.Example 记录。

1   从 AWS S3 读取文件需要 Apache Beam 2.19 或更高版本,这是 TFX 版本 0.22 开始支持的。

2   有关如何管理 AWS 访问密钥的更多详细信息,请参阅文档

3   有关如何创建和管理服务帐户的更多详细信息,请参阅文档

4   访问 proto-lens GitHub 了解 关于 protoc 安装的详细信息

第四章:数据验证

在第三章中,我们讨论了如何将数据从各种来源导入我们的管道。在本章中,我们现在想要通过验证数据开始消耗数据,如图 4-1 所示。

图 4-1. 数据验证作为 ML 管道的一部分

数据是每个机器学习模型的基础,模型的实用性和性能取决于用于训练、验证和分析模型的数据。正如你可以想象的那样,没有健壮的数据,我们就无法构建健壮的模型。用口头表达来说,你可能听过这样的短语:“垃圾进,垃圾出”—这意味着如果底层数据没有经过精心策划和验证,我们的模型将无法表现出色。这正是我们机器学习管道中第一个工作流步骤的确切目的:数据验证。

在本章中,我们首先推动数据验证的概念,然后向您介绍了 TensorFlow Extended 生态系统中名为 TensorFlow 数据验证(TFDV)的 Python 包。我们展示了如何在您的数据科学项目中设置这个包,引导您通过常见的用例,并突出一些非常有用的工作流程。

数据验证步骤检查您的管道中的数据是否符合您的特征工程步骤期望的数据。它帮助您比较多个数据集。它还突出显示如果您的数据随时间变化,例如,如果您的训练数据与为您的模型提供推断的新数据显著不同。

在本章末尾,我们将我们的第一个工作流步骤整合到我们的 TFX 管道中。

为什么需要数据验证?

在机器学习中,我们试图从数据集中学习模式并推广这些学习。这使得数据在我们的机器学习工作流程中占据了核心位置,数据的质量对我们的机器学习项目的成功至关重要。

我们机器学习管道中的每一步都决定了工作流是否可以进入下一步,或者整个工作流是否需要被放弃并重新启动(例如,使用更多的训练数据)。数据验证是一个特别重要的检查点,因为它在数据进入耗时的预处理和训练步骤之前捕捉到数据的变化。

如果我们的目标是自动化我们的机器学习模型更新,验证我们的数据就是必不可少的。特别是,当我们说验证时,我们指的是对我们的数据进行三个不同的检查:

  • 检查数据异常。

  • 检查数据模式是否发生了变化。

  • 检查我们新数据集的统计数据是否仍然与我们之前的训练数据集的统计数据保持一致。

我们管道中的数据验证步骤执行这些检查并突出显示任何失败。如果检测到失败,我们可以停止工作流程,并手动解决数据问题,例如,策划一个新的数据集。

从数据处理步骤到数据验证步骤,这也是有用的。数据验证可以生成关于数据特征的统计信息,并突出显示特征是否包含高百分比的缺失值或特征是否高度相关。在决定哪些特征应包含在预处理步骤中以及预处理的形式应该是什么时,这些信息非常有用。

数据验证让您可以比较不同数据集的统计信息。这一简单步骤可以帮助您调试模型问题。例如,数据验证可以比较您的训练数据与验证数据的统计信息。几行代码就可以把任何差异带给您的注意。您可能使用完美的标签分布训练二元分类模型,其中正标签和负标签分别占 50%,但您的验证集中标签分布并非 50/50。标签分布的这种差异最终会影响您的验证指标。

在数据集持续增长的世界中,数据验证对确保我们的机器学习模型仍能胜任任务至关重要。因为我们可以比较模式,我们可以快速检测到新获得的数据集中的数据结构是否发生了变化(例如当特征被弃用时)。它还可以检测您的数据是否开始漂移。这意味着您新收集的数据具有与用于训练模型的初始数据集不同的底层统计信息。漂移可能意味着需要选择新特征或更新数据预处理步骤(例如,如果数值列的最小值或最大值发生变化)。数据漂移可能由多种原因引起:数据中的潜在趋势、数据的季节性,或者由于反馈循环而导致的,正如我们在 第十三章 中讨论的那样。

在接下来的章节中,我们将详细讨论这些不同的使用案例。但在此之前,让我们看一下安装 TFDV 并使其运行所需的步骤。

TFDV

TensorFlow 生态系统提供了一个可以帮助您进行数据验证的工具,TFDV。它是 TFX 项目的一部分。TFDV 允许您执行我们之前讨论过的各种分析(例如生成模式并验证新数据与现有模式的匹配性)。它还基于 Google PAIR 项目 Facets 提供可视化,如 图 4-2 所示。

TFDV 接受两种输入格式来开始数据验证:TensorFlow 的 TFRecord 和 CSV 文件。与其他 TFX 组件一样,它使用 Apache Beam 进行分析分布。

图 4-2. TFDV 可视化的屏幕截图

安装

当我们安装了 第二章 中介绍的 tfx 包时,TFDV 已经作为依赖包安装了。如果我们想单独使用 TFDV 包,可以使用以下命令进行安装:

$ pip install tensorflow-data-validation

安装了tfxtensorflow-data-validation之后,我们现在可以将数据验证集成到您的机器学习工作流程中,或者在 Jupyter Notebook 中对数据进行可视化分析。我们将在以下几节中讨论一些用例。

从您的数据生成统计信息。

我们数据验证流程的第一步是为数据生成一些摘要统计信息。例如,我们可以直接使用 TFDV 加载我们的消费者投诉 CSV 数据,并为每个特征生成统计信息。

import``tensorflow_data_validation``as``tfdv``stats``=``tfdv``.``generate_statistics_from_csv``(``data_location``=``'/data/consumer_complaints.csv'``,``delimiter``=``','``)

我们可以使用以下代码从 TFRecord 文件生成特征统计,方法类似。

stats``=``tfdv``.``generate_statistics_from_tfrecord``(``data_location``=``'/data/consumer_complaints.tfrecord'``)

我们讨论如何在第三章中生成 TFRecord 文件。

TFDV 的两种方法都生成一个数据结构,用于存储每个特征的摘要统计信息,包括最小、最大和平均值。

数据结构如下所示:

datasets``{``num_examples``:``66799``features``{``type``:``STRING``string_stats``{``common_stats``{``num_non_missing``:``66799``min_num_values``:``1``max_num_values``:``1``avg_num_values``:``1.0``num_values_histogram``{``buckets``{``low_value``:``1.0``high_value``:``1.0``sample_count``:``6679.9``...``}}}}}}

对于数值特征,TFDV 为每个特征计算:

  • 数据记录的总数。

  • 缺失数据记录的数量。

  • 数据记录中特征的均值和标准差。

  • 数据记录中特征的最小值和最大值。

  • 数据记录中特征零值的百分比。

此外,它还为每个特征的值生成直方图。

对于分类特征,TFDV 提供以下内容:

  • 数据记录的总数。

  • 缺失数据记录的百分比。

  • 唯一记录的数量。

  • 所有记录中特征的平均字符串长度。

  • 对于每个类别,TFDV 确定每个标签的样本计数及其排名。

稍后,您将看到我们如何将这些统计数据转化为可操作的内容。

从您的数据生成模式。

生成摘要统计信息后,下一步是生成数据集的模式。数据模式描述数据集的表示形式。模式定义了数据集中预期包含的特征以及每个特征的类型(浮点数、整数、字节等)。此外,您的模式应定义数据的边界(例如,特征的允许缺失记录的最小值、最大值和阈值)。

您数据集的模式定义随后可用于验证未来数据集,以确定它们是否符合先前训练集的要求。TFDV 生成的模式还可用于以下工作流步骤,即在预处理数据集以转换为可用于训练机器学习模型的数据时使用。

如下所示,您可以通过单个函数调用从生成的统计信息中生成模式信息:

schema``=``tfdv``.``infer_schema``(``stats``)

tfdv.infer_schema 生成了 TensorFlow 定义的模式协议:1

feature``{``name``:``"product"``type``:``BYTES``domain``:``"product"``presence``{``min_fraction``:``1.0``min_count``:``1``}``shape``{``dim``{``size``:``1``}``}``}

您可以在任何 Jupyter Notebook 中通过单个函数调用显示模式:

tfdv``.``display_schema``(``schema``)

并且结果显示在 Figure 4-3 中。

图 4-3. 模式可视化截图

在此可视化中,Presence 表示特征是否必须存在于 100%的数据示例中(required)或不需要(optional)。Valency 表示每个训练示例所需的值的数量。对于分类特征,single 意味着每个训练示例必须具有该特征的一个类别。

此处生成的模式可能并不完全符合我们的需求,因为它假设当前数据集完全代表所有未来数据。如果某个特征在此数据集中的所有训练示例中都存在,它将被标记为required,但实际上它可能是optional。我们将展示如何根据您对数据集的了解更新模式在 “更新模式” 中。

现在定义了模式后,我们可以比较我们的训练或评估数据集,或者检查可能影响我们模型的任何问题。

发现您数据中的问题

在前面的部分中,我们讨论了如何为我们的数据生成摘要统计信息和模式。这些描述了我们的数据,但并没有发现其中的潜在问题。在接下来的几个部分中,我们将描述 TFDV 如何帮助我们发现数据中的问题。

比较数据集

假设我们有两个数据集:训练数据集和验证数据集。在训练我们的机器学习模型之前,我们想确定验证集在数据结构方面与训练集的代表性如何。验证数据是否遵循我们的训练数据模式?是否缺少任何特征列或大量特征值?使用 TFDV,我们可以快速确定答案。

如下所示,我们可以加载两个数据集,然后可视化这两个数据集。如果我们在 Jupyter Notebook 中执行以下代码,我们可以轻松比较数据集的统计信息:

train_stats``=``tfdv``.``generate_statistics_from_tfrecord``(``data_location``=``train_tfrecord_filename``)``val_stats``=``tfdv``.``generate_statistics_from_tfrecord``(``data_location``=``val_tfrecord_filename``)``tfdv``.``visualize_statistics``(``lhs_statistics``=``val_stats``,``rhs_statistics``=``train_stats``,``lhs_name``=``'VAL_DATASET'``,``rhs_name``=``'TRAIN_DATASET'``)

图 4-4 展示了两个数据集之间的差异。例如,验证数据集(包含 4,998 条记录)中缺少sub_issue值的比率较低。这可能意味着该特征在验证集中的分布发生了变化。更重要的是,可视化显示超过一半的记录不包含sub_issue信息。如果sub_issue对我们的模型训练很重要,我们需要修复数据采集方法,以收集具有正确问题标识符的新数据。

我们先前生成的训练数据的架构现在非常有用。TFDV 允许我们根据架构验证任何数据统计,并报告任何异常。

图 4-4. 训练集和验证集之间的比较

可以使用以下代码检测异常:

anomalies``=``tfdv``.``validate_statistics``(``statistics``=``val_stats``,``schema``=``schema``)

然后,我们可以使用以下代码显示异常:

tfdv``.``display_anomalies``(``anomalies``)

这显示了在表 4-1 中显示的结果。

表 4-1. 在 Jupyter Notebook 中可视化异常

Feature name Anomaly short description Anomaly long description
“company” Column dropped The feature was present in fewer examples than expected.

以下代码显示了底层的异常协议。这包含了我们可以用来自动化机器学习工作流程的有用信息:

anomaly_info``{``key``:``"company"``value``{``description``:``"The feature was present in fewer examples than expected."``severity``:``ERROR``short_description``:``"Column dropped"``reason``{``type``:``FEATURE_TYPE_LOW_FRACTION_PRESENT``short_description``:``"Column dropped"``description``:``"The feature was present in fewer examples than expected."``}``path``{``step``:``"company"``}``}``}

更新架构

前面的异常协议显示了如何从我们的数据集自动生成的架构中检测到变化。但 TFDV 的另一个用例是根据我们对数据的领域知识手动设置架构。根据先前讨论的sub_issue特征,如果我们决定需要在我们的训练示例中要求此特征出现的百分比大于 90%,我们可以更新架构以反映这一点。

首先,我们需要从其序列化位置加载架构:

schema``=``tfdv``.``load_schema_text``(``schema_location``)

然后,我们更新这个特定特征,以便在 90% 的情况下是必需的:

sub_issue_feature``=``tfdv``.``get_feature``(``schema``,``'sub_issue'``)``sub_issue_feature``.``presence``.``min_fraction``=``0.9

我们还可以更新美国州列表以删除阿拉斯加:

state_domain``=``tfdv``.``get_domain``(``schema``,``'state'``)``state_domain``.``value``.``remove``(``'AK'``)

一旦我们满意架构,我们将架构文件写入其序列化位置,步骤如下:

tfdv``.``write_schema_text``(``schema``,``schema_location``)

然后我们需要重新验证统计信息以查看更新的异常情况:

updated_anomalies``=``tfdv``.``validate_statistics``(``eval_stats``,``schema``)``tfdv``.``display_anomalies``(``updated_anomalies``)

通过这种方式,我们可以调整适合我们数据集的异常情况。2

数据倾斜和漂移

TFDV 提供了内置的“倾斜比较器”,用于检测两个数据集统计信息之间的大差异。这不是倾斜的统计定义(数据集围绕其均值不对称分布),在 TFDV 中定义为两个数据集的服务统计信息的 L-infinity 范数差异。如果两个数据集之间的差异超过给定特征的 L-infinity 范数阈值,TFDV 将使用本章前述的异常检测将其标记为异常。

L-INFINITY NORM

L-infinity 范数是用于定义两个向量(在我们的案例中是服务统计信息)之间差异的表达式。L-infinity 范数定义为向量条目的最大绝对值。

例如,向量[3, –10, –5]的 L-infinity 范数为 10。范数通常用于比较向量。如果我们希望比较向量[2, 4, –1]和[9, 1, 8],我们首先计算它们的差异,即[–7, 3, –9],然后计算此向量的 L-infinity 范数,结果为 9。

在 TFDV 的情况下,这两个向量是两个数据集的摘要统计信息。返回的范数是这两组统计信息之间的最大差异。

以下代码展示了如何比较数据集之间的倾斜:

tfdv``.``get_feature``(``schema``,``'company'``)``.``skew_comparator``.``infinity_norm``.``threshold``=``0.01``skew_anomalies``=``tfdv``.``validate_statistics``(``statistics``=``train_stats``,``schema``=``schema``,``serving_statistics``=``serving_stats``)

而表 4-2 展示了结果。

表 4-2. 可视化展示训练和服务数据集之间的数据倾斜

特征名称 异常简短描述 异常详细描述
“公司” 训练和服务之间的高 L-infinity 距离 训练和服务之间的 L-infinity 距离为 0.0170752(精确到六位有效数字),高于阈值 0.01。具有最大差异的特征值为:Experian

TFDV 还提供了一个 drift_comparator,用于比较同一类型的两个数据集的统计数据,例如在两个不同日期收集的两个训练集。如果检测到漂移,则数据科学家应检查模型架构或确定是否需要重新进行特征工程。

类似于这个偏斜的例子,你应该为你想观察和比较的特征定义你的 drift_comparator。然后,你可以使用两个数据集统计数据作为参数来调用 validate_statistics,一个用作基线(例如昨天的数据集),另一个用作比较(例如今天的数据集):

tfdv``.``get_feature``(``schema``,``'公司'``)``.``drift_comparator``.``infinity_norm``.``threshold``=``0.01``drift_anomalies``=``tfdv``.``validate_statistics``(``statistics``=``train_stats_today``,``schema``=``schema``,``previous_statistics``=``\ `train_stats_yesterday``)

这导致了显示在 表 4-3 中的结果。

表 4-3. 两个训练集之间数据漂移的可视化

特征名称 异常简短描述 异常详细描述
“公司” 当前和上一次之间的高 L-infinity 距离 当前和上一次的 L-infinity 距离为 0.0170752(保留六个有效数字),超过了阈值 0.01. 具有最大差异的特征值是:Experian

skew_comparatordrift_comparator 中的 L-infinity 范数对于显示数据集之间的大差异非常有用,特别是可能显示我们的数据输入管道存在问题的情况。因为 L-infinity 范数只返回一个单一的数字,模式可能更有用来检测数据集之间的变化。

偏倚数据集

输入数据集的另一个潜在问题是偏见。我们在这里定义偏见为在某种程度上不代表真实世界的数据。这与我们在 第七章 中定义的公平性形成对比,后者是我们模型在不同人群中产生不同影响的预测。

偏见可以通过多种方式进入数据。数据集始终是真实世界的子集,我们不可能希望捕捉所有细节。我们对真实世界的采样方式总是以某种方式存在偏差。我们可以检查的偏见类型之一是选择偏见,其中数据集的分布与真实世界数据的分布不同。

我们可以使用 TFDV 来检查选择偏见,使用我们之前描述的统计可视化工具。例如,如果我们的数据集包含 Gender 作为分类特征,我们可以检查它是否偏向于男性类别。在我们的消费者投诉数据集中,我们有 State 作为分类特征。理想情况下,不同美国州的示例计数分布应反映每个州的相对人口。

我们可以在图 4-5 中看到,情况并非如此(例如,德克萨斯州排名第三,其人口比排名第二的佛罗里达州更多)。如果我们发现数据中存在这种类型的偏差,并且我们相信这种偏差可能会损害我们模型的性能,我们可以回头收集更多数据或对数据进行过采样/欠采样,以获取正确的分布。

图 4-5. 数据集中一个有偏的特征的可视化

您还可以使用先前描述的异常协议来自动警报您这类问题。利用您对数据集的领域知识,您可以强制执行限制数值的上限,以尽可能保证数据集的无偏性,例如,如果您的数据集包含人们的工资作为数值特征,则可以强制执行特征值的平均值是现实的。

有关更多详细信息和偏差的定义,请参阅谷歌的机器学习入门课程提供的有用材料。

在 TFDV 中切片数据

我们还可以使用 TFDV 来切片我们选择的特征数据集,以帮助显示它们是否存在偏差。这类似于我们在第七章中描述的对切片特征的模型性能计算。例如,数据出现缺失时,偏差可能悄然而至。如果数据不是随机缺失的,那么在数据集中的某些人群可能会更频繁地出现缺失情况。这可能意味着,当最终模型训练完成时,其性能对这些群体可能更差。

在这个例子中,我们将查看来自不同美国州的数据。我们可以切分数据,只获取加利福尼亚州的统计信息,使用以下代码:

from``tensorflow_data_validation.utils``import``slicing_util``slice_fn1``=``slicing_util``.``get_feature_value_slicer``(``features``=``{``'state'``:``[``b``'CA'``]})slice_options``=``tfdv``.``StatsOptions``(``slice_functions``=``[``slice_fn1``])``slice_stats``=``tfdv``.``generate_statistics_from_csv``(``data_location``=``'data/consumer_complaints.csv'``,``stats_options``=``slice_options``)

注意,特征值必须以二进制值列表的形式提供。

我们需要一些辅助代码来将切片后的统计数据复制到可视化中:

from``tensorflow_metadata.proto.v0``import``statistics_pb2``def``display_slice_keys``(``stats``):``print``(``list``(``map``(``lambda``x``:``x``.``name``,``slice_stats``.``datasets``)))``def``get_sliced_stats``(``stats``,``slice_key``):``for``sliced_stats``in``stats``.``datasets``:``if``sliced_stats``.``name``==``slice_key``:``result``=``statistics_pb2``.``DatasetFeatureStatisticsList``()``result``.``datasets``.``add``()``.``CopyFrom``(``sliced_stats``)``return``result``print``(``'Invalid Slice key'``)``def``compare_slices``(``stats``,``slice_key1``,``slice_key2``):``lhs_stats``=``get_sliced_stats``(``stats``,``slice_key1``)``rhs_stats``=``get_sliced_stats``(``stats``,``slice_key2``)``tfdv``.``visualize_statistics``(``lhs_stats``,``rhs_stats``)

我们可以用以下代码来可视化结果:

tfdv``.``visualize_statistics``(``get_sliced_stats``(``slice_stats``,``'state_CA'``))

然后比较加利福尼亚州的统计数据与总体结果:

compare_slices``(``slice_stats``,``'state_CA'``,``'All Examples'``)

这些结果显示在图 4-6 中。

图 4-6. 按特征值切片的数据可视化

在本节中,我们展示了 TFDV 的一些有用功能,可以帮助您发现数据中的问题。接下来,我们将看看如何利用 Google Cloud 的产品来扩展您的数据验证。

使用 GCP 处理大型数据集

随着我们收集更多数据,数据验证成为机器学习工作流程中更耗时的步骤。减少验证时间的一种方法是利用可用的云解决方案。通过使用云提供商,我们不受限于笔记本电脑或本地计算资源的计算能力。

举个例子,我们将介绍如何在 Google Cloud 的 Dataflow 产品上运行 TFDV。TFDV 基于 Apache Beam 运行,这使得切换到 GCP Dataflow 非常容易。

Dataflow 让我们通过并行化和分布在分配的节点上执行数据处理任务来加速数据验证任务。虽然 Dataflow 按照分配的 CPU 数量和内存的 GB 数收费,但它可以加快我们的管道步骤。

我们将演示一个最小的设置来分发我们的数据验证任务。有关更多信息,我们强烈建议查看扩展的 GCP文档。我们假设您已经创建了 Google Cloud 帐户,设置了计费详细信息,并在终端 Shell 中设置了GOOGLE_APPLICATION_CREDENTIALS环境变量。如果您需要帮助开始,请参阅第三章或 Google Cloud文档

我们可以使用之前讨论过的相同方法(例如,tfdv.generate_statistics_from_tfrecord),但这些方法需要额外的参数pipeline_optionsoutput_path。其中,output_path指向 Google Cloud 存储桶,用于存放数据验证结果,pipeline_options是一个包含所有 Google Cloud 细节的对象,用于在 Google Cloud 上运行我们的数据验证。以下代码展示了如何设置这样的管道对象:

from``apache_beam.options.pipeline_options``import``(``PipelineOptions``,``GoogleCloudOptions``,``StandardOptions``)``options``=``PipelineOptions``()``google_cloud_options``=``options``.``view_as``(``GoogleCloudOptions``)``google_cloud_options``.``project``=``'<YOUR_GCP_PROJECT_ID>'google_cloud_options``.``job_name``=``'<YOUR_JOB_NAME>'google_cloud_options``.``staging_location``=``'gs://<YOUR_GCP_BUCKET>/staging'google_cloud_options``.``temp_location``=``'gs://<YOUR_GCP_BUCKET>/tmp'``options``.``view_as``(``StandardOptions``)``.``runner``=``'DataflowRunner'

设置你项目的标识符。

给你的任务取一个名字。

指向一个用于暂存和临时文件的存储桶。

我们建议为 Dataflow 任务创建一个存储桶。这个存储桶将容纳所有数据集和临时文件。

配置好 Google Cloud 选项后,我们需要为 Dataflow 工作节点配置设置。所有任务都在工作节点上执行,这些节点需要预装运行任务所需的必要软件包。在我们的情况下,我们需要通过指定额外的软件包来安装 TFDV。

要做到这一点,请将最新的 TFDV 软件包(二进制的.whl文件)3 下载到你的本地系统。选择一个可以在 Linux 系统上执行的版本(例如,tensorflow_data_validation-0.22.0-cp37-cp37m-manylinux2010_x86_64.whl)。

要配置工作节点设置选项,请按照下面的示例将下载包的路径指定到setup_options.extra_packages列表中:

from``apache_beam.options.pipeline_options``import``SetupOptions``setup_options``=``options``.``view_as``(``SetupOptions``)``setup_options``.``extra_packages``=``[``'/path/to/tensorflow_data_validation'``'-0.22.0-cp37-cp37m-manylinux2010_x86_64.whl'``]

所有选项配置就绪后,你可以从本地机器启动数据验证任务。它们将在 Google Cloud Dataflow 实例上执行:

data_set_path``=``'gs://<YOUR_GCP_BUCKET>/train_reviews.tfrecord'``output_path``=``'gs://<YOUR_GCP_BUCKET>/'``tfdv``.``generate_statistics_from_tfrecord``(``data_set_path``,``output_path``=``output_path``,``pipeline_options``=``options``)

当你启动 Dataflow 进行数据验证后,你可以切换回 Google Cloud 控制台。你新启动的任务应该以类似图 4-7 的方式列出。

图 4-7. Google Cloud Dataflow 作业控制台

您可以查看运行作业的详细信息,其状态以及自动缩放的详细信息,如图 4-8 所示。

通过几个步骤,您可以在云环境中并行和分布数据验证任务。在下一节中,我们将讨论如何将数据验证任务集成到我们的自动化机器学习流水线中。

图 4-8. Google Cloud Dataflow 作业详细信息

将 TFDV 集成到您的机器学习流水线中

到目前为止,我们讨论的所有方法都可以在独立设置中使用。这对于在流水线设置之外研究数据集非常有帮助。

TFX 提供了一个名为StatisticsGen的流水线组件,它接受前一个ExampleGen组件的输出作为输入,然后执行统计信息的生成:

from``tfx.components``import``StatisticsGen``statistics_gen``=``StatisticsGen``(``examples``=``example_gen``.``outputs``[``'examples'``])``context``.``run``(``statistics_gen``)

就像我们在第 3 章中讨论的那样,我们可以使用以下方法在交互式环境中可视化输出:

context``.``show``(``statistics_gen``.``outputs``[``'statistics'``])

这使我们得到了图 4-9 所示的可视化效果。

图 4-9. StatisticsGen 组件生成的统计信息

生成模式与生成统计信息一样简单:

from``tfx.components``import``SchemaGen``schema_gen``=``SchemaGen``(``statistics``=``statistics_gen``.``outputs``[``'statistics'``],``infer_feature_shape``=``True``)``context``.``run``(``schema_gen``)

SchemaGen组件仅在不存在模式时才生成模式。建议在此组件的首次运行时审查模式,如果需要,则手动调整模式,正如我们在“更新模式”中讨论的那样。然后,我们可以使用此模式,直到需要更改它,例如,如果我们添加了新的特征。

现在,有了统计数据和模式,我们可以验证我们的新数据集了:

from``tfx.components``import``ExampleValidator``example_validator``=``ExampleValidator``(``statistics``=``statistics_gen``.``outputs``[``'statistics'``],``schema``=``schema_gen``.``outputs``[``'schema'``])``context``.``run``(``example_validator``)

注意

ExampleValidator可以使用我们之前描述的偏移和漂移比较器自动检测针对模式的异常。然而,这可能无法涵盖数据中的所有潜在异常。如果您需要检测其他特定的异常,您需要按照我们在第 10 章中描述的方法编写自己的定制组件。

如果ExampleValidator组件检测到新旧数据集之间的数据统计或模式不一致,它将在元数据存储中将状态设置为失败,并且管道最终会停止。否则,管道将继续到下一步,即数据预处理。

摘要

在本章中,我们讨论了数据验证的重要性以及如何有效执行和自动化此过程。我们讨论了如何生成数据统计和模式,并如何根据它们的统计和模式比较两个不同的数据集。我们通过一个示例演示了如何在 Google Cloud 上使用 Dataflow 运行数据验证,并最终将此机器学习步骤集成到我们的自动化管道中。这是我们管道中非常重要的一步,因为它可以阻止脏数据通过到耗时的预处理和训练步骤。

在接下来的章节中,我们将通过开始数据预处理来扩展我们的管道设置。

1   您可以在TensorFlow 存储库中找到模式协议的协议缓冲定义。

2   你还可以调整模式,以便在训练和服务环境中需要不同的特性。参见文档获取更多详细信息。

3   下载TFDV 包

第五章:数据预处理

我们用于训练机器学习模型的数据通常以我们的机器学习模型无法消费的格式提供。例如,在我们的示例项目中,我们希望用于训练模型的一个特征只以 Yes 和 No 标记的形式提供。任何机器学习模型都需要这些值的数值表示(例如,1 和 0)。在本章中,我们将解释如何将特征转换为一致的数值表示,以便您的机器学习模型能够使用这些特征的数值表示进行训练。

我们在本章中讨论的一个主要方面是专注于一致的预处理。如 图 5-1 所示,预处理发生在我们讨论的数据验证之后,该数据验证在 第四章 中有所讨论。TensorFlow Transform (TFT) 是 TFX 的数据预处理组件,允许我们将预处理步骤构建为 TensorFlow 图。在接下来的章节中,我们将讨论何时以及为何这是一个良好的工作流程,以及如何导出预处理步骤。在 第六章 中,我们将使用预处理的数据集和保留的转换图来训练和导出我们的机器学习模型,分别。

图 5-1. 数据预处理作为 ML 流水线的一部分

数据科学家可能会认为用 TensorFlow 操作(operations)表示的预处理步骤作为 TensorFlow Transform (TFT) 的一部分会增加太多的开销。毕竟,当您使用 Python 的 pandasnumpy 编写预处理步骤时,需要进行不同的实现。我们并不主张在实验阶段使用 TFT。然而,正如我们在接下来的章节中所展示的,当将您的机器学习模型引入生产环境时,将您的预处理步骤转换为 TensorFlow 操作将有助于避免我们在 第四章 中讨论的训练-服务偏差。

为什么需要数据预处理?

根据我们的经验,TFT 是 TFX 库中学习曲线最陡的组件之一,因为需要通过 TensorFlow 操作来表达预处理步骤。然而,有许多理由说明为什么应该在机器学习流水线中使用 TFT 标准化数据预处理,包括:

  • 在整个数据集的背景下高效地预处理您的数据

  • 有效地缩放预处理步骤

  • 避免潜在的训练-服务偏差

在整个数据集的背景下预处理数据

当我们希望将数据转换为数值表示时,通常必须在整个数据集的上下文中进行。例如,如果我们想要对数值特征进行归一化,我们必须首先确定训练集中特征的最小值和最大值。有了确定的边界,我们可以将我们的数据归一化到 0 到 1 之间的值。这种归一化步骤通常需要对数据进行两次遍历:一次用于确定边界,一次用于转换每个特征值。TFT 在幕后提供了管理数据遍历的函数。

扩展预处理步骤

TFT 在幕后使用 Apache Beam 执行预处理指令。这使我们可以根据需要在我们选择的 Apache Beam 后端上分发预处理。如果您没有访问 Google Cloud 的 Dataflow 产品或 Apache Spark 或 Apache Flink 集群,则 Apache Beam 将默认回退到其直接运行器模式。

避免训练-服务偏差

TFT 创建并保存了预处理步骤的 TensorFlow 图。首先,它将创建一个处理数据的图(例如,确定最小/最大值)。然后,它将保留具有确定边界的图。此图随后可以在模型生命周期的推断阶段中使用。此过程确保推断生命周期步骤中的模型看到与训练期间使用的模型相同的预处理步骤。

什么是训练-服务偏差?

当模型训练中使用的预处理步骤与推断过程中使用的步骤不一致时,我们称之为训练-服务偏差。在许多情况下,用于训练模型的数据在 Python 笔记本中通过pandas或者 Spark 作业进行处理。当模型部署到生产环境时,预处理步骤在数据进入模型进行预测之前会在 API 中实施。如您所见,如图 5-2 所示,这两个过程需要协调确保步骤始终保持一致。

图 5-2. 一个常用的机器学习设置

使用 TFT,我们可以避免预处理步骤的不一致性。如图 5-3 所示,现在请求预测的客户端可以提交原始数据,并且预处理发生在部署模型图上。

图 5-3. 使用 TFT 避免训练-服务偏差

这样的设置减少了所需的协调量并简化了部署。

部署预处理步骤和 ML 模型作为一个整体

为避免预处理步骤与训练模型之间的不一致,我们的管道导出模型应包括预处理图和训练模型。然后,我们可以像部署任何其他 TensorFlow 模型一样部署该模型,但在推断期间,数据将作为模型推断的一部分在模型服务器上进行预处理。这避免了需要在客户端进行预处理的要求,并简化了请求模型预测的客户端(如 Web 或移动应用程序)的开发。在第 11 和 12 章中,我们将讨论整个端到端管道如何生成这种“组合”保存模型。

在您的管道中检查预处理结果

使用 TFT 实现数据预处理并将其集成到我们的管道中带来了额外的好处。我们可以从预处理数据生成统计信息,并检查它们是否仍符合我们训练机器学习模型的要求。一个例子是将文本转换为标记。如果文本包含大量新词汇,未知标记将转换为所谓的 UNK 或未知标记。如果一定数量的我们的标记只是未知的,那么机器学习模型往往难以有效地从数据中推广,因此模型的准确性会受到影响。在我们的管道中,我们现在可以通过在预处理步骤后生成统计信息(显示在 Chapter 4 中)来检查预处理步骤的结果。

TF.DATA 和 TF.TRANSFORM 之间的区别

人们经常对 tf.data 和 tf.transform 感到困惑。tf.data 是用于使用 TensorFlow 构建高效输入管道以进行模型训练的 TensorFlow API。该库的目标是充分利用硬件资源,如主机 CPU 和 RAM,用于训练期间发生的数据摄入和预处理。另一方面,tf.transform 用于表达应在训练和推断时发生的预处理。该库使得能够对输入数据进行全通行分析(例如计算用于数据归一化的词汇表或统计信息),并且此分析在训练之前执行。

使用 TFT 进行数据预处理

TensorFlow 生态系统中用于数据预处理的库是 TFT。与 TFDV 类似,它是 TFX 项目的一部分。

TFT 使用我们之前生成的数据集架构处理我们流入管道的数据,并输出两个结果:

  • TFRecord 格式中的预处理训练和评估数据集。生成的数据集可以在我们管道的 Trainer 组件中下游消耗。

  • 导出预处理图(包括资源),在导出机器学习模型时将使用此图。

TFT 的关键在于preprocessing_fn函数,如图 5-4 所示。该函数定义了我们希望应用于原始数据的所有转换。当我们执行Transform组件时,preprocessing_fn函数将接收原始数据,应用转换,并返回处理后的数据。数据以 TensorFlow 张量或 SparseTensor 形式提供(取决于特征)。应用于张量的所有转换都必须是 TensorFlow 操作。这使得 TFT 能够有效地分发预处理步骤。

图 5-4. TFT 概述

TFT 功能

TFT 功能会在后台执行复杂的处理步骤,例如tft.compute_and_apply_vocabulary,可以通过tft前缀来识别。在 Python 命名空间中将 TFT 映射到缩写tft是一种常见做法。通常的 TensorFlow 操作将使用tf前缀加载,如tf.reshape

TensorFlow Transform 还提供了一些有用的功能(例如,tft.bucketizetft.compute_and_apply_vocabulary或者tft.scale_to_z_score)。当这些功能应用于数据集特征时,它们会执行所需的数据传递,然后将获取的边界应用于数据。例如,tft.compute_and_apply_vocabulary将生成语料库的词汇集,将创建的标记到索引映射应用于特征,并返回索引值。该功能可以将词汇标记数量限制为最相关标记的前 n 个。在接下来的章节中,我们将重点介绍一些最有用的 TFT 操作。

安装

当我们像在第二章中介绍的那样安装了tfx包时,TFT 会作为依赖项一起安装。如果我们想将 TFT 作为独立包使用,可以通过以下方式安装 PyPI 包:

$ pip install tensorflow-transform

安装了tfxtensorflow-transform之后,我们可以将预处理步骤集成到我们的机器学习管道中。让我们一起讨论几个用例。

预处理策略

正如我们之前讨论的,应用的转换在名为preprocessing_fn()的函数中定义。该函数将被我们的Transform管道组件或我们独立设置的 TFT 所消费。以下是我们将在接下来的章节中详细讨论的一个预处理函数示例:

def``preprocessing_fn``(``inputs``):``x``=``inputs``[``'x'``]``x_normalized``=``tft``.``scale_to_0_1``(``x``)``return``{``'x_xf'``:``x_normalized``}

该函数接收一个输入批次,作为一个 Python 字典。键是特征的名称,值是应用预处理之前的原始数据。首先,TFT 将执行一个分析步骤,如图 5-5 所示。在我们的小型演示示例中,它将通过对数据的完整遍历确定特征的最小值和最大值。由于在 Apache Beam 上执行预处理步骤,这个步骤可以以分布式的方式进行。

在对数据进行第二次遍历时,确定的值(在我们的案例中,特征列的最小值和最大值)被用来将我们的特征 x 缩放到 0 和 1 之间,如图 5-6 所示。

TFT 还为带有保留的最小值和最大值的预测生成一个图形。这将保证执行的一致性。

图 5-5. TFT 执行过程中的分析步骤

图 5-6. 应用分析步骤的结果

PREPROCESSING_FN()

请注意,TFT 将从preprocessing_fn()函数构建一个图,并在其自己的会话中运行。预期该函数返回一个字典,字典的值是转换后的特征。

最佳实践

在 在我们与 TFT 合作的过程中,我们学到了许多经验教训。以下是其中的一些:

特征名称很重要

预处理输出特征的命名很重要。如你将在以下 TFT 实现中看到,我们重用输入特征的名称并附加_xf。此外,TensorFlow 模型的输入节点名称需要与preprocessing_fn函数的输出特征名称匹配。

考虑数据类型

TFT 限制输出特征的数据类型。它将所有预处理的特征导出为tf.stringtf.float32tf.int64值。如果你的模型无法处理这些数据类型,这是很重要的。一些 TensorFlow Hub 的模型需要将输入呈现为tf.int32值(例如,BERT 模型)。我们可以通过在模型内部将输入转换为正确的数据类型,或者在估计器输入函数中转换数据类型来避免这种情况。

预处理发生在批次中

当你编写预处理函数时,你可能会认为它是一次处理一行数据。事实上,TFT 是批量执行操作。这就是为什么我们需要将preprocessing_fn()函数的输出重塑为 Tensor 或 SparseTensor,当我们在Transform组件的上下文中使用它时。

记住,不要使用即时执行

preprocessing_fn()函数中的函数需要由 TensorFlow 操作表示。如果你想将输入字符串转换为小写字母,不能使用lower()。你必须使用 TensorFlow 操作tf.strings.lower()在图形模式下执行相同的过程。不支持即时执行;所有操作都依赖于纯 TensorFlow 图形操作。

tf.function 可以在 preprocessing_fn() 函数中使用,但有限制:你只能使用接受张量的 tf.function(例如,lower() 不适用,因为它不适用于张量)。你不能调用 TFT 分析器(或依赖于分析器的映射器,例如 tft.scale_to_z_score)。

TFT 函数

TFT 提供了各种函数来促进高效的特征工程。所提供的函数列表非常广泛且不断增长。这就是为什么我们不宣称提供支持函数的完整列表,但我们想要强调与词汇生成、标准化和分桶相关的有用操作。

tft.scale_to_z_score()

如果你想要对一个特征进行标准化,使其均值为 0,标准差为 1,你可以使用这个有用的 TFT 函数。

tft.bucketize()

这个有用的函数可以将一个特征分桶化。它返回一个桶或者桶索引。你可以指定参数 num_buckets 来设置桶的数量。TFT 将会等分这些桶。

tft.pca()

这个函数让你能够为给定的特征计算主成分分析(PCA)。PCA 是一种常见的技术,通过线性投影数据到最能保留数据方差的子空间来减少维度。它需要参数 output_dim 来设置你的 PCA 表示的维度。

tft.compute_and_apply_vocabulary()

这是最惊人的 TFT 函数之一。它计算一个特征列的所有唯一值,然后将最频繁的值映射到一个索引。这个索引映射然后用于将特征转换为数值表示。该函数在幕后为你的图生成所有资产。我们可以通过两种方式配置最频繁的值:要么通过定义 top_k 中排名最高的 n 个唯一项,要么通过使用 frequency_threshold 以上每个元素以考虑词汇表中的频率。

tft.apply_saved_model()

这个函数让你能够在一个特征上应用整个 TensorFlow 模型。我们可以加载一个带有给定 tagsignature_name 的保存模型,然后 inputs 将会被传递给模型。来自模型执行的预测将会返回。

自然语言问题的文本数据

如果你正在处理自然语言处理问题,并且希望利用 TFT 对你的语料预处理以将文档转换为数值表示,TFT 提供了许多有用的函数。除了介绍的函数 tft.compute_and_apply_vocabulary(),你还可以使用以下 TFT 函数。

tft.ngrams()

这将生成 n-gram。它接受字符串值的 SparseTensor 作为输入。例如,如果你想要为列表['Tom', 'and', 'Jerry', 'are', 'friends']生成一元和二元组,该函数返回[b'Tom', b'Tom and', b'and', b'and Jerry', b'Jerry', b'Jerry are', b'are', b'are friends', b'friends']。除了稀疏输入张量外,该函数还接受两个额外的参数:ngram_rangeseparatorngram_range设置 n-gram 的范围。如果你的 n-gram 应包含一元和二元组,请将ngram_range设置为(1, 2)separator允许我们设置连接的字符串或字符。在我们的例子中,我们将separator设置为" "

tft.bag_of_words()

此函数使用tft.ngrams并生成一个以每个唯一 n-gram 为行的词袋向量。如果例如在输入中标记重复,可能不会保留 n-gram 的原始顺序。

tft.tfidf()

自然语言处理中经常使用的概念是 TFIDF(词频-逆文档频率),它生成两个输出:一个带有标记索引的向量和一个表示它们 TFIDF 权重的向量。该函数期望一个稀疏输入向量,表示标记索引(即tft.compute_and_apply_vocabulary()函数的结果)。这些向量的维度由vocab_size输入参数设置。每个标记索引的权重由标记在文档中的文档频率乘以逆文档频率计算得出。这个计算通常需要大量资源。因此,使用 TFT 来分布计算具有很大的优势。

TensorFlow Text还允许您使用 TensorFlow Text 库中的所有可用函数。该库为文本归一化、文本标记化、n-gram 计算以及像 BERT 这样的现代语言模型提供了广泛的 TensorFlow 支持。

用于计算机视觉问题的图像数据

如果你正在开发计算机视觉模型,TFT 可以为你预处理图像数据集。TensorFlow 提供了多种图像预处理操作,使用tf.imagestf.io APIs

tf.io提供了打开图像作为模型图的一部分的有用函数(例如,tf.io.decode_jpegtf.io.decode_png)。tf.images提供了裁剪或调整图像大小、转换颜色方案、调整图像(例如对比度、色调或亮度)或执行图像翻转、转置等操作的函数。

在第三章中,我们讨论了将图像导入到我们的管道中的策略。在 TFT 中,我们现在可以从 TFRecord 文件中读取编码图像,并且例如将它们调整为固定大小或将彩色图像缩减为灰度图像。以下是这样一个preprocessing_fn函数的实现示例:

def process_image(raw_image): raw_image = tf.reshape(raw_image, [-1]) img_rgb = tf.io.decode_jpeg(raw_image, channels=3)![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00002.jpg)img_gray = tf.image.rgb_to_grayscale(img_rgb)img = tf.image.convert_image_dtype(img, tf.float32) resized_img = tf.image.resize_with_pad(![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00064.jpg)img, target_height=300, target_width=300) img_grayscale = tf.image.rgb_to_grayscale(resized_img)![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00055.jpg)return tf.reshape(img_grayscale, [-1, 300, 300, 1])`

解码 JPEG 图像格式。

将加载的 RGB 图像转换为灰度。

将图像调整为 300 × 300 像素。

将图像转换为灰度。

tf.reshape() 操作作为 return 语句的一部分有一个注意事项:TFT 可能会以批处理的方式处理输入。由于批处理大小由 TFT(和 Apache Beam)处理,我们需要重新调整函数的输出以处理任何批处理大小。因此,我们将返回张量的第一个维度设置为 -1。其余维度表示我们的图像。我们将它们调整为 300 × 300 像素,并将 RGB 通道减少为灰度通道。

TFT 的独立执行

定义完 preprocessing_fn 函数后,我们需要关注如何执行 Transform 函数。对于执行,我们有两个选择。我们可以在独立的 Apache Beam 设置中执行预处理转换,也可以作为 TFX 组件的一部分执行在 Google Cloud 的 Dataflow 服务中。这两种执行方式都可以在本地 Apache Beam 设置上执行。在本节中,我们将讨论 TFT 的独立执行。如果您希望在流水线中集成 TFT,可以跳转到 “将 TFT 集成到您的机器学习流水线中”。

Apache Beam 提供了丰富的功能,超出了本书的范围。它值得有自己的出版物。然而,我们想通过 Apache Beam 的预处理“Hello World”示例向您介绍。

在我们的示例中,我们希望在我们的小型原始数据集上应用之前介绍过的标准化预处理函数,如以下源代码所示:

raw_data = [{'x': 1.20}, {'x': 2.99}, {'x': 100.00}]

首先,我们需要定义一个数据架构。我们可以从特征规范生成一个模式,如以下源代码所示。我们的小型数据集仅包含一个名为 x 的特征。我们使用 tf.float32 数据类型定义该特征:

import``tensorflow``as``tf``from``tensorflow_transform.tf_metadata``import``dataset_metadata``from``tensorflow_transform.tf_metadata``import``schema_utils``raw_data_metadata``=``dataset_metadata``.``DatasetMetadata``(``schema_utils``.``schema_from_feature_spec``({``'x'``:``tf``.``io``.``FixedLenFeature``([],``tf``.``float32``),``}))

加载数据集并生成数据模式后,我们现在可以执行之前定义的预处理函数 preprocessing_fn。TFT 提供了在 Apache Beam 上执行的绑定,使用函数 AnalyzeAndTransformDataset。这个函数执行了我们前面讨论过的两步过程:首先分析数据集,然后对数据集进行转换。执行通过 Python 上下文管理器 tft_beam.Context 完成,它允许我们设置例如所需的批处理大小。然而,我们建议使用默认的批处理大小,因为在通常的使用情况下更为高效。以下示例展示了使用 AnalyzeAndTransformDataset 函数的用法:

import``tempfile``import``tensorflow_transform.beam.impl``as``tft_beam``with``beam``.``Pipeline``()``as``pipeline``:``with``tft_beam``.``Context``(``temp_dir``=``tempfile``.``mkdtemp``()):``tfrecord_file``=``"/your/tf_records_file.tfrecord"``raw_data``=``(``pipeline``|``beam``.``io``.``ReadFromTFRecord``(``tfrecord_file``))``transformed_dataset``,``transform_fn``=``(``(``raw_data``,``raw_data_metadata``)``|``tft_beam``.``AnalyzeAndTransformDataset``(``preprocessing_fn``))

Apache Beam 函数调用的语法与通常的 Python 调用有些不同。在前面的示例中,我们使用 Apache Beam 函数 AnalyzeAndTransformDataset() 应用 preprocessing_fn 函数,并提供了我们的数据 raw_data 和我们定义的元数据模式 raw_data_metadata 作为两个参数。AnalyzeAndTransformDataset() 然后返回两个结果:预处理后的数据集和一个函数,这里命名为 transform_fn,代表应用于我们数据集的转换操作。

如果我们测试我们的“Hello World”示例,执行预处理步骤并打印结果,我们将看到这个小型处理后的数据集:

transformed_data``,``transformed_metadata``=``transformed_dataset``print``(``transformed_data``)``[``{``'x_xf'``:``0.0``},``{``'x_xf'``:``0.018117407``},``{``'x_xf'``:``1.0``}``]

在我们的“Hello World”示例中,我们完全忽略了数据不是作为 Python 字典可用的事实,通常需要从磁盘读取。Apache Beam 提供了处理文件摄取的函数(例如 beam.io.ReadFromText()beam.io.ReadFromTFRecord()),在构建 TensorFlow 模型的背景下非常有效。

正如您所看到的,定义 Apache Beam 执行可以迅速变得复杂,我们理解数据科学家和机器学习工程师不是从头开始编写执行指令的专业人士。这就是为什么 TFX 如此方便。它在幕后抽象了所有指令,并让数据科学家专注于他们的问题特定设置,例如定义preprocessing_fn()函数。在下一节中,我们将更详细地了解如何为我们的示例项目设置Transform

将 TFT 集成到您的机器学习流水线中

在本章的最后一节中,我们讨论如何将 TFT 的能力应用到我们的示例项目中。在第四章中,我们调查了数据集,并确定哪些特征是分类的或数值的,哪些特征应该被分桶化,以及我们希望将哪些特征从字符串表示转换为向量表示。这些信息对于定义我们的特征工程非常关键。

在下面的代码中,我们定义了我们的特征。为了简化后续的处理,我们将输入特征名字分组到代表每种转换输出数据类型的字典中:一位热编码特征、桶化特征和原始字符串表示:

import``tensorflow``as``tf``import``tensorflow_transform``as``tft``LABEL_KEY``=``"consumer_disputed"``# Feature name, feature dimensionality.``ONE_HOT_FEATURES``=``{``"product"``:``11``,``"sub_product"``:``45``,``"company_response"``:``5``,``"state"``:``60``,``"issue"``:``90``}``# Feature name, bucket count.``BUCKET_FEATURES``=``{``"zip_code"``:``10``}``# Feature name, value is unused.``TEXT_FEATURES``=``{``"consumer_complaint_narrative"``:``None``}

在我们可以循环遍历这些输入特征字典之前,让我们定义一些辅助函数以有效地转换数据。给特征名称添加后缀(例如 _xf)是一个良好的实践。该后缀将有助于区分错误是否源自输入或输出特征,并防止我们在实际模型中意外使用未转换的特征:

def``transformed_name``(``key``):``return``key``+``'_xf'

我们的一些特征具有稀疏性质,但是 TFT 期望转换输出是稠密的。我们可以使用下面的辅助函数将稀疏特征转换为稠密特征,并用默认值填充缺失值:

def``fill_in_missing``(``x``):``default_value``=``''``if``x``.``dtype``==``tf``.``string``or``to_string``else``0``if``type``(``x``)``==``tf``.``SparseTensor``:``x``=``tf``.``sparse``.``to_dense``(``tf``.``SparseTensor``(``x``.``indices``,``x``.``values``,``[``x``.``dense_shape``[``0``],``1``]),``default_value``)``return``tf``.``squeeze``(``x``,``axis``=``1``)

在我们的模型中,大多数输入特征都表示为一位热编码向量。下面的辅助函数将给定索引转换为一位热编码表示,并返回该向量:

def``convert_num_to_one_hot``(``label_tensor``,``num_labels``=``2``):``one_hot_tensor``=``tf``.``one_hot``(``label_tensor``,``num_labels``)``return``tf``.``reshape``(``one_hot_tensor``,``[``-``1``,``num_labels``])

在我们处理特征之前,我们需要另一个辅助函数将表示为字符串的邮政编码转换为浮点值。我们的数据集列出的邮政编码如下:

zip codes 97XXX 98XXX

为了正确地桶化具有缺失邮政编码的记录,我们用零替换占位符,并将结果浮点数桶化为 10 个桶:

def``convert_zip_code``(``zip_code``):``if``zip_code``==``''``:``zip_code``=``"00000"``zip_code``=``tf``.``strings``.``regex_replace``(``zip_code``, r``'X{0,5}'``,``"0"``)``zip_code``=``tf``.``strings``.``to_number``(``zip_code``,``out_type``=``tf``.``float32``)``return``zip_code

有了所有辅助函数的准备,我们现在可以循环遍历每个特征列并根据类型进行转换。例如,为了将我们的特征转换为独热特征,我们使用tft.compute_and_apply_vocabulary()将类别名称转换为索引,然后使用我们的辅助函数convert_num_to_one_hot()将索引转换为独热向量表示。由于我们使用了tft.compute_and_apply_vocabulary(),TensorFlow Transform 首先会遍历所有类别,然后确定完整的类别到索引映射。此映射将在模型的评估和服务阶段应用:

def``preprocessing_fn``(``inputs``):``outputs``=``{}``for``key``in``ONE_HOT_FEATURES``.``keys``():``dim``=``ONE_HOT_FEATURES``[``key``]``index``=``tft``.``compute_and_apply_vocabulary``(``fill_in_missing``(``inputs``[``key``]),``top_k``=``dim``+``1``)``outputs``[``transformed_name``(``key``)]``=``convert_num_to_one_hot``(``index``,``num_labels``=``dim``+``1``)``...``return``outputs

我们对桶特征的处理非常类似。我们决定对邮政编码进行桶化,因为单独编码的邮政编码似乎太稀疏了。在我们的情况下,每个特征被桶化为 10 个桶,我们将桶的索引编码为一个独热向量:

for``key``,``bucket_count``in``BUCKET_FEATURES``.``items``():``temp_feature``=``tft``.``bucketize``(``convert_zip_code``(``fill_in_missing``(``inputs``[``key``])),``bucket_count``,``always_return_num_quantiles``=``False``)``outputs``[``transformed_name``(``key``)]``=``convert_num_to_one_hot``(``temp_feature``,``num_labels``=``bucket_count``+``1``)

我们的文本输入特征以及标签列不需要任何转换;因此,我们只需将它们转换为稠密特征,以防某些特征可能是稀疏的:

for``key``in``TEXT_FEATURES``.``keys``():``outputs``[``transformed_name``(``key``)]``= \ fill_in_missing``(``inputs``[``key``])``outputs``[``transformed_name``(``LABEL_KEY``)]``=``fill_in_missing``(``inputs``[``LABEL_KEY``])

为什么我们没有将文本特征嵌入到向量中

也许你会想知道为什么我们没有将文本特征嵌入到转换步骤的固定向量中。这当然是可能的。但我们决定将 TensorFlow Hub 模型加载为模型的一部分,而不是预处理的一部分。这个决定的关键原因是我们可以在训练阶段使嵌入可训练,并在训练过程中改进向量表示。因此,在训练阶段,它们不能被硬编码到预处理步骤中,并在训练阶段表示为固定的图形。

如果我们在管道中使用来自 TFX 的Transform组件,它期望将转换代码提供在一个单独的 Python 文件中。用户可以设置模块文件的名称(例如,在我们的情况下是module.py),但入口点preprocessing_fn()必须包含在模块文件中,并且该函数不能被重命名:

transform``=``Transform``(``examples``=``example_gen``.``outputs``[``'examples'``],``schema``=``schema_gen``.``outputs``[``'schema'``],``module_file``=``os``.``path``.``abspath``(``"module.py"``))``context``.``run``(``transform``)

当我们执行Transform组件时,TFX 将会将我们在module.py模块文件中定义的转换应用于加载的输入数据,这些数据在数据摄取步骤期间已转换为 TFRecord 数据结构。然后,该组件将输出我们的转换后的数据、转换图以及所需的元数据。

转换后的数据和转换图可以在我们的下一个步骤中使用,即Trainer组件。查看“运行 Trainer 组件”了解如何消耗我们Transform组件的输出。接下来的章节还强调了如何将生成的转换图与训练模型结合起来导出保存的模型。更多细节可以在示例 6-2 中找到。

概要

在本章中,我们讨论了如何有效地在我们的机器学习管道中使用 TFT 对数据进行预处理。我们介绍了如何编写preprocessing_fn函数,概述了 TFT 提供的一些可用函数,并讨论了如何将预处理步骤集成到 TFX 管道中。现在数据已经预处理完毕,是时候训练我们的模型了。

第六章:模型训练

现在数据预处理步骤已经完成,并且数据已被转换为我们的模型所需的格式,我们管道中的下一步是使用新转换的数据训练模型。

正如我们在 第一章 中讨论的,我们不会涵盖选择模型架构的过程。我们假设您在拿起本书之前已经进行了独立的实验过程,并且您已经知道要训练的模型类型。我们在 第十五章 中讨论如何跟踪这个实验过程,因为它有助于为模型创建完整的审计跟踪。但我们不涵盖您需要了解模型训练过程的任何理论背景。如果您想进一步了解,请强烈推荐 O'Reilly 出版的《Scikit-Learn、Keras 和 TensorFlow 实战:机器学习实用指南》第 2 版。

在本章中,我们涵盖作为机器学习管道一部分的模型训练过程,包括在 TFX 管道中如何自动化这一过程。我们还包括 TensorFlow 中可用的分布策略的一些细节,以及如何在管道中调整超参数。这一章比大多数其他章节更专注于 TFX 管道,因为我们不单独涵盖训练作为一个独立的过程。

正如 图 6-1 所示,到这个时候数据已被摄取、验证和预处理。这确保了模型所需的所有数据都存在,并且已被可复制地转换为模型需要的特征。所有这些都是必要的,因为我们不希望管道在下一步失败。我们希望确保培训顺利进行,因为这通常是整个管道中耗时最长的部分。

图 6-1. ML 管道中的模型训练

在 TFX 管道中训练模型的一个非常重要的特性是,我们在 第五章 中讨论的数据预处理步骤会与训练好的模型权重一起保存。一旦我们的模型部署到生产环境中,这将非常有用,因为这意味着预处理步骤将始终生成模型期望的特征。如果没有这个特性,就有可能更新数据预处理步骤而不更新模型,那么模型在生产中可能会失败,或者预测将基于错误的数据。因为我们将预处理步骤和模型导出为一个图,所以我们消除了这种潜在的错误来源。

在接下来的两节中,我们将详细介绍作为 TFX 管道的一部分训练 tf.Keras 模型所需的步骤。1

为我们的示例项目定义模型

即使模型架构已经定义,这里仍然需要一些额外的代码。我们需要使得能够自动化管道中的模型训练部分成为可能。在本节中,我们将简要描述我们在整个本章中使用的模型。

我们示例项目的模型是一个假设实现,我们可能需要优化模型架构。但是,它展示了许多深度学习模型的一些常见组成部分:

  • 从预训练模型进行迁移学习

  • 密集层

  • 连接层

正如我们在第一章讨论的那样,我们示例项目中的模型使用来自美国消费者金融保护局的数据,以预测消费者是否对有关金融产品的投诉提出异议。我们模型的特征包括金融产品、公司的响应、美国州份和消费者投诉叙述。我们的模型受到宽与深模型架构的启发,并添加了来自TensorFlow HubUniversal Sentence Encoder来编码自由文本特征(消费者投诉叙述)。

您可以在图 6-2 中看到我们模型架构的视觉表示,其中文本特征(narrative_xf)采用“深层”路线,而其他特征采用“宽层”路线。

图 6-2. 我们示例项目的模型架构

示例 6-1 展示了完整的模型架构定义。因为我们希望导出带有我们预处理步骤的模型,我们需要确保模型输入名称与preprocessing_fn()转换的特征名称匹配,我们在第五章中讨论过。在我们的示例模型中,我们重用了在第五章中描述的transformed_name()函数,以添加后缀_xf到我们的特征中。

示例 6-1. 定义我们的模型架构

```importtensorflowastfimporttensorflow_hubashubdeftransformed_name(key):returnkey+'_xf'defget_model():# One-hot 分类特征input_features=[]forkey,diminONE_HOT_FEATURES.items():![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00002.jpg)input_features.append(tf.keras.Input(shape=(dim+1,),name=transformed_name(key)))# 添加分桶特征forkey,diminBUCKET_FEATURES.items():input_features.append(tf.keras.Input(shape=(dim+1,),name=transformed_name(key)))# 添加文本输入特征input_texts=[]forkeyinTEXT_FEATURES.keys():input_texts.append(tf.keras.Input(shape=(1,),name=transformed_name(key),dtype=tf.string))inputs=input_features+input_texts# 嵌入文本特征MODULE_URL="https://tfhub.dev/google/universal-sentence-encoder/4"embed=hub.KerasLayer(MODULE_URL)![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00075.jpg)reshaped_narrative=tf.reshape(input_texts[0],[-1])`![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00064.jpg)`embed_narrative=embed(reshaped_narrative)deep_ff=tf.keras.layers.Reshape((512,),input_shape=(1,512))(embed_narrative)deep=tf.keras.layers.Dense(256,activation='relu')(deep_ff)deep=tf.keras.layers.Dense(64,activation='relu')(deep)deep=tf.keras.layers.Dense(16,activation='relu')(deep)wide_ff=tf.keras.layers.concatenate(input_features)wide=tf.keras.layers.Dense(16,activation='relu')(wide_ff)both=tf.keras.layers.concatenate([deep,wide])output=tf.keras.layers.Dense(1,activation='sigmoid')(both)keras_model=tf.keras.models.Model(inputs,output)``keras_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),loss='binary_crossentropy',metrics=[tf.keras.metrics.BinaryAccuracy(),tf.keras.metrics.TruePositives()])return``keras_model`

循环遍历特征,并为每个特征创建一个输入。

加载tf.hub模块的通用句子编码器模型。

Keras 输入是二维的,但编码器期望一维的输入。

使用功能 API 组装模型图。

现在我们已经定义了我们的模型,让我们继续描述将其集成到 TFX 管道中的过程。

TFX 训练组件

TFX Trainer 组件处理我们流水线中的训练步骤。在本节中,我们将首先描述如何从示例项目中进行一次性训练运行 Keras 模型。在本节末尾,我们将添加一些考虑其他训练情况和 Estimator 模型的内容。

我们将描述的所有步骤可能看起来比普通的 Keras 训练代码冗长和不必要。但关键在于 Trainer 组件将生成一个模型,该模型将投入生产中,用于转换新数据并使用模型进行预测。由于这个模型包含了 Transform 步骤,数据预处理步骤将始终与模型期望的匹配。这消除了我们部署模型时可能产生的巨大错误源。

在我们的示例项目中,Trainer 组件需要以下输入:

  • 由数据验证步骤生成的先前生成的数据模式,在 第四章 中讨论

  • 转换后的数据及其预处理图,在 第五章 中讨论

  • 训练参数(例如,训练步数)

  • 包含 run_fn() 函数的模块文件,该函数定义了训练过程

在下一节中,我们将讨论 run_fn 函数的设置。我们还将介绍如何在我们的流水线中训练机器学习模型并将其导出到我们将在 第七章 中讨论的下一个流水线步骤中。

run_fn() 函数

Trainer 组件将在我们的模块文件中查找 run_fn() 函数,并使用该函数作为执行训练过程的入口点。模块文件需要对 Trainer 组件可访问。如果您在交互式环境中运行组件,只需定义模块文件的绝对路径并将其传递给组件即可。如果您在生产中运行流水线,请参阅 第十一章 或 第十二章 获取有关如何提供模块文件的详细信息。

run_fn() 函数是训练步骤的通用入口点,而不是 tf.Keras 特定的。它执行以下步骤:

  • 加载训练和验证数据(或数据生成器)

  • 定义模型架构并编译模型

  • 训练模型

  • 导出模型以便在下一个流水线步骤中进行评估

我们示例项目的 run_fn 在 示例 6-2 中执行这四个步骤。

示例 6-2. 我们示例流水线的 run_fn() 函数

def``run_fn``(``fn_args``):``tf_transform_output``=``tft``.``TFTransformOutput``(``fn_args``.``transform_output``)``train_dataset``=``input_fn``(``fn_args``.``train_files``,``tf_transform_output``)eval_dataset``=``input_fn``(``fn_args``.``eval_files``,``tf_transform_output``)``model``=``get_model``()model``.``fit``(``train_dataset``,``steps_per_epoch``=``fn_args``.``train_steps``,``validation_data``=``eval_dataset``,``validation_steps``=``fn_args``.``eval_steps``)signatures``=``{``'serving_default'``:``_get_serve_tf_examples_fn``(``model``,``tf_transform_output``)``.``get_concrete_function``(``tf``.``TensorSpec``(``shape``=``[``None``],``dtype``=``tf``.``string``,``name``=``'examples'``)``)``}model``.``save``(``fn_args``.``serving_model_dir``,``save_format``=``'tf'``,``signatures``=``signatures``)

调用input_fn函数以获取数据生成器。

调用get_model函数以获取已编译的 Keras 模型。

使用由Trainer组件传递的训练步数和评估步数来训练模型。

定义模型签名,包括稍后将描述的服务函数。

此函数相当通用,可以与任何其他tf.Keras模型重用。项目特定的细节由诸如get_model()input_fn()之类的辅助函数定义。

在接下来的章节中,我们将深入了解如何在run_fn()函数中加载数据、训练和导出我们的机器学习模型。

加载数据

run_fn中的以下行加载我们的训练和评估数据:

def``run_fn``(``fn_args``):``tf_transform_output``=``tft``.``TFTransformOutput``(``fn_args``.``transform_output``)``train_dataset``=``input_fn``(``fn_args``.``train_files``,``tf_transform_output``)``eval_dataset``=``input_fn``(``fn_args``.``eval_files``,``tf_transform_output``)

在第一行中,run_fn函数通过fn_args对象接收一组参数,包括变换图、示例数据集和训练参数。

数据加载用于模型训练和验证,以批处理方式处理,加载由input_fn()函数处理,如示例 6-3 所示。

示例 6-3. 我们示例管道的Input_fn函数

LABEL_KEY``=``'labels'``def``_gzip_reader_fn``(``filenames``):``return``tf``.``data``.``TFRecordDataset``(``filenames``,``compression_type``=``'GZIP'``)``def``input_fn``(``file_pattern``,``tf_transform_output``,``batch_size``=``32``):``transformed_feature_spec``=``(``tf_transform_output``.``transformed_feature_spec``()``.``copy``())``dataset``=``tf``.``data``.``experimental``.``make_batched_features_dataset``(``file_pattern``=``file_pattern``,``batch_size``=``batch_size``,``features``=``transformed_feature_spec``,``reader``=``_gzip_reader_fn``,``label_key``=``transformed_name``(``LABEL_KEY``))return``dataset

数据集将被分批为正确的批次大小。

input_fn函数允许我们加载由上一个 Transform 步骤生成的压缩预处理数据集。2 为此,我们需要将tf_transform_output传递给函数。这为我们提供了从 Transform 组件生成的 TFRecord 数据结构加载数据集的数据模式。通过使用预处理数据集,我们可以避免在训练期间进行数据预处理,从而加快训练过程。

input_fn返回一个生成器(一个batched_features_dataset),它将以一批一次的方式向模型提供数据。

编译并训练模型

现在,我们已经定义了数据加载步骤,下一步是定义我们的模型架构并编译我们的模型。在我们的run_fn中,这将需要调用get_model(),我们已经描述了这一点,所以只需要一行代码:

model``=``get_model``()

接下来,我们使用 Keras 的fit()方法训练我们已编译的tf.Keras模型:

model``.``fit``(``train_dataset``,``steps_per_epoch``=``fn_args``.``train_steps``,``validation_data``=``eval_dataset``,``validation_steps``=``fn_args``.``eval_steps``)

训练步骤与轮数的比较

TFX Trainer组件通过训练步数而不是轮数定义训练过程。训练步骤是指模型在单个数据批次上训练的次数。使用步数而不是轮数的好处在于,我们可以用大型数据集训练或验证模型,并且只使用数据的一小部分。同时,如果您希望在训练过程中多次遍历训练数据集,可以增加步长以达到可用样本的倍数。

模型训练完成后,下一步是导出训练好的模型。我们将在第八章详细讨论导出模型以用于部署。在接下来的部分中,我们希望强调如何将预处理步骤与模型一起导出。

模型导出

最后,我们导出模型。我们将前一个管道组件的预处理步骤与训练模型结合起来,并将模型保存为 TensorFlow 的 SavedModel 格式。我们根据由 Example 6-4 函数生成的图定义模型签名。我们将在“模型签名”中更详细地描述模型签名在第八章中。

run_fn函数中,我们定义了模型签名,并使用以下代码保存模型:

signatures``=``{``'serving_default'``:``_get_serve_tf_examples_fn``(``model``,``tf_transform_output``)``.``get_concrete_function``(``tf``.``TensorSpec``(``shape``=``[``None``],``dtype``=``tf``.``string``,``name``=``'examples'``)``)``}``model``.``save``(``fn_args``.``serving_model_dir``,``save_format``=``'tf'``,``signatures``=``signatures``)

run_fnget_serve_tf_examples_fn导出为模型签名的一部分。当模型被导出并部署时,每个预测请求都会通过 Example 6-4 中显示的serve_tf_examples_fn()。每个请求,我们解析序列化的tf.Example记录,并将预处理步骤应用于原始请求数据。然后,模型对预处理数据进行预测。

Example 6-4. 将预处理图应用于模型输入

def``get_serve_tf_examples_fn``(``model``,``tf_transform_output``):``model``.``tft_layer``=``tf_transform_output``.``transform_features_layer``()@tf.function``def``serve_tf_examples_fn``(``serialized_tf_examples``):``feature_spec``=``tf_transform_output``.``raw_feature_spec``()``feature_spec``.``pop``(``LABEL_KEY``)``parsed_features``=``tf``.``io``.``parse_example``(``serialized_tf_examples``,``feature_spec``)transformed_features``=``model``.``tft_layer``(``parsed_features``)outputs``=``model``(``transformed_features``)return``{``'outputs'``:``outputs``}``return``serve_tf_examples_fn

加载预处理图。

解析来自请求的原始tf.Example记录。

将预处理转换应用于原始数据。

使用预处理数据进行预测。

完成

运行训练组件

如 Example 6-5 所示,Trainer组件将以下内容作为输入:

  • Python 模块文件,保存为 module.py,包含我们之前讨论过的run_fn()input_fn()get_serve_tf_examples_fn()等相关函数

  • 转换组件生成的转换示例

  • 转换组件生成的转换图

  • 数据验证组件生成的模式

  • 训练和评估步骤的数量

Example 6-5. Trainer组件

from``tfx.components``import``Trainer``from``tfx.components.base``import``executor_spec``from``tfx.components.trainer.executor``import``GenericExecutorfrom``tfx.proto``import``trainer_pb2``TRAINING_STEPS``=``1000``EVALUATION_STEPS``=``100``trainer``=``Trainer``(``module_file``=``os``.``path``.``abspath``(``"module.py"``),``custom_executor_spec``=``executor_spec``.``ExecutorClassSpec``(``GenericExecutor``),transformed_examples``=``transform``.``outputs``[``'transformed_examples'``],``transform_graph``=``transform``.``outputs``[``'transform_graph'``],``schema``=``schema_gen``.``outputs``[``'schema'``],``train_args``=``trainer_pb2``.``TrainArgs``(``num_steps``=``TRAINING_STEPS``),``eval_args``=``trainer_pb2``.``EvalArgs``(``num_steps``=``EVALUATION_STEPS``))

加载 GenericExecutor 来覆盖训练执行器。

覆盖执行器以加载 run_fn() 函数。

在笔记本环境(交互式环境)中,我们可以像以前的组件一样运行 Trainer 组件,使用以下命令:

context``.``run``(``trainer``)

在完成模型训练和导出后,该组件将注册导出模型的路径到元数据存储中。下游组件可以提取模型进行模型验证。

Trainer 组件是通用的,并不限于运行 TensorFlow 模型。然而,管道中稍后的组件期望模型以 TensorFlow SavedModel 格式保存。SavedModel 图包括 Transform 图,因此数据预处理步骤是模型的一部分。

覆盖 Trainer 组件的执行器

在我们的示例项目中,我们覆盖了 Trainer 组件的执行器,以启用通用的训练入口函数 run_fn(),而不是仅支持 tf.Estimator 模型的默认 trainer_fn() 函数。在 第十二章,我们将介绍另一个 Trainer 执行器,即 ai_platform_trainer_executor.GenericExecutor。此执行器允许您在谷歌云的 AI 平台上训练模型,而不是在管道环境内部。如果您的模型需要特定的训练硬件(例如 GPU 或张量处理单元 [TPU]),这是一种替代方法。

其他 Trainer 组件注意事项

到目前为止,在本章的示例中,我们只考虑了对 Keras 模型的单次训练运行。但我们也可以使用 Trainer 组件对先前运行的模型进行微调,或者同时训练多个模型,并且我们将在 “高级管道概念” 中描述这些内容。我们还可以使用它通过超参数搜索来优化模型,我们将在 “模型调优” 中更详细地讨论这一点。

在本节中,我们还将讨论如何与Estimator模型一起使用Trainer组件,以及如何在 TFX 管道之外加载由Trainer组件导出的 SavedModel。

使用 Trainer 组件与 Estimator 模型

直到最近,TFX 仅支持tf.Estimator模型,而Trainer组件仅设计用于EstimatorTrainer组件的默认实现使用trainer_fn()函数作为训练过程的入口点,但该入口点非常依赖于tf.EstimatorTrainer组件期望 Estimator 输入由函数如train_input_fn()eval_input_fn()serving_receiver_fn()定义。3

如我们在“运行训练器组件”中讨论的,该组件的核心功能可以通过通用训练执行器GenericExecutor进行替换,该执行器使用run_fn()函数作为训练过程的入口点。4 正如执行器的名称所示,训练过程变得通用化,不再局限于tf.Estimatortf.Keras模型。

在管道之外使用 SavedModel

如果我们想要在 TFX 管道之外检查导出的 SavedModel,我们可以将该模型加载为具体函数,5 代表单个签名的图形:

model_path``=``trainer``.``outputs``.``model``.``get``()[``0``]``.``uri``model``=``tf``.``saved_model``.``load``(``export_dir``=``model_path``)``predict_fn``=``model``.``signatures``[``"serving_default"``]

当模型加载为具体函数后,我们现在可以进行预测。导出的模型期望以tf.Example数据结构提供输入数据,如下面的示例所示。有关tf.Example数据结构的更多细节,以及如何转换其他特征(如整数和浮点数),请参见 Example 3-1。以下代码显示如何创建序列化数据结构,并通过调用prediction_fn()函数执行模型预测:

example``=``tf``.``train``.``Example``(``features``=``tf``.``train``.``Features``(``feature``=``{``'feature_A'``:``_bytes_feature``(``feature_A_value``),``...``}))serialized_example``=``example``.``SerializeToString``()``print``(``predict_fn``(``tf``.``constant``([``serialized_example``])))

_bytes_feature辅助函数在 Example 3-1 中定义。

如果您希望在训练过程中详细检查模型的进展,可以使用 TensorBoard。我们将在下一节中描述如何在我们的管道中使用 TensorBoard。

在交互式管道中使用 TensorBoard

TensorBoard 是 TensorFlow 生态系统中的另一个精彩工具。它具有许多有用的功能,我们可以在我们的流水线中使用,例如在训练过程中监控指标、可视化自然语言处理问题中的词嵌入,或查看模型中层的激活情况。一个新的Profiler feature让我们能够分析模型以理解性能瓶颈。

TensorBoard 的基本可视化示例如图 Figure 6-3 所示。

图 6-3. 在 TensorBoard 中查看训练过程中的指标

要在我们的流水线中使用 TensorBoard,我们需要在 run_fn 函数中添加回调并将训练日志记录到指定的文件夹中:

log_dir``=``os``.``path``.``join``(``os``.``path``.``dirname``(``fn_args``.``serving_model_dir``),``'logs'``)``tensorboard_callback``=``tf``.``keras``.``callbacks``.``TensorBoard``(``log_dir``=``log_dir``,``update_freq``=``'batch'``)

我们还需要将回调添加到我们的模型训练中:

model``.``fit``(``train_dataset``,``steps_per_epoch``=``fn_args``.``train_steps``,``validation_data``=``eval_dataset``,``validation_steps``=``fn_args``.``eval_steps``,``callbacks``=``[``tensorboard_callback``])

然后,要在笔记本中查看 TensorBoard,我们获取模型训练日志的位置,并将其传递给 TensorBoard:

model_dir``=``trainer``.``outputs``[``'output'``]``.``get``()[``0``]``.``uri``%``load_ext``tensorboard``%``tensorboard``--``logdir``{``model_dir``}

我们还可以在笔记本之外运行 TensorBoard:

tensorboard --logdir path/to/logs

然后连接到 localhost:6006/ 查看 TensorBoard。这将为我们提供一个更大的视窗来查看细节。

接下来,我们将介绍一些在多个 GPU 上训练大型模型时有用的策略。

分布策略

TensorFlow 提供了分布策略,用于无法在单个 GPU 上充分训练的机器学习模型。当您希望加速训练或无法将整个模型放入单个 GPU 时,您可能需要考虑分布策略。

这里描述的策略是将模型参数抽象化以分布到多个 GPU 或多个服务器上。一般来说,有两组策略:同步训练和异步训练。在同步策略下,所有训练工作节点都同步使用不同切片的训练数据进行训练,然后在更新模型之前聚合所有工作节点的梯度。异步策略则是独立训练模型,并行处理整个数据集。每个工作节点异步更新模型的梯度,无需等待其他工作节点完成。通常,同步策略通过全局归约操作 6 协调,而异步策略通过参数服务器架构实现。

存在一些同步和异步策略,它们各自有其优缺点。在撰写本节时,Keras 支持以下策略:

MirroredStrategy

该策略适用于单个实例上的多个 GPU,并遵循同步训练模式。该策略会在各个工作器之间镜像模型和参数,但每个工作器会接收不同的数据批次。如果你在单节点多 GPU 环境下训练机器学习模型,并且你的模型适合 GPU 内存,MirroredStrategy 是一个很好的默认策略。

CentralStorageStrategy

与 MirroredStrategy 相比,这种策略不会将变量镜像到所有 GPU 上。相反,它们存储在 CPU 内存中,然后复制到分配的 GPU 上执行相关操作。在单 GPU 操作的情况下,CentralStorageStrategy 会将变量存储在 GPU 上,而不是 CPU 上。当你在单节点多 GPU 环境下进行训练,且整个模型不适合单个 GPU 内存,或者 GPU 之间的通信带宽过于有限时,CentralStorageStrategy 是一个很好的策略。

MultiWorkerMirroredStrategy

这种策略遵循 MirroredStrategy 的设计模式,但是它将变量复制到多个工作器(例如计算实例)之间。如果单个节点不足以支持你的模型训练,MultiWorkerMirroredStrategy 是一个选择。

TPUStrategy

此策略允许您使用 Google Cloud 的 TPU。它遵循同步训练模式,基本上与 MirroredStrategy 类似,但使用 TPU 而不是 GPU。由于 MirroredStrategy 使用 GPU 特定的全局减函数,因此 TPU 策略需要自己的策略。TPU 具有大量可用的 RAM,并且跨 TPU 通信高度优化,这就是为什么 TPU 策略使用了镜像方法。

ParameterServerStrategy

ParameterServerStrategy 使用多个节点作为中央变量存储库。对于超出单节点可用资源(例如 RAM 或 I/O 带宽)的模型,此策略非常有用。如果无法在单节点上进行训练且模型超出了节点的 RAM 或 I/O 限制,则 ParameterServerStrategy 是唯一的选择。

OneDeviceStrategy

OneDeviceStrategy 的整体目的是在进行真正的分布式训练之前测试整个模型设置。该策略强制模型训练仅使用一个设备(例如一个 GPU)。一旦确认训练设置有效,可以切换策略。

并非所有策略都可以通过 TFX Trainer 组件使用

在撰写本节时,TFX 的Trainer组件仅支持 MirroredStrategy。虽然不同的策略目前可以在tf.keras中使用,但根据TFX 路线图,它们将在 2020 年下半年通过Trainer组件变得可访问。

因为MirroredStrategy由 TFX Trainer 支持,我们将在此展示一个示例。在调用我们的模型创建及后续的model.compile()之前,我们可以轻松地应用MirroredStrategy,只需添加几行代码:

mirrored_strategy``=``tf``.``distribute``.``MirroredStrategy``()with``mirrored_strategy``.``scope``():model``=``get_model``()

分布策略的实例。

用 Python 管理器包装模型创建和编译。

在这个示例设置中,我们创建了一个MirroredStrategy的实例。为了将分布策略应用到我们的模型中,我们将模型创建和编译包装在 Python 管理器中(在我们的情况下,这一切发生在get_model()函数内部)。这将在我们选择的分布范围下创建和编译我们的模型。MirroredStrategy将使用实例中所有可用的 GPU。如果您希望减少使用的 GPU 实例数(例如在共享实例的情况下),可以通过更改分布策略的创建来指定要使用的 GPU:

mirrored_strategy``=``tf``.``distribute``.``MirroredStrategy``(``devices``=``[``"/gpu:0"``,``"/gpu:1"``])

在本例中,我们指定了两个 GPU 用于训练运行。

使用 MirroredStrategy 时的批量大小要求

MirroredStrategy期望批量大小与设备数量成比例。例如,如果您使用五个 GPU 进行训练,则批量大小需要是 GPU 数量的倍数。在设置您的input_fn()函数时,请牢记这一点,如示例 6-3 所述。

这些分布策略对于那些单个 GPU 内存无法容纳的大型训练作业非常有用。模型调优是我们需要这些策略的常见原因,在下一节中将对此进行讨论。

模型调优

超参数调优是实现准确机器学习模型的重要部分。根据用例,它可能是我们在初始实验中执行的事项,也可能是我们想要包含在管道中的内容。这不是模型调优的全面介绍,但我们将在此简要概述并描述其如何包含在管道中。

超参数调优策略

根据管道中模型的类型,选择的超参数会有所不同。如果您的模型是深度神经网络,则超参数调优对于实现良好性能尤为重要。控制优化和网络架构的两组最重要的超参数之一。

对于优化,我们建议默认使用AdamNAdam。学习率是一个非常重要的参数,可以进行实验,有许多可能的选择用于学习率调度器。我们建议使用适合 GPU 内存的最大批量大小。

对于非常大的模型,我们建议以下步骤:

  • 调节初始学习率,从 0.1 开始。

  • 选择一个训练步数(尽可能多,只要耐心允许)。

  • 在指定的步数内线性衰减学习率至 0。

对于较小的模型,我们建议使用提前停止以避免过拟合。使用此技术时,模型训练会在经过用户定义的一定数量的 epochs 后,验证损失不再改善时停止。

对于网络架构,调节的两个最重要的参数是大小和层数。增加这些参数将改善训练性能,但可能导致过拟合,并意味着模型训练时间更长。您还可以考虑在层之间添加残差连接,特别是对于深层架构。

最流行的超参数搜索方法是网格搜索和随机搜索。在网格搜索中,会穷举尝试每个参数的所有组合;而在随机搜索中,参数从可用选项中抽样,可能不会尝试每个组合。如果可能的超参数数量很大,网格搜索可能会非常耗时。尝试了一系列值之后,可以通过选择表现最佳的超参数,并围绕它们开始新的搜索来进行微调。

在 TensorFlow 生态系统中,使用Keras TunerKatib来实现超参数调优,Katib 在 Kubeflow 中提供超参数调优。除了网格搜索和随机搜索外,这两个包还支持贝叶斯搜索和Hyperband 算法

TFX 管道中的超参数调优

在 TFX 管道中,超参数调优使用 Transform 组件中的数据进行,训练多种模型以确定最佳超参数。然后将这些超参数传递给Trainer组件,使用它们训练最终模型。

在这种情况下,模型定义函数(例如我们示例中的get_model函数)需要接受超参数作为输入,并根据指定的超参数构建模型。例如,层数需要作为输入参数定义。

TFX 调节器组件

在我们完成本书的过程中发布了 TFX 调节器组件。您可以在项目的GitHub 仓库中查看源代码。

概要

在本章中,我们描述了如何将我们的模型训练从独立脚本转移到我们流水线的集成部分。这意味着该过程可以自动化,并在需要时触发——例如在新数据到达流水线或者之前的模型准确度低于预定水平时。我们还描述了如何将模型和数据预处理步骤一起保存,以避免预处理与训练之间的不匹配导致的任何错误。此外,我们还介绍了分发模型训练和调整超参数的策略。

现在我们有了一个保存的模型,下一步是深入了解它能做什么。

1   在我们的示例项目中使用了 Keras 模型,但 TFX 与 Estimator 模型也能完美配合。示例可以在《TFX 文档》中找到。

2   Trainer 组件可以在没有前置的 Transform 组件的情况下使用,并且可以加载原始数据集。然而,在这种情况下,我们将错过 TFX 的一个很好的特性,即将预处理和模型图导出为一个 SavedModel 图。

3   tf.Keras 模型可以通过 tf.model_to_estimator() 转换成 tf.Estimator 模型。然而,由于 TFX 的最新更新,这不再是推荐的最佳实践。

4   如果您对组件执行者如何开发和交换的步骤感兴趣,我们建议查看《重复使用现有组件》 章节中的 第十章。

5   想要了解具体函数的更多细节,请查看《TensorFlow 文档》

6   全局归约操作将所有工作节点的信息归约为单一信息;换句话说,它实现了所有训练工作节点之间的同步。

第七章:模型分析与验证

在机器学习流水线的这一点上,我们已经检查了数据的统计信息,将数据转换为正确的特征,并训练了我们的模型。现在肯定是将模型投入生产的时候了吧?在我们看来,在部署模型之前应该有两个额外的步骤:深入分析模型的性能,并检查其是否优于已经投入生产的任何模型。我们展示了这些步骤如何融入流水线中,见图 7-1。

图 7-1 模型分析与验证作为 ML 流水线的一部分

在我们训练模型时,我们会在训练过程中监控其在评估集上的表现,并尝试各种超参数以达到最佳表现。但通常在训练过程中只使用一个指标,而且这个指标通常是准确率。

在构建机器学习流水线时,我们经常试图回答复杂的业务问题或建模复杂的现实系统。通常一个单一的指标不足以告诉我们我们的模型是否能够回答那个问题。特别是如果我们的数据集不平衡或者我们模型的一些决策比其他决策有更高的后果时,情况就更是如此。

此外,一个单一的指标,用于评估整个评估集的性能平均值,可能隐藏了许多重要的细节。如果您的模型处理的数据涉及到人们,那么与模型交互的每个人都会有相同的体验吗?您的模型对女性用户表现更好还是对男性用户表现更好?来自日本的用户是否比来自美国的用户看到更差的结果?这些差异可能会在商业上造成损害,并对真实的人造成伤害。如果您的模型正在为自动驾驶车辆进行物体检测,它是否在所有光照条件下都能正常工作?使用一个指标来评估整个训练集可能会隐藏重要的边缘和特殊案例。因此,能够跨数据集的不同切片监控指标是至关重要的。

在部署之前、部署之后以及生产过程中监控您的指标非常重要。即使您的模型是静态的,流水线中输入的数据会随时间变化,这经常会导致性能下降。

在本章中,我们将介绍 TensorFlow 生态系统中的下一个工具包:TensorFlow Model Analysis(TFMA),它具有所有这些功能。我们将展示如何获取模型性能的详细指标,如何对数据进行切片以获取不同群体的指标,并深入探讨使用公平性指标和 What-If Tool 进行模型公平性分析。然后我们将解释如何在分析之外,开始解释模型的预测结果。

在部署新模型之前的最后一步,我们还将描述验证该模型是否优于任何先前版本。重要的是,部署到生产环境的任何新模型都代表了一步前进,以便依赖该模型的任何其他服务也得到改进。如果新模型在某些方面没有改进,那么部署的努力就不值得。

如何分析您的模型

我们的模型分析过程始于我们对度量标准的选择。正如我们之前讨论的那样,我们的选择对我们的机器学习流水线的成功非常重要。在面对业务问题时,选择多个有意义的度量标准是一个好的实践,因为单一的度量标准可能隐藏重要的细节。在本节中,我们将回顾一些对分类和回归问题都非常重要的度量标准。

分类度量指标

要计算许多分类度量指标,首先需要计算评估集中真假阳性示例和真假阴性示例的数量。以我们标签中的任一类别为例:

真阳性

属于该类别并且被分类器正确地标记为该类别的训练示例。例如,如果真实标签是1,预测标签也是1,则该示例将是真阳性。

假阳性

训练示例不属于该类别,并且分类器错误地标记为该类别。例如,如果真实标签是0,但预测标签是1,则该示例将是假阳性。

真阴性

训练示例不属于该类别,并且分类器正确地标记为不属于该类别。例如,如果真实标签是0,预测标签也是0,则该示例将是真阴性。

假阴性

属于该类别且被分类器错误地标记为不属于该类别的训练示例。例如,如果真实标签是1,但预测标签是0,则该示例将是假阴性。

这些基本度量通常显示在表格 7-1 中。

表 7-1. 混淆矩阵

预测 预测
真实值 1 真阳性 假阴性
真实值 0 假阳性 真阴性

如果我们为示例项目中的模型计算所有这些度量指标,我们将得到图表 7-2 中显示的结果。

图 7-2. 我们示例项目的混淆矩阵

当我们在本章后面讨论模型公正性时,我们将看到这些计数特别有用。有几个其他度量标准用这些计数合并为一个单一数字比较模型:

准确率

精度被定义为(真正例 + 真负例)/ 总示例数,或者分类正确的示例比例。这是用于数据集的合适度量标准,其中正类和负类平衡,但如果数据集不平衡,则可能会误导。

精度

精度被定义为真正例 /(真负例 + 假正例),或者被预测为正类的示例中被正确分类的比例。因此,如果分类器具有高精度,则它预测为正类的大多数示例将确实属于正类。

召回率

召回率被定义为真正例 /(真正例 + 假反例),或分类器正确识别的正类示例比例。因此,如果分类器具有高召回率,则它将正确识别大多数真正属于正类的示例。

描述模型性能的另一种方法是通过 AUC(曲线下面积)生成单个数字。这里的“曲线”是接收者操作特征(ROC),它绘制了真正例率(TPR)与假正例率(FPR)之间的关系。

TPR 是召回率的另一种称呼,定义如下:

FPR 的定义如下:

ROC 是通过计算所有分类阈值下的 TPR 和 FPR 来生成的。分类阈值是将示例分配到正类或负类的概率截断值,通常为 0.5. 图 7-3 显示了我们示例项目的 ROC 和 AUC。对于随机预测器,ROC 将是从原点到 [1,1] 的直线,沿 x 轴。随着 ROC 从 x 轴向绘图的左上方移动,模型改进并且 AUC 增加。AUC 是另一个可以在 TFMA 中绘制的有用指标。

图 7-3. 我们示例项目的 ROC 曲线

回归指标

在回归问题中,模型为每个训练示例预测某个数值,并将其与实际值进行比较。在 TFMA 中可以使用的常见回归指标包括:

平均绝对误差(MAE)

MAE 的定义如下:

其中 n 是训练示例的数量,y 是真实值,ŷ 是预测值。对于每个训练示例,计算预测值和真实值之间的绝对差。换句话说,MAE 是模型产生的平均误差。

平均绝对百分比误差(MAPE)

MAPE 的定义如下:

正如其名称所示,该指标给出所有示例的百分比误差。当模型产生系统性错误时,这特别有用。

均方误差(MSE)

MSE 的定义如下:

这与 MAE 类似,只是 y – ŷ 项被平方。这使得异常值对整体误差的影响更大。

一旦您选择了适合业务问题的指标,下一步就是将它们包含在您的机器学习流水线中。您可以使用 TFMA 来完成这一步骤,我们将在下一节中描述它。

TensorFlow 模型分析

TFMA 提供了比模型训练期间仅使用的更详细的指标的简便方法。它允许我们将指标可视化为模型版本间的时间序列,并且可以查看数据集切片上的指标。由于使用了 Apache Beam,它还可以轻松扩展到大型评估集。

在 TFX 流水线中,TFMA 基于由Trainer组件导出的保存模型计算指标,这正是将要部署的模型。因此,它避免了不同模型版本之间的混淆。在模型训练期间,如果使用 TensorBoard,您将仅获得对小批量测量的近似指标,但 TFMA 会计算整个评估集上的指标。这对于大型评估集尤为重要。

分析单个 TFMA 模型

在本节中,我们将介绍如何将 TFMA 作为一个独立的包使用。TFMA 的安装方法如下:

$ pip install tensorflow-model-analysis

它接受一个保存的模型和一个评估数据集作为输入。在本例中,我们假设一个 Keras 模型以SavedModel格式保存,评估数据集以 TFRecord 文件格式可用。

首先,SavedModel必须转换为EvalSharedModel

import``tensorflow_model_analysis``as``tfma``eval_shared_model``=``tfma``.``default_eval_shared_model``(``eval_saved_model_path``=``_MODEL_DIR``,``tags``=``[``tf``.``saved_model``.``SERVING``])

接下来,我们提供了一个EvalConfig。在这一步中,我们告诉 TFMA 我们的标签是什么,提供了任何按特征之一对模型进行切片的规范,并规定了我们希望 TFMA 计算和显示的所有指标:

eval_config``=``tfma``.``EvalConfig``(``model_specs``=``[``tfma``.``ModelSpec``(``label_key``=``'consumer_disputed'``)],``slicing_specs``=``[``tfma``.``SlicingSpec``()],``metrics_specs``=``[``tfma``.``MetricsSpec``(``metrics``=``[``tfma``.``MetricConfig``(``class_name``=``'BinaryAccuracy'``),``tfma``.``MetricConfig``(``class_name``=``'ExampleCount'``),``tfma``.``MetricConfig``(``class_name``=``'FalsePositives'``),``tfma``.``MetricConfig``(``class_name``=``'TruePositives'``),``tfma``.``MetricConfig``(``class_name``=``'FalseNegatives'``),``tfma``.``MetricConfig``(``class_name``=``'TrueNegatives'``)``])``]``)

分析 TFLite 模型

我们还可以在 TFMA 中分析 TFLite 模型。在这种情况下,必须将模型类型传递给ModelSpec

eval_config``=``tfma``.``EvalConfig``(``model_specs``=``[``tfma``.``ModelSpec``(``label_key``=``'my_label'``,``model_type``=``tfma``.``TF_LITE``)],``...``)

我们将在“TFLite”中更详细地讨论 TFLite。

然后,运行模型分析步骤:

eval_result``=``tfma``.``run_model_analysis``(``eval_shared_model``=``eval_shared_model``,``eval_config``=``eval_config``,``data_location``=``_EVAL_DATA_FILE``,``output_path``=``_EVAL_RESULT_LOCATION``,``file_format``=``'tfrecords'``)

并在 Jupyter Notebook 中查看结果:

tfma``.``view``.``render_slicing_metrics``(``eval_result``)

即使我们希望查看总体指标,我们仍然调用render_slicing_metrics。在这种情况下,切片是整体切片,即整个数据集。结果显示在图 7-4 中。

图 7-4. TFMA 笔记本中总体指标的可视化

在 Jupyter Notebook 中使用 TFMA

TFMA 的工作方式如 Google Colab 笔记本中所述。但是在独立的 Jupyter Notebook 中查看可视化需要一些额外的步骤。使用以下命令安装并启用 TFMA 笔记本扩展:

$ jupyter nbextension enable --py widgetsnbextension $ jupyter nbextension install --py \ --symlink tensorflow_model_analysis $ jupyter nbextension enable --py tensorflow_model_analysis

如果您在 Python 虚拟环境中运行这些命令,请为每个命令附加--sys_prefix标志。widgetsnbextensionipywidgetsjupyter_nbextensions_configurator包可能需要安装或升级。

我们编写时,TFMA 可视化仅在 Jupyter Notebook 中可用,而不在 Jupyter Lab 中。

我们在 TFMA 中描述的所有指标都可以通过在metrics_specs参数中提供它们来显示。到EvalConfig

metrics_specs``=``[``tfma``.``MetricsSpec``(``metrics``=``[``tfma``.``MetricConfig``(``class_name``=``'BinaryAccuracy'``),``tfma``.``MetricConfig``(``class_name``=``'AUC'``),``tfma``.``MetricConfig``(``class_name``=``'ExampleCount'``),``tfma``.``MetricConfig``(``class_name``=``'Precision'``),``tfma``.``MetricConfig``(``class_name``=``'Recall'``)``])``]

结果显示在图 7-5 中。

图 7-5. TFMA 笔记本中其他指标的可视化

在 TFMA 中分析多个模型

我们还可以使用 TFMA 比较多个模型的指标。例如,这些可能是在不同数据集上训练的同一模型,或者在相同数据集上训练的具有不同超参数的两个模型。

对于我们比较的模型,我们首先需要生成类似于前面代码示例的eval_result。我们需要确保指定一个output_path位置来保存模型。我们为两个模型使用相同的EvalConfig以便能够计算相同的指标:

eval_shared_model_2``=``tfma``.``default_eval_shared_model``(``eval_saved_model_path``=``_EVAL_MODEL_DIR``,``tags``=``[``tf``.``saved_model``.``SERVING``])``eval_result_2``=``tfma``.``run_model_analysis``(``eval_shared_model``=``eval_shared_model_2``,``eval_config``=``eval_config``,``data_location``=``_EVAL_DATA_FILE``,``output_path``=``_EVAL_RESULT_LOCATION_2``,``file_format``=``'tfrecords'``)

然后,我们使用以下代码将它们加载:

eval_results_from_disk``=``tfma``.``load_eval_results``(``[``_EVAL_RESULT_LOCATION``,``_EVAL_RESULT_LOCATION_2``],``tfma``.``constants``.``MODEL_CENTRIC_MODE``)

并且我们可以使用以下方式进行可视化:

tfma``.``view``.``render_time_series``(``eval_results_from_disk``,``slices``[``0``])

结果显示在图 7-6 中。

图 7-6. TFMA 可视化比较两个模型

这里需要注意的关键是,在 TFMA 中,无论是分类模型还是回归模型,都可以同时查看许多指标,而不是在训练过程中仅限于一两个。这有助于在模型部署后避免出现意外行为。

我们还可以根据数据集的特征对评估数据进行切片,例如,在我们的演示项目中,通过产品获取准确性。我们将在下一节中描述如何做到这一点。

公平性的模型分析

我们用来训练模型的所有数据在某种程度上都存在偏见:现实世界非常复杂,无法从数据样本中充分捕捉到所有这些复杂性。在第四章中,我们探讨了数据偏见问题,本章我们将研究模型预测的公平性。

公平性和偏见

“公平性”和“偏见”这两个术语通常可以互换使用,用来指代不同群体是否从机器学习模型中获得不同的表现。在这里,我们将使用“公平性”一词来避免与数据偏见混淆,我们在第四章中讨论过这个问题。

要分析我们的模型是否公平,我们需要确定某些群体的体验是否以一种问题方式不同于其他群体。例如,一个群体可能是不偿还贷款的人。如果我们的模型试图预测谁应该获得信贷,这个群体的体验应该与其他人不同。我们要避免的问题类型的例子是,仅有某一种族的人因某些原因被错误地拒绝贷款。

一个群体从模型中获得不同经历的知名例子是预测累犯风险的 COMPAS 算法。根据Propublica报道,该算法对黑人和白人被告的错误率大致相同。然而,它特别容易错误预测黑人被告将来会成为罪犯的概率,大致是错误预测白人被告的两倍。

我们应该在将模型部署到生产环境之前尝试识别这类问题。首先,定义公平性的数值化含义是很有用的。以下是几种分类问题的示例方法:

人口统计学平衡

决策在模型中对所有群体的速率相同。例如,男性和女性的贷款批准率相同。

平等机会

在提供机会的班级中,所有群体的错误率都相同。根据问题设置的方式不同,这可以是正类或负类。例如,能够偿还贷款的人中,男性和女性的贷款批准率相同。

相等的准确率

某些度量指标如准确率、精确度或 AUC 在所有群体中都相等。例如,面部识别系统对深肤色女性和浅肤色男性的准确性应该一样。

相等的准确率有时可能会产生误导,就像前面的 COMPAS 例子一样。在那个例子中,两组的准确率是相等的,但对一个组的后果却要严重得多。重要的是考虑错误的方向,这些错误对你的模型造成最严重的后果。

公平性的定义

没有一个适用于所有机器学习项目的公平定义。你需要探索对你具体业务问题最佳的解决方案,考虑到模型对用户可能造成的潜在危害和好处。更多指导请参阅 Solon Barocas 等人的书籍《机器学习中的公平性》,Google 的 Martin Wattenberg 等人的这篇文章,以及 Ben Hutchinson 等人的公平性指标文档

我们所指的群体可以是不同类型的客户、来自不同国家的产品使用者,或者不同性别和种族的人。在美国法律中,有受保护群体的概念,这些群体的个体受到基于性别、种族、年龄、残疾、肤色、信仰、国籍、宗教和基因信息的歧视保护。这些群体是交叉的:你可能需要检查你的模型是否不歧视多种组合的群体。

群体是一种简化

在现实世界中,人群从未是清晰的。每个人都有自己复杂的故事:有些人可能在一生中改变了宗教或性别。有些人可能属于多个种族或多个国籍。寻找这些边缘案例,并为人们提供告诉您如果他们对您的模型有不良体验的方法。

即使您在模型中未使用这些组作为特征,这并不意味着您的模型是公平的。许多其他特征,例如地点,可能与这些受保护的群体之一强相关。例如,如果您使用美国邮政编码作为特征,这与种族高度相关。您可以通过为其中一个受保护群体切片数据来检查这些问题,就像我们在接下来的章节中描述的那样,即使这不是您用来训练模型的特征。

公平性并非易于处理的话题,会引发许多可能复杂或有争议的伦理问题。然而,有几个项目可以帮助我们从公平的角度分析我们的模型,接下来的几节我们将描述如何使用它们。这种分析可以通过为每个人提供一致的体验,为您带来伦理和商业优势。它甚至可能是纠正您正在建模的系统中潜在不公平的机会,例如在亚马逊的招聘工具分析中发现了女性候选人面临的潜在劣势。

在接下来的几节中,我们将描述如何使用三个项目来评估 TensorFlow 中的公平性:TFMA、Fairness Indicators 和 What-If Tool。

在 TFMA 中切片模型预测

评估机器学习模型公平性的第一步是按照您感兴趣的组切片您的模型预测,例如性别、种族或国家。这些切片可以由 TFMA 或 Fairness Indicators 工具生成。

在 TFMA 中切片数据,必须提供一个slicing column作为SliceSpec。在这个例子中,我们将根据产品特征进行切片:

slices=[``tfma``.``slicer``.``SingleSliceSpec``(),``tfma``.``slicer``.``SingleSliceSpec``(``columns``=['Product'])]`

没有指定参数的SingleSliceSpec返回整个数据集。

接下来,运行指定了切片的模型分析步骤:

eval_result=tfma``.``run_model_analysis``(``eval_shared_model``=eval_shared_model,eval_config=`eval_config_viz,data_location=_EVAL_DATA_FILE``,``output_path``=_EVAL_RESULT_LOCATION,file_format=`'tfrecords',slice_spec=slices``)

并查看结果,如图 7-7 所示:

tfma``.``view``.``render_slicing_metrics``(``eval_result``,``slicing_spec``=slices[1``])`

如果我们要考虑人口统计学平等,如前所定义,那么我们需要检查每个组中正类的比例是否相同。我们可以通过查看每个组的 TPR 和 TNR 来进行检查。

图 7-7. TFMA 分片可视化

考虑哪一类是有益的

我们假设模型做了对个人有益的某种选择,并假设这是正类。如果正类对个人无益,负类对个人有害,那么我们应该考虑真阴性率和假阳性率。

对于平等机会,我们可以检查每个群体的 FPR。关于此更多细节,请参考有用建议中的公平性指标项目。

使用公平性指标检查决策阈值

公平性指标是模型分析的另一个极其有用的工具。它与 TFMA 有一些重叠的功能,但其特别有用的功能之一是能够在各种决策阈值上查看分特征的度量指标。正如我们之前讨论的,决策阈值是分类模型中确定类别边界的概率分数。这让我们可以检查我们的模型在不同决策阈值下是否对各个群体公平。

有几种访问公平性指标工具的方法,但将其作为独立库使用的最简单方法是通过 TensorBoard。我们还提到如何在 TFX 管道的一部分加载它,详见“评估组件”。我们通过以下方式安装 TensorBoard 公平性指标插件:

$ pip install tensorboard_plugin_fairness_indicators

接下来,我们使用 TFMA 评估模型,并要求它计算我们提供的一组决策阈值的度量指标。这在metrics_spec参数中提供给 TFMA,同时还包括我们希望计算的任何其他度量:

eval_config``=``tfma``.``EvalConfig``(``model_specs``=``[``tfma``.``ModelSpec``(``label_key``=``'consumer_disputed'``)],``slicing_specs``=``[``tfma``.``SlicingSpec``(),``tfma``.``SlicingSpec``(``feature_keys``=``[``'product'``])],``metrics_specs``=``[``tfma``.``MetricsSpec``(``metrics``=``[``tfma``.``MetricConfig``(``class_name``=``'FairnessIndicators'``,``config``=``'{"thresholds":[0.25, 0.5, 0.75]}'``)``])``]``)

然后通过tfma.run_model_analysis运行模型分析步骤。

接下来,将 TFMA 评估结果写入日志目录,以便 TensorBoard 可以读取:

from``tensorboard_plugin_fairness_indicators``import``summary_v2``writer``=``tf``.``summary``.``create_file_writer``(``'./fairness_indicator_logs'``)``with``writer``.``as_default``():``summary_v2``.``FairnessIndicators``(``'./eval_result_fairness'``,``step``=``1``)``writer``.``close``()

并将结果加载到 Jupyter 笔记本的 TensorBoard 中:

%``load_ext``tensorboard``%``tensorboard``--``logdir``=./``fairness_indicator_logs

公平性指标工具突出显示与整体度量值的差异,如图 7-8 所示(详见 filepos693700)。

图 7-8. 公平性指标分片可视化

对于我们的示例项目,图 7-9 显示在将决策阈值降低到 0.25 时,各组之间的差异更加明显。

图 7-9. 公平性指标阈值可视化

除了探索整体模型中的公平性考虑外,我们可能还想查看个别数据点,了解个别用户受我们模型影响的情况。幸运的是,在 TensorFlow 生态系统中还有另一个工具可帮助我们做到这一点:What-If Tool。

深入了解 What-If Tool

在使用 TFMA 和公平性指标查看数据集切片之后,我们可以通过 Google 的另一个项目深入研究:What-If Tool (WIT)。这使我们能够生成一些非常有用的可视化,并调查个别数据点。

有多种方法可以使用 WIT 分析已部署的 TensorFlow Serving 模型 通过 TensorBoard,或在 GCP 上运行的模型。也可以直接与 Estimator 模型一起使用。但对于我们的示例项目,最直接的使用方法是编写一个自定义预测函数,该函数接受一组训练示例,并返回这些示例的模型预测。这样,我们可以在独立的 Jupyter Notebook 中加载可视化。

我们可以使用以下命令安装 WIT:

$ pip install witwidget

接下来,我们创建一个 TFRecordDataset 来加载数据文件。我们抽样了 1,000 个训练示例,并将其转换为 TFExamples 的列表。What-If Tool 的可视化效果对这些训练示例数量很好,但如果样本更大,则变得难以理解:

eval_data``=``tf``.``data``.``TFRecordDataset``(``_EVAL_DATA_FILE``)``subset``=``eval_data``.``take``(``1000``)``eval_examples``=``[``tf``.``train``.``Example``.``FromString``(``d``.``numpy``()``)``for``d``in``subset``]

接下来,我们加载模型并定义一个预测函数,该函数接受 TFExamples 列表并返回模型对这些示例的预测:

model``=``tf``.``saved_model``.``load``(``export_dir``=``_MODEL_DIR``)``predict_fn``=``model``.``signatures``[``'serving_default'``]``def``predict``(``test_examples``):``test_examples``=``tf``.``constant``([``example``.``SerializeToString``()``for``example``in``examples``])``preds``=``predict_fn``(``examples``=``test_examples``)``return``preds``[``'outputs'``]``.``numpy``()

然后我们使用以下配置 WIT:

from``witwidget.notebook.visualization``import``WitConfigBuilder``config_builder``=``WitConfigBuilder``(``eval_examples``)``.``set_custom_predict_fn``(``predict``)

我们可以在笔记本中查看它:

from``witwidget.notebook.visualization``import``WitWidget``WitWidget``(``config_builder``)

这将在 图 7-10 中为我们提供可视化。

图 7-10. WIT 主页

在 JUPYTER NOTEBOOK 中使用 WIT

与 TFMA 类似,在独立笔记本中运行 WIT 需要额外的几个步骤。使用以下命令安装并启用 WIT 笔记本扩展:

$ jupyter nbextension install --py --symlink \ --sys-prefix witwidget $ jupyter nbextension enable witwidget --py --sys-prefix

如果在 Python 虚拟环境中运行这些命令,请在每个命令中追加标志 --sys_prefix

在 WIT 中包含许多功能,我们将在这里描述其中一些最有用的。完整文档请参见WIT 项目主页

WIT 提供反事实,对于任何单个训练样本,显示其来自不同分类的最近邻居。所有特征尽可能相似,但反事实的模型预测为另一类别。这帮助我们了解每个特征如何影响模型对特定训练示例的预测。如果我们发现改变人口统计特征(种族、性别等)会将模型的预测变为另一类别,这是模型可能对不同群体不公平的警告信号。

我们可以通过在浏览器中编辑所选示例来进一步探索这个功能。然后,我们可以重新运行推断,看看这对特定示例的预测有什么影响。这可以用来探索公平性的人口统计特征或其他特征,看看如果它们被改变会发生什么。

反事实可以作为解释模型行为的依据。但请注意,每个数据点可能存在许多可能的反事实,这些反事实接近于最近的邻居,并且特征之间可能存在复杂的相互作用。因此,反事实本身不应被呈现为完全解释模型行为的因素。

WIT 的另一个特别有用的功能是部分依赖图(PDP)。这些图展示了每个特征如何影响模型的预测,例如,数值特征的增加是否改变了类别预测的概率。PDP 显示了这种依赖的形状:它是否是线性的、单调的或者更复杂的。PDP 也可以为分类特征生成,如图 7-11 所示。同样,如果模型的预测显示对人口统计特征的依赖性,这可能是您的模型预测不公平的警告信号。

图 7-11. WIT PDPs

更高级的功能是针对公平策略优化决策阈值,这作为 WIT 的一页提供。可以根据选择的策略自动设置决策阈值,如图 7-12 所示。

图 7-12. WIT 决策阈值

我们在模型公平性这一节描述的所有工具也可以用来审查任何模型,即使它没有伤害用户的潜力。它们可以帮助更好地理解模型在部署前的行为,并且有助于避免它在现实世界中出现意外情况。这是一个活跃研究的领域,新的工具经常发布。一个有趣的发展是为模型公平性存在的约束优化,其中模型不仅仅优化一个指标,还可以考虑其他约束,比如平等的准确率。在 TensorFlow 中已经存在一个实验性库用于此目的。

模型可解释性

讨论公平性并使用 WIT 自然而然地引导我们讨论如何不仅描述我们模型的表现,还要解释其内部运行情况。我们在公平性的前一节简要提到过这一点,但在这里我们将进一步扩展。

模型可解释性旨在解释模型所作出预测的原因。与分析相反,分析描述了模型在各种指标下的表现。机器学习中的可解释性是一个重要的主题,目前有大量关于该主题的活跃研究。它不是我们流程的一部分,因为根据定义,解释需要向人们展示。我们将简要概述一下,更多细节建议阅读ebook《可解释机器学习》Christoph Molnar和谷歌云的这篇白皮书

有几个可能的原因驱使你解释模型的预测:

  • 帮助数据科学家调试他们模型中的问题

  • 建立对模型的信任

  • 审计模型

  • 向用户解释模型的预测

我们后面讨论的技术在所有这些用例中都是有帮助的。

简单模型的预测比复杂模型的预测容易解释得多。线性回归、逻辑回归和单一决策树相对容易解释。我们可以查看每个特征的权重并知道该特征的确切贡献。对于这些模型来说,查看整个模型提供了一个解释,因为它们的结构设计使它们可以被人类理解。例如,线性回归模型的系数提供了一个解释,无需进一步处理即可理解。

解释随机森林和其他集成模型,以及深度神经网络是最困难的。这是因为神经网络中的参数和连接数量巨大,导致特征之间的相互作用极其复杂。如果您的模型预测具有较高的后果并且您需要解释,请选择更容易解释的模型。关于如何以及何时使用解释的更多细节,请参阅 Umang Bhatt 等人的论文 “Explainable Machine Learning in Deployment”

局部和全局解释

我们可以将机器学习的可解释性方法分为两大类:局部解释和全局解释。局部解释旨在解释模型为单个数据点做出特定预测的原因。全局解释则旨在解释模型整体运作的方式,通过大量数据点来衡量。在下一节中,我们将介绍这两种技术。

在下一节中,我们将介绍一些从您的模型生成解释的技术。

使用 WIT 生成解释

在 “使用 What-If 工具进行更深入的分析” 中,我们描述了如何使用 WIT 来帮助解决模型公平性问题。但是 WIT 也可以用于解释我们的模型,特别是使用反事实和 PDP,正如我们注意到的那样。反事实提供给我们局部解释,而 PDP 可以提供局部或全局解释。我们之前展示过全局 PDP 的例子,如 图 7-11,现在我们将考虑局部 PDP,如 图 7-13 所示。

图 7-13. WIT 局部 PDP

PDP 显示了不同特征有效值的预测结果(推断分数)的变化。在 company 特征上推断分数没有变化,表明这个数据点的预测不依赖于该特征的值。但是在 company_response 特征上,推断分数有变化,显示模型预测对该特征的值具有一定依赖性。

PDP 的假设

PDP 包含一个重要的假设:所有特征彼此独立。对于大多数数据集,尤其是那些需要神经网络模型进行准确预测的复杂数据集来说,这不是一个好的假设。在处理这些图表时应谨慎:它们可以给出您的模型正在做什么的指示,但并不提供完整的解释。

如果您的模型是使用 Google Cloud 的 AI 平台部署的,您可以在 WIT 中看到特征归因。对于单个数据示例,特征归因为每个特征提供正负分数,表明特征对模型预测的影响和大小。它们也可以聚合以提供特征在模型中重要性的全局解释。特征归因基于 Shapley 值,这些值在下一节中描述。Shapley 值不假设您的特征是独立的,因此与 PDP 不同,如果您的特征彼此相关,它们是有用的。在撰写本文时,特征归因仅适用于使用 TensorFlow 1.x 训练的模型。

其他可解释性技术

LIME,即本地可解释的模型无关解释,是另一种生成局部解释的方法。它将模型视为黑盒,并在我们希望获得解释的点周围生成新数据点。然后,LIME 获取这些新数据点的模型预测,并使用这些点训练一个简单模型。这个简单模型的权重给出了我们的解释。

SHAP,即 Shapley 加法解释,库使用 Shapley 值提供全局和局部解释。这些计算起来计算成本很高,因此 SHAP 库包含了加速计算或为提升树和深度神经网络计算近似值的实现。这个库是展示您模型中特征重要性的一个很好的方式。

SHAPLEY VALUES

Shapley 值对于局部和全局解释都很有用。这个概念是从博弈论借来的算法,用于在合作博弈中为每个玩家分配增益和损失。在机器学习的背景下,每个特征都是一个“玩家”,Shapley 值可以通过以下方式获得:

  1. 获取不包含特征 F 的所有可能子集。

  2. 计算将 F 添加到所有子集中对模型预测的影响。

  3. 结合这些效果以获取特征 F 的重要性。

所有这些都是相对于某个基线的。对于我们的示例项目,我们可以将其表述为“预测受到公司响应为‘解决方案为说明关闭’而不是‘解决方案为货币补偿关闭’的驱动程度”。值“解决方案为货币补偿关闭”是我们的基线。

我们还想提到model cards,这是一个报告机器学习模型的框架。这些是共享有关机器学习模型事实和限制的正式方式。我们在这里包括这些是因为即使它们不解释模型为何做出其预测,它们对建立对模型的信任非常有价值。模型卡应包括以下信息:

  • 在公共数据集上对模型的基准性能进行了评估,包括在人口统计特征中划分的性能

  • 例如,模型的限制,比如揭示如果图像质量降低是否会导致图像分类模型的结果不够精确

  • 模型做出的任何权衡,例如,说明更大的图像是否会导致更长的处理时间

模型卡片在高风险情境中沟通模型非常有用,它鼓励数据科学家和机器学习工程师记录他们构建的模型的使用案例和限制。

解释的限制

我们建议在处理模型可解释性时要小心谨慎。这些技术可能会让你感觉你理解了模型的操作,但实际上它可能在做一些无法解释的非常复杂的事情。这在深度学习模型中尤其如此。

人类不可能以可读的方式表达组成深度神经网络的数百万权重的所有复杂性。在模型决策对现实世界有重大影响的情况下,我们建议构建尽可能简单的模型,并使用易于解释的特征。

TFX 中的分析和验证

到目前为止,在本章中,我们集中讨论了人类参与的模型分析。这些工具非常有用,可以监控我们的模型,确保它们按照我们期望的方式运行。但在自动化机器学习流水线中,我们希望流水线能够顺利运行并警报我们问题。在 TFX 中,有几个组件处理流水线的这部分内容:ResolverEvaluatorPusher。这些组件共同检查模型在评估数据集上的性能,并将其发送到服务位置,如果它改进了先前的模型。

TFX使用认可的概念来描述决定是否将模型部署到服务中的门控过程。如果模型改善了先前的模型,根据我们定义的阈值,它将被认可,并可以继续下一步骤。

ResolverNode

如果我们想将新模型与先前模型进行比较,则需要一个Resolver组件。ResolverNodes是查询元数据存储的通用组件。在这种情况下,我们使用latest_blessed_model_resolver。它检查最后一个被认可的模型并将其作为基线传递给Evaluator组件,与新候选模型一起使用。如果我们不想根据某些指标的阈值验证我们的模型,则不需要Resolver。但我们强烈建议这一步骤。如果您不验证新模型,即使其性能比先前模型差,它也会自动推送到服务目录。在Evaluator的第一次运行中,如果没有被认可的模型,Evaluator会自动认可该模型。

在交互式环境中,我们可以这样运行 Resolver 组件:

from``tfx.components``import``ResolverNode``from``tfx.dsl.experimental``import``latest_blessed_model_resolver``from``tfx.types``import``Channel``from``tfx.types.standard_artifacts``import``Model``from``tfx.types.standard_artifacts``import``ModelBlessing``model_resolver``=``ResolverNode``(``instance_name``=``'latest_blessed_model_resolver'``,``resolver_class``=``latest_blessed_model_resolver``.``LatestBlessedModelResolver``,``model``=``Channel``(``type``=``Model``),``model_blessing``=``Channel``(``type``=``ModelBlessing``)``)``context``.``run``(``model_resolver``)

评估器组件

评估器组件使用 TFMA 库在验证数据集上评估模型的预测。它的输入包括来自 ExampleGen 组件的数据、来自 Trainer 组件的训练模型,以及用于 TFMA 的 EvalConfig(与独立使用 TFMA 库时相同)。

首先,我们定义 EvalConfig

import``tensorflow_model_analysis``as``tfma``eval_config``=``tfma``.``EvalConfig``(``model_specs``=``[``tfma``.``ModelSpec``(``label_key``=``'consumer_disputed'``)],``slicing_specs``=``[``tfma``.``SlicingSpec``(),``tfma``.``SlicingSpec``(``feature_keys``=``[``'product'``])],``metrics_specs``=``[``tfma``.``MetricsSpec``(``metrics``=``[``tfma``.``MetricConfig``(``class_name``=``'BinaryAccuracy'``),``tfma``.``MetricConfig``(``class_name``=``'ExampleCount'``),``tfma``.``MetricConfig``(``class_name``=``'AUC'``)``])``]``)

然后运行评估器组件:

`fromtfx.componentsimportEvaluatorevaluator=Evaluator(examples=example_gen.outputs['examples'],model=trainer.outputs['model'],baseline_model=model_resolver.outputs['model'],eval_config=eval_config)context.run(evaluator``)

我们也可以展示 TFMA 的可视化:

eval_result``=``evaluator``.``outputs``[``'evaluation'``]``.``get``()[``0``]``.``uri``tfma_result``=``tfma``.``load_eval_result``(``eval_result``)

我们还可以加载公平性指标:

tfma``.``addons``.``fairness``.``view``.``widget_view``.``render_fairness_indicator``(``tfma_result``)

在评估器组件中进行验证

评估器组件还进行验证,检查我们刚刚训练的候选模型是否优于基线模型(例如当前正在生产中的模型)。它从评估数据集中获取两个模型的预测,并比较性能指标(例如模型准确性)。如果新模型优于先前的模型,则新模型会收到“blessing”(祝福)工件。目前只能在整个评估集上计算度量,不能对切片进行计算。

要进行验证,我们需要在 EvalConfig 中设置一个阈值:

eval_config``=``tfma``.``EvalConfig``(``model_specs``=``[``tfma``.``ModelSpec``(``label_key``=``'consumer_disputed'``)],``slicing_specs``=``[``tfma``.``SlicingSpec``(),``tfma``.``SlicingSpec``(``feature_keys``=``[``'product'``])],``metrics_specs``=``[``tfma``.``MetricsSpec``(``metrics``=``[``tfma``.``MetricConfig``(``class_name``=``'BinaryAccuracy'``),``tfma``.``MetricConfig``(``class_name``=``'ExampleCount'``),``tfma``.``MetricConfig``(``class_name``=``'AUC'``)``],``thresholds``=``{``'AUC'``:``tfma``.``config``.``MetricThreshold``(``value_threshold``=``tfma``.``GenericValueThreshold``(``lower_bound``=``{``'value'``:``0.65``}),``change_threshold``=``tfma``.``GenericChangeThreshold``(``direction``=``tfma``.``MetricDirection``.``HIGHER_IS_BETTER``,``absolute``=``{``'value'``:``0.01``}``)``)``}``)``]``)

在本例中,我们指定 AUC 必须超过 0.65,并且希望模型的 AUC 至少比基线模型高出 0.01。任何其他指标可以替代 AUC,但请注意,您添加的指标也必须包含在 MetricsSpec 的列表中。

我们可以通过以下方式检查结果:

eval_result``=``evaluator``.``outputs``[``'evaluation'``]``.``get``()[``0``]``.``uri``print``(``tfma``.``load_validation_result``(``eval_result``))

如果验证通过,将返回以下结果:

validation_ok``:``true

TFX 推送组件

Pusher 组件是我们流水线中的一个重要组成部分。它接收一个已保存的模型、Evaluator 组件的输出以及存储模型的文件路径,然后检查 Evaluator 是否已经批准了模型(即该模型是否改进了之前的版本,并且高于我们设置的任何阈值)。如果模型已被批准,Pusher 将其推送到服务文件路径中。

Pusher 组件接收 Evaluator 的输出模型及服务目的地:

from``tfx.components``import``Pusher``from``tfx.proto``import``pusher_pb2``_serving_model_dir``=``"serving_model_dir"``pusher``=``Pusher``(``model``=``trainer``.``outputs``[``'model'``],``model_blessing``=``evaluator``.``outputs``[``'blessing'``],``push_destination``=``pusher_pb2``.``PushDestination``(``filesystem``=``pusher_pb2``.``PushDestination``.``Filesystem``(``base_directory``=``_serving_model_dir``)))``context``.``run``(``pusher``)

一旦新模型被推送到服务目录,TensorFlow Serving 就可以接收并处理它,详细内容请参见下一章节。

概要

在本章中,我们详细介绍了如何比在模型训练期间更深入地分析模型的性能,并开始考虑如何使模型的性能更公平。我们还讨论了检查模型性能是否优于先前部署模型的过程。此外,我们还介绍了机器学习的可解释性,并简要概述了该领域的一些技术。

不过在这里,我们必须提出警告:仅仅因为你已经通过公平性指标详细分析了你的模型的性能,这并不能保证你的模型是公平或符合伦理的。一旦模型投入使用,继续监控模型并为用户提供反馈的途径非常重要,让他们知道如果他们觉得模型的预测是不公正的。特别是在利益重大且模型的决策可能对用户造成重大实际影响时,这点尤为重要。

现在我们已经分析和验证了我们的模型,是时候进入管道中至关重要的下一步了:为模型提供服务!接下来的两章将告诉你关于这一重要步骤的所有必要信息。

第八章:使用 TensorFlow Serving 进行模型部署

在其他人可以使用您的模型进行预测之前,部署您的机器学习模型是最后一步。不幸的是,机器学习模型的部署在当今的数字世界分工思维中存在灰色地带。它不仅仅是 DevOps 的任务,因为它需要一些对模型架构及其硬件需求的了解。同时,部署机器学习模型有点超出了机器学习工程师和数据科学家的舒适区。他们对自己的模型了如指掌,但在部署机器学习模型方面往往遇到困难。在本章和接下来的章节中,我们希望弥合这一差距,指导数据科学家和 DevOps 工程师完成部署机器学习模型的步骤。图 8-1 显示了机器学习流水线中部署步骤的位置。

图 8-1. 作为 ML 流水线一部分的模型部署

机器学习模型可以通过三种主要方式部署:使用模型服务器、在用户的浏览器中或者在边缘设备上。今天最常见的部署机器学习模型的方式是使用模型服务器,本章我们将重点讨论这一点。请求预测的客户端将输入数据提交给模型服务器,然后接收预测结果。这要求客户端能够连接到模型服务器。

有些情况下,你不希望将输入数据提交到模型服务器(例如当输入数据很敏感或存在隐私问题时)。在这些情况下,你可以将机器学习模型部署到用户的浏览器上。例如,如果你想要确定一张图片是否包含敏感信息,你可以在上传到云服务器之前对图片的敏感级别进行分类。

然而,还有第三种模型部署方式:部署到边缘设备。有些情况下,你无法连接到模型服务器进行预测(例如远程传感器或物联网设备)。部署到边缘设备的应用数量正在增加,使其成为模型部署的一个有效选项。在第十章中,我们讨论了如何将 TensorFlow 模型转换为 TFLite 模型,以便在边缘设备上执行。

在本章中,我们突出了 TensorFlow 的 Serving 模块,这是通过模型服务器部署 TensorFlow 模型的一种简单且一致的方式。我们将介绍其设置并讨论高效的部署选项。这并不是部署深度学习模型的唯一方式;在本章末尾,我们还将讨论一些替代选项。

在我们深入探讨 TensorFlow Serving 之前,让我们从如何不应该设置模型服务器开始这一章节。

一个简单的模型服务器

大多数关于部署机器学习模型的介绍大致遵循相同的工作流程:

  • 使用 Python 创建 Web 应用程序(例如使用 Flask 或 Django)。

  • 在 Web 应用程序中创建一个 API 端点,正如我们在 示例 8-1 中展示的。

  • 加载模型结构及其权重。

  • 在加载的模型上调用 predict 方法。

  • 返回预测结果作为 HTTP 请求。

示例 8-1. 使用 Flask 端点设置来推断模型预测的示例

import``json``from``flask``import``Flask``,``request``from``tensorflow.keras.models``import``load_model``from``utils``import``preprocessmodel``=``load_model``(``'model.h5'``)app``=``Flask``(``__name__``)``@app.route``(``'/classify'``,``methods``=``[``'POST'``])``def``classify``():``complaint_data``=``request``.``form``[``"complaint_data"``]``preprocessed_complaint_data``=``preprocess``(``complaint_data``)``prediction``=``model``.``predict``([``preprocessed_complaint_data``])return``json``.``dumps``({``"score"``:``prediction``})

预处理以转换数据结构。

加载您训练过的模型。

执行预测。

返回预测结果的 HTTP 响应。

这种设置适用于演示项目的快速简易实现。然而,我们不建议将 示例 8-1 用于将机器学习模型部署到生产端点。

接下来,让我们讨论为何不建议使用此类设置部署机器学习模型的原因。原因在于我们对提议的部署解决方案的基准。

使用 Python-Based API 部署模型的缺点

虽然 示例 8-1 的实现对演示目的可能足够,但这类部署通常面临挑战。挑战从 API 和数据科学代码之间的适当分离开始,一个一致的 API 结构和由此产生的不一致的模型版本管理,以及低效的模型推断。我们将在接下来的部分更详细地探讨这些挑战。

缺乏代码分离

在 示例 8-1 中,我们假设训练过的模型正在与同一 API 代码库一起部署。这意味着 API 代码和机器学习模型之间没有分离,当数据科学家想要更新模型时,这种更新就需要与 API 团队协调。这种协调还要求 API 和数据科学团队同步工作,以避免在模型部署上造成不必要的延迟。

纠缠的 API 和数据科学代码库还会在 API 拥有权方面造成歧义。

代码分离的缺乏还要求模型必须在与 API 代码相同的编程语言中加载。这种后端与数据科学代码的混合最终可能阻止 API 团队升级 API 后端。然而,它也提供了良好的责任分离:数据科学家可以专注于模型训练,DevOps 同事可以专注于训练模型的部署。

我们强调了如何有效地将您的模型与 API 代码分离,并简化您的部署工作流程,详见 “TensorFlow Serving”。

模型版本控制的缺乏

示例 8-1 没有为不同的模型版本做任何准备。如果您想要添加一个新版本,您将不得不创建一个新的端点(或在现有端点中添加一些分支逻辑)。这需要额外注意以保持所有端点在结构上相同,并且需要大量样板代码。

模型版本控制的缺乏还要求 API 和数据科学团队协调确定默认版本及如何逐步引入新模型。

低效的模型推理

对于写入 Flask 设置中预测端点的任何请求,如 示例 8-1 所示,都会执行一个完整的往返。这意味着每个请求都会单独进行预处理和推断。我们认为这种设置仅用于演示的关键原因是其效率极低。在训练模型时,您可能会使用批处理技术,允许您同时计算多个样本,然后将批处理的梯度变化应用于网络的权重。当您希望模型进行预测时,可以应用相同的技术。模型服务器可以在可接受的时间范围内或直到批处理满时收集所有请求,并询问模型其预测结果。当推理运行在 GPU 上时,这是一种特别有效的方法。

在 “批处理推断请求” 中,我们介绍了如何为您的模型服务器轻松设置这样的批处理行为。

TensorFlow Serving

正如你在本书的前几章中所见,TensorFlow 提供了一个出色的扩展和工具生态系统。早期的开源扩展之一是 TensorFlow Serving。它允许你部署任何 TensorFlow 图,并通过其标准化的端点从图中进行预测。正如我们接下来会讨论的,TensorFlow Serving 为你处理模型和版本管理,让你根据策略提供模型,允许你从各种来源加载你的模型。同时,它专注于高性能吞吐量以进行低延迟的预测。TensorFlow Serving 在 Google 内部使用,并已被许多公司和初创企业采用。1

TensorFlow 架构概述

TensorFlow Serving 为您提供了从给定来源(例如 AWS S3 存储桶)加载模型并在源更改时通知加载器的功能。如 Figure 8-2 所示,TensorFlow Serving 的背后所有操作都由模型管理器控制,该管理器负责何时更新模型以及哪个模型用于预测。推断决策规则由策略管理,该策略也由模型管理器管理。根据您的配置,例如,您可以一次加载一个模型,并且一旦源模块检测到更新版本,模型将自动更新。

图 8-2. TensorFlow Serving 架构概述

导出 TensorFlow Serving 的模型

在我们深入讨论 TensorFlow Serving 配置之前,让我们讨论一下如何导出您的机器学习模型,以便它们可以被 TensorFlow Serving 使用。

根据您的 TensorFlow 模型类型,导出步骤略有不同。导出的模型与我们在 Example 8-2 中看到的文件结构相同。对于 Keras 模型,您可以使用:

saved_model_path``=``model``.``save``(``file``path``=``"./saved_models"``,``save_format``=``"tf"``)

为您的导出路径添加时间戳

当您手动保存模型时,建议为 Keras 模型的导出路径添加导出时间戳。与tf.Estimatorsave方法不同,model.save()不会自动创建带时间戳的路径。您可以使用以下 Python 代码轻松创建文件路径:

import``time``ts``=``int``(``time``.``time``())``file``path``=``"./saved_models/{}"``.``format``(``ts``)``saved_model_path``=``model``.``save``(``file``path``=``file``path``,``save_format``=``"tf"``)

对于 TensorFlow Estimator 模型,您需要首先声明一个接收函数

import``tensorflow``as``tf``def``serving_input_receiver_fn``():``# an example input feature``input_feature``=``tf``.``compat``.``v1``.``placeholder``(``dtype``=``tf``.``string``,``shape``=``[``None``,``1``],``name``=``"input"``)``fn``=``tf``.``estimator``.``export``.``build_raw_serving_input_receiver_fn``(``features``=``{``"input_feature"``:``input_feature``})``return``fn

使用Estimatorexport_saved_model方法导出 Estimator 模型:

estimator``=``tf``.``estimator``.``Estimator``(``model_fn``,``"model"``,``params``=``{})``estimator``.``export_saved_model``(``export_dir_base``=``"saved_models/"``,``serving_input_receiver_fn``=``serving_input_receiver_fn``)

两种导出方法产生的输出与以下示例类似:

... INFO:tensorflow:在导出中包含的签名: None INFO:tensorflow:在导出中包含的签名: None INFO:tensorflow:在导出中包含的签名: [``'serving_default'``] INFO:tensorflow:在导出中包含的签名: None INFO:tensorflow:在导出中包含的签名: None INFO:tensorflow:没有要保存的资产。 INFO:tensorflow:没有要写入的资产。 INFO:tensorflow:SavedModel 已写入: saved_models/1555875926/saved_model.pb 模型已导出至:  b``'saved_models/1555875926'

在我们的模型导出示例中,我们指定了文件夹 saved_models/作为模型的目标位置。对于每个导出的模型,TensorFlow 会创建一个以导出时间戳命名的目录:

示例 8-2. 导出模型的文件夹和文件结构

$ tree saved_models/ saved_models/ └── 1555875926     ├── assets     │   └── saved_model.json     ├── saved_model.pb     └── variables         ├── checkpoint         ├── variables.data-00000-of-00001         └── variables.index 3 directories, 5 files

文件夹包含以下文件和子目录:

saved_model.pb

二进制协议缓冲文件以MetaGraphDef对象形式包含导出模型图结构。

变量

文件夹包含具有导出变量值的二进制文件以及对应于导出模型图的检查点。

assets

当需要加载导出模型的其他文件时,会创建此文件夹。附加文件可以包括词汇表,这些词汇表在第五章中有介绍。

模型签名

模型签名标识模型图的输入和输出,以及图签名的方法。定义输入和输出签名允许我们将服务输入映射到给定图节点以进行推理。如果我们想要更新模型而不改变模型服务器的请求,这些映射就非常有用。

此外,模型的方法定义了输入和输出的预期模式。目前支持三种签名类型:预测、分类或回归。我们将在下一节详细讨论这些细节。

签名方法

最灵活的签名方法是预测。如果我们没有指定不同的签名方法,TensorFlow 将使用预测作为默认方法。示例 8-3 展示了方法预测的签名示例。在该示例中,我们将键inputs映射到名为 sentence 的图节点。模型的预测输出是图节点 y 的输出,我们将其映射到输出键scores

预测方法允许您定义额外的输出节点。当您希望捕获注意力层输出以进行可视化或调试网络节点时,添加更多推理输出非常有用。

示例 8-3. 模型预测签名示例

signature_def``:``{``key``:``"prediction_signature"``value``:``{``inputs``:``{``key``:``"inputs"``value``:``{``name``:``"sentence:0"``dtype``:``DT_STRING``tensor_shape``:``...``},``...``}``outputs``:``{``key``:``"scores"``value``:``{``name``:``"y:0"``dtype``:``...``tensor_shape``:``...``}``}``method_name``:``"tensorflow/serving/predict"``}``}

另一种签名方法是 classify。该方法期望一个名为 inputs 的输入,并提供两个输出张量,classesscores。至少需要定义一个输出张量。在我们示例中显示的示例 8-4 中,分类模型接受输入 sentence 并输出预测的 classes 以及相应的 scores

示例 8-4. 示例模型分类签名

signature_def``:``{``key``:``"classification_signature"``value``:``{``inputs``:``{``key``:``"inputs"``value``:``{``name``:``"sentence:0"``dtype``:``DT_STRING``tensor_shape``:``...``}``}``outputs``:``{``key``:``"classes"``value``:``{``name``:``"y_classes:0"``dtype``:``DT_UINT16``tensor_shape``:``...``}``}``outputs``:``{``key``:``"scores"``value``:``{``name``:``"y:0"``dtype``:``DT_FLOAT``tensor_shape``:``...``}``}``method_name``:``"tensorflow/serving/classify"``}``}

第三种可用的签名方法是 regress。此方法仅接受名为 inputs 的输入,并仅提供名为 outputs 的输出。这种签名方法设计用于回归模型。示例 8-5 展示了回归签名的示例。

示例 8-5. 示例模型回归签名

signature_def``:``{``key``:``"regression_signature"``value``:``{``inputs``:``{``key``:``"inputs"``value``:``{``name``:``"input_tensor_0"``dtype``:``...``tensor_shape``:``...``}``}``outputs``:``{``key``:``"outputs"``value``:``{``name``:``"y_outputs_0"``dtype``:``DT_FLOAT``tensor_shape``:``...``}``}``method_name``:``"tensorflow/serving/regress"``}``}

在“URL 结构”中,当我们为模型端点定义 URL 结构时,我们将再次看到签名方法。

检查导出的模型

在讨论了如何导出模型及其对应模型签名之后,让我们讨论在部署 TensorFlow Serving 之前如何检查导出的模型。

您可以通过以下 pip 命令安装 TensorFlow Serving Python API:

$ pip install tensorflow-serving-api

安装后,您可以使用一个称为 SavedModel 命令行接口(CLI)的实用命令行工具。此工具可以让您:

检查导出模型的签名

主要在您没有自行导出模型且希望了解模型图的输入和输出时非常有用。

测试导出的模型

使用 TensorFlow Serving 的 CLI 工具,您可以推断模型而无需部署它。这在您希望测试模型输入数据时非常有用。

我们将在以下两个部分中涵盖这两种用例。

检查模型

saved_model_cli 可帮助您了解模型的依赖关系,而无需检查原始图形代码。

如果您不知道可用的标签集合,2 您可以使用以下命令检查模型:

$ saved_model_cli show --dir saved_models/ The given SavedModel contains the following tag-sets: serve

如果您的模型包含不同环境的不同图形(例如,用于 CPU 或 GPU 推断的图形),则会看到多个标签。如果模型包含多个标签,则需要指定一个标签以检查模型的详细信息。

一旦您知道要检查的 tag_set,请将其添加为参数,saved_model_cli 将提供可用的模型签名。我们的演示模型只有一个名为 serving_default 的签名:

$ saved_model_cli show --dir saved_models/ --tag_set serve The given SavedModel 'MetaGraphDef' contains 'SignatureDefs' with the following keys: SignatureDef key: "serving_default"

借助 tag_setsignature_def 信息,您现在可以检查模型的输入和输出。要获取详细信息,请将 signature_def 添加到 CLI 参数中。

以下示例签名取自我们演示流水线生成的模型。在 示例 6-4 中,我们定义了签名函数,该函数将序列化的 tf.Example 记录作为输入,并通过输出张量 outputs 提供预测结果,如以下模型签名所示:

$ saved_model_cli show --dir saved_models/ \ --tag_set serve --signature_def serving_default The given SavedModel SignatureDef contains the following input``(``s``)``:   inputs``[``'examples'``] tensor_info:       dtype: DT_STRING       shape: (``-1``) name: serving_default_examples:0 The given SavedModel SignatureDef contains the following output``(``s``)``:   outputs``[``'outputs'``] tensor_info:       dtype: DT_FLOAT       shape: (``-1, 1``) name: StatefulPartitionedCall_1:0 Method name is: tensorflow/serving/predict

如果要查看所有签名而不考虑 tag_setsignature_def,可以使用 --all 参数:

$ saved_model_cli show --dir saved_models/ --all ...

在我们调查模型签名后,我们现在可以在部署机器学习模型之前测试模型推理。

测试模型

saved_model_cli 还允许您使用示例输入数据测试导出模型。

您有三种不同的方式提交模型测试推理的示例输入数据:

--inputs

该参数指向一个包含以 NumPy ndarray 格式编码的输入数据的 NumPy 文件。

--input_exprs

该参数允许您定义 Python 表达式以指定输入数据。您可以在表达式中使用 NumPy 功能。

--input_examples

该参数期望以 tf.Example 数据结构格式化的输入数据(请参见 第四章)。

要测试模型,您可以准确指定一个输入参数。此外,saved_model_cli 提供了三个可选参数:

--outdir

saved_model_cli 将任何图形输出写入 stdout。如果您更喜欢将输出写入文件,则可以使用 --outdir 指定目标目录。

--overwrite

如果选择将输出写入文件,可以使用 --overwrite 指定文件可以被覆盖。

--tf_debug

如果您想进一步检查模型,可以使用 TensorFlow Debugger (TFDBG) 逐步查看模型图。

$ saved_model_cli run --dir saved_models/ \ --tag_set serve \ --signature_def x1_x2_to_y \ --input_examples 'examples=[{"company": "汇丰银行", ...}]'

在介绍如何导出和检查模型后,让我们深入了解 TensorFlow Serving 的安装、设置和操作。

设置 TensorFlow Serving

有两种简单的方法可以在您的服务实例上安装 TensorFlow Serving。您可以在 Docker 上运行 TensorFlow Serving,或者如果您在服务实例上运行 Ubuntu 操作系统,则可以安装 Ubuntu 包。

Docker 安装

安装 TensorFlow Serving 的最简单方法是下载预构建的 Docker 镜像。正如您在第二章中看到的那样,您可以通过运行以下命令获取该镜像:

$ docker pull tensorflow/serving

如果您在具有 GPU 的实例上运行 Docker 容器,则需要下载具有 GPU 支持的最新版本构建。

$ docker pull tensorflow/serving:latest-gpu

具有 GPU 支持的 Docker 镜像需要 Nvidia 的 Docker 支持。安装步骤可以在公司的网站找到。

本地 Ubuntu 安装

如果您想要在没有运行 Docker 的开销的情况下运行 TensorFlow Serving,则可以安装适用于 Ubuntu 发行版的 Linux 二进制包。

安装步骤与其他非标准 Ubuntu 软件包类似。首先,您需要在 Linux 终端中执行以下操作,将一个新的软件包源添加到发行版的源列表中或者将一个新的列表文件添加到 sources.list.d 目录中:

$ echo``"deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt \``  stable tensorflow-model-server tensorflow-model-server-universal"``\``| sudo tee /etc/apt/sources.list.d/tensorflow-serving.list

在更新您的软件包注册表之前,您应将软件包的公钥添加到您发行版的密钥链中:

$ curl https://storage.googleapis.com/tensorflow-serving-apt/``\ tensorflow-serving.release.pub.gpg | sudo apt-key add -

在更新您的软件包注册表后,您可以在您的 Ubuntu 操作系统上安装 TensorFlow Serving:

$ apt-get update $ apt-get install tensorflow-model-server

用于 TensorFlow Serving 的两个 Ubuntu 软件包

谷歌为 TensorFlow Serving 提供了两个 Ubuntu 软件包!前文提到的 tensorflow-model-server 软件包是首选软件包,并带有特定的 CPU 优化预编译(例如,AVX 指令)。

在撰写本章时,还提供了名为 tensorflow-model-server-universal 的第二个软件包。它不包含预编译的优化,因此可以在旧硬件上运行(例如,没有 AVX 指令集的 CPU)。

从源代码构建 TensorFlow Serving。

建议使用预构建的 Docker 镜像或 Ubuntu 软件包运行 TensorFlow Serving。在某些情况下,您需要编译 TensorFlow Serving,例如当您希望为底层硬件优化模型服务时。目前,您只能为 Linux 操作系统构建 TensorFlow Serving,需要使用构建工具 bazel。您可以在 TensorFlow Serving 文档 中找到详细的说明。

优化您的 TensorFlow Serving 实例。

如果您从头开始构建 TensorFlow Serving,我们强烈建议为您的模型的特定 TensorFlow 版本和您服务实例的可用硬件编译 Serving 版本。

配置 TensorFlow 服务器。

TensorFlow Serving 出厂设置可以在两种不同模式下运行。首先,您可以指定一个模型,并让 TensorFlow Serving 始终提供最新的模型。或者,您可以指定一个包含所有您想要加载的模型和版本的配置文件,并让 TensorFlow Serving 加载所有命名模型。

单一模型配置。

如果您想通过加载单个模型并在可用时切换到更新的模型版本来运行 TensorFlow Serving,则首选单一模型配置。

如果您在 Docker 环境中运行 TensorFlow Serving,则可以使用以下命令运行 tensorflow\serving 镜像:

$ docker run -p 8500:8500 \ -p 8501:8501 \ --mount type``=``bind``,source``=``/tmp/models,target``=``/models/my_model \ -e MODEL_NAME``=``my_model \ -e MODEL_BASE_PATH``=``/models/my_model \ -t tensorflow/serving

指定默认端口。

挂载模型目录。

指定您的模型。

指定 Docker 镜像。

默认情况下,TensorFlow Serving 配置为创建一个表述状态传输(REST)和 Google 远程过程调用(gRPC)端点。通过指定端口 8500 和 8501,我们暴露了 REST 和 gRPC 的能力。4 Docker run 命令在主机(源)和容器(目标)文件系统之间创建了一个挂载点。在第二章中,我们讨论了如何向 Docker 容器传递环境变量。要在单个模型配置中运行服务器,您需要指定模型名称 MODEL_NAME

如果您想要运行预先构建的用于 GPU 的 Docker 镜像,您需要将 docker 镜像的名称更换为最新的 GPU 构建版本:

$ docker run ...              -t tensorflow/serving:latest-gpu

如果您决定在没有 Docker 容器的情况下运行 TensorFlow Serving,您可以使用以下命令运行它:

$ tensorflow_model_server --port``=``8500``\ --rest_api_port``=``8501``\ --model_name``=``my_model \ --model_base_path``=``/models/my_model

在这两种情况下,您应该在终端上看到类似以下的输出:

2019-04-26 03:51:20.304826: I tensorflow_serving/model_servers/ server.cc:82``] 构建单个 TensorFlow 模型文件配置:   model_name: my_model model_base_path: /models/my_model 2019-04-26 03:51:20: I tensorflow_serving/model_servers/server_core.cc:461``] 添加/更新模型。 2019-04-26 03:51:20: I tensorflow_serving/model_servers/ server_core.cc:558``]``(``重新``)``添加模型:my_model ... 2019-04-26 03:51:34.507436: I tensorflow_serving/core/loader_harness.cc:86``] 成功加载可服务版本 {``name: my_model version: 1556250435``} 2019-04-26 03:51:34.516601: I tensorflow_serving/model_servers/server.cc:313``] 在 0.0.0.0:8500 运行 gRPC ModelServer ... [``warn``] getaddrinfo:不支持节点名称的地址族 [``evhttp_server.cc:237``] RAW:进入事件循环 ... 2019-04-26 03:51:34.520287: I tensorflow_serving/model_servers/server.cc:333``] 在 localhost:8501 导出 HTTP/REST API ...

从服务器输出中,您可以看到服务器成功加载了我们的模型 my_model,并创建了两个端点:一个 REST 端点和一个 gRPC 端点。

TensorFlow Serving 使得部署机器学习模型变得极其简单。使用 TensorFlow Serving 提供模型的一个巨大优势是其热替换能力。如果上传了新模型,服务器的模型管理器将检测到新版本,卸载现有模型,并加载更新的模型以进行推理。

假设您更新了模型并将新模型版本导出到主机机器上的挂载文件夹(如果您正在使用 Docker 设置),不需要进行配置更改。模型管理器将检测到更新的模型并重新加载端点。它将通知您有关旧模型卸载和新模型加载的消息。在您的终端中,您应该会找到类似以下的消息:

2019-04-30 00:21:56.486988: I tensorflow_serving/core/basic_manager.cc:739 ] 成功保留资源以加载可服务的 { name: my_model version: 1556583584 } 2019-04-30 00:21:56.487043: I tensorflow_serving/core/loader_harness.cc:66 ] 批准加载 服务版本 { name: my_model version: 1556583584 } 2019-04-30 00:21:56.487071: I tensorflow_serving/core/loader_harness.cc:74 ] 加载服务版本 { name: my_model version: 1556583584 } ... 2019-04-30 00:22:08.839375: I tensorflow_serving/core/loader_harness.cc:119 ] 卸载服务版本 { name: my_model version: 1556583236 } 2019-04-30 00:22:10.292695: I ./tensorflow_serving/core/simple_loader.h:294 ] 在释放带有 1262338988 的服务卸载后调用 MallocExtension_ReleaseToSystem () 2019-04-30 00:22:10.292771: I tensorflow_serving/core/loader_harness.cc:127 ] 完成卸载服务版本 { name: my_model version: 1556583236 }

默认情况下,TensorFlow Serving 将加载具有最高版本号的模型。如果您使用本章前面展示的导出方法,所有模型将以时代时间戳作为文件夹名称导出。因此,新模型的版本号将高于旧模型。

TensorFlow Serving 的相同默认模型加载策略还允许模型回滚。如果您想回滚模型版本,可以从基本路径删除模型版本。模型服务器将在下一次轮询文件系统时检测到版本的删除,卸载已删除的模型,并加载最近的现有模型版本。

多模型配置

您还可以配置 TensorFlow Serving 同时加载多个模型。为此,您需要创建一个配置文件来指定模型:

model_config_list { config { name: 'my_model' base_path: '/models/my_model/' model_platform: 'tensorflow' } config { name: 'another_model' base_path: '/models/another_model/' model_platform: 'tensorflow' } }

配置文件包含一个或多个 config 字典,所有这些都在 model_config_list 键下列出。

在您的 Docker 配置中,您可以挂载配置文件,并使用配置文件而不是单个模型加载模型服务器:

$ docker run -p 8500:8500 \ -p 8501:8501 \ --mount type``=``bind``,source``=``/tmp/models,target``=``/models/my_model \ --mount type``=``bind``,source``=``/tmp/model_config,``\ target``=``/models/model_config \ -e MODEL_NAME``=``my_model \ -t tensorflow/serving \              --model_config_file``=``/models/model_config

挂载配置文件。

指定模型配置文件。

如果您在 Docker 容器之外使用 TensorFlow Serving,可以使用参数 model_config_file 将模型服务器指向配置文件,该文件加载并从文件中进行配置:

$ tensorflow_model_server --port``=``8500``\ --rest_api_port``=``8501``\ --model_config_file``=``/models/model_config

配置特定模型版本

有些情况下,您不仅想加载最新的模型版本,而是所有或特定的模型版本。例如,您可能想进行模型 A/B 测试,如我们将在“使用 TensorFlow Serving 进行模型 A/B 测试”中讨论的那样,或者提供一个稳定版本和一个开发版本。TensorFlow Serving 默认始终加载最新的模型版本。如果您想加载一组可用的模型版本,您可以通过以下方式扩展模型配置文件:

`...config{name:'another_model'base_path:'/models/another_model/'model_version_policy{specific{versions:1556250435versions:1556251435}}}...

如果您想指定特定的模型版本,您也可以定义它们:

`...config{name:'another_model'base_path:'/models/another_model/'model_version_policy{specific{versions:1556250435versions:1556251435}}}...

您甚至可以提供模型版本的标签。这些标签在以后从模型进行预测时非常方便。在撰写本文时,版本标签仅通过 TensorFlow Serving 的 gRPC 端点提供:

`...model_version_policy{specific{versions:1556250435versions:1556251435}}version_labels{key:'stable'value:1556250435}version_labels{key:'testing'value:1556251435}``...

现在配置了模型版本,我们可以使用这些端点来运行我们的模型 A/B 测试版本。如果您想了解如何推断这些模型版本,我们建议参考“使用 TensorFlow Serving 进行模型 A/B 测试”,这里有一个简单实现的例子。

自 TensorFlow Serving 2.3 开始,版本标签功能将适用于 REST 端点,除了现有的 TensorFlow Serving 的 gRPC 功能之外。

REST 与 gRPC

在“单一模型配置”中,我们讨论了 TensorFlow Serving 如何允许两种不同的 API 类型:REST 和 gRPC。这两种协议都有各自的优缺点,在我们深入介绍如何与这些端点通信之前,我们想先介绍一下它们。

REST

REST 是当今 Web 服务使用的通信“协议”。它不是正式协议,而是定义客户端如何与 Web 服务通信的通信样式。REST 客户端使用标准的 HTTP 方法(如GETPOSTDELETE等)与服务器通信。请求的有效负载通常编码为 XML 或 JSON 数据格式。

gRPC

gRPC 是由 Google 开发的远程过程调用协议。虽然 gRPC 支持不同的数据格式,但与 gRPC 一起使用的标准数据格式是协议缓冲区,这在本书中被广泛使用。gRPC 提供低延迟通信和较小的有效负载(如果使用协议缓冲区)。gRPC 设计时考虑了 API。缺点是有效负载以二进制格式存在,这可能使快速检查变得困难。

使用哪种协议?

一方面,通过 REST 与模型服务器进行通信看起来非常方便。端点易于推断,有效负载可以轻松检查,并且可以使用curl请求或浏览器工具测试端点。REST 库广泛适用于各种客户端,并且通常已在客户端系统上可用(例如移动应用程序)。

另一方面,gRPC API 在初始阶段具有更高的入门门槛。 gRPC 库通常需要在客户端上安装。但是,根据模型推断所需的数据结构,它们可以带来显著的性能改进。如果您的模型接收到许多请求,则由于协议缓冲区序列化而减少的有效负载大小可能会有所帮助。

在内部,TensorFlow Serving 将通过 REST 提交的 JSON 数据结构转换为tf.Example数据结构,这可能导致性能较慢。因此,如果转换需要许多类型转换(即,如果您提交包含浮点值的大数组),则通过 gRPC 请求可能会获得更好的性能。

从模型服务器获取预测

到目前为止,我们完全专注于模型服务器的设置。在本节中,我们想展示客户端(例如 Web 应用程序)如何与模型服务器交互。所有关于 REST 或 gRPC 请求的代码示例都在客户端上执行。

通过 REST 获取模型预测

要通过 REST 调用模型服务器,您需要一个 Python 库来为您简化通信。目前的标准库是requests。安装该库:

$ pip install requests

下面的示例展示了一个POST请求的示例。

import``requests``url``=``"http://some-domain.abc"``payload``=``{``"key_1"``:``"value_1"``}``r``=``requests``.``post``(``url``,``json``=``payload``)print``(``r``.``json``())# {'data': ...}

提交请求。

查看 HTTP 响应。

URL 结构

您的 HTTP 请求 URL 包含有关您想要推断的模型和版本的信息:

http://``{``HOST``}``:``{``PORT``}``/v1/models/``{``MODEL_NAME``}``:``{``VERB``}

主机

主机是您模型服务器的 IP 地址或域名。如果您在同一台机器上运行模型服务器和客户端代码,您可以将主机设置为 localhost

端口

在您的请求 URL 中需要指定端口。REST API 的标准端口为 8501. 如果这与您服务生态系统中的其他服务冲突,您可以在服务器启动期间通过服务器参数更改端口。

模型名称

当您设置模型配置或启动模型服务器时,模型名称需要与您模型的名称匹配。

动词

URL 中的动词指定了模型的类型。您有三个选项:predictclassifyregress。动词对应于终点的签名方法。

模型版本

如果您想要从特定模型版本进行预测,您需要使用模型版本标识符扩展 URL:

http://``{``HOST``}``:``{``PORT``}``/v1/models/``{``MODEL_NAME``}[``/versions/``${``MODEL_VERSION``}``]``:``{``VERB``}

负载

在 URL 就绪后,让我们讨论请求负载。TensorFlow Serving 期望将输入数据作为 JSON 数据结构提交,如以下示例所示:

{``"signature_name"``:``<string>``,``"instances"``:``<value>``}

不需要 signature_name。如果未指定,模型服务器将推断使用默认的 serving 标签签署的模型图。

输入数据预期为对象列表或输入值列表。要提交多个数据样本,您可以将它们作为列表提交在 instances 键下。

如果您想要为推理提交一个数据示例,您可以使用 inputs 并将所有输入值列为列表。其中的一个键,instancesinputs,必须存在,但不能同时出现:

{``"signature_name"``:``<string>``,``"inputs"``:``<value>``}

示例 8-6 展示了如何从我们的 TensorFlow 服务端点请求模型预测的示例。在我们的示例中,我们只提交了一个数据示例用于推理,但我们可以轻松地提交代表多个请求的数据输入列表。

示例 8-6. 使用 Python 客户端进行模型预测请求的示例

import``requests``def``get_rest_request``(``text``,``model_name``=``"my_model"``):``url``=``"http://localhost:8501/v1/models/{}:predict"``.``format``(``model_name``)payload``=``{``"instances"``:``[``text``]}response``=``requests``.``post``(``url``=``url``,``json``=``payload``)``return``response``rs_rest``=``get_rest_request``(``text``=``"classify my text"``)``rs_rest``.``json``()

将服务器未在同一台机器上运行时,将localhost替换为 IP 地址。

如果要推断更多示例,请在instance列表中添加更多示例。

通过 gRPC 使用 TensorFlow Serving

如果要使用 gRPC 模型,步骤与 REST API 请求略有不同。

首先,建立一个 gRPC channel。该通道提供与给定主机地址上的 gRPC 服务器的连接,并通过给定端口。如果需要安全连接,则需要在此时建立安全通道。通道建立后,将创建一个stubstub是一个本地对象,复制自服务器中可用的方法:

import``grpc``from``tensorflow_serving.apis``import``predict_pb2``from``tensorflow_serving.apis``import``prediction_service_pb2_grpc``import``tensorflow``as``tf``def``create_grpc_stub``(``host``,``port``=``8500``):``hostport``=``"{}:{}"``.``format``(``host``,``port``)``channel``=``grpc``.``insecure_channel``(``hostport``)``stub``=``prediction_service_pb2_grpc``.``PredictionServiceStub``(``channel``)``return``stub

创建 gRPC stub 后,我们可以设置模型和签名,以访问正确模型的预测并提交我们的数据进行推理:

def``grpc_request``(``stub``,``data_sample``,``model_name``=``'my_model'``, \ signature_name``=``'classification'``):``request``=``predict_pb2``.``PredictRequest``()``request``.``model_spec``.``name``=``model_name``request``.``model_spec``.``signature_name``=``signature_name``request``.``inputs``[``'inputs'``]``.``CopyFrom``(``tf``.``make_tensor_proto``(``data_sample``,``shape``=``[``1``,``1``]))result_future``=``stub``.``Predict``.``future``(``request``,``10``)return``result_future

inputs是我们神经网络输入的名称。

10 是函数超时前的最大时间(秒数)。

现在,有了这两个可用的函数,我们可以使用这两个函数调用推断我们的示例数据集:

stub``=``create_grpc_stub``(``host``,``port``=``8500``)``rs_grpc``=``grpc_request``(``stub``,``data``)

安全连接

grpc库还提供了与 gRPC 端点安全连接的功能。以下示例显示如何从客户端端创建安全通道与 gRPC:

import grpc cert``= open``(``client_cert_file, 'rb'``)``.read``()``key``= open``(``client_key_file, 'rb'``)``.read``()``ca_cert``= open``(``ca_cert_file, 'rb'``)``.read``()``if ca_cert_file else``''``credentials``= grpc.ssl_channel_credentials``( ca_cert, key, cert )``channel``= implementations.secure_channel``(``hostport, credentials``)

在服务器端,如果配置了 SSL,TensorFlow Serving 可以终止安全连接。要终止安全连接,请按以下示例创建 SSL 配置文件:6

server_key: "-----BEGIN PRIVATE KEY-----\n``              <your_ssl_key>\n``              -----END PRIVATE KEY-----" server_cert: "-----BEGIN CERTIFICATE-----\n``              <your_ssl_cert>\n``              -----END CERTIFICATE-----" custom_ca: "" client_verify: false

创建了配置文件后,您可以在启动 TensorFlow Serving 时将文件路径传递给 --ssl_config_file 参数:

$ tensorflow_model_server --port``=``8500``\ --rest_api_port``=``8501``\ --model_name``=``my_model \ --model_base_path``=``/models/my_model \ --ssl_config_file``=``"<path_to_config_file>"

从分类和回归模型获取预测结果

如果您对从分类和回归模型进行预测感兴趣,可以使用 gRPC API。

如果您想从分类模型获取预测结果,则需要替换以下行:

from``tensorflow_serving.apis``import``predict_pb2``...``request``=``predict_pb2``.``PredictRequest``()

with:

from``tensorflow_serving.apis``import``classification_pb2``...``request``=``classification_pb2``.``ClassificationRequest``()

如果您想从回归模型获取预测结果,可以使用以下导入语句:

from``tensorflow_serving.apis``import``regression_pb2``...``regression_pb2``.``RegressionRequest``()

Payloads

gRPC API 使用协议缓冲作为 API 请求的数据结构。通过使用二进制协议缓冲有效减少了 API 请求所需的带宽,与 JSON 负载相比。此外,根据模型输入数据结构的不同,您可能会体验到更快的预测速度,如同 REST 终端节点一样。性能差异在于,提交的 JSON 数据将被转换为 tf.Example 数据结构。这种转换可能会减慢模型服务器的推断速度,您可能会遇到比 gRPC API 情况下更慢的推断性能。

您提交给 gRPC 终端节点的数据需要转换为协议缓冲数据结构。TensorFlow 为您提供了一个便捷的实用函数来执行转换,称为 tf.make_tensor_proto。它允许各种数据格式,包括标量、列表、NumPy 标量和 NumPy 数组。该函数将会把给定的 Python 或 NumPy 数据结构转换为推断所需的协议缓冲格式。

使用 TensorFlow Serving 进行模型 A/B 测试

A/B 测试是在实际情况下测试不同模型的优秀方法论。在这种情况下,一定比例的客户将接收模型版本 A 的预测,所有其他请求将由模型版本 B 提供服务。

我们之前讨论过,您可以配置 TensorFlow Serving 加载多个模型版本,然后在您的 REST 请求 URL 或 gRPC 规范中指定模型版本。

TensorFlow Serving 不支持服务器端的 A/B 测试,这意味着模型服务器将所有客户端请求重定向到两个模型版本中的单一端点。但是通过稍微调整我们的请求 URL,我们可以为客户端提供对随机 A/B 测试的适当支持:7

from``random``import``randomdef``get_rest_url``(``model_name``,``host``=``'localhost'``,``port``=``8501``,``verb``=``'predict'``,``version``=``None``):``url``=``"http://{}:{}/v1/models/{}/"``.``format``(``host``,``port``,``model_name``)``if``version``:``url``+=``"versions/{}"``.``format``(``version``)``url``+=``":{}"``.``format``(``verb``)``return``url``...``# 从此客户端的所有请求中提交 10% 给版本 1。``# 90% 的请求应该转到默认模型。``threshold``=``0.1``version``=``1``if``random``()``<``threshold``else``Noneurl``=``get_rest_url``(``model_name``=``'complaints_classification'``,``version``=``version``)

random 库将帮助我们选择一个模型。

如果 version = None,TensorFlow Serving 将使用默认版本进行推断。

正如您所看到的,随机更改我们模型推理的请求 URL(在我们的 REST API 示例中),可以为您提供一些基本的 A/B 测试功能。如果您希望通过在服务器端执行模型推理的随机路由来扩展这些能力,我们强烈建议使用像 Istio 这样的路由工具。Istio 最初设计用于网络流量,可用于将流量路由到特定模型,逐步引入模型,执行 A/B 测试或创建数据路由到特定模型的策略。

当您使用模型进行 A/B 测试时,通常有必要从模型服务器请求模型的信息。在接下来的部分中,我们将解释如何从 TensorFlow Serving 请求元数据信息。

从模型服务器请求模型元数据

在本书的开头,我们阐述了模型生命周期,并解释了我们希望自动化机器学习生命周期的方式。连续生命周期的关键组成部分是生成关于模型版本的准确性或一般性能反馈。我们将深入研究如何生成这些反馈循环,详见第十三章,但现在,想象一下,您的模型对某些数据进行分类(例如文本的情感),然后请用户对预测结果进行评分。模型是否正确预测了某些事物的信息对于改进未来的模型版本至关重要,但仅当我们知道哪个模型版本执行了预测时才有用。

模型服务器提供的元数据将包含用于注释反馈循环的信息。

模型元数据的 REST 请求

使用 TensorFlow Serving 请求模型元数据非常直接。TensorFlow Serving 为模型元数据提供了一个端点:

http://``{``HOST``}``:``{``PORT``}``/v1/models/``{``MODEL_NAME``}[``/versions/``{``MODEL_VERSION``}]``/metadata

类似我们之前讨论的 REST API 推断请求,您可以选择在请求 URL 中指定模型版本,或者如果不指定,模型服务器将提供关于默认模型的信息。

如示例 8-7 所示,我们可以通过单个 GET 请求请求模型元数据。

示例 8-7. 使用 Python 客户端请求模型元数据的示例

import``requests``def``metadata_rest_request``(``model_name``,``host``=``"localhost"``,``port``=``8501``,``version``=``None``):``url``=``"http://{}:{}/v1/models/{}/"``.``format``(``host``,``port``,``model_name``)``if``version``:``url``+=``"versions/{}"``.``format``(``version``)``url``+=``"/metadata"response``=``requests``.``get``(``url``=``url``)return``response

在请求 URL 中追加 /metadata 获取模型信息。

执行 GET 请求。

模型服务器将以 model_spec 字典和 metadata 字典的形式返回模型规格和模型定义:

{``"model_spec"``:``{``"name"``:``"complaints_classification"``,``"signature_name"``:``""``,``"version"``:``"1556583584"``},``"metadata"``:``{``"signature_def"``:``{``"signature_def"``:``{``"classification"``:``{``"inputs"``:``{``"inputs"``:``{``"dtype"``:``"DT_STRING"``,``"tensor_shape"``:``{``...

gRPC 请求模型元数据

使用 gRPC 请求模型元数据与 REST API 情况几乎一样简单。在 gRPC 情况下,您需要提交一个 GetModelMetadataRequest,将模型名称添加到规范中,并通过 stubGetModelMetadata 方法提交请求。

from``tensorflow_serving.apis``import``get_model_metadata_pb2``def``get_model_version``(``model_name``,``stub``):``request``=``get_model_metadata_pb2``.``GetModelMetadataRequest``()``request``.``model_spec``.``name``=``model_name``request``.``metadata_field``.``append``(``"signature_def"``)``response``=``stub``.``GetModelMetadata``(``request``,``5``)``return``response``.``model_spec``model_name``=``'complaints_classification'``stub``=``create_grpc_stub``(``'localhost'``)``get_model_version``(``model_name``,``stub``)``name``:``"complaints_classification"``version``{``value``:``1556583584``}

gRPC 响应包含一个ModelSpec对象,其中包含加载模型的版本号。

  • 更有趣的是获取已加载模型的模型签名信息的用例。几乎使用相同的请求函数,我们可以确定模型的元数据。唯一的区别在于,我们不访问响应对象的model_spec属性,而是访问metadata。为了便于阅读,需要对信息进行序列化,因此我们将使用SerializeToString方法将协议缓冲区信息转换为人类可读的格式。

from``tensorflow_serving.apis``import``get_model_metadata_pb2``def``get_model_meta``(``model_name``,``stub``):``request``=``get_model_metadata_pb2``.``GetModelMetadataRequest``()``request``.``model_spec``.``name``=``model_name``request``.``metadata_field``.``append``(``"signature_def"``)``response``=``stub``.``GetModelMetadata``(``request``,``5``)``return``response``.``metadata``[``'signature_def'``]``model_name``=``'complaints_classification'``stub``=``create_grpc_stub``(``'localhost'``)``meta``=``get_model_meta``(``model_name``,``stub``)``print``(``meta``.``SerializeToString``()``.``decode``(``"utf-8"``,``'ignore'``))``# type.googleapis.com/tensorflow.serving.SignatureDefMap``# serving_default``# complaints_classification_input``#         input_1:0``#                2@``# complaints_classification_output(``# dense_1/Sigmoid:0``#                tensorflow/serving/predict

gRPC 请求比 REST 请求更复杂;然而,在对性能要求较高的应用中,它们可以提供更快的预测性能。通过批量处理预测请求,我们还可以增加模型的预测性能。

批量推断请求

  • TensorFlow Serving 中批量推断请求的使用案例是最强大的功能之一。在模型训练过程中,批处理加速了我们的训练,因为我们可以并行计算我们的训练样本。同时,如果我们将批次的内存需求与 GPU 的可用内存匹配,还可以高效地使用计算硬件。

如果您在未启用批处理的情况下运行 TensorFlow Serving,如图 8-3 所示,则每个客户端请求都会单独并按顺序处理。例如,如果您对图像进行分类,您的第一个请求将在您的 CPU 或 GPU 上推断模型,然后才是第二个请求、第三个请求等。在这种情况下,我们未充分利用 CPU 或 GPU 的可用内存。

如图 8-4 所示,多个客户端可以请求模型预测,模型服务器将不同的客户端请求批处理为一个“批次”进行计算。通过这个批处理步骤推断每个请求可能需要比单个请求更长的时间,这是由于超时或批次限制。然而,与我们的训练阶段类似,我们可以并行计算批处理并在批处理计算完成后将结果返回给所有客户端。这比单个样本请求更有效地利用硬件。

图 8-3. TensorFlow Serving 概述(未进行批处理)

图 8-4. TensorFlow Serving 概述(带有批处理)

配置批处理预测

需要启用 TensorFlow Serving 的批处理预测,并为您的用例进行配置。您有五个配置选项:

max_batch_size

此参数控制批次大小。大批量大小将增加请求的延迟,并可能导致耗尽 GPU 内存。小批量大小则会失去使用最佳计算资源的好处。

batch_timeout_micros

此参数设置填充批次的最大等待时间。此参数对于限制推理请求的延迟非常有用。

num_batch_threads

线程数量配置了可以并行使用多少个 CPU 或 GPU 核心。

max_enqueued_batches

此参数设置了用于预测排队的最大批次数。这种配置有助于避免请求的不合理积压。如果达到最大数量,请求将返回错误而不是排队。

pad_variable_length_inputs

此布尔参数确定是否对具有可变长度的输入张量进行填充,使所有输入张量的长度相同。

可想而知,为了实现最佳批处理设置参数需要进行一些调整,并且这取决于应用。如果您进行在线推理,应该旨在限制延迟。通常建议首先将batch_timeout_micros设置为 0,然后将超时调整为 10,000 微秒。相反,批处理请求将受益于较长的超时时间(毫秒到秒),以始终使用最佳性能的批处理大小。当达到max_batch_size或超时时,TensorFlow Serving 将对批次进行预测。

如果您为 CPU 预测配置 TensorFlow Serving,请将 num_batch_threads 设置为 CPU 核心数。如果您配置了 GPU 设置,请调整 max_batch_size 以实现 GPU 内存的最佳利用率。在调整配置时,请确保将 max_enqueued_batches 设置为一个大数,以避免某些请求提前返回而没有进行适当推理。

您可以将参数设置在文本文件中,如下例所示。在我们的示例中,我们创建了一个名为 batching_parameters.txt 的配置文件,并添加了以下内容:

max_batch_size { value: 32``} batch_timeout_micros { value: 5000``} pad_variable_length_inputs: true

如果您想启用批处理,需要将两个额外的参数传递给运行 TensorFlow Serving 的 Docker 容器。要启用批处理,请将 enable_batching 设置为 true,并将 batching_parameters_file 设置为容器内批处理配置文件的绝对路径。请记住,如果批处理配置文件不位于与模型版本相同的文件夹中,则必须挂载额外的文件夹。

下面是使用启用批处理的 docker run 命令的完整示例,它启动了带有批处理功能的 TensorFlow Serving Docker 容器。然后,参数将传递给 TensorFlow Serving 实例:

docker run -p 8500:8500 \ -p 8501:8501 \ --mount type``=``bind``,source``=``/path/to/models,target``=``/models/my_model \ --mount type``=``bind``,source``=``/path/to/batch_config,target``=``/server_config \ -e MODEL_NAME``=``my_model -t tensorflow/serving \ --enable_batching``=``true --batching_parameters_file``=``/server_config/batching_parameters.txt

正如前文所述,批处理的配置需要额外的调整,但性能增益应该弥补初始设置的成本。我们强烈建议启用此 TensorFlow Serving 功能。这对于使用离线批处理过程推断大量数据样本尤为有用。

其他 TensorFlow Serving 优化

TensorFlow Serving 提供了多种额外的优化功能。其他特性标志包括:

--file_system_poll_wait_seconds=1

TensorFlow Serving 会定期检查是否有新的模型版本可用。您可以通过将其设置为1来禁用此功能。如果您只想加载模型一次而不更新它,可以将其设置为0。该参数期望一个整数值。如果您从云存储桶加载模型,我们强烈建议您增加轮询时间,以避免因频繁列出云存储桶上的操作而产生不必要的云服务提供商费用。

--tensorflow_session_parallelism=0

TensorFlow Serving 将自动确定在 TensorFlow 会话中使用多少线程。如果您希望手动设置线程数,可以通过将此参数设置为任意正整数值来覆盖它。

--tensorflow_intra_op_parallelism=0

此参数设置了用于运行 TensorFlow Serving 的核心数。可用线程数确定将并行化多少操作。如果值为0,将使用所有可用核心。

--tensorflow_inter_op_parallelism=0

此参数设置了在池中执行 TensorFlow 操作的可用线程数。这对于最大化 TensorFlow 图中独立操作的执行非常有用。如果值设置为0,将使用所有可用核心,并为每个核心分配一个线程。

与之前的示例类似,您可以将配置参数传递给docker run命令,如下例所示:

docker run -p 8500:8500 \ -p 8501:8501 \ --mount type``=``bind``,source``=``/path/to/models,target``=``/models/my_model \ -e MODEL_NAME``=``my_model -t tensorflow/serving \ --tensorflow_intra_op_parallelism``=``4``\ --tensorflow_inter_op_parallelism``=``4``\ --file_system_poll_wait_seconds``=``10``\ --tensorflow_session_parallelism``=``2

讨论的配置选项可以提高性能并避免不必要的云提供商费用。

TensorFlow Serving 替代方案

TensorFlow Serving 是部署机器学习模型的一个很好的方式。使用 TensorFlow 的Estimator和 Keras 模型,您应该可以涵盖大量的机器学习概念。但是,如果您想要部署传统模型或者您选择的机器学习框架不是 TensorFlow 或 Keras,这里有一些选择。

BentoML

BentoML 是一个与框架无关的库,用于部署机器学习模型。它支持通过 PyTorch、scikit-learn、TensorFlow、Keras 和 XGBoost 训练的模型。对于 TensorFlow 模型,BentoML 支持SavedModel格式。BentoML 支持批处理请求。

Seldon

英国初创公司 Seldon 提供了多种开源工具来管理模型生命周期,其中核心产品之一是Seldon Core。Seldon Core 为您提供一个工具箱,用于将您的模型包装为 Docker 镜像,然后通过 Seldon 在 Kubernetes 集群中部署。

在撰写本章时,Seldon 支持用 TensorFlow、scikit-learn、XGBoost 甚至 R 训练的机器学习模型。

Seldon 自带其生态系统,允许将预处理构建到其自己的 Docker 镜像中,这些镜像与部署镜像一起部署。它还提供了一个路由服务,允许您执行 A/B 测试或多臂老丨虎丨机实验。

Seldon 与 Kubeflow 环境高度集成,并且与 TensorFlow Serving 类似,是在 Kubernetes 上部署模型的一种方式。

GraphPipe

GraphPipe 是另一种部署 TensorFlow 和非 TensorFlow 模型的方法。Oracle 推动这个开源项目。它允许您部署不仅仅是 TensorFlow(包括 Keras)模型,还可以是 Caffe2 模型和所有可以转换为开放神经网络交换格式(ONNX)的机器学习模型。8 通过 ONNX 格式,您可以使用 GraphPipe 部署 PyTorch 模型。

除了为 TensorFlow、PyTorch 等提供模型服务器外,GraphPipe 还提供了 Python、Java 和 Go 等编程语言的客户端实现。

简单的 TensorFlow 服务

简单 TensorFlow 服务 是 4Paradigm 的陈迪豪开发的。这个库不仅仅支持 TensorFlow 模型。当前支持的模型框架列表包括 ONNX、scikit-learn、XGBoost、PMML 和 H2O。它支持多个模型,在 GPU 上进行预测,并提供各种语言的客户端代码。

简单 TensorFlow 服务的一个重要方面是它支持对模型服务器的身份验证和加密连接。身份验证目前不是 TensorFlow 服务的功能,支持 SSL 或传输层安全性(TLS)需要自定义构建 TensorFlow 服务。

MLflow

MLflow 支持部署机器学习模型,但这只是由 DataBricks 创建的工具的一个方面。MLflow 旨在通过 MLflow Tracking 管理模型实验。该工具具有内置的模型服务器,为通过 MLflow 管理的模型提供 REST API 端点。

MLflow 还提供界面,可以直接将模型从 MLflow 部署到 Microsoft 的 Azure ML 平台和 AWS SageMaker。

Ray Serve

Ray 项目 提供了部署机器学习模型的功能。Ray Serve 是框架无关的,支持 PyTorch、TensorFlow(包括 Keras)、Scikit-Learn 模型或自定义模型预测。该库具备批处理请求的能力,并允许在模型及其版本之间进行流量路由。

Ray Serve 已集成到 Ray 项目生态系统中,并支持分布式计算设置。

使用云服务提供商部署

到目前为止,我们讨论的所有模型服务器解决方案都必须由您安装和管理。但是,所有主要的云提供商——Google Cloud、AWS 和 Microsoft Azure——都提供包括托管机器学习模型在内的机器学习产品。

在本节中,我们将指导您通过 Google Cloud 的 AI 平台进行示例部署。让我们从模型部署开始,稍后我们将解释如何从您的应用程序客户端获取部署模型的预测。

使用案例

如果您希望无缝部署模型且不必担心扩展模型部署,则托管云部署是运行模型服务器实例的良好选择。所有云服务提供商都提供了通过推理请求数量扩展的部署选项。

然而,模型部署的灵活性也伴随着成本。托管服务提供了无忧的部署方式,但其费用较高。例如,两个全天候运行的模型版本(需要两个计算节点)的费用比运行 TensorFlow Serving 实例的可比计算实例更高。托管部署的另一个缺点是产品的限制。一些云服务提供商要求您通过它们自己的软件开发工具包进行部署,其他提供商则对节点大小和模型占用内存的限制。对于庞大的机器学习模型来说,特别是包含很多层的模型(即语言模型),这些限制可能是严重的限制。

使用 GCP 的部署示例

在本节中,我们将为您介绍如何在 Google Cloud 的 AI 平台上进行部署。与编写配置文件和执行终端命令不同,我们可以通过 Web UI 设置模型端点。

GCP AI 平台的模型大小限制

GCP 的端点限制模型大小最高为 500 MB。但是,如果您通过 N1 类型的计算引擎部署端点,则最大模型限制增加到 2 GB。在撰写本文时,此选项作为测试版功能可用。

模型部署

部署包括三个步骤:

  • 使模型在 Google Cloud 上可访问。

  • 创建一个新的 Google Cloud AI 平台模型实例。

  • 创建一个包含模型实例的新版本。

部署始于将您的导出 TensorFlow 或 Keras 模型上传到存储桶。如图 8-5 所示,您需要上传整个导出模型。一旦模型上传完成,请复制存储位置的完整路径。

图 8-5. 将训练好的模型上传到云存储

一旦您上传了您的机器学习模型,请转至 GCP 的 AI 平台,设置您的机器学习模型以进行部署。如果这是您在 GCP 项目中首次使用 AI 平台,则需要启用 API。Google Cloud 的自动启动过程可能需要几分钟。

如图 8-6 所示,您需要为模型提供一个唯一标识符。一旦创建了标识符,选择您首选的部署区域,9 并创建一个可选的项目描述,继续通过点击“创建”来完成设置。

图 8-6. 创建一个新的模型实例

一旦注册新模型,该模型将显示在仪表板中,如 图 8-7 所示。您可以通过在溢出菜单中点击 “创建版本” 来为仪表板创建新模型版本。

图 8-7. 创建新模型版本

创建新模型版本时,您需配置一个运行模型的计算实例。Google Cloud 提供多种配置选项,如 图 8-8 所示。版本名称 非常重要,因为稍后您将在客户端设置中引用 版本名称。请将 模型 URI 设置为您在先前步骤中保存的存储路径。

Google Cloud AI 平台支持多种机器学习框架,包括 XGBoost、scikit-learn 和自定义预测例程。

图 8-8. 设置实例详细信息

GCP 还允许您配置模型实例在模型经历大量推理请求时的扩展方式。您可以选择手动扩展或自动扩展之间的两种扩展行为。

手动扩展为您提供了设置用于模型版本预测的节点数量的选项。相比之下,自动扩展功能使您能够根据端点需求调整实例数量。如果您的节点没有任何请求,节点数量甚至可能降至零。请注意,如果自动扩展将节点数量降至零,则需要一些时间来重新启动您的模型版本,以处理下一个请求命中模型版本端点。此外,如果在自动扩展模式下运行推理节点,则会按照 10 分钟的间隔计费。

一旦整个模型版本配置完成,Google Cloud 将为您启动实例。如果一切就绪以进行模型预测,则会在版本名称旁边看到绿色的检查图标,如 图 8-9 所示。

图 8-9. 完成部署,新版本可用

您可以同时运行多个模型版本。在模型版本的控制面板中,您可以将一个版本设置为默认版本,任何未指定版本的推理请求将路由到指定的 “默认版本”。请注意,每个模型版本将托管在单独的节点上,并会积累 GCP 成本。

模型推理

由于 TensorFlow Serving 在 Google 内部经过了大量测试,并且在 GCP 幕后也得到了广泛使用,因此它也在 AI 平台中使用。您会注意到,AI 平台不仅仅使用了我们在 TensorFlow Serving 实例中看到的相同模型导出格式,而且负载的数据结构与之前看到的相同。

唯一的显著区别在于 API 连接。正如本节所述,您将通过处理请求认证的 GCP API 连接到模型版本。

要连接到 Google Cloud API,您需要使用以下命令安装google-api-python-client库:

$ pip install google-api-python-client

所有 Google 服务都可以通过服务对象连接。下面代码片段中的辅助函数突出了如何创建服务对象。Google API 客户端采用服务名称服务版本,返回一个对象,通过返回的对象的方法提供所有 API 功能:

import``googleapiclient.discovery``def``_connect_service``():``return``googleapiclient``.``discovery``.``build``(``serviceName``=``"ml"``,``version``=``"v1"``)

与我们之前的 REST 和 gRPC 示例类似,我们将推断数据嵌套在一个固定的instances键下,其中包含一个输入字典列表。我们创建了一个小助手函数来生成有效负载。此函数包含任何预处理,如果需要在推断之前修改输入数据:

def``_generate_payload``(``sentence``):``return``{``"instances"``:``[{``"sentence"``:``sentence``}]}

在客户端创建的服务对象和生成的有效负载后,现在是时候从托管在 Google Cloud 上的机器学习模型请求预测了。

AI 平台服务的服务对象包含一个预测方法,接受namebodyname是一个路径字符串,包含您的 GCP 项目名称、模型名称,以及如果要使用特定模型版本进行预测,则版本名称。如果您不指定版本号,则将使用默认模型版本进行模型推断。body包含我们之前生成的推断数据结构:

project``=``"``yourGCPProjectName``"``model_name``=``"demo_model"``version_name``=``"v1"``request``=``service``.``projects``()``.``predict``(``name``=``"projects/{}/models/{}/versions/{}"``.``format``(``project``,``model_name``,``version_name``),``body``=``_generate_payload``(``sentence``)``)``response``=``request``.``execute``()

Google Cloud AI 平台响应包含对不同类别的预测分数,类似于从 TensorFlow Serving 实例的 REST 响应:

{``'predictions'``:``[``{``'label'``:``[``0.9000182151794434``,``0.02840868942439556``,``0.009750653058290482``,``0.06182243302464485``]}``]}

示范的部署选项是一种快速部署机器学习模型的方式,而无需设置整个部署基础设施。其他云提供商如 AWS 或 Microsoft Azure 提供类似的模型部署服务。根据您的部署要求,云提供商可能是自托管部署选项的良好替代方案。缺点可能包括更高的成本以及不完全优化端点(例如通过提供 gRPC 端点或批处理功能,正如我们在"批处理推断请求"中讨论的)。

使用 TFX Pipelines 进行模型部署

在本章的引言中,在 Figure 8-1 中展示了部署步骤作为机器学习流水线的一个组成部分。在讨论模型部署的内部工作原理,特别是 TensorFlow Serving 后,我们希望在本节中将机器学习流水线与此连接起来。

在 Figure 8-10 中,您可以看到持续模型部署的步骤。我们假设您已经运行并配置了 TensorFlow Serving,以从给定的文件位置加载模型。此外,我们假设 TensorFlow Serving 将从外部文件位置(例如云存储桶或挂载的持久卷)加载模型。TFX 管道和 TensorFlow Serving 实例两个系统需要访问相同的文件系统。

图 8-10. TFX 管道生成的模型部署

在 “TFX Pusher Component” 中,我们讨论了 Pusher 组件。这个 TFX 组件允许我们将经过验证的模型推送到指定位置(例如,云存储桶)。TensorFlow Serving 可以从云存储位置获取新的模型版本,卸载之前的模型版本,并加载给定模型端点的最新版本。这是 TensorFlow Serving 的默认模型策略。

基于默认的模型策略,我们可以相对容易地使用 TFX 和 TensorFlow Serving 构建一个简单的持续部署设置。

摘要

在本章中,我们讨论了如何设置 TensorFlow Serving 来部署机器学习模型,以及为什么模型服务器比通过 Flask web 应用部署机器学习模型更具扩展性。我们详细介绍了安装和配置步骤,介绍了两种主要的通信选项,REST 和 gRPC,并简要讨论了这两种通信协议的优缺点。

此外,我们解释了 TensorFlow Serving 的一些重要优势,包括模型请求的批处理和获取不同模型版本元数据的能力。我们还讨论了如何通过 TensorFlow Serving 快速设置 A/B 测试。

我们在本章结尾简要介绍了托管云服务的概念,以 Google Cloud AI Platform 为例。托管云服务允许您部署机器学习模型,而无需管理自己的服务器实例。

在下一章中,我们将讨论如何增强我们的模型部署,例如通过从云提供商加载模型,或者通过 Kubernetes 部署 TensorFlow Serving。

1   有关应用实例,请访问TensorFlow

2   模型标签集用于识别用于加载的 MetaGraph。一个模型可以导出为指定用于训练和服务的图形。这两个 MetaGraph 可以通过不同的模型标签提供。

3   如果您之前没有安装或使用过 Docker,请查看我们在 附录 A 中的简要介绍。

4   想要更详细地了解 REST 和 gRPC,请查看 “REST Versus gRPC”。

5   模型的加载和卸载仅在 file_system_poll_wait_seconds 配置为大于 0 时才有效。默认配置为 2 秒。

6   SSL 配置文件基于 SSL 配置协议缓冲区,可以在 TensorFlow Serving API 中找到。

7   A/B 测试如果没有对与两个模型交互产生的结果进行统计检验,是不完整的。所展示的实现仅提供了 A/B 测试的后端。

8   ONNX 是描述机器学习模型的一种方式。

9   为了达到最低的预测延迟,请选择距离模型请求地理区域最近的区域。

第九章:使用 TensorFlow Serving 进行高级模型部署

在上一章中,我们讨论了如何使用 TensorFlow 或 Keras 模型以及 TensorFlow Serving 进行高效部署。现在我们在本章介绍机器学习模型部署的高级用例。这些用例涉及多种主题,例如模型 A/B 测试、优化模型以进行部署和扩展,以及监视模型部署。如果您还没有机会查看前一章,我们建议您这样做,因为它为本章提供了基础知识。

解耦部署周期

在 第八章 中展示的基本部署方式效果良好,但有一个限制:训练和验证过的模型需要在构建步骤中包含在部署容器镜像中,或在容器运行时挂载到容器中,正如我们在前一章中讨论的那样。这两个选项都需要了解 DevOps 过程(例如更新 Docker 容器镜像)或在部署新模型版本的阶段协调数据科学和 DevOps 团队。

正如我们在 第八章 中简要提到的,TensorFlow Serving 可以从远程存储驱动器(例如 AWS S3 或 GCP 存储桶)加载模型。TensorFlow Serving 的标准加载器策略频繁轮询模型存储位置,在检测到新模型后卸载先前加载的模型并加载新模型。由于这种行为,我们只需要部署我们的模型服务容器一次,它会持续更新存储文件夹位置中可用的模型版本。

工作流程概述

在我们深入了解如何配置 TensorFlow Serving 从远程存储位置加载模型之前,让我们先看看我们提议的工作流程。

图 9-1 显示了工作流程的分离。模型服务容器只需部署一次。数据科学家可以通过存储桶的 Web 界面或命令行复制操作上传新版本的模型到存储桶中。任何模型版本的更改都将被服务实例发现。不需要重新构建模型服务器容器或重新部署容器。

图 9-1。数据科学与 DevOps 部署周期的分离

如果您的存储桶文件夹是公开访问的,您可以通过简单地更新模型基本路径到远程路径来提供远程模型服务:

docker run -p 8500:8500 \ -p 8501:8501 \ -e MODEL_BASE_PATH``=``s3://bucketname/model_path/ \ -e MODEL_NAME``=``my_model \ -t tensorflow/serving

远程存储桶路径

其余配置保持不变

如果您的模型存储在私有云存储桶中,您需要更详细地配置 TensorFlow Serving 以提供访问凭据。设置与提供程序有关。本章将涵盖两个提供商的示例:AWS 和 GCP。

从 AWS S3 访问私有模型

AWS 通过用户特定的访问密钥和访问密钥验证用户。要访问私有 AWS S3 存储桶,您需要创建用户访问密钥和密钥。1

您可以将 AWS 访问密钥和密钥作为环境变量提供给 docker run 命令。这使得 TensorFlow Serving 可以获取凭据并访问私有存储桶:

docker run -p 8500:8500 \ -p 8501:8501 \ -e MODEL_BASE_PATH``=``s3://bucketname/model_path/ \ -e MODEL_NAME``=``my_model \ -e AWS_ACCESS_KEY_ID``=``XXXXX``\ -e AWS_SECRET_ACCESS_KEY``=``XXXXX``\ -t tensorflow/serving

环境变量的名称非常重要。

TensorFlow Serving 依赖于标准的 AWS 环境变量及其默认值。您可以覆盖默认值(例如,如果您的存储桶不位于 us-east-1 地区,或者您想更改 S3 端点)。

您有以下配置选项:

  • AWS_REGION=us-east-1

  • S3_ENDPOINT=s3.us-east-1.amazonaws.com

  • S3_USE_HTTPS=1

  • S3_VERIFY_SSL=1

配置选项可以作为环境变量添加,或者如下示例所示添加到 docker run 命令中:

docker run -p 8500:8500 \ -p 8501:8501 \ -e MODEL_BASE_PATH``=``s3://bucketname/model_path/ \ -e MODEL_NAME``=``my_model \ -e AWS_ACCESS_KEY_ID``=``XXXXX``\ -e AWS_SECRET_ACCESS_KEY``=``XXXXX``\ -e AWS_REGION``=``us-west-1 \ -t tensorflow/serving

可以通过环境变量添加额外的配置。

通过为 TensorFlow Serving 提供这几个额外的环境变量,您现在可以从远程 AWS S3 存储桶加载模型。

从 GCP 存储桶访问私有模型

GCP 通过服务账户对用户进行验证。要访问私有 GCP 存储桶,您需要创建一个服务账户文件。2

与 AWS 不同,在 GCP 的情况下,我们无法简单地将凭证作为环境变量提供,因为 GCP 验证需要一个包含服务账户凭据的 JSON 文件。在 GCP 情况下,我们需要在主机机器上挂载一个文件夹到 Docker 容器中,该文件夹包含凭证,并定义一个环境变量指向正确的凭证文件。

在下面的示例中,假设您已将新创建的服务帐户凭据文件保存在主机机器上的/home/``your_username``/.credentials/目录下。我们从 GCP 下载了服务帐户凭据并将文件保存为sa-credentials.json。您可以为凭据文件指定任何名称,但需要在 Docker 容器内更新环境变量GOOGLE_APPLICATION_CREDENTIALS的完整路径:

docker run -p 8500:8500 \ -p 8501:8501 \ -e MODEL_BASE_PATH``=``gcp://``bucketname``/``model_path``/ \ -e MODEL_NAME``=``my_model``\            -v /home/``your_username``/.credentials/:/credentials/ -e GOOGLE_APPLICATION_CREDENTIALS``=``/``credentials``/``sa-credentials.json``\ -t tensorflow/serving

挂载主机目录以使用凭据。

指定容器内的路径。

几个步骤即可将远程 GCP 存储桶配置为存储位置。

远程模型加载的优化

默认情况下,TensorFlow Serving 每两秒轮询一次任何模型文件夹,以查找更新的模型版本,无论该模型存储在本地还是远程位置。如果您的模型存储在远程位置,轮询操作将通过您的云提供商生成一个存储桶列表视图。如果您持续更新模型版本,您的存储桶可能包含大量文件。这将导致大型列表视图消息,并因此消耗少量但随时间积累的流量。您的云提供商很可能会为这些列表操作生成的网络流量收费。为避免费用意外,我们建议将轮询频率降低到每 120 秒,这仍然可以提供每小时高达 30 次的更新可能性,但会减少 60 倍的流量:

docker run -p 8500:8500 \ ...            -t tensorflow/serving \ --file_system_poll_wait_seconds``=``120

docker run命令的镜像规范之后需要添加 TensorFlow Serving 参数。您可以指定大于一秒的任何轮询等待时间。如果将等待时间设置为零,TensorFlow Serving 将不会尝试刷新加载的模型。

部署模型的优化

随着机器学习模型尺寸的增加,模型优化对于高效部署变得更加重要。模型量化允许您通过减少权重表示的精度来减少模型的计算复杂度。模型修剪允许您通过将其归零来隐式地删除不必要的权重。而模型蒸馏将强制较小的神经网络学习较大神经网络的目标。

所有三种优化方法旨在实现更小的模型,从而实现更快的模型推理。在接下来的几节中,我们将进一步解释这三种优化选项。

量化

神经网络的权重通常存储为 32 位浮点数据类型(或者,IEEE 754 标准称之为单精度二进制浮点格式)。浮点数存储如下:1 位存储数的符号,8 位存储指数,以及 23 位存储浮点数的精度。

然而,网络权重可以以 bfloat16 浮点格式或 8 位整数表示。如图 9-2 所示,存储权重时,我们仍然需要 1 位来存储数的符号。当我们将权重存储为 bfloat16 浮点数时,指数仍然使用 8 位,因为它被 TensorFlow 使用。但是,分数表示从 23 位减少到 7 位。有时,权重甚至可以仅使用 8 位整数表示。

图 9-2. 浮点精度的降低

通过将网络权重表示改为 16 位浮点数或整数,我们可以获得以下好处:

  • 权重可以用更少的字节表示,在模型推理期间需要更少的内存。

  • 由于权重的减少表示,预测可以更快地推断出来。

  • 量化允许在 16 位甚至 8 位嵌入式系统上执行神经网络。

当前的模型量化工作流在模型训练后应用,通常称为后训练量化。由于量化模型可能由于精度不足而导致欠拟合,我们强烈建议在部署之前分析和验证任何模型。作为模型量化的示例,我们讨论了 Nvidia 的 TensorRT 库(参见 “使用 TensorRT 与 TensorFlow Serving”)和 TensorFlow 的 TFLite 库(参见 “TFLite”)。

剪枝

除了降低网络权重精度的方法外,模型剪枝是一种替代方法。其核心思想是通过移除不必要的权重,将训练过的网络压缩成一个更小的网络。实际操作中,这意味着将“不必要”的权重设为零。通过将不必要的权重设为零,可以加快推理或预测的速度。此外,由于稀疏权重导致更高的压缩率,剪枝模型还可以压缩成更小的模型尺寸。

如何剪枝模型

模型可以在训练阶段进行剪枝,使用类似于 TensorFlow 的模型优化包 tensorflow-model-optimization.3

蒸馏

与其减少网络连接,我们也可以训练一个较小、复杂度较低的神经网络,从一个更广泛的网络中学习已经训练好的任务。这种方法被称为蒸馏。不同于简单地训练一个与较大模型相同目标的较小机器学习模型,较大模型(教师神经网络)的预测会影响较小模型(学生神经网络)权重的更新,如在图 9-3 中所示。通过使用来自教师和学生神经网络的预测,可以强制学生网络从教师网络中学习一个目标。最终,我们可以用更少的权重和一个原本无法学习目标的模型架构来表达相同的模型目标。

图 9-3. 学生网络从教师网络中学习

使用 TensorRT 与 TensorFlow Serving

在将训练好的 TensorFlow 模型部署到生产环境之前,进行量化的一种选择是将模型转换为 Nvidia 的 TensorRT。

如果您在 Nvidia GPU 上运行计算密集型深度学习模型,可以使用这种额外的方式来优化您的模型服务器。Nvidia 提供了一个名为 TensorRT 的库,通过降低网络权重和偏差的数值表示精度来优化深度学习模型的推理。TensorRT 支持 int8 和 float16 表示。降低精度将降低模型的推理延迟。

在您的模型训练完成后,您需要使用 TensorRT 自带的优化器或 saved_model_cli4 来优化模型。优化后的模型可以加载到 TensorFlow Serving 中。在撰写本章时,TensorRT 仅限于某些 Nvidia 产品,包括 Tesla V100 和 P4。

首先,我们将使用 saved_model_cli 转换我们的深度学习模型:

$ saved_model_cli convert --dir saved_models/ \ --output_dir trt-savedmodel/ \ --tag_set serve tensorrt

转换后,您可以按以下方式在我们的 TensorFlow Serving GPU 设置中加载模型:

$ docker run --runtime``=``nvidia \ -p 8500:8500 \ -p 8501:8501 \ --mount type``=``bind``,source``=``/path/to/models,target``=``/models/my_model \ -e MODEL_NAME``=``my_model \ -t tensorflow/serving:latest-gpu

如果您在 Nvidia GPU 上推理您的模型,则 TensorRT 支持该硬件。切换到 TensorRT 可以进一步降低推理延迟。

TFLite

如果您希望优化您的机器学习模型但又没有运行 Nvidia GPU,则可以使用 TFLite 来对机器学习进行优化。

TFLite 传统上用于将机器学习模型转换为较小的模型尺寸,以部署到移动设备或物联网设备。但是,这些模型也可以与 TensorFlow Serving 一起使用。因此,与其将机器学习模型部署到边缘设备,不如使用 TensorFlow Serving 部署具有低推理延迟和较小内存占用的机器学习模型。

尽管使用 TFLite 进行优化看起来非常有前途,但是有一些注意事项:在撰写本节时,TensorFlow Serving 对 TFLite 模型的支持仅处于实验阶段。此外,并非所有 TensorFlow 操作都可以转换为 TFLite 指令。然而,支持的操作数量正在不断增加。

使用 TFLite 优化模型的步骤

TFLite 还可以用于优化 TensorFlow 和 Keras 模型。该库提供了多种优化选项和工具。您可以通过命令行工具或 Python 库转换模型。

起始点始终是以SavedModel格式训练并导出的模型。在以下示例中,我们关注 Python 指令。转换过程包括四个步骤:

  1. 加载导出的保存模型

  2. 定义您的优化目标

  3. 转换模型

  4. 将优化后的模型保存为 TFLite 模型

import``tensorflow``as``tf``saved_model_dir``=``"path_to_saved_model"``converter``=``tf``.``lite``.``TFLiteConverter``.``from_saved_model``(``saved_model_dir``)``converter``.``optimizations``=````tf``.``lite``.``Optimize``.``DEFAULT![]``tflite_model``=``converter``.``convert``()``with``open``(``"/tmp/model.tflite"``,``"wb"``)``as``f``:``f``.``write``(``tflite_model``)

设置优化策略。

TFLITE 优化

TFLite 提供了预定义的优化目标。通过更改优化目标,转换器将以不同的方式优化模型。几个选项是DEFAULTOPTIMIZE_FOR_LATENCYOPTIMIZE_FOR_SIZE

DEFAULT模式下,您的模型将针对延迟和大小进行优化,而另外两个选项则更倾向于一个选项。您可以设置转换选项如下:

...``converter``.``optimizations``=``[``tf``.``lite``.``Optimize``.``OPTIMIZE_FOR_SIZE``]``converter``.``target_spec``.``supported_types``=``[``tf``.``lite``.``constants``.``FLOAT16``]``tflite_model``=``converter``.``convert``()``...

如果您的模型包含在导出模型时 TFLite 不支持的 TensorFlow 操作,则转换步骤将失败并显示错误消息。您可以在执行转换器之前启用额外的选定 TensorFlow 操作,以使其在转换过程中可用。但是,这将增加约 30 MB 的 TFLite 模型大小。以下代码段显示了如何在执行转换器之前启用额外的 TensorFlow 操作:

...``converter``.``target_spec``.``supported_ops``=``[``tf``.``lite``.``OpsSet``.``TFLITE_BUILTINS``,``tf``.``lite``.``OpsSet``.``SELECT_TF_OPS``]``tflite_model``=``converter``.``convert``()``...

如果您的模型转换由于不支持的 TensorFlow 操作而失败,可以向 TensorFlow 社区寻求帮助。社区正在积极增加 TFLite 支持的操作数量,并欢迎未来操作的建议。您可以通过 TFLite Op 请求表单 提名 TensorFlow 操作。

使用 TensorFlow Serving 提供 TFLite 模型

最新的 TensorFlow Serving 版本可以无需进行任何主要配置更改读取 TFLite 模型。您只需启动 TensorFlow Serving,并使用启用的 use_tflite_model 标志加载优化模型,如以下示例所示:

docker run -p 8501:8501 \            --mount type=bind,\             source=/path/to/models,\             target=/models/my_model \            -e MODEL_BASE_PATH=/models \            -e MODEL_NAME=my_model \            -t tensorflow/serving:latest \            --use_tflite_model=true

启用 TFLite 模型加载。

TensorFlow Lite 优化模型可以提供低延迟和低内存占用的模型部署。

部署您的模型到边缘设备

在优化 TensorFlow 或 Keras 模型并使用 TensorFlow Serving 部署 TFLite 机器学习模型之后,您还可以将模型部署到各种移动和边缘设备;例如:

  • Android 和 iOS 手机

  • 基于 ARM64 的计算机

  • 微控制器和其他嵌入式设备(例如 Raspberry Pi)

  • 边缘设备(例如 IoT 设备)

  • 边缘 TPU(例如 Coral)

如果您对部署到移动或边缘设备感兴趣,我们推荐阅读 Anirudh Koul 等人编著的《Practical Deep Learning for Cloud, Mobile, and Edge》(O’Reilly)。如果您正在寻找关于以 TFMicro 为重点的边缘设备的材料,我们推荐 Pete Warden 和 Daniel Situnayake 编著的《TinyML》(O’Reilly)。

监控您的 TensorFlow Serving 实例

TensorFlow Serving 允许您监视推理设置。为此,TensorFlow Serving 提供了可以被 Prometheus 消费的度量端点。Prometheus 是一个实时事件记录和警报的免费应用程序,目前在 Apache License 2.0 下发布。它在 Kubernetes 社区广泛使用,但也可以轻松在没有 Kubernetes 的环境中使用。

要跟踪推理指标,您需要同时运行 TensorFlow Serving 和 Prometheus。然后,可以配置 Prometheus 连续地从 TensorFlow Serving 拉取指标。这两个应用程序通过 REST 端点进行通信,即使您的应用程序仅使用 gRPC 端点,TensorFlow Serving 也需要启用 REST 端点。

Prometheus 设置

在配置 TensorFlow Serving 将指标提供给 Prometheus 之前,我们需要设置和配置我们的 Prometheus 实例。为了简化这个示例,我们将两个 Docker 实例(TensorFlow Serving 和 Prometheus)并排运行,如图 9-4 所示。在更详细的设置中,这些应用程序将是 Kubernetes 部署。

图 9-4. Prometheus Docker 设置

在启动 Prometheus 之前,我们需要创建一个 Prometheus 配置文件。为此,我们将创建一个位于/tmp/prometheus.yml 的配置文件,并添加以下配置细节:

global``:``scrape_interval``: 15s evaluation_interval``: 15s external_labels``:``monitor``:``'tf-serving-monitor'``scrape_configs``: - job_name``:``'prometheus'``scrape_interval``: 5s metrics_path``: /monitoring/prometheus/metrics static_configs``: - targets``: [``'host.docker.internal:8501'``]

拉取指标的间隔。

TensorFlow Serving 的指标端点。

用你的应用程序的 IP 地址替换。

在我们的示例配置中,我们配置目标主机为host.docker.internal。我们利用 Docker 的域名解析功能,通过主机机器访问 TensorFlow Serving 容器。Docker 会自动将域名host.docker.internal解析为主机的 IP 地址。

创建完你的 Prometheus 配置文件后,可以启动运行 Prometheus 实例的 Docker 容器:

$ docker run -p 9090:9090 \ -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml \ prom/prometheus

启用 9090 端口。

挂载你的配置文件。

Prometheus 提供了一个仪表盘用于指标,稍后我们将通过 9090 端口访问。

TensorFlow Serving 配置

类似于我们之前为推断批处理配置的配置,我们需要编写一个小的配置文件来配置日志记录设置。

使用你选择的文本编辑器,创建一个包含以下配置的文本文件(在我们的示例中,我们将配置文件保存为/tmp/monitoring_config.txt):

prometheus_config {    enable: true,    path: "/monitoring/prometheus/metrics"``}

在配置文件中,我们设置了用于指标数据的 URL 路径。该路径需要与我们之前创建的 Prometheus 配置文件中指定的路径匹配(/tmp/prometheus.yml)。

要启用监控功能,我们只需添加monitoring_config_file的路径,TensorFlow Serving 将为 Prometheus 提供包含指标数据的 REST 端点:

$ docker run -p 8501:8501 \ --mount type``=``bind``,source``=``` pwd ```,target``=``/models/my_model \ --mount type``=``bind``,source``=``/tmp,target``=``/model_config \ tensorflow/serving \ --monitoring_config_file``=``/model_config/monitoring_config.txt

《PROMETHEUS IN ACTION》

使用 Prometheus 实例运行后,您现在可以访问 Prometheus 仪表盘,通过 Prometheus UI 查看 TensorFlow Serving 的度量标准,如 图 9-5 所示。

图 9-5. TensorFlow Serving 的 Prometheus 仪表盘

Prometheus 提供了一个标准化的 UI 来查看常见的度量标准。Tensorflow Serving 提供了多种度量选项,包括会话运行次数、加载延迟或特定图形的运行时间,如 图 9-6 所示。

图 9-6. TensorFlow Serving 的 Prometheus 指标选项

使用 TensorFlow Serving 和 Kubernetes 进行简单扩展

到目前为止,我们已经讨论了部署单个 TensorFlow Serving 实例来托管一个或多个模型版本。虽然这种解决方案对于许多部署来说已经足够,但对于经历高预测请求量的应用程序来说还不够。在这些情况下,您的单个 TensorFlow Serving Docker 容器需要复制以响应额外的预测请求。通常由像 Docker Swarm 或 Kubernetes 这样的工具来管理容器的复制编排。虽然深入介绍 Kubernetes 超出了本出版物的范围,但我们想提供一个小的预览,说明您的部署如何通过 Kubernetes 进行编排。

对于以下示例,我们假设您将运行一个 Kubernetes 集群,并且通过 kubectl 访问该集群。因为您可以在不构建特定 Docker 容器的情况下部署 TensorFlow 模型,所以在我们的示例中,您将看到我们重用了由 Google 提供的 Docker 容器,并配置了 Kubernetes 从远程存储桶加载我们的模型。

第一个源代码示例突出了两个方面:

  • 通过 Kubernetes 部署而无需构建特定的 Docker 容器

  • 处理 Google Cloud 认证以访问远程模型存储位置

在以下示例中,我们使用 GCP 作为我们部署的云提供商:5

apiVersion``: apps/v1 kind``: Deployment metadata``:``labels``:``app``: ml-pipelines name``: ml-pipelines spec``:``replicas``: 1 selector``:``matchLabels``:``app``: ml-pipelines template``:``spec``:``containers``: - args``: - --rest_api_port=8501             - --model_name=my_model             - --model_base_path=gs://your_gcp_bucket/my_model command``: - /usr/bin/tensorflow_model_server env``: - name``: GOOGLE_APPLICATION_CREDENTIALS value``: /secret/gcp-credentials/user-gcp-sa.json image``: tensorflow/serving name``: ml-pipelines ports``: - containerPort``: 8501 volumeMounts``: - mountPath``: /secret/gcp-credentials name``: gcp-credentials volumes``: - name``: gcp-credentials secret``:``secretName``: gcp-credentials

如有需要增加副本。

从远程位置加载模型。

在此处提供用于 GCP 的云凭据。

加载预构建的 TensorFlow Serving 镜像。

挂载服务帐号凭据文件(如果 Kubernetes 集群通过 GCP 部署)。

将凭据文件作为卷加载。

有了这个示例,我们现在可以部署和扩展您的 TensorFlow 或 Keras 模型,而无需构建自定义 Docker 镜像。

您可以使用以下命令在 Kubernetes 环境中创建您的服务帐号凭据文件:

$ kubectl create secret generic gcp-credentials \ --from-file``=``/path/to/your/user-gcp-sa.json

给定模型部署的相应 Kubernetes 服务设置可能如下所示配置:

apiVersion``: v1 kind``: Service metadata``:``name``: ml-pipelines spec``:``ports``: - name``: http nodePort``: 30601 port``: 8501 selector``:``app``: ml-pipelines type``: NodePort

使用几行 YAML 配置代码,您现在可以部署并且最重要的是扩展您的机器学习部署。对于更复杂的情况,如使用 Istio 对部署的 ML 模型进行流量路由,我们强烈建议深入了解 Kubernetes 和 Kubeflow。

深入阅读 Kubernetes 和 Kubeflow

Kubernetes 和 Kubeflow 是令人惊叹的 DevOps 工具,我们无法在这里提供全面的介绍。它们需要单独的出版物。如果您想进一步了解这两个主题,我们可以推荐以下出版物:

概述

在本章中,我们讨论了高级部署场景,例如通过远程云存储桶部署模型来分离数据科学和 DevOps 部署生命周期,优化模型以减少预测延迟和模型内存占用,或者如何扩展您的部署。

在接下来的章节中,我们现在希望将所有单独的流水线组件组合成一个单一的机器学习流水线,以提供可重复使用的机器学习工作流。

1   有关管理 AWS 访问密钥的更多详细信息,请查看文档

2   有关如何创建和管理服务帐户的详细信息,请查看文档

3   请访问 TensorFlow 网站获取有关优化方法深度修剪示例的更多信息。

4   请参阅Nvidia TensorRT 文档

5   AWS 的部署类似;需要提供 AWS 的secretkey作为环境变量,而不是凭证文件。

第十章:TensorFlow Extended 进阶

在完成有关模型部署的前两章后,我们完成了对个别管道组件的概述。在我们深入研究这些管道组件的编排之前,我们想要暂停一下,并在本章介绍 TFX 的高级概念。

到目前为止,我们介绍的管道组件已经能够为大多数问题创建机器学习管道。然而,有时我们需要构建自己的 TFX 组件或更复杂的管道图。因此,在本章中,我们将重点介绍如何构建自定义的 TFX 组件。我们将使用一个自定义的摄入组件来介绍这个主题,该组件直接摄入图像以供计算机视觉 ML 管道使用。此外,我们将介绍管道结构的高级概念:同时生成两个模型(例如,用于 TensorFlow Serving 和 TFLite 的部署),以及将人工审阅者加入管道工作流程。

正在进行的开发

在撰写本文时,我们介绍的一些概念仍在开发中,因此可能会有未来的更新。在制作本出版物的过程中,我们已经尽力更新了 TFX 功能的代码示例中的更改,并且所有示例均适用于 TFX 0.22。TFX API 的更新可以在TFX 文档中找到。

高级管道概念

在这一节中,我们将讨论三个额外的概念,以推进您的管道设置。到目前为止,我们讨论的所有管道概念都包括具有一个入口点和一个出口点的线性图。在第一章中,我们讨论了有向无环图的基础知识。只要我们的管道图是有向的并且不创建任何循环连接,我们就可以在设置上进行创意发挥。在接下来的几节中,我们将强调一些增加管道生产力的概念:

  • 同时训练多个模型

  • 导出模型以进行移动部署

  • 模型训练的温启动

同时训练多个模型

正如我们之前提到的,您可以同时训练多个模型。从同一个管道训练多个模型的常见用例是,当您想训练不同类型的模型(例如,更简单的模型)时,但您希望确保训练过的模型正在使用完全相同的转换数据和完全相同的转换图。图 10-1 显示了这种设置的工作原理。

图 10-1。同时训练多个模型

您可以通过定义多个Trainer组件来组装这样的图,如下面的代码示例所示:

def``set_trainer``(``module_file``,``instance_name``,``train_steps``=``5000``,``eval_steps``=``100``):return``Trainer``(``module_file``=``module_file``,``custom_executor_spec``=``executor_spec``.``ExecutorClassSpec``(``GenericExecutor``),``examples``=``transform``.``outputs``[``'transformed_examples'``],``transform_graph``=``transform``.``outputs``[``'transform_graph'``],``schema``=``schema_gen``.``outputs``[``'schema'``],``train_args``=``trainer_pb2``.``TrainArgs``(``num_steps``=``train_steps``),``eval_args``=``trainer_pb2``.``EvalArgs``(``num_steps``=``eval_steps``),``instance_name``=``instance_name``)``prod_module_file``=``os``.``path``.``join``(``pipeline_dir``,``'prod_module.py'``)trial_module_file``=``os``.``path``.``join``(``pipeline_dir``,``'trial_module.py'``)``...``trainer_prod_model``=``set_trainer``(``module_file``,``'production_model'``)trainer_trial_model``=``set_trainer``(``trial_module_file``,``'trial_model'``,``train_steps``=``10000``,``eval_steps``=``500``)``...

有效实例化Trainer的函数。

加载每个Trainer的模块。

为每个图形分支实例化一个Trainer组件。

在这一步,我们基本上将图形分支成尽可能多的训练分支。每个Trainer组件都从摄入、模式和Transform组件的相同输入中获取。组件之间的关键区别在于,每个组件可以运行不同的训练设置,这些设置在各个训练模块文件中定义。我们还将训练和评估步骤的参数添加为函数的参数。这允许我们使用相同的训练设置(即相同的模块文件)训练两个模型,但我们可以根据不同的训练运行比较这些模型。

每个实例化的训练组件都需要由其自身的Evaluator消耗,如下面的代码示例所示。之后,模型可以由其自身的Pusher组件推送:

evaluator_prod_model``=``Evaluator``(``examples``=``example_gen``.``outputs``[``'examples'``],``model``=``trainer_prod_model``.``outputs``[``'model'``],``eval_config``=``eval_config_prod_model``,``instance_name``=``'production_model'``)``evaluator_trial_model``=``Evaluator``(``examples``=``example_gen``.``outputs``[``'examples'``],``model``=``trainer_trial_model``.``outputs``[``'model'``],``eval_config``=``eval_config_trial_model``,``instance_name``=``'trial_model'``)``...

正如我们在本节中所见,我们可以使用 TFX 组装相当复杂的管道场景。在接下来的部分中,我们将讨论如何修改训练设置以将模型导出为适用于 TFLite 的移动部署。

导出 TFLite 模型

移动部署已成为机器学习模型的一个日益重要的平台。机器学习流水线可以帮助实现移动部署的一致性导出。与部署到模型服务器(如 TensorFlow Serving,在第八章中讨论)相比,移动部署需要的变更非常少。这有助于保持移动端和服务器端模型的一致更新,并帮助模型的使用者在不同设备上获得一致的体验。

TFLITE 限制

由于移动和边缘设备的硬件限制,TFLite 不支持所有 TensorFlow 操作。因此,并非每个模型都可以转换为 TFLite 兼容的模型。有关支持哪些 TensorFlow 操作的更多信息,请访问TFLite 网站

在 TensorFlow 生态系统中,TFLite 是移动部署的解决方案。TFLite 是 TensorFlow 的一个版本,可在边缘或移动设备上运行。图 10-2 展示了流水线如何包括两个训练分支。

图 10-2. 在移动应用程序中部署模型

我们可以使用前面讨论过的分支策略,并修改模块文件的run_fn函数,将保存的模型重写为 TFLite 兼容格式。

示例 10-1 展示了我们需要添加到run_fn函数的附加功能。

示例 10-1. TFX 重写器示例

from``tfx.components.trainer.executor``import``TrainerFnArgs``from``tfx.components.trainer.rewriting``import``converters``from``tfx.components.trainer.rewriting``import``rewriter``from``tfx.components.trainer.rewriting``import``rewriter_factory``def``run_fn``(``fn_args``:``TrainerFnArgs``):``...``temp_saving_model_dir``=``os``.``path``.``join``(``fn_args``.``serving_model_dir``,``'temp'``)``model``.``save``(``temp_saving_model_dir``,``save_format``=``'tf'``,``signatures``=``signatures``)tfrw``=``rewriter_factory``.``create_rewriter``(``rewriter_factory``.``TFLITE_REWRITER``,``name``=``'tflite_rewriter'``,``enable_experimental_new_converter``=``True``)converters``.``rewrite_saved_model``(``temp_saving_model_dir``,fn_args``.``serving_model_dir``,``tfrw``,``rewriter``.``ModelType``.``TFLITE_MODEL``)``tf``.``io``.``gfile``.``rmtree``(``temp_saving_model_dir``)

将模型导出为保存的模型

实例化 TFLite 重写器。

将模型转换为 TFLite 格式。

转换完成后删除保存的模型。

在训练后不是导出保存模型,而是将保存模型转换为符合 TFLite 的模型,并在导出后删除保存模型。然后我们的Trainer组件会将 TFLite 模型导出并注册到元数据存储中。下游组件如EvaluatorPusher可以消耗符合 TFLite 标准的模型。以下示例展示了我们如何评估 TFLite 模型,这对于检测模型优化(例如量化)是否导致模型性能下降非常有帮助:

eval_config``=``tfma``.``EvalConfig``(``model_specs``=``[``tfma``.``ModelSpec``(``label_key``=``'my_label'``,``model_type``=``tfma``.``TF_LITE``)],``...``)``evaluator``=``Evaluator``(``examples``=``example_gen``.``outputs``[``'examples'``],``model``=``trainer_mobile_model``.``outputs``[``'model'``],``eval_config``=``eval_config``,``instance_name``=``'tflite_model'``)

通过这种管道设置,我们现在可以自动为移动部署生成模型,并将其推送到模型部署的工件存储中。例如,Pusher组件可以将生成的 TFLite 模型传送到云存储桶,移动开发人员可以从中获取模型,并在 iOS 或 Android 移动应用中使用Google’s ML Kit部署。

将模型转换为 TensorFlow.js

自 TFX 版本 0.22 开始,rewriter_factory提供了一个额外的功能:将现有的 TensorFlow 模型转换为 TensorFlow.js 模型。此转换允许将模型部署到 Web 浏览器和 Node.js 运行环境中。您可以通过在示例 10-1 中将rewriter_factory名称替换为rewriter_factory.TFJS_REWRITER并将rewriter.ModelType设置为rewriter.ModelType.TFJS_MODEL来使用此新功能。

模型温启动训练

在某些情况下,我们可能不希望从头开始训练模型。温启动是从先前训练运行的检查点开始模型训练的过程,特别是当模型庞大且训练时间长时特别有用。在欧洲数据保护条例(GDPR)下,用户可以随时撤销其数据使用同意,这时温启动训练可以只删除属于该特定用户的数据并对模型进行微调,而不需要从头开始训练。

在 TFX 管道中,温启动训练需要我们在 第七章 中介绍的Resolver组件。Resolver会获取最新训练模型的详细信息,并将其传递给Trainer组件:

latest_model_resolver``=``ResolverNode``(``instance_name``=``'latest_model_resolver'``,``resolver_class``=``latest_artifacts_resolver``.``LatestArtifactsResolver``,``latest_model``=``Channel``(``type``=``Model``))

然后将最新模型传递给 Trainer,使用 base_model 参数:

trainer``=``Trainer``(``module_file``=``trainer_file``,``transformed_examples``=``transform``.``outputs``[``'transformed_examples'``],``custom_executor_spec``=``executor_spec``.``ExecutorClassSpec``(``GenericExecutor``),``schema``=``schema_gen``.``outputs``[``'schema'``],``base_model``=``latest_model_resolver``.``outputs``[``'latest_model'``],``transform_graph``=``transform``.``outputs``[``'transform_graph'``],``train_args``=``trainer_pb2``.``TrainArgs``(``num_steps``=``TRAINING_STEPS``),``eval_args``=``trainer_pb2``.``EvalArgs``(``num_steps``=``EVALUATION_STEPS``))

管道然后按照正常流程继续。接下来,我们想介绍另一个可添加到我们管道中的有用功能。

人在回环

作为高级 TFX 概念的一部分,我们希望突出一个实验性组件,可以提升您的管道设置。到目前为止,我们讨论的所有管道都是从头到尾自动运行的,它们可能会自动部署您的机器学习模型。一些 TFX 用户对完全自动化的设置表示担忧,因为他们希望在自动模型分析后由人类进行审查。这可能是为了检查您训练的模型或增强自动管道设置的信心。

在本节中,我们将讨论人在回环组件的功能。在 第七章 中,我们讨论了一旦模型通过验证步骤,它就被“祝福”。下游的 Pusher 组件会监听此祝福信号,以确定是否推送模型。但是,如图 10-3 所示,这样的祝福也可以由人类生成。

图 10-3. 人在回环中

谷歌的 TFX 团队发布了一个 Slack 通知组件作为自定义组件的示例。我们在本节讨论的功能可以扩展,不仅限于 Slack 信使。

组件的功能非常简单。一旦由编排工具触发,它会向指定的 Slack 频道提交一条消息,附带最新导出模型的链接,并请求数据科学家进行审查(见图 10-4)。数据科学家现在可以使用 WIT 手动调查模型并审查在Evaluator步骤中未测试的边缘情况。

图 10-4. Slack 消息请求审查

数据科学家完成手动模型分析后,可以在 Slack 线程中回复他们的批准或拒绝。 TFX 组件监听 Slack 的响应,并将决策存储在元数据存储中。然后,下游组件可以使用此决策。它在模型的审计跟踪中被跟踪。 Figure 10-5 显示了来自 Kubeflow Pipeline 的血统浏览器的示例记录。 元数据存储跟踪数据科学家(即决策者)的“blessing”和时间戳(Slack 线程 ID 1584638332.0001 标识时间戳为 Unix epoch 格式的时间)。

Figure 10-5. Kubeflow Pipelines 中的审计跟踪

Slack 组件设置

要使 Slack 组件与您的 Slack 帐户通信,需要一个 Slack 机器人令牌。 您可以通过 Slack API 请求一个机器人令牌。 一旦您获得了令牌,按照以下 bash 命令设置管道环境中的环境变量来存储令牌字符串:

$ export SLACK_BOT_TOKEN``={``your_slack_bot_token``}

Slack 组件不是标准的 TFX 组件,因此需要单独安装。 您可以通过从 GitHub 克隆 TFX 存储库,然后单独安装组件来安装组件:

$ git clone https://github.com/tensorflow/tfx.git $ cd tfx/tfx/examples/custom_components/slack $ pip install -e .

一旦组件包在您的 Python 环境中安装,该组件可以在 Python 路径中找到并加载到您的 TFX 脚本中。 以下是 Python 代码的示例。 还请记得在运行 TFX 管道的环境中安装 Slack 组件。 例如,如果您使用 Kubeflow Pipelines 运行管道,则必须为管道组件创建一个自定义 Docker 映像,其中包含 Slack 组件的源代码(因为它不是标准 TFX 组件)。

如何使用 Slack 组件

安装后的 Slack 组件可以像任何其他 TFX 组件一样加载:

from``slack_component.component``import``SlackComponent``slack_validator``=``SlackComponent``(``model``=``trainer``.``outputs``[``'model'``],``model_blessing``=``model_validator``.``outputs``[``'blessing'``],``slack_token``=``os``.``environ``[``'SLACK_BOT_TOKEN'``],slack_channel_id``=``'my-channel-id'``,timeout_sec``=``3600``,``)

从您的环境加载 Slack 令牌。

指定消息应出现的频道。

当执行时,组件将发布一条消息,并等待最多一小时(由 timeout_sec 参数定义)的答复。 在此期间,数据科学家可以评估模型并回复他们的批准或拒绝。 下游组件(例如 Pusher 组件)可以从 Slack 组件消耗结果,如下面的代码示例所示:

pusher``= Pusher``(``model``=``trainer.outputs``[``'model'``]``, model_blessing``=``slack_validator.outputs``[``'slack_blessing'``]``, push_destination``=``pusher_pb2.PushDestination``(``filesystem``=``pusher_pb2.PushDestination.Filesystem``(``base_directory``=``serving_model_dir``)))

Slack 组件提供的模型祝福。

通过几个额外步骤,您可以丰富您的管道,通过管道本身触发的机器学习模型的人工审计。这为管道应用程序开放了更多的工作流(例如,审计数据集统计信息或审查数据漂移度量)。

SLACK API 标准

Slack 组件的实现依赖于实时消息传递(RTM)协议。该协议已被弃用,可能会被新的协议标准取代,这将影响组件的功能。

自定义 TFX 组件

在第二章中,我们讨论了 TFX 组件的架构,以及每个组件包含三个部分:驱动程序、执行器和发布者。在本节中,我们希望深入探讨如何构建自己的组件。首先,我们讨论如何从头开始编写组件,然后,我们将讨论如何重用现有组件并为您自己的用例进行定制。总体而言,更改现有组件的功能总是比从头开始编写组件更容易。

为了向您演示实现,正如我们在图 10-6 中看到的那样,我们将开发一个自定义组件,用于在管道中摄取 JPEG 图像及其标签。我们将从提供的文件夹加载所有图像,并根据文件名确定标签。在我们的示例中,我们想要训练一个机器学习模型来对猫和狗进行分类。我们的图像文件名携带图像的内容(例如,dog-1.jpeg),因此我们可以根据文件名本身确定标签。我们将加载每个图像,将其转换为tf.Example数据结构,并将所有样本一起保存为 TFRecord 文件,供下游组件消费。

图 10-6. 我们演示的自定义组件功能

自定义组件的用例

即使我们正在讨论作为自定义组件示例的摄取组件,您也不受架构限制。您的自定义组件可以应用于机器学习管道的任何位置。以下各节讨论的概念为您提供了最高的灵活性,以根据需要定制您的机器学习管道。一些使用自定义组件的想法包括:

  • 从您的自定义数据库中获取数据

  • 向数据科学团队发送生成数据统计信息的电子邮件

  • 如果导出了新模型,则通知 DevOps 团队

  • 启动 Docker 容器的导出后构建过程

  • 在您的机器学习审计跟踪中跟踪额外信息

我们不会单独描述如何构建这些组件,但如果这些想法中的一个对您有用,接下来的各节将提供构建自己组件的知识。

从头开始编写自定义组件

如果我们想要从头开始编写自定义组件,我们需要实现几个组件部分。首先,我们必须将组件的输入和输出定义为 ComponentSpec。然后,我们可以定义我们的组件执行器,它定义了如何处理输入数据和生成输出数据。如果组件需要的输入未在元数据存储中注册,我们需要编写自定义组件驱动程序。例如,当我们想要在组件中注册图像路径而工件以前未在元数据存储中注册时,就会发生这种情况。

我们自定义组件的部分见 Figure 10-7

Figure 10-7 中的步骤可能看起来很复杂,但我们将在接下来的各节中逐一讨论它们。

尝试重用组件

如果您考虑修改现有的 TFX 组件功能,请考虑重用现有的 TFX 组件并更改执行器,而不是从头开始,正如我们将在“重用现有组件”部分中讨论的那样。

组件规范

组件规范或ComponentSpec定义了组件之间如何通信。它们描述了每个组件的三个重要细节:组件输入、组件输出以及组件执行期间可能需要的组件参数。组件通过通道进行通信,这些通道包括输入和输出。正如我们将在以下示例中看到的那样,这些通道都是类型化的。组件输入定义了组件将接收的来自先前执行的组件或新工件(如文件路径)的工件。组件输出定义了将在元数据存储中注册的工件。

组件参数定义了执行所需但在元数据存储中不可用的选项。例如,Pusher 组件的 push_destinationTrainer 组件的 train_args。以下示例显示了我们图像摄入组件规范的定义:

from``tfx.types.component_spec``import``ChannelParameter``from``tfx.types.component_spec``import``ExecutionParameter``from``tfx.types``import``standard_artifacts``class``ImageIngestComponentSpec``(``types``.``ComponentSpec``):``"""用于自定义 TFX 图像摄入组件的 ComponentSpec。"""``PARAMETERS``=``{``'name'``:``ExecutionParameter``(``type``=``Text``),``}``INPUTS``=``{``'input'``:``ChannelParameter``(``type``=``standard_artifacts``.``ExternalArtifact``),}``OUTPUTS``=``{``'examples'``:``ChannelParameter``(``type``=``standard_artifacts``.``Examples``),}

使用ExternalArtifact允许新的输入路径

导出Examples

在我们的示例实现中,通过输入参数input摄取了一个输入路径的ImageIngestComponentSpec。 转换后的图像生成的 TFRecord 文件将通过examples参数传递给下游组件的路径存储。 此外,我们为组件定义了一个名为name的参数。

组件通道

在我们的示例ComponentSpec中,我们介绍了两种组件通道类型:ExternalArtifactExamples。 这是一种用于摄取组件的特定模式,因为它们通常是管道中的第一个组件,并且没有上游组件可供我们接收已处理的Examples。 如果您在管道中的后续开发组件,可能希望摄取Examples。 因此,通道类型需要是standard_arti⁠facts.Examples。 但我们不仅限于两种类型。 TFX 提供了各种类型。 以下是可用类型的简要列表:

  • ExampleStatistics

  • Model

  • ModelBlessing

  • Bytes

  • String

  • Integer

  • Float

  • HyperParameters

现在我们已经设置了我们的ComponentSpec,让我们来看看组件执行器。

组件执行器

组件执行器定义了组件内部的流程,包括如何使用输入生成组件输出。 即使我们将从头开始编写此基本组件,我们也可以依赖于 TFX 类来继承函数模式。 作为Executor对象的一部分,TFX 将寻找一个名为Do的函数,用于描述我们组件的执行细节。 我们将在这个函数中实现我们组件的功能:

from``tfx.components.base``import``base_executor``class``Executor``(``base_executor``.``BaseExecutor``):``"""用于图像摄取组件的执行器。"""``def``Do``(``self``,``input_dict``:``Dict``[``Text``,``List``[``types``.``Artifact``]],``output_dict``:``Dict``[``Text``,``List``[``types``.``Artifact``]],``exec_properties``:``Dict``[``Text``,``Any``])``->``None``:``...

代码片段显示了我们的ExecutorDo函数期望三个参数:input_dictoutput_dictexec_properties。 这些 Python 字典包含我们传递给组件和从组件传递的工件引用以及执行属性。

工件包含引用

通过input_dictoutput_dict提供的信息包含在元数据存储中。 这些是工件的引用,而不是底层数据本身。 例如,我们的input_dict字典将包含一个协议缓冲区,其中包含文件位置信息,而不是数据。 这使我们能够使用诸如 Apache Beam 之类的程序有效地处理数据。

要详细介绍执行器的工作Do方法的基本实现,我们将重复使用我们在“计算机视觉问题的图像数据”中讨论的实现,将图像转换为 TFRecord 数据结构。有关转换过程和 TFRecord 数据结构的详细说明可以在那里找到。这段代码应该看起来很熟悉:

def``convert_image_to_TFExample``(``image_filename``,``tf_writer``,``input_base_uri``):``image_path``=``os``.``path``.``join``(``input_base_uri``,``image_filename``)lowered_filename``=``image_path``.``lower``()if``"dog"``in``lowered_filename``:``label``=``0``elif``"cat"``in``lowered_filename``:``label``=``1``else``:``raise``NotImplementedError``(``"Found unknown image"``)``raw_file``=``tf``.``io``.``read_file``(``image_path``)example``=``tf``.``train``.``Example``(``features``=``tf``.``train``.``Features``(``feature``=``{'image_raw'``:``_bytes_feature``(``raw_file``.``numpy``()),``'label'``:``_int64_feature``(``label``)``}))``writer``.``write``(``example``.``SerializeToString``())

组装完整的图像路径。

根据文件路径确定每个图像的标签。

从磁盘读取图像。

创建TensorFlow Example数据结构。

tf.Example写入 TFRecord 文件。

现在我们已经完成了读取图像文件并将其存储在包含 TFRecord 数据结构文件中的通用函数,我们现在可以专注于特定于自定义组件的代码。

我们希望我们非常基础的组件能够加载我们的图像,将它们转换为tf.Examples,并返回两组图像用于训练和评估。为了我们示例的简单性,我们将硬编码评估示例的数量。在生产级组件中,这个参数应该通过ComponentSpecs中的执行参数动态设置。我们组件的输入将是包含所有图像的文件夹的路径。我们组件的输出将是我们将存储训练和评估数据集的路径。该路径将包含两个子目录(traineval),这些子目录包含 TFRecord 文件:

class``ImageIngestExecutor``(``base_executor``.``BaseExecutor``):``def``Do``(``self``,``input_dict``:``Dict``[``Text``,``List``[``types``.``Artifact``]],``output_dict``:``Dict``[``Text``,``List``[``types``.``Artifact``]],``exec_properties``:``Dict``[``Text``,``Any``])``->``None``:``self``.``_log_startup``(``input_dict``,``output_dict``,``exec_properties``)input_base_uri``=``artifact_utils``.``get_single_uri``(``input_dict``[``'input'``])image_files``=``tf``.``io``.``gfile``.``listdir``(``input_base_uri``)random``.``shuffle``(``image_files``)``splits``=``get_splits``(``images``)``for``split_name``,``images``in``splits``:``output_dir``=``artifact_utils``.``get_split_uri``(``output_dict``[``'examples'``],``split_name``)tfrecord_filename``=``os``.``path``.``join``(``output_dir``,``'images.tfrecord'``)``options``=``tf``.``io``.``TFRecordOptions``(``compression_type``=``None``)``writer``=``tf``.``io``.``TFRecordWriter``(``tfrecord_filename``,``options``=``options``)for``image``in``images``:``convert_image_to_TFExample``(``image``,``tf_writer``,``input_base_uri``)

记录参数。

从 artifact 中获取文件夹路径。

获取所有文件名。

设置分割统一资源标识符(URI)。

创建带有选项的 TFRecord 写入器实例。

将图像写入包含 TFRecord 数据结构的文件中。

我们的基本Do方法接收input_dictoutput_dictexec_properties作为方法的参数。第一个参数包含作为 Python 字典存储在元数据存储中的 artifact 引用,第二个参数接收我们希望从组件中导出的引用,最后一个方法参数包含额外的执行参数,例如我们的情况下组件名称。TFX 提供了非常有用的artifact_utils函数,让我们能够处理我们的 artifact 信息。例如,我们可以使用以下代码提取数据输入路径:

artifact_utils``.``get_single_uri``(``input_dict``[``'input'``])

我们还可以根据分割名称设置输出路径的名称:

artifact_utils``.``get_split_uri``(``output_dict``[``'examples'``],``split_name``)

上述函数提出了一个很好的观点。为了示例的简单性,我们忽略了动态设置数据分割的选项,正如我们在第三章中讨论的那样。实际上,在我们的示例中,我们正在硬编码分割名称和数量:

对于生产中的组件来说,这样的功能并不理想,但完整的实现将超出本章的范围。在接下来的部分中,我们将讨论如何重用现有组件功能并简化您的实现。本节中的组件将具有与我们在第三章中讨论的相同功能。

组件驱动器

如果我们使用到目前为止定义的执行器运行组件,我们将遇到一个 TFX 错误,即输入未在元数据存储中注册,并且我们需要在运行自定义组件之前执行前一个组件。但在我们的情况下,我们没有上游组件,因为我们正在将数据摄取到我们的管道中。数据摄取步骤是每个管道的开始。那么问题出在哪里呢?

正如我们之前讨论的,TFX 中的组件通过元数据存储彼此通信,组件期望输入工件已经在元数据存储中注册。在我们的情况下,我们希望从磁盘摄取数据,在我们的管道中第一次读取数据;因此,数据不是从不同的组件传递下来,我们需要在元数据存储中注册数据源。

自定义驱动器是罕见的。

你很少需要实现自定义驱动器。如果你可以重用现有 TFX 组件的输入/输出架构,或者如果输入已经在元数据存储中注册了,你就不需要编写自定义驱动器,可以跳过这一步。

类似于我们的自定义执行器,我们可以重用 TFX 提供的 BaseDriver 类来编写自定义驱动器。我们需要重写组件的标准行为,可以通过重写 BaseDriverresolve_input_artifacts 方法来实现。一个最简驱动器将注册我们的输入,这是直截了当的。我们需要解包通道以获取 input_dict。通过遍历 input_dict 的所有值,我们可以访问每个输入列表。再次遍历每个列表,我们可以获取每个输入,然后通过将其传递给函数 publish_artifacts 在元数据存储中注册它。publish_artifacts 将调用元数据存储,发布工件,并设置工件的状态为准备发布:

class``ImageIngestDriver``(``base_driver``.``BaseDriver``):``"""ImageIngest 的自定义驱动程序。"""``def``resolve_input_artifacts``(``self``,``input_channels``:``Dict``[``Text``,``types``.``Channel``],``exec_properties``:``Dict``[``Text``,``Any``],``driver_args``:``data_types``.``DriverArgs``,``pipeline_info``:``data_types``.``PipelineInfo``)``->``Dict``[``Text``,``List``[``types``.``Artifact``]]:``"""重写 BaseDriver.resolve_input_artifacts()。"""``del``driver_argsdel``pipeline_info``input_dict``=``channel_utils``.``unwrap_channel_dict``(``input_channels``)for``input_list``in``input_dict``.``values``():``for``single_input``in``input_list``:``self``.``_metadata_handler``.``publish_artifacts``([``single_input``])absl``.``logging``.``debug``(``"已注册输入:{}"``.``format``(``single_input``))``absl``.``logging``.``debug``(``"single_input.mlmd_artifact "``"{}"``.``format``(``single_input``.``mlmd_artifact``))return``input_dict

删除未使用的参数。

解包通道以获取输入字典。

发布该工件。

打印工件信息。

在循环每个输入时,我们可以打印额外的信息:

print``(``"已注册新输入:{}"``.``format``(``single_input``))``print``(``"工件 URI:{}"``.``format``(``single_input``.``uri``))``print``(``"MLMD 工件信息:{}"``.``format``(``single_input``.``mlmd_artifact``))

自定义驱动程序现已就位,我们需要组装我们的组件。

组装自定义组件

ImageIngestComponentSpec定义完成,ImageIngestExecutor已完成,ImageIngestDriver已设置好,现在让我们在ImageIngestComponent中将它们统一起来。例如,我们可以将该组件加载到一个训练图像分类模型的流水线中。

要定义实际的组件,我们需要定义规范、执行程序和驱动程序类。我们可以通过设置SPEC_CLASSEXECUTOR_SPECDRIVER_CLASS来完成这一点,如下例所示。作为最后一步,我们需要用组件的参数(例如输入和输出示例以及提供的名称)实例化我们的ComponentSpecs,并将其传递给实例化的ImageIngestComponent

如果我们没有提供输出工件的情况极少见,我们可以将我们的默认输出工件设置为tf.Example类型,定义我们硬编码的分割名称,并将其设置为一个通道:

from``tfx.components.base``import``base_component``from``tfx``import``types``from``tfx.types``import``channel_utils``class``ImageIngestComponent``(``base_component``.``BaseComponent``):``"""自定义 ImageIngestWorld 组件。"""``SPEC_CLASS``=``ImageIngestComponentSpec``EXECUTOR_SPEC``=``executor_spec``.``ExecutorClassSpec``(``ImageIngestExecutor``)``DRIVER_CLASS``=``ImageIngestDriver``def __init__``(``self``,``input``,``output_data``=``None``,``name``=``None``):``if``not``output_data``:``examples_artifact``=``standard_artifacts``.``Examples``()``examples_artifact``.``split_names``= \ artifact_utils``.``encode_split_names``([``'train'``,``'eval'``])``output_data``=``channel_utils``.``as_channel``([``examples_artifact``])``spec``=``ImageIngestComponentSpec``(``input``=``input``,``examples``=``output_data``,``name``=``name``)``super``(``ImageIngestComponent``,``self``)``.``__init__``(``spec``=``spec``)

通过组装我们的ImageIngestComponent,我们已经将基本自定义组件的各个部分联系在一起。在下一节中,我们将看看如何执行我们的基本组件。

使用我们的基本自定义组件

在实施完整的基本组件以摄入图像并将其转换为 TFRecord 文件之后,我们可以像管道中的任何其他组件一样使用该组件。以下代码示例展示了如何执行。请注意,它看起来与我们在第三章中讨论的其他摄入组件的设置完全相同。唯一的区别是,我们需要导入我们新创建的组件,然后运行初始化的组件:

import``os``from``tfx.utils.dsl_utils``import``external_input``from``tfx.orchestration.experimental.interactive.interactive_context``import \ InteractiveContext``from``image_ingestion_component.component``import``ImageIngestComponent``context``=``InteractiveContext``()``image_file_path``=``"/path/to/files"``examples``=``external_input``(``dataimage_file_path_root``)``example_gen``=``ImageIngestComponent``(``input``=``examples``,``name``=``u``'ImageIngestComponent'``)``context``.``run``(``example_gen``)

组件的输出可以被下游组件(例如StatisticsGen)所使用:

from``tfx.components``import``StatisticsGen``statistics_gen``=``StatisticsGen``(``examples``=``example_gen``.``outputs``[``'examples'``])``context``.``run``(``statistics_gen``)``context``.``show``(``statistics_gen``.``outputs``[``'statistics'``])

非常基础的实现

我们要提醒您,所讨论的实现仅提供基本功能,不适合生产环境。有关缺失功能的详细信息,请参阅下一节。要获取产品级实现,请参阅我们在接下来的章节中更新的组件实现。

实施审查

在前面的章节中,我们介绍了基本组件的实现。虽然该组件功能正常,但缺少我们在 第三章 中讨论过的一些关键功能(例如,动态拆分名称或拆分比例)—我们期望从我们的摄取组件中获得这样的功能。基本实现还需要大量样板代码(例如,组件驱动的设置)。需要有效且可扩展地处理图像的摄取。我们可以通过 TFX 组件底层的 Apache Beam 使用来实现这种高效的数据摄取。

在接下来的章节中,我们将讨论如何简化实现并采纳我们在 第三章 中讨论过的模式—例如,从 Presto 数据库中摄取数据。通过重用诸如组件驱动之类的常见功能,我们可以加快实施速度并减少代码错误。

重用现有组件

不必从头开始编写 TFX 组件,我们可以继承现有组件,并通过覆盖执行器功能来进行定制。如 图 10-8 所示,这通常是在组件重用现有组件架构时的首选方法。对于我们的演示组件而言,其架构等同于基于文件的摄取组件(例如,CsvExampleGen)。这类组件接收一个目录路径作为组件输入,从提供的目录加载数据,将数据转换为 tf.Examples,并将数据结构作为 TFRecord 文件从组件输出返回。

图 10-8. 扩展现有组件

正如我们在 第三章 中讨论过的那样,TFX 提供了 FileBasedExampleGen 来实现这一目的。由于我们将重用现有组件,类似于我们的 Avro 和 Parquet 示例,我们只需专注于开发我们的自定义执行器,并使其与我们之前的基本组件更加灵活。通过重用现有的代码基础设施,我们还可以依赖现有的 Apache Beam 实现。

通过重用现有组件架构将数据摄取到我们的流水线中,我们还可以利用 Apache Beam 有效地重用设置来进行数据摄取。TFX 和 Apache Beam 提供了类(例如,GetInputSourceToExamplePTransform)和函数装饰器(例如,@beam.ptransform_fn)来通过 Apache Beam 管道进行数据摄取。在我们的示例中,我们使用了函数装饰器 @beam.ptransform_fn,它允许我们定义 Apache Beam 转换(PTransform)。该装饰器接受一个 Apache Beam 管道,运行给定的转换(例如,在我们的情况下,加载图像并将其转换为 tf.Examples),并返回具有转换结果的 Apache Beam PCollection

转换功能由一个与我们先前实现非常相似的函数处理。更新后的转换实现有一个主要区别:我们不需要实例化和使用 TFRecord 写入器;相反,我们可以完全专注于加载图像并将它们转换为tf.Examples。我们不需要实现任何将tf.Examples写入 TFRecord 数据结构的函数,因为我们在先前的实现中已经做过了。相反,我们返回生成的tf.Examples,让底层的 TFX/Apache Beam 代码处理 TFRecord 文件的写入。以下代码示例展示了更新后的转换函数:

def``convert_image_to_TFExample``(``image_path``)):# 根据文件路径确定每个图像的标签。``lowered_filename``=``image_path``.``lower``()``print``(``lowered_filename``)``if``"dog"``in``lowered_filename``:``label``=``0``elif``"cat"``in``lowered_filename``:``label``=``1``else``:``raise``NotImplementedError``(``"Found unknown image"``)``# 读取图像。``raw_file``=``tf``.``io``.``read_file``(``image_path``)``# 创建 TensorFlow Example 数据结构。``example``=``tf``.``train``.``Example``(``features``=``tf``.``train``.``Features``(``feature``=``{``'image_raw'``:``_bytes_feature``(``raw_file``.``numpy``()),``'label'``:``_int64_feature``(``label``)``}))``return``example

只需要文件路径。

该函数返回示例而不是将其写入磁盘。

使用更新后的转换函数,我们现在可以专注于实现核心执行器功能。由于我们正在定制现有的组件架构,所以可以使用与我们在第三章讨论过的相同参数,例如分割模式。在以下代码示例中的image_to_example函数中,我们接受四个输入参数:一个 Apache Beam 管道对象,一个带有工件信息的input_dict,一个包含执行属性的字典以及摄取的分割模式。在函数中,我们生成给定目录中可用文件的列表,并将图像列表传递给 Apache Beam 管道,以将摄取目录中找到的每个图像转换为tf.Examples

@beam.ptransform_fn``def``image_to_example``(``pipeline``:``beam``.``Pipeline``,``input_dict``:``Dict``[``Text``,``List``[``types``.``Artifact``]],``exec_properties``:``Dict``[``Text``,``Any``],``split_pattern``:``Text``)``->``beam``.``pvalue``.``PCollection``:``input_base_uri``=``artifact_utils``.``get_single_uri``(``input_dict``[``'input'``])``image_pattern``=``os``.``path``.``join``(``input_base_uri``,``split_pattern``)``absl``.``logging``.``info``(``"Processing input image data {} "``"to tf.Example."``.``format``(``image_pattern``))``image_files``=``tf``.``io``.``gfile``.``glob``(``image_pattern``)if``not``image_files``:``raise``RuntimeError``(``"Split pattern {} did not match any valid path."``""``.``format``(``image_pattern``))``p_collection``=``(``pipeline``|``beam``.``Create``(``image_files``)|``'ConvertImagesToTFRecords'``>>``beam``.``Map``(``lambda``image``:``convert_image_to_TFExample``(``image``)))``return``p_collection

生成摄入路径中存在的文件列表。

将列表转换为 Beam 的 PCollection

将转换应用于每个图像。

我们自定义执行器的最后一步是用我们的 image_to_example 覆盖 BaseExampleGenExecutorGetInputSourceToExamplePTransform

class``ImageExampleGenExecutor``(``BaseExampleGenExecutor``):``@beam.ptransform_fn``def``image_to_example``(``...``):``...``def``GetInputSourceToExamplePTransform``(``self``)``->``beam``.``PTransform``:``return``image_to_example

我们的自定义图像摄入组件现在已经完成!

使用我们的自定义执行器

由于我们正在重用摄入组件并交换处理执行器,现在我们可以按照我们在 第三章 中讨论的相同模式进行操作,并指定一个 custom_executor_spec。通过重用 FileBasedExampleGen 组件并覆盖 executor,我们可以使用我们在 第三章 中讨论过的摄入组件的全部功能,比如定义输入分割模式或输出训练/评估分割。以下代码片段给出了使用我们自定义组件的完整示例:

from``tfx.components``import``FileBasedExampleGen``from``tfx.utils.dsl_utils``import``external_input``from``image_ingestion_component.executor``import``ImageExampleGenExecutor``input_config``=``example_gen_pb2``.``Input``(``splits``=``[``example_gen_pb2``.``Input``.``Split``(``name``=``'images'``,``pattern``=``'sub-directory/if/needed/*.jpg'``),``])``output``=``example_gen_pb2``.``Output``(``split_config``=``example_gen_pb2``.``SplitConfig``(``splits``=``[``example_gen_pb2``.``SplitConfig``.``Split``(``name``=``'train'``,``hash_buckets``=``4``),``example_gen_pb2``.``SplitConfig``.``Split``(``name``=``'eval'``,``hash_buckets``=``1``)``])``)``example_gen``=``FileBasedExampleGen``(``input``=``external_input``(``"/path/to/images/"``),``input_config``=``input_config``,``output_config``=``output``,``custom_executor_spec``=``executor_spec``.``ExecutorClassSpec``(``ImageExampleGenExecutor``)``)

正如我们在本节讨论的那样,扩展组件执行器总是比从头开始编写自定义组件更简单且更快。因此,如果您能够重用现有的组件架构,我们建议使用这个过程。

摘要

在本章中,我们扩展了前几章的 TFX 概念。我们详细讨论了如何编写自定义组件。编写自定义组件使我们能够扩展现有的 TFX 组件,并根据我们流水线的需求进行定制。自定义组件允许我们将更多步骤集成到我们的机器学习流水线中。通过向我们的流水线添加更多组件,我们可以保证所有流水线生成的模型经过相同的步骤。由于自定义组件的实施可能较为复杂,我们回顾了从头开始实现组件的基本实现,并突出了通过继承现有组件功能来实现新组件执行器的实现。

我们还讨论了用于训练设置的高级设置,例如分支流水线图,以从同一流水线执行中生成多个模型。此功能可用于生成适用于移动应用程序部署的 TFLite 模型。我们还讨论了温暖启动训练过程,以持续训练机器学习模型。温暖启动模型训练是缩短持续训练模型训练步骤的绝佳方法。

我们介绍了在机器学习流水线设置中引入人类参与的概念,并讨论了如何实现实验组件。人类参与的概念是在部署模型之前将专家审查作为必需的流水线步骤添加的一种方式。我们相信,完全自动化的组件与少数数据科学家的关键审查的结合将支持机器学习流水线的采纳。

在接下来的两章中,我们将看看如何在您选择的编排环境中运行我们的 TFX 流水线。

第十一章:管道第一部分:Apache Beam 和 Apache Airflow

在前几章中,我们介绍了使用 TFX 构建机器学习管道所需的所有组件。在本章中,我们将把所有组件整合起来,展示如何使用两个编排工具(Apache Beam 和 Apache Airflow)运行完整的管道。在第十二章中,我们还将展示如何使用 Kubeflow Pipelines 运行管道。所有这些工具遵循类似的原则,但我们将展示细节上的差异,并为每个工具提供示例代码。

正如我们在第一章中讨论的那样,管道编排工具对于抽象化我们需要编写的胶水代码以自动化机器学习管道非常重要。如图 11-1 所示,管道编排工具位于我们在前几章已经提到的组件之下。如果没有这些编排工具之一,我们将需要编写代码来检查一个组件何时完成,启动下一个组件,安排管道的运行等等。幸运的是,所有这些代码已经以这些编排工具的形式存在!

图 11-1. 管道编排工具

我们将从讨论不同工具的使用案例开始本章。然后,我们将逐步介绍一些常见代码,这些代码是从交互式管道转移到可以由这些工具编排的管道所必需的。Apache Beam 和 Apache Airflow 的设置比 Kubeflow Pipelines 简单,因此我们将在本章中讨论它们,然后再转向功能更强大的 Kubeflow Pipelines,在第十二章中详细讨论。

选择哪种编排工具?

在本章和第十二章中,我们将讨论三种你可以使用的管道编排工具:Apache Beam、Apache Airflow 和 Kubeflow Pipelines。你需要选择其中一种工具来运行每个管道。在深入研究如何使用所有这些工具之前,我们将描述每个工具的一些优缺点,这将帮助你决定哪种工具最适合你的需求。

Apache Beam

如果你正在使用 TFX 进行管道任务,那么你已经安装了 Apache Beam。因此,如果你正在寻找一个最小的安装选项,重用 Beam 来编排是一个合乎逻辑的选择。这样设置非常简单,而且还允许你使用任何你可能已经熟悉的现有分布式数据处理基础设施(例如 Google Cloud Dataflow)。在转移到 Airflow 或 Kubeflow Pipelines 之前,你也可以使用 Beam 作为中间步骤来确保你的管道正确运行。

然而,Apache Beam 缺少多种用于调度模型更新或监视管道作业过程的工具。这就是 Apache Airflow 和 Kubeflow Pipelines 脱颖而出的地方。

Apache Airflow

Apache Airflow 通常已经在公司中用于数据加载任务。将现有的 Apache Airflow 设置扩展到运行你的管道意味着你不需要学习 Kubeflow 等新工具。

如果你将 Apache Airflow 与像 PostgreSQL 这样的生产就绪数据库结合使用,你可以利用执行部分管道的优势。如果一个耗时的管道失败,你可以节省大量时间,而且你想避免重新运行所有之前的管道步骤。

Kubeflow Pipelines

如果你已经有了 Kubernetes 的经验并且可以访问 Kubernetes 集群,考虑使用 Kubeflow Pipelines 是有道理的。虽然设置 Kubeflow 比 Airflow 安装更复杂,但它开启了各种新机会,包括查看 TFDV 和 TFMA 的可视化、模型谱系和艺术品集合。

Kubernetes 也是一个优秀的基础设施平台,用于部署机器学习模型。通过 Kubernetes 工具 Istio 进行推理路由目前是机器学习基础设施领域的最新技术。

你可以使用各种云提供商设置 Kubernetes 集群,因此你不限于单一供应商。Kubeflow Pipelines 还允许你利用由云提供商提供的最先进的训练硬件。你可以高效地运行你的管道,并在你的集群节点上进行缩放。

AI 平台上的 Kubeflow Pipelines

你也可以在 Google 的 AI 平台上运行 Kubeflow Pipelines,这是 GCP 的一部分。这为你处理了大部分基础设施,并且使得从 Google Cloud 存储桶加载数据变得轻松。此外,Google 的 Dataflow 集成简化了管道的扩展。但是,这将使你锁定在一个单一的云提供商上。

如果你决定选择 Apache Beam 或 Airflow,本章包含了你将需要的信息。如果你选择 Kubeflow(通过 Kubernetes 或 Google Cloud 的 AI 平台),你只需要阅读本章的下一节。这将向你展示如何将你的交互式管道转换为脚本,然后你可以前往第十二章。

将你的交互式 TFX 管道转换为生产管道

到目前为止,我们的示例展示了如何在笔记本式环境或交互式环境中运行 TFX 管道的所有不同组件。要在笔记本中运行管道,每个组件需要在前一个组件完成后手动触发。为了自动化我们的管道,我们将需要编写一个 Python 脚本,该脚本将在没有我们任何输入的情况下运行所有这些组件。

幸运的是,我们已经有了这个脚本的所有部分。我们将总结到目前为止我们讨论过的所有管道组件:

ExampleGen

从我们希望使用的数据源摄取新数据(第三章)

StatisticsGen

计算新数据的摘要统计信息(第四章)

SchemaGen

定义模型期望的特征,以及它们的类型和范围(第四章)

ExampleValidator

检查数据是否符合模式,并标记任何异常(第四章)

Transform

将数据预处理为模型期望的正确数值表示(第五章)

Trainer

在新数据上训练模型(第六章)

Resolver

检查之前已经认证的模型是否存在,并返回以进行比较(第七章)

Evaluator

在评估数据集上评估模型的性能,并在其是前一个版本的改进时验证模型(第七章)

Pusher

如果模型通过验证步骤,则将模型推送到服务目录中(第七章)

完整的管道展示在 例子 11-1 中。

例子 11-1. 基本管道

import``tensorflow_model_analysis``as``tfma``from``tfx.components``import``(``CsvExampleGen``,``Evaluator``,``ExampleValidator``,``Pusher``,``ResolverNode``,``SchemaGen``,``StatisticsGen``,``Trainer``,``Transform``)``from``tfx.components.base``import``executor_spec``from``tfx.components.trainer.executor``import``GenericExecutor``from``tfx.dsl.experimental``import``latest_blessed_model_resolver``from``tfx.proto``import``pusher_pb2``,``trainer_pb2``from``tfx.types``import``Channel``from``tfx.types.standard_artifacts``import``Model``,``ModelBlessing``from``tfx.utils.dsl_utils``import``external_input``def``init_components``(``data_dir``,``module_file``,``serving_model_dir``,``training_steps``=``2000``,``eval_steps``=``200``):``examples``=``external_input``(``data_dir``)``example_gen``=``CsvExampleGen``(``...``)``statistics_gen``=``StatisticsGen``(``...``)``schema_gen``=``SchemaGen``(``...``)``example_validator``=``ExampleValidator``(``...``)``transform``=``Transform``(``...``)``trainer``=``Trainer``(``...``)``model_resolver``=``ResolverNode``(``...``)``eval_config``=``tfma``.``EvalConfig``(``...``)``evaluator``=``Evaluator``(``...``)``pusher``=``Pusher``(``...``)``components``=``[``example_gen``,``statistics_gen``,``schema_gen``,``example_validator``,``transform``,``trainer``,``model_resolver``,``evaluator``,``pusher``]``return``components

在我们的示例项目中,我们已将组件实例化从管道配置中分离,以便专注于不同编排器的管道设置。

init_components 函数实例化组件。除了训练步骤数和评估步骤数之外,还需要三个输入:

data_dir

训练/评估数据的路径。

module_file

TransformTrainer组件所需的 Python 模块。分别在第五章和第六章进行了描述。

serving_model_dir

导出模型应存储的路径。

除了我们将在第十二章中讨论的 Google Cloud 设置微调外,每个编排器平台的组件设置都将相同。因此,我们将在 Apache Beam、Apache Airflow 和 Kubeflow Pipelines 的不同示例设置中重用组件定义。如果您想要使用 Kubeflow Pipelines,您可能会发现 Beam 对于调试管道很有用。但如果您想直接进入 Kubeflow Pipelines,请转到下一章!

Beam 和 Airflow 的简单交互式管道转换

如果您希望使用 Apache Beam 或 Airflow 编排您的管道,您还可以通过以下步骤将笔记本转换为管道。对于您不希望导出的笔记本中的任何单元格,请在每个单元格的开头使用%%skip_for_export Jupyter 魔法命令。

首先,设置管道名称和编排工具:

runner_type``=``'beam'pipeline_name``=``'consumer_complaints_beam'

或者,airflow

然后,设置所有相关文件路径:

notebook_file``=``os``.``path``.``join``(``os``.``getcwd``(),``notebook_filename``)``# 管道输入``data_dir``=``os``.``path``.``join``(``pipeline_dir``,``'data'``)``module_file``=``os``.``path``.``join``(``pipeline_dir``,``'components'``,``'module.py'``)``requirement_file``=``os``.``path``.``join``(``pipeline_dir``,``'requirements.txt'``)``# 管道输出``output_base``=``os``.``path``.``join``(``pipeline_dir``,``'output'``,``pipeline_name``)``serving_model_dir``=``os``.``path``.``join``(``output_base``,``pipeline_name``)``pipeline_root``=``os``.``path``.``join``(``output_base``,``'pipeline_root'``)``metadata_path``=``os``.``path``.``join``(``pipeline_root``,``'metadata.sqlite'``)

接下来,列出您希望包含在管道中的组件:

components``=``[``example_gen``,``statistics_gen``,``schema_gen``,``example_validator``,``transform``,``trainer``,``evaluator``,``pusher``]

然后导出管道:

pipeline_export_file``=``'consumer_complaints_beam_export.py'``context``.``export_to_pipeline``(``notebook_file``path``=``_notebook_file``,``export_file``path``=``pipeline_export_file``,``runner_type``=``runner_type``)

此导出命令将生成一个脚本,可以使用 Beam 或 Airflow 运行,具体取决于您选择的runner_type

Apache Beam 简介

因为 Apache Beam 在许多 TFX 组件的后台运行,所以我们在第二章中引入了它。各种 TFX 组件(例如 TFDV 或 TensorFlow Transform)使用 Apache Beam 来抽象内部数据处理。但是许多相同的 Beam 功能也可以用来运行您的管道。在接下来的部分中,我们将展示如何使用 Beam 来编排我们的示例项目。

使用 Apache Beam 编排 TFX 管道

Apache Beam 已作为 TFX 的依赖项安装,这使得将其作为我们的管道编排工具非常简单。Beam 非常简单,并且不具有像图形可视化、计划执行等 Airflow 或 Kubeflow Pipelines 的所有功能。

Beam 也可以是调试机器学习管道的好方法。通过在管道调试期间使用 Beam,然后切换到 Airflow 或 Kubeflow Pipelines,您可以排除由更复杂的 Airflow 或 Kubeflow Pipelines 设置引起的管道错误的根本原因。

在本节中,我们将演示如何使用 Beam 设置和执行我们的示例 TFX 管道。我们在第二章中介绍了 Beam Pipeline函数。这是我们将与我们的 Example 11-1 脚本一起使用来运行管道的内容。我们将定义一个 Beam Pipeline,它接受 TFX 管道组件作为参数,并连接到持有 ML MetadataStore 的 SQLite 数据库:

import``absl``from``tfx.orchestration``import``metadata``,``pipeline``def``init_beam_pipeline``(``components``,``pipeline_root``,``direct_num_workers``):``absl``.``logging``.``info``(``"Pipeline root set to: {}"``.``format``(``pipeline_root``))``beam_arg``=````"--direct_num_workers={}"``.``format``(``direct_num_workers``),!["--requirements_file={}"``.``format``(``requirement_file``)``]``p``=``pipeline``.``Pipeline``(pipeline_name``=``pipeline_name``,``pipeline_root``=``pipeline_root``,``components``=``components``,``enable_cache``=``False``,metadata_connection_config``=``\ metadata``.``sqlite_metadata_connection_config``(``metadata_path``),``beam_pipeline_args``=``beam_arg``)``return``p

Beam allows you to specify the number of workers. A sensible default is half the number of CPUs (if there is more than one CPU).

在这里,您可以使用配置定义管道对象。

如果希望避免重新运行已完成的组件,我们可以将缓存设置为True。如果将此标志设置为False,每次运行管道时都会重新计算所有内容。

Beam 管道配置需要包括管道名称、管道目录根路径以及作为管道执行一部分的组件列表。

接下来,我们将按照 示例 11-1 初始化组件,初始化流水线如前所述,并使用 BeamDagRunner().run(pipeline) 运行流水线:

from``tfx.orchestration.beam.beam_dag_runner``import``BeamDagRunner``components``=``init_components``(``data_dir``,``module_file``,``serving_model_dir``,``training_steps``=``100``,``eval_steps``=``100``)``pipeline``=``init_beam_pipeline``(``components``,``pipeline_root``,``direct_num_workers``)``BeamDagRunner``()``.``run``(``pipeline``)

这是一个最小设置,您可以轻松集成到其余基础架构中,或使用 cron 作业进行调度。您还可以使用 Apache Flink 或 Spark 扩展此流水线。有关使用 Flink 的示例,请参见 此 TFX 示例 中的简要描述。

在接下来的部分,我们将继续使用 Apache Airflow,当我们用它来编排我们的流水线时,它提供了许多额外的功能。

Apache Airflow 简介

Airflow 是 Apache 的工作流自动化项目。该项目始于 2016 年,并自那时起引起了大公司和整个数据科学社区的广泛关注。2018 年 12 月,该项目从 Apache 孵化器“毕业”,成为自己的 Apache 项目

Apache Airflow 允许您通过用 Python 代码表示的 DAG 表示工作流任务。此外,Airflow 还允许您调度和监视工作流。这使它成为我们 TFX 流水线的理想编排工具。

在本节中,我们将介绍设置 Airflow 的基础知识。然后,我们将展示如何使用它来运行我们的示例项目。

安装和初始设置

Apache Airflow 的基本设置非常简单。如果您使用的是 Mac 或 Linux,可以使用以下命令定义 Airflow 数据的位置:

$ $ export AIRFLOW_HOME``=``~/airflow

定义好 Airflow 的主数据文件夹后,您可以安装 Airflow:

$ pip install apache-airflow

Airflow 可以安装多种依赖项。在撰写本文时,扩展列表包括 PostgreSQL 支持、Dask、Celery 和 Kubernetes。

您可以在 Airflow 文档 中找到完整的 Airflow 扩展列表以及安装方法。

现在安装了 Airflow,需要创建一个初始数据库,用于存储所有任务状态信息。Airflow 提供了一个命令来初始化 Airflow 数据库:

$ airflow initdb

如果您使用的是 Airflow 的默认设置,并且没有更改任何配置,则 Airflow 将实例化一个 SQLite 数据库。此设置适用于执行演示项目和运行较小的工作流程。如果您希望通过 Apache Airflow 扩展工作流程,请务必深入研究 文档

最小化的 Airflow 设置包括 Airflow 调度器,协调任务和任务依赖关系,以及 Web 服务器,提供 UI 来启动、停止和监视任务。

使用以下命令启动调度器:

$ airflow scheduler

在另一个终端窗口,使用以下命令启动 Airflow Web 服务器:

$ airflow webserver -p 8081

命令参数-p设置了你的网络浏览器访问 Airflow 接口的端口。一切正常后,请访问127.0.0.1:8081,你应该能看到 Figure 11-2 中展示的界面。

Figure 11-2. Apache Airflow UI

AIRFLOW 配置

通过更改 Airflow 配置中的相关参数,可以覆盖 Airflow 的默认设置。如果您将图定义存储在与~/airflow/dags不同的位置,可能需要通过定义管道图的新位置在~/airflow/airflow.cfg中覆盖默认配置。

基本 Airflow 示例

在 Airflow 安装完成后,让我们看看如何设置一个基本的 Airflow 管道。在本示例中,我们不包括任何 TFX 组件。

工作流管道被定义为 Python 脚本,并且 Airflow 期望 DAG 定义位于~/airflow/dags。一个基本的管道包括项目特定的 Airflow 配置、任务定义和任务依赖的定义。

项目特定配置

Airflow 提供了配置项目特定设置的选项,例如何时重试失败的工作流或在工作流失败时通知特定人员。配置选项列表非常广泛。我们建议您参考Airflow 文档获取更新的概述。

你的 Airflow 管道定义始于导入相关 Python 模块和项目配置:

from``airflow``import``DAG``from``datetime``import``datetime``,``timedelta``project_cfg``=``{'owner'``:``'airflow'``,``'email'``:``[``'``your-email@example.com``'``],``'email_on_failure'``:``True``,``'start_date'``:``datetime``(``2019``,``8``,``1``),``'retries'``:``1``,``'retry_delay'``:``timedelta``(``hours``=``1``),``}``dag``=``DAG``('basic_pipeline'``,``default_args``=``project_cfg``,``schedule_interval``=``timedelta``(``days``=``1``))

定义项目配置的位置。

DAG 对象将被 Airflow 拾取。

再次提醒,Airflow 提供了一系列配置选项来设置 DAG 对象。

任务定义

一旦设置了 DAG 对象,我们可以创建工作流任务。Airflow 提供任务运算符,可以在 Bash 或 Python 环境中执行任务。其他预定义的运算符允许您连接到云数据存储桶,如 GCP Storage 或 AWS S3。

任务定义的一个非常基本的示例如下所示:

`fromairflow.operators.python_operatorimportPythonOperatordefexample_task(_id,**kwargs):print("task {}".format(_id))return"completed task {}".format(_id)task_1=PythonOperator(task_id='task 1',provide_context=True,python_callable=example_task,op_kwargs={'_id':1},dag=dag,)task_2=PythonOperator(task_id='task 2',provide_context=True,python_callable=example_task,op_kwargs={'_id':2},dag=dag,)``

在 TFX 流水线中,您不需要定义这些任务,因为 TFX 库会为您处理。但这些示例将帮助您理解背后的运行原理。

任务依赖关系

在我们的机器学习流水线中,任务彼此依赖。例如,我们的模型训练任务要求在开始训练之前执行数据验证。Airflow 提供了多种选项来声明这些依赖关系。

假设我们的 task_2 依赖于 task_1。您可以如下定义任务依赖关系:

task_1``.``set_downstream``(``task_2``)

Airflow 也提供了一个 bit-shift 操作符来表示任务依赖关系:

task_1``>>``task_2``>>``task_X

在上述示例中,我们定义了一个任务链。如果前一个任务成功完成,每个任务将被执行。如果任务未能成功完成,依赖任务将不会执行,Airflow 会标记它们为跳过状态。

同样,在 TFX 流水线中,这将由 TFX 库处理。

将所有内容放在一起

在解释所有单独设置步骤之后,让我们把所有内容放在一起。在您的 AIRFLOW_HOME 路径下的 DAG 文件夹中,通常在 ~/airflow/dags,创建一个新文件 basic_pipeline.py

from``airflow``import``DAG``from``airflow.operators.python_operator``import``PythonOperator``from``datetime``import``datetime``,``timedelta``project_cfg``=``{``'owner'``:``'airflow'``,``'email'``:``[``'``your-email@example.com``'``],``'email_on_failure'``:``True``,``'start_date'``:``datetime``(``2020``,``5``,``13``),``'retries'``:``1``,``'retry_delay'``:``timedelta``(``hours``=``1``),``}``dag``=``DAG``(``'basic_pipeline'``,``default_args``=``project_cfg``,``schedule_interval``=``timedelta``(``days``=``1``))``def``example_task``(``_id``,``**``kwargs``):``print``(``"Task {}"``.``format``(``_id``))``return``"completed task {}"``.``format``(``_id``)``task_1``=``PythonOperator``(``task_id``=``'task_1'``,``provide_context``=``True``,``python_callable``=``example_task``,``op_kwargs``=``{``'_id'``:``1``},``dag``=``dag``,``)``task_2``=``PythonOperator``(``task_id``=``'task_2'``,``provide_context``=``True``,``python_callable``=``example_task``,``op_kwargs``=``{``'_id'``:``2``},``dag``=``dag``,``)``task_1``>>``task_2

您可以通过在终端中执行以下命令来测试流水线设置:

python ~/airflow/dags/basic_pipeline.py

我们的print语句将被打印到 Airflow 的日志文件中,而不是终端。您可以在以下位置找到日志文件:

~/airflow/logs/``你的流水线名称``/``任务名称``/``执行时间``/

如果我们想检查我们基本流水线的第一个任务的结果,我们必须查看日志文件:

$ cat ../logs/basic_pipeline/task_1/2019-09-07T19``\:``36``\:``18.027474+00``\:``00/1.log ... [``2019-09-07 19:36:25,165``]``{``logging_mixin.py:95``} INFO - Task 1[``2019-09-07 19:36:25,166``]``{``python_operator.py:114``} INFO - Done. Returned value was:     completed task 1 [``2019-09-07 19:36:26,112``]``{``logging_mixin.py:95``} INFO - [``2019-09-07 19:36:26,112``]{``local_task_job.py:105``} INFO - Task exited with return code 0

我们的打印语句

我们成功完成后的返回消息

要测试 Airflow 是否识别了新流水线,您可以执行:

$ airflow list_dags ------------------------------------------------------------------- DAGS ------------------------------------------------------------------- basic_pipeline

这显示了流水线已成功识别。

现在您已经了解了 Airflow 流水线背后的原理,让我们通过我们的示例项目实践一下。

使用 Apache Airflow 编排 TFX 流水线

在本节中,我们将演示如何使用 Airflow 编排 TFX 流水线。这使我们可以使用 Airflow 的 UI 和其调度功能等功能,这些在生产环境设置中非常有帮助。

流水线设置

使用 Airflow 设置 TFX 流水线与为 Beam 设置BeamDagRunner相似,只是我们需要为 Airflow 用例配置更多设置。

我们将不再导入BeamDagRunner,而是使用AirflowDAGRunner。该运行器需要一个额外的参数,即 Apache Airflow 的配置(与我们在“项目特定配置”中讨论的配置相同)。AirflowDagRunner负责我们之前描述的所有任务定义和依赖关系,以便我们可以专注于我们的流水线。

正如我们之前讨论的那样,Airflow 流水线的文件需要位于~/airflow/dags 文件夹中。我们还讨论了一些 Airflow 的常见配置,如调度。我们为我们的流水线提供这些配置:

airflow_config``=``{``'schedule_interval'``:``None``,``'start_date'``:``datetime``.``datetime``(``2020``,``4``,``17``),``'pipeline_name'``:``'your_ml_pipeline'``,``}

与 Beam 示例类似,我们初始化组件并定义工作人员的数量:

from``tfx.orchestration``import``metadata``,``pipeline``def``init_pipeline``(``components``,``pipeline_root``:``Text``,``direct_num_workers``:``int``)``->``pipeline``.``Pipeline``:``beam_arg``=``[``"--direct_num_workers={}"``.``format``(``direct_num_workers``),``]``p``=``pipeline``.``Pipeline``(``pipeline_name``=``pipeline_name``,``pipeline_root``=``pipeline_root``,``components``=``components``,``enable_cache``=``True``,``metadata_connection_config``=``metadata``.``sqlite_metadata_connection_config``(``metadata_path``),``beam_pipeline_args``=``beam_arg``)``return``p

然后,我们初始化流水线并执行它。

from``tfx.orchestration.airflow.airflow_dag_runner``import``AirflowDagRunner``from``tfx.orchestration.airflow.airflow_dag_runner``import``AirflowPipelineConfig``from``base_pipeline``import``init_components``components``=``init_components``(``data_dir``,``module_file``,``serving_model_dir``,``training_steps``=``100``,``eval_steps``=``100``)``pipeline``=``init_pipeline``(``components``,``pipeline_root``,``0``)``DAG``=``AirflowDagRunner``(``AirflowPipelineConfig``(``airflow_config``))``.``run``(``pipeline``)

再次,这段代码与 Apache Beam 流水线的代码非常相似,但我们使用的是AirflowDagRunnerAirflowPipelineConfig,而不是BeamDagRunner。我们使用示例 11-1 初始化组件,然后 Airflow 会寻找名为DAG的变量。

在这本书的 GitHub 存储库中,我们提供了一个 Docker 容器,允许您轻松尝试使用 Airflow 的示例流水线。它设置了 Airflow Web 服务器和调度程序,并将文件移动到正确的位置。您还可以在附录 A 中了解有关 Docker 的更多信息。

流水线执行

正如我们之前讨论的那样,一旦启动了 Airflow Web 服务器,我们可以在定义的端口上打开 UI。视图应该看起来与图 11-3 非常相似。要运行一个流水线,我们需要打开流水线,然后使用触发 DAG 按钮来触发它,该按钮由播放图标指示。

图 11-3. 在 Airflow 中启动 DAG

在 Web 服务器 UI 中的图形视图(图 11-4)对于查看组件的依赖关系和流水线执行的进度非常有用。

图 11-4. Airflow 图形视图

您需要刷新浏览器页面以查看更新的进度。随着组件完成,它们将在边缘周围获得绿色框,如图 11-5 所示。您可以通过点击它们来查看每个组件的日志。

图 11-5. Airflow 中完成的流水线

使用 Airflow 来编排流水线是一个不错的选择,如果您想要一个包含 UI 的相对轻量级设置,或者您的公司已经在使用 Airflow。但如果您的公司已经在运行 Kubernetes 集群,下一章将介绍 Kubeflow Pipelines,这是一个更好的编排工具适用于这种情况。

总结

在本章中,我们讨论了不同的选项来编排您的机器学习流水线。您需要选择最适合您的设置和用例的工具。我们演示了如何使用 Apache Beam 来运行流水线,然后介绍了 Airflow 及其原理,最后展示了如何使用 Airflow 运行完整的流水线。

在下一章中,我们将展示如何使用 Kubeflow Pipelines 和 Google 的 AI 平台来运行流水线。如果这些不符合您的用例,您可以直接跳到第十三章,我们将展示如何使用反馈循环将您的流水线转变为一个循环。

第十二章:流水线第二部分:Kubeflow Pipelines

在第十一章中,我们讨论了如何使用 Apache Beam 和 Apache Airflow 编排我们的流水线。这两个编排工具有一些很棒的优点:Apache Beam 设置简单,而 Apache Airflow 在其他 ETL 任务中被广泛采用。

在本章中,我们想讨论如何使用 Kubeflow Pipelines 编排我们的流水线。Kubeflow Pipelines 允许我们在 Kubernetes 集群中运行机器学习任务,从而提供了一个高度可扩展的流水线解决方案。正如我们在第十一章中讨论的并在图 12-1 中展示的那样,我们的编排工具负责协调流水线组件之间的关系。

图 12-1:流水线编排器

Kubeflow Pipelines 的设置比安装 Apache Airflow 或 Apache Beam 更复杂。但是,正如我们将在本章后面讨论的那样,它提供了许多出色的功能,包括 Pipeline Lineage Browser、TensorBoard 集成以及查看 TFDV 和 TFMA 可视化的能力。此外,它充分利用了 Kubernetes 的优势,例如计算 Pod 的自动扩展、持久卷、资源请求和限制等。

本章分为两个部分。在第一部分中,我们将讨论如何使用 Kubeflow Pipelines 设置和执行流水线。所示的设置与执行环境无关。可以是提供托管 Kubernetes 集群的云提供商,也可以是本地的 Kubernetes 安装。

Kubernetes 简介

如果 Kubernetes 的概念和术语对您来说很新,请查看我们的附录。附录 A 提供了 Kubernetes 的简要概述。

本章的第二部分将讨论如何在 Google Cloud AI 平台上运行 Kubeflow Pipelines。这是特定于 Google Cloud 环境的。它负责大部分基础架构,并允许您使用 Dataflow 轻松扩展数据任务(例如数据预处理)。如果您想使用 Kubeflow Pipelines 但不想花时间管理 Kubernetes 基础架构,我们建议选择这条路线。

Kubeflow Pipelines 简介

Kubeflow Pipelines 是一个以机器学习为核心的基于 Kubernetes 的编排工具。而 Apache Airflow 专为 ETL 流程设计,Kubeflow Pipelines 则注重机器学习流水线的端到端执行。

Kubeflow Pipelines 提供了一致的用户界面来追踪机器学习管道运行,作为数据科学家之间协作的中心位置(正如我们将在 “Kubeflow Pipelines 的有用特性” 中讨论的那样),并提供了一种计划连续模型构建运行的方式。此外,Kubeflow Pipelines 提供了其自己的软件开发工具包(SDK),用于构建用于管道运行的 Docker 容器或编排容器。Kubeflow Pipeline 领域特定语言(DSL)允许更灵活地设置管道步骤,但也需要组件之间更多的协调。我们认为 TFX 管道导致更高水平的管道标准化,因此更少出错。如果您对 Kubeflow Pipelines SDK 的更多细节感兴趣,我们可以推荐在 “Kubeflow 对比 Kubeflow Pipelines” 中建议的阅读内容。

当我们设置 Kubeflow Pipelines 时,正如我们在 “安装和初始设置” 中讨论的那样,Kubeflow Pipelines 将安装各种工具,包括 UI、工作流控制器、一个 MySQL 数据库实例,以及我们在 “什么是 ML Metadata?” 中讨论的 ML MetadataStore。

当我们在 Kubeflow Pipelines 中运行我们的 TFX 管道时,您会注意到每个组件都作为其自己的 Kubernetes pod 运行。正如在 图 12-2 中所示,每个组件都与集群中的中央元数据存储连接,并可以从 Kubernetes 集群的持久存储卷或云存储桶加载工件。所有组件的输出(例如 TFDV 执行的数据统计或导出的模型)都将在元数据存储中注册,并作为持久卷或云存储桶上的工件存储。

Figure 12-2. Kubeflow Pipelines 概览

KUBEFLOW 对比 KUBEFLOW PIPELINES

Kubeflow 和 Kubeflow Pipelines 经常被混淆。Kubeflow 是一个开源项目套件,包含多种机器学习工具,包括用于训练机器学习模型的 TFJob,用于优化模型超参数的 Katib,以及用于部署机器学习模型的 KFServing。Kubeflow Pipelines 是 Kubeflow 套件的另一个项目,专注于部署和管理端到端的机器学习工作流。

在本章中,我们将专注于 Kubeflow Pipelines 的安装和操作。如果您对 Kubeflow 想要更深入的介绍,我们建议阅读 项目文档

此外,我们可以推荐两本 Kubeflow 图书:

  • Josh Patterson 等著,《Kubeflow 运维指南》(O'Reilly)

  • Holden Karau 等著,《即将推出的 Kubeflow 机器学习》(O'Reilly)

正如我们将在本章演示的那样,Kubeflow Pipelines 提供了一种高度可扩展的运行机器学习流水线的方式。Kubeflow Pipelines 在后台运行 Argo 来编排各个组件的依赖关系。由于这种通过 Argo 进行的编排,我们的流水线编排将具有不同的工作流程,正如我们在第十一章讨论的那样。我们将在“使用 Kubeflow Pipelines 编排 TFX Pipelines”中查看 Kubeflow Pipelines 的编排工作流程。

什么是 ARGO?

Argo 是一组工具,用于管理工作流程、部署和持续交付任务。最初设计用于管理 DevOps 任务,同时也是机器学习工作流的优秀管理工具。在 Kubernetes 环境中,Argo 将所有任务作为容器进行管理。更多信息,请参阅不断增长的文档

安装和初始设置

Kubeflow Pipelines 在 Kubernetes 集群内执行。对于本节,我们假设您已经创建了一个至少拥有 16 GB 内存和 8 个 CPU 的节点池的 Kubernetes 集群,并且已经配置了kubectl与您新创建的 Kubernetes 集群连接。

创建一个 Kubernetes 集群

对于在本地计算机或 Google Cloud 等云提供商上基本设置 Kubernetes 集群,请参阅附录 A 和附录 B。由于 Kubeflow Pipelines 的资源需求,建议使用云提供商的 Kubernetes 设置。云提供商提供的托管 Kubernetes 服务包括:

  1. 亚马逊弹性 Kubernetes 服务(Amazon EKS)

  2. Google Kubernetes Engine(GKE)

  3. 微软 Azure Kubernetes 服务(AKS)

  4. IBM 的 Kubernetes 服务

关于 Kubeflow 底层架构 Kubernetes 的更多细节,我们强烈推荐 Brendan Burns 等人的《Kubernetes: Up and Running》(O’Reilly)。

为了编排我们的流水线,我们正在安装 Kubeflow Pipelines 作为一个独立的应用程序,而不包括 Kubeflow 项目的所有其他工具。通过以下bash命令,我们可以设置我们的独立 Kubeflow Pipelines 安装。完整的设置可能需要五分钟才能正确完全启动。

$ export PIPELINE_VERSION``=``0.5.0 $ kubectl apply -k "github.com/kubeflow/pipelines/manifests/"``\``"kustomize/cluster-scoped-resources?ref=``$PIPELINE_VERSION``" customresourcedefinition.apiextensions.k8s.io/     applications.app.k8s.io created ... clusterrolebinding.rbac.authorization.k8s.io/     kubeflow-pipelines-cache-deployer-clusterrolebinding created $ kubectl wait --for condition``=``established \ --timeout``=``60s crd/applications.app.k8s.io customresourcedefinition.apiextensions.k8s.io/     applications.app.k8s.io condition met $ kubectl apply -k "github.com/kubeflow/pipelines/manifests/"``\``"kustomize/env/dev?ref=``$PIPELINE_VERSION``"

您可以通过打印有关已创建的 pod 的信息来检查安装的进度:

$ kubectl -n kubeflow get pods NAME                                              READY   STATUS       AGE cache-deployer-deployment-c6896d66b-62gc5         0/1     Pending      90s cache-server-8869f945b-4k7qk                      0/1     Pending      89s controller-manager-5cbdfbc5bd-bnfxx               0/1     Pending      89s ...

几分钟后,所有 pod 的状态应该变为 Running。如果您的流水线遇到任何问题(例如,计算资源不足),pod 的状态将指示错误:

$ kubectl -n kubeflow get pods NAME                                              READY   STATUS       AGE cache-deployer-deployment-c6896d66b-62gc5         1/1     Running      4m6s cache-server-8869f945b-4k7qk                      1/1     Running      4m6s controller-manager-5cbdfbc5bd-bnfxx               1/1     Running      4m6s ...

可以使用以下命令单独调查每个 pod:

kubectl -n kubeflow describe pod <pod name>

管理的 Kubeflow Pipelines 安装

如果您希望尝试 Kubeflow Pipelines,Google Cloud 提供通过 Google Cloud AI 平台进行托管安装。在“基于 Google Cloud AI 平台的 Pipelines”中,我们将深入讨论如何在 Google Cloud AI 平台上运行您的 TFX 管道以及如何从 Google Cloud Marketplace 上创建 Kubeflow Pipelines 的设置。

访问您的 Kubeflow Pipelines 安装

如果安装成功完成,无论您使用的是哪个云服务提供商或 Kubernetes 服务,您都可以通过在 Kubernetes 上创建端口转发来访问已安装的 Kubeflow Pipelines UI:

$ kubectl port-forward -n kubeflow svc/ml-pipeline-ui 8080:80

当端口转发正在运行时,您可以通过浏览器访问 localhost:8080 来访问 Kubeflow Pipelines。对于生产用例,应为 Kubernetes 服务创建负载均衡器。

Google Cloud 用户可以通过访问为您的 Kubeflow 安装创建的公共域名来访问 Kubeflow Pipelines。您可以通过执行以下命令获取 URL:

$ kubectl describe configmap inverse-proxy-config -n kubeflow \``| grep googleusercontent.com <id>-dot-<region>.pipelines.googleusercontent.com

您可以使用浏览器访问提供的 URL。如果一切顺利,您将看到 Kubeflow Pipelines 仪表板或如 图 12-3 所示的起始页面。

图 12-3. 使用 Kubeflow Pipelines 入门

一旦 Kubeflow Pipelines 设置完毕并运行,我们可以专注于如何运行流水线。在接下来的部分中,我们将讨论从 TFX 到 Kubeflow Pipelines 的流水线编排和工作流。

用 Kubeflow Pipelines 编排 TFX 流水线

在前面的章节中,我们讨论了如何在 Kubernetes 上设置 Kubeflow Pipelines 应用程序。在本节中,我们将描述如何在 Kubeflow Pipelines 设置上运行您的流水线,并且我们将专注于仅在您的 Kubernetes 集群内执行。这保证了流水线执行可以在与云服务提供商无关的集群上执行。在 “基于 Google Cloud AI 平台的流水线” 中,我们将展示如何利用像 GCP 的 Dataflow 这样的托管云服务来扩展您的流水线,超出您的 Kubernetes 集群的范围。

在深入讨论如何使用 Kubeflow Pipelines 编排机器学习流水线之前,我们想稍作停顿。从 TFX 代码到流水线执行的工作流比我们在 第十一章 中讨论的要复杂一些,因此我们将首先概述整体情况。图 12-4 展示了总体架构。

与 Airflow 和 Beam 一样,我们仍然需要一个定义流水线中 TFX 组件的 Python 脚本。我们将重用 第十一章 中的示例 11-1 脚本。与 Apache Beam 或 Airflow TFX 运行器执行不同,Kubeflow 运行器不会触发流水线运行,而是生成配置文件,以便在 Kubeflow 设置中执行。

如 图 12-4 所示,TFX KubeflowRunner 将把我们的 Python TFX 脚本与所有组件规格转换为 Argo 指令,然后可以在 Kubeflow Pipelines 中执行。Argo 将会为每个 TFX 组件启动一个单独的 Kubernetes Pod,并在容器中运行特定组件的 TFX Executor

图 12-4. 从 TFX 脚本到 Kubeflow Pipelines 的工作流程

自定义 TFX 容器镜像

所有组件容器使用的 TFX 镜像需要包含所有所需的 Python 包。默认的 TFX 镜像提供了最新版本的 TensorFlow 和基本的包。如果您的流水线需要额外的包,您需要构建一个自定义的 TFX 容器镜像,并在 KubeflowDagRunnerConfig 中指定它。我们在 附录 C 中描述了如何做到这一点。

所有组件都需要读取或写入执行器容器之外的文件系统。例如,数据摄入组件需要从文件系统读取数据,或者最终模型需要被Pusher推送到特定位置。仅在组件容器内部读取和写入是不现实的;因此,我们建议将工件存储在可以被所有组件访问的硬盘中(例如,云存储桶或 Kubernetes 集群中的持久卷)。如果你有兴趣设置持久卷,请参阅“通过持久卷交换数据”在附录 C 中的内容。

管道设置

你可以将训练数据、Python 模块和管道工件存储在云存储桶或持久卷中;这取决于你。你的管道只需访问这些文件。如果你选择从云存储桶读取或写入数据,请确保你的 TFX 组件在 Kubernetes 集群中运行时具有必要的云凭据。

现在,所有文件已就绪,并且为我们的管道容器准备了自定义的 TFX 镜像(如果需要),我们现在可以“组装” TFX Runner 脚本,以生成 Argo YAML 指令,用于我们的 Kubeflow Pipelines 执行。1

正如我们在第十一章中讨论的那样,我们可以重用init_components函数来生成我们的组件。这使我们能够专注于 Kubeflow 特定的配置。

首先,让我们为运行TransformTrainer组件所需的 Python 模块代码配置文件路径。此外,我们将设置原始训练数据、管道工件的文件夹位置,以及我们训练的模型应该存储的位置。在下面的示例中,我们展示了如何挂载一个持久卷与 TFX:

import``os``pipeline_name``=``'consumer_complaint_pipeline_kubeflow'``persistent_volume_claim``=``'tfx-pvc'``persistent_volume``=``'tfx-pv'``persistent_volume_mount``=``'/tfx-data'``# 管道输入``data_dir``=``os``.``path``.``join``(``persistent_volume_mount``,``'data'``)``module_file``=``os``.``path``.``join``(``persistent_volume_mount``,``'components'``,``'module.py'``)``# 管道输出``output_base``=``os``.``path``.``join``(``persistent_volume_mount``,``'output'``,``pipeline_name``)``serving_model_dir``=``os``.``path``.``join``(``output_base``,``pipeline_name``)

如果你决定使用云存储提供商,文件夹结构的根目录可以是一个存储桶,如下例所示:

import``os``...``bucket``=``'gs://tfx-demo-pipeline'``# 管道输入``data_dir``=``os``.``path``.``join``(``bucket``,``'data'``)``module_file``=``os``.``path``.``join``(``bucket``,``'components'``,``'module.py'``)``...

有了文件路径的定义,我们现在可以配置我们的KubeflowDagRunnerConfig。在我们的 Kubeflow Pipelines 设置中,配置 TFX 设置的三个重要参数是:

kubeflow_metadata_config

Kubeflow 在 Kubernetes 集群内运行一个 MySQL 数据库。调用get_default_kubeflow_metadata_config()将返回 Kubernetes 集群提供的数据库信息。如果您希望使用托管数据库(例如 AWS RDS 或 Google Cloud 数据库),可以通过参数覆盖连接详细信息。

tfx_image

图像 URI 是可选的。如果未定义 URI,则 TFX 将设置与执行 runner 的 TFX 版本对应的映像。在我们的示例演示中,我们将 URI 设置为容器注册表中映像的路径(例如 gcr.io/oreilly-book/ml-pipelines-tfx-custom:0.22.0)。

pipeline_operator_funcs

此参数访问一个配置信息列表,用于在 Kubeflow Pipelines 内运行 TFX(例如 gRPC 服务器的服务名称和端口)。由于这些信息可以通过 Kubernetes ConfigMap 提供,get_default_pipeline_operator_funcs函数将读取 ConfigMap 并将详细信息提供给pipeline_operator_funcs参数。在我们的示例项目中,我们将手动挂载一个持久卷以存储项目数据;因此,我们需要在列表中追加这些信息:

from``kfp``import``onprem``from``tfx.orchestration.kubeflow``import``kubeflow_dag_runner``...``PROJECT_ID``=``'oreilly-book'``IMAGE_NAME``=``'ml-pipelines-tfx-custom'``TFX_VERSION``=``'0.22.0'``metadata_config``= \ kubeflow_dag_runner``.``get_default_kubeflow_metadata_config``()pipeline_operator_funcs``= \ kubeflow_dag_runner``.``get_default_pipeline_operator_funcs``()pipeline_operator_funcs``.``append``(onprem``.``mount_pvc``(``persistent_volume_claim``,``persistent_volume``,``persistent_volume_mount``))``runner_config``=``kubeflow_dag_runner``.``KubeflowDagRunnerConfig``(``kubeflow_metadata_config``=``metadata_config``,``tfx_image``=``"gcr.io/{}/{}:{}"``.``format``(``PROJECT_ID``,``IMAGE_NAME``,``TFX_VERSION``),pipeline_operator_funcs``=``pipeline_operator_funcs``)

获得默认的元数据配置。

获得默认的 OpFunc 函数。

通过将它们添加到 OpFunc 函数中来挂载卷。

根据需要添加自定义 TFX 映像。

OPFUNC FUNCTIONS

OpFunc 函数允许我们设置特定于集群的细节,这些细节对于执行我们的流水线非常重要。这些函数允许我们与 Kubeflow Pipelines 中的底层数字订阅线(DSL)对象进行交互。OpFunc 函数以 Kubeflow Pipelines DSL 对象 dsl.ContainerOp 作为输入,应用额外的功能,并返回相同的对象。

将 OpFunc 函数添加到pipeline_operator_funcs中的两个常见用例是请求最小内存或指定容器执行的 GPU。但是 OpFunc 函数还允许设置特定于云提供商的凭据或请求 TPU(对于 Google Cloud)。

让我们看看 OpFunc 函数的两个最常见用法:设置运行 TFX 组件容器所需的最小内存限制和请求执行所有 TFX 组件的 GPU。以下示例设置运行每个组件容器所需的最小内存资源为 4 GB:

def``request_min_4G_memory``():``def``_set_memory_spec``(``container_op``):``container_op``.``set_memory_request``(``'4G'``)``return``_set_memory_spec``...``pipeline_operator_funcs``.``append``(``request_min_4G_memory``())

函数接收container_op对象,设置限制,并返回函数本身。

我们可以以同样的方式为执行 TFX 组件容器请求 GPU,如下例所示。如果您需要 GPU 来执行容器,只有在您的 Kubernetes 集群中完全配置和可用 GPU 时,您的流水线才会运行:3。

def``request_gpu``():``def``_set_gpu_limit``(``container_op``):``container_op``.``set_gpu_limit``(``'1'``)``return``_set_gpu_limit``...``pipeline_op_funcs``.``append``(``request_gpu``())

Kubeflow Pipelines SDK 为每个主要云提供商提供了常见的 OpFunc 函数。以下示例显示如何向 TFX 组件容器添加 AWS 凭据:

from``kfp``import``aws``...``pipeline_op_funcs``.``append``(``aws``.``use_aws_secret``()``)

函数use_aws_secret()假设 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY 已注册为 base64 编码的 Kubernetes 密钥。4 Google Cloud 凭据的等效函数称为use_gcp_secrets()

有了runner_config的设置,我们现在可以初始化组件并执行KubeflowDagRunner。但是,与其开始管道运行不同,该运行程序将输出 Argo 配置,我们将在 Kubeflow Pipelines 的下一部分中上传它:

from``tfx.orchestration.kubeflow``import``kubeflow_dag_runner``from``pipelines.base_pipeline``import``init_components``,``init_pipelinecomponents``=``init_components``(``data_dir``,``module_file``,``serving_model_dir``,``training_steps``=``50000``,``eval_steps``=``15000``)``p``=``init_pipeline``(``components``,``output_base``,``direct_num_workers``=``0``)``output_filename``=``"{}.yaml"``.``format``(``pipeline_name``)``kubeflow_dag_runner``.``KubeflowDagRunner``(``config``=``runner_config``,``output_dir``=``output_dir``,output_filename``=``output_filename``)``.``run``(``p``)

重复使用组件的基础模块。

可选参数。

参数 output_diroutput_filename 是可选的。如果未提供,将提供 Argo 配置作为压缩的 tar.gz 文件,位置与执行以下 python 脚本的同一目录相同。为了更好地可见性,我们配置了输出格式为 YAML,并设置了特定的输出路径。

运行以下命令后,您将在目录 pipelines/kubeflow_pipelines/argo_pipeline_files/ 中找到 Argo 配置 consumer_complaint_pipeline_kubeflow.yaml:

$ python pipelines/kubeflow_pipelines/pipeline_kubeflow.py

执行流水线

现在是访问您的 Kubeflow Pipelines 仪表板的时候了。如果您想创建新的流水线,请点击“上传流水线”进行上传,如 图 12-5 所示。或者,您可以选择现有的流水线并上传新版本。

图 12-5. 加载流水线的概述

选择 Argo 配置,如 图 12-6 所示。

图 12-6. 选择生成的 Argo 配置文件

Kubeflow Pipelines 现在将可视化您的组件依赖关系。如果您想启动新的流水线运行,请选择“创建运行”,如 图 12-7 所示。

您现在可以配置流水线运行。流水线可以一次运行或定期运行(例如使用 cron 作业)。Kubeflow Pipelines 还允许您将流水线运行分组为实验。

图 12-7. 创建流水线运行

一旦您点击开始,如 图 12-8 所示,Kubeflow Pipelines 在 Argo 的帮助下将启动并为每个容器创建一个 pod,具体取决于您的直接组件图。当组件的所有条件都满足时,将会为该组件创建一个 pod 并运行组件的执行器。

如果您想查看正在进行的运行的执行细节,可以点击“运行名称”,如 图 12-9 所示。

图 12-8. 定义的流水线运行详细信息

图 12-9. 运行中的流水线运行

您现在可以在组件执行期间或之后检查组件。例如,如果组件失败,您可以检查特定组件的日志文件。 图 12-10 显示一个示例,其中 Transform 组件缺少一个 Python 库。缺少的库可以通过将它们添加到自定义 TFX 容器映像中来提供,如 附录 C 中所述。

图 12-10. 检查组件失败

成功的流水线运行显示在 图 12-11 中。运行完成后,您可以在设置为 Pusher 组件的文件系统位置找到经过验证和导出的机器学习模型。在我们的示例中,我们将模型推送到持久卷上的路径 /tfx-data/output/consumer_complaint_pipeline_kubeflow/。

图 12-11. 成功的流水线运行

您还可以使用 kubectl 检查流水线的状态。由于每个组件都作为自己的 Pod 运行,所有带有流水线名称前缀的 Pod 应该处于完成状态:

$ `kubectl -n kubeflow get pods NAME                                                   READY  STATUS      AGE cache-deployer-deployment-c6896d66b-gmkqf              1/1    Running     28m cache-server-8869f945b-lb8tb                           1/1    Running     28m consumer-complaint-pipeline-kubeflow-nmvzb-1111865054  0/2    Completed   10m consumer-complaint-pipeline-kubeflow-nmvzb-1148904497  0/2    Completed   3m38s consumer-complaint-pipeline-kubeflow-nmvzb-1170114787  0/2    Completed   9m consumer-complaint-pipeline-kubeflow-nmvzb-1528408999  0/2    Completed   5m43s consumer-complaint-pipeline-kubeflow-nmvzb-2236032954  0/2    Completed   13m consumer-complaint-pipeline-kubeflow-nmvzb-2253512504  0/2    Completed   13m consumer-complaint-pipeline-kubeflow-nmvzb-2453066854  0/2    Completed   10m consumer-complaint-pipeline-kubeflow-nmvzb-2732473209  0/2    Completed   11m consumer-complaint-pipeline-kubeflow-nmvzb-997527881   0/2    Completed   10m ...

您还可以通过执行以下命令使用 kubectl 检查特定组件的日志。可以通过相应的 Pod 检索特定组件的日志:

$ kubectl logs -n kubeflow podname

TFX CLI

与基于 UI 的流水线创建过程的替代方法是,您还可以通过 TFX CLI 编程方式创建流水线并启动流水线运行。您可以在 “TFX Command-Line Interface” 的 附录 C 中找到有关如何设置 TFX CLI 和如何在没有 UI 的情况下部署机器学习流水线的详细信息。

Kubeflow Pipelines 的有用功能

在接下来的部分中,我们想要强调 Kubeflow Pipelines 的有用功能。

重新启动失败的流水线

流水线运行的执行可能需要一段时间,有时甚至几个小时。TFX 将每个组件的状态存储在 ML MetadataStore 中,Kubeflow Pipelines 可以跟踪流水线运行的成功完成组件任务。因此,它提供重新启动失败流水线运行的功能,从上次失败的组件开始。这将避免重新运行已成功完成的组件,从而节省流水线重新运行的时间。

重复运行

除了启动单个流水线运行外,Kubeflow Pipelines 还允许我们根据时间表运行流水线。如 图 12-12 所示,我们可以类似于 Apache Airflow 中的调度运行。

图 12-12. 使用 Kubeflow Pipelines 调度重复运行

协作和审核流水线运行

Kubeflow Pipelines 为数据科学家提供了界面,以便团队协作并审查管道运行。在第四章和第七章中,我们讨论了用于显示数据或模型验证结果的可视化工具。在完成这些管道组件之后,我们可以审查组件的结果。

图 12-13 显示了数据验证步骤的结果作为示例。由于组件输出保存到磁盘或云存储桶中,我们也可以回顾管道运行的结果。

Figure 12-13. Kubeflow Pipelines 中提供的 TFDV 统计数据

由于每个管道运行和这些运行的组件的结果都保存在 ML MetadataStore 中,我们也可以比较这些运行。如图 12-14 所示,Kubeflow Pipelines 提供了一个 UI 来比较管道运行。

Figure 12-14. 使用 Kubeflow Pipelines 比较流水线运行

Kubeflow Pipelines 还很好地集成了 TensorFlow 的 TensorBoard。正如您在图 12-15 中所看到的,我们可以使用 TensorBoard 查看模型训练运行的统计信息。在创建基础 Kubernetes pod 之后,我们可以使用 TensorBoard 查看模型训练运行的统计信息。

Figure 12-15. 使用 TensorFlow 的 TensorBoard 审查训练运行

审计流水线血统

对于机器学习的广泛采用,审查模型的创建是至关重要的。例如,如果数据科学家观察到训练后的模型不公平(正如我们在第 7 章中讨论的),重溯和再现我们使用的数据或超参数是很重要的。我们基本上需要为每个机器学习模型建立审计追踪。

Kubeflow Pipelines 通过 Kubeflow Lineage Explorer 为这样的审计追踪提供了解决方案。它创建了一个可以轻松查询 ML MetadataStore 数据的用户界面。

如图 12-16 右下角所示,一个机器学习模型被推送到某个位置。血统探索器允许我们追溯到导出模型的所有组件和工件,一直回溯到最初的原始数据集。如果使用了人在回路组件(参见“人在回路”),我们可以追溯到谁签署了模型,或者我们可以检查数据验证结果并调查初始训练数据是否开始漂移。

如您所见,Kubeflow Pipelines 是一个非常强大的工具,用于编排我们的机器学习流水线。如果您的基础架构基于 AWS 或 Azure,或者如果您希望完全控制您的设置,我们建议使用这种方法。然而,如果您已经在使用 GCP,或者如果您希望使用 Kubeflow Pipelines 的简化方式,请继续阅读。

Figure 12-16. 使用 Kubeflow Pipelines 检查流水线血统

基于 Google Cloud AI Platform 的 Pipelines

如果你不想花费时间管理自己的 Kubeflow Pipelines 设置,或者希望与 GCP 的 AI Platform 或其他 GCP 服务(如 Dataflow、AI Platform 训练与服务等)集成,那么这一节适合你。接下来,我们将讨论如何通过 Google Cloud 的 AI Platform 设置 Kubeflow Pipelines。此外,我们还将介绍如何使用 Google Cloud 的 AI 作业训练你的机器学习模型,并使用 Google Cloud 的 Dataflow 扩展你的预处理,后者可以用作 Apache Beam 运行器。

Pipeline 设置

Google 的 AI Platform Pipelines 允许你通过 UI 创建 Kubeflow Pipelines 设置。图 12-17 展示了 AI Platform Pipelines 的首页,你可以在这里开始创建你的设置。

BETA 产品

如你在图 12-17 中所见,截至撰写本文时,这款 Google Cloud 产品仍处于 beta 阶段。所展示的工作流程可能会有所更改。

图 12-17. Google Cloud AI Platform Pipelines

当你点击页面右上角的 New Instance,它会将你导向 Google Marketplace,如图 12-18 所示。

图 12-18. 用于 Kubeflow Pipelines 的 Google Cloud Marketplace 页面

在选择配置后,你将被要求在菜单顶部选择要么选择现有的 Kubernetes 集群,要么创建一个集群,如图 12-19 所示。

节点大小

在创建新的 Kubernetes 集群或选择现有集群时,请考虑节点的可用内存。每个节点实例需要提供足够的内存以容纳整个模型。对于我们的演示项目,我们选择了 n1-standard-4 作为实例类型。在撰写本文时,我们无法在从 Marketplace 启动 Kubeflow Pipelines 时创建自定义集群。如果你的管道设置需要更大的实例,请首先创建集群及其节点,然后在从 GCP Marketplace 创建 Kubeflow Pipelines 设置时从现有集群列表中选择该集群。

图 12-19. 配置你的集群以用于 Kubeflow Pipelines

访问范围

在创建 Kubeflow Pipelines 或自定义集群时,请选择“允许对所有 Cloud APIs 的完全访问权限”,这是对集群节点访问范围的要求。Kubeflow Pipelines 需要访问多种 Cloud APIs。授予对所有 Cloud APIs 的访问权限可以简化设置过程。

在配置你的 Kubernetes 集群后,Google Cloud 将实例化你的 Kubeflow Pipelines 设置,如图 12-20 所示。

图 12-20. 创建你的 Kubeflow Pipelines 设置

几分钟后,您的设置将准备就绪,您可以在 AI Platform Pipelines 部署的 Kubeflow 设置列表中找到作为实例列出的 Kubeflow Pipelines 设置。如果您单击“打开 Pipelines 仪表板”,如图 12-21 所示,您将被重定向到您新部署的 Kubeflow Pipelines 设置。从这里开始,Kubeflow Pipelines 将按照我们在前一节讨论的方式运行,并且 UI 看起来非常相似。

图 12-21. Kubeflow 部署列表

在 AI Platform Pipelines 仪表板中提供的逐步安装说明

如果您按照逐步手动安装 Kubeflow Pipelines 的步骤,正如在“访问您的 Kubeflow Pipelines 安装”和附录 B 中讨论的那样,您的 Kubeflow Pipelines 设置也将在 AI Platform Pipelines 实例下列出。

TFX 管道设置

我们的 TFX 管道配置与我们之前讨论的KubeflowDagRunner的配置非常相似。事实上,如果您像在“管道设置”中讨论的那样挂载了一个带有所需 Python 模块和训练数据的持久卷,您可以在 AI Platform Pipelines 上运行您的 TFX 管道。

在接下来的章节中,我们将展示一些对早期 Kubeflow Pipelines 设置的更改,这些更改可以简化您的工作流程(例如,从 Google Storage 存储桶加载数据),或者帮助您扩展超出 Kubernetes 集群的管道(例如,通过 AI Platform Jobs 训练机器学习模型)。

使用 Cloud Storage 存储桶进行数据交换

在“管道设置”中,我们讨论了可以从挂载在 Kubernetes 集群中的持久卷加载管道执行所需的数据和 Python 模块。如果您在 Google Cloud 生态系统内运行管道,还可以从 Google Cloud Storage 存储桶加载数据。这将简化工作流程,使您能够通过 GCP Web 界面或gcloud SDK 上传和审查文件。

存储桶路径可以像磁盘上的文件路径一样提供,如下面的代码片段所示:

input_bucket``=``'gs://``YOUR_INPUT_BUCKET``'``output_bucket``=``'gs://``YOUR_OUTPUT_BUCKET``'``data_dir``=``os``.``path``.``join``(``input_bucket``,``'data'``)``tfx_root``=``os``.``path``.``join``(``output_bucket``,``'tfx_pipeline'``)``pipeline_root``=``os``.``path``.``join``(``tfx_root``,``pipeline_name``)``serving_model_dir``=``os``.``path``.``join``(``output_bucket``,``'serving_model_dir'``)``module_file``=``os``.``path``.``join``(``input_bucket``,``'components'``,``'module.py'``)

将存储桶分割为输入(例如 Python 模块和训练数据)和输出数据(例如训练好的模型)通常是有益的,但您也可以使用相同的存储桶。

使用 AI Platform 作业训练模型

如果您想通过 GPU 或 TPU 扩展模型训练,可以配置流水线以在这些硬件上运行机器学习模型的训练步骤:

project_id``=``'``YOUR_PROJECT_ID``'``gcp_region``=``'``GCP_REGION>``'ai_platform_training_args``=``{``'project'``:``project_id``,``'region'``:``gcp_region``,``'masterConfig'``:``{``'imageUri'``:``'gcr.io/oreilly-book/ml-pipelines-tfx-custom:0.22.0'``}'scaleTier'``:``'BASIC_GPU'``,}

例如,us-central1

提供自定义镜像(如果需要)。

其他选项包括 BASIC_TPUSTANDARD_1PREMIUM_1

为了让 Trainer 组件能够观察 AI 平台的配置,您需要配置组件执行器,并将我们迄今为止使用的 GenericExecutor 替换为 Trainer 组件。以下代码片段显示了所需的附加参数:

from``tfx.extensions.google_cloud_ai_platform.trainer``import``executor \ as``ai_platform_trainer_executor``trainer``=``Trainer``(``...``custom_executor_spec``=``executor_spec``.``ExecutorClassSpec``(``ai_platform_trainer_executor``.``GenericExecutor``),``custom_config``=``{``ai_platform_trainer_executor``.``TRAINING_ARGS_KEY``:``ai_platform_training_args``}``)

与在 Kubernetes 集群内训练机器学习模型不同,您可以使用 AI 平台分发模型训练。除了分布式训练功能外,AI 平台还提供了像 TPU 这样的加速训练硬件的访问。

当流水线中触发 Trainer 组件时,它将在 AI 平台作业中启动训练作业,如 Figure 12-22 所示。在那里,您可以检查训练任务的日志文件或完成状态。

图 12-22. AI 平台训练作业

通过 AI 平台端点提供模型服务

如果您在 Google Cloud 生态系统内运行流水线,还可以将机器学习模型部署到 AI 平台的端点。这些端点具有根据推断峰值来扩展模型的选项。

与我们在 “TFX Pusher Component” 中讨论过的设置 push_destination 不同,我们可以覆盖执行器并为 AI 平台部署提供 Google Cloud 详细信息。以下代码片段显示了所需的配置细节:

ai_platform_serving_args``=``{``'model_name'``:``'consumer_complaint'``,``'project_id'``:``project_id``,``'regions'``:``[``gcp_region``],``}

Trainer 组件的设置类似,我们需要交换组件的执行器,并提供包含部署详细信息的 custom_config

from``tfx.extensions.google_cloud_ai_platform.pusher``import``executor \ `asai_platform_pusher_executorpusher=Pusher(...custom_executor_spec=executor_spec.ExecutorClassSpec(ai_platform_pusher_executor.Executor),custom_config={ai_platform_pusher_executor.SERVING_ARGS_KEY:ai_platform_serving_args})

如果您提供了Pusher组件的配置,则可以通过使用 AI 平台避免设置和维护 TensorFlow Serving 的实例。

部署限制

此刻,模型通过 AI 平台进行部署的最大限制是 512 MB。我们的演示项目超出了此限制,因此目前无法通过 AI 平台端点进行部署。

与 Google 的 Dataflow 一起扩展

到目前为止,依赖于 Apache Beam 的所有组件都使用默认的DirectRunner执行数据处理任务,这意味着处理任务将在启动 Apache Beam 任务的同一实例上执行。在这种情况下,Apache Beam 会尽可能消耗多个 CPU 核心,但不会扩展到单个实例之外。

一个替代方案是使用 Google Cloud 的 Dataflow 执行 Apache Beam。在这种情况下,TFX 将使用 Apache Beam 处理作业,并且后者将向 Dataflow 提交任务。根据每个作业的要求,Dataflow 将启动计算实例并在实例之间分发作业任务。这是扩展数据预处理作业(如统计生成或数据预处理)的一种非常好的方式。

为了利用 Google Cloud Dataflow 的扩展能力,我们需要提供一些额外的 Beam 配置,这些配置将传递给我们的流水线实例化:

tmp_file_location``=``os``.``path``.``join``(``output_bucket``,``"tmp"``)``beam_pipeline_args``=``[``"--runner=DataflowRunner"``,``"--experiments=shuffle_mode=auto"``,``"--project={}"``.``format``(``project_id``),``"--temp_location={}"``.``format``(``tmp_file_location``),``"--region={}"``.``format``(``gcp_region``),``"--disk_size_gb=50"``,``]

除了将runner类型配置为DataflowRunner之外,我们还将shuffle_mode设置为auto。这是 Dataflow 的一个有趣特性。不像在 Google Compute Engine 的 VM 中运行GroupByKey等转换操作,而是在 Dataflow 的服务后端处理该操作。这样可以减少计算实例的执行时间以及 CPU/内存成本。

流水线执行

在 Google Cloud AI 平台上执行流水线与我们在“使用 Kubeflow Pipelines 编排 TFX 流水线”中讨论的情况并无不同。TFX 脚本将生成 Argo 配置。然后可以将该配置上传到设在 AI 平台上的 Kubeflow Pipelines 设置中。

在管道执行期间,您可以检查训练作业,如在“Training models with an AI Platform job”中所讨论的,还可以详细观察 Dataflow 作业,如在 Figure 12-23 中所示。

图 12-23 详细介绍了 Google Cloud Dataflow 作业的详情。

Dataflow 仪表板提供有关作业进度和扩展需求的宝贵洞察。

总结

使用 Kubeflow 管道运行管道提供了很大的好处,我们认为这些好处超过了额外的设置要求。我们看到管道谱系浏览、与 TensorBoard 的无缝集成以及重复运行的选项是选择 Kubeflow 管道作为管道编排器的充分理由。

正如我们之前讨论的,与在第十一章中讨论的 Apache Beam 或 Apache Airflow 上运行的管道不同,使用 Kubeflow 管道运行 TFX 管道的当前工作流程是不同的。然而,TFX 组件的配置与我们在前一章中讨论的是相同的。

在本章中,我们介绍了两种 Kubeflow 管道设置:第一种设置几乎适用于任何托管的 Kubernetes 服务,如 AWS Elastic Kubernetes Service 或 Microsoft Azure Kubernetes Service。第二种设置适用于 Google Cloud 的 AI 平台。

在接下来的章节中,我们将讨论如何通过反馈循环将您的管道转变为循环。

1   您可以在本书的 GitHub 存储库中跟随生成 Argo YAML 指令的脚本。

2   想了解更多关于 Kubernetes ConfigMaps 的信息,请查看“Some Kubernetes Definitions”。

3   访问Nvidia获取有关为 Kubernetes 集群安装最新驱动程序的更多信息。

4   参阅文档获取有关 Kubernetes secrets 及其设置方法的信息。

5   数据流仅透过 Google Cloud 提供。替代的分布式运行程序有 Apache Flink 和 Apache Spark。

第十三章:反馈循环

现在,我们已经建立了一个顺畅的流水线,将机器学习模型投入生产中,我们不希望只运行一次。一旦部署了模型,模型就不应该是静态的。随着收集新数据、数据分布的变化(详见第四章)、模型漂移(讨论见第七章),我们希望我们的流水线能够不断改进。

将某种形式的反馈引入到机器流水线中将其转变为一个生命周期,如图 13-1 所示。模型的预测结果导致新数据的收集,从而持续改进模型。

图 13-1. 作为 ML 流水线一部分的模型反馈

如果没有新鲜数据,模型的预测能力可能会随着时间的推移而下降。事实上,ML 模型的部署可能会改变输入的训练数据,因为用户体验发生变化;例如,在视频推荐系统中,模型提供更好的推荐会导致用户做出不同的观看选择。反馈循环可以帮助我们收集新数据来更新我们的模型。对于个性化模型特别有用,例如推荐系统或预测文本。

此时,确保流水线的其余部分设置稳健非常重要。引入新数据应仅在新数据导致数据验证中设置的限制超出范围,或者导致模型分析中设置的边界外移时才导致流水线失败。这时可以触发事件,例如模型重新训练、新的特征工程等。如果其中一个触发器发生,新模型应该获得一个新版本号。

除了收集新的训练数据外,反馈循环还可以提供有关模型实际使用情况的信息。这可以包括活跃用户的数量、他们与模型互动的时间以及许多其他数据。这类数据对向业务利益相关者展示模型价值非常有用。

反馈循环可能会带来危险

反馈循环也可能带来负面影响,因此应谨慎处理。如果将模型的预测结果反馈到新的训练数据中而没有人为输入,模型将从其错误和正确预测中学习。反馈循环还可能放大原始数据中存在的任何偏见或不平等。仔细的模型分析可以帮助您发现其中的一些情况。

显性和隐性反馈

我们可以将反馈分为两种主要类型:隐式和显式。1 隐式反馈是指人们在正常使用产品时通过行动给模型提供反馈,例如通过购买推荐系统建议的东西或观看建议的电影。隐式反馈需要在用户隐私方面进行仔细考虑,因为很容易跟踪用户的每一个动作。显式反馈是指用户直接对预测提供一些直接的输入,例如对推荐的点赞或点踩,或者更正一个预测。

数据飞轮

在某些情况下,你可能已经拥有足够的数据来创建一个由机器学习驱动的新产品。但在其他情况下,你可能需要收集更多数据。这在处理监督学习问题时特别频繁。监督学习比无监督学习更成熟,通常提供更可靠的结果,因此在生产系统中部署的大多数模型都是监督模型。经常出现这样的情况:你拥有大量未标记数据但标记数据不足。然而,随着我们在示例项目中使用的迁移学习的发展,某些机器学习问题不再需要大量标记数据。

当你有大量未标记的数据并需要收集更多标签时,数据飞轮的概念尤为有用。这种数据飞轮允许你通过使用产品的现有数据、手动标记的数据或公共数据设置初始模型来扩展训练数据集。通过从用户那里收集对初始模型的反馈,你可以标记数据,从而改进模型预测,吸引更多用户使用产品,进而标记更多数据,如图 13-2 所示。

图 13-2. 数据飞轮

现实世界中的反馈循环

机器学习系统中最熟悉的反馈循环例子之一是当模型的预测暴露给客户时。这在推荐系统中尤为常见,其中模型预测特定用户的前 k 个最相关选择。通常在推出产品前很难为推荐系统收集训练数据,因此这些系统通常严重依赖于用户的反馈。

Netflix 的电影推荐系统 是反馈循环的经典案例。用户得到电影推荐后,通过评分提供反馈。随着用户评分的增加,他们会收到更符合个人口味的推荐。

最初,当 Netflix 的主要业务是邮寄 DVD 时,它使用了一个一到五星的评级系统来收集 DVD 的评级,这表明客户实际观看了 DVD。在这种情况下,Netflix 只能收集到显式反馈。但当其业务转向在线流媒体电影时,公司也能够收集到用户是否观看了推荐给他们的电影,以及用户是否观看了整部电影的隐式反馈。因此,Netflix 改用了一个简单的大拇指向上或向下的系统,而不再使用一到五星的评级系统,这使得它能够收集更多的反馈,因为这个系统需要用户投入的时间更少。此外,更精细的评级可能不太具有操作性:如果一部电影评为三星,模型应该如何响应?三星评价并不表明预测是正确还是错误,而大拇指向上或向下则对模型提供了明确的信号。

另一个反馈循环的例子——在这种情况下是负面反馈——是微软臭名昭著的 Twitter 机器人TAY。2016 年,它在推出后仅 16 小时内因其具有攻击性和有时带有种族主义色彩的推文而被下线。在被下线之前,它已经发布了超过 96,000 条推文。它是基于其推文的回复自动重新训练的,这些推文是有意挑衅的。这种情况下的反馈循环是,系统将其初始推文的回复并入其训练数据中。这可能本意是使机器人听起来更像人类,但结果是它吸引了最糟糕的回复,并变得极具攻击性。

什么可能会出错?

重要的是考虑反馈循环可能出现的问题,以及最理想的情况。你的用户可能会做什么最糟糕的事情?如何保护系统免受可能以有组织或自动化方式破坏系统的恶意行为者的影响?

第三个现实世界的反馈循环例子来自在线支付公司 Stripe。Stripe 建立了一个二分类器来预测信用卡交易中的欺诈行为,如果模型预测交易可能涉及欺诈,其系统将阻止这些交易。公司从过去的交易数据中获得了一个训练集,并在其上训练了一个模型,在训练集上表现良好。然而,由于如果模型预测交易是欺诈的,那么这些交易就会被阻止,因此我们无法确定生产系统的精确度和召回率。我们无法确定这些交易是否真的是欺诈行为,因为它们从未发生过。

当模型基于新数据重新训练时,出现了一个更大的问题:其准确率下降。在这种情况下,反馈循环导致所有原始类型的欺诈交易被阻止,因此它们无法成为新的训练数据。新模型正在训练未被捕获的剩余欺诈交易。Stripe 的解决方案是放宽规则,允许少量的收费通过,即使模型预测它们可能是欺诈的。这使得它可以评估模型并提供新的相关训练数据。

反馈循环的后果

反馈循环通常会产生一些在设计过程中并不明显的后果。在部署后继续监控系统是至关重要的,以确保反馈循环带来的是积极变化而不是负面循环。我们建议使用第七章中的技术密切关注系统。

在 Stripe 的前述示例中,反馈循环导致模型的准确率下降。然而,准确率的提高也可能是一个不希望的效果。YouTube 的推荐系统旨在增加人们观看视频的时间。用户的反馈意味着模型准确预测他们将会观看的内容。这一策略非常成功:人们每天在 YouTube 上观看超过十亿小时的视频。然而,有人担心这一系统会导致用户观看越来越极端的内容视频。当系统变得非常庞大时,极其难以预见反馈循环的所有后果。因此,请谨慎行事,并确保为用户设置保护措施。

正如这些例子所示,反馈循环可以是积极的,并帮助我们获取更多的训练数据,以改善模型甚至构建业务。然而,它们也可能导致严重问题。如果你已经仔细选择了确保反馈循环是积极的模型指标,下一步就是学习如何收集反馈,我们将在下一节中讨论。

收集反馈的设计模式

在这一节中,我们将讨论一些常见的收集反馈的方法。你选择的方法将取决于几个因素:

  • 你试图解决的业务问题

  • 应用程序或产品的类型和设计

  • 机器学习模型的类型:分类、推荐系统等。

如果你计划从产品用户那里收集反馈,告知用户发生的情况非常重要,这样他们可以同意提供反馈。这也可以帮助你收集更多的反馈:如果用户投入到改进系统中,他们更有可能提供反馈。

我们将在接下来的部分详细解析收集反馈的不同选择:

  • “用户根据预测采取某些行动”

  • “用户评价预测的质量”

  • “用户纠正预测”

  • “众包标注”

  • “专家标注”

  • “自动产生反馈”

尽管您的机器学习流水线所要解决的问题在很大程度上会驱动您选择的设计模式,但您的选择将影响您如何跟踪反馈以及如何将其重新整合到您的机器学习流水线中。

用户根据预测采取某些行动

在这种方法中,我们将模型的预测直接展示给用户,用户因此采取一些在线行动。我们记录这个行动,并且这条记录为模型提供了一些新的训练数据。

一个例子是任何类型的产品推荐系统,比如亚马逊用来向用户推荐下一个购买的系统。系统向用户展示了一组模型预测将感兴趣的产品。如果用户点击了其中一个产品或者继续购买该产品,那么推荐是成功的。然而,关于用户没有点击的其他产品是否是好的推荐则没有信息。这是隐式反馈:它并没有提供我们训练模型所需的精确数据(即对每个预测进行排名)。相反,反馈需要在许多不同的用户上进行聚合,以提供新的训练数据。

用户评价预测的质量

使用这种技术,模型的预测显示给用户,用户通过某种信号来表明他们喜欢或不喜欢预测。这是显式反馈的一个例子,用户必须采取额外的行动来提供新的数据。反馈可以是星级评分或简单的二进制赞成或反对。这非常适合推荐系统,尤其对个性化很有用。必须注意反馈是可操作的:例如,在前面提到的 Netflix 示例中,五颗星中的三颗星并不提供有关模型预测是否有用或准确的太多信息。

这种方法的一个局限性是反馈是间接的——在推荐系统的情况下,用户表明哪些是不好的预测,但并没有告诉你正确的预测应该是什么。这个系统的另一个限制是反馈可以被解释的方式有很多。用户“喜欢”的东西未必是他们想要看到更多的东西。例如,在电影推荐系统中,用户可能点赞表示他们想看更多同一类型、同一导演或同一演员的电影。当只能提供二进制反馈时,所有这些细微差别都会丢失。

用户纠正预测

这种方法是显式反馈的一个例子,其工作方式如下:

  • 从低精度模型得出的预测结果被展示给用户。

  • 如果预测结果正确,用户接受该预测;如果不正确,则更新。

  • 预测结果(现在已由用户验证)可以用作新的训练数据。

在用户高度关注结果的情况下效果最佳。一个很好的例子是银行应用,用户可以通过该应用存款支票。图像识别模型会自动填写支票金额。如果金额正确,用户确认;如果不正确,则用户输入正确的金额。在这种情况下,用户有兴趣输入正确的金额,以便将钱存入他们的账户。随着用户创建更多的训练数据,该应用随时间变得更加准确。如果您的反馈循环可以使用这种方法,这将是快速收集大量高质量新数据的绝佳方式。

当使用这种方法时,务必确保机器学习系统的目标与用户的目标高度一致。如果用户接受不正确的响应,因为他们没有理由去努力更改它,那么训练数据将充满错误,模型也不会随时间变得更加准确。如果用户提供不正确的结果能够给用户带来一些好处,这将使新的训练数据存在偏差。

众包标注

如果您有大量未标记的数据并且无法通过产品的正常使用从用户那里收集标签,则此方法特别有用。许多自然语言处理和计算机视觉领域的问题属于此类:很容易收集大量图像语料库,但数据未针对您机器学习模型的特定用例进行标记。例如,如果您想训练一个图像分类模型,将手机图像分类为文档或非文档,您可能会让用户拍摄许多照片但不提供标签。

在这种情况下,通常会收集大量未标记的数据,然后将其传递给众包平台,如 AWS Mechanical Turk 或 Figure Eight。然后会付给人工标注者一定的费用(通常很少)来标记数据。这最适合不需要特别培训的任务。

使用此方法时,有必要控制标注质量的差异,并且通常设置注释工具,使多个人标记同一数据示例。Google PAIR guide 提供了一些出色且详细的设计注释工具的建议,但需要考虑的关键因素是标注者的激励需要与模型的结果保持一致。此方法的主要优势在于可以对创建的新数据非常具体,以便完全符合复杂模型的需求。

然而,这种方法存在一些缺点,例如,可能不适用于私有或敏感数据。此外,务必确保有一个反映产品用户和整个社会的多样化评分池。此方法的成本也可能很高,不一定适合大量用户。

专家注释

专家注释设置类似于众包,但选择的注释者经过精心挑选。这可能是你(构建流水线的人),使用诸如Prodigy这样的文本数据注释工具。或者可能是领域专家,例如,如果你正在对医学图像进行图像分类器的训练。这种方法特别适用于以下情况:

  • 数据需要一些专业知识来进行注释。

  • 数据在某种方式上是私有或敏感的。

  • 只需要少量标签(例如,迁移学习或半监督学习)。

  • 注释中的错误对人们有着高度真实世界的影响。

此方法允许收集高质量的反馈,但成本高昂,需要手动操作,并且不易扩展。

自动产生反馈

在某些机器学习流水线中,无需人类收集反馈。模型进行预测,然后发生某些未来事件,告诉我们模型是否正确。在这种情况下,系统会自动收集新的训练数据。虽然这不涉及任何单独的基础设施来收集反馈,但仍需小心:由于预测的存在可能会扰乱系统。来自 Stripe 的上述例子很好地说明了这一点:模型会影响其自身的未来训练数据。4

如何跟踪反馈回路

一旦确定了哪种类型的反馈回路最适合您的业务和您的模型类型,就该将其纳入您的机器学习流水线中了。这就是我们在第七章讨论的模型验证的重要性所在:新数据将通过系统传播,而且在这个过程中,不能使系统的性能下降违背您正在追踪的指标。

关键概念在于每个预测都应该有一个跟踪 ID,如图 13-3 所示。可以通过某种预测注册表来实现这一点,其中每个预测都与跟踪 ID 一起存储。预测和 ID 传递给应用程序,然后将预测显示给用户。如果用户提供反馈,则该过程继续。

图 13-3. 跟踪反馈

当收集到反馈时,它会存储在一个反馈注册表中,与该预测的跟踪 ID 一起。数据处理步骤将反馈与原始预测连接起来。这使您能够通过数据和模型验证步骤跟踪反馈,从而知道哪些反馈推动了新模型版本的产生。

追踪显式反馈

如果系统正在收集显式反馈,如前文所述,有两种跟踪方法:

二进制反馈

在大多数情况下,只有告诉您预测正确的反馈才能为您提供具有关联跟踪 ID 的新训练数据。例如,在多类分类系统中,用户反馈只告诉您预测的类是否正确。如果预测的类标记为不正确,您不知道应该是其他类中的哪一个。如果预测的类是正确的,数据与预测的配对形成一个新的训练样本。二进制分类问题是唯一可以使用预测不正确反馈的情况。在这种情况下,这个反馈告诉我们这个示例属于负类。

重新分类或更正

当用户给出模型的正确答案时,输入数据与新分类的配对形成一个新的训练样本,并应该获得一个跟踪 ID。

追踪隐式反馈

隐式反馈生成二进制反馈。如果推荐系统建议一个产品,用户点击了该产品,产品与用户数据的配对形成一个新的训练样本并获得一个跟踪 ID。然而,如果用户没有点击一个产品,这并不意味着推荐是不好的。在这种情况下,可能需要等待许多推荐的产品的二进制反馈,然后重新训练模型。

摘要

反馈循环将机器学习管道转化为一个循环,并帮助其成长和改进。将新数据整合到机器学习管道中至关重要,以防止模型陈旧化和精度下降。确保选择最符合您模型类型和成功指标的反馈方法。

反馈循环需要仔细监控。一旦开始收集新数据,就很容易违反许多机器学习算法的最基本假设之一:您的训练和验证数据来自相同的分布。理想情况下,您的训练和验证数据将代表您建模的真实世界,但在实践中,这从未发生过。因此,在收集新数据时,生成新的验证数据集以及训练数据集非常重要。

反馈循环要求您与产品相关的设计师、开发人员和用户体验专家密切合作。他们需要建立捕获数据并改进模型的系统。重要的是您与他们合作,将反馈与用户将看到的改进联系起来,并设定反馈何时将改变产品的期望。这一努力将有助于保持用户对提供反馈的投入。

一个需要注意的地方是,反馈循环可能会强化初始模型中的任何有害偏见或不公平。永远不要忘记,这个过程可能会影响到某个人!考虑为用户提供一种方法,使其能够反馈模型对某人造成了伤害,以便他们能够轻松标记需要立即修复的情况。这将需要比一到五星评级更详细的细节。

一旦您设置好反馈循环,并能够跟踪模型的预测和对预测的响应,您就拥有了管道的所有组成部分。

1   更多详细信息,请参阅Google 的 PAIR 手册

2   反馈应该易于收集,并提供可操作的结果。

3   见 Michael Manapat 的演讲《机器学习模型的反事实评估》,(演示文稿,PyData Seattle 2015),oreil.ly/rGCHo

4   更多相关内容请参阅 D. Sculley 等人的文章《机器学习系统中的隐藏技术债务》,载于《神经信息处理系统进展》第 28 卷(NIPS,2015 年),oreil.ly/eUyZM

第十四章:机器学习的数据隐私

在本章中,我们将介绍数据隐私的一些方面,以其在机器学习流水线中的应用。隐私保护机器学习是一个非常活跃的研究领域,刚开始被纳入 TensorFlow 和其他框架。我们将解释写作时最有前途的技术背后的一些原则,并展示它们如何在机器学习流水线中应用的一些实际示例。

在本章中,我们将介绍隐私保护机器学习的三种主要方法:差分隐私、联邦学习和加密机器学习。

数据隐私问题

数据隐私关乎信任和限制人们希望保密的数据的曝光。有许多不同的隐私保护机器学习方法,为了在它们之间做出选择,您应该尝试回答以下问题:

  • 你试图保护数据免受谁的侵扰?

  • 系统的哪些部分可以保护隐私,哪些可以向世界公开?

  • 谁是可以查看数据的可信方?

这些问题的答案将帮助您决定本章描述的方法中哪一个最适合您的用例。

为什么我们关心数据隐私?

数据隐私正在成为机器学习项目的重要组成部分。有许多法律要求围绕用户隐私,例如 2018 年 5 月生效的欧盟通用数据保护条例(GDPR)和 2020 年 1 月生效的加利福尼亚消费者隐私法案。关于将个人数据用于机器学习存在伦理考虑,而使用由 ML 驱动的产品的用户开始关心其数据的去向。因为机器学习传统上需要大量数据,并且因为许多机器学习模型的预测基于从用户收集的个人数据,所以机器学习处于围绕数据隐私展开的辩论前沿。

在撰写时,隐私总是有成本的:增加隐私会带来模型精度、计算时间或两者的成本。在一个极端,不收集数据使得交互完全私密,但对机器学习来说完全无用。在另一个极端,了解一个人的所有细节可能会危及其隐私,但这使我们能够创建非常精确的机器学习模型。我们现在刚开始看到隐私保护 ML 的发展,其中可以增加隐私而不会如此大幅降低模型精度。

在某些情况下,隐私保护的机器学习可以帮助您使用由于隐私问题而无法用于训练机器学习模型的数据。然而,这并不意味着只要使用本章节中的某种方法,就可以随意处理数据。您应该与其他利益相关者讨论您的计划,例如数据所有者、隐私专家甚至公司的法律团队。

增加隐私的最简单方法

通常,建立基于机器学习的产品的默认策略是收集所有可能的数据,然后再决定哪些数据对训练特定模型有用。尽管这是在用户同意的情况下进行的,增加用户隐私的最简单方法是仅收集训练特定模型所需的数据。在结构化数据的情况下,诸如姓名、性别或种族等字段可以直接删除。文本或图像数据可以处理以删除大部分个人信息,例如从图像中删除面部或从文本中删除姓名。然而,在某些情况下,这可能会降低数据的效用或使其无法训练出准确的模型。如果不收集种族和性别的数据,就无法判断模型是否对特定群体存在偏见。

数据的收集控制也可以交给用户:收集数据的同意可以比简单的选择“接受”或“拒绝”更为细致,产品的使用者可以明确指定关于他们的数据可以收集哪些。这带来了设计挑战:提供较少数据的用户是否应该接收到比提供更多数据的用户更不准确的预测?我们如何通过机器学习管道跟踪同意?如何衡量我们模型中单个特征的隐私影响?这些都是机器学习社区需要更多讨论的问题。

哪些数据需要保持私密?

在机器学习管道中,数据通常来自人们,但某些数据更需要进行隐私保护的机器学习。个人识别信息(PII)是可以直接识别单个人的数据,例如他们的姓名、电子邮件地址、街道地址、ID 号码等,这些需要保持私密。PII 可能出现在自由文本中,例如反馈评论或客户服务数据,而不仅仅是在直接要求用户提供这些数据时。在某些情况下,人们的图像也可能被视为 PII。通常会有法律标准约束这一点——如果您的公司有隐私团队,最好在启动使用此类数据的项目之前咨询他们。

敏感数据也需要特别小心。这通常被定义为可能会对某人造成伤害的数据,例如健康数据或专有公司数据(例如财务数据)。在确保这类数据不会在机器学习模型的预测中泄露的过程中,应该小心谨慎。

另一类是准标识数据。如果已知足够多的准标识符,比如位置跟踪或信用卡交易数据,准标识数据可以唯一标识某人。如果了解了同一人的多个位置点,这就提供了一个可以与其他数据集组合以重新识别该人的唯一迹象。2019 年 12 月,《纽约时报》发表了关于使用手机数据重新识别的深度文章,这只是众多质疑释放此类数据的声音之一。

差分隐私

如果我们确定了机器学习流程中需要额外隐私的需求,有不同的方法可以在尽可能保留数据效用的同时增加隐私。我们将讨论的第一个方法是差分隐私 1。差分隐私(DP)是一个概念的形式化,即查询或数据集的转换不应揭示一个人是否在该数据集中。它通过添加噪音来给出一个人在数据集中的隐私损失的数学度量,并通过最小化这种隐私损失来减少它。

差分隐私描述了数据持有者或馆长向数据主体作出的承诺,这个承诺如下:“无论其他研究、数据集或信息来源如何,允许您的数据用于任何研究或分析都不会对您产生不利影响。”

辛西娅·德沃克 2

换句话说,尊重隐私的数据集转换,在从该数据集中删除一个人时不应该改变。对于机器学习模型而言,如果一个模型在设计时考虑了隐私,那么模型做出的预测在从训练集中删除一个人后不应该改变。差分隐私通过向转换添加某种形式的噪音或随机性来实现。

要给出一个更具体的例子,实现差分隐私的最简单方法之一是随机响应的概念,如图 14-1 所示。这在问卷调查中尤其有用,这些问卷会问一些敏感问题,比如“你是否曾被判犯罪?”被问者需要抛硬币来回答这个问题。如果硬币正面朝上,他们就如实回答。如果是反面,他们需要再抛一次,并根据硬币的结果回答“是”或“否”。这使他们能够否认,称他们给出的是随机答案而不是真实答案。由于我们知道硬币抛掷的概率,如果我们向很多人询问这个问题,我们可以相对准确地计算出犯罪记录的比例。当更多人参与调查时,计算的准确性会增加。

图 14-1. 随机响应流程图

这些随机变换是差分隐私的关键。

假设每个人只有一个训练样本。

在本章中,为简化起见,假设数据集中的每个训练样本都与一个独立的个人相关联或收集自个人。

本地和全局差分隐私

DP 可以分为两种主要方法:本地 DP 和全局 DP。在本地 DP 中,如前面的随机响应示例中一样,会在个体级别添加噪音或随机性,因此在个体和数据收集者之间保持隐私。在全局 DP 中,会对整个数据集的转换添加噪音。数据收集者信任原始数据,但转换结果不会泄露有关个人的数据。

全局 DP 要求我们相对于本地 DP 添加更少的噪音,这导致在类似的隐私保证下查询的效用或准确性有所提高。缺点是对于全局 DP,必须信任数据收集者,而对于本地 DP,只有个体用户看到他们自己的原始数据。

ε、δ 和隐私预算

实现 DP 的最常见方式可能是使用(ε-δ)DP。当比较包含特定个人的数据集上的随机转换结果与不包含该人的另一个结果时,描述这些转换结果之间的最大差异。因此,如果 ε 是 0,两个转换将完全返回相同的结果。如果 ε 的值较小,则这两个转换返回相同结果的概率较大;ε 值越小,隐私保证越强。如果多次查询数据集,则需要将每个查询的 ε 相加以获得总的隐私预算。

是不满足的概率,或者说个体数据在随机转换结果中暴露的概率。通常情况下,我们将设置为人口规模的倒数:例如,对于包含 2000 人的数据集,我们将设置为 1/1000。3

您应该选择多少的值作为 ε?这允许我们比较不同算法和方法的隐私性,但给出“足够”隐私的绝对值取决于使用案例。4

为了决定使用哪个值来设置 ε,可以查看系统在 ε 减小时的准确性。在保留业务问题的可接受数据效用的同时,选择尽可能私密的参数值。或者,如果泄露数据的后果非常严重,则可以先设置 ε 和 δ 的可接受值,然后调整其他超参数以获得最佳的模型准确性。DP 的一个缺点是 ε 不容易解释。正在开发其他方法来帮助解决这个问题,例如在模型的训练数据中种植秘密,并测量它们在模型预测中暴露的可能性。5

机器学习中的差分隐私

如果您想将 DP 作为机器学习流程的一部分使用,目前有几个可选项可以添加,尽管我们预计未来会看到更多选项。首先,DP 可以包含在联邦学习系统中(参见“联邦学习”),并且可以使用全局或本地 DP。其次,TensorFlow Privacy 库是全局 DP 的一个示例:原始数据可用于模型训练。

第三个选项是 Private Aggregation of Teacher Ensembles(PATE)方法。6 这是一个数据共享场景:如果有 10 个人拥有标记数据,但你没有,他们会在本地训练一个模型,并对你的数据进行预测。然后执行 DP 查询,以生成数据集中每个示例的最终预测,以便您不知道哪个模型生成了预测。然后,从这些预测中训练一个新模型——这个模型以一种不可能了解那些隐藏数据集的方式包含了这 10 个隐藏数据集的信息。PATE 框架展示了在这种情况下如何花费。

TensorFlow 隐私简介

TensorFlow 隐私(TFP)在模型训练期间将 DP 添加到优化器中。TFP 中使用的 DP 类型是全局 DP 的一个示例:在训练过程中添加噪声,以使私密数据不会暴露在模型的预测中。这使我们能够提供强大的 DP 保证,即个人数据未被记忆,同时最大化模型精度。如图 14-2 所示,在此情况下,原始数据可供信任的数据存储和模型训练者使用,但最终预测是不可信的。

图 14-2。DP 的受信方

使用差分私密优化器进行训练

优化器算法通过在每个训练步骤中向梯度添加随机噪声来进行修改。这比较了有或没有每个单独数据点的梯度更新,并确保无法确定特定数据点是否包含在梯度更新中。此外,梯度被剪裁,以防止它们变得过大——这限制了任一训练样本的贡献。作为额外的奖励,这也有助于防止过度拟合。

TFP 可以通过 pip 进行安装。在撰写本文时,它需要 TensorFlow 版本 1.X:

$ pip install tensorflow_privacy

我们从一个简单的tf.keras二元分类示例开始:

import``tensorflow``as``tf``model``=``tf``.``keras``.``models``.``Sequential``([``tf``.``keras``.``layers``.``Dense``(``128``,``activation``=``'relu'``),``tf``.``keras``.``layers``.``Dense``(``128``,``activation``=``'relu'``),``tf``.``keras``.``layers``.``Dense``(``1``,``activation``=``'sigmoid'``)``])

要求不同 ially privat 优化器与正常的tf.keras模型相比需要设置两个额外的超参数:噪声倍增器和 L2 范数剪辑。最好调整这些参数以适应您的数据集,并测量它们对:

NOISE_MULTIPLIER``=``2``NUM_MICROBATCHES``=``32LEARNING_RATE``=``0.01``POPULATION_SIZE``=``5760L2_NORM_CLIP``=``1.5``BATCH_SIZE``=``32EPOCHS``=``70

批处理大小必须与微批处理数量完全可除。

训练集中的示例数。

人口数量必须与批次大小完全可除。

接下来,初始化不同 ially privat 优化器:

from``tensorflow_privacy.privacy.optimizers.dp_optimizer \ import``DPGradientDescentGaussianOptimizer``optimizer``=``DPGradientDescentGaussianOptimizer``(``l2_norm_clip``=``L2_NORM_CLIP``,``noise_multiplier``=``NOISE_MULTIPLIER``,``num_microbatches``=``NUM_MICROBATCHES``,``learning_rate``=``LEARNING_RATE``)``loss``=``tf``.``keras``.``losses``.``BinaryCrossentropy``(``from_logits``=``True``,``reduction``=``tf``.``losses``.``Reduction``.``NONE``)

损失必须基于每个示例而不是整个小批量计算。

训练私密模型就像训练正常的tf.keras模型一样:

model``.``compile``(``optimizer``=``optimizer``,``loss``=``loss``,``metrics``=``[``'accuracy'``])``model``.``fit``(``X_train``,``y_train``,``epochs``=``EPOCHS``,``validation_data``=``(``X_test``,``y_test``),``batch_size``=``BATCH_SIZE``)

计算 Epsilon

现在,我们计算我们的模型和我们选择的噪声倍增器和梯度剪辑的差分隐私参数:

from``tensorflow_privacy.privacy.analysis``import``compute_dp_sgd_privacy``compute_dp_sgd_privacy``.``compute_dp_sgd_privacy``(``n``=``POPULATION_SIZE``,``batch_size``=``BATCH_SIZE``,``noise_multiplier``=``NOISE_MULTIPLIER``,``epochs``=``EPOCHS``,``delta``=``1e-4``)

Delta 的值设置为数据集大小的倒数,四舍五入到最接近的数量级。

TFP 仅支持 TensorFlow 1.X

我们展示如何将先前章节的示例项目转换为我们的GitHub 存储库中的 DP 模型。不过,在 TFX 管道中,TFP 支持 TensorFlow 2.X 之前,无法使用此模型。

此计算的最终输出,epsilon 的值,告诉我们特定模型的隐私保证强度。然后,我们可以探索如何改变先前讨论的 L2 范数剪辑和噪声乘数超参数会同时影响 epsilon 和模型精度。如果增加这两个超参数的值,而保持其他参数不变,epsilon 将减少(因此隐私保证变得更强)。在某个点上,精度将开始减少,模型将不再有用。可以探索这种权衡,以获得尽可能强大的隐私保证,同时仍保持有用的模型精度。

联邦学习

联邦学习(FL)是一个协议,其中机器学习模型的训练分布在许多不同的设备上,并在中央服务器上组合训练好的模型。关键点在于原始数据永远不会离开各自的设备,也永远不会集中到一个地方。这与在中心位置收集数据集然后训练模型的传统架构非常不同。

在移动电话具有分布数据或用户的浏览器的背景下,FL 通常非常有用。另一个潜在的用例是在分布在多个数据所有者之间的敏感数据共享方面。例如,AI 初创公司可能希望训练一个模型来检测皮肤癌。皮肤癌图像由许多医院拥有,但由于隐私和法律问题,它们不能集中在一个地方。FL 允许初创公司在数据不离开医院的情况下训练模型。

在联邦学习(FL)设置中,每个客户端接收模型架构和一些训练指令。每个客户端的设备上训练一个模型,并将权重返回到中央服务器。这在一定程度上增加了隐私性,因为拦截者很难从模型权重中了解用户的任何信息,而不是从原始数据中获取,但这并不能提供任何隐私保证。分发模型训练的步骤并不能为用户提供免于公司收集数据影响的隐私保护,因为公司通常可以通过了解模型架构和权重来推断原始数据可能的内容。

然而,使用 FL 来增加隐私还有一个非常重要的步骤:将权重安全聚合到中央模型中。有许多算法可以做到这一点,但它们都要求中央方信任不会在权重组合之前尝试检查权重。

图 14-3 显示了在 FL 设置中哪些方可以访问用户的个人数据。收集数据的公司可以建立安全平均值,以便他们不会看到用户返回的模型权重。中立的第三方也可以执行安全聚合。在这种情况下,只有用户可以看到他们的数据。

图 14-3. 联邦学习中的信任方

FL 的一个额外的隐私保护扩展是将 DP 整合到这一技术中。在这种情况下,DP 限制了每个用户对最终模型的贡献信息量。研究表明,如果用户数量庞大,生成的模型几乎和非 DP 模型一样准确。7 然而,目前尚未将其实施到 TensorFlow 或 PyTorch 中。

FL 在生产中的一个例子是Google 的 Gboard 安卓手机键盘。Google 能够训练模型以更好地预测下一个单词,而无需了解用户的私密消息。FL 在以下具有相似特征的使用案例中最为有用:8

  • 模型所需的数据只能从分布源收集。

  • 数据源数量庞大。

  • 数据在某种程度上是敏感的。

  • 数据不需要额外标记,标签直接由用户提供且不离开源。

  • 理想情况下,数据来自接近相同的分布。

FL 在设计机器学习系统时引入了许多新的考虑因素:例如,不是所有数据源都可能在一次训练运行和下一次之间收集了新数据,不是所有移动设备都一直开机,等等。收集的数据通常不平衡,且几乎每个设备都是独特的。当设备池庞大时,每次训练运行获取足够的数据是最容易的。任何使用 FL 的项目都必须开发新的安全基础设施。9

在训练 FL 模型的设备上需要注意避免性能问题。训练可能会迅速消耗移动设备的电量或造成大量数据使用,从而增加用户的费用。尽管移动电话的处理能力在迅速增加,但它们仍然只能训练小型模型,因此更复杂的模型应该在中央服务器上进行训练。

TensorFlow 中的联邦学习

TensorFlow Federated(TFF)模拟了 FL 的分布式设置,并包含一种可以在分布数据上计算更新的随机梯度下降(SGD)版本。传统的 SGD 要求在集中数据集的批次上计算更新,而在联邦设置中不存在这种集中数据集。截至撰写本文时,TFF 主要用于研究和实验新的联邦算法。

PySyft 是由 OpenMined 组织开发的用于隐私保护机器学习的开源 Python 平台。它包含使用安全多方计算来聚合数据的 FL 实现(在下一节中进一步解释)。最初开发时支持 PyTorch 模型,但已发布了TensorFlow 版本

加密机器学习

加密机器学习是隐私保护机器学习的另一个领域,目前在研究人员和实践者中受到广泛关注。它依赖于密码学社区的技术和研究成果,并将这些技术应用于机器学习。迄今为止已经采用的主要方法包括同态加密(HE)和安全多方计算(SMPC)。有两种使用这些技术的方式:加密已经在明文数据上训练过的模型,以及在训练期间保持数据加密状态的整个系统。

HE 类似于公钥加密,但不同之处在于在应用计算之前,数据不需要解密。可以对加密数据执行计算(例如从机器学习模型中获取预测)。用户可以以加密形式提供其数据,使用本地存储的加密密钥,然后接收加密预测结果,随后可以解密以获取模型在其数据上的预测。这为用户提供了隐私保护,因为他们的数据不会与训练模型的一方共享。

SMPC 允许多个参与方合并数据,在其上执行计算,并在自己的数据上查看计算结果,而无需了解其他参与方的数据。这是通过 秘密分享 实现的,即任何单个值被分割成发送到不同参与方的份额的过程。原始值无法从任何份额中重建,但仍然可以对每个份额进行个别计算。只有在重新组合所有份额后,计算的结果才有意义。

这些技术都有其成本。在撰写本文时,HE 很少用于训练机器学习模型:它导致训练和预测速度大幅减慢。因此,我们不再讨论 HE。SMPC 在网络传输时间方面也有开销,因为需要在各方之间传递份额和结果,但它比 HE 明显更快。这些技术与 FL 一起,适用于数据无法集中存储的情况。然而,它们无法阻止模型记忆敏感数据—差分隐私(DP)是解决这个问题的最佳方案。

TensorFlow 提供了针对加密机器学习的支持,通过 TF Encrypted(TFE)实现,主要由 Cape Privacy 开发。TFE 还可以提供用于 FL 所需的 安全聚合

加密模型训练

您可能希望使用加密机器学习的第一种情况是在加密数据上训练模型。当原始数据需要对训练模型的数据科学家保密,或者两个或更多方拥有原始数据并希望使用所有方的数据训练模型时,这是非常有用的。如图 14-4 所示,在这种情况下,只信任数据所有者或所有者。

图 14-4. 使用加密模型训练的受信任方

对于此用例,TFE 可用于训练加密模型。像往常一样使用 pip 安装:

$ pip install tf_encrypted

构建 TFE 模型的第一步是定义一个类,该类以批量方式提供训练数据。这个类由数据所有者(们)在本地实现。它使用装饰器转换为加密数据:

@tfe.local_computation

在 TFE 中编写模型训练代码几乎与常规 Keras 模型相同 —— 只需用 tfe 替换 tf

import``tf_encrypted``as``tfe``model``=``tfe``.``keras``.``Sequential``()``model``.``add``(``tfe``.``keras``.``layers``.``Dense``(``1``,``batch_input_shape``=``[``batch_size``,``num_features``]))``model``.``add``(``tfe``.``keras``.``layers``.``Activation``(``'sigmoid'``))

唯一的区别是必须向 Dense 的第一层提供 batch_input_shape 参数。

本文档提供了这方面的工作示例 TFE documentation。截至撰写本文时,TFE 尚未包含常规 Keras 的所有功能,因此我们无法以此格式展示我们的示例项目。

将训练好的模型转换为提供加密预测

TFE 的第二种有用场景是您希望为在明文数据上训练的加密模型提供服务。在这种情况下,如图 14-5 所示,您可以完全访问未加密的训练数据,但希望应用程序的用户能够接收私密预测。这为上传加密数据并接收加密预测的用户提供了隐私保护。

图 14-5. 在加密训练模型时的受信任方

此方法可能是当今机器学习管道的最佳选择,因为模型可以像正常训练一样进行训练并转换为加密版本。它还可用于使用 DP 训练的模型。与非加密模型的主要区别在于需要多个服务器:每个服务器都托管原始模型的一部分。如果任何人在一个服务器上查看模型的一部分或发送到任何一个服务器的数据的一部分,都不会泄露关于模型或数据的任何信息。

可通过以下方式将 Keras 模型转换为 TFE 模型:

tfe_model``=``tfe``.``keras``.``models``.``clone_model``(``model``)

在这种情况下,需要执行以下步骤:

  • 在客户端上加载和预处理数据。

  • 在客户端对数据进行加密。

  • 将加密数据发送到服务器。

  • 在加密数据上进行预测。

  • 将加密预测结果发送到客户端。

  • 在客户端解密预测结果并显示给用户。

TFE 提供了一系列笔记本,展示如何提供私密预测结果的方法,详见这里

其他数据隐私方法

还有许多其他技术可为包含在机器学习模型中的个人增强隐私保护。仅仅使用正则表达式和命名实体识别模型对文本数据进行清理(如姓名、地址、电话号码等),可能会非常简单。

K-匿名性

K-匿名性,通常简称为匿名化,在增强机器学习流程中的隐私保护方面并不是一个良好的选择。K-匿名性要求数据集中的每个个体在其准标识符(如性别、种族和邮政编码等间接识别个体的数据)方面与其他 k-1 个个体不可区分。这通常通过聚合或删除数据来实现,直到数据集满足此要求。这种数据的移除通常会显著降低机器学习模型的准确性。10

摘要

当您处理个人或敏感数据时,请选择最适合您需求的数据隐私解决方案,考虑谁可信、需要何种模型性能水平以及从用户那里获得了什么同意。

本章描述的所有技术都非常新颖,它们的实际应用还不广泛。不要认为使用本章描述的任何框架可以完全保护用户的隐私。为了在机器学习流程中增加隐私,通常需要大量的额外工程努力。隐私保护机器学习领域正在迅速发展,目前正在进行新的研究。我们鼓励您寻找该领域的改进,并支持诸如PySyftTFE等数据隐私的开源项目。

数据隐私和机器学习的目标通常是高度一致的,即我们希望了解整个人群并做出对每个人都同样有利的预测,而不是仅仅了解个体。添加隐私可以阻止模型过度拟合于某个人的数据。我们期望未来,在模型训练个人数据时,隐私将从一开始就被设计进机器学习流程中。

1   Cynthia Dwork,《差分隐私》,载于《密码学与安全百科全书》,编辑:Henk C. A. van Tilborg 和 Sushil Jajodia,波士顿:Springer,2006 年。

2   Cynthia Dwork 和 Aaron Roth,《差分隐私的算法基础》,《理论计算机科学基础和趋势》,第 9 卷,第 3-4 期:211–407,(2014),https://www.cis.upenn.edu/~aaroth/Papers/privacybook.pdf。

3   更多关于此背后数学的细节可以在 Dwork 和 Roth 的论文中找到,“差分隐私的算法基础”。

4   更多细节可以在 Justin Hsu 等人的论文中找到,“差分隐私:选择ε的经济方法”(论文展示,2014 年 IEEE 计算机安全基础对称研讨会,奥地利维也纳,2014 年 2 月 17 日),arxiv.org/pdf/1402.3329.pdf

5   Nicholas Carlini 等人,“秘密共享者”,2019 年 7 月。arxiv.org/pdf/1802.08232.pdf

6   Nicolas Papernot 等人,“半监督知识转移用于来自私有训练数据的深度学习”,2016 年 10 月,arxiv.org/abs/1610.05755

7   Robin C. Geyer 等人,“差分私有联邦学习:客户端视角”,2017 年 12 月,arxiv.org/abs/1712.07557

8   这一点在 H. Brendan McMahan 等人的论文中有更详细的讨论,“分散数据中深度网络的通信有效学习”,发表于第 20 届人工智能与统计学国际会议论文集,PMLR 54 (2017): 1273–82,arxiv.org/pdf/1602.05629.pdf

9   关于 FL 系统设计的更多细节,请参阅 Keith Bonawitz 等人的论文,“朝着规模化的联邦学习:系统设计”(演示,第 2 届 SysML 会议论文集,加利福尼亚州帕洛阿尔托,2019 年),arxiv.org/pdf/1902.01046.pdf

10   此外,“匿名化”数据集中的个体可以使用外部信息重新识别;详见 Luc Rocher 等人的论文,“使用生成模型估算不完整数据集中重新识别的成功率”,自然通讯 10, 文章号 3069 (2019),www.nature.com/articles/s41467-019-10933-3

第十五章:管道的未来和下一步

在过去的 14 章中,我们捕捉了机器学习管道的当前状态,并给出了如何构建它们的建议。机器学习管道是一个相对较新的概念,在这个领域还有很多内容等待探索。在本章中,我们将讨论一些我们认为重要但与当前管道不太契合的事物,并考虑 ML 管道的未来步骤。

模型实验跟踪

在本书的整个过程中,我们假设你已经进行了实验,并且模型架构基本上已经确定。然而,我们想分享一些关于如何跟踪实验并使实验过程更顺利的想法。你的实验过程可能包括探索潜在的模型架构、超参数和特征集。但无论你探索什么,我们想要强调的关键点是,你的实验过程应该与你的生产过程密切配合。

无论是手动优化模型还是自动调整模型,捕获和分享优化过程的结果都是必不可少的。团队成员可以快速评估模型更新的进展。同时,模型的作者可以收到执行实验的自动记录。良好的实验跟踪有助于数据科学团队提高效率。

实验跟踪还增加了模型的审计追踪,并可能作为对潜在诉讼的防范措施。如果一个数据科学团队面临是否在训练模型时考虑了边缘案例的问题,实验跟踪可以帮助追踪模型参数和迭代过程。

实验跟踪工具包括Weights and BiasesSacred。图 15-1 展示了 Weights and Biases 在使用中的示例,显示了每个模型训练运行的损失随训练轮次的变化。有许多不同的可视化方式可供选择,我们可以存储每个模型运行的所有超参数。

图 15-1. Weights and Biases 中的实验跟踪

在未来,我们预计实验和生产过程将更紧密地联系在一起,这样数据科学家可以顺利地从尝试新的模型架构转换到将其添加到他们的流水线中。

对模型发布管理的思考

在软件工程中,已经建立了版本控制和发布管理的成熟流程。可能造成向后不兼容的重大更改会导致主版本更改(例如从 0.x 到 1.0)。较小的功能增加会导致次要版本更改(1.0 到 1.1)。但这在机器学习领域意味着什么?从一个机器学习模型到下一个,数据的输入格式可能相同,预测结果的输出格式也保持不变,因此没有破坏性变化。管道仍然运行;不会抛出错误。但是新模型的性能可能与之前完全不同。机器学习管道的标准化要求模型版本控制实践。

我们建议以下模型发布管理策略:

  • 如果更改输入数据,则模型版本进行次要更改。

  • 如果改变了超参数,则模型版本进行主要更改。这包括网络中的层次数或层中的节点数。

  • 如果完全改变了模型架构(例如从递归神经网络[RNN]到 Transformer 架构),这将成为一个全新的管道。

模型验证步骤控制发布是否发生,通过验证新模型的性能是否优于先前模型的性能。在撰写本文时,TFX 管道仅使用此步骤中的单一指标。我们预期验证步骤将在未来变得更加复杂,包括其他因素,如推断时间或数据不同切片上的准确性。

未来管道能力

在本书中,我们记录了撰写时的机器学习管道状态。但是未来的机器学习管道将会是什么样子?我们希望看到的一些功能包括:

  • 隐私和公平成为首要关注:在撰写本文时,假设管道不包括保护隐私的机器学习。包含公平性分析,但 ModelValidator 步骤只能使用整体指标。

  • FL 的整合,正如我们在第十四章中讨论的那样。如果数据预处理和模型训练发生在大量个体设备上,机器学习管道将需要与本书描述的非常不同。

  • 测量我们管道的碳排放能力。随着模型变得越来越大,它们的能源消耗变得显著。尽管这在实验过程中通常更相关(特别是在搜索模型架构时),将排放追踪集成到管道中将非常有用。

  • 数据流的摄取:在本书中,我们仅考虑了在数据批次上训练的管道。但随着更复杂的数据管道,机器学习管道应该能够消耗数据流。

未来的工具可能进一步抽象本书中的一些过程,我们预期未来的流水线将更加顺畅易用且更加自动化。

我们还预测未来的流水线将需要解决其他类型的机器学习问题。我们仅讨论了监督学习,几乎完全是分类问题。从监督分类问题开始是有道理的,因为这些问题是最容易理解并纳入流水线的一些问题。回归问题以及其他类型的监督学习,如图像字幕或文本生成,将很容易替换成我们在本书中描述的流水线的大多数组件。但是强化学习问题和无监督问题可能不太适合。这些在生产系统中仍然很少见,但我们预计它们将在未来变得更加普遍。我们流水线的数据摄入、验证和特征工程组件应该仍然可以处理这些问题,但是训练、评估和验证部分将需要进行重大改变。反馈循环也将有所不同。

TFX 与其他机器学习框架

未来的机器学习流水线还可能包括对底层机器学习框架的开放性,这样数据科学家就不需要在在 TensorFlow、PyTorch、scikit-learn 或任何其他未来框架之间做出选择。

很高兴看到 TFX 正朝着去除对纯 TensorFlow 依赖的方向发展。正如我们在第四章中讨论的那样,一些 TFX 组件可以与其他 ML 框架一起使用。其他组件正在进行过渡,以允许与其他 ML 框架集成。例如,Trainer组件现在提供了一个执行器,可以独立于 TensorFlow 训练模型。我们希望能看到更多能够轻松集成 PyTorch 或 scikit-learn 等框架的通用组件。

测试机器学习模型

机器学习工程中一个新兴的话题是机器学习模型的测试。这里,我们指的不是模型验证,如我们在第七章中讨论的那样,而是模型推断的测试。这些测试可以是模型的单元测试,也可以是模型与应用程序交互的完整端到端测试。

以及测试系统是否端到端运行,其他测试可能围绕以下内容展开:

  • 推断时间

  • 内存消耗

  • 移动设备的电池消耗

  • 模型大小与准确性之间的权衡

我们期待软件工程中的最佳实践与数据科学实践融合,模型测试将成为其中的一部分。

用于机器学习的 CI/CD 系统

随着机器学习流水线在未来几个月变得更加简化,我们将看到机器学习流水线向更完整的 CI/CD 工作流迈进。作为数据科学家和机器学习工程师,我们可以从软件工程工作流中学习。例如,我们期待在 ML 流水线中更好地集成数据版本控制,或者采用促进机器学习模型部署回滚的最佳实践。

机器学习工程社区

随着机器学习工程领域的形成,围绕该主题的社区将至关重要。我们期待与机器学习社区分享最佳实践、定制组件、工作流程、使用案例和流水线设置。我们希望本出版物对新兴领域做出一点贡献。与软件工程中的 DevOps 类似,我们希望看到更多的数据科学家和软件工程师对机器学习工程学科产生兴趣。

总结

本书包含了我们如何将您的机器学习模型转化为流畅流水线的建议。图 15-2 展示了我们认为在撰写时最为必要的所有步骤和我们认为最佳的工具。我们鼓励您对这个主题保持好奇心,跟随新的发展,并为围绕机器学习流水线的各种开源努力做出贡献。这是一个非常活跃发展的领域,新的解决方案经常发布。

图 15-2. 机器学习流水线架构

图 15-2 具有三个极为重要的特性:它是自动化的、可扩展的和可重现的。由于它是自动化的,它解放了数据科学家维护模型的时间,让他们有时间尝试新的模型。由于它是可扩展的,它能够扩展以处理大量数据。由于它是可重现的,一旦您在基础架构上为一个项目设置好,构建第二个项目将变得容易。这些都是成功的机器学习流水线所必需的。

附录 A. 机器学习基础设施简介

本附录简要介绍了一些最有用的机器学习基础设施工具:以 Docker 或 Kubernetes 的形式的容器。虽然这可能是您将流水线交给软件工程团队的时候,但对于任何构建机器学习流水线的人来说,了解这些工具是非常有用的。

什么是容器?

所有 Linux 操作系统都基于文件系统,或包括所有硬盘和分区的目录结构。从这个文件系统的根目录(表示为 /)可以访问几乎所有 Linux 系统的方面。容器创建一个新的、更小的根目录,并将其用作在更大的主机内部的“小型 Linux”。这使您可以拥有一整套专用于特定容器的库。此外,容器还允许您控制每个容器的 CPU 时间或内存等资源。

Docker 是一个用户友好的 API,用于管理容器。使用 Docker 可以多次构建、打包、保存和部署容器。它还允许开发者在本地构建容器,然后发布到一个中央注册表,其他人可以从中拉取并立即运行容器。

依赖管理在机器学习和数据科学中是一个重大问题。无论您是在 R 还是 Python 中编写代码,几乎总是依赖于第三方模块。这些模块经常更新,可能会在版本冲突时对您的流水线造成破坏性变化。通过使用容器,您可以预先打包您的数据处理代码以及正确的模块版本,避免这些问题。

Docker 简介

要在 Mac 或 Windows 上安装 Docker,请访问 docs.docker.com/install,并下载适合您操作系统的最新稳定版 Docker Desktop。

对于 Linux 操作系统,Docker 提供了一个非常方便的脚本,只需几个命令即可安装 Docker:

$ curl -fsSL https://get.docker.com -o get-docker.sh $ sudo sh get-docker.sh

您可以使用以下命令测试 Docker 安装是否正常工作:

docker run hello-world

Docker 镜像简介

Docker 镜像是容器的基础,由对根文件系统的一系列更改和运行容器的执行参数组成。在运行之前,必须先“构建”镜像。

Docker 镜像背后一个有用的概念是存储层。构建镜像意味着为您的包安装一个几乎完整的专用 Linux 操作系统。为了避免每次都运行这个操作,Docker 使用了一个分层文件系统。其工作方式如下:如果第一层包含文件 A 和 B,第二层添加文件 C,结果的文件系统会显示 A、B 和 C。如果我们想创建第二个使用文件 A、B 和 D 的镜像,只需要更改第二层以添加文件 D。这意味着我们可以有基础镜像拥有所有基本软件包,然后我们可以专注于针对您的镜像的特定更改,如图 A-1 所示。

图 A-1. 分层文件系统示例

Docker 镜像名称称为标签。它们遵循模式docker registry``/``docker 命名空间``/``镜像名称``:``标签。例如,docker.io/tensorflow/tensorflow:nightly 将指向 DockerHub 中 tensorflow 命名空间中的 tensorflow 镜像。标签通常用于标记特定镜像的版本。在我们的示例中,标签 nightly 保留用于 TensorFlow 的每夜构建版本。

Docker 镜像是根据Dockerfile构建的。Dockerfile 中的每一行都以几个子句之一开头。最重要的是:

FROM

指示要构建的 Docker 基础容器。我们总是需要使用这个子句。可以下载许多基础容器,比如ubuntu

RUN

运行 bash。这是大多数 Docker 镜像的核心。在这里,我们希望执行包安装、目录创建等操作。因为每行操作都会在镜像中创建一个层,所以最好将包安装和其它的长时间任务作为 Dockerfile 中的最前面行之一。这意味着在重建过程中,Docker 将尝试使用缓存中的层。

ARG

构建参数。如果您想要拥有同一镜像的多个版本,比如开发和生产版本,这很有用。

COPY

从上下文中复制文件。上下文路径是docker build中使用的参数。上下文是一组本地文件,在构建过程中会暴露给 Docker,它只在过程中使用这些文件。这可以用来将源代码复制到容器中。

ENV

设置环境变量。这个变量将成为镜像的一部分,将在构建和运行中可见。

CMD

这是容器的默认命令。在 Docker 中的一个良好实践是每个容器运行一个命令。Docker 将监视该命令,当命令退出时退出,并将其标准输出(STDOUT)发布到docker logs。指定此命令的另一种方法是使用 ENTRYPOINT。这两者之间有几个细微差别,但这里我们将关注CMD

用户

容器中的默认用户。这与主机系统用户不同。如果要以某个用户身份运行命令,则应在构建过程中创建该用户。

工作目录

图像中的默认目录。这将是执行默认命令的目录。

暴露

指定容器将使用的端口。例如,HTTP 服务应该有 EXPOSE 80

构建你的第一个 Docker 镜像

让我们来构建我们的第一个镜像!

首先,我们需要为我们的小型 Docker 项目创建一个新目录:

$ mkdir hello-docker $ cd hello-docker

在这个目录中,创建一个名为 Dockerfile 的文件,内容如下:

FROM ubuntu RUN apt-get update RUN apt-get -y install cowsay CMD /usr/games/cowsay "Hello Docker"

要构建它,使用命令 docker build . -t hello-docker-t 标志指定了这个镜像的标签。你会看到一系列在容器中运行的命令。我们镜像中的每一层(对应于 Dockerfile 中的每个命令)在上一个层的临时容器中被调用。差异被保存下来,最终得到一个完整的镜像。第一层(我们不构建的那一层)基于 Ubuntu Linux。Dockerfile 中的 FROM 命令告诉 Docker 从注册表(在我们的情况下是 DockerHub)拉取这个镜像,并将其作为基础镜像使用。

构建完成后,调用 docker images 应该显示类似以下的内容:

REPOSITORY      TAG        IMAGE ID          CREATED          SIZE hello-docker    latest     af856e494ed4      2 minutes ago    155MB ubuntu          latest     94e814e2efa8      5 weeks ago      88.9MB

我们应该能看到基础 Ubuntu 镜像和我们的新镜像。

即使我们已经构建了这个镜像,这并不意味着它已经可以使用了。下一步是运行这个镜像。docker run 可能是 Docker 中最重要的命令。它从现有镜像创建一个新容器(或者,如果系统上不存在镜像,则尝试从注册表拉取)。为了运行我们的镜像,我们应该调用 docker run -it hello-docker。这应该显示我们的 cowsay 命令的输出。

Docker 注册表

Docker 的一个重要优势之一是方便地发布构建好的镜像。Docker 镜像的存储库称为注册表。默认的 Docker 注册表叫做 DockerHub,由 Docker, Inc. 支持。在 DockerHub 上注册账号是免费的,并且允许你将公共镜像推送到上面。

深入了解 Docker CLI

Docker CLI 是在本地机器上与镜像和容器交互的主要方式。在本节中,我们将讨论它的最重要的命令和选项。让我们从 docker run 开始。

我们可以传递许多重要的选项给 docker run。通过这些选项,我们可以覆盖 Dockerfile 中设置的大多数选项。这很重要,因为许多 Docker 镜像会有一个基本的默认命令,但通常这并不完全符合我们的运行需求。让我们来看看我们的 cowsay 示例:

docker run -it hello-docker /usr/games/cowsay "Our own message"

图像标记之后的参数将覆盖我们在 Dockerfile 中设置的默认命令。这是指定我们自己的命令行标志给默认二进制文件的最佳方式。docker run 的其他有用标志包括:

-it

意味着 “交互式”(i)和 tty(t),允许我们与从我们的 shell 运行的命令进行交互。

-v

将 Docker 卷或主机目录挂载到容器中,例如包含数据集的目录。

-e

通过环境变量传递配置。例如,docker run -e MYVARNAME=value image 将在容器中创建 MYVARNAME 环境变量。

-d

允许容器以分离模式运行,非常适合长时间运行的任务。

-p

将主机端口转发到容器,以允许外部服务通过网络与容器交互。例如,docker run -d -p 8080:8080 imagename 将主机的 localhost:8080 转发到容器的端口 8080。

DOCKER COMPOSE

当你开始挂载目录、管理容器链接等时,docker run 可以变得相当复杂。Docker Compose 是一个项目,旨在帮助处理这些问题。它允许你创建一个 docker-compose.yaml 文件,在其中可以为任意数量的容器指定所有 Docker 选项。然后,你可以通过网络连接容器,或者挂载相同的目录。

其他有用的 Docker 命令包括:

docker ps

显示所有正在运行的容器。若要显示已退出的容器,添加 -a 标志。

docker images

列出机器上存在的所有镜像。

docker inspect 容器 ID

允许我们详细检查容器的配置。

docker rm

删除容器。

docker rmi

删除镜像。

docker logs

显示容器产生的 STDOUT 和 STDERR 信息,非常有助于调试。

docker exec

允许你在运行的容器内部调用命令。例如,docker exec -it 容器 ID bash 允许你使用 bash 进入容器环境并进行内部检查。-it 标志的工作方式与 docker run 中的相同。

Kubernetes 介绍

到目前为止,我们只讨论了在单台机器上运行的 Docker 容器。如果你想要扩展规模呢?Kubernetes 是一个开源项目,最初由 Google 开发,用于管理基础设施的调度和扩展。它动态地将负载扩展到多台服务器,并跟踪计算资源。Kubernetes 还通过将多个容器放置在一台机器上(取决于它们的大小和需求),并管理容器之间的通信来最大化效率。它可以在任何云平台上运行,如 AWS、Azure 或 GCP。

一些 Kubernetes 定义

Kubernetes 的入门中最难的部分之一是术语。以下是一些定义,帮助你理解:

集群

一个集群是包含控制 Kubernetes API 服务器的中心节点和许多工作节点的一组机器。

节点

一个节点是集群中的单个机器(可以是物理机器或虚拟机器)。

Pod

一个 Pod 是一组在同一节点上运行的容器。通常,一个 Pod 只包含一个容器。

Kubelet

kubelet 是每个工作节点上管理与中心节点通信的 Kubernetes 代理。

服务

服务是一组 Pod 和访问它们的策略。

卷是同一 Pod 中所有容器共享的存储空间。

命名空间

命名空间是将物理集群空间划分为不同环境的虚拟集群。例如,我们可以将集群划分为开发环境、生产环境或不同团队的环境。

ConfigMap

ConfigMap 为在 Kubernetes 中存储非机密配置信息(环境变量、参数等)提供 API。ConfigMap 有助于将配置与容器映像分离。

kubectl

kubectl 是 Kubernetes 的命令行工具。

使用 Minikube 和 kubectl 入门

我们可以使用一个名为 Minikube 的工具创建一个简单的本地 Kubernetes 集群。Minikube 可以在任何操作系统上轻松设置 Kubernetes。它创建一个虚拟机,在其上安装 Docker 和 Kubernetes,并添加一个连接的本地用户。

不要在生产环境中使用 Minikube

Minikube 不应在生产环境中使用;它设计用于快速而轻松的本地环境。获取生产质量的 Kubernetes 集群的最简单方法是从任何主要公共云提供商购买托管的 Kubernetes 服务。

首先,安装 kubectl,即 Kubernetes 的命令行工具。

对于 Mac 用户,可以使用brew安装 kubectl:

brew install kubectl

对于 Windows 用户,请参阅其资源

对于 Linux:

curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.14.0/bin/linux/amd64/kubectl chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl

要安装 Minikube,首先需要安装一个创建和运行虚拟机的 hypervisor,比如VirtualBox

在 Mac 上,可以使用brew安装 Minikube:

brew install minikube

对于 Windows 用户,请参阅资源

对于 Linux 机器,请使用以下步骤:

curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 chmod +x minikube sudo cp minikube /usr/local/bin && rm minikube

安装完成后,可以通过一个简单的命令启动一个简单的 Kubernetes 集群:

minikube start

要快速检查 Minikube 是否已准备就绪,可以尝试列出集群中的节点:

kubectl get nodes

与 Kubernetes CLI 交互

Kubernetes API 基于资源。几乎 Kubernetes 世界中的所有内容都表示为资源。kubectl就是基于这一理念构建的,因此它在大多数资源交互中遵循类似的模式。

例如,列出所有 Pod 的典型kubectl调用为:

kubectl get pods

这应该产生一个所有运行中的 pod 的列表,但因为我们还没有创建任何 pod,所以列表是空的。这并不意味着我们的集群上当前没有运行的 pod。Kubernetes 将其内部服务运行在名为 kube-system 的 namespace 中。要列出任何 namespace 中的所有 pod,可以使用 -n 选项:

kubectl get pods -n kube-system

这应该返回多个结果。我们还可以使用 --all-namespaces 显示所有 namespace 中的所有 pod。

您可以仅使用名称显示一个 pod:

kubectl get po mypod

您还可以通过标签进行过滤。例如,此调用应显示所有在 kube-system 中具有标签 component=etcd 值的 pod:

kubectl get po -n kube-system -l component=etcd

get 显示的信息也可以修改。例如:

# 显示 pod 的节点和地址。 kubectl get po -n kube-system -o wide # 显示名为 mypod 的 pod 的 yaml 定义。 kubectl get po mypod -o yaml

要创建新资源,kubectl 提供两个命令:createapply。不同之处在于,create 将始终尝试创建新资源(如果已存在则失败),而 apply 将创建或更新现有资源。

创建新资源最常见的方法是使用包含资源定义的 YAML(或 JSON)文件,我们将在下一节中看到。以下 kubectl 命令允许我们创建或更新 Kubernetes 资源(例如 pod):

# 创建在 pod.yaml 中定义的 pod。 kubectl create -f pod.yaml # 这也可以与 HTTP 一起使用。 kubectl create -f http://url-to-pod-yaml # apply 允许对资源进行更改。 kubectl apply -f pod.yaml

要删除资源,请使用 kubectl delete

# 删除名为 foo 的 pod。 kubectl delete pod foo # 删除在 pods.yaml 中定义的所有资源。 kubectl delete -f pods.yaml

您可以使用 kubectl edit 快速更新现有资源。这将打开一个编辑器,您可以在其中编辑加载的资源定义:

kubectl edit pod foo

定义 Kubernetes 资源

Kubernetes 资源通常定义为 YAML(虽然也可以使用 JSON)。基本上,所有资源都是具有几个关键部分的数据结构。

apiVersion

每个资源都是由 Kubernetes 本身或第三方提供的 API 的一部分。版本号显示 API 的成熟度。

kind

资源类型(例如 pod、volume 等)。

metadata

任何资源所需的数据。

name

每个资源都可以通过查询的关键字来查询,这个关键字必须是唯一的。

labels

每个资源可以有任意数量的键值对,称为标签。然后可以将这些标签用于选择器,用于查询资源,或仅作为信息。

annotations

仅作信息用途的次要键值对,不能用于查询或选择器。

namespace

显示资源属于特定命名空间或团队的标签。

spec

资源的配置。所有实际运行所需的信息都应该在 spec 中。每个 spec 模式都是特定资源类型的唯一。

这是一个使用这些定义的示例 .yaml 文件:

apiVersion:v1 kind:Pod metadata:name:myapp-pod labels:app:myapp spec:containers:— name:`myapp-container     image: busybox     command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']

在这个文件中,我们有 apiVersionkind,定义了这个资源是什么。我们有指定名称和标签的 metadata,还有构成资源主体的 spec。我们的 Pod 包含一个单一容器,在镜像 busybox 中运行命令 sh -c echo Hello Kubernetes! && sleep 3600

将应用程序部署到 Kubernetes

在本节中,我们将通过 Minikube 完整部署一个功能性的 Jupyter Notebook。我们将为我们的笔记本创建一个持久卷,并创建一个 NodePort 服务,以允许我们访问我们的笔记本。

首先,我们需要找到正确的 Docker 镜像。jupyter/tensorflow-notebook 是由 Jupyter 社区维护的官方镜像。接下来,我们需要找出我们的应用程序将监听的端口:在这种情况下,它是 8888(Jupyter Notebooks 的默认端口)。

我们希望我们的笔记本在会话之间持久存在,因此我们需要使用 PVC(持久卷声明)。我们创建一个 pvc.yaml 文件来为我们完成这个任务:

kind:PersistentVolumeClaim apiVersion:v1 metadata:name:notebooks spec:accessModes:— ReadWriteOnce resources:requests:storage:3Gi

现在我们可以通过调用以下命令来创建此资源:

kubectl apply -f pvc.yaml

这应该创建一个卷。要确认,我们可以列出所有的卷和 PVC:

kubectl get pv kubectl get pvc kubectl describe pvc notebooks

接下来,我们创建我们的 deployment .yaml 文件。我们将有一个 Pod,它将挂载我们的卷并暴露端口 8888:

apiVersion:apps/v1 kind:Deployment metadata:name:jupyter labels:app:jupyter spec:selector:matchLabels:app:jupyter template:metadata:labels:app:jupyter spec:containers:— image:jupyter/tensorflow-notebook ![](https://github.com/OpenDocCN/ibooker-ml-zh/raw/master/docs/bd-ml-ppl/img/00075.jpg)name:jupyter ports:— containerPort:8888           name: jupyter volumeMounts:— name:notebooks           mountPath: /home/jovyan volumes:— name:notebooks         persistentVolumeClaim:claimName:`notebooks

非常重要的是,此选择器必须与模板中的标签匹配。

我们的镜像。

通过应用此资源(与 PVC 相同的方式),我们将创建一个带有 Jupyter 实例的 Pod:

# 让我们看看我们的部署是否准备就绪。 kubectl get deploy # 列出属于此应用程序的 Pod。 kubectl get po -l app``=``jupyter

当我们的 Pod 处于 Running 状态时,我们应该获取一个令牌,用于连接到我们的笔记本。此令牌将出现在日志中:

kubectl logs deploy/jupyter

要确认 Pod 是否正常工作,让我们使用 port-forward 访问我们的笔记本:

# 首先我们需要为我们的 Pod 命名; 它将具有随机后缀。 kubectl get po -l app``=``jupyter kubectl port-forward jupyter-84fd79f5f8-kb7dv 8888:8888

有了这个,我们应该能够在 localhost:8888 访问笔记本。问题是,其他人无法访问,因为它是通过我们的本地 kubectl 代理的。让我们创建一个 NodePort 服务来让我们访问这个笔记本:

apiVersion``: v1 kind``: Service metadata``:``name``: jupyter-service labels``:``app``: jupyter spec``:``ports``:``— port``: 8888       nodePort: 30888 selector``:``app``: jupyter type``: NodePort

当这个创建完成后,我们应该能够访问我们的 Jupyter!但首先,我们需要找到我们的 Pod 的 IP 地址。我们应该能够通过这个地址和端口 30888 访问 Jupyter:

minikube ip # 这将显示我们的 kubelet 地址是什么。 192.168.99.100:30888

其他人现在可以通过获取的 IP 地址和服务端口访问 Jupyter Notebook(见 Figure A-2)。一旦您在浏览器中访问该地址,您应该能看到 Jupyter Notebook 实例。

图 A-2. 运行在 Kubernetes 上的 Jupyter Notebook

这是关于 Kubernetes 及其部件的简要概述。Kubernetes 生态系统非常广泛,简短的附录无法提供全面的概述。有关 Kubeflow 底层架构 Kubernetes 的更多详细信息,我们强烈推荐 O’Reilly 出版的《Kubernetes: Up and Running》一书,作者是 Brendan Burns 等人。

附录 B. 在 Google Cloud 上设置 Kubernetes 集群

本附录简要介绍了如何在 Google Cloud 上创建一个能够运行我们示例项目的 Kubernetes 集群。如果您对 Kubernetes 还不熟悉,请查看附录 A 以及我们在第九章末尾推荐的阅读内容。虽然我们将涵盖的确切命令仅适用于 Google Cloud,但是与其他托管 Kubernetes 服务如 AWS EKS 或 Microsoft Azure 的 AKS 相比,整体设置流程是相同的。

在开始之前

对于接下来的安装步骤,我们假设您已经拥有 Google Cloud 的账户。如果您没有账户,您可以创建一个。此外,我们假设您已经在本地计算机上安装了 Kubernetes kubectl(客户端版本 1.18.2 或更高版本),并且您也可以执行 Google Cloud 的 SDK gcloud(版本 289.0.0 或更高版本)。

注意您的云基础设施成本

经营 Kubernetes 集群可能会累积显著的基础设施成本。因此,我们强烈建议通过设置账单警报和预算来监控您的基础设施成本。详情请参阅Google Cloud 文档。我们还建议关闭空闲的计算实例,因为它们即使处于空闲状态并且没有在计算管道任务时也会产生成本。

如何为您的操作系统安装 kubectl 客户端的详细步骤可以在Kubernetes 文档中找到。Google Cloud 文档提供了如何为您的操作系统安装他们的客户端的逐步详细说明。

Kubernetes 在 Google Cloud 上

在接下来的五个部分中,我们将逐步介绍如何使用 Google Cloud 从头开始创建 Kubernetes 集群的过程。

选择 Google Cloud 项目

对于 Kubernetes 集群,我们需要在Google Cloud 项目仪表板中创建一个新的 Google Cloud 项目或选择一个现有项目。

请注意接下来的步骤需要项目 ID。我们将在名为oreilly-book的项目中部署我们的集群,如图 B-1 所示。

图 B-1. Google Cloud 项目仪表板

设置您的 Google Cloud 项目

在创建 Kubernetes 集群之前,让我们设置您的 Google Cloud 项目。在您操作系统的终端中,您可以通过以下命令对您的 Google Cloud SDK 客户端进行身份验证:

$ gcloud auth login

然后更新 SDK 客户端:

$ gcloud components update

在成功进行身份验证并更新 SDK 客户端之后,让我们配置一些基础设置。首先,我们将把 GCP 项目设置为默认项目,并选择一个计算区域作为默认区域。在我们的示例中,我们选择了 us-central-1。您可以在Google Cloud 文档中找到所有可用区域的列表。选择一个最接近您物理位置或所需的 Google Cloud 服务可用的区域(并非所有服务在所有区域都可用)。

通过设置这些默认值,我们在后续命令中无需再指定它们。我们还将请求启用 Google Cloud 的容器 API。此最后一步仅需每个项目执行一次:

$ export PROJECT_ID``=``<``your gcp project id``> $ export GCP_REGION``=``us-central1-c $ gcloud config set project $PROJECT_ID``$ gcloud config set compute/zone $GCP_REGION``$ gcloud services enable container.googleapis.com

使用前一步骤中的项目 ID 替换。

选择您偏好的区域或地域。

启用 API。

创建 Kubernetes 集群

我们的 Google Cloud 项目准备就绪后,现在可以创建一个 Kubernetes 集群,该集群包含一些计算节点作为集群的一部分。在我们的示例集群 kfp-oreilly-book 中,我们允许集群在名为 kfp-pool 的池中的任意时间点运行零到五个节点,并且期望的可用节点数量为三个。我们还为集群分配了一个服务账号。通过服务账号,我们可以控制来自集群节点的请求的访问权限。要了解更多有关 Google Cloud 服务账号的信息,请查看在线文档

$ export CLUSTER_NAME``=``kfp-oreilly-book $ export POOL_NAME``=``kfp-pool $ export MAX_NODES``=``5 $ export NUM_NODES``=``3 $ export MIN_NODES``=``0 $ export SERVICE_ACCOUNT``=``service-account@oreilly-book.iam.gserviceaccount.com

现在在环境变量中定义了集群参数,我们可以执行以下命令:

$ gcloud container clusters create $CLUSTER_NAME``\ --zone $GCP_REGION``\ --machine-type n1-standard-4 \ --enable-autoscaling \ --min-nodes``=``$MIN_NODES``\ --num-nodes``=``$NUM_NODES``\ --max-nodes``=``$MAX_NODES``\ --service-account``=``$SERVICE_ACCOUNT

对于我们的演示流水线,我们选择了实例类型 n1-standard-4,每个节点提供 4 个 CPU 和 15 GB 内存。这些实例提供足够的计算资源来训练和评估我们的机器学习模型及其数据集。您可以通过运行以下 SDK 命令找到所有可用实例类型的完整列表:

$ gcloud compute machine-types list

如果你想在集群中添加 GPU,你可以通过添加 accelerator 参数来指定 GPU 类型和数量,如下例所示:

$ gcloud container clusters create $CLUSTER_NAME``\ ...       --accelerator``=``type``=``nvidia-tesla-v100,count``=``1

创建 Kubernetes 集群可能需要几分钟,直到所有资源完全分配给你的项目并可用为止。这个时间取决于你请求的资源和节点数。对于我们的演示集群,你可以期待等待约 5 分钟,直到所有资源都可用。

使用 kubectl 访问你的 Kubernetes 集群

当你的新创建的集群可用时,你可以设置你的kubectl来访问这个集群。Google Cloud SDK 提供了一个命令来注册这个集群到你本地的kubectl配置:

$ gcloud container clusters get-credentials $CLUSTER_NAME --zone $GCP_REGION

在更新kubectl配置后,你可以通过运行以下命令来检查是否选择了正确的集群:

$ kubectl config current-context gke_oreilly-book_us-central1-c_kfp-oreilly-book

使用你的 Kubernetes 集群与 kubectl

因为你的本地kubectl可以连接到远程的 Kubernetes 集群,所有像我们在下面提到的 Kubeflow Pipelines 步骤和 第十二章 中提到的命令都将在远程集群上执行:

$ export PIPELINE_VERSION``=``0.5.0 $ kubectl apply -k "github.com/kubeflow/pipelines/manifests/kustomize/"``\``"cluster-scoped-resources?ref=``$PIPELINE_VERSION``"``$ kubectl wait --for condition``=``established \ --timeout``=``60s crd/applications.app.k8s.io $ kubectl apply -k "github.com/kubeflow/pipelines/manifests/kustomize/"``\``"env/dev?ref=``$PIPELINE_VERSION``"

Kubeflow Pipelines 的持久卷设置

在 “通过持久卷交换数据”,我们将讨论在我们的 Kubeflow Pipelines 设置中设置持久卷的过程。持久卷及其声明的完整配置可以在以下代码块中看到。所展示的设置是针对 Google Cloud 环境的。

示例 B-1 展示了我们 Kubernetes 集群的持久卷配置:

示例 B-1. 持久卷配置

apiVersion:v1 kind:PersistentVolume metadata:name:tfx-pv namespace:kubeflow annotations:kubernetes.io/createdby:gce-pd-dynamic-provisioner pv.kubernetes.io/bound-by-controller:"yes" pv.kubernetes.io/provisioned-by:kubernetes.io/gce-pd spec:accessModes:- ReadWriteOnce capacity:storage:20Gi claimRef:apiVersion:v1 kind:PersistentVolumeClaim name:tfx-pvc namespace:kubeflow gcePersistentDisk:fsType:ext4 pdName:tfx-pv-disk nodeAffinity:required:nodeSelectorTerms:- matchExpressions:- key:failure-domain.beta.kubernetes.io/zone operator:In values:- us-central1-c - key:failure-domain.beta.kubernetes.io/region operator:In values:- us-central1 persistentVolumeReclaimPolicy:Delete storageClassName:standard volumeMode:Filesystem status:phase:Bound

一旦持久卷被创建,我们可以通过持久卷声明索取可用存储的部分或全部。 配置文件可见于 示例 B-2:

示例 B-2. 持久卷声明配置

kind:PersistentVolumeClaim apiVersion:v1 metadata:name:tfx-pvc namespace:kubeflow spec:accessModes:- ReadWriteOnce resources:requests:storage:20Gi

使用所提供的配置,我们现在在 Kubernetes 集群中创建了持久卷及其声明。 如 “Pipeline Setup” 中所讨论的,该卷现在可以被挂载,或如附录中的 “Exchange Data Through Persistent Volumes” 中所讨论的那样使用。

附录 C. 操作 Kubeflow Pipelines 的提示

当您使用 Kubeflow Pipelines 操作您的 TFX 管道时,您可能希望自定义 TFX 组件的底层容器映像。如果您的组件依赖于 TensorFlow 和 TFX 软件包之外的其他 Python 依赖项,则需要自定义 TFX 映像。在我们的演示管道中,我们有一个额外的 Python 依赖项,即 TensorFlow Hub 库,用于访问我们的语言模型。

在本附录的后半部分,我们希望向您展示如何在本地计算机和持久卷之间传输数据。如果可以通过云存储提供程序访问数据(例如使用本地 Kubernetes 集群),持久卷设置非常有用。提供的步骤将指导您完成在集群中复制数据的过程。

自定义 TFX 映像

在我们的示例项目中,我们使用 TensorFlow Hub 提供的语言模型。我们使用tensorflow_hub库来高效加载语言模型。这个特定的库不包含在原始的 TFX 映像中,因此我们需要构建一个带有所需库的自定义 TFX 映像。如果您计划使用自定义组件,比如我们在第十章讨论的组件,情况也是如此。

幸运的是,正如我们在附录 A 中讨论的那样,Docker 映像可以轻松构建。以下 Dockerfile 显示了我们的自定义映像设置:

FROM tensorflow/tfx:0.22.0``RUN python3.6 -m pip install "tensorflow-hub"RUN ... ENTRYPOINT ["python3.6", "/tfx-src/tfx/scripts/run_executor.py"]

安装所需的软件包。

如有需要,请安装额外的包。

不要更改容器的入口点。

我们可以轻松地将标准 TFX 映像作为自定义映像的基础。为了避免 TFX API 的突然更改,我们强烈建议将基础映像的版本固定到特定的构建版本(例如 tensorflow/tfx:0.22.0),而不是常见的latest标签。TFX 映像基于 Ubuntu Linux 分发,并预装有 Python。在我们的情况下,我们可以简单地为 TensorFlow Hub 模型安装额外的 Python 包。

提供与基础映像配置的相同入口点非常重要。Kubeflow Pipelines 期望入口点将触发组件的执行程序。

定义了 Docker 映像之后,我们可以构建并推送映像到容器注册表。可以是 AWS Elastic、GCP 或 Azure 容器注册表。确保运行的 Kubernetes 集群可以从容器注册表中拉取映像并具有对私有容器的权限非常重要。在下面的代码中,我们展示了 GCP 容器注册表的这些步骤:

$ export TFX_VERSION``=``0.22.0 $ export PROJECT_ID``=``<``your gcp project id``> $ export IMAGE_NAME``=``ml-pipelines-tfx-custom $ gcloud auth configure-docker $ docker build pipelines/kubeflow_pipelines/tfx-docker-image/. \     -t gcr.io/``$PROJECT_ID``/``$IMAGE_NAME``:``$TFX_VERSION``$ docker push gcr.io/``$PROJECT_ID``/``$IMAGE_NAME``:``$TFX_VERSION

一旦构建的镜像上传完成,您可以在云提供商的容器注册表中看到可用的镜像,如 图 C-1 所示。

特定组件图像

在撰写本文时,还不能为特定组件容器定义自定义图像。目前,所有组件的要求都需要包含在图像中。不过,目前正在讨论允许将来使用特定组件图像的提案。

图 C-1. 谷歌云的容器注册表

现在,我们可以在 Kubeflow Pipelines 设置中的所有 TFX 组件中使用这个容器镜像。

通过持久卷交换数据

正如我们之前讨论的,我们需要提供容器以挂载文件系统,以便在容器文件系统之外的位置读取和写入数据。在 Kubernetes 世界中,我们可以通过持久卷(PV)和持久卷声明(PVC)来挂载文件系统。简单来说,我们可以为 Kubernetes 集群内提供一个驱动器,并在其内部索取该文件系统的全部或部分空间。

您可以通过我们在 “Kubeflow Pipelines 的持久卷设置” 中提供的 Kubernetes 配置来设置这些 PV。如果您希望使用此设置,您需要在云提供商(例如 AWS 弹性块存储或 GCP 块存储)中创建一个磁盘。在下面的示例中,我们创建了一个名为 tfx-pv-disk 的大小为 20 GB 的磁盘驱动器:

$ export GCP_REGION``=``us-central1-c $ gcloud compute disks create tfx-pv-disk --size``=``20Gi --zone``=``$GCP_REGION

现在,我们可以为 Kubernetes 集群中的 PV 提供磁盘。以下 kubectl 命令将帮助进行提供:

$ kubectl apply -f "https://github.com/Building-ML-Pipelines/"``\``"building-machine-learning-pipelines/blob/master/pipelines/"``\``"kubeflow_pipelines/kubeflow-config/storage.yaml"``$ kubectl apply -f "https://github.com/Building-ML-Pipelines/"``\``"building-machine-learning-pipelines/blob/master/pipelines/"``\``"kubeflow_pipelines/kubeflow-config/storage-claim.yaml"

完成提供后,您可以通过调用 kubectl get pvc 来检查执行是否成功,如下例所示:

$ kubectl -n kubeflow get pvc NAME             STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE tfx-pvc          Bound    tfx-pvc   20Gi       RWO            manual         2m

Kubernetes 的 kubectl 提供了一个便捷的 cp 命令,用于将数据从我们的本地机器复制到远程 PV。为了复制流水线数据(例如,用于转换和训练步骤的 Python 模块以及训练数据),我们需要将卷挂载到 Kubernetes pod。对于复制操作,我们创建了一个简单的应用程序,基本上只是空闲状态,并允许我们访问 PV。您可以使用以下 kubectl 命令创建 pod:

$ kubectl apply -f "https://github.com/Building-ML-Pipelines/"``\``"building-machine-learning-pipelines/blob/master/pipelines/"``\``"kubeflow_pipelines/kubeflow-config/storage-access-pod.yaml"

pod data-access 将挂载 PV,然后我们可以创建必要的文件夹并将所需数据复制到卷中:

$ export DATA_POD``=``` kubectl -n kubeflow get pods -o name` `|` `grep data-access ```$ kubectl -n kubeflow exec``$DATA_POD -- mkdir /tfx-data/data $ kubectl -n kubeflow exec``$DATA_POD -- mkdir /tfx-data/components $ kubectl -n kubeflow exec``$DATA_POD -- mkdir /tfx-data/output

$ kubectl -n kubeflow cp \ ../building-machine-learning-pipelines/components/module.py \``${``DATA_POD``#*/``}``:/tfx-data/components/module.py $ kubectl -n kubeflow cp \ ../building-machine-learning-pipelines/data/consumer_complaints.csv ${``DATA_POD``#*/``}``:/tfx-data/data/consumer_complaints.csv

在所有数据传输到 PV 后,您可以通过运行以下命令删除 data-access pod:

$ kubectl delete -f \ pipelines/kubeflow_pipelines/kubeflow-config/storage-access-pod.yaml

cp 命令也适用于反向操作,如果您想将从 Kubernetes 集群导出的模型复制到集群外的其他位置。

TFX 命令行界面

TFX 提供了一个 CLI 来管理您的 TFX 项目及其编排运行。该 CLI 工具为您提供了 TFX 模板,预定义的文件夹和文件结构。使用提供的文件夹结构的项目可以通过 CLI 工具管理,而不是通过 Web UI(在 Kubeflow 和 Airflow 的情况下)。它还集成了 Skaffold 库来自动创建和发布自定义 TFX 映像。

TFX CLI 正在积极开发中

在撰写本节时,TFX CLI 正在积极开发中。命令可能会更改或添加更多功能。此外,未来可能会提供更多的 TFX 模板。

TFX 和其依赖项

TFX CLI 需要 Kubeflow Pipelines SDK 和 Skaffold,这是一个用于持续构建和部署 Kubernetes 应用程序的 Python 工具。

如果您尚未安装或更新来自 Kubeflow Pipelines 的 TFX 和 Python SDK,请运行以下两个 pip install 命令:

$ pip install -U tfx $ pip install -U kfp

Skaffold 的安装取决于您的操作系统:

Linux

$ curl -Lo skaffold \ https://storage.googleapis.com/``\ skaffold/releases/latest/skaffold-linux-amd64 $ sudo install skaffold /usr/local/bin/

MacOS

$ brew install skaffold

Windows

$ choco install -y skaffold

安装 Skaffold 后,请确保将工具的执行路径添加到执行 TFX CLI 工具的终端环境的 PATH 中。以下是 Linux 用户如何将 Skaffold 路径添加到其 PATH bash 变量的示例:

$ export PATH``=``$PATH``:/usr/local/bin/

在讨论如何使用 TFX CLI 工具之前,让我们简要讨论一下 TFX 模板。

TFX 模板

TFX 提供了项目模板,用于组织机器学习流水线项目。这些模板提供了预定义的文件夹结构,以及特征、模型和预处理定义的蓝图。以下 tfx template copy 命令将下载 TFX 项目的出租车示例项目:

$ export PIPELINE_NAME``=``"customer_complaint"``$ export PROJECT_DIR``=``$PWD``/``$PIPELINE_NAME``$ tfx template copy --pipeline-name``=``$PIPELINE_NAME``\ --destination-path``=``$PROJECT_DIR``\ --model``=``taxi

当复制命令完成执行后,您可以找到如下所示的文件夹结构:

$ tree . . ├── __init__.py ├── beam_dag_runner.py ├── data │   └── data.csv ├── data_validation.ipynb ├── kubeflow_dag_runner.py ├── model_analysis.ipynb ├── models │   ├── __init__.py │   ├── features.py │   ├── features_test.py │   ├── keras │   │   ├── __init__.py │   │   ├── constants.py │   │   ├── model.py │   │   └── model_test.py │   ├── preprocessing.py │   └── preprocessing_test.py ├── pipeline │   ├── __init__.py │   ├── configs.py │   └── pipeline.py └── template_pipeline_test.tar.gz

我们采用了出租车模板 1,并调整了我们的书籍示例项目以匹配该模板。结果可以在 书籍的 GitHub 仓库 中找到。如果您想跟随这个示例,请将 CSV 文件 consumer_complaints.csv 复制到文件夹中:

$pwd``/``$PIPELINE_NAME``/data

还要双重检查定义了 GCS 桶和其他流水线细节的 pipelines/config.py 文件。使用您创建的或通过 GCP 的 AI 平台创建 Kubeflow Pipelines 安装时创建的 GCS 桶路径更新。您可以使用以下命令找到路径:

$ gsutil -l

使用 TFX CLI 发布您的流水线

我们可以将基于 TFX 模板创建的 TFX 管道发布到我们的 Kubeflow Pipelines 应用程序。要访问我们的 Kubeflow Pipelines 设置,我们需要定义我们的 GCP 项目、TFX 容器镜像的路径以及我们的 Kubeflow Pipelines 端点的 URL。在“访问您的 Kubeflow Pipelines 安装”中,我们讨论了如何获取端点 URL。在使用 TFX CLI 发布我们的管道之前,让我们为我们的示例设置所需的环境变量:

$ export PIPELINE_NAME``=``"<``pipeline name``>"``$ export PROJECT_ID``=``"<``your gcp project id``>"``$ export CUSTOM_TFX_IMAGE``=``gcr.io/``$PROJECT_ID``/tfx-pipeline $ export ENDPOINT``=``"``<id>-dot-<region>``.pipelines.googleusercontent.com"

定义好详细信息后,我们现在可以通过 TFX CLI 创建管道,命令如下:

$ tfx pipeline create --pipeline-path``=``kubeflow_dag_runner.py \ --endpoint``=``$ENDPOINT``\ --build-target-image``=``$CUSTOM_TFX_IMAGE

tfx pipeline create 命令会执行各种操作。通过 Skaffold 的帮助,它创建一个默认的 Docker 镜像,并通过 Google Cloud Registry 发布容器镜像。正如我们在第十二章中讨论的那样,它还运行 Kubeflow Runner,并上传 Argo 配置到管道端点。命令执行完成后,在模板文件夹结构中会找到两个新文件:Dockerfile 和 build.yaml。

Dockerfile 包含一个与我们在“自定义 TFX 镜像”中讨论过的 Dockerfile 类似的镜像定义。build.yaml 文件配置 Skaffold,并设置了 docker 镜像注册表的详细信息和标签策略。

您现在可以在您的 Kubeflow Pipelines UI 中看到注册的管道。您可以使用以下命令启动管道运行:

$ tfx run create --pipeline-name``=``$PIPELINE_NAME``\ --endpoint``=``$ENDPOINT 创建管道运行:customer_complaint_tfx 检测到 Kubeflow。如果您打算使用不同的编排器,请使用--engine 标志。为 pipeline: customer_complaint_tfx 创建运行:customer_complaint_tfx +------------------------+----------+----------+---------------------------+ | pipeline_name | run_id | status | created_at | +``========================``+``==========``+``==========``+``===========================``+ | customer_complaint_tfx | <run-id> |``| 2020-05-31T21:30:03+00:00 | +------------------------+----------+----------+---------------------------+

你可以使用以下命令检查管道运行的状态:

$ tfx run status --pipeline-name``=``$PIPELINE_NAME``\ --endpoint``=``$ENDPOINT``\ --run_id <run_id> 列出管道运行的所有运行:customer_complaint_tfx +------------------------+----------+------------+---------------------------+

可以使用以下命令获取给定管道的所有运行列表:

$ tfx run list --pipeline-name``=``$PIPELINE_NAME``\ --endpoint``=``$ENDPOINT 列出管道运行的所有运行:customer_complaint_tfx +------------------------+----------+------------+---------------------------+

停止和删除管道运行

您可以使用 tfx run terminate 命令停止管道运行。可以使用 tfx run delete 删除管道运行。

TFX CLI 是 TFX 工具链中非常有用的工具。它不仅支持 Kubeflow Pipelines,还支持 Apache Airflow 和 Apache Beam 编排器。

1   在撰写本文时,这是唯一可用的模板。

posted @ 2025-11-21 09:08  绝不原创的飞龙  阅读(4)  评论(0)    收藏  举报