AWS-数据科学-全-
AWS 数据科学(全)
原文:
zh.annas-archive.org/md5/5d14065fe9f81bb7ebb64bbbac0c8fc0译者:飞龙
序言
通过这本实用书籍,AI 和机器学习(ML)从业者将学会如何在亚马逊网络服务(AWS)上成功构建和部署数据科学项目。亚马逊 AI 和 ML 堆栈统一了数据科学、数据工程和应用程序开发,帮助提升您的技能水平。本指南向您展示如何在云中构建和运行流水线,然后将结果集成到应用程序中,从而节省时间,只需几分钟而非几天。整本书中,作者 Chris Fregly 和 Antje Barth 演示了如何降低成本并提高性能。
-
将亚马逊 AI 和 ML 堆栈应用于自然语言处理、计算机视觉、欺诈检测、对话设备等真实用例。
-
使用自动化机器学习(AutoML)通过 Amazon SageMaker Autopilot 实现特定的用例子集。
-
深入探讨基于 BERT 的自然语言处理(NLP)用例的完整模型开发生命周期,包括数据摄入和分析等。
-
将所有内容整合成可重复使用的机器学习运营(MLOps)流水线。
-
利用 Amazon Kinesis 和 Amazon Managed Streaming for Apache Kafka(Amazon MSK)探索实时 ML、异常检测和流式分析的实时数据流。
-
学习数据科学项目和工作流程的安全最佳实践,包括 AWS 身份和访问管理(IAM)、认证、授权,包括数据摄入和分析、模型训练和部署。
章节概述
第一章概述了广泛而深入的亚马逊 AI 和 ML 堆栈,这是一个功能强大且多样化的服务、开源库和基础设施集,可用于任何复杂性和规模的数据科学项目。
第二章描述如何将亚马逊 AI 和 ML 堆栈应用于推荐、计算机视觉、欺诈检测、自然语言理解(NLU)、对话设备、认知搜索、客户支持、工业预测性维护、家庭自动化、物联网(IoT)、医疗保健和量子计算等真实用例。
第三章演示了如何使用 AutoML 通过 SageMaker Autopilot 实现这些用例的特定子集。
第四章至第九章深入探讨基于 BERT 的 NLP 用例的完整模型开发生命周期(MDLC),包括数据摄入和分析、特征选择和工程、模型训练和调优,以及与 Amazon SageMaker、Amazon Athena、Amazon Redshift、Amazon EMR、TensorFlow、PyTorch 和无服务器 Apache Spark 的模型部署。
第十章将所有内容整合到使用 SageMaker Pipelines、Kubeflow Pipelines、Apache Airflow、MLflow 和 TFX 的可重复流水线中的 MLOps。
第十一章演示了在实时数据流中使用 Amazon Kinesis 和 Apache Kafka 进行实时机器学习、异常检测和流分析。
第十二章介绍了数据科学项目和工作流的全面安全最佳实践,包括 IAM、身份验证、授权、网络隔离、数据静态加密、后量子网络传输加密、治理和可审计性。
本书始终提供了在 AWS 上减少成本、提高性能的数据科学项目的技巧。
适合阅读本书的人群
本书适合任何利用数据进行关键业务决策的人。本指南将帮助数据分析师、数据科学家、数据工程师、机器学习工程师、研究科学家、应用程序开发人员和 DevOps 工程师扩展其对现代数据科学技术栈的理解,并在云中提升其技能水平。
Amazon AI 和 ML 技术栈统一了数据科学、数据工程和应用开发,帮助用户超越当前角色的技能水平。我们展示了如何在云中构建和运行流水线,然后在几分钟内将结果集成到应用程序中,而不是几天。
理想情况下,为了从本书中获得最大收益,我们建议读者具备以下知识:
-
对云计算的基本理解
-
基本的 Python、R、Java/Scala 或 SQL 编程技能
-
基本的数据科学工具如 Jupyter Notebook、pandas、NumPy 或 scikit-learn 的基本了解
其他资源
本书从许多优秀的作者和资源中汲取了灵感:
-
Aurélien Géron 的 Hands-on Machine Learning with Scikit-Learn, Keras, and TensorFlow(O’Reilly)是使用流行工具如 Python、scikit-learn 和 TensorFlow 构建智能机器学习系统的实用指南。
-
Jeremy Howard 和 Sylvain Gugger 的 Deep Learning for Coders with fastai and PyTorch(O’Reilly)是使用 PyTorch 构建深度学习应用的优秀参考,"无需博士学位"。
-
Hannes Hapke 和 Catherine Nelson 的 Building Machine Learning Pipelines(O’Reilly)是构建使用 TensorFlow 和 TFX 的 AutoML 流水线的绝佳易读参考。
-
Eric R. Johnston、Nic Harrigan 和 Mercedes Gimeno-Segovia 的 Programming Quantum Computers(O’Reilly)是介绍量子计算机的绝佳入门书籍,使用易于理解的示例展示了量子优势。
-
Micha Gorelick 和 Ian Ozsvald 的 High Performance Python (O’Reilly) 是一本高级参考书,揭示了许多有价值的技巧和窍门,用于分析和优化 Python 代码,以实现高性能数据处理、特征工程和模型训练。
-
Data Science on AWS 有一个专门为本书提供高级研讨会、每月网络研讨会、聚会、视频和与本书内容相关的幻灯片的网站。
本书中使用的约定
本书使用以下排版约定:
Italic
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
Constant width
用于程序清单,以及在段落中引用程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。
**Constant width bold**
显示用户应按照字面意思键入的命令或其他文本。
提示
此元素表示提示或建议。
注意
此元素表示一般注意事项。
使用代码示例
可以从https://github.com/data-science-on-aws下载附加材料(代码示例、练习等)。本书中显示的一些代码示例已缩短,以突出特定实现。该存储库包含本书未涵盖但对读者有用的额外笔记本。笔记本按书的章节组织,应易于跟随。
本书旨在帮助您完成工作。一般而言,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分内容,否则无需联系我们请求许可。例如,编写一个使用本书多个代码片段的程序不需要许可。销售或分发 O’Reilly 书籍中的示例代码需要许可。引用本书并引用示例代码回答问题不需要许可。将本书大量示例代码整合到产品文档中需要许可。
我们赞赏但不需要署名。通常,署名包括标题、作者、出版商和 ISBN。例如:“Data Science on AWS by Chris Fregly and Antje Barth (O’Reilly). Copyright 2021 Antje Barth and Flux Capacitor, LLC, 978-1-492-07939-2.”
如果您觉得您使用的代码示例超出了合理使用范围或上述许可,请随时通过permissions@oreilly.com联系我们。
O’Reilly 在线学习
注意
40 多年来,O’Reilly Media 提供技术和商业培训、知识和见解,帮助公司取得成功。
我们独特的专家和创新者网络通过书籍、文章以及我们的在线学习平台分享他们的知识和专长。O’Reilly 的在线学习平台让您随时访问现场培训课程、深入学习路径、交互式编码环境,以及来自 O’Reilly 和其他 200 多家出版商的大量文本和视频。更多信息,请访问http://oreilly.com。
如何联系我们
有关本书的评论和问题,请联系出版商:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-998-9938(美国或加拿大)
-
707-829-0515(国际或当地)
-
707-829-0104(传真)
本书有一个网页,我们在那里列出勘误、示例和任何其他信息。您可以访问此页面:oreil.ly/data-science-aws。
发送电子邮件至 bookquestions@oreilly.com 对本书发表评论或提出技术问题。
获取有关我们的书籍和课程的新闻和信息,请访问oreilly.com。
在 Facebook 上找到我们:facebook.com/oreilly
在 Twitter 上关注我们:twitter.com/oreillymedia
在 YouTube 上观看我们:www.youtube.com/oreillymedia
作者经常在 Twitter 或 LinkedIn 上分享相关博客文章、会议演讲、幻灯片、社群邀请和研讨会日期。
在 Twitter 上关注作者:https://twitter.com/cfregly 和 https://twitter.com/anbarth
在 LinkedIn 上找到作者:https://www.linkedin.com/in/cfregly 和 https://www.linkedin.com/in/antje-barth
致谢
我们要感谢我们的 O’Reilly 开发编辑 Gary O’Brien,在我们编写书籍过程中帮助我们导航,更重要的是,每次交谈时都让我们笑。谢谢你,Gary,让我们在第一章中包含源代码和低级硬件规格!我们还要感谢资深收购编辑 Jessica Haberman,在初步书籍提案到最终页数计算的各个阶段都提供了关键建议。在提交了七年的书籍提案之后,你帮助我们提高了水平,直到提案被接受!特别感谢 O’Reilly 的 Mike Loukides 和 Nicole Taché,在书写过程的早期,包括章节大纲、引言和总结,你们提供了周到的建议。
我们要衷心感谢那些不知疲倦地审阅——并重新审阅——这本书中的每一页的书评人员。按照名字的字母顺序排列,以下是审阅人员的名单:Ali Arsanjani,Andy Petrella,Brent Rabowsky,Dean Wampler,Francesco Mosconi,Hannah Marlowe,Hannes Hapke,Josh Patterson,Josh Wills,Liam Morrison,Noah Gift,Ramine Tinati,Robert Monarch,Roy Ben-Alta,Rustem Feyzkhanov,Sean Owen,Shelbee Eigenbrode,Sireesha Muppala,Stefan Natu,Ted Dunning 和 Tim O’Brien。你们深厚的技术专业知识和详尽的反馈不仅对这本书至关重要,也对我们今后呈现技术材料的方式产生了深远影响。你们帮助把这本书从好变得更好,并且我们非常享受与你们一同在这个项目上工作的时光。
克里斯
我要将这本书献给我已故的父亲,Thomas Fregly。爸爸:当我 8 岁时,你给我带回了我的第一台苹果电脑,从此改变了我的一生。你在我 10 岁时帮助我消化大学的微积分书,进一步坚定了我对数学的浓厚兴趣。你教会了我如何阅读,如何简洁地写作,如何有效地演讲,如何快速打字,以及如何及早提出问题。看着你在密歇根湖上被困修理船引擎时,我不断受到启发,深入理解支撑软件的硬件。在芝加哥太阳报的办公室里四处走动时,我学会了每个人都有一个有趣的故事,无论是前台人员、CEO 还是维护人员。你对每个人一视同仁,询问他们的孩子,倾听他们的故事,并通过你自己的有趣故事让他们开心。当我小的时候牵着你的手在你的大学校园里走来走去时,我学会了可以离开人行道,沿着草地开辟自己的道路。你说:“别担心,克里斯,他们最终会铺这条路,因为显然这是从工程楼到餐厅的最短路径。”爸爸,你是对的。多年后,我们走过那条新铺的路,从餐厅拿了你最喜欢的饮料,Diet Pepsi。从你身上,我学会了在生活中开辟自己的道路,而不是总是随波逐流。虽然你没能看到 Windows 95,老实说,你也没错过什么。而且,Mac OS 最终也转向了 Linux。在那方面,你也是对的。
我还要感谢我的合著者安特耶·巴特,因为她在许多深夜和周末为这本书的写作经历做出了巨大贡献。尽管我们身处旧金山和杜塞尔多夫之间有 8-9 小时的时差,你总是能够为虚拟白板会议、临时源代码改进以及牛津逗号讨论提供支持。因为这次经历,我们成为了更好的朋友,没有你的帮助,我无法创作出如此密集和高质量的书籍。我期待着与你在未来的许多项目中继续合作!
安特耶
我要感谢泰德·邓宁和艾伦·弗里德曼作为出色的导师,始终鼓励我接受新挑战。泰德,每次我们交谈时,你总能分享些智慧之言,帮助我从不同的角度看问题,无论是为演示竞赛做准备,还是在指导我们如何让读者从本书中获益最多时。艾伦,我依然记得你在我开始为 O'Reilly Strata 和 AI 会议提交演讲时,如何指导我创建引人入胜的会议议题提案。直到今天,我仍然会特别考虑如何想出吸引眼球的标题。不幸的是,O’Reilly 拒绝了我的建议,将这本书命名为Alexa,请训练我的模型。
你们两位在提到“帮助实现女孩们的梦想,展示她们的成就”时,不仅仅是身体力行。正因为如此,我想把这本书献给所有梦想着或者正在追求科技职业的女性和女孩们。只要你相信自己,没有什么能阻止你在这个行业实现梦想。
在我职业生涯中,有更多人支持和鼓励过我。感谢你们所有人。
我还要感谢克里斯作为一位有趣而富有洞见的合著者。从一开始,你总是坚持最高标准,推动我深入探索,鼓励我保持好奇心并多提问题。你帮助简化了我的代码,清晰地表达了我的思想,最终还接受了有争议的牛津逗号!
第一章:在 AWS 上介绍数据科学
在本章中,我们讨论在云中构建数据科学项目的好处。我们首先讨论云计算的好处。接下来,我们描述一个典型的机器学习工作流程及从原型阶段将我们的模型和应用程序移动到生产环境的常见挑战。我们介绍在亚马逊网络服务(AWS)上开发数据科学项目的整体好处,并介绍每个模型开发工作流程步骤相关的 AWS 服务。我们还分享了关于运营卓越、安全性、可靠性、性能和成本优化的架构最佳实践。
云计算的好处
云计算通过互联网按需提供 IT 资源,并采用按使用量付费的定价模式。因此,我们可以获得计算能力、存储、数据库和其他服务等技术,而不是购买、拥有和维护自己的数据中心和服务器。类似于电力公司在我们家里打开电灯开关时立即发送电力,云通过点击按钮或调用 API 按需提供 IT 资源。
“没有经验的压缩算法”是亚马逊网络服务首席执行官安迪·贾西的一句名言。这句话表达了公司自 2006 年以来在建立可靠、安全和高性能服务方面的长期经验。
AWS 一直在不断扩展其服务组合,以支持几乎所有的云工作负载,包括人工智能和机器学习领域的许多服务和功能。这些 AI 和机器学习服务中的许多源于亚马逊在过去 20 年中在推荐系统、计算机视觉、语音/文本和神经网络方面的开创性工作。2003 年的一篇题为“Amazon.com Recommendations: Item-to-Item Collaborative Filtering”的论文最近荣获电气和电子工程师学会颁发的“历久弥新”奖。让我们在 AWS 上的数据科学项目背景下审视云计算的好处。
敏捷性
云计算使我们能够根据需要快速和频繁地启动资源。这使我们能够快速进行实验。也许我们想测试一个新的库来运行数据质量检查,或者通过利用最新一代 GPU 计算资源加速模型训练。我们可以在几分钟内启动数十、数百甚至数千台服务器来执行这些任务。如果一个实验失败,我们可以随时取消这些资源而没有任何风险。
成本节约
云计算允许我们将资本支出转换为可变支出。我们只支付我们使用的部分,无需预先投资于可能在几个月后过时的硬件。如果我们启动计算资源来执行数据质量检查、数据转换或模型训练,我们只需为使用这些计算资源的时间付费。通过利用 Amazon EC2 Spot 实例进行模型训练,我们可以进一步节省成本。Spot 实例允许我们利用 AWS 云中未使用的 EC2 容量,并且相较于按需实例可享受高达 90%的折扣。预留实例和储蓄计划允许我们通过预付一定时间来节省资金。
弹性
云计算使我们能够根据应用程序的需求自动扩展或缩减资源。假设我们已将数据科学应用部署到生产环境,并且我们的模型正在提供实时预测。如果我们观察到模型请求量的高峰,我们现在可以自动扩展模型托管资源。同样地,当模型请求量下降时,我们也可以自动缩减资源。无需过度配置资源来处理高峰负载。
更快创新
云计算允许我们更快地创新,因为我们可以专注于开发区别于我们业务的应用程序,而不是花时间在管理基础设施的无差别的重活上。云帮助我们在几秒钟而不是几个月内尝试新算法、框架和硬件。
几分钟内全球部署
云计算使我们能够在几分钟内全球部署我们的数据科学应用。在全球经济中,与客户接近非常重要。AWS 引入了区域的概念,这是 AWS 数据中心集群的物理位置,每个逻辑数据中心组称为一个可用区(AZ)。每个 AWS 区域包括多个隔离且物理分离的可用区,这些区域分布在全球各地。AWS 区域和可用区的数量正在不断增长。
我们可以利用 AWS 区域和可用区的全球部署优势,使我们的数据科学应用靠近客户,通过超快的响应时间提高应用性能,并遵守每个区域的数据隐私限制。
从原型到生产的平稳过渡
在云中开发数据科学项目的好处之一是从原型到生产的平稳过渡。我们可以在几分钟内从在笔记本中运行模型原型代码切换到运行数据质量检查或跨 PB 级数据进行分布式模型训练。一旦完成,我们可以部署我们训练好的模型,为全球数百万用户提供实时或批处理预测。
原型开发通常在单机开发环境中使用 Jupyter Notebook、NumPy 和 pandas 进行。这种方法对小数据集效果良好。当扩展到大数据集时,我们很快会超出单机的 CPU 和 RAM 资源。此外,我们可能需要使用 GPU 或多台机器来加速模型训练。这通常是单机无法实现的。
当我们希望将模型(或应用程序)部署到生产环境时,下一个挑战就出现了。我们还需要确保我们的应用程序能够处理全球规模的成千上万个并发用户。
生产部署通常需要数据科学、数据工程、应用程序开发和 DevOps 等各个团队之间的强大协作。一旦我们的应用程序成功部署,我们需要持续监控并对模型性能和数据质量问题做出反应,这些问题可能在模型推送到生产后出现。
在云中开发数据科学项目使我们能够顺利将我们的模型从原型转换到生产,同时消除构建自己的物理基础设施的需求。托管云服务为我们提供了工具,可以自动化我们的工作流程,并将模型部署到可扩展和高性能的生产环境中。
数据科学流水线和工作流程
数据科学的流水线和工作流程涉及许多复杂的、跨学科的和迭代的步骤。让我们以典型的机器学习模型开发工作流程为例。我们从数据准备开始,然后进行模型训练和调整。最终,我们将我们的模型(或应用程序)部署到生产环境中。每一个步骤都包含几个子任务,如图 1-1 所示。

图 1-1。典型的机器学习工作流程涉及许多复杂的、跨学科的和迭代的步骤。
如果我们正在使用 AWS,我们的原始数据很可能已经存储在亚马逊简单存储服务(Amazon S3)中,以 CSV、Apache Parquet 或等效格式存储。我们可以通过直接指向数据集并点击一个“训练”按钮,快速开始使用亚马逊人工智能或自动化机器学习(AutoML)服务来建立基线模型性能。我们在第二章和第三章深入探讨 AI 服务和 AutoML。
对于更加定制化的机器学习模型——本书的主要关注点——我们可以开始手动数据摄入和探索阶段,包括数据分析、数据质量检查、汇总统计、缺失值、分位数计算、数据偏斜分析、相关性分析等等。我们在第四章和第五章深入探讨数据摄入和探索。
然后,我们应该定义机器学习问题类型——回归、分类、聚类等。一旦确定了问题类型,我们可以选择最适合解决给定问题的机器学习算法。根据我们选择的算法,我们需要选择我们数据的子集来训练、验证和测试我们的模型。我们的原始数据通常需要转换为数学向量,以实现数值优化和模型训练。例如,我们可能决定将分类列转换为独热编码向量,或将基于文本的列转换为词嵌入向量。在将一部分原始数据转换为特征后,我们应该将这些特征拆分为训练、验证和测试特征集,为模型训练、调优和测试做准备。我们在第五章和第六章深入探讨了特征选择和转换。
在模型训练阶段,我们选择一个算法,并使用训练特征集训练我们的模型,以验证我们的模型代码和算法是否适合解决给定的问题。我们在第七章深入探讨了模型训练。
在模型调优阶段,我们调整算法的超参数,并根据验证特征集评估模型性能。我们重复这些步骤——根据需要添加更多数据或更改超参数——直到模型在测试特征集上达到预期结果。在第八章中,我们深入探讨了超参数调优。
最终阶段——从原型开发进入生产——通常对数据科学家和机器学习从业者来说是最大的挑战。我们在第九章深入探讨了模型部署。
在第十章,我们将所有内容综合到一个自动化流水线中。在第十一章,我们对流数据进行数据分析和机器学习。第十二章总结了在云中保障数据科学的最佳实践。
一旦我们建立了机器学习工作流的每个步骤,我们可以开始将这些步骤自动化为单一、可重复的机器学习流水线。当新数据进入 S3 时,我们的流水线会使用最新数据重新运行,并将最新模型推送到生产环境以服务我们的应用程序。有几种工作流编排工具和 AWS 服务可供我们构建自动化的机器学习流水线。
Amazon SageMaker Pipelines
Amazon SageMaker Pipelines 是在亚马逊 SageMaker 上实现 AI 和机器学习流水线的标准、功能完整且最全面的方式。SageMaker Pipelines 与 SageMaker 特征存储、SageMaker 数据整形器、SageMaker 处理作业、SageMaker 训练作业、SageMaker 超参数调整作业、SageMaker 模型注册表、SageMaker 批量转换和 SageMaker 模型端点集成,我们将在整本书中讨论这些。我们将在 第十章 深入研究托管的 SageMaker Pipelines,同时讨论如何使用 AWS Step Functions、Kubeflow Pipelines、Apache Airflow、MLflow、TFX 和人工协作工作流建立流水线。
AWS Step Functions Data Science SDK
Step Functions 是一个托管的 AWS 服务,非常适合构建复杂的工作流,无需建立和维护自己的基础设施。我们可以使用 Step Functions Data Science SDK 从诸如 Jupyter Notebook 的 Python 环境构建机器学习流水线。我们将在 第十章 深入探讨托管的用于机器学习的 Step Functions。
Kubeflow Pipelines
Kubeflow 是建立在 Kubernetes 上的一个相对较新的生态系统,包括一个名为Kubeflow Pipelines的编排子系统。使用 Kubeflow,我们可以重新启动失败的流水线,调度流水线运行,分析训练指标,并跟踪流水线的来源。我们将在 第十章 深入探讨如何在亚马逊弹性 Kubernetes 服务(Amazon EKS)上管理 Kubeflow 集群。
在 AWS 上的托管 Apache Airflow 工作流
Apache Airflow 是一个非常成熟和流行的选项,主要用于编排数据工程和 ETL(抽取-转换-加载)流水线。我们可以使用 Airflow 来编写任务的有向无环图(DAG)工作流。Airflow 调度程序在一组工作节点上执行任务,并遵循指定的依赖关系。我们可以通过 Airflow 用户界面可视化运行中的流水线,监视进度,并在需要时解决问题。我们将在 第十章 深入探讨亚马逊托管的 Apache Airflow 工作流(Amazon MWAA)。
MLflow
MLflow 是一个开源项目,最初专注于实验追踪,现在支持名为MLflow Workflows的流水线。我们可以在 Kubeflow 和 Apache Airflow 工作流中使用 MLflow 来追踪实验。然而,MLflow 要求我们建立和维护自己的亚马逊 EC2 或亚马逊 EKS 集群。我们将在 第十章 更详细地讨论 MLflow。
TensorFlow Extended
TensorFlow Extended(TFX)是一组开源的 Python 库,用于在管道编排器(如 AWS Step Functions、Kubeflow Pipelines、Apache Airflow 或 MLflow)中使用。TFX 特定于 TensorFlow,并依赖于另一个开源项目 Apache Beam,以实现超越单个处理节点的扩展。我们将在第十章更详细地讨论 TFX。
人在回路工作流程
尽管 AI 和机器学习服务使我们的生活更加轻松,但人类远未被淘汰。事实上,“人在回路”概念已成为许多 AI/ML 工作流中的重要基石。在生产中,人类为敏感和受监管的模型提供重要的质量保证。
亚马逊增强型 AI(Amazon A2I)是一个完全托管的服务,用于开发包括清晰用户界面、基于角色的访问控制(使用 AWS Identity and Access Management(IAM))、可扩展数据存储(使用 S3)在内的人在回路工作流程。亚马逊 A2I 与许多亚马逊服务集成,包括用于内容审核的亚马逊 Rekognition 和用于表单数据提取的亚马逊 Textract。我们还可以将亚马逊 A2I 与亚马逊 SageMaker 和我们的任何自定义 ML 模型一起使用。我们将在第十章深入探讨人在回路工作流程。
MLOps 最佳实践
机器学习运营(MLOps)领域在过去十年中出现,用于描述操作“软件加数据”系统(如 AI 和机器学习)的独特挑战。通过 MLOps,我们正在开发端到端架构,用于自动化模型训练、模型托管和管道监控。从一开始就使用完整的 MLOps 策略,我们正在建立专业知识,减少人为错误,降低项目风险,并释放时间集中解决难点数据科学挑战。
我们看到 MLOps 在过去十年中经历了三个不同成熟阶段:
MLOps v1.0
手动构建、训练、调优和部署模型
MLOps v2.0
手动构建和编排模型管道
MLOps v3.0
当新数据到达或代码更改时,自动运行管道,基于确定性触发器如 GitOps 或基于统计触发器如漂移、偏差和可解释性差异,当模型开始性能下降时
AWS 和 Amazon SageMaker Pipelines 支持完整的 MLOps 策略,包括使用确定性的 GitOps 触发器以及数据漂移、模型偏差和可解释性差异等统计触发器进行自动化管道重训练。我们将在第 5、6、7 和 9 章节深入探讨统计漂移、偏差和可解释性。在第十章中,我们将使用各种管道编排和自动化选项,包括 SageMaker Pipelines、AWS Step Functions、Apache Airflow、Kubeflow 等选项,以及包括人在回路工作流在内的选项,来实施持续和自动化流水线。现在,让我们回顾一些操作卓越、安全性、可靠性、性能效率和 MLOps 成本优化的最佳实践。
运营卓越。
以下是一些云中机器学习特定的最佳实践,帮助我们在云中构建成功的数据科学项目:
数据质量检查。
由于所有我们的机器学习项目都始于数据,请确保能够访问高质量的数据集,并实施可重复的数据质量检查。数据质量不佳导致许多项目失败。请在管道的早期阶段解决这些问题。
从简单开始,并重用现有解决方案。
如果没有必要,就从最简单的解决方案开始,没有必要重新发明轮子。很可能有人工智能服务可用来解决我们的任务。利用像 Amazon SageMaker 这样的托管服务,其中包含大量内置算法和预训练模型。
定义模型性能指标。
将模型性能指标映射到业务目标,并持续监控这些指标。我们应该制定策略,在性能下降时触发模型失效和重新训练模型。
跟踪和版本控制所有内容。
通过实验和谱系跟踪跟踪模型开发。我们还应该对数据集、特征转换代码、超参数和训练模型进行版本控制。
为模型训练和模型服务选择适当的硬件。
在许多情况下,模型训练与模型预测服务具有不同的基础设施要求。选择每个阶段的适当资源。
持续监控已部署的模型。
检测数据漂移和模型漂移,并采取适当的措施,如模型重训练。
自动化机器学习工作流程。
构建一致的自动化流水线,以减少人为错误,并释放时间专注于难题。流水线可以包括人工批准步骤,用于在推送到生产之前批准模型。
安全性。
安全性和合规性是 AWS 和客户之间共同的责任。AWS 确保“云中”的安全性,而客户负责“云中”的安全性。
在云中构建安全数据科学项目时,最常见的安全考虑涉及访问管理、计算和网络隔离、加密、治理和可审计性。
我们需要围绕我们的数据实施深度安全和访问控制功能。我们应限制对数据标记作业、数据处理脚本、模型、推断端点和批处理预测作业的访问。
我们还应实施数据治理策略,确保数据集的完整性、安全性和可用性。实施并强制执行数据血统,监控和跟踪应用于我们的训练数据的数据转换。确保数据在静态和运动状态下都进行加密。此外,我们应根据需要强制执行法规遵从性。
我们将在 第十二章 更详细地讨论在 AWS 上构建安全数据科学和机器学习应用的最佳实践。
可靠性
可靠性指系统从基础设施或服务中断中恢复的能力,动态获取计算资源以满足需求,并减轻配置错误或瞬时网络问题等中断。
我们应该自动化变更跟踪和版本控制我们的训练数据。这样,我们可以在发生故障时重新创建模型的确切版本。我们将构建一次,并使用模型工件在多个 AWS 账户和环境中部署模型。
性能效率
性能效率 指的是有效利用计算资源以满足要求,并在需求变化和技术演进时如何保持效率。
我们应该为我们的机器学习工作负载选择合适的计算资源。例如,我们可以利用基于 GPU 的实例更有效地训练深度学习模型,使用更大的队列深度、更高的算术逻辑单元和增加的寄存器计数。
熟悉模型的延迟和网络带宽性能要求,并根据需要将每个模型部署更接近客户。在某些情况下,我们可能希望在“边缘”部署我们的模型,以提高性能或遵守数据隐私法规。“边缘部署”指的是在设备本身上运行模型以在本地进行预测。我们还希望持续监控模型的关键性能指标,及早发现性能偏差。
成本优化
我们可以通过利用不同的 Amazon EC2 实例定价选项来优化成本。例如,储蓄计划相比按需实例价格提供了显著的节省,换取对特定时间段内使用特定数量计算能力的承诺。储蓄计划非常适合稳定的推断工作负载等已知/稳定状态工作负载。
使用按需实例,我们根据所运行的实例的小时或秒数付费计算能力。按需实例适合新的或状态性强的工作负载,如短期模型训练作业。
最后,亚马逊 EC2 Spot 实例允许我们请求多余的亚马逊 EC2 计算能力,价格可以低至按需价格的 90%。Spot 实例可以覆盖灵活、容错的工作负载,例如不受时间限制的模型训练作业。图 1-2 显示了节省计划、按需实例和 Spot 实例的混合结果。

图 1-2. 通过选择节省计划、按需实例和 Spot 实例的混合优化成本。
通过许多托管服务,我们可以从“按使用量付费”的模式中受益。例如,使用 Amazon SageMaker,我们只支付我们模型训练或运行自动模型调优的时间。开始使用较小的数据集开发模型,以便更快、更节省地迭代。一旦我们有一个表现良好的模型,我们可以扩展到使用完整数据集进行训练。另一个重要方面是适当调整模型训练和模型托管实例的大小。
许多时候,模型训练受益于 GPU 加速,但模型推断可能不需要同样的加速。事实上,大多数机器学习工作负载实际上是预测。虽然模型可能需要几小时或几天来训练,但部署的模型可能每天 24 小时、每周 7 天运行,支持数百万客户的数千个预测服务器。我们应该决定我们的用例是否需要 24 × 7 实时端点或在晚间使用 Spot 实例进行批量转换。
亚马逊 AI 服务和使用亚马逊 SageMaker 的自动机器学习
我们知道,数据科学项目涉及许多复杂的、跨学科的和迭代的步骤。我们需要访问一个支持模型原型阶段的机器学习开发环境,并提供顺畅过渡以准备我们的模型投入生产的环境。我们可能希望尝试各种机器学习框架和算法,并开发定制的模型训练和推断代码。
其他时候,我们可能只想使用现成的、预训练的模型来解决简单的任务。或者我们可能希望利用 AutoML 技术为我们的项目创建第一个基线。AWS 为每种情景提供了广泛的服务和功能。图 1-3 显示了整个亚马逊 AI 和机器学习堆栈,包括 AI 服务和 Amazon SageMaker Autopilot 用于 AutoML。

图 1-3. 亚马逊 AI 和机器学习堆栈。
亚马逊 AI 服务
对于许多常见的使用场景,例如个性化产品推荐、内容审核或需求预测,我们还可以使用亚马逊的托管 AI 服务,并选择在我们的定制数据集上进行微调。我们可以通过简单的 API 调用将这些“一键式”AI 服务集成到我们的应用程序中,而无需太多(有时甚至不需要)机器学习经验。
完全托管的 AWS AI 服务是通过简单的 API 调用为我们的应用程序增加智能的最快最简单的方式。这些 AI 服务提供预训练或自动训练的机器学习模型,用于图像和视频分析、高级文本和文档分析、个性化推荐或需求预测。
AI 服务包括 Amazon Comprehend 用于自然语言处理,Amazon Rekognition 用于计算机视觉,Amazon Personalize 用于生成产品推荐,Amazon Forecast 用于需求预测,以及 Amazon CodeGuru 用于自动化源代码审查。
使用 SageMaker Autopilot 的 AutoML
在另一种情况下,我们可能希望自动化数据分析、数据准备和简单且知名的机器学习问题的模型训练的重复步骤。这帮助我们将时间集中在更复杂的用例上。AWS 在 Amazon SageMaker 服务中提供 AutoML。
提示
AutoML 并不局限于 SageMaker。许多 Amazon AI 服务执行 AutoML,以找到给定数据集的最佳模型和超参数。
“AutoML” 通常指自动化模型开发工作流程的典型步骤,正如我们之前描述的那样。Amazon SageMaker Autopilot 是一种完全托管的服务,将 AutoML 技术应用于我们的数据集。
SageMaker Autopilot 首先分析我们的表格数据,识别机器学习问题类型(如回归、分类),选择算法(如 XGBoost)来解决问题。它还创建必要的数据转换代码,以预处理模型训练数据。Autopilot 随后创建多种不同的机器学习模型候选管道,表示数据转换和选择算法的变化。它在特征工程步骤中应用数据转换,然后训练和调整每个模型候选。其结果是基于定义的客观度量标准(如验证准确率)的模型候选排名列表(排行榜)。
SageMaker Autopilot 是透明的 AutoML 的一个例子。Autopilot 不仅与我们分享数据转换的代码,还生成额外的 Jupyter 笔记本,记录数据分析步骤的结果以及模型候选管道,以重现模型训练。
在许多场景下,我们可以利用 SageMaker Autopilot。我们可以让更多在我们组织中的人构建模型,例如,具有有限机器学习经验的软件开发人员。我们可以自动化解决简单机器学习问题的模型创建,并将我们的时间集中在新的复杂用例上。我们可以自动化数据分析和数据准备的第一步,然后使用结果作为基线,应用我们的领域知识和经验来调整和进一步改进模型。Autopilot 生成的模型指标也为我们提供了可通过提供的数据集实现的模型质量的良好基线。我们将在第三章深入探讨 SageMaker Autopilot。
AWS 中的数据摄入、探索和准备
我们将在第四章,第五章和第六章分别讨论数据摄入、探索和准备。但现在,让我们讨论模型开发工作流程的这一部分,以了解我们可以在每个步骤中利用哪些 AWS 服务和开源工具。
使用 Amazon S3 和 AWS Lake Formation 的数据摄入和数据湖
一切都始于数据。如果我们在过去几十年中看到了一个一致的趋势,那就是数据的持续爆炸增长。数据呈指数增长,且日益多样化。今天,业务成功往往与公司快速从其数据中提取价值的能力密切相关。现在有越来越多的人员、团队和应用程序需要访问和分析数据。这就是为什么许多公司正在转向一个高度可扩展、可用、安全和灵活的数据存储,通常被称为数据湖。
数据湖是一个集中和安全的存储库,使我们能够以任何规模存储、管理、发现和共享数据。有了数据湖,我们可以高效地运行任何类型的分析,并且可以使用多个 AWS 服务,而无需转换或移动数据。
数据湖可以包含结构化关系数据以及半结构化和非结构化数据。我们甚至可以摄入实时数据。数据湖为数据科学和机器学习团队提供了访问大型和多样化数据集的能力,以训练和部署更准确的模型。
Amazon 简单存储服务(Amazon S3)是一种对象存储,专为从任何地方、以任何格式存储和检索任意量的数据而构建。我们可以通过精细调整的访问控制组织我们的数据,以满足业务和合规要求。我们将在第十二章深入讨论安全性。Amazon S3 设计的耐久性为 99.999999999%(11 个九),并且具有强大的写后一致性。在 AWS 中,S3 是数据湖的热门选择。
我们可以利用 AWS Lake Formation 服务来创建我们的数据湖。该服务帮助收集和目录化来自数据库和对象存储的数据。Lake Formation 不仅移动我们的数据,还使用机器学习算法清洗、分类和安全地访问我们的敏感数据。
我们可以利用 AWS Glue 自动发现和分析新数据。AWS Glue 是一个可伸缩的无服务器数据目录和数据准备服务。该服务包括 ETL 引擎、与 Apache Hive 兼容的数据目录服务,以及数据转换和分析服务。我们可以构建数据爬虫定期检测和目录化新数据。AWS Glue DataBrew 是一个具有易于使用 UI 的服务,简化了数据摄入、分析、可视化和转换。
使用 Amazon Athena、Amazon Redshift 和 Amazon QuickSight 进行数据分析
在我们开始开发任何机器学习模型之前,我们需要理解数据。在数据分析步骤中,我们探索我们的数据,收集统计信息,检查缺失值,计算分位数,并识别数据相关性。
有时候我们只是想快速分析开发环境中可用的数据,并原型化一些首个模型代码。也许我们只是想快速尝试一个新的算法。我们称这种情况为“特别探索”和原型化,我们查询部分数据以首先理解特定机器学习问题中的数据架构和数据质量。然后我们开发模型代码并确保其功能正确。这种特别探索和原型化可以从开发环境如 SageMaker Studio、AWS Glue DataBrew 和 SageMaker Data Wrangler 进行。
Amazon SageMaker 为我们提供了托管的 Jupyter 环境以及与 SageMaker Studio 集成的集成开发环境。我们可以使用诸如pandas之类的工具直接在笔记本环境中开始分析数据集。pandas 是一个流行的 Python 开源数据分析和操作工具。请注意,pandas 使用内存数据结构(DataFrames)来保存和操作数据。由于许多开发环境具有受限的内存资源,我们需要小心地拉取多少数据到 pandas DataFrames 中。
为了在我们的笔记本中进行数据可视化,我们可以利用诸如Matplotlib和Seaborn等流行的开源库。Matplotlib 允许我们在 Python 中创建静态、动画和交互式可视化。Seaborn 建立在 Matplotlib 之上,增加了对额外统计图形的支持,以及更易于使用的编程模型。这两个数据可视化库与 pandas 数据结构密切集成。
开源的AWS Data Wrangler 库扩展了 pandas 在 AWS 上的功能。AWS Data Wrangler 连接 pandas DataFrames 与 AWS 服务,如 Amazon S3、AWS Glue、Amazon Athena 和 Amazon Redshift。
AWS Data Wrangler 提供了优化的 Python 函数,执行在数据湖、数据仓库和数据库之间加载和卸载数据的常见 ETL 任务。安装 AWS Data Wrangler 后,使用 pip install awswrangler 导入 AWS Data Wrangler,我们可以直接从 S3 中将数据集读取到 pandas DataFrame 中,如下所示:
import awswrangler as wr
# Retrieve the data directly from Amazon S3
df = wr.s3.read_parquet("s3://<BUCKET>/<DATASET>/"))
AWS Data Wrangler 还带有额外的内存优化,例如按块读取数据。如果我们需要查询大型数据集,则这尤为有帮助。启用块读取后,AWS Data Wrangler 会将路径中的每个数据集文件作为单独的 pandas DataFrame 读取和返回。我们还可以设置块大小,以返回与我们定义的块大小相当的 DataFrame 行数。要查看完整的功能列表,请参阅文档。我们将在第五章深入探讨 AWS Data Wrangler。
我们可以利用托管服务(例如 Amazon Athena)在我们的笔记本内部对 S3 中的数据运行交互式 SQL 查询。Amazon Athena 是一个托管的、无服务器的、动态可扩展的分布式 SQL 查询引擎,专为对极其大型数据集进行快速并行查询而设计。Athena 基于流行的开源查询引擎 Presto,并且无需维护。使用 Athena,我们只需为运行的查询付费。并且我们可以直接在我们的 S3 数据湖中以原始形式查询数据,无需进行额外的转换。
Amazon Athena 还利用 AWS Glue 数据目录服务存储和检索我们 SQL 查询所需的模式元数据。当我们定义 Athena 数据库和表时,我们指向 S3 中的数据位置。Athena 然后将这种表到 S3 的映射存储在 AWS Glue 数据目录中。我们可以使用 PyAthena,一个流行的开源库,从基于 Python 的笔记本和脚本查询 Athena。我们将在第四章和第五章深入探讨 Athena、AWS Glue 数据目录和 PyAthena。
Amazon Redshift 是一个完全托管的云数据仓库服务,允许我们针对 PB 级结构化数据运行复杂的分析查询。我们的查询在多个节点上分布和并行化执行。与优化用于行存储数据并主要服务事务应用的关系数据库相反,Amazon Redshift 实施的是列存储,这对于我们主要关注这些列的汇总统计信息的分析应用来说是优化的。
Amazon Redshift 还包括 Amazon Redshift Spectrum,允许我们直接从 Amazon Redshift 对接 PB 级非结构化数据的 Amazon S3 数据湖执行 SQL 查询,无需实际移动数据。Amazon Redshift Spectrum 根据接收到的数据量自动扩展所需的计算资源,因此对 Amazon S3 的查询运行速度快,无论我们的数据大小如何。
如果我们需要创建仪表板样式的数据可视化,可以利用 Amazon QuickSight。QuickSight 是一个易于使用的无服务器业务分析服务,可以快速构建强大的可视化。我们可以创建交互式仪表板和报告,并通过浏览器或移动设备安全共享给同事。QuickSight 已经配备了广泛的可视化、图表和表格库。
QuickSight 实现了机器学习和自然语言处理能力,帮助我们从数据中获得更深入的洞见。使用 ML Insights,我们可以发现数据中的隐藏趋势和异常值。该功能还使任何人都能够进行假设分析和预测,无需任何机器学习经验。我们还可以通过将 QuickSight 连接到在 Amazon SageMaker 中构建的机器学习模型来构建预测性仪表板。
使用 AWS Deequ 和 SageMaker 处理作业评估数据质量
构建高质量模型需要高质量数据。在创建训练数据集之前,我们希望确保数据符合某些质量约束。在软件开发中,我们运行单元测试以确保代码符合设计和质量标准,并且按预期运行。类似地,我们可以对数据集运行单元测试,以确保数据符合我们的质量预期。
AWS Deequ 是建立在 Apache Spark 之上的开源库,允许我们为数据定义单元测试并在大数据集中测量数据质量。使用 Deequ 单元测试,我们可以在数据用于模型训练之前早期发现异常和错误。Deequ 设计用于处理非常大的数据集(数十亿行)。这个开源库支持表格数据,如 CSV 文件、数据库表、日志或扁平化的 JSON 文件。任何可以放入 Spark 数据框架的东西,我们都可以用 Deequ 进行验证。
在后续示例中,我们将利用 Deequ 对样本数据集实施数据质量检查。我们将利用 SageMaker Processing Jobs 支持 Apache Spark 以规模化运行我们的 Deequ 单元测试。在此设置中,我们无需自行配置任何 Apache Spark 集群,因为 SageMaker Processing 为我们处理了繁重的工作。我们可以将其视为“无服务器”Apache Spark。一旦拥有高质量数据,我们现在可以创建我们的训练数据集。
使用 SageMaker Ground Truth 标记训练数据
许多数据科学项目都实现了监督学习。在监督学习中,我们的模型通过示例进行学习。我们首先需要收集和评估数据,然后提供准确的标签。如果存在错误的标签,我们的机器学习模型将从错误的示例中学习,最终导致不准确的预测结果。SageMaker Ground Truth 帮助我们高效准确地标记存储在 Amazon S3 中的数据。SageMaker Ground Truth 使用自动化和人工数据标记的组合。
SageMaker Ground Truth 为常见的数据标记任务提供预构建的工作流和界面。我们定义标记任务,并将标记工作分配给公共工作力量(通过 Amazon Mechanical Turk)或私人工作力量,例如我们的同事。我们还可以利用 AWS Marketplace 列出的经过亚马逊预筛选的第三方数据标记服务提供商。
SageMaker Ground Truth 实施预构建工作流的主动学习技术。它创建一个模型来自动标记数据子集,基于人工工作人员分配的标签。随着模型不断从人工工作人员中学习,准确性会提高,并且需要发送给人工工作人员的数据量会减少。随着时间的推移和足够的数据量,SageMaker Ground Truth 的主动学习模型能够提供高质量和自动注释,从而降低总体标记成本。我们将在第十章深入探讨 SageMaker Ground Truth。
使用 AWS Glue DataBrew、SageMaker Data Wrangler 和 SageMaker Processing Jobs 进行数据转换
现在让我们继续进行数据转换。我们假设我们的数据存储在一个 S3 数据湖或 S3 存储桶中。通过数据分析,我们对数据集有了深入的理解。现在的下一步是准备我们的数据用于模型训练。
数据转换可能包括在数据集中删除或合并数据。我们可能需要将文本数据转换为词嵌入以供自然语言模型使用。或者我们可能需要将数据转换为另一种格式,从数值到文本表示,反之亦然。有许多 AWS 服务可以帮助我们实现这一点。
AWS Glue DataBrew 是一种可视化数据分析和准备工具。使用 250 种内置转换,DataBrew 可以检测异常值,将数据转换为标准格式并修复无效或缺失值。DataBrew 可以对我们的数据进行分析,计算摘要统计信息,并可视化列之间的相关性。
我们还可以使用 Amazon SageMaker Data Wrangler 开发大规模的自定义数据转换。SageMaker Data Wrangler 提供低代码、UI 驱动的数据转换。我们可以从各种来源读取数据,包括 Amazon S3、Athena、Amazon Redshift 和 AWS Lake Formation。SageMaker Data Wrangler 具有预配置的数据转换功能,类似于 AWS DataBrew,用于转换列类型、执行独热编码和处理文本字段。SageMaker Data Wrangler 支持使用 Apache Spark 进行自定义用户定义函数,并生成包括 Python 脚本和 SageMaker 处理作业的代码。
SageMaker 处理作业允许我们对 S3 中的数据运行自定义数据处理代码,进行数据转换、数据验证或模型评估。配置 SageMaker 处理作业时,我们定义所需的资源,包括实例类型和实例数量。SageMaker 接收我们的自定义代码,从 Amazon S3 复制数据,然后拉取 Docker 容器执行处理步骤。
SageMaker 提供预构建的容器镜像,用于使用 Apache Spark 和 scikit-learn 运行数据处理。如果需要,我们还可以提供自定义容器镜像。SageMaker 然后根据作业的持续时间启动我们指定的集群资源,并在作业完成时终止它们。处理结果在作业完成时写回 Amazon S3 存储桶。
使用 Amazon SageMaker 进行模型训练和调优
让我们详细讨论我们模型开发工作流程中的模型训练和调优步骤,并学习如何利用哪些 AWS 服务和开源工具。
使用 SageMaker 训练和实验训练模型
Amazon SageMaker 训练作业提供了大量功能来支持我们的模型训练。我们可以使用 SageMaker 实验组织、跟踪和评估我们的单个模型训练运行。使用 SageMaker 调试器,我们可以透明地查看我们的模型训练过程。调试器在训练期间自动捕获实时指标,并提供可视化界面分析调试数据。调试器还分析和监视系统资源利用率,并识别资源瓶颈,如过度利用的 CPU 或 GPU。
使用 SageMaker 训练,我们只需指定我们数据的 Amazon S3 位置,执行我们模型训练代码的算法容器,以及定义我们需要的 SageMaker 机器学习实例的类型和数量。SageMaker 将负责初始化资源并运行我们的模型训练。如果指定,SageMaker 将启动一个分布式计算集群。一旦模型训练完成,SageMaker 将结果写入 S3 并终止机器学习实例。
SageMaker 还支持托管 Spot 实例训练。托管 Spot 实例训练利用 Amazon EC2 Spot 实例执行模型训练。使用 Spot 实例,我们可以将模型训练成本降低高达 90%,相比按需实例。
除了 SageMaker 自动驾驶之外,我们可以从 Amazon SageMaker 提供的任何内置算法中选择,或通过带来自己的模型代码(脚本模式)或自己的算法/框架容器来定制模型训练。
内置算法
SageMaker 配备了许多内置算法,帮助机器学习从业者快速开始训练和部署机器学习模型。内置算法无需额外的代码。我们只需提供数据和任何模型设置(超参数),并指定计算资源。大多数内置算法还支持开箱即用的分布式训练,以支持无法在单台机器上容纳的大型数据集。
对于监督学习任务,我们可以选择回归和分类算法,例如线性学习器和 XGBoost。分解机(Factorization Machines)非常适合推荐系统。
对于无监督学习任务,如聚类、降维、模式识别和异常检测,还提供了额外的内置算法。这些算法包括主成分分析(PCA)和 K 均值聚类。
我们还可以利用内置算法来处理文本分析任务,如文本分类和主题建模。这些算法包括 BlazingText 和神经主题模型。
对于图像处理,我们将找到用于图像分类和物体检测的内置算法,包括语义分割。
自带脚本(脚本模式)
如果我们需要更灵活性,或者没有适合我们用例的内置解决方案,我们可以提供自己的模型训练代码。这通常被称为“脚本模式”。脚本模式让我们专注于我们的训练脚本,而 SageMaker 为每个熟悉的开源框架(如 TensorFlow、PyTorch、Apache MXNet、XGBoost 和 scikit-learn)提供高度优化的 Docker 容器。我们可以通过一个 requirements 文件添加所有需要的代码依赖项,SageMaker 将根据我们选择的框架之一运行我们的自定义模型训练代码。
自带容器
如果既没有内置算法也不适用于脚本模式的情况下,我们可以使用自己的定制 Docker 镜像来托管模型训练。Docker 是一个软件工具,提供了构建时和运行时支持称为“Docker 容器”的隔离环境。
SageMaker 使用 Docker 镜像和容器提供数据处理、模型训练和预测服务的功能。
如果我们需要的软件包或软件不包含在支持的框架中,我们可以使用“自带容器”(BYOC)。这种方法为我们提供了无限的选择和最大的灵活性,因为我们可以构建自己的 Docker 容器并安装我们需要的任何内容。通常情况下,当用户有定制的安全要求,或者希望预先安装库到 Docker 容器中以避免第三方依赖时(如 PyPI、Maven 或 Docker Registry),他们会选择这个选项。
当使用 BYOC 选项来使用我们自己的 Docker 镜像时,我们首先需要将 Docker 镜像上传到像 DockerHub 或 Amazon Elastic Container Registry(Amazon ECR)这样的 Docker 注册表中。如果我们熟悉开发、维护和支持具有高效 Docker 镜像管道的定制 Docker 镜像,我们应该选择 BYOC 选项。否则,我们应该使用内置的 SageMaker 容器。
注意
我们不需要在构建时将我们的代码“烧入”到 Docker 镜像中。我们可以简单地从 Docker 镜像内部指向 Amazon S3 中的代码,并在启动 Docker 容器时动态加载代码。这有助于避免每次代码更改时都进行不必要的 Docker 镜像构建。
使用 SageMaker JumpStart 的预构建解决方案和预训练模型
SageMaker JumpStart 提供了从 AWS、TensorFlow Hub 和 PyTorch Hub 获取预构建的机器学习解决方案和预训练模型的访问。预构建的解决方案涵盖了许多常见用例,如欺诈检测、预测性维护和需求预测。预训练模型涵盖了自然语言处理、对象检测和图像分类等领域。我们可以使用自己的数据集对模型进行微调,并通过几次点击将其部署到我们 AWS 账户的生产环境中。我们将在第七章中深入探讨 SageMaker JumpStart。
使用 SageMaker 超参数调整调优和验证模型
开发高质量模型的另一个重要步骤是找到正确的模型配置或模型超参数。与算法学习的模型参数相比,超参数控制算法如何学习参数。
Amazon SageMaker 自带自动模型调优和验证功能,以找到最佳的模型超参数,适用于我们的模型和数据集。我们需要定义一个优化的客观指标,例如验证准确率,并探索要优化的超参数范围。然后,SageMaker 将运行多个模型训练作业,以探索我们指定的超参数范围,并根据客观指标评估结果以衡量成功。
有不同的策略可以探索超参数范围:网格搜索、随机搜索和贝叶斯优化是最常见的方法。我们将深入探讨 SageMaker 超参数调整在第八章中。
使用 Amazon SageMaker 和 AWS Lambda 函数部署模型
一旦我们已经训练、验证和优化了我们的模型,我们就可以准备部署和监控我们的模型。通常有三种方式可以使用 Amazon SageMaker 部署我们的模型,具体取决于我们的应用需求:SageMaker 端点用于基于 REST 的预测,AWS Lambda 函数用于无服务器预测,以及 SageMaker 批量转换用于批量预测。
SageMaker 端点
如果我们需要优化低延迟、实时预测的模型部署,我们可以使用 SageMaker 托管服务部署我们的模型。这些服务将启动一个 SageMaker 端点来托管我们的模型,并提供一个 REST API 来提供预测。我们可以从我们的应用程序调用 REST API 来接收模型预测结果。SageMaker 模型端点支持根据当前流量模式自动扩展,并在多个可用区部署以实现高可用性。
SageMaker 批量转换
如果我们需要为整个数据集获取预测结果,我们可以使用 SageMaker 批转换。批转换针对高吞吐量进行了优化,无需实时、低延迟预测。SageMaker 将启动指定数量的资源来对我们的 S3 数据执行大规模批量预测。作业完成后,SageMaker 将数据写入 S3 并拆除计算资源。
使用 AWS Lambda 进行无服务器模型部署
另一个用于提供模型预测的选项是 AWS Lambda 函数用于无服务器模型服务器。在使用 SageMaker 训练模型之后,我们使用 AWS Lambda 函数从 S3 检索模型并提供预测。AWS Lambda 具有内存和延迟限制,因此在最终确定部署方法之前,请务必在规模上进行测试。
AWS 上的流分析和机器学习
到目前为止,我们假设我们所有的数据都集中存储在一个静态位置,例如基于 S3 的数据湖。实际上,数据不断地从世界各地的许多不同来源同时流动。在许多情况下,我们希望在数据落入数据湖之前对这些流数据进行实时分析和机器学习。需要在短时间内(业务)洞察以获取竞争优势,并迅速响应变化的客户和市场趋势。
流技术为我们提供了在实时中收集、处理和分析数据流的工具。AWS 提供了多种流技术选项,包括 Amazon Kinesis 和 Amazon 管理的 Apache Kafka(Amazon MSK)。我们将深入探讨流分析和机器学习在 第十一章 中。
Amazon Kinesis 流式处理
Amazon Kinesis 是一个流数据服务,帮助我们实时收集、处理和分析数据。通过 Kinesis Data Firehose,我们可以连续准备和加载实时数据到各种目的地,包括 Amazon S3 和 Amazon Redshift。通过 Kinesis Data Analytics,我们可以处理和分析数据在到达时。通过 Amazon Kinesis Data Streams,我们可以管理数据流的摄取,用于定制应用程序。
Amazon 管理的 Apache Kafka 流式处理
Amazon MSK 是一个流式数据服务,管理 Apache Kafka 基础架构和运营。Apache Kafka 是一个流行的开源、高性能、容错和可扩展平台,用于构建实时流式数据管道和应用程序。使用 Amazon MSK,我们可以在 AWS 上运行我们的 Apache Kafka 应用程序,无需自行管理 Apache Kafka 集群。
流式预测和异常检测
在流数据章节中,我们将专注于分析我们从可用的在线渠道收集的连续产品评价消息流。我们将运行流式预测以检测客户的情绪,从而可以识别哪些客户可能需要高优先级的关注。
接下来,我们对传入的评论消息进行连续流分析,以捕获每个产品类别的平均情感。我们为业务线(LOB)所有者在度量仪表板中可视化连续的平均情感。
LOB 所有者现在可以快速检测情感趋势并采取行动。我们还计算传入消息的异常分数,以检测数据模式或数据值中的异常。在异常分数上升的情况下,我们可以提醒负责的应用程序开发人员调查根本原因。
作为最后的度量标准,我们还计算收到消息的持续近似计数。数字营销团队可以使用在线消息数量来衡量社交媒体活动的有效性。
AWS 基础设施和自定义构建硬件
云计算的一个关键优势是能够尝试特别匹配我们工作负载的基础设施选项。AWS 为我们的数据科学项目提供了许多高性能计算、网络和存储基础设施选项,如图 1-4 所示。让我们看看每个选项,我们将在整个过程中引用它们。

图 1-4. AWS 用于数据科学和机器学习项目的基础设施选项。
SageMaker 计算实例类型
AWS 允许我们根据工作负载选择多种实例类型。以下是常用于数据科学用例的实例类型列表:
T 实例类型
通用型、突发性能实例,当我们不需要持续高水平 CPU 时,但在需要时受益于快速 CPU
M 实例类型
具有良好计算、内存和网络带宽平衡的通用实例
C 实例类型
适用于计算密集型工作负载和高 CPU 需求的计算优化实例
R 实例类型
针对需要将大型数据集存储在内存中的工作负载进行优化的内存优化实例,如 Apache Spark
P、G、Inferentia 和 Trainium 实例类型
配备硬件加速器或协处理器(如 GPU 或亚马逊自定义硬件,例如 AWS Inferentia 用于推断和 AWS Trainium 用于训练工作负载)的高性能计算实例
Amazon Elastic 推断加速器
当需要特定工作负载的额外计算能力时,其他实例类型使用的网络附加协处理器,如批处理转换和推断
GPU 和亚马逊自定义计算硬件
类似于 Amazon S3 在云中提供存储,Amazon Elastic Compute Cloud(Amazon EC2)提供计算资源。我们可以根据业务需求和工作负载选择超过 350 个实例。AWS 还提供选择英特尔、AMD 和基于 ARM 的处理器。硬件加速的 P4、P3 和 G4 实例类型是基于 GPU 的高性能模型训练的热门选择。亚马逊还提供了优化用于模型训练和推断的自定义构建硬件。
P4d 实例由八个 NVIDIA A100 Tensor Core GPU 组成,具备 400 Gbps 的实例网络和支持带 NVIDIA GPUDirect RDMA(远程直接内存访问)的 Elastic Fabric Adapter(EFA)。P4d 实例部署在称为 Amazon EC2 超级集群 的超大规模集群中,为日常的机器学习开发人员、研究人员和数据科学家提供超级计算机级别的性能。每个 P4d 实例的 EC2 超级集群可以访问超过 4,000 个 NVIDIA A100 GPU、PB 级非阻塞网络以及通过 Amazon FSx for Lustre 实现的高吞吐量/低延迟存储。
P3 实例包含最多八个 NVIDIA V100 Tensor Core GPU,并提供高达 100 Gbps 的网络吞吐量。每个 P3 实例可以提供高达一 petaflop 的混合精度性能。P3dn.24xlarge 实例也支持 EFA。
G4 实例是成本敏感、小规模训练或推断工作负载的理想选择。G4 实例包含 NVIDIA T4 GPU,具备高达 100 Gbps 的网络吞吐量和高达 1.8 TB 的本地 NVMe 存储。
AWS 还提供了专为机器学习训练而定制的 AWS Trainium 芯片,以及专为推断工作负载而设计的 AWS Inferentia 芯片。AWS Trainium 和 AWS Inferentia 都旨在提高机器学习性能并降低基础设施成本。
AWS Trainium 已经针对深度学习训练工作负载进行了优化,包括图像分类、语义搜索、翻译、语音识别、自然语言处理和推荐引擎。
AWS Inferentia 处理器支持许多流行的机器学习模型,包括用于计算机视觉的单次检测器和 ResNet,以及用于自然语言处理的 Transformers 和 BERT。
AWS Inferentia 可通过 Amazon EC2 Inf1 实例使用。我们可以在每个 Inf1 实例中选择 1 到 16 个 AWS Inferentia 处理器,其提供高达 2,000 万亿次操作每秒的性能。我们可以使用 AWS Neuron SDK 来编译我们的 TensorFlow、PyTorch 或 Apache MXNet 模型以在 Inf1 实例上运行。
Inf1 实例有助于降低推断成本,每次推断的成本比 Amazon EC2 G4 实例低高达 45%,吞吐量则高出 30%。表 1-1 展示了用于模型推断的一些实例类型选项,包括 Amazon 自定义的 Inferentia 芯片、CPU 和 GPU。
表 1-1. 用于模型推断的 EC2 实例选项
| 模型特征 | EC2 Inf1 | EC2 C5 | EC2 G4 |
|---|---|---|---|
| 需要低延迟和高吞吐量以低成本 | X | ||
| 对延迟和吞吐量的敏感性低 | X | ||
| 需要 NVIDIA 的 CUDA、CuDNN 或 TensorRT 库 | X |
Amazon Elastic Inference 是利用加速计算进行模型推断的另一种选择。弹性推断允许我们将部分 GPU 加速附加到任何 Amazon EC2(基于 CPU)实例类型。使用弹性推断,我们可以将模型推断的实例选择与推断加速的数量分离开来。
如果我们需要不同于 Inf1 实例提供的实例特性,或者性能需求低于最小的 Inf1 实例提供的性能要求,选择 Elastic Inference 而不是 Inf1 可能是有意义的选择。
弹性推断从单精度 TFLOPS(每秒万亿浮点运算)扩展到 32 个混合精度 TFLOPS 的推断加速。
Graviton 处理器是 AWS 自定义的 ARM 处理器。这些 CPU 利用 64 位 Arm Neoverse 核心和 AWS 设计的使用先进的 7 纳米制造技术的定制硅。基于 ARM 的实例可以为在 Amazon EC2 中运行的许多工作负载提供具有吸引力的性价比。
Graviton 处理器的第一代提供了 Amazon EC2 A1 实例。与第一代相比,Graviton2 处理器的性能提升了 7 倍,计算核心增加了 4 倍,内存速度提升了 5 倍,缓存大小增加了 2 倍。我们可以在 Amazon EC2 T4g、M6g、C6g 和 R6g 实例中找到 Graviton2 处理器。
AWS Graviton2 处理器为视频编码工作负载提供了增强的性能,为压缩工作负载提供了硬件加速,并支持机器学习预测。
GPU 优化的网络和定制硬件
AWS 提供先进的网络解决方案,可以帮助我们有效地进行分布式模型训练和扩展推断。
Amazon EFA 是 Amazon EC2 实例的网络接口,可优化规模化的节点间通信。EFA 使用定制的 OS 旁路硬件接口,增强了节点间通信的性能。如果我们正在使用 NVIDIA Collective Communications Library 进行模型训练,我们可以使用 EFA 扩展到数千个 GPU。
我们可以将每个实例的网络带宽组合到 400 Gbps,并使用 NVIDIA GPUDirect RDMA 实现实例之间低延迟的 GPU 到 GPU 通信。这使我们可以在云中获得与本地 GPU 集群相当的性能,同时具有按需的弹性和灵活性。
针对大规模模型训练优化的存储选项
我们已经了解到,在 Amazon S3 上构建数据湖的好处。如果我们需要更快的存储访问以进行分布式模型训练,我们可以使用 Amazon FSx for Lustre。
Amazon FSx for Lustre 提供开源 Lustre 文件系统作为完全托管的服务。Lustre 是一种高性能文件系统,提供亚毫秒级的延迟,高达数百 GB/s 的吞吐量和数百万 IOPS。
我们可以将 FSx for Lustre 文件系统与 Amazon S3 存储桶链接起来。这使我们能够通过 FSx 文件系统访问和处理数据,并从 Amazon S3 获取数据。使用 FSx for Lustre,我们可以设置我们的模型训练计算实例通过高性能共享存储访问相同的数据集。
Amazon 弹性文件系统(Amazon EFS)是另一种文件存储服务,为多达数千个 Amazon EC2 实例提供文件系统接口。文件系统接口提供标准操作系统文件 I/O API,并支持文件系统访问语义,例如强一致性和文件锁定。
使用标签、预算和警报降低成本
本书中我们提供了如何利用亚马逊人工智能和机器学习堆栈来减少数据科学项目成本的建议。总体而言,我们应始终为我们的资源打上业务单位、应用程序、环境和用户的名称标签。我们应使用能够显示我们的花费情况的标签。除了 AWS 内置的成本分配标签外,我们还可以提供自己领域特定的用户定义的分配标签。AWS 预算帮助我们在成本接近或超过给定阈值时创建警报。
摘要
在本章中,我们讨论了在云中开发数据科学项目的好处,特别关注了 AWS。我们展示了如何利用亚马逊人工智能和机器学习堆栈快速为我们的应用程序增加智能。我们介绍了 AutoML 的概念,并解释了 SageMaker Autopilot 如何提供透明的 AutoML 方法。然后我们讨论了云中典型的机器学习工作流程,并介绍了在工作流程的每个步骤中帮助的相关 AWS 服务。我们概述了可用的工作流编排工具,用于构建和自动化机器学习流水线。我们描述了如何在实时数据上运行流式分析和机器学习。我们在本章末尾概述了 AWS 基础设施选项,以利用在我们的数据科学项目中使用。
在第二章中,我们将讨论媒体、广告、物联网和制造业等行业中显著的数据科学用例。
第二章:数据科学应用案例
在本章中,我们展示了 AI 和机器学习如何几乎颠覆了每个行业,并将继续在未来如此。我们讨论了跨媒体、广告、物联网和制造业等行业的重要应用案例。随着越来越多的构建模块变得可用,越来越多的应用案例变得具体可行。云原生开发人员通过像亚马逊 Rekognition 这样的即用 AI 服务、包括亚马逊 SageMaker 的完全可定制的 ML 服务,以及易于访问的亚马逊 Braket 量子计算机,可以访问这些构建模块。
AI 和机器学习由于云计算的最新创新、计算能力的飞跃和数据收集的激增,已经真正无处不在。这种 AI 和机器学习的民主化得益于大量易于与应用集成、几乎不需要维护并提供按需付费定价的 AI 服务的爆发。
在不需要数据科学博士学位的情况下,我们可以通过一次 API 调用实现产品推荐以取悦我们的客户,实现高度准确的预测模型以改善我们的供应链,或者构建虚拟助手简化我们的客户支持!这些 AI 服务释放了宝贵的人力资源,让其集中精力关注领域特定和产品差异化的特性。
创新横跨每个行业
许多 AI 和机器学习应用案例可以归为两类:改善业务运营或创建新的客户体验。改善业务运营的突出例子包括 AI 驱动的需求预测、资源优化和欺诈检测。用于创建新客户体验的例子包括个性化产品推荐和丰富的视频流体验。毫无疑问,AI 和机器学习正在推动每个行业的创新。以下是各种行业的几个示例:
媒体和娱乐
公司正在通过高度引人入胜、个性化的内容让客户感到愉悦。AI 还能够高效且有效地提取元数据,使媒体内容更易于客户和媒体制作工作者发现和搜索。
生命科学
公司通过 AI 和机器学习在药物发现、临床试验管理、药物制造、数字治疗开发和临床决策支持方面受益匪浅。
金融服务
AI 和机器学习改善了合规、监控和欺诈检测。它们帮助加快文件处理,创建个性化定价和金融产品推荐,并协助交易决策。
汽车
AI 和机器学习推动了自动驾驶、导航和互联车辆。
制造业
AI 和机器学习支持工程设计、供应链库存管理和维护、修理和运营的优化。它们增强了装配线并支撑智能产品或工厂。
游戏
游戏行业利用 AI 和机器学习实施智能自动扩展他们的游戏服务器,以应对一天中需求的变化。
让我们详细讨论一些突出的 AI 应用案例,看看我们如何开始使用 AWS 的现成 AI 服务来实施它们。
个性化产品推荐
近几十年来,消费者在个性化的在线产品和内容推荐方面体验越来越多。推荐无处不在,包括亚马逊推荐下一个要购买的产品和亚马逊 Prime Video 推荐下一个要观看的节目。
很多推荐系统基于客户如何与目录中的项目合作来找到相似性。早期实施这种“协同过滤”的例子在亚马逊公司 2003 年的一篇论文中有描述,“亚马逊推荐:项目到项目的协同过滤”。
今天,精密的深度学习技术能够在正确的时间和正确的上下文中理解客户的需求。无论我们是在亚马逊市场上购物,还是在 Prime Music 上听音乐,在 Prime Video 上观看节目,在 Amazon Kindle 上阅读电子书,或者在 Audible 上听有声书,我们都会被呈现出新的个性化推荐。
简单的推荐系统通常从基于规则的系统开始。随着我们系统中用户和产品的数量增加,定义足够特定以为每个用户提供有意义推荐的规则变得困难。广泛的规则通常不够专业化,无法吸引客户持续回归。
即使是基于机器学习的推荐系统也可能面临挑战。我们还必须处理新用户和新项目进入我们系统的情况,我们没有任何数据来基于它们建立推荐。这是现代推荐引擎中要解决的经典“冷启动”问题。冷启动是当我们没有或很少有历史事件活动用作信号来为给定用户或产品构建我们的推荐模型时的情况。
推荐系统还应避免“流行陷阱”,该陷阱只推荐热门项目,可能忽略了非热门项目的愉快推荐。这可以通过使用多臂老丨虎丨机等算法探索新项目的推荐系统来解决,我们将在第九章中讨论。
此外,我们希望在用户使用我们的应用程序时处理其实时意图的变化。这需要一个实时动态的推荐系统,而不是传统的离线预先计算的从数据库提供的推荐。
在这样一个动态系统中,客户将欣赏到相关及时的内容,而我们的业务将从更个性化的客户体验中获得以下好处:
产品参与度的增加
通过向用户推荐相关内容,我们增加了网站的黏性,鼓励用户经常回访,并使他们停留更长时间。
产品转化率增加
用户更有可能购买更相关的产品。
点击率增加
通过个性化产品更新,针对个人用户的目标,我们很可能会看到更高的点击率。
收入增加
当客户在适当的时间得到正确的推荐时,公司会看到收入增加。
流失减少
我们可以减少总体流失率,并减少对有趣电子邮件活动的退出。
在过去的二十年中,亚马逊一直在不断推进其个性化的机器学习研究。Smith 和 Linden(2017)的论文“亚马逊.com 的两个十年推荐系统” 很好地总结了这一历程。
小贴士
更多关于亚马逊的科学研究和学术出版物的深入了解,请访问亚马逊。
推荐使用亚马逊个性化推荐产品。
像很多机器学习一样,没有单一的算法可以解决个性化中所有的挑战。如果任何人都能够利用亚马逊.com 在创建个性化产品和内容推荐方面的丰富经验,并将此功能添加到我们的应用程序中,那不是很棒吗?亚马逊个性化正是提供这种功能。
亚马逊个性化反映了亚马逊.com 在创建、扩展和管理个性化技术方面数十年的经验。亚马逊个性化使开发人员能够轻松创建个性化产品推荐以及有针对性的营销促销活动。这种人工智能服务使开发人员能够构建定制的个性化模型,而无需处理自己的机器学习基础设施管理的复杂性。
要开始生成推荐,我们只需向亚马逊个性化提供我们应用程序的持续活动流(即点击、页面浏览、注册、购买)以及我们想要推荐的产品库存,如图 2-1 所示。
活动数据包括有关用户如何与系统进行交互的事件信息。一些示例事件活动包括用户点击、购物车添加、商品购买和电影观看。这些事件活动对于构建有效的推荐模型提供了强有力的信号。

图 2-1。提供活动数据集和产品清单给亚马逊个性化,开始生成推荐。
我们还可以提供有关参与活动的用户和产品的额外元数据,如产品类别、产品价格、用户年龄、用户位置等。尽管这些额外的元数据是可选的,但在处理“冷启动”场景(即没有或很少历史事件活动可用作信号来构建我们的推荐模型时),它们非常有帮助。
另外,Amazon Personalize 最近推出了一种新的冷启动算法,结合了神经网络和强化学习,在用户的数据很少时提供更相关的推荐。
有了这些事件活动和元数据,Amazon Personalize 为我们的用户和产品训练、调优和部署了一个定制的推荐模型。Amazon Personalize 执行机器学习管道的所有步骤,包括特征工程、算法选择、模型调优和模型部署。一旦 Amazon Personalize 为我们的数据集选择、训练和部署了最佳模型,我们只需调用 Amazon Personalize 的get_recommendations() API 即可实时生成用户的推荐:
get_recommendations_response = personalize_runtime.get_recommendations(
campaignArn = campaign_arn,
userId = user_id
)
item_list = get_recommendations_response['itemList']
recommendation_list = []
for item in item_list:
item_id = get_movie_by_id(item['itemId'])
recommendation_list.append(item_id)
使用包含数百万用户电影评分的流行 MovieLens 数据集进行训练,Amazon Personalize 为我们的示例用户生成以下推荐电影:
Shrek |
|---|
Amelie |
Lord of the Rings: The Two Towers |
Toy Story 2 |
Good Will Hunting |
Eternal Sunshine of the Spotless Mind |
Spirited Away |
Lord of the Rings: The Return of the King |
Schindler's List |
Leon: The Professional |
使用 Amazon SageMaker 和 TensorFlow 生成推荐
多任务推荐器创建一个模型,同时优化两个或更多目标。模型在模型训练期间通过任务之间共享变量进行迁移学习。
在下面使用TensorFlow 推荐器(TFRS)库的 TensorFlow 示例中,我们将找到一个模型,该模型训练一个推荐器来预测评分(排名任务)以及预测电影观看次数(检索任务):
user_model = tf.keras.Sequential([
tf.keras.layers.experimental.preprocessing.StringLookup(
vocabulary=unique_user_ids),
# We add 2 to account for unknown and mask tokens.
tf.keras.layers.Embedding(len(unique_user_ids) + 2, embedding_dimension)
])
movie_model = tf.keras.Sequential([
tf.keras.layers.experimental.preprocessing.StringLookup(
vocabulary=unique_movie_titles),
tf.keras.layers.Embedding(len(unique_movie_titles) + 2, embedding_dimension)
])
rating_task = tfrs.tasks.Ranking(
loss=tf.keras.losses.MeanSquaredError(),
metrics=[tf.keras.metrics.RootMeanSquaredError()],
)
retrieval_task = tfrs.tasks.Retrieval(
metrics=tfrs.metrics.FactorizedTopK(
candidates=movies.batch(128).map(self.movie_model)
)
)
使用 Amazon SageMaker 和 Apache Spark 生成推荐
Amazon SageMaker 通过 SageMaker 处理作业支持无服务器 Apache Spark(Python 和 Scala)。我们将在整本书中使用 SageMaker 处理作业执行数据质量检查和特征转换。但在本节中,我们将使用 SageMaker 处理作业与 Apache Spark ML 的协同过滤算法Alternating Least Squares(ALS)生成推荐。如果我们已经有一个基于 Spark 的数据管道,并希望使用该管道生成推荐,我们将使用此算法。
这是生成推荐使用 Apache Spark ML 和 ALS 的train_spark.py文件:
import pyspark
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.recommendation import ALS
from pyspark.sql import Row
def main():
...
lines = spark.read.text(s3_input_data).rdd
parts = lines.map(lambda row: row.value.split("::"))
ratingsRDD = parts.map(lambda p: Row(userId=int(p[0]),
movieId=int(p[1]),
rating=float(p[2]),
timestamp=int(p[3])))
ratings = spark.createDataFrame(ratingsRDD)
(training, test) = ratings.randomSplit([0.8, 0.2])
# Build the recommendation model using ALS on the training data
als = ALS(maxIter=5,
regParam=0.01,
userCol="userId",
itemCol="itemId",
ratingCol="rating",
coldStartStrategy="drop")
model = als.fit(training)
# Evaluate the model by computing the RMSE on the test data
predictions = model.transform(test)
evaluator = RegressionEvaluator(metricName="rmse",
labelCol="rating",
predictionCol="prediction")
rmse = evaluator.evaluate(predictions)
# Generate top 10 recommendations for each user
userRecs = model.recommendForAllUsers(10)
userRecs.show()
# Write top 10 recommendations for each user
userRecs.repartition(1).write.mode("overwrite")\
.option("header", True).option("delimiter", "\t")\
.csv(f"{s3_output_data}/recommendations")
现在让我们在作为 SageMaker 处理作业运行的无服务器 Apache Spark 环境中启动 PySpark 脚本:
from sagemaker.spark.processing import PySparkProcessor
from sagemaker.processing import ProcessingOutput
processor = PySparkProcessor(base_job_name='spark-als',
role=role,
instance_count=1,
instance_type='ml.r5.2xlarge',
max_runtime_in_seconds=1200)
processor.run(submit_app='train_spark_als.py',
arguments=['s3_input_data', s3_input_data,
's3_output_data', s3_output_data,
],
logs=True,
wait=False
)
输出显示用户 ID 和按推荐程度排序的推荐列表(项目 ID,排名):
|userId| recommendations|
+------+--------------------+
| 12|[[46, 6.146928], ...|
| 1|[[46, 4.963598], ...|
| 6|[[25, 4.5243497],...|
+------+--------------------+
使用 Amazon Rekognition 检测不适当的视频
计算机视觉对许多不同的用例很有用,包括用户生成内容的内容审核,安全登录的数字身份验证,以及无人驾驶汽车的危险识别。
Amazon Rekognition 是一个高级 AI 服务,能够识别图像和视频中的对象,包括人、文本和活动。Amazon Rekognition 使用 AutoML 来训练定制模型,以识别特定于我们用例和业务领域的对象。
让我们使用 Amazon Rekognition 来检测我们应用用户上传的视频中的暴力内容。例如,我们希望使用 Amazon Rekognition 的内容审查 API 来拒绝包含武器的视频,操作如下:
startModerationLabelDetection = rekognition.start_content_moderation(
Video={
'S3Object': {
'Bucket': bucket,
'Name': videoName,
}
},
)
moderationJobId = startModerationLabelDetection['JobId']
getContentModeration = rekognition.get_content_moderation(
JobId=moderationJobId,
SortBy='TIMESTAMP'
)
while(getContentModeration['JobStatus'] == 'IN_PROGRESS'):
time.sleep(5)
print('.', end='')
getContentModeration = rekognition.get_content_moderation(
JobId=moderationJobId,
SortBy='TIMESTAMP')
display(getContentModeration['JobStatus'])
这里是显示检测标签的输出。请注意Timestamp,它表示从视频开始的偏移量,以及Confidence,它表示 Amazon Rekognition 预测的置信度:
{'JobStatus': 'SUCCEEDED',
'VideoMetadata': {'Codec': 'h264',
'DurationMillis': 6033,
'Format': 'QuickTime / MOV',
'FrameRate': 30.0,
'FrameHeight': 1080,
'FrameWidth': 1920},
'ModerationLabels': [{'Timestamp': 1999,
'ModerationLabel': {'Confidence': 75.15272521972656,
'Name': 'Violence',
'ParentName': ''}},
{'Timestamp': 1999,
'ModerationLabel': {'Confidence': 75.15272521972656,
'Name': 'Weapons',
'ParentName': 'Violence'}},
{'Timestamp': 2500,
'ModerationLabel': {'Confidence': 87.55487060546875,
'Name': 'Violence',
'ParentName': ''}}]
Moderation labels in video
=======================================
At 1999 ms: Violence (Confidence: 75.15)
At 1999 ms: Weapons (Confidence: 75.15)
At 2500 ms: Violence (Confidence: 87.55)
注意
我们可以通过使用自己的数据集进行训练来进一步提高 Amazon Rekognition 预测的置信度。这一功能称为“自定义标签”,并得到许多 Amazon AI 服务的支持。
需求预测
需求预测在许多领域中被用来预测电力使用、供应链库存、呼叫中心人员配备、现金流规划、医院床位使用以及许多其他用例中的需求。预测是一个使用流行算法如自回归积分移动平均、误差趋势季节性、非参数时间序列、Prophet 和 DeepAR++解决的时间序列问题。
企业使用从简单的电子表格到复杂的时间序列软件来预测未来的业务结果,如产品需求、资源需求或财务表现。这些方法通常使用历史时间序列数据构建预测模型,假设未来需求由过去活动决定。这些纯时间序列方法在处理不规则趋势和模式时往往难以产生准确的预测。此外,这些方法通常不考虑与预测相关的重要元数据,如产品价格、产品类别和店铺位置。
过度预测可能会降低效率并增加成本,因为资源超配,而这些资源仍然被低效利用。反之,低估预测可能会降低客户满意度,通过使系统饥饿于必要资源而降低收入,并且需要像支付加班工资这样的高成本替代方案。
有效的需求预测系统展示了以下特征:
分析复杂关系而不仅仅是时间序列数据
将时间序列数据与产品特性和店铺位置等其他元数据结合起来。
将预测预测时间从几个月缩短到几小时
在自动加载、检查和识别关键数据集属性之后,系统将快速训练、优化并部署适合我们数据集的自定义模型。
为许多不同的用例创建预测
使用大量算法自动确定最适合我们特定用例的预测,包括供应链、物流和财务等几乎所有用例。
保持数据安全
每个数据点都受到静态和动态加密的保护,以保持敏感信息的安全和机密性。
在需要时自动重新训练和部署模型
当新数据到达或者模型评估指标低于某个阈值时,将重新训练并部署模型以改进预测。
使用 Amazon Forecast 预测能源消耗
Amazon Forecast 是一个完全托管的服务,基于驱动亚马逊.com 需求预测需求的技术,如高效库存管理,即时产品履行和当天交付。Forecast 使用机器学习自动训练,调整和优化高度专业的需求预测模型,从我们的数据集中。我们只需向 Forecast 注册我们的历史数据集和相关元数据,即可开始生成需求预测。需求预测可以导出为 CSV 文件,通过 AWS 控制台 UI 访问,或通过 Forecast API 集成到我们的应用程序中。
让我们使用 Forecast 的 DeepAR++ 算法和来自UCI 机器学习库的公共数据集来训练一个需求预测模型,以预测未来 24 小时的个体家庭能源消耗。
这是数据集的一部分,包括每位客户的功耗:
| timestamp | value | item |
|---|---|---|
| 2014-01-01 01:00:00 | 38.34991708126038 | client_12 |
| 2014-01-01 02:00:00 | 33.5820895522388 | client_12 |
| 2014-01-01 03:00:00 | 34.41127694859037 | client_12 |
这里是我们在 Forecast 中定义的模式,以表示公共数据集:
forecast_schema ={
"Attributes":[
{
"AttributeName":"timestamp",
"AttributeType":"timestamp"
},
{
"AttributeName":"target_value",
"AttributeType":"float"
},
{
"AttributeName":"item_id",
"AttributeType":"string"
}
]
}
让我们注册数据集到 Forecast:
response=forecast.create_dataset(
Domain="CUSTOM",
DatasetType='TARGET_TIME_SERIES',
DatasetName=forecast_dataset_name,
DataFrequency=DATASET_FREQUENCY,
Schema = forecast_schema
)
现在让我们使用 Forecast 训练需求预测模型:
forecast_horizon = 24 # hours
algorithm_arn = 'arn:aws:forecast:::algorithm/Deep_AR_Plus'
create_predictor_response = \
forecast.create_predictor(PredictorName=predictor_name,
AlgorithmArn=algorithm_arn,
ForecastHorizon=forecast_horizon,
PerformAutoML= False,
PerformHPO=False,
EvaluationParameters= {
"NumberOfBacktestWindows": 1,
"BackTestWindowOffset": 24
},
InputDataConfig= {
"DatasetGroupArn": forecast_dataset_group_arn
},
FeaturizationConfig= {
"ForecastFrequency": "H",
"Featurizations": [{
"AttributeName": "target_value",
"FeaturizationPipeline":
[{
"FeaturizationMethodName": "filling",
"FeaturizationMethodParameters": {
"frontfill": "none",
"middlefill": "zero",
"backfill": "zero"
}
}]
}]
})
让我们做出预测:
forecastResponse = forecastquery.query_forecast(
ForecastArn=forecast_arn,
Filters={"item_id":"client_12"}
)
预测亚马逊 EC2 实例需求与亚马逊预测
AWS 使用 Forecast 预测 Amazon Redshift 集群内 Amazon EC2 实例的需求。随着新数据被摄入 Forecast,Amazon Redshift 控制平面查询 Forecast 调整 Amazon EC2 温暖池的大小,如图 2-2 所示。

图 2-2. Amazon Redshift 控制平面使用 Forecast 调整 Amazon EC2 实例的温暖池缓存。
图 2-2 中的步骤可以描述如下:
-
Amazon EC2 温暖池缓存需求的变化被发布到 S3。
-
Forecast 摄取来自 S3 的需求数据,然后创建新的预测。
-
一个 Lambda 函数将新的预测预测复制到 Amazon DynamoDB。
-
Amazon EC2 集群缩放器从 DynamoDB 读取预测预测,并根据预计的需求调整温暖池缓存大小。
使用亚马逊欺诈检测器识别假账户
每年全球都会因在线诈骗而损失数十亿美元。在线公司尤其容易受到恶意行为者的攻击,他们试图通过创建假用户账户并使用盗窃的信用卡购买商品来欺诈系统。通常检测恶意行为者的欺诈检测系统往往依赖于业务规则,这些规则对最新的欺诈技术适应缓慢。
有效的欺诈检测和隐私泄漏预防系统具备以下特点:
在他们影响我们的业务之前,制止恶意行为者
在恶意行为者造成实际伤害之前标记可疑活动。
高质量的欺诈检测模型无需大量数据
预先训练的算法可以分析即使是最少量的历史事件数据,并且仍能提供高质量的欺诈检测模型。
让欺诈团队更快速、更有控制力
当新的事件数据可用时,自动处理构建、训练、调优、部署和更新我们的欺诈检测模型所需的复杂任务。
亚马逊欺诈检测器是一个完全托管的服务,可以识别潜在的欺诈在线活动,如在线支付和虚假账户。亚马逊欺诈检测器利用来自 AWS 和 Amazon.com 的 20 年欺诈检测专业知识和机器学习。
使用亚马逊欺诈检测器,我们只需点击几下,上传相对较少的历史数据和最小量的代码即可创建一个欺诈检测模型。我们只需上传我们的历史在线事件数据,如在线交易和账户注册,亚马逊欺诈检测器将完成其余工作,包括训练、调优和部署为我们业务量身定制的欺诈检测模型。
这是训练亚马逊欺诈检测器使用我们的交易数据集的代码:
response = client.create_model_version(
modelId = MODEL_NAME,
modelType = 'ONLINE_FRAUD_INSIGHTS',
trainingDataSource = 'EXTERNAL_EVENTS',
trainingDataSchema = trainingDataSchema,
externalEventsDetail = {
'dataLocation' : S3_FILE_LOC,
'dataAccessRoleArn': ARN_ROLE
}
)
这里是用于预测给定交易是否欺诈的代码:
pred = client.get_event_prediction(
detectorId = DETECTOR_NAME,
detectorVersionId = DETECTOR_VER,
eventId = str(eventId),
eventTypeName = EVENT_TYPE,
eventTimestamp = timestampStr,
entities = [{'entityType': ENTITY_TYPE,
'entityId':str(eventId.int)}],
eventVariables = record)
record["score"] = pred['modelScores'][0]['scores']\
["{0}_insightscore".format(MODEL_NAME)]
这是亚马逊欺诈检测器预测的输出,显示相关数据、预测结果和预测置信度:
| IP 地址 | 电子邮件地址 | 州 | 邮政编码 | 姓名 | 电话号码 | 分数 | 结果 |
|---|---|---|---|---|---|---|---|
| 84.138.6.238 | synth1@yahoo.com | 洛杉矶 | 32733 | Brandon Moran | (555)784 - 5238 | 5.0 | [批准] |
| 194.147.250.63 | synth2@yahoo.com | 明尼苏达州 | 34319 | Dominic Murray | (555)114 - 6133 | 4.0 | [批准] |
| 192.54.60.50 | synth3@gmail.com | 华盛顿州 | 32436 | Anthony Abbott | (555)780 - 7652 | 5.0 | [批准] |
| 169.120.193.154 | synth4@gmail.com | 阿拉巴马州 | 34399.0 | Kimberly Webb | (555)588 - 4426 | 938.0 | [审核] |
| 192.175.55.43 | synth5@hotmail.com | 伊利诺伊州 | 33690.0 | Renee James | (555)785 - 8274 | 16.0 | [批准] |
使用亚马逊 Macie 启用隐私泄漏检测
一种良好配置的应用程序会生成许多日志和指标,以增加洞察力并保持高系统运行时间,避免客户不满。然而,有时这些日志包含敏感账户信息,如家庭邮政编码或信用卡号码。我们需要一种系统来监控我们的数据以寻找敏感信息,检测对此类信息的访问,并在检测到未经授权访问或数据泄露时发送通知。
有效的系统用于检测和监控对敏感信息的访问具有以下特点:
持续评估数据敏感性和访问控制
攻击者 ROI 数学决定了一个具有敏感客户数据和松散配置的 IAM 角色的 S3 存储桶是一个容易的目标。我们通过持续监控整个 S3 环境并生成可操作步骤,以在需要时快速响应,保持领先地位。
支持多种数据源
评估数据敏感性,并评估跨多个不同数据源(例如 S3、Amazon 关系数据库服务(Amazon RDS)、Amazon Aurora、电子邮件、文件共享、协作工具等)的访问控制。
维护法规合规性
除了监控和保护敏感数据之外,合规团队还需要提供证据,证明他们正在执行数据安全和隐私以满足法规合规要求。
在数据迁移期间识别敏感数据
在将大量数据迁移到 AWS 时,我们希望知道数据是否包含敏感信息。如果是这样,当数据迁移时,我们可能需要更新安全访问控制、加密设置和资源标签。
Amazon Macie 是一种完全托管的安全服务,利用机器学习在我们基于 AWS 的数据源中识别诸如个人身份信息等敏感数据。Macie 提供了数据存储位置的可视性以及访问数据的人员信息。通过监控对敏感数据的访问,Macie 可以在检测到泄漏或泄漏风险时发送警报。
Macie 持续识别敏感数据并评估对这些数据的安全和访问控制。Macie 帮助维护数据的隐私和安全,并为调度数据敏感性和访问控制分析提供全面的选项,以满足我们的数据隐私和合规要求。
我们可以安排每天、每周或每月的发现作业,以生成我们的发现结果,包括评估结果、时间戳以及所有敏感数据扫描的存储桶和对象的历史记录。这些发现结果总结在符合数据隐私和保护审核标准的标准报告中,以确保长期数据保留。对于数据迁移,Macie 在我们的数据进入 AWS 时自动配置数据保护和基于角色的访问策略。
对话设备和语音助手
无论是 Alexa 还是其他著名的家庭语音助手,它们都使用最先进的深度学习技术,涉及自动语音识别(ASR)和自然语言理解(NLU)领域,以识别我们所说文本的意图。
使用 Amazon Lex 进行语音识别
使用 Amazon Lex 构建语音和文本的对话界面,我们可以访问支持 Amazon Alexa 的深度学习技术。Amazon Lex 是一个完全托管的服务,使用 ASR 将语音转换为文本。Amazon Lex 还使用 NLU 来识别文本的意图。我们可以为广泛的语音和文本查询构建定制响应,例如“这个办公室里的 IT 帮助台在哪里?”和“为接下来的 30 分钟预订这个房间。”
使用 Amazon Polly 进行文本转语音转换
Amazon Polly 是一个自动化的文本转语音服务,提供多种语言、方言和性别的数十种人声。我们可以使用 Amazon Polly 构建支持语音的应用程序,将文本转换为人类般的语音,例如用于辅助功能。
使用 Amazon Transcribe 进行语音转文本转换
Amazon Transcribe 是一个 ASR 服务,使开发人员能够轻松将语音转换为文本,从而为其实时和批处理应用程序增加了此功能。Amazon Transcribe 通过批处理或实时处理音频来将语音转换为文本。Amazon Transcribe 的流行用例包括创建图像标题和视频字幕。
文本分析和自然语言处理
自然语言处理(NLP)是人工智能的一个领域,专注于机器阅读、理解和从人类语言中获取意义的能力。这是一个研究已久的领域,早在 1900 年代初就有相关的研究论文发表。
快进到 2021 年,我们依然见证了具有开创性的 NLP 研究,几乎每个月都会出现新的语言模型。在接下来的章节中,我们将讨论 NLP 算法的演变,探讨新型的 Transformer 神经网络架构,并深入探讨 BERT 系列的 NLP 算法。
有效的文本分析和认知搜索系统具有以下特点:
快速发现时间
新文档应快速变得可搜索,而不需要人工校正错误。
高效的处理工作流程
文档处理工作流程应自动化,以提高速度和质量,同时减少人力成本、定制代码和费用。
使用 Amazon Translate 翻译语言
在今天的全球经济中,我们需要通过将内容翻译成许多本地化、区域特定的多语言版本来吸引国际用户。流行的用例包括对用户生成内容进行按需翻译、通信应用程序的实时翻译以及对社交媒体内容进行多语言情感分析。
Amazon Translate 是一个神经机器翻译服务,比传统的统计和基于规则的翻译模型创建更准确流畅的翻译。
使用 Amazon Comprehend 对客户支持信息进行分类
客户迷恋是亚马逊的关键领导原则之一——客户关注对于每一个业务和行业都很重要。在许多情况下,客户的体验很大程度上受到客户支持质量的影响。在本节中,我们将使用 Amazon Comprehend 来对样本客户支持信息的情感进行分类。
文本分类是自然语言处理领域中的一项流行任务。正如前面描述的,我们可以使用 Amazon Comprehend 作为完全托管的自然语言处理服务来实现文本分类,而无需太多的机器学习经验。
更广泛地说,Amazon Comprehend 可以从我们的文本文档中识别重要实体、关键短语、情感、语言和主题。重要实体包括姓名、地点、物品和日期。关键短语包括“早上好”、“谢谢”和“不高兴”。情感包括“积极的”、“中性的”和“消极的”。Amazon Comprehend 目前支持许多语言,并且会定期添加新的语言支持。
注意
Amazon Comprehend 还支持一组名为Amazon Comprehend Medical的医疗 API。Amazon Comprehend Medical 已经在广泛的医疗数据集上进行了预训练,可以识别医疗条件、药物、检查、治疗、程序、解剖和受保护的健康信息。
让我们看看如何使用 Amazon Comprehend 的开箱即用情感分析 API,只需几行代码就可以对样本产品评论进行分类。
首先,让我们使用 Amazon Comprehend 的 create_document_classifier() API 来创建分类器:
training_job = comprehend.create_document_classifier(
DocumentClassifierName=comprehend_training_job_name,
DataAccessRoleArn=iam_role_comprehend_arn,
InputDataConfig={
'S3Uri': comprehend_train_s3_uri
},
OutputDataConfig={
'S3Uri': s3_output_job
},
LanguageCode='en'
)
接下来,让我们使用分类器使用 Amazon Comprehend 的 detect_sentiment() API 预测一个正面评论的情感:
txt = """I loved it! I will recommend this to everyone."""
response = comprehend.detect_sentiment(
Text=txt
)
这是输出:
{
"SentimentScore": {
"Mixed": 0.030585512690246105,
"Positive": 0.94992071056365967,
"Neutral": 0.0141543131828308,
"Negative": 0.00893945890665054
},
"Sentiment": "POSITIVE",
"LanguageCode": "en"
}
接下来,让我们使用分类器使用 Amazon Comprehend 的 detect_sentiment() API 预测一个负面评论的情感:
txt = """Really bad. I hope they don't make this anymore."""
response = comprehend.detect_sentiment(
Text=txt
)
这里是负面评论的输出:
{
"SentimentScore": {
"Mixed": 0.030585512690246105,
"Positive": 0.00893945890665054,
"Neutral": 0.0141543131828308,
"Negative": 0.94992071056365967
},
"Sentiment": "NEGATIVE",
"LanguageCode": "en"
}
使用 Amazon Comprehend Custom Labels,我们可以训练 Amazon Comprehend 来预测特定于我们数据集的自定义标签。
注意
在第三章中,我们将训练一个定制的 Amazon Comprehend 模型,将支持消息分类为星级评分(1-5),这是情感分析的更精细形式。我们将使用 Amazon Customer Reviews 数据集。
使用 Amazon Textract 和 Comprehend 提取简历详细信息
组织长期以来一直在努力高效处理半结构化文档,使其易于索引和搜索。文档处理通常需要大量的定制和配置。Amazon Textract 是一个完全托管的服务,通过光学字符识别(OCR)和机器学习从我们扫描的文档中自动提取信息,以准确地提取文本。
亚马逊文本提取不仅仅是 OCR,它还使用 NLP 来解析和保存文档中找到的特定单词、短语、日期和数字。结合亚马逊理解,亚马逊文本提取可以构建和维护我们文档内容的智能索引。我们还可以使用亚马逊文本提取构建自动化文档处理工作流程,并维护文档档案的合规性。
在扫描和解析 PDF 简历之后,亚马逊文本提取生成这个基于文本的版本:
NAME
...
LOCATION
...
WORK EXPERIENCE
...
EDUCATION
...
SKILLS
C (Less than 1 year), Database (Less than 1 year),
Database Management (Less than 1 year),
Database Management System (Less than 1 year),
Java (Less than 1 year)
...
TECHNICAL SKILLS
Programming language: C, C++, Java
Oracle PeopleSoft
Internet of Things
Machine Learning
Database Management System
Computer Networks
Operating System worked on: Linux, Windows, Mac
...
NON-TECHNICAL SKILLS
Honest and Hard-Working
Tolerant and Flexible to Different Situations
Polite and Calm
Team-Player
让我们训练亚马逊理解来理解一个称为“SKILLS”的新概念,这是特定于我们简历领域的。
comprehend_client = boto3.client('comprehend')
custom_recognizer_name = 'resume-entity-recognizer-'+ str(int(time.time()))
comprehend_custom_recognizer_response = \
comprehend_client.create_entity_recognizer(
RecognizerName = custom_recognizer_name,
DataAccessRoleArn = iam_role_textract_comprehend_arn,
InputDataConfig = {
'EntityTypes': [
{'Type': 'SKILLS'},
],
'Documents': {
'S3Uri': comprehend_input_doucuments
},
'EntityList': {
'S3Uri': comprehend_input_entity_list
}
},
LanguageCode='en'
)
使用我们与亚马逊理解构建的新技能实体识别器,我们可以对之前从 PDF 中提取的基于文本的简历执行实体识别:
# Start a recognizer Job:
custom_recognizer_job_name = 'recognizer-job-'+ str(int(time.time()))
recognizer_response = comprehend_client.start_entities_detection_job(
InputDataConfig = {
'S3Uri': s3_test_document,
'InputFormat': 'ONE_DOC_PER_LINE'
},
OutputDataConfig = {
'S3Uri': s3_test_document_output
},
DataAccessRoleArn = iam_role_textract_comprehend_arn,
JobName = custom_recognizer_job_name,
EntityRecognizerArn = comprehend_model_response['EntityRecognizerProperties']\
['EntityRecognizerArn'],
LanguageCode = 'en'
)
这是我们自定义的亚马逊理解实体识别器的输出,包括文档中的文本、偏移量、实体类型(SKILLS)和预测置信度:
| 开始偏移量 | 结束偏移量 | 置信度 | 文本 | 类型 |
|---|---|---|---|---|
| 9 | 39 | 0.9574943836014351 | 分析和解决问题 | SKILLS |
| 8 | 11 | 0.7915781756343004 | AWS | SKILLS |
| 33 | 41 | 0.9749685544856893 | 解决方案 | SKILLS |
| 20 | 23 | 0.9997213663311131 | SQL | SKILLS |
| 2 | 13 | 0.9996676358048374 | 编程 | SKILLS |
| 25 | 27 | 0.9963501364429431 | C, | SKILLS |
| 28 | 32 | 0.9637213743240001 | C++, | SKILLS |
| 33 | 37 | 0.9984518452247634 | Java | SKILLS |
| 39 | 42 | 0.9986466628533158 | PHP | SKILLS |
| 44 | 54 | 0.9993487072806023 | JavaScript | SKILLS |
认知搜索与自然语言理解
在某个时间点,我们都曾经历过在网站、企业内容管理系统、企业 wiki 或企业文件共享中深埋的相关信息的寻找困难。我们也知道反复回答同样经常问到的问题的痛苦。
提供相关和及时的搜索结果是一个长期存在的问题,产生了许多开源解决方案,包括 Apache Lucene、Apache SOLR 和 Elasticsearch。这些解决方案根植于多年前创建的较旧的 NLP 技术。当与这些解决方案交互时,我们通常发出关键字搜索,如果输入不正确或顺序混乱,可能导致搜索结果不佳。
认知搜索是解决发现信息这一古老问题的现代解决方案。基于现代自然语言理解,认知搜索允许最终用户提出像人类自然提问的自然语言问题。
亚马逊肯德拉利用机器学习、自然语言理解和认知搜索以现代方式解决企业搜索问题。与需要额外努力去提炼的传统关键词搜索不同,我们可以向亚马逊肯德拉提出完全自然语言的问题,比如,“IT 部门在这个办公室的哪一层?”并得到一个具体的答案,如“19 楼”。
Amazon Kendra 集成了许多不同的数据源,包括 Amazon S3、SharePoint、Salesforce、ServiceNow、Amazon RDS 数据库、OneDrive 等等。它支持所有类型的数据架构,包括结构化、非结构化和半结构化。它还支持多种不同的格式,包括 PDF、HTML、富文本、Microsoft Word 和 PowerPoint。
虽然 Amazon Kendra 默认带有跨多个领域优化的多个预训练模型,但我们可以使用我们的数据集训练 Amazon Kendra 来提高结果的准确性。此外,Amazon Kendra 还根据最终用户的使用模式(包括特定搜索结果的赞和踩)积极学习和重新训练自己。
结合 Amazon Kendra 和 Lex,我们可以跨多种设备构建支持聊天机器人,帮助回答常见问题。在此示例中,我们还包括了流行的工作协作工具 Slack,如 图 2-3 所示。

图 2-3. 使用 Slack、Amazon Lex 和 Amazon Kendra 自动回答问题。
下面是一个样本对话,其中 Antje 在直播研讨会期间使用 Slackbot 提问。聊天机器人回答与参与者常见问题相关的问题。这使得研讨会的主持人可以专注于需要人工干预的更复杂问题:
Antje: “嗨。”
Slackbot: “你好!我能帮忙什么?”
Antje: “你们录制这个研讨会吗?”
Slackbot: “是的,这个研讨会正在录制。”
Antje: “我在哪里找到录音?”
Slackbot: “录音将在 https://youtube.datascienceonaws.com 上在 24 小时内分享。”
Antje: “你知道如何开始使用 SageMaker 吗?”
Slackbot: “我认为你问题的答案是:在 Amazon SageMaker Studio 页面上,在开始使用中,选择快速开始,然后选择创建工作室域。”
智能客户支持中心
质量客户支持对每个行业和企业都非常重要(正如前文所述,客户至上是亚马逊的关键领导原则之一)。在许多情况下,客户支持直接影响客户对企业的感知。Amazon Connect 是一种云联系中心解决方案,利用机器学习提供智能联系中心功能。通过 Connect Wisdom,客户支持代理可以简单地输入问题,例如“书籍的换货政策是什么?”,Wisdom 将返回最相关的信息和最佳答案。Wisdom 还对现场通话记录运行机器学习,自动识别客户问题并向代理推荐响应。
Contact Lens for Amazon Connect 为基于相同技术支持亚马逊客服的云联系中心服务 Amazon Connect 添加了机器学习功能。Contact Lens 使用语音转文本转录、自然语言处理(NLP)和认知搜索能力分析客户-代理互动。
通过自动索引通话转录,Contact Lens 允许我们搜索特定词语、短语和情绪,同时从转录中删除敏感信息以避免泄露。Contact Lens 帮助主管实时发现互动中的重复主题,自动培训代理人以提高他们的客户支持技能,并基于客户使用的关键词和短语连续分类联系人。
使用 Contact Lens for Amazon Connect,联系中心主管可以单一视图查看客户与代理人的互动、产品反馈趋势以及潜在的合规风险。Amazon Connect 复制成功的互动,突出产品反馈异常,并将不良的客户与代理人互动升级至主管。
工业 AI 服务与预测性维护
作为 AWS 工业服务组合的一部分,AWS 提供一系列 AI 服务和硬件,包括 Amazon Lookout for Metrics、Lookout for Vision、Lookout for Equipment、Amazon Monitron 和 AWS Panorama。
我们可以使用 Amazon Lookout for Metrics 创建准确的异常检测模型。上传数据后,Lookout for Metrics 将自动检查数据并构建异常检测模型。如果模型检测到异常,服务将将相关异常分组并分配严重性评分。Lookout for Metrics 配备了与流行数据源的内置连接器,包括 Amazon S3、Amazon Redshift、Amazon CloudWatch、Amazon RDS 以及各种 SaaS 应用程序。异常检测模型利用人在环回反馈持续改进。
我们可以使用 Amazon Lookout for Vision 来发现产品缺陷。Lookout for Vision 利用计算机视觉来识别物体的视觉缺陷。它可以帮助自动检测零部件的损坏,识别缺失的组件,或者发现制造线上的流程问题。Lookout for Vision 已经配备了预训练的异常检测模型。我们只需对特定的图像进行微调。
我们可以使用 Amazon Lookout for Equipment 监控设备的健康和效率。将历史设备传感器数据上传到 Lookout for Equipment,服务将构建一个定制的机器学习模型来检测任何异常设备行为。此外,服务还将自动发送警报,以便我们采取行动。Lookout for Equipment 适用于任何时间序列模拟数据,包括温度、流量等传感器数据。
我们可以使用 Amazon Monitron 实现端到端的预测性维护用例,包括设备传感器、安全连接到 AWS 的网关设备,以及用于分析异常机器模式数据的托管服务。Amazon Monitron 捕获我们设备的传感器数据,识别健康的传感器模式,并训练特定设备的机器学习模型。我们可以通过 Amazon Monitron 移动应用程序提供反馈以改进模型,例如。
我们可以通过 AWS Panorama 为本地摄像头启用计算机视觉,它配备了一个硬件设备,可以连接到我们的网络和现有摄像头。然后,我们可以将计算机视觉应用部署到该设备上,以处理连接摄像头的视频流。摄像头设备制造商可以使用 AWS Panorama SDK 构建新的摄像头,在边缘运行计算机视觉模型。
使用 AWS IoT 和 Amazon SageMaker 的家庭自动化
我们生活在一个估计有五十亿人拥有某种移动设备的世界,超过一半的互联网流量通过移动设备产生。此外,工业物联网(IoT)革命引领着数十亿个连接的传感器和设备进入我们的家庭、办公楼、工厂、汽车、船只、飞机、油井、农田等领域。
移动和物联网设备的这一趋势也推动计算移到边缘,无论我们是出于数据隐私合规的原因需要在将数据发送和导入到中央数据湖之前分析和预处理数据,还是通过更快地提供应用程序响应来改善用户体验,消除到云端的往返延迟。我们还看到越来越多的机器学习发生在边缘。虽然训练机器学习模型通常需要强大的计算资源,但对这些模型进行推断通常需要远低于此的计算能力。
在边缘执行推断有助于减少延迟和成本,因为我们节省了到云端的往返时间。我们还可以更快地捕获和分析预测结果,本地触发某些动作,或将分析数据发送回云端以重新训练和改进我们的机器学习模型。
AWS IoT Greengrass 将模型从 S3 部署到边缘,以便利用边缘本地数据进行预测。AWS IoT Greengrass 还将模型推断结果同步回 S3 存储桶。然后可以使用这些预测数据来重新训练和改进 SageMaker 模型。AWS IoT Greengrass 支持空中部署,本地在每个设备上运行,并将 AWS 扩展到这些设备。
图 2-4 展示了一个运行 AWS IoT Greengrass 的家庭自动化用例,本地家庭自动化服务器称为“边缘设备”。AWS IoT Greengrass 将一个 SageMaker 模型部署到边缘设备,并使用在边缘设备上运行的 Lambda 的边缘版本处理从摄像头、灯开关和灯泡接收的数据。

图 2-4. 使用 AWS IoT Greengrass 的家庭自动化用例。
AWS 提供了多种服务来实现边缘上的机器学习,包括 AWS IoT Greengrass 用于模型部署到边缘,SageMaker Neo 用于模型优化,以及 SageMaker Edge Manager 用于管理边缘上的模型。我们将在第九章深入探讨 SageMaker Neo 和 Edge Manager。
从医疗文件中提取医疗信息
在医疗领域,AWS 提供许多专用服务。这些服务专门针对医疗数据的特性和需求进行开发,并符合医疗法规。作为亚马逊 AI 符合 HIPAA 标准的医疗健康服务组合的一部分,AWS 提供了 Amazon Comprehend Medical、Amazon Transcribe Medical 和 Amazon HealthLake。
Comprehend Medical 是一个专门针对医疗语言进行了预训练的 NLP 服务。Comprehend Medical 自动化从医疗文本中提取健康数据,例如医生的笔记、临床试验报告或患者健康记录。
Transcribe Medical 是一个已经在医疗语言上进行了预训练的 ASR 服务。我们可以使用 Transcribe Medical 将医疗语音转录为文本。通过简单的 API 调用,我们可以自动化临床文档工作流程,甚至为远程医疗提供字幕。
HealthLake 是一个符合快速医疗互操作资源行业标准的安全数据湖。除了存储、索引和转换医疗数据外,Amazon HealthLake 还利用机器学习从原始数据中识别、理解和提取医疗信息,例如医疗报告和患者笔记。我们可以使用 Amazon QuickSight、Athena 和 SageMaker 在我们的医疗数据上运行高级分析和机器学习。
自我优化和智能化云基础设施
到目前为止,我们介绍的亚马逊 AI/ML 服务并不是唯一提供复杂机器学习功能的服务。事实上,越来越多的现有 AWS 服务正在丰富机器学习功能,并且在各种用例中推出新的基于机器学习的服务。让我们快速浏览一些这些潜力明星。
预测自动缩放 Amazon EC2
Amazon EC2,即弹性计算云,在 AWS 云中提供虚拟服务器实例。在这些 Amazon EC2 实例上运行我们的应用程序的一个挑战是如何确保根据当前工作负载扩展实例数量,基本上是匹配供给和需求。幸运的是,有 Amazon EC2 Auto Scaling,它可以帮助我们做到这一点。根据需求的变化,我们可以配置 Amazon EC2 Auto Scaling 自动增加或减少计算能力。然而,这种动态缩放方法仍然是响应性的,因为它根据监控流量和 Amazon EC2 实例利用率指标来执行。
我们可以通过与名为AWS 自动缩放的服务结合,进一步采取主动的方法。AWS 自动缩放提供了一个单一接口来设置多个 AWS 服务的自动扩展,包括 Amazon EC2。它结合了动态和预测缩放。通过预测缩放,AWS 使用机器学习算法根据每日和每周的趋势预测我们未来的流量,并提前配置正确数量的 Amazon EC2 实例,如图 2-5 所示。

图 2-5. 使用 AWS 自动缩放进行预测缩放,以预测流量变化,从而提供正确数量的 Amazon EC2 实例。
数据流中的异常检测
流技术为我们提供了在实时收集、处理和分析数据流的工具。AWS 提供了广泛的流技术选项,包括 Amazon MSK 和 Amazon Kinesis。虽然我们在第十章深入研究使用 Amazon Kinesis 和 Apache Kafka 进行流分析和机器学习,但我们想要强调 Kinesis Data Analytics 作为仅需几行代码即可创建流应用程序的简单而强大的方法。
Kinesis Data Analytics 提供了使用 Kinesis Data Analytics 中的 Random Cut Forest (RCF)函数进行实时构建机器学习模型并计算每条消息中数值的异常分数的内置功能。该分数指示值与观察到的趋势的不同程度。RCF 函数还计算每列的归因分数,反映了该特定列中数据的异常性。所有列的所有归因分数之和即为总体异常分数。
认知和预测商业智能
许多机器学习应用程序和模型假设数据在数据湖中随时可用(在第四章讨论)。然而,实际上,世界上大部分数据存储和处理在结构化的关系数据库中。为了将机器学习应用于这些结构化数据,我们必须将数据导出或开发定制应用程序来读取数据,然后才能应用机器学习。如果我们可以直接从我们的商业智能服务、数据仓库或数据库使用机器学习,那岂不是太棒了?让我们看看如何在 AWS 上实现这一点。
使用 Amazon QuickSight 进行自然语言提问
Amazon QuickSight 是一个商业智能服务,可以在数据源如 Amazon Redshift、Amazon RDS、Amazon Athena 和 Amazon S3 上执行交互式查询和构建可视化。QuickSight 还可以通过 QuickSight ML Insights 和 QuickSight Q 从我们的数据中检测异常、创建预测并回答自然语言问题。
QuickSight ML Insights 运行 RCF 算法来识别数亿数据点中数百万度量的变化。ML Insights 还能基于观察到的度量进行预测。RCF 算法会自动检测数据中的季节性模式,排除任何异常值,并填补缺失值。
使用 QuickSight Q,我们可以询问诸如“在加利福尼亚州哪些产品类别畅销?”之类的自然语言问题。QuickSight 使用机器学习理解问题,将问题应用于我们的数据,并创建图表来回答我们的问题,如图 2-6 所示。我们将在第五章深入探讨 QuickSight。

图 2-6. QuickSight Q 理解自然语言问题,并自动创建图表来回答这些问题。
用 Amazon Redshift 训练和调用 SageMaker 模型
Amazon Redshift 是一个完全托管的数据仓库,允许我们对 PB 级结构化数据运行复杂的分析查询。使用 Amazon Redshift ML,我们可以在 Amazon Redshift 中使用我们的数据,随着新数据到来训练 SageMaker Autopilot 模型。SageMaker Autopilot 会自动训练、调优和部署模型。然后,我们在 Amazon Redshift 查询中注册并调用该模型作为用户定义函数(UDF)。图 2-7 展示了我们如何使用USING FUNCTION SQL 子句进行预测。我们将在第三章中展示 Amazon Redshift ML 和 SageMaker Autopilot 的更详细示例。

图 2-7. Amazon Redshift 使用 SageMaker Autopilot 来训练和调用 SageMaker 模型作为 UDF。
注意
我们可以创建一个 UDF 来使用 Lambda 函数调用任何 AWS 服务。此示例 UDF 调用一个 Lambda 函数:
USING FUNCTION invoke_lambda(input VARCHAR)
RETURNS VARCHAR TYPE LAMBDA_INVOKE WITH
(lambda_name='<LAMBDA_NAME>') SELECT invoke('<INPUT>');
从 Amazon Aurora SQL 数据库调用 Amazon Comprehend 和 SageMaker 模型
Aurora 是一个与 MySQL 和 PostgreSQL 兼容的关系数据库,与 Amazon Comprehend 和 Amazon SageMaker 本地集成,如图 2-8 所示。

图 2-8. Aurora ML 可以调用 Amazon Comprehend 和 SageMaker 中的模型。
我们可以在查询中使用内置 SQL 函数(与 Amazon Comprehend 一起)或自定义编写的 SQL 函数(与 Amazon SageMaker 一起)来将机器学习应用于数据。正如前面所示,我们可以利用 Amazon Comprehend 进行客户情感分析(内置 SQL 函数),可能是产品评价,或者使用 Amazon SageMaker 进行任何自定义开发的机器学习模型集成。
假设我们在一个关系表中有一些样本产品评价:
CREATE TABLE IF NOT EXISTS product_reviews (
review_id INT AUTO_INCREMENT PRIMARY KEY,
review_body VARCHAR(255) NOT NULL
);
INSERT INTO product_reviews (review_body)
VALUES ("Great product!");
INSERT INTO product_reviews (review_body)
VALUES ("It's ok.");
INSERT INTO product_reviews (review_body)
VALUES ("The worst product.");
然后,我们可以使用以下内置 SQL 函数让 Amazon Comprehend 返回情感和置信度分数:
SELECT review_body,
aws_comprehend_detect_sentiment(review_body, 'en') AS sentiment,
aws_comprehend_detect_sentiment_confidence(review_body, 'en') AS confidence
FROM product_reviews;
这将显示类似于这样的结果:
review_body sentiment confidence
---------------------------------------------------------
Great product! POSITIVE 0.9969872489
It's ok. POSITIVE 0.5987234553
The worst product. NEGATIVE 0.9876742876
从 Amazon Athena 调用 SageMaker 模型
同样,我们可以使用 Amazon Athena,这是一个允许我们使用 SQL 查询 Amazon S3 中存储的数据的服务,并直接从这些查询中调用 SageMaker 机器学习模型进行推理,如图 2-9 所示。

图 2-9. 亚马逊 Athena 可以调用 SageMaker 模型。
我们使用USING FUNCTION SQL 语句定义一个 UDF,调用一个自定义构建的 Amazon SageMaker Endpoint 来服务情感预测。随后的任何SELECT语句中,可以引用该函数将值传递给模型。
这里有一个简单的例子:
USING FUNCTION predict_sentiment(review_body VARCHAR(65535))
RETURNS VARCHAR(10) TYPE
SAGEMAKER_INVOKE_ENDPOINT WITH (sagemaker_endpoint = '<ENDPOINT_NAME>')
SELECT predict_sentiment(review_body) AS sentiment
FROM "dsoaws"."amazon_reviews_tsv"
WHERE predict_sentiment(review_body)="POSITIVE";
在 Amazon Neptune 上运行图数据预测
Amazon Neptune 是一款完全托管的图形数据库,允许我们构建和运行跨高度连接的数据集的应用程序。Neptune ML 实现了图神经网络(GNN),用于使用图数据进行预测。虽然诸如 XGBoost 之类的算法专为传统表格数据集设计,但 GNN 专门设计用于处理图形的复杂性和可能的数十亿个关系。Neptune ML 使用开源的 Deep Graph Library 和 Amazon SageMaker,自动选择、训练和部署最佳模型来处理我们的图数据。
培养下一代 AI 和 ML 开发者
亚马逊和 AWS 提供了许多计划和服务,帮助培养下一代 AI/ML 开发者。亚马逊的Machine Learning University program —— 用于培训亚马逊员工 —— 于 2020 年向公众发布。AWS 培训与认证(T&C)提供广泛的按需和课堂培训课程,帮助准备 AWS 机器学习专业认证考试。此外,AWS 与 Udacity、Coursera 和 DeepLearning.AI 合作,创建了多个大规模在线开放课程,以便通过亚马逊 AI 和机器学习技术栈进行实践经验。
在本节中,我们讨论了深度学习驱动的 AWS 设备,提供了一个有趣和教育性的方式来进行计算机视觉、强化学习和生成对抗网络(GANs)的实践经验。
面向开发者的设备系列包括以下几种:AWS DeepLens、DeepRacer 和 DeepComposer。AWS DeepLens 是一款支持深度学习的无线视频摄像头。AWS DeepRacer 是一款由强化学习驱动的全自动 1/18 比例赛车。而 AWS DeepComposer 是一款音乐键盘,由 GAN 技术驱动,可以将我们的旋律转化为原创歌曲。
使用 AWS DeepLens 构建计算机视觉模型
AWS DeepLens 是一款支持深度学习的视频摄像头,提供丰富的计算机视觉教程和预构建模型。如果我们想要学习如何构建计算机视觉应用程序,并在几分钟内看到第一个结果,我们可以使用其中一个带有预训练模型和简单推理功能的样本项目。摄像头将在设备上执行对部署模型的本地推理。
如果我们是经验丰富的开发人员,可以在支持的深度学习框架(如 TensorFlow、Apache MXNet 或 Caffe)中构建和训练自定义卷积神经网络(CNN)模型,然后将项目部署到 AWS DeepLens 设备上。图 2-10 展示了典型的 AWS DeepLens 工作流程。

图 2-10. AWS DeepLens 捕获输入视频流,使用部署模型处理流,并生成两个输出视频流。
AWS DeepLens 既是边缘设备又是摄像头。因此,AWS DeepLens 运行 AWS IoT Greengrass Core 并可以执行自己的 Lambda 函数。使用 AWS IoT Greengrass 将新模型推送到 AWS DeepLens。摄像头捕获输入视频流并生成两个输出流:一个是设备流,原样传递;另一个是项目流,是部署模型处理后的视频帧的结果。
我们部署的任何项目都需要包含一个 Lambda 函数来处理输入的视频帧,也称为推理 Lambda 函数。我们首先将该函数与 Lambda 运行时和一个训练模型捆绑在一起。然后,使用 AWS IoT Greengrass 将项目部署到 AWS DeepLens 设备上。
用 AWS DeepRacer 学习强化学习
AWS DeepRacer 是一款完全自主的 1/18 比例的赛车,通过强化学习驱动。该车配备了两个摄像头、一台激光雷达传感器和一个板载计算模块。计算模块执行推理以驾驶车辆沿赛道行驶。
强化学习应用于各种自主决策问题。当 DeepMind 团队在 2015 年发布了首个击败职业人类围棋选手的计算机程序 AlphaGo 时,它受到了广泛关注。
注意
围棋是一种古老的战略棋类游戏,因其复杂性而闻名。约三千年前在中国发明,至今仍然由业余爱好者和各种专业联赛玩家在全球范围内进行比赛。
尽管 AlphaGo 是通过与人类玩家进行数千场比赛来学习该游戏的,随后发布的 AlphaGo Zero 则是通过与自己对弈来学习围棋。这再次彻底改变了强化学习领域,因为它的表现甚至比之前的版本更好,并表明该模型能够发现新知识并应用非传统策略取得胜利。
在高层次上,强化学习是一种通过与环境交互来实现特定目标的机器学习方法,代理通过试错学习来做出自主决策,如 图 2-11 所示。

图 2-11. 增强学习是一种机器学习方法,旨在通过与环境的交互实现自主决策目标。
我们将深入探讨增强学习,以便在第九章中将生产模型与多臂老丨虎丨机进行比较,但让我们回到我们的自动驾驶车辆竞赛场景。在我们的例子中,代理是 AWS DeepRacer 车辆,环境包括赛道布局、行驶路线和交通状况。动作包括左转、右转、刹车和加速。动作的选择是为了最大化一个奖励函数,这代表了快速到达目的地且避免事故的目标。动作导致状态。图 2-12 展示了 AWS DeepRacer 的动作、奖励和状态流程。

图 2-12. AWS DeepRacer 基于状态和奖励采取行动。
我们甚至不需要实体赛道或车辆即可开始。我们可以在 AWS DeepRacer 控制台中开始训练我们的自定义增强学习模型,并使用 AWS DeepRacer 模拟器在虚拟赛道上评估我们的模型,如图 2-13 所示。

图 2-13. 使用 AWS DeepRacer 模拟器进行模型评估。来源:AWS DeepRacer 开发者指南。
AWS 还维护全球 AWS DeepRacer 联赛和排行榜,根据一整年举办的官方 AWS DeepRacer 联赛赛事的车辆表现进行排名,包括物理和虚拟事件。
了解 AWS DeepComposer 中的 GANs
是的,在 AWS 在 2019 年 12 月的年度 AWS re:Invent 大会上介绍 AWS DeepComposer 设备时,每个人都显得有些困惑。然而很快,我们开始听到这些独特的声音从拉斯维加斯酒店走廊传出。AWS DeepComposer 是一款音乐 USB 键盘,帮助我们学习生成式 AI。它专为与 AWS DeepComposer 服务配合使用,将简单的旋律转化为原创歌曲而设计。AWS DeepComposer 设备如图 2-14 所示。

图 2-14. AWS DeepComposer 是一款音乐 USB 键盘,帮助我们学习生成式 AI。来源:AWS。
生成式 AI,特别是 GANs 的形式,用于根据我们提供的输入生成新内容。这些输入可以是图像、文本,甚至是音乐。生成式 AI 模型会自动发现和学习数据中的模式,并利用这些知识基于其训练数据生成新数据。GANs 使用两个竞争算法,生成器和判别器,来生成新内容,如图 2-15 所示。

图 2-15. GANs 利用生成器和判别器算法。
生成器是一个卷积神经网络(CNN),它根据输入数据的模式学习创建新内容。鉴别器是另一个 CNN,经过训练后真正区分真实内容和生成内容。生成器和鉴别器交替训练,强制生成器创建越来越真实的内容,同时鉴别器在识别合成内容与真实内容方面不断改进。
应用到我们的音乐示例中,当我们在键盘上演奏一段旋律时,AWS DeepComposer 可以添加高达三个附加伴奏轨道,以创建新的作品。生成网络源自计算机视觉中广泛使用的流行 U-Net 架构,并已在公开可用的巴赫作品数据集上进行了训练。
使用量子计算编程自然操作系统
构建有用的量子应用程序需要新的技能和完全不同的问题解决方法。获取这些专业知识需要时间,并需要访问量子技术和编程工具。
Amazon Braket 帮助我们探索量子硬件的潜力,理解量子算法,并为量子未来重新调整。图 2-16 展示了通过更好的硬件、更多的开发者和更多的使用案例推动量子计算生态系统增长的量子计算飞轮。

图 2-16. 通过 Amazon Braket 推动量子计算的飞轮增长。
当今的图形处理单元(GPU)与明天的量子处理单元(QPU)之间存在许多相似之处。GPU 通过高度并行的数字计算彻底改变了人工智能和机器学习。GPU 还需要一套不同的技能、库(例如 NVIDIA 的 CUDA)和硬件,以利用这种大规模并行性。此外,GPU 设备与传统管理较大计算工作流程的 CPU 设备是“片外”的。在 CPU 和 GPU 之间同步数据需要特殊的硬件和软件来适应物理分离。
类似地,QPUs 通过高度并行的量子计算执行计算,比其数字对应物并行度高出许多个数量级。此外,QPUs 需要一套不同的技能、库和硬件。它们与 CPU 片外,因此需要特殊的硬件和软件来执行类似于 GPU 的同步操作,如图 2-17 所示。

图 2-17. 使用经典数字计算机的量子处理器单元(QPU)。
量子位与数字位的对比
量子比特(qubits)是经典数字比特的量子计算等效物。然而,它们的状态(0 或 1)是概率性的,因此在知道值之前需要进行一次读取操作。这种概率状态被称为“叠加态”,是量子计算的关键原理之一。
当前可访问的量子计算机约为 70 至 100 个量子比特。然而,由于量子硬件的相对“嘈杂”环境,这些比特的大部分都需要用于纠错。例如,加密需要近 6000 个干净的量子比特来破解 2048 位 RSA。六千个干净的量子比特需要约 1000000 个纠错冗余量子比特来调整当前量子硬件提供的嘈杂环境。
量子霸权和量子计算时代
直到最近,我们处于“经典模拟”阶段,在这个阶段我们能够模拟量子计算机的性能提升。然而,2019 年,我们达到了“量子霸权”的阶段,在今天的数字计算机的限制下,我们不再能够模拟和测量量子计算机的额外性能提升。
当前时代被称为噪声中间尺度量子。在这个时代,我们试图纠正由量子计算环境引入的噪声,这需要非常具体的温度和真空特性。类似于纠错寄存器和 RAM 芯片,我们需要纠错量子比特和 QRAM 来进入下一个时代,即纠错量子计算机时代,如图 2-18 所示。

图 2-18. 量子计算时代。
破解密码学
据估计,距离量子计算机破解现代 RSA 加密仅有大约 10 年左右的时间。今天,加密是有效的,因为我们没有足够的计算能力来执行需要破解代码的数值因式分解。
然而,使用约 6000 个“完美”量子比特(无需纠错),我们可以在几分钟内破解 RSA 代码。这是令人担忧的,并且已经催生了“量子感知”或“后量子”密码学,例如亚马逊的 s2n TLS 协议的开源实现,它使用后量子密码学而不是经典密码学。我们在第十二章中深入探讨后量子密码学。
分子模拟和药物发现
量子计算机具有独特的并行化能力,并且能够本地操作量子机械状态。因此,它们有可能解决非常重要的问题,如映射分子的电子结构。量子模拟可能会导致发现新材料、催化剂、药物和高温超导体。
物流和金融优化
优化问题在许多领域中普遍存在,包括供应链物流和金融服务。从指数集合中找到最优方法可能会耗尽经典数字计算机的资源。量子计算机可以突破这一障碍,并加速许多优化技术,包括线性规划算法和蒙特卡洛方法。
量子机器学习和人工智能
不幸的是,目前量子计算机在机器学习和人工智能中的应用还相当有限。我们已经看到一些线性算法,如支持向量机和主成分分析,有一些早期的改进。我们还看到量子研究已经启发了改进经典推荐算法的例子。未来,纠错量子计算机可能会导致一类可伸缩和高性能的量子机器学习和人工智能模型。
使用 Amazon Braket 编程量子计算机
Amazon Braket 支持 Jupyter Notebook,并提供了 Python SDK,允许开发人员与量子计算机进行交互。使用 Python SDK,我们可以将任务异步提交到远程 QPU。这类似于早期计算时我们提交作业并“租用”共享计算机来完成这些作业的方式。这也类似于将计算从 CPU 转移到 GPU。然而,关键的区别在于 CPU 和 GPU 共享经典的数字基础,而 QPU 则不然。
以下代码演示了如何构建涉及多量子比特的量子电路。这个示例展示了如何执行“量子传输”,其中信息(而不是 物质)从一个量子比特传输到另一个量子比特,而不使用经典数字电路或网络电缆:
from braket.aws import AwsDevice
from braket.circuits import Circuit, Gate, Moments
from braket.circuits.instruction import Instruction
device = AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice")
# Alice and Bob initially share a Bell pair.
circ = Circuit();
circ.h([0]);
circ.cnot(0,1);
# Define Alice's encoding scheme.
# Define four possible messages and their gates.
message = {
"00": Circuit().i(0),
"01": Circuit().x(0),
"10": Circuit().z(0),
"11": Circuit().x(0).z(0)
}
# Alice selects a message to send. Let’s choose '01'.
m = "01"
# Alice encodes her message by applying the gates defined above.
circ.add_circuit(message[m]);
# Alice then sends her qubit to Bob so that Bob has both qubits.
# Bob decodes Alice's message by disentangling the two qubits.
circ.cnot(0,1);
circ.h([0]);
print(circ)
### OUTPUT ###
T : |0|1|2|3|4|
q0 : -H-C-X-C-H-
| |
q1 : ---X---X---
T : |0|1|2|3|4|
AWS 量子计算中心
AWS 已经与加州理工学院合作建立 AWS 量子计算中心,预计于 2021 年开放。该中心将专注于开发有用的量子应用、纠错量子比特、量子编程模型和新的量子硬件。
提高性能并降低成本
如果我们能够将代码速度提高一倍并将服务器池大小减少一半会怎么样?我们可能会节省大量资金。如果我们能够自动检测应用程序中的操作问题并查看推荐的修复方法来提高可用性,会怎么样?减少应用程序停机时间是另一个巨大的节省成本的机会。
在本节中,我们介绍了完全托管的服务 Amazon CodeGuru Reviewer、Amazon CodeGuru Profiler 和 Amazon DevOps Guru。CodeGuru Reviewer 和 Profiler 帮助我们提高代码性能并减少资源需求,而 Amazon DevOps Guru 则有助于检测操作问题并提高应用程序的可用性。
使用 CodeGuru Reviewer 进行自动代码审查
代码审查是软件开发中众所周知的最佳实践。其思想是我们的代码由经验更丰富的团队成员审查,以提供关于代码性能、质量和安全性的反馈。除了领域专业知识外,这些经验丰富的团队成员还具备团队编码习惯的隐性知识和对代码“气味”的敏锐感知。
然而,有时即使是最经验丰富的团队成员也会忽略微妙的性能瓶颈或处理不当的异常。这些审查者通常专注于特定领域的问题,如领域模型的不良实现或服务集成的配置错误。此外,审查者通常受限于对代码的静态视图,而非代码运行时的实时指标。CodeGuru 包括 CodeGuru Reviewer 用于自动化代码审查和 CodeGuru Profiler 用于监控代码性能。
CodeGuru Reviewer 自动化代码审查过程,并使用机器学习模型进行建议,这些模型在数百万行亚马逊内部代码库和 10,000+ GitHub 开源项目的基础上进行了训练。
我们只需以安全和私密的方式将 CodeGuru 指向我们的源代码库即可 —— CodeGuru 将开始提出建议。CodeGuru 分析我们源代码库中的所有拉取请求,并自动标记关键缺陷,如凭证泄漏、资源泄漏、并发竞争条件以及对 AWS 资源的低效使用。它建议对特定代码行进行更改,以纠正这些缺陷,如 图 2-19 所示。

图 2-19. CodeGuru Reviewer 分析我们的源代码,并添加建议以改善性能并降低成本。
在这种情况下,Lambda 函数的原始代码在每次调用时都会创建一个新的 DynamoDB 客户端,而不是创建一次客户端并对其进行缓存。如果不进行这些更改,我们将浪费不必要的计算周期和内存寄存器,因为我们会不断重新创建相同的 DynamoDB 客户端对象。通过这种改变,我们的 Lambda 函数可以处理更多的请求每秒,这导致资源更少,成本更低。
CodeGuru Reviewer 检查 Python 和 Java 的最佳实践,包括连接池和异常处理。Reviewer 包括 Security Detector 来检测诸如未经过消毒的参数传递到操作系统级 Python 子进程调用等安全问题。CodeGuru Reviewer 还识别代码异味,减少技术债务,并提高代码库的可维护性。
通过 CodeGuru Profiler 改进应用程序性能
CodeGuru Profiler 可以在运行时分析应用程序的运行时配置文件,检测代码中的瓶颈,并提供智能建议。Profiler 创建可视化图表,如 图 2-20 中的火焰图,以确定我们应该优化性能并节省最多资金的位置。

图 2-20. CodeGuru Profiler 生成的火焰图,突出显示我们代码中的性能瓶颈。
火焰图以人类可读的形式显示调用堆栈及其确切的函数名称。在分析火焰图时,我们应该深入研究发现的任何平台。平台通常表明某个资源因等待网络或磁盘 I/O 而停滞不前。理想情况下,我们的火焰图将显示许多窄峰和少量平台。
通过 DevOps Guru 提高应用程序的可用性
Amazon DevOps Guru 是一项由 ML 驱动的运营服务,可以自动检测应用程序中的操作问题并建议修复措施。DevOps Guru 查看应用程序的指标、日志和事件,以识别任何偏离正常操作模式的行为,例如增加的响应延迟、升高的错误率和过度的资源利用。一旦识别出这样的模式,DevOps Guru 将发送警报,并附上相关异常的摘要、潜在的根本原因和可能的解决方案。
总结
在本章中,我们展示了许多可以通过各种 AWS AI 和机器学习服务解决的用例,无需或几乎不需要编写代码。无论我们是应用开发人员并且对机器学习知之甚少,还是经验丰富的数据科学家希望专注于复杂的机器学习问题,亚马逊提供的托管 AI 和机器学习服务都值得探索。
我们可以轻松地为我们的应用程序增添现成的 AI 服务,无论我们的业务用例是否需要将机器学习引入边缘,还是我们刚刚开始 AI/ML 之旅,希望找到一些有趣的教育方式来开始计算机视觉、强化学习或 GANs。
我们还展示了如何利用高级 AI 服务,包括 Amazon Personalize 提供推荐和 Forecast 预测需求。
我们展示了机器学习如何驱动许多现有的 AWS 服务,包括预测性 Amazon EC2 自动扩展和暖池技术。我们还探讨了如何使用 Macie 检测和防止敏感数据泄漏,并利用 Amazon Fraud Detector 防止欺诈行为。我们介绍了如何通过 Amazon Contact Lens for Amazon Connect、Comprehend、Kendra 和 Lex 来改善客户支持体验。我们还描述了如何利用 CodeGuru Reviewer、CodeGuru Profiler 和 DevOps Guru 自动化源代码审查,并识别性能和成本效益。
在第三章中,我们将讨论自动化机器学习的概念。我们将展示如何仅需几次点击即可使用 Amazon SageMaker Autopilot 和 Amazon Comprehend 构建预测模型。
第三章:自动化机器学习
在本章中,我们将展示如何使用完全托管的亚马逊 AI 和机器学习服务,避免为我们的 AI 和机器学习流水线管理自己的基础设施需求。我们深入探讨了两个亚马逊服务:Amazon SageMaker Autopilot 和 Amazon Comprehend。这两个服务都专为希望通过简单点击即可从其数据集构建强大预测模型的用户而设计。我们可以使用 SageMaker Autopilot 和 Comprehend 来以非常低的工作量和成本建立基准模型性能。
机器学习从业者通常花费数周或数月来构建、训练和调整他们的模型。他们准备数据并决定使用的框架和算法。在迭代过程中,机器学习从业者试图找到适合其数据集和问题类型的最佳执行算法。不幸的是,对于这个过程,没有捷径。我们仍然需要经验、直觉和耐心来运行许多实验,找到我们算法和数据集的最佳超参数。经验丰富的数据科学家凭借多年的经验和直觉为给定数据集和问题类型选择最佳算法,但他们仍然需要通过实际训练运行和重复模型验证来验证他们的直觉。
如果我们只需点击一次就能使用一个服务,该服务会找到适合我们数据集的最佳算法,训练和调整模型,并将模型部署到生产环境中,那该有多好?Amazon SageMaker Autopilot 简化了模型训练和调整过程,并加快了整体模型开发生命周期。通过在特征选择和超参数调整等样板生命周期阶段花费较少时间,我们可以花更多时间处理领域特定问题。
通过分析我们在 S3 中的数据,SageMaker Autopilot 基于亚马逊多年的 AI 和机器学习经验,探索不同的算法和配置。SageMaker Autopilot 比较各种回归、分类和深度学习算法,找出适合我们数据集和问题类型的最佳算法。
SageMaker Autopilot 通过一组自动生成的 Jupyter 笔记本和 Python 脚本总结了模型候选人。我们完全控制这些生成的笔记本和脚本。我们可以修改它们,自动化它们,并与同事分享。我们可以根据我们期望的模型准确性、模型大小和预测延迟选择顶级模型候选人。
使用 SageMaker Autopilot 的自动化机器学习
我们通过将原始数据以表格形式的 CSV 文件提供到 S3 存储桶中,配置 SageMaker Autopilot 作业。我们还需要告诉 SageMaker Autopilot 哪一列是目标列。然后,SageMaker Autopilot 应用自动化机器学习技术来分析数据,识别适合我们数据集的最佳算法,并生成最佳模型候选人。
SageMaker Autopilot 分析并平衡数据集,并将数据集分为训练/验证集。根据我们试图预测的目标属性,SageMaker Autopilot 自动识别机器学习问题类型,如回归、二元分类或多类分类。然后,SageMaker Autopilot 根据问题类型比较一组算法。算法选择包括逻辑回归、线性回归、XGBoost、神经网络等。
SageMaker Autopilot 生成执行每种算法特定模型流水线的代码。生成的代码包括数据转换、模型训练和模型调优。由于 SageMaker Autopilot 具有透明性,我们可以完全访问这些生成的代码以便自行重现。我们甚至可以修改代码并随时重新运行流水线。
在并行训练和调整生成的流水线之后,SageMaker Autopilot 根据准确率、AUC 和 F1 分数等客观指标对训练好的模型进行排名。
SageMaker Autopilot 使用透明的自动机器学习方法。在不透明的方法中,如图 3-1 所示,我们无法控制或看到所选算法、应用的数据转换或超参数选择。我们将自动化机器学习服务指向我们的数据,并接收一个训练好的模型。

图 3-1。在许多自动机器学习服务中,我们无法看到所选算法、应用的数据转换或超参数选择。
这使得理解、解释和重现模型变得困难。许多自动机器学习解决方案采用这种不透明的方法。相比之下,SageMaker Autopilot 在数据分析、特征工程和模型调优步骤中文档并分享其发现。
SageMaker Autopilot 不仅分享模型;它还记录所有观察到的指标,并生成包含用于重现模型流水线的代码的 Jupyter 笔记本,如图 3-2 所示。

图 3-2。SageMaker Autopilot 生成 Jupyter 笔记本、特征工程脚本和模型代码。
数据分析步骤识别可能影响模型性能的数据质量问题,如缺失值。数据探索笔记本包含来自数据分析步骤的结果。SageMaker Autopilot 还生成另一个 Jupyter 笔记本,其中包含所有流水线定义,以提供透明性和可重现性。候选定义笔记本突出显示了学习给定数据集的最佳算法,以及使用每种算法需要的代码和配置。
提示
第一个数据分析步骤之后,两个 Jupyter 笔记本都可用。我们可以配置 Autopilot 只进行“干跑”并在此步骤后停止。
使用 SageMaker Autopilot 跟踪实验
SageMaker 自动驾驶使用 SageMaker 实验来跟踪所有数据分析、特征工程和模型训练/调优作业。 Amazon SageMaker 家族的这一更广泛的 ML 服务功能帮助我们组织、跟踪、比较和评估机器学习实验。 SageMaker 实验使模型版本控制和谱系跟踪在 ML 生命周期的所有阶段均可实现。
SageMaker 实验由试验组成。 试验是一系列步骤的集合,包括数据预处理、模型训练和模型调优。 SageMaker 实验还提供了跨 S3 位置、算法、超参数、训练模型和模型性能指标的谱系跟踪。
我们可以通过 UI 或使用 SDK(如 Amazon SageMaker Python SDK 或 AWS SDK for Python (Boto3))来探索和管理 SageMaker 自动驾驶实验和试验。
注意
SageMaker SDK 是建立在 Boto3 之上的高级 SageMaker 特定抽象,是 SageMaker 模型开发和管理的首选选择。
使用 SageMaker 自动驾驶训练和部署文本分类器
让我们创建一个 SageMaker 自动驾驶实验,构建一个定制的文本分类器,用于分类我们销售产品的社交反馈。 产品反馈来自各种在线渠道,如我们的网站、合作伙伴网站、社交媒体、客户支持电子邮件等。 我们捕获产品反馈,并希望我们的模型将反馈分类为星级评价类别,5 表示最佳反馈,1 表示最差反馈。
作为输入数据,我们利用来自 亚马逊客户评论数据集 的样本。 该数据集是从 1995 年到 2015 年在 Amazon.com 上收集的超过 1.5 亿产品评论的集合。 这些产品评论和星级评分是 Amazon.com 的热门客户功能。 我们将在第四章和第五章详细描述和探索此数据集。 现在,我们关注 review_body(特征)和 star_rating(预测标签)。
使用 SageMaker 自动驾驶 UI 进行训练和部署
SageMaker 自动驾驶 UI 集成在 SageMaker Studio 中,这是一个提供单一、基于 Web 的可视界面的 IDE,我们可以在其中进行机器学习开发。 只需导航到 AWS 控制台中的 Amazon SageMaker,并单击 SageMaker Studio。 然后按照说明设置 SageMaker Studio 并单击 Open Studio。
这将带我们进入 SageMaker Studio UI,在那里我们可以通过实验和试验菜单访问 SageMaker 自动驾驶 UI。 在那里,我们可以单击 Create Experiment 来创建和配置我们的第一个 SageMaker 自动驾驶实验。
在准备我们的 SageMaker Autopilot 实验时,我们使用亚马逊客户评论数据集的子集来训练我们的模型。我们希望训练一个分类器模型,以预测给定 review_body 的 star_rating。我们创建了输入 CSV 文件,包含 star_rating 作为我们的标签/目标列和包含产品反馈的 review_body 列:
star_rating,review_body
5,"GOOD, GREAT, WONDERFUL"
2,"It isn't as user friendly as TurboTax."
4,"Pretty easy to use. No issues."
…
在其他情况下,我们可能希望从数据集中使用更多列,并让 SageMaker Autopilot 通过自动特征选择选择最重要的列。然而,在我们的示例中,我们保持简单,并使用 star_rating 和 review_body 列来专注于创建 Autopilot 实验的步骤。
接下来,我们使用几个输入参数配置 SageMaker Autopilot 实验,这些参数定义数据集、要预测的目标列,以及可选的问题类型,如二元分类、多类分类或回归。如果我们没有指定问题类型,SageMaker Autopilot 可以根据目标列中的值自动确定问题类型。
实验名称
用于标识实验的名称,例如 amazon-customer-reviews。
输入数据位置
我们训练数据的 S3 路径,例如 s3://
目标
我们要预测的目标列,例如 star_rating。
输出数据位置
存储生成输出的 S3 路径,例如模型和其他工件的 s3://
问题类型
机器学习问题类型,如二元分类、多类分类和回归。默认情况下,“自动”允许 SageMaker Autopilot 根据给定的输入数据(包括分类数据)自行选择。
运行完整实验
我们可以选择运行完整实验,或者仅生成数据探索和候选定义笔记本作为数据分析阶段的一部分。在这种情况下,SageMaker Autopilot 在数据分析阶段结束后停止,并且不会运行特征工程、模型训练和调优步骤。
让我们点击“创建实验”并启动我们的第一个 SageMaker Autopilot 作业。我们可以通过 SageMaker Studio 的 Autopilot UI 观察作业的进展,包括预处理、候选生成、特征工程和模型调优。一旦 SageMaker Autopilot 完成候选生成阶段,我们可以在 UI 中看到两个生成笔记本的链接:候选生成和数据探索。
我们可以直接从 UI 下载这些文件,或者直接从 S3 自动化下载。我们可以在以下结构中找到生成的笔记本、代码和转换后的数据:
amazon-customer-reviews/
sagemaker-automl-candidates/
...
generated_module/
candidate_data_processors/
dpp0.py
dpp1.py
...
notebooks/
SageMakerAutopilotCandidateDefinitionNotebook.ipynb
SageMakerAutopilotDataExplorationNotebook.ipynb
...
data-processor-models/
amazon-cus-dpp0-1-xxx/
output/model.tar.gz
amazon-cus-dpp1-1-xxx/
output/model.tar.gz
...
preprocessed-data/
header/
headers.csv
tuning_data/
train/
chunk_20.csv
chunk_21.csv
...
validation/
chunk_0.csv
chunk_1.csv
...
当特征工程阶段开始时,我们将看到 SageMaker 训练作业出现在 AWS 控制台或直接在 SageMaker Studio 中。每个训练作业都是一个模型候选者与数据预处理器(dpp)代码的组合,命名为 dpp0 到 dpp9。我们可以将这些训练作业看作是 SageMaker Autopilot 构建的 10 个机器学习管道,以找到性能最佳的模型。我们可以选择任何一个训练作业来查看作业状态、配置、参数和日志文件。我们将在第六章深入探讨特征工程(Chapter 6)和 SageMaker 训练作业(Chapter 7)。
一旦完成特征工程阶段,我们可以直接在 S3 中按管道分组查看转换后的数据。数据已经被分成更小的块,并分为独立的训练和验证数据集,如下所示:
transformed-data/
dpp0/
rpb/
train/
chunk_20.csv_out
chunk_21.csv_out
...
validation/
chunk_0.csv_out
chunk_1.csv_out
...
dpp1/
csv/
train/
chunk_20.csv_out
chunk_21.csv_out
...
validation/
chunk_0.csv_out
chunk_1.csv_out
...
..
dpp9/
最后,SageMaker Autopilot 运行模型调优阶段,我们开始在 SageMaker Studio 的 Autopilot UI 中看到试验出现。模型调优阶段创建了一个 SageMaker 超参数调整作业。HPT 或超参数优化(HPO),作为 Amazon SageMaker 的本地支持功能,可以在 SageMaker Autopilot 之外的自定义模型上进行独立的 HPT 作业,我们将在第八章中看到(Chapter 8)。
SageMaker 超参数调整作业通过在我们指定的算法和超参数范围上运行多个训练作业来找到模型的最佳版本。SageMaker 支持多种 HPT 算法,包括随机搜索和贝叶斯搜索。随机搜索时,SageMaker 从我们指定的范围内随机选择超参数的组合。贝叶斯搜索时,SageMaker 将调整视为回归问题。我们将在第八章中探索 SageMaker 的自动模型调优功能(Chapter 8)。
我们可以在 SageMaker 训练作业 UI 或直接在 SageMaker Studio 中找到相应的训练作业列表。同样,我们可以点击并检查这些作业以查看作业状态、配置、参数和日志文件。回到 SageMaker Autopilot UI,我们可以检查试验。
SageMaker Autopilot 的四个试验组件构成以下作业的管道:
处理作业
将数据分割为训练和验证数据,并分离头数据
训练作业
使用先前分割的训练和验证数据以及数据预处理器代码(dpp[0-9].py)训练批量转换模型的每个模型候选者
批量转换作业
将原始数据转换为特征
调优作业
使用先前转换的特定算法特征来优化算法配置和参数,找到性能最佳的模型候选者
这四个组件通过跟踪所有超参数、输入数据集和输出物件保留模型的血统。完成模型调整步骤后,我们可以在 S3 存储桶中找到按模型候选管线组织的最终输出和模型候选项:
tuning/
amazon-cus-dpp0-xgb/
tuning-job-1-8fc3bb8155c645f282-001-da2b6b8b/
output/model.tar.gz
tuning-job-1-8fc3bb8155c645f282-004-911d2130/
output/model.tar.gz
tuning-job-1-8fc3bb8155c645f282-012-1ea8b599/
output/model.tar.gz
...
amazon-cus-dpp3-ll/
...
amazon-cus-dpp-9-xgb/
请注意,管线名称(即amazon-cus-dpp0-xgb)方便地包含所使用的设置信息(dpp0 = 数据预处理管线 dpp0,xgb = 选择的算法 XGBoost)。除了以编程方式检索最佳模型外,我们还可以使用 SageMaker Studio 的 Autopilot UI 直观地突出显示最佳模型。
现在,将此模型部署到生产环境就像右键单击名称并选择“部署模型”操作一样简单。我们只需为我们的端点命名,选择要在其上部署模型的 AWS 实例类型,例如ml.m5.xlarge,并定义服务模型的实例数。
提示
Amazon SageMaker 及其性能特征支持的所有 AWS 实例类型概述
请注意,这些实例名称以ml.开头。
可选地,我们可以启用对部署模型的所有预测请求和响应的数据捕获。现在,我们可以点击“部署模型”并观察我们的模型端点正在创建。一旦端点显示为已启动,我们就可以调用端点来提供预测。
这是一个简单的 Python 代码片段,展示了如何调用部署到 SageMaker 端点的模型。我们传递一个样本评论(“我喜欢它!”),看看我们的模型选择了哪个星级评分。请记住,1 星是最差的,5 星是最好的:
import boto3
sagemaker_runtime = boto3.client('sagemaker-runtime')
csv_line_predict = """I loved it!"""
ep_name = 'reviews-endpoint'
response = sagemaker_runtime.invoke_endpoint(
EndpointName=ep_name,
ContentType='text/csv',
Accept='text/csv',
Body=csv_line_predict)
response_body = response['Body'].read().decode('utf-8').strip()
print(response_body)
这里是我们的模型预测的星级评分:
"5"
我们的模型成功将评论分类为 5 星评级。
使用 SageMaker Autopilot Python SDK 训练和部署模型
除了使用前面的 SageMaker Autopilot UI 外,我们还可以使用 Python SDK 启动 SageMaker Autopilot 作业,仅需几行代码即可训练和部署文本分类器,具体如下:
import boto3
import sagemaker
session = sagemaker.Session(default_bucket="dsoaws-amazon-reviews")
bucket = session.default_bucket()
role = sagemaker.get_execution_role()
region = boto3.Session().region_name
sm = boto3.Session().client(service_name='sagemaker',
region_name=region)
我们可以指定要探索的模型候选数目,并为每个训练作业和整体 SageMaker Autopilot 作业设置最长运行时间(以秒为单位):
max_candidates = 3
job_config = {
'CompletionCriteria': {
'MaxRuntimePerTrainingJobInSeconds': 600,
'MaxCandidates': max_candidates,
'MaxAutoMLJobRuntimeInSeconds': 3600
},
}
与 SageMaker Autopilot UI 配置类似,我们提供了 S3 输入和输出位置,并定义了预测的目标属性:
input_data_config = [
{
'DataSource': {
'S3DataSource': {
'S3DataType': 'S3Prefix',
'S3Uri': 's3://<BUCKET>/amazon_reviews.csv'
}
},
'TargetAttributeName': 'star_rating'
}
]
output_data_config = {
'S3OutputPath': 's3://<BUCKET>/autopilot/output/'
}
接下来,我们创建我们的 SageMaker Autopilot 作业。请注意,我们向 SageMaker Autopilot 作业名称添加了时间戳,这有助于保持作业的唯一性和易于跟踪。我们传递作业名称、输入/输出配置、作业配置和执行角色。执行角色是 AWS 身份和访问管理(IAM)服务的一部分,管理服务访问权限:
from time import gmtime, strftime, sleep
timestamp_suffix = strftime('%d-%H-%M-%S', gmtime())
auto_ml_job_name = 'automl-dm-' + timestamp_suffix
sm.create_auto_ml_job(AutoMLJobName=auto_ml_job_name,
InputDataConfig=input_data_config,
OutputDataConfig=output_data_config,
AutoMLJobConfig=job_config,
RoleArn=role)
SageMaker Autopilot 作业已经创建,并且有一个唯一标识符,先前被描述为AutoMLJobArn。ARN(Amazon 资源名称)通常以arn:partition:service:region:account-id:resource-id的形式编码。ARN 在所有 AWS 服务中用于明确指定资源。
我们可以轮询 SageMaker Autopilot 作业状态,并检查数据分析步骤是否已完成:
job = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)
job_status = job['AutoMLJobStatus']
job_sec_status = job['AutoMLJobSecondaryStatus']
if job_status not in ('Stopped', 'Failed'):
while job_status in ('InProgress') and job_sec_status in ('AnalyzingData'):
job = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)
job_status = job['AutoMLJobStatus']
job_sec_status = job['AutoMLJobSecondaryStatus']
print(job_status, job_sec_status)
sleep(30)
print("Data analysis complete")
print(job)
代码将返回以下输出(已缩短):
InProgress AnalyzingData
InProgress AnalyzingData
...
Data analysis complete
类似地,我们可以查询job_sec_status in ('FeatureEngineering')和job_sec_status in ('ModelTuning'),来进行 SageMaker Autopilot 的另外两个步骤。
一旦 SageMaker Autopilot 作业完成,我们可以列出所有模型候选者:
candidates = sm.list_candidates_for_auto_ml_job(AutoMLJobName=auto_ml_job_name,
SortBy='FinalObjectiveMetricValue')['Candidates']
for index, candidate in enumerate(candidates):
print(str(index) + " "
+ candidate['CandidateName'] + " "
+ str(candidate['FinalAutoMLJobObjectiveMetric']['Value']))
这将生成类似于这样的输出:
0 tuning-job-1-655f4ef810d441d4a8-003-b80f5233 0.4437510073184967
1 tuning-job-1-655f4ef810d441d4a8-001-7c10cb15 0.29365700483322144
2 tuning-job-1-655f4ef810d441d4a8-002-31088991 0.2874149978160858
我们也可以检索最佳候选者:
best_candidate = \
sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)['BestCandidate']
best_candidate_identifier = best_candidate['CandidateName']
print("Candidate name: " + best_candidate_identifier)
print("Metric name: " + \
best_candidate['FinalAutoMLJobObjectiveMetric']['MetricName'])
print("Metric value: " + \
str(best_candidate['FinalAutoMLJobObjectiveMetric']['Value']))
这将生成类似于这样的输出:
Candidate name: tuning-job-1-655f4ef810d441d4a8-003-b80f5233
Metric name: validation:accuracy
Metric value: 0.4437510073184967
现在,让我们将最佳模型部署为 REST 端点。首先,我们需要创建一个模型对象:
model_name = 'automl-dm-model-' + timestamp_suffix
model_arn = sm.create_model(Containers=best_candidate['InferenceContainers'],
ModelName=model_name,
ExecutionRoleArn=role)
print('Best candidate model ARN: ', model_arn['ModelArn'])
输出应该类似于这样:
Best candidate model ARN:
arn:aws:sagemaker:<region>:<account_id>:model/automl-dm-model-01-16-34-00
上述代码揭示了 UI 中隐藏的另一个细节。当我们将模型部署为 REST 端点时,实际上部署了整个推理流水线。推理流水线由三个容器组成。
数据转换容器
这本质上是“请求处理程序”,将应用程序输入(例如review_body)转换为模型认可的格式(即 NumPy 数组或张量)。该容器托管了 SageMaker Autopilot 为特征工程步骤训练的模型。
算法容器
此容器托管实际用于提供预测的模型。
逆标签转换器容器
这是“响应处理程序”,将算法特定的输出(例如 NumPy 数组或张量)转换为调用者认可的格式(例如star_rating)。
Figure 3-3 展示了推理流水线的示例。

图 3-3. SageMaker Autopilot 将模型部署为推理流水线。
我们将我们的评论作为原始文本传递,数据转换容器将文本转换为 TF/IDF 向量。TF/IDF 代表词频-逆文档频率,导致常见词项被降权,而独特词项被加权。TF/IDF 编码了一个词对于文档集合中文档的相关性。
算法容器处理输入并预测星级评分。请注意,我们示例中的算法将预测结果作为基于 0 的索引值返回。逆标签转换器容器的任务是将索引(0,1,2,3,4)映射到正确的星级评分标签(1,2,3,4,5)。
要部署推理流水线,我们需要创建一个端点配置:
# EndpointConfig name
timestamp_suffix = strftime('%d-%H-%M-%S', gmtime())
epc_name = 'automl-dm-epc-' + timestamp_suffix
# Endpoint name
ep_name = 'automl-dm-ep-' + timestamp_suffix
variant_name = 'automl-dm-variant-' + timestamp_suffix
ep_config = sm.create_endpoint_config(
EndpointConfigName = epc_name,
ProductionVariants=[{
'InstanceType': 'ml.c5.2xlarge',
'InitialInstanceCount': 1,
'ModelName': model_name,
'VariantName': variant_name}])
create_endpoint_response = sm.create_endpoint(
EndpointName=ep_name,
EndpointConfigName=epc_name)
SageMaker Autopilot 现在正在部署推理流水线。让我们查询端点状态,查看何时流水线成功投入服务:
response = sm.describe_endpoint(EndpointName=autopilot_endpoint_name)
status = response['EndpointStatus']
print("Arn: " + response['EndpointArn'])
print("Status: " + status)
几分钟后,输出应该类似于这样:
Arn: arn:aws:sagemaker:<region>:<account_id>:endpoint/automl-dm-ep-19-13-29-52
Status: InService
现在我们可以调用端点并运行一个样本预测。我们传递评论 “还行。” 来查看模型预测的星级分类:
sagemaker_runtime = boto3.client('sagemaker-runtime')
csv_line_predict = """It's OK."""
response = sagemaker_runtime.invoke_endpoint(
EndpointName=ep_name,
ContentType='text/csv',
Accept='text/csv',
Body=csv_line_predict)
response_body = response['Body'].read().decode('utf-8').strip()
让我们打印响应:
response_body
'3'
我们的端点已成功将此样本评论分类为 3 星评级。
注意
我们可以再次检查 S3 输出位置,查看所有生成的模型、代码和其他工件,包括数据探索笔记本和候选定义笔记本。
使用 SageMaker SDK 调用我们的模型只是其中一种选项。在 AWS 中还有许多其他服务集成可用。在下一节中,我们将描述如何通过 Amazon Athena 在 SQL 查询中运行实时预测。
使用 Amazon Athena 和 SageMaker Autopilot 进行预测
Amazon Athena 是一个交互式查询服务,允许我们使用标准 SQL 分析存储在 S3 中的数据。由于 Athena 是无服务器的,我们无需管理任何基础设施,只需为我们运行的查询付费。通过 Athena,我们可以查询大量数据(TB+),而无需将数据移动到关系数据库。现在,我们可以通过调用 SageMaker 模型端点丰富我们的 SQL 查询,并接收模型预测。
要从 Athena 调用 SageMaker,我们需要使用 USING FUNCTION 子句定义一个函数,如 Figure 3-4 中所示。随后的任何 SELECT 语句都可以引用该函数来调用模型预测。

图 3-4. 我们可以通过用户定义函数从 Amazon Athena ML 调用 SageMaker 模型。
这是一个简单的 SQL 查询,选择存储在名为 dsaws.product_reviews 的 Athena 表中的产品评论。函数 predict_star_rating 然后使用名称为 reviews 的 SageMaker 端点进行预测:
USING FUNCTION predict_star_rating(review_body VARCHAR)
RETURNS VARCHAR TYPE
SAGEMAKER_INVOKE_ENDPOINT WITH (sagemaker_endpoint = 'reviews')
SELECT review_id, review_body,
predict_star_rating(REPLACE(review_body, ',', ' '))
AS predicted_star_rating
FROM dsoaws.product_reviews
结果应该类似于以下内容(已缩短):
| review_id | review_body | predicted_star_rating |
|---|---|---|
| R23CFDQ6SLMET | 本书的照片令人失望。我 ... | 1 |
| R1301KYAYKX8FU | 这是我为所有我半 ... 的评论的评论 ... | 5 |
| R1CKM3AKI920D7 | 我简直不敢相信我能成为第一个告诉 ... | 5 |
| RA6CYWHAHSR9H | 圣诞节有点不同 / 黛比·麦克 ... | 5 |
| R1T1CCMH2N9LBJ | Murray Straus 的这一修订版是一个 ... | 1 |
| ... | ... | ... |
此示例展示了如何使用简单的 SQL 查询丰富我们的 S3 数据,利用机器学习预测结果。
使用 Amazon Redshift ML 和 SageMaker Autopilot 进行训练和预测
Amazon Redshift 是一款完全托管的数据仓库,允许我们针对以 PB 计的结构化数据运行复杂的分析查询。借助 Amazon Redshift ML,我们可以利用 Amazon Redshift 中的数据使用 SageMaker Autopilot 来创建和训练模型,随着新数据的到来。以下是使用从 Amazon Redshift 查询检索到的训练数据来训练文本分类器模型的代码。SELECT语句指向 Amazon Redshift 中我们要用作模型训练数据的数据。TARGET关键字定义了要预测的列。FUNCTION关键字定义了在预测 Amazon Redshift 查询中调用模型时使用的函数名称。
CREATE MODEL dsoaws.predict_star_rating
FROM (SELECT review_body,
star_rating
FROM dsoaws.amazon_reviews_tsv_2015)
TARGET star_rating
FUNCTION predict_star_rating
IAM_ROLE '<ROLE_ARN>'
SETTINGS (
S3_BUCKET '<BUCKET_NAME>'
);
上述语句执行了一个 Amazon Redshift 查询,将选择的数据导出到 S3,并触发了一个 SageMaker Autopilot 作业来生成和部署模型。然后,Amazon Redshift ML 在我们称为predict_star_rating的 Amazon Redshift 集群中部署训练好的模型和函数。
要使用我们训练过的 Amazon Customer Reviews 文本分类器模型进行预测,我们在 Amazon Redshift 中查询review_body列,并预测star_rating如下:
SELECT review_body,
predict_star_rating(review_body) AS "predicted_star_rating"
FROM dsoaws.amazon_reviews_tsv_2015
这里是演示 Amazon Redshift ML 的示例查询结果:
| review_body | predicted_star_rating |
|---|---|
| I love this product! | 5 |
| 没问题。 | 3 |
| This product is terrible. | 1 |
使用 Amazon Comprehend 进行自动化机器学习
Amazon Comprehend 是一款完全托管的 AI 服务,用于自然语言处理(NLP)任务,使用 AutoML 来为我们的数据集找到最佳模型。Amazon Comprehend 将文本文档作为输入,并识别实体、关键短语、语言和情感。随着新的语言模型被发现并纳入托管服务中,Amazon Comprehend 继续改进。
使用 Amazon Comprehend 内置模型进行预测
情感分析是一个文本分类任务,用于预测给定输入文本的正面、负面或中性情感。例如,如果我们想要分析产品评价并从社交流中识别产品质量问题,这将非常有帮助。
让我们使用 Amazon Comprehend 实现这个文本分类器。作为输入数据,我们利用亚马逊客户评价数据集的一个子集。我们希望 Amazon Comprehend 对提供的评价分类情感。Comprehend UI 是开始的最简单方式。我们可以粘贴任何文本,Amazon Comprehend 将实时使用内置模型分析输入。我们可以点击“分析”,在“洞察”标签下看到正面情感预测和预测置信度分数。该分数告诉我们,Amazon Comprehend 对我们的示例评价有 99%的置信度认为是正面情感。现在让我们实现一个自定义模型,再次将我们的产品评价分类为星级评分。
使用 Amazon Comprehend UI 训练和部署自定义模型
Comprehend 自定义是自动化机器学习的一个例子,它使从业者能够微调 Amazon Comprehend 的内置模型以适应特定数据集。让我们从之前的 SageMaker Autopilot 示例中重新使用 Amazon 客户评价数据集文件作为我们的训练数据:
star_rating,review_body
5,"GOOD, GREAT, WONDERFUL"
2,"It isn't as user friendly as TurboTax"
4,"Pretty easy to use. No issues."
…
我们可以使用 Comprehend UI 通过为自定义分类器提供一个名称、选择多类模式并输入训练数据的路径来训练自定义多类文本分类器。接下来,我们定义一个 S3 位置来存储训练模型输出,并选择一个具有访问该 S3 位置权限的 IAM 角色。然后,我们点击“训练分类器”来开始训练过程。现在我们在 UI 中看到自定义分类器显示为“已提交”,不久后变为“训练中”状态。
一旦分类器显示为已训练,我们就可以将其部署为 Comprehend 端点以提供预测。只需选择已训练的模型并点击操作。给端点取一个名字然后点击创建端点。在 Comprehend UI 中,导航到实时分析并选择自定义分析类型。从端点下拉列表中选择自定义端点。Amazon Comprehend 现在可以使用自定义文本分类器模型分析输入文本。让我们粘贴评论“真的很糟糕。我希望他们不再制造这个了。”然后点击分析。
从结果中我们可以看到,我们的自定义模型现在将输入文本分类为星级评分从 1 到 5(5 为最佳评级)。在这个例子中,模型有 76%的置信度认为该评论属于星级评分 2。
只需点击几下,我们就在 Amazon 客户评价数据集上训练了一个 Comprehend 自定义模型,以预测评论文本的星级评分。这就是 Amazon AI 服务的威力。
使用 Amazon Comprehend Python SDK 训练和部署自定义模型
我们还可以通过 Amazon Comprehend 进行程序化交互。让我们使用 Amazon Comprehend Python SDK 来训练和部署自定义分类器:
import boto3
comprehend = boto3.client('comprehend')
# Create a unique timestamp ID to attach to our training job name
import datetime
id = str(datetime.datetime.now().strftime("%s"))
# Start training job
training_job = comprehend.create_document_classifier(
DocumentClassifierName='Amazon-Customer-Reviews-Classifier-'+ id,
DataAccessRoleArn=iam_role_comprehend_arn,
InputDataConfig={
'S3Uri': 's3://<bucket>/<path>/amazon_reviews.csv'
},
OutputDataConfig={
'S3Uri': 's3://<bucket>/<path>/model/outputs'
},
LanguageCode='en'
)
输入参数如下:
DocumentClassifierName
自定义模型的名称
DataAccessRoleArn
授予 Amazon Comprehend 读取输入数据权限的 IAM 角色的 ARN。
InputDataConfig
指定训练数据的格式和位置(S3Uri: 训练数据的 S3 路径)
OutputDataConfig
指定模型输出的位置(S3Uri: 模型输出的 S3 路径)
LanguageCode
训练数据的语言
训练作业将根据需要处理的训练数据量运行一段时间。一旦完成,我们可以部署一个端点,使用我们的自定义分类器进行预测。
要部署自定义模型,让我们首先找出需要引用的模型的 ARN:
model_arn = training_job['DocumentClassifierArn']
使用model_arn,我们现在可以创建一个模型端点:
inference_endpoint_response = comprehend.create_endpoint(
EndpointName='comprehend-inference-endpoint',
ModelArn = model_arn,
DesiredInferenceUnits = 1
)
输入参数如下:
EndpointName
我们端点的名称。
ModelArn
将端点附加到模型的 ARN。
DesiredInferenceUnits
模型附加到此端点上所需的推理单元数。每个推理单元表示每秒一百个字符的吞吐量。
一旦模型端点成功创建并处于In Service状态,我们可以为样本预测调用它。要调用定制模型,让我们找出端点的 ARN:
endpoint_arn = inference_endpoint_response["EndpointArn"]
现在,我们可以使用comprehend.classify_document()来运行预测,以及我们想要分类的文本和端点的 ARN:
# Sample text to classify
txt = """It's OK."""
response = comprehend.classify_document(
Text= txt,
EndpointArn = endpoint_arn
)
JSON 格式的响应将类似于这样:
{
"Classes": [
{
"Name": "3",
"Score": 0.977475643157959
},
{
"Name": "4",
"Score": 0.021228035911917686
},
{
"Name": "2",
"Score": 0.001270478474907577
}
],
...
}
我们的定制分类器有 97%的置信度认为我们的样本评论应该得到 3 星评分。只需几行 Python 代码,我们就能在亚马逊顾客评论数据集上训练一个 Amazon Comprehend 定制模型,以预测评论文本的星级评分。
总结
在本章中,我们讨论了 AutoML 的概念。我们介绍了 SageMaker Autopilot 对 AutoML 的透明化方法。SageMaker Autopilot 可以在提供完整自动化过程可见性的同时,卸下构建 ML 管道的重担。我们展示了如何通过 Amazon Athena 从 SQL 查询中调用机器学习模型。我们还展示了 Amazon Comprehend 如何利用 AutoML 在几次点击或几行 Python 代码中,基于公共 Amazon 顾客评论数据集训练和部署定制文本分类模型。
在接下来的章节中,我们将深入探讨如何使用 Amazon SageMaker 和 TensorFlow 构建基于 BERT 的定制文本分类器,用于分类来自不同来源(包括社交渠道和合作伙伴网站)的产品评论。
第四章:将数据引入云中
在本章中,我们将展示如何将数据引入云中。为此,我们将查看一个典型的场景,即应用程序将文件写入亚马逊 S3 数据湖,然后由 ML 工程师/数据科学团队以及商业智能/数据分析团队访问,如图 4-1 所示。

图 4-1. 一个应用程序将数据写入我们的 S3 数据湖,供数据科学、机器学习工程和商业智能团队使用。
亚马逊简单存储服务(Amazon S3)是一种完全托管的对象存储,具有极高的耐久性、高可用性和无限的数据可伸缩性,成本非常低廉。因此,它是构建数据湖、训练数据集和模型的理想基础。在接下来的章节中,我们将更多地了解在亚马逊 S3 上构建数据湖的优势。
让我们假设我们的应用程序持续捕获数据(例如网站上的客户互动、产品评论消息),并将数据以制表符分隔值(TSV)文件格式写入 S3。
作为数据科学家或机器学习工程师,我们希望能够快速探索原始数据集。我们将介绍亚马逊 Athena,并展示如何利用 Athena 作为交互式查询服务来分析 S3 中的数据,使用标准 SQL 而无需移动数据。在第一步中,我们将在 Athena 中注册我们 S3 存储桶中的 TSV 数据,然后对数据集运行一些即席查询。我们还将展示如何轻松将 TSV 数据转换为更适合查询的列式文件格式 Apache Parquet。
我们的商业智能团队可能还希望在数据仓库中有一部分数据子集,然后可以使用标准 SQL 客户端进行转换和查询,以创建报告并可视化趋势。我们将介绍亚马逊 Redshift,一个完全托管的数据仓库服务,并展示如何将 TSV 数据插入亚马逊 Redshift,以及如何通过亚马逊 Redshift Spectrum 将数据仓库查询与仍在我们 S3 数据湖中的不经常访问的数据结合起来。我们的商业智能团队还可以使用亚马逊 Redshift 的数据湖导出功能,将(转换、丰富的)数据以 Parquet 文件格式再次卸载到我们的 S3 数据湖中。
我们将以一些关于使用压缩算法增加性能和利用 S3 智能分层降低成本的技巧和窍门来结束本章。在第十二章中,我们将深入探讨保护数据集、跟踪数据访问、数据静态加密和数据传输加密。
数据湖
在第三章中,我们讨论了人工智能和数据科学在过去几年中的民主化、数据的爆炸增长,以及云服务如何提供基础设施的灵活性来存储和处理任意量的数据。
然而,为了有效利用所有这些数据,公司需要打破现有的数据孤岛,并找到分析非常多样化数据集的方法,处理结构化和非结构化数据,同时确保符合数据治理、数据安全和隐私法规的最高标准。这些(大)数据挑战为数据湖奠定了基础。
数据湖最大的优势之一是我们无需预定义任何模式。我们可以按规模存储原始数据,然后稍后决定需要以哪些方式处理和分析它。数据湖可以包含结构化、半结构化和非结构化数据。图 4-2 显示了集中且安全的数据湖仓库,使我们能够以任意规模(甚至实时)存储、治理、发现和共享数据。

图 4-2. 数据湖是一个集中且安全的仓库,使我们能够以任意规模存储、治理、发现和共享数据。
数据湖为数据科学和机器学习提供了完美的基础,因为它们使我们能够访问大量丰富多样的数据集,从而训练和部署更加精确的模型。构建数据湖通常包括以下(高级别)步骤,如图 4-3 所示:
-
设置存储。
-
移动数据。
-
清洗、准备和编目数据。
-
配置和执行安全和合规策略。
-
使数据可用于分析。
每个步骤涉及一系列工具和技术。虽然我们可以从零开始手动构建数据湖,但云服务可帮助我们简化此过程,例如 AWS Lake Formation。

图 4-3. 构建数据湖涉及许多步骤。
Lake Formation从数据库和对象存储中收集和编目数据,将数据移入基于 S3 的数据湖,保障对敏感数据的访问,并利用机器学习去重数据。
Lake Formation 的附加功能包括行级安全性、列级安全性和支持原子、一致、隔离和持久事务的“治理”表。通过行级和列级权限,用户只能看到他们有权限访问的数据。使用 Lake Formation 事务,用户可以并发和可靠地在治理表中插入、删除和修改行。Lake Formation 还通过自动压缩数据存储和优化治理表的数据布局来提高查询性能。
S3 已成为数据湖的热门选择,因为它提供了多种方式来摄取我们的数据,并通过智能数据分层(包括冷存储和存档能力)实现成本优化。S3 还为安全和合规性提供了许多对象级别的控制。
在 S3 数据湖之上,AWS 实施了 Lake House 架构。Lake House 架构将我们的 S3 数据湖与 Amazon Redshift 数据仓库集成,形成统一的治理模型。在本章中,当我们运行跨 Amazon Redshift 数据仓库和 S3 数据湖的数据联接查询时,我们将看到这种架构的一个示例。
从数据分析的角度来看,将数据存储在亚马逊 S3 中的另一个关键好处是显著缩短了“洞察时间”,因为我们可以直接在 S3 中对数据运行特定查询。我们不需要通过复杂的转换流程和数据管道将数据导入传统的企业数据仓库,正如我们将在本章后面的部分中看到的那样。
将数据导入 S3 数据湖
现在我们已经准备好将数据导入 S3 了。我们选择了亚马逊客户评论数据集作为本书的主要数据集。
亚马逊客户评论数据集包含了从 1995 年到 2015 年在亚马逊网站上涵盖 43 个不同产品类别的 1.5 亿多条客户评论。它是展示诸如自然语言处理(NLP)等机器学习概念的重要资源,正如本书中我们所展示的那样。
许多人在考虑是否通过亚马逊市场购买产品时,都看过亚马逊网站上的客户评论。图 4-4 显示了亚马逊 Echo Dot 设备的产品评论部分。

图 4-4。亚马逊 Echo Dot 设备的评论。来源:Amazon.com。
描述数据集
客户评论是亚马逊对于希望做出明智购买决策的客户而言最宝贵的工具之一。在亚马逊的年度股东信中,亚马逊创始人杰夫·贝索斯经常详细阐述了“口碑”的重要性作为客户获取工具。“杰夫热爱‘客户的持续不满’,正如他所称:
“现在我们为客户提供…更多的评论、内容、浏览选项和推荐功能…口碑仍然是我们拥有的最强大的客户获取工具,我们感谢客户对我们的信任。重复购买和口碑的结合使得亚马逊成为在线图书销售的市场领导者。”
—杰夫·贝索斯,1997 年股东(“股东”)信
这是数据集的架构:
marketplace
两位字母国家代码(在本例中全部为“US”)。
customer_id
可用于汇总由单个作者撰写的评论的随机标识符。
review_id
评论的唯一 ID。
product_id
亚马逊标准识别号(ASIN)。
product_parent
该 ASIN 的父级。多个 ASIN(同一产品的颜色或格式变体)可以汇总为一个产品父级。
product_title
产品的标题描述。
product_category
可以用来分组评论的广泛产品类别。
星级评分
对评论的评分为 1 到 5 星,其中 1 是最差,5 是最好。
有用票数
评论有用票数。
总票数
评论接收到的总票数。
Vine
评论是否作为 Vine 计划的一部分撰写?
经过验证的购买
评论是否来自经过验证的购买?
评论标题
评论本身的标题。
评论正文
评论的标题。
评论日期
评论撰写日期。
数据集在公共亚马逊 S3 存储桶中共享,并提供两种文件格式:
-
TSV,文本格式:s3://amazon-reviews-pds/tsv
-
Parquet,一种优化的列式二进制格式:s3://amazon-reviews-pds/parquet
Parquet 数据集按 product_category 列进行分区(分成子文件夹),以进一步提高查询性能。通过这种方式,我们可以在 SQL 查询中使用 WHERE 子句来只读取特定类别的数据。
我们可以使用 AWS 命令行界面(AWS CLI)来列出 S3 存储桶内容,具体命令如下:
-
aws s3 ls s3://amazon-reviews-pds/tsv -
aws s3 ls s3://amazon-reviews-pds/parquet
注意
AWS CLI 工具提供了统一的命令行界面,用于 Amazon Web Services。我们可以在 如何安装和配置该工具 找到更多信息。
以下列表显示了 TSV 格式和 Parquet 分区文件夹结构中的可用数据集文件。
TSV 格式的数据集文件:
2017-11-24 13:49:53 648641286 amazon_reviews_us_Apparel_v1_00.tsv.gz
2017-11-24 13:56:36 582145299 amazon_reviews_us_Automotive_v1_00.tsv.gz
2017-11-24 14:04:02 357392893 amazon_reviews_us_Baby_v1_00.tsv.gz
2017-11-24 14:08:11 914070021 amazon_reviews_us_Beauty_v1_00.tsv.gz
2017-11-24 14:17:41 2740337188 amazon_reviews_us_Books_v1_00.tsv.gz
2017-11-24 14:45:50 2692708591 amazon_reviews_us_Books_v1_01.tsv.gz
2017-11-24 15:10:21 1329539135 amazon_reviews_us_Books_v1_02.tsv.gz
...
2017-11-25 08:39:15 94010685 amazon_reviews_us_Software_v1_00.tsv.gz
2017-11-27 10:36:58 872478735 amazon_reviews_us_Sports_v1_00.tsv.gz
2017-11-25 08:52:11 333782939 amazon_reviews_us_Tools_v1_00.tsv.gz
2017-11-25 09:06:08 838451398 amazon_reviews_us_Toys_v1_00.tsv.gz
2017-11-25 09:42:13 1512355451 amazon_reviews_us_Video_DVD_v1_00.tsv.gz
2017-11-25 10:50:22 475199894 amazon_reviews_us_Video_Games_v1_00.tsv.gz
2017-11-25 11:07:59 138929896 amazon_reviews_us_Video_v1_00.tsv.gz
2017-11-25 11:14:07 162973819 amazon_reviews_us_Watches_v1_00.tsv.gz
2017-11-26 15:24:07 1704713674 amazon_reviews_us_Wireless_v1_00.tsv.gz
Parquet 格式的数据集文件:
PRE product_category=Apparel/
PRE product_category=Automotive/
PRE product_category=Baby/
PRE product_category=Beauty/
PRE product_category=Books/
...
PRE product_category=Watches/
PRE product_category=Wireless/
请注意,PRE 代表“前缀”。目前,我们可以将前缀视为 S3 中的文件夹。
在查询中有时使用 EXPLAIN 是很有用的,以确保 S3 分区被充分利用。例如,Spark 将突出显示在 Spark SQL 中使用的分区。如果我们的查询模式随时间改变,可能需要重新审视更新现有分区——甚至添加新分区以满足业务需求。
那么我们应该选择哪种数据格式呢?Parquet 列式文件格式在运行分析查询时绝对是首选,因为许多分析查询在数据列上执行汇总统计(AVG、SUM、STDDEV 等)。另一方面,许多应用程序将数据写入简单的 CSV 或 TSV 文件中,例如应用程序日志文件。因此,我们假设暂时没有准备好使用 Parquet 文件,这样可以展示如何从 CSV 或 TSV 文件轻松转换到 Parquet 文件。
首先,让我们将来自亚马逊公共 S3 存储桶的 TSV 数据复制到私人托管的 S3 存储桶中,以模拟该过程,如 图 4-5 所示。

图 4-5. 我们将数据集从公共 S3 存储桶复制到私有 S3 存储桶。
我们可以再次使用 AWS CLI 工具执行以下步骤。
-
创建新的私有 S3 存储桶:
aws s3 mb s3://data-science-on-aws -
将公共 S3 存储桶的内容复制到我们新创建的私有 S3 存储桶,操作如下(仅包括以
amazon_reviews_us_开头的文件,即跳过该目录中的任何索引、多语言和样本数据文件):aws s3 cp --recursive s3://amazon-reviews-pds/tsv/ \ s3://data-science-on-aws/amazon-reviews-pds/tsv/ \ --exclude "*" --include "amazon_reviews_us_*"
现在我们可以使用 Amazon Athena 注册和查询数据,并将 TSV 文件转换为 Parquet。
使用 Amazon Athena 查询 Amazon S3 数据湖
Amazon Athena 是一个交互式查询服务,可以使用标准 SQL 轻松分析 Amazon S3 中的数据。通过 Athena,我们可以直接从基于 S3 的数据湖中查询原始数据,包括加密数据。Athena 将计算与存储分离,并降低我们业务的整体洞察时间。当我们使用 Athena 将 Athena 表注册到我们的 S3 数据时,Athena 存储表与 S3 映射。Athena 使用 AWS Glue 数据目录作为 Hive Metastore 兼容的服务来存储表与 S3 的映射。我们可以将 AWS Glue 数据目录视为 AWS 帐户中的持久性元数据存储。其他 AWS 服务,如 Athena 和 Amazon Redshift Spectrum,可以使用数据目录定位和查询数据。Apache Spark 也可以从 AWS Glue 数据目录中读取数据。
除了数据目录外,AWS Glue 还提供了构建 ETL(抽取-转换-加载)工作流的工具。ETL 工作流可能包括从不同来源自动发现和提取数据。我们可以利用 Glue Studio 在可视化环境中组合和运行 ETL 工作流,而无需编写代码。Glue Studio 还提供了监视所有 ETL 作业的单一窗口。AWS Glue 在基于 Apache Spark 的无服务器 ETL 引擎上执行工作流。
Athena 查询在一个动态缩放、无服务器查询引擎内并行运行。Athena 会根据所涉及的查询和数据集自动扩展集群。这使得 Athena 在处理大数据集时非常快速,并且使用户不必担心基础设施的细节。
此外,Athena 支持 Parquet 列式文件格式,并支持数千万个分区(例如按product_category、year或marketplace)。以提高查询性能。例如,如果我们计划频繁查询并按product_category分组结果,则应在 Athena 中为product_category创建一个分区。创建后,Athena 将相应更新 AWS Glue 数据目录,以便将来的查询可以继承这个新分区的性能优势。
Athena 基于 Presto,这是一个开源的分布式 SQL 查询引擎,专为在大数据集上进行快速、即席数据分析而设计。类似于 Apache Spark,Presto 使用高 RAM 集群执行查询。但 Presto 不需要大量磁盘,因为它设计用于即席查询(而不是自动化、可重复的查询),因此不执行需要容错的检查点操作。
对于运行时间较长的 Athena 作业,我们可以使用 Amazon CloudWatch Events 监听查询完成事件。查询完成后,所有监听器都会收到事件详细信息,包括查询成功状态、总执行时间和扫描的总字节数。
使用名为 Athena Federated Query 的功能,我们还可以在存储在关系数据库(例如 Amazon RDS 和 Aurora)、非关系数据库(例如 DynamoDB)、对象存储(例如 Amazon S3)以及自定义数据源中的数据上运行 SQL 查询。这使我们可以在不实际移动数据的情况下,通过统一的分析视图访问我们的数据仓库、数据湖和操作数据库中存储的数据。
我们可以通过 AWS 管理控制台、API,或者通过 Open Database Connectivity (ODBC) 或 Java Database Connectivity (JDBC) 驱动程序进行编程访问 Athena。让我们看看如何通过 AWS 管理控制台使用 Amazon Athena。
从 AWS 控制台访问 Athena
要使用 Amazon Athena,我们首先需要快速设置该服务。首先,在 AWS 管理控制台中点击 Amazon Athena。如果我们被要求为 Athena 在 S3 中的“查询结果”位置设置位置,请指定一个 S3 位置作为查询结果的位置(例如 s3://
在下一步中,我们创建一个数据库。在 Athena 查询编辑器中,我们看到一个带有示例查询的查询面板。我们可以在查询面板的任何位置开始输入我们的查询。要创建我们的数据库,请输入以下CREATE DATABASE语句,运行查询,并确认dsoaws出现在目录仪表板的数据库列表中:
CREATE DATABASE dsoaws;
当我们在 Athena 中使用 AWS Glue 数据目录作为源运行 CREATE DATABASE 和 CREATE TABLE 查询时,我们会自动看到在 AWS Glue 数据目录中创建的数据库和表元数据条目。
注册 S3 数据作为 Athena 表
现在我们有了一个数据库,可以基于亚马逊客户评论数据集创建表。我们定义映射到数据的列,指定数据的分隔方式,并提供数据的 Amazon S3 路径。
让我们定义一个“基于读取的模式”,避免在数据写入和摄入时需要预定义严格的模式。在 Athena 控制台中,确保选择了dsoaws作为数据库,然后选择新查询。运行以下 SQL 语句来读取压缩(compression=gzip)文件,并跳过每个文件顶部的 CSV 标头(skip.header.line.count=1)。运行 SQL 语句后,请验证新创建的表amazon_reviews_tsv在左侧的表下是否出现:
CREATE EXTERNAL TABLE IF NOT EXISTS dsoaws.amazon_reviews_tsv(
marketplace string,
customer_id string,
review_id string,
product_id string,
product_parent string,
product_title string,
product_category string,
star_rating int,
helpful_votes int,
total_votes int,
vine string,
verified_purchase string,
review_headline string,
review_body string,
review_date string
) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
LOCATION 's3://data-science-on-aws/amazon-reviews-pds/tsv'
TBLPROPERTIES ('compressionType'='gzip', 'skip.header.line.count'='1')
让我们运行一个示例查询,以检查一切是否正常工作。此查询将生成如下表格中显示的结果:
SELECT *
FROM dsoaws.amazon_reviews_tsv
WHERE product_category = 'Digital_Video_Download' LIMIT 10
| marketplace | customer_id | review_id | product_id | product_title | product_category |
|---|---|---|---|---|---|
| US | 12190288 | R3FBDHSJD | BOOAYB23D | Enlightened | Digital_Video_Download |
| ... | ... | ... | ... | ... | ... |
使用 AWS Glue Crawler 更新 Athena 表以处理新数据到达
以下代码每天在 UTC 时间 23:59 自动爬取 S3,并在新数据到达时更新 Athena 表。例如,如果我们向 S3 添加另一个 .tar.gz 文件,我们将在爬虫完成预定运行后在 Athena 查询中看到新数据:
glue = boto3.Session().client(service_name='glue', region_name=region)
create_response = glue.create_crawler(
Name='amazon_reviews_crawler',
Role=role,
DatabaseName='dsoaws',
Description='Amazon Customer Reviews Dataset Crawler',
Targets={
'CatalogTargets': [
{
'DatabaseName': 'dsoaws',
'Tables': [
'amazon_reviews_tsv',
]
}
]
},
Schedule='cron(59 23 * * ? *)', # run every night at 23:59 UTC
SchemaChangePolicy={
'DeleteBehavior': 'LOG'
},
RecrawlPolicy={
'RecrawlBehavior': 'CRAWL_EVERYTHING'
}
)
在 Athena 中创建基于 Parquet 的表
接下来,我们将展示如何轻松地将数据转换为 Apache Parquet 列式文件格式以提高查询性能。Parquet 针对基于列的查询进行了优化,如计数、求和、平均值和其他基于列的汇总统计,重点放在列值而不是行信息上。
通过以列式格式存储我们的数据,Parquet 可以执行针对列的顺序读取以进行列式汇总统计。这使得数据访问更加高效,并且在性能上表现更好,与磁盘控制器从行到行跳转并重新查找以检索列数据相比,有着“机械同情心”。如果我们进行任何类型的大规模数据分析,我们应该使用像 Parquet 这样的列式文件格式。我们在性能部分讨论了 Parquet 的好处。
注意
尽管我们已经从公共数据集中以 Parquet 格式获取了数据,但我们认为创建一个 Parquet 表是一个足够重要的主题,可以在本书中进行演示。
再次确保选择 DATABASE 中的dsoaws,然后选择 New Query 并运行以下 CREATE TABLE AS (CTAS) SQL 语句:
CREATE TABLE IF NOT EXISTS dsoaws.amazon_reviews_parquet
WITH (format = 'PARQUET', \
external_location = 's3://<BUCKET>/amazon-reviews-pds/parquet', \
partitioned_by = ARRAY['product_category']) AS
SELECT marketplace,
customer_id,
review_id,
product_id,
product_parent,
product_title,
star_rating,
helpful_votes,
total_votes,
vine,
verified_purchase,
review_headline,
review_body,
CAST(YEAR(DATE(review_date)) AS INTEGER) AS year,
DATE(review_date) AS review_date,
product_category
FROM dsoaws.amazon_reviews_tsv
如查询所示,我们还通过将review_date字符串转换为日期格式,然后将年份从日期中提取,向我们的数据集添加了一个新的year列。让我们将年份值存储为整数。运行完 CTAS 查询后,我们现在应该可以在左侧的表下看到新创建的amazon_reviews_parquet表。作为最后一步,我们需要加载 Parquet 分区。为此,只需执行以下 SQL 命令:
MSCK REPAIR TABLE amazon_reviews_parquet;
注意
我们可以自动化执行MSCK REPAIR TABLE命令,以在从任何工作流管理器中获取数据后加载分区(或使用 Lambda 函数,当新数据上传到 S3 时运行)。
我们可以再次运行我们的示例查询来检查一切是否正常:
SELECT *
FROM dsoaws.amazon_reviews_parquet
WHERE product_category = 'Digital_Video_Download' LIMIT 10
两个表还在 Hive Metastore 兼容的 AWS Glue 数据目录中有元数据条目。这些元数据定义了许多查询和数据处理引擎(如 Amazon EMR、Athena、Redshift、Kinesis、SageMaker 和 Apache Spark)使用的模式。
只需几个步骤,我们就设置了 Amazon Athena,将 TSV 数据集文件转换为 Apache Parquet 文件格式。与对 TSV 文件的查询相比,Parquet 文件的查询时间大大缩短。通过利用列式 Parquet 文件格式和product_category分区方案,我们加快了查询响应时间。
使用 AWS Glue Crawler 持续摄取新数据
应用程序始终在传送新数据,我们需要一种方法将这些新数据注册到我们的系统中进行分析和模型训练。AWS Glue 提供复杂的数据清洗和机器学习转换,包括“模糊”记录去重。将新数据从 S3 注册到我们的 AWS Glue 数据目录的一种方式是使用 Glue 爬虫,如 图 4-6 所示。

图 4-6. 使用 AWS Glue 爬虫从各种数据源摄取和注册数据。
我们可以按计划定期触发爬虫,也可以使用例如 S3 触发器。以下代码创建爬虫,并安排每天晚上 23:59 UTC 注入新的 S3 文件夹(前缀):
create_response = glue.create_crawler(
Name='amazon_reviews_crawler',
Role=role,
DatabaseName='dsoaws',
Description='Amazon Customer Reviews Dataset Crawler',
Targets={
'CatalogTargets': [
{
'DatabaseName': 'dsoaws',
'Tables': [
'amazon_reviews_tsv',
]
}
]
},
Schedule='cron(59 23 * * ? *)',
SchemaChangePolicy={
'DeleteBehavior': 'LOG'
},
RecrawlPolicy={
'RecrawlBehavior': 'CRAWL_NEW_FOLDERS_ONLY'
}
)
假设我们正在将新数据存储在新文件夹中。通常,我们使用包括年、月、日、小时、刻钟等信息的 S3 前缀。例如,我们可以使用以下 S3 前缀命名约定将应用程序日志存储在每小时的 S3 文件夹中:s3://<S3_BUCKET>/CRAWL_EVERYTHING 作为我们的 RecrawlBehavior。我们可以使用不同的 cron() 触发器更改计划。我们还可以添加第二个触发器,在调度的 Glue 爬虫达到 SUCCEEDED 状态时启动 ETL 作业以转换和加载新数据。
使用亚马逊 Redshift Spectrum 构建数据湖
数据湖和数据仓库之间的一个基本区别在于,虽然我们在数据湖中摄取和存储大量原始未加工的数据,但通常只将最近数据的一部分加载到数据仓库中。根据我们的业务和分析用例,这可能是过去几个月、一年或最近两年的数据。假设我们想在数据仓库中分析亚马逊客户评论数据集的过去两年数据以分析年度客户行为和评论趋势,我们将使用亚马逊 Redshift 作为我们的数据仓库。
亚马逊 Redshift 是一个完全托管的数据仓库,允许我们针对千兆字节的结构化数据、半结构化数据和 JSON 数据运行复杂的分析查询。我们的查询分布和并行化在多个节点上执行。与关系数据库相比,后者优化了按行存储数据,并主要用于事务应用程序;亚马逊 Redshift 实施了列存储,这对于分析应用程序而言更为优化,因为我们主要关注的是单个列内的数据。
Amazon Redshift 还包括 Amazon Redshift Spectrum,允许我们直接从 Amazon Redshift 针对 S3 数据湖中的数百亿字节的非结构化数据执行 SQL 查询,而无需实际移动数据。Amazon Redshift Spectrum 是 Lake House Architecture 的一部分,统一了我们的 S3 数据湖和 Amazon Redshift 数据仓库,包括共享安全性和基于行和列的访问控制。Amazon Redshift Spectrum 支持各种开源存储框架,包括 Apache Hudi 和 Delta Lake。
由于 Amazon Redshift Spectrum 根据检索的数据量自动扩展所需的计算资源,因此针对 Amazon S3 的查询速度很快,而不管数据大小如何。Amazon Redshift Spectrum 将使用推送过滤器、布隆过滤器和物化视图来减少搜索时间,并提高对 S3 等外部数据存储的查询性能。我们稍后在“降低成本并提高性能”中讨论更多性能提示。
Amazon Redshift Spectrum 通过在加载到 Amazon Redshift 后转换和清理数据,将传统的 ETL 转换为提取-加载-转换(ELT)。我们将使用 Amazon Redshift Spectrum 访问我们在 S3 中的数据,然后展示如何将存储在 Amazon Redshift 中的数据与仍在 S3 中的数据结合起来。
这听起来可能与我们早些时候展示的 Amazon Athena 的方法相似,但请注意,在这种情况下,我们展示了我们的业务智能团队如何在其查询中使用未存储在数据仓库本身中的数据进行丰富。一旦我们设置和配置了 Redshift 集群,我们可以导航到 AWS 控制台和 Amazon Redshift,然后点击查询编辑器来执行命令。
我们可以利用在 AWS Glue Data Catalog 中存储的 Amazon Athena 的表格及其元数据和模式信息来通过 Amazon Redshift Spectrum 访问我们在 S3 中的数据。我们所需做的就是在 Amazon Redshift 中创建一个外部模式,将其指向 AWS Glue Data Catalog,并指向我们创建的数据库。
在 Amazon Redshift 查询编辑器(或通过我们可能更喜欢使用的任何其他 ODBC/JDBC SQL 客户端)中执行以下命令:
CREATE EXTERNAL SCHEMA IF NOT EXISTS athena FROM DATA CATALOG
DATABASE 'dsoaws'
IAM_ROLE '<IAM-ROLE>'
CREATE EXTERNAL DATABASE IF NOT EXISTS
使用此命令,我们在 Amazon Redshift 中创建一个名为athena的新模式,以突出我们通过 Amazon Athena 设置的表格中的数据访问:
-
FROM DATA CATALOG表示外部数据库在 AWS Glue Data Catalog 中定义。 -
DATABASE指的是在 AWS Glue Data Catalog 中之前创建的数据库。 -
IAM_ROLE需要指向用于集群身份验证和授权的 IAM 角色的 Amazon 资源名称(ARN)。
IAM 是 AWS 身份和访问管理服务,它使我们能够管理和控制访问我们帐户中的 AWS 服务和资源。通过 IAM 角色,我们可以指定用户或服务被授予的权限。在本例中,IAM 角色必须至少具有执行 Amazon S3 存储桶上的LIST操作和 Amazon S3 对象上的GET操作的权限。如果外部数据库在 Amazon Athena 数据目录中定义,IAM 角色必须具有访问 Athena 的权限,除非指定了CATALOG_ROLE。在本章后面的部分中,当我们讨论如何保护我们的数据时,我们将详细介绍 IAM。
如果我们现在在 Amazon Redshift 查询编辑器的模式下拉菜单中选择athena,我们可以看到我们使用 Amazon Athena 创建的两个表,amazon_reviews_tsv和amazon_reviews_parquet。让我们再次运行一个示例查询来确保一切正常。在查询编辑器中,运行以下命令:
SELECT
product_category,
COUNT(star_rating) AS count_star_rating
FROM
athena.amazon_reviews_tsv
GROUP BY
product_category
ORDER BY
count_star_rating DESC
我们应该看到类似下表的结果:
| product_category | count_star_rating |
|---|---|
| Books | 19531329 |
| Digital_Ebook_Purchase | 17622415 |
| Wireless | 9002021 |
| ... | ... |
所以,只需一条命令,我们现在可以访问并从 Amazon Redshift 中的 S3 数据湖查询数据,而无需将任何数据移动到我们的数据仓库中。这就是 Amazon Redshift Spectrum 的威力。
现在,让我们实际从 S3 复制一些数据到 Amazon Redshift。让我们拉取 2015 年的客户评论数据。
首先,我们使用以下 SQL 命令创建另一个 Amazon Redshift 模式称为redshift:
CREATE SCHEMA IF NOT EXISTS redshift
接下来,我们将创建一个新的表来表示我们的客户评论数据。我们还将添加一个新列,并将year添加到我们的表中:
CREATE TABLE IF NOT EXISTS redshift.amazon_reviews_tsv_2015(
marketplace varchar(2) ENCODE zstd,
customer_id varchar(8) ENCODE zstd,
review_id varchar(14) ENCODE zstd,
product_id varchar(10) ENCODE zstd DISTKEY,
product_parent varchar(10) ENCODE zstd,
product_title varchar(400) ENCODE zstd,
product_category varchar(24) ENCODE raw,
star_rating int ENCODE az64,
helpful_votes int ENCODE zstd,
total_votes int ENCODE zstd,
vine varchar(1) ENCODE zstd,
verified_purchase varchar(1) ENCODE zstd,
review_headline varchar(128) ENCODE zstd,
review_body varchar(65535) ENCODE zstd,
review_date varchar(10) ENCODE bytedict,
year int ENCODE az64) SORTKEY (product_category)
在性能部分,我们将深入探讨SORTKEY、DISTKEY和ENCODE属性。现在,让我们从 S3 复制数据到我们的新 Amazon Redshift 表,并运行一些示例查询。
对于这样的批量插入,我们可以使用COPY命令或INSERT INTO命令。一般来说,COPY命令更受青睐,因为它可以并行加载数据,效率更高,可以从 Amazon S3 或其他支持的数据源加载数据。
如果我们从一个表加载数据或数据子集到另一个表中,我们可以使用INSERT INTO命令和带有SELECT子句的高性能数据插入。因为我们正在从athena.amazon_reviews_tsv表加载数据,所以让我们选择这个选项:
INSERT
INTO
redshift.amazon_reviews_tsv_2015
SELECT
marketplace,
customer_id,
review_id,
product_id,
product_parent,
product_title,
product_category,
star_rating,
helpful_votes,
total_votes,
vine,
verified_purchase,
review_headline,
review_body,
review_date,
CAST(DATE_PART_YEAR(TO_DATE(review_date,
'YYYY-MM-DD')) AS INTEGER) AS year
FROM
athena.amazon_reviews_tsv
WHERE
year = 2015
我们使用日期转换来从我们的review_date列中解析年份,并将其存储在一个单独的year列中,然后我们使用它来过滤 2015 年的记录。这是一个简化 ETL 任务的示例,因为我们将数据转换逻辑直接放在SELECT查询中,并将结果摄入到 Amazon Redshift 中。
另一种优化表格的方法是将它们创建为一系列时间序列表,特别是当我们的数据具有固定的保留期限时。假设我们希望将最近两年(24 个月)的数据存储在我们的数据仓库中,并每月更新新数据。
如果我们每月创建一个表格,我们可以通过在相应的表格上运行DROP TABLE命令轻松删除旧数据。这种方法比运行大规模的DELETE流程要快得多,并且还能避免运行后续的VACUUM过程以回收空间和重新排序行。
要跨表合并查询结果,我们可以使用UNION ALL视图。类似地,当我们需要删除旧数据时,我们从UNION ALL视图中删除已丢弃的表。
这是一个跨两个表格进行UNION ALL视图的示例,涉及来自 2014 年和 2015 年的客户评论数据。以下表格显示了查询结果:
SELECT
product_category,
COUNT(star_rating) AS count_star_rating,
year
FROM
redshift.amazon_reviews_tsv_2014
GROUP BY
redshift.amazon_reviews_tsv_2014.product_category,
year
UNION
ALL SELECT
product_category,
COUNT(star_rating) AS count_star_rating,
year
FROM
redshift.amazon_reviews_tsv_2015
GROUP BY
redshift.amazon_reviews_tsv_2015.product_category,
year
ORDER BY
count_star_rating DESC,
year ASC
| 产品类别 | 评分计数 | 年份 |
|---|---|---|
| 数字电子书购买 | 6615914 | 2014 |
| 数字电子书购买 | 4533519 | 2015 |
| 图书 | 3472631 | 2014 |
| 无线 | 2998518 | 2015 |
| 无线 | 2830482 | 2014 |
| 图书 | 2808751 | 2015 |
| 服装 | 2369754 | 2015 |
| 家居 | 2172297 | 2015 |
| 服装 | 2122455 | 2014 |
| 家居 | 1999452 | 2014 |
现在,让我们实际运行一个查询,并将 Amazon Redshift 的数据与仍然存储在 S3 中的数据进行合并。让我们使用以下命令从之前查询的数据获取 2015 年和 2014 年的数据,并查询 Athena/S3 中的 2013 年至 1995 年的数据:
SELECT
year,
product_category,
COUNT(star_rating) AS count_star_rating
FROM
redshift.amazon_reviews_tsv_2015
GROUP BY
redshift.amazon_reviews_tsv_2015.product_category,
year
UNION
ALL SELECT
year,
product_category,
COUNT(star_rating) AS count_star_rating
FROM
redshift.amazon_reviews_tsv_2014
GROUP BY
redshift.amazon_reviews_tsv_2014.product_category,
year
UNION
ALL SELECT
CAST(DATE_PART_YEAR(TO_DATE(review_date,
'YYYY-MM-DD')) AS INTEGER) AS year,
product_category,
COUNT(star_rating) AS count_star_rating
FROM
athena.amazon_reviews_tsv
WHERE
year <= 2013
GROUP BY
athena.amazon_reviews_tsv.product_category,
year
ORDER BY
product_category ASC,
year DESC
| 年份 | 产品类别 | 评分计数 |
|---|---|---|
| 2015 | 服装 | 4739508 |
| 2014 | 服装 | 4244910 |
| 2013 | 服装 | 854813 |
| 2012 | 服装 | 273694 |
| 2011 | 服装 | 109323 |
| 2010 | 服装 | 57332 |
| 2009 | 服装 | 42967 |
| 2008 | 服装 | 33761 |
| 2007 | 服装 | 25986 |
| 2006 | 服装 | 7293 |
| 2005 | 服装 | 3533 |
| 2004 | 服装 | 2357 |
| 2003 | 服装 | 2147 |
| 2002 | 服装 | 907 |
| 2001 | 服装 | 5 |
| 2000 | 服装 | 6 |
| 2015 | 汽车 | 2609750 |
| 2014 | 汽车 | 2350246 |
将 Amazon Redshift 数据导出到 S3 数据湖作为 Parquet
Amazon Redshift 数据湖导出使我们能够将 Amazon Redshift 查询的结果卸载到我们的 S3 数据湖中,以优化的 Apache Parquet 列式文件格式。这使我们能够将在 Amazon Redshift 中进行的任何数据转换和增强重新共享到我们的 S3 数据湖中,以开放格式存储。卸载的数据会自动在 AWS Glue 数据目录中注册,供任何 Hive Metastore 兼容的查询引擎使用,包括 Amazon Athena、EMR、Kinesis、SageMaker 和 Apache Spark。
我们可以指定一个或多个分区列,使得卸载的数据自动分区到 Amazon S3 存储桶中的文件夹中。例如,我们可以选择卸载我们的客户评论数据,并按product_category进行分区。
我们只需运行以下 SQL 命令,将我们的 2015 年客户评论数据以 Parquet 文件格式卸载到 S3 中,并按product_category分区:
UNLOAD (
'SELECT marketplace, customer_id, review_id, product_id, product_parent,
product_title, product_category, star_rating, helpful_votes, total_votes,
vine, verified_purchase, review_headline, review_body, review_date, year
FROM redshift.amazon_reviews_tsv_2015')
TO 's3://data-science-on-aws/amazon-reviews-pds/parquet-from-redshift/2015'
IAM_ROLE '<IAM_ROLE>'
PARQUET PARALLEL ON
PARTITION BY (product_category)
我们可以再次使用 AWS CLI 工具列出 S3 文件夹,并查看我们以 Parquet 格式卸载的 2015 年数据:
aws s3 ls s3://data-science-on-aws/amazon-reviews-pds/parquet-from-redshift/2015
在亚马逊 Redshift 集群之间共享数据
亚马逊 Redshift 还实现了数据共享功能,允许我们在不移动数据的情况下安全地在亚马逊 Redshift 集群之间共享实时数据。相反,我们创建一个“数据共享”对象,指定要共享的数据以及被允许访问数据的亚马逊 Redshift 集群列表。在消费的亚马逊 Redshift 集群上,我们从数据共享对象创建一个新的数据库,并分配权限给相关的 IAM 用户和组,以管理对数据库的访问。数据共享功能在需要在多个业务单元之间共享数据或者希望从中央数据仓库集群向其他 BI 和分析集群共享数据时非常有用。
在亚马逊 Athena 和亚马逊 Redshift 之间做出选择
当在存储在亚马逊 S3 中的数据上运行即席 SQL 查询时,亚马逊 Athena 是首选。它不需要我们设置或管理任何基础设施资源——我们不需要移动任何数据。它支持结构化、非结构化和半结构化数据。使用 Athena,我们在“读时模式”下定义模式——基本上只需登录、创建表格并开始运行查询。
亚马逊 Redshift 针对 PB 级结构化数据的现代数据分析进行了优化。在这里,我们需要预定义的“写时模式”。与无服务器的 Athena 不同,亚马逊 Redshift 要求我们创建一个集群(计算和存储资源)、摄入数据并构建表格,然后才能开始查询,但能够满足性能和规模需求。因此,对于具有事务性特性的高度关系型数据(数据更新)、涉及复杂连接或需要次秒级响应时间的工作负载,亚马逊 Redshift 是正确的选择。
Athena 和亚马逊 Redshift 都专为读重型分析工作负载进行了优化;它们不适用于写重型的关系数据库,比如亚马逊关系数据库服务(RDS)和 Aurora。从高层次来看,使用 Athena 进行探索性分析和操作调试;使用亚马逊 Redshift 进行业务关键报告和仪表板。
减少成本并提高性能
在本节中,我们想提供一些有关在数据摄入期间减少成本和提高性能的技巧,包括文件格式、分区、压缩以及排序/分布键。我们还将演示如何使用亚马逊 S3 智能分层来降低存储成本。
S3 智能分层
在本章中,我们介绍了亚马逊 S3 作为一种可扩展的、持久的存储服务,用于构建云中的共享数据集,比如数据湖。虽然在本书中我们将 S3 的使用保持相对简单,但实际上该服务为我们提供了多种选项来优化随着数据增长而增长的存储成本。
根据我们数据的访问频率模式和服务级别协议(SLA)需求,我们可以选择不同的 Amazon S3 存储类别。 表 4-1 比较了 Amazon S3 存储类别在数据访问频率和数据检索时间方面的差异。
表 4-1. Amazon S3 存储类别比较
| 从频繁访问 | 到不经常访问 |
|---|---|
| S3 标准(默认存储类) | S3 智能分层 |
| --- | --- |
| 通用存储活跃,频繁
访问数据
毫秒级访问 | 数据具有未知或变化的访问模式
毫秒级访问
自动选择
存档 | 不经常访问的(IA)数据
毫秒级访问 | 更低的耐久性(单可用区)可重建数据
毫秒级访问 | 存档数据分钟或小时级访问 | 长期存档数据小时级访问 |
但是我们如何知道要移动哪些对象?想象一下我们的 S3 数据湖随着时间的推移不断增长,我们可能在多个 S3 存储桶中拥有数十亿个对象,其中一些对象非常重要,而其他对象可能数月甚至数年未访问。这就是 S3 智能分层发挥作用的地方。
Amazon S3 智能分层通过在频繁访问数据优化层和低成本不经常访问数据优化层之间移动对象,自动优化我们的存储成本,适应变化的访问模式。智能分层监控我们的访问模式,并在粒度对象级别自动分层,没有性能影响或任何操作开销。
Parquet 分区和压缩
Athena 支持 Parquet 列式格式用于大规模分析工作负载。Parquet 为我们的查询提供以下性能优化:
分区和下推
分区是在磁盘上物理数据的分组,以匹配我们的查询模式(例如SELECT * FROM reviews WHERE product_category='Books')。现代查询引擎如 Athena、Amazon Redshift 和 Apache Spark 会将WHERE条件“下推”到物理存储系统,允许磁盘控制器仅需一次寻址即可扫描所有相关数据,而无需随机跳转到磁盘的不同区域。这提高了查询性能,即使使用固态硬盘(SSD),其寻址时间比传统的基于介质的硬盘低。
字典编码/压缩
当少量分类值一起存储在磁盘上(例如我们数据集中共有 43 个product_category值),这些值可以被压缩成少量比特来表示每个值(例如Books、Lawn_and_Garden、Software等),而不是存储整个字符串。
类型压缩
类似类型的值(例如 String、Date、Integer)在磁盘上存储在一起时,这些值可以一起压缩:(String, String), (Date, Date), (Integer, Integer)。这种压缩比将这些值以行方式分开存储在磁盘上更为高效:(String, Date, Integer), (String, Date, Integer)
矢量化聚合
因为列值在磁盘上存储在一起,磁盘控制器只需执行一个磁盘搜索来找到数据的起始位置。从那时起,它将扫描数据执行聚合操作。此外,现代芯片/处理器提供高性能的矢量化指令,以在大量数据上执行计算,而不是将数据刷新到各种数据缓存(L1、L2)或主内存中。
在 图 4-7 中看到行与列式数据格式的示例。

图 4-7. 使用 Parquet 等列式数据格式,我们可以对查询执行和数据编码应用各种性能优化。
Amazon Redshift 表设计和压缩
这里是我们用来创建 Amazon Redshift 表的 CREATE TABLE 语句:
CREATE TABLE IF NOT EXISTS redshift.amazon_reviews_tsv_2015(
marketplace varchar(2) ENCODE zstd,
customer_id varchar(8) ENCODE zstd,
review_id varchar(14) ENCODE zstd,
product_id varchar(10) ENCODE zstd DISTKEY,
product_parent varchar(9) ENCODE zstd,
product_title varchar(400) ENCODE zstd,
product_category varchar(24) ENCODE raw,
star_rating int ENCODE az64,
helpful_votes int ENCODE zstd,
total_votes int ENCODE zstd,
vine varchar(1) ENCODE zstd,
verified_purchase varchar(1) ENCODE zstd,
review_headline varchar(128) ENCODE zstd,
review_body varchar(65535) ENCODE zstd,
review_date varchar(10) ENCODE bytedict,
year int ENCODE az64) SORTKEY (product_category)
当我们创建表时,可以指定一个或多个列作为 SORTKEY。Amazon Redshift 按照 SORTKEY 的顺序将数据存储在磁盘上,因此我们可以通过选择反映我们最常用查询类型的 SORTKEY 来优化表。如果我们频繁查询最近的数据,可以将时间戳列指定为 SORTKEY。如果我们经常基于一列上的范围或等值过滤进行查询,则应选择该列作为 SORTKEY。在下一章节我们将运行大量基于 product_category 进行过滤的查询,让我们将其选择为我们的 SORTKEY。
提示
Amazon Redshift Advisor 持续推荐频繁查询的表使用 SORTKEY。Advisor 将生成一个 ALTER TABLE 命令,我们可以直接运行,而无需重新创建表——不影响并发读写查询。请注意,如果 Advisor 没有看到足够的数据(查询),或者收益相对较小,它将不会提供推荐。
我们还可以为每个表定义一个分布样式。当我们将数据加载到表中时,Amazon Redshift 根据表的分布样式将表的行分布到我们的集群节点中。当执行查询时,查询优化器根据需要将行重新分布到集群节点,以执行任何连接和聚合操作。因此,我们的目标应该是优化行分布,以最小化数据移动。我们可以选择三种分布样式中的一种:
KEY 分布
根据一列中的值分发行。
ALL 分布
将整个表的副本分发到每个节点。
EVEN 分布
行以循环轮换方式在所有节点之间分布,这是默认的分布样式。
对于我们的表,我们基于product_id选择了KEY分布,因为这一列具有高基数,显示均匀分布,并且可以用于与其他表进行连接。
在任何时候,我们都可以在我们的 Amazon Redshift 查询中使用EXPLAIN来确保DISTKEY和SORTKEY得到了利用。如果我们的查询模式随时间变化,我们可能需要重新审视更改这些键。
此外,我们为大多数列使用了压缩,以减少总体存储占用和降低成本。Table 4-2 分析了我们模式中每个 Amazon Redshift 列使用的压缩方式。
表 4-2。我们 Amazon Redshift 表中使用的压缩类型
| Column | Data type | Encoding | 解释 |
|---|---|---|---|
| marketplace | varchar(2) | zstd | 基数较低,对于更高的压缩开销来说太小了 |
| customer_id | varchar(8) | zstd | 高基数,重复值相对较少 |
| review_id | varchar(14) | zstd | 唯一,无限的基数,无重复值 |
| product_id | varchar(10) | zstd | 无限的基数,重复词汇数量相对较低 |
| product_parent | varchar(10) | zstd | 无限的基数,重复词汇数量相对较低 |
| product_title | varchar(400) | zstd | 无限的基数,重复词汇数量相对较低 |
| product_category | varchar(24) | raw | 基数较低,许多重复值,但第一个 SORT 键是 raw |
| star_rating | int | az64 | 基数较低,许多重复值 |
| helpful_votes | int | zstd | 基数较高 |
| total_votes | int | zstd | 基数相对较高 |
| vine | varchar(1) | zstd | 基数较低,对于更高的压缩开销来说太小了 |
| verified_purchase | varchar(1) | zstd | 基数较低,对于更高的压缩开销来说太小了 |
| review_headline | varchar(128) | zstd | 变长文本,高基数,重复词汇数量低 |
| review_body | varchar(65535) | zstd | 变长文本,高基数,重复词汇数量低 |
| review_date | varchar(10) | bytedict | 固定长度,基数相对较低,许多重复值 |
| year | int | az64 | 基数较低,许多重复值 |
注意
尽管 AWS CEO Andy Jassy 坚持认为“没有经验的压缩算法”,但对于数据来说,却有压缩算法。压缩是处理不断增长的大数据世界的强大工具。所有现代大数据处理工具都支持压缩,包括 Amazon Athena、Redshift、Parquet、pandas 和 Apache Spark。对于诸如varchar(1)这样的小值使用压缩可能不会提高性能。然而,由于本地硬件支持,使用压缩几乎没有任何缺点。
zstd 是一种通用的压缩算法,适用于许多不同的数据类型和列大小。star_rating 和 year 字段设置为默认的 az64 编码,适用于大多数数值和日期字段。对于大多数列,通过为整数使用默认的 az64 编码和为文本等其他内容使用灵活的 zstd 编码,我们获得了快速的优势。
我们使用 bytedict 对 review_date 进行字典编码,以处理基于字符串的日期 (YYYY-MM-DD)。尽管它看似有大量唯一的值,实际上 review_date 只包含少量唯一值,因为在 20 年的时间跨度内只有大约 7300 天(每年 365 天 × 20 年)。这个基数很低,可以用几个比特捕获所有可能的日期,而不是对每个日期使用完整的 varchar(10)。
虽然 product_category 是 bytedict 字典编码的绝佳候选项,但它是我们的第一个(也是唯一一个,在这种情况下)SORTKEY。作为性能最佳实践,第一个 SORTKEY 不应该被压缩。
虽然 marketplace、product_category、vine 和 verified_purchase 看起来是 bytedict 的良好候选项,但它们太小,无法从额外的开销中受益。目前我们将它们保留为 zstd。
如果我们有一个现有的 Amazon Redshift 表需要优化,可以在 Amazon Redshift 中运行 ANALYZE COMPRESSION 命令,生成推荐的压缩编码报告如下:
ANALYZE COMPRESSION redshift.customer_reviews_tsv_2015
结果将如下表所示,展示如果切换到另一种编码方式,压缩改善的百分比:
| 列名 | 编码方式 | 估计的压缩率 (%) |
|---|---|---|
marketplace |
zstd |
90.84 |
customer_id |
zstd |
38.88 |
review_id |
zstd |
36.56 |
product_id |
zstd |
44.15 |
product_parent |
zstd |
44.03 |
product_title |
zstd |
30.72 |
product_category |
zstd |
99.95 |
star_rating |
az64 |
0 |
helpful_votes |
zstd |
47.58 |
total_votes |
zstd |
39.75 |
vine |
zstd |
85.03 |
verified_purchase |
zstd |
73.09 |
review_headline |
zstd |
30.55 |
review_body |
zstd |
32.19 |
review_date |
bytedict |
64.1 |
year |
az64 |
0 |
我们对一个没有指定任何 ENCODE 属性的 CREATE TABLE 版本进行了此分析。默认情况下,Amazon Redshift 将对数值/日期使用 az64,对其他所有内容使用 lzo(因此对于 az64 建议的增益为 0%)。我们还可以使用 ALTER TABLE 语句更改每列使用的压缩方式。
请记住,这些仅仅是建议,不一定适用于我们特定的环境。我们应该尝试不同的数据集编码,并查询 STV_BLOCKLIST 表,比较物理块数目的减少百分比。例如,分析器建议在我们的 SORTKEY 中使用 zstd,但我们的经验表明,当我们压缩 SORTKEY 时,查询性能会受到影响。我们正在利用额外的磁盘空间来提高查询性能。
Amazon Redshift 支持自动表优化和其他自调整功能,利用机器学习优化峰值性能并适应变化的工作负载。性能优化包括自动清理删除、智能工作负载管理、自动表排序以及自动选择分发和排序键。
使用 Bloom 过滤器提高查询性能
Amazon Redshift 是一个分布式查询引擎,而 S3 是一个分布式对象存储。分布式系统由许多集群实例组成。为了提高分布式查询的性能,我们需要尽量减少扫描的实例数量以及实例之间的数据传输量。
Bloom 过滤器是一种概率和内存效率高的数据结构,用于回答“特定的集群实例是否包含可能包含在查询结果中的数据?”的问题。Bloom 过滤器的答案可以是肯定的“NO”或者“MAYBE”。如果 Bloom 过滤器的答案是“NO”,引擎将完全跳过该集群实例,并扫描其余 Bloom 过滤器答案为“MAYBE”的实例。
通过过滤掉不匹配给定查询的数据行,Bloom 过滤器显著提高连接查询的性能。并且由于 Bloom 过滤器在数据源附近执行,减少了在连接查询期间分布式集群节点之间的数据传输。这最终提高了对诸如 S3 这样的数据存储的查询性能。
Amazon Redshift Spectrum 实际上会自动在诸如 S3 等外部数据上创建和管理 Bloom 过滤器,但我们应该意识到在改善分布式数据存储的查询性能中,它们的重要性。Bloom 过滤器是分布式计算中广泛使用的一种模式,包括分布式查询引擎。
在 Amazon Redshift Spectrum 中的物化视图
Materialized views provide repeatable and predictable query performance on external data sources such as S3. They pretransform and prejoin data before SQL queries are executed. Materialized views can be updated either manually or on a predefined schedule using Amazon Redshift Spectrum.
摘要
在本章中,我们概述了如何将数据加载到 Amazon S3 中,讨论了 S3 数据湖的价值,并展示了如何利用 Amazon Athena 在 S3 中运行自由查询 SQL 而无需实际移动数据。我们展示了如何使用 AWS Glue Crawler 持续摄取新的应用程序数据。我们还介绍了我们的数据集,即亚马逊客户评论数据集,这将是本书后续章节的使用对象。
鉴于不同的使用情况需要不同格式的数据,我们详细阐述了如何使用 Athena 将制表符分隔的数据转换为查询优化的列式 Parquet 数据。
在我们的 S3 数据湖中,数据通常不仅需要由数据科学和机器学习团队访问,还需要由商业智能团队访问。我们介绍了基于亚马逊 Redshift 的 Lake House 架构,这是 AWS 的 PB 级云数据仓库。我们展示了如何使用 Amazon Redshift Spectrum 跨数据存储库(包括 Amazon Redshift 和 S3)合并查询。
结束本章时,我们讨论了各种数据压缩格式和 S3 分层选项,展示它们如何降低成本并改善查询性能。
在第五章中,我们将更详细地探索数据集。我们将运行查询以理解和可视化我们的数据集。我们还将展示如何使用 Apache Spark 和 Amazon SageMaker 处理作业检测数据异常。
第五章:探索数据集
在前一章中,我们演示了如何使用 Amazon Athena 和 Redshift 将数据导入到云中。Amazon Athena 提供无服务器的即席 SQL 查询,用于处理 S3 中的数据,无需设置、扩展和管理任何集群。Amazon Redshift 提供企业报告和商业智能工作负载的最快查询性能,特别是涉及到复杂 SQL、跨多个数据源(包括关系数据库和平面文件)的多个连接和子查询的情况。我们使用 AWS Glue Catalog 在 S3 中创建了基于数据湖的数据目录映射。我们使用 Athena 在我们的数据湖上运行了即席查询。我们使用 Amazon Redshift 在我们的数据仓库上运行了查询。
我们还初步了解了我们的数据集。正如我们所了解的,亚马逊顾客评论数据集包含自 1995 年至 2015 年在亚马逊网站上对超过 150+万个产品的 43 个不同产品类别的客户评论。数据集包含实际的客户评论文本以及附加的元数据。数据集有两种格式:基于行的制表符分隔值(TSV)和基于列的 Apache Parquet。
在本章中,我们将使用 SageMaker Studio 集成开发环境(IDE)作为我们的主要工作空间,用于数据分析和模型开发生命周期。SageMaker Studio 提供完全托管的 Jupyter Notebook 服务器。只需点击几下,我们就可以配置 SageMaker Studio IDE 并开始使用 Jupyter notebooks 进行即席数据分析和启动基于 Apache Spark 的数据质量作业。
我们将在本书的其余部分使用 SageMaker Studio 启动数据处理和特征工程作业,如第六章中的数据准备,第七章中的模型训练,第八章中的模型优化,第九章中的模型部署,第十章中的流水线构建,第十一章中的流处理应用以及第十二章中的数据科学项目安全性。
让我们更深入地探索我们的数据集,分析数据的相关性、异常、偏见、不平衡和有用的业务见解。这些数据分析和探索的知识将为我们在第六章中的数据偏见、特征选择和特征工程,以及在第七章和第九章中的模型偏见、公平性和可解释性分析做准备。
在 AWS 中探索数据的工具
让我们介绍一些工具和服务,这些工具和服务将帮助我们完成数据探索任务。为了选择正确的工具以实现正确的目的,我们将描述 AWS 中可用的工具的广度和深度,并使用这些工具来回答有关我们的 Amazon Customer Reviews 数据集的问题。
要从运行在 SageMaker Studio IDE 中的 Jupyter 笔记本与 AWS 资源进行交互,我们利用 AWS Python SDK Boto3 和 Python DB 客户端 PyAthena 连接到 Athena,Python SQL 工具包 SQLAlchemy 连接到 Amazon Redshift,以及开源的 AWS Data Wrangler library 用于在 pandas 和 Amazon S3、Athena、Redshift、Glue 和 EMR 之间进行数据移动。
提示
开源的 AWS Data Wrangler library 与 SageMaker Data Wrangler 无关。这是一个不幸的名称冲突。AWS Data Wrangler 专注于将数据引入和在 AWS 存储服务(如 Amazon S3、Athena、Redshift 等)之间移动,而 SageMaker Data Wrangler 专注于基于 ML 的数据引入、分析和转换,以便可重复使用的流水线。我们稍后在本章中将更详细地描述 SageMaker Data Wrangler,并描述何时使用其中之一。
Amazon EMR 支持灵活的、高度分布式的数据处理和分析框架,如 Apache Spark 和 Hadoop。Amazon EMR 是一个托管服务,具有自动化的集群设置和自动缩放功能,并支持 Spot 实例。Amazon EMR 允许我们运行具有特定计算、内存和存储参数的自定义作业,以优化我们的分析查询。Amazon EMR Studio 是 AWS 上的统一 IDE,用于数据处理。SageMaker Studio 也通过 EMR 特定的 Jupyter 内核支持 Amazon EMR。
QuickSight 是一项快速、易于使用的业务智能服务,可从多个数据源(跨多个设备)构建可视化、执行即席分析并构建仪表板。
使用 SageMaker Studio 可视化我们的数据湖
在本节中,我们将开始使用 Amazon SageMaker Studio IDE,该 IDE 为我们提供托管的 Jupyter 笔记本。我们将使用我们在第四章介绍的 Amazon Customer Reviews 数据集。以下是数据集模式的快速概述:
marketplace
两位字母国家代码(在本例中仅为“US”)。
customer_id
用于聚合单个作者撰写的评论的随机标识符。
review_id
评论的唯一 ID。
product_id
亚马逊标准识别号(ASIN)。
product_parent
多个 ASIN(同一产品的变体)可以汇总为一个父级。
product_title
产品的标题描述。
产品类别
广泛的产品类别用于组织评论。
星级评分
评论的评级从 1 到 5 星,其中 1 星是最差,5 星是最好。
有用投票数
对评论的有用投票数。
total_votes
评论的总票数。
vine
评论是否作为 Vine 计划的一部分编写?
verified_purchase
评论是否来自已验证购买?
review_headline
评论的标题。
review_body
实际评论文本。
review_date
评论提交日期。
准备 SageMaker Studio 以可视化我们的数据集
在这本 Jupyter 笔记本中进行探索性数据分析时,我们将使用 pandas,NumPy,Matplotlib 和 Seaborn,这些库可能是 Python 中最常用的用于数据分析和数据可视化的库。Seaborn 基于 Matplotlib 构建,增加了对 pandas 的支持,并通过简化的 API 提供了更高级的可视化功能。我们还将使用 PyAthena,这是 Amazon Athena 的 Python 数据库客户端,可以直接从我们的笔记本中运行 Athena 查询:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format='retina'
import seaborn as sns
提示
在使用具有 Retina 显示的 Mac 时,请确保在 Matplotlib 上指定 retina 设置,以获得更高分辨率的图像。
在 Amazon Athena 中定义包含我们的亚马逊客户评论数据集信息的数据库和表:
database_name = 'dsoaws'
table_name = 'amazon_reviews_parquet'
现在,我们已准备好从笔记本中直接运行我们的第一个 SQL 查询了。
在 SageMaker Studio 中运行示例 Athena 查询
在以下显示的第一个示例中,我们将查询数据集以获取不同产品类别的列表。PyAthena 建立了与数据源的连接。然后我们将使用 pandas 执行 SQL 命令,将 SQL 语句传递给执行和 PyAthena 连接对象:
# PyAthena imports
from pyathena import connect
# Set the Athena query results S3 bucket
s3_staging_dir = 's3://{0}/athena/staging'.format(bucket)
# Set up the PyAthena connection
conn = connect(region_name=region, s3_staging_dir=s3_staging_dir)
# The SQL statement to execute
sql_statement="""
SELECT DISTINCT product_category from {0}.{1}
ORDER BY product_category
""".format(database_name, table_name)
# Execute the SQL statement with pandas
import pandas as pd
pd.read_sql(sql_statement, conn)
这是查询所有产品类别的 read_sql() 调用结果:
| 产品类别 | 产品类别(续) |
|---|---|
| 服装 | 行李 |
| 汽车 | 大型家电 |
| 婴儿 | 移动应用 |
| 美容 | 移动电子产品 |
| 书籍 | 音乐 |
| 相机 | 音乐乐器 |
| 数字电子书购买 | 办公用品 |
| 数字音乐购买 | 户外 |
| 数字软件 | 个人电脑 |
| 数字视频下载 | 个人护理电器 |
| 数字视频游戏 | 宠物产品 |
| 电子产品 | 鞋类 |
| 家具 | 软件 |
| 礼品卡 | 运动 |
| 食品杂货 | 工具 |
| 健康与个人护理 | 玩具 |
| 家 | 视频 |
| 家庭娱乐 | 视频 DVD |
| 家庭装修 | 视频游戏 |
| 珠宝 | 手表 |
| 厨房 | 无线 |
| 草坪和花园 |
注意
如果我们处理的数据集过大,超过笔记本服务器可用的内存,我们可能需要使用 pandas 游标。在读取数据进 DataFrame 时要注意文件大小。处理大数据集时,很容易超出可用内存。
深入研究 Athena 和 SageMaker 中的数据集
我们需要了解我们的数据,以便为特征选择和特征工程的下一步准备。我们将跨数据运行查询,了解数据相关性,识别数据异常和类别不平衡。
让我们使用 Athena、SageMaker Studio、Matplotlib 和 Seaborn 来跟踪整个数据集中以下问题的答案:
-
哪些产品类别的平均评分最高?
-
哪些产品类别有最多的评论?
-
根据首次评论日期,每个产品类别何时在 Amazon 目录中可用?
-
每个产品类别的星级评分(1–5)分布如何?
-
产品类别的星级评分如何随时间变化?某些产品类别在年内是否存在评分下降点?
-
哪些星级评分(1–5)最有帮助?
-
评论长度(字数)的分布是怎样的?
注意
从这一点开始,我们只会展示 Athena 查询和结果。执行和渲染结果的完整源代码可在附带的 GitHub 存储库中找到。
1. 哪些产品类别的平均评分最高?
以下是可以回答这个问题的 SQL 查询:
SELECT product_category, AVG(star_rating) AS avg_star_rating
FROM dsoaws.amazon_reviews_parquet
GROUP BY product_category
ORDER BY avg_star_rating DESC
让我们使用 Seaborn 和 Matplotlib 绘制水平条形图,以提供哪些产品类别比其他产品类别更受欢迎的高级概述。在接下来的几章中选择我们的训练数据集时,我们可能需要考虑这种分布。
图 5-1 显示 Amazon 礼品卡是评分最高的产品类别,平均星级为 4.73,其次是音乐购买,平均为 4.64,以及音乐,平均为 4.44。

图 5-1. 在 Amazon.com 市场上,礼品卡是评分最高的产品类别。
2. 哪些产品类别有最多的评论?
以下是可以回答这个问题的 SQL 查询:
SELECT product_category,
COUNT(star_rating) AS count_star_rating
FROM dsoaws.amazon_reviews_parquet
GROUP BY product_category
ORDER BY count_star_rating DESC
让我们再次使用 Seaborn 和 Matplotlib 将结果绘制为水平条形图,显示在图 5-2 中。

图 5-2. 图书产品类别有接近 2000 万条评论。
我们可以在图 5-2 中看到,“图书”产品类别的评论最多,接近 2000 万条。这是因为Amazon.com 最初作为“地球上最大的书店”于 1995 年开始运营。
第二评论数量最多的类别是“数字电子书购买”,代表 Kindle 书籍评论。因此,我们注意到无论是印刷书籍还是电子书籍,书籍评论仍然占据了大多数评论。
“个人护理电器”评论数量最少。这可能是因为该产品类别是最近添加的原因。
让我们通过查询每个类别的第一条评论来检查这一点,这将为我们提供产品类别引入的大致时间表。
3. 每个产品类别何时在 Amazon 目录中可用?
初审日期是每个产品类别何时在 Amazon.com 上线的强有力指标。以下是可以回答这个问题的 SQL 查询:
SELECT
product_category,
MIN(year) AS first_review_year
FROM dsoaws.amazon_reviews_parquet
GROUP BY product_category
ORDER BY first_review_year
结果应该类似于这样:
| 产品类别 | 首次评论年份 |
|---|---|
| 图书 | 1995 |
| 视频游戏 | 1997 |
| 办公用品 | 1998 |
| 宠物用品 | 1998 |
| 软件 | 1998 |
| 礼品卡 | 2004 |
| 数字视频游戏 | 2006 |
| 数字软件 | 2008 |
| 移动应用 | 2010 |
我们可以看到个人护理电器确实是稍后添加到 Amazon.com 目录中的,但这似乎并不是评论数量较低的唯一原因。移动应用似乎是在 2010 年左右添加的。
让我们来看看每个产品类别每年的首次评论数量,如图 5-3 所示。

图 5-3. 我们的数据集包括 1999 年的 13 个首次产品类别评论。
我们注意到我们的首次产品类别评论中有很多(13 条)发生在 1999 年。无论这是否真的与这些产品类别在此时期的引入有关,还是仅仅是由我们数据集中的可用数据造成的巧合,我们无法确定。
4. 每个产品类别的星级评分(1-5)的详细情况是什么?
这是能够回答此问题的 SQL 查询:
SELECT product_category,
star_rating,
COUNT(*) AS count_reviews
FROM dsoaws.amazon_reviews_parquet
GROUP BY product_category, star_rating
ORDER BY product_category ASC, star_rating DESC,
count_reviews
结果应该与此类似(缩短版):
| 产品类别 | 星级评分 | 评论数量 |
|---|---|---|
| 服装 | 5 | 3320566 |
| 服装 | 4 | 1147237 |
| 服装 | 3 | 623471 |
| 服装 | 2 | 369601 |
| 服装 | 1 | 445458 |
| 汽车 | 5 | 2300757 |
| 汽车 | 4 | 526665 |
| 汽车 | 3 | 239886 |
| 汽车 | 2 | 147767 |
| 汽车 | 1 | 299867 |
| ... | ... | ... |
通过这些信息,我们还可以快速按星级评分进行分组,并计算每个评分(5、4、3、2、1)的评论数量:
SELECT star_rating,
COUNT(*) AS count_reviews
FROM dsoaws.amazon_reviews_parquet
GROUP BY star_rating
ORDER BY star_rating DESC, count_reviews
结果应该与此类似:
| 星级评分 | 评论数量 |
|---|---|
| 5 | 93200812 |
| 4 | 26223470 |
| 3 | 12133927 |
| 2 | 7304430 |
| 1 | 12099639 |
大约 62%的所有评论都获得了 5 星评级。当我们进行特征工程准备模型训练时,我们将回到这种星级评分的相对不平衡。
现在我们可以可视化堆叠百分比水平条形图,显示每个产品类别中每个星级评分的比例,如图 5-4 所示。

图 5-4. 每个产品类别的星级评分分布(5、4、3、2、1)。
我们可以看到每个产品类别中 5 星和 4 星评级占据了最大比例。但让我们看看是否能够发现不同产品满意度随时间的差异。
5. 评星随时间的变化如何?某些产品类别在一年中是否存在评星下降点?
让我们首先看看各产品类别在多年间的平均星级评分。这是能够回答此问题的 SQL 查询:
SELECT year, ROUND(AVG(star_rating), 4) AS avg_rating
FROM dsoaws.amazon_reviews_parquet
GROUP BY year
ORDER BY year;
结果应该与此类似:
| 年份 | 平均评分 |
|---|---|
| 1995 | 4.6169 |
| 1996 | 4.6003 |
| 1997 | 4.4344 |
| 1998 | 4.3607 |
| 1999 | 4.2819 |
| 2000 | 4.2569 |
| ... | ... |
| 2010 | 4.069 |
| 2011 | 4.0516 |
| 2012 | 4.1193 |
| 2013 | 4.1977 |
| 2014 | 4.2286 |
| 2015 | 4.2495 |
如果我们绘制这个,如图表 5-5 所示,我们注意到总体上升趋势,但在 2004 年和 2011 年有两次低谷。

图表 5-5. 所有产品类别的平均星级评分随时间变化。
现在让我们来看看我们的五大产品类别按评分数量排名('Books', 'Digital_Ebook_Purchase', 'Wireless', 'PC', 和 'Home')。这是能回答这个问题的 SQL 查询:
SELECT
product_category,
year,
ROUND(AVG(star_rating), 4) AS avg_rating_category
FROM dsoaws.amazon_reviews_parquet
WHERE product_category IN
('Books', 'Digital_Ebook_Purchase', 'Wireless', 'PC', 'Home')
GROUP BY product_category, year
ORDER BY year
结果应该类似于这样(缩短):
| 产品类别 | 年份 | 平均评分类别 |
|---|---|---|
| 书籍 | 1995 | 4.6111 |
| 书籍 | 1996 | 4.6024 |
| 书籍 | 1997 | 4.4339 |
| 主页 | 1998 | 4.4 |
| 无线 | 1998 | 4.5 |
| 书籍 | 1998 | 4.3045 |
| 主页 | 1999 | 4.1429 |
| 数字电子书购买 | 1999 | 5.0 |
| PC | 1999 | 3.7917 |
| 无线 | 1999 | 4.1471 |
如果我们现在绘制,如图表 5-6 所示,我们可以看到一些有趣的东西。

图表 5-6. 产品类别随时间变化的平均星级评分(前五名)。
虽然书籍的star_rating相对稳定,值在 4.1 到 4.6 之间,但其他类别更受客户满意度的影响。数字电子书购买(Kindle books)似乎波动较大,在 2005 年低至 3.5,在 2003 年高达 5.0。这绝对需要仔细查看我们的数据集,以决定这是否由于当时评论有限或某种数据偏斜导致,或者这确实反映了客户的声音。
6. 哪些星级评价(1-5)最有帮助?
这是能回答这个问题的 SQL 查询:
SELECT star_rating,
AVG(helpful_votes) AS avg_helpful_votes
FROM dsoaws.amazon_reviews_parquet
GROUP BY star_rating
ORDER BY star_rating DESC
结果应该类似于这样:
| star_rating | avg_helpful_votes |
|---|---|
| 5 | 1.672697561905362 |
| 4 | 1.6786973653753678 |
| 3 | 2.048089542651773 |
| 2 | 2.5066350146417995 |
| 1 | 3.6846412525200134 |
我们发现客户认为负面评价比正面评价更有帮助,这在图表 5-7 中有可视化。

图表 5-7. 客户认为负面评价(1 星评级)最有帮助。
7. 评论长度(单词数量)的分布是什么?
这是能回答这个问题的 SQL 查询:
SELECT CARDINALITY(SPLIT(review_body, ' ')) as num_words
FROM dsoaws.amazon_reviews_parquet
我们可以通过百分位数描述结果分布:
summary = df['num_words']\
.describe(percentiles=\
[0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00])
总结结果应该类似于这样:
count 396601.000000
mean 51.683405
std 107.030844
min 1.000000
10% 2.000000
20% 7.000000
30% 19.000000
40% 22.000000
50% 26.000000
60% 32.000000
70% 43.000000
80% 63.000000
90% 110.000000
100% 5347.000000
max 5347.000000
如果我们现在绘制,如图表 5-8 所示,我们可以看到 80%的评论只有 63 个单词或更少。

图表 5-8. 直方图可视化评论长度的分布。
查询我们的数据仓库。
此时,我们将使用 Amazon Redshift 查询和可视化用例。与 Athena 示例类似,我们首先需要准备 SageMaker Studio 环境。
在 SageMaker Studio 中运行 Amazon Redshift 示例查询。
在以下示例中,我们将查询数据集,以获得每个产品类别的唯一客户数量。我们可以使用 pandas 的read_sql_query函数运行我们的 SQLAlchemy 查询,并将查询结果存储在 pandas DataFrame 中:
df = pd.read_sql_query("""
SELECT product_category, COUNT(DISTINCT customer_id) as num_customers
FROM redshift.amazon_reviews_tsv_2015
GROUP BY product_category
ORDER BY num_customers DESC
""", engine)
df.head(10)
输出结果应类似于这样:
| 产品类别 | 客户数量 |
|---|---|
| 无线通讯 | 1979435 |
| 数字电子书购买 | 1857681 |
| 书籍 | 1507711 |
| 服装 | 1424202 |
| 家居 | 1352314 |
| 个人电脑 | 1283463 |
| 健康与个人护理 | 1238075 |
| 美容 | 1110828 |
| 鞋类 | 1083406 |
| 运动 | 1024591 |
我们可以看到Wireless产品类别拥有最多提供评论的独特客户,其次是Digital_Ebook_Purchase和Books类别。现在我们准备从 Amazon Redshift 查询更深入的客户洞察。
深入挖掘 Amazon Redshift 和 SageMaker 数据集
现在,让我们从 Amazon Redshift 中查询 2015 年的数据,深入了解我们的客户,并找到以下问题的答案:
-
2015 年哪些产品类别的评论最多?
-
2015 年哪些产品评论最有帮助?这些评论的长度是多少?
-
2015 年星级评分如何变化?在整年中,是否有某些产品类别的评分出现了下降点?
-
2015 年哪些客户写了最有帮助的评论?他们分别写了多少评论?涵盖了多少个类别?他们的平均星级评分是多少?
-
2015 年哪些客户为同一产品提供了多于一条评论?每个产品的平均星级评分是多少?
注
与 Athena 示例一样,我们将只展示 Amazon Redshift 的 SQL 查询和结果。执行和呈现结果的完整源代码可在附带的 GitHub 仓库中找到。
让我们运行查询,找出答案吧!
1. 2015 年哪些产品类别的评论最多?
下面是将回答此问题的 SQL 查询:
SELECT
year,
product_category,
COUNT(star_rating) AS count_star_rating
FROM
redshift.amazon_reviews_tsv_2015
GROUP BY
product_category,
year
ORDER BY
count_star_rating DESC,
year DESC
结果应类似于以下数据子集:
| 年份 | 产品类别 | 评分计数 |
|---|---|---|
| 2015 | 数字电子书购买 | 4533519 |
| 2015 | 无线通讯 | 2998518 |
| 2015 | 书籍 | 2808751 |
| 2015 | 服装 | 2369754 |
| 2015 | 家居 | 2172297 |
| 2015 | 健康与个人护理 | 1877971 |
| 2015 | 个人电脑 | 1877971 |
| 2015 | 美容 | 1816302 |
| 2015 | 数字视频下载 | 1593521 |
| 2015 | 运动 | 1571181 |
| ... | ... | ... |
我们注意到书籍仍然是最多评论的产品类别,但实际上现在是电子书(Kindle 书籍)。让我们在水平条形图中将结果可视化,如图 5-9 所示。

图 5-9. 2015 年,数字电子书购买拥有最多的评论。
2. 2015 年哪些产品评论最有帮助?
此外,这些评论的长度是多少?以下是将回答此问题的 SQL 查询:
SELECT
product_title,
helpful_votes,
LENGTH(review_body) AS review_body_length,
SUBSTRING(review_body, 1, 100) AS review_body_substring
FROM
redshift.amazon_reviews_tsv_2015
ORDER BY
helpful_votes DESC LIMIT 10
结果应类似于以下内容:
| product_title | helpful_votes | review_body_length | review_body_substring |
|---|---|---|---|
| Fitbit Charge HR Wireless Activity Wristband | 16401 | 2824 | 完全公开,我只在放弃 Jawbone 未能按时交货之后才订购 Fitbit Charge HR |
| Kindle Paperwhite | 10801 | 16338 | 评价更新于 2015 年 9 月 17 日 作为背景,我是一名退休的信息系统专业人员 |
| Kindle Paperwhite | 8542 | 9411 | [[VIDEOID:755c0182976ece27e407ad23676f3ae8]]如果你正在阅读关于新第三代 Pape 的评论 |
| Weslo Cadence G 5.9 Treadmill | 6246 | 4295 | 我得到了 Weslo 跑步机,非常喜欢它。我身高 6'2",体重 230 磅,喜欢在外面跑步(ab |
| Haribo Gummi Candy Gold-Bears | 6201 | 4736 | 这是我本学期最后一节课,期末考占了我们成绩的 30%。 在一个长 |
| FlipBelt - World’s Best Running Belt & Fitness Workout Belt | 6195 | 211 | 选择上尺码表有误。我已附上照片,你可以看看真正需要的。 |
| Amazon.com eGift Cards | 5987 | 3498 | 我觉得写这个可能只是在浪费时间,但一定也发生在其他人身上。某事 |
| Melanie’s Marvelous Measles | 5491 | 8028 | 如果你喜欢这本书,可以看看同一作者的其他精彩作品: Abby’s |
| Tuft & Needle Mattress | 5404 | 4993 | 简而言之:经过一些难关,绝佳的客户服务使得这款床垫非常出色。The |
| Ring Wi-Fi Enabled Video Doorbell | 5399 | 3984 | 首先,Ring 门铃非常酷。我真的很喜欢它,许多来我家门口的人(销售) |
我们看到“Fitbit Charge HR Wireless Activity Wristband”在 2015 年拥有最多的有用评价,共 16,401 票,并且评论长度为 2,824 个字符。其后是两篇关于“Kindle Paperwhite”的评论,人们写了长达 16,338 和 9,411 个字符的评论。
3. 2015 年星级评分发生了什么变化?
此外,某些产品类别是否在全年内有下降点?以下是能回答这个问题的 SQL 查询:
SELECT
CAST(DATE_PART('month', TO_DATE(review_date, 'YYYY-MM-DD')) AS integer)
AS month,
AVG(star_rating ::FLOAT) AS avg_rating
FROM redshift.amazon_reviews_tsv_2015
GROUP BY month
ORDER BY month
结果应该与以下类似:
| month | avg_rating |
|---|---|
| 1 | 4.277998926134835 |
| 2 | 4.267851231035101 |
| 3 | 4.261042822856084 |
| 4 | 4.247727865199895 |
| 5 | 4.239633709986397 |
| 6 | 4.235766635971452 |
| 7 | 4.230284081689972 |
| 8 | 4.231862792031927 |
我们注意到我们只有到 2015 年 8 月的数据。我们还可以看到平均评分在缓慢下降,正如我们在 Figure 5-10 中清楚地看到的。尽管我们没有确切的解释,但这种下降很可能在 2015 年得到调查。

Figure 5-10. 2015 年各产品类别的平均星级评分。
现在让我们探讨这是否由于某个产品类别在客户满意度上急剧下降,还是所有类别都有这种趋势。以下是能回答这个问题的 SQL 查询:
SELECT
product_category,
CAST(DATE_PART('month', TO_DATE(review_date, 'YYYY-MM-DD')) AS integer)
AS month,
AVG(star_rating ::FLOAT) AS avg_rating
FROM redshift.amazon_reviews_tsv_2015
GROUP BY product_category, month
ORDER BY product_category, month
结果应该类似于这样(缩短版):
| 产品类别 | 月份 | 平均评分 |
|---|---|---|
| 服装 | 1 | 4.159321618698804 |
| 服装 | 2 | 4.123969612021801 |
| 服装 | 3 | 4.109944336469443 |
| 服装 | 4 | 4.094360325567125 |
| 服装 | 5 | 4.0894595692213125 |
| 服装 | 6 | 4.09617799917213 |
| 服装 | 7 | 4.097665115845663 |
| 服装 | 8 | 4.112790034578352 |
| 汽车 | 1 | 4.325502388403887 |
| 汽车 | 2 | 4.318120214368761 |
| ... | ... | ... |
让我们通过可视化结果来更容易地发现趋势,如图 5-11 所示。

图 5-11. 2015 年每个产品类别的平均星级评分。
虽然这个图表有点凌乱,但我们可以看到大多数类别在月份间都有相同的平均星级评分,其中三个类别比其他类别波动更大:“数字软件”,“软件”和“移动电子产品”。不过,它们在整年内都有所改善,这是好事。
4. 哪些客户在 2015 年写的评论最有帮助?
同时,他们写了多少评论?跨多少个类别?平均星级评分是多少?以下是能回答这些问题的 SQL 查询:
SELECT
customer_id,
AVG(helpful_votes) AS avg_helpful_votes,
COUNT(*) AS review_count,
COUNT(DISTINCT product_category) AS product_category_count,
ROUND(AVG(star_rating::FLOAT), 1) AS avg_star_rating
FROM
redshift.amazon_reviews_tsv_2015
GROUP BY
customer_id
HAVING
count(*) > 100
ORDER BY
avg_helpful_votes DESC LIMIT 10;
结果应该类似于这样:
| 客户 ID | 平均有用票数 | 评论数 | 产品类别数 | 平均星级评分 |
|---|---|---|---|---|
| 35360512 | 48 | 168 | 26 | 4.5 |
| 52403783 | 44 | 274 | 25 | 4.9 |
| 28259076 | 40 | 123 | 7 | 4.3 |
| 15365576 | 37 | 569 | 30 | 4.9 |
| 14177091 | 29 | 187 | 15 | 4.4 |
| 28021023 | 28 | 103 | 18 | 4.5 |
| 20956632 | 25 | 120 | 23 | 4.8 |
| 53017806 | 25 | 549 | 30 | 4.9 |
| 23942749 | 25 | 110 | 22 | 4.5 |
| 44834233 | 24 | 514 | 32 | 4.4 |
我们可以看到那些写了平均评分最有帮助票数的评论(提供了一百多条评论)的客户,这些评论通常反映了积极的情绪。
5. 哪些客户在 2015 年为同一产品提供了多次评论?
此外,每个产品的平均星级评分是多少?以下是能回答这个问题的 SQL 查询:
SELECT
customer_id,
product_category,
product_title,
ROUND(AVG(star_rating::FLOAT), 4) AS avg_star_rating,
COUNT(*) AS review_count
FROM
redshift.amazon_reviews_tsv_2015
GROUP BY
customer_id,
product_category,
product_title
HAVING
COUNT(*) > 1
ORDER BY
review_count DESC LIMIT 5
结果应该类似于这样:
| 客户 ID | 产品类别 | 产品标题 | 平均星级评分 | 评论数 |
|---|---|---|---|---|
| 2840168 | 相机 | (按照亚马逊指南创建一个通用标题) | 5.0 | 45 |
| 9016330 | 视频游戏 | 迪士尼无限 迪士尼无限:漫威超级英雄(2.0 版)角色 | 5.0 | 23 |
| 10075230 | 视频游戏 | 天降奇兵:斯派罗历险记:角色包 | 5.0 | 23 |
| 50600878 | 数字电子书购买 | 孙子兵法 | 2.35 | 20 |
| 10075230 | 视频游戏 | Activision Skylanders Giants Single Character Pack Core Series 2 | 4.85 | 20 |
请注意,avg_star_rating并非始终是整数。这意味着一些顾客随着时间的推移对产品的评价有所不同。
值得一提的是,顾客 9016330 发现迪士尼无限:漫威超级英雄视频游戏获得了 5 星评价——即使玩了 23 次!顾客 50600878 已经着迷到阅读《孙子兵法》20 次,但仍在努力寻找其中的积极情绪。
使用 Amazon QuickSight 创建仪表板。
QuickSight 是一种托管、无服务器且易于使用的业务分析服务,我们可以利用它快速构建强大的可视化效果。QuickSight 会自动发现我们 AWS 账户中的数据源,包括 MySQL、Salesforce、Amazon Redshift、Athena、S3、Aurora、RDS 等等。
让我们使用 QuickSight 创建一个仪表板,展示我们的 Amazon 顾客评论数据集。只需点击几下,我们就可以在任何设备上获得产品类别评论数量的可视化效果,甚至是我们的手机。我们可以使用 QuickSight Python SDK 在数据摄入后自动刷新仪表板。通过 QuickSight UI,我们可以看到我们数据集中的不平衡情况,如图 5-12 所示。

图 5-12。在 QuickSight 中可视化产品类别的评论数量。
产品类别 Books 和 Digital_Ebook_Purchase 拥有迄今为止最多的评论。在下一章节中,当我们准备数据集来训练我们的模型时,我们将更详细地分析和解决这种不平衡。
使用 Amazon SageMaker 和 Apache Spark 检测数据质量问题。
数据从未完美——特别是在一个跨越 20 年、拥有 1.5 亿行数据的数据集中!此外,随着引入新的应用功能和淘汰旧功能,数据质量实际上可能会随时间推移而下降。架构演变,代码老化,查询变慢。
由于数据质量对上游应用团队并非总是优先考虑,因此下游的数据工程和数据科学团队需要处理糟糕或缺失的数据。我们希望确保我们的数据对包括商业智能、ML 工程和数据科学团队在内的下游消费者是高质量的。
图 5-13 显示了应用程序如何为工程师、科学家和分析师生成数据,以及这些团队在访问数据时可能使用的工具和服务。

图 5-13。工程师、科学家和分析师使用各种工具和服务访问数据。
数据质量问题可能会阻碍数据处理流水线。如果不能及早发现这些问题,可能会导致误导性报告(例如重复计算的收入)、偏倚的 AI/ML 模型(偏向/反对单一性别或种族)以及其他意外的数据产品。
要尽早捕捉这些数据问题,我们使用了来自 AWS 的两个开源库,Deequ 和 PyDeequ。这些库利用 Apache Spark 分析数据质量,检测异常,并能在“凌晨 3 点通知数据科学家”发现数据问题。Deequ 在整个模型的完整生命周期内持续分析数据,从特征工程到模型训练再到生产环境中的模型服务。图 5-14 展示了 Deequ 架构和组件的高级概述。
从一次运行到下一次运行学习,Deequ 将建议在下一次通过数据集时应用的新规则。例如,在模型训练时,Deequ 学习我们数据集的基线统计信息,然后在新数据到达进行模型预测时检测异常。这个问题经典上称为“训练-服务偏差”。基本上,一个模型是用一组学习到的约束进行训练的,然后模型看到不符合这些现有约束的新数据。这表明数据从最初预期的分布发生了移动或偏差。

图 5-14. Deequ 的组件:约束、指标和建议。
因为我们有超过 1.5 亿条评论,我们需要在集群上运行 Deequ,而不是在我们的笔记本内部运行。这是在大规模数据处理中的一个权衡。笔记本对小数据集的探索性分析效果良好,但不适合处理大数据集或训练大模型。我们将使用笔记本在独立的、短暂的、无服务器的 Apache Spark 集群上启动 Deequ Spark 作业,以触发处理作业。
SageMaker 处理作业
使用 SageMaker 处理作业可以在完全托管、按需付费的 AWS 基础设施上运行任何 Python 脚本或自定义 Docker 镜像,使用熟悉的开源工具如 scikit-learn 和 Apache Spark。图 5-15 展示了 SageMaker 处理作业容器。

图 5-15. 亚马逊 SageMaker 处理作业容器。
幸运的是,Deequ 是建立在 Apache Spark 之上的高级 API,因此我们使用 SageMaker 处理作业来运行我们的大规模分析作业。
注意
Deequ 在概念上类似于 TensorFlow Extended,特别是 TensorFlow 数据验证组件。然而,Deequ 在流行的开源 Apache Spark 基础上构建,以增加可用性、调试能力和可扩展性。此外,Apache Spark 和 Deequ 本地支持 Parquet 格式——我们首选的分析文件格式。
使用 Deequ 和 Apache Spark 分析我们的数据集
表 5-1 展示了 Deequ 支持的部分指标。
表 5-1. Deequ 指标示例
| 指标 | 描述 | 使用示例 |
|---|---|---|
| ApproxCountDistinct | 使用 HLL++的近似不同值数量 | ApproxCountDistinct(“review_id”) |
| ApproxQuantiles | Approximate quantiles of a distribution | ApproxQuantiles(“star_rating”, quantiles = Seq(0.1, 0.5, 0.9)) |
| Completeness | Fraction of non-null values in a column | Completeness(“review_id”) |
| Compliance | Fraction of rows that comply with the given column constraint | Compliance(“top star_rating”, “star_rating >= 4.0”) |
| Correlation | Pearson correlation coefficient | Correlation(“total_votes”, “star_rating”) |
| Maximum | Maximum value | Maximum(“star_rating”) |
| Mean | Mean value; null valuesexcluded | Mean(“star_rating”) |
| Minimum | Minimum value | Minimum(“star_rating”) |
| MutualInformation | How much information about one column can be inferred from another column | MutualInformation(Seq(“total_votes”, “star_rating”)) |
| Size | Number of rows in a DataFrame | Size() |
| Sum | Sum of all values of a column | Sum(“total_votes”) |
| Uniqueness | Fraction of unique values | Uniqueness(“review_id”) |
让我们通过调用PySparkProcessor启动基于 Apache Spark 的 Deequ 分析作业,并从笔记本电脑上启动一个包含 10 个节点的 Apache Spark 集群。我们选择高内存的r5实例类型,因为 Spark 通常在更多内存的情况下表现更好:
s3_input_data='s3://{}/amazon-reviews-pds/tsv/'.format(bucket)
s3_output_analyze_data='s3://{}/{}/output/'.format(bucket, output_prefix)
from sagemaker.spark.processing import PySparkProcessor
processor =
PySparkProcessor(base_job_name='spark-amazon-reviews-analyzer',
role=role,
framework_version='2.4',
instance_count=10,
instance_type='ml.r5.8xlarge',
max_runtime_in_seconds=300)
processor.run(submit_app='preprocess-deequ-pyspark.py',
submit_jars=['deequ-1.0.3-rc2.jar'],
arguments=['s3_input_data', s3_input_data,
's3_output_analyze_data', s3_output_analyze_data,
],
logs=True,
wait=False
)
下面是我们的 Deequ 代码,指定了我们希望应用于 TSV 数据集的各种约束条件。在此示例中,我们使用 TSV 以保持与其他示例的一致性,但只需更改一行代码即可轻松切换到 Parquet:
dataset = spark.read.csv(s3_input_data,
header=True,
schema=schema,
sep="\t",
quote="")
定义分析器:
from pydeequ.analyzers import *
analysisResult = AnalysisRunner(spark) \
.onData(dataset) \
.addAnalyzer(Size()) \
.addAnalyzer(Completeness("review_id")) \
.addAnalyzer(ApproxCountDistinct("review_id")) \
.addAnalyzer(Mean("star_rating")) \
.addAnalyzer(Compliance("top star_rating", \
"star_rating >= 4.0")) \
.addAnalyzer(Correlation("total_votes", \
"star_rating")) \
.addAnalyzer(Correlation("total_votes",
"helpful_votes")) \
.run()
定义检查、计算指标并验证检查条件:
from pydeequ.checks import *
from pydeequ.verification import *
verificationResult = VerificationSuite(spark) \
.onData(dataset) \
.addCheck(
Check(spark, CheckLevel.Error, "Review Check") \
.hasSize(lambda x: x >= 150000000) \
.hasMin("star_rating", lambda x: x == 1.0) \
.hasMax("star_rating", lambda x: x == 5.0) \
.isComplete("review_id") \
.isUnique("review_id") \
.isComplete("marketplace") \
.isContainedIn("marketplace", ["US", "UK", "DE", "JP", "FR"])) \
.run()
我们已定义了要应用于数据集的一组约束和断言。让我们运行作业并确保我们的数据符合预期。Table 5-2 显示了我们 Deequ 作业的结果,总结了我们指定的约束和检查结果。
表 5-2. Deequ 作业结果
| check_name | columns | value |
|---|---|---|
| ApproxCountDistinct | review_id | 149075190 |
| Completeness | review_id | 1.00 |
| Compliance | Marketplace contained in US,UK, DE,JP,FR | 1.00 |
| Compliance | top star_rating | 0.79 |
| Correlation | helpful_votes,total_votes | 0.99 |
| Correlation | total_votes,star_rating | -0.03 |
| Maximum | star_rating | 5.00 |
| Mean | star_rating | 4.20 |
| Minimum | star_rating | 1.00 |
| Size | * | 150962278 |
| Uniqueness | review_id | 1.00 |
我们学到了以下内容:
-
review_id没有缺失值,大约(精确度在 2%以内)有 149,075,190 个唯一值。 -
79%的评价具有“顶级”
star_rating为 4 或更高。 -
total_votes和star_rating之间存在弱相关性。 -
helpful_votes和total_votes之间存在强相关性。 -
平均
star_rating为 4.20。 -
数据集包含确切的 150,962,278 条评价(与
ApproxCountDistinct相比略有不同,差异为 1.27%)。
Deequ 支持 MetricsRepository 的概念,以跟踪这些指标随时间的变化,并在检测到数据质量下降时可能停止我们的流水线。以下是创建 FileSystemMetricsRepository、使用修订后的 AnalysisRunner 开始跟踪我们的指标以及加载我们的指标进行比较的代码:
from pydeequ.repository import *
metrics_file = FileSystemMetricsRepository.helper_metrics_file(spark,
'metrics.json')
repository = FileSystemMetricsRepository(spark, metrics_file)
resultKey = ResultKey(spark, ResultKey.current_milli_time())
analysisResult = AnalysisRunner(spark) \
.onData(dataset) \
.addAnalyzer(Size()) \
.addAnalyzer(Completeness("review_id")) \
.addAnalyzer(ApproxCountDistinct("review_id")) \
.addAnalyzer(Mean("star_rating")) \
.addAnalyzer(Compliance("top star_rating", \
"star_rating >= 4.0")) \
.addAnalyzer(Correlation("total_votes", \
"star_rating")) \
.addAnalyzer(Correlation("total_votes", \
"helpful_votes")) \
.useRepository(repository) \
.run()
df_result_metrics_repository = repository.load() \
.before(ResultKey.current_milli_time()) \
.forAnalyzers([ApproxCountDistinct("review_id")]) \
.getSuccessMetricsAsDataFrame()
Deequ 还根据我们数据集的当前特征提出了有用的约束条件。当我们有新数据进入系统时,这是非常有用的,因为新数据在统计上可能与原始数据集不同。在现实世界中,这是非常常见的,因为新数据随时都在进入。
在 表格 5-3 中,Deequ 建议我们添加以下检查和相应的代码,以便在新数据进入系统时检测异常。
表格 5-3. Deequ 建议添加的检查
| column | check | deequ_code |
|---|---|---|
| customer_id | 'customer_id'具有整数类型 |
.hasDataType(\"customer_id\", ConstrainableDataTypes.Integral)" |
| helpful_votes | 'helpful_votes'没有负值 |
.isNonNegative(\"helpful_votes\")" |
| review_headline | 'review_headline'有不到 1%的缺失值 |
.hasCompleteness(\"review_headline\", lambda x: x >= 0.99, Some(\"应该高于 0.99!\"))" |
| product_category | 'product_category'的值范围包括 'Books', 'Digital_Ebook_Purchase', 'Wireless', 'PC', 'Home', 'Apparel', 'Health & Personal Care', 'Beauty', 'Video DVD', 'Mobile_Apps', 'Kitchen', 'Toys', 'Sports', 'Music', 'Shoes', 'Digital_Video_Download', 'Automotive', 'Electronics', 'Pet Products', 'Office Products', 'Home Improvement', 'Lawn and Garden', 'Grocery', 'Outdoors', 'Camera', 'Video Games', 'Jewelry', 'Baby', 'Tools', 'Digital_Music_Purchase', 'Watches', 'Musical Instruments', 'Furniture', 'Home Entertainment', 'Video', 'Luggage', 'Software', 'Gift Card', 'Digital_Video_Games', 'Mobile_Electronics', 'Digital_Software', 'Major Appliances', 'Personal_Care_Appliances' |
.isContainedIn(\"product_category\", Array(\"Books\", \"Digital_Ebook_Purchase\", \"Wireless\", \"PC\", \"Home\", \"Apparel\", \"Health & Personal Care\", \"Beauty\", \"Video DVD\", \"Mobile_Apps\", \"Kitchen\", \"Toys\", \"Sports\", \"Music\", \"Shoes\", \"Digital_Video_Download\", \"Automotive\", \"Electronics\", \"Pet Products\", \"Office Products\", \"Home Improvement\", \"Lawn and Garden\", \"Grocery\", \"Outdoors\", \"Camera\", \"Video Games\", \"Jewelry\", \"Baby\", \"Tools\", \"Digital_Music_Purchase\", \"Watches\", \"Musical Instruments\", \"Furniture\", \"Home Entertainment\", \"Video\", \"Luggage\", \"Software\", \"Gift Card\", \"Digital_Video_Games\", \"Mobile_Electronics\", \"Digital_Software\", \"Major Appliances\", \"Personal_Care_Appliances\"))" |
| vine | 'vine' 的值范围至少有 99.0% 的值为 'N' |
.isContainedIn(\"vine\", Array(\"N\"), lambda x: x >= 0.99, Some(\"It should be above 0.99!\"))" |
除了整型和非负数检查外,Deequ 还建议我们将 product_category 限制为目前已知的 43 个值,包括 Books、Software 等。Deequ 还意识到至少 99% 的 vine 值为 N,并且 < 1% 的 review_headline 值为空,因此建议我们在未来增加对这些条件的检查。
检测数据集中的偏见
使用 Seaborn 库的几行 Python 代码,如下所示,我们可以识别出 pandas DataFrame 中三个样本产品类别在五个不同 star_rating 类别中评论数量的不平衡。Figure 5-16 可视化了这些数据的不平衡。
import seaborn as sns
sns.countplot(data=df,
x="star_rating",
hue="product_category")

图 5-16。数据集在星级评分类别和产品类别的评论数量上存在不平衡。
现在我们将使用 SageMaker Data Wrangler 和 Clarify 在规模上分析数据集中的不平衡和其他潜在的偏见。
使用 SageMaker Data Wrangler 生成和可视化偏见报告
SageMaker Data Wrangler 与 SageMaker Studio 集成,专为机器学习、数据分析、特征工程、特征重要性分析和偏差检测而设计。通过 Data Wrangler,我们可以指定自定义的 Apache Spark 用户定义函数、pandas 代码和 SQL 查询。此外,Data Wrangler 还提供超过 300 种内置的数据转换功能,用于特征工程和偏差缓解。我们将在 第六章 深入研究 SageMaker Data Wrangler 进行特征工程。现在,让我们使用 Data Wrangler 和 SageMaker Clarify 分析数据集,后者是亚马逊 SageMaker 的一个功能,我们将在本书的其余部分中使用它来评估偏见、统计漂移/偏移、公平性和可解释性。
注意
SageMaker Data Wrangler 服务与开源 AWS Data Wrangler 项目不同。AWS Data Wrangler 主要用于数据摄取和在 AWS 服务之间传输数据。SageMaker Data Wrangler 则是首选的面向 ML 的数据摄取、分析和转换工具,因为它在整个机器学习流水线中保持完整的数据血统。
让我们使用 SageMaker Data Wrangler 分析相对于 product_category 列的类别不平衡,或者在这种情况下通常称为“facet”的情况。通常,我们正在分析像年龄和种族这样敏感的“facet”。我们选择分析 product_category facet,因为在撰写礼品卡评论与软件评论时可能存在语言使用的差异。
类不平衡是数据偏差的一个例子,如果不加以缓解,可能导致模型偏差,即模型不成比例地偏向过度表示的类别,例如Gift Card,而损害到欠表示的类别,例如Digital_Software。这可能导致欠表示的劣势类别训练误差较高。
换句话说,我们的模型可能更准确地预测礼品卡的star_rating,而不是软件,因为我们的数据集中礼品卡的评论比软件的多。对于我们的情况,这通常被称为“选择偏差”关于product_category这一维度。我们使用 SageMaker Data Wrangler 和 Clarify 生成包括类不平衡(CI)、正面比例标签差异(DPL)和 Jensen-Shannon 散度(JS)在内的多个度量的偏差报告。图 5-17 显示了我们数据集的一个子集在Gift Card产品类别和目标star_rating为 5 和 4 时的类不平衡情况。
这份偏差报告显示了Gift Card产品类别维度的 0.45 的类不平衡。类不平衡的值范围在[-1, 1]区间内。值接近 0 表明样本在分析的维度上分布均衡。接近-1 和 1 的值表明数据集不平衡,可能需要在进行特征选择、特征工程和模型训练之前进行平衡处理。

图 5-17. 通过 SageMaker Data Wrangler 偏差报告检测类不平衡。
除了使用 SageMaker Data Wrangler 检测数据偏差外,SageMaker Clarify 还帮助选择最佳的列(也称为“特征”)进行模型训练,在训练后检测模型的偏差,解释模型预测,并检测模型预测输入和输出的统计漂移。图 5-18 显示了 SageMaker Clarify 在机器学习流水线的其余阶段中的使用,包括模型训练、调优和部署。

图 5-18. 使用 SageMaker Clarify 测量数据偏差、模型偏差、特征重要性和模型可解释性。
在第七章中,我们将计算“后训练”指标,以类似的方式检测模型预测中的偏差。在第九章中,我们将通过设定各种分布距离指标的阈值,将数据分布的漂移和模型可解释性计算在我们的生产中的实时模型上,将实时指标与从我们部署模型前的训练模型创建的基线指标进行比较,并在超过阈值后通知我们。
使用 SageMaker Clarify 处理作业检测偏差
我们还可以将 Clarify 作为 SageMaker 处理作业运行,以持续分析我们的数据集并在新数据到达时计算偏差度量。以下是配置和运行SageMakerClarifyProcessor作业的代码示例,使用DataConfig指定我们的输入数据集和BiasConfig指定我们要分析的product_category类别:
from sagemaker import clarify
import pandas as pd
df = pd.read_csv('./amazon_customer_reviews_dataset.csv')
bias_report_output_path = 's3://{}/clarify'.format(bucket)
clarify_processor = clarify.SageMakerClarifyProcessor(
role=role,
instance_count=1,
instance_type='ml.c5.2xlarge',
sagemaker_session=sess)
data_config = clarify.DataConfig(
s3_data_input_path=data_s3_uri,
s3_output_path=bias_report_output_path,
label='star_rating',
headers=df.columns.to_list(),
dataset_type='text/csv')
data_bias_config = clarify.BiasConfig(
label_values_or_threshold=[5, 4],
facet_name='product_category',
facet_values_or_threshold=['Gift Card'],
group_name=product_category)
clarify_processor.run_pre_training_bias(
data_config=data_config,
data_bias_config=data_bias_config,
methods='all',
wait=True)
我们正在使用methods='all'来在“预训练”阶段计算所有数据偏差度量,但我们可以指定度量列表,包括 CI、DPL、JS、Kullback–Leibler divergence (KL)、Lp-norm (LP)、total variation distance (TVD)、Kolmogorov–Smirnov metric (KS)和 conditional demographic disparity (CDD)。
一旦SageMakerClarifyProcessor作业完成对我们的数据集进行偏差分析,我们可以在 SageMaker Studio 中查看生成的偏差报告,如图 5-19 所示。此外,SageMaker Clarify 生成analysis.json文件以及包含偏差度量的report.ipynb文件,用于可视化偏差度量并与同事分享。

图 5-19. 由 SageMaker Processing Job 生成的 SageMaker Clarify 偏差报告摘录。
将偏差检测集成到自定义脚本中,使用 SageMaker Clarify 开源工具
SageMaker 还将 Clarify 作为独立的、开源 Python 库提供,以将偏差和漂移检测集成到我们的自定义 Python 脚本中。以下是使用smclarify Python 库从 CSV 文件中检测偏差和类不平衡的示例。要安装此库,请使用pip install smclarify:
from smclarify.bias import report
import pandas as pd
df = pd.read_csv('./amazon_customer_reviews_dataset.csv')
facet_column = report.FacetColumn(name='product_category')
label_column = report.LabelColumn(
name='star_rating',
data=df['star_rating'],
positive_label_values=[5, 4]
)
group_variable = df['product_category']
report.bias_report(
df,
facet_column,
label_column,
stage_type=report.StageType.PRE_TRAINING,
group_variable=group_variable
)
结果如下所示:
[{'value_or_threshold': 'Gift Card',
'metrics': [{'name': 'CDDL',
'description': 'Conditional Demographic Disparity in Labels (CDDL)',
'value': -0.3754164610069102},
{'name': 'CI',
'description': 'Class Imbalance (CI)',
'value': 0.4520671273445213},
{'name': 'DPL',
'description': 'Difference in Positive Proportions in Labels (DPL)',
'value': -0.3679426717770344},
{'name': 'JS',
'description': 'Jensen-Shannon Divergence (JS)',
'value': 0.11632161004661548},
{'name': 'x',
'description': 'Kullback-Leibler Divergence (KL)',
'value': 0.3061581684888518},
{'name': 'KS',
'description': 'Kolmogorov-Smirnov Distance (KS)',
'value': 0.36794267177703444},
{'name': 'LP', 'description': 'L-p Norm (LP)', 'value': 0.5203495166028743},
{'name': 'TVD',
'description': 'Total Variation Distance (TVD)',
'value': 0.36794267177703444}]},
}]
通过平衡数据来减少数据偏差
通过平衡评级星级和产品类别中评论数量的平衡来减少数据集中的不平衡,如下所示。图 5-20 展示了使用 Seaborn 对三个样本产品类别进行的结果可视化:
df_grouped_by = df.groupby(
["product_category", "star_rating"]
)[["product_category", "star_rating"]]
df_balanced = df_grouped_by.apply(
lambda x: x.sample(df_grouped_by.size().min())\
.reset_index(drop=True)
)
import seaborn as sns
sns.countplot(data=df_balanced,
x="star_rating",
hue="product_category")

图 5-20. 现在评级星级和产品类别中的评论数量已经平衡。
我们可以在平衡数据集上重新运行 SageMaker Clarify 的偏差分析。以下是“Gift Card”类别面值的样本结果:
[{'value_or_threshold': 'Gift Card',
'metrics': [{'name': 'CDDL',
'description': 'Conditional Demographic Disparity in Labels (CDDL)',
'value': 0.0},
{'name': 'CI',
'description': 'Class Imbalance (CI)',
'value': 0.3333333333333333},
{'name': 'DPL',
'description': 'Difference in Positive Proportions in Labels (DPL)',
'value': 0.0},
{'name': 'JS',
'description': 'Jensen-Shannon Divergence (JS)',
'value': 0.0},
{'name': 'KL',
'description': 'Kullback-Leibler Divergence (KL)',
'value': 0.0},
{'name': 'KS',
'description': 'Kolmogorov-Smirnov Distance (KS)',
'value': 0.0},
{'name': 'LP', 'description': 'L-p Norm (LP)', 'value': 0.0},
{'name': 'TVD',
'description': 'Total Variation Distance (TVD)',
'value': 0.0}]}]
我们可以看到除一个偏差度量值外,所有偏差度量值均为 0,这表明三个产品类别之间的分布是均等的。类不平衡度量值为 0.33 表示均衡,因为我们总共有三个产品类别。其他两个产品类别,Digital_Software 和 Digital_Video_Games,其类不平衡度量值也为 0.33,如下所示:
[{'value_or_threshold': 'Digital_Software',
'metrics': [
...
{'name': 'CI',
'description': 'Class Imbalance (CI)',
'value': 0.3333333333333333},
...
]}
]
[{'value_or_threshold': 'Digital_Video_Games',
'metrics': [
...
{'name': 'CI',
'description': 'Class Imbalance (CI)',
'value': 0.3333333333333333},
...
]}
]
类不平衡度量值为 0.33 表示数据集均衡,因为我们正在分析三个产品类别。如果我们分析四个产品类别,每个产品类别的理想类不平衡度量值将为 0.25。
使用 SageMaker Clarify 检测不同类型的漂移
数据分布中的统计变化通常称为统计术语中的“变化”,或者应用数据科学术语中的“漂移”。漂移的多种类型包括“协变量漂移”、“标签漂移”和“概念漂移”。协变量漂移发生在模型输入(自变量)的数据分布中。标签漂移发生在模型输出(因变量)的数据分布中。概念漂移发生在标签的实际定义因特定特征(如地理位置或年龄组)而改变时。
注意
本书中,我们通常将“漂移”和“变化”这两个术语互换使用,以表示统计分布的变化。有关分布漂移/变化类型的更多信息,请参见d2l.ai。
让我们通过分析美国不同地区对“软饮料”有不同名称来分析概念漂移。美国东部地区称软饮料为“苏打水”,北中部地区称其为“汽水”,南部地区则称其为“可乐”。相对于地理位置的标签变化(概念漂移)在图 5-21 中有所说明。

图 5-21. 美国软饮料名称的概念漂移。来源:http://popvssoda.com.
另一个概念漂移的例子涉及我们早期的书评者,他在描述第九章时使用“牛肉”一词。虽然这个术语最初被本书的美国和德国作者解释为负面的,但我们意识到在书评者的地理位置和年龄组中,“牛肉”一词意味着积极的含义。如果我们要建立一个用于分类我们书评的模型,我们可能需要考虑到评审人员的地理位置和年龄,或者也许为不同的地点和年龄建立单独的模型。
要检测协变量漂移和标签漂移,我们在模型训练期间计算基线统计信息。然后,我们可以使用之前讨论过的各种统计分布距离度量设置阈值,例如 KL、KS、LP、L-infinity norm 等。这些度量回答了关于偏差的各种问题。例如,散度度量 KL 回答了“不同产品类别的星级评分分布有多不同?”而 KS 度量则回答了“每个产品类别中哪些星级评分造成了最大的差异?”
如果计算得出的漂移大于给定的阈值,SageMaker 可以警告我们并自动触发重新训练操作。例如,为了检测预测标签相对于特定特征的概念漂移,我们使用 SageMaker 模型监控捕获实时模型输入和输出,并将模型输入发送到线下人工标注工作流程以创建地面真实标签。我们使用 SageMaker Clarify 将捕获的模型输出与人类提供的地面真实标签进行比较。如果模型输出的分布相对于地面真实标签超出了给定阈值,SageMaker 可以通知我们并自动触发模型重新训练。我们展示了如何使用 SageMaker 模型监控和 Clarify 来监控第九章中的实时预测。
在第九章中,我们展示了 SageMaker 模型监控如何对实时模型输入和输出进行采样,计算模型特征重要性和可解释性统计,并将这些统计数据与从我们训练的模型创建的基准进行比较。如果 SageMaker 模型监控检测到特征重要性和模型可解释性相对于基准的变化,它可以自动触发模型重新训练,并通知适当的科学家或工程师。
使用 AWS Glue DataBrew 分析我们的数据
我们还可以使用 Glue DataBrew 来分析我们的数据。虽然它与 SageMaker 的谱系和工件跟踪没有原生集成,但 DataBrew 提供了一个流畅、互动的视觉界面,可以在不编写任何代码的情况下进行数据摄取和分析。我们可以连接来自数据湖、数据仓库和数据库的数据源。让我们将亚马逊客户评论数据集(Parquet 格式)加载到 DataBrew 中,并分析一些可视化效果:
db.create_dataset(
Name='amazon-reviews-parquet',
Input={
'S3InputDefinition': {
'Bucket': 'amazon-reviews-pds',
'Key': 'parquet/'
}
}
)
一旦在 DataBrew 中创建了数据集,我们开始看到相关性和其他摘要统计信息,如图 5-22 所示。具体来说,我们可以看到helpful_votes和total_votes之间存在强相关性,而star_rating与helpful_votes或total_votes均不相关。
除了相关性之外,DataBrew 还突出显示了缺失单元格、重复行和类别不平衡情况,如图 5-23 所示。

图 5-22. Glue DataBrew 展示了数据集列之间的相关性。

图 5-23. Glue DataBrew 突出显示了星级评分类别 1-5 之间的类别不平衡情况。
我们可以使用 Glue DataBrew 进行大量数据分析和转换用例,但是对于基于机器学习的工作负载,我们应该使用 SageMaker Data Wrangler 以便在整个流水线中更好地跟踪我们的数据和模型谱系。
降低成本,提高性能
在本节中,我们希望提供一些技巧,帮助在数据探索过程中降低成本并提高性能。我们可以通过使用近似计数来优化大数据集中昂贵的 SQL COUNT 查询。利用 Redshift AQUA,我们可以减少网络 I/O 并提升查询性能。如果我们觉得 QuickSight 仪表板的性能可以得到提升,可以考虑启用 QuickSight SPICE。
使用共享的 S3 存储桶存储非敏感 Athena 查询结果
通过为我们团队选择一个共享的 S3 位置来存储 Athena 查询结果,我们可以重复使用缓存的查询结果,提升查询性能,并节省数据传输成本。以下代码示例突出了 s3_staging_dir,这个目录可以在不同的团队成员之间共享,以改善常见查询的性能:
from pyathena import connect
import pandas as pd
# Set the Athena query results S3 bucket
s3_staging_dir = 's3://{0}/athena/staging'.format(bucket)
conn = connect(region_name=region, s3_staging_dir=s3_staging_dir)
sql_statement="""
SELECT DISTINCT product_category from {0}.{1}
ORDER BY product_category
""".format(database_name, table_name)
pd.read_sql(sql_statement, conn)
使用 HyperLogLog 进行近似计数
在分析中,计数是非常重要的。我们经常需要计算用户数(日活跃用户)、订单数、退货数、支持电话等。在不断增长的数据集中保持超快速的计数能力可能是与竞争对手的关键优势。
Amazon Redshift 和 Athena 都支持 HyperLogLog(HLL),这是一种“基数估计”或 COUNT DISTINCT 算法,旨在在很短的时间内提供高度精确的计数(< 2% 的误差),并且只需很小的存储空间(1.2 KB)来存储 1.5 亿个以上的计数。HLL 是一种概率数据结构,在计数类似点赞数、页面访问数、点击量等场景中非常常见。
注意
其他形式的 HLL 包括 HyperLogLog++、Streaming HyperLogLog 和 HLL-TailCut+ 等。Count-Min Sketch 和 Bloom Filters 是用于近似计数和集合成员关系的类似算法。局部敏感哈希(LSH)是另一种在大数据集上计算“模糊”相似度度量的流行算法,且占用空间很小。
这种工作方式是,当在数据库中插入新数据时,Amazon Redshift 和 Athena 会更新一个小的 HLL 数据结构(类似于一个小型哈希表)。当用户发送计数查询时,Amazon Redshift 和 Athena 只需在 HLL 数据结构中查找该值,然后快速返回结果,而无需物理扫描整个集群中的所有磁盘进行计数。
让我们比较在 Amazon Redshift 中 SELECT APPROXIMATE COUNT() 和 SELECT COUNT() 的执行时间。以下是 SELECT APPROXIMATE COUNT() 的执行示例:
%%time
df = pd.read_sql_query("""
SELECT APPROXIMATE COUNT(DISTINCT customer_id)
FROM {}.{}
GROUP BY product_category
""".format(redshift_schema, redshift_table_2015), engine)
对于这个查询,我们应该看到类似于以下输出:
CPU times: user 3.66 ms, sys: 3.88 ms, total: 7.55 ms
Wall time: 18.3 s
接下来是,SELECT COUNT():
%%time
df = pd.read_sql_query("""
SELECT COUNT(DISTINCT customer_id)
FROM {}.{}
GROUP BY product_category
""".format(redshift_schema, redshift_table_2015), engine)
对于这个查询,我们应该看到类似于以下输出:
CPU times: user 2.24 ms, sys: 973 μs, total: 3.21 ms
Wall time: 47.9 s
注意
请注意,我们首先运行 APPROXIMATE COUNT 来排除查询缓存带来的性能提升。COUNT 的速度要慢得多。如果我们重新运行,由于查询缓存的存在,这两个查询都将变得非常快。
我们看到,在这种情况下,APPROXIMATE COUNT DISTINCT比常规的COUNT DISTINCT快 160%。结果大约相差 1.2%,满足 HLL 保证的小于 2%的误差。
请记住,HLL 是一种近似方法,可能不适用于需要精确数字(例如财务报告)的用例。
使用 AQUA 动态扩展 Amazon Redshift 的数据仓库
现有的数据仓库在查询执行期间将数据从存储节点移动到计算节点。这需要节点间的高网络 I/O,降低了整体查询性能。AQUA(高级查询加速器)是我们 Amazon Redshift 数据仓库顶部的硬件加速、分布式缓存。AQUA 使用定制的 AWS 设计芯片直接在缓存中执行计算。这减少了从存储节点到计算节点移动数据的需求,因此减少了网络 I/O 并增加了查询性能。这些 AWS 设计的芯片采用可编程门阵列实现,有助于加快数据加密和压缩,确保我们数据的最大安全性。AQUA 还可以动态扩展更多的容量。
使用 QuickSight SPICE 改进仪表板性能
QuickSight 是使用“超快、并行、内存计算引擎”SPICE 构建的。SPICE 使用列存储、内存存储和机器代码生成的组合来在大型数据集上运行低延迟查询。QuickSight 在底层数据源(包括 Amazon S3 和 Redshift)数据更改时更新其缓存。
摘要
在本章中,我们使用 AWS 分析堆栈中的工具(包括 Athena 和 Amazon Redshift)回答了关于我们数据的各种问题。我们使用 QuickSight 创建了业务智能仪表板,并使用开源的 AWS Deequ 和 Apache Spark 部署了 SageMaker Processing Job,以持续监控数据质量并在新数据到达时检测异常。这种持续的数据质量监控增强了我们数据管道的信心,并允许包括数据科学家和 AI/ML 工程师在内的下游团队开发高度精确和相关的模型,供我们的应用程序消费。我们还使用 Glue DataBrew 和 SageMaker Data Wrangler 分析我们的数据,寻找相关性、异常、不平衡和偏见。
在第六章中,我们将从数据集中选择并准备特征,以在第七章和第八章中用于模型训练和优化阶段。
第六章:为模型训练准备数据集
在上一章中,我们使用 SageMaker Studio 和各种基于 Python 的可视化库探索了我们的数据集。我们使用亚马逊客户评论数据集获得了一些关键的业务见解。此外,我们使用 SageMaker 处理作业、Apache Spark 和 AWS Deequ 开源库对数据集执行了汇总统计和质量检查。
在本章中,我们讨论如何将人类可读文本转换为机器可读向量,这个过程称为“特征工程”。具体来说,我们将从亚马逊客户评论数据集的原始review_body列转换为 BERT 向量。我们将使用这些 BERT 向量来训练和优化评审分类器模型,在第七章和第八章分别进行。我们还将深入探讨自然语言处理和 BERT 在第七章的起源。
我们将使用评审分类器模型来预测社交渠道、合作伙伴网站等的产品评论的star_rating。通过预测在实际环境中的评论star_rating,产品管理和客户服务团队可以使用这些预测来在问题公开发生之前解决质量问题,而不是等待直接的入站电子邮件或电话。这将把检测质量问题的平均时间从几天/几月缩短到分钟/小时。
执行特征选择和工程
AI 和机器学习算法是数值优化方法,操作的是数字和向量,而不是原始文本和图像。这些向量通常被称为“嵌入”,它们被投射到高维向量空间中。算法在这个高维向量空间中执行优化。
One-hot 编码是在表格数据集中用于分类数据的一种嵌入形式。使用 One-hot 编码,我们将每个分类值表示为一个唯一的 0 和 1 组成的向量。向量的维度数量——每个向量的大小——等于唯一分类值的数量。
AI 和机器学习中最重要的一个方面是特征工程,通常需要比典型机器学习流水线中的任何其他阶段更多的时间。特征工程有助于减少数据的维度,防止某些特征在统计上主导算法,加快模型训练时间,减少数值不稳定性,并提高整体模型预测准确性。
通过许多特征工程迭代和可视化,我们将开始真正理解我们的数据集,包括异常值、相关性和主成分。在模型的背景下分析特征,我们还会对哪些特征比其他特征更重要有直觉。某些特征将改善模型性能,而其他特征则显示没有改进或降低模型性能。
粗心的特征工程可能会导致灾难性的结果。最糟糕的情况下,糟糕的特征工程可能会导致社会上具有种族、性别和年龄偏见的模型传播。最好的情况下,糟糕的特征工程会产生次优的模型,导致推荐电影不准确,过度估计收入预测或者创建过多的库存。
虽然领域专家确实可以帮助评估包括哪些特征及其工程化方式,但我们数据集中存在一些“潜在”特征,人类并不立即能够识别。Netflix 的推荐系统以发现超出传统戏剧、恐怖和浪漫喜剧等通常流派的新电影流派而闻名。例如,他们发现了非常具体的流派,如“血腥的加拿大复仇电影”、“11-12 岁观众的关于马的感情电影”、“根据经典文学改编的浪漫犯罪电影”和“淫秽的疯狂科学家喜剧”。
注意
许多这些“秘密”流派是通过 Netflix 的观看历史服务——也称为“VHS”,这是对上世纪八九十年代流行的录像带格式的一种回顾——发现的。
在高层次上,特征工程分为三种逻辑类型:选择、创建和转换。并非所有类型都适用于我们的用例,但应考虑并进行探索。
特征选择确定最能代表我们数据集的数据属性。此外,特征选择使用统计方法过滤掉不相关和冗余的属性。例如,如果两个数据点高度相关,比如total_votes和helpful_votes,那么也许只需要一个来训练我们的模型。只选择其中一个属性有助于降低特征维度并更快地训练模型,同时保持模型准确性。
特征创建将现有的数据点结合成新特征,有助于提高我们模型的预测能力。例如,将review_headline和review_body组合成一个单一特征可能比单独使用它们更能提高预测准确性。
特征转换将数据从一种表示转换为另一种,以便于机器学习。将连续值如时间戳转换为小时、天或月的分类“桶”,有助于减少维度。虽然在分桶转换过程中我们会失去一些信息和细节,但我们的模型实际上可能会从更广泛的泛化中受益。两种常见的统计特征转换是归一化和标准化。归一化将特定数据点的所有值缩放到 0 到 1 之间,而标准化则将值转换为均值为 0、标准差为 1。与归一化相比,标准化通常更受欢迎,因为它比归一化更好地处理异常值,并允许我们比较不同单位和尺度的特征。这些技术有助于减少大数值数据点(如以千计表示的评论数)和小数值数据点(如以十计表示的helpful_votes)之间的影响差异。如果没有这些技术,模型可能会在数值大小上偏向于评论数而忽略helpful_votes。
让我们从特征选择到特征转换走一遍典型的特征工程流水线,如图 6-1 所示。

图 6-1. 典型特征工程流水线中的步骤。
基于特征重要性选择训练特征
我们可以使用 SageMaker Data Wrangler 的“快速模型”分析来评估我们的数据中哪些列在预测给定标签(在我们的情况下是star_rating)时最有用。我们只需选择要让 Data Wrangler 分析的数据,以及我们想要预测的star_rating标签。Data Wrangler 自动预处理数据,训练“快速模型”,评估模型,并计算每个特征的特征重要性分数。图 6-2 展示了使用 Data Wrangler 的快速模型分析功能对我们的亚马逊客户评论数据集进行特征重要性分析的结果。

图 6-2. 数据整理师快速模型分析允许我们分析特征重要性。
在快速模型分析之后,我们数据集中最重要的特征是review_body,其次是review_headline、product_title和product_category。
因为我们计划使用我们的模型来分类来自社交渠道和合作伙伴网站的产品评论,“在野外”,这些评论只有原始的评论文本,所以我们决定只使用review_body列来预测star_rating。在我们的情况下,star_rating是“标签”,而review_body的转换版本是“特征”。star_rating标签是我们训练数据集中的实际star_rating值,从中我们训练出的模型将学习在第七章中预测的值。从原始文本转换为一系列 BERT 向量的review_body特征,是我们模型训练过程的输入。在本章的后面部分,我们将演示如何将原始文本转换为 BERT 向量。
我们同时使用特征和标签来训练我们的模型,以从社交渠道和合作伙伴网站的review_body文本中预测star_rating标签。接下来,我们将star_rating和review_body列视为 pandas DataFrame:
df = pd.read_csv('./data/amazon_reviews_us_Digital_Software_v1_00.tsv.gz',
delimiter='\t',
quoting=csv.QUOTE_NONE,
compression='gzip')
df.head(5)
| star_rating | review_body |
|---|---|
| 1 | 剥夺用户能力的差劲商业决策... |
| 5 | Avast 是一个易于使用和下载的产品。我觉得...... |
| 2 | 从一开始就有问题。已经过去 30 天,... |
| 4 | 运行良好。 |
| 3 | 使用起来困难 |
由于star_rating标签是离散的分类特征(1、2、3、4 或 5),我们将使用“分类”算法。我们不将其视为回归问题,因为我们使用的是仅具有五个可能值(1、2、3、4 或 5)的star_rating作为分类特征。如果star_rating包含连续值,比如 3.14、4.20 或 1.69,则我们可能会使用star_rating作为具有回归模型的连续特征。
我们将不再使用传统的机器学习分类算法,而是使用基于 Keras API 和 TensorFlow 2.x 的基于神经网络的分类模型。我们将在下一章节深入研究模型训练。让我们继续并准备我们的亚马逊客户评论数据集,以训练一个能够从review_body文本中预测star_rating(1–5)的模型。
平衡数据集以提高模型准确性
在上一章节中,我们展示了数据集中所有评论的star_rating分布情况,发现大约 62%的评论的star_rating为 5,如图 6-3 所示。

图 6-3. 我们的数据集包含了星级评价不平衡的评论数量。
如果我们在这个不平衡的数据集上进行天真的训练,我们的分类器可能会简单地学会预测star_rating为 5,因为在 5 个类别(1、2、3、4 或 5)上,62% 的准确率比随机准确率(20%)要好。换句话说,在现实中,一个不平衡的训练数据集可能会创建一个模型,该模型只学会在每次预测时预测 5。这种模型在生产环境中表现不佳。
注意
一些算法如 XGBoost 支持缩放因子以抵消不平衡类的问题。然而,一般来说,在特征工程过程中处理类别不平衡是个好主意,以避免后续误用这些特征。
平衡数据集并防止偏向特定类别有两种常见方法:对多数类进行欠采样(star_rating 5)和对少数类进行过采样(star_rating 2 和 3)。在选择采样策略时,我们应仔细考虑采样如何影响特征数据分布的整体均值和标准差。我们在图 6-4 中看到欠采样的示例和图 6-5 中看到过采样的示例。

图 6-4. 将多数类欠采样至少数类。

图 6-5. 将少数类过采样至多数类。
思路是沿着特定标签或常称的“类”均匀分布数据。在我们的情况下,类别是我们的分类 star_rating 字段。因此,我们希望我们的训练数据集包含每个 star_rating:1、2、3、4 和 5 的一致数量的评论。以下是使用 star_rating 对原始数据集进行欠采样的代码:
df_grouped_by = df.groupby(["star_rating"])
df_balanced = df_grouped_by.apply(
lambda x: x.sample(df_grouped_by.size().min())\
.reset_index(drop=True)
)
现在我们有一个平衡的数据集,如图 6-6 所示。

图 6-6. star_rating 类的平衡数据集。
欠采样的一个缺点是,训练数据集的大小被采样到最小类别的大小。这可能通过减少来自欠采样类的信号,降低了训练模型的预测能力和鲁棒性。在本例中,我们将评论数量从大约 100,000 减少到 35,000,减少了 65%。
过采样将为代表性不足的类别人工创建新数据。在我们的情况下,star_rating 2 和 3 代表性不足。一种常见的技术称为合成少数类过采样技术,它使用统计方法从现有数据中合成生成新数据。当我们拥有更大的数据集时,它们通常能发挥更好的效果,因此在使用少数样本的小数据集时要小心使用过采样。
将数据集分割为训练、验证和测试集
模型开发通常遵循三个阶段:模型训练,模型验证和模型测试(参见图 6-7)。

图 6-7. 典型模型开发生命周期的阶段。
为了与这三个阶段保持一致,我们将平衡的数据分割成单独的训练、验证和测试数据集。训练数据集用于模型训练。验证数据集用于验证称为“超参数”的模型训练配置。而测试数据集用于测试选择的超参数。对于我们的模型,我们选择了 90%的训练数据、5%的验证数据和 5%的测试数据,因为这种分布,在图表 6-8 中显示的工作效果很好,适合我们的数据集和模型。

图表 6-8. 模型开发生命周期典型阶段的数据集拆分。
让我们使用 scikit-learn 的train_test_split函数来分割数据,其中stratify参数设置为star_rating字段,以保持我们先前的平衡努力。如果不指定stratify参数,拆分函数可以自由选择给定数据集中的任何数据,导致拆分变得不平衡。
from sklearn.model_selection import train_test_split
# Split all data into 90% train and 10% holdout
df_train, df_holdout = train_test_split(
df_balanced,
test_size=0.10,
stratify=df_balanced['star_rating'])
# Split holdout data into 50% validation and 50% test
df_validation, df_test = train_test_split(
df_holdout,
test_size=0.50,
stratify=df_holdout[ 'star_rating'])
在这种情况下,我们不使用 k 折交叉验证——一种经典的机器学习技术,该技术在不同的拆分中重复使用数据的每一行,包括训练、验证和测试。K 折交叉验证传统上适用于较小的数据集,在我们的情况下,由于数据量很大,我们可以避免 k 折的缺点:在训练、验证和测试阶段之间的数据“泄漏”。数据泄漏可能会导致我们训练模型的人工膨胀的模型准确性。这些模型在实验室外的真实世界数据上表现不佳。总之,训练、验证和测试的每个阶段都应使用单独和独立的数据集,否则可能会发生泄漏。
在相关说明中,时间序列数据通常容易在拆分之间泄漏。公司通常希望在将模型推向生产之前,使用“回溯”历史信息验证新模型。在处理时间序列数据时,请确保模型不会意外地窥视未来。否则,这些模型可能看起来比它们实际上更准确。
注意
几乎在电影《回到未来》中,窥探未来几乎导致了灾难。同样地,窥探未来可能会给我们的建模工作带来麻烦。
此外,我们可能希望将同一客户的所有数据保留在同一拆分中。否则,个人客户数据将分散在多个拆分中,可能会引发问题。在这种情况下,我们将在创建拆分之前按customer_id对数据进行分组。我们的模型不要求我们按customer_id分组数据,所以我们将跳过这一步。
在使用 SageMaker 处理大规模数据时,我们可以将数据跨多个实例分布在一个集群中。这被称为分片,我们稍后将演示如何在 SageMaker 集群中使用 scikit-learn、Apache Spark 和 TensorFlow 转换数据时进行多实例处理。
将原始文本转换为 BERT 嵌入向量。
我们将使用 TensorFlow 和一种称为BERT的最先进的自然语言处理(NLP)和自然语言理解神经网络架构。稍后我们将深入探讨BERT。从高层次来看——与以往的 NLP 模型(如Word2Vec)不同,BERT 捕捉了每个句子中每个单词的双向(从左到右和从右到左)上下文。这使得 BERT 能够学习同一个单词在不同句子中的不同含义。例如,单词bank在以下两个句子中的含义不同:“一个小偷从银行金库偷走了钱”和“后来,他在河边钓鱼时被逮捕。”
对于每个review_body,我们使用 BERT 在先前学习的、30,000 个词或“令牌”的高维向量空间内创建特征向量。BERT 通过在包括维基百科和谷歌图书在内的数百万文档上进行训练来学习这些令牌。
图 6-9 展示了 BERT 如何将原始输入文本转换为最终的 BERT 嵌入,然后通过实际的模型架构进行传递。

图 6-9. BERT 将原始输入文本转换为嵌入。
BERT 首先将原始输入文本应用 WordPiece 令牌化。WordPiece 是一种在 NLP 任务中将单词分割为子词级别的技术,其词汇维度约为 30,000 个令牌。请注意,BERT 还在输入序列的开头添加特殊令牌,如[CLS]以标记分类任务。
接下来,BERT 通过查找任何输入令牌的 768 维向量表示来创建令牌嵌入。input_id 是指向相关令牌嵌入向量的实际 ID。input_mask 指定了 BERT 应关注的令牌(0 或 1)。如果我们将多个句子传递给 BERT,片段嵌入将把每个令牌映射到相应的输入句子(0 表示第一个句子,1 表示第二个句子)。然后,位置嵌入跟踪输入序列中每个令牌的位置(0、1、2 等)。我们将了解到,对于 BERT 来说,一个非常重要的超参数是max_seq_length,它定义了我们可以在每个样本中传递给 BERT 的最大输入令牌数。由于该参数的最大值为 512,位置嵌入是一个维度为 (512, 768) 的查找表。
在最后一步,BERT 创建了令牌嵌入、片段嵌入和位置嵌入的逐元素求和。得到的维度为 (n, 768) 的嵌入,其中 n 表示输入令牌的数量,将作为 BERT 的输入嵌入。
让我们使用 BERT 的一个变种称为DistilBERT。DistilBERT 是 BERT 的轻量级版本,速度快 60%,体积小 40%,同时保留了 97%的 BERT 语言理解能力。我们使用名为 Transformers 的流行 Hugging Face Python 库来执行转换。要安装此库,只需输入pip install transformers:
from transformers import DistilBertTokenizer
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
tokens = tokenizer.tokenize("""I needed an antivirus application and know
the quality of Norton products. This was a no brainer for me and I am
glad it was so simple to get.""")
分词器执行小写处理,并将文本解析成预训练的 DistilBERT 词汇表中包含的一组单词。Transformers 库使用另一个流行的名为 WordPieces 的库来将文本解析为单词 token:
print(tokens)
['i', 'needed', 'an', 'anti', '##virus', 'application', 'and', 'know', 'the',
'quality', 'of', 'norton', 'products', '.', 'this', 'was', 'a', 'no',
'brain', '##er', 'for', 'me', 'and', 'i', 'am', 'glad', 'it', 'was', 'so',
'simple', 'to', 'get', '.']
大多数 BERT 变体,包括 DistilBERT,都有一个“最大序列长度”的概念,它定义了用于表示每个文本输入的最大 token 数量。在我们的情况下,任何经过 tokenization 后具有max_seq_length token 的评论将被截断为 64 个 token。少于 64 个 token 的评论将被填充到长度为 64 个 token。经验上,我们选择 64 作为最大序列长度,因为我们的评论中有 80%少于 64 个单词,正如我们在第五章中看到的,虽然不精确,但单词数是 token 数的一个很好的指示。以下是在第五章中呈现的每个评论的单词数量分布:
10% 2.000000
20% 7.000000
30% 19.000000
40% 22.000000
50% 26.000000
60% 32.000000
70% 43.000000
80% 63.000000 <===
90% 110.000000
100% 5347.000000
我们在特征工程和模型训练期间必须使用相同的最大序列长度。因此,如果我们想尝试不同的值,我们需要使用更新后的值重新生成 BERT 嵌入。如果我们不确定选择哪个值,可以尝试使用 128、256 和 512 作为最大序列长度生成多个版本的嵌入。这些对大多数 BERT 任务效果良好。更大的值可能会增加模型训练时间,因为维度更高。
但是,我们仍然需要进行更多处理,因为我们的 DistilBERT 模型使用了从前面基于文本的 token 导出的长度为 64 的数字数组:
input_ids
BERT 词汇表中 token 的数字 ID
input_mask
指定 BERT 应该关注哪些 token(0 或 1)
segment_ids
在我们的情况下始终为 0,因为我们正在执行单序列 NLP 任务(如果我们执行双序列 NLP 任务,如下一句预测,则为 1)
幸运的是,Transformers 分词器为我们创建了三个数组中的两个——并且根据最大序列长度进行了必要的填充和截断!
MAX_SEQ_LENGTH = 64
encode_plus_tokens = tokenizer.encode_plus(
text_input.text,
pad_to_max_length=True,
max_length=MAX_SEQ_LENGTH)
# Convert tokens to ids from the pre-trained BERT vocabulary
input_ids = encode_plus_tokens['input_ids']
print(input_ids)
输出:
[101, 1045, 2734, 2019, 3424, 23350, 4646, 1998, 2113, 1996, 3737, 1997, 10770,
3688, 1012, 2023, 2001, 1037, 2053, 4167, 2121, 2005, 2033, 1998, 1045, 2572,
5580, 2009, 2001, 2061, 3722, 2000, 2131, 1012, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0]
# Specifies which tokens BERT should pay attention to (0 or 1)
input_mask = encode_plus_tokens['attention_mask']
print(input_mask)
输出:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
第三个数组,segment_ids,在我们的情况下很容易生成,因为它包含了所有的 0,因为我们正在执行单序列分类 NLP 任务。对于诸如问答的双序列 NLP 任务,sequence_id要么是 0(问题),要么是 1(答案):
segment_ids = [0] * MAX_SEQ_LENGTH
print(segment_ids)
输出:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
将特征和标签转换为优化的 TensorFlow 文件格式
我们特征工程旅程的最后一步是将我们新设计的特征存储在TFRecord文件格式(.tfrecord文件扩展名)中。TFRecord是一种二进制、轻量级文件格式,针对 TensorFlow 数据处理进行了优化,基于协议缓冲区(“protobufs”)。TFRecord是跨平台和跨语言的,对于数据处理工作负载非常高效。它们被编码和优化用于在模型训练期间使用的顺序、基于行的访问。此编码包含特征和标签,以及每个示例的任何相应元数据。
注意
在机器学习上下文中,“示例”一词表示用于模型训练(包括标签)或预测(预测标签)的数据行。
虽然TFRecord是文件格式,tf.train.Example和tf.train.Feature是存储在TFRecord中最常见的数据结构。tf.train.Feature使用tf.train.BytesList、FloatList和Int64List分别存储byte、float或int64的列表。
这是使用 TensorFlow API 将我们的特征转换为TFRecord的代码:
import tensorflow as tf
import collections
tfrecord_writer = tf.io.TFRecordWriter(output_file)
tfrecord_features = collections.OrderedDict()
tfrecord_features['input_ids'] =
tf.train.Feature(int64_list=tf.train.Int64List(
value=input_ids))
tfrecord_features['input_mask'] =
tf.train.Feature(int64_list=tf.train.Int64List(
value=input_mask))
tfrecord_features['segment_ids'] =
tf.train.Feature(int64_list=tf.train.Int64List(
value=segment_ids))
# Target label (star_rating)
tfrecord_features['label_ids'] =
tf.train.Feature(int64_list=tf.train.Int64List(
value=[label_id]))
tfrecord = tf.train.Example(
features=tf.train.Features(feature=tfrecord_features))
tfrecord_writer.write(tfrecord.SerializeToString())
tfrecord_writer.close()
tf.train.Example.SerializeToString()生成一个序列化的、二进制的、不可读的字符串,如下所示:
[b'\n\xfe\x03\n\x96\x01\n\x0bsegment_ids\x12\x86\x01\x1a\x83\x01\n\x80\x01\n\
xb6\x01\n\tinput_ids\x12\xa8\x01\x1a\xa5\x01\n\xa2\x01e\x95\x08\x8d\x10\x8a\
x1d\xd0\x0f\xd3\x10\xf4\x07f\n\x95\x01\n\ninput_mask\x12\x01\x01\x01\x01\x01\
x01\x01\n\tlabel_ids\x12\x05\x1a\x03\n\x01\x04']
使用 SageMaker 处理作业扩展特征工程
到目前为止,我们一直在 SageMaker 笔记本上处理数据集的一个样本。现在让我们把我们的自定义 Python 代码移到 SageMaker 处理作业中,并将我们的特征工程扩展到数据集中的所有 1.5 亿条评论。SageMaker 处理作业将我们的自定义脚本(即“脚本模式”)或 Docker 镜像(即“自定义容器”)并行化在许多 SageMaker 实例上,如图 6-10 所示。

图 6-10. SageMaker 处理作业可以在集群中的多个 SageMaker 实例上并行化代码和 Docker 镜像。
在后面的章节中,我们将使用端到端的流水线自动化这一步骤。现在,让我们把注意力集中在使用处理作业将我们的特征工程扩展到 SageMaker 集群上。
使用 scikit-learn 和 TensorFlow 进行转换
让我们使用 TensorFlow、scikit-learn、BERT 和 SageMaker 处理作业在集群中平衡、拆分和转换整个数据集,如图 6-11 所示。

图 6-11. 使用 scikit-learn 和 SageMaker 处理作业将原始文本转换为 BERT 嵌入。
首先,我们配置了 scikit-learn 处理作业的版本、实例类型和集群中的实例数量。由于变换是无状态的,我们使用的实例越多,处理速度就越快。请注意,我们仅使用 scikit-learn 来平衡和拆分数据。重要的工作是使用 TensorFlow 和 Transformers 完成的:
from sagemaker.sklearn.processing import SKLearnProcessor
from sagemaker.processing import ProcessingInput, ProcessingOutput
processor = SKLearnProcessor(framework_version='<SCIKIT_LEARN_VERSION>',
role=role,
instance_type='ml.c5.4xlarge',
instance_count=2)
提示
在 SageMaker 处理作业中,我们可以指定instance_type='local'来在笔记本内或本地笔记本上运行脚本。这使我们可以在笔记本上的小数据子集上“本地”运行处理作业,然后再启动全面的 SageMaker 处理作业。
接下来,我们通过指定转换特征的位置并将数据分片到我们处理作业集群中的两个实例,以减少转换数据所需的时间来启动 SageMaker 处理作业。我们指定输入数据集的 S3 位置和各种参数,如训练、验证和测试数据集的分割百分比。我们还提供了我们为 BERT 选择的max_seq_length:
processor.run(code='preprocess-scikit-text-to-bert.py',
inputs=[
ProcessingInput(input_name='raw-input-data',
source=raw_input_data_s3_uri,
destination='/opt/ml/processing/input/data/',
s3_data_distribution_type='ShardedByS3Key')
],
outputs=[
ProcessingOutput(output_name='bert-train',
s3_upload_mode='EndOfJob',
source='/opt/ml/processing/output/bert/train'),
ProcessingOutput(output_name='bert-validation',
s3_upload_mode='EndOfJob',
source='/opt/ml/processing/output/bert/validation'),
ProcessingOutput(output_name='bert-test',
s3_upload_mode='EndOfJob',
source='/opt/ml/processing/output/bert/test'),
],
arguments=['--train-split-percentage',
str(train_split_percentage),
'--validation-split-percentage',
str(validation_split_percentage),
'--test-split-percentage',
str(test_split_percentage),
'--max-seq-length', str(max_seq_length)],
logs=True,
wait=False)
作业完成后,我们按如下方式检索 S3 输出位置:
output_config = processing_job_description['ProcessingOutputConfig']
for output in output_config['Outputs']:
if output['OutputName'] == 'bert-train':
processed_train_data_s3_uri = output['S3Output']['S3Uri']
if output['OutputName'] == 'bert-validation':
processed_validation_data_s3_uri = output['S3Output']['S3Uri']
if output['OutputName'] == 'bert-test':
processed_test_data_s3_uri = output['S3Output']['S3Uri']
使用 Apache Spark 和 TensorFlow 进行转换
Apache Spark 是由 SageMaker 处理作业支持的强大数据处理和特征转换引擎。虽然 Apache Spark 本身不原生支持 BERT,但我们可以在 PySpark 应用程序中使用基于 Python 的 BERT Transformers 库来在分布式 Spark 集群上扩展我们的 BERT 转换。在这种情况下,我们将 Spark 仅用作分布式处理引擎,Transformers 则作为集群中安装的另一个 Python 库,如图 6-12 和 6-13 所示。

图 6-12. 安装了多个流行库的 Apache Spark 集群,包括 TensorFlow 和 BERT。
Apache Spark ML 库包括文本特征工程的高度并行、分布式实现的词频-逆文档频率(TF-IDF)。TF-IDF 可以追溯到 1980 年代,需要一个有状态的预训练步骤来计算术语频率并在给定数据集上建立“词汇表”。这限制了 TF-IDF 在给定数据集之外学习更广泛的语言模型的能力。
另一方面,BERT 已在数百万文档上进行了预训练,并且通常在我们的自然语言数据集上比 TF-IDF 表现更好,因此我们将在这里使用 BERT 进行呈现的特征工程任务。

图 6-13. 使用 Apache Spark 将原始文本转换为 BERT 嵌入。
如果我们喜欢使用 Apache Spark,因为我们已经有一个基于 Spark 的特征工程管道,我们可以启动一个云原生、无服务器、按使用付费的 Apache Spark 集群来使用 SageMaker 处理作业从原始review_body数据创建 BERT 向量。
我们只需提供我们的 PySpark 脚本,指定实例类型,并决定集群实例计数 - SageMaker 将在集群上运行我们的 Spark 作业。由于 Spark 在更多内存的情况下表现更佳,我们使用高内存r5实例类型:
from sagemaker.spark.processing import PySparkProcessor
processor = PySparkProcessor(base_job_name='spark-amazon-reviews-processor',
role=role,
framework_version='<SPARK_VERSION>',
instance_count=2,
instance_type='ml.r5.xlarge',
max_runtime_in_seconds=7200)
让我们运行处理作业。由于 Apache Spark 能够直接读写 S3,我们不需要为我们的run()函数指定通常的ProcessingInput和ProcessingOutput参数。相反,我们使用arguments参数传入四个 S3 位置:一个用于原始 TSV 文件,另外三个用于训练、验证和测试集的生成 BERT 向量。我们还传递了 BERT 的分割百分比和max_seq_length:
train_data_bert_output = 's3://{}/{}/output/bert-train'.format(bucket,
output_prefix)
validation_data_bert_output = 's3://{}/{}/output/bert-validation'.format(bucket,
output_prefix)
test_data_bert_output = 's3://{}/{}/output/bert-test'.format(bucket,
output_prefix)
processor.run(submit_app='preprocess-spark-text-to-bert.py',
arguments=['s3_input_data',
s3_input_data,
's3_output_train_data',
train_data_bert_output,
's3_output_validation_data',
validation_data_bert_output,
's3_output_test_data',
test_data_bert_output,
'train_split_percentage',
str(train_split_percentage),
'validation_split_percentage',
str(validation_split_percentage),
'test_split_percentage',
str(test_split_percentage),
'max_seq_length',
str(max_seq_length)
],
outputs=[
ProcessingOutput(s3_upload_mode='EndOfJob',
output_name='bert-train',
source='/opt/ml/processing/output/bert/train'),
ProcessingOutput(s3_upload_mode='EndOfJob',
output_name='bert-validation',
source='/opt/ml/processing/output/bert/validation'),
ProcessingOutput(s3_upload_mode='EndOfJob',
output_name='bert-test',
source='/opt/ml/processing/output/bert/test'),
],
logs=True,
wait=False
)
上述代码在笔记本中运行,并在运行 Apache Spark 的 SageMaker 处理作业集群上启动preprocess-spark-text-to-bert.py脚本。以下代码是此 PySpark 脚本的片段:
def transform(spark, s3_input_data, s3_output_train_data,
s3_output_validation_data, s3_output_test_data):
schema = StructType([
StructField('marketplace', StringType(), True),
StructField('customer_id', StringType(), True),
StructField('review_id', StringType(), True),
StructField('product_id', StringType(), True),
StructField('product_parent', StringType(), True),
StructField('product_title', StringType(), True),
StructField('product_category', StringType(), True),
StructField('star_rating', IntegerType(), True),
StructField('helpful_votes', IntegerType(), True),
StructField('total_votes', IntegerType(), True),
StructField('vine', StringType(), True),
StructField('verified_purchase', StringType(), True),
StructField('review_headline', StringType(), True),
StructField('review_body', StringType(), True),
StructField('review_date', StringType(), True)
])
df_csv = spark.read.csv(path=s3_input_data,
sep='\t',
schema=schema,
header=True,
quote=None)
这是 Spark 用户定义函数(UDF),用于使用 Transformers Python 库将原始文本转换为 BERT 嵌入:
MAX_SEQ_LENGTH = 64
DATA_COLUMN = 'review_body'
LABEL_COLUMN = 'star_rating'
LABEL_VALUES = [1, 2, 3, 4, 5]
label_map = {}
for (i, label) in enumerate(LABEL_VALUES):
label_map[label] = i
def convert_input(label, text):
encode_plus_tokens = tokenizer.encode_plus(
text,
pad_to_max_length=True,
max_length=MAX_SEQ_LENGTH)
# Convert the text-based tokens to ids from the pre-trained BERT vocabulary
input_ids = encode_plus_tokens['input_ids']
# Specifies which tokens BERT should pay attention to (0 or 1)
input_mask = encode_plus_tokens['attention_mask']
# Segment ids are always 0 for single-sequence tasks
# (or 1 if two-sequence tasks)
segment_ids = [0] * MAX_SEQ_LENGTH
# Label for our training data (star_rating 1 through 5)
label_id = label_map[label]
return {'input_ids': input_ids, 'input_mask': input_mask,
'segment_ids': segment_ids, 'label_ids': [label_id]}
这是调用集群中每个工作节点上的 UDF 的 Spark 代码。请注意,我们准备写入TFRecord,因此我们正在设置与所需TFRecord格式匹配的 PySpark 模式:
tfrecord_schema = StructType([
StructField("input_ids", ArrayType(IntegerType(), False)),
StructField("input_mask", ArrayType(IntegerType(), False)),
StructField("segment_ids", ArrayType(IntegerType(), False)),
StructField("label_ids", ArrayType(IntegerType(), False))
])
bert_transformer = udf(lambda text, label: convert_input(text, label), \
tfrecord_schema)
接下来,我们将数据分成训练、验证和测试集,并以TFRecord格式保存在 S3 中:
train_df, validation_df, test_df = features_df.randomSplit(
[
train_split_percentage,
validation_split_percentage,
test_split_percentage
]
)
train_df.write.format('tfrecord').option('recordType', 'Example')\
.save(path=s3_output_train_data)
validation_df.write.format('tfrecord').option('recordType', 'Example')\
.save(path=s3_output_validation_data)
test_df.write.format('tfrecord').option('recordType', 'Example')\
.save(path=s3_output_test_data)
注意
我们正在使用开源库中实现的 Apache Spark DataFrameReader和DataFrameWriter接口提供的format('tfrecord')。关于这个库的参考资料可以在本书的 GitHub 仓库中找到。
通过 SageMaker 特征存储共享特征
特征工程需要直觉、耐心、试验和错误。随着越来越多的团队利用 AI 和机器学习解决业务用例,需要一个集中、可发现和可重复使用的特征库。这种类型的库称为特征存储。
特征存储是用于机器学习特征的数据湖。由于特征有时需要进行大量计算处理,例如我们之前使用 SageMaker Processing Jobs 演示的 BERT 特征,我们希望在整个组织中存储和重复使用这些特征,如果可能的话。
针对面向 SageMaker 的机器学习工作流程和面向 Amazon Redshift 的业务智能报表和仪表板的特征存储,可能需要不同的转换。例如,我们会将 BERT 嵌入存储在特征存储中,而将清洁和增强数据存储在我们的数据仓库中,如图 6-14 所示。

图 6-14. 特征存储、数据湖和数据仓库之间的关系。
我们可以通过 Amazon SageMaker 利用托管的特征存储,而不是自己构建一个特征存储。SageMaker 特征存储可以存储离线和在线特征。离线特征存储在针对高吞吐量和批量检索工作负载进行优化的存储库中,例如模型训练。在线特征存储在针对低延迟和实时请求进行优化的存储库中,例如模型推断。
由于我们花了相当多的时间生成我们的 BERT 特征,我们希望与我们组织中的其他团队共享它们。也许这些团队可以发现我们从未探索过的特征的新组合和改进。我们希望使用我们的特征存储来帮助我们安全地“穿越时间”,避免泄漏。
特征存储可以将频繁访问的特征缓存在内存中,以减少模型训练时间。它还可以提供治理和访问控制,以规范和审核我们的特征。最后,特征存储可以通过确保批量训练和实时预测中存在相同的特征来确保模型训练和推断之间的一致性。
将特征注入到 SageMaker 特征存储中
假设我们有以下数据框df_records,其中包含使用 DistilBERT 处理的 BERT 特征,最大序列长度为 64:
| input_ids | input_mask | segment_ids | label_id | review_id | date | label | split_type |
|---|---|---|---|---|---|---|---|
| [101, 1045, 2734, 2019, 1000, 3424, 23350, 100... | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | 4 | ABCD12345 | 2021-01-30T20:55:33Z | 5 | train |
| [101, 1996, 3291, 2007, 10777, 23663, 2003, 20.. | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | 2 | EFGH12345 | 2021-01-30T20:55:33Z | 3 | train |
| [101, 6659, 1010, 3904, 1997, 2026, 9537, 2499... | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | 0 | IJKL2345 | 2021-01-30T20:55:33Z | 1 | train |
现在我们将 BERT 特征df_records注入到特征存储中,特征组名称为reviews_distilbert_64_max_seq_length:
from sagemaker.feature_store.feature_group import FeatureGroup
reviews_feature_group_name = "reviews_distilbert_max_seq_length_64"
reviews_feature_group = FeatureGroup(name=reviews_feature_group_name,
sagemaker_session=sagemaker_session)
我们需要在我们的情况下指定唯一的记录标识列review_id。此外,我们需要指定一个事件时间,该事件时间对应于在特征存储中创建或更新记录的时间。在我们的情况下,我们将在注入时生成一个时间戳。所有记录必须具有唯一 ID 和事件时间:
record_identifier_feature_name = "review_id"
event_time_feature_name = "date"
reviews_feature_group.load_feature_definitions(data_frame=df_records)
SageMaker 特征存储 Python SDK 将根据输入数据自动检测数据模式。以下是检测到的模式:
FeatureGroup(
feature_definitions=[
FeatureDefinition(feature_name='input_ids', \
feature_type=<FeatureTypeEnum.STRING: 'String'>),
FeatureDefinition(feature_name='input_mask', \
feature_type=<FeatureTypeEnum.STRING: 'String'>),
FeatureDefinition(feature_name='segment_ids', \
feature_type=<FeatureTypeEnum.STRING: 'String'>),
FeatureDefinition(feature_name='label_id', \
feature_type=<FeatureTypeEnum.INTEGRAL: 'Integral'>),
FeatureDefinition(feature_name='review_id', \
feature_type=<FeatureTypeEnum.STRING: 'String'>),
FeatureDefinition(feature_name='date', \
feature_type=<FeatureTypeEnum.STRING: 'String'>),
FeatureDefinition(feature_name='label', \
feature_type=<FeatureTypeEnum.INTEGRAL: 'Integral'>),
FeatureDefinition(feature_name=split_type, \
feature_type=<FeatureTypeEnum.STRING: 'String'>),
...
]
)
为了创建特征组,我们还需要指定用于存储df_records的 S3 存储桶以及一个标志,以启用推断的在线特征存储选项:
reviews_feature_group.create(
s3_uri="s3://{}/{}".format(bucket, prefix),
record_identifier_name=record_identifier_feature_name,
event_time_feature_name=event_time_feature_name,
role_arn=role,
enable_online_store=True)
现在让我们将数据注入到特征存储中。数据被注入到离线和在线存储库中,除非我们指定其中之一:
reviews_feature_group.ingest(
data_frame=df_records, max_workers=3, wait=True)
从 SageMaker 特征存储检索特征
我们可以使用 Athena 从离线特征存储中检索特征。我们可以在模型训练中使用这些特征,例如:
reviews_feature_store_query = reviews_feature_group.athena_query()
reviews_feature_store_table = reviews_feature_store_query.table_name
query_string = """
SELECT review_body, input_ids, input_mask, segment_ids, label_id FROM "{}"
""".format(reviews_feature_store_query)
reviews_feature_store_query.run(query_string=query_string, ...)
这是来自特征存储查询的输出,显示我们的 BERT 特征:
| review_body | input_ids | input_mask | segment_ids | label_id |
|---|---|---|---|---|
| 我需要一个“防病毒”应用程序并知道它有。 | 101, 1996, 3291, 2007, 10777, 23663, 2003, 20... | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | 2 |
| ElephantDrive 的问题在于它要... | [101, 6659, 1010, 3904, 1997, 2026, 9537, 2499... | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | 0 |
| 我的代码一个都不好用。 | [101, 1045, 2734, 2019, 1000, 3424, 23350, 100... | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | 4 |
注意,label_id 是从 0 开始索引的。在这种情况下,label_id 为 0 对应于 star_rating 类别 1,4 表示 star_rating 为 5 等。
我们还可以通过其记录标识符查询特定特征组中的特征,用于模型预测:
featurestore_runtime = boto3.Session()\
.client(
service_name='sagemaker-featurestore-runtime',
region_name=region)
record_identifier_value = 'IJKL2345'
featurestore_runtime.get_record(
FeatureGroupName=reviews_feature_group_name,
RecordIdentifierValueAsString=record_identifier_value)
使用 SageMaker Data Wrangler 进行数据摄取和转换
Data Wrangler 是 SageMaker 原生的,专注于机器学习用例,并跨完整的模型开发生命周期(MDLC)保留工件血统,包括数据摄取、特征工程、模型训练、模型优化和模型部署。除了在 [第五章 中分析我们的数据外,SageMaker Data Wrangler 还准备并转换我们的机器学习特征,支持超过 300+ 内置转换,以及自定义 SQL、pandas 和 Apache Spark 代码。Data Wrangler 用于许多目的,例如转换列数据类型、填充缺失数据值、将数据集分割为训练/验证/测试集、缩放和归一化列以及删除列。
数据转换步骤存储为 Data Wrangler 的 .flow 定义文件,并在新数据到达系统时重新使用。
我们还可以将 .flow 数据处理转换导出为 SageMaker 处理作业、流水线、特征存储或原始 Python 脚本。让我们将我们的 Data Wrangler 流导出到 SageMaker 流水线中,以自动化转换并使用 SageMaker Lineage 追踪其血统。我们将在下一节深入探讨血统,以及在 第十章 中的 SageMaker 流水线。这是由 Data Wrangler 生成的代码摘录,当我们将流导出到 SageMaker 流水线时:
import time
from sagemaker.workflow.parameters import (
ParameterInteger,
ParameterString,
)
from sagemaker.workflow.pipeline import Pipeline
with open(flow_file_name) as f:
flow = json.load(f)
s3_client = boto3.client("s3")
s3_client.upload_file(flow_file_name, bucket,
f"{prefix}/{flow_name}.flow")
pipeline_name = f"datawrangler-pipeline-{int(time.time() * 10**7)}"
instance_type = ParameterString(name="InstanceType",
default_value="ml.m5.4xlarge")
instance_count = ParameterInteger(name="InstanceCount",
default_value=1)
step_process = Step(
name="DataWranglerProcessingStep",
step_type=StepTypeEnum.PROCESSING,
step_args=processing_job_arguments
)
pipeline = Pipeline(
name=pipeline_name,
parameters=[instance_type, instance_count],
steps=[step_process],
sagemaker_session=sess
)
pipeline.create(role_arn=role)
pipeline.start()
使用 Amazon SageMaker 跟踪工件和实验血统
人类天生好奇。当展示一个物体时,人们可能想知道该物体是如何制造的。现在考虑一下由机器学习学习的预测模型这样一个强大而神秘的物体。我们自然想知道这个模型是如何创建的。使用了哪个数据集?选择了哪些超参数?探索了哪些其他超参数?这个模型版本与上一个版本相比如何?所有这些问题都可以通过 SageMaker ML Lineage Tracking 和 SageMaker Experiments 来解答。
作为最佳实践,我们应该跟踪从特征工程到模型训练再到模型部署的整体 MDLC 的数据转换的血统。SageMaker 数据整理器自动跟踪其摄取或转换的任何数据的血统。此外,SageMaker 处理作业、训练作业和端点也跟踪它们的血统。我们可以随时使用 SageMaker Studio IDE 或直接使用 SageMaker 血统 API 检查血统。对于我们工作流程中的每个步骤,我们存储输入工件、操作和生成的输出工件。我们可以使用血统 API 检查血统图并分析步骤、操作和工件之间的关系。
我们可以利用 SageMaker 血统 API 和血统图来达到多种目的,例如维护模型实验的历史记录,与同事分享工作,重现工作流以增强模型,在生产中追踪用于训练每个模型的数据集,确定模型已部署的位置,以及遵守法规标准和审计要求。
理解血统追踪概念
SageMaker 血统追踪 API 利用以下关键概念:
血统图
连接的图追踪我们的机器学习工作流程端到端。
工件
代表 URI 可寻址对象或数据。工件通常是操作的输入或输出。
操作
表示所采取的操作,如计算、转换或作业。
上下文
提供一种逻辑上组织其他实体的方法。
关联
在血统图中连接两个实体的有向边。关联可以是Produced、DerivedFrom、AssociatedWith或ContributedTo类型。
血统遍历
从任意点开始,追踪血统图以发现和分析工作流中步骤之间的关系,无论是上游还是下游。
实验
包括试验和试验组件在内的实验实体是血统图的一部分。它们与 SageMaker 血统核心组件相关联,包括工件、操作和上下文。
SageMaker 自动为 SageMaker 管道中的每个步骤创建血统跟踪实体,包括 SageMaker 处理作业、训练作业、模型、模型包和端点。每个管道步骤与输入工件、操作、输出工件和元数据相关联。我们将在第 7、8 和 9 章继续建立我们的血统图,然后在第十章中将所有内容汇总到一个完整的端到端血统图中。
显示特征工程作业的血统
我们可以展示已捕获的 SageMaker 处理作业用于从原始评审文本创建 BERT 嵌入的血统信息:
import time
Import sagemaker
from sagemaker.lineage.visualizer import LineageTableVisualizer
viz = LineageTableVisualizer(sagemaker.session.Session())
viz.show(processing_job_name='<sm_processing_job_name>')
输出应该类似于这样:
| 名称/来源 | 方向 | 类型 | 关联类型 | 衍生类型 |
|---|---|---|---|---|
| s3://../amazon-reviews-pds/tsv/ | 输入 | 数据集 | 贡献于 | 工件 |
| 68331.../sagemaker-scikit-learn:0.20.0-cpu-py3 | 输入 | 镜像 | 贡献于 | 工件 |
| s3://.../output/bert-test | 输出 | 数据集 | 产出 | 工件 |
| s3://.../output/bert-validation | 输出 | 数据集 | 产出 | 工件 |
| s3://.../output/bert-train | 输出 | 数据集 | 产出 | 工件 |
SageMaker Lineage Tracking 自动记录了输入数据(TSV)、输出数据(TFRecord)和 SageMaker 容器镜像。关联类型显示输入数据对该流水线步骤的ContributedTo。
生成的训练数据分为训练、验证和测试数据集,已记录为此步骤的输出。关联类型正确地将它们分类为此步骤的Produced工件。
了解 SageMaker 实验 API
SageMaker 实验是我们数据科学工具包中的一个宝贵工具,可以深入了解模型训练和调整过程。通过实验,我们可以跟踪、组织、可视化和比较我们的 AI 和机器学习模型在 MDLC 的所有阶段(包括特征工程、模型训练、模型调整和模型部署)中的表现。实验与 SageMaker Studio、处理作业、训练作业和端点无缝集成。SageMaker 实验 API 由以下关键抽象组成:
实验
一组相关的试验。将试验添加到一个我们希望进行比较的实验中。
试验
多步机器学习工作流程的描述。工作流程中的每个步骤由一个试验组件描述。
试验组件
机器学习工作流程中的单个步骤描述,例如数据转换、特征工程、模型训练、模型评估等。
跟踪器
单个试验组件的信息记录器。
虽然 SageMaker 实验本质上是集成在 SageMaker 中的,但我们可以通过使用 SageMaker 实验 API 和几行代码,从任何 Jupyter 笔记本或 Python 脚本跟踪实验。
图 6-15 显示了单个实验中的三个试验:试验 A、B 和 C。所有试验都重复使用相同的特征工程试验组件“准备 A”,使用不同的超参数训练三个不同的模型。试验 C 提供了最佳精度,因此我们部署模型并跟踪部署试验组件“部署 C”。

图 6-15。使用 SageMaker 实验比较具有不同超参数的训练运行。
使用 SageMaker Experiments API,我们创建了每一步和超参数的完整记录,用于重新创建模型 A、B 和 C。在任何给定时间点,我们都可以确定模型是如何训练的,包括使用的确切数据集和超参数。这种可追溯性对于审计、解释和改进我们的模型至关重要。我们将在章节 7,8 和 9 深入探讨跟踪模型训练、优化和部署步骤。目前,让我们使用 SageMaker Experiment API 跟踪我们的特征工程步骤。首先,我们创建Experiment如下:
import time
from smexperiments.experiment import Experiment
experiment_name = 'Experiment-{}'.format(int(time.time()))
experiment = Experiment.create(
experiment_name=experiment_name,
description='Amazon Customer Reviews BERT Experiment',
sagemaker_boto_client=sm)
接下来,让我们创建experiment_config参数,当我们创建 BERT 嵌入时将其传递给处理器。这个experiment_config参数将在 SageMaker 处理作业中使用,用于添加一个名为prepare的新TrialComponent,用于跟踪原始评论输入和转换后的训练、验证和测试输出拆分的 S3 位置:
experiment_config = {
'ExperimentName': experiment_name,
'TrialName': trial.trial_name,
'TrialComponentDisplayName': 'prepare'
}
processor.run(code='preprocess-scikit-text-to-bert.py',
...
experiment_config=experiment_config)
我们可以使用 SageMaker Experiments API 显示我们prepare步骤中使用的参数,如下所示。我们将继续通过章节 7,8 和 9 跟踪我们的实验谱系:
from sagemaker.analytics import ExperimentAnalytics
lineage_table = ExperimentAnalytics(
sagemaker_session=sess,
experiment_name=experiment_name,
sort_by="CreationTime",
sort_order="Ascending",
)
lineage_df = lineage_table.dataframe()
lineage_df
| TrialComponentName | DisplayName | max_seq_length | train_split_percentage | validation_split_percentage | test_split_percentage |
|---|---|---|---|---|---|
| bert-transformation-2021-01-09-062410-pxuy | prepare | 64.0 | 0.90 | 0.05 | 0.05 |
使用 AWS Glue DataBrew 摄取和转换数据
我们可以使用内置的 Glue DataBrew 数据转换工具来合并、旋转或转置数据。应用的数据转换序列被记录在一个配方中,我们可以将其应用于到达的新数据。SageMaker Data Wrangler 优于 Glue DataBrew 用于机器学习用例,因为 Data Wrangler 与 SageMaker 集成,并跟踪 MDLC 所有阶段的完整谱系。虽然 Data Wrangler 专注于机器学习用例,数据转换可以导出为处理代码,但我们可以利用 Glue DataBrew 进行定期的初始数据清洗和转换。
应用的数据转换序列被记录在一个配方中,我们可以将其应用于到达的新数据。虽然 DataBrew 专注于传统的抽取-转换-加载工作流,但它包括一些非常强大的统计函数,用于分析和转换数据,包括亚马逊评论客户数据集中的文本数据。
让我们在 DataBrew UI 中创建一个简单的配方,通过创建名为amazon-reviews-dataset-recipe的配方来删除数据集中的一些未使用字段。从 UI 中导出recipe.json后,我们可以使用 DataBrew Python SDK 以编程方式删除列。这是从我们的数据集中删除未使用列的recipe.json:
[
{
"Action": {
"Operation": "DELETE",
"Parameters": {
"sourceColumns": "[\"marketplace\",\"customer_id\", \
\"product_id\",\"product_parent\",\"product_title\", \
\""total_votes\",\"vine\",\"verified_purchase\", \
\"review_headline\",\"year\"]"
}
}
}
]
我们需要为我们的数据集和配方创建一个 DataBrew 项目:
project_name = 'amazon-customer-reviews-dataset-project'
recipe_name='amazon-customer-reviews-dataset-recipe'
response = db.create_project(
Name=project_name,
DatasetName=dataset_name,
RecipeName=recipe_name,
Sample={
'Size': 500,
'Type': 'FIRST_N'
},
RoleArn=<ROLE_ARN>
)
现在让我们调用 DataBrew Python SDK,基于前面列出的recipe.json创建一个转换作业:
job_name = 'amazon-customer-reviews-dataset-recipe-job'
response = db.create_recipe_job(
Name=job_name,
LogSubscription='ENABLE',
MaxCapacity=10,
MaxRetries=0,
Outputs=[
{
'Format': 'CSV',
'PartitionColumns': [],
'Location': {
'Bucket': <S3_BUCKET>,
'Key': <S3_PREFIX>
},
'Overwrite': True
},
],
ProjectName=project_name,
RoleArn=<IAM_ROLE>,
Timeout=2880
)
我们开始数据转换作业如下:
response = db.start_job_run(
Name=job_name
)
DataBrew 跟踪每个数据转换步骤的谱系,如图 6-16 所示。
DataBrew 作业完成后,我们的转换数据就在 S3 中。这里是作为 pandas DataFrame 的数据样本:
| star_rating | review_body |
|---|---|
| 5 | 参加了几节气功课程后,我想要... |
| 4 | Krauss 追溯了出色的转变... |
| 4 | Rebecca,一名牙科卫生师,接到一通... |
| 5 | 好人物和情节线。我花了一些愉快的时光... |

图 6-16. Glue DataBrew 谱系显示应用于数据集的数据转换步骤。
总结
在本章中,我们通过一个真实的例子探讨了特征工程,将原始的亚马逊客户评论转换为机器学习特征,使用 BERT 和 TensorFlow。我们描述了如何使用 SageMaker Data Wrangler 选择特征并对数据进行转换,以便进行模型训练的准备。我们还展示了如何使用 SageMaker Lineage 和 Experiment API 跟踪和分析转换的谱系。我们还展示了如何使用 Glue DataBrew 作为 SageMaker 之外的另一种数据分析和转换选项。
在第七章中,我们将使用这些特征训练一个评估模型,以预测从社交渠道、合作伙伴网站和其他产品评论来源捕获的评论文本中的star_rating。我们将深入探讨各种模型训练和深度学习选项,包括 TensorFlow、PyTorch、Apache MXNet 甚至 Java!我们演示了如何分析训练作业的配置文件、检测模型偏差,并通过 SageMaker Debugger 解释模型预测。
第七章:训练您的第一个模型
在前一章中,我们使用 SageMaker 处理作业通过“特征工程”过程将原始数据集转换为可用于机器的特征。在本章中,我们使用这些特征使用 TensorFlow、PyTorch、BERT和 SageMaker 来训练自定义评论分类器,用于从社交渠道、合作伙伴网站等“野外”分类评论。我们甚至展示了如何使用 Java 训练 BERT 模型!
在这个过程中,我们解释了诸如变压器架构、BERT 和微调预训练模型等关键概念。我们还描述了 SageMaker 提供的各种培训选项,包括内置算法和“自带”选项。接下来,我们讨论 SageMaker 基础设施,包括容器、网络和安全性。然后,我们使用 SageMaker 训练、评估和配置我们的模型。性能分析帮助我们调试模型、减少训练时间和成本。最后,我们提供了进一步减少成本和提高模型性能的技巧。
了解 SageMaker 基础设施
大部分基于容器的,SageMaker 管理基础架构并帮助我们专注于特定的机器学习任务。开箱即用,我们可以直接利用许多内置算法,涵盖自然语言处理(NLP)、分类、回归、计算机视觉和强化学习等用例。除了这些内置算法外,SageMaker 还为许多流行的人工智能和机器学习框架(如 TensorFlow、PyTorch、Apache MXNet、XGBoost 和 scikit-learn)提供预构建的容器。最后,我们还可以使用自己选择的库和框架提供自己的 Docker 容器。在本节中,我们详细讨论了 SageMaker 基础设施,包括环境变量、S3 位置、安全性和加密。
我们可以选择在单个实例上或在分布式实例集群上进行训练。Amazon SageMaker 消除了管理底层基础设施的负担,并为我们处理了不同的重型工作。
介绍 SageMaker 容器
在运行训练作业时,SageMaker 从 Amazon S3 读取输入数据,使用该数据训练模型,最后将模型工件写回 Amazon S3。图 7-1 说明了 SageMaker 如何使用容器进行训练和推断。从左下角开始,来自 S3 的训练数据可用于模型训练实例容器,该容器从 Amazon Elastic Container Registry 中拉取。训练作业将模型工件持久化到在训练作业配置中指定的输出 S3 位置。当准备部署模型时,SageMaker 启动新的 ML 实例,并拉取这些模型工件用于批处理或实时模型推断。

图 7-1. SageMaker 容器、输入和输出。来源:Amazon SageMaker Workshop。
就像软件框架一样,SageMaker 提供了多个“热点”供我们的训练脚本利用。有两个值得关注的热点是:输入/输出数据位置和环境变量。
SageMaker 为我们的容器提供了训练输入和输出文件的位置。例如,典型的训练作业读取数据文件,训练模型,并输出模型文件。一些 AI 和机器学习框架支持模型检查点,以防训练作业失败或我们决定使用比最新模型具有更好预测性能的先前检查点。在这种情况下,作业可以从离开的地方重新启动。这些输入、输出和检查点文件必须在短暂的 Docker 容器内部和更持久的存储(如 S3)之间移动,否则当训练作业结束并且 Docker 容器消失时,数据将会丢失。
虽然看似简单,但这种映射在训练性能拼图中是非常关键的一部分。如果这个层次的映射没有优化,我们的训练时间将会大大受到影响。稍后,我们将讨论一种称为Pipe模式的 SageMaker 功能,专门优化了数据在这一层的移动。图 7-2 展示了将 Docker 容器内部文件位置映射到容器外部 S3 位置的情况。

图 7-2. 容器文件位置映射到 S3 位置。
SageMaker 会自动为我们的容器提供许多预定义的环境变量,例如可用于容器的 GPU 数量和日志级别。我们的训练脚本可以使用这些由 SageMaker 注入的环境变量来相应地修改我们训练作业的行为。以下是 SageMaker 传递给我们的脚本的环境变量子集,从 Jupyter 笔记本、脚本、管道等:
SM_MODEL_DIR
包含训练或处理脚本以及依赖库和资产的目录 (/opt/ml/model)
SM_INPUT_DIR
包含输入数据的目录 (/opt/ml/input)
SM_INPUT_CONFIG_DIR
包含输入配置的目录 (/opt/ml/input/config)
SM_CHANNELS
包含数据拆分的 S3 位置,包括“训练”、“验证”和“测试”
SM_OUTPUT_DATA_DIR
用于存储评估结果和其他非训练相关输出资产的目录 (/opt/ml/output/data)
SM_HPS
算法使用的模型超参数
SM_CURRENT_HOST
当前实例的唯一主机名
SM_HOSTS
集群中所有实例的主机名
SM_NUM_GPUS
当前实例的 GPU 数量
SM_NUM_CPUS
当前实例的 CPU 数量
SM_LOG_LEVEL
训练脚本使用的日志级别
SM_USER_ARGS
用户指定并由训练或处理脚本解析的附加参数
_DIR变量映射是运行我们训练代码的 Docker 容器内部的本地文件路径。这些路径映射到由 SageMaker 提供并在启动训练作业时由用户指定的 S3 中的外部输入和输出文件位置。然而,我们的训练脚本在读取输入或写入输出时引用本地路径。
通过计算和网络隔离提高可用性
从高可用性的角度来看,网络隔离同样重要。虽然我们通常在微服务和实时系统方面讨论高可用性,但我们也应努力提高训练作业的可用性。
我们的训练脚本几乎总是通过pip install安装来自 PyPI 的 Python 库,或从互联网上的第三方模型库(或“模型动物园”)下载预训练模型。通过创建对外部资源的依赖,我们的训练作业现在依赖于这些第三方服务的可用性。如果其中一个服务暂时不可用,我们的训练作业可能无法启动。
注意
在 Netflix,我们将所有依赖项“烧制”到我们的 Docker 镜像和 Amazon Machine Images(AMIs)中,以消除所有外部依赖项并实现更高的可用性。在快速扩展事件和故障转移场景中,减少外部依赖项绝对至关重要。
为了提高可用性,建议尽可能减少外部依赖,通过将这些资源复制到我们的 Docker 镜像或我们自己的 S3 存储桶中来实现。这样做的附加好处是减少网络延迟并更快地启动我们的训练作业。以下 IAM 策略将不会启动具有禁用网络隔离的 SageMaker 训练作业。如果我们不启用网络隔离,训练作业将立即失败,这正是我们想要强制执行的:
{
"Sid": "SageMakerNetworkIsolation",
"Effect": "Deny",
"Action": [
"sagemaker:CreateTrainingJob"
],
"Resource": "*",
"Condition": {
"Bool": {
"sagemaker:NetworkIsolation": "false"
}
}
}
计算和网络隔离还可以提高安全性并减少攻击者获取我们数据的风险。作为安全最佳实践,所有 SageMaker 组件应在没有直接互联网连接的虚拟私有云(VPC)中使用。这要求我们仔细配置 IAM 角色、VPC 终端节点、子网和安全组,以最小特权访问策略管理 Amazon S3、SageMaker、Redshift、Athena、CloudWatch 和数据科学工作流中使用的任何其他 AWS 服务。在第十二章中,我们将深入探讨如何使用计算隔离、网络隔离、VPC 终端节点和 IAM 策略来保护我们的数据科学环境。
使用 SageMaker JumpStart 部署预训练的 BERT 模型
SageMaker JumpStart 提供访问来自 AWS、TensorFlow Hub 和 PyTorch Hub 的预构建机器学习解决方案和预训练模型,涵盖了许多用例和任务,如欺诈检测、预测性维护、需求预测、NLP、目标检测和图像分类,如图 7-3 所示。

图 7-3. 使用 SageMaker JumpStart 部署预训练模型。
当我们希望快速在我们的数据集上测试解决方案或模型并生成基线评估指标时,SageMaker JumpStart 非常有用。我们可以快速排除不适合我们数据的模型,并深入研究那些确实适合的解决方案和模型。
让我们使用亚马逊客户评论数据集对预训练的 BERT 模型进行微调,并在 SageMaker Studio 中仅需点击几下即可将模型部署到生产环境,如图 7-4 所示。

图 7-4. 使用 SageMaker JumpStart,我们只需点击几下即可对预训练的 BERT 模型进行微调和部署。
在使用亚马逊客户评论数据集对选择的 BERT 模型进行微调后,SageMaker JumpStart 部署模型,因此我们可以立即开始进行预测:
import json
import boto3
text1 = 'i simply love this product'
text2 = 'worst product ever'
label_map = {0: "1", 1: "2", 2: "3", 3: "4", 4: "5"}
def query_endpoint(encoded_text):
endpoint_name = 'jumpstart-tf-tc-bert-en-uncased-l-12-h-768-a-12-2'
client = boto3.client('runtime.sagemaker')
response = client.invoke_endpoint(
EndpointName = endpoint_name,
ContentType = 'application/x-text',
Body = encoded_text)
model_predictions = json.loads(response['Body'].read())['predictions'][0]
return model_predictions
for text in [text1, text2]:
model_predictions = query_endpoint(text.encode('utf-8'))
class_index = model_predictions.index(max(model_predictions))
输出将类似于以下内容:
Review text: 'i simply love this product'
Predicted star_rating: 5
Review text: 'worst product ever'
Predicted star_rating: 1
开发一个 SageMaker 模型
正如亚马逊为顾客提供了通过亚马逊市场多种选择一样,亚马逊 SageMaker 为构建、训练、调整和部署模型提供了多种选择。我们将在第八章深入研究模型调优,并在第九章讨论部署。根据所需的定制程度,有三个主要选项,如图 7-5 所示。

图 7-5. SageMaker 有三个选项来构建、训练、优化和部署我们的模型。
内置算法
SageMaker 提供了一些内置算法,可以直接用于多个不同领域,如 NLP、计算机视觉、异常检测和推荐等。只需将这些高度优化的算法指向我们的数据,我们将获得一个完全训练良好、易于部署的机器学习模型,可以集成到我们的应用程序中。这些算法如下图所示,针对那些不想管理大量基础设施、希望重复使用经过战斗测试的算法,设计用于处理非常大型数据集并被数以万计的客户使用。此外,它们提供诸如大规模分布式训练以减少训练时间和混合精度浮点支持以提高模型预测延迟的便利。
| 分类
-
线性学习者
-
XGBoost
-
KNN
| 计算机视觉
-
图像分类
-
目标检测
-
语义分割
| 处理文本
-
BlazingText
-
监督
-
无监督
|
| 回归
-
线性学习者
-
XGBoost
-
KNN
| 异常检测
-
随机切分森林
-
IP 见解
| 主题建模
-
LDA
-
NTM
|
| 序列翻译
- Seq2Seq
| 推荐
- 因子分解机
| 聚类
- K 均值
|
| 特征减少
-
PCA
-
Object2Vec
| 预测
- DeepAR
自带脚本
SageMaker 提供了更多可定制选项“自定义脚本”,通常称为Script Mode。Script Mode 让我们专注于我们的训练脚本,而 SageMaker 为每个熟悉的开源框架(如 TensorFlow、PyTorch、Apache MXNet、XGBoost 和 scikit-learn)提供了高度优化的 Docker 容器,如图 7-6 所示。

图 7-6. 受 Amazon SageMaker 支持的流行人工智能和机器学习框架。
这个选项是高度定制和低维护的良好平衡。本书中其余的大部分 SageMaker 示例将利用 Script Mode 与 TensorFlow 和 BERT 进行 NLP 和自然语言理解(NLU)用例,如图 7-7 所示。

图 7-7. SageMaker 脚本模式与 BERT 和 TensorFlow 的结合是高度定制和低维护的良好平衡。
自定义容器部署
最可定制的选项是“自定义容器部署”。此选项允许我们构建和部署自己的 Docker 容器到 SageMaker。这个 Docker 容器可以包含任何库或框架。虽然我们对训练脚本及其依赖项的详细控制权,SageMaker 管理低级基础设施,如日志记录、监控、注入环境变量、注入超参数、映射数据集输入和输出位置等。这个选项面向具有系统背景的更低级别的机器学习从业者,或者需要出于合规性和安全性原因使用自己的 Docker 容器的场景。将现有的 Docker 镜像转换为 SageMaker 内运行的步骤简单明了,只需按照此AWS 开源项目中列出的步骤进行即可。
自然语言处理简史
在前一章中,我们将原始的亚马逊客户评论转换为 BERT 特征向量,最终构建了一个评论分类器模型,用于从review_body文本预测star_rating。在构建自然语言模型之前,我们想要介绍一些关于 NLP 的背景知识。
1935 年,著名的英国语言学家 J.R.弗斯(J. R. Firth)曾说过:“一个词的完整意义始终是在其语境中,任何脱离上下文的意义研究都不能被认真对待。”80 年后的 2013 年:词向量或“词嵌入”开始主导语言表示,如图 7-8 所示。这些词嵌入捕捉了文档集(通常称为“语料库”)中单词之间的上下文关系。

图 7-8. 自然语言处理(NLP)算法和架构的演变。
Word2Vec 和 GloVe 是过去十年中流行的两种 NLP 算法。它们都使用上下文信息在向量空间中创建文本数据的向量表示,使我们能够进行诸如单词相似性和单词差异等数学计算。
FastText 继续创新上下文 NLP 算法,并使用子词标记化构建单词嵌入。这使得 FastText 能够使用相对较少的数据量学习非英语语言模型。Amazon SageMaker 提供了一个内置的、按使用量付费的 SageMaker 算法,称为 BlazingText,它使用了针对 AWS 优化的 FastText 实现。这个算法在 “Built-in Algorithms” 中展示过。
这一代 NLP 模型存在一些缺点,因为它们都是静态词嵌入的形式。虽然静态嵌入捕捉了单词的语义意义,但它们实际上并不理解高级语言概念。事实上,一旦创建了嵌入,实际模型通常在训练后被丢弃(即 Word2Vec、GloVe),仅保留单词嵌入以作为传统机器学习算法(如逻辑回归和 XGBoost)的特征使用。
ELMo 保留训练后的模型,并使用两个长短期记忆(LSTM)网络分支:一个从左到右学习,一个从右到左学习。上下文被捕获在 LSTM 状态中,并在每个网络分支中的每个单词后更新。因此,ELMo 并没有学习语料库中单词和短语的真正的双向上下文表示,但它表现仍然非常好。
注
LSTM 是一种特殊类型的循环神经网络(RNN),它选择性地选择要记住和要遗忘的信息。这使得 LSTM 能够高效地利用内存和计算资源,避免消失梯度问题,并保持非常好的预测能力。门控循环单元是另一种比 LSTM 更简单且表现良好的 RNN 变体。然而,ELMo 具体使用 LSTM。
GPT 和更新的 GPT-2 和 GPT-3 模型(GPT-n)保留训练后的模型,并使用称为“Transformer”的神经网络架构来学习上下文词表示。Transformers 与其注意力机制伴侣一起在 2017 年的《Attention Is All You Need》论文中广为人知。Transformer 提供高度并行计算,以实现更高的吞吐量、更好的性能和更高效的计算资源利用。LSTM 和 ELMo 不支持并行计算。
GPT-n transformer 使用一种定向的、从左到右的“掩码自注意力”机制来学习左到右的上下文表示,如 图 7-9 所示。这防止模型提前窥视句子中的下一个单词。即使有此限制,GPT-n 在文本生成任务中表现非常出色,因为它采用了这种左到右的机制。

图 7-9. GPT-n 的掩码自注意力机制。
2018 年,推出了一种新的基于神经网络的自然语言处理算法称为双向编码器表示转换(BERT)。BERT 彻底改变了自然语言处理(NLP)和自然语言理解(NLU)领域,并且现在广泛应用于 Facebook、LinkedIn、Netflix、Amazon 等许多以人工智能为先的公司。BERT 建立在高度可并行化的 Transformer 架构基础上,并添加了真正的双向自注意力机制,可以同时向前和向后查看。BERT 的自注意力机制改进了 GPT-n 的向后查看、掩码自注意力机制。
BERT Transformer 架构
BERT Transformer 架构的核心是使用注意机制来“关注”语料库中特定和有趣的单词或短语。具体来说,BERT transformer 使用“自注意力”机制来关注数据中的每个标记,以及输入序列中的所有其他标记。此外,BERT 使用“多头注意力”来处理单词含义上的歧义,也称为多义性(希腊语 poly = 多, sema = 符号)。一个注意力的示例显示在 图 7-10 中,其中单词 it 高度关注单词 movie,以及单词 funny 和 great,尽管相对于单词 movie,关注程度较低。

图 7-10. “自注意力”机制关注数据中的每个标记到输入序列中的所有其他标记。
没有这种双向注意力,算法可能会为以下两个句子中的单词 bank 创建相同的嵌入:“A thief stole money from the bank vault” 和 “Later, he was arrested while fishing on a river bank.” 注意,单词 bank 在每个句子中有不同的含义。人类因为有终身的自然“预训练”,很容易区分这一点,但是对于没有类似预训练的机器来说并不容易。BERT 通过学习在特定上下文中为每个标记学习不同的向量来区分这两个单词(标记)。学习的标记向量称为“输入标记向量表示”,学习的句向量称为“汇聚文本向量表示”。
BERT 基于 Transformer 的序列模型由多个堆叠的 Transformer 块组成。预训练的 BERT[Base] 模型包含 12 个这样的 Transformer 块,而 BERT[Large] 模型包含 24 个 Transformer 块。每个 Transformer 块实现多头注意力层和全连接前馈层。每一层都带有跳跃连接(残差连接)和层归一化模块。
我们增加了一个额外的层来对模型进行微调,以适应特定的 NLP 任务。对于文本分类,我们会添加一个分类器层。训练数据经过所有 Transformer 块处理后,数据通过微调层,并学习特定于我们的 NLP 任务和数据集的参数。图 7-11 展示了 BERT 的架构。

图 7-11. BERT 模型架构。
让我们更仔细地看一看 BERT 如何实现注意力。我们可以将注意力视为根据其对解决 NLP 任务的重要性而分配权重给输入标记的过程。更具数学化的术语,注意力是一个函数,接受一个输入序列 X 并返回另一个序列 Y,由与 X 中相同长度的向量组成。Y 中的每个向量都是 X 中向量的加权平均,如图 7-12 所示。

图 7-12. 注意力是输入向量的加权平均。
权重表达了模型在计算加权平均时对 X 中每个输入向量的关注程度。那么,BERT 如何计算注意力权重呢?
兼容性函数为每对单词分配一个分数,指示它们彼此关注的强度。首先,模型创建一个查询向量(用于注意的单词)和一个键向量(用于被注意的单词),作为实际值向量的线性变换。然后计算兼容性分数,作为一个单词的查询向量与另一个单词的键向量的点积。通过应用 softmax 函数对分数进行归一化。结果就是注意力权重,如图 7-13 所示。

图 7-13. 注意力权重是查询向量和键向量的归一化点积。
从零开始训练 BERT
虽然我们可以直接使用 BERT 而无需从头开始训练,了解 BERT 如何使用单词屏蔽和下一个句子预测—并行—来学习和理解语言是很有用的。
掩蔽语言模型
当 BERT 看到新文本时,它会在每个句子或“序列”中遮蔽 15% 的词(在 BERT 术语中称为“序列”)。然后,BERT 预测遮蔽的词并在预测错误时进行自我纠正(即“更新模型权重”)。这被称为遮蔽语言模型或 Masked LM。遮蔽强迫模型学习每个序列的周围词语,如 图 7-14 所示。

图 7-14. BERT Masked LM 遮蔽 15% 的输入标记,并通过预测遮蔽的标记来学习——当它预测错误的时候会进行自我纠正。
更具体地说,BERT 是通过强迫它预测句子中的遮蔽词(实际上是标记)来训练的。例如,如果我们输入这本书的内容,我们可以让 BERT 预测下面句子中的缺失词:“这本书被称为 Data ____ on AWS。”显然,缺失的词是“Science”。对于一个从出生开始就被预训练在数百万文档上的人类来说,这很容易,但对于机器来说并不容易——至少没有经过训练的机器。
下一句预测
当 BERT 同时遮蔽和预测输入标记时,它还在输入序列对上执行下一句预测(NSP)。这两个训练任务被优化在一起,为组合训练工作创建单一的准确度分数。这导致一个更强大的模型,能够执行词级和句级预测任务。
为了执行 NSP,BERT 随机选择 50% 的句子对,并用文档中另一部分的随机句子替换其中的一个句子。然后,BERT 预测这两个句子是否是一个有效的句子对,如 图 7-15 所示。当 BERT 预测错误时会进行自我纠正。

图 7-15. 在训练过程中,BERT 在输入序列对上同时执行遮蔽和 NSP。
想要了解更多关于 BERT 的细节,请查看 2018 年的论文“BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding”。
在大多数情况下,我们不需要从头开始训练 BERT。神经网络设计为可以重复使用并在新数据到达系统时进行持续训练。由于 BERT 已经在来自维基百科和 Google Books Corpus 的数百万公共文档上进行了预训练,其词汇表和学习表示可转移到大量的 NLP 和 NLU 任务中,跨越各种领域。
从头开始训练 BERT 需要大量的数据和计算资源,但它允许 BERT 使用高度专业化的词汇表学习定制数据集的表示。像亚马逊和 LinkedIn 这样的公司已经从头开始预训练了内部版本的 BERT,以学习特定领域的语言表示。例如,LinkedIn 的变体已经学习了特定于职称、简历、公司和商业新闻的语言模型。
微调预训练的 BERT 模型
ELMo、GPT/GPT-2 和 BERT 保留了某些被称为“预训练模型”的训练模型。这些模型在许多不同领域的数百万篇文档上进行了预训练,它们不仅擅长预测缺失的单词,还能学习单词的含义、句子结构和句子之间的关联。它们生成有意义、相关和真实的文本的能力令人称奇和畏惧。让我们深入了解一下 BERT 的预训练模型。
BERT 的预训练模型与大多数神经网络模型一样,仅是从迄今为止见过的数据学习到的模型权重的即时快照。与大多数模型一样,BERT 随着数据量的增加变得更加有价值。
BERT 的核心预训练模型分为“base”和“large”两个变种,其层数、注意力头数、隐藏单元和参数数量不同,如下表所示。我们发现即使使用只有 12 个注意力头和 1.1 亿参数的较小模型,也能得到非常好的性能。
| 层次 | 隐藏单元 | 参数 | |
|---|---|---|---|
| BERT base | 12 | 768 | 110M |
| BERT large | 24 | 1024 | 340M |
此外,社区已经使用领域和语言特定的数据集创建了许多 BERT 的预训练版本,包括 PatentBERT(美国专利数据)、ClinicalBERT(医疗数据)、CamemBERT(法语)、GermanBERT(德语)和 BERTje(荷兰语)。
这些 BERT 的变体是从头开始预训练的,因为默认的 BERT 模型是在英文版本的维基百科和谷歌图书上进行训练的,并不与定制数据集(例如 CamemBERT 的法语和 ClinicalBERT 的医疗术语)共享相同的词汇。当从头开始训练时,我们可以重用 BERT 的神经网络变换器架构,但是舍弃从维基百科和谷歌图书学到的预训练基础模型权重。
对于我们的亚马逊客户评论数据集,我们可以安全地重用默认的 BERT 模型,因为它们具有类似的词汇表和语言表示。毫无疑问,从头开始训练 BERT 以学习特定的亚马逊.com 产品目录将提高某些任务(如实体识别)的准确性。然而,默认的 BERT 模型在我们的评论文本上表现非常好,因此我们将保持简单,使用“微调”默认的 BERT 模型来创建一个使用我们的亚马逊客户评论数据集的定制文本分类器。
让我们利用预训练的 BERT 模型学习一个新的领域特定的 NLP 任务,使用亚马逊客户评论数据集。这个过程称为“微调”,如图 7-16 所示。

图 7-16. 我们可以使用自定义数据集对预训练的 BERT 模型进行领域特定任务的微调。
BERT 自注意机制的简单性和双向性使我们能够将基础 BERT 模型微调到各种即插即用的“下游” NLP/NLU 任务中,包括文本分类分析情感、实体识别检测产品名称,以及下一句预测回答自然语言问题,如图 7-17 所示。

图 7-17. 我们可以将默认的 BERT 模型微调到许多“下游” NLP 和 NLU 任务。
由于微调是一个有监督的训练过程(与无监督的预训练相对),在微调过程中不会进行掩码和下一句预测——这些仅在预训练期间发生。因此,微调非常快速,仅需要相对较少的样本,或者在我们的情况下是评论。这意味着更低的处理能力、更低的成本和更快的训练/调整迭代。
记住,我们可以使用 SageMaker JumpStart 快速尝试这些预训练模型,并确定它们作为解决方案在我们的机器学习任务中的有效性。通过快速将预训练的 BERT 模型微调到我们的数据集,我们可以确定 BERT 是否合适。
由于我们已经从第六章中原始的 review_body 文本生成了 BERT 嵌入,我们已经准备好了!让我们微调 BERT,创建一个自定义文本分类器,用于从 review_body 预测 star_rating,使用我们的数据集,如图 7-18 所示。

图 7-18. 我们可以微调 BERT 模型,使用我们的评论数据集创建一个自定义文本分类器。
我们可以使用这个分类器预测即将到来的客户服务电子邮件或 Twitter 评论的情绪,例如。因此,当新的电子邮件或评论进入系统时,我们首先将电子邮件分类为负面(star_rating 1)、中性(star_rating 3)或正面(star_rating 5)。这可以帮助我们确定回复的紧急性——或者帮助我们将消息路由到正确的人员,如图 7-19 所示。

图 7-19. 我们可以将 BERT 微调到将评论文本分类为 star_rating 1(最差)到 5(最好)的类别。
创建训练脚本
让我们创建一个名为 tf_bert_reviews.py 的训练脚本,使用 TensorFlow 和 Keras 创建我们的分类器。然后,我们将前一章生成的特征传递给我们的分类器进行模型训练。
设置训练、验证和测试数据集拆分
在上一章中,我们使用 SageMaker 处理作业将原始的亚马逊客户评论转换为 BERT 嵌入,如图 7-20 所示。

图 7-20. BERT 嵌入作为 TensorFlow 模型训练的输入。
在本节中,我们加载训练、验证和测试数据集以供模型训练使用。我们将使用 TensorFlow 的 TFRecordDataset 实现并行加载 TFRecord 并对数据进行洗牌,以防止模型学习到数据呈现方式的模式。
注意
在人工智能和机器学习领域,随机性是被赞扬的。正确地对训练数据进行洗牌将提供足够的随机性,以防止模型学习到关于数据在磁盘上存储或者呈现给模型的任何模式。“自助法”是描述带有替换的随机抽样的常见技术。自助法为抽样过程增加了偏差、方差、置信区间和其他指标。
在第六章中,我们创建了一个 SageMaker 处理作业,使用 Hugging Face Transformers 库将原始 review_body 列转换为 BERT 嵌入。该处理作业使用 TensorFlow 优化的 TFRecord 文件格式将嵌入存储在 S3 中,我们将在训练作业中使用它们。让我们创建一个帮助函数来加载、解析和洗牌 TFRecord。
def file_based_input_dataset_builder(channel,
input_filenames,
pipe_mode,
is_training,
drop_remainder,
batch_size,
epochs,
steps_per_epoch,
max_seq_length):
dataset = tf.data.TFRecordDataset(input_filenames)
dataset = dataset.repeat(epochs * steps_per_epoch * 100)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
name_to_features = {
"input_ids": tf.io.FixedLenFeature([max_seq_length], tf.int64),
"input_mask": tf.io.FixedLenFeature([max_seq_length], tf.int64),
"segment_ids": tf.io.FixedLenFeature([max_seq_length], tf.int64),
"label_ids": tf.io.FixedLenFeature([], tf.int64),
}
def _decode_record(record, name_to_features):
"""Decodes a record to a TensorFlow example."""
record = tf.io.parse_single_example(record, name_to_features)
return record
dataset = dataset.apply(
tf.data.experimental.map_and_batch(
lambda record: _decode_record(record, name_to_features),
batch_size=batch_size,
drop_remainder=drop_remainder,
num_parallel_calls=tf.data.experimental.AUTOTUNE))
dataset.cache()
if is_training:
dataset = dataset.shuffle(seed=42,
buffer_size=steps_per_epoch * batch_size,
reshuffle_each_iteration=True)
return dataset
如果 is_training 为真,则表示我们处于训练阶段。在训练阶段,我们希望在迭代之间对数据进行洗牌。否则,模型可能会学习到关于数据在磁盘上存储和呈现给模型的模式,即先是所有的 5,然后是所有的 4,3,2,1 等等。为了防止模型学习到这种模式,我们对数据进行洗牌。如果 is_training 为假,则表示我们处于验证或测试阶段,可以避免洗牌的开销,并按顺序迭代。
让我们使用之前创建的帮助函数读取训练、验证和测试数据集:
# Training Dataset
train_data_filenames = glob(os.path.join(train_data,
'*.tfrecord'))
train_dataset = file_based_input_dataset_builder(
channel='train',
input_filenames=train_data_filenames,
pipe_mode=pipe_mode,
is_training=True,
drop_remainder=False,
batch_size=train_batch_size,
epochs=epochs,
steps_per_epoch=train_steps_per_epoch,
max_seq_length=max_seq_length)\
.map(select_data_and_label_from_record)
# Validation Dataset
validation_data_filenames = glob(os.path.join(validation_data,
'*.tfrecord'))
validation_dataset = file_based_input_dataset_builder(
channel='validation',
input_filenames=validation_data_filenames,
pipe_mode=pipe_mode,
is_training=False,
drop_remainder=False,
batch_size=validation_batch_size,
epochs=epochs,
steps_per_epoch=validation_steps,
max_seq_length=max_seq_length)\
.map(select_data_and_label_from_record)
我们很快将把这些训练、验证和测试数据集传递给我们的模型训练过程。但首先,让我们使用 TensorFlow、Keras、BERT 和 Hugging Face 设置自定义评论分类器。
设置自定义分类器模型
很快,我们将把 review_body 嵌入和 star_rating 标签输入到神经网络中,以微调 BERT 模型并训练自定义评论分类器,如图 7-21 所示。请注意,图中显示的单词可能在标记化过程中被分成更小的词标记。但为了说明目的,我们展示它们为完整单词。

图 7-21. 使用我们的自定义分类器将评论分类为星级评分 1(最差)到 5(最佳)。
为此,我们使用 TensorFlow 2.x 的 Keras API,在预训练的 BERT 模型顶部添加一个神经分类器层来学习star_rating(1-5)。请记住,我们使用的是一个相对轻量级的 BERT 变体叫做 DistilBERT,它需要更少的内存和计算资源,但在我们的数据集上保持了非常好的准确性。为了减少模型的大小,DistilBERT,一个学生神经网络,通过一个更大的教师神经网络进行了知识蒸馏的过程,如图 7-22 所示。

图 7-22。知识蒸馏从一个教师模型训练出一个学生模型。
让我们加载DistilBertConfig,将我们的 1 索引的star_rating标签映射到 0 索引的内部类,并按如下方式加载我们预训练的 DistilBERT 模型:
from transformers import DistilBertConfig
from transformers import TFDistilBertForSequenceClassification
CLASSES=[1, 2, 3, 4, 5]
config = DistilBertConfig.from_pretrained('distilbert-base-uncased',
num_labels=len(CLASSES),
id2label={
0: 1, 1: 2, 2: 3, 3: 4, 4: 5
},
label2id={
1: 0, 2: 1, 3: 2, 4: 3, 5: 4
})
transformer_model = TFDistilBertForSequenceClassification.from_pretrained(
"distilbert-base-uncased", config=config
)
强调from_pretrained()函数调用会从 Hugging Face 服务下载一个大型模型是很重要的。我们应考虑下载此模型到我们自己的 S3 存储桶,并将 S3 URI 传递给from_pretrained()函数调用。这个小改动将使我们脱离 Hugging Face 服务,去除潜在的单点故障,实现网络隔离,并减少模型训练作业的启动时间。接下来,让我们设置输入和模型层:
input_ids = tf.keras.layers.Input(shape=(max_seq_length,),
name='input_ids',
dtype='int32')
input_mask = tf.keras.layers.Input(shape=(max_seq_length,),
name='input_mask',
dtype='int32')
embedding_layer = transformer_model.distilbert(input_ids,
attention_mask=input_mask)[0]
X = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(50,
return_sequences=True,
dropout=0.1,
recurrent_dropout=0.1))(embedding_layer)
X = tf.keras.layers.GlobalMaxPool1D()(X)
X = tf.keras.layers.Dense(50, activation='relu')(X)
X = tf.keras.layers.Dropout(0.2)(X)
X = tf.keras.layers.Dense(len(CLASSES), activation='softmax')(X)
model = tf.keras.Model(inputs=[input_ids, input_mask], outputs = X)
for layer in model.layers[:3]:
layer.trainable = not freeze_bert_layer
我们选择不训练 BERT 层,通过指定trainable=False。这是有意为之,以保持底层 BERT 模型不变——仅集中于训练我们的自定义分类器。训练 BERT 层可能会提高我们的准确性,但训练作业会更长。由于我们在不训练底层 BERT 模型的情况下准确率已经相当不错,我们只专注于训练分类器层。接下来,让我们添加一个基于 Keras 的神经分类器来完成我们的神经网络并准备模型训练:
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
metric=tf.keras.metrics.SparseCategoricalAccuracy('accuracy')
optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate, epsilon=epsilon)
model.compile(optimizer=optimizer, loss=loss, metrics=[metric])
model.summary()
这里是模型摘要的输出,显示了可训练和不可训练参数的详细情况:
__________________________________________________________________________
Layer (type) Output Shape Param #
==========================================================================
input_ids (InputLayer) [(None, 64)] 0
__________________________________________________________________________
input_mask (InputLayer) [(None, 64)] 0
__________________________________________________________________________
distilbert (TFDistilBertMainLay ((None, 64, 768),) 66362880
__________________________________________________________________________
bidirectional (Bidirectional) (None, 64, 100) 327600
__________________________________________________________________________
global_max_pooling1d (GlobalMax (None, 100) 0
__________________________________________________________________________
dense (Dense) (None, 50) 5050
__________________________________________________________________________
dropout_19 (Dropout) (None, 50) 0
__________________________________________________________________________
dense_1 (Dense) (None, 5) 255
==========================================================================
Total params: 66,695,785
Trainable params: 332,905
Non-trainable params: 66,362,880
__________________________________________________________________________
训练和验证模型
到此为止,我们已经准备好了训练、验证和测试数据集作为输入数据,并定义了我们的自定义分类器model。让我们把所有的东西都汇总起来,并在我们的模型上使用train_dataset和validation_dataset调用fit()函数。
通过传递validation_dataset,我们使用 TensorFlow 2.x 中的 Keras API 同时进行训练和验证:
train_and_validation_history = model.fit(train_dataset,
shuffle=True,
epochs=5,
...
validation_data=validation_dataset)
我们设置shuffle=True以对数据集进行洗牌,epochs=5以使数据集训练五次。epochs的数量(发音为“eh-puhks”)是可配置和可调整的。我们将在下一章节探讨模型调优。
保存模型
现在,让我们使用 TensorFlow 的SavedModel格式保存模型,用于我们的预测应用程序:
model.save('./tensorflow/', save_format='tf')
在第九章中,我们将使用保存在./tensorflow/中的模型与 TensorFlow Serving 部署我们的模型,并使用 SageMaker 批转换(离线、批处理)和 SageMaker 端点(在线、实时)提供评论分类预测。
从 SageMaker Notebook 启动训练脚本
让我们逐步了解从 SageMaker Notebook 运行训练脚本所需的步骤。稍后,我们将从自动化流水线中运行相同的脚本。目前,我们从笔记本运行脚本。首先,我们将设置需要监视训练作业的指标。然后,我们将配置我们算法特定的超参数。接下来,我们将选择我们集群中的实例类型和实例数量。最后,我们将启动我们的训练作业。
定义要捕获和监视的指标
我们可以根据训练脚本打印或记录到控制台的任何内容创建指标。假设我们的 TensorFlow 模型通过以下日志行发出训练损失和训练精度(loss、accuracy),以及验证损失和验证精度(val_loss、val_accuracy):
5000/10000 [>....................] - loss: 0.1420 - accuracy: 0.800103
6000/10000 [>....................] - loss: 0.1081 - accuracy: 0.939455
...
10000/10000 [>....................] - val_loss: 0.1420 - val_accuracy: 0.512193
接下来,我们定义四个正则表达式来通过解析日志行中的值来填充四个指标。如果我们升级框架——或者切换到新框架——这些正则表达式可能需要调整。当这种情况发生时,我们会知道,因为我们将不再在我们的 CloudWatch 仪表板上看到正确的模型指标:
metrics_definitions = [
{'Name': 'train:loss', 'Regex': 'loss: ([0-9\\.]+)'},
{'Name': 'train:accuracy', 'Regex': 'accuracy: ([0-9\\.]+)'},
{'Name': 'validation:loss', 'Regex': 'val_loss: ([0-9\\.]+)'},
{'Name': 'validation:accuracy', 'Regex': 'val_accuracy: ([0-9\\.]+)'},
]
配置我们算法的超参数
值得注意的是,“参数”(也称为“权重”)是模型在训练过程中学习的内容,“超参数”则是模型在学习过程中学习参数的方式。每种算法都支持一组超参数,这些参数可以改变算法在学习数据集时的行为。超参数可以是从决策树深度到神经网络层数等任何内容。
超参数选择涉及延迟和准确性之间的通常权衡。例如,具有许多层的更深的神经网络可能比较浅的神经网络提供更好的准确性,但更深的网络可能导致推断时延迟增加,因为每层的预测时间随着网络中的层数增加而增加。
虽然大多数超参数都有基于经验测试的合适默认值,但它们可以进行高度调整。事实上,机器学习中有一个专门的子领域致力于超参数调整/超参数优化。
我们将深入探讨第八章中的超参数选择和优化的艺术与科学,以找到最佳的超参数组合。目前,我们通过我们的经验、直觉以及一些轻量级的、临时的经验测试与特定数据集和算法来手动设置这些超参数:
epochs=500
learning_rate=0.00001
epsilon=0.00000001
train_batch_size=128
validation_batch_size=128
train_steps_per_epoch=100
validation_steps=100
test_steps=100
train_volume_size=1024
use_xla=True
use_amp=True
freeze_bert_layer=True
在评估算法时,我们应该努力理解所有可用的超参数。将这些超参数设置为次优值可能决定了数据科学项目的成败。这就是为什么超参数优化子领域如此重要的原因。
选择实例类型和实例数量
实例类型和实例数量的选择取决于我们的工作负载和预算。幸运的是,AWS 提供了许多不同的实例类型,包括 AI/ML 优化实例,配备超快速 GPU、TB 级内存和 GB 级网络带宽。在云端,我们可以轻松地扩展我们的训练作业到更大的实例,具有更多的内存和计算能力,或者扩展到数十、数百甚至数千个实例,只需一行代码即可。
让我们使用 p4d.24xlarge 实例类型进行训练,它配备 8 个 NVIDIA Tesla A100 GPU、96 个 CPU、1.1TB 内存、400Gb/s 网络带宽和 600Gb/s NVIDIA NVSwitch“网格”网络硬件之间的 GPU 间通信,如图 7-23 所示。

图 7-23. 单个实例上 GPU 之间的网格通信。
为了节约成本,我们通常会从小规模开始,逐渐增加适合我们特定工作负载所需的计算资源,以找到最低成本的选项。这通常被称为“权衡我们的集群”。根据经验,我们发现这种实例类型与我们特定的模型、数据集和成本预算非常匹配。对于我们的示例,我们只需要一个这样的实例,因此我们将 train_instance_count 设置为 1,如下所示:
train_instance_type='ml.p4.24xlarge'
train_instance_count=1
小贴士
我们可以在 SageMaker 训练作业中指定 instance_type='local' 来在笔记本内部或本地笔记本上运行脚本。有关更多信息,请参见“降低成本并提高性能”。
选择能够从多个集群实例中获益的可并行化算法非常重要。如果我们的算法不可并行化,那么增加实例数量是没有意义的,因为它们不会被使用。而且增加过多的实例可能会通过增加实例之间的通信开销而实际上减慢我们的训练作业速度。大多数基于神经网络的算法如 BERT 都是可并行化的,并且在训练或对大型数据集进行微调时受益于分布式集群。
在笔记本中将所有内容整合在一起
这是我们使用 SageMaker 脚本模式设置和调用 TensorFlow 训练作业的 Jupyter 笔记本:
from sagemaker.tensorflow import TensorFlow
epochs=500
learning_rate=0.00001
epsilon=0.00000001
train_batch_size=128
validation_batch_size=128
test_batch_size=128
train_steps_per_epoch=100
validation_steps=100
test_steps=100
train_instance_count=1
train_instance_type='ml.p4d.24xlarge'
train_volume_size=1024
use_xla=True
use_amp=True
freeze_bert_layer=False
enable_sagemaker_debugger=True
enable_checkpointing=False
enable_tensorboard=True
input_mode='File'
run_validation=True
run_test=True
run_sample_predictions=True
max_seq_length=64
hyperparameters={'epochs': epochs,
'learning_rate': learning_rate,
'epsilon': epsilon,
'train_batch_size': train_batch_size,
'validation_batch_size': validation_batch_size,
'test_batch_size': test_batch_size,
'train_steps_per_epoch': train_steps_per_epoch,
'validation_steps': validation_steps,
'test_steps': validation_steps,
'use_xla': use_xla,
'use_amp': use_amp,
'max_seq_length': max_seq_length,
'freeze_bert_layer': freeze_bert_layer,
'run_validation': run_validation,
'run_sample_predictions': run_sample_predictions}
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
instance_count=train_instance_count,
instance_type=train_instance_type,
volume_size=train_volume_size,
py_version='<PYTHON_VERSION>',
framework_version='<TENSORFLOW_VERSION>',
hyperparameters=hyperparameters,
metric_definitions=metrics_definitions,
max_run=7200 # seconds)
最后,我们使用 estimator.fit() 方法来启动笔记本上的训练作业,使用训练、验证和测试数据集分割,具体操作如下:
from sagemaker.inputs import TrainingInput
s3_input_train_data =
TrainingInput(s3_data=processed_train_data_s3_uri,
distribution='ShardedByS3Key')
s3_input_validation_data =
TrainingInput(s3_data=processed_validation_data_s3_uri,
distribution='ShardedByS3Key')
s3_input_test_data =
TrainingInput(s3_data=processed_test_data_s3_uri,
distribution='ShardedByS3Key')
estimator.fit(inputs={'train': s3_input_train_data,
'validation': s3_input_validation_data,
'test': s3_input_test_data
},
wait=False)
从 S3 下载并检查我们训练好的模型
让我们使用 AWS CLI 从 S3 下载我们的模型,并使用 TensorFlow 的 saved_model_cli 脚本进行检查:
aws s3 cp s3://$bucket/$training_job_name/output/model.tar.gz \
./model.tar.gz
mkdir -p ./model/
tar -xvzf ./model.tar.gz -C ./model/
saved_model_cli show --all --dir ./model/tensorflow/saved_model/0/
### OUTPUT ###
signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['input_ids'] tensor_info:
dtype: DT_INT32
shape: (-1, 64)
name: serving_default_input_ids:0
inputs['input_mask'] tensor_info:
dtype: DT_INT32
shape: (-1, 64)
name: serving_default_input_mask:0
The given SavedModel SignatureDef contains the following output(s):
outputs['dense_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 5)
name: StatefulPartitionedCall:0
我们看到模型期望两个大小为 64 的输入向量,即input_ids和input_mask向量的max_seq_length,并返回大小为 5 的输出向量,即star_rating的五个类别。输出表示五个类别的置信度分布。最有信心的预测将是我们的star_rating预测。
让我们使用saved_model_cli来使用示例数据(全零向量)进行快速预测,以验证模型接受预期输入大小的两个向量,并返回预期输出大小的一个向量。这里实际的输入和输出值并不重要,我们只是测试网络以确保模型接受预期输入和输出:
saved_model_cli run --dir '$tensorflow_model_dir' \
--tag_set serve \
--signature_def serving_default \
--input_exprs \ 'input_ids=np.zeros((1,64));input_mask=np.zeros((1,64))'
### OUTPUT ###
Result for output key dense_1:
[[0.5148565 0.50950885 0.514237 0.5389632 0.545161 ]]
展示我们 SageMaker 训练任务的实验血统
一旦完成超参数调整作业,我们可以直接在我们的笔记本或通过 SageMaker Studio 分析结果。
让我们总结到目前为止的实验血统。在第八章中,我们将调整超参数,并扩展实验血统以包括超参数优化。在第九章中,我们将部署模型,并进一步扩展实验血统以包括模型部署。我们将在第十章中将所有内容整合成端到端的流水线,并进行全程血统跟踪:
from sagemaker.analytics import ExperimentAnalytics
lineage_table = ExperimentAnalytics(
sagemaker_session=sess,
experiment_name=experiment_name,
metric_names=['validation:accuracy'],
sort_by="CreationTime",
sort_order="Ascending",
)
lineage_table.dataframe()
| TrialComponentName | DisplayName | max_seq_length | learning_rate | train_accuracy | ... |
|---|---|---|---|---|---|
| TrialComponent-2021-01-09-062410-pxuy | prepare | 64.0 | NaN | NaN | ... |
| tensorflow-training-2021-01-09-06-24-12-989 | train | 64.0 | 0.00001 | 0.9394 | ... |
展示我们 SageMaker 训练任务的工件血统
我们可以展示已捕获的 SageMaker 训练任务的工件血统信息,用于优化我们的产品评论分类器:
import time
Import sagemaker
from sagemaker.lineage.visualizer import LineageTableVisualizer
viz = LineageTableVisualizer(sagemaker.session.Session())
df = viz.show(training_job_name='<TRAINING_JOB_NAME>')
输出应该类似于这样:
| 名称/来源 | 方向 | 类型 | 协会类型 | 血统类型 |
|---|---|---|---|---|
| s3://.../output/bert-test | 输入 | 数据集 | ContributedTo | artifact |
| s3://.../output/bert-validation | 输入 | 数据集 | ContributedTo | artifact |
| s3://.../output/bert-train | 输入 | 数据集 | ContributedTo | artifact |
| 76310.../tensorflow-training:2.3.1-gpu-py37 | 输入 | 图像 | ContributedTo | artifact |
| s3://.../output/model.tar.gz | 输出 | 模型 | 产出 | artifact |
SageMaker Lineage Tracking 自动记录了输入数据、输出工件和 SageMaker 容器镜像。协会类型显示输入数据ContributedTo这个流水线步骤。让我们在第八章和第九章中继续构建模型血统图,调整并部署模型。我们将在第十章中将所有内容整合成端到端的流水线,并进行全程血统跟踪。
评估模型
在我们训练和验证模型之后,我们可以使用剩余的保留测试数据集——测试数据集——来执行我们自己的预测并测量模型的性能。使用测试数据集测试模型有助于评估模型在未见数据上的泛化能力。因此,我们不应该将保留测试数据集用于训练或验证。基于测试结果,我们可能需要修改我们的算法、超参数或训练数据。此外,更多的训练数据和更多的多样化特征工程可能有助于改善我们的评估结果。
下面是使用 Keras API 在 TensorFlow 中评估模型的代码,与我们在前一节中训练和验证模型的方式类似:
test_batch_size = 128
test_steps = 1000
test_data_filenames = glob(os.path.join(test_data, '*.tfrecord'))
test_dataset = file_based_input_dataset_builder(
channel='test',
input_filenames=test_data_filenames,
pipe_mode=pipe_mode,
is_training=False,
drop_remainder=False,
batch_size=test_batch_size,
epochs=epochs,
steps_per_epoch=test_steps,
max_seq_length=max_seq_length)\
.map(select_data_and_label_from_record)
test_history = model.evaluate(test_dataset,
steps=test_steps,
callbacks=callbacks)
print(test_history)
test_history包含test_loss和test_accuracy,分别如下:
[0.17315794393, 0.50945542373]
从笔记本运行一些临时预测
我们还可以从笔记本中运行一些临时预测,以快速满足我们对模型健康状态的好奇心。以下是运行样本预测的相关代码片段:
import pandas as pd
import numpy as np
from transformers import DistilBertTokenizer
tokenizer =
DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
def predict(text):
encode_plus_tokens = tokenizer.encode_plus(
text,
pad_to_max_length=True,
max_length=max_seq_length,
truncation=True,
return_tensors='tf')
input_ids = encode_plus_tokens['input_ids']
input_mask = encode_plus_tokens['attention_mask']
outputs = model.predict(x=(input_ids, input_mask))
prediction = [{"label": config.id2label[item.argmax()], \
"score": item.max().item()} for item in outputs]
return prediction[0]
predict('This is great!')
predict('This is OK.')
predict('This is terrible.')
下面的输出显示了预测的label(1–5)以及每个预测label的置信度。在这种情况下,模型对于评论文本“This is great!”预测label为 5 的置信度为 92%:
{'label': 5, 'score': 0.92150515}
{'label': 3, 'score': 0.2807838}
{'label': 1, 'score': 0.48466408}
使用混淆矩阵分析我们的分类器
混淆矩阵是评估分类器性能的一种可视化方式。让我们创建一个混淆矩阵,通过比较预测和实际值来视觉检查测试结果。我们首先读取包含原始review_body文本的保留测试数据集:
import csv
df_test_reviews = pd.read_csv(
'./data/holdout_test.tsv.gz',
delimiter='\t',
quoting=csv.QUOTE_NONE,
compression='gzip')[['review_body', 'star_rating']]
df_test_reviews = df_test_reviews.sample(n=100,000)
接下来,我们使用predict函数计算预测的y_test数据集。我们将使用以下代码将其与观察到的值y_actual进行比较:
y_test = df_test_reviews['review_body'].map(predict)
y_actual = df_test_reviews['star_rating']
这导致了混淆矩阵显示在图 7-24。

图 7-24. 显示真实(实际)标签和预测标签的混淆矩阵。
使用 TensorBoard 可视化我们的神经网络
TensorBoard 是一个由 TensorFlow 社区维护的开源可视化和探索工具,用于提供有关 TensorFlow 模型训练的洞察。SageMaker 在模型训练期间捕获并保存 TensorBoard 指标到 S3 中。然后,我们可以使用 SageMaker Studio 笔记本直接从保存的 TensorBoard 指标的 S3 位置可视化这些指标,如图 7-25 所示。

图 7-25. TensorBoard 显示随时间变化的损失和准确性。
我们还可以检查我们的神经网络,如图 7-26 所示。

图 7-26. TensorBoard 显示 BERT 的 TensorFlow 图。
要从 SageMaker Notebook 运行 TensorBoard,只需使用pip install tensorboard进行安装,指向 S3 中的 TensorBoard 日志,并从笔记本终端启动进程,如下所示:
S3_REGION=<REGION> tensorboard --port 6006 \
--logdir s3://$bucket/$training_job_name/debug-output/events
使用我们的浏览器,我们可以安全地访问运行在我们的 SageMaker 笔记本中的 TensorBoard,方法如下:
https://<NOTEBOOK_NAME>.notebook.
注意
6006 端口是由创建 TensorBoard 的 Google 工程师之一选择的。这个端口是术语“goog”的倒过来写法!
在 SageMaker Studio 中监控指标
训练后,我们应该使用准确率等指标来评估模型的性能,以确定模型是否达到我们的业务目标。在我们的示例中,我们希望衡量我们的模型是否能从review_body正确预测star_rating。请注意,我们在同一 Keras 步骤中进行了训练和验证。
我们可以在整个训练过程中直接使用 SageMaker Studio 可视化我们的训练和验证指标,如图 7-27 所示。我们很早就可以直观地看到过拟合的发生,因此我们可能希望尽早停止训练作业以节省成本。

图 7-27. 直接在 SageMaker Studio 内监控训练和验证指标。
使用 CloudWatch 指标监控指标
我们还可以在 CloudWatch 中将我们的模型指标与 CPU、GPU 和内存利用等系统指标一起可视化。图 7-28 显示了 CloudWatch 中训练和验证准确率指标与系统指标的仪表板。

图 7-28. CloudWatch 中训练和验证准确率指标的仪表板。
使用 SageMaker Debugger 调试和分析模型训练
在训练期间,我们可以使用 SageMaker Debugger 全面了解模型训练的情况,通过监视、记录和分析每个模型训练作业的状态,而无需进行任何代码更改。当我们检测到某些条件(如过拟合)时,我们可以使用 SageMaker Debugger 提前停止训练作业以节省成本。
使用 SageMaker Debugger,我们可以交互式地和视觉化地探索训练期间捕获的数据,包括张量、梯度和资源利用情况。SageMaker Debugger 捕获这些调试和分析信息,适用于单实例训练作业以及多实例分布式训练集群。
使用 SageMaker Debugger 规则和操作检测和解决问题
结合 CloudWatch Events,如果满足特定规则条件,SageMaker Debugger 可以触发警报,例如坏的训练数据、消失的梯度和爆炸的梯度。 坏数据包括 NaN 和 null 值。 消失的梯度发生在当非常小的值乘以其他非常小的值时,结果对于我们的float数据类型来说太小而无法存储。 爆炸的梯度是消失梯度的相反。 它们发生在当非常大的值乘以其他非常大的值时,结果不能由我们float数据类型的 32 位表示。 在深度神经网络中,由于在各层中进行的矩阵乘法的数量,消失和爆炸的梯度都可能发生。 当小数乘以其他小数时,它们最终会接近零,并且不能再用 32 位float表示。
如果 SageMaker Debugger 在凌晨 3 点触发警报,例如,SageMaker 可以自动停止训练作业。 SageMaker 还可以向负责的数据科学家发送电子邮件或短信以调查问题。 然后,数据科学家将使用 SageMaker Debugger 分析训练运行,可视化张量,查看 CPU 和 GPU 系统指标,并确定警报的根本原因。
除了消失和爆炸的梯度外,SageMaker Debugger 还支持常见调试场景的内置规则,例如loss_not_decreasing、overfit、overtraining和class_imbalance。 SageMaker 为每个指定的 SageMaker 规则启动评估作业。 我们还可以通过提供 Docker 镜像和Rule框架的实现来提供自己的规则。
以下是创建两个规则以检测训练损失停止以充分速率减少(loss_not_decreasing)和模型在经过正常下降行为的若干步骤后开始过拟合时的代码。 这两者都是“提前停止”训练作业的信号,减少整体训练作业的成本,避免过度拟合我们的模型到训练数据集,并允许模型在新的、未见过的数据上更好地泛化。 规则配置了阈值,以定义规则何时触发以及规则触发时采取的操作:
from sagemaker.debugger import Rule
from sagemaker.debugger import rule_configs
from sagemaker.debugger import CollectionConfig
from sagemaker.debugger import DebuggerHookConfig
actions=rule_configs.ActionList(
rule_configs.StopTraining(),
rule_configs.Email("<EMAIL_ADDRESS>"),
rule_configs.SMS("<PHONE_NUMBER>")
)
rules=[
Rule.sagemaker(
base_config=rule_configs.loss_not_decreasing(),
rule_parameters={
'collection_names': 'losses,metrics',
'use_losses_collection': 'true',
'num_steps': '10',
'diff_percent': '50'
},
collections_to_save=[
CollectionConfig(name='losses',
parameters={
'save_interval': '10',
}),
CollectionConfig(name='metrics',
parameters={
'save_interval': '10',
})
],
actions=actions
),
Rule.sagemaker(
base_config=rule_configs.overtraining(),
rule_parameters={
'collection_names': 'losses,metrics',
'patience_train': '10',
'patience_validation': '10',
'delta': '0.5'
},
collections_to_save=[
CollectionConfig(name='losses',
parameters={
'save_interval': '10',
}),
CollectionConfig(name='metrics',
parameters={
'save_interval': '10',
})
],
actions=actions
)
]
我们还需要创建一个调试器钩子,以便在 TensorFlow 2.x 中与 Keras API 一起使用,如下所示:
hook_config = DebuggerHookConfig(
hook_parameters={
'save_interval': '10', # number of steps
'export_tensorboard': 'true',
'tensorboard_dir': 'hook_tensorboard/',
})
然后,我们需要在我们的 Estimator 中设置规则和调试器钩子,如下所示:
from sagemaker.tensorflow import TensorFlow
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
...
rules=rules,
debugger_hook_config=hook_config,
...
)
配置训练作业
让我们配置一些ProfileRule来分析 CPU、GPU、网络和磁盘 I/O 指标,并为我们的训练作业生成一个ProfilerReport。 在这里,我们正在从调试部分添加更多到我们现有的rules列表:
from sagemaker.debugger import Rule
from sagemaker.debugger import rule_configs
from sagemaker.debugger import ProfilerRule
rules=[
Rule.sagemaker(...),
ProfilerRule.sagemaker(rule_configs.ProfilerReport()),
ProfilerRule.sagemaker(rule_configs.BatchSize()),
ProfilerRule.sagemaker(rule_configs.CPUBottleneck()),
ProfilerRule.sagemaker(rule_configs.GPUMemoryIncrease()),
ProfilerRule.sagemaker(rule_configs.IOBottleneck()),
ProfilerRule.sagemaker(rule_configs.LoadBalancing()),
ProfilerRule.sagemaker(rule_configs.LowGPUUtilization()),
ProfilerRule.sagemaker(rule_configs.OverallSystemUsage()),
ProfilerRule.sagemaker(rule_configs.StepOutlier())
]
然后,我们需要创建一个ProfilerConfig并将其传递给我们的 Estimator,如下所示:
from sagemaker.debugger import ProfilerConfig, FrameworkProfile
profiler_config = ProfilerConfig(
system_monitor_interval_millis=500,
framework_profile_params=FrameworkProfile(
local_path="/opt/ml/output/profiler/",
start_step=5,
num_steps=10)
)
from sagemaker.tensorflow import TensorFlow
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src',
...
py_version='<PYTHON_VERSION>',
framework_version='<TENSORFLOW_VERSION>',
hyperparameters={...},
rules=rules,
debugger_hook_config=hook_config,
profiler_config=profiler_config,
图 7-29 显示了 SageMaker Debugger 在我们的训练运行期间生成的概要报告。此报告包括建议增加批次大小以提高 GPU 利用率、加快训练作业速度和减少成本。

图 7-29. SageMaker Debugger 深度分析模型训练作业。
解释和说明模型预测
我们还可以使用 SageMaker Debugger 跟踪训练过程中的梯度、层和权重。我们将使用它来监视 BERT 注意力机制在模型训练期间的情况。通过了解模型的学习过程,我们可以更好地识别模型偏差,并可能解释模型预测。为此,我们需要捕获张量,包括注意力分数、查询向量和键向量作为 SageMaker Debugger “集合”。然后,可以使用这些信息来绘制注意力头部和查询键向量中的单个神经元。让我们创建我们的 DebuggerHookConfig 和 CollectionConfig,使用正则表达式在训练期间的特定间隔捕获注意力张量:
debugger_hook_config = DebuggerHookConfig(
s3_output_path=s3_bucket_for_tensors,
collection_configs=[
CollectionConfig(
name="all",
parameters={
"include_regex":
".*multiheadattentioncell0_output_1|.*key_output|.*query_output",
"train.save_steps": "0",
"eval.save_interval": "1"}
)]
)
我们还在训练脚本的验证循环中添加了以下行来记录输入标记的字符串表示:
if hook.get_collections()['all'].save_config\
.should_save_step(modes.EVAL, hook.mode_steps[modes.EVAL]):
hook._write_raw_tensor_simple("input_tokens", input_tokens)
为了可视化结果,我们创建一个指向捕获张量的试验:
from smdebug.trials import create_trial
trial = create_trial( path )
我们将使用一个脚本,使用 Bokeh,一个交互式可视化库,绘制注意力头部:
from utils import attention_head_view, neuron_view
from ipywidgets import interactive
让我们获取注意力分数的张量名称:
tensor_names = []
for tname in sorted(trial.tensor_names(regex='.*multiheadattentioncell0_output_1'):
tensor_names.append(tname)
由于我们使用的是具有 12 个注意力头的 BERT 模型,因此张量名称应该类似于这样:
['bertencoder0_transformer0_multiheadattentioncell0_output_1',
'bertencoder0_transformer10_multiheadattentioncell0_output_1',
'bertencoder0_transformer11_multiheadattentioncell0_output_1',
'bertencoder0_transformer1_multiheadattentioncell0_output_1',
'bertencoder0_transformer2_multiheadattentioncell0_output_1',
'bertencoder0_transformer3_multiheadattentioncell0_output_1',
'bertencoder0_transformer4_multiheadattentioncell0_output_1',
'bertencoder0_transformer5_multiheadattentioncell0_output_1',
'bertencoder0_transformer6_multiheadattentioncell0_output_1',
'bertencoder0_transformer7_multiheadattentioncell0_output_1',
'bertencoder0_transformer8_multiheadattentioncell0_output_1',
'bertencoder0_transformer9_multiheadattentioncell0_output_1']
接下来,我们迭代可用张量并检索张量值:
steps = trial.steps(modes.EVAL)
tensors = {}
for step in steps:
print("Reading tensors from step", step)
for tname in tensor_names:
if tname not in tensors:
tensors[tname]={}
tensors[tname][step] = trial.tensor(tname).value(step, modes.EVAL)
num_heads = tensors[tname][step].shape[1]
接下来,我们检索查询和键输出张量名称:
ayers = []
layer_names = {}
for index, (key, query) in enumerate(
zip(trial.tensor_names(regex='.*key_output_'),
trial.tensor_names(regex='.*query_output_'))):
layers.append([key,query])
layer_names[key.split('_')[1]] = index
我们还检索输入标记的字符串表示:
input_tokens = trial.tensor('input_tokens').value(0, modes.EVAL)
现在我们可以绘制注意力头部,显示不同标记之间的注意力分数。线条越粗,分数越高。让我们使用以下代码绘制前 20 个标记,摘要见 图 7-30:
n_tokens = 20
view = attention_head_view.AttentionHeadView(input_tokens,
tensors,
step=trial.steps(modes.EVAL)[0],
layer='bertencoder0_transformer0_multiheadattentioncell0_output_1',
n_tokens=n_tokens)

图 7-30. 前 20 个标记的注意力头部视图。来源:“在基于 Transformer 的语言表示模型中可视化注意力”。
接下来,我们检索查询和键向量张量:
queries = {}
steps = trial.steps(modes.EVAL)
for step in steps:
print("Reading tensors from step", step)
for tname in trial.tensor_names(regex='.*query_output'):
query = trial.tensor(tname).value(step, modes.EVAL)
query = query.reshape((query.shape[0], query.shape[1], num_heads, -1))
query = query.transpose(0,2,1,3)
if tname not in queries:
queries[tname] = {}
queries[tname][step] = query
keys = {}
steps = trial.steps(modes.EVAL)
for step in steps:
print("Reading tensors from step", step)
for tname in trial.tensor_names(regex='.*key_output'):
key = trial.tensor(tname).value(step, modes.EVAL)
key = key.reshape((key.shape[0], key.shape[1], num_heads, -1))
key = key.transpose(0,2,1,3)
if tname not in keys:
keys[tname] = {}
keys[tname][step] = key
有了张量值,我们可以绘制详细的神经元视图:
view = neuron_view.NeuronView(input_tokens,
keys=keys,
queries=queries,
layers=layers,
step=trial.steps(modes.EVAL)[0],
n_tokens=n_tokens,
layer_names=layer_names)
所得可视化结果显示在 图 7-31 中。

图 7-31. 查询和键向量的神经元视图。来源:“在基于 Transformer 的语言表示模型中可视化注意力”。
在此可视化中,颜色越深,神经元对注意力分数的影响越大。
如前所述,BERT 注意力的可视化有助于识别不正确模型预测的根本原因。目前,行业内就是否可以使用注意力进行模型可解释性存在激烈的辩论。用于模型可解释性的更流行的方法是基于梯度的工具包,例如生成显著性地图的工具包 AllenNLP Interpret。显著性地图标识哪些输入标记对模型预测产生了最大影响,可能是 NLP 模型解释性的更直接方法。让我们使用 AllenNLP 演示网站 来创建一个显著性地图,以预测以下评论文本的情感:“一个非常精心制作的、有趣和令人娱乐的图片”。图 7-32 显示了导致“正面”预测的前 10 个最重要的词。

图 7-32。使用 AllenNLP Interpret 对情感分析预测的前 10 个最重要的词进行可视化。
我们可以通过使用 pip install allennlp 安装 AllenNLP,将 AllenNLP 显著性地图集成到我们的 Python 应用程序中。在接下来的过程中,我们正在计算集成梯度,这是每个标记对预测影响的一种度量。我们特别使用了称为 RoBERTa 的 BERT 变体,但 AllenNLP 支持许多 BERT 的变体:
from pprint import pprint
from allennlp.predictors.predictor import Predictor
from allennlp.interpret.saliency_interpreters import IntegratedGradient
predictor = Predictor.from_path(
"https://.../allennlp-public-models/sst-roberta-large-2020.06.08.tar.gz"
)
integrated_gradient_interpreter = IntegratedGradient(predictor)
sentence = "a very well-made, funny and entertaining picture."
integrated_gradient_interpretation = \
integrated_gradient_interpreter.saliency_interpret_from_json(inputs)
pprint(integrated_gradient_interpretation)
输出如下所示:
{'instance_1': {'grad_input_1': [0.10338538634781776,
0.19893729477254535,
0.008472852427212439,
0.0005615125409780962,
0.01615882936970941,
0.19841675479930443,
0.06983715792756516,
0.02557800239689324,
0.06044705677145928,
0.16507210055696683,
0.1531329783765724]}}
检测模型偏差并解释预测
即使是一个无偏的数据集,仍然存在训练一个偏置模型的可能性。这听起来令人惊讶,但某些超参数可能会偏向于与相同特征的其他方面不同的输入特征的特定方面。此外,当使用存在偏见的预训练模型进行微调时,我们应当小心。例如,BERT 由于其训练数据的类型而存在偏见。由于该模型能够从上下文中学习,BERT 获取了维基百科训练数据的统计特性,包括任何表达的偏见和社会刻板印象。正如我们在努力减少社会中的偏见和刻板印象一样,我们还应该实施机制来检测和阻止这种偏见传播到我们的模型中。
SageMaker Clarify 帮助我们在机器学习管道的每个步骤中检测偏差并评估模型的公平性。我们在第五章中看到如何使用 SageMaker Clarify 检测数据集中的偏差和类别不平衡。现在我们使用 SageMaker Clarify 来分析我们训练过的模型。
对于后训练偏差分析,SageMaker Clarify 与 SageMaker Experiments 集成。SageMaker Clarify 将审查训练数据、标签和模型预测,并运行一组算法来计算常见的数据和模型偏差度量。我们还可以使用 SageMaker Clarify 通过分析特征重要性来解释模型预测。
使用 SageMaker Clarify 处理作业检测偏差
类似于预训练偏差分析,我们可以将 SageMaker Clarify 作为处理作业运行,以计算后训练数据和模型偏差指标。计算后训练偏差指标需要一个经过训练的模型,因为分析现在包括数据、标签和模型预测结果。我们在ModelConfig中定义我们的训练模型,并在ModelPredictedLabelConfig中指定模型预测格式。SageMaker Clarify 通过比较模型预测结果与训练数据中的标签,针对所选的方面执行后训练偏差分析。
提供的训练数据必须与模型期望的推理输入以及标签列匹配。我们选择仅使用review_body作为单个输入特征来训练我们的模型。然而,在这个例子中,我们添加了product_category作为第二个特征,并重新训练了我们的模型。我们使用product_category作为分析偏差和不平衡的方面,跨礼品卡、数字软件和数字视频游戏进行分析:
from sagemaker import clarify
clarify_processor = clarify.SageMakerClarifyProcessor(
role=role,
instance_count=1,
instance_type='ml.c5.2xlarge',
sagemaker_session=sess)
bias_report_output_path = 's3://{}/clarify'.format(bucket)
data_config = clarify.DataConfig(
s3_data_input_path=train_dataset_s3_uri,
s3_output_path=bias_report_output_path,
label='star_rating',
headers=['product_category', 'review_body'],
dataset_type='text/csv')
bias_config = clarify.BiasConfig(
label_values_or_threshold=[5,4]
facet_name='product_category',
facet_values_or_threshold=['Gift Card'],
group_name='product_category')
在ModelConfig中,我们定义了我们的训练模型,并指定了用于影子端点的实例类型和数量,SageMaker Clarify 会创建这些影子端点:
model_config = clarify.ModelConfig(
model_name=model_name,
instance_type='ml.m5.4xlarge',
instance_count=1,
content_type='text/csv',
accept_type='application/jsonlines')
ModelPredictedLabelConfig定义了如何解析和读取模型预测结果。我们可以指定label、probability、probability_threshold和label_headers。如果我们的模型返回类似于{"predicted_label": 5}的 JSON 输出,我们可以通过设置label='predicted_label'来解析预测结果。如果模型输出与训练数据提供的标签类型和格式匹配,我们可以简单地保持不变:
predictions_config = clarify.ModelPredictedLabelConfig(label='predicted_label')
clarify_processor.run_post_training_bias(
data_config=data_config,
data_bias_config=bias_config,
model_config=model_config,
model_predicted_label_config=predictions_config,
methods=['DPPL', 'DI', 'DCA', 'DCR', 'RD', \
'DAR', 'DRR', 'AD', 'CDDPL', 'TE'],
wait=True)
在methods中,我们可以选择计算哪些后训练偏差指标。
在我们的例子中,我们可以分析我们的模型是否对特定产品类别(方面)值预测更多的负面星级评分,例如数字软件,与其他方面值如礼品卡相比。其中一个相关的偏差指标是条件拒绝差异(DCR),它比较了每个方面(product_category)的标签与预测标签在负面分类(拒绝)上的差异。在这种情况下,我们可以定义star_rating==5和star_rating==4作为正面结果,而其他类别作为负面结果。
注意
后训练指标包括预测标签中正比例的差异、不公平影响、条件接受差异、条件拒绝差异、召回差异、接受率差异、拒绝率差异、准确率差异、处理平等性、预测标签中的条件人口统计差异以及反事实翻转测试。
SageMaker Clarify 通过验证提供的配置输入和输出参数来启动后训练偏倚分析。然后,SageMaker Clarify 创建一个临时的影子 SageMaker 模型端点,并部署训练好的模型。处理作业然后计算定义的偏倚度量。表 7-1 显示了我们模型的计算后训练偏倚度量。作业完成后,SageMaker Clarify 生成输出文件并删除影子端点。
表 7-1. 后训练偏倚度量分析结果
| name | 描述 | 值 |
|---|---|---|
| AD | 准确率差异(AD) | -0.25 |
| CDDPL | 预测标签中条件人口统计差异(CDDPL) | -0.333333 |
| DAR | 接受率差异(DAR) | -0.444444 |
| DCA | 条件接受差异(DCA) | -0.333333 |
| DCR | 条件拒绝差异(DCR) | -1.27273 |
| DI | 不平等影响(DI) | 2.22222 |
| DPPL | 预测标签中正比例的差异(DPPL) | -0.55 |
| DRR | 拒绝率差异(DRR) | -0.909091 |
| RD | 召回率差异(RD) | -0.166667 |
| TE | 处理平等性(TE) | -0.25 |
此外,SageMaker Clarify 生成了analysis.json与偏倚度量,以及report.ipynb用于可视化偏倚度量并与同事分享。处理工作还生成了一个偏倚基线,我们将与 SageMaker Model Monitor 一起使用,以检测在线模型端点上偏倚的漂移。我们将在第九章中详细描述这一点。
使用 SageMaker Clarify 和 SHAP 进行特征归因和重要性分析
SageMaker Clarify 还支持 SHAP,这是从博弈论引入到机器学习内容中的概念,用于确定每个特征对模型预测的贡献。我们可以使用这些信息来选择特征或创建新的特征组合。以下是使用 SageMaker Clarify 处理作业执行特征归因和模型可解释性的代码:
from sagemaker import clarify
shap_config = clarify.SHAPConfig(
baseline=[shap_dataset.iloc[0].values.tolist()],
num_samples=15,
agg_method='mean_abs')
explainability_output_path = 's3://{}/clarify'.format(bucket)
explainability_data_config = clarify.DataConfig(
s3_data_input_path=train_dataset_s3_uri,
s3_output_path=explainability_output_path,
label='star_rating',
headers=['product_category', 'review_body'],
dataset_type='text/csv')
clarify_processor.run_explainability(
data_config=explainability_data_config,
model_config=model_config,
model_score='predicted_label',
explainability_config=shap_config)
除了analysis.json和report.ipynb,处理工作还生成了explanations_shap/out.csv,其中包含数据集中每个特征和预测标签的 SHAP 值。以下是analysis.json中关于特征归因和解释的相关片段:
"explanations": {
"kernel_shap": {
"star_rating": {
"global_shap_values": {
"product_category": 0.04999999999999998,
"review_body": 1.3833333333333333
},
"expected_value": 2.0
}
}
}
我们还可以在 SageMaker Studio 中看到每个特征的聚合 SHAP 值的可视化,如图 7-33 所示。这代表了每个特征对预测的重要性。

图 7-33. SageMaker Studio 展示的特征重要性。
处理工作还生成一个解释性基准,我们将与 SageMaker Model Monitor 一起使用,以检测在线模型端点上特征归因和模型可解释性的漂移。我们将在第九章中详细描述这一点。
BERT 的更多训练选项
尽管本书使用了大量的 TensorFlow 示例,SageMaker 支持其他流行的 AI 和机器学习框架,包括 PyTorch 和 Apache MXNet,正如我们将在接下来的几节中讨论的那样。我们还将演示如何使用 AWS 开源的Deep Java Library在 Java 中训练深度学习模型。这对于希望将深度学习集成到基于 Java 的应用程序中的企业非常有用。
将 TensorFlow BERT 模型转换为 PyTorch
在某些情况下,我们可能希望尝试不同的框架,以查看是否能获得更好的训练或推断性能。由于我们使用流行的 Transformers 库进行 BERT,我们可以在几行代码中将我们的模型从 TensorFlow 转换为 PyTorch:
# Import the PyTorch version of DistilBert (without the TF prefix)
from transformers import DistilBertForSequenceClassification
# Using from_tf=True to load the model from TensorFlow to PyTorch
loaded_pytorch_model =
DistilBertForSequenceClassification.from_pretrained(
tensorflow_model_path, from_tf=True)
# Save the model as PyTorch
loaded_pytorch_model.save_pretrained(pytorch_models_dir)
注意
我们也可以将 PyTorch 模型转换为 TensorFlow。这是 Transformers 库的一个功能,不适用于非基于 Transformers 的 PyTorch 和 TensorFlow 模型。
在转换模型之后,我们得到了与 TensorFlow 训练的相同模型的 PyTorch 版本,使用相同的权重。我们将使用 TorchServe 运行时在第九章中部署此 PyTorch 模型。TorchServe 由 AWS、Facebook 和 PyTorch 社区共同构建和优化,用于提供 PyTorch 模型预测并在 AWS 的弹性基础设施上扩展:
print(loaded_pytorch_model)
### OUTPUT ###
DistilBertForSequenceClassification(
(distilbert): DistilBertModel(
(embeddings): Embeddings(
(word_embeddings): Embedding(30522, 768, padding_idx=0)
(position_embeddings): Embedding(512, 768)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(transformer): Transformer(
(layer): ModuleList(
(0): TransformerBlock(
(attention): MultiHeadSelfAttention(
(dropout): Dropout(p=0.1, inplace=False)
(q_lin): Linear(in_features=768, out_features=768, bias=True)
(k_lin): Linear(in_features=768, out_features=768, bias=True)
(v_lin): Linear(in_features=768, out_features=768, bias=True)
(out_lin): Linear(in_features=768, out_features=768, bias=True)
)
(sa_layer_norm): LayerNorm((768,), eps=1e-12, \
elementwise_affine=True)
(ffn): FFN(
(dropout): Dropout(p=0.1, inplace=False)
(lin1): Linear(in_features=768, out_features=3072, bias=True)
(lin2): Linear(in_features=3072, out_features=768, bias=True)
)
(output_layer_norm): LayerNorm((768,), eps=1e-12, \
elementwise_affine=True)
)
...
)
)
)
(pre_classifier): Linear(in_features=768, out_features=768, bias=True)
(classifier): Linear(in_features=768, out_features=5, bias=True)
...
)
使用 SageMaker 训练 PyTorch BERT 模型
PyTorch 是一个流行的深度学习框架,拥有来自多家公司(如 Facebook 和 AWS)的大量贡献者社区。PyTorch 在 SageMaker 中得到了原生支持,包括分布式模型训练、调试、性能分析、超参数调整和模型推断端点。以下是在 SageMaker 上训练 DistilBERT PyTorch 模型并将代码保存到 S3 以部署在第九章中的代码片段。本书的 GitHub 存储库中提供了完整的代码:
from sagemaker.pytorch import PyTorch
estimator = PyTorch(
entry_point='train.py',
source_dir='src',
role=role,
instance_count=train_instance_count,
instance_type=train_instance_type,
volume_size=train_volume_size,
py_version='<PYTHON_VERSION>',
framework_version='<PYTORCH_VERSION>',
hyperparameters=hyperparameters,
metric_definitions=metric_definitions,
input_mode=input_mode,
debugger_hook_config=debugger_hook_config
)
estimator.fit(inputs={'train': s3_input_train_data,
'validation': s3_input_validation_data,
'test': s3_input_test_data
},
experiment_config=experiment_config,
wait=False)
以下是 Python train.py脚本,设置网络并训练模型。请注意,我们使用的是 PyTorch 的DistilBertForSequenceClassification,而不是使用TF前缀来区分实现的 TensorFlow 的TFDistilBertForSequenceClassification:
import torch
import torch.distributed as dist
import torch.nn as nn
import torch.optim as optim
import torch.utils.data
PRE_TRAINED_MODEL_NAME = 'distilbert-base-uncased'
tokenizer = DistilBertTokenizer.from_pretrained(
PRE_TRAINED_MODEL_NAME)
config = DistilBertConfig.from_pretrained(PRE_TRAINED_MODEL_NAME,
num_labels=len(CLASS_NAMES),
id2label={0: 1, 1: 2, 2: 3, 3: 4, 4: 5},
label2id={1: 0, 2: 1, 3: 2, 4: 3, 5: 4}
)
config.output_attentions=True
model = DistilBertForSequenceClassification.from_pretrained(
PRE_TRAINED_MODEL_NAME, config=config)
device = torch.device('cuda' if use_cuda else 'cpu')
model.to(device)
ds_train = ReviewDataset(
reviews=df_train.review_body.to_numpy(),
targets=df_train.star_rating.to_numpy(),
tokenizer=tokenizer,
max_seq_len=max_seq_len
)
train_data_loader = DataLoader(
ds_train,
batch_size=batch_size,
shuffle=True
)
loss_function = nn.CrossEntropyLoss()
optimizer = optim.Adam(params=model.parameters(), lr=args.lr)
for epoch in range(args.epochs):
for i, (sent, label) in enumerate(train_data_loader):
model.train()
optimizer.zero_grad()
sent = sent.squeeze(0)
if torch.cuda.is_available():
sent = sent.cuda()
label = label.cuda()
output = model(sent)[0]
_, predicted = torch.max(output, 1)
loss = loss_function(output, label)
loss.backward()
optimizer.step()
...
torch.save(model.state_dict(), save_path)
使用 SageMaker 训练 Apache MXNet BERT 模型
MXNet 是另一个流行的深度学习框架,在 Amazon 和 AWS 内部广泛使用于许多不同的用例,包括需求预测、航运物流、基础设施资源优化、自然语言处理、计算机视觉、欺诈检测等。MXNet 在 SageMaker 中得到了原生支持,包括分布式训练、调试、性能分析、超参数调整和模型推断端点。以下是使用 MXNet 训练 BERT 模型的代码:
from sagemaker.mxnet import MXNet
estimator = MXNet(
entry_point='train.py',
source_dir='src',
role=role,
instance_count=train_instance_count,
instance_type=train_instance_type,
volume_size=train_volume_size,
py_version='<PYTHON_VERSION>',
framework_version='<MXNET_VERSION>',
hyperparameters=hyperparameters,
metric_definitions=metric_definitions,
input_mode=input_mode,
debugger_hook_config=debugger_hook_config
)
estimator.fit(inputs={'train': s3_input_train_data,
'validation': s3_input_validation_data,
'test': s3_input_test_data
},
experiment_config=experiment_config,
wait=False)
使用 PyTorch 和 AWS 的 Deep Java 库训练 BERT 模型
虽然 Python 和 C 是数据科学的主要语言,但有些情况需要与自 1990 年代以来编写的数十亿行 Java 代码集成。此外,许多大数据框架如 Apache Hadoop、Spark 和 ElasticSearch 都是用 Java 实现的。以下是一系列代码片段,演示如何使用 AWS Deep Learning Java 使用 Java Native Interface 从 Java 调用 TensorFlow、PyTorch 和 Apache MXNet 库来训练 BERT 模型。这些示例来自于 Deep Java Library GitHub 仓库。
首先,让我们定义大量的导入:
import ai.djl.*;
import ai.djl.engine.Engine;
import ai.djl.basicdataset.CsvDataset;
import ai.djl.basicdataset.utils.DynamicBuffer;
import ai.djl.modality.nlp.SimpleVocabulary;
import ai.djl.modality.nlp.bert.BertFullTokenizer;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.repository.zoo.*;
import ai.djl.training.*;
import ai.djl.training.dataset.Batch;
import ai.djl.training.dataset.RandomAccessDataset;
import ai.djl.training.evaluator.Accuracy;
import ai.djl.training.listener.CheckpointsTrainingListener;
import ai.djl.training.listener.TrainingListener;
接下来,我们定义一个 Java 类来将原始文本转换为 BERT 嵌入:
final class BertFeaturizer implements CsvDataset.Featurizer {
private final BertFullTokenizer tokenizer;
private final int maxLength; // the cut-off length
public BertFeaturizer(BertFullTokenizer tokenizer, int maxLength) {
this.tokenizer = tokenizer;
this.maxLength = maxLength;
}
@Override
public void featurize(DynamicBuffer buf, String input) {
SimpleVocabulary vocab = tokenizer.getVocabulary();
// convert sentence to tokens (toLowerCase for uncased model)
List<String> tokens = tokenizer.tokenize(input.toLowerCase());
// trim the tokens to maxLength
tokens = tokens.size() > maxLength ?
tokens.subList(0, maxLength) : tokens;
// BERT embedding convention "[CLS] Your Sentence [SEP]"
buf.put(vocab.getIndex("[CLS]"));
tokens.forEach(token -> buf.put(vocab.getIndex(token)));
buf.put(vocab.getIndex("[SEP]"));
}
}
让我们定义一个函数来检索我们的亚马逊客户评论数据集。在本例中,我们使用 Digital_Software 产品类别:
CsvDataset getDataset(int batchSize, BertFullTokenizer tokenizer, int maxLength){
String amazonReview =
"https://s3.amazonaws.com/amazon-reviews-
pds/tsv/amazon_reviews_us_Digital_Software_v1_00.tsv.gz";
float paddingToken = tokenizer.getVocabulary().getIndex("[PAD]");
return CsvDataset.builder()
.optCsvUrl(amazonReview) // load from Url
.setCsvFormat(CSVFormat.TDF.withQuote(null).withHeader())
.setSampling(batchSize, true) // make sample size and random access
.addFeature(
new CsvDataset.Feature(
"review_body", new BertFeaturizer(tokenizer,
maxLength)))
.addLabel(
new CsvDataset.Feature(
"star_rating", (buf, data) ->
buf.put(Float.parseFloat(data) - 1.0f)))
.optDataBatchifier(
PaddingStackBatchifier.builder()
.optIncludeValidLengths(false)
.addPad(0, 0, (m) ->
m.ones(new Shape(1)).mul(paddingToken))
.build())
.build();
}
现在我们从 Deep Java Library 模型仓库中获取一个预训练的 DistilBERT PyTorch 模型:
String modelUrls =
"https://resources.djl.ai/test-models/traced_distilbert_wikipedia_uncased.zip";
}
Criteria<NDList, NDList> criteria = Criteria.builder()
.optApplication(Application.NLP.WORD_EMBEDDING)
.setTypes(NDList.class, NDList.class)
.optModelUrls(modelUrls)
.optProgress(new ProgressBar())
.build();
ZooModel<NDList, NDList> embedding = ModelZoo.loadModel(criteria);
让我们构建我们的模型,用我们的亚马逊客户评论数据集来微调 DistilBERT:
Predictor<NDList, NDList> embedder = embedding.newPredictor();
Block classifier = new SequentialBlock()
// text embedding layer
.add(
ndList -> {
NDArray data = ndList.singletonOrThrow();
NDList inputs = new NDList();
long batchSize = data.getShape().get(0);
float maxLength = data.getShape().get(1);
if ("PyTorch".equals(Engine.getInstance().getEngineName())) {
inputs.add(data.toType(DataType.INT64, false));
inputs.add(data.getManager().full(data.getShape(), 1,
DataType.INT64));
inputs.add(data.getManager().arange(maxLength)
.toType(DataType.INT64, false)
.broadcast(data.getShape()));
} else {
inputs.add(data);
inputs.add(data.getManager().full(new Shape(batchSize),
maxLength));
}
// run embedding
try {
return embedder.predict(inputs);
} catch (TranslateException e) {
throw new IllegalArgumentException("embedding error", e);
}
})
// classification layer
.add(Linear.builder().setUnits(768).build()) // pre classifier
.add(Activation::relu)
.add(Dropout.builder().optRate(0.2f).build())
.add(Linear.builder().setUnits(5).build()) // 5 star rating
.addSingleton(nd -> nd.get(":,0")); // Take [CLS] as the head
Model model = Model.newInstance("AmazonReviewRatingClassification");
model.setBlock(classifier);
最后,让我们将所有内容结合在一起,将我们的数据集转换为 BERT 嵌入,设置一个检查点回调监听器,并使用 Java 训练我们基于 BERT 的评论分类器!
// Prepare the vocabulary
SimpleVocabulary vocabulary = SimpleVocabulary.builder()
.optMinFrequency(1)
.addFromTextFile(embedding.getArtifact("vocab.txt"))
.optUnknownToken("[UNK]")
.build();
// Prepare dataset
int maxTokenLength = 64; // cutoff tokens length
int batchSize = 128;
BertFullTokenizer tokenizer = new BertFullTokenizer(vocabulary, true);
CsvDataset amazonReviewDataset = getDataset(batchSize, tokenizer, maxTokenLength);
RandomAccessDataset[] datasets = amazonReviewDataset.randomSplit(0.9, 0.1);
RandomAccessDataset trainingSet = datasets[0];
RandomAccessDataset validationSet = datasets[1];
CheckpointsTrainingListener listener =
new CheckpointsTrainingListener("build/model");
listener.setSaveModelCallback(
trainer -> {
TrainingResult result = trainer.getTrainingResult();
Model model = trainer.getModel();
// track for accuracy and loss
float accuracy = result.getValidateEvaluation("Accuracy");
model.setProperty("Accuracy", String.format("%.5f", accuracy));
model.setProperty("Loss", String.format("%.5f",
result.getValidateLoss()));
});
DefaultTrainingConfig config =
new DefaultTrainingConfig(Loss.softmaxCrossEntropyLoss())
.addEvaluator(new Accuracy())
.optDevices(Device.getDevices(1)) // train using single GPU
.addTrainingListeners(TrainingListener.Defaults.logging("build/model"))
.addTrainingListeners(listener);
int epoch = 2;
Trainer trainer = model.newTrainer(config);
trainer.setMetrics(new Metrics());
Shape encoderInputShape = new Shape(batchSize, maxTokenLength);
// initialize trainer with proper input shape
trainer.initialize(encoderInputShape);
EasyTrain.fit(trainer, epoch, trainingSet, validationSet);
System.out.println(trainer.getTrainingResult());
model.save(Paths.get("build/model"), "amazon-review.param");
我们可以使用自定义的 Translator 类运行一些样本预测,该类使用 DistilBERT 分词器将原始文本转换为 BERT 嵌入:
class MyTranslator implements Translator<String, Classifications> {
private BertFullTokenizer tokenizer;
private SimpleVocabulary vocab;
private List<String> ranks;
public MyTranslator(BertFullTokenizer tokenizer) {
this.tokenizer = tokenizer;
vocab = tokenizer.getVocabulary();
ranks = Arrays.asList("1", "2", "3", "4", "5");
}
@Override
public Batchifier getBatchifier() {return new StackBatchifier();}
@Override
public NDList processInput(TranslatorContext ctx, String input) {
List<String> tokens = tokenizer.tokenize(input);
float[] indices = new float[tokens.size() + 2];
indices[0] = vocab.getIndex("[CLS]");
for (int i = 0; i < tokens.size(); i++) {
indices[i+1] = vocab.getIndex(tokens.get(i));
}
indices[indices.length - 1] = vocab.getIndex("[SEP]");
return new NDList(ctx.getNDManager().create(indices));
}
@Override
public Classifications processOutput(TranslatorContext ctx, NDList list)
{
return new Classifications(ranks, list.singletonOrThrow().softmax(0));
}
}
String review = "It works great, but takes too long to update";
Predictor<String, Classifications> predictor =
model.newPredictor(new MyTranslator(tokenizer));
System.out.println(predictor.predict(review));
### OUTPUT ###
4
降低成本并提升性能
在本节中,我们提供如何通过硬件和基础架构优化(如减少精度和使用 Spot 实例)来提高性能和降低成本的技巧。此外,我们描述了在训练停止改进时如何提前停止训练作业。
使用小型笔记本实例
作为最佳实践,我们应该将所有基于 GPU 的重型计算放在 SageMaker 处理、训练或批处理转换作业中,而不是在我们的笔记本中进行。这帮助我们节省资金,因为我们可以使用较小的实例类型来运行较长时间的笔记本实例。如果发现我们在 SageMaker 笔记本中使用 GPU 实例类型,可以通过切换到更便宜的笔记本实例类型,并将基于 GPU 的计算移到 SageMaker 训练或处理作业中,在训练或处理作业的持续时间内仅支付 GPU 的费用,从而节省资金。
在笔记本中测试模型训练脚本
在我们的 SageMaker 训练作业中可以指定 instance_type='local' 来在 SageMaker 笔记本或我们的本地笔记本上运行脚本。这让我们可以在笔记本中“本地”运行训练作业,使用数据集的一个小子集进行一到两个 epochs 的训练。如果在笔记本中运行,我们应记住我们受限于笔记本实例的内存和计算资源。因此,当在笔记本实例内进行训练时,应该使用较小的批次大小和数据集的子集运行。
使用 SageMaker 调试器分析训练任务
Profiler 提供了对我们的训练任务瓶颈的宝贵见解,并提供了有用的建议来解决这些瓶颈。通常情况下,我们实际上并不是计算受限,而是 I/O 受限。SageMaker 调试器通过实际数据帮助我们识别这些较不直观的瓶颈,以帮助我们增加资源利用率,减少训练时间并降低成本。在这个例子中,SageMaker 调试器识别出一个 CPU 瓶颈,并建议我们添加更多的数据加载器或启用更积极的数据预取:
CPUBottleneck - Issue Found
CPU bottlenecks can happen when data preprocessing is very compute intensive.
You should consider increasing the number of data-loader processes or apply
pre-fetching.
Number of times the rule triggered: 16
Number of violations: 8090
Number of datapoints: 62020
Rule parameters:
threshold: 50%
cpu_threshold: 90%
gpu_threshold: 10%
patience: 1000
SageMaker 调试器还建议使用较小的实例或增加批处理大小,因为我们的 GPU 利用率较低:
BatchSize - Issue Found
Run on a smaller instance type or increase batch size
Number of times the rule triggered: 4072
Number of violations: 4072
Number of datapoints: 62012
Rule parameters:
cpu_threshold_p95: 70%
gpu_threshold_p95: 70%
gpu_memory_threshold_p95: 70%
patience: 1000
window: 500
LowGPUUtilization - Issue Found
Check for bottlenecks, minimize blocking calls, change distributed training
strategy, increase batch-size.
Number of times the rule triggered: 4072
Number of violations: 4072
Number of datapoints: 62013
Rule parameters:
threshold_p95: 70%
threshold_p5: 10%
window: 500
patience: 1000
- 使用预训练模型
调整预训练模型如 BERT 可以通过避免已为我们完成的任务节省大量时间和金钱。在我们的领域使用与 BERT 等选项大不相同的语言模型的一些情况下,我们可能需要从头开始训练一个模型。然而,我们应该首先尝试这些预训练模型,看看它们能为我们提供多大帮助。
使用 16 位半精度和 bfloat16
大多数模型使用完整的 32 位数值精度存储参数并进行计算。直觉上,如果我们将精度降低到 16 位或“减少”或“半精度”,不仅可以将存储参数的占用空间减少一半,而且由于芯片可以在同样的 32 位硬件上执行两个 16 位计算,计算性能也会提高 2 倍。
另一个降低精度的 16 位浮点数,bfloat16,是 float32 的截断版本,保留了 float32 的 8 位指数部分,但只留下了 7 位用于小数部分。请注意,bfloat 不符合 IEEE 标准;然而,在 ARM、Intel、Google 和 Amazon 等现代芯片中都得到了原生支持。图 7-34 显示了 float16、float32 和 bfloat16 的比较,包括用于表示指数和小数部分的位数。
降低精度也有其不利因素。在这个完美的世界中,虽然训练时间趋向于零,但准确性和数值不稳定性也会如此。通过将数值精度降低到 16 位,我们的模型可能学习能力不及 32 位模型。此外,由于模型只有 16 位来表示参数和梯度,我们可能会遇到更频繁的梯度消失现象。因此,数值值变为 0 的机会比使用 32 位要高得多。bfloat16通过保留 float32 的 8 位指数来减少梯度消失的可能性。我们还可以使用损失缩放策略来减少梯度消失的潜力。

图 7-34。float16、float32 和 bfloat 的比较。来源:维基百科。
在将模型部署到内存有限的小型设备时,我们可能需要将浮点数的精度降低到 8 位、4 位,甚至 1 位。挑战在于在这种较低精度下保持准确性。
混合 32 位全精度和 16 位半精度
选择 32 位或 16 位是另一个需要优化的超参数。一些算法和数据集对降低精度更为敏感。然而,有一个叫做“混合精度”的中间地带,它以 32 位“全精度”存储参数以保持数值稳定性,但使用 16 位“半精度”进行计算。理想情况下,半精度可以使操作速度提升 2 倍,同时减少一半内存的使用。然而,实际上由于开销问题,我们看到的改善并不尽如人意。
TensorFlow 和 Keras 在网络层级别提供本地的混合精度支持。在这里,我们设置全局策略,使用自动混合精度“策略”,允许框架决定哪些层和操作应该使用 16 位半精度:
import tf.keras.mixed_precision.Policy
policy = mixed_precision.Policy('mixed_float16')
mixed_precision.set_policy(policy)
这实际上是模型训练的“加速按钮”;然而,我们应该像对待任何其他超参数一样来处理它,并针对我们特定的数据集和算法进行调优。
量化
在模型部署的未来章节中,我们将描述如何在训练后将模型的精度从 32 位减少到 16 位,以减小模型大小并加快计算速度。量化过程使用根据音频信号处理而来的统计方法,以保留参数值的动态范围。虽然不是必需的,我们可以修改我们的训练脚本以“量化感知”方式准备模型,以在量化后保持模型准确性。
使用训练优化的硬件
AWS Trainium 是一款专为流行的深度学习框架(包括 TensorFlow、PyTorch 和 Apache MXNet)加速模型训练工作负载而设计的训练优化芯片。AWS Trainium 使用 AWS Neuron SDK,并支持将 32 位全精度浮点数自动转换为 16 位的bfloat以增加吞吐量并降低成本。
Spot 实例和检查点
如果我们使用支持检查点的算法(如 TensorFlow、PyTorch 和 Apache MXNet),我们可以使用 SageMaker 训练作业的 Spot 实例来节省成本。Spot 实例比按需实例更便宜。要使用 Spot 实例进行训练,我们在估算器中指定use_spot_instances=True,如下所示:
checkpoint_s3_uri = 's3://<BUCKET>/<CHECKPOINT_PREFIX/'
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src'
use_spot_instances=True,
max_wait=120, # seconds,
checkpoint_s3_uri=checkpoint_s3_uri,
...
当训练作业正在运行时,Spot 实例可能会被终止。使用max_wait参数,SageMaker 将等待max_wait秒以获取新的 Spot 实例来替换先前终止的 Spot 实例。超过max_wait秒后,作业将结束。最新的检查点用于从 Spot 实例被终止的时间点开始训练。
图 7-35 展示了一个示例,其中一个 Spot 实例在时间 0 被替换,三个 Spot 实例在时间 1 被替换。然而,替换节奏受到 Spot 实例供需驱动,预测起来有一定难度。使用检查点的单实例训练作业也可以从 Spot 实例的节省中受益。

图 7-35. 使用检查点在 Spot 实例被替换时继续训练。
然后,我们的脚本利用提供的检查点位置,使用 Keras 的 ModelCheckpoint 来保存检查点,如下所示:
checkpoint_path = '/opt/ml/checkpoints'
checkpoint_callback = ModelCheckpoint(
filepath=os.path.join(checkpoint_path, 'tf_model_{epoch:05d}.h5'),
save_weights_only=False,
verbose=1,
monitor='val_accuracy')
callbacks.append(checkpoint_callback)
要加载模型,我们的脚本使用检查点位置来加载模型,如下所示:
def load_checkpoint_model(checkpoint_path):
import glob
import os
glob_pattern = os.path.join(checkpoint_path, '*.h5')
print('glob pattern {}'.format(glob_pattern))
list_of_checkpoint_files = glob.glob(glob_pattern)
print('List of checkpoint files {}'.format(list_of_checkpoint_files))
latest_checkpoint_file = max(list_of_checkpoint_files)
loaded_model = TFDistilBertForSequenceClassification.from_pretrained(
latest_checkpoint_file,
config=config)
if os.listdir(checkpoint_path):
model = load_checkpoint_model(checkpoint_path)
SageMaker Debugger 中的早停规则
SageMaker Debugger 支持多种内置操作,在规则触发时执行。例如,StopTraining() 操作通过在训练的目标指标(例如准确率)达到平稳期且随后没有进一步改善时停止训练作业来降低成本。平稳期由诸如 overfit 的规则检测到。我们配置规则,以相对变化的时间或步数为基准。例如,如果我们的准确率在一千步内没有提高 1%,我们希望停止训练作业并节省一些费用。
当触发规则时,StopTraining() 操作会突然结束训练作业。与使用 Spot 实例类似,我们应该使用检查点,特别是在作业提前停止前的最后一个检查点。
总结
在本章中,我们使用了 TensorFlow 2.x 的 Keras API、BERT 和 Amazon SageMaker 训练了我们的第一个模型。我们深入了解了 SageMaker 的基础架构、模型开发 SDK 和 SageMaker 训练作业。我们使用 SageMaker 训练了一个模型,描述了安全最佳实践,并探讨了一些节省成本和提升性能的技巧。
我们还了解了 BERT 的 Transformer 神经网络架构如何通过双向方法学习语料库中单词的上下文表示,从而革新了自然语言处理(NLP)和自然语言理解(NLU)领域。我们演示了如何微调预训练的 BERT 模型,以构建一个针对产品评论的领域特定文本分类器。这与前一代 NLP 模型(如 Word2Vec、GloVe 和 ELMo)形成对比,前者要么(1)仅一次性单向学习,(2)丢弃原始模型并仅保留学习的嵌入,或者(3)使用需要大量内存和计算的复杂循环神经网络(RNN)架构。
在第八章,我们将使用不同的配置和超参数重新训练我们的模型,这个过程称为超参数优化或超参数调整。通过这个过程,我们将找到提供最高准确率的最佳模型和超参数组合。我们还将进一步优化模型,以利用目标部署硬件(如 NVIDIA GPU 或 AWS Inferentia 芯片)提供的硬件优化。在第九章,我们将在生产环境中部署和监控我们的模型。在第十章,我们将使用 SageMaker Pipelines、AWS Step Functions、Apache Airflow、Kubeflow 和其他开源选项为我们的模型构建端到端的流水线。
第八章:大规模训练和优化模型
彼得·德鲁克(Peter Drucker)是杰夫·贝佐斯最喜欢的商业战略家之一,他曾说过:“如果你不能衡量它,你就不能改进它。”这句话捕捉到了本章的核心,重点在于衡量、优化和改进我们的预测模型。
在上一章中,我们使用 Amazon SageMaker 训练了一个具有单一超参数集的单一模型。我们还展示了如何微调预训练的 BERT 模型,构建一个评估文本情感的分类器模型,以预测来自社交渠道、合作伙伴网站等的产品评论的情感。
在本章中,我们将使用 SageMaker 实验来量化、跟踪、比较和改进我们的模型。我们还将使用 SageMaker Hyper-Parameter Tuning 选择特定算法和数据集的最佳超参数。我们还展示了如何使用各种通信策略和分布式文件系统进行分布式训练。最后,我们提供了如何通过 SageMaker Autopilot 的超参数选择算法、SageMaker 优化的管道到 S3 和 AWS 增强网络硬件来降低成本和提高性能的建议。
自动找到最佳模型超参数
现在我们知道如何跟踪和比较模型训练运行,我们可以使用名为超参数调优(HPT)或超参数优化(HPO)的可扩展过程自动找到适合我们数据集和算法的最佳超参数。SageMaker 原生支持 HPT 作业。这些调优作业是 SageMaker Autopilot 的构建模块,详细讨论请见第三章。
我们已经了解到超参数在模型训练期间控制我们的机器学习算法如何学习模型参数。在调整超参数时,我们需要定义一个优化的目标,例如模型准确度。换句话说,我们需要找到一组超参数,使其满足或超过我们给定的目标。
在每次 HPT 运行之后,我们评估模型性能并调整超参数,直到达到目标。手动执行此操作非常耗时,因为模型调优通常需要数十甚至数百个训练作业才能收敛于最佳超参数组合以实现我们的目标。SageMaker 的 HPT 作业通过使用给定的调优策略并行运行多个训练作业,加速和扩展了优化过程,如图 8-1 所示。

图 8-1. SageMaker HPT 支持常见的调优策略。
SageMaker 支持随机搜索和贝叶斯超参数优化策略。使用随机搜索时,我们会随机挑选超参数组合,直到找到表现良好的组合。这种方法非常快速且易于并行化,但可能会错过最佳超参数集,因为我们是从超参数空间随机选择的。使用贝叶斯优化时,我们将任务视为回归问题。
类似于我们实际模型如何学习最小化损失函数的模型权重,贝叶斯优化通过使用替代模型和获取函数来迭代,以使用在先前优化运行期间学习到的先验知识在超参数空间上进行信息搜索,以找到最佳超参数。贝叶斯优化通常比手动、随机或网格搜索更高效,但需要我们顺序执行一些优化(而不是并行执行),以建立所需的先验知识,以在超参数空间上进行信息搜索。
那么,网格搜索呢?使用网格搜索,我们将评估超参数空间中每一种可能的超参数组合的网格。这种方法通常效率低下,并且完成时间相对于随机搜索和贝叶斯优化策略需要花费数量级更长的时间。在撰写本文时,SageMaker HPT 不支持低效的网格搜索优化策略。相反,我们建议使用随机搜索和贝叶斯优化策略。
设置超参数范围
让我们使用 SageMaker HPT 来为前一章节中基于 BERT 的评论分类器找到最佳超参数。首先,让我们创建一个optimize实验追踪器,并将其与我们的实验关联起来。
from smexperiments.tracker import Tracker
tracker_optimize = Tracker.create(display_name='optimize-1',
sagemaker_boto_client=sm)
optimize_trial_component_name =
tracker_optimize.trial_component.trial_component_name
trial.add_trial_component(tracker_optimize.trial_component)
为了使这个例子保持简单,并避免试验运行的组合爆炸,我们将冻结大部分超参数,并仅在此次优化运行中探索有限的集合。在资源和预算无限的理想情况下,我们将探索每一种超参数的组合。但现在,我们将手动选择以下一些超参数,并在我们的 HPO 运行中探索其余的。
epochs=500
epsilon=0.00000001
train_batch_size=128
validation_batch_size=128
test_batch_size=128
train_steps_per_epoch=100
validation_steps=100
test_steps=100
use_xla=True
use_amp=True
freeze_bert_layer=True
接下来,让我们设置我们希望探索的超参数范围。我们根据直觉、领域知识和算法文档选择这些超参数。我们还可能发现研究论文或社区中的其他先前工作有用。在机器学习和预测分析的生命周期的这一阶段,我们几乎总能找到解决我们试图解决的问题的相关信息。
如果我们仍然找不到合适的起始点,我们应该对数范围进行探索(而不是线性范围),以帮助获得超参数规模的感觉。例如,如果我们最佳的超参数与数千的量级差异很大,那么探索集合[1, 2, 3, 4]就没有意义。
SageMaker Autopilot 是确定我们问题和数据集的基准超参数集的另一种方法。SageMaker Autopilot 的超参数选择过程已在亚马逊广泛的数据集、算法和用例中经过数千小时的训练作业的精炼。
SageMaker HPT 支持三种参数范围类型:分类、连续和整数。分类 用于离散值集合(例如 产品类别)。连续 用于浮点数,整数 用于整数。我们还可以为每种超参数范围类型指定缩放类型。缩放类型可以设置为 线性、对数、反对数 或 自动,这样 SageMaker 就可以决定。某些超参数更适合某些缩放类型。在这里,我们指定 SageMaker 调优作业应使用线性尺度在给定范围内探索连续超参数 learning_rate:
from sagemaker.tuner import ContinuousParameter
hyperparameter_ranges = {
'learning_rate': ContinuousParameter(0.00001, 0.00005,
scaling_type='Linear'),
}
提示
如果我们没有适合探索特定超参数的范围——即使在研究其他解决类似问题的算法之后——我们可以从 对数 缩放类型开始处理该超参数,并逐渐使用 线性 缩放类型探索范围。
最后,我们需要定义 HPT 作业尝试优化的客观指标——在我们的例子中是验证准确率。请记住,我们需要提供正则表达式(regex)来从 SageMaker 容器日志中提取指标。我们选择还收集训练损失、训练准确率和验证损失以供信息目的使用:
objective_metric_name = 'validation:accuracy'
metrics_definitions = [
{'Name': 'train:loss', 'Regex': 'loss: ([0-9\\.]+)'},
{'Name': 'train:accuracy', 'Regex': 'accuracy: ([0-9\\.]+)'},
{'Name': 'validation:loss', 'Regex': 'val_loss: ([0-9\\.]+)'},
{'Name': 'validation:accuracy', 'Regex': 'val_accuracy: ([0-9\\.]+)'}]
运行超参数调优作业
我们首先像前一章一样创建我们的 TensorFlow 评估器。请注意,在这种情况下,我们没有指定 learning_rate 超参数。我们将在稍后将其作为超参数范围传递给 HyperparameterTuner:
from sagemaker.tensorflow import TensorFlow
hyperparameters={'epochs': epochs,
'epsilon': epsilon,
'train_batch_size': train_batch_size,
'validation_batch_size': validation_batch_size,
'test_batch_size': test_batch_size,
'train_steps_per_epoch': train_steps_per_epoch,
'validation_steps': validation_steps,
'test_steps': test_steps,
'use_xla': use_xla,
'use_amp': use_amp,
'max_seq_length': max_seq_length,
'freeze_bert_layer': freeze_bert_layer,
}
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src',
role=role,
instance_count=train_instance_count,
instance_type=train_instance_type,
py_version='<PYTHON_VERSION>',
framework_version='<TENSORFLOW_VERSION>',
hyperparameters=hyper_parameters,
metric_definitions=metrics_definitions,
)
接下来,我们可以通过传递 TensorFlow 评估器、超参数范围、客观指标、调优策略、并行/总作业数以及早停策略来创建我们的 HPT 作业。SageMaker 将使用给定的优化策略(即“贝叶斯”或“随机”)来探索给定范围内的值:
objective_metric_name = 'validation:accuracy'
tuner = HyperparameterTuner(
estimator=estimator,
objective_type='Maximize',
objective_metric_name=objective_metric_name,
hyperparameter_ranges=hyperparameter_ranges,
metric_definitions=metrics_definitions,
max_jobs=100,
max_parallel_jobs=10,
strategy='Bayesian',
early_stopping_type='Auto'
)
在这个例子中,我们使用了 贝叶斯 优化策略,并行进行了 10 个作业,总共有 100 个作业。每次只做 10 个作业,我们给 贝叶斯 策略一个机会从之前的运行中学习。换句话说,如果我们同时进行了所有 100 个作业,贝叶斯 策略将无法利用先前的信息来选择范围内更好的值。
通过将 early_stopping_type 设置为 Auto,如果调优作业不能在客观指标上取得改进,SageMaker 将停止调优作业。这有助于节省时间,减少对训练数据集的过拟合可能性,并降低调优作业的总体成本。
通过调用 tuner.fit() 来启动调整作业,使用训练、验证和测试数据集拆分:
s3_input_train_data =
TrainingInput(s3_data=processed_train_data_s3_uri,
distribution='ShardedByS3Key')
s3_input_validation_data =
TrainingInput(s3_data=processed_validation_data_s3_uri,
distribution='ShardedByS3Key')
s3_input_test_data =
TrainingInput(s3_data=processed_test_data_s3_uri,
distribution='ShardedByS3Key')
tuner.fit(inputs={'train': s3_input_train_data,
'validation': s3_input_validation_data,
'test': s3_input_test_data
},
include_cls_metadata=False)
分析调整作业中的最佳超参数
以下是确定最佳超参数的调整作业的结果。这次调整作业为最佳候选者实现了 0.9416 的最终训练精度,高于手动选择的超参数值集合 第七章 中的 0.9394 精度:
from sagemaker.analytics import HyperparameterTuningJobAnalytics
hp_results = HyperparameterTuningJobAnalytics(
sagemaker_session=sess,
hyperparameter_tuning_job_name=tuning_job_name
)
df_results = hp_results.dataframe()
df_results
| freeze_bert_layer | learning_rate | train_batch_size | TrainingJobName | TrainingJobStatus | FinalObjectiveValue | TrainingElapsedTimeSeconds |
|---|---|---|---|---|---|---|
| “False” | 0.000017 | “128” | tensorflow-training-210109-0222-003-cf95cdaa | Completed | 0.9416 | 11245.0 |
| … | ||||||
| “False” | 0.000042 | “128” | tensorflow-training-210109-0222-004-48da4bab | Stopped | 0.8056 | 693.0 |
鉴于这次调整作业的结果,最佳超参数组合是 learning_rate 为 0.000017,train_batch_size 为 128,freeze_bert_layer 为 False。当超参数组合未能改善训练准确度目标度量时,SageMaker 提前停止了作业。这是 SageMaker 智能停止未能为业务目标增加价值的作业,从而节省了我们的成本。
显示我们 SageMaker 调整作业的实验谱系
HPT 作业完成后,我们可以直接在笔记本或 SageMaker Studio 中分析结果。
首先,让我们更新实验谱系,包括我们的 HPT 作业找到的最佳超参数和目标度量:
best_learning_rate = df_results.sort_values('FinalObjectiveValue',
ascending=0).head(1)['learning_rate']
tracker_optimize.log_parameters({
'learning_rate': best_learning_rate
})
best_accuracy = df_results.sort_values('FinalObjectiveValue',
ascending=0).head(1)['FinalObjectiveValue']
tracker_optimize.log_metrics({
'train:accuracy': best_accuracy
})
tracker_optimize.trial_component.save()
现在,让我们总结到目前为止的实验谱系。在 第九章 中,我们将部署模型,并进一步扩展我们的实验谱系以包括模型部署。然后,我们将在 第十章 中将所有内容整合到一个端到端管道中,实现全面的谱系跟踪。
from sagemaker.analytics import ExperimentAnalytics
lineage_table = ExperimentAnalytics(
sagemaker_session=sess,
experiment_name=experiment_name,
metric_names=['train:accuracy'],
sort_by="CreationTime",
sort_order="Ascending",
)
lineage_table.dataframe()
| TrialComponentName | DisplayName | max_seq_length | learning_rate | train_accuracy | … |
|---|---|---|---|---|---|
| TrialComponent-2021-01-09-062410-pxuy | prepare | 64.0 | NaN | NaN | … |
| tensorflow-training-2021-01-09-06-24-12-989 | train | 64.0 | 0.00001 | 0.9394 | … |
| TrialComponent-2020-06-12-193933-bowu | optimize-1 | 64.0 | 0.000017 | 0.9416 | … |
在这个示例中,我们优化了 TensorFlow BERT 分类器层的超参数。SageMaker HPT 还支持通过将算法列表添加到调整作业定义来跨多个算法进行自动超参数调整。我们可以为每个算法指定不同的超参数和范围。类似地,SageMaker Autopilot 使用多算法调整来根据我们的问题类型、数据集和目标函数找到最佳模型。
使用 Warm Start 进行额外的 SageMaker 超参数调整作业
一旦我们有了最佳候选者,我们可以选择使用称为“热启动”的技术执行另一轮超参数优化。热启动重复使用先前 HPT 作业或一组作业的结果,以加快优化过程并降低总成本。热启动创建了多对多的父-子关系。在我们的示例中,我们与单个父作业——前一个调整作业——执行热启动,如图 8-2 所示。

图 8-2. 使用热启动从先前的调整作业开始一个额外的 HPT 作业。
当我们想要从上一个作业更改可调参数范围或添加新的超参数时,热启动尤其有用。两种情况都使用前一个调整作业来更快地找到最佳模型。这两种情况分别用两种热启动类型实现:IDENTICAL_DATA_AND_ALGORITHM和TRANSFER_LEARNING。
如果我们选择IDENTICAL_DATA_AND_ALGORITHM,新的调整作业将使用与父作业相同的输入数据和训练图像。我们可以更新可调参数范围和最大训练作业数。我们还可以将之前的固定超参数添加到可调超参数列表中,反之亦然——只要固定加可调超参数总数保持不变即可。完成后,采用这种策略的调整作业将返回一个额外字段,OverallBestTrainingJob,其中包含最佳模型候选者,包括此调整作业以及已完成的父调整作业。
如果我们选择TRANSFER_LEARNING,我们可以使用更新后的训练数据和不同版本的训练算法。也许自上次优化运行以来我们收集了更多的训练数据,现在我们想要使用更新后的数据集重新运行调整作业。或者可能发布了更新的算法版本,我们想要重新运行优化过程。
运行使用热启动的 HPT 作业
我们需要使用一个或多个先前的 HPT 作业作为父作业来配置调整作业的WarmStartConfig。父 HPT 作业必须以Completed、Stopped或Failed状态完成。不支持递归的父-子关系。我们还需要指定WarmStartType。在我们的示例中,我们将使用IDENTICAL_DATA_AND_ALGORITHM,因为我们计划仅修改超参数范围,而不使用更新后的数据集或算法版本。
让我们从设置WarmStartConfig开始:
from sagemaker.tuner import WarmStartConfig
from sagemaker.tuner import WarmStartTypes
warm_start_config = WarmStartConfig(
warm_start_type=WarmStartTypes.IDENTICAL_DATA_AND_ALGORITHM,
parents={tuning_job_name})
让我们定义那些不打算调整的固定超参数:
epochs=500
epsilon=0.00000001
train_batch_size=128
validation_batch_size=128
test_batch_size=128
train_steps_per_epoch=100
validation_steps=100
test_steps=100
use_xla=True
use_amp=True
freeze_bert_layer=False
from sagemaker.tensorflow import TensorFlow
hyperparameters={'epochs': epochs,
'epsilon': epsilon,
'train_batch_size': train_batch_size,
'validation_batch_size': validation_batch_size,
'test_batch_size': test_batch_size,
'train_steps_per_epoch': train_steps_per_epoch,
'validation_steps': validation_steps,
'test_steps': test_steps,
'use_xla': use_xla,
'use_amp': use_amp,
'max_seq_length': max_seq_length,
'freeze_bert_layer': freeze_bert_layer
}
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src',
role=role,
instance_count=train_instance_count,
instance_type=train_instance_type,
volume_size=train_volume_size,
py_version='<PYTHON_VERSION>',
framework_version='<TENSORFLOW_VERSION>',
hyperparameters=hyperparameters,
metric_definitions=metrics_definitions,
)
虽然我们可以选择在这个热启动调整作业中调整更多超参数,但我们将简单地修改我们的learning_rate范围,以便缩小到在父调整作业中找到的最佳值:
from sagemaker.tuner import IntegerParameter
from sagemaker.tuner import ContinuousParameter
from sagemaker.tuner import CategoricalParameter
hyperparameter_ranges = {
'learning_rate': ContinuousParameter(0.00015, 0.00020,
scaling_type='Linear')}
现在让我们定义目标度量,使用前述warm_start_config创建HyperparameterTuner,并启动调整作业:
objective_metric_name = 'validation:accuracy'
tuner = HyperparameterTuner(
estimator=estimator,
objective_type='Maximize',
objective_metric_name=objective_metric_name,
hyperparameter_ranges=hyperparameter_ranges,
metric_definitions=metrics_definitions,
max_jobs=50,
max_parallel_jobs=5,
strategy='Bayesian',
early_stopping_type='Auto',
warm_start_config=warm_start_config
)
最后,让我们配置数据集拆分并启动我们的调优作业:
s3_input_train_data =
TrainingInput(s3_data=processed_train_data_s3_uri,
distribution='ShardedByS3Key')
s3_input_validation_data =
TrainingInput(s3_data=processed_validation_data_s3_uri,
distribution='ShardedByS3Key')
s3_input_test_data =
TrainingInput(s3_data=processed_test_data_s3_uri,
distribution='ShardedByS3Key')
tuner.fit({'train': s3_input_train_data,
'validation': s3_input_validation_data,
'test': s3_input_test_data},
},
include_cls_metadata=False)
分析温启动调优作业的最佳超参数
以下是用于确定最佳超参数的调优作业结果。调优作业导致了 0.9216 的最佳候选训练精度,低于父 HPT 作业的最佳候选训练精度 0.9416。在更新我们的实验谱系以包含温启动 HPT 结果后,我们将采用产生父调优作业中最高训练精度 0.9416 的候选超参数。
| TrialComponentName | DisplayName | max_seq_length | learning_rate | train_accuracy | … |
|---|---|---|---|---|---|
| TrialComponent-2021-01-09-062410-pxuy | 准备 | 64.0 | NaN | NaN | … |
| tensorflow-training-2021-01-09-06-24-12-989 | 训练 | 64.0 | 0.00001 | 0.9394 | … |
| TrialComponent-2021-01-09-193933-bowu | 优化-1 | 64.0 | 0.000017 | 0.9416 | … |
| TrialComponent-2021-01-09-234445-dep | 优化-2 | 64.0 | 0.000013 | 0.9216 | … |
在这个例子中,我们已经优化了我们的 TensorFlow BERT 分类器层的超参数。SageMaker HPT 还通过在调优作业定义中添加算法列表来支持多算法的自动 HPT。我们可以为每个算法指定不同的超参数和范围。类似地,SageMaker Autopilot 使用多算法调优来基于问题类型、数据集和目标函数找到最佳模型。
温启动调优作业未能超越父调优作业的最佳候选精度。因此,在这个例子中,仍然选择父调优作业中找到的超参数作为最佳候选。
使用 SageMaker 分布式训练扩展
大多数现代 AI 和机器学习框架都支持某种形式的分布式处理,以扩展计算能力。如果没有分布式处理,训练作业将受限于单个实例的资源。虽然单个实例类型在能力方面(RAM、CPU 和 GPU)不断增强,但我们现代的大数据世界需要一个集群来支持持续数据摄取、实时分析和数据密集型机器学习模型。
让我们运行一个分布式训练作业,使用 TensorFlow 2.x Keras API、BERT 和 SageMaker 的本地分布式训练支持来构建我们的评论分类器模型。
注意
虽然本章没有包含 PyTorch 的示例,但 SageMaker 绝对支持分布式 PyTorch。请查看我们的 GitHub 存储库,了解 PyTorch 和 BERT 示例。此外,Hugging Face Transformers 库原生支持 SageMaker 的分布式训练基础设施,适用于 TensorFlow 和 PyTorch。
选择分布式通信策略
任何分布式计算都要求集群实例之间进行通信并共享信息。这种集群通信受益于实例之间更高带宽的连接。因此,如果可能的话,实例应该在云数据中心内彼此物理靠近。幸运的是,SageMaker 为我们处理了所有这些繁重的工作,因此我们可以专注于创建我们的评论分类器并解决我们在野外分类产品评论的业务问题。SageMaker 支持使用许多分布式原生框架进行分布式计算,包括 Apache Spark、TensorFlow、PyTorch 和 Apache MXNet。
注意
虽然大多数现代 AI 和机器学习框架如 TensorFlow、PyTorch 和 Apache MXNet 都设计用于分布式计算,但许多经典的数据科学库如 scikit-learn 和 pandas 并不原生支持分布式通信协议或分布式数据集。Dask 是一个流行的运行时,可帮助将某些 scikit-learn 模型扩展到集群中的多个节点。
“参数服务器”是大多数分布式机器学习框架支持的一种原始分布式训练策略。请记住,参数是算法学习的内容。参数服务器存储学习到的参数,并在训练过程中与每个实例共享。由于参数服务器存储参数的状态,SageMaker 在每个实例上运行参数服务器以提高可用性,如 图 8-3 所示。

图 8-3. 参数服务器的分布式通信。
在每个实例上运行有状态的参数服务器有助于 SageMaker 在训练过程中从故障情况或当 Spot 实例被终止并替换时进行恢复。
另一种根植于并行计算和消息传递接口(MPI)的常见分布式通信策略是“all-reduce”。All-reduce 使用类似环形的通信模式,如 图 8-4 所示,并提高了在通信开销对参数服务器之间的影响较大的非常大集群中的整体训练效率。

图 8-4. All-reduce 分布式通信策略。
SageMaker 的 all-reduce 分布式训练策略与 Horovod 兼容,后者是一种流行的 all-reduce 和 MPI 实现,常用于将 TensorFlow 和 PyTorch 训练作业扩展到集群中的多个实例。如果我们当前正在使用 Horovod 进行分布式训练,我们可以轻松过渡到 SageMaker 的 all-reduce 策略。在我们的示例中,我们将使用 SageMaker 内置的分布式 all-reduce 通信策略。
选择并行策略
在执行分布式计算时,有两种主要的并行方式:数据并行和模型并行。我们大多数人已经熟悉数据并行,它来自于类似 Apache Spark 这样的经典 Map-Reduce 数据处理工具,将数据集分割成“分片”并将它们放置在单独的实例上。每个实例在“映射”阶段分别处理其分片,然后在“归约”阶段合并结果。当我们的数据集无法适应单个实例时,如现代大数据处理和分布式机器学习的情况下,就需要数据并行。图 8-5 显示数据并行如何将数据分割到不同实例上,以便模型分别处理。

图 8-5。将数据集通过数据并行分片到多个实例上进行分布式训练。
模型并行性则相反,将处理分割到单独的实例上,并在每个实例上分别处理整个数据集。这要复杂得多,通常在我们的模型由于内存限制而无法适应单个实例资源时需要使用。图 8-6 显示模型并行性如何将模型分割到不同实例上,并处理每个“模型分片”的完整数据集。

图 8-6。使用模型并行将模型分片到多个实例上进行分布式训练。
SageMaker 原生支持数据并行和模型并行。对于我们的 BERT 模型,我们将使用数据并行,因为我们的模型适合单个实例,因此我们将把数据集分片到不同的实例上进行训练,并通过内置到 SageMaker 分布式训练中的全局归约通信策略合并结果。
选择分布式文件系统
通常,我们的分布式训练集群直接与 S3 进行数据读写。然而,某些框架和工具未经过 S3 本地优化,或者仅支持 POSIX 兼容的文件系统。对于这些情况,我们可以使用 FSx for Lustre(Linux)或 Amazon FSx for Windows File Server,在 S3 之上提供一个 POSIX 兼容的文件系统。这一额外层还提供了关键的缓存性能优势,可以将大型数据集的训练时间降至合理水平。
Amazon FSx for Lustre 是一个高性能的、与 POSIX 兼容的文件系统,能够与 S3 本地集成。FSx for Lustre 基于开源 Lustre 文件系统设计,专为具有 PB 级数据、每秒 TB 级聚合 I/O 吞吐量和一致低延迟的高度可扩展、高度分布式和高度并行的训练作业而设计。
还有 Amazon FSx for Windows File Server,它提供与 Windows 兼容的文件系统,并与 S3 原生集成。然而,我们选择关注 FSx for Lustre,因为我们的示例是基于 Linux 的。这两种文件系统都针对使用 S3 的机器学习、分析和高性能计算工作负载进行了优化。并且这两种文件系统都提供类似的功能。
FSx for Lustre 是一个完全托管的服务,简化了设置和管理 Lustre 文件系统的复杂性。将 S3 存储桶挂载为文件系统后,FSx for Lustre 让我们能够同时从任意数量的实例访问数据,并且缓存 S3 对象以提高迭代机器学习工作负载的性能,这些工作负载会多次处理数据集以拟合高精度模型。图 8-7 展示了 SageMaker 如何使用 FSx for Lustre 提供快速、共享的 S3 数据访问,加速我们的训练和调优任务。

图 8-7。SageMaker 使用 FSx for Lustre 提升训练和调优任务的性能。
我们的 SageMaker 训练集群实例使用/mnt/data/file1.txt访问 FSx for Lustre 中的文件。FSx for Lustre 会转换此请求并向 S3 发出GetObject请求。文件将被缓存并返回到集群实例。如果文件未更改,则后续请求将从 FSx for Lustre 的缓存返回。由于训练数据在训练作业运行期间通常不会更改,因此在多个训练周期中迭代数据集时,我们看到了巨大的性能提升。
一旦设置了 FSx for Lustre 文件系统,我们可以将 FSx for Lustre 文件系统的位置传递给训练作业,如下所示:
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src',
instance_count=train_instance_count,
instance_type=train_instance_type,
subnets=['subnet-1', 'subnet-2']
security_group_ids=['sg-1'])
fsx_data = FileSystemInput(file_system_id='fs-1',
file_system_type='FSxLustre',
directory_path='/mnt/data,
file_system_access_mode='ro')
estimator.fit(inputs=fsx_data)
请注意,在创建我们的 FSx for Lustre 文件系统时,我们需要指定subnets和security_group_ids。我们将在第十二章深入探讨网络和安全性。
另一种分布式训练选项是 Amazon Elastic File System(Amazon EFS)。Amazon EFS 兼容行业标准的网络文件系统协议,但针对 AWS 的云原生和弹性环境进行了优化,包括网络、访问控制、加密和可用性。在本节中,我们调整我们的分布式训练作业,同时使用 FSx for Lustre(Linux)和 Amazon EFS。Amazon EFS 为分布式训练集群中的数千个实例提供集中、共享的训练数据集访问,如图 8-8 所示。

图 8-8。Amazon EFS 与 SageMaker。
注意
SageMaker Studio 使用 Amazon EFS 为所有团队成员提供集中、共享和安全的代码和笔记本访问权限。
存储在 Amazon EFS 中的数据在多个可用区复制,提供更高的可用性和读/写吞吐量。随着新数据的摄入,Amazon EFS 文件系统将自动扩展。
假设我们已经挂载并填充了 Amazon EFS 文件系统的训练数据,我们可以通过两种不同的实现将 Amazon EFS 挂载传递到训练作业中:FileSystemInput和FileSystemRecordSet。
此示例展示了如何使用FileSystemInput实现:
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src',
instance_count=train_instance_count,
instance_type=train_instance_type,
subnets=['subnet-1', 'subnet-2']
security_group_ids=['sg-1'])
efs_data = FileSystemInput(file_system_id='fs-1',
file_system_type='EFS',
directory_path='/mnt/data,
file_system_access_mode='ro')
estimator.fit(inputs=efs_data)
请注意,在创建 Amazon EFS 文件系统时,我们需要指定用于其的子网和安全组 ID。我们将深入研究第十二章中的网络和安全性。
对于我们的示例,我们将使用 FSx for Lustre,因为其具有 S3 缓存功能,可以极大地提高我们的训练性能。
启动分布式训练作业
SageMaker 遵循云原生原则,本质上是分布式和可扩展的。在上一章中,我们使用了单个实例,通过指定train_instance_count=1。在这里,我们将增加train_instance_count并在我们的 TensorFlow 估算器中指定distribution参数,以启用 SageMaker 的分布式训练,如下所示:
train_instance_count=3
train_instance_type='ml.p4d.24xlarge'
from sagemaker.tensorflow import TensorFlow
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src',
instance_count=train_instance_count,
instance_type=train_instance_type,
...
py_version='<PYTHON_VERSION>',
framework_version='<TENSORFLOW_VERSION>',
distribution={'smdistributed':{
'dataparallel':{
'enabled': True
}
}
)
SageMaker 会自动将相关集群信息传递给 TensorFlow,以启用全减少策略并使用分布式 TensorFlow。
注意
SageMaker 还将相同的集群信息传递给分布式 PyTorch 和 Apache MXNet,但在本示例中仅显示 TensorFlow。
减少成本并提高性能
在本节中,我们讨论了使用一些高级 SageMaker 功能来提高成本效益和性能的各种方法,包括 SageMaker Autopilot 用于基线超参数选择,ShardedByS3Key用于在所有训练实例之间分发输入文件,以及Pipe模式来提高 I/O 吞吐量。我们还强调了 AWS 增强的网络功能,包括弹性网络适配器(ENA)和弹性布线适配器(EFA),以优化训练和调整集群中实例之间的网络性能。
从合理的超参数范围开始
通过研究他人的工作,我们很可能可以找到一系列超参数的范围,这将缩小搜索空间并加快我们的 SageMaker HPT 作业。如果我们没有一个好的起点,我们可以使用对数缩放策略来确定我们应该探索的范围。仅仅知道 10 的幂次可以显著减少找到最佳超参数的时间,适用于我们的算法和数据集。
使用ShardedByS3Key对数据进行分片
在大规模训练时,我们需要考虑集群中每个实例如何读取大型训练数据集。我们可以采用蛮力方法将所有数据复制到所有实例中。然而,对于更大的数据集,这可能需要很长时间,并且可能会主导整体训练时间。例如,在执行特征工程之后,我们的分词训练数据集大约有 45 个TFRecord“part”文件,如下所示:
part-algo-1-amazon_reviews_us_Apparel_v1_00.tfrecord
...
part-algo-2-amazon_reviews_us_Digital_Software_v1_00.tfrecord
part-algo-4-amazon_reviews_us_Digital_Video_Games_v1_00.tfrecord
...
part-algo-9-amazon_reviews_us_Sports_v1_00.tfrecord
与其将所有 45 个分片文件加载到集群中的所有实例中,我们可以通过将每个集群实例上仅放置 15 个分片文件来改善启动性能,总共在集群中分布了 45 个分片文件。这称为“分片”。我们将使用 SageMaker 的ShardedByS3Key功能,将分片文件均匀分布到集群中,如图 8-9 所示。

图 8-9. 使用ShardedByS3Key分布策略将输入文件分布到集群实例中。
在这里,我们为我们的 S3 输入数据(包括训练、验证和测试数据集)设置了ShardedByS3Key分布策略:
s3_input_train_data =
sagemaker.s3_input(s3_data=processed_train_data_s3_uri,
distribution='ShardedByS3Key')
s3_input_validation_data =
sagemaker.s3_input(s3_data=processed_validation_data_s3_uri,
distribution='ShardedByS3Key')
s3_input_test_data =
sagemaker.s3_input(s3_data=processed_test_data_s3_uri,
distribution='ShardedByS3Key')
接下来,我们为每个数据集分片(包括训练、验证和测试)调用fit():
estimator.fit(inputs={'train': s3_input_train_data,
'validation': s3_input_validation_data,
'test': s3_input_test_data
})
在这种情况下,我们集群中的每个实例将收到每个数据集分片约 15 个文件。
使用 Pipe 模式动态流式传输数据
除了分片之外,我们还可以使用 SageMaker 的Pipe模式,按需动态加载数据。到目前为止,我们一直在使用默认的File模式,在训练作业启动时将所有数据复制到所有实例,这会导致长时间的启动暂停。使用大型数据集(10、100 或 1,000 GB 范围内)时,Pipe模式提供了最显著的性能提升。如果我们的数据集较小,则应使用File模式。
Pipe模式可以并行地从 S3 直接流式传输数据到每个实例的训练过程中,其 I/O 吞吐量明显高于File模式。通过仅在需要时流式传输数据,我们的训练和调优作业启动更快、完成更快,并且总体上使用更少的磁盘空间。这直接降低了我们训练和调优作业的成本。
Pipe模式与 S3 一起按需获取训练数据的行。在幕后,Pipe模式使用 Unix 先进先出(FIFO)文件从 S3 读取数据并在数据即将被训练作业使用前在实例上缓存。这些 FIFO 文件是单向可读的,换句话说,我们无法随机备份或跳过。
这是我们配置训练作业使用Pipe模式的方式:
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src',
instance_count=train_instance_count,
instance_type=train_instance_type,
...
input_mode='Pipe')
由于Pipe模式包装了我们的 TensorFlow 数据集读取器,我们需要稍微修改我们的 TensorFlow 代码以检测Pipe模式并使用PipeModeDataset包装器:
if input_mode == 'Pipe':
from sagemaker_tensorflow import PipeModeDataset
dataset = PipeModeDataset(channel=channel,
record_format='TFRecord')
else:
dataset = tf.data.TFRecordDataset(input_filenames)
启用增强型网络
在规模化训练中,集群内实例之间需要超快的通信。务必选择一种能够利用 ENA 和 EFA 提供高网络带宽以及集群内实例之间一致网络延迟的实例类型。
ENA 与 AWS 深度学习实例类型(包括 C、M、P 和 X 系列)兼容良好。这些实例类型提供大量的 CPU,因此它们从网络适配器的高效共享中受益匪浅。通过执行各种网络级别的优化,如基于硬件的校验和生成和软件路由,ENA 减少了开销,提高了可伸缩性,并最大化了一致性。所有这些优化措施旨在减少瓶颈,卸载 CPU 的工作,并为网络数据包创建有效路径。
EFA 使用自定义构建的操作系统级旁路技术来提高集群内实例之间的网络性能。EFA 原生支持 MPI,这对于扩展到数千个 CPU 的高性能计算应用至关重要。EFA 得到许多计算优化实例类型的支持,包括 C 和 P 系列。
尽管没有足够的具体数据来验证,但一些从业者注意到,在虚拟私有云(VPC)中运行分布式 SageMaker 作业时,性能有所提升。这很可能归因于在同一 VPC 中运行的集群实例之间的网络延迟减少。如果我们的训练作业特别对延迟敏感,我们可能想尝试在 VPC 中运行我们的训练作业。我们在第十二章深入探讨了 VPC 和 SageMaker。
摘要
在本章中,我们使用 SageMaker 实验和 HPT 来跟踪、比较和选择我们特定算法和数据集的最佳超参数。我们探讨了各种分布式通信策略,如参数服务器和全局减少。我们演示了如何使用 FSx for Lustre 来增加 S3 的性能,并如何配置我们的训练作业以使用 Amazon EFS。最后,我们讨论了使用 SageMaker 自动驾驶的超参数选择功能和像 ShardedByS3Key 和 Pipe 模式这样的 SageMaker 优化数据加载策略来降低成本和提高性能的几种方法。我们还讨论了计算优化实例类型,包括 ENA 和 EFA 的增强网络功能。
在第九章,我们将使用各种推出、A/B 测试和多臂老丨虎丨机策略将我们的模型部署到生产环境中。我们将讨论如何使用实时 REST 端点、离线批处理作业和边缘设备将模型预测集成到应用程序中。我们演示了如何基于内置和自定义 CloudWatch 指标自动扩展我们的端点。我们还深入探讨了如何使用 SageMaker 模型监视器检测我们的实时 SageMaker 端点中数据分布的变化、模型偏差和模型可解释性的漂移。
第九章:将模型部署到生产环境
在前几章中,我们演示了如何训练和优化模型。在本章中,我们将焦点从研究实验室中的模型开发转移到生产环境中的模型部署。我们展示了如何部署、优化、扩展和监控模型以服务我们的应用程序和业务用例。
我们将模型部署以提供在线实时预测,并展示如何进行离线批处理预测。对于实时预测,我们通过 SageMaker 端点部署模型。我们讨论最佳实践和部署策略,例如金丝雀发布和蓝/绿部署。我们展示如何使用 A/B 测试测试和比较新模型,以及如何使用多臂老丨虎丨机(MAB)测试实现强化学习。我们展示如何根据模型预测流量的变化自动扩展我们的模型托管基础设施。我们展示如何持续监控部署的模型以检测概念漂移、模型质量或偏差漂移以及特征重要性的漂移。我们还涉及通过 Lambda 提供服务器无 API 来提供模型预测的内容,以及如何优化和管理边缘模型。我们通过如何减少模型大小、降低推理成本以及使用 AWS Inferentia 硬件、SageMaker Neo 服务和 TensorFlow Lite 库等各种硬件、服务和工具来增加预测性能结束本章。
选择实时或批处理预测
我们需要了解应用程序和业务背景来选择实时和批处理预测。我们是在优化延迟还是吞吐量?应用程序是否需要我们的模型在一天中自动扩展以处理周期性的流量需求?我们是否计划通过 A/B 测试在生产中比较模型?
如果我们的应用程序需要低延迟,那么我们应将模型部署为实时 API,以便在单个预测请求上通过 HTTPS 提供超快速预测,例如。我们可以使用 SageMaker 端点使用 REST API 协议、HTTPS 和 JSON 部署、扩展和比较我们的模型预测服务器,如图 9-1 所示。

图 9-1。将模型部署为实时 REST 端点。
对于对延迟敏感度较低但需要高吞吐量的应用程序,我们应将模型部署为批处理作业,在例如 S3 中的大量数据上执行批处理预测。我们将使用 SageMaker 批处理转换来执行批处理预测,以及像 Amazon RDS 或 DynamoDB 这样的数据存储来将预测产品化,如图 9-2 所示。

图 9-2。通过 SageMaker 批处理转换将我们的模型部署为批处理作业以在 S3 中执行大量数据的批处理预测。
使用 SageMaker 端点进行实时预测
2002 年,亚马逊创始人杰夫·贝索斯写了一份备忘录给他的员工,后来被称为“贝索斯 API 法令”。该法令要求所有团队必须通过 API 公开其服务,并通过这些 API 进行通信。该法令解决了亚马逊在 2000 年初面临的“僵局”情况,即每个人都想构建和使用 API,但没有人愿意花时间重构他们的单块代码以支持这种理想的最佳实践。该法令解决了僵局,并要求所有团队在亚马逊内部构建和使用 API。
注意
被视为亚马逊早期成功的基石,贝索斯 API 法令是亚马逊网络服务今日的基础。API 帮助亚马逊将其内部生态系统重复使用为其他组织可构建的可扩展托管服务。
遵循贝索斯 API 法令,我们将使用 SageMaker 终端节点将我们的模型部署为 REST API。默认情况下,SageMaker 终端节点是分布式容器。应用程序通过简单的 RESTful 接口调用我们的模型,如 图 9-3 所示,该图展示了模型部署在多个集群实例和可用区以提高可用性。

图 9-3. 应用调用我们托管在 REST 终端点上的高可用模型。
使用 SageMaker Python SDK 部署模型
使用 SageMaker Python SDK 部署模型有两种方式。我们可以在模型对象上调用 deploy(),或者在用于训练模型的 SageMaker 估算器对象上调用 deploy()。
注意
我们还可以部署未使用 SageMaker 训练的模型到 SageMaker。这通常被称为“自带模型”。
接下来是部署我们使用 SageMaker 训练的基于 TensorFlow 和 BERT 的评论分类器模型的代码:
from sagemaker.tensorflow.model import TensorFlowModel
tensorflow_model = TensorFlowModel(
name=tensorflow_model_name,
source_dir='code',
entry_point='inference.py',
model_data=<TENSORFLOW_MODEL_S3_URI>,
role=role,
framework_version='<TENSORFLOW_VERSION>')
tensorflow_model.deploy(endpoint_name=<ENDPOINT_NAME>,
initial_instance_count=1,
instance_type='ml.m5.4xlarge',
wait=False)
接下来是前面指定的 inference.py。这个 Python 脚本包含将原始 JSON 转换为 TensorFlow 张量及从中转换的 input_handler() 和 output_handler() 函数。这些函数是预测请求/响应过程的关键部分。
input_handler() 函数使用 DistilBertTokenizer 将包含原始评论文本的 JSON 转换为 BERT 嵌入张量。这些嵌入被转换为张量并用作 TensorFlow 模型的输入:
def input_handler(data, context):
data_str = data.read().decode('utf-8')
jsonlines = data_str.split("\n")
transformed_instances = []
for jsonline in jsonlines:
review_body = json.loads(jsonline)["features"][0]
encode_plus_tokens = tokenizer.encode_plus(
review_body,
pad_to_max_length=True,
max_length=max_seq_length,
truncation=True)
input_ids = encode_plus_tokens['input_ids']
input_mask = encode_plus_tokens['attention_mask']
transformed_instance = {
"input_ids": input_ids,
"input_mask": input_mask
}
transformed_instances.append(transformed_instance)
transformed_data = {
"signature_name":"serving_default",
"instances": transformed_instances
}
transformed_data_json = json.dumps(transformed_data)
return transformed_data_json
output_handler() 将 TensorFlow 响应从张量转换为包含预测标签(star_rating)和预测置信度的 JSON 响应:
def output_handler(response, context):
response_json = response.json()
outputs_list = response_json["predictions"]
predicted_classes = []
for outputs in outputs_list:
predicted_class_idx = tf.argmax(outputs, axis=-1, output_type=tf.int32)
predicted_class = classes[predicted_class_idx]
prediction_dict = {}
prediction_dict["predicted_label"] = predicted_class
jsonline = json.dumps(prediction_dict)
predicted_classes.append(jsonline)
predicted_classes_jsonlines = "\n".join(predicted_classes)
response_content_type = context.accept_header
return predicted_classes_jsonlines, response_content_type
在我们的实验中跟踪模型部署
我们还希望在我们的实验中跟踪部署以获取数据血统:
from smexperiments.trial import Trial
trial = Trial.load(trial_name=trial_name)
from smexperiments.tracker import Tracker
tracker_deploy = Tracker.create(display_name='deploy',
sagemaker_boto_client=sm)
deploy_trial_component_name = tracker_deploy.trial_component.trial_component_name
# Attach the 'deploy' Trial Component and Tracker to the Trial
trial.add_trial_component(tracker_deploy.trial_component)
# Track the Endpoint Name
tracker_deploy.log_parameters({
'endpoint_name': endpoint_name,
})
# Must save after logging
tracker_deploy.trial_component.save()
分析已部署模型的实验血统
让我们使用实验分析 API 展示我们模型在生产中的血统,包括特征工程、模型训练、超参数优化和模型部署。我们将在第十章中将所有内容绑定在一起,形成端到端的管道,并进行全面的血统追踪,但让我们分析到目前为止的实验血统:
from sagemaker.analytics import ExperimentAnalytics
lineage_table = ExperimentAnalytics(
sagemaker_session=sess,
experiment_name=experiment_name,
metric_names=['validation:accuracy'],
sort_by="CreationTime",
sort_order="Ascending",
)
lineage_table.dataframe()
| TrialComponentName | DisplayName | max_seq_length | learning_rate | train_accuracy | endpoint_name |
|---|---|---|---|---|---|
| TrialComponent-2021-01-09-062410-pxuy | prepare | 64.0 | NaN | NaN | |
| tensorflow-training-2021-01-09-06-24-12-989 | train | 64.0 | 0.00001 | 0.9394 | |
| TrialComponent-2021-01-09-193933-bowu | optimize-1 | 64.0 | 0.000017 | 0.9416 | |
| TrialComponent-2021-01-09214921-dgtu | deploy | NaN | NaN | NaN | tensorflow-training-2021-01-09-06-24-12-989 |
使用 SageMaker Python SDK 调用预测
这里是用于调用我们部署的模型端点并将原始产品评论分类为star_rating 1–5 的简单应用程序代码:
import json
from sagemaker.tensorflow.model import TensorFlowPredictor
from sagemaker.serializers import JSONLinesSerializer
from sagemaker.deserializers import JSONLinesDeserializer
predictor =
TensorFlowPredictor(endpoint_name=tensorflow_endpoint_name,
sagemaker_session=sess,
model_name='saved_model',
model_version=0,
content_type='application/jsonlines',
accept_type='application/jsonlines',
serializer=JSONLinesSerializer(),
deserializer=JSONLinesDeserializer())
inputs = [
{"features": ["This is great!"]},
{"features": ["This is OK."]}
{"features": ["This is bad."]}
]
predicted_classes = predictor.predict(inputs)
for predicted_class in predicted_classes:
print(predicted_class)
### OUTPUT ###
{"predicted_label": 5}
{"predicted_label": 3}
{"predicted_label": 1}
现在让我们使用 pandas DataFrame 对一批原始产品评论进行预测:
import pandas as pd
df_reviews = pd.read_csv('./data/amazon_reviews_us_Digital_Software_v1_00.tsv.gz',
delimiter='\t',
quoting=csv.QUOTE_NONE,
compression='gzip')
df_sample_reviews = df_reviews[['review_body']].sample(n=100)
def predict(review_body):
inputs = [
{"features": [review_body]}
]
predicted_classes = predictor.predict(inputs)
return predicted_classes[0]['predicted_label']
df_sample_reviews['predicted_class'] = \
df_sample_reviews['review_body'].map(predict)
输出显示了star_rating 1–5 的预测类别。
| review_body | predicted_class |
|---|---|
| “这太棒了!” | 5 |
| “这还行。” | 3 |
| “这太糟糕了。” | 1 |
使用 HTTP POST 调用预测
当我们将模型作为微服务投入生产时,我们需要决定如何使我们的预测可供客户端应用程序使用。假设我们具有适当的身份验证凭据和 HTTP 标头,我们可以直接使用以下 HTTP 请求/响应语法调用 SageMaker 端点中的模型。
HTTP 请求语法:
POST /endpoints/<EndpointName>/invocations HTTP/1.1
Content-Type: *ContentType*
Accept: *Accept*
X-Amzn-SageMaker-Custom-Attributes: <CustomAttributes>
X-Amzn-SageMaker-Target-Model: <TargetModel>
X-Amzn-SageMaker-Target-Variant: <TargetVariant>
X-Amzn-SageMaker-Inference-Id: <InferenceId>
This is great!
HTTP 响应语法:
HTTP/1.1 200
Content-Type: *ContentType*
x-Amzn-Invoked-Production-Variant: <InvokedProductionVariant>
X-Amzn-SageMaker-Custom-Attributes: <CustomAttributes>
{'label': 5, 'score': 0.92150515}
在此示例中,我们使用一个inference.py脚本实现了input_handler()和output_handler()函数。对于更复杂的请求和响应处理,我们可以使用 SageMaker 推断流水线将每个函数部署到其自己的容器中,如我们在下一节中所见。
创建推断流水线
推断流水线是部署在单个端点上的一系列步骤。根据我们的例子,我们可以将请求处理程序部署为其自己的 scikit-learn 容器(step1),然后是其自己的 TensorFlow Serving 容器中的 TensorFlow/BERT 模型(step2),最后是其自己的 scikit-learn 容器中的响应处理程序(step3),如图 9-4 所示。

图 9-4. 推断流水线,包含三个步骤。
我们还可以在不同的 AI 和机器学习框架(包括 TensorFlow、PyTorch、scikit-learn、Apache Spark ML 等)之间部署模型集合。每个步骤都是由 SageMaker 控制的容器之间的一系列 HTTPS 请求。一个步骤的响应被用作下一个步骤的预测请求,依此类推。最后一个步骤将最终响应返回到推断管道,推断管道将响应返回给调用应用程序。推断管道完全由 SageMaker 管理,可用于实时预测和批处理转换。
要部署推断管道,我们创建一个包含请求处理程序、模型预测和响应处理程序的PipelineModel序列步骤。然后我们可以在PipelineModel上调用deploy(),这将部署推断管道并返回端点 API:
# Define model name and endpoint name
model_name = 'inference-pipeline-model'
endpoint_name = 'inference-pipeline-endpoint'
# Create a PipelineModel with a list of models to deploy in sequence
pipeline_model = PipelineModel(
name=model_name,
role=sagemaker_role,
models=[
request_handler,
model,
response_handler])
# Deploy the PipelineModel
pipeline_model.deploy(
initial_instance_count=1,
instance_type='ml.c5.xlarge',
endpoint_name=endpoint_name)
pipeline_model.deploy()返回一个预测器,如单一模型示例所示。每当我们向该预测器发出推断请求时,请确保我们传递第一个容器期望的数据。预测器返回最后一个容器的输出。
如果我们想使用PipelineModel运行批处理转换作业,只需按照创建pipeline_model.transformer()对象和调用transform()的步骤即可:
transformer = pipeline_model.transformer(
instance_type='ml.c5.xlarge',
instance_count=1,
strategy='MultiRecord',
max_payload=6,
max_concurrent_transforms=8,
accept='text/csv',
assemble_with='Line',
output_path='<S3_OUTPUT_PATH>')
transformer.transform(
data='<S3_PATH_TO_DATA>',
content_type='text/csv',
split_type='Line')
上述示例演示了如何从一系列 Python 脚本创建步骤。使用 SageMaker 推断管道,我们还可以为每个步骤提供自己的 Docker 容器。
从 SQL 和基于图的查询中调用 SageMaker 模型
AWS 在 Amazon AI、机器学习和分析服务之间提供深度集成。Amazon Redshift、Athena 和 Aurora 可以执行部署为 SageMaker 端点的模型的预测 SQL 查询。Neptune 也可以执行基于图形的查询,并与 SageMaker 端点集成。
使用 Amazon CloudWatch 自动缩放 SageMaker 端点
虽然我们可以使用EndpointConfig中的InstanceCount参数手动缩放,但我们可以根据请求每秒等给定的度量配置我们的端点,使其自动扩展(增加实例)或缩小(减少实例)。随着更多请求的到来,SageMaker 将自动扩展我们的模型集群以满足需求。
在云计算中,除了典型的“缩小”和“放大”外,我们还谈论“扩展内部”和“扩展外部”。扩展内部和扩展外部分别指移除和添加相同类型的实例。缩小和放大则指使用较小或较大的实例类型。通常,较大的实例具有更多的 CPU、GPU、内存和网络带宽。
在定义集群时最好使用同类实例类型。如果混合使用不同的实例类型,可能会难以调整集群并定义适用于异构集群中每个实例的一致缩放策略。在尝试新实例类型时,建议创建一个仅包含该实例类型的新集群,并将每个集群作为单个单位进行比较。
使用 AWS 提供的指标定义缩放策略
在本例中,我们使用 SageMakerVariantInvocationsPerInstance,AWS 提供的 CloudWatch 指标,当我们达到特定的每实例调用阈值时自动扩展我们的模型端点。在接下来的部分中,我们将使用自定义自动缩放指标:
autoscale = boto3.Session().client(
service_name='application-autoscaling',
region_name=region)
autoscale.register_scalable_target(
ServiceNamespace='sagemaker',
ResourceId="endpoint/" + tensorflow_endpoint_name + "/variant/AllTraffic",
ScalableDimension='sagemaker:variant:DesiredInstanceCount',
MinCapacity=1,
MaxCapacity=2,
RoleARN=role,
SuspendedState={
'DynamicScalingInSuspended': False,
'DynamicScalingOutSuspended': False,
'ScheduledScalingSuspended': False
}
)
autoscale.put_scaling_policy(
PolicyName='bert-reviews-autoscale-policy',
ServiceNamespace='sagemaker',
ResourceId="endpoint/" + tensorflow_endpoint_name + "/variant/AllTraffic",
ScalableDimension='sagemaker:variant:DesiredInstanceCount',
PolicyType='TargetTrackingScaling',
TargetTrackingScalingPolicyConfiguration={
'TargetValue': 1000.0,
'PredefinedMetricSpecification': {
'PredefinedMetricType': 'SageMakerVariantInvocationsPerInstance',
},
'ScaleOutCooldown': 60,
'ScaleInCooldown': 300,
}
)
在我们向端点发送大量流量后,我们可以在 CloudWatch 中看到 InvocationsPerInstance 指标的峰值,如图 9-5 所示,以及 CPU 和内存利用率的峰值,如图 9-6 所示。

图 9-5. InvocationsPerInstance 指标的峰值。

图 9-6. CPUUtilization、DiskUtilization 和 MemoryUtilization 由于预测流量的增加而出现的峰值。
这将触发警报,从一个实例扩展到两个实例来处理预测流量的激增,通过在两个实例之间共享流量来实现。图 9-7 显示了向端点集群添加额外实例的正面影响。随着 InvocationsPerInstance 的减少,CPU 和内存利用率也相应减少。

图 9-7. 当我们向端点集群添加第二个实例时,InvocationsPerInstance 的数量减少。
使用自定义指标定义缩放策略
Netflix 以“每秒启动次数”或 SPS(Starts per Second)的自定义自动缩放指标而闻名。每当用户点击“播放”观看电影或电视节目时,就记录一个启动。这是自动缩放的关键指标,因为“每秒启动次数”越多,我们的流媒体控制平面将接收到的流量也就越多。
假设我们正在发布 StartsPerSecond 指标,我们可以使用这个自定义指标来扩展我们的集群,因为更多的电影开始播放。这个指标被称为“目标跟踪”指标,我们需要定义指标名称、目标值、模型名称、变体名称和汇总统计。以下缩放策略将在我们模型服务集群中的所有实例的平均 StartsPerSecond 指标超过 50% 时开始扩展集群:
{
"TargetValue": 50,
"CustomizedMetricSpecification":
{
"MetricName": "StartsPerSecond",
"Namespace": "/aws/sagemaker/Endpoints",
"Dimensions": [
{"Name": "EndpointName", "Value": "ModelA" },
{"Name": "VariantName","Value": "VariantA"}
],
"Statistic": "Average",
"Unit": "Percent"
}
}
当使用自定义指标进行缩放策略时,我们应选择一个衡量实例利用率的指标,随着添加实例而减少,并随着移除实例而增加。
使用冷却期调整响应性
当我们的端点在自动扩展时,我们可能希望指定一个“冷却”期(以秒为单位)。冷却期本质上通过定义扩展策略迭代之间的秒数来减少响应性。当流量激增时,我们可能希望快速扩展,但在快速扩展事件期间,应缓慢缩减以确保处理任何临时的流量下降。下面的扩展策略将在缩减和扩展时采用不同的时间,如ScaleInCooldown和ScaleOutCooldown属性所示:
{
"TargetValue": 60.0,
"PredefinedMetricSpecification":
{
"PredefinedMetricType":
"SageMakerVariantInvocationsPerInstance"
},
"ScaleInCooldown": 600,
"ScaleOutCooldown": 300
}
自动扩展策略
在设置我们的 SageMaker 端点的自动扩展时,有三种主要的扩展策略可供选择:
目标跟踪
指定一个单一的度量,并根据需要由 AWS 自动扩展;例如,“保持InvocationsPerInstance = 1000”。这种策略需要的配置最少。
简单
在给定阈值上触发度量,并使用固定的扩展量;例如,“当InvocationsPerInstance > 1000时,添加 1 个实例”。这种策略需要一些配置,但提供了更多对目标跟踪策略的控制。
步进扩展
在各种阈值上触发度量,并配置每个阈值的扩展量;例如,“当InvocationsPerInstance > 1000时,添加 1 个实例;InvocationsPerInstance > 2000时,添加 5 个实例”等。这种策略需要最多的配置,但在如突发流量等情况下提供了最多的控制。
部署新模型和更新模型的策略
我们可以在单个 SageMaker 端点后面测试和部署新的和更新的模型,使用名为“生产变体”的概念。这些变体可以通过硬件(CPU/GPU)、数据(喜剧/戏剧电影)或区域(美国西部或德国北部)的不同而不同。我们可以安全地在端点中的模型变体之间转移流量,用于金丝雀发布、蓝/绿部署、A/B 测试和 MAB 测试。使用这些部署策略,我们可以在推送新的和更新的模型到生产环境时最小化风险。
分流金丝雀发布
由于我们的数据不断变化,我们的模型需要演变以捕捉这种变化。当我们更新模型时,我们可以选择使用“金丝雀发布”来逐步进行,这个名字来自于使用金丝雀来检测煤矿中是否适合人类呼吸的过程。如果金丝雀在煤矿中存活下来,那么条件是良好的,我们可以继续进行。如果金丝雀没有存活下来,那么我们应该进行调整,并稍后使用不同的金丝雀再次尝试。类似地,我们可以将少量流量指向我们的“金丝雀”模型,并测试模型的服务情况。也许在研究实验室中没有发现的内存泄漏或其他生产特定问题。
云实例提供计算、内存和存储,模型容器应用程序的组合称为“生产变体”。生产变体定义了实例类型、实例数量和模型。默认情况下,SageMaker 端点配置为单个生产变体,但我们可以根据需要添加多个变体。
下面是设置单个变体 VariantA 的代码,在一个端点上接收来自 20 个实例的 100% 流量:
endpoint_config = sm.create_endpoint_config(
EndpointConfigName='my_endpoint_config_name',
ProductionVariants=[
{
'VariantName': 'VariantA',
'ModelName': 'ModelA',
'InstanceType':'ml.m5.large',
'InitialInstanceCount': 20,
'InitialVariantWeight': 100,
}
])
创建新的金丝雀生产变体后,我们可以创建新的端点,并将少量流量(5%)指向金丝雀,其余流量(95%)指向我们的现有变体,如图 9-8 所示。

图 9-8. 将 5% 的流量分配到新模型,用于金丝雀发布。
接下来是创建新端点的代码,包括接受 5% 流量的新金丝雀 VariantB。请注意,我们为新金丝雀 VariantB 指定了 'InitialInstanceCount': 1。假设 20 个实例处理当前流量的 100%,那么每个实例可能处理大约 5% 的流量。这个 5% 与我们希望发送到新金丝雀实例的流量量相匹配。例如,如果我们希望将 10% 的流量发送到新金丝雀,我们将选择 'InitialInstanceCount': 2 来支持 10% 的金丝雀流量。这假设我们对新金丝雀使用相同的实例类型。如果选择不同的实例类型,可能需要更多或更少的实例来处理 % 流量负载:
updated_endpoint_config=[
{
'VariantName': 'VariantA',
'ModelName': 'ModelA',
'InstanceType':'ml.m5.large',
'InitialInstanceCount': 20,
'InitialVariantWeight': 95,
},
{
'VariantName': 'VariantB',
'ModelName': 'ModelB',
'InstanceType':'ml.m5.large',
'InitialInstanceCount': 1,
'InitialVariantWeight': 5,
}
])
sm.update_endpoint(
EndpointName='my_endpoint_name',
EndpointConfigName='my_endpoint_config_name'
)
金丝雀发布可以安全地向小部分用户发布新模型,进行实际生产环境的初步测试。如果我们希望在生产环境中进行测试而不影响整个用户群体,金丝雀发布将非常有用。由于大部分流量流向现有模型,金丝雀模型的集群规模可以相对较小。在上述示例中,我们仅使用单个实例作为金丝雀变体。
蓝/绿部署的流量转移
如果新模型表现良好,我们可以进行蓝/绿部署,将所有流量转移到新模型,如图 9-9 所示。蓝/绿部署有助于在需要回滚到旧部署时减少停机时间。通过蓝/绿部署,我们使用新的金丝雀模型在现有模型服务器集群的完整克隆上进行运行。然后将所有流量从旧集群(蓝)转移到新集群(绿),如图 9-9 所示。蓝/绿部署可以防止部分部署场景,即一些实例运行新金丝雀模型,而另一些实例运行现有模型。在大规模情况下,这种部分部署场景很难调试和管理。

图 9-9. 将流量转移到模型变体 B,用于蓝/绿部署。
以下是更新端点并将 100%流量转移至成功的金丝雀模型VariantB的代码。请注意,我们还增加了新集群的大小,以匹配现有集群的大小,因为新集群现在处理所有流量:
updated_endpoint_config=[
{
'VariantName': 'VariantA',
'ModelName': 'ModelA',
'InstanceType':'ml.m5.large',
'InitialInstanceCount': 20,
'InitialVariantWeight': 0,
},
{
'VariantName': 'VariantB',
'ModelName': 'ModelB',
'InstanceType':'ml.m5.large',
'InitialInstanceCount': 20,
'InitialVariantWeight': 100,
}
])
sm.update_endpoint_weights_and_capacities(
EndpointName='my_endpoint_name',
DesiredWeightsAndCapacities=updated_endpoint_config
)
我们将保留旧的VariantA集群闲置 24 小时,以防我们的金丝雀测试失败,需要快速回滚到旧集群。24 小时后,我们可以移除旧环境并完成蓝/绿部署。以下是移除旧模型VariantA的代码,方法是从端点配置中移除VariantA并更新端点:
updated_endpoint_config=[
{
'VariantName': 'VariantB',
'ModelName': 'ModelB',
'InstanceType':'ml.m5.large',
'InitialInstanceCount': 20,
'InitialVariantWeight': 100,
}
])
sm.update_endpoint(
EndpointName='my_endpoint_name',
EndpointConfigName='my_endpoint_config_name'
)
虽然在我们的示例中,将旧集群闲置一段时间(24 小时)可能看起来是浪费的,但请考虑在需要回滚并扩展先前的VariantA模型所需时间中的停机成本。有时新模型集群在开始几小时运行正常,然后在夜间 cron 作业、清晨产品目录刷新或其他未经测试的场景后突然退化或崩溃。在这些情况下,我们能够立即将流量切换回旧集群,并正常运营。
测试和比较新模型
我们可以在单个 SageMaker 端点后面测试新模型,使用在模型部署的前一节中描述的相同的“生产变体”概念。在本节中,我们将配置我们的 SageMaker 端点,以在端点内的模型之间转移流量,通过 A/B 和 MAB 测试来比较生产中的模型性能。
在生产环境中测试我们的模型时,我们需要定义和跟踪希望优化的业务指标。业务指标通常与收入或用户参与度相关,例如购买订单、观看电影或点击广告次数。我们可以将这些指标存储在任何数据库中,比如 DynamoDB,如图 9-10 所示。分析师和科学家将使用这些数据来确定我们测试中的优胜模型。

图 9-10. 跟踪业务指标以确定最佳模型变体。
在我们的文本分类器示例中,我们将创建一个测试来最大化成功标记的客户服务消息的数量。当客户服务收到新消息时,我们的应用程序将预测消息的 star_rating(1-5)并将 1 和 2 分的消息路由到高优先级的客户服务队列。如果代表同意预测的 star_rating,他们将将我们的预测标记为成功(积极反馈);否则,他们将把预测标记为失败(消极反馈)。不成功的预测可以通过使用 Amazon A2I 和 SageMaker Ground Truth 进行人为介入的工作流程路由,我们将在 第十章 中详细讨论这一点。然后,我们将选择成功预测数量最多的模型变体,并开始将流量转移到这个获胜的变体。让我们深入探讨如何管理实验和转移流量。
执行 A/B 测试以比较模型变体
类似于金丝雀发布,我们可以使用流量分割将用户子集引导到不同的模型变体,以比较和测试生产中的不同模型。目标是看看哪些变体表现更好。通常,这些测试需要长时间运行(几周)才能具有统计学意义。图 9-11 展示了使用随机的 50/50 流量分割部署的两个不同推荐模型。

图 9-11. 通过 50/50 流量分割进行的两个模型变体的 A/B 测试。
虽然 A/B 测试看起来与金丝雀发布类似,但它们专注于收集有关模型不同变体的数据。A/B 测试针对较大的用户群体,需要更多的流量,并且运行时间更长。金丝雀发布更专注于风险缓解和平稳升级。
注意
对于基于 IP 地址、HTTP 头、查询字符串或有效负载内容进行细粒度流量路由,可以在 SageMaker 端点前使用应用程序负载均衡器。
一个模型 A/B 测试的例子可以是流媒体音乐推荐。假设我们正在推荐一个星期天早晨的播放列表。我们可能想要测试我们是否能够识别更倾向于听力量感强烈的醒目音乐的特定用户群体(模型 A),或者更喜欢柔和的休闲音乐的用户群体(模型 B)。让我们使用 Python 实现这个 A/B 测试。我们首先创建一个 SageMaker Endpoint 配置,定义模型 A 和模型 B 的单独生产变体。我们使用相同的实例类型和实例计数初始化这两个生产变体:
import time
timestamp = '{}'.format(int(time.time()))
endpoint_config_name = '{}-{}'.format(training_job_name, timestamp)
variantA = production_variant(model_name='ModelA',
instance_type="ml.m5.large",
initial_instance_count=1,
variant_name='VariantA',
initial_weight=50)
variantB = production_variant(model_name='ModelB',
instance_type="ml.m5.large",
initial_instance_count=1,
variant_name='VariantB',
initial_weight=50)
endpoint_config = sm.create_endpoint_config(
EndpointConfigName=endpoint_config_name,
ProductionVariants=[variantA, variantB]
)
endpoint_name = '{}-{}'.format(training_job_name, timestamp)
endpoint_response = sm.create_endpoint(
EndpointName=endpoint_name,
EndpointConfigName=endpoint_config_name)
在我们监控了一段时间两个模型的性能后,我们可以将 100% 的流量转移到表现更好的模型,我们这里是模型 B。让我们将我们的流量从 50/50 分割转移到 0/100 分割,如 图 9-12 所示。

图 9-12. A/B 测试流量从 50/50 到 0/100 的转移。
以下是代码,用于将所有流量转移到VariantB,并在我们确信VariantB正常运行时最终移除VariantA:
updated_endpoint_config = [
{
'VariantName': 'VariantA',
'DesiredWeight': 0,
},
{
'VariantName': 'VariantB',
'DesiredWeight': 100,
}
]
sm.update_endpoint_weights_and_capacities(
EndpointName='my_endpoint_name',
DesiredWeightsAndCapacities=updated_endpoint_config
)
updated_endpoint_config=[
{
'VariantName': 'VariantB',
'ModelName': 'ModelB',
'InstanceType':'ml.m5.large',
'InitialInstanceCount': 2,
'InitialVariantWeight': 100,
}
])
sm.update_endpoint(
EndpointName='my_endpoint_name',
EndpointConfigName='my_endpoint_config_name'
)
使用多臂老丨虎丨机测试的强化学习
A/B 测试是静态的,必须运行一段时间——有时是几周或几个月——才能被认为具有统计显著性。在此期间,我们可能部署了一个影响收入的糟糕模型变体。然而,如果我们提前停止测试,我们将破坏实验的统计显著性,并不能从结果中得出多少意义。换句话说,我们的模型可能最初表现不佳,但如果实验运行时间更长,实际上可能是更好的模型。A/B 测试是静态的,并不允许我们在实验期间动态地转移流量,以减少由表现不佳的模型引起的“后悔”。它们也不允许我们在实验的生命周期内添加或删除模型变体。
一种更动态的测试不同模型变体的方法称为多臂老丨虎丨机(MABs)。这种方法得名于一个可以迅速夺走我们钱的老丨虎丨机,这些淘气的"土匪"事实上可以通过动态地将流量转移到获胜的模型变体来为我们赚取相当可观的收益,远比 A/B 测试更快。这就是 MAB 的“利用”部分。与此同时,MAB 仍然“探索”那些未获胜的模型变体,以防早期的获胜者并不是整体最佳模型变体。这种“利用和探索”的动态平衡赋予了 MAB 他们的力量。基于强化学习(RL),MAB 依赖于正负反馈机制来选择“动作”。
在我们的情况下,MAB 根据当前奖励指标和选择的利用-探索策略选择模型变体。基于 RL 的 MAB 充当主要的 SageMaker 端点,并动态路由预测流量到可用的基于 BERT 的 SageMaker 端点,如图 9-13 所示。

图 9-13. 使用 RL 和 MABs 找到最佳的 BERT 模型。
存在多种 MAB 探索策略,包括ε-greedy、Thompson 采样、bagging 和在线覆盖。ε-greedy 使用固定的利用-探索阈值,而 Thompson 采样基于先验信息使用更复杂和动态的阈值,这是一种基于贝叶斯统计的技术。Bagging 使用集成方法,通过训练数据的随机子集生成一组策略以进行集成。在线覆盖理论上是基于论文《驯服怪物:一种快速简单的情境赌博算法》的最优探索算法。与 Bagging 不同的是,在线覆盖训练一组策略以生成更复杂和完整的预测,以实现更复杂和全面的探索策略。
SageMaker 原生支持流行的 RL 库,包括 Vowpal Wabbit、Ray、Coach、Unity 等。此外,我们可以通过为 SageMaker 构建自己的 Docker 镜像来使用任何其他强化学习库进行部署和管理。在我们的示例中,我们将使用 Vowpal Wabbit 和在线覆盖探索策略。我们基于 Vowpal Wabbit 的 MAB 持续根据最新的奖励指标进行训练,并将调整预测流量,向获胜的基于 BERT 的模型发送更多流量,如图 9-14 所示,其中模型 2 开始随着累积奖励接收更多流量。

图 9-14. MAB 动态转移流量至“获胜”模型变体。
图 9-15 展示了在 AWS 上使用 Vowpal Wabbit RL 框架、SageMaker、Amazon Kinesis Firehose、S3 持久存储以及 Athena 应用查询的完整端到端 MAB 生产实现。

图 9-15. AWS 上 MAB 和 RL 的完整端到端实现。
我们正在使用 Vowpal Wabbit RL 框架持续训练我们的多臂赌博机(MAB),随着新的奖励数据从我们的应用程序流入系统。新版本的 MAB 模型持续部署为 SageMaker 端点。由于 MAB 的动态特性,我们可以动态地添加和移除测试中的模型变体。这是我们在传统的 A/B 测试中做不到的,因为在实验的整个生命周期内,我们需要保持所有模型变体不变。
以下是我们的 MAB 模型配置的一部分,使用了与 SageMaker、DynamoDB 和 Kinesis 原生集成的 Vowpal Wabbit,同时也突出了在线覆盖探索策略使用的超参数,包括要训练的子策略数量以及要使用的反事实分析(CFA)策略:
resource:
shared_resource:
experiment_db:
table_name: "BanditsExperimentTable" # Status of an experiment
model_db:
table_name: "BanditsModelTable" # Status of trained models
join_db:
table_name: "BanditsJoinTable" # Status of reward ingestion
image: "sagemaker-rl-vw-container:vw-<VW_VERSION>"
<VOWPAL_WABBIT_VERSION>-<CPU_OR_GPU>" # Vowpal Wabbit container
algor: # Vowpal Wabbit algorithm parameters
algorithms_parameters:
exploration_policy: "cover"
num_policies: 3 # number of online cover policies to create
num_arms: 2
cfa_type: "dr" # supports "dr", "ips"
我们选择在决定采取哪个操作(调用哪个 BERT 模型)时训练三个子策略,以及双重稳健(DR)CFA 方法。有关这些超参数的更多信息,请参阅Vowpal Wabbit 文档和与本书相关的 GitHub 存储库。
以下是来自 SageMaker 培训作业日志的片段,因为强盗模型不断根据系统中到达的新奖励数据进行训练。在这种情况下,捡起了六百个新奖励:
/usr/bin/python train-vw.py --cfa_type dr --epsilon 0.1 --exploration_policy
cover --num_arms 2 --num_policies 3
INFO:root:channels ['pretrained_model', 'training']
INFO:root:hps: {'cfa_type': 'dr', 'epsilon': 0.1, 'exploration_policy':
'cover', 'num_arms': 2, 'num_policies': 3}
INFO:root:Loading model from /opt/ml/input/data/pretrained_model/vw.model
INFO:VW CLI:creating an instance of VWModel
INFO:VW CLI:successfully created VWModel
INFO:VW CLI:command: ['vw', '--cb_explore', '2', '--cover', '3', '-i',
'/opt/ml/input/data/pretrained_model/vw.model', '-f', '/opt/ml/model/vw.model',
'--save_resume', '-p', '/dev/stdout']
INFO:VW CLI:Started VW process!
INFO:root:Processing training data: [PosixPath('/opt/ml/input/data/training/local-
joined-data-1605218616.csv')]
finished run
number of examples = 600
INFO:root:Model learned using 600 training experiences.
INFO Reporting training SUCCESS
假设我们想比较两个 BERT 模型:BERT 模型 1 和 BERT 模型 2。我们将重复使用我们在第七章中训练的 BERT 模型 1。该模型的训练准确率接近 93%,验证准确率约为 50%。考虑到随机预测五个类别的机会为 20%,50%并不算糟糕。对于 BERT 模型 2,我们训练了一个准确率略低于 40%的模型。
我们部署了两个 BERT 模型和一个新的 MAB。在生产中运行这些模型后,我们分析了 MAB 用于选择 Model 1 或 Model 2 的最新概率。动作概率是根据当前奖励信息和背景选择 Model 1 或 Model 2 作为最佳选择的概率测量值。BERT 模型 1 的平均动作概率为 0.743,BERT 模型 2 为 0.696。在这种情况下,BERT 模型 1 因较高的动作概率而受青睐。图 9-16 显示了 MAB 用于所有预测的动作概率图表。

图 9-16. MAB 动作概率。
样本概率是衡量强盗将根据探索策略、当前奖励信息和背景选择 Model 1 或 Model 2 的概率。结合动作概率和样本概率,确定强盗用于分类评论测试的 BERT 模型。我们的强盗使用 BERT 模型 1 的平均样本概率为 0.499,BERT 模型 2 为 0.477。在这种情况下,BERT 模型 1 因较高的样本概率而受青睐。
图 9-17 显示了 MAB 在所有预测中用于选择 BERT 模型 1 和 BERT 模型 2 之间的样本概率。

图 9-17. MAB 样本概率。
我们还注意到两个变体之间流量的变化,如图 9-18 所示。模型 2 开始时拥有所有流量,但随着 MAB 开始因较高的奖励而更喜欢模型 1,其接收的流量逐渐减少,这导致更高的样本概率。

图 9-18. 流量分割在 BERT 模型 1 和 BERT 模型 2 之间。
我们看到 BERT 模型 1,即现有模型,在挑战者模型 BERT 模型 2 面前具有优势。在这种情况下,我们选择保留模型 1 并不替换为 BERT 模型 2。
让我们分析奖励与遗憾,确保我们的模型在探索过程中适当地进行利用和探索,并在此过程中不放弃太多。如果模型预测star_rating正确,则分配奖励为 1;如果预测错误,则分配奖励为 0。因此,奖励与模型准确度相关联。平均奖励为 0.472,这不巧是我们在第七章中训练的 BERT 模型 1 和 BERT 模型 2 的验证精度的混合值。图 9-19 展示了所有预测中滚动一百次的平均奖励的图表。

图 9-19. 实验中滚动一百次的平均奖励。
所有这些图表表明,贝叶斯测试初始通过将流量发送到 BERT 模型 1 和 BERT 模型 2 来探索动作空间,找到早期的优胜者,并利用 BERT 模型 2 进行约 230 次预测。然后它再次开始探索,直到约 330 次预测时,它再次开始利用 BERT 模型 2,直至第 500 次预测,那时它可能会再次开始探索。
探索和利用之间的这种权衡由选择的探索策略控制,是 A/B 测试和多臂老丨虎丨机测试之间的关键区别。通过激进的探索策略,我们将看到贝叶斯在空间中进行探索,并降低平均奖励。在这里,我们使用自调整的在线覆盖探索策略。
贝叶斯帮助我们最小化在生产中部署性能不佳的模型所带来的遗憾,并让我们在真实数据上快速了解我们的 BERT 模型性能。如果我们的某个 BERT 模型表现不佳,我们可以从实验中移除该模型,甚至添加新的模型变体,使用我们选择的探索策略来开始探索。
我们可以使用框架文档中描述的超参数来调整我们的 Vowpal Wabbit 贝叶斯模型。有关在线覆盖探索策略的 Vowpal Wabbit 超参数的更多信息,请参阅Vowpal Wabbit 文档。
我们还可以提供历史数据,以在最初部署到生产环境之前对 RL 模型进行预训练。这将用我们的模型种子化动作和样本概率,这可能会减少 RL 模型在从头学习动作和样本空间时由初始探索阶段引起的遗憾。
请记住,这只是在几分钟内的预测示例。我们可能希望延长实验时间,以获得更多关于哪种模型更适合我们的应用程序和用例的见解。
监控模型性能并检测漂移
世界在我们周围继续变化。客户行为相对快速变化。应用团队发布新功能。Netflix 目录正在充实新内容。欺诈者正找到聪明的方法来盗取我们的信用卡信息。一个不断变化的世界需要持续对我们的预测模型进行再培训和重新部署,以调整这些真实世界漂移情况。
在第 5 章中,我们讨论了可能导致模型性能下降的各种漂移类型。通过自动记录 SageMaker 端点的输入(特征)和输出(预测),SageMaker Model Monitor 自动检测并测量与提供的基线的漂移。当漂移达到用户指定的基线(在我们训练模型期间学习并在模型部署期间指定)的阈值时,SageMaker Model Monitor 将通知我们。
SageMaker Model Monitor 使用统计方法(如 Kullback–Leibler 散度和 L-infinity 范数)计算漂移。例如,对于 L-infinity 范数,SageMaker Model Monitor 支持linf_simple和linf_robust。linf_simple方法基于两个分布的累积分布函数之间的最大绝对差异。linf_robust方法基于linf_simple,但在样本不足时使用。linf_robust公式基于两样本 Kolmogorov–Smirnov 测试。
启用数据捕获
SageMaker Model Monitor 分析我们模型的预测(及其输入),以检测数据质量、模型质量、模型偏差或特征归因的漂移。首先,我们需要为给定的端点启用数据捕获,如图 9-20 所示。

图 9-20. 启用给定端点的数据捕获。
以下是启用数据捕获的代码。我们可以在DataCaptureConfig对象中定义所有配置选项。使用此配置可以选择捕获请求有效负载、响应有效负载或两者。捕获配置适用于端点的所有模型生产变体:
from sagemaker.model_monitor import DataCaptureConfig
data_capture_config = DataCaptureConfig(
enable_capture=True,
sampling_percentage=100,
destination_s3_uri='<S3_PATH>')
Next, we pass the DataCaptureConfig in the model.deploy()call:
predictor = model.deploy(
initial_instance_count=1,
instance_type='ml.m5.xlarge',
endpoint_name=endpoint_name,
data_capture_config=data_capture_config)
现在我们正在捕获指定的 S3 目的地中的所有推断请求和预测结果。
理解基线和漂移
在第 5 章中,我们探索了数据集并可视化了每个product_category和star_rating的评论分布。我们将使用这些数据创建基准分布度量标准,以与我们的 SageMaker 模型端点观察到的实时分布进行比较。图 9-21 显示了每个产品类别的评论数量。

图 9-21. 我们数据中每个产品类别的评论数量是输入特征分布的基准示例。
这代表用于训练我们的模型的product_category输入特征的基线分布。SageMaker Model Monitor 捕获我们 SageMaker 模型端点看到的实际模型输入分布,将其与训练期间使用的基线分布进行比较,并生成衡量模型输入分布中协变量漂移的漂移度量。
如果我们指定的测量漂移超过阈值,SageMaker Model Monitor 会通知我们,并可能重新训练和部署在最新输入数据分布上训练的模型的更新版本。图 9-22 显示每个product_category和star_rating的数据的基线分布,来自第五章。

图 9-22. 我们训练数据中star_rating标签的分布是目标分布的一个示例基线。
我们可以使用 SageMaker Model Monitor 的数据质量监控功能检测模型输入分布中的协变量漂移。我们还可以使用 SageMaker Model Monitor 的模型质量监控功能检测概念漂移,该功能将实时预测与由 SageMaker Model Monitor 在实时预测中捕获的相同模型输入的地面实况标签进行比较。这些地面实况标签由人类在离线人类环路工作流中提供,例如使用 Amazon A2I 和 SageMaker Ground Truth,在第三章中有描述。
此外,SageMaker Model Monitor 的模型质量功能可以监控、衡量和检测模型偏差、特征重要性和模型可解释性的漂移。每个漂移相对于我们训练模型生成的基线进行测量。这些基线由启用 SageMaker Model Monitor 的每个 SageMaker 终端节点提供。
监控部署的 SageMaker 终端节点的数据质量
我们的模型学习并适应我们训练数据的统计特征。如果我们在线模型接收到的数据的统计特征从该基线漂移,模型质量将会下降。我们可以使用 Deequ 创建数据质量基线,如在第五章中讨论的那样。Deequ 分析输入数据,并为每个输入特征创建模式约束和统计信息。我们可以识别缺失值并相对于该基线检测协变量漂移。SageMaker Model Monitor 使用 Deequ 为数据质量监控创建基线。
创建基线以衡量数据质量
数据质量基线帮助我们检测在线模型输入的统计特征中的漂移,这些输入来自所提供的基线数据。通常,我们使用我们的训练数据创建第一个基线,如图 9-23 所示。

图 9-23. 从训练数据创建数据质量基线。
训练数据集模式和推理数据集模式必须完全匹配,包括特征数量和它们传递的顺序。现在我们可以启动 SageMaker 处理作业,建议一组基线约束,并生成数据统计,如下所示:
from sagemaker.model_monitor import DefaultModelMonitor
from sagemaker.model_monitor.dataset_format import DatasetFormat
my_default_monitor = DefaultModelMonitor(
role=role,
instance_count=1,
instance_type='ml.m5.xlarge',
volume_size_in_gb=20,
max_runtime_in_seconds=3600,
)
my_default_monitor.suggest_baseline(
baseline_dataset='s3://*my_bucket*/*path*/some.csv',
dataset_format=DatasetFormat.csv(header=True),
output_s3_uri='s3://*my_bucket*/*output_path*/',
wait=True
)
基线作业完成后,我们可以查看生成的统计数据:
import pandas as pd
baseline_job = my_default_monitor.latest_baselining_job
statistics = pd.io.json.json_normalize(
baseline_job.baseline_statistics().body_dict["features"])
这是我们review_body预测输入的一组统计数据示例:
"name" : "Review Body",
"inferred_type" : "String",
"numerical_statistics" : {
"common" : {
"num_present" : 1420,
"num_missing" : 0
}, "data" : [ [ "I love this item.", "This item is OK", … ] ]
我们可以按以下方式查看生成的约束:
constraints = pd.io.json.json_normalize(
baseline_job.suggested_constraints().body_dict["features"])
这里是我们review_body预测输入的约束定义示例:
{
"name" : "Review Body",
"inferred_type" : "String",
"completeness" : 1.0
}
在这个例子中,如果review_body中有缺失值,约束将引发警报。通过基线,我们现在可以创建和调度数据质量监控作业。
调度数据质量监控作业
SageMaker 模型监控使我们能够定期监控从端点收集的数据。我们可以使用CreateMonitoringSchedule API 创建一个周期性间隔的调度。类似于数据质量基线作业,SageMaker 模型监控启动一个 SageMaker 处理作业,用于比较当前分析的数据集与基线统计数据和约束条件。结果将生成违规报告。此外,SageMaker 模型监控将每个特征的指标发送到 CloudWatch,如图 9-24 所示。

图 9-24. SageMaker 模型监控使我们能够定期监控从端点收集的数据。
我们可以使用my_default_monitor.create_monitoring_schedule()为端点创建模型监控调度。在监控调度的配置中,我们指向基线统计数据和约束,并定义一个 cron 调度:
from sagemaker.model_monitor import DefaultModelMonitor
from sagemaker.model_monitor import CronExpressionGenerator
mon_schedule_name = 'my-model-monitor-schedule'
my_default_monitor.create_monitoring_schedule(
monitor_schedule_name=mon_schedule_name,
endpoint_input=predictor.endpoint,
output_s3_uri=s3_report_path,
statistics=my_default_monitor.baseline_statistics(),
constraints=my_default_monitor.suggested_constraints(),
schedule_cron_expression=CronExpressionGenerator.hourly(),
enable_cloudwatch_metrics=True,
)
SageMaker 模型监控现在按计划间隔运行,并分析捕获的数据与基线的比较。作业生成违规报告,并将报告存储在 Amazon S3 中,同时存储收集数据的统计报告。
一旦监控作业开始执行,我们可以使用list_executions()查看所有执行情况:
executions = my_monitor.list_executions()
SageMaker 模型监控作业应以以下状态之一退出:
完成
监控执行完成且未发现违规。
带违规完成
监控执行完成,但发现了约束违规。
失败
监控执行失败,可能是由于角色权限不正确或基础设施问题。
停止
作业超出了指定的最大运行时间或被手动停止。
注意
我们可以使用预处理和后处理脚本创建自定义的监控调度和流程。我们也可以构建自己的分析容器。
检查数据质量结果
通过收集的监控数据并持续与数据质量基线进行比较,我们现在能够更好地决定如何改进模型。根据模型监控结果,我们可能决定重新训练和部署模型。在这一最后步骤中,我们展示并解释数据质量监控结果,如图 9-25 所示。

图 9-25. 可视化和解释数据质量监控结果。
让我们查询生成报告的位置:
report_uri=latest_execution.output.destination
print('Report Uri: {}'.format(report_uri))
接下来,我们可以列出生成的报告:
from urllib.parse import urlparse
s3uri = urlparse(report_uri)
report_bucket = s3uri.netloc
report_key = s3uri.path.lstrip('/')
print('Report bucket: {}'.format(report_bucket))
print('Report key: {}'.format(report_key))
s3_client = boto3.Session().client('s3')
result = s3_client.list_objects(Bucket=report_bucket,
Prefix=report_key)
report_files = [report_file.get("Key") for report_file in
result.get('Contents')]
print("Found Report Files:")
print("\n ".join(report_files))
输出:
s3://<bucket>/<prefix>/constraint_violations.json
s3://<bucket>/<prefix>/constraints.json
s3://<bucket>/<prefix>/statistics.json
我们已经查看了constraints.json和statistics.json,所以让我们分析违规情况:
violations =
my_default_monitor.latest_monitoring_constraint_violations()
violations = pd.io.json.json_normalize(
violations.body_dict["violations"])
下面是我们对review_body输入的违规示例:
{
"feature_name" : "review_body",
"constraint_check_type" : "data_type_check",
"description" : "Value: 1.0 meets the constraint requirement"
}, {
"feature_name" : "review_body",
"constraint_check_type" : "baseline_drift_check",
"description" : "Numerical distance: 0.2711598746081505 exceeds
numerical threshold: 0"
}
要找出数据质量漂移的根本原因,我们需要检查模型输入并检查最近引入的任何上游应用程序错误(或功能)。例如,如果应用团队添加了我们的模型未经过训练的新产品类别集,模型可能对这些特定产品类别预测不准确。在这种情况下,SageMaker Model Monitor 将检测模型输入中的协变量漂移,通知我们,并有可能重新训练和部署模型。
举个极端的例子,假设应用团队开始将表情符号作为主要的评论机制。鉴于我们的评论分类器并未在包含表情符号的词汇表上进行训练,模型可能对包含表情符号的评论预测不准确。在这种情况下,SageMaker Model Monitor 将通知我们评论语言分布的变化。然后,我们可以重新训练和部署一个理解表情符号语言的更新模型。
监控部署的 SageMaker 端点的模型质量
我们还可以使用 SageMaker Model Monitor 检测模型质量指标如准确性的漂移。SageMaker Model Monitor 将在线模型预测与提供的地面实况标签进行比较。模型质量监控可用于检测概念漂移。
输入数据由 SageMaker Model Monitor 使用实时数据捕获功能捕获。这些数据保存到 S3,并由人类离线标记。然后,模型质量作业根据我们定义的时间表比较离线数据。如果模型质量下降,SageMaker Model Monitor 将通知我们,并有可能重新训练和部署模型,包括由人类标记的地面实况数据。请注意,由于需要人类交互,地面实况标签的可用性可能会延迟。图 9-26 显示了使用人力工作人员提供的离线地面实况标签进行模型质量漂移检测的高级概述。

图 9-26. 比较模型预测与由人力离线生成的地面实况数据标签。
在这里,模型质量任务将实际的人工选择的star_rating与模型端点预测的star_rating进行比较。该任务计算混淆矩阵和标准多类别分类指标,包括准确率、精确率、召回率等:
"multiclass_classification_metrics" : {
"confusion_matrix" : {
...
},
"accuracy" : {
"value" : 0.6288167938931297,
"standard_deviation" : 0.00375663881299405
},
...
}
在开始监控模型质量之前,我们需要创建一个基准。
创建基准来衡量模型质量
模型质量基准作业将模型的预测与我们在 S3 中存储的提供的地面实况标签进行比较。然后,基准作业计算相关的模型质量指标,并建议适用的约束条件以识别漂移。
我们首先创建一个ModelQualityMonitor如下所示:
from sagemaker.model_monitor import ModelQualityMonitor
model_quality_monitor = ModelQualityMonitor(
role=role,
instance_count=1,
instance_type='ml.m5.xlarge',
volume_size_in_gb=20,
max_runtime_in_seconds=1800,
sagemaker_session=sess
)
然后,我们可以按以下方式使用suggest_baseline启动基准作业:
job = model_quality_monitor.suggest_baseline(
job_name=baseline_job_name,
baseline_dataset=baseline_dataset_uri,
dataset_format=DatasetFormat.csv(header=True),
output_s3_uri = baseline_results_uri,
problem_type='MulticlassClassification',
inference_attribute= 'prediction',
probability_attribute= 'probability',
ground_truth_attribute= 'star_rating')
任务完成后,我们可以在指定的 S3 输出路径中的constraints.json文件中查看建议的约束条件。在我们的示例中,该文件将包含我们多类别分类模型的建议约束条件。确保审查约束条件并根据需要进行调整。然后,在安排模型质量监控任务时,我们将把约束条件作为参数传递:
{
"version" : 0.0,
"multiclass_classification_constraints" : {
"weighted_recall" : {
"threshold" : 0.5714285714285714,
"comparison_operator" : "LessThanThreshold"
},
"weighted_precision" : {
"threshold" : 0.6983172269629505,
"comparison_operator" : "LessThanThreshold"
},
...
}
安排模型质量监控作业
模型质量监控作业遵循与数据质量监控作业相同的调度步骤。需要注意的一个区别是,模型质量监控作业假定捕获的预测已经准备好地面实况标签。由于需要人工提供地面实况标签,我们需要处理潜在的延迟。因此,模型质量监控作业提供额外的StartOffset和EndOffset参数,分别从作业的开始时间和结束时间中减去指定的偏移量。
例如,如果我们开始提供地面实况标签的时间比数据捕获晚一天,我们可以通过指定StartOffset为-P3D 和EndOffset为-P1D 为监控作业授予三天的窗口期来标记地面实况数据。假设在那段时间内标记了地面实况数据,则作业将分析从三天前到一天前的数据。然后,作业将地面实况标签与捕获的模型预测合并,并计算分布漂移。
我们可以按以下方式创建模型质量监控作业:
sm = boto3.Session().client(service_name='sagemaker', region_name=region)
sm.create_model_quality_job_definition(
JobDefinitionName=<NAME>,
ModelQualityBaselineConfig={...},
ModelQualityAppSpecification={...},
ModelQualityJobInput={...
'EndpointInput': {...},
'GroundTruthS3Input': {...},
ModelQualityJobOutputConfig={...},
JobResources={...}
NetworkConfig={...},
RoleArn=<IAM_ROLE_ARN>)
我们定义我们的ModelQualityMonitor的监控计划如下:
model_quality_monitor.create_monitoring_schedule(
endpoint_input=<ENDPOINT_NAME>,
ground_truth_input=<S3_INPUT_PATH>,
problem_type='MulticlassClassification',
record_preprocessor_script=<S3_PRE_SCRIPT_PATH>,
post_analytics_processor_script=<S3_POST_SCRIPT_PATH>,
output_s3_uri=<S3_OUTPUT_PATH>,
constraints=<S3_CONSTRAINTS_PATH>,
monitor_schedule_name=<NAME>,
schedule_cron_expression=<SCHEDULE>,
enable_cloudwatch_metrics=True)
ModelQualityMonitor现在按预定间隔运行,并基于捕获数据和地面实况标签比较模型质量指标与基线。我们可以在 Amazon S3 中检查约束违规报告。
检查模型质量监控结果
ModelQualityMonitor 将约束违规存储在 Amazon S3 中。我们可以直接在 SageMaker Studio 中比较基线和观察到的模型质量度量,如 Figure 9-27 所示,或者通过以下代码程序化地检查约束违规。顶部是基线的平均准确率,底部是当前的平均准确率。

Figure 9-27. SageMaker Studio 端点详细信息显示模型质量度量图表,如平均准确率。
import pandas as pd
latest_exec = model_quality_monitor.list_executions()[-1]
report_uri =
latest_exec.describe()\
["ProcessingOutputConfig"]["Outputs"][0]["S3Output"]["S3Uri"]
pd.options.display.max_colwidth = None
violations =
latest_exec.constraint_violations().body_dict["violations"]
pd.json_normalize(violations)
监控部署的 SageMaker 端点的偏差漂移
即使我们已经清除了训练数据中的偏差并采取了措施来减轻我们训练的模型中的偏差,偏差仍可能会在部署的模型中引入。这种情况发生在我们的模型所见的数据与训练数据的分布不同的情况下。新数据也可能导致我们的模型对输入特征分配不同的权重。SageMaker Clarify 与 SageMaker Model Monitor 集成,帮助我们检测部署模型中的偏差漂移。
创建基线以检测偏差
SageMaker Clarify 持续监控我们部署模型的偏差度量,并在这些度量超出定义的阈值时发出警报。我们从创建 ModelBiasMonitor 开始:
from sagemaker.model_monitor import ModelBiasMonitor
model_bias_monitor = ModelBiasMonitor(
role=role,
sagemaker_session=sagemaker_session,
max_runtime_in_seconds=1800,
)
类似于在 Chapter 7 中使用 SageMaker Clarify 检测训练后模型偏差,我们需要指定 DataConfig、BiasConfig 和 ModelConfig,指向用于推理的模型。ModelPredictedLabelConfig 再次指定如何解析模型预测:
from sagemaker import clarify
data_config = clarify.DataConfig(
s3_data_input_path=validation_dataset,
s3_output_path=model_bias_baselining_job_result_uri,
label='star_rating',
headers=['review_body', 'product_category', ...],
dataset_type='text/csv')
bias_config = clarify.BiasConfig(
label_values_or_threshold=[5, 4]
facet_name='product_category',
facet_values_or_threshold=['Gift Card'],
group_name='product_category')
model_config = clarify.ModelConfig(
model_name=model_name,
instance_type='ml.m5.4xlarge',
instance_count=1,
content_type='text/csv',
accept_type='application/jsonlines')
predictions_config = clarify.ModelPredictedLabelConfig(label='predicted_label')
有了这个配置,我们可以创建并启动模型偏差基线作业:
model_bias_monitor.suggest_baseline(
model_config=model_config,
data_config=data_config,
bias_config=bias_config,
model_predicted_label_config=model_predicted_label_config,
)
通过调用 suggest_baseline(),我们启动 SageMaker Clarify 处理作业以生成约束条件。一旦作业完成并且我们有了偏差基线,我们可以创建偏差漂移监控作业并进行调度。
调度偏差漂移监控作业
监控将自动从基线作业中获取结果作为其模型偏差分析配置。如果我们还没有运行基线作业,我们也可以手动创建分析配置:
model_bias_monitor.create_monitoring_schedule(
analysis_config=analysis_config,
output_s3_uri=s3_report_path,
endpoint_input=EndpointInput(
endpoint_name=endpoint_name,
destination="/opt/ml/processing/input/endpoint",
start_time_offset="-PT1H",
end_time_offset="-PT0H",
probability_threshold_attribute=<THRESHOLD>,
),
ground_truth_input=ground_truth_upload_path,
schedule_cron_expression=schedule_expression,
)
请注意,模型偏差监视器还使用提供的地面真实标签数据。偏差监控作业将地面真实标签与捕获的模型预测合并,并使用组合数据作为其验证数据集。偏差漂移监控结果再次存储在 Amazon S3 中。
检查偏差漂移监控结果
我们在 SageMaker Studio 中检查每个监控端点的偏差和漂移结果,如 Figure 9-28 所示,或者通过以下代码进行程序化检查:
schedule_desc = model_bias_monitor.describe_schedule()
exec_summary = schedule_desc.get("LastMonitoringExecutionSummary")
if exec_summary and exec_summary["MonitoringExecutionStatus"] in
["Completed", "CompletedWithViolations"]:
last_exec = model_bias_monitor.list_executions()[-1]
last_exec_report_uri = last_exec.output.destination
last_exec_report_files =
sorted(S3Downloader.list(last_exec_report_uri))
last_exec = None

Figure 9-28. SageMaker Studio 端点详细信息显示偏差漂移监控结果。
如果偏差漂移监视器检测到与其基线相比的任何违规行为,我们可以列出违规行为如下:
if last_exec:
model_bias_violations = last_exec.constraint_violations()
if model_bias_violations:
print(model_bias_violations.body_dict)
监控部署的 SageMaker 端点的特征归因漂移
与模型偏差漂移类似,SageMaker Clarify 监控随时间变化的贡献于预测的特征。特征归因有助于解释模型预测。如果特征归因的排名发生变化,SageMaker Clarify 将提出特征归因漂移警报。SageMaker Clarify 实施了一种称为SHAP的模型不可知方法来分析全局和局部特征重要性。SHAP 受博弈论启发,生成仅通过一个特征不同的多个数据集。SHAP 使用训练模型为每个生成的数据集接收模型预测。该算法将结果与预先计算的基线统计数据进行比较,以推断每个特征对预测目标的重要性。
创建一个基线以监控特征归因
特征归因基线作业可以利用用于模型偏差基线作业的相同数据集:
model_explainability_data_config = DataConfig(
s3_data_input_path=validation_dataset,
s3_output_path=model_explainability_baselining_job_result_uri,
label='star_rating',
headers=['review_body', product_category', ...],
dataset_type='text/csv')
SageMaker Clarify 为模型解释实施了 SHAP。因此,我们需要按照以下SHAPConfig提供:
# Using the mean value of test dataset as SHAP baseline
test_dataframe = pd.read_csv(test_dataset, header=None)
shap_baseline = [list(test_dataframe.mean())]
shap_config = SHAPConfig(
baseline=shap_baseline,
num_samples=100,
agg_method="mean_abs",
save_local_shap_values=False,
)
shap_baseline需要包含用作基线数据集的行列表,或者基线数据集的 S3 对象 URI。数据应只包含特征列,没有标签列。num_samples指定在 Kernel SHAP 算法中使用的样本数。agg_method定义全局 SHAP 值的聚合方法。我们可以选择mean_abs(绝对 SHAP 值的平均)、median(所有 SHAP 值的中位数)和mean_sq(平方 SHAP 值的平均)之间。
然后,我们可以按以下方式启动特征归因基线作业:
model_explainability_monitor.suggest_baseline(
data_config=model_explainability_data_config,
model_config=model_config,
explainability_config=shap_config,
)
通过调用suggest_baseline(),我们启动 SageMaker Clarify 处理作业以生成约束条件。基线作业完成后,我们可以查看建议的约束条件如下:
model_explainability_constraints =
model_explainability_monitor.suggested_constraints()
现在我们可以创建特征归因漂移监控作业并进行调度。
定时特征归因漂移监控作业
监视器将自动从基线作业中提取其特征归因分析配置的结果。如果我们尚未运行基线作业,我们也可以手动创建分析配置:
model_explainability_monitor.create_monitoring_schedule(
output_s3_uri=s3_report_path,
endpoint_input=endpoint_name,
schedule_cron_expression=schedule_expression,
)
检查特征归因漂移监控结果
我们可以按以下方式检查特征归因漂移监控结果:
schedule_desc = model_explainability_monitor.describe_schedule()
exec_summary = schedule_desc.get("LastMonitoringExecutionSummary")
if exec_summary and exec_summary["MonitoringExecutionStatus"] in \
["Completed", "CompletedWithViolations"]:
last_exec = model_explainability_monitor.list_executions()[-1]
last_exec_report_uri = last_exec.output.destination
last_exec_report_files = sorted(S3Downloader.list(last_exec_report_uri))
else:
last_exec = None
如果特征归因漂移监视器检测到与其基线相比的任何违规行为,我们可以列出违规行为如下:
if last_exec:
explainability_violations = last_exec.constraint_violations()
if explainability_violations:
print(explainability_violations.body_dict)
在 SageMaker Studio 中,我们还可以在端点详细信息中找到每个监控端点的可解释性结果,如图 9-29 所示。此外,我们可以看到一个图表,可视化了前 10 个特征的变化,如图 9-30 所示。

图 9-29. SageMaker Studio 端点详细信息显示模型可解释性监控结果,在生成报告时显示“无问题”。

图 9-30。SageMaker Studio 端点详细信息显示前 10 个特征的变化,其中review_body、review_headline、product_category、product_title和total_votes是前 5 个。
现在我们已经建立了详细的模型监控体系,我们可以构建额外的自动化。我们可以利用 SageMaker Model Monitor 与 CloudWatch 的集成来在基线漂移警报时触发操作,例如模型更新、训练数据更新或自动重新训练我们的模型。
使用 SageMaker 批量转换执行批量预测
Amazon SageMaker 批量转换允许我们在 S3 上的数据批次上进行预测,而无需设置 REST 端点。批量预测也称为“离线”预测,因为它们不需要在线 REST 端点。通常用于可以容忍较高延迟和较低新鲜度的高吞吐量工作负载,批量预测服务器通常不像实时预测服务器那样全天运行。它们在一批数据上运行几个小时,然后关闭,因此称为“批量”。SageMaker 批量转换管理执行推断所需的所有资源,包括作业完成后的集群的启动和终止。
例如,如果我们的电影目录每天只更改几次,那么我们可能只需每晚运行一次批量预测作业,该作业使用当天新电影和用户活动训练的新推荐模型。由于我们每天晚上只更新一次推荐,因此我们的推荐在一天中的大部分时间可能会有些陈旧。但是,我们能够最小化整体成本,并且更重要的是保持可预测性。
另一种选择是在一天中不断地重新训练和部署新的推荐模型,每当新电影加入或离开我们的电影目录时就进行这样的操作。这可能导致过多的模型训练和部署成本,这些成本难以控制和预测。这些类型的持续更新通常属于像 Facebook 和 Netflix 等流行网站的“当前热门”类别,它们提供实时内容推荐。我们在讨论流数据分析时探讨了这些类型的连续模型。
选择实例类型
与模型训练类似,实例类型的选择通常涉及延迟、吞吐量和成本之间的平衡。始终从较小的实例类型开始,然后根据需要逐步增加。批量预测可能比实时端点预测更适合使用 GPU,因为 GPU 在处理大批量数据时性能更好。然而,我们建议首先尝试 CPU 实例,以设定延迟、吞吐量和成本的基线。在这里,我们正在使用一组高 CPU 实例集群:
instance_type='ml.c5.18xlarge'
instance_count=5
设置输入数据
让我们指定输入数据。在我们的情况下,我们使用存储为gzip压缩文本文件的原始 TSV 文件:
# Specify the input data
input_csv_s3_uri =
's3://{}/amazon-reviews-pds/tsv/'.format(bucket)
我们指定MultiRecord作为我们的策略以利用多个 CPU。由于我们的输入数据使用gzip压缩,我们指定Gzip作为压缩类型。我们使用 TSV 格式,因此text/csv是合适的accept_type和content_type。由于我们的行是通过换行符分隔的,因此我们使用Line作为assemble_with和split_type:
strategy='MultiRecord'
compression_type='Gzip'
accept_type='text/csv'
content_type='text/csv'
assemble_with='Line'
split_type='Line'
调整 SageMaker 批处理转换配置
当我们启动批处理转换作业时,我们的代码在 TensorFlow Serving 推理容器内部的 HTTP 服务器上运行。请注意,TensorFlow Serving 本地支持单个请求上的数据批处理。
让我们利用 TensorFlow Serving 的内置批处理功能,对多个记录进行批处理,以增加预测吞吐量,特别是在对数据批次性能良好的 GPU 实例上。设置以下环境变量以启用批处理:
batch_env = {
# Configures whether to enable record batching.
'SAGEMAKER_TFS_ENABLE_BATCHING': 'true',
# Name of the model - this is important in multi-model deployments
'SAGEMAKER_TFS_DEFAULT_MODEL_NAME': 'saved_model',
# Configures how long to wait for a full batch, in microseconds.
'SAGEMAKER_TFS_BATCH_TIMEOUT_MICROS': '50000', # microseconds
# Corresponds to "max_batch_size" in TensorFlow Serving.
'SAGEMAKER_TFS_MAX_BATCH_SIZE': '10000',
# Number of seconds for the SageMaker web server timeout
'SAGEMAKER_MODEL_SERVER_TIMEOUT': '3600', # Seconds
# Configures number of batches that can be enqueued.
'SAGEMAKER_TFS_MAX_ENQUEUED_BATCHES': '10000'
}
准备 SageMaker 批处理转换作业
我们可以直接将预处理和后处理代码注入到批处理转换容器中,以自定义预测流程。预处理代码在inference.py中指定,并将请求从原始数据(即review_body文本)转换为机器可读的特征(即 BERT 标记)。然后,这些特征被馈送到模型进行推理。模型预测结果随后通过inference.py中的后处理代码传递,将模型预测转换为人类可读的响应后保存到 S3 中。Figure 9-31 详细展示了 SageMaker 批处理转换的工作原理。

图 9-31. 使用 SageMaker 批处理转换进行离线预测。来源:Amazon SageMaker 开发者指南。
让我们设置批处理转换器以使用我们稍后将展示的inference.py脚本。我们正在指定在前一章节中训练的分类器模型的 S3 位置:
batch_model = Model(entry_point='inference.py',
source_dir='src_tsv',
model_data=<TENSORFLOW_MODEL_S3_URI>,
role=role,
framework_version='<TENSORFLOW_VERSION>',
env=batch_env)
batch_predictor = batch_model.transformer(
strategy=strategy,
instance_type=instance_type,
instance_count=instance_count,
accept=accept_type,
assemble_with=assemble_with,
max_concurrent_transforms=max_concurrent_transforms,
max_payload=max_payload, # This is in Megabytes
env=batch_env)
以下是前面定义的 Batch Transform 作业使用的inference.py脚本。此脚本具有用于请求处理的input_handler和用于响应处理的output_handler,如 Figure 9-32 所示。

图 9-32. 预处理请求处理程序和后处理响应处理程序。
输入处理程序input_handler和输出处理程序output_handler类似于之前用于 SageMaker REST 端点的函数。input_handler函数使用 Transformer 库将批量的原始文本转换为 BERT 标记。然后 SageMaker 将这些从input_handler输出的批量输出传递到我们的模型中,模型生成批量预测。预测结果经过output_handler函数,将预测转换为 JSON 响应。然后 SageMaker 将每个预测与其特定输入行结合起来。这为每个传入的行产生了单一的连贯输出行。
运行 SageMaker 批处理转换作业
接下来,我们将指定输入数据,并启动实际的批量转换作业。请注意,我们的输入数据使用 gzip 进行了压缩,因为批量转换作业支持多种类型的压缩:
batch_predictor.transform(data=input_csv_s3_uri,
split_type=split_type,
compression_type=compression_type,
content_type=content_type,
join_source='Input',
experiment_config=experiment_config,
wait=False)
我们指定 join_source='Input',以强制 SageMaker 在写入 S3 之前将我们的预测结果与原始输入进行合并。尽管此处未显示,但 SageMaker 允许我们指定要传递到批量转换过程的精确输入特征,使用 InputFilter 和要写入 S3 的精确数据使用 OutputFilter。这有助于减少开销、降低成本,并提高批量预测性能。
如果我们同时使用 join_source='Input' 和 InputFilter,SageMaker 将原始输入(包括被过滤掉的输入)与预测结果合并,以保持所有数据的完整性。我们还可以过滤输出,以减小写入 S3 的预测文件的大小。整个流程如 图 9-33 所示。

图 9-33. 过滤和合并输入以减少开销和提高性能。
查看批量预测
批量转换作业完成后,我们可以查看生成的逗号分隔的 .out 文件,其中包含我们的 review_body 输入和 star_rating 预测,如下所示:
amazon_reviews_us_Digital_Software_v1_00.tsv.gz.out
amazon_reviews_us_Digital_Video_Games_v1_00.tsv.gz.out
这里是一些示例预测:
'This is the best movie I have ever seen', 5, 'Star Wars'
'This is an ok, decently-funny movie.', 3, 'Spaceballs'
'This is the worst movie I have ever seen', 1, 'Star Trek'
到目前为止,我们已经进行了大量预测,并生成了逗号分隔的输出文件。借助一点应用程序代码(SQL、Python、Java 等),我们可以利用这些预测来支持基于自然语言的应用程序,以改进客户服务体验,例如。
AWS Lambda 函数和亚马逊 API 网关
我们还可以将模型部署为 Lambda 的无服务器 API。当预测请求到达时,Lambda 函数加载模型并执行推理函数代码。模型可以直接从 Lambda 函数内部加载,也可以从 Amazon S3 和 EFS 等数据存储加载。Lambda 函数可以从许多 AWS 服务调用,包括 Amazon Simple Queue Service 和 S3,以有效地实现基于事件的预测。
我们可以使用 Lambda 的“预置并发”功能预加载模型到函数中,大大提高预测延迟。亚马逊 API 网关还提供了额外的支持,用于应用程序身份验证、授权、缓存、速率限制和 Web 应用程序防火墙规则。图 9-34 显示了我们如何使用 Lambda 和 API 网关实现无服务器推理。

图 9-34. 使用 AWS Lambda 进行无服务器推理。
在边缘优化和管理模型
我们可以利用 Amazon SageMaker Neo 编译作业来优化我们的模型,使其适配特定的硬件平台,例如 AWS Inferentia、NVIDIA GPU、Intel CPU 和 ARM CPU。SageMaker Neo 使我们摆脱了手动调整模型以适配不同 CPU 和 GPU 架构或具有有限计算和存储资源的边缘设备平台的繁琐工作。SageMaker Neo 编译器使用设备特定的指令集将模型转换为高效且紧凑的格式。这些指令集直接在目标设备上执行低延迟的机器学习推理。
注意
2019 年,AWS 开源了SageMaker Neo,以便处理器供应商、设备制造商和软件开发人员可以合作,并将 ML 模型带到多种硬件优化平台上。
一旦模型由 SageMaker Neo 编译,SageMaker Edge Manager 将对模型进行加密签名,打包模型与轻量级运行时,并将模型包上传到 S3 存储桶,以便部署。SageMaker Edge Manager 管理所有注册的边缘设备上的模型,跟踪模型版本,收集健康指标,并定期捕获模型输入和输出,以检测模型漂移和退化。
使用 TorchServe 部署 PyTorch 模型
TorchServe 是 AWS、Facebook 和 PyTorch 社区之间的开源协作项目。借助 TorchServe,我们可以将 PyTorch 模型作为 REST 端点在生产中提供服务,类似于 TensorFlow Serving。SageMaker 提供了本地的 TorchServe 集成,允许我们专注于预测请求的业务逻辑,而不是基础设施代码。
与之前创建的基于 TensorFlow Serving 的 SageMaker 端点类似,我们需要提供一个基于 Python 的请求和响应处理程序,称为inference.py,将原始的评论文本从 JSON 格式的 REST 请求转换为 PyTorch 输入的 BERT 向量。此外,inference.py还需要将 PyTorch star_rating分类响应转换回 JSON 格式,以返回给调用应用程序。以下是inference.py的相关片段:
def model_fn(model_dir):
model_path = '{}/{}'.format(model_dir, MODEL_NAME)
device = torch.device(
'cuda' if torch.cuda.is_available() else 'cpu')
config = DistilBertConfig.from_json_file(
'/opt/ml/model/code/config.json')
model = DistilBertForSequenceClassification.from_pretrained(
model_path,config=config)
model.to(device)
return model
def predict_fn(input_data, model):
model.eval()
data_str = input_data.decode('utf-8')
jsonlines = data_str.split("\n")
predicted_classes = []
for jsonline in jsonlines:
review_body = json.loads(jsonline)["features"][0]
encode_plus_token = tokenizer.encode_plus(
review_body,
max_length=max_seq_length,
add_special_tokens=True,
return_token_type_ids=False,
pad_to_max_length=True,
return_attention_mask=True,
return_tensors='pt',
truncation=True)
input_ids = encode_plus_token['input_ids']
attention_mask = encode_plus_token['attention_mask']
output = model(input_ids, attention_mask)
softmax_fn = nn.Softmax(dim=1)
softmax_output = softmax_fn(output[0])
print("softmax_output: {}".format(softmax_output))
_, prediction = torch.max(softmax_output, dim=1)
predicted_class_idx = prediction.item()
predicted_class = classes[predicted_class_idx]
prediction_dict = {}
prediction_dict['predicted_label'] = predicted_class
jsonline = json.dumps(prediction_dict)
predicted_classes.append(jsonline)
predicted_classes_jsonlines = '\n'.join(predicted_classes)
return predicted_classes_jsonlines
让我们使用我们的inference.py请求/响应处理程序将模型部署为 SageMaker 端点:
class StarRatingPredictor(Predictor):
def __init__(self, endpoint_name, sagemaker_session):
super().__init__(endpoint_name,
sagemaker_session=sagemaker_session,
serializer=JSONLinesSerializer(),
deserializer=JSONLinesDeserializer())
model = PyTorchModel(model_data=<PYTORCH_MODEL_S3_URI>,
name=pytorch_model_name,
role=role,
entry_point='inference.py',
source_dir='code-pytorch',
framework_version='<PYTORCH_VERSION>',
predictor_cls=StarRatingPredictor)
predictor = model.deploy(initial_instance_count=1,
instance_type='ml.m5.4xlarge',
endpoint_name=pytorch_endpoint_name,
wait=False)
现在我们可以通过将评论文本传递给我们的评论分类器端点来进行预测:
import json
inputs = [
{"features": ["This is great!"]},
{"features": ["This is OK."]}
{"features": ["This is bad."]}
]
predicted_classes = predictor.predict(inputs)
for predicted_class in predicted_classes:
print(predicted_class)
### OUTPUT ###
{'predicted_label': 5}
{'predicted_label': 3}
{'predicted_label': 1}
使用 AWS Deep Java Library 进行 TensorFlow-BERT 推理
让我们从 AWS Deep Java Library (DJL)导入所需的 Java 库:
import ai.djl.*;
import ai.djl.engine.*;
import ai.djl.inference.*;
import ai.djl.modality.*;
import ai.djl.modality.nlp.*;
import ai.djl.modality.nlp.bert.*;
import ai.djl.ndarray.*;
import ai.djl.repository.zoo.*;
import ai.djl.translate.*;
import ai.djl.training.util.*;
import ai.djl.util.*;
接下来,让我们下载预训练的 DistilBERT TensorFlow 模型:
String modelUrl =
"https://resources.djl.ai/demo/tensorflow/amazon_review_rank_classification.zip";
DownloadUtils.download(modelUrl,
"build/amazon_review_rank_classification.zip",
new ProgressBar());
Path zipFile = Paths.get("build/amazon_review_rank_classification.zip");
Path modelDir = Paths.get("build/saved_model");
if (Files.notExists(modelDir)) {
ZipUtils.unzip(Files.newInputStream(zipFile), modelDir);
}
接下来,我们设置了 BERT Tokenizer 并定义了 Translator 来将原始文本转换为 BERT 嵌入:
// Prepare the vocabulary
Path vocabFile = modelDir.resolve("vocab.txt");
SimpleVocabulary vocabulary = SimpleVocabulary.builder()
.optMinFrequency(1)
.addFromTextFile(vocabFile)
.optUnknownToken("[UNK]")
.build();
BertFullTokenizer tokenizer = new BertFullTokenizer(vocabulary, true);
int maxTokenLength = 64; // cutoff tokens length
class MyTranslator implements Translator<String, Classifications> {
private BertFullTokenizer tokenizer;
private SimpleVocabulary vocab;
private List<String> ranks;
private int length;
public MyTranslator(BertFullTokenizer tokenizer, int length) {
this.tokenizer = tokenizer;
this.length = length;
vocab = tokenizer.getVocabulary();
ranks = Arrays.asList("1", "2", "3", "4", "5");
}
@Override
public Batchifier getBatchifier() {
return new StackBatchifier();
}
@Override
public NDList processInput(TranslatorContext ctx, String input) {
List<String> tokens = tokenizer.tokenize(input);
long[] indices = new long[length];
long[] mask = new long[length];
long[] segmentIds = new long[length];
int size = Math.min(length, tokens.size());
for (int i = 0; i < size; i++) {
indices[i + 1] = vocab.getIndex(tokens.get(i));
}
Arrays.fill(mask, 0, size, 1);
NDManager m = ctx.getNDManager();
return new NDList(m.create(indices),
m.create(mask),
m.create(segmentIds));
}
@Override
public Classifications processOutput(TranslatorContext ctx, NDList list) {
return new Classifications(ranks, list.singletonOrThrow().softmax(0));
}
}
最后,我们加载模型,并使用 BERT 和 Java 进行一些预测!
MyTranslator translator = new MyTranslator(tokenizer, maxTokenLength);
Criteria<String, Classifications> criteria = Criteria.builder()
.setTypes(String.class, Classifications.class)
.optModelPath(modelDir) // Load model form model directory
.optTranslator(translator) // use custom translaotr
.build();
ZooModel<String, Classifications> model = ModelZoo.loadModel(criteria);
String review = "It works great, but it takes too long to update";
Predictor<String, Classifications> predictor = model.newPredictor();
predictor.predict(review);
### OUTPUT ###
5
减少成本并提高性能
在本节中,我们描述了通过将多个模型打包到单个 SageMaker 部署容器中、利用基于 GPU 的弹性推理加速器、优化我们的训练模型以适应特定硬件,并利用 AWS Inferentia 芯片等推理优化硬件的多种方式来降低成本并提高性能。
删除未使用的端点并在资源利用率低的集群下进行缩放
SageMaker 端点是长期运行的资源,在成功的蓝/绿部署后很容易保持运行状态。我们应尽快删除未使用的资源。我们可以设置 CloudWatch 警报,以在 SageMaker 端点未收到调用时通知我们。同样,如果集群过度配置且资源利用不足,我们应记得缩减 SageMaker 端点集群。
在一个容器中部署多个模型
如果我们有大量相似的模型可以通过共享服务容器提供,并且不需要同时访问所有模型,我们可以在单个 SageMaker 端点中部署多个模型。当存在一长尾的 ML 模型很少被访问时,使用一个端点可以有效地服务推理流量并实现显著的成本节约。
每个 SageMaker 端点可以根据流量和资源利用率自动加载和卸载模型。例如,如果对模型 1 的流量为零而模型 2 的流量激增,则 SageMaker 将动态卸载模型 1 并加载模型 2 的另一个实例。我们可以通过在预测请求中指定目标模型名称作为参数来调用特定的模型变体,如图 9-35 所示。

图 9-35. 在托管多个模型的 SageMaker 端点内调用特定模型。
这使我们能够训练两种不同的类别特定的 TensorFlow 模型—Digital_Software和Gift_Card,例如—并将它们部署到一个单一的端点以方便和节省成本。以下是将这两个模型部署到单个 SageMaker 端点的代码。
对于 TensorFlow,我们需要按以下方式打包模型:
└── multi
├── model1
│ └── <version number>
│ ├── saved_model.pb
│ └── variables
│ └── ...
└── model2
└── <version number>
├── saved_model.pb
└── variables
└── ...
from sagemaker.tensorflow.serving import Model, Predictor
# For endpoints with multiple models, we should set the default
# model name in this environment variable.
# If it isn't set, the endpoint will work, but the model
# it will select as default is unpredictable.
env = {
'SAGEMAKER_TFS_DEFAULT_MODEL_NAME': 'model1' # <== This must match the directory
}
model_data = '{}/multi.tar.gz'.format(multi_model_s3_uri)
model = Model(model_data=model_data,
role=role,
framework_version='<TENSORFLOW_VERSION>',
env=env)
附加基于 GPU 的弹性推理加速器
弹性推理加速器(EIA)是 SageMaker 实例的低成本、动态附加的 GPU 驱动附件。虽然独立的 GPU 实例非常适合大型数据集的模型训练,但对于消耗小量 GPU 资源的小批量推理请求来说通常是过度配置的。
AWS 提供了多种不同 GPU、CPU、网络带宽和内存组合的实例类型,我们的模型可能使用自定义组合。使用 EIAs,我们可以从选择基础 CPU 实例开始,并添加 GPU,直到找到适合我们模型推理需求的平衡点。否则,我们可能被迫优化一个资源集合如 CPU 和 RAM,但未充分利用其他资源如 GPU 和网络带宽。
这里是部署我们同一模型但带有 EIA 的代码:
import time
timestamp = '{}'.format(int(time.time()))
endpoint_config_name = '{}-{}'.format(training_job_name, timestamp)
variantA = production_variant(model_name='ModelA',
instance_type="ml.m5.large",
initial_instance_count=1,
variant_name='VariantA',
initial_weight=50,
accelerator_type='ml.eia2.medium')
variantB = production_variant(model_name='ModelB',
instance_type="ml.m5.large",
initial_instance_count=1,
variant_name='VariantB',
initial_weight=50)
endpoint_config = sm.create_endpoint_config(
EndpointConfigName=endpoint_config_name,
ProductionVariants=[variantA, variantB]
)
endpoint_name = '{}-{}'.format(training_job_name, timestamp)
endpoint_response = sm.create_endpoint(
EndpointName=endpoint_name,
EndpointConfigName=endpoint_config_name)
使用 SageMaker Neo 和 TensorFlow Lite 优化训练模型
SageMaker Neo 接受经过训练的模型,并执行一系列针对硬件的特定优化,如 16 位量化、图形修剪、层融合和常量折叠,以最小化精度损失获得高达 2 倍的模型预测加速。SageMaker Neo 适用于流行的 AI 和机器学习框架,包括 TensorFlow、PyTorch、Apache MXNet 和 XGBoost。
SageMaker Neo 解析模型、优化图形、量化张量,并为包括 Intel x86 CPU、NVIDIA GPU 和 AWS Inferentia 在内的各种目标环境生成硬件特定代码,如图 9-36 所示。

图 9-36. SageMaker Neo 提供模型编译作为服务。
SageMaker Neo 支持 TensorFlow Lite(TFLite),这是一个针对内存和计算资源有限的小型设备高度优化的轻量级 TensorFlow 运行时解释器和代码生成器。SageMaker Neo 使用 TFLite 转换器执行针对硬件的特定优化,以用于 TensorFlow Lite 运行时解释器,如图 9-37 所示。

图 9-37. TFLite 解释器。来源:TensorFlow。
我们可以选择优化小尺寸(tf.lite.Optimize.OPTIMIZE_FOR_SIZE)、优化低延迟(tf.lite.OPTIMIZE_FOR_LATENCY)或在尺寸和性能之间取得平衡(tf.lite.Optimize.DEFAULT)。以下是在 TensorFlow 模型上执行 16 位量化的 TFLite 代码,以在尺寸和性能之间取得平衡:
import tensorflow as tf
converter = tf.lite.TocoConverter.from_saved_model('./tensorflow/')
converter.post_training_quantize = True
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
tflite_model_path = './tflite/tflite_optimized_model.tflite'
model_size = open(tflite_model_path, "wb").write(tflite_model)
这里是由于量化而导致预测时间提升一个数量级的预测代码:
import numpy as np
import tensorflow as tf
# Load TFLite model and allocate tensors.
interpreter = tf.lite.Interpreter(model_path=tflite_model_path)
interpreter.allocate_tensors()
# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
# Test model on random input data.
input_shape = input_details[0]['shape']
input_data = np.array(np.random.random_sample(input_shape),
dtype=np.float32)
interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()
output_data = interpreter.get_tensor(output_details[0]['index'])
print('Prediction: %s' % output_data)
### OUTPUT ###
5
使用推理优化硬件
AWS Inferentia 是亚马逊“Inf”实例类型使用的推理优化芯片。该芯片通过 AWS Neuron 编译器生成的 16 位和 8 位浮点操作来优化我们的模型,以适应 AWS Inferentia 芯片以及 SageMaker Neo 和 Neuron 运行时(见图 9-38)。

图 9-38. SageMaker Neuron 编译器和 AWS Inferentia 芯片的 Neo 运行时。
总结
在本章中,我们将我们的模型从研究实验室移至最终用户应用领域。我们展示了如何利用真实世界的、生产就绪的基础设施,如金丝雀发布、蓝/绿部署和 A/B 测试来衡量、改进和部署我们的模型。我们演示了如何进行数据漂移、模型漂移和特征归因漂移检测。此外,我们进行了批处理转换以提高离线模型预测的吞吐量。最后,我们提供了使用 SageMaker Neo、TensorFlow Lite、SageMaker 多模型端点以及推理优化硬件(如 EIA 和 AWS Inferentia)来降低成本和提高性能的提示。
在 第十章 中,我们利用 SageMaker Pipelines、AWS Step Functions、Apache Airflow、Kubeflow 和其他多种开源选项,将特征工程、模型训练、模型验证和模型部署步骤统一到一个端到端自动化流水线中。
第十章:管道和 MLOps
在前几章中,我们演示了如何执行典型 ML 管道的每个单独步骤,包括数据摄取、分析和特征工程,以及模型训练、调优和部署。
在本章中,我们将所有内容结合到可重复和自动化的管道中,使用完整的机器学习运营(MLOps)解决方案与 SageMaker 管道。我们还讨论了各种管道编排选项,包括 AWS Step Functions、Kubeflow Pipelines、Apache Airflow、MLFlow 和 TensorFlow Extended(TFX)。
然后,我们将深入探讨如何在新代码提交时、新数据到达时或按固定时间表自动化我们的 SageMaker 管道。我们描述了如何在检测到部署模型中的统计变化(如数据漂移或模型偏差)时重新运行管道。我们还将讨论人在环回工作流的概念,这可以帮助提高我们模型的准确性。
机器学习运营
完整的模型开发生命周期通常需要应用程序、数据科学和 DevOps 团队之间的密切协作,以成功地将我们的模型产品化,如图 10-1 所示。

图 10-1. 将机器学习应用产品化需要各团队之间的协作。
通常,数据科学家提供训练好的模型,DevOps 工程师管理托管模型的基础设施作为 REST API,并且应用开发者将 REST API 集成到他们的应用程序中。每个团队必须了解其他团队的需求和要求,以实施高效的工作流程和顺畅的交接流程。
MLOps 已经发展到了三个成熟阶段:
MLOps v1.0
手动构建、训练、调优和部署模型
MLOps v2.0
手动构建和编排模型管道
MLOps v3.0
当新数据到达或代码发生变化时,自动运行管道,这些变化可以是确定性触发器,如 GitOps,或者是基于统计触发器,如漂移、偏差和可解释性差异,当模型性能开始下降时。
在本章中,我们描述了 SageMaker 如何支持完整的 MLOps 策略,包括管道编排、从数据或代码变化的确定性自动化,以及从漂移、偏差或可解释性变化的统计自动化。
软件管道
2000 年代初期,软件从业者开始使用持续集成(CI)和持续交付(CD)来直接、安全地构建、测试和部署他们的软件模块到生产环境。CI 和 CD 促进了 DevOps 工程师与软件工程师之间低摩擦的协作。在 CI 和 CD 之前,软件工程师会将他们的代码“扔过墙”给 DevOps 工程师,在预生产环境中确认成功的集成测试结果,并与质量保证(QA)团队协调等后,将软件推送到生产环境。示例软件管道如 Figure 10-2 所示。

图 10-2. 简单的应用部署管道。
Jenkins 是一个流行的开源工具,用于管理软件管道。凭借其丰富的插件架构,Jenkins 可以编排复杂的 CI/CD 软件管道,并在管道执行的任何阶段提供深入的报告。对于大型代码库,管道执行可能需要几天时间,并且组件可能因各种原因而失败。Jenkins 提供机制来重新启动任何失败的组件并保持管道运行。然而,通常需要人工干预。Jenkins 还支持手动、人为反馈环节。
除了重新启动外,像 Jenkins 这样的高级管道编排引擎还支持组件缓存策略,以提高管道执行性能。例如,如果我们的管道在集成测试阶段失败,因为远程系统不可用,编排引擎可以检测哪些管道步骤已经运行,如果没有更改依赖项,则重用缓存的结果,重试失败的步骤,并继续执行管道直至完成。
机器学习管道
虽然 CI 和 CD 管道主要用于自动化软件开发周期并提高应用发布的质量,它们也可以改进机器学习的发布过程。机器学习工程师和数据科学家致力于以最小的摩擦度一致地训练、测试和部署模型到生产环境中。这使我们能够花更多时间构建和尝试新模型,而不是手动重新训练和重新部署使用最新数据集的现有模型。
类似于 CI 和 CD 以高效地更新和改进生产中的软件,机器学习管道自动执行持续训练和持续交付,以高效地更新和改进模型。自动化、可复制和参数化的管道有助于在整个过程中维护和跟踪框架版本、容器运行时和硬件,从特征摄取和特征工程到模型训练和部署。
使用自动化 ML 流水线而不是手动的一次性 Python 脚本,有助于减少可能出现在流水线任何步骤中的细微错误。例如,上游应用程序的小改变可能引入数据质量问题,例如星级评分超出了介于 1(最差)和 5(最佳)之间的边界和离散值范围。
虽然模型可能在质量低下的数据上看起来训练成功,但如果推送到生产环境,模型可能会对我们的业务产生负面影响。通过在模型训练之前自动化数据质量检查,我们可以引发流水线异常,通知应用团队有关坏数据的情况,并节省训练糟糕模型的成本。
我们还可以将 ML 流水线与模型可复现性和审计的工件和实验跟踪结合起来。工件跟踪提供了部署模型的血统,直到模型训练期间使用的原始数据集版本。实验跟踪记录了训练过程中使用的超参数以及训练结果,如模型准确度。SageMaker 实验和血统 API 在整个 SageMaker 中集成,以处理这些情况。
可验证的 ML 流水线可以帮助解决模型退化的问题。由于在生产环境中监控模型的复杂性,模型退化是一个相对常见且工程不足的情况。模型预测的退化导致错误分类的评论和错失的业务机会。
通过持续使用 SageMaker Model Monitor 和 Clarify 监控我们的模型预测,我们可以检测到数据分布的变化、模型偏差和模型可解释性,从而触发重新训练和部署新的审阅分类器模型的流水线。
图 10-3 显示了映射到 AWS 服务的样本机器学习流水线,包括 S3、Data Wrangler 和 SageMaker。

图 10-3. 映射到 AWS 服务的机器学习流水线。
流水线顺利运行后,我们可以通过添加同时流水线来增加实验速度,将同一模型的多个版本部署到生产环境,如 图 10-4 所示。这可用于在线 A/B/C 或多臂赌博(MAB)测试。

图 10-4. 训练、调优和部署同一模型的多个版本,以提高实验速度。
有效机器学习流水线的组成部分
仍然有许多机器学习流水线包含一个高摩擦的步骤,即数据科学家将他们的模型“扔过墙”给 DevOps 工程师或 ML 工程师来部署。机器学习流水线正好可以进行革新,类似于 2000 年代初惊艳软件工程社区的革新。
有效的机器学习流水线隐藏了流水线实现的细节,并允许数据科学从业者专注于其特定业务的数据科学问题。机器学习是连续的。我们自动化的过程越多,我们就越能自由地解决其他业务问题。否则,每次有新数据到达时我们就需要手动重新运行一次性脚本。虽然运行脚本相对简单,但监控或重新启动脚本则需要我们可能本可以用来解决更高价值任务的认知负荷。
从“临时的 Jupyter 笔记本”到“可重复的机器学习流水线”再到“生产集群”的能力仍然是一个复杂、易错且未经充分工程化的工作流程。然而,我们将提供一些选项来如何最小化复杂性并减少 AWS 中的错误。
有效的机器学习流水线应包括以下内容:
-
诸如数据摄取、数据版本控制、数据质量检查、数据预处理和特征工程等以数据为重点的任务
-
模型构建任务,如模型训练、模型质量检查和模型版本控制
-
自动化模型部署、模型扩展、模型解释和偏差检测
-
实验和谱系跟踪以反向工作并从头开始重现任何模型版本
-
自动捕捉新数据到达(S3
PutObject事件)并重新训练,或者可能使用类似 cron 的定时器进行自动化(每晚午夜) -
反馈机制以根据我们的业务目标和关键结果持续改进模型,例如在接下来的 12 个月内将客户满意度提高 10%
根据我们的经验,数据质量问题是糟糕的机器学习流水线的主要原因。在第五章中,我们演示了如何使用 AWS Deequ 开源库对我们的数据进行数据质量检查,作为机器学习流水线的“步骤 0”。没有一致和预期的数据质量,我们的机器学习流水线最多会迅速失败并最大程度地降低成本。在最坏的情况下,低质量的数据将生成低质量的模型,可能包含偏差并对我们的业务产生负面影响。
在机器学习探索的初期阶段,我们可能不需要流水线。流水线的严格性可能显得过于限制。当我们准备开始定期训练模型时,通常会部署流水线。如果我们正在快速尝试许多不同类型的特征、模型和超参数,我们可能希望保持在研究实验室,直到准备好长期自动化并获得定期流水线执行的好处,包括数据质量检查、谱系跟踪和基础设施扩展。然而,即使是最简单的流水线也可以帮助我们改进模型探索。
有效的机器学习流水线步骤
下面是构成有效现代机器学习流水线的步骤集合。我们将在接下来的章节中演示如何使用 SageMaker Pipelines、AWS Step Functions、Airflow、Kubeflow 和其他开源选项执行每个步骤。
数据摄入和版本控制
从数据源(例如数据库、S3 或流)中读取原始数据集。将数据集转换为下一步流水线中将使用的格式(例如 CSV、Parquet 等),并对原始和转换后的数据集进行版本控制。
数据分析和验证
分析所摄入数据集的质量和偏差。验证数据是否准备好进入下一个流水线步骤。
特征工程
将数据集转换为诸如 BERT 嵌入之类的特征,供下一个流水线步骤使用。平衡和拆分数据集为训练、验证和测试集。发布这些特征到特征存储中,以供整个组织在训练和推断时使用。
模型训练和调优
使用前一流水线步骤中创建的特征以及特定于模型算法的一组超参数来训练模型。使用已知的验证数据集拆分分析模型和超参数的准确性,并使用不同的超参数集重复,直到模型准确性达到要求。
模型评估
使用已知的测试数据集拆分测试训练模型,计算额外的指标(例如混淆矩阵和曲线下面积),验证不同测试数据集拆分(例如不同的产品类别)上的模型偏差,并重新训练和调整以减少或消除偏差。
模型版本和部署
对训练模型及其超参数和数据集拆分进行版本控制,并将模型部署到生产环境作为实时端点或批量预测作业。
模型反馈和偏差检测
分析模型在业务指标(例如收入增长、成功识别欺诈等)上的表现。通过分析模型输入和输出(预测)与训练数据基准之间的差异来检测训练-服务偏差,并在检测到偏差时重新训练模型。
使用 SageMaker Pipelines 进行流水线编排
SageMaker Pipelines 是在 AWS 上实现 AI 和机器学习流水线的最完整方式。让我们为基于 BERT 的评论分类器构建一个流水线,并执行前几章描述的许多步骤,包括数据摄入、特征工程、模型训练和模型部署,如图 10-5 所示。

图 10-5。使用 SageMaker Pipeline 来训练、验证、创建和注册我们训练过的 BERT 模型。
让我们使用 SageMaker Python SDK 以编程方式设置流水线,定义前面讨论的每个步骤。
创建一个实验来跟踪我们的流水线谱系。
首先,我们创建一个实验和试验来跟踪和比较我们的流水线运行:
import time
from smexperiments.experiment import Experiment
experiment_name = 'Experiment-{}'.format(int(time.time()))
experiment = Experiment.create(
experiment_name=experiment_name,
description='Amazon Customer Reviews BERT Pipeline Experiment',
...)
trial_name = 'trial-{}'.format(int(time.time()))
trial = Trial.create(trial_name=trial_name,
experiment_name=experiment_name,
...)
定义我们的管道步骤
我们管道的第一步是使用 SageMaker 处理作业将原始评审文本转换为 BERT 特征。我们将重用来自第六章的相同processor,但将其包装在 SageMaker Pipeline Python SDK 的ProcessingStep中:
experiment_config_prepare = {
'ExperimentName': experiment_name,
'TrialName': trial_name,
'TrialComponentDisplayName': 'prepare'
}
from sagemaker.processing import ProcessingInput, ProcessingOutput
from sagemaker.workflow.steps import ProcessingStep
processing_step = ProcessingStep(
name='Processing',
code='preprocess-scikit-text-to-bert-feature-store.py',
processor=processor,
inputs=processing_inputs,
outputs=processing_outputs,
job_arguments=['--train-split-percentage', \
str(train_split_percentage.),
'--validation-split-percentage', \
str(validation_split_percentage.),
'--test-split-percentage', \
str(test_split_percentage.),
'--max-seq-length', \
str(max_seq_length.),
'--balance-dataset', \
str(balance_dataset.),
'--feature-store-offline-prefix', \
str(feature_store_offline_prefix.),
'--feature-group-name', \
str(feature_group_name)
]
)
现在让我们使用先前特征工程处理步骤的输出来训练我们的模型。我们将使用来自第七章的相同estimator,但将其包装在 SageMaker Pipeline Python SDK 的TrainingStep中:
from sagemaker.inputs import TrainingInput
from sagemaker.workflow.steps import TrainingStep
experiment_config_train = {
'ExperimentName': experiment_name,
'TrialName': trial_name,
'TrialComponentDisplayName': 'train'
}
training_step = TrainingStep(
name='Train',
estimator=estimator,
inputs={
'train': TrainingInput(
s3_data=\
processing_step.properties.ProcessingOutputConfig.Outputs[
'bert-train'
].S3Output.S3Uri,
content_type='text/csv'
),
'validation': TrainingInput(
s3_data=\
processing_step.properties.ProcessingOutputConfig.Outputs[
'bert-validation'
].S3Output.S3Uri,
content_type='text/csv'
),
'test': TrainingInput(
s3_data=\
processing_step.properties.ProcessingOutputConfig.Outputs[
'bert-test'
].S3Output.S3Uri,
content_type='text/csv'
)
}
)
接下来,让我们添加一步,使用 SageMaker 处理作业评估我们的模型,计算模型测试准确率,并将结果写入名为evaluation.json的文件中存储在 S3 中。该文件将由下一步骤有条件地注册和准备模型部署使用:
from sagemaker.workflow.properties import PropertyFile
experiment_config_evaluate = {
'ExperimentName': experiment_name,
'TrialName': trial_name,
'TrialComponentDisplayName': 'evaluate'
}
evaluation_report = PropertyFile(
name='EvaluationReport',
output_name='metrics',
path='evaluation.json'
)
from sagemaker.sklearn.processing import SKLearnProcessor
evaluation_processor = SKLearnProcessor(
framework_version='<SCIKIT_LEARN_VERSION>',
role=role,
...)
evaluation_step = ProcessingStep(
name='Evaluation',
processor=evaluation_processor,
code='evaluate_model_metrics.py',
inputs=[
ProcessingInput(
source=\
training_step.properties.ModelArtifacts.S3ModelArtifacts,
destination='/opt/ml/processing/input/model'
),
ProcessingInput(
source=raw_input_data_s3_uri,
destination='/opt/ml/processing/input/data'
)
],
outputs=[
ProcessingOutput(output_name='metrics',
s3_upload_mode='EndOfJob',
source='/opt/ml/processing/output/metrics/'),
],
job_arguments=[
'--max-seq-length', \
str(max_seq_length.default_value),
],
property_files=[evaluation_report],
experiment_config=experiment_config_evaluate
)
evaluate_model_metrics.py文件下载模型,运行一组测试预测,并将结果写入evaluation.json,如下所示:
def predict(text):
encode_plus_tokens = tokenizer.encode_plus(
text,
pad_to_max_length=True,
max_length=args.max_seq_length,
truncation=True,
return_tensors='tf')
input_ids = encode_plus_tokens['input_ids']
input_mask = encode_plus_tokens['attention_mask']
outputs = model.predict(x=(input_ids, input_mask))
scores = np.exp(outputs) / np.exp(outputs).sum(-1, keepdims=True)
prediction = [{"label": config.id2label[item.argmax()],
"score": item.max().item()} for item in scores]
return prediction[0]['label']
...
df_test_reviews = pd.read_csv(
test_data_path,
delimiter='\t',
quoting=csv.QUOTE_NONE,
compression='gzip')[['review_body', 'star_rating']]
y_test = df_test_reviews['review_body'].map(predict)
y_actual = df_test_reviews['star_rating']
accuracy = accuracy_score(y_true=y_test, y_pred=y_actual)
metrics_path = os.path.join(args.output_data, 'metrics/')
os.makedirs(metrics_path, exist_ok=True)
report_dict = {
"metrics": {
"accuracy": {
"value": accuracy,
},
},
}
evaluation_path = "{}/evaluation.json".format(metrics_path)
with open(evaluation_path, "w") as f:
f.write(json.dumps(report_dict))
让我们在 SageMaker 模型注册表中注册我们训练过的模型。一旦模型注册成功,我们的管道需要手动批准步骤将模型部署到 staging 环境。我们首先需要在名为model_metrics的 Python 对象中捕获上一个评估步骤生成的评估指标,如下所示:
from sagemaker.model_metrics import MetricsSource, ModelMetrics
model_metrics = ModelMetrics(
model_statistics=MetricsSource(
s3_uri="{}/evaluation.json".format(
evaluation_step.arguments["ProcessingOutputConfig"]\
["Outputs"][0]["S3Output"]["S3Uri"]
),
content_type="application/json"
)
)
让我们传递model_metrics并使用来自先前TrainingStep的estimator创建RegisterModel步骤。我们可以通过分别为inference_instances和transform_instances指定列表来限制 SageMaker Endpoint 和 Batch Transform 作业的实例类型:
from sagemaker.workflow.step_collections import RegisterModel
inference_image_uri = sagemaker.image_uris.retrieve(
framework="tensorflow",
region=region,
version="<TENSORFLOW_VERSION>",
py_version="<PYTHON_VERSION>",
instance_type=deploy_instance_type,
image_scope="inference"
)
register_step = RegisterModel(
name="RegisterModel",
estimator=estimator,
image_uri=inference_image_uri,
model_data=
training_step.properties.ModelArtifacts.S3ModelArtifacts,
content_types=["application/jsonlines"],
response_types=["application/jsonlines"],
inference_instances=["ml.m5.4xlarge"],
transform_instances=["ml.c5.18xlarge"],
model_package_group_name=model_package_group_name,
model_metrics=model_metrics
)
现在我们将编写CreateModelStep来封装 SageMaker 中用于 SageMaker Endpoint 和 Batch Transform 作业的Model:
from sagemaker.model import Model
model = Model(
name=<MODEL_NAME>,
image_uri=inference_image_uri,
model_data=
training_step.properties.ModelArtifacts.S3ModelArtifacts,
...
)
from sagemaker.inputs import CreateModelInput
create_inputs = CreateModelInput(
instance_type="ml.m5.4xlarge",
)
from sagemaker.workflow.steps import CreateModelStep
create_step = CreateModelStep(
name="CreateModel",
model=model,
inputs=create_inputs,
)
让我们添加一个ConditionStep来比较评估准确性指标与阈值。只有当模型准确率超过 95%的给定阈值时,我们的管道才会注册、创建并准备模型部署,如下所示:
from sagemaker.workflow.conditions import ConditionGreaterThanOrEqualTo
from sagemaker.workflow.condition_step import (
ConditionStep,
JsonGet,
)
minimum_accuracy_condition = ConditionGreaterThanOrEqualTo(
left=JsonGet(
step=evaluation_step,
property_file=evaluation_report,
json_path="metrics.accuracy.value",
),
right=0.95 # 95% accuracy
)
minimum_accuracy_condition_step = ConditionStep(
name="AccuracyCondition",
conditions=[minimum_accuracy_condition],
# success, continue with model registration
if_steps=[register_step, create_step],
# fail, end the pipeline
else_steps=[],
)
配置管道参数
在创建管道之前,我们必须使用 SageMaker Pipelines Python SDK 中的ParameterInteger、ParameterString和ParameterFloat定义参数占位符,以在管道的所有步骤中使用。这些只是现在的占位符,因为我们正在定义管道。当我们启动管道时,我们将为每个参数指定确切的值,或者如果未提供值,则使用default_value:
from sagemaker.workflow.parameters import (
ParameterInteger,
ParameterString,
ParameterFloat,
)
input_data = ParameterString(
name="InputData",
default_value=raw_input_data_s3_uri,
)
...
max_seq_length = ParameterInteger(
name="MaxSeqLength",
default_value=64,
)
...
learning_rate = ParameterFloat(
name="LearningRate",
default_value=0.00001,
)
...
创建管道
接下来,我们使用所有先前定义的步骤创建管道。这包括processing_step、training_step、evaluation_step以及minimum_accuracy_condition_step,后者在模型评估过程中如果模型达到 95%的最低准确率条件时有条件地调用register_step和create_step:
pipeline = Pipeline(
name=<PIPELINE_NAME>,
parameters=[
input_data, # InputData
...
max_seq_length, # MaxSeqLength
...
learning_rate, # LearningRate
...
],
steps=[processing_step, training_step, evaluation_step, \
minimum_accuracy_condition_step]
)
pipeline.create(role_arn=role)
使用 Python SDK 启动管道
最后,我们通过提供所需的参数值启动Pipeline,包括评论数据集的 S3 位置、BERT token 的最大序列长度以及 TensorFlow 梯度下降优化器的学习率:
execution = pipeline.start(
InputData=raw_input_data_s3_uri,
MaxSeqLength=64,
LearningRate=0.000012,
...
)
使用 SageMaker Studio UI 启动管道
我们还可以通过 SageMaker Studio UI 启动 SageMaker 管道执行,如图 10-6 所示。Studio UI 为我们的Pipeline对象中定义的每个参数提供输入字段。

图 10-6. 通过 SageMaker Studio UI 启动管道执行。
批准模型用于暂存和生产环境
我们可以通过 SageMaker 模型注册表手动在 SageMaker Studio UI 或通过我们的笔记本程序来批准模型。批准模型将自动将模型部署到用于测试的暂存环境。如果测试成功,我们的管道随后需要单独批准将模型从暂存环境移至生产环境。我们可以使用以下代码通过程序来将模型批准至暂存环境:
for execution_step in execution.list_steps():
if execution_step['StepName'] == 'RegisterModel':
model_package_arn =
execution_step['Metadata']['RegisterModel']['Arn']
break
model_package_update_response = sm.update_model_package(
ModelPackageArn=model_package_arn,
ModelApprovalStatus="Approved",
)
查看管道 artifact 谱系
我们可以直接通过 SageMaker Studio UI 或通过我们的笔记本程序使用 Python SDK 来查看 artifact 谱系。以下是列出所有步骤的 artifact 的代码,包括特征工程、模型训练、评估、批准和部署:
import time
from sagemaker.lineage.visualizer import LineageTableVisualizer
viz = LineageTableVisualizer(sagemaker.session.Session())
for execution_step in reversed(execution.list_steps()):
if execution_step['StepName'] == 'Processing':
processing_job_name=
execution_step['Metadata']['ProcessingJob']['Arn']\
.split('/')[-1]
display(viz.show(processing_job_name=processing_job_name))
else:
display(viz.show(pipeline_execution_step=execution_step))
time.sleep(5)
输出结果类似于以下表格:
| 名称/来源 | 方向 | 类型 | 关联类型 | 谱系类型 | |
|---|---|---|---|---|---|
| 0 | preprocess-scikit-text-to-bert-feature-store.py | 输入 | 数据集 | 贡献至 | artifact |
| 1 | s3://.../amazon-reviews-pds/tsv/ | 输入 | 数据集 | 贡献至 | artifact |
| 2 | 68331...om/sagemaker-scikit-learn:0.23-1-cpu-py3 | 输入 | 图像 | 贡献至 | artifact |
| 3 | s3://.../output/bert-test | 输出 | 数据集 | 产出 | artifact |
| 4 | s3://.../output/bert-validation | 输出 | 数据集 | 产出 | artifact |
| 5 | s3://.../output/bert-train | 输出 | 数据集 | 产出 | artifact |
| 6 | s3://.../output/bert-test | 输入 | 数据集 | 贡献至 | artifact |
| 7 | s3://.../output/bert-validation | 输入 | 数据集 | 贡献至 | artifact |
| 8 | s3://.../output/bert-train | 输入 | 数据集 | 贡献至 | artifact |
| 9 | 76310.../tensorflow-training:2.3.1-cpu-py37 | 输入 | 图像 | 贡献至 | artifact |
| 10 | model.tar.gz | 输出 | 模型 | 产出 | artifact |
| 11 | model.tar.gz | 输入 | 模型 | 贡献至 | artifact |
| 12 | 76310.../tensorflow-inference:2.1.0-cpu | 输入 | 图像 | 贡献至 | artifact |
| 13 | bert-reviews-1610437484-1-Approved-1610443150-aws-model-group | 输入 | 批准 | 贡献至 | action |
| 14 | bert-reviews-1610437484-1-Approved-1610443150-aws-endpoint | 输出 | 模型部署 | 贡献至 | action |
| 15 | bert-reviews-1610437484-1-aws-model-group | 输出 | ModelGroup | AssociatedWith | context |
查看管道实验谱系
使用 SageMaker 实验 API,我们可以通过管道的所有步骤展示管道的实验谱系,包括特征工程、模型训练、评估和部署,如下所示:
from sagemaker.analytics import ExperimentAnalytics
experiment_analytics = ExperimentAnalytics(
experiment_name=experiment_name,
)
experiment_analytics.dataframe()
| TrialComponentName | DisplayName | max_seq_length | learning_rate | train_accuracy | test_accuracy | endpoint_name |
|---|---|---|---|---|---|---|
| pipelines-0tsa93mahu8v-processing-kch2vw03qc-aws-processing-job | prepare | 64.0 | NaN | NaN | NaN | |
| pipelines-0tsa93mahu8v-Train-tlvC7YdBl9-aws-training-job | train | 64.0 | 0.000017 | 0.9416 | NaN | |
| pipelines-1daa23hlku3v-processing-hkc9w0v0q-aws-processing-job | evaluate | 64.0 | NaN | NaN | 0.9591 | |
| TrialComponent-2021-01-09214921-dgtu | deploy | NaN | NaN | NaN | NaN | bert-reviews-1610437484-endpoint |
使用 SageMaker 管道进行自动化
有两种主要的自动启动管道的方式:基于事件的触发器和基于时间的触发器。基于事件的触发器将在特定事件发生时启动管道,例如,当一个新的train.py提交到我们基于 Git 的代码仓库时。这通常称为“GitOps”自动化。当新数据通过PutObject事件进入 S3 时,我们也可以启动新的管道。基于时间的触发器将按计划启动管道,例如每周、每两天或每四小时一次。让我们讨论如何实现 GitOps、S3 和基于时间的触发器,以自动启动 SageMaker 管道。
在提交代码时通过 GitOps 触发
SageMaker 通过 SageMaker 项目实现 GitOps 管道自动化。SageMaker 项目提供了预构建的 MLOps 模板,自动化模型构建和部署管道。我们可以根据需要定制这些模板,或者创建自己的模板。
我们可以选择使用 SageMaker 提供的预构建 MLOps 模板之一,或者使用我们自己提供的自定义模板来创建我们自己的项目。MLOps 模板使用 AWS CloudFormation 自动设置所有必需的组件,以便我们的 GitOps 自动化工作流可以与 SageMaker 管道配合使用。MLOps 模板还设置了一个触发器,每次我们向代码仓库提交新代码时都会运行管道。
我们 SageMaker 管道的 MLOps 模板主要由两个组件组成:modelbuild和modeldeploy。modelbuild组件用于构建和注册模型。modeldeploy组件将模型部署到暂存和生产环境。将模型部署到生产环境需要进行第二次手动批准步骤,如图 10-7 所示。

图 10-7. MLOps 管道,用于将模型部署到暂存和生产环境,带有手动批准。
modelbuild 和 modeldeploy 的分离允许责任和访问控制的分离。例如,数据科学家可能负责 modelbuild 阶段将模型推送到暂存区,而 DevOps 团队负责 modeldeploy 阶段将模型推送到生产环境。
S3 在新数据到达时触发
当新数据直接从应用程序或通过数据流服务(如 Kinesis Streams 和 Managed Streaming for Apache Kafka)进入系统时,我们可能希望持续运行我们的管道并更新我们的模型以包括新数据。虽然每周、每天甚至每小时手动运行管道是完全可以接受的,但我们可以在上游应用程序从 S3 中新数据着陆时轻松自动化管道,如 图 10-8 所示。

图 10-8. 当新数据到达 S3 时自动启动 SageMaker 管道。
首先,我们需要在我们的 S3 存储桶上启用 AWS CloudTrail 数据事件日志记录,以便在新数据到达 S3 时收到通知:
watched_bucket_arn=<S3_BUCKET_ARN_TO_WATCH>
event_selector=\
'\'[{ "ReadWriteType": "WriteOnly", "IncludeManagementEvents":true, \
"DataResources": \
[{ "Type": "AWS::S3::Object", \
"Values": ["' + watched_bucket_arn + '"]
}]
}]\''
!aws cloudtrail put-event-selectors \
--trail-name $trail_name \
--event-selectors $event_selector
接下来,我们将创建一个 Amazon EventBridge 规则,使用一个 EventBridge 规则来触发 SageMaker 管道,每当新文件上传到 S3 存储桶时触发,这个规则匹配 S3 的 PutObject 和 CompleteMultipartUpload。以下是启用此行为的 Python 代码:
events = boto3.client('events')
watched_bucket=<S3_BUCKET_NAME_TO_WATCH>
pattern = {
"source": [
"aws.s3"
],
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"s3.amazonaws.com"
],
"eventName": [
"PutObject",
"CompleteMultipartUpload",
"CopyObject"
],
"requestParameters": {
"bucketName": [
"{}".format(watched_bucket)
]
}
}
}
response = events.put_rule(
Name='S3-Trigger',
EventPattern=json.dumps(pattern),
State='ENABLED',
Description='Triggers an event on S3 PUT',
EventBusName='default'
)
最后,我们将规则与 AWS Lambda 函数关联,以在匹配规则时启动我们的管道:
response = events.put_targets(
Rule='S3-Trigger',
EventBusName='default',
Targets=[
{
'Id': '1',
'Arn': lambda_arn,
'RoleArn': iam_role_eventbridge_arn,
}
]
)
这是用于触发我们 SageMaker 管道的 AWS Lambda 函数的摘录:
sm = boto3.client('sagemaker', region_name=region)
timestamp = int(time.time())
def lambda_handler(event, context):
response = sm.start_pipeline_execution(
PipelineName=<PIPELINE_NAME>,
PipelineExecutionDisplayName='<PIPELINE_EXECUTION_DISPLAY_NAME>',
PipelineParameters=[
...
]
)
每当有新文件上传到这个 S3 存储桶时,EventBridge 将触发规则并启动我们的管道执行。我们可以使用 lambda_handler 函数的 event 变量来确定上传的确切文件,也许可以仅对新文件进行增量训练我们的模型。根据我们的用例,我们可能不希望为每个上传到 S3 的文件启动新管道。然而,这是构建我们自己的规则和触发器的一个良好起点,适用于许多 AWS 服务。
注意
在撰写本文时,EventBridge 与 SageMaker 管道之间没有原生集成,因此我们需要使用 Lambda 函数的桥接。然而,到本书出版时,可能会有原生集成,因此我们可以跳过 Lambda 函数直接将 EventBridge 与 SageMaker 管道集成。
基于时间的计划触发器
我们可能希望在特定时间段内(例如每小时、每天、每月等)对数据批次触发我们的管道。类似于配置 cron 作业,我们可以创建一个 EventBridge 规则来按计划运行我们的管道。我们可以使用熟悉的 cron 语法或通过定义固定的频率(例如每小时)来指定计划。或者,我们可以使用 AWS Python SDK 以编程方式定义计划的规则。以下代码每小时触发管道运行:
events = boto3.client('events')
response = events.put_rule(
Name='Hourly_Time_Based_Trigger',
ScheduleExpression='rate(1 hour)',
State='ENABLED',
Description='Hourly Time-Based Trigger',
EventBusName='default'
)
统计漂移触发器
如果 SageMaker Model Monitor 检测到相对于给定基线或预测标签的真实性的数据质量漂移、模型质量漂移、偏差漂移或可解释性漂移,我们也可以启动新的流水线。我们可以为数据质量、模型质量、模型偏差和特征重要性创建基线,并使用 SageMaker Model Monitor 监控我们部署的模型,如第九章所讨论的。
Model Monitor 捕获实时模型预测,并分析数据分布,比较模型输入和模型输出与从训练数据中学习的基线阈值。这有助于我们检测统计变化,如协变量漂移或概念漂移,这可能触发新的流水线执行来重新训练模型。
Model Monitor 与 SageMaker Clarify 集成。使用 Clarify,SageMaker 连续监控部署的模型,检测模型偏差和特征重要性的变化。我们基于离线训练数据定义模型偏差指标的置信区间。我们持续监控模型在线预测中观察到的置信区间。如果观察到的置信区间与定义的置信区间不重叠,SageMaker Clarify 将触发一个偏差漂移警报,我们可以用来启动新的流水线。类似地,如果特征重要性的变化超过了定义的阈值,SageMaker Clarify 将触发一个特征归因漂移警报,我们可以用来启动新的流水线。
更多的流水线选项
虽然 SageMaker Pipelines 是在 AWS 上实现 AI 和机器学习流水线的标准方式,我们还介绍了 AWS Step Functions 以及各种开源选项,如 Kubeflow Pipelines、Apache Airflow、TFX 和 MLflow。这些工具对包括 Amazon S3、Athena、EMR、EFS 和 FSx for Lustre 在内的 AWS 数据存储提供了良好的支持。
AWS Step Functions 和 Data Science SDK
Step Functions 是一个很好的选择,可以构建复杂的工作流,而无需构建和维护自己的基础设施。虽然 Step Functions 并非专门为机器学习而设计,但它们提供了与许多 AWS 服务深度集成的灵活性,并公开了 Step Functions Data Science SDK。
图 10-9 显示了一个 Step Function 流水线,用于编排在 SageMaker Pipelines 部分展示的基于 BERT 的评论分类器流水线。

图 10-9. Step Function 流水线,用于在 SageMaker 上编排我们基于 BERT 的流水线。
这里是我们流水线中训练步骤的 Step Function 配置的摘录。完整的代码可以在与本书关联的 GitHub 仓库中找到:
"Training": {
"AlgorithmSpecification": {
"TrainingImage": "<TENSORFLOW_IMAGE_URI>".format(region),
"TrainingInputMode": "{}".format(input_mode)
},
"HyperParameters": {
"epochs": "{}".format(epochs),
"learning_rate": "{}".format(learning_rate),
"epsilon": "{}".format(epsilon),
...
}
}
}
Kubeflow Pipelines
Kubeflow 是建立在 Kubernetes 上的流行机器学习生态系统,包括一个称为 Kubeflow Pipelines 的编排子系统。尽管 Kubeflow 要求我们构建和维护自己的 Amazon EKS 集群,但它在 AWS 上得到了很好的支持,如 图 10-10 所示。

图 10-10. Kubeflow 在 AWS 上得到了很好的支持,因为与 Amazon EKS 的紧密集成。
使用 Kubeflow,我们可以运行分布式训练作业,分析训练指标,跟踪流水线谱系,重新启动失败的流水线,并安排流水线运行。Kubeflow 中使用的约定是明确定义的,并得到了许多组织中开源贡献者大规模支持的支持。如果我们已经在使用 Kubernetes,Kubeflow 可能是管理我们的流水线的一个好选择。
尽管对于一些人来说管理 Kubernetes 是一件有趣的事情,包括本书的作者们,在日常数据科学和工程任务中却是一种分心。本书的作者们在许多夜晚和周末都在解决 Kubernetes 层面的问题,这段时间本可以用来工程更多功能和训练更好的模型。
由于 Kubeflow 与 Kubernetes 的紧密集成,几乎可以通过查阅 Kubernetes 和 Amazon EKS 的功能来回答关于管理和扩展 Kubeflow 集群的每一个问题。以下是一些示例:
问题: “如何监控我的 Kubeflow 训练作业中的 GPU?”
回答: “在 AWS 上,您监控 Kubernetes 上其他系统资源的方式相同:Prometheus、Grafana 和 CloudWatch。”
问题: “如何自动缩放我的 Kubeflow REST 端点?”
回答: “在 AWS 上,您自动缩放 Kubernetes 资源的方式与其他资源相同:水平 Pod 自动缩放、集群自动缩放和 CloudWatch。”
问题: “Kubeflow 支持 Spot Instances 吗?”
回答: “是的,因为 Amazon EKS 支持 Spot Instances。”
注意
值得注意的是,在使用 Spot Instances 训练 Kubeflow 模型时,我们必须使用一个能够容忍 Spot Instances 离开集群(在训练作业期间)的框架,在新的 Spot Instances 可用时替换它们。当 Spot Instances 被替换时,它们会从集群中移除,并在训练作业中出现为失败的实例。现代框架如 TensorFlow、PyTorch 和 Apache MXNet 支持实例故障,但需要额外的代码和配置来执行所需的检查点,以有效地从故障中恢复并继续训练。我们在 第八章 中展示了 TensorFlow 代码和 SageMaker 配置用于检查点。
让我们创建一个开源的 Kubeflow 流水线,使用托管的 Amazon SageMaker 和前几章节中使用的相同的 Amazon Customer Reviews 数据集来训练一个 BERT 模型,如 图 10-11 所示。

图 10-11. Kubeflow 流水线在 SageMaker 上编排我们基于 BERT 的流水线。
首先,我们导入 SageMaker Components for Kubeflow Pipelines Python 库和支持资产以在我们的 Kubeflow Pipeline 中使用。以下 YAML 可以在GitHub 上找到:
sagemaker_process_op = components.load_component_from_url( \
'components/aws/sagemaker/process/component.yaml')
sagemaker_train_op = components.load_component_from_url(
'components/aws/sagemaker/train/component.yaml')
sagemaker_model_op = components.load_component_from_url(
'components/aws/sagemaker/model/component.yaml')
sagemaker_deploy_op = components.load_component_from_url(
'components/aws/sagemaker/deploy/component.yaml')
现在让我们设置原始训练数据的 S3 位置:
def processing_input(input_name,
s3_uri,
local_path,
s3_data_distribution_type):
return {
"InputName": input_name,
"S3Input": {
"LocalPath": local_path,
"S3Uri": s3_uri,
"S3DataType": "S3Prefix",
"S3DataDistributionType": s3_data_distribution_type,
"S3InputMode": "File",
},
}
让我们定义转换后的 BERT 特征的 S3 位置:
def processing_output(output_name, s3_uri,
local_path, s3_upload_mode):
return {
"OutputName": output_name,
"S3Output": {
"LocalPath": local_path,
"S3Uri": s3_uri,
"S3UploadMode": s3_upload_mode
},
}
让我们使用 Kubeflow Pipelines Python SDK 定义实际的 Kubeflow Pipeline:
@dsl.pipeline(
name="BERT Pipeline",
description="BERT Pipeline",
)
def bert_pipeline(role=role,
bucket=bucket,
region=region,
raw_input_data_s3_uri=<RAW_DATA_S3_URI>):
现在让我们将原始输入数据转换为 BERT 特征:
# Training input and output location based on bucket name
process = sagemaker_process_op(
...
container_arguments=['--train-split-percentage',
str(train_split_percentage),
'--validation-split-percentage',
str(validation_split_percentage),
'--test-split-percentage',
str(test_split_percentage),
'--max-seq-length',
str(max_seq_length),
'--balance-dataset',
str(balance_dataset)])
让我们训练模型:
hyperparameters={
'epochs': '{}'.format(epochs),
'learning_rate': '{}'.format(learning_rate),
'epsilon': '{}'.format(epsilon),
...
}
hyperparameters_json = json.dumps(hyperparameters)
training = sagemaker_train_op(
hyperparameters=hyperparameters_json,
...
).after(process)
部署 BERT 模型作为基于 REST 的 SageMaker 端点:
create_model = sagemaker_model_op(
model_name=training.outputs["job_name"],
model_artifact_url=training.outputs["model_artifact_url"],
...
)
deploy_model = sagemaker_deploy_op(
variant_name_1='AllTraffic',
model_name_1=create_model.output,
instance_type_1=deploy_instance_type,
initial_instance_count_1=deploy_instance_count
)
让我们编译并运行 Kubeflow Pipeline,结果是部署一个带有我们的 BERT 模型的 SageMaker 端点:
kfp.compiler.Compiler().compile(bert_pipeline, 'bert-pipeline.zip')
client = kfp.Client()
experiment = client.create_experiment(name='kubeflow')
my_run = client.run_pipeline(experiment.id,
'bert-pipeline',
'bert-pipeline.zip')
让我们调用 SageMaker 端点并从评论文本中获取星级预测:
sm_runtime =
boto3.Session(region_name=region).client('sagemaker-runtime')
review = "This is great!".encode('utf-8')
response = sm_runtime.invoke_endpoint(
EndpointName=endpoint_name,
ContentType='application/jsonlines',
Body=review)
json.loads(response['Body'].read().decode())
### OUTPUT ###
{'predicted_label': 5}
Apache Airflow
Apache Airflow 是一个非常成熟和流行的选项,最初开发用于编排数据工程和 ETL 管道以支持分析工作负载。然而,Airflow 已经扩展到机器学习领域作为一个可行的管道编排工具。Amazon 支持 Amazon Managed Workflows for Apache Airflow 以减少在 AWS 上运行 Airflow 集群的操作负担。
带有大量第三方插件库和与许多 AWS 服务的原生集成,Amazon MWAA 是在 AWS 上管理使用 Airflow 的管道的一个很好的选择。如果我们已经在数据工程和 ETL 管道中使用 Airflow,Airflow 可能是编排机器学习管道的一个很好的选择。图 10-12 展示了我们基于 BERT 的评论分类器管道,通过 Amazon MWAA 和 SageMaker 实现为 Apache Airflow 的有向无环图(DAG)。

图 10-12. Amazon MWAA 在 SageMaker 上编排我们的基于 BERT 的管道。
让我们演示如何使用 SageMaker 构建一个 Airflow DAG 来编排我们基于 BERT 的机器学习管道。首先,我们需要定义 Airflow DAG:
import airflow
from airflow import DAG
default_args = {
'owner': 'airflow',
'provide_context': True
}
dag = DAG('bert_reviews',
default_args=default_args,
schedule_interval='@once')
接下来,让我们将原始数据转换为 BERT 特征:
from airflow.contrib.operators.sagemaker_processing_operator \
import SageMakerProcessingOperator
from sagemaker.workflow.airflow import processing_config
process_config = processing_config(estimator=estimator,
inputs=input_data_s3_uri,
outputs=output_data_s3_uri)
process_op = SageMakerProcessingOperator(
task_id='process',
config=process_config,
wait_for_completion=True,
dag=dag)
让我们训练模型:
import sagemaker
from sagemaker.tensorflow import TensorFlow
estimator = TensorFlow(
entry_point='tf_bert_reviews.py',
source_dir='src',
role=role,
instance_count=train_instance_count,
instance_type=train_instance_type,
volume_size=train_volume_size,
use_spot_instances=True,
# Seconds to wait for spot instances to become available
max_wait=7200,
checkpoint_s3_uri=checkpoint_s3_uri,
py_version='<PYTHON_VERSION>',
framework_version='<TENSORFLOW_VERSION>',
hyperparameters={
'epochs': epochs,
'learning_rate': learning_rate,
'epsilon': epsilon,
...
},
input_mode=input_mode,
metric_definitions=metrics_definitions,
rules=rules,
debugger_hook_config=hook_config,
max_run=7200, # number of seconds
)
from airflow.contrib.operators.sagemaker_training_operator \
import SageMakerTrainingOperator
from sagemaker.workflow.airflow import training_config
train_config = training_config(estimator=estimator,
inputs=training_data_s3_uri)
train_op = SageMakerTrainingOperator(
task_id='train',
config=train_config,
wait_for_completion=True,
dag=dag)
现在让我们部署模型:
from airflow.contrib.operators.sagemaker_model_operator \
import SageMakerModelOperator
from sagemaker.workflow.airflow import model_config
model_op = SageMakerModelOperator(
task_id='model',
config=model_config,
wait_for_completion=True,
dag=dag)
from airflow.contrib.operators.sagemaker_endpoint_operator \
import SageMakerEndpointOperator
from sagemaker.workflow.airflow import endpoint_config
deploy_op = SageMakerEndpointOperator(
task_id='deploy',
config=endpoint_config,
wait_for_completion=True,
dag=dag)
让我们定义管道:
init.set_downstream(process_op)
processing_op.set_downstream(train_op)
train_op.set_downstream(model_op)
model_op.set_downstream(deploy_op)
MLflow
MLflow 是一个开源项目,提供实验跟踪和多框架支持,包括 Apache Spark,但工作流支持有限。虽然 MLflow 具有一些不错的功能,但它要求我们建立和维护自己的 Amazon EC2 或 EKS 集群。如果我们需要一种轻量级、简单的方法来跟踪实验和运行简单的工作流,MLflow 可能是一个不错的选择。
TensorFlow Extended
TFX 是一组开源的 Python 库,用于在流水线编排器(如 Kubeflow Pipelines、Apache Airflow 和 MLflow)中使用。从非常高的层次来看,TFX 是一组解决机器学习流水线的每个步骤的 Python 库。虽然大多数在 TensorFlow 社区中使用,TFX 对其他框架(如 scikit-learn)也有有限的支持。如果我们已经在使用 TensorFlow 并希望为我们的流程添加一些结构,TFX 可能是一个不错的选择。然而,要在单个节点之外扩展、调整和管理 TFX,我们应该理解支持 TFX 分布式数据处理的 Apache Beam。Apache Beam 学习曲线略高,但一旦深入研究,就会变得非常直观。图 10-13 展示了 TFX 的不同库和组件。

图 10-13. TFX 库和组件。
人在环回路工作流程
尽管 AI 和机器学习服务使我们的生活更加便利,但人类远未过时。事实上,“人在环”概念已经成为许多 AI/ML 工作流的重要基石。在将敏感或受监管的模型推向生产之前,人类提供必要的质量保证。我们还可以通过向人类“众包”数据标注任务来利用人类智慧。
我们描述了两个服务,Amazon A2I 和 SageMaker Ground Truth,展示了人类与 AI 成功协作的案例。Amazon A2I 可让机器学习从业者将人工审核工作流整合到他们的应用程序中。SageMaker Ground Truth 则利用人力工作人员结合主动学习方法来创建准确的训练数据集。
使用 Amazon A2I 改善模型准确性
Amazon A2I 是一个完全托管的服务,用于开发人在环回路工作流程,包括用户界面、基于角色的 IAM 访问控制和 S3 数据存储。Amazon A2I 与 Amazon Rekognition(用于内容审核)和 Amazon Textract(用于表单数据提取)等服务集成。图 10-14 展示了一个 Amazon A2I 工作流程,用于审核来自 Amazon Comprehend 的模型预测。我们还可以将 Amazon A2I 与 Amazon SageMaker 和自定义 ML 模型一起使用。

图 10-14. Amazon 增强型 AI 工作流程,用于审核模型预测。
在这个例子中,Amazon Comprehend 收到预测请求的输入数据。我们设置了一个置信度阈值,用于确定何时需要人工审核。如果模型的预测满足置信度阈值,Amazon A2I 将直接将预测结果发送给客户端应用程序。如果模型无法进行高置信度预测,Amazon A2I 将任务发送给人工审核员。
在我们分类产品评论的示例中,置信度较低的预测可能会错误地将负面评论分类为中性或正面评论。如果我们没有自动修复这些置信度低的预测并改进我们的模型的方法,我们的业务可能会受到负面影响。
我们可能也希望随机审核所有预测的样本,无论置信度高低。这对于做出关键决策的模型可能很重要,例如在医疗保健行业。在这种情况下,我们可能也希望让人类审查和审核置信度高的预测,以确保模型表现正确。
Amazon A2I 整合人工审核者的结果,并将最终的预测响应发送给客户端应用程序。Amazon A2I 还可以将人工审核结果存储在 S3 中,我们可以将其用作新的训练数据。
Amazon A2I 引入了几个新术语:Worker Task Template(工作人员任务模板)、Flow Definition(流程定义)和 Human Loop(人工循环)。Worker Task Template 定义了工作人员的人工任务 UI。此 UI 显示工作人员的输入数据和指令。Flow Definition 定义了人工审核工作流程。该定义包含选择的工作人员,并提供有关如何完成审核任务的信息。Human Loop 表示实际的人工审核工作流程。一旦触发了人工循环,Amazon A2I 将按照流程定义将人工审核任务发送给工作人员。
让我们定义一些产品评论示例,我们将发送给 Amazon Comprehend:
sample_reviews = [
'I enjoy this product',
'I am unhappy with this product',
'It is okay',
'sometimes it works'
]
我们还定义了一个预测置信度阈值为 70%,这对我们的使用案例非常有效。如果我们的模型返回一个较低置信度的预测,亚马逊 A2I 将触发人工循环,我们的工作人员团队将收到一个任务:
human_loops_started = []
CONFIDENCE_SCORE_THRESHOLD = 0.70
for sample_review in sample_reviews:
# Call the Comprehend Custom model
response = comprehend.classify_document(
Text=sample_review,
EndpointArn=comprehend_endpoint_arn)
star_rating = response['Classes'][0]['Name']
confidence_score = response['Classes'][0]['Score']
print(f'Processing sample_review: \"{sample_review}\"')
# Our condition for when we want to engage a human for review
if (confidence_score < CONFIDENCE_SCORE_THRESHOLD):
humanLoopName = str(uuid.uuid4())
inputContent = {
'initialValue': star_rating,
'taskObject': sample_review
}
start_loop_response = a2i.start_human_loop(
HumanLoopName=humanLoopName,
FlowDefinitionArn=flowDefinitionArn,
HumanLoopInput={
'InputContent': json.dumps(inputContent)
}
)
human_loops_started.append(humanLoopName)
print(f'Confidence score of {confidence_score} for star rating of \
{star_rating} is less than the threshold of \
{CONFIDENCE_SCORE_THRESHOLD}')
print(f'Confidence score of {confidence_score} for star rating of \
{star_rating} is above threshold of \
{CONFIDENCE_SCORE_THRESHOLD}')
print('No human loop created. \n')
如果我们运行此代码,我们将看到以下响应:
Processing sample_review: "I enjoy this product"
Confidence score of 0.8727718591690063 for star rating of 3 is
above threshold of 0.7
No human loop created.
Processing sample_review: "I am unhappy with this product"
Confidence score of 0.8727718591690063 for star rating of 3 is
above threshold of 0.7
*** ==> Starting human loop with name: 72331439-0df9-4190-a42b-3e4048efb0a9
Processing sample_review: "It is okay"
Confidence score of 0.9679936170578003 for star rating of 4 is
above threshold of 0.7
No human loop created.
Processing sample_review: "sometimes it works"
Confidence score of 0.6361567974090576 for star rating of 3 is
less than the threshold of 0.7
*** ==> Starting human loop with name: e7994a4c-57bf-4578-aa27-dc5fb8c11d36
我们看到两个预测未达到我们的置信度阈值,并启动了人工循环。当分配的工作人员登录审核系统时,工作人员可以看到提交的审核任务。
借助增强型 AI,我们可以选择公共或私人工作人员。公共工作人员与亚马逊 Mechanical Turk 服务集成,拥有经过亚马逊预筛选的成千上万名人类标注者。我们还可以使用列在 AWS Marketplace 上的第三方经过预筛选的工作人员提供者。或者我们可以创建私人工作人员,与同事或员工一起工作。
指导指令是“将评价分类为介于 1 星(最差)和 5 星(最佳)之间的星级”。工作人员看到输入数据“有时候有效”,可能会将其分类为 3 星评级。请注意,我们可以将单个任务分配给多个人工审阅者,以减少人为偏见。Amazon A2I 通过加权审阅者评分来整合每个任务的多个回应。一旦所有审阅任务完成,UI 会从工作人员的界面中清除该任务。我们可以在 S3 中使用这些新标记的数据来构建用于训练和改进我们的 Comprehend Custom 模型的连续流水线,如图 10-15 所示。

图 10-15. 用于改进模型预测的连续训练流水线。
我们的模型变得越来越精确,发送给我们工作人员的评审越少。这个概念也被称为“主动学习”,在 SageMaker Ground Truth 中实现。
使用 SageMaker Ground Truth 的主动学习反馈循环
主动学习始于人类标记工作流程,然后在看到足够样本后转向自动标记。主动学习反馈循环用于持续重新训练模型并提升未来标签预测的置信度。主动学习有助于通过处理高置信度预测来扩展数据标记过程,并释放工作力量专注于需要专业人工智能处理的低置信度预测。
Amazon SageMaker Ground Truth 是用于自动数据标记的增强型 AI 工作流实现。随着数据量的增加,SageMaker Ground Truth 将人类审阅工作流程与主动学习结合起来。随着人类工作力量对越来越多的数据进行标记,SageMaker Ground Truth 积极训练模型以加入工作力量,并自动标记到达的新数据。如果模型信心不足,则将数据发送给人类工作力量进行审阅。图 10-16 说明了 SageMaker Ground Truth 的工作流程以及从手动标记到自动标记的过渡。

图 10-16. SageMaker Ground Truth 使用主动学习增强人类数据标记。
SageMaker Ground Truth 提供预构建的标记工作流和任务模板来处理图像、文本和视频。我们也可以定义一个自定义工作流。在下面的示例中,我们将为图像创建一个主动学习流水线。SageMaker Ground Truth 在背后积极创建一个新的对象检测模型,随着看到越来越多的人工标签。SageMaker Ground Truth 使用这个新模型来自动检测图像中的对象,并且随着精度的增加。这允许人类专注于标记更难分类的图像。图 10-17 展示了 SageMaker Ground Truth 中用于检测和标记每个图像中对象的示例工作人员界面。

图 10-17. SageMaker Ground Truth 中的示例工作人员界面。
减少成本,提高性能
大多数管道编排引擎支持某种类型的步骤缓存,以避免重新执行未更改的步骤。这称为管道的“步骤缓存”。因为管道通常基于其他原语(如 SageMaker 训练作业),我们将强调 Spot 实例成本节省对我们的 SageMaker 管道使用的 SageMaker 训练作业的影响。
缓存管道步骤
在某些情况下,我们可以重用先前成功的管道步骤的结果,并避免再次运行该步骤。SageMaker Pipelines 通过检查相同输入工件和参数的先前成功步骤执行来支持步骤缓存。其他编排器也支持管道步骤缓存,包括 Kubeflow Pipelines。
要在 SageMaker Pipelines 中启用步骤缓存,我们在每个步骤创建时提供缓存配置,如下所示适用于特征工程ProcessingStep。如果 SageMaker Pipelines 检测到原始数据集和处理参数未发生变化,它将跳过步骤执行,重用生成的 BERT 嵌入,并继续管道的执行:
from sagemaker.workflow.steps import CacheConfig
cache_config_prepare = CacheConfig(
enable_caching=True,
expire_after=<EXPIRE_TIME>
)
experiment_config_prepare = {
'ExperimentName': experiment_name,
'TrialName': trial_name,
'TrialComponentDisplayName': 'prepare'
}
processing_step = ProcessingStep(
name='Processing',
code='preprocess-scikit-text-to-bert-feature-store.py',
processor=processor,
inputs=processing_inputs,
outputs=processing_outputs,
job_arguments=[...],
experiment_config=experiment_config_prepare,
cache_config=cache_config_prepare
)
使用成本较低的 Spot 实例
SageMaker Pipelines 基于像训练作业这样的 SageMaker 原语构建,支持 Spot 实例。我们演示了如何在 SageMaker 训练作业中启用 Spot 实例,在第七章中展示了如何定义我们的估算器时也要启用检查点。
checkpoint_s3_uri = 's3://<BUCKET>/<CHECKPOINT_PREFIX>/'
estimator = TensorFlow(
entry_point='tf_bert_reviews.py',
source_dir='src',
use_spot_instances=True,
checkpoint_s3_uri=checkpoint_s3_uri,
...
)
training_step = TrainingStep(
name='Train',
estimator=estimator,
...
)
摘要
在本章中,我们描述了如何通过有效的机器学习管道提高模型质量,并释放人力资源专注于更高级别的任务。我们确定了有效机器学习管道的关键组成部分,如数据摄取时的数据质量检查和模型训练后的模型验证。我们演示了如何使用 SageMaker Pipelines 和其他各种选项(包括 AWS Step Functions、Kubeflow Pipelines、Apache Airflow、MLflow 和 TFX)来编排管道。
我们展示了如何使用 SageMaker Pipelines 实现管道自动化。我们讨论了基于事件的触发器,如代码提交和新数据到达 S3 以启动管道执行。我们学习了如何设置基于时间的调度和统计触发器来自动运行管道执行。我们展示了如何使用人在环路工作流自动化数据标记,如何使用 Amazon 增强 AI 提高模型准确性,以及如何使用 SageMaker Ground Truth 实现主动学习反馈循环。
通过掌握如何创建可重复和自动化的流水线,我们现在已经完全准备好将我们的数据科学项目从实验阶段转移到生产阶段。通过自动化模型开发和模型部署工作流中的所有步骤,我们提高了生产力并确保了可重复性。通过实施 GitOps 实践来强化一致性和质量,我们提升了可靠性。通过使用 SageMaker 实验追踪所有流水线步骤和执行情况以及 ML Lineage 追踪输入/输出工件,我们实现了可审计性。我们还可以通过自动检测数据集、模型、预测和解释的统计属性变化来维护高质量的模型。
在第十一章中,我们将我们的分析和机器学习扩展到流数据。我们将计算实时摘要统计信息,检测异常,并在连续的产品评论数据流上训练模型。
第十一章:流式分析与机器学习
在前面的章节中,我们假设所有数据都在一个集中的静态位置可用,比如我们基于 S3 的数据湖。现实世界的数据是从全球多个不同来源同时持续流入的。我们需要对数据流执行机器学习,用于欺诈预防和异常检测等用例,这些用例中批处理的延迟是不可接受的。我们可能还希望对实时数据流进行持续分析,以获得竞争优势并缩短业务洞察的时间。
在本章中,我们从客户评论的训练数据集转向了现实场景。我们将专注于分析我们从所有可用的在线渠道收集的产品评论消息的连续流。客户对产品的反馈无处不在,包括社交媒体渠道、合作伙伴网站和客户支持系统。我们需要尽快捕获这些有价值的客户对我们产品的情感,以便及时发现趋势并迅速做出反应。
使用流式分析和机器学习,我们能够分析连续的数据流,例如应用程序日志、社交媒体信息流、电子商务交易、客户支持票务和产品评论。例如,我们可能希望通过分析实时产品评论来检测质量问题。
首先,我们将分析客户的情感,以便识别哪些客户可能需要高优先级的关注。接下来,我们将对传入的评论消息进行连续流分析,以捕获每个产品类别的平均情感。我们将在业务线(LOB)所有者的度量仪表板中可视化连续的平均情感。LOB 所有者现在可以快速检测情感趋势并采取行动。我们还将计算传入消息的异常分数,以检测数据架构或数据值中的异常。如果异常分数上升,我们将警告负责的应用程序开发人员来调查根本原因。作为最后一个指标,我们还将计算接收消息的连续近似计数。数字营销团队可以使用这些在线消息数量来衡量社交媒体活动的效果。
本章提供了描述性分析(汇总统计)和使用我们在前几章中训练、调优和部署的基于 BERT 的 SageMaker 模型的预测性分析示例。
在线学习与离线学习
在 第九章 中,我们演示了如何通过实时奖励数据持续训练强化学习模型,以实现几乎实时的“在线学习”,使用的是来自示例客户评论应用程序的数据。在线学习,或增量学习,是机器学习的一个小子集,有些难以适应传统离线算法以有效地进行在线训练。在线学习将新数据整合到模型中,无需使用完整数据集进行完全重新训练。
总的来说,像线性回归、逻辑回归和 K-Means 聚类这样的线性算法更容易通过实时数据进行训练,因为它们背后的数学比较简单。Scikit-learn 支持在某些线性算法上使用partial_fit()函数进行增量学习。Apache Spark 支持线性回归和 K-Means 聚类的流式版本。
深度学习算法也能够进行在线学习,因为它们不断地使用新数据的小批量进行学习权重的微调。实际上,每当我们从现有模型检查点或预训练模型(而不是随机初始权重)中训练深度学习模型时,我们本质上是在进行在线增量训练,尽管数据通常是从磁盘而不是流中提供给算法的。
流处理应用程序
流处理应用程序数据与传统应用程序数据不同,传统应用程序数据通常由 REST API 和关系型数据库处理。大数据的 3V 特征也适用于流数据:数据量、速度和多样性。这些数据通常是小的,可能具有不同的结构,并且以大量的方式传入——比典型的应用程序数据更快。REST API 的开销和关系数据库的引用完整性通常无法满足高容量和高速率流处理应用程序的性能要求,这些应用程序可能消费半结构化或非结构化数据。
分布式流处理系统如 Apache Kafka 和 Amazon Kinesis 需要多个实例通过网络通信来扩展并共享处理高容量和高速率流的负载。由于多个实例需要通过网络通信,这些实例有时会因网络故障、硬件故障和其他意外情况而导致处理数据的速度不同步。因此,分布式流处理系统无法保证从流中消费数据的顺序与放入流中的顺序相同,这通常被称为“全序”。
流应用程序需要调整以适应这种缺乏总排序保证并维护自己的排序概念。虽然本章节不会详细介绍总排序保证,但在构建流应用程序时需要考虑这一点。一些分布式流系统允许我们启用总排序,但总排序会对性能产生负面影响,并可能抵消构建流应用程序的好处。
流技术为我们提供了实时收集、处理和分析数据流的工具。AWS 提供了多种流技术选项,包括 Amazon MSK 和 Kinesis 服务。通过 Kinesis 数据输送管道,我们可以持续准备和加载数据到我们选择的目的地。使用 Kinesis 数据分析,我们可以使用 SQL 或 Apache Flink 应用程序在数据到达时处理和分析数据。Apache Flink 使用 Scala 和 Java 编写,提供了包括减少停机时间的检查点和增加性能的并行执行在内的高级流分析功能。
使用 Kinesis 数据流,我们可以管理自定义应用程序的数据流摄取。而使用 Kinesis 视频流,我们可以捕获和存储视频流用于分析。AWS Glue 数据目录帮助我们定义和强制结构化流数据的模式。我们可以使用像 Apache Avro 这样的自描述文件格式与 AWS Glue 数据目录、Kafka 和 Kinesis 一起维护整个流应用程序中的结构化数据。
流数据的窗口查询
描述性流分析通常受窗口限制以处理——无论是时间还是输入记录数。例如,我们可以指定一个 30 秒的窗口或 1,000 条输入记录。
如果我们实现基于时间的窗口,我们的输入记录需要包含一个时间戳列。Kinesis 数据分析会自动添加一个名为 ROWTIME 的时间戳列,我们可以在 SQL 查询中使用它来定义基于时间的窗口。
Kinesis 数据分析支持三种不同类型的窗口:Stagger 窗口、Tumbling 窗口和 Sliding 窗口。稍后,我们将使用窗口查询来实现使用产品评论数据的流式分析和机器学习用例。
Stagger 窗口
Stagger 窗口是基于时间的窗口,随着新数据的到达而打开,是推荐的聚合数据的方式,因为它们减少了延迟或乱序数据。因此,如果我们需要分析在不一致时间到达但应该一起聚合的数据组,Stagger 窗口是一个很好的选择。我们指定一个分区键来标识哪些记录应该聚合在一起。当匹配分区键的第一个事件到达时,Stagger 窗口将打开。为了关闭窗口,我们指定一个窗口年龄,从窗口打开时开始计算。我们使用 Kinesis 特定的 SQL 子句 WINDOWED BY 来定义 Stagger 窗口。Stagger 窗口以分区键和窗口长度作为参数定义:
...
FROM <stream-name>
WHERE <... optional statements...>
WINDOWED BY STAGGER(
PARTITION BY <partition key(s)>
RANGE INTERVAL '1' MINUTE
);
分区键可以是产品评论消息出现的时间,以及产品类别:
PARTITION BY FLOOR(message_time TO MINUTE), product_category
结果的错开窗口显示在图 11-1 中。

图 11-1. 错开窗口。
在这个示例中,我们看到四个数据记录到达:
| ROWTIME | message_time | product_category |
|---|---|---|
| 11:00:20 | 11:00:10 | BOOKS |
| 11:00:30 | 11:00:20 | BOOKS |
| 11:01:05 | 11:00:55 | BOOKS |
| 11:01:15 | 11:01:05 | BOOKS |
假设我们正在计算 SQL 查询中每个产品类别的数据记录数。一分钟的错开窗口将聚合记录如下:
| ROWTIME | message_time | product_category | count |
|---|---|---|---|
| 11:01:20 | 11:00:00 | BOOKS | 3 |
| 11:02:15 | 11:01:00 | BOOKS | 1 |
我们的错开窗口正在按一分钟间隔进行分组。当我们接收到每个产品类别的第一条消息时,窗口就会打开。对于BOOKS,这发生在ROWTIME为 11:00:20 时。一分钟窗口在 11:01:20 到期。在此时,根据ROWTIME和message_time落入此一分钟窗口内的结果将被发出。例如,这个示例中的计数将为 3。第四个数据记录的message_time在一分钟窗口外,将单独聚合。这是因为message_time在分区键中指定。例如,第一个窗口中message_time的分区键是 11:00。
滚动窗口
滚动窗口在非重叠的窗口中处理流式数据记录,最适合于在规则间隔打开和关闭的不同基于时间的窗口。在这里,每个数据记录属于特定的窗口,并且仅处理一次,如图 11-2 所示。
使用GROUP BY SQL 子句的聚合查询在滚动窗口中处理行:
SELECT ...
FROM <stream-name>
GROUP BY <column>,
STEP(<stream-name>.ROWTIME BY INTERVAL '60' SECOND);

图 11-2. 滚动窗口。
在这个示例中,滚动窗口是基于时间的,为一分钟窗口。我们按ROWTIME对记录进行分组。STEP函数将ROWTIME向下舍入到最接近的分钟。请注意,STEP可以将值向下舍入到任意间隔,而FLOOR函数只能将时间值向下舍入到整数时间单位,如小时、分钟或秒。
滑动窗口
滑动窗口连续使用固定的间隔和固定的大小进行数据聚合。它们随时间连续滑动。我们可以使用显式的WINDOW子句而不是GROUP BY子句来创建滑动窗口,间隔可以是基于时间或行。滑动窗口可以重叠,数据记录可以属于多个窗口。如果数据记录属于多个窗口,则在每个窗口中处理记录,如图 11-3 所示。

图 11-3. 滑动窗口。
下面的示例创建一个一分钟的滑动窗口:
SELECT ...
FROM <stream-name>
WINDOW W1 AS (
PARTITION BY <column>
RANGE INTERVAL '1' MINUTE PRECEDING);
我们还可以根据行数定义滑动窗口:
SELECT ...
FROM <stream-name>
WINDOW
last2rows AS (PARTITION BY <column> ROWS 2 PRECEDING),
last10rows AS (PARTITION BY <column> ROWS 10 PRECEDING);
在这个例子中,我们创建了一个 2 行滑动窗口和一个 10 行滑动窗口。2 行滑动窗口将重叠在 10 行滑动窗口上。如果我们计算不同大小记录批次的平均指标,这种情况非常有用。
现在我们对如何处理窗口查询有了更好的理解,让我们使用 AWS 实现我们的在线产品评论示例。
在 AWS 上的流式分析和机器学习
我们将使用 Kinesis 服务来实现我们的在线产品评论示例。为简单起见,假设流媒体团队已经解析了社交媒体反馈消息,并为每条消息附加了唯一的评论 ID 和相关的产品类别。
我们从这些消息的摄入开始。我们设置了 Kinesis Data Firehose 传递流,接收消息并将其连续传送到 S3 位置,如 图 11-4 中的摄入和存储消息列所示。
我们还希望通过客户情感来丰富消息。我们可以利用前几章中调优过的基于 BERT 的模型,将消息分类为星级评分,如 图 11-4 中的检测客户情感列所示。星级评分将作为情感的代理指标。我们可以将预测的星级评分 4 到 5 映射为积极情感,星级评分 3 映射为中性情感,星级评分 1 到 2 映射为负面情感。

图 11-4. 在线产品评论消息的流数据架构。
接下来,我们要分析我们的消息。我们设置了 Kinesis 数据分析来处理我们的情感丰富消息,如 图 11-4 中的分析和计算指标列所示。Kinesis 数据分析使我们能够在流数据上运行 SQL 查询。Kinesis 数据分析 SQL 基于 ANSI 2008 SQL 标准,具有处理流数据的扩展功能。
我们定义一个 SQL 查询,持续计算平均星级评分以反映情感的变化,并将结果推送到实时指标仪表板,如 图 11-4 中的可视化和消费指标列所示。我们定义另一个 SQL 查询,持续根据消息数据计算异常分数,以捕获任何意外的模式或数据值。例如,我们突然接收到一个星级评分为 100,这是不存在的。解析消息的应用程序可能存在错误。在这种情况下,我们希望通知负责团队调查可能的根本原因并修复问题。在第三个 SQL 查询中,我们持续计算可能被数字营销团队的应用程序消耗的消息的近似计数。
SQL 查询持续运行在我们的入站产品评审消息数据流上。我们可以通过基于时间或行的窗口定义流数据记录的小批处理。当计算每批次的平均值和近似计数时,我们可以将 SQL 查询限制为仅针对这些小批处理(窗口)的流数据记录。这种类型的 SQL 查询称为窗口查询。
使用 Amazon Kinesis、AWS Lambda 和 Amazon SageMaker 对实时产品评审进行分类
我们设置了一个 Kinesis Data Firehose 传送流,用于接收和转换来自客户的实时消息,如图 11-5 所示。

图 11-5. 使用 Kinesis Data Firehose 接收和转换数据记录。
-
我们接收实时输入数据并预测星级评分,以推断客户情感。这种情感可以用来快速识别可能需要我们高优先级关注的客户。
-
Kinesis Firehose 允许我们通过 Lambda 函数来转换我们的数据记录。我们构建了一个 Lambda 函数,接收 Firehose 数据记录,并将评审消息发送到托管我们精调的基于 BERT 模型的 SageMaker Endpoint。
-
模型预测
star_rating,我们的 Lambda 函数将其添加到原始数据记录中;然后函数将新记录返回给我们的 Firehose 传送流。 -
Firehose 传送流然后将转换后的数据记录传送到我们指定的一个 S3 存储桶。Kinesis Firehose 还允许我们备份原始数据记录。我们可以将这些备份数据记录传送到另一个 S3 存储桶。
使用 Amazon Kinesis Data Firehose 实现流数据摄取
Kinesis Data Firehose 是一个完全托管的服务,用于将实时流数据传送到目的地,如 Amazon S3、Redshift、Elasticsearch 或任何自定义 HTTP 终端。
作为数据源,我们可以选择DirectPut或 Kinesis 数据流。使用DirectPut,我们可以直接将数据发送到传送流,或从 AWS IoT、CloudWatch Logs 或 CloudWatch Events 检索数据。在我们的示例中,我们将选择DirectPut。
创建 Lambda 函数以调用 SageMaker Endpoint
在创建 Kinesis Firehose 传送流之前,我们需要创建 Lambda 函数以调用我们的 SageMaker Endpoint。Lambda 函数通过提供一种简单的机制来动态增加或减少基于 Python 的 Lambda 函数的数量(每个都运行其自己的 Python 解释器),来帮助扩展我们的 Python 代码。这类似于在单个实例上通过增加更多进程和解释器来扩展 Python。Lambda 函数的这种自动扩展功能类似于我们在第 9 章中展示的 SageMaker Endpoint 的自动扩展功能。
我们创建一个 Lambda 函数,从 Kinesis Firehose 传递流接收数据记录。除了 Kinesis 元数据如recordID外,每个数据记录包括review_id、product_category和实际的review_body。我们解析review_body并发送到指定的 SageMaker Endpoint。我们接收预测结果,将其添加到我们的数据记录中,并将带有原始recordID的修改后数据记录返回给 Kinesis 数据 Firehose。
以下是我们 Lambda 函数的 Python 代码摘录,它在推送新数据到 Kinesis 流时调用 SageMaker Endpoint:
ENDPOINT_NAME = os.environ['ENDPOINT_NAME']
runtime = boto3.client('runtime.sagemaker')
def lambda_handler(event, context):
outputs = []
for record in event['records']:
...
inputs = [
{"features": [review_body]}
]
response = runtime.invoke_endpoint(
EndpointName=ENDPOINT_NAME,
ContentType='application/jsonlines',
Accept='application/jsonlines',
Body=json.dumps(inputs).encode('utf-8')
)
...
output_record = {
'recordId': record['recordId'],
'result': 'Ok',
'data': ...
}
outputs.append(output_record)
return {'records': outputs}
我们可以直接在 AWS 控制台中创建 Lambda 函数,或者使用 Python SDK 进行编程,如下所示:
lam = boto3.Session().client(service_name='lambda',
region_name=region)
response = lam.create_function(
FunctionName=<FUNCTION_NAME>,
Runtime='<PYTHON_VERSION>',
Role=<IAM_ROLE>
Handler='<FUNCTION_NAME>.lambda_handler',
Code={
'ZipFile': code
},
Description='InvokeQuery SageMaker Endpoint.',
Timeout=300,
MemorySize=128,
Publish=True
)
我们可以使用引用 SageMaker 模型端点的环境变量更新 Lambda 函数以调用:
response = lam.update_function_configuration(
FunctionName=<FUNCTION_NAME>,
Environment={
'Variables': {
'ENDPOINT_NAME': <ENDPOINT_NAME>
}
}
)
现在我们可以创建我们的 Kinesis 数据 Firehose 传递流。
创建 Kinesis 数据 Firehose 传递流
我们将传递流类型配置为DirectPut,这样我们可以直接将产品评价放在流上。此外,为了存储流数据记录,我们定义了指向 S3 存储桶的ExtendedS3DestinationConfiguration。我们在ProcessingConfiguration中添加调用 SageMaker Endpoint 并将预测星级评分添加到我们数据中的 Lambda 函数。我们在S3BackupConfiguration中指定另一个 S3 存储桶,用于备份转换前的原始产品评价。
这是用于通过编程方式创建具有上述所有配置的 Kinesis 数据 Firehose 传递流的代码:
firehose = boto3.Session().client(service_name='firehose', region_name=region)
response = firehose.create_delivery_stream(
DeliveryStreamName=<FIREHOSE_NAME>,
DeliveryStreamType='DirectPut',
ExtendedS3DestinationConfiguration={
'RoleARN': <KINESIS_ROLE_ARN>,
'BucketARN': <S3_BUCKET_ARN>,
'Prefix': 'kinesis-data-firehose/',
...
'ProcessingConfiguration': {
'Enabled': True,
'Processors': [{
'Type': 'Lambda',
'Parameters': [
{
'ParameterName': 'LambdaArn',
'ParameterValue': '<LAMBDA_ARN>:$LATEST'
},
...
]
}]
},
'S3BackupMode': 'Enabled',
'S3BackupConfiguration': {
'RoleARN': <KINESIS_ROLE_ARN>,
'BucketARN': <BACKUP_S3_BUCKET_ARN>,
'Prefix': 'kinesis-data-firehose-source-record/',
...
},
...
}
)
我们需要等待几秒钟,直到传递流变为active状态。然后,我们可以将一些实时消息放在我们的 Kinesis 数据 Firehose 传递流上,并查看结果。
将消息放在流上
为了模拟我们连续的在线产品评价消息流,我们可以从亚马逊客户评价数据集中读取我们的样本客户评价,并将包含review_id、product_category和review_body的消息发送到 Kinesis 数据 Firehose,如下所示:
import boto3
import csv
import pandas as pd
firehose = boto3.Session().client(service_name='firehose', region_name=region)
# Read in sample reviews
df =
pd.read_csv('./data/amazon_reviews_us_Digital_Software_v1_00.tsv.gz',
delimiter='\t',
quoting=csv.QUOTE_NONE,
compression='gzip')
# Generate 500 online messages
step = 1
for start_idx in range(0, 500, step):
end_idx = start_idx + step
# Create message (review_id, product_category, review_body)
df_messages = df[['review_id',
'product_category',
'review_body']][start_idx:end_idx]
reviews_tsv = df_messages.to_csv(sep='\t',
header=None,
index=False)
# Put messages on Firehose
response = firehose.put_record(
Record={
'Data': reviews_tsv.encode('utf-8')
},
DeliveryStreamName=<FIREHOSE_NAME>
)
一旦消息到达,Firehose 调用InvokeSageMakerEndpointFromKinesis,指定的 Lambda 函数,以转换数据。我们可以看到原始消息格式,其中包含review_id、product_category和review_body:
['R1066MVAFC477L', 'Digital_Software', "It's good"]
我们的 Lambda 函数解析review_body,"It's good",将review_body发送到 SageMaker Endpoint,接收端点响应,并解码 5 的star_rating预测结果。
在最后一步中,Lambda 函数将星级评分添加到原始数据记录中,并将其返回给 Kinesis 数据 Firehose:
R1066MVAFC477L 5 Digital_Software It's good
我们还可以检查 Kinesis 数据 Firehose 指定的 S3 存储桶目的地。在这里,我们应该找到转换后的数据记录:
而且,确实,在s3://
...
R2EI7QLPK4LF7U 5 Digital_Software So far so good
R1W5OMFK1Q3I3O 3 Digital_Software Needs a little more work.....
RPZWSYWRP92GI 1 Digital_Software Please cancel.
R2WQWM04XHD9US 5 Digital_Software Works as Expected!
...
这些是我们转换后的数据记录。我们还配置了 Firehose 来备份我们的源数据记录。类似地,我们可以检查为备份指定的 S3 存储桶:
s3://
我们找到另一个与此类似的源记录文件:
...
R2EI7QLPK4LF7U Digital_Software So far so good
R1W5OMFK1Q3I3O Digital_Software Needs a little more work.....
RPZWSYWRP92GI Digital_Software Please cancel.
R2WQWM04XHD9US Digital_Software Works as Expected!
...
请注意缺失的星级评分。这里缺少星级评分,因为这是最初收到的产品评论消息。这些数据表示我们在调用基于 BERT 模型的 Lambda 函数(用于预测并添加星级评分到流数据记录中)之前的产品评论消息。我们保留这些原始数据作为备份。
这表明使用 Kinesis 数据 Firehose 进行流数据摄取和数据转换是有效的。现在让我们进入下一步。
使用流分析总结实时产品评论
我们想要持续计算的第一个业务指标是每个产品类别的平均情感值。我们可以将结果推送到实时指标仪表板。在我们的示例实现中,我们将平均星级评分(作为情感的代理指标)发布到亚马逊 CloudWatch。业务部门现在可以快速检测情感趋势并采取行动。
另一个我们持续计算的业务指标是基于消息数据计算的异常分数,以捕捉任何意外的模式或数据值。在应用程序错误的情况下,我们希望通知负责团队调查可能的根本原因并快速修复。在我们的实现中,我们将使用亚马逊简单通知服务(Amazon SNS)通过电子邮件发送计算的异常分数。亚马逊 SNS 是一个完全托管的服务,用于发送短信、电子邮件和移动推送通知。
作为最后一个指标,我们持续计算产品评论消息的近似计数,这些计数可以供数字营销团队使用,以评估和引导在线活动。在我们的实施中,我们将近似计数作为连续记录流传送到 Kinesis 数据流。数字营销团队可以开发一个自定义应用程序,从 Kinesis 数据流中读取数据记录并根据需要处理记录。
图 11-6 展示了我们演进的流数据用例实现。

图 11-6. 使用 Kinesis 数据分析分析连续流的产品评论消息。
设置亚马逊 Kinesis 数据分析
我们将建立一个 Kinesis 数据分析应用程序来分析我们的产品评论消息。Kinesis 数据分析使我们能够在流数据上运行 SQL 查询。
我们将使用 Kinesis 数据 Firehose 交付流作为 Kinesis 数据分析应用程序的输入源。然后我们将开发一个 Kinesis 数据分析应用程序来执行 SQL 查询,计算传入消息的平均情感、异常分数和近似计数。
与 Kinesis Firehose 类似,我们有选项对传入的流数据进行预处理。我们将重用现有的 Lambda 函数来调用 SageMaker 端点并接收我们传入消息的星级评分。星级评分将再次作为我们的情感代理度量。
注意
为什么不重用已经包含星级评分的来自 Kinesis Firehose 的转换数据记录?这些转换后的记录直接传送到 S3 目标存储桶。我们在 Kinesis Data Analytics 中只接收来自 Firehose 传送流的源数据记录。
Kinesis 数据分析支持将分析结果发送到各种目的地。我们将设置两个 Lambda 函数和一个 Kinesis 数据流作为目的地。我们可以利用 Lambda 函数与 Amazon CloudWatch 和 Amazon SNS 集成。让我们从目的地开始实现这种架构所需的组件。
创建一个 Kinesis 数据流以将数据传送到自定义应用程序
Kinesis 数据流用于实时摄取大量数据、存储数据并使数据可用于消费者应用程序。Kinesis 数据流存储的数据单元是数据记录。数据流代表一组数据记录。数据流中的数据记录分布在分片中。每个分片都有数据流中的数据记录序列。创建数据流时,需要指定数据流的分片数量。数据流的总容量是其分片容量之和。根据需要可以增加或减少数据流中的分片数量。
在流数据的上下文中,我们经常谈论生产者和消费者。生产者是生成数据的应用程序或服务。消费者是接收流数据以进一步处理的应用程序或服务。图 11-7 显示了 Kinesis 数据流的高级架构。

图 11-7. Kinesis 数据流架构由数据生产者和消费者组成,数据记录分布在分片中。
注意,在 Kinesis 数据流中数据只会暂时存储。Kinesis 数据流的默认数据保留期目前限制为 24 小时。但是,我们可以增加数据保留期长达一年以实现长期保留。更长的保留期可以帮助满足合规要求,无需将数据移动到像 S3 这样的长期存储中。较长的保留期还有助于在背压场景下,即使在意外数据推送量增加时,消费者无法跟上生产者的情况下,Kinesis 也可以存储流数据。在这种情况下,Kinesis 将存储流式数据,直到消费者扩展以处理高峰量,或者数据量减少并且消费者可以追赶上来。较长的保留期还允许我们使用从 Kinesis 直接获取的在线数据更快地训练模型,或者将在线 Kinesis 数据与 S3 中的离线数据结合使用。
在我们的示例中,Kinesis 数据流将接收来自我们消息近似计数的结果,因此生产者是 Kinesis 数据分析应用程序。消费者可以是任何自定义应用程序。在我们的示例中,我们建议使用来自数字营销团队的应用程序。图 11-8 突出了我们即将实施的架构当前步骤。

图 11-8. Kinesis 数据流用作 Kinesis 数据分析的近似计数目标。
这是创建 Kinesis 数据流的代码:
kinesis = boto3.Session().client(service_name='kinesis',
region_name=region)
kinesis.create_stream(
StreamName=<STREAM_NAME>,
ShardCount=<SHARD_COUNT>
)
我们需要等待几分钟,让 Kinesis 数据流变为active。
我们可以使用以下代码编程地检查流的状态,并等待流变为active:
import time
status = ''
while status != 'ACTIVE':
r = kinesis.describe_stream(StreamName=<STREAM_NAME>)
description = r.get('StreamDescription')
status = description.get('StreamStatus')
time.sleep(5)
接下来,让我们创建一个 Lambda 函数,作为我们异常分数的 Kinesis 数据分析目标。
创建 AWS Lambda 函数以通过 Amazon SNS 发送通知
在我们的 Kinesis 数据分析应用程序中,我们将为数据计算异常分数。如果异常分数升高,我们希望通知应用程序开发人员进行调查和修复。为了发送通知,我们利用 Amazon SNS。我们将向负责团队发送一封包含我们接收到的最新异常分数的电子邮件。
由于 Amazon SNS 不直接支持作为 Kinesis 数据分析的目标,我们创建另一个 Lambda 函数作为代理目标。图 11-9 突出了我们即将实施的架构步骤。

图 11-9. Lambda 函数用作 Kinesis 数据分析的异常分数目标。
这是创建我们的 Amazon SNS 主题的代码:
import boto3
sns = boto3.Session().client(service_name='sns', region_name=region)
response = sns.create_topic(
Name=<SNS_TOPIC_NAME>,
)
sns_topic_arn = response['TopicArn']
以下是我们 Lambda 函数代码的摘录,push_notification_to_sns.py,该函数记录了批处理输入记录中的最高异常分数,并将分数发布到 Amazon SNS 主题:
import boto3
import base64
import os
SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN']
sns = boto3.client('sns')
def lambda_handler(event, context):
output = []
highest_score = 0
...
r = event['records']
for record in event['records']:
try:
payload = base64.b64decode(record['data'])
text = payload.decode("utf-8")
score = float(text)
if (score != 0) and (score > highest_score):
highest_score = score
output.append({'recordId': record['recordId'], \
'result': 'Ok'})
...
if (highest_score != 0):
sns.publish(TopicArn=SNS_TOPIC_ARN, \
Message='New anomaly score: {}'\
.format(str(highest_score)), \
Subject='New Reviews Anomaly Score Detected')
return {'records': output}
与以前的 Lambda 函数类似,我们可以通过编程方式创建此 Lambda 函数,并更新该函数,其中环境变量设置为我们的 Amazon SNS 主题 ARN。
我们可以订阅 Amazon SNS 主题以接收 Amazon SNS 通知如下:
response = sns.subscribe(
TopicArn=sns_topic_arn,
Protocol='email',
Endpoint='<EMAIL_ADDRESS>',
)
我们还有一个 Lambda 函数要实现。
创建 AWS Lambda 函数以将指标发布到 Amazon CloudWatch
在我们的 Kinesis Data Analytics 应用程序中,我们还将计算流消息窗口的平均情感。我们希望将平均情感结果作为自定义指标发布到 CloudWatch。同样,我们将使用星级评分作为我们的情感代理指标。由于 CloudWatch 不直接支持作为 Kinesis Data Analytics 目的地,我们需要另一个 Lambda 函数作为代理目的地。图 11-10 强调了我们即将实施的架构步骤。

图 11-10. 用作 Kinesis Data Analytics 平均星级评分的 Lambda 函数目的地。
以下是我们 Lambda 函数代码 deliver_metrics_to_cloudwatch.py 的摘录,用于将平均星级评分作为自定义指标发布到 CloudWatch:
client = boto3.client('cloudwatch')
def lambda_handler(event, context):
output = []
...
for record in event['records']:
payload = base64.b64decode(record['data'])
datapoint = float(payload)
client.put_metric_data(
Namespace='kinesis/analytics/AVGStarRating',
MetricData=[
{
'MetricName': 'AVGStarRating',
'Dimensions': [
{
'Name': 'Product Category',
'Value': 'All'
},
],
'Value': datapoint,
'StorageResolution': 1
}
]
)
output.append({'recordId': record['recordId'], 'result': 'Ok'})
...
return {'records': output}
创建 Lambda 函数后,我们已经安装了所有 Kinesis Data Analytics 应用程序的目的地,并且现在可以创建 Kinesis Data Analytics 应用程序。
转换 Kinesis Data Analytics 中的流数据
与 Kinesis Data Firehose 中的数据转换功能类似,我们可以在 Kinesis Data Analytics 中转换传入的流数据。我们可以使用 Lambda 函数来转换、转换、丰富或过滤我们的流数据。此步骤在数据分析应用程序为数据流创建架构之前执行。在我们的示例中,我们将重用为 Kinesis Data Firehose 数据转换创建的 Lambda 函数。我们将再次使用该函数来再次用星级评分丰富我们的消息。图 11-11 可视化了此步骤的详细信息。

图 11-11. 在 Kinesis Data Analytics 中预处理流数据。
工作流如下所示:
-
我们在 Kinesis Data Firehose 交付流上接收产品评审消息,该流将记录传递到 S3。
-
我们设置了 Kinesis Data Analytics 应用程序,以 Firehose 交付流作为输入流。该应用程序从 Firehose 交付流接收产品评审消息,并将其发送到 Lambda 函数进行预处理。
-
我们正在重用 Lambda 函数
InvokeSageMakerEndpointFromKinesis,该函数调用托管在 SageMaker Endpoint 上的基于 BERT 的模型,以预测基于产品评审消息中的评审文本的星级评分。 -
Lambda 函数从我们的模型接收预测的星级评分,并将其附加到产品评审消息中。
-
Lambda 函数将产品评论消息返回到 Kinesis 数据分析应用程序,并将其与星级评分一起丰富。丰富的产品评论消息现在用作所有后续 SQL 查询的输入。
由于我们已经有了 Lambda 函数,我们可以继续为我们的应用程序开发 SQL 查询。
理解应用程序流和泵
Kinesis 数据分析应用程序中的一个重要概念是应用程序流和泵。在我们的示例中,我们将使用 Firehose 传输流作为数据分析应用程序的输入。这个输入流需要映射到数据分析应用程序中的应用程序流。一旦映射完成,数据将持续从输入流流入应用程序流中。我们可以将应用程序流视为一个表,然后可以使用 SQL 语句查询它。由于我们实际上不是在处理表,而是在处理连续的数据流,所以我们称其为流。
请注意,Kinesis 数据分析中的应用程序流仅存在于分析应用程序内部。它们存储我们 SQL 查询的中间结果。如果我们希望在应用程序外部处理结果,我们需要将应用程序流映射到支持的 Kinesis 数据分析目的地。因此,我们设置了三个不同的目的地来捕获我们应用程序流的结果。
下面是创建具有三列的应用程序流(MY_STREAM)的示例:
CREATE OR REPLACE STREAM "MY_STREAM" (
"column1" BIGINT NOT NULL,
"column2" INTEGER,
"column3" VARCHAR(64));
要向此流插入数据,我们需要一个泵。将泵视为一个持续运行的插入查询,将数据从一个应用程序流插入到另一个应用程序流。
这是一个示例,创建了一个泵(MY_PUMP),并通过从另一个 INPUT_STREAM 中选择数据记录将数据插入到 MY_STREAM 中:
CREATE OR REPLACE PUMP "MY_PUMP" AS
INSERT INTO "MY_STREAM" ( "column1",
"column2",
"column3")
SELECT STREAM inputcolumn1,
inputcolumn2,
inputcolumn3
FROM "INPUT_STREAM";
假设我们在数据分析应用程序中的输入流(我们的 Firehose 传输流)称为 SOURCE_SQL_STREAM_001。
亚马逊 Kinesis 数据分析应用程序
让我们创建三个应用程序流来计算平均 star_rating、异常分数和消息的近似计数。
计算平均星级评分
我们的第一个应用程序流名为 AVG_STAR_RATING_SQL_STREAM。我们使用 GROUP BY 语句计算接收到的消息的五秒滚动窗口内的平均星级评分,指定了 INTERVAL ‘5’。
下面是实现此目的的 SQL 代码:
CREATE OR REPLACE STREAM "AVG_STAR_RATING_SQL_STREAM" (
avg_star_rating DOUBLE);
CREATE OR REPLACE PUMP "AVG_STAR_RATING_SQL_STREAM_PUMP" AS
INSERT INTO "AVG_STAR_RATING_SQL_STREAM"
SELECT STREAM AVG(CAST("star_rating" AS DOUBLE)) AS avg_star_rating
FROM "SOURCE_SQL_STREAM_001"
GROUP BY
STEP("SOURCE_SQL_STREAM_001".ROWTIME BY INTERVAL '5' SECOND);
检测流数据中的异常
第二个应用程序流名为 ANOMALY_SCORE_SQL_STREAM。我们利用内置的 RANDOM_CUT_FOREST 实现,在消息的滑动窗口中计算异常分数。
Kinesis 数据分析中的随机切割森林 (RCF) 实现基于由 AWS 共同撰写的 “Robust Random Cut Forest Based Anomaly Detection on Streams” 研究论文。该论文详细介绍了在实时数据流中使用 RCF 进行在线学习。但是,AWS 提供了用于离线批量训练的内置 SageMaker 算法。RCF 还用于 QuickSight 中的异常检测。
Kinesis 数据分析中的 RANDOM_CUT_FOREST 函数构建一个机器学习模型,用于计算每条消息中数值的异常分数。该分数表示该值与观察到的趋势相比有多不同。该函数还计算每列的归因分数,反映了该特定列中数据的异常程度。所有列的所有归因分数之和即为总体异常分数。
由于 RANDOM_CUT_FOREST 是基于数值值的,我们将根据星级评分计算异常分数。RANDOM_CUT_FOREST 函数的唯一必需参数是指向我们输入流的指针,我们使用 CURSOR 函数定义这一点。以下是实现此操作的 SQL 代码:
CREATE OR REPLACE STREAM "ANOMALY_SCORE_SQL_STREAM" (
anomaly_score DOUBLE);
CREATE OR REPLACE PUMP "ANOMALY_SCORE_STREAM_PUMP" AS
INSERT INTO "ANOMALY_SCORE_SQL_STREAM"
SELECT STREAM anomaly_score
FROM TABLE(RANDOM_CUT_FOREST(
CURSOR(SELECT STREAM "star_rating"
FROM "SOURCE_SQL_STREAM_001")
)
);
计算流数据的近似计数
第三个应用程序内流被称为 APPROXIMATE_COUNT_SQL_STREAM。我们在五秒滚动窗口内计算传入消息的近似计数。Kinesis 数据分析具有内置函数,使用 COUNT_DISTINCT_ITEMS_TUMBLING 计算近似计数,滚动窗口大小设置为五秒。该函数使用 HyperLogLog 算法,该算法在小型数据结构中存储大量近似计数。
下面的 SQL 代码实现了在五秒滚动窗口上对 review_id 列的近似不同项计数:
CREATE OR REPLACE STREAM "APPROXIMATE_COUNT_SQL_STREAM"(
number_of_distinct_items BIGINT);
CREATE OR REPLACE PUMP "APPROXIMATE_COUNT_STREAM_PUMP" AS
INSERT INTO "APPROXIMATE_COUNT_SQL_STREAM"
SELECT STREAM number_of_distinct_items
FROM TABLE(COUNT_DISTINCT_ITEMS_TUMBLING(
CURSOR(SELECT STREAM "review_id" FROM "SOURCE_SQL_STREAM_001"),'review_id', 5)
);
创建 Kinesis 数据分析应用程序
现在我们已经完全准备好创建我们的 Kinesis 数据分析应用程序,所以让我们首先创建一个联合的 SQL 语句,其中包含我们的三个 SQL 查询,以计算平均星级评分、检测异常以及计算给定窗口大小下的流数据的近似计数。当我们创建应用程序时,我们将这个联合 SQL 查询作为 ApplicationCode 传递。以下是代码:
in_app_stream_name = 'SOURCE_SQL_STREAM_001' # Firehose input stream
window_seconds = 5
sql_code = '''
CREATE OR REPLACE STREAM "AVG_STAR_RATING_SQL_STREAM" (
avg_star_rating DOUBLE);
CREATE OR REPLACE PUMP "AVG_STAR_RATING_SQL_STREAM_PUMP" AS
INSERT INTO "AVG_STAR_RATING_SQL_STREAM"
SELECT STREAM AVG(CAST("star_rating" AS DOUBLE))
AS avg_star_rating
FROM "{}"
GROUP BY
STEP("{}".ROWTIME BY INTERVAL '{}' SECOND);
CREATE OR REPLACE STREAM "ANOMALY_SCORE_SQL_STREAM"
(anomaly_score DOUBLE);
CREATE OR REPLACE PUMP "ANOMALY_SCORE_STREAM_PUMP" AS
INSERT INTO "ANOMALY_SCORE_SQL_STREAM"
SELECT STREAM anomaly_score
FROM TABLE(RANDOM_CUT_FOREST(
CURSOR(SELECT STREAM "star_rating"
FROM "{}"
)
)
);
CREATE OR REPLACE STREAM "APPROXIMATE_COUNT_SQL_STREAM"
(number_of_distinct_items BIGINT);
CREATE OR REPLACE PUMP "APPROXIMATE_COUNT_STREAM_PUMP" AS
INSERT INTO "APPROXIMATE_COUNT_SQL_STREAM"
SELECT STREAM number_of_distinct_items
FROM TABLE(COUNT_DISTINCT_ITEMS_TUMBLING(
CURSOR(SELECT STREAM "review_id" FROM "{}"),
'review_id',
{}
)
);
'''.format(in_app_stream_name,
in_app_stream_name,
window_seconds,
in_app_stream_name,
in_app_stream_name,
window_seconds)
接下来,让我们创建 Kinesis 数据分析应用程序。我们将应用程序输入设置为我们的 Firehose 传递流,并配置 InputProcessingConfiguration 调用我们的 Lambda 函数来调用基于 BERT 模型的数据。然后,我们定义 InputSchema 来匹配我们丰富的产品评论消息,其中包括 review_id、star_rating、product_category 和 review_body。
对于应用程序的输出,我们引用了三个 SQL 查询的应用程序内流名称,并定义了目标。我们将目标 AVG_STAR_RATING_SQL_STREAM 和 ANOMALY_SCORE_SQL_STREAM 设置为相应的 Lambda 函数。我们将 APPROXIMATE_COUNT_SQL_STREAM 连接到 Kinesis 数据流目标。以下是创建 Kinesis 数据应用程序并引用先前定义的 sql_code 的代码:
kinesis_analytics = \
boto3.Session().client(service_name='kinesisanalytics',
region_name=region)
response = kinesis_analytics.create_application(
ApplicationName=kinesis_data_analytics_app_name,
Inputs=[
{
'NamePrefix': 'SOURCE_SQL_STREAM',
'KinesisFirehoseInput': {
...
},
'InputProcessingConfiguration': {
'InputLambdaProcessor': {
...
}
},
'InputSchema': {
'RecordFormat': {
'RecordFormatType': 'CSV',
'MappingParameters': {
'CSVMappingParameters': {
'RecordRowDelimiter': '\n',
'RecordColumnDelimiter': '\t'
}
}
},
'RecordColumns': [
{
'Name': 'review_id',
...
},
{
'Name': 'star_rating',
...
},
{
'Name': 'product_category',
...
},
{
'Name': 'review_body',
...
}
]
}
},
],
Outputs=[
{
'Name': 'AVG_STAR_RATING_SQL_STREAM',
'LambdaOutput': {
...
},
'DestinationSchema': {
'RecordFormatType': 'CSV'
}
},
{
'Name': 'ANOMALY_SCORE_SQL_STREAM',
'LambdaOutput': {
...
},
'DestinationSchema': {
'RecordFormatType': 'CSV'
}
},
{
'Name': 'APPROXIMATE_COUNT_SQL_STREAM',
'KinesisStreamsOutput': {
...
},
'DestinationSchema': {
'RecordFormatType': 'CSV'
}
}
],
ApplicationCode=sql_code
)
启动 Kinesis 数据分析应用程序。
创建 Kinesis 数据分析应用程序后,我们必须显式启动应用程序以接收和处理数据。以下是启动我们的 Kinesis 数据分析应用程序的代码:
input_id =
response['ApplicationDetail']['InputDescriptions'][0]['InputId']
response = kinesis_analytics.start_application(
ApplicationName=kinesis_data_analytics_app_name,
InputConfigurations=[
{
'Id': input_id,
'InputStartingPositionConfiguration': {
'InputStartingPosition': 'NOW'
}
}
]
)
将消息放入流中。
应用程序运行后,我们可以通过将消息放入流中来测试我们的流水线。为了模拟我们连续的在线产品评论消息流,我们重复使用之前的代码。我们从亚马逊客户评论数据集中读取我们的示例客户评论,并将包含 review_id、product_category 和 review_body 的消息发送到 Kinesis Data Firehose。我们的 Kinesis 数据分析应用程序配置为使用 Firehose 交付流作为输入源。
让我们审查一下我们的数据分析应用程序的结果。如果我们在 AWS 控制台中打开 Kinesis 数据分析应用程序,我们可以看到源和目标配置,如 11-12 和 11-13 所示。

Figure 11-12. Kinesis 数据分析应用程序,源配置。
Firehose 交付流映射到应用程序内流 SOURCE_SQL_STREAM_001。我们还使用 Lambda 函数 InvokeSageMakerEndpointFromKinesis 对输入记录进行预处理。

Figure 11-13. Kinesis 数据分析应用程序,目标配置。
目标配置显示了我们的三个应用程序内流 AVG_STAR_RATING_SQL_STREAM、ANOMALY_SCORE_SQL_STREAM 和 APPROXIMATE_COUNT_SQL_STREAM 与其相应目标的正确映射。
从该控制台,我们还可以打开实时分析仪表板,查看 SQL 查询执行结果作为消息到达。如果我们选择 Source 标签,我们可以看到传入的消息,如 Figure 11-14 所示。这些消息已经通过我们的 Lambda 函数预处理,并包含星级评分。

Figure 11-14. 输入消息流。
如果我们选择实时分析标签,我们可以看到我们的三个应用程序内流的结果,包括平均星级评分、不同项数和异常分数,如 Figure 11-15 所示。

Figure 11-15. ANOMALY_SCORE_SQL_STREAM 的应用程序内流结果。
最后,让我们回顾一下我们的目标地点。如果我们导航到 CloudWatch Metrics,我们可以找到我们的自定义指标 AVGStarRating。我们可以将该指标添加到图表中,查看传入消息的实时情感趋势。我们的 Amazon SNS 主题还接收到了最新的异常分数,并通过电子邮件通知了应用团队。
使用 Apache Kafka、AWS Lambda 和 Amazon SageMaker 对产品评价进行分类
Amazon MSK 是用于 Apache Kafka 分布式流处理集群的全管理服务。我们可以创建一个 Lambda 函数,使用 Amazon MSK 流中的数据调用我们的 SageMaker 终端节点进行预测输入,并用预测输出丰富我们的 Kafka 流。这类似于我们的 Kinesis 流触发 Lambda 函数,Lambda 函数调用 SageMaker 终端节点,使用 Kinesis 流中的数据作为预测输入,并用预测输出丰富 Kinesis 流。图 11-16 展示了如何使用 Amazon MSK 接收和转换数据记录,我们可以描述步骤如下:

图 11-16. 使用 Amazon MSK 接收和转换数据记录。
-
我们接收实时输入数据并预测星级评分以推断客户情感。
-
Amazon MSK 允许我们通过 Lambda 函数转换数据记录。我们创建了一个 Lambda 函数,接收 Kafka 数据记录,并将评论消息发送到托管我们精调的基于 BERT 模型的 SageMaker 终端节点。
-
模型预测
star_rating,我们的 Lambda 函数将其添加到原始数据记录中;然后函数将新记录返回到我们的 Kafka 流。 -
然后,Kafka 流使用 Amazon S3 的 Kafka S3 沉降连接器将转换后的数据记录传送到 S3 存储桶。
要设置这一过程,我们需要创建 Amazon MSK 集群,为模型输入创建 Kafka 输入主题(输入流),并为模型预测创建 Kafka 输出主题(输出流)。接下来,我们需要使用 Amazon MSK Python API 的 create_event_source_mapping() 创建 Lambda 事件源映射,将我们的 Kafka 输入流映射到调用 SageMaker 终端节点的 Lambda 函数输入,并将预测写入 Kafka 输出流。
这是创建 Amazon MSK 集群和 Lambda 函数之间事件源映射的代码,通过 reviews 主题:
response = client.create_event_source_mapping(
EventSourceArn='<MSK_CLUSTER_ARN>',
FunctionName='<LAMBDA_FUNCTION_NAME>',
Enabled=True,
Topics=[
'reviews',
]
)
降低成本和提升性能
我们可以进一步优化流数据架构以降低成本和提升性能。例如,Lambda 函数符合计算节省计划的资格,该计划为一年或三年的计算使用承诺提供折扣。有几种方式可以通过 Kinesis 服务降低成本。一种最佳实践是将较小的数据记录聚合成一个 PUT 请求。我们还可以考虑使用 Kinesis Firehose 而不是 Data Streams 来节省费用。通过启用增强型分发(EFO),我们可以提高 Kinesis Data Streams 的性能。
聚合消息
Kinesis Data Streams 的成本基于预留的分片数量和我们的消息 PUT 负载,以 25 KB 单位计量。减少成本的最佳实践是将较小的消息聚合成一个 PUT 请求。我们可以使用 Kinesis Producer Library (KPL) 实现这种技术。KPL 将多个逻辑数据记录聚合和压缩为一个 Kinesis 数据记录,然后我们可以有效地将其放入流中。
考虑 Kinesis Firehose 与 Kinesis Data Streams
Kinesis Firehose 最适合需要零管理并且可以容忍一些数据处理延迟的用例。Firehose 提供准实时处理。它由 AWS 完全管理,并自动扩展以匹配吞吐需求。我们还可以批量和压缩数据,以最小化目的地的存储占用。使用 Firehose,我们只支付处理的数据。
Kinesis Data Streams 最适合需要为每个传入记录进行自定义处理的用例。它提供实时处理。我们必须自行管理 Kinesis Data Stream 的吞吐能力。Kinesis Data Streams 的成本基于处理的数据 和 预留分片的数量来满足我们的吞吐需求。
注意
如果我们选择使用 Lambda 函数来提供训练模型的服务,我们可以直接将 Kinesis Data Stream 连接到 Lambda 函数。Lambda 函数直接从 Kinesis Data Stream 中读取记录,并使用事件数据同步执行预测。
启用 Kinesis Data Streams 的增强扇出
没有 EFO,所有消费者都在竞争每个分片的读取吞吐量限制。这限制了每个流的消费者数量,并且需要扩展到额外的流以扩展到大量的消费者,如 图 11-17 所示。

图 11-17. 在没有 EFO 的情况下扩展消费者使用多个流。
使用 EFO,每个分片-消费者组合可以利用自己的专用的全读取吞吐量限制。图 11-18 显示了具有全读取吞吐量的专用分片-消费者管道。

图 11-18. 使用单流并使用专用的全吞吐量分片-消费者连接来扩展消费者的 EFO 缩放。
为了启用 EFO,我们使用 Kinesis Data Streams Python API 中的 register_stream_consumer() 和 subscribe_to_share() 函数。当注册我们的消费者到 EFO 时,Kinesis Data Streams 将使用高度并行、非阻塞的 HTTP/2 协议向消费者推送数据。这种推送机制导致更具响应性、低延迟和高性能的流式应用程序,可以扩展到大量的消费者。
概要
在本章中,我们展示了如何使用流数据进行流分析和机器学习。我们使用 Kinesis 流技术建立了端到端的流数据管道,用于捕获我们的产品评论,执行描述性分析,并应用预测性机器学习。我们对持续流动的产品评论进行了摘要统计,对流数据进行了异常检测,并使用基于 BERT 的 SageMaker 模型对数据进行了增强预测。我们在 CloudWatch Metrics 仪表盘中可视化了结果,发送电子邮件通知以警示团队,并使结果可供其他应用程序使用。
在第十二章中,我们将讨论如何在 AWS 上保护数据科学和机器学习项目。在介绍 AWS 共享责任模型并讨论常见的安全考虑因素后,我们将重点介绍 Amazon SageMaker 的安全最佳实践,涉及访问管理、计算和网络隔离、加密、治理以及可审计性。
第十二章:安全的 AWS 数据科学
在所有层面,从网络到应用程序,以及从数据摄入到模型部署的整个数据科学工作流程中,保持最小特权安全至关重要。在本章中,我们强调安全是 AWS 的首要任务,通常被称为“零工作”或“零优先级”。我们讨论常见的安全考虑,并提出在 AWS 上构建安全数据科学和机器学习项目的最佳实践。我们将描述旨在阻止事件发生的预防控制,以及快速检测潜在事件的检测控制。我们还确定响应和纠正控制,以帮助纠正安全违规行为。
在云中构建安全数据科学项目的最常见安全考虑涉及访问管理、计算和网络隔离以及加密领域。让我们首先讨论这些更一般的安全最佳实践和安全优先原则。然后,我们将应用这些实践和原则,通过网络级安全和应用程序安全来保护从笔记本到 S3 存储桶的数据科学环境。我们还讨论了合规性和监管的治理和审计最佳实践。
AWS 和客户之间的共享责任模型
AWS 通过实施共享责任模型,提供全球安全基础设施和基础计算、存储、网络和数据库服务,以及一系列安全服务,我们可以利用这些服务来保护我们在其上构建和运行的任何内容。
安全和合规性是 AWS 和客户之间的共同责任。AWS 确保云的安全,“而”客户负责云中的安全,如图 12-1 所示。

图 12-1. 安全是 AWS 和客户之间的共同责任。来源:Amazon。
AWS 保护运行 AWS 服务的 AWS 云基础设施。这包括从主机操作系统和虚拟化层到物理设施的物理安全的所有组件。AWS 安全的有效性定期由第三方审计师测试和验证。我们可以通过AWS Artifact访问按需的安全和合规性报告以及选择在线协议。
反过来,AWS 的客户负责确保云中的安全。客户责任的范围由具体的 AWS 服务确定。此外,客户可以从各种安全服务和功能中选择,在 AWS 云中构建安全和合规的应用程序。
应用 AWS 身份和访问管理
IAM 是一个帮助我们管理访问 AWS 资源的服务。IAM 控制谁可以访问(认证)环境以及经认证用户具有什么权限(授权)。我们可以使用 IAM 定义用户、用户组和角色。IAM 实施了主体、操作、资源和条件的概念。这定义了哪些主体可以在哪些资源上以及在哪些条件下执行哪些操作。
通过创建 IAM 策略并将其附加到 IAM 身份或 AWS 资源,我们控制对特定资源的访问。根据不同的工作角色或功能,我们可能希望向用户授予不同的权限。例如,一些开发人员可能只需启动笔记本进行临时数据探索。数据科学家很可能需要对数据存储、训练作业和实验拥有权限。数据工程师和机器学习工程师可能需要权限来构建可重复使用的数据和模型流水线。DevOps 团队需要访问模型部署和性能监视器。
Amazon SageMaker 利用 IAM 进行基于角色的访问控制。我们还可以映射来自 AWS 目录服务、企业用户目录或 Web 身份提供者(称为federated users)的任何现有用户/组/角色。
IAM 用户
我们可以为访问我们的 AWS 账户的个人创建单独的 IAM 用户。每个用户都将拥有唯一的安全凭证。我们还可以将 IAM 用户分配给具有定义访问权限的 IAM 组(即针对特定工作职能),并且 IAM 用户继承这些权限。
IAM 策略
访问权限通过 IAM 策略定义。仅授予执行给定任务所需的特定权限是一种标准的安全最佳实践。
IAM 用户角色
委托访问权限的更优方法是通过 IAM 角色。与仅与一个人相关联的 IAM 用户不同,角色可以由需要它的任何人扮演,并为角色会话的持续时间提供临时安全凭证。IAM 服务角色控制服务代表我们执行哪些操作。IAM 用户角色由个人用户扮演。
最佳实践是为不同的工作角色创建单独的 IAM 用户角色,例如DataScientistRole、MLEngineerRole、DataEngineeringRole、MLOpsEngineeringRole等。这样可以为模型开发生命周期中的不同角色提供精细化和明确的策略。
IAM 服务角色
IAM 服务角色由 AWS 服务扮演。最佳做法是为不同的服务创建单独的服务角色,并为每个服务的不同任务创建单独的角色。对于 Amazon SageMaker,我们可以按以下方式分离服务角色:
SageMakerNotebookExecutionRole
SageMaker 笔记本实例或 SageMaker Studio 应用程序所扮演的角色,定义了对 SageMaker 训练或模型托管服务的访问权限。
SageMakerProcessingRole
SageMaker 处理作业所假定的角色,定义了对数据输入/输出的 S3 存储桶的访问权限。
SageMakerTrainingRole
SageMaker 训练或调整作业所假定的角色,在模型训练/调整期间定义权限。
SageMakerModelRole
SageMaker 终端节点上模型托管推理容器所假定的角色,在模型推理期间定义权限。
图 12-2 展示了数据科学家 IAM 用户角色和讨论的各种 SageMaker IAM 服务角色。

图 12-2. Amazon SageMaker 的示例 IAM 用户和服务角色。
在通过 IAM 策略定义用户和服务权限时,我们应始终分配执行所需任务的最低权限。
为 IAM 角色指定条件键。
我们可以使用 IAM 条件键在策略中指定防护栏。例如,当主体调用服务 API 以创建资源时,请求信息将与主体 IAM 策略中定义的条件进行比较。如果条件语句通过,则 API 调用成功;如果条件语句失败,则请求将被拒绝。条件语句通常如下所示:
"Condition": {
"{condition-operator}": {
"{condition-key}": "{condition-value}"
}
}
下面是一个示例条件策略语句,拒绝上传未加密对象到 S3:
"Statement": [{
"Sid": "DenyUnencryptedObjectUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<bucket_name>/*",
"Condition": {
"StringNotEquals": {
"S3:x-amz-server-side-encryption": "aws:kms"
}
}
}]
SageMaker 支持全局条件键,并添加了一些特定于服务的条件键。全局条件上下文键以aws:前缀开头。SageMaker 支持以下全局条件键:
aws:RequestTag/${TagKey}
比较请求中传递的标签键值对与策略中指定的标签对。
aws:ResourceTag/${TagKey}
比较策略中指定的标签键值对与资源附加的键值对。
aws:SourceIp
比较请求者的 IP 地址与策略中指定的 IP 地址。
aws:SourceVpc
检查请求是否来自策略中指定的 Amazon Virtual Private Cloud(Amazon VPC)。
aws:SourceVpce
比较请求的 Amazon VPC 终端节点标识符与策略中指定的终端节点 ID。
aws:TagKeys
比较请求中的标签键与策略中指定的键。
SageMaker 添加以sagemaker:前缀开头的特定于服务的条件键,如下所示:
sagemaker:AcceleratorTypes
在创建或更新笔记本实例以及创建端点配置时,使用特定的 Amazon Elastic Inference 加速器。
sagemaker:DirectInternetAccess
控制笔记本实例的直接互联网访问。
sagemaker:FileSystemAccessMode
指定与输入数据通道(Amazon EFS 或 FSx)关联的目录的访问模式。
sagemaker:FileSystemDirectoryPath
指定与训练和超参数调整(HPT)请求中资源关联的文件系统目录路径。
sagemaker:FileSystemId
指定与训练和 HPT 请求中资源关联的文件系统 ID。
sagemaker:FileSystemType
指定与训练和 HPT 请求中资源关联的文件系统类型。
sagemaker:InstanceTypes
指定笔记本实例、训练作业、HPT 作业、批转换作业和端点配置的所有实例类型列表,用于托管实时推理。
sagemaker:InterContainerTrafficEncryption
在分布式训练和 HPT 作业中控制容器间流量加密。
sagemaker:MaxRuntimeInSeconds
通过指定训练、HPT 或编译作业可运行的最大时间长度(以秒为单位)来控制成本。
sagemaker:ModelArn
指定用于批转换作业和端点配置托管实时推理的模型关联的亚马逊资源名称(ARN)。
Sagemaker:NetworkIsolation
在创建训练、HPT 和推理作业时启用网络隔离。
sagemaker:OutputKmsKey
指定用于加密存储在亚马逊 S3 中的输出数据的 AWS KMS 密钥。
sagemaker:RequestTag/${TagKey}
比较请求中传递的标签键值对与策略中指定的标签对。
sagemaker:ResourceTag/${TagKey}
比较策略中指定的标签键值对与附加到资源的键值对。
sagemaker:RootAccess
在笔记本实例上控制根访问。
sagemaker:VolumeKmsKey
在创建笔记本实例、训练作业、HPT 作业、批转换作业和端点配置托管实时推理时,指定用于加密存储卷的 AWS KMS 密钥。
sagemaker:VPCSecurityGroupIds
列出与亚马逊 SageMaker 在亚马逊 VPC 子网中创建的弹性网络接口(ENI)关联的所有亚马逊 VPC 安全组 ID。
sagemaker:VPCSubnets
列出亚马逊 SageMaker 创建 ENI 以与其他资源(如亚马逊 S3)通信的所有亚马逊 VPC 子网。
启用多因素认证
SageMaker 还支持多因素认证 MFA。MFA 提供额外的安全性,因为它要求用户从 AWS 支持的 MFA 机制中提供第二个唯一的认证。支持的 MFA 机制包括虚拟 MFA 设备、U2F 安全密钥、硬件 MFA 设备或基于短信文本的 MFA。
作为最佳实践,我们应该为具有管理员访问权限的用户启用 MFA。我们还应将 MFA 添加为授权的第二步骤,以防止终止和删除资源等破坏性操作。在合规性和治理政策要求模型在删除之前存储一段时间时,这将非常有用。
使用 IAM 角色和策略实现最小权限访问控制
IAM 策略控制对 AWS 资源的访问。我们将 IAM 策略附加到 IAM 身份或 AWS 资源上,以定义身份或资源的权限。默认情况下,IAM 用户或角色没有任何权限。管理员必须授予 IAM 用户或角色权限。当用户属于一个组时,用户会继承该组的权限。
根据需要定义 IAM 策略池,然后将适用的策略分配给我们的 IAM 身份。图 12-3 展示了 IAM 策略与 IAM 用户/组/角色之间的典型多对多关系示例。

图 12-3. IAM 策略与 IAM 用户/角色之间的关系。
有不同类型的策略,包括基于身份和基于资源的策略。基于身份的策略是我们附加到 IAM 用户/组/角色的 JSON 策略文档。该策略定义了用户/组/角色的权限。
基于资源的 IAM 策略
基于资源的策略是我们附加到 AWS 资源(例如 S3 存储桶)的 JSON 策略文档。在基于资源的策略中,策略控制对资源的访问,即谁有权以何种条件访问 S3 存储桶。
请注意,基于资源的策略需要一个主体(允许对资源执行操作及其条件)。主体可以是 AWS 账户、IAM 用户、IAM 角色、联合用户或其他 AWS 服务。
这是一个基于资源的 IAM 策略示例。以下 S3 存储桶策略要求通过 MFA 访问存储桶。这通过aws:MultiFactorAuthAge条件键实现:
{
"Version": "2012-10-17",
"Id": "123",
"Statement": [
{
"Sid": "",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::<SAMPLE_BUCKET>/*",
"Condition": { "Null": { "aws:MultiFactorAuthAge": true }}
}
]
}
如果 Amazon S3 接收到带有 MFA 的存储桶访问请求,则aws:MultiFactorAuthAge携带一个数值,表示自创建临时凭证以来的秒数。如果该键为null,则表示凭证未通过 MFA 设备创建,访问请求将被拒绝。
基于身份的 IAM 策略
这是一个身份为数据科学家的 IAM 角色可以附加的基于身份的 IAM 策略示例。该策略授予角色对特定 S3 存储桶和 SageMaker Studio 环境的访问权限。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:Abort*",
"s3:DeleteObject",
"s3:Get*",
"s3:List*",
"s3:PutAccelerateConfiguration",
"s3:PutBucketCors",
"s3:PutBucketLogging",
"s3:PutBucketNotification",
"s3:PutBucketTagging",
"s3:PutObject",
"s3:Replicate*",
"s3:RestoreObject"
],
"Resource": [
"arn:aws:s3:::<BUCKET_NAME>/*"
]
},
{
"Effect": "Allow",
"Action": [
"sagemaker:CreatePresignedDomainUrl",
"sagemaker:DescribeDomain",
"sagemaker:ListDomains",
"sagemaker:DescribeUserProfile",
"sagemaker:ListUserProfiles",
"sagemaker:*App",
"sagemaker:ListApps"
],
"Resource": "*"
},
]
}
隔离计算和网络环境
我们可以通过创建独立的账户和每个账户内的独立 VPC 来隔离开发、测试和生产环境。这样做可以为我们部署 Amazon SageMaker、S3、CloudWatch、Redshift 和其他 AWS 资源提供所需的计算和网络隔离,以最小权限且无互联网的方式进行。如果没有计算和网络隔离,我们可能会有数据泄露到网络外并落入错误的手中的风险。此外,我们还面临外部攻击者查看计算节点上的数据或检查网络数据包的风险。
虚拟私有云
我们可以通过路由表指定允许与我们的 VPC 之间的网络通信。路由表包含定义从我们的虚拟私有云(VPC)子网或网关发送网络流量的规则(“路由”)。
VPC 包括一个或多个子网。VPC 是一个区域性服务,我们的 VPC 可以跨所选区域中的一个或所有可用区(AZ)创建一个或多个与 AZ 关联的子网。我们还可以在每个 AZ 中添加一个或多个子网。子网被定义为一系列 IP 地址范围。对于每个子网,我们可以进一步指定允许与我们的 Amazon EC2 实例(例如我们的 SageMaker 笔记本实例)之间的通信,通过安全组。VPC 还可以进行对等连接,以在账户内和账户之间形成安全连接。许多热门的 SaaS 产品使用 VPC 对等连接来连接主机和客户账户。
图 12-4 展示了 VPC 与相关组件(如网关、路由表、子网、安全组和实例)之间的关系。

图 12-4. VPC 与相关组件之间的关系。
VPC 终端节点和 PrivateLink
VPC 终端节点允许我们连接由 AWS PrivateLink 生态系统支持的服务,包括大多数 AWS 服务以及第三方 AWS 合作伙伴和市场提供的服务。服务的所有者是“服务提供者”。服务的消费者是“服务消费者”。
VPC 终端节点是放置在特定子网中的 ENI,通过私有 IP 地址访问。我们可以通过 VPC 安全组控制该 ENI 的网络通信。为了控制访问 VPC 终端节点后面的资源,我们指定 VPC 终端节点策略。
我们可以创建 VPC 终端节点,以建立我们的 VPC 与 AWS 资源(如 Amazon S3、SageMaker、Redshift、Athena 和 CloudWatch)之间的私有连接。如果没有 VPC 终端节点,我们将通过公共互联网安全地访问这些服务,而不是通过私有隧道,如图 12-5 所示。这就是为什么我们应该使用 VPC 终端节点来访问我们使用的服务,如图 12-6 所示。

图 12-5. 没有 VPC 终端节点,我们的私有 VPC 通过公共互联网访问 AWS 服务,以安全但公共的隧道。

图 12-6. 使用 VPC 终端节点,我们的私有 VPC 通过安全且私有的隧道与 AWS 服务通信。
幸运的是,包括 Amazon S3、SageMaker、Redshift、Athena 和 CloudWatch 在内的大多数服务都支持 VPC 终端节点。但是,在集成不提供 VPC 终端节点的第三方 AWS 合作伙伴或市场服务时,我们应该保持警惕。连接将是安全的,但除非使用 VPC 终端节点,否则不会是私有的。
使用 VPC 终端节点策略限制 Athena API
我们可以创建 VPC 终端点策略,仅允许特定资源的特定 API 调用。例如,让我们通过以下基于资源的策略将 Athena VPC 终端点限制为仅特定的工作组和一组 Athena API 调用:
{
"Statement": [{
"Principal": "*",
"Effect": "Allow",
"Action": [
"athena:StartQueryExecution",
"athena:RunQuery",
"athena:GetQueryExecution",
"athena:GetQueryResults",
"athena:CancelQueryExecution",
"athena:ListWorkGroups",
"athena:GetWorkGroup",
"athena:TagResource"
],
"Resource": [
"arn:aws:athena:<REGION>:<ACCOUNT_ID>:workgroup/<WORKGROUP>"
]
}]
}
安全 Amazon S3 数据访问
在当今世界上,保护数据的安全和安全性是头等大事。默认情况下,所有 Amazon S3 资源都是私有的,因此只有资源所有者,即创建它的 AWS 账户,才能访问该资源。资源所有者可以选择通过编写访问策略向其他人授予访问权限。
Amazon S3 与 AWS IAM 集成,用于安全性和访问管理。我们已经了解到,我们可以通过 IAM 用户/组/角色附加的 IAM 基于身份的策略来指定允许或拒绝对 AWS 资源(即 S3 存储桶)执行的操作。我们还可以提供基于资源的 IAM 策略,例如 S3 存储桶策略,它们通过特定主体在存储桶上定义权限。如果不保护数据访问,敏感数据可能会暴露给错误的受众。
通常情况下,如果我们需要为不仅仅是 S3 定义权限,或者我们有多个 S3 存储桶,每个都有不同的权限要求,我们会使用 IAM 基于身份的策略。我们可能希望将访问控制策略保留在 IAM 环境中。
如果我们需要一种简单的方式在我们的 S3 环境中授予跨账户访问权限而不使用 IAM 角色,或者如果我们达到了 IAM 策略的大小限制,我们将使用 S3 存储桶策略。我们可能希望将访问控制策略保留在 S3 环境中。
请注意,我们可以同时应用 IAM 基于身份的策略来定义对存储桶的权限,以及同一存储桶的 S3 存储桶策略。授权结果将是所有定义权限的联合的最小权限。
当我们为数据科学和机器学习项目创建 S3 存储桶时,我们应考虑创建符合我们数据分类和数据访问控制需求的单独存储桶。在必须遵守诸如支付卡行业等标准和控制的高度管制行业中,我们应将我们的 S3 存储桶与也符合相同标准和控制的单独账户对齐。在这种情况下,我们的敏感和原始数据集只能从符合要求的账户访问,而非敏感的、转换的和掩码的数据集可以从数据科学账户访问,例如。
作为最佳实践,我们还应考虑为不同的团队、特征存储、模型工件和自动化流水线创建单独的存储桶。此外,我们应启用 S3 存储桶版本控制,以保留对象的多个版本或从意外用户操作中恢复。通过版本化的 S3 存储桶,我们还可以启用 S3 对象锁定,以确保对象在写入后不会更改或删除,从而满足金融和医疗行业的合规法规要求。
在其他场景中,我们需要能够根据请求删除特定的用户数据。例如,我们可能需要遵守“被遗忘权”规则,这是许多数据保护法规(如《通用数据保护条例》)中的重要支柱之一。
根据所使用的数据存储,有多种实现此操作的方式。例如,使用存储在 S3 中的 Amazon Redshift Spectrum,我们可以复制外部表格,将需要删除的数据复制到临时的 Amazon Redshift 表格中。然后删除受影响的记录,并将临时表格重新写入 S3,覆盖键名。最后,删除临时的 Amazon Redshift 表格。如果需要扩展和自动化数据删除过程,我们可以利用 Apache Spark 将数据从数据源加载到临时表格中,删除待遗忘的数据,并将数据重新写入原始数据存储。
在模型已经使用待遗忘数据进行训练和部署的情况下,我们需要追踪数据的血统,以找到所有使用该数据进行训练的模型。在移除数据后,根据数据保护法规的具体细节,我们可能需要重新训练和部署模型,以真正地“遗忘”用户并删除他们的数据。
需要具有 S3 存储桶策略的 VPC 端点
在我们讨论 IAM 角色和 VPC 端点的基础上,我们可以通过使用 S3 存储桶策略要求 VPC 端点,来限制对特定 S3 存储桶的访问,如下所示:
{
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<s3-bucket-name>/*",
"arn:aws:s3:::<s3-bucket-name>"
],
"Condition": {
"StringNotEquals": {
"aws:sourceVpce": "<S3_VPC_ENDPOINT_ID>"
}
}
}
]
}
使用 VPC 端点策略限制 S3 存储桶的 S3 APIs
我们还可以为 S3 的 VPC 端点附加策略,并且只允许在特定 S3 存储桶上执行 S3 APIs 的子集,如下所示:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<S3_BUCKET_NAME>",
"arn:aws:s3:::<S3_BUCKET_NAME>/*"
]
}
]
}
使用 S3 存储桶策略限制特定 VPC 的 S3 存储桶访问
而不是完全锁定 S3 存储桶,我们可以如下限制对指定 VPC 的访问:
{
"Version": "2008-10-17",
"Statement": [{
"Effect": "Deny",
"Principal": "*",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<BUCKET_NAME>"
],
"Condition": {
"StringNotEquals": {
"aws:sourceVpc": <vpc_id>
}
}
}]
}
通过附加到 S3 存储桶的此 S3 存储桶策略,来自指定源 VPC 以外的所有访问请求都将被拒绝。
我们可以验证访问被拒绝的方式如下:
!aws s3 ls s3://<BUCKET_NAME>
我们将收到类似于以下错误消息:
An error occurred (AccessDenied) when calling the ListObjectsV2 operation
使用 S3 存储桶策略限制 S3 APIs
我们可以通过指定以下 S3 存储桶策略,限制对特定存储桶的 ListBucket API 操作:
{
'Version': '2012-10-17',
'Statement': [{
'Sid': '',
'Effect': 'Deny',
'Principal': '*',
'Action': [
's3:ListBucket'
],
'Resource': [
'arn:aws:s3:::<BUCKET_NAME>'
]
}]
}
我们可以验证访问被拒绝的方式如下:
!aws s3 ls s3://<BUCKET_NAME>
我们将收到类似于以下错误消息:
An error occurred (AccessDenied) when calling the ListObjectsV2 operation
使用 IAM 角色策略限制 S3 数据访问
下面的示例展示了如何使用基于身份的 IAM 策略限制对我们的 S3 存储桶的访问:
{
'Version': '2012-10-17',
'Statement': [{
'Sid': '',
'Effect': 'Deny',
'Action': [
's3:ListBucket'
],
'Resource': [
'arn:aws:s3:::<BUCKET_NAME>'
]
}]
}
我们可以按如下方式验证访问是否被拒绝:
!aws s3 ls s3://<BUCKET_NAME>
我们将收到类似于这样的错误消息:
An error occurred (AccessDenied) when calling the ListObjectsV2 operation
使用 IAM 角色策略限制 S3 存储桶访问特定 VPC
我们可以按以下方式限制对 S3 存储桶的访问:
{
'Version': '2012-10-17',
'Statement': [{
'Sid': '',
'Effect': 'Deny',
'Action': [
's3:ListBucket'
],
'Resource': [
'arn:aws:s3:::<BUCKET_NAME>'
],
'Condition': {
'StringNotEquals': {
'aws:sourceVpc': <VPC_ID>
}
}
}]
}
将此 IAM 策略附加到角色后,使用此角色发起的所有ListBucket请求必须来自于 VPC,否则将被拒绝。
我们可以按如下方式验证访问是否被拒绝:
!aws s3 ls s3://<BUCKET_NAME>
我们将收到类似于这样的错误消息:
An error occurred (AccessDenied) when calling the ListObjectsV2 operation
使用 S3 访问点限制 S3 数据访问
Amazon S3 访问点简化了对于像数据湖这样的大型共享存储桶的访问控制。传统上,我们通过唯一的存储桶主机名访问我们的 S3 存储桶,并通过 IAM 策略和单一存储桶策略定义访问控制。可以想象,对于需要访问的共享数据集以及越来越多的用户、团队和应用程序,这可能很快变得复杂,难以维护。
Amazon S3 访问点通过提供自定义路径进入存储桶来简化数据访问管理,每个访问点具有唯一的主机名和 IAM 访问策略,用于强制执行通过访问点发出的请求的特定权限和网络控制。这对于管理对共享数据集的访问特别有用。
我们还可以要求所有访问点仅限于特定的 VPC,通过在本质上将我们的数据防火墙化到我们的私有网络内提供额外的安全层级。
假设我们有名为data-science-on-aws的样本 S3 存储桶,其中包含名为feature-store和data-warehouse的前缀(子文件夹)。我们的数据科学团队需要对特征存储数据具有读写访问权限,而我们的商业智能团队需要对存储在该存储桶中的数据仓库数据具有读取权限。
图 12-7 展示了在不使用 S3 访问点的情况下该场景的外观。

图 12-7. 使用唯一存储桶主机名访问 Amazon S3 中的对象,无需 S3 访问点。
单个 S3 存储桶策略可能看起来像这样:
"Sid":”PrefixBasedAccessDataScience",
"Effect":"Allow",
"Principal":{"AWS":”arn:aws:iam::123456789012:group/ds},
"Action":["s3:GetObject","s3:PutObject"],
"Resource":"arn:aws:s3:::data-science-on-aws/feature-store/*"
...
"Sid":”TagBasedAccessBusinessIntelligence",
"Effect":"Allow",
"Principal":{"AWS":”arn:aws:iam::123456789012:group/bi},
"Action":["s3:GetObject"],
"Resource":"arn:aws:s3:::data-science-on-aws/data-warehouse/*”
...
现在让我们看看如何通过使用 S3 访问点来简化这个过程。以下示例命令展示了如何通过 AWS CLI 命令在我们的样本存储桶data-science-on-aws上创建名为ap1-ds和ap2-bi的访问点:
aws s3control create-access-point \
--name ap1-ds \
--account-id 123456789012 \
--bucket data-science-on-aws
aws s3control create-access-point \
--name ap2-bi \
--account-id 123456789012 \
--bucket data-science-on-aws
在访问点策略中,我们为我们的数据科学家团队(“ds”)(账户 123456789012 中)的 IAM 组授予通过访问点ap1-ds对前缀为feature-store/的对象执行GET和PUT操作的权限,并为我们的商业智能团队(“bi”)授予通过访问点ap2-bi对前缀为data-warehouse/的对象执行GET操作的权限:
{
"Version":"2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:group/ds"
},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource":
"arn:aws:s3:us-east-1:123456789012:accesspoint/ap1-ds/
object/feature-store/*"
}]
}
{
"Version":"2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:group/bi"
},
"Action": ["s3:GetObject"],
"Resource":
"arn:aws:s3:us-east-1:123456789012:accesspoint/ap2-bi/
object/data-warehouse/*"
}]
}
图 12-8 展示了我们如何通过 S3 访问点管理对 S3 对象的访问。

图 12-8. 使用 S3 访问点访问 Amazon S3 中的对象。
如果我们位于 us-east-1 地区并具有访问权限,则通过 S3 访问点对该存储桶中的对象进行 AWS CLI 请求会是这样的:
aws s3api get-object \
--key sample_us.tsv \
--bucket arn:aws:s3:us-east-1:123456789012:accesspoint/
ap1-ds feature-store/raw/sample_us.tsv
我们还可以通过 AWS 管理控制台、AWS SDK 或 S3 REST APIs 访问 Amazon S3 存储桶中的对象。应用程序或用户要通过访问点访问对象,必须同时允许请求的访问点和底层存储桶。
静态加密
如果没有加密,数据可以被任何获取访问权的人阅读。所有数据都应加密,作为额外的保护层,以防数据泄漏到恶意攻击者(无论是内部还是外部组织)手中。
SageMaker 本地集成了 AWS Key Management Service(AWS KMS),使用对称或非对称客户主密钥(CMK)对我们的数据进行静态加密。CMK 是 AWS KMS 的主要资源,是主密钥的逻辑表示,包括 ID、描述、创建日期和密钥状态等元数据。
有三种类型的 CMK:客户管理、AWS 托管和 AWS 拥有。它们根据谁管理密钥、谁可以访问密钥元数据、密钥多久自动轮换以及密钥如何跨账户作用域而不同。总结如下,详见表 12-1。
表 12-1. 不同类型的 CMK
| CMK 类型 | 可以查看 CMK 元数据 | 可以管理 CMK | 仅用于我们的 AWS 账户 | 自动轮换 |
|---|---|---|---|---|
| 客户管理的 CMK | 是 | 是 | 是 | 每 365 天(1 年)可选 |
| AWS 托管的 CMK | 是 | 否 | 是 | 每 1095 天(3 年)必需 |
| AWS 托管的 CMK | 否 | 否 | 否 | 各异 |
我们应该为所有存储卷启用默认加密,包括 Amazon S3、Amazon EC2 实例磁盘、网络附加 Amazon Elastic Block Store(Amazon EBS)和分布式 Amazon EFS。此外,建议我们使用拒绝策略阻止向这些存储卷上传未加密数据。我们应该加密所有数据工件,包括笔记本、转换特征、训练模型、批量预测和端点预测。此外,我们不应忘记加密存储在 Amazon ECR 中的 Docker 镜像,以及在数据处理和模型训练期间使用的临时“scratch”本地存储和 Amazon EBS 卷。
创建 AWS KMS 密钥
我们首先通过 AWS KMS 创建一个密钥,用于加密我们 SageMaker 示例中使用的存储卷:
kms = boto3.Session().client(service_name='kms', region_name=region)
key_response = kms.create_key()
key_id = key_response['KeyMetadata']['KeyId']
在培训期间加密 Amazon EBS 卷
下面的示例展示了如何在 SageMaker 训练作业中使用 AWS KMS 密钥来加密 SageMaker 实例的 Amazon EBS 卷:
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
role=role,
instance_count=1,
instance_type='ml.p4d.24xlarge',
framework_version='<TENSORFLOW_VERSION>,
volume_kms_key=key_id)
estimator.fit(inputs)
培训后在 S3 中加密上传的模型
下面的示例展示了如何在 SageMaker 训练作业期间使用 AWS KMS 密钥对生成的输出资产(包括我们在 S3 中的训练模型)进行加密:
estimator = TensorFlow(
entry_point='tf_bert_reviews.py',
source_dir='src',
role=role,
instance_count=1,
instance_type='ml.p4d.24xlarge',
framework_version='<TENSORFLOW_VERSION>,
output_kms_key=key_id<KMS_KEY_ID>)
estimator.fit(inputs)
使用 AWS KMS 存储加密密钥
AWS KMS 是一个托管服务,可以轻松创建和控制用于加密操作的密钥。在 Amazon S3 中实施数据静态加密时,可以使用两种方法使用 AWS KMS。我们可以使用服务器端加密来使用主密钥保护我们的数据,或者我们可以使用 AWS KMS CMK 与 Amazon S3 加密客户端来保护客户端端的数据。
如果选择服务器端加密,我们可以在以下选项之间选择:
SSE-S3
要求 Amazon S3 管理数据和主加密密钥
SSE-C
要求我们管理加密密钥
SSE-KMS
要求 AWS 管理数据密钥,但我们管理 AWS KMS 中的 CMK
强制上传到 Amazon S3 对象的 S3 加密
要求特定 Amazon S3 存储桶中所有对象都执行服务器端加密(强制数据静态加密),我们可以使用存储桶策略。例如,以下存储桶策略如果请求不包括 x-amz-server-side-encryption 头部来请求使用 SSE-KMS 进行服务器端加密,则拒绝给所有人上传对象 (s3:PutObject) 权限:
{
"Version": "2012-10-17",
"Id": "DenyIncorrectEncryptionalgorithmAES256",
"Statement": [
{
"Sid": "DenyUnencryptedObjectUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<bucket_name>/*",
"Condition": {
"StringNotEquals": {
"S3:x-amz-server-side-encryption": "aws:kms"
}
}
}
]
}
在这种情况下,S3 将在存储每个对象之前进行加密,并在检索每个对象之后进行解密。这种加密和解密过程在幕后无缝完成。当我们上传对象时,可以使用头部 x-amz-server-side-encryption-aws-kms-key-id 指定 AWS KMS CMK。如果请求中没有包含该头部,Amazon S3 将假定使用 AWS 托管的 CMK。
强制 SageMaker Jobs 的数据静态加密
下面的 IAM 策略将不允许创建没有 Amazon EBS 卷加密的 SageMaker Job:
{
"Sid": "SageMakerJobVolumeEncryption",
"Effect": "Deny",
"Action": [
"sagemaker:CreateTrainingJob"
],
"Resource": "*",
"Condition": {
"Null": {
"sagemaker:VolumeKmsKey": "true"
}
}
}
强制 SageMaker Notebooks 的数据静态加密
下面的 IAM 策略将不允许创建没有 Amazon EBS 卷加密的 SageMaker Notebook 实例:
{
"Sid": "SageMakerNotebookVolumeEncryption",
"Effect": "Deny",
"Action": [
"sagemaker:CreateNotebookInstance",
"sagemaker:UpdateNotebookInstance"
],
"Resource": "*",
"Condition": {
"Null": {
"sagemaker:VolumeKmsKey": "true"
}
}
}
强制 SageMaker Studio 的数据静态加密
下面的 IAM 策略将不允许创建没有 Amazon EFS 卷加密的 SageMaker Studio 域:
{
"Sid": "SageMakerStudioVolumeEncryption",
"Effect": "Deny",
"Action": [
"sagemaker:CreateDomain"
],
"Resource": "*",
"Condition": {
"Null": {
"sagemaker:HomeEfsFileSystemKmsKey": "true"
}
}
}
传输中的加密
默认情况下,所有公共 AWS API 调用都通过安全的传输层安全性(TLS)加密隧道进行。这意味着例如 SageMaker 和 S3 之间的所有网络流量都默认情况下在传输过程中进行了加密。如果没有这种加密,数据可能在网络中以明文形式传输,可能会被攻击者查看。请记住,攻击可能来自组织内部和外部。
对于传输数据,SageMaker 支持分布式训练和 HPT 作业的容器间加密。通常传递给训练实例的信息包括模型权重和其他元数据,而不是训练数据本身,但启用此设置可以帮助满足监管要求并增加数据保护。
使用 KMS 实现传输中的后量子 TLS 加密
AWS KMS 支持量子抗性或“后量子”选项,用于交换 TLS 加密密钥。虽然经典 TLS 密码套件实现足以防止对今天的密钥交换机制进行暴力攻击,但在大规模量子计算机可用之后,它将不再足够强大。
AWS KMS 提供了许多后量子 TLS 加密的密钥交换算法选项,包括Kyber,Bit Flipping Key Encapsulation和Supersingular Isogeny Key Encapsulation。图 12-9 显示了经典 TLS 1.2 和后量子 TLS 1.2 之间的差异。

图 12-9. 经典与后量子 TLS 1.2。
这些后量子密钥交换机制将影响性能,因为它们需要额外的计算开销。因此,在部署到生产环境之前,我们应该始终彻底测试这些算法的性能。
加密训练集群容器之间的流量
对于分布式模型训练作业,我们可以选择在分布式训练集群的容器之间加密内部网络流量。虽然容器间加密可能会增加训练时间,但我们应该启用此设置以防止敏感数据泄露。
以下是如何使用encrypt_inter_container_traffic=True标志加密容器间通信的示例:
from sagemaker.tensorflow import TensorFlow
estimator = TensorFlow(entry_point='tf_bert_reviews.py',
source_dir='src',
role=role,
instance_count=2,
instance_type='ml.p4d.24xlarge',
framework_version='<TENSORFLOW_VERSION>',
encrypt_inter_container_traffic=True)
强制 SageMaker 作业间容器加密
除非启用容器间流量加密,否则以下策略将不允许 SageMaker 训练作业运行:
{
"Sid": "SageMakerInterContainerTrafficEncryption",
"Effect": "Deny",
"Action": [
"sagemaker:CreateTrainingJob"
],
"Resource": "*",
"Condition": {
"Bool": {
"sagemaker:InterContainerTrafficEncryption": "false"
}
}
}
保护 SageMaker 笔记本实例
通过在 VPC 内运行我们的 SageMaker 笔记本实例,我们创建了所需的网络和计算隔离,以防止外部组织访问我们的敏感笔记本。请记住,笔记本与典型软件源文件不同,通常包含描述数据集的可视化和摘要统计等输出。这些输出与数据本身一样敏感。
注意
如果我们希望为我们的数据科学团队实施集中、治理和自助访问 SageMaker 笔记本实例,我们可以使用 AWS 服务目录将 SageMaker 笔记本实例定义为产品,并预配置所有必需的安全策略。
创建 SageMaker 笔记本实例时,可以通过指定子网 ID 和安全组将其连接到我们的私有 VPC,如下所示:
sm.create_notebook_instance(
NotebookInstanceName='dsoaws',
InstanceType='ml.t3.medium',
SubnetId='<SUBNET_ID>',
SecurityGroupIds=[
'<SECURITY_GROUP_IDS>',
],
RoleArn='arn:aws:iam::<ACCOUNT_ID>:role/service-role/<ROLE_NAME>',
KmsKeyId='<KEY_ID>',
DirectInternetAccess='Disabled',
VolumeSizeInGB=10,
RootAccess='Disabled'
)
禁止在 SageMaker 笔记本内部访问根目录
注意,示例还指定了 SageMaker 执行 IAM 角色和用于加密附加卷的 KMS 密钥,禁用笔记本的直接互联网访问,并禁用用户的根访问。如果我们希望限制用户创建启用了根访问权限的笔记本实例,我们可以将以下 IAM 策略附加到 SageMaker 执行角色。
{
"Sid": "DenyRootAccess",
"Effect": "Deny",
"Action": [
"sagemaker:CreateNotebookInstance",
"sagemaker:UpdateNotebookInstance"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"sagemaker:RootAccess": [
"Enabled"
]
}
}
}
禁用 SageMaker 笔记本的互联网访问
另一个最佳实践是禁止访问/从具有对我们数据访问权限的 VPC 的互联网。 我们可以通过单独的共享服务 VPC 提供任何外部项目依赖项。 例如,这个 VPC 可以托管一个带有我们批准的 Python 包的 PyPI 镜像。
以下示例 IAM 策略将不允许启用直接互联网访问的 SageMaker 笔记本实例创建:
{
"Sid": "PreventDirectInternet",
"Effect": "Deny",
"Action": "sagemaker:CreateNotebookInstance",
"Resource": "*",
"Condition": {
"StringEquals": {
"sagemaker:DirectInternetAccess": [
"Enabled"
]
}
}
}
保护 SageMaker Studio
通过将 SageMaker Studio 锁定到我们的 VPC,我们可以防止外部攻击者访问包含敏感数据的笔记本,例如描述数据集的可视化和摘要统计信息。 SageMaker Studio 还支持 IAM 和单点登录(SSO)身份验证和授权机制。 使用 IAM 和 SSO,我们可以根据最小权限安全原则限制 Studio 的访问权限,仅限少数个人或组。 如果没有 IAM 和 SSO 身份验证和授权,恶意攻击者可能会访问我们的笔记本和其他 Studio 资产。
要求 SageMaker Studio 的 VPC
通过将参数AppNetworkAccessType设置为VpcOnly,我们可以要求从我们的 VPC 访问 SageMaker Studio。 这种部署设置将创建一个 ENI,通过该 ENI,我们的 VPC 中的资源可以使用 VPC 终端节点与 SageMaker Studio 服务通信。 我们可以通过向 VPC 终端节点创建的 ENI 应用安全组来进一步控制通信。
以下示例 IAM 策略将不允许在私有 VPC 之外创建 SageMaker Studio 域:
{
"Sid": "PreventDirectInternetforStudio",
"Effect": "Allow",
"Action": "sagemaker:CreateDomain",
"Resource": "*",
"Condition": {
"StringEquals": {
"sagemaker:AppNetworkAccessType": [
"VpcOnly"
]
}
}
}
使用VpcOnly模式,所有 SageMaker Studio 流量都通过指定的 VPC 和子网路由。 默认设置为PublicInternetOnly,将所有非 Amazon EFS 流量通过具有启用互联网访问的 AWS 托管服务 VPC 发送。
我们在域创建期间为 SageMaker Studio 定义 IAM 角色。 我们可以通过AppNetworkAccessType=VpcOnly指定私有 VPC 进行网络通信,并提供相关的子网 ID 和 VPC ID。 我们还可以传递一个 KMS 密钥来加密由 SageMaker Studio 设置的 Amazon EFS 卷。
下面是如何以编程方式创建 SageMaker Studio 域、用户配置文件和具有上述设置的 SageMaker Studio 应用程序的示例:
sagemaker.create_domain(DomainName='default',
AuthMode='IAM',
DefaultUserSettings={
'ExecutionRole': <ROLE_ARN>,
'SecurityGroups': <SECURITY_GROUP_IDS>,
},
SubnetIds='<SUBNET_IDS>',
VpcId='<VPC_ID>',
AppNetworkAccessType='VpcOnly',
KmsKeyId='<EFS_KMS_KEY_ID>')
sagemaker.create_user_profile(DomainId=domain_id,
UserProfileName='default')
sagemaker.create_app(DomainId=domain_id,
UserProfileName='default',
AppType='JupyterServer',
AppName='default')
SageMaker Studio 身份验证
SageMaker Studio 支持两种身份验证用户的模式:SSO 和 IAM。 在 SSO 模式下,我们将联合身份池映射到用户。 在 IAM 模式下,SageMaker Studio 与 AWS IAM 完全集成,并遵循我们的 IAM 用户、角色和策略配置。 我们在运行在 SageMaker 服务账户和平台 VPC 中的 SageMaker Studio 中进行身份验证,该 VPC 通过私有隧道连接到我们的私有账户和 VPC,如图 12-10 所示。

图 12-10. SageMaker Studio 跨用户 VPC 和 SageMaker 平台 VPC 的高级网络架构。
保护 SageMaker 作业和模型
我们还可以使用服务级 IAM 角色为 SageMaker 作业定义权限,以限制 IAM 用户/组/角色的权限,类似于我们讨论过的防止数据访问到我们的 S3 存储桶的防护栏。我们可以限制 SageMaker 作业只能访问特定资源,例如 S3 存储桶或其他数据源。此外,我们可以要求 SageMaker 作业在私有 VPC 中运行,以提供所需的计算和网络隔离,以防止外部攻击者访问存储在计算节点上的数据或在网络上传输。
要求为 SageMaker 作业配置一个 VPC
在 SageMaker 的上下文中,我们可以指定需要 SageMaker 在没有 VPC 的情况下创建资源的 IAM 策略。以下是这样一个 IAM 策略的示例:
{
"Sid": "SageMakerJobsVPC",
"Effect": "Deny",
"Action": [
"sagemaker:CreateTrainingJob"
],
"Resource": "*",
"Condition": {
"Null": {
"sagemaker:VpcSubnets": "true",
"sagemaker:VpcSecurityGroupIds": "true"
}
}
}
这是如何通过为我们的 SageMaker 训练作业提供 subnets 和 security_group_ids 来将 SageMaker 训练作业连接到我们的私有 VPC 的示例:
from sagemaker.tensorflow import TensorFlow
estimator = TensorFlow(
entry_point='tf_bert_reviews.py',
source_dir='src',
role=role,
instance_count=1,
instance_type='ml.p4d.24xlarge',
py_version='<PYTHON_VERSION>',
framework_version='<TENSORFLOW_VERSION>',
hyperparameters={...},
subnets=[
"<SUBNET_ID>"
],
security_group_ids=[
"<SECURITY_GROUP_ID>"
]
)
使用这种配置,SageMaker 将创建 ENI 来连接训练容器到我们指定的 VPC。
我们可以通过类似于这样的 IAM 策略强制执行特定的 VPC 配置:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SageMakerJobsVPC",
"Effect": "Deny",
"Action": [
"sagemaker:CreateTrainingJob"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"sagemaker:VpcSecurityGroupIds":
"<SECURITY_GROUP_IDS>",
"sagemaker:VpcSubnets": [
"<SUBNET_ID>",
"<SUBNET_ID>"
]
}
}
}
]
}
在我们可以在 VPC 中运行训练作业之前,我们需要确保 VPC 通过设置在我们的 VPC 内部的 S3 VPC 终端节点(或 NAT 设备)访问 S3。这包括配置子网路由表、安全组和网络访问控制列表(ACL)。如果我们没有这样做,我们将看到类似于这样的错误:
UnexpectedStatusException: Error for Training job: Failed. Reason: ClientError:
Data download failed:Please ensure that the subnet's route table has a route to
an S3 VPC endpoint or a NAT device, both the security groups and the subnet's
network ACL allow outbound traffic to S3.
使用这个示例 IAM 策略,我们明确拒绝了模型创建以及创建 SageMaker 自动驾驶作业、训练作业、处理作业或超参数调整作业,除非使用指定的 VPC 子网 ID 和安全组。
让我们在不指定匹配 VPC 参数的情况下运行一个训练作业:
from sagemaker.tensorflow import TensorFlow
estimator = TensorFlow(
entry_point='tf_bert_reviews.py',
source_dir='src',
role=role,
instance_count=1,
instance_type='ml.p4d.24xlarge',
py_version='<PYTHON_VERSION>',
framework_version='<TENSORFLOW_VERSION>',
hyperparameters={...},
)
estimator.fit(inputs={...})
我们将看到类似于以下的客户端错误:
ClientError: An error occurred (AccessDeniedException) when calling the
CreateTrainingJob operation: User: arn:aws:sts::<ACCOUNT_ID>:assumed-role/<ROLE>/
SageMaker is not authorized to perform: sagemaker:CreateTrainingJob on resource:
arn:aws:sagemaker:<REGION>:<ACCOUNT_ID>:training-job/<JOB>
with an explicit deny
图 12-11 显示了 SageMaker 训练作业在 UTC 时间 17:56 开始和停止的情况。

图 12-11. SageMaker 训练作业因不符合策略而停止。
需要为 SageMaker 作业进行网络隔离
如果我们需要完全隔离我们的模型训练作业,我们可以为执行模型训练的容器启用网络隔离。在这种情况下,容器将被限制从所有出站网络通信(包括对 Amazon S3 的 API 调用),并且只能与本地 Amazon EBS 卷通信。训练作业所需的所有输入和输出数据都必须存储在容器的本地 Amazon EBS 卷上,这些卷应该是加密的。
此外,当启用网络隔离时,容器运行时环境不会提供任何 AWS 凭证。如果我们运行分布式训练作业,则网络通信仅限于训练集群的容器,这也可以进行加密。
在网络隔离模式下运行 SageMaker 作业可以有效防止数据外泄风险。然而,并非必须在我们的 VPC 中限制对特定 AWS 资源(如 S3)的流量才需要网络隔离。为此,我们使用 VPC 子网和安全组配置。
如果网络隔离被禁用,以下示例策略将拒绝 SageMaker 作业的创建:
{
"Sid": "SageMakerNetworkIsolation",
"Effect": "Deny",
"Action": [
"sagemaker:CreateTrainingJob"
],
"Resource": "*",
"Condition": {
"Bool": {
"sagemaker:NetworkIsolation": "false"
}
}
}
如果我们试图访问容器外的任何资源,将会看到以下 NoCredentialsError:
botocore.exceptions.NoCredentialsError: Unable to locate credentials
虽然由于网络隔离,训练容器无法直接访问 S3,但 SageMaker 运行时仍然可以将数据在 S3 和运行训练作业容器的底层 SageMaker 实例之间复制。在将 S3 数据复制到训练实例后,容器仍可通过 SageMaker 挂载的 /opt/ml/input/ 目录访问训练数据。类似地,训练好的模型将被放置在 /opt/ml/output/,SageMaker 将其复制到 S3,如图 12-12 所示。

图 12-12. 网络隔离不会阻止 SageMaker 将数据从 S3 挂载到训练容器中。
我们可以通过额外的 IAM 或 S3 存储桶策略进一步限制 SageMaker 运行时对 S3 的访问。此外,网络隔离模式可以与 VPC 结合使用,此时数据的下载/上传将通过 VPC 子网路由。模型训练容器将继续被隔离,无法访问我们的 VPC 或互联网上的资源。
保护 AWS Lake Formation
AWS Lake Formation 为特定主体的数据行和列提供细粒度访问控制。通过 Lake Formation,我们可以在表、行和列上指定权限,而不是在 S3 存储桶、前缀和对象上指定。通过 Lake Formation 的“数据权限”用户界面,我们可以在单个视图中分析授予用户的所有策略。
Lake Formation 实时监控和记录所有数据访问事件。我们可以订阅以接收访问敏感数据时的警报。除了查看实时仪表板和警报外,我们还可以导出数据访问日志进行离线审计和报告。
使用 AWS Secrets Manager 保护数据库凭据
我们绝不能在脚本、应用程序或笔记本中使用硬编码的明文凭据。通过暴露用户名、密码和 API 密钥,我们会造成安全漏洞,从而导致恶意攻击和数据泄露。相反,我们应该从 AWS Secrets Manager 存储和检索我们的凭据。
Secrets Manager 使用 AWS KMS 加密秘密并利用 AWS IAM 策略控制对存储凭据的访问。除了手动轮换凭据外,我们还可以使用 Secrets Manager 定期按计划轮换凭据。许多 AWS 数据库已与 Secrets Manager 集成,包括 Amazon RDS、Aurora 和 Redshift。对于这些数据库,在执行查询时,我们指定唯一的 ARN。然后 AWS 在后台检索和验证凭据,而不会暴露任何用户名、密码或 API 密钥。
治理
我们讨论了几种机制,以实施和执行符合我们组织安全政策的配置。示例显示了在一个 AWS 账户内特定于 IAM 用户、角色和策略的控制措施。如果我们希望在 AWS 账户和区域间实施安全和治理,我们可以利用 AWS 组织、AWS Config 和多账户环境。
使用 AWS 组织,我们可以定义服务控制策略(SCPs),这些策略为我们在组织中所有账户上的最大可用权限提供了集中控制。如果我们需要设置新的安全的多账户 AWS 环境,我们可以使用 AWS 控制塔。
我们可以使用 AWS Config 根据最佳实践和自定义策略评估我们账户中 AWS 资源的配置。AWS Config 是一个检测控制的例子,用于在配置不符合规范时向我们发出警报。
然后,我们可以应用多账户设置来改善我们数据科学项目的治理和安全性,例如,通过在数据科学、演示和生产环境之间分离模型部署工作流程。
使用 AWS 控制塔保护多账户 AWS 环境的安全
AWS 控制塔使我们能够在几次点击内设置和管理新的安全的多账户 AWS 环境。使用 AWS 控制塔,我们可以通过多账户结构、身份访问管理和账户配置工作流的最佳实践蓝图自动设置我们的 AWS 环境。例如,我们可能希望禁止所有 S3 存储桶的公共访问、未加密的存储桶或者禁用版本控制的存储桶。
使用 AWS 组织管理账户
AWS 组织是一个账户管理服务,允许我们将多个 AWS 账户合并到一个组织中。然后,我们可以集中管理映射到该组织的所有账户。
如果我们需要分组特定的 AWS 账户,我们可以创建组织单位(OUs),添加相关账户,并为每个 OU 附加不同的策略。图 12-13 展示了如何将个别账户分组到 OUs 并附加策略。

图 12-13. AWS 组织与 OUs、成员账户和策略。
使用 SCPs 强制执行账户级权限
AWS 组织允许我们指定 SCP 以定义组织中成员账户的权限。我们可以利用 SCP 来跨 AWS 账户实施和执行讨论过的安全控制。
我们可以利用 SCP 限制在每个映射的 AWS 成员账户中的用户和角色对 AWS 服务、资源和个别 API 操作的访问。请注意,这些限制甚至会优先于成员账户的管理员。换句话说,SCP 为我们提供了对组织中所有账户的最大可用权限的集中控制。
我们可以利用 SCP 作为一种防护栏或者定义成员账户管理员可以授予个别账户 IAM 用户和角色的操作限制。我们仍需在成员账户中创建 IAM 策略并附加这些策略到 IAM 用户/角色上。最终的权限将取决于 SCP 允许的内容以及成员账户的 IAM 策略的交集。
基于 IAM 的条件键,以下示例定义了一个 SCP,强制在映射的 AWS 成员账户中使用特定的 KMS 密钥对所有 SageMaker 训练任务进行加密:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EncryptSageMakerTraining",
"Effect": "Deny",
"Action": "sagemaker:CreateTrainingJob",
"Resource": [
"*"
],
"Condition": {
"ArnNotEquals": {
"sagemaker:OutputKmsKey": [
arn:aws:kms:<REGION>:<ACCOUNT_ID>:key/<KMS_KEY_ID>"
]
}
}
}
]
}
让我们在一个已附加的成员账户中启动 SageMaker 训练任务,而不指定给定的 KMS 密钥:
estimator = TensorFlow(
entry_point='tf_bert_reviews.py',
role=role,
train_instance_count=1,
train_instance_type='ml.p4d.24xlarge',
framework_version='<TENSORFLOW_VERSION>'
)
在这里,我们看到训练任务将因为预期的AccessDeniedException而失败:
ClientError: An error occurred (AccessDeniedException) when calling the
CreateTrainingJob operation: User: arn:aws:sts::<ACCOUNT_ID>:assumed-role/<ROLE>/
SageMaker is not authorized to perform: sagemaker:CreateTrainingJob on resource:
arn:aws:sagemaker:<REGION>:<ACCOUNT_ID>:training-job/<JOB>
with an explicit deny
要解决此问题,我们将使用指定的 KMS 密钥启动相同的训练任务,训练任务将成功启动:
estimator = TensorFlow(
entry_point='tf_bert_reviews.py',
role=role,
train_instance_count=1,
train_instance_type='ml.p4d.24xlarge',
framework_version='<TENSORFLOW_VERSION>',
subnets=<SUBNETS>,
security_group_ids=<SECURITY_GROUP_IDS>,
ouput_kms_key="<KMS_KEY_ID>"
)
estimator.fit(inputs)
SageMaker 训练任务成功启动,并使用提供的 KMS 密钥:
arn:aws:iam:<ACCOUNT_ID>:role/service-role/<ROLE_NAME> \
2020-10-30 16:04:01 Starting - Starting the training job.
training job.
实施多账户模型部署
我们可以利用 AWS 控制塔、AWS 组织和 AWS Config 来设置和管理多个 AWS 账户。为了模型部署的治理和安全性,我们应为数据科学家、以及分阶段和生产环境分别创建独立的 AWS 账户。一个简单的 AWS 组织结构可以定义相应的 OU 和映射的 AWS 账户,如下所示:
ROOT
├── DATA_SCIENCE_MULTI_ACCOUNT_DEPLOYMENTS (OU)
│ ├── <AWS_ACCOUNT_DATA_SCIENCE>
│ ├── STAGING (OU)
│ │ └── <AWS_ACCOUNT_STAGING>
│ └── PRODUCTION (OU)
│ └── <AWS_ACCOUNT_PRODUCTION>
数据科学家应能够在数据科学账户中自由构建、训练和调整模型。一旦训练后的模型符合部署条件,数据科学家批准模型,将其部署到分阶段环境。分阶段环境可供 DevOps 团队在将模型部署到生产环境之前运行单元和集成测试。在 第十章 中,我们讨论了如何使用 Amazon SageMaker 项目自动化我们的模型部署流水线,涵盖数据科学、分阶段和生产环境。我们可以调整 SageMaker 项目模板以适应任何自定义的多账户设置。
可审计性
除了实施安全控制之外,我们还需要通过记录活动、收集事件以及追踪用户活动和 API 调用来审计我们的环境。审计能力是实施合规性框架和流程的主要要求。AWS 提供了多种服务和功能来实现审计能力。我们可以对资源进行标记,并利用 CloudWatch Logs 和 CloudTrail 接收日志并跟踪 API 调用。
标记资源
我们可以向任何 AWS 资源添加标签。资源标记可用作审计的一种机制。例如,我们可以通过 IAM 策略中的条件键来强制执行 SageMaker Studio 应用程序包含特定团队或项目标识符,如下所示:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": ”EnforceAppTag",
"Effect": "Allow",
"Action": [
"sagemaker:CreateApp”
],
"Resource": "*",
"Condition": {
"ForAllValues:StringLike": {
"aws:RequestTag/Project": "development"
}
}
}
]
}
如果我们将此 IAM 策略附加到属于“开发”项目的主体,IAM 用户或角色将无法创建带有其他项目标签的应用程序。
记录活动并收集事件
在我们的模型开发过程中,Amazon SageMaker 自动记录所有 API 调用、事件、数据访问和交互。我们可以追踪并跟踪到单个用户和 IP 地址的交互。
我们可以利用 CloudWatch Logs 监视、存储和访问我们的 SageMaker 日志文件。来自 SageMaker Studio 笔记本、SageMaker 处理或模型训练作业的日志也会作为 CloudWatch 事件捕获。我们可以跟踪指标并使用 CloudWatch Metrics 创建定制仪表板。我们可以在指标达到指定阈值时设置通知或操作。需要注意的是,SageMaker 容器日志和指标会传送到我们的 CloudWatch 环境,而底层基础设施日志由 SageMaker 服务平台保留。
追踪用户活动和 API 调用
我们可以使用 CloudTrail 跟踪单个用户的活动和 API 调用。CloudTrail 还将显示 SageMaker 实例代表我们执行的 API 调用,包括所假定的 IAM 角色。如果我们需要将活动映射到每个用户,我们需要在每个承担角色的 SageMaker 服务中为每个用户创建单独的 IAM 角色。
所有捕获的 API 调用日志将传送到我们指定的 Amazon S3 存储桶。API 日志包括每个 API 调用的用户和账户标识、源 IP 地址以及 API 调用的时间戳。
降低成本并提升性能
我们可以通过使用 IAM 策略限制用户可用的实例类型来减少 KMS API 调用次数从而降低 KMS 成本。此外,我们还可以通过使用 IAM 策略来减少 SageMaker 成本。
限制实例类型以控制成本
我们可能希望在生产环境中,仅允许长期运行的实时模型端点使用 CPU 实例类型,将 GPU 留给相对短期的计算密集型批处理训练作业。以下策略限制在创建 SageMaker 模型端点时使用基于 CPU 的实例类型:
{
"Sid": "LimitSageMakerModelEndpointInstances",
"Effect": "Deny",
"Action": [
"sagemaker:CreateEndpoint"
],
"Resource": "*",
"Condition": {
"ForAnyValue:StringNotLike": {
"sagemaker:InstanceTypes": [
"ml.c5.large",
"ml.m5.large"
]
}
}
}
我们还可以限制用于 SageMaker 笔记本实例和 SageMaker Studio 域的实例类型。由于笔记本实例和 SageMaker Studio 是长期资源,我们可能希望限制实例类型为基于 CPU 的实例,因为 SageMaker 训练作业的 GPU 重型工作应该在 SageMaker 集群中进行,而不是在我们的笔记本上。以下策略将限制长期存在的 SageMaker 笔记本实例和 SageMaker Studio 应用程序的实例类型,以帮助控制成本并促进更好地利用更昂贵的 GPU 实例:
{
"Sid": "LimitSageMakerNotebookInstanceTypes",
"Effect": "Deny",
"Action": [
"sagemaker:CreateNotebookInstance"
],
"Resource": "*",
"Condition": {
"ForAnyValue:StringNotLike": {
"sagemaker:InstanceTypes": [
"ml.c5.large",
"ml.m5.large",
"ml.t3.medium"
]
}
}
}
{
"Sid": "LimitSageMakerStudioInstanceTypes",
"Effect": "Deny",
"Action": [
"sagemaker:CreateApp"
],
"Resource": "*",
"Condition": {
"ForAnyValue:StringNotLike": {
"sagemaker:InstanceTypes": [
"ml.c5.large",
"ml.m5.large",
"ml.t3.medium"
]
}
}
}
隔离或删除未标记的资源
为了控制成本,我们应该为每个资源添加标记,以便正确跟踪和监控我们的支出。我们可以使用 AWS Config 服务的“required-tags”规则强制执行标记。此规则检查资源是否具有所需的标记。如果资源没有所需的标记,可以将其隔离或删除以节省成本。
使用 S3 存储桶 KMS 密钥以降低成本并提高性能
通过使用 S3 存储桶密钥,我们可以降低加密成本,这减少了上传新对象时对 AWS KMS 服务的 API 调用次数。我们可以使用以下代码向我们的存储桶添加 S3 存储桶密钥:
response = client.put_bucket_encryption(
Bucket=<BUCKET_NAME>,
ServerSideEncryptionConfiguration={
'Rules': [
{
'ApplyServerSideEncryptionByDefault': {
'SSEAlgorithm': 'aws:kms',
'KMSMasterKeyID': <KMS_KEY_ID>
},
'BucketKeyEnabled': True
},
]
}
)
摘要
在本章中,我们首先讨论了 AWS 云安全如何是“零工作”和“零优先级”。我们介绍了相关的安全概念和可以利用的 AWS 安全服务和功能,以及 AWS 共享责任安全模型。我们展示了如何在 AWS 上构建安全的数据科学和机器学习项目。我们描述了如何实施预防和检测控制以阻止事件发生,以及响应和纠正控制,帮助补救安全违规行为。
我们在计算和网络隔离、身份验证和授权、加密、治理以及可审计性和合规性领域描述了最佳实践。我们学习了如何通过 AWS IAM 实施访问控制,并使用 VPC 限制网络访问。我们强调了一些重要的概念,我们应该利用这些概念来保护我们的数据,并展示了如何向我们的 S3 数据访问和 SageMaker 作业添加不同安全级别的具体示例。我们展示了 S3 访问点如何帮助管理对共享 S3 存储桶(也称为我们的 S3 数据湖)中数据的访问。我们描述了 AWS KMS 提供的数据静态加密和传统以及后量子密码学的数据传输加密。接下来,我们讨论了实施治理和可审计性的机制。最后,我们通过分享如何通过 AWS 安全服务来减少成本和提高性能的提示来结束本章。


浙公网安备 33010602011771号