MLOps-实践指南-全-
MLOps 实践指南(全)
原文:
zh.annas-archive.org/md5/9d4956e6d6c725fb18b42a297e745787译者:飞龙
前言
我们为什么写这本书
我们两个大部分职业生涯都在自动化事务。当我们初次见面时,阿尔弗雷多不懂 Python,诺亚建议每周自动化一个任务。自动化是 MLOps、DevOps 和本书的核心支柱。您应该将本书中的所有示例和观点放在未来自动化的背景下思考。
如果诺亚能总结他在 2000 年至 2020 年间的经历,他几乎自动化了所有能够自动化的事情,从电影制作流水线到软件安装再到机器学习流水线。作为湾区初创公司的工程经理和首席技术官,他从零开始建立了许多数据科学团队。因此,在人工智能/机器学习革命的早期阶段,他看到了许多将机器学习应用到生产环境中的核心问题。
在过去几年中,诺亚一直在杜克大学、西北大学和加州大学戴维斯分校担任兼职教授,主要教授云计算、数据科学和机器学习工程相关课题。这种教学和工作经验使他对机器学习解决方案在现实世界部署中涉及的问题有了独特的视角。
阿尔弗雷多在系统管理员时代就有丰富的运维背景,对自动化有着相似的热情。如果没有一键自动化,是无法建立弹性基础设施的。在灾难发生时,重新运行脚本或流水线以重建崩溃的内容是最令人满足的事情。
当 COVID-19 爆发时,加速了我们共同关注的一个问题:“为什么我们不将更多模型投入到生产中?”诺亚在一篇他为 Forbes 写的 文章 中触及了其中一些问题。该文章的总结前提是,数据科学出现了问题,因为组织没有看到他们投资的回报。
后来在 O’Reilly 的 “Foo Camp”,诺亚主持了一个关于“为什么我们不能在生产环境中提高机器学习速度 10 倍?”的讨论会,与包括 Tim O’Reilly、Mike Loukides、Roger Magoulas 在内的众多人进行了深入讨论。讨论的结果是:“是的,我们可以达到 10 倍速度。”因此感谢 Tim 和 Mike 引发了这样一场引人入胜的讨论,并让本书顺利进行。
机器学习在过去几十年中感觉很像其他许多技术。起初,要取得成果需要花费多年时间。史蒂夫·乔布斯谈到,NeXT 希望能够将软件构建速度提高 10 倍(他成功了)。您可以在 YouTube 观看该采访。目前机器学习的一些问题是什么?
-
焦点放在“代码”和技术细节上,而非业务问题
-
缺乏自动化
-
HiPPO(最高薪水人士的意见)
-
非云原生
-
缺乏解决可解决问题的紧迫性
引用讨论中 Noah 提出的一点:“我反对一切精英主义。编程是人类的权利。认为只有某些特权阶层才有资格做这件事是错误的。” 与机器学习类似,技术不应只掌握在少数人手中。通过 MLOps 和 AutoML,这些技术可以走进公众的生活。我们可以通过使机器学习和人工智能技术民主化来做得更好。真正的 AI/ML 从业者将模型推向生产环境,在“真实”的未来,如医生、律师、技师和教师等人也将利用 AI/ML 来帮助他们完成工作。
本书组织结构
我们设计本书的方式是让你可以把每一章当作独立的部分来使用,旨在为你提供即时帮助。每章末尾都附有旨在促进批判性思维的讨论问题,以及旨在提高你对材料理解的技术练习。
这些讨论问题和练习也非常适合在数据科学、计算机科学或 MBA 课程中使用,以及对自我学习有动力的人。最后一章包含了几个案例研究,有助于作为 MLOps 专家建立工作组合。
本书分为 12 章,我们将在以下部分详细介绍一下。书末还附有一个附录,收录了一些实施 MLOps 的宝贵资源。
章节
前几章涵盖了 DevOps 和 MLOps 的理论与实践。其中一个涉及的项目是如何建立持续集成和持续交付。另一个关键主题是 Kaizen,即在各个方面持续改进的理念。
云计算有三章涵盖了 AWS、Azure 和 GCP。作为微软的开发者倡导者,Alfredo 是 Azure 平台 MLOps 知识的理想来源。同样,Noah 多年来一直致力于培训学生云计算,并与 Google、AWS 和 Azure 的教育部门合作。这些章节是熟悉基于云的 MLOps 的绝佳途径。
其他章节涵盖了 MLOps 的关键技术领域,包括 AutoML、容器、边缘计算和模型可移植性。这些主题涵盖了许多具有活跃追踪的前沿新兴技术。
最后,在最后一章中,Noah 讲述了他在社交媒体初创公司的时间以及他们在进行 MLOps 时面临的挑战的一个真实案例研究。
附录
附录是一些在完成《Python for DevOps》(O'Reilly)和本书之间几年间出现的文章、想法和宝贵物品的集合。使用它们的主要方法是帮助您做出未来的决策。
练习问题
在这本书的练习中,一个有用的启发式方法考虑如何利用它们通过 GitHub 创建一个作品集,并且使用 YouTube 演示你的操作步骤。保持着“图像胜于千言万语”的表达,将一个可重复的 GitHub 项目的 YouTube 链接添加到简历上可能价值 10000 字,并且将简历置于新的职位资格类别之中。
在阅读本书和做练习时,请考虑以下关键思维框架。
讨论问题
根据乔纳森·哈伯在《批判性思维》(麻省理工出版社基本知识系列)和非营利组织批判性思维基金会的说法,讨论问题是关键的批判性思维组成部分。由于社交媒体上的误信息和浅薄内容的泛滥,世界急需批判性思维。掌握以下技能使个人脱颖而出:
智力谦逊
承认自己知识的局限性。
智力勇气
即使在社会压力面前,也能为自己的信仰辩护的能力。
智力同理
理解他人立场并将自己置于他人思维之中的能力。
智力自主性
在无视他人的情况下独立思考的能力。
智力诚信
以你期望他人对待你的智力标准思考和辩论的能力。
智力毅力
提供支持你立场的证据的能力。
自信的理由
相信有不可辩驳的事实,并且理性是获得知识的最佳解决方案。
公正思维
以诚信的努力对待所有观点的能力。
根据这些标准,评估每章的讨论问题。
章节引用的起源
作者:诺亚
我在 1998 年底大学毕业,并花了一年时间在美国或欧洲的小联盟训练篮球,同时担任私人教练。我的备胎计划是找 IT 工作。我申请加州理工学院帕萨迪纳分校的系统管理员职位,因机缘巧合获得了 Mac IT 专家职位。我决定低薪职业运动员的风险/回报比不值得,接受了这份工作。
说加州理工改变了我的生活实在是轻描淡写。午餐时,我玩极限飞盘,并听说了 Python 编程语言,我学会了它以便“融入”我的极限飞盘朋友中,他们是加州理工的员工或学生。后来,我直接为加州理工的管理层工作,并成为大卫·巴尔的摩尔博士的个人 Mac 专家,他 30 多岁就获得了诺贝尔奖。我以许多意想不到的方式与许多名人互动,这增强了我的自信心并扩展了我的人脉。
我还与许多后来在人工智能/机器学习领域做出不可思议成就的人们有过像阿甘正传式的随机相遇。有一次,我与斯坦福大学 AI 负责人李飞飞博士和她的男友共进晚餐;我记得被她得她的男友整个夏季都在与他父亲一起编写视频游戏,令我印象深刻。当时我非常赞叹,并想,“谁会做这种事情?”后来,我在著名物理学家戴维·古德斯坦博士的桌子下安装了一个邮件服务器,因为他不断受到 IT 部门对他的邮箱存储限制的苛责。这些经历使我开始对建立“影子基础设施”产生兴趣。因为我直接为管理层工作,所以如果有充分理由,我可以违反规定。
我 我随机遇到的人之一是约瑟夫·博根博士,他是一位神经外科医生,也是加州理工学院的客座教授。在加州理工学院,他对我的生活影响最深远。有一天,我接到一个帮助台呼叫,需要去他家修理他的电脑,后来这演变成了每周与他和他的妻子格伦达在他家吃晚餐的活动。从大约 2000 年开始,直到他去世那天,他一直是我的朋友和导师。
当时,我对人工智能非常感兴趣,我记得加州理工计算机科学教授告诉我这是一个死胡同,我不应该专注于它。尽管如此,我制定了一个计划,到 40 岁时精通许多软件编程语言,并写人工智能程序。果然,我的计划成功了。
如果没有遇见乔·博根,我可以明确地说,我今天不会做我现在在做的事情。当他告诉我他做过第一次半脑切除手术来帮助一名患有严重癫痫的病人时,他让我震惊了。我们会花数小时讨论意识的起源,上世纪七十年代使用神经网络来确定谁将成为空军飞行员,以及你的大脑是否包含“你的两个自我”,一个在每个半球。最重要的是,博根给了我对我的智力的信心。直到那时,我对自己能做什么有严重的怀疑,但我们的对话就像是一个高级思维的硕士学位。作为一名教授,我考虑他对我的生活产生了多大的影响,并且希望向我与之互动的其他学生,无论是正式的教师还是他们所遇到的人,回馈。您可以自己从博根博士的加州理工个人主页的档案和他的传记阅读这些引用。
本书中使用的约定
本书中使用的以下排版约定:
Italic
指示新术语、网址、电子邮件地址、文件名和文件扩展名。
Constant width
用于程序清单,以及段落内用来指代程序元素如变量或函数名、数据库、数据类型、环境变量、语句和关键字。
Constant width bold
显示用户应按字面输入的命令或其他文本。
Constant width italic
显示应替换为用户提供的值或由上下文确定的值的文本。
小贴士
此元素表示提示或建议。
注意
此元素表示一般说明。
警告
此元素表示警告或注意事项。
Using Code Examples
补充材料(代码示例、练习等)可在https://github.com/paiml/practical-mlops-book下载。
如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com。
本书旨在帮助您完成工作。一般而言,如果此书提供了示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分内容,否则无需联系我们请求权限。例如,编写一个使用本书多个代码块的程序不需要权限。销售或分发 O’Reilly 书籍示例需要权限。引用本书并引用示例代码来回答问题不需要权限。将本书的大量示例代码整合到产品文档中需要权限。
我们感激但通常不要求署名。署名通常包括书名、作者、出版商和 ISBN。例如:“Practical MLOps by Noah Gift and Alfredo Deza (O’Reilly). Copyright 2021 Noah Gift and Alfredo Deza, 978-1-098-10301-9.”
如果您认为使用代码示例超出了公平使用范围或上述授权,请随时联系我们,邮件至permissions@oreilly.com。
致谢
来自 Noah
正如前面提到的,如果不是 Mike Loukides 邀请我参加 Foo Camp 并与 Tim O'Reilly 进行了一次很好的讨论,这本书就不会存在。接下来,我要感谢我的合著者 Alfredo。我有幸与 Alfredo 合作写了五本书,其中两本是为 O'Reilly 出版的,另外三本是自我出版的,这主要归功于他接受工作并完成任务的能力。对于努力工作的渴望可能是最好的才能,而 Alfredo 在这方面拥有丰富的技能。
我们的编辑 Melissa Potter 在将事情整理到位方面做了大量工作,而她编辑前后的书几乎是两本不同的书。我感到很幸运能和这样一个才华横溢的编辑合作。
我们的技术编辑,包括 Steve Depp、Nivas Durairaj 和 Shubham Saboo,在提供关于何时要“走弯道”和何时“直道”的出色反馈中发挥了至关重要的作用。许多改进都归功于 Steve 的详细反馈。此外,我还要感谢 Julien Simon 和 Piero Molino,他们用对 MLOps 的实际思考丰富了我们的书籍。
我想感谢我的家人 Liam、Leah 和 Theodore,在疫情期间紧张的截止日期内给我完成这本书的空间。我也期待着未来看到他们写的一些书。另外,我要特别感谢我在西北大学、杜克大学、加州大学戴维斯分校和其他学校教过的所有前学生。他们的许多问题和反馈都融入了这本书中。
最后感谢 Joseph Bogen 博士,他是 AI/ML 和神经科学的早期先驱。如果我们在加州理工学院没有相遇,我绝对不可能成为教授,也不可能有这本书存在。他对我的生活影响如此之大。
来自 Alfredo
我在写这本书的过程中,完全感谢我的家人的支持:Claudia、Efrain、Ignacio 和 Alana——你们的支持和耐心对完成这本书至关重要。再次感谢你与我一同工作的所有机会,Noah;这是另一次不可思议的旅程。我珍视我们的友谊和专业关系。
特别感谢 Melissa Potter(毫无疑问是我合作过的最好的编辑)出色的工作。我们的技术编辑做得很好,发现问题并突出需要完善的地方,这总是一件难事。
我也非常感谢 Lee Stott 在 Azure 方面的帮助。没有他,Azure 的内容不会那么好。还要感谢 Francesca Lazzeri、Mike McCoy 和我在 Microsoft 联系过的所有其他人,你们都非常有帮助。
第一章:介绍 MLOps
By Noah Gift
自 1986 年以来,我经历了更多的死亡,其中一些是由于注意力不足,但主要是因为故意在各个方向上推限度,冒险盆景有点像冒险恋爱;最好的结果需要冒险接触受伤并且没有成功的保证。
Dr. Joseph Bogen
科幻小说的一个强大之处在于它能够想象一个没有约束的未来。有史以来最具影响力的科幻节目之一是 60 年前左右首次播出的电视剧星际迷航。这种文化影响激发了像 Palm Pilot 和手持式手机的技术设计者。此外,星际迷航还影响了苹果计算机的联合创始人史蒂夫·沃兹尼亚克,促使他创建了苹果计算机。
在这个机器学习创新时代,原始系列中的许多重要思想与即将到来的 MLOps(或机器学习运营)工业革命相关。例如,星际迷航手持三角仪可以使用预训练的多类别分类模型即时对物体进行分类。但最终,在这个未来的科幻世界中,像科学官员、医疗官员或者飞船船长这样的领域专家们,并不花几个月时间来训练机器学习模型。同样,他们的科学船员,企业号的船员,也不被称为数据科学家。相反,他们的工作经常涉及使用数据科学。
在 2020 年代,星际迷航科幻未来的许多机器学习方面已不再是科幻。本章引导读者进入使这一切成为可能的基础理论。让我们开始吧。
机器学习工程师和 MLOps 的兴起
机器学习(ML)以其在全球范围内的广泛应用,引发了对构建 ML 系统的系统化和高效方法的需求,从而导致对 ML 工程师的需求迅速增长。这些 ML 工程师又将已建立的 DevOps 最佳实践应用于新兴的机器学习技术。主要的云供应商都有针对这些从业者的认证。我在 AWS、Azure 和 GCP 作为机器学习的专家与之直接合作的经验。在某些情况下,这包括帮助创建机器学习认证和官方培训材料。此外,我在杜克大学和西北大学的一些顶尖数据科学项目中教授机器学习工程和云计算。亲身经历中,我看到了机器学习工程师的兴起,许多以前的学生成为了机器学习工程师。
Google 有一个 专业机器学习工程师认证。它描述 ML 工程师是“设计、构建和将 ML 模型投入生产以解决业务挑战的人…” Azure 有一个 Microsoft Certified: Azure 数据科学家关联。它描述这种从业者是“应用他们在数据科学和机器学习方面的知识来实现和运行机器学习工作负载的人…” 最后,AWS 描述了一个 AWS Certified 机器学习专家。他们具备“设计、实施、部署和维护解决特定业务问题的机器学习解决方案的能力。”
一种看待数据科学与机器学习工程的方式是考虑科学与工程本身的区别。科学倾向于研究,而工程则倾向于生产。随着机器学习超越仅仅是研究的一面,公司迫切希望在围绕 AI 和 ML 的雇佣中获得投资回报。根据 payscale.com 和 glassdoor.com 的数据,2020 年底,数据科学家、数据工程师和机器学习工程师的中位薪资相似。根据 LinkedIn 在 2020 年第四季度的数据显示,191K 个工作提及云计算,有 70K 个数据工程工作列表,55K 个机器学习工程工作列表和 20K 个数据科学工作列表,如图 Figure 1-1 所示。
另一种看待这些工作趋势的方式是它们是技术炒作周期的自然组成部分。组织意识到,要产生投资回报(ROI),他们需要具备硬技能的员工:云计算、数据工程和机器学习。他们还需要比数据科学家更多。因此,2020 年代可能会显示出将数据科学视为一种行为而非职称的加速趋势。DevOps 是一种行为,就像数据科学一样。考虑到 DevOps 和数据科学的原则。在这两种情况下,DevOps 和数据科学都是评估世界的方法论,不一定是唯一的职称。

图 1-1. 机器学习工作
让我们来看一下组织中机器学习工程倡议的成功衡量选项。首先,您可以统计进入生产的机器学习模型数量。其次,您可以衡量 ML 模型对业务 ROI 的影响。这些指标汇总为模型的运行效率。维护它所需的成本、正常运行时间和人员是预测机器学习工程项目成功或失败的信号。
先进技术组织知道他们需要利用能降低机器学习项目失败风险的方法和工具。那么,机器学习工程中使用的这些工具和流程是什么呢?以下是部分列表:
云原生 ML 平台
AWS SageMaker,Azure ML Studio 和 GCP AI Platform
容器化工作流
Docker 格式容器、Kubernetes 和私有以及公共容器注册表
无服务器技术
AWS Lambda, AWS Athena, Google Cloud Functions, Azure Functions
用于机器学习的专用硬件
GPU、Google TPU(TensorFlow 处理单元)、Apple A14、AWS Inferentia 弹性推断
大数据平台和工具
Databricks、Hadoop/Spark、Snowflake、Amazon EMR(弹性 Map Reduce)、Google Big Query
有关机器学习的一个明显模式是它与云计算的紧密联系。这是因为机器学习的原始成分需要大量计算、广泛的数据和专门的硬件。因此,与云平台的深度集成具有自然的协同效应,与机器学习工程相结合。进一步支持这一点的是,云平台正在构建专门的平台来增强机器学习的操作能力。因此,如果您从事机器学习工程,您很可能是在云中进行。接下来,让我们讨论 DevOps 在其中的作用。
什么是 MLOps?
为什么机器学习不是快 10 倍?大多数构建机器学习系统的问题涉及机器学习建模周围的一切:数据工程、数据处理、问题可行性和业务对齐。其中一个问题是过于关注“代码”和技术细节,而不是用机器学习解决业务问题。还存在自动化不足和最高薪水者的观点(HiPPO)文化问题。最后,许多机器学习并非云原生,使用学术数据集和学术软件包,无法解决大规模问题。
反馈循环越快(见 Kaizen),就越有时间专注于像快速检测 Covid 最新问题,检测现实世界中的口罩与非口罩计算机视觉解决方案以及更快的药物发现等业务问题。这些问题存在解决技术,但这些解决方案为何在现实世界中无法使用?为什么呢?
注意
什么是 Kaizen?在日语中,它意味着改善。Kaizen 作为一种软件管理哲学起源于二战后的日本汽车工业。它支撑许多其他技术:看板、根本原因分析和五个为什么、以及六西格玛。要实践 Kaizen,需要对世界状态进行准确而现实的评估,并追求卓越的日常、渐进改进。
模型没有进入生产的原因促使 MLOps 作为关键的行业标准出现。MLOps 与 DevOps 有着相同的历史渊源,因为 DevOps 在哲学上要求自动化。一个常见的说法是如果不自动化,就是有问题的。同样,在 MLOps 中,系统中不应该有人为杠杆的组件。自动化的历史表明,人类在重复任务中的价值最低,但在利用技术作为架构师和实践者时最有价值。同样地,开发者、模型和运维之间的协调必须通过透明的团队合作和健康的协作来实现。把 MLOps 看作是使用 DevOps 方法自动化机器学习的过程。
注意
什么是 DevOps?它结合了包括微服务、持续集成和持续交付在内的最佳实践,消除了运维与开发之间以及团队之间的障碍。你可以在我们的书籍 Python for DevOps(O’Reilly)中详细了解 DevOps。Python 是脚本、DevOps 和机器学习的主要语言。因此,这本 MLOps 书籍着重于 Python,就像 DevOps 书籍着重于 Python 一样。
对于 MLOps 来说,不仅需要对软件工程过程进行全面自动化,还需要对数据和建模进行自动化。模型训练和部署是传统 DevOps 生命周期中新增的问题。最后,额外的监控和仪表必须考虑到可能出现故障的新因素,例如数据漂移——即数据自上次模型训练以来的变化量。
将机器学习模型投入生产的一个基本问题是数据科学行业的不成熟。软件行业已经采用 DevOps 来解决类似的问题;现在,机器学习社区也在推广 MLOps。让我们深入了解如何做到这一点。
DevOps 和 MLOps
DevOps 是一组旨在提高组织发布高质量软件速度的技术和管理实践。DevOps 的一些好处包括速度、可靠性、规模和安全性。这些好处通过遵循以下最佳实践实现:
持续集成(CI)
CI 是持续测试软件项目并基于这些测试结果改进质量的过程。它是使用开源和 SaaS 构建服务器(如 GitHub Actions、Jenkins、GitLab、CircleCI 或云原生构建系统如 AWS Code Build)进行自动化测试。
持续交付(CD)
这种方法在没有人为干预的情况下将代码交付到新环境。CD 是通过基础设施即代码自动部署代码的过程。
微服务
微服务是具有独特功能且几乎没有依赖性的软件服务。最流行的基于 Python 的微服务框架之一是 Flask。例如,机器学习预测端点非常适合作为微服务。这些微服务可以使用各种技术,包括 FaaS(函数即服务)。云函数的一个完美示例是 AWS Lambda。微服务可以准备好容器,并使用 CaaS(容器即服务)将具有 Dockerfile 的 Flask 应用程序部署到 AWS Fargate、Google Cloud Run 或 Azure 应用服务。
基础设施即代码
基础设施即代码(IaC)是将基础设施检入源代码存储库并“部署”到该存储库以推送更改的过程。IaC 允许幂等行为,并确保基础设施不需要人类来构建它。在代码中定义的云环境并检入源代码存储库是一个很好的示例用例。流行的技术包括云特定的 IaC,如 AWS Cloud Formation 或 AWS SAM(无服务器应用程序模型)。多云选项包括 Pulumi 和 Terraform。
监控与仪表
监控与仪表是用于允许组织对软件系统的性能和可靠性做出决策的过程和技术。通过记录和其他工具,如 New Relic、Data Dog 或 Stackdriver 等应用程序性能监控工具,监控和仪表本质上是收集有关生产或数据科学中部署的软件系统的应用行为的数据。这个过程是 Kaizen 起作用的地方;数据驱动的组织使用这些仪表来每天或每周使事情变得更好。
有效的技术沟通
这种技能涉及创建有效、可重复和高效的沟通方法的能力。一个很好的有效技术沟通的例子可以是采用自动机器学习(AutoML)来进行系统的初步原型设计。当然,最终,AutoML 模型可能会被保留或丢弃。然而,自动化可以作为一个信息工具,以防止在一个棘手的问题上工作。
有效的技术项目管理
这个过程可以有效地利用人类和技术解决方案,如票务系统和电子表格,来管理项目。此外,适当的技术项目管理需要将问题分解为小的、离散的工作块,以便进行增量进展。在机器学习中的反模式通常是团队致力于解决问题“完美”的一个生产机器模型。相反,每天或每周交付较小的成功案例是建立模型的更可扩展和谨慎的方法。
持续集成和持续交付是 DevOps 的两个最关键支柱之一。持续集成涉及将代码合并到源代码控制库中,并通过测试自动检查代码质量。持续交付是指代码更改在自动测试后自动部署到预备环境或生产环境。这两种技术都是持续改进精神下的自动化形式。
一个好问题是,团队中谁应该实施 CI/CD?这个问题类似于在民主国家中谁负责纳税。在民主国家,税款用于修建道路、桥梁、执法、紧急服务、学校及其他基础设施,所以所有人都必须为建设更好的社会贡献力量。同样,所有 MLOps 团队成员都应该帮助开发和维护 CI/CD 系统。一个良好维护的 CI/CD 系统是对团队和公司未来的一种投资。
ML 系统同样也是一个软件系统,但它包含一个独特的组件:机器学习模型。DevOps 的同样好处可以适用于 ML 系统。自动化的采纳是为什么新的方法如数据版本控制和自动机器学习在捕捉 DevOps 思维方式方面充满了许多希望。
MLOps 需求层次
一种思考机器学习系统的方法是考虑马斯洛的需求层次理论,如图 1-2 所示。金字塔的底层反映了“生存”,而真正的人类潜力在满足基本生存和情感需求后才会显现。

图 1-2. 马斯洛需求层次理论
这个概念同样适用于机器学习。一个 ML 系统是一个软件系统,而当 DevOps 和数据工程的最佳实践到位时,软件系统才能高效可靠地运行。因此,如果没有 DevOps 的基本基础规则或者数据工程没有完全自动化,那么如何将机器学习的真正潜力带给一个组织呢?图 1-3 中展示的 ML 需求层次不是一个确定的指南,但却是开始讨论的绝佳起点。

图 1-3. ML 工程需求层次
阻碍机器学习项目的主要因素之一是 DevOps 的这种必要基础。在这个基础建立完成后,接下来是数据自动化,然后是平台自动化,最后才是真正的 ML 自动化或者 MLOps。MLOps 的顶峰是一个能够运行的机器学习系统。负责运营和构建机器学习应用的人是机器学习工程师和/或数据工程师。让我们深入探讨 ML 需求层次的每一步,并确保您能够牢固地实施它们,从 DevOps 开始。
实施 DevOps
DevOps 的基础是持续集成。没有自动化测试,DevOps 无法前进。对于现代工具可用的 Python 项目来说,持续集成相对来说不太痛苦。第一步是为 Python 项目建立一个“脚手架”,如 图 1-4 所示。

图 1-4. Python 项目脚手架
Python 机器学习项目的运行时几乎可以保证在 Linux 操作系统上。因此,以下是为 ML 项目实现简单的 Python 项目结构。你可以访问 GitHub 上的这个例子的源代码 链接 作为阅读本节内容的参考。这些组件如下:
Makefile
Makefile 通过 make 系统运行“recipes”,这是 Unix 操作系统自带的。因此,Makefile 是简化连续集成步骤的理想选择,例如以下内容。请注意,Makefile 是项目的良好起点,通常会随着需要自动化的新组件而发展。
注意
如果你的项目使用 Python 虚拟环境,在使用 Makefile 之前需要激活它,因为 Makefile 只是运行命令。对于 Python 的新手来说,将 Makefile 与虚拟环境混淆是一个常见的错误。同样,如果你使用像 Microsoft Visual Studio Code 这样的编辑器,你需要告诉编辑器你的 Python 虚拟环境,以便它可以准确地提供语法高亮、linting 和其他可用库。
执行安装
这一步通过 make install 命令安装软件
执行 lint
这一步通过 make lint 命令检查语法错误
执行测试
这一步通过 make test 命令运行测试:
install:
pip install --upgrade pip &&\
pip install -r requirements.txt
lint:
pylint --disable=R,C hello.py
test:
python -m pytest -vv --cov=hello test_hello.py
requirements.txt
requirements.txt 文件是 pip 安装工具的一种约定,这是 Python 的默认安装工具。如果需要在不同的环境中安装不同的包,一个项目可以包含一个或多个这样的文件。
源代码和测试
Python 脚手架的最后一部分是添加一个源代码文件和一个测试文件,如下所示。这个脚本存在于一个名为 hello.py 的文件中:
def add(x, y):
"""This is an add function"""
return x + y
print(add(1, 1))
接下来,通过 pytest 框架创建测试文件非常简单。这个脚本会在一个名为 test_hello.py 的文件中,与 hello.py 放在同一个文件夹中,这样 from hello import add 就可以工作:
from hello import add
def test_add():
assert 2 == add(1, 1)
这四个文件:Makefile、requirements.txt、hello.py 和 test_hello.py 就足以启动连续集成之旅,除了创建本地 Python 虚拟环境外。要做到这一点,首先创建它:
python3 -m venv ~/.your-repo-name
另外,需要注意一般有两种方法创建虚拟环境。首先,许多 Linux 发行版将包含命令行工具 virtualenv,它与 python3 -m venv 的功能相同。
接下来,需要激活它来“激活”它:
source ~/.your-repo-name/bin/activate
注意
为什么要创建和使用 Python 虚拟环境?对于 Python 新手来说,这个问题无处不在,但答案很简单。因为 Python 是一种解释型语言,它可以从操作系统的任何地方“获取”库。Python 虚拟环境将第三方包隔离到一个特定的目录中。还有其他解决此问题的方案和许多开发工具。它们有效地解决了同样的问题:Python 库和解释器被隔离到特定的项目中。
一旦设置了这个框架,你可以在本地执行以下持续集成步骤:
-
使用
make install安装你项目所需的库。输出将类似于 图 1-5(此示例显示在 GitHub Codespaces 中运行)。
$ make install pip install --upgrade pip &&\ pip install -r requirements.txt Collecting pip Using cached pip-20.2.4-py2.py3-none-any.whl (1.5 MB) [.....more output suppressed here......]![GitHub Code Spaces]()
图 1-5. GitHub Codespaces
-
运行
make lint来对你的项目进行代码风格检查:$ make lint pylint --disable=R,C hello.py ------------------------------------ Your code has been rated at 10.00/10 -
运行
make test来测试你的项目:$ make test python -m pytest -vv --cov=hello test_hello.py ===== test session starts ==== platform linux -- Python 3.8.3, pytest-6.1.2,\ /home/codespace/.venv/bin/python cachedir: .pytest_cache rootdir: /home/codespace/workspace/github-actions-demo plugins: cov-2.10.1 collected 1 item test_hello.py::test_add PASSED [100%] ----------- coverage: platform linux, python 3.8.3-final-0 ----------- Name Stmts Miss Cover ------------------------------ hello.py 3 0 100%
一旦在本地工作正常,将这个相同的过程集成到远程 SaaS 构建服务器中就很简单了。选项包括 GitHub Actions、像 AWS Code Build 这样的云原生构建服务器、GCP CloudBuild、Azure DevOps Pipelines,或者像 Jenkins 这样的开源、自托管构建服务器。
使用 GitHub Actions 配置持续集成
对于这个 Python 框架项目实施持续集成最简单的方法之一是使用 GitHub Actions。要做到这一点,你可以在 GitHub UI 中选择“Actions”并创建一个新的操作,或者在你创建的这些目录中创建一个文件,如下所示:
.github/workflows/<yourfilename>.yml
GitHub Actions 文件本身很容易创建,以下是一个示例。注意,Python 的确切版本设置为项目所需的任何解释。在此示例中,我想检查在 Azure 上运行的特定版本的 Python。由于之前创建 Makefile 的辛勤工作,持续集成步骤实施起来非常简单:
name: Azure Python 3.5
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.5.10
uses: actions/setup-python@v1
with:
python-version: 3.5.10
- name: Install dependencies
run: |
make install
- name: Lint
run: |
make lint
- name: Test
run: |
make test
从 GitHub 仓库的“push”事件运行时,GitHub Actions 的概览如 图 1-6 所示。

图 1-6. GitHub Actions
这一步完成了设置持续集成的最后部分。接下来的逻辑步骤是持续部署,即将机器学习项目自动推送到生产环境。这一步涉及使用持续交付流程和 IaC(基础设施即代码)将代码部署到特定位置。此过程如 图 1-7 所示。

图 1-7. 持续交付
DataOps 和数据工程
在 ML 需求层次结构中的下一步是自动化数据流。例如,想象一个只有井作为唯一水源的城镇。由于需要为水安排旅行,日常生活变得复杂,而我们认为理所当然的事情可能无法正常工作,比如按需热水淋浴、按需洗碗或自动灌溉。类似地,一个没有数据自动流的组织无法可靠地进行 MLOps。
许多商业工具正在演变成 DataOps。一个例子包括由 Airbnb 设计,后来开源的Apache Airflow,用于调度和监控其数据处理作业。AWS 工具包括 AWS 数据管道和 AWS Glue。AWS Glue 是一个无服务器的 ETL(抽取、加载、转换)工具,它检测数据源的架构,然后存储数据源的元数据。其他工具如 AWS Athena 和 AWS QuickSight 可以查询和可视化数据。
在这里需要考虑的一些问题是数据的大小,信息更改的频率以及数据的清洁程度。许多组织使用集中式数据湖作为围绕数据工程的所有活动的中心枢纽。数据湖之所以有助于建立围绕其周围的自动化,包括机器学习,是因为它在 I/O 方面提供了“接近无限”的规模,并具有高耐用性和可用性。
注意
数据湖通常与云对象存储系统(例如 Amazon S3)等同。数据湖允许在原地进行数据处理,无需移动数据。数据湖通过接近无限的容量和计算特性实现此目标。
当我在电影行业工作时,比如Avatar,数据是巨大的;确实需要通过一个非常复杂的系统进行移动。现在有了云,这个问题解决了。
图 1-8 展示了基于云数据湖的工作流程。请注意,在同一位置执行许多任务的能力,无需移动数据。
专门的职称,比如数据工程师,可以全天候建造处理这些多样化用例的系统:
-
定期收集数据和运行作业
-
处理流数据
-
无服务器和事件驱动数据
-
大数据作业
-
用于 ML 工程任务的数据和模型版本控制

图 1-8。云数据湖的数据工程
就像一个没有自来水的村庄不能使用自动洗碗机一样,一个没有数据自动化的组织也不能使用先进的机器学习方法。因此,数据处理需要自动化和运营化。这一步骤使得更下游的 ML 任务能够实现运营化和自动化。
平台自动化
一旦数据自动流动起来,评估组织如何利用高级平台构建机器学习解决方案就成为清单上的下一个项目。例如,如果组织已将数据收集到云平台的数据湖中,如 Amazon S3,将机器学习工作流程与 Amazon Sagemaker 结合是很自然的选择。同样,如果组织使用 Google,它可以使用 Google AI 平台或 Azure 来使用 Azure Machine Learning Studio。类似地,对于使用 Kubernetes 而不是公共云的组织,Kubeflow将是合适的选择。
解决这些问题的一个优秀平台示例见图 1-9。请注意,AWS SageMaker 为真实的机器学习问题编排了复杂的 MLOps 序列,包括启动虚拟机、读写 S3 以及配置生产终端点。在生产场景中,执行这些基础设施步骤而不进行自动化至少是愚蠢的。

图 1-9. Sagemaker MLOps 管道
一个 ML 平台解决了真实世界中可重复性、规模化和操作化问题。
MLOps
假设所有其他层次(DevOps、数据自动化和平台自动化)都已完成,MLOps 就可能实现。请记住,使用 DevOps 方法自动化机器学习的过程称为 MLOps。构建机器学习的方法是机器学习工程。
因此,MLOps 是一种行为,就像 DevOps 是一种行为一样。虽然有些人作为 DevOps 工程师工作,但软件工程师更频繁地使用 DevOps 最佳实践执行任务。同样,机器学习工程师应使用 MLOps 最佳实践创建机器学习系统。
DevOps 和 MLOps 结合的最佳实践?
还记得本章前面描述的 DevOps 实践吗?MLOps 基于这些实践,并将特定项目扩展到直接针对机器学习系统。
表达这些最佳实践的一种方式是考虑它们通过创建可复制的模型,具有强大的模型打包、验证和部署来增强解释和观察模型性能的能力。图 1-10 更详细展示了这一点。

图 1-10. MLOps 反馈循环
反馈循环包括以下内容:
使用可重用的 ML 管道创建和重新训练模型
仅仅创建一次模型是不够的。数据可能会发生变化,客户可能会发生变化,制定模型的人可能会发生变化。解决方案是使用版本化的可重用 ML 管道。
ML 模型的持续交付
ML 模型的持续交付类似于软件的持续交付。当所有步骤都包括基础设施在内自动化,使用 IaC 时,模型可以随时部署到新环境,包括生产环境。
MLOps 管道的审计跟踪
对于机器学习模型进行审计至关重要。机器学习中存在许多问题,包括安全性、偏见和准确性。因此,拥有一个有用的审计追踪系统是无价的,就像在生产软件工程项目中拥有足够的日志记录一样关键。此外,审计追踪系统是反馈循环的一部分,您可以不断改进解决问题和实际问题的方法。
观察模型数据漂移的使用以改进未来的模型
机器学习的一个独特方面是数据可以在模型下方实际“转移”。因此,两年前适用于客户的模型今天很可能不会起同样的作用。通过监控数据漂移,即从上次进行模型训练以来的变化量,可以在引起生产问题之前防止准确性问题。
结论
本章讨论了在机器学习背景下使用 DevOps 原则的重要性。除了软件本身外,机器学习还增加了管理数据和模型的新复杂性。解决这种复杂性的方法是像软件工程社区在 DevOps 中所做的那样接受自动化。
建造书架与种植树木不同。书架需要初步设计,然后一次性建造。涉及机器学习的复杂软件系统更像是种植树木。一个成功生长的树木需要多个动态输入,包括土壤、水、风和阳光。
同样,理解 MLOps 的一种方法是 25%法则。在图 1-13 中,软件工程、数据工程、建模和业务问题同样重要。MLOps 的跨学科特性使其难以处理。然而,有许多公司在遵循这个 25%法则的 MLOps 方面有很好的例子。

图 1-13。25%法则
特斯拉汽车是一个很好的例子;它们通过半自动驾驶汽车形式提供客户所需的功能。它们还在软件工程实践中做了很好的更新。同时,汽车系统还不断训练模型,以根据接收到的新数据进行改进。符合 25%法则的产品的另一个例子是亚马逊的 Alexa 设备。
下一章将讨论 MLOps 所需的基础技能。这些包括程序员的数学、数据科学项目示例以及完整的端到端 MLOps 流程。通过在本章末推荐的练习,您将使自己处于吸收后续内容的绝佳位置。
练习
-
使用
Makefile创建一个新的 GitHub 存储库,并生成必要的 Python 支架结构,包括 linting 和测试。然后,在您的 Makefile 中执行额外的步骤,如代码格式化。 -
使用GitHub Actions来测试一个 GitHub 项目,使用两个或更多 Python 版本。
-
使用云原生构建服务器(如 AWS Code Build、GCP CloudBuild 或 Azure DevOps Pipelines),为您的项目执行持续集成。
-
通过集成 Dockerfile 将 GitHub 项目容器化,并自动将新容器注册到容器注册表。
-
使用负载测试框架如locust或loader io为您的应用程序创建一个简单的负载测试,并在将更改推送到预备分支时自动运行此测试。
批判性思维讨论问题
-
连续集成(CI)系统解决了哪些问题?
-
为什么 CI 系统是 SaaS 软件产品和 ML 系统的重要组成部分?
-
为什么云平台是分析应用程序的理想目标?数据工程和 DataOps 如何帮助构建基于云的分析应用程序?
-
深度学习如何从云计算中受益?深度学习在没有云计算的情况下可行吗?
-
解释 MLOps 是什么以及它如何增强机器学习工程项目。
第二章:MLOps 基础
作者:诺亚·吉夫
医学院是一次悲惨的经历,是一长串事实,其起源很少被解释,其有用性很少被证明。我对死记硬背的厌恶和质疑态度并不被 96 名学生中的大多数人所分享。在一次生物化学讲师声称正在推导涅恩斯特方程时,这一点尤为明显。班上的学生们忠实地抄写他在黑板上写的东西。一年前,我在加州大学洛杉矶分校为化学专业的 Pchem 课程只上了一年,我以为他在吹牛。
“你从哪里得到那个 k 值的?”我问道。
班上的学生们把我打住了:“让他结束!只是复制它。”
约瑟夫·博根博士
有一个坚实的基础是任何技术努力的关键。在本章中,几个关键的基础模块为本书的其余部分奠定了基础。在与新接触数据科学和机器学习的学生打交道时,我经常遇到关于本章涵盖内容的误解。本章旨在为使用 MLOps 方法学建立坚实的基础。
Bash 和 Linux 命令行
大多数机器学习都发生在云端,大多数云平台假定您会在某种程度上与终端进行交互。因此,了解 Linux 命令行的基础知识对于执行 MLOps 至关重要。本节旨在为您提供足够的知识,以确保您在进行 MLOps 时取得成功。
当我让学生接触终端时,他们常常震惊且恐惧。在大多数现代计算领域,由于像 MacOS 操作系统或 Windows 这样的 GUI 界面的强大功能,初始反应是合理的。然而,更好的方法是把终端看作是你正在工作的环境的“高级设置”:云端、机器学习或编程。如果需要进行高级任务,这就是执行它们的方式。因此,掌握 Linux 终端的能力可以极大地增强任何技能集。此外,在大多数情况下,开发云 shell 环境是一个更好的主意,这需要熟悉 Bash 和 Linux。
大多数服务器现在运行 Linux;许多新的部署选项使用容器,这些容器也运行 Linux。MacOS 操作系统的终端与 Linux 非常接近,特别是如果您安装了像Homebrew这样的第三方工具。您应该了解 Bash 终端,本节将为您提供足够的知识,以使您能够胜任它。
终端学习的关键和最小组件是什么?这些组件包括使用基于云的 shell 开发环境、Bash shell 和命令、文件和导航、输入/输出、配置和编写脚本。所以让我们深入研究每一个主题。
云端 shell 开发环境
无论你是刚接触云计算还是有数十年经验,从个人工作站转向基于 Web 的云 Shell 开发环境都是值得的。一个很好的类比是想每天在海滩冲浪的冲浪者。理论上,他们可以每天往返 50 英里去海滩,但这样做会非常不方便、低效和昂贵。如果可能的话,更好的策略是住在海滩上,每天早上起床,走到海滩上冲浪。
同样,云开发环境解决了多个问题:它更安全,因为您无需传递开发人员密钥。许多问题在本地机器上难以解决,因为您可能需要频繁地在本地机器和云之间传输大量数据。云开发环境提供的工具包括深度集成,这使工作更高效。与搬到海滩不同,云开发环境是免费的。所有主要云服务提供商都在免费层上提供其云开发环境。如果您对这些环境不熟悉,我建议从 AWS Cloud 平台开始。在 AWS 上开始的两个选项。第一个选项是 AWS CloudShell,如图 2-1 所示。
AWS CloudShell 是一个带有独特 AWS 命令完成功能的 Bash Shell。如果您经常使用 AWS CloudShell,建议编辑~/.bashrc以自定义您的体验。为此,您可以使用内置的vim编辑器。许多人推迟学习vim,但在云 Shell 时代,他们必须精通它。您可以参考官方 vim FAQ了解如何完成任务。

图 2-1. AWS CloudShell
AWS 的第二个选项是 AWS Cloud9 开发环境。AWS CloudShell 和 AWS Cloud9 环境之间的一个关键区别是它是开发软件解决方案的更全面的方式。例如,您可以在图 2-2 中看到一个 Shell 和一个 GUI 编辑器,用于多种语言的语法高亮,包括 Python、Go 和 Node。

图 2-2. AWS Cloud9 开发环境
特别是在开发机器学习微服务时,Cloud9 环境非常理想,因为它允许您从控制台向部署的服务发起 Web 请求,并与 AWS Lambda 深度集成。另一方面,如果您使用其他平台,如 Microsoft Azure 或 Google Cloud,同样的概念适用,即基于云的开发环境是构建机器学习服务的理想场所。
Note
我创建了一个名为“云计算的 Bash 基础知识”的可选视频资源,可以带您了解基础知识。您可以在O’Reilly 平台或Pragmatic AI Labs YouTube 频道上观看。
Bash Shell 和 Commands
Shell 是一个交互式环境,包含一个提示符和运行命令的能力。大多数 shell 今天运行的是 Bash 或 ZSH。
在你通常用于开发的环境中,有几件立即有价值的事情是安装 ZSH 和vim配置。对于vim,一个推荐的设置是awesome vim,而对于 ZSH,则有ohmyzsh。
什么是“shell”?最终它是一个像 MacOS Finder 一样控制计算机的用户界面。作为 MLOps 从业者,了解如何使用最强大的用户界面——命令行对于处理数据的人来说是值得的。以下是你可以做的一些事情。
列出文件
使用 shell,你可以通过ls命令列出文件。标志-l添加额外的列表信息:
bash-3.2$ ls -l
total 11
drwxrwxr-x 130 root admin 4160 Jan 20 22:00 Applications
drwxr-xr-x 75 root wheel 2400 Dec 14 23:13 Library
drwxr-xr-x@ 9 root wheel 288 Jan 1 2020 System
drwxr-xr-x 6 root admin 192 Jan 1 2020 Users
运行命令
在 GUI 中,你点击一个按钮或打开一个应用程序来工作。在 shell 中,你运行一个命令。在 shell 中有许多有用的内置命令,它们通常可以很好地配合使用。例如,找出 shell 可执行文件的位置的一个绝佳方法是使用which。这里是一个示例:
bash-3.2$ which ls
/bin/ls
注意,ls命令在/bin目录中。这个“提示”表明我可以在这个目录中找到其他可执行文件。这里是/bin/中可执行文件的计数(管道操作符|稍后将会解释,但简言之,它接受来自另一个命令的输入):
bash-3.2$ ls -l /bin/ | wc -l
37
文件和导航
在 GUI 中,你打开一个文件夹或文件;在 shell 中,你使用命令来完成相同的事情。
pwd显示了你所在位置的完整路径:
bash-3.2$ pwd
/Users/noahgift
cd切换到一个新目录:
bash-3.2$ cd /tmp
输入/输出
在前面的示例中,ls的输出重定向到另一个命令。管道是输入和输出操作的一个示例,用于完成更复杂的任务。通常使用 shell 将一个命令的输出传递到另一个命令中。
这里有一个示例,显示了一个具有重定向和管道的工作流程。首先,单词“foo bar baz”被重定向到一个名为out.txt的文件。接下来,这个文件的内容通过cat打印出来,然后它们被管道到命令wc中,它可以通过-w计算单词数或通过-c计算字符数:
bash-3.2$ cd /tmp
bash-3.2$ echo "foo bar baz" > out.txt
bash-3.2$ cat out.txt | wc -c
12
bash-3.2$ cat out.txt | wc -w
3
这里有另一个示例,将shuf命令的输出定向到一个新文件。你可以从我的GitHub 仓库下载该文件。shuf可执行文件可以在限制行数的情况下对文件进行“洗牌”。在这种情况下,它获取一个接近 1GB 的文件,并且获取其前 10 万行,然后使用>运算符输出一个新文件。
bash-3.2$ time shuf -n 100000 en.openfoodfacts.org.products.tsv >\
10k.sample.en.openfoodfacts.org.products.tsv
1.89s user 0.80s system 97% cpu 2.748 total
使用像这样的 shell 技术可以在处理笔记本电脑上太大而无法处理数据科学库的 CSV 文件时派上用场。
配置
Shell 配置文件(ZSH 和 Bash)存储的设置会在每次打开终端时调用。正如我之前提到的,推荐在基于云的开发环境中定制你的 Bash 环境。对于 ZSH 来说,一个绝佳的起点是.zshrc,而对于 Bash 来说,则是.bashrc。下面是我在 MacOS 笔记本上存储在.zshrc配置中的一个示例。首先是一个别名,允许我一键输入命令flask-azure-ml,cd到一个目录,并启动一个 Python 虚拟环境。第二部分是我导出 AWS 命令行工具变量的地方,这样我就能够进行 API 调用:
## Flask ML Azure
alias flask-azure-ml="/Users/noahgift/src/flask-ml-azure-serverless &&\
source ~/.flask-ml-azure/bin/activate"
## AWS CLI
export AWS_SECRET_ACCESS_KEY="<key>"
export AWS_ACCESS_KEY_ID="<key>"
export AWS_DEFAULT_REGION="us-east-1"
总结一下,我建议为你的笔记本和基于云的开发环境定制你的 shell 配置文件。这样的小投入在你构建自动化到常规工作流程中时会带来巨大的回报。
写一个脚本
想要写你的第一个 shell 脚本可能会有点令人望而却步。其语法比 Python 复杂得多,有一些奇怪的字符。幸运的是,在许多方面,它更容易上手。写 shell 脚本的最佳方式是将一个命令放入文件中,然后运行它。这里有一个很好的“hello world”脚本示例。
第一行被称为“shebang”行,告诉脚本使用 Bash。第二行是一个 Bash 命令,echo。Bash 脚本的好处在于你可以在其中粘贴任何你想要的命令。这一点使得即使对编程了解不多,也能轻松地实现小任务的自动化:
#!/usr/bin/env bash
echo "hello world"
接下来,你可以使用chmod命令设置可执行标志,使得这个脚本可以执行。最后,通过追加./来运行它:
bash-3.2$ chmod +x hello.sh
bash-3.2$ ./hello.sh
Hello World
Shell 的主要要点是你必须至少具备一些基本的技能来进行 MLOps。然而,入门是相对容易的,在你意识到之前,通过 shell 脚本的自动化和 Linux 命令行的使用,你可以显著改善你每天的工作。接下来,让我们开始概述云计算的基本要素。
云计算基础和构建模块
可以肯定地说,几乎所有形式的机器学习都在某种形式上需要云计算。云计算中的一个关键概念是接近无限的资源,正如《云上之云:加州大学伯克利关于云计算的视角》中所描述的那样。没有云计算,许多机器学习模型根本无法实现。例如,《无穷力量:微积分如何揭示宇宙的秘密》(Mariner Books)的作者斯蒂芬·斯特罗格茨(Stephen Strogatz)提到,“通过恰到好处地运用无穷大,微积分可以揭示宇宙的秘密。”几个世纪以来,像求解圆形的形状这样的具体问题,如果没有微积分处理无限大的数字是不可能的。云计算也是如此;在机器学习中,特别是模型的操作化方面,许多问题如果没有云计算是无法解决的。正如图 2-3 所示,云计算提供接近无限的计算和存储,且在数据不移动的情况下处理数据。

图 2-3. 云计算利用接近无限的计算和数据资源
现在我们知道,利用计算机能力在不移动数据的情况下,通过像 AWS SageMaker 或 Azure ML Studio 这样的机器学习平台,使用接近无限的资源,是云计算的杀手级特性,这是无法在没有云计算的情况下复制的。伴随这一杀手级特性的是我所说的“自动化定律”。一旦公众开始讨论某个行业的自动化——如自动驾驶汽车、IT、工厂、机器学习——最终这种自动化都会发生。
这个概念并不意味着会有一只神奇的独角兽出现,撒上仙女们的魔尘,然后项目变得更加容易管理;而是人类集体很擅长发现趋势。例如,当我还是个十几岁的电视行业工作者时,只有“线性”编辑的概念。这种工作流程意味着你需要三种不同的磁带才能实现到一个黑屏的溶解效果——源磁带、编辑主磁带以及一个包含黑色素材的第三个磁带。
我记得人们曾经谈论过如何不断更换新磁带是多么繁重的工作,以及如果这种工作流程能够自动化将会多么美妙。后来,通过非线性编辑的引入,这确实变得完全自动化了。这项技术允许你存储材料的数字副本,并对镜头进行数字处理,而不是像线性磁带那样插入新材料。在 1990 年代初,这些非线性编辑系统的成本高达数十万美元。现在,我在成千上万的磁带存储容量足够的千元笔记本电脑上进行更复杂的编辑工作。
在 2000 年代初期,云计算也出现了同样的情景。我在许多公司工作时,它们都使用由维护团队运营的自己的数据中心。当云计算的最初组成部分出现时,许多人说,“我打赌将来公司可以在他们的笔记本电脑上控制整个数据中心。”许多专家数据中心技术人员对自己的工作会受到自动化影响的想法不屑一顾,然而,自动化法则再次发挥了作用。到 2020 年以及以后,大多数公司都使用某种形式的云计算,而新的工作涉及利用这种力量。
同样地,通过 AutoML 自动化机器学习是一个非平凡的进步,它能够更快地创建模型,具有更高的准确性和更好的可解释性。因此,数据科学行业的工作将发生变化,就像编辑和数据中心操作员的工作发生了变化。
注意
AutoML 是机器学习建模方面的自动化。AutoML 的一个简单直观的例子是一个执行线性回归的 Excel 电子表格。您告诉 Excel 要预测的列是哪一列,然后哪一列是特征。
更复杂的 AutoML 系统工作方式类似。您选择要预测的值,例如图像分类、数值趋势、分类文本分类或者聚类。然后 AutoML 软件系统执行许多数据科学家会执行的相同技术,包括超参数调整、算法选择和模型可解释性。
所有主要的云平台都集成了 AutoML 工具到 MLOps 平台中。因此,AutoML 是所有基于云的机器学习项目的选择,并且越来越成为它们的另一种生产力提升。
Tyler Cowen 是一位经济学家、作家,是彭博社的专栏作家,并且从小就参与竞技棋类比赛。在他的书《平均水平是如何被打败的》(Plume)中,Cowen 提到,象棋软件最终击败了人类,也证明了自动化法则的实施。然而,令人惊讶的是,在 Cowen 书的结尾处,专家人类与象棋软件一起击败了单独的象棋软件。最终,这个故事可能会在机器学习和数据科学领域发生。自动化可能会取代简单的机器学习任务,并使领域专家人类控制的 ML 自动化命令效果提高数倍。
开始使用云计算
推荐开始使用云计算的方法是建立一个多云开发环境,如在O'Reilly 视频课程:使用 Python 进行云计算中所示。这段视频是本节的绝佳伴侣,但不需要跟随。一个多云环境的基本结构显示,云壳是所有这些云共同拥有的东西,如在#Figure-2-4 中所示。
使用 GitHub 或类似服务的源代码控制存储库是最初所有三个云环境通信的中心位置。AWS、Azure 和 GCP 每个都有通过云 Shell 进行基于云的开发环境。在第一章中,一个必要的 Python 脚手架展示了开发可重复和可测试结构的优势。通过 GitHub Actions 的 CI/CD(持续集成/持续交付)流程确保 Python 代码运行良好且质量高。

图 2-4. 启动云计算
注意
测试和审查 Python 代码是验证软件项目质量的过程。开发人员会在本地运行测试和审查来帮助保持软件质量的高水平。这个过程类似于当你想要清洁房间时打开的机器人吸尘器(robovac)。Robovac 是一个有用的助手,可以保持你的房间处于良好状态,而运行代码审查和测试则可以保持你的代码处于良好状态。
CI/CD 流水线在发布到另一个生产环境之前在外部环境中运行这些质量控制检查,以确保应用程序正常工作。这条流水线允许软件部署变得可重复和可靠,是现代软件工程的最佳实践——另一个这些软件工程最佳实践的名称是 DevOps。
如果你正在学习云计算,同时使用三大云服务商是熟悉云计算的一个好方法。这种跨云工作流有助于巩固知识。毕竟,事物的名称可能不同,但概念是相同的。如果你需要帮助理解某些术语,请参考附录 A。
接下来我们深入研究 Python,这是 DevOps、云计算、数据科学和机器学习的事实上语言。
Python Crash Course
Python 占主导地位的一个关键原因是该语言是为开发者而非计算机进行优化的。像 C 或 C++ 这样的语言因为是“低级”的,所以具有出色的性能,但开发者在解决问题时必须付出更多努力。例如,C 程序员必须分配内存、声明类型并编译程序。而 Python 程序员则可以输入一些命令并运行它们,通常在 Python 中的代码量明显较少。
尽管如此,Python 的性能比 C、C#、Java 和 Go 慢得多。此外,Python 本身存在一些语言限制,包括缺乏真正的线程,缺乏 JIT(即时编译器),以及缺乏像 C# 或 F# 中的类型推断。然而,随着云计算的发展,语言性能不再限制很多问题。因此,可以说 Python 的性能之所以出现了偶然的好运,是因为两个原因:云计算和容器。使用云计算,设计完全分布式,基于 AWS Lambda 和 AWS SQS(简单队列服务)等技术进行构建。类似地,像 Kubernetes 这样的容器技术也在构建分布式系统方面发挥着重要作用,因此 Python 线程突然变得无关紧要。
注意
AWS Lambda 是一种函数即服务(FaaS)技术,运行在 AWS 平台上。它被称为 FaaS,因为 AWS Lambda 函数可以仅仅是几行代码——确实是一个函数。这些函数可以附加到像云排队系统 Amazon SQS 或上传到 Amazon S3 对象存储的图像等事件上。
把云看作一个操作系统的一种方式是合理的。上世纪 80 年代中期,Sun Computer 使用了营销口号“网络即计算机”。这个口号在 1980 年可能为时过早,但在 2021 年却非常准确。例如,与其在单台机器上创建线程,你可以在云中生成行为类似操作系统的 AWS Lambda 函数,它拥有无限可扩展的资源。
在我参加的一次谷歌演讲中,退休的伯克利计算机科学教授兼 TPU(TensorFlow Processing Unit)的共同创造者 Dr. Patterson 提到,Python 在图 2-5 中显示的等效矩阵操作方面比 C 慢了 64,000 倍。这个事实除了缺乏真正的线程外还有这个。

图 2-5. Python 性能比 C 中的矩阵操作慢 64,000 倍
同时,一篇研究论文“跨编程语言的能效”显示,Python 中的许多操作比 C 中的等效操作消耗的能量多 50 倍。这项能效研究也将 Python 列为能源效率最差的语言之一,与其他语言相比执行任务时需要的能量,如图 2-6 所示。随着 Python 成为全球最流行的语言之一,这引发了对其是否更像煤炭发电厂而不是绿色能源太阳能系统的担忧。最终,Python 可能需要再次挽救能源消耗的解决方案可能是由一家主要的云供应商积极构建一个利用现代计算机科学技术的新运行时来利用 JIT 编译器。
数据科学编程的新手们常见的一个技术障碍是他们采用传统的计算机科学学习编码的方法。例如,云计算和机器学习与传统的软件工程项目(如通过 GUI 开发用户界面应用程序)截然不同。相反,大部分云计算和机器学习的世界都涉及编写小型函数。大部分时间,这些 Python 的其他部分是不需要的,即面向对象的代码,这些会让新手感到望而却步。

图 2-6. Python 语言在能源消耗方面是最严重的罪魁祸首之一
计算机科学的主题包括并发、面向对象编程、元编程和算法理论。不幸的是,学习这些主题与云计算和数据科学中大部分编程所需的编程风格正交。并不是这些主题没有价值;它们对于平台、库和工具的创建者是有益的。如果你最初不是“创建”库和框架而是“使用”库和框架,那么你可以安全地忽略这些高级主题,专注于函数的使用。
这种速成课程方法暂时忽略了他人使用的代码的创建者,而是更倾向于代码和库的使用者,即数据科学家或 MLOps 从业者。这个简短的速成课程是为使用者设计的,大多数人在数据科学领域的职业生涯中会花费时间。学完这些主题后,如果你感兴趣,你将拥有坚实的基础,可以进一步学习更复杂的以计算机科学为重点的主题。这些高级主题对于立即在 MLOps 中高效工作并不是必需的。
极简主义 Python 教程
如果你想学习开始使用最少量的 Python,你需要知道什么?Python 的两个最基本组成部分是语句和函数。所以让我们从 Python 语句开始。Python 语句是对计算机的一条指令;即类似于告诉一个人“你好”,你可以告诉计算机“打印 hello”。以下示例是 Python 解释器。请注意,示例中的“语句”是短语 print("Hello World"):
Python 3.9.0 (default, Nov 14 2020, 16:06:43)
[Clang 12.0.0 (clang-1200.0.32.27)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello World")
Hello World
使用 Python,你也可以用分号将两个语句链接在一起。例如,我导入了 os 模块,其中有一个我想要使用的函数 os.listdir,然后我调用它来列出我所在目录的内容:
>>> import os;os.listdir(".")
['chapter11', 'README.md', 'chapter2', '.git', 'chapter3']
这种模式在数据科学笔记本中非常普遍,这是你开始使用 Python 所需知道的一切。我建议首先在 Python 或 IPython 中尝试一些东西,或者使用 Jupyter REPL 作为熟悉 Python 的第一步。
第二件要了解的事情是如何在 Python 中编写和使用函数。让我们在下面的示例中做到这一点。这个例子是一个将两个数字x和y相加的两行函数。Python 函数的整个目的是作为一个“工作单位”。例如,厨房里的烤面包机作为一个工作单位。它接受面包作为输入,加热面包,然后返回烤面包。同样,一旦我写了add函数,我可以用新的输入多次使用它:
>>> def add(x,y):
... return x+y
...
>>> add(1,1)
2
>>> add(3,4)
7
>>>
让我们汇集我们的知识并构建一个 Python 脚本。在 Python 脚本的开头,有一行 shebang 行,就像在 Bash 中一样。接下来,导入choices库。稍后在“循环”中,该模块用于向add函数发送随机数:
#!/usr/bin/env python
from random import choices
def add(x,y):
print(f"inside a function and adding {x}, {y}")
return x+y
#Send random numbers from 1-10, ten times to the add function
numbers = range(1,10)
for num in numbers:
xx = choices(numbers)[0]
yy = choices(numbers)[0]
print(add(xx,yy))
脚本需要通过运行chmod +x add.py来使其可执行,就像 Bash 脚本一样:
bash-3.2$ ./add.py
inside a function and adding 7, 5
12
inside a function and adding 9, 5
14
inside a function and adding 3, 1
4
inside a function and adding 7, 2
9
inside a function and adding 5, 8
13
inside a function and adding 6, 1
7
inside a function and adding 5, 5
10
inside a function and adding 8, 6
14
inside a function and adding 3, 3
6
你可以更深入地了解 Python,但像这里展示的例子一样“玩弄”是从“零到一”的最快方式。因此,让我们继续另一个话题,程序员的数学,并以一种极简的方式解决它。
程序员的数学速成课程
数学可能既令人畏惧又令人恼火,但理解基础知识对于使用机器学习至关重要。因此,让我们来解决一些有用且基本的概念。
描述统计和正态分布
世界上的许多事物都是“正态”分布的。一个很好的例子是身高和体重。如果你绘制世界上每个人的身高,你会得到一个“钟形”分布。这种分布直观地表明,你遇到的大多数人都是平均身高,见到七英尺高的篮球运动员是不寻常的。让我们浏览包含 25,000 条 19 岁儿童身高和体重记录的Jupyter 笔记本:
In [0]:
import pandas as pd
In [7]:
df = pd.read_csv("https://raw.githubusercontent.com/noahgift/\
regression-concepts/master/height-weight-25k.csv")
Out[7]:
IndexHeight-InchesWeight-Pounds
01 65.78331 112.9925
12 71.51521 136.4873
23 69.39874 153.0269
34 68.21660 142.3354
45 67.78781 144.2971
接下来的一个图表,显示在#Figure-2-7 中,展示了身高和体重之间的线性关系,这是我们大多数人直觉上知道的。你越高,你就越重:
In [0]:
import seaborn as sns
import numpy as np
In [9]:
sns.lmplot("Height-Inches", "Weight-Pounds", data=df)

图 2-7. 身高和体重
数据集中可视化数据的步骤称为“探索性数据分析”。总体思路是使用数学和可视化“四处看看”。下一步是查看这个“正态分布”的描述统计。
在 Pandas 中,可以通过使用 df.describe() 获得这些描述性统计数据。考虑描述性统计数据的一种方法是将其视为“数值上看到”的方式,就像眼睛所看到的视觉效果一样。例如,第 50 百分位数或中位数显示了代表确切中间身高的数字。此值约为 68 英寸。此数据集中的最大统计值为 75 英寸。最大值代表了极端观测或数据集中测量的最高个人。在正态分布的数据集中,最大观测是罕见的,就像最小观测一样。您可以在 图 2-8 中看到这一趋势。Pandas 中的 DataFrame 提供了一个 describe 方法,调用该方法将给出一整套描述性统计数据:
In [10]: df.describe()

图 2-8. 身高/体重描述性统计
要可视化身高和体重的正态钟形分布最好的方法之一是使用核密度图:
In [11]:
sns.jointplot("Height-Inches", "Weight-Pounds", data=df, kind="kde");
体重和身高都显示出“钟形分布”。极端值很少,大多数值位于中间,如 图 2-9 所示。

图 2-9. 身高和体重的核密度图
机器学习在很大程度上建立在正态分布的理念上,具备这种直觉对于构建和维护机器学习模型至关重要。然而,需要注意的是,其他分布超出了正态分布,这使得世界更难以建模。一个很好的例子是作者纳西姆·塔勒布所称的“大尾巴”,即难以预测且罕见的事件,这些事件显著影响世界。
注意
对于过度自信地模拟世界的危险,还可以在史蒂文·库宁博士的书《不安定》(BenBella Books)中找到。我曾与库宁博士在加州理工学院管理层工作时合作过,发现他是一位充满热情的科学家,和他随意交谈是一种愉快的体验。以下是他书中关于建模的一句引用:
由于我们对控制物质和能量的物理定律有非常牢固的理解,很容易陷入这样的幻想,即我们只需将当前大气和海洋状态输入计算机,对未来的人为和自然影响进行一些假设,就能准确预测未来数十年的气候。不幸的是,这只是一个幻想,你可以从天气预报中推断出,它们只能准确预测出两周左右的时间。
优化
机器学习中一个基本问题是优化的概念。优化是找到问题的最佳或足够好的解决方案的能力。梯度下降是深度学习核心的优化算法。梯度下降的目标是转向全局最小值,即最优解,而不是陷入局部最小值。这个算法的直觉相对简单,如果你想象在黑暗中下山,全局最小值的解决方案意味着你安全地从山底下来。局部最小值意味着你意外地走进了山脚下 1,000 英尺处的湖中。
让我们通过优化问题的示例来详细说明。开始观察与优化相关的符号类型是一个很好的起点。创建模型时,你需要理解 Python 标记代数表达式的方法。在图 2-10 的快速总结中,比较了电子表格、代数和 Python 之间的术语。一个关键的要点是你可以在白板上、Excel 中或使用少量代码完成相同的工作。

图 2-10. 符号是相对的
现在,让我们看一下正确找零的解决方案。你可以在GitHub 上找到这个解决方案的代码。这个代码示例的一般思路是选择一种贪婪的方法来做找零。贪婪算法总是优先选择最好的选项。如果你不关心完美的解决方案,或者找到完美的解决方案是不可能的,但你可以接受一个“足够好”的解决方案,那么这种算法也会很有效。在这种情况下,它将是使用面值最高的硬币来进行找零,然后转向下一个最高面值的硬币:
python change.py --full 1.34
Quarters 5: , Remainder: 9
Dimes 0: , Remainder: 0
Nickles 1: , Remainder: 4
Pennies 4:
下面是核心部分的代码,用于进行贪婪匹配。注意,一个递归函数解决了每次迭代的问题,因为大面值的硬币最终用完了。接下来,算法找到了中面值的硬币,然后用完了;最后,它转向了最小面值的硬币:
def recursive_change(self, rem):
"""Greedy Coin Match with Recursion
>>> c = Change(.71)
>>> c.recursive_change(c.convert)
2 quarters
2 dimes
1 pennies
[1, 0, 2, 2]
"""
if len(self.coins) == 0:
return []
coin = self.coins.pop()
num, new_rem = divmod(rem, coin)
self.printer(num,coin)
return self.recursive_change(new_rem) + [num]
尽管表达算法的方式有很多种,但基本思想是一样的。如果不知道如何找到“完美”的解决方案,那么当面对选择时,总是选择最佳的选项是合适的答案。直观地说,这就像走向城市的对角目的地,每次前方的信号灯变红时,你都选择直走或向右转。
这里是该算法的一系列测试。它们展示了算法的表现,这在测试涉及优化解决方案时通常是个好主意:
#!/usr/bin/env python2.5
#Noah Gift
#Greedy Coin Match Python
import unittest
import change
class TestChange(unittest.TestCase):
def test_get_quarter(self):
c = change.Change(.25)
quarter, qrem, dime, drem, nickel, nrem, penny =\
c.make_change_conditional()
self.assertEqual(quarter,1) #quarters
self.assertEqual(qrem, 0) #quarter remainder
def test_get_dime(self):
c = change.Change(.20)
quarter, qrem, dime, drem, nickel, nrem, penny =\
c.make_change_conditional()
self.assertEqual(quarter,0) #quarters
self.assertEqual(qrem, 20) #quarter remainder
self.assertEqual(dime, 2) #dime
self.assertEqual(drem, 0) #dime remainder
def test_get_nickel(self):
c = change.Change(.05)
quarter, qrem, dime, drem, nickel, nrem, penny =\
c.make_change_conditional()
self.assertEqual(dime, 0) #dime
self.assertEqual(drem, 0) #dime remainder
self.assertEqual(nickel, 1) #nickel
self.assertEqual(nrem, 0) #nickel remainder
def test_get_penny(self):
c = change.Change(.04)
quarter, qrem, dime, drem, nickel, nrem, penny =\
c.make_change_conditional()
self.assertEqual(penny, 4) #nickel
def test_small_number(self):
c = change.Change(.0001)
quarter, qrem, dime, drem, nickel, nrem, penny =\
c.make_change_conditional()
self.assertEqual(quarter,0) #quarters
self.assertEqual(qrem, 0) #quarter remainder
self.assertEqual(dime, 0) #dime
self.assertEqual(drem, 0) #dime remainder
self.assertEqual(nickel, 0) #nickel
self.assertEqual(nrem, 0) #nickel remainder
self.assertEqual(penny, 0) #penny
def test_large_number(self):
c = change.Change(2.20)
quarter, qrem, dime, drem, nickel, nrem, penny =\
c.make_change_conditional()
self.assertEqual(quarter, 8) #nickel
self.assertEqual(qrem, 20) #nickel
self.assertEqual(dime, 2) #nickel
self.assertEqual(drem, 0) #nickel
def test_get_quarter_dime_penny(self):
c = change.Change(.86)
quarter, qrem, dime, drem, nickel, nrem, penny =\
c.make_change_conditional()
self.assertEqual(quarter,3) #quarters
self.assertEqual(qrem, 11) #quarter remainder
self.assertEqual(dime, 1) #dime
self.assertEqual(drem, 1) #dime remainder
self.assertEqual(penny, 1) #penny
def test_get_quarter_dime_nickel_penny(self):
c = change.Change(.91)
quarter, qrem, dime, drem, nickel, nrem, penny =\
c.make_change_conditional()
self.assertEqual(quarter,3) #quarters
self.assertEqual(qrem, 16) #quarter remainder
self.assertEqual(dime, 1) #dime
self.assertEqual(drem, 6) #dime remainder
self.assertEqual(nickel, 1) #nickel
self.assertEqual(nrem, 1) #nickel remainder
self.assertEqual(penny, 1) #penny
if __name__ == "__main__":
unittest.main()
接下来,让我们在以下问题中进一步探讨贪婪算法。优化中最为研究的问题之一是旅行推销员问题。你可以在GitHub 上找到源代码。这个例子是一个routes.py文件中路线的列表,显示了旧金山湾区不同公司之间的距离。
这是一个没有完美解决方案的极好示例,但是存在一个足够好的解决方案。一般性问题问的是:“如何能够旅行到一系列城市并且最小化距离?”
一种方法是使用“贪婪”算法。它在每个选择时都选择正确的解决方案。通常这可能导致足够好的答案。在这个特定的例子中,每次随机选择一个城市作为起点。该示例添加了模拟选择最短距离的能力。模拟的使用者可以模拟尽可能多次数。最小的总长度是最佳答案。以下是在进入 TSP 算法处理之前输入看起来像的样本:
values = [
("AAPL", "CSCO", 14),
("AAPL", "CVX", 44),
("AAPL", "EBAY", 14),
("AAPL", "GOOG", 14),
("AAPL", "GPS", 59),
("AAPL", "HPQ", 14),
("AAPL", "INTC", 8),
("AAPL", "MCK", 60),
("AAPL", "ORCL", 26),
("AAPL", "PCG", 59),
("AAPL", "SFO", 46),
("AAPL", "SWY", 37),
("AAPL", "URS", 60),
("AAPL", "WFC", 60),
让我们运行脚本。首先,请注意它作为输入的完整模拟运行:
#!/usr/bin/env python
"""
Traveling salesman solution with random start and greedy path selection
You can select how many iterations to run by doing the following:
python greedy_random_start.py 20 #runs 20 times
"""
import sys
from random import choice
import numpy as np
from routes import values
dt = np.dtype([("city_start", "S10"), ("city_end", "S10"), ("distance", int)])
data_set = np.array(values, dtype=dt)
def all_cities():
"""Finds unique cities
array([["A", "A"],
["A", "B"]])
"""
cities = {}
city_set = set(data_set["city_end"])
for city in city_set:
cities[city] = ""
return cities
def randomize_city_start(cities):
"""Returns a randomized city to start trip"""
return choice(cities)
def get_shortest_route(routes):
"""Sort the list by distance and return shortest distance route"""
route = sorted(routes, key=lambda dist: dist[2]).pop(0)
return route
def greedy_path():
"""Select the next path to travel based on the shortest, nearest path"""
itinerary = []
cities = all_cities()
starting_city = randomize_city_start(list(cities.keys()))
# print "starting_city: %s" % starting_city
cities_visited = {}
# we want to iterate through all cities once
count = 1
while True:
possible_routes = []
# print "starting city: %s" % starting_city
for path in data_set:
if starting_city in path["city_start"]:
# we can't go to cities we have already visited
if path["city_end"] in cities_visited:
continue
else:
# print "path: ", path
possible_routes.append(path)
if not possible_routes:
break
# append this to itinerary
route = get_shortest_route(possible_routes)
# print "Route(%s): %s " % (count, route)
count += 1
itinerary.append(route)
# add this city to the visited city list
cities_visited[route[0]] = count
# print "cities_visited: %s " % cities_visited
# reset the starting_city to the next city
starting_city = route[1]
# print "itinerary: %s" % itinerary
return itinerary
def get_total_distance(complete_itinerary):
distance = sum(z for x, y, z in complete_itinerary)
return distance
def lowest_simulation(num):
routes = {}
for _ in range(num):
itinerary = greedy_path()
distance = get_total_distance(itinerary)
routes[distance] = itinerary
shortest_distance = min(routes.keys())
route = routes[shortest_distance]
return shortest_distance, route
def main():
"""runs everything"""
if len(sys.argv) == 2:
iterations = int(sys.argv[1])
print("Running simulation %s times" % iterations)
distance, route = lowest_simulation(iterations)
print("Shortest Distance: %s" % distance)
print("Optimal Route: %s" % route)
else:
# print "All Routes: %s" % data_set
itinerary = greedy_path()
print("itinerary: %s" % itinerary)
print("Distance: %s" % get_total_distance(itinerary))
if __name__ == "__main__":
main()
让我们运行这个“贪婪”算法 25 次。请注意,它找到了一个“好”的解决方案为 129。在更广泛的坐标集中,这个版本可能是最优解决方案,但它已足够作为我们的路线规划起点:
> ./greedy-random-tsp.py 25
Running simulation 25 times
Shortest Distance: 129
Optimal Route: [(b'WFC', b'URS', 0), (b'URS', b'GPS', 1),\
(b'GPS', b'PCG', 1), (b'PCG', b'MCK', 3), (b'MCK', b'SFO', 16),\
(b'SFO', b'ORCL', 20), (b'ORCL', b'HPQ', 12), (b'HPQ', b'GOOG', 6),\
(b'GOOG', b'AAPL', 11), (b'AAPL', b'INTC', 8), (b'INTC', b'CSCO', 6),\
(b'CSCO', b'EBAY', 0), (b'EBAY', b'SWY', 32), (b'SWY', b'CVX', 13)]
请注意,如果我只运行一次模拟,它随机选择了一个较差的距离为 143:
> ./greedy-random-tsp.py 1
Running simulation 1 times
Shortest Distance: 143
Optimal Route: [(b'CSCO', b'EBAY', 0), (b'EBAY', b'INTC', 6),\
(b'INTC', b'AAPL', 8), (b'AAPL', b'GOOG', 14), (b'GOOG', b'HPQ', 6),\
(b'HPQ', b'ORCL', 12), (b'ORCL', b'SFO', 20), (b'SFO', b'MCK', 16),\
(b'MCK', b'WFC', 2), (b'WFC', b'URS', 0), (b'URS', b'GPS', 1),\
(b'GPS', b'PCG', 1), (b'PCG', b'CVX', 44), (b'CVX', b'SWY', 13)]
请注意在图 2-11 中,我如何在真实场景中运行代码的多次迭代来“尝试想法”。如果数据集非常庞大而且我很匆忙,我可能只进行几次模拟,但如果我要离开一整天,我可能会让它运行 1,000 次,并在第二天早上回来时完成。在地理坐标数据集中可能存在许多局部最小值,即问题的解决方案并没有完全达到全局最小值或最优解。

图 2-11. TSP 模拟
优化是我们生活的一部分,我们使用贪婪算法来解决日常问题,因为它们直观。优化也是机器学习如何使用梯度下降算法的核心。机器学习问题通过梯度下降算法迭代地朝向局部或全局最小值,如图 2-12 所示。

图 2-12. 优化
接下来,让我们深入了解机器学习的核心概念。
机器学习关键概念
机器学习是计算机在没有显式编程的情况下执行任务的能力。它们通过从数据中“学习”来实现这一点。正如之前讨论的,一个好的直觉是基于身高来预测体重的机器学习模型。它可以从 25,000 次观察中“学习”,然后进行预测。
机器学习涉及三个类别:监督学习、无监督学习和强化学习。监督学习是指“标签”已知,模型从历史数据中学习。在前面的例子中,身高和体重就是标签。另外,这 25,000 条观测数据是历史数据的一个例子。需要注意的是,所有的机器学习都要求数据以数值形式存在,并且需要进行缩放。想象一下,如果朋友吹嘘他跑了 50。他是指 50 英里还是 50 英尺?幅度是在处理预测之前缩放数据的原因。
无监督机器学习致力于“发现”标签。理解这如何运作的一个好直觉是考虑 NBA 赛季。在显示在图 2-15 中的可视化中,计算机“学会”了如何将不同的 NBA 球员分组。在这种情况下,由领域专家(也就是我)选择适当的标签。该算法能够对群组进行聚类,其中一个我标记为“最佳”球员。
注意
作为篮球领域的专家,我随后增加了一个称为“最佳”的标签。然而,另一位领域专家可能会持不同意见,将这些球员称为“精英全能”或其他标签。聚类既是一门艺术也是一门科学。对于如何为聚类数据集进行标记,有一个理解权衡的领域专家可能是决定无监督机器学习预测是否有用的关键。

图 2-15. NBA 球员的聚类和分面 K 均值聚类
计算机根据四个属性的比较(得分、篮板、盖帽和助攻)对数据进行了分组。然后,在多维空间中,距离彼此最近的球员被分组形成一个标签。这种聚类算法就是为什么勒布朗·詹姆斯和凯文·杜兰特被分在一起的原因;他们有相似的指标。此外,斯蒂芬·库里和克里斯·保罗也很像,因为他们得分很高并且助攻很多。
注意
K 均值聚类的一个常见难题是如何选择正确的聚类数。这个问题的解决既是艺术也是科学,因为没有一个完美的答案。一种解决方案是使用一个框架为你创建肘部图,比如Yellowbrick用于 sklearn。
另一种 MLOps 风格的解决方案是让 MLOps 平台,比如 AWS Sagemaker,通过自动超参数调整来执行 K 均值聚类分配。
最后,通过强化学习,一个“代理人”探索环境以学习如何执行任务。例如,考虑一只宠物或一个小孩子。他们通过探索他们的环境来学会如何与世界互动。一个更具体的例子是 AWS DeepRacer 系统,它允许你训练一个模型车在赛道上行驶,如图 2-16 所示。

图 2-16. AWS DeepRacer
代理(即汽车)与赛道(即环境)进行交互。车辆通过赛道的每个部分移动,平台记录其在赛道上的位置数据。奖励函数决定了代理在每次通过赛道时的交互方式。随机性在训练这种类型的模型中起着重要作用,因此不同的奖励函数策略可能会产生不同的结果。
下面是为 AWS DeepRacer 编写的奖励函数示例,奖励车辆保持在中心线上:
def reward_function(params):
'''
Example of rewarding the agent for following the centerline
'''
# Read input parameters
track_width = params['track_width']
distance_from_center = params['distance_from_center']
# Calculate 3 markers that are at varying distances away from the centerline
marker_1 = 0.1 * track_width
marker_2 = 0.25 * track_width
marker_3 = 0.5 * track_width
# Give higher reward if the car is closer to centerline and vice versa
if distance_from_center <= marker_1:
reward = 1.0
elif distance_from_center <= marker_2:
reward = 0.5
elif distance_from_center <= marker_3:
reward = 0.1
else:
reward = 1e-3 # likely crashed/ close to off track
return float(reward)
这是另一个奖励函数,用于奖励代理车辆保持在赛道的两个边界内。这种方法与前一个奖励函数类似,但可能产生截然不同的结果:
def reward_function(params):
'''
Example of rewarding the agent for staying inside the two borders of the
track
'''
# Read input parameters
all_wheels_on_track = params['all_wheels_on_track']
distance_from_center = params['distance_from_center']
track_width = params['track_width']
# Give a very low reward by default
reward = 1e-3
# Give a high reward if no wheels go off the track and
# the agent is somewhere in between the track borders
if all_wheels_on_track and (0.5*track_width - distance_from_center) >= 0.05:
reward = 1.0
# Always return a float value
return float(reward)
在生产环境中进行机器学习需要本章介绍的基础知识,即了解使用哪种方法。例如,通过无监督机器学习发现标签对确定最佳付费客户非常有价值。同样,预测下个季度销售的单位数量可以通过监督机器学习方法完成,该方法使用历史数据创建预测。接下来,让我们深入探讨数据科学的基础知识。
进行数据科学
另一个需要掌握的基础技能是“数据科学方法”。我建议在教学中创建以下公式化结构的笔记本类:Ingest、EDA、Modeling 和 Conclusion。这种结构允许团队中的任何人快速切换到不同的项目部分,以便对其有所了解。此外,对于部署到生产环境的模型,旁边检查笔记本作为项目背后思考的README也是有益的。你可以在图 2-17 中看到这种示例。

图 2-17. Colab 笔记本
你可以在Colab 笔记本 Covid 数据科学中看到这种结构的示例。
这份笔记本各部分的清晰分解意味着每个部分都可以作为写数据科学书籍的“章节”。Ingest 部分受益于通过网页请求加载的数据源,即直接提供给 Pandas。其他人可以通过这种方法复制笔记本数据源,如图 2-18 所示。

图 2-18. Colab 笔记本结构
EDA 部分用于探索想法。数据出现了什么情况?这是找出答案的机会,正如使用 Plotly 在图 2-19 中显示的顶级 Covid 州所示。

图 2-19. Colab 笔记本 EDA
建模部分是模型所在的地方。随后,这种可重复性可能至关重要,因为 MLOps 流水线可能需要引用模型创建的方式。例如,您可以在这个Boston Housing Pickle Colab 笔记本中看到一个优秀的序列化 sklearn 模型的示例。请注意,我测试了这个模型最终将如何在 API 或基于云的系统中工作,就像这个Flask ML 部署项目一样。
结论部分应为决策业务领导做出决定的总结。最后,将您的项目提交到 GitHub 以构建您的 MLOps 作品集。在 ML 项目成熟时,增加这些额外的文档是值得的。特别是运营团队可能会发现理解模型为何投入生产以及决定移除不再合适的模型的原始思考非常有价值。
接下来,让我们逐步讨论构建 MLOps 流水线。
从零开始构建 MLOps 流水线
让我们将本章中的所有内容整合在一起,并深入探讨如何在 Azure 应用服务上部署 Flask 机器学习应用程序。请注意,在 图 2-20 中,GitHub 事件触发 Azure Pipelines 构建过程的构建,然后将更改部署到无服务器平台。尽管在其他云平台上名称不同,但在概念上这些事情在 AWS 和 GCP 中非常相似。

图 2-20. MLOps 概述
若要在本地运行它,请按以下步骤操作:
-
创建虚拟环境并激活:
python3 -m venv ~/.flask-ml-azure source ~/.flask-ml-azure/bin/activate -
运行
make install。 -
运行
python app.py。 -
在另一个 Shell 中运行
./make_prediction.sh。
在 Azure Pipelines 中运行它(参考Azure 官方文档指南):
-
启动 Azure Shell,如 图 2-21 所示。
![启动 Azure 云 Shell]()
图 2-21. 启动 Azure 云 Shell
-
创建一个启用了 Azure Pipelines 的 GitHub 仓库(可以是此仓库的分支),如 图 2-22 所示。
![创建带有 Azure Pipelines 的 GitHub 仓库]()
图 2-22. 创建带有 Azure Pipelines 的 GitHub 仓库
-
将仓库克隆到 Azure 云 Shell 中。
注意:
如果您需要关于如何设置 SSH 密钥的更多信息,请参阅 YouTube 视频指南,如设置 SSH 密钥和配置云 Shell 环境。
-
创建虚拟环境并激活:
python3 -m venv ~/.flask-ml-azure source ~/.flask-ml-azure/bin/activate -
运行
make install。 -
在 Cloud Shell 中创建一个应用服务,并首次部署您的应用,如 图 2-23 所示。
az webapp up -n <your-appservice>![Flask ML 服务]()
图 2-23. Flask ML 服务
-
通过浏览部署的网址验证部署的应用程序是否正常工作:
https://<your-appservice>.azurewebsites.net/。您将看到如 图 2-24 所示的输出。
![Flask 部署应用]()
图 2-24. Flask 部署应用
-
验证机器学习预测的工作性能,如 图 2-25 所示。
更改
make_predict_azure_app.sh中的一行以匹配部署预测-X POST https://<yourappname>.azurewebsites.net:$PORT/predict。![成功的预测]()
图 2-25. 成功的预测
-
创建一个 Azure DevOps 项目,并连接到 Azure(正如官方文档描述),如图 2-26 所示。
![Azure DevOps 连接]()
图 2-26. Azure DevOps 连接
-
根据图 2-27,连接到 Azure 资源管理器。
![Figure-2-28]()
图 2-27. 服务连接器
-
根据图 2-29,配置连接到先前部署的资源组。
![新的服务连接]()
图 2-28. 新的服务连接
-
使用 GitHub 集成创建一个新的 Python 流水线,如图 2-29 所示。
![新的流水线]()
图 2-29. 新的流水线
最后,按照图 2-30 设置 GitHub 集成。
![GitHub 集成]()
图 2-30. GitHub 集成
此过程将创建一个类似于以下代码所示的 YAML 文件。有关更多信息,请参阅官方的 Azure Pipeline YAML 文档。这是机器生成文件的第一部分:
# Python to Linux Web App on Azure # Build your Python project and deploy it to Azure as a Linux Web App. # Change python version to one thats appropriate for your application. # https://docs.microsoft.com/azure/devops/pipelines/languages/python trigger: - master variables: # Azure Resource Manager connection created during pipeline creation azureServiceConnectionId: 'df9170e4-12ed-498f-93e9-79c1e9b9bd59' # Web app name webAppName: 'flask-ml-service' # Agent VM image name vmImageName: 'ubuntu-latest' # Environment name environmentName: 'flask-ml-service' # Project root folder. Point to the folder containing manage.py file. projectRoot: $(System.DefaultWorkingDirectory) # Python version: 3.7 pythonVersion: '3.7' stages: - stage: Build displayName: Build stage jobs: - job: BuildJob pool: vmImage: $(vmImageName) steps: - task: UsePythonVersion@0 inputs: versionSpec: '$(pythonVersion)' displayName: 'Use Python $(pythonVersion)' - script: | python -m venv antenv source antenv/bin/activate python -m pip install --upgrade pip pip install setup pip install -r requirements.txt workingDirectory: $(projectRoot) -
通过更改 app.py 来验证 Azure Pipelines 的持续交付。
您可以观看这个过程的 YouTube 演示。
-
添加一个 lint 步骤(这将阻止代码语法错误):
- script: | python -m venv antenv source antenv/bin/activate make install make lint workingDirectory: $(projectRoot) displayName: 'Run lint tests'
注意
若要详细了解代码,请观看以下YouTube 演示此 MLOps 部署过程的视频。
结论
本章的目标是为您提供将机器学习部署到生产环境中所需的基础知识,即 MLOps。MLOps 的挑战之一是这个领域的多学科性。当处理 inherently 复杂的东西时,一个好的方法是从小处着手,先让最基本的解决方案运行起来,然后再逐步迭代。
如果组织希望进行 MLOps,则了解基础技能至关重要。具体来说,团队必须掌握云计算的基础知识,包括 Linux 终端及其导航方式。同样重要的是对 DevOps 的深入理解——即如何设置和使用 CI/CD——这是进行 MLOps 所必需的组成部分。这最后一个练习是在本书后续更复杂主题之前测试你的技能的绝佳机会,并将所有这些基础组件融合成一种极简的 MLOps 风格项目。
在下一章中,我们将深入探讨容器和边缘设备。这些是大多数 MLOps 平台(如 AWS SageMaker 或 Azure ML Studio)的重要组成部分,并且建立在本章涵盖的知识基础之上。
练习
-
运行一个简单的 hello world Python GitHub 项目,并在 AWS、Azure 和 GCP 的所有三个云上进行测试。
-
创建一个新的 Flask 应用程序,使用 AWS Elastic Beanstalk 提供一个“hello world”类型的路由,你认为其他人会发现有帮助,并将代码与服务请求的 GitHub README.md 截图放入 GitHub 存储库中。然后,创建一个持续交付过程,使用 AWS CodeBuild 部署 Flask 应用程序。
-
Fork 这个存储库,其中包含一个 Flask 机器学习应用程序,并使用 Elastic Beanstalk 和 Code Pipeline 进行持续交付部署到 AWS。
-
Fork 这个存储库,其中包含一个 Flask 机器学习应用程序,并使用 Google App Engine 和 Cloud Build 或 Cloud Run 和 Cloud Build 进行持续交付部署到 GCP。
-
Fork 这个存储库,其中包含一个 Flask 机器学习应用程序,并使用 Azure App Services 和 Azure DevOps Pipelines 进行持续交付部署到 Azure。
-
使用旅行推销员代码示例,并将其移植到从 API 中获取的坐标工作,例如一个城市中所有最好的餐馆。你再也不会以相同的方式考虑度假了。
-
使用 TensorFlow Playground,尝试在不同数据集和问题类型之间改变超参数。你能确定不同数据集的隐藏层、学习率和正则化率的最佳配置吗?
批判性思维讨论问题
-
一家专门从事 GPU 数据库的公司有一位关键技术成员主张停止使用云,因为购买自己的 GPU 硬件会更实际,因为他们全天候运行。这一步还将使他们比现有方式更快地获得专门的 GPU。另一方面,另一位拥有所有 AWS 认证的关键技术成员承诺如果他敢尝试就把他解雇。他声称他们已经在 AWS 上投入了太多。为或反对此建议进行论述。
-
一位“红帽认证工程师”为一家只有 100 名员工的公司在东南部建立了一个最成功的数据中心。尽管该公司是一家电子商务公司而不是云公司,但他声称这给公司带来了巨大优势。
另一方面,一位“Google 认证架构师”和“杜克数据科学硕士”毕业生声称公司使用自己拥有的数据中心处于风险位置。他们指出,公司不断失去数据中心工程师给 Google,而且没有灾难恢复计划或容错能力。为或反对此建议进行论述。
-
AWS Lambda 和 AWS Elastic Beanstalk 之间的关键技术差异,包括每种解决方案的优缺点是什么?
-
为什么像 AWS 上的 EFS 或 Google Filestore 这样的托管文件服务在企业美国的实际 MLOps 工作流程中会有帮助?
-
Kaizen 始于一个简单的问题:我们能做得更好吗?如果可以,那么这周或今天我们应该做些什么来变得更好?最后,我们如何将 Kaizen 应用到我们的机器学习项目中呢?
第三章:用于容器和边缘设备的 MLOps
作者:Alfredo Deza
分裂脑实验始于眼间转移问题。也就是说,如果一个人用一只眼睛学会了解决一个问题,然后用另一只眼睛遮盖住,并使用另一只眼睛,他就能轻松地解决这个问题,而无需进一步学习。这被称为“学习的眼间转移”。当然,学习并不是在眼睛中进行然后转移到另一只眼睛,但通常是这样描述的。转移发生的事实可能显而易见,但质疑显而易见的东西往往会产生发现。在这种情况下,问题是:一个眼睛学习后,如何在使用另一个眼睛时表现出来?放在可以实验测试的术语中,这两只眼睛在哪里连接?实验表明,转移实际上是通过大脑半球之间的胼胝体进行的。
作者:Dr. Joseph Bogen
当我开始涉足技术领域时,虚拟机(托管在物理机器上的虚拟化服务器)处于非常有利且普遍的位置——无论是从托管提供商到在 IT 房间里拥有大型服务器的普通公司,到处都能找到它们。很多在线软件提供商都提供虚拟化托管服务。在工作中,我不断磨练技能,努力学习虚拟化技术的方方面面。能够在其他主机上运行虚拟机提供了很多(受欢迎的)灵活性。
每当新技术解决一个问题(或者任意数量的问题),就会带来一系列其他问题需要解决。对于虚拟机来说,其中一个问题是如何处理它们的移动。如果主机服务器A需要安装新操作系统,系统管理员需要将虚拟机迁移到主机服务器B。虚拟机的大小与初始配置时的数据大小一样大:一个 50 GB 的虚拟驱动器意味着存在一个大小为 50GB 的虚拟驱动器文件。将 50GB 从一个服务器移动到另一个服务器将花费时间。如果您正在移动运行虚拟机的关键服务,如何最小化停机时间?
大多数这些问题都有它们的策略来最小化停机时间并增加鲁棒性:快照、恢复、备份。像Xen Project和VMWare这样的软件项目专门解决这些问题,而云服务提供商则几乎消除了这些问题。
如今,虚拟机在云计算中仍然占据重要地位。例如,Google Cloud 将其称为Compute Engine,其他提供商也有类似的参考名称。许多这些虚拟机提供增强型 GPU,以提供针对机器学习操作的更好性能。
虚拟机虽然依然存在,但越来越重要的是掌握两种模型部署技术:容器和边缘设备。认为虚拟机适合在边缘设备(如手机)上运行或在开发过程中快速迭代并使用可重现文件集是不合理的。你不会总是需要选择其中一种,但对这些选择(以及它们的运作方式)有清晰的理解会使你成为更好的机器学习工程师。
容器
虽然虚拟机拥有强大和稳健的功能,但理解容器和容器化技术是至关重要的。我记得 2013 年在圣克拉拉的 PyCon 大会上,Docker 被宣布时的那种感觉!展示的精简虚拟化对于 Linux 并不新鲜。新颖和革命性的是工具链。Linux 早已有LXC(或Linux 容器),提供了今天我们认为理所当然的许多容器功能。但 LXC 的工具链令人沮丧,而 Docker 带来了一个成功的关键因素:通过注册表轻松的协作和分享。
注册表允许任何开发者推送他们的更改到一个中心位置,其他人可以拉取这些更改并在本地运行。使用与容器相关的同样工具支持注册表(一切无缝衔接),极大推动了技术的快速发展。
Tip
在这一节中,请确保已安装容器运行时。对于这一节中的示例,使用Docker可能会更容易。安装完成后,请确保docker命令显示帮助输出,以验证安装是否成功。
有关容器与虚拟机对比的最重要描述之一来自红帽。简而言之,容器专注于应用程序本身,只有应用程序本身(如源代码和其他支持文件),而不是运行所需的(如数据库)。传统上,工程师经常使用虚拟机作为一体化服务,其中安装、配置和运行数据库、Web 服务器和其他系统服务。这些类型的应用程序是单块式的,所有组件在一个机器上有紧密的互联依赖。
另一方面,微服务是一个与数据库等系统要求完全解耦的应用程序,可以独立运行。虽然你可以将虚拟机用作微服务,但通常容器更适合这个概念。
如果你已经熟悉创建和运行容器,在“用于机器学习模型持续交付的基础设施即代码”一章中,我详细介绍了如何使用预训练模型程序化地构建它们,这进一步将这些概念与自动化融合。
容器运行时
你可能已经注意到,我提到了容器、Docker 和容器运行时。当人们将它们混用时,这些术语可能会令人困惑。由于 Docker(公司)最初开发了用于创建、管理和运行容器的工具,因此将“Docker 容器”称为常见用语。运行时——也就是在系统中运行容器所需的软件——也是由 Docker 创建的。在新容器技术发布几年后,红帽(负责 RHEL 操作系统的公司)贡献了一种不同的容器运行方式,使用了新的(替代的)运行环境。这种新环境还引入了一套新的操作容器工具,与 Docker 提供的工具有一定的兼容性。如果你听说过容器运行时,你必须意识到不止一种存在。
这些新工具和运行时的一些好处意味着,你不再需要使用具有广泛权限的超级用户账户,这对许多不同的使用情况是合理的。尽管红帽和许多其他开源贡献者在这些工具上做得很好,但在非 Linux 操作系统上运行它们仍然有些复杂。另一方面,Docker 使工作无缝体验,无论你使用 Windows、MacOS 还是 Linux。让我们开始通过完成创建容器所需的所有步骤来开始。
创建容器
Dockerfile 是创建容器的核心。每次你创建一个容器时,必须在当前目录中有 Dockerfile 存在。这个特殊文件可以有几个部分和命令,允许创建容器镜像。打开一个新文件并命名为 Dockerfile,并将以下内容添加到其中:
FROM centos:8
RUN dnf install -y python38
文件有两个部分;每个部分都由唯一的关键字分隔。这些关键字被称为指令。文件的开头使用了FROM指令,它确定容器的基础是什么。基础(也称为基础镜像)是 CentOS 发行版的第 8 版。在这种情况下,版本是一个标签。在容器中,标签定义了一个时间点。当没有定义标签时,默认为latest标签。通常情况下,版本会被用作标签,就像这个例子中的情况一样。
容器的许多有用方面之一是它们可以由许多层组成,这些层可以在其他容器中使用或重复使用。这种分层工作流程防止了每个使用它的容器每次都需要下载一个 10 兆字节的基础层。实际操作中,你只需下载一次 10 兆字节的层,并且多次重复使用。这与虚拟机非常不同,在虚拟机中,即使所有这些虚拟机都具有相同的文件,你仍然需要将它们全部下载为整体。
接下来,RUN 指令运行一个系统命令。该系统命令安装 Python 3,而基本的 CentOS 8 镜像中并不包含它。注意 dnf 命令如何使用 -y 标志,这可以防止安装程序在构建容器时询问确认。避免运行命令时触发任何提示是至关重要的。
现在从存放 Dockerfile 的同一目录构建容器:
$ docker build .
[+] Building 11.2s (6/6) FINISHED
=> => transferring context: 2B
=> => transferring dockerfile: 83B
=> CACHED [1/2] FROM docker.io/library/centos:8
=> [2/2] RUN dnf install -y python38
=> exporting to image
=> => exporting layers
=> => writing
image sha256:3ca470de8dbd5cb865da679ff805a3bf17de9b34ac6a7236dbf0c367e1fb4610
输出报告说我已经拥有 CentOS 8 的初始层,因此无需再次拉取它。然后,它安装 Python 3.8 完成镜像的创建。确保启动构建时指向存放 Dockerfile 的位置。在这种情况下,我在同一目录中,因此使用点号让构建知道当前目录是构建的目录。
这种构建镜像的方式不是很健壮,存在一些问题。首先,很难后续识别这个镜像。我们只有 sha256 摘要来引用它,没有其他信息。要查看刚刚构建的镜像的一些信息,重新运行 docker:
$ docker images
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 3ca470de8dbd 15 minutes ago 294MB
没有与之关联的存储库或标签。镜像 ID 是摘要,缩短为仅 12 个字符。如果没有额外的元数据,处理这个镜像会很具挑战性。在构建镜像时给它打标签是一个好习惯。这是创建相同镜像并打标签的方法:
$ docker build -t localbuild:removeme .
[+] Building 0.3s (6/6) FINISHED
[...]
=> => writing
image sha256:4c5d79f448647e0ff234923d8f542eea6938e0199440dfc75b8d7d0d10d5ca9a 0.0s
=> => naming to docker.io/library/localbuild:removeme
关键区别在于现在 localbuild 有一个 removeme 的标签,并且在列出镜像时会显示出来:
$ docker images localbuild
REPOSITORY TAG IMAGE ID CREATED SIZE
localbuild removeme 3ca470de8dbd 22 minutes ago 294MB
由于镜像根本没有变化,构建过程非常迅速,内部构建系统已经为已构建的镜像打了标签。命名和标记镜像在推送镜像到注册表时很有帮助。我需要拥有 localbuild 存储库才能推送到它。因为我没有,推送将被拒绝:
$ docker push localbuild:removeme
The push refers to repository [docker.io/library/localbuild]
denied: requested access to the resource is denied
但是,如果我将容器重新标记到注册表中的我的存储库,推送将会成功。要重新标记,我首先需要引用原始标签 (localbuild:removeme),然后使用我的注册表账户和目的地 (alfredodeza/removeme):
$ docker tag localbuild:removeme alfredodeza/removeme
$ docker push alfredodeza/removeme
The push refers to repository [docker.io/alfredodeza/removeme]
958488a3c11e: Pushed
291f6e44771a: Pushed
latest: digest: sha256:a022eea71ca955cafb4d38b12c28b9de59dbb3d9fcb54b size: 741
现在去 注册表(在本例中是 Docker Hub),可以看到最近推送的镜像已经可用(见 图 3-1)。
由于我的账户是开放的,注册表没有限制访问,任何人都可以通过运行 docker pull alfredodeza/removeme 来“拉取”容器镜像。如果你之前没有接触过容器或注册表,这应该感觉革命性。正如我在本章开头提到的,这是容器在开发者社区迅速流行的基础。对于“如何安装你的软件?”的答案现在几乎可以是“只需拉取容器”。

图 3-1. Docker Hub 镜像
运行容器
现在Dockerfile构建了容器,我们可以运行它了。在运行虚拟机时,通常会启用 SSH(也称为安全外壳)守护程序并公开一个端口进行远程访问,甚至可能添加默认的 SSH 密钥以防止密码提示。不习惯运行容器的人可能会要求 SSH 访问正在运行的容器实例。虽然可以启用 SSH 并使其工作,但这不是访问运行中容器的方法。
确保容器正在运行。在此示例中,我运行的是 CentOS 8:
$ docker run -ti -d --name centos-test --rm centos:8 /bin/bash
1bb9cc3112ed661511663517249898bfc9524fc02dedc3ce40b5c4cb982d7bcd
此命令中有几个新标志。它使用-ti来分配一个 TTY(模拟终端),并将stdin附加到其中,以便稍后在终端中与之交互。接下来,-d标志使容器在后台运行,以防止接管当前终端。我分配了一个名称(centos-test),然后使用--rm以便 Docker 在停止容器后移除它。执行命令后,会返回一个摘要,表示容器已启动。现在,验证它是否正在运行:
$ docker ps
CONTAINER ID IMAGE COMMAND NAMES
1bb9cc3112ed centos:8 "/bin/bash" centos-test
有些容器使用了ENTRYPOINT(可选使用CMD)指令创建。这些指令旨在使容器能够为特定任务启动和运行。在我们刚刚为 CentOS 构建的示例容器中,必须指定/bin/bash可执行文件,否则容器将无法保持运行状态。这些指令意味着,如果需要一个长时间运行的容器,至少应该创建一个执行程序的ENTRYPOINT。更新Dockerfile,使其看起来像这样:
FROM centos:8
RUN dnf install -y python38
ENTRYPOINT ["/bin/bash"]
现在可以在后台运行容器,而无需指定/bin/bash命令:
$ docker build -t localbuild:removeme .
$ docker run --rm -it -d localbuild:removeme
023c8e67d91f3bb3998e7ac1b9b335fe20ca13f140b6728644fd45fb6ccb9132
$ docker ps
CONTAINER ID IMAGE COMMAND NAMES
023c8e67d91f removeme "/bin/bash" romantic_khayyam
我之前提到过,通常使用 SSH 访问虚拟机,而访问容器有些不同。虽然理论上可以为容器启用 SSH,但我不建议这样做。以下是使用容器 ID 和exec子命令访问运行中容器的方法:
$ docker exec -it 023c8e67d91f bash
[root@023c8e67d91f /]# whoami
root
在那种情况下,我必须使用我想要运行的命令。由于我想要与容器进行交互操作(就像我与虚拟机一样),我调用 Bash 程序的可执行文件。
或者,您可能不想使用交互式 Shell 环境访问,并且您只想运行一些命令。必须更改命令才能实现此目的。将先前示例中使用的 Shell 可执行文件替换为要使用的命令:
$ docker exec 023c8e67d91f tail /var/log/dnf.log
python38-setuptools-wheel-41.6.0-4.module_el8.2.0+317+61fa6e7d.noarch
2020-12-02T13:00:04Z INFO Complete!
2020-12-02T13:00:04Z DDEBUG Cleaning up.
由于没有交互需求(我不通过 Shell 发送任何输入),我可以省略-it标志。
注意
容器开发的一个常见方面是尽可能保持其大小尽可能小。这就是为什么 CentOS 容器会比新安装的 CentOS 虚拟机少得多的软件包。当您期望某个软件包存在时(例如像 Vim 这样的文本编辑器),但实际上不存在时,会带来惊讶的经历。
最佳实践
第一件事(也强烈推荐)在尝试新的语言或工具时,是找一个可以帮助导航约定和常见用法的代码检查工具。有几种用于使用 Dockerfile 创建容器的代码检查工具。其中一个是hadolint。它被方便地打包为一个容器。修改最后一个Dockerfile示例,使其看起来像这样:
FROM centos:8
RUN dnf install -y python38
RUN pip install pytest
ENTRYPOINT ["/bin/bash"]
现在运行代码检查工具,看看是否有好的建议:
$ docker run --rm -i hadolint/hadolint < Dockerfile
DL3013 Pin versions in pip.
Instead of `pip install <package>` use `pip install <package>==<version>`
这是一个好建议。固定软件包版本总是一个好主意,因为这样可以确保依赖项的更新不会与您的应用程序需要的代码不兼容。请注意,固定依赖项并永远不更新它们并不是一个好主意。确保回到固定的依赖项,并查看是否有必要进行更新。
由于容器化工具的一个目标是尽可能保持其小巧,您可以在创建 Dockerfile 时实现几件事情。每次有RUN指令时,都会创建一个包含该执行的新层。容器由个别层组成,因此层数越少,容器的大小就越小。这意味着最好使用一行命令安装多个依赖项,而不是一个依赖项一个命令:
RUN apk add --no-cache python3 && python3 -m ensurepip && pip3 install pytest
在每个命令的末尾使用&&将所有内容链接在一起,创建一个单一的层。如果前面的示例为每个安装命令使用单独的RUN指令,那么容器的大小将更大。也许对于这个特定示例,大小不会有太大的区别;然而,在需要大量依赖项的容器中,这将是显著的。
代码检查提供了一个有用的选项:自动化代码检查的机会。留意自动化流程的机会,消除任何手动步骤,并让您集中精力在将模型部署到生产中的过程的核心部分(在这种情况下编写一个好的 Dockerfile)。
构建容器的另一个关键部分是确保安装的软件没有与之相关的漏洞。发现认为应用程序不太可能有漏洞,因为他们编写高质量代码的工程师并不罕见。问题在于容器带有预安装的库。它是一个完整的操作系统,在构建时将拉取额外的依赖项以满足您尝试交付的应用程序。如果您要使用像 Flask 这样的 Web 框架从容器中提供训练模型,您必须清楚地了解可能与 Flask 或其依赖项之一相关联的常见漏洞和曝光(CVEs)。
这些是 Flask(版本为1.1.2)带来的依赖项:
click==7.1.2
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
Werkzeug==1.0.1
CVE 可能随时报告,并且用于警报漏洞的软件系统确保在报告时准确更新多次。像 Flask 这样的应用程序的关键部分今天可能不会对版本 1.1.2 有漏洞,但是明天早上发现并报告新 CVE 时就肯定会有。许多不同的解决方案专门用于扫描和报告容器中的漏洞以减轻这些漏洞。这些安全工具扫描应用程序安装的库和操作系统的软件包,提供详细和准确的漏洞报告。
一个非常快速且易于安装的解决方案是 Anchore 的grype命令行工具。要在 Macintosh 计算机上安装它:
$ brew tap anchore/grype
$ brew install grype
或在任何 Linux 机器上:
$ curl -sSfL \
https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s
使用curl这种方式可以将grype部署到几乎任何持续集成系统中以扫描漏洞。curl安装方法会将可执行文件放置在当前工作路径下的bin/目录中。安装完成后,运行它对一个容器进行扫描:
$ grype python:3.8
✔ Vulnerability DB [no update available]
⠴ Loading image ━━━━━━━━━━━━━━━━━━━━━ [requesting image from docker]
✔ Loaded image
✔ Parsed image
✔ Cataloged image [433 packages]
✔ Scanned image [1540 vulnerabilities]
一千多个漏洞看起来有些令人惊讶。输出太长无法在此捕获,因此过滤结果以检查High严重性的漏洞:
$ grype python:3.8 | grep High
[...]
python2.7 2.7.16-2+deb10u1 CVE-2020-8492 High
报告了几个漏洞,因此我将输出减少到只有一个。CVE令人担忧,因为如果攻击者利用漏洞,可能会导致系统崩溃。由于我知道应用程序使用 Python 3.8,所以这个容器不会有漏洞,因为 Python 2.7 未使用。虽然这是一个 Python 3.8 容器,但是为了方便起见,镜像包含一个较旧的版本。关键的区别在于现在您知道什么是有漏洞的,并且可以就最终将服务部署到生产环境做出执行决策。
一个有用的自动化增强功能是在特定漏洞级别(如high)上失败:
$ grype --fail-on=high centos:8
[...]
discovered vulnerabilities at or above the severity threshold!
这是您可以与 Linting 一起自动化的另一个检查,用于构建强大的容器。一个写得很好的Dockerfile,并且始终报告漏洞是提升容器化模型生产交付的极好方式。
在 HTTP 上为训练模型提供服务
现在核心概念中有几个已经清晰,让我们创建一个容器,该容器将使用 Flask Web 框架通过 HTTP API 提供训练模型的服务。正如您已经了解的,一切都始于 Dockerfile,所以现在创建一个,假设当前工作目录中有requirements.txt文件:
FROM python:3.7
ARG VERSION
LABEL org.label-schema.version=$VERSION
COPY ./requirements.txt /webapp/requirements.txt
WORKDIR /webapp
RUN pip install -r requirements.txt
COPY webapp/* /webapp
ENTRYPOINT [ "python" ]
CMD [ "app.py" ]
这个文件中有几个我以前没有涉及过的新东西。首先,我们定义了一个名为 VERSION 的参数,它作为 LABEL 的变量使用。我正在使用一个 标签模式约定,有助于规范这些标签的命名。使用版本是一种有用的方式,用于添加有关容器本身的信息元数据。当容器未能从模型中产生预期的准确性时,添加标签有助于识别问题模型的版本。虽然此文件只使用一个标签,但可以想象使用更多带有描述性数据的标签会更好。
注意
此构建使用的容器镜像略有不同。这个构建使用 Python 3.7,因为在撰写本文时,一些依赖项尚不支持 Python 3.8. 您可以随意将 3.7 替换为 3.8 并检查它是否现在可以工作。
接下来,将 requirements.txt 文件复制到容器中。创建具有以下依赖项的 requirements 文件:
Flask==1.1.2
pandas==0.24.2
scikit-learn==0.20.3
现在,创建一个名为 webapp 的新目录,以便将 Web 文件放在一个地方,并添加 app.py 文件,使其看起来像这样:
from flask import Flask, request, jsonify
import pandas as pd
from sklearn.externals import joblib
from sklearn.preprocessing import StandardScaler
app = Flask(__name__)
def scale(payload):
scaler = StandardScaler().fit(payload)
return scaler.transform(payload)
@app.route("/")
def home():
return "<h3>Sklearn Prediction Container</h3>"
@app.route("/predict", methods=['POST'])
def predict():
"""
Input sample:
{
"CHAS": { "0": 0 }, "RM": { "0": 6.575 },
"TAX": { "0": 296 }, "PTRATIO": { "0": 15.3 },
"B": { "0": 396.9 }, "LSTAT": { "0": 4.98 }
}
Output sample:
{ "prediction": [ 20.35373177134412 ] }
"""
clf = joblib.load("boston_housing_prediction.joblib")
inference_payload = pd.DataFrame(request.json)
scaled_payload = scale(inference_payload)
prediction = list(clf.predict(scaled_payload))
return jsonify({'prediction': prediction})
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000, debug=True)
最后需要的文件是训练好的模型。如果正在训练波士顿房屋预测数据集,请确保将其放置在 webapp 目录中,与 app.py 文件一起,并将其命名为 boston_housing_prediction.joblib。您也可以在这个 GitHub 仓库 中找到一个训练好的模型版本。
项目的最终结构应如下所示:
.
├── Dockerfile
└── webapp
├── app.py
└── boston_housing_prediction.joblib
1 directory, 3 files
现在构建容器。在示例中,我将使用 Azure 在我训练模型时给出的运行 ID 作为版本,以便更容易识别模型的来源。如果不需要版本,请随意使用不同的版本(或根本不使用版本):
$ docker build --build-arg VERSION=AutoML_287f444c -t flask-predict .
[+] Building 27.1s (10/10) FINISHED
=> => transferring dockerfile: 284B
=> [1/5] FROM docker.io/library/python:3.7
=> => resolve docker.io/library/python:3.7
=> [internal] load build context
=> => transferring context: 635B
=> [2/5] COPY ./requirements.txt /webapp/requirements.txt
=> [3/5] WORKDIR /webapp
=> [4/5] RUN pip install -r requirements.txt
=> [5/5] COPY webapp/* /webapp
=> exporting to image
=> => writing image sha256:5487a63442aae56d9ea30fa79b0c7eed1195824aad7ff4ab42b
=> => naming to docker.io/library/flask-predict
双重检查构建后镜像是否可用:
$ docker images flask-predict
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-predict latest 5487a63442aa 6 minutes ago 1.15GB
现在在后台运行容器,暴露端口 5000,并验证它是否在运行:
$ docker run -p 5000:5000 -d --name flask-predict flask-predict
d95ab6581429ea79495150bea507f009203f7bb117906b25ffd9489319219281
$docker ps
CONTAINER ID IMAGE COMMAND STATUS PORTS
d95ab6581429 flask-predict "python app.py" Up 2 seconds 0.0.0.0:5000->5000/tcp
在你的浏览器中打开 http://localhost:5000,home() 函数返回的 HTML 应该欢迎您使用 Sklearn 预测应用程序。另一种验证方法是使用 curl:
$ curl 192.168.0.200:5000
<h3>Sklearn Prediction Container</h3>
您可以使用任何能够通过 HTTP 发送信息并处理返回响应的工具。本示例使用几行 Python 代码与 requests 库(确保在运行之前安装它)发送带有示例 JSON 数据的 POST 请求:
import requests
import json
url = "http://localhost:5000/predict"
data = {
"CHAS": {"0": 0},
"RM": {"0": 6.575},
"TAX": {"0": 296.0},
"PTRATIO": {"0": 15.3},
"B": {"0": 396.9},
"LSTAT": {"0": 4.98},
}
# Convert to JSON string
input_data = json.dumps(data)
# Set the content type
headers = {"Content-Type": "application/json"}
# Make the request and display the response
resp = requests.post(url, input_data, headers=headers)
print(resp.text)
将 Python 代码写入文件并命名为 predict.py。在终端上执行该脚本以获取一些预测结果:
$ python predict.py
{
"prediction": [
20.35373177134412
]
}
容器化部署是创建可由他人尝试的便携数据的绝佳方式。通过共享容器,减少了设置环境的摩擦,同时确保了一个可重复使用的系统进行交互。现在你知道如何为机器学习创建、运行、调试和部署容器,可以利用这一点来开始自动化非容器化环境,加快生产部署并增强整个流程的稳健性。除了容器之外,还有一个推动力,即使服务更接近用户,这就是我接下来要讨论的边缘设备和部署。
边缘设备
几年前,(快速)推理的计算成本是天文数字。今天可用的一些更先进的机器学习功能在不久前是成本禁止的。成本不仅降低了,而且更强大的芯片正在生产中。其中一些芯片专门为机器学习任务量身定制。这些芯片的所需功能的正确组合允许在诸如手机之类的设备上进行推理:快速、小巧且专为机器学习任务而设计。当技术中提到“部署到边缘”时,指的是不在数据中心内的计算设备,以及成千上万的其他服务器。手机、树莓派和智能家居设备是符合“边缘设备”描述的一些例子。在过去几年中,大型电信公司一直在推动边缘计算。这些边缘部署中的大多数都希望向用户提供更快的反馈,而不是将昂贵的计算请求路由到远程数据中心。
一般的想法是,计算资源距离用户越近,用户体验就会越快。有一条细微的界线,界定了什么可能在边缘落地,而不是完全回到数据中心。但正如我提到的,专用芯片变得更小、更快、更有效;可以预见未来意味着边缘计算中更多的机器学习。在这种情况下,边缘将意味着更多我们之前认为不能处理机器学习任务的设备。
居住在拥有大量数据中心托管应用数据的国家的大多数人几乎不会遇到延迟问题。对于那些不是这样的国家,问题会更加严重。例如,秘鲁有几条连接其与南美其他国家的海底电缆,但没有直接连接到美国的通路。这意味着,如果您从秘鲁上传图片到在美国数据中心托管应用的服务,速度将比像巴拿马这样的国家花费的时间长得多。这个上传图片的例子虽然微不足道,但在像 ML 预测这样的计算操作上则会更糟。本节探讨了边缘设备如何通过尽可能接近用户执行快速推理来帮助解决长距离问题。如果长距离是一个问题,想象一下当像远程农场这样没有(或者连接非常有限)的地方会发生什么。如果您需要在远程位置进行快速推理,选项有限,这就是 部署到边缘 比任何数据中心都有优势的地方。
记住,用户并不太关心勺子:他们对于能顺利尝试美味汤的无缝方式更感兴趣。
Coral
珊瑚项目 是一个平台,帮助构建本地(设备上的)推理,捕捉边缘部署的本质:快速、靠近用户和离线。在本节中,我将介绍 USB 加速器,这是一种支持所有主要操作系统并且与 TensorFlow Lite 模型兼容良好的边缘设备。您可以编译大多数 TensorFlow Lite 模型以在这种边缘 TPU(张量处理单元)上运行。ML 的运营化的一些方面意味着需要了解设备支持、安装方法和兼容性。这三个方面对于珊瑚边缘 TPU 是正确的:它适用于大多数操作系统,与 TensorFlow Lite 模型一起工作,只要能编译运行在 TPU 上即可。
如果您被委派在边缘的远程位置部署快速推理解决方案,您必须确保所有部署所需的组件能正常工作。这本书贯穿了 DevOps 的核心概念:可重复部署的方法创建可重现的环境至关重要。为了确保这一点,您必须了解兼容性。
首先,开始安装 TPU 运行时。对于我的机器,这意味着下载并解压文件以运行安装脚本:
$ curl -O https://dl.google.com/coral/edgetpu_api/edgetpu_runtime_20201204.zip
[...]
$ unzip edgetpu_runtime_20201204.zip
Archive: edgetpu_runtime_20201204.zip
creating: edgetpu_runtime/
inflating: edgetpu_runtime/install.sh
[...]
$ cd edgetpu_runtime
$ sudo bash install.sh
Password:
[...]
Installing Edge TPU runtime library [/usr/local/lib]...
Installing Edge TPU runtime library symlink [/usr/local/lib]...
注意
这些设置示例使用的是 Macintosh 计算机,因此安装方法和依赖项会与其他操作系统有所不同。查看入门指南,如果您需要支持不同计算机。
现在系统中安装了运行时依赖项,我们准备尝试使用边缘 TPU。Coral 团队有一个有用的 Python3 代码仓库,可以通过一个命令来运行图像分类。创建一个目录来克隆该仓库的内容,以设置图像分类的工作空间:
$ mkdir google-coral && cd google-coral
$ git clone https://github.com/google-coral/tflite --depth 1
[...]
Resolving deltas: 100% (4/4), done.
$ cd tflite/python/examples/classification
注意
git命令使用--depth 1标志执行浅克隆。当不需要完整的仓库内容时,浅克隆是可取的。由于这个例子使用了仓库的最新更改,因此不需要执行包含完整仓库历史记录的完整克隆。
例如,在这个例子中,请不要运行install_requirements.sh脚本。首先确保你的系统中安装了 Python3,并使用它创建一个新的虚拟环境;确保在激活后,Python 解释器指向虚拟环境而不是系统 Python:
$ python3 -m venv venv
$ source venv/bin/activate
$ which python
~/google-coral/tflite/python/examples/classification/venv/bin/python
现在虚拟环境virtualenv已经激活,请安装两个库依赖项和 TensorFlow Lite 运行支持:
$ pip install numpy Pillow
$ pip install https://github.com/google-coral/pycoral/releases/download/\
release-frogfish/tflite_runtime-2.5.0-cp38-cp38-macosx_10_15_x86_64.whl
numpy和Pillow在大多数系统中安装都很简单。唯一的异常是接下来的非常长的链接。这个链接非常重要,必须与您的平台和架构匹配。如果没有这个库,将无法与 Coral 设备进行交互。TensorFlow Lite 的 Python 安装指南是确认您需要使用哪个链接的正确来源。
现在一切都安装好并准备好执行图像分类,运行classify_image.py脚本以获取帮助菜单。在这种情况下,重新显示帮助菜单是验证所有依赖项都已安装且脚本正常工作的好方法:
usage:
classify_image.py [-h] -m MODEL -i INPUT [-l LABELS] [-k TOP_K] [-c COUNT]
classify_image.py:
error: the following arguments are required: -m/--model, -i/--input
因为在调用脚本时我没有定义任何标志,返回了一个错误,提醒我需要传递一些标志。在开始使用其他标志之前,我们需要检索一个 TensorFlow 模型,用于处理要测试的图像。
Coral AI 网站有一个模型部分,您可以浏览一些专门预训练的模型,用于进行图像分类。找到识别一千多种不同昆虫的iNat 昆虫模型。下载tflite模型和标签。
例如,在这个例子中,下载一个普通飞行图像的示例。图像的原始来源在 Pixabay 上,但在本书的 GitHub 存储库中也很方便地可以获取。
创建模型、标签和图像的目录。分别将所需文件放置在它们的目录中。尽管不一定需要按照这个顺序,但以后添加更多分类模型、标签和图像以进一步使用 TPU 设备时会更方便。
目录结构应该如下所示:
.
├── README.md
├── classify.py
├── classify_image.py
├── images
│ └── macro-1802322_640.jpg
├── install_requirements.sh
├── labels
│ └── inat_insect_labels.txt
└── models
└── mobilenet_v2_1.0_224_inat_insect_quant_edgetpu.tflite
3 directories, 7 files
最后,我们可以尝试使用 Coral 设备进行分类操作。确保设备已插入 USB 电缆,否则您将得到一长串的追溯信息(不幸的是,这并不真正解释问题所在):
Traceback (most recent call last):
File "classify_image.py", line 122, in <module>
main()
File "classify_image.py", line 99, in main
interpreter = make_interpreter(args.model)
File "classify_image.py", line 72, in make_interpreter
tflite.load_delegate(EDGETPU_SHARED_LIB,
File "~/lib/python3.8/site-packages/tflite_runtime/interpreter.py",
line 154, in load_delegate
raise ValueError('Failed to load delegate from {}\n{}'.format(
ValueError: Failed to load delegate from libedgetpu.1.dylib
那个错误意味着设备未插入。请插入设备并运行分类命令:
$ python3 classify_image.py \
--model models/mobilenet_v2_1.0_224_inat_insect_quant_edgetpu.tflite \
--labels labels/inat_insect_labels.txt \
--input images/macro-1802322_640.jpg
----INFERENCE TIME----
Note: The first inference on Edge TPU is slow because it includes loading
the model into Edge TPU memory.
11.9ms
2.6ms
2.5ms
2.5ms
2.4ms
-------RESULTS--------
Lucilia sericata (Common Green Bottle Fly): 0.43359
图像被正确分类,普通苍蝇被检测到!找一些其他昆虫图片,并重新运行命令检查模型在不同输入下的表现。
Azure Percept
在书写本书时,微软宣布发布一个名为 Azure Percept 的平台和硬件。虽然我没有足够的时间去实际操作如何利用其功能的实例,但我觉得值得提到一些它的功能。
与前一节中 Coral 设备以及边缘一般相同的概念适用于 Percept 设备:它们允许在边缘进行无缝机器学习操作。
首先,重要的是要强调,尽管 Percept 产品大多作为硬件组件进行广告宣传,但 Azure Percept 是一个完整的边缘计算平台,从设备本身到在 Azure 中的部署、训练和管理。还支持主要的 AI 平台如 ONNX 和 TensorFlow,使得试用预建模型变得更加简单。
与 Coral 设备相比,Azure Percept 硬件的一个缺点是价格昂贵,这使得很难购买其捆绑产品来尝试新技术。正如以往一样,微软在文档化并添加大量上下文和示例方面做得非常出色,如果您感兴趣,值得探索。
TFHub
找到 TensorFlow 模型的一个很好的资源是TensorFlow Hub。该 hub 是一个存储库,其中包含数千个预训练模型可供使用。对于 Coral Edge TPU,并非所有模型都能正常工作。由于 TPU 具有针对设备的单独说明,模型需要专门为其编译。
现在您可以使用 Coral USB 设备运行分类操作,可以使用 TFHub 查找其他预训练模型进行工作。在 hub 上,有一个 Coral 模型格式;点击进入用于 TPU 的可用模型,如图 3-2 所示。

图 3-2. TFHub Coral 模型
选择MobileNet Quantized V2模型进行下载。该模型可以从图像中检测超过一千个对象。之前使用 Coral 的示例需要标签和模型,因此请确保您也下载了这些。
注意
当这些模型在 TFHub 网站上展示时,有多种不同的格式可用。确保仔细检查您获取的模型格式,并且(在本例中)它与 Coral 设备兼容。
移植非 TPU 模型
你可能会发现你需要的模型在某些情况下可用,但没有为你所拥有的 TPU 设备编译。Coral Edge TPU 确实有一个可用的编译器,但并非每个平台都可以安装运行时依赖项。当遇到这种情况时,你必须在解决方案上进行创意尝试,并始终尝试找到任何可能的解决方案中的自动化。编译器文档要求 Debian 或 Ubuntu Linux 发行版,并且设置编译器的说明与该特定发行版相关联。
在我的情况下,我是在一台 Apple 电脑上工作,并且没有其他运行 Linux 的计算机。我拥有的是本地安装的容器运行时,在这个运行时中,我可以用几个命令运行来自任何发行版的任何镜像。我们已经讨论过如何开始使用容器,如何运行它们以及如何创建它们。这是一个创建一个新的基于 Debian 的容器,并为编译器安装所有内容来解决这个问题的完美用例。
现在我们理解了问题,并且有了一个使用容器的解决方案,我们可以创建一个新的Dockerfile来构建一个容器镜像以供编译器使用。
FROM debian:stable
RUN apt-get update && apt install -yq curl build-essential gnupg
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
apt-key add -
RUN \
echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | \
tee /etc/apt/sources.list.d/coral-edgetpu.list
RUN apt-get update && apt-get install -yq edgetpu-compiler
CMD ["/bin/bash"]
利用新创建的Dockerfile,创建一个新的镜像来运行编译器:
$ docker build -t tpu-compiler .
[+] Building 15.5s (10/10) FINISHED
=> => transferring dockerfile: 408B
[...]
=> [5/5] RUN apt update && apt install -yq edgetpu-compiler
=> exporting to image
=> => exporting layers
=> => writing image
sha256:08078f8d7f7dd9002bd5a1377f24ad0d9dbf8f7b45c961232cf2cbf8f9f946e4
=> => naming to docker.io/library/tpu-compiler
我已经找到了一个我想要与 TPU 编译器一起使用但没有为其编译的模型。
注意
只有为 TensorFlow Lite 预编译且量化的模型才能与编译器一起工作。确保模型同时是tflite和quantized,然后再将其下载以便用编译器转换。
在本地下载模型。在这种情况下,我使用命令行将其保存在当前工作目录中:
$ wget -O mobilenet_v1_50_160_quantized.tflite \
https://tfhub.dev/tensorflow/lite-model/\
mobilenet_v1_0.50_160_quantized/1/default/1?lite-format=tflite
Resolving tfhub.dev (tfhub.dev)... 108.177.122.101, 108.177.122.113, ...
Connecting to tfhub.dev (tfhub.dev)|108.177.122.101|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1364512 (1.3M) [application/octet-stream]
Saving to: ‘mobilenet_v1_50_160_quantized.tflite’
(31.8 MB/s) - ‘mobilenet_v1_50_160_quantized.tflite’ saved [1364512/1364512]
$ ls
mobilenet_v1_50_160_quantized.tflite
虽然我已经使用了命令行,但你也可以通过访问网站上的模型来下载模型。确保将文件移动到当前工作目录以进行下一步操作。
我们需要将下载的模型放入容器中,然后将文件本地复制回来。通过bind mount,Docker 可以使这个任务变得更加可管理。这种挂载操作将把我的机器上的路径链接到容器中,有效地共享我拥有的任何东西到容器中。这对于在容器中创建的文件也非常有效,因为我需要它们回到本地环境中。这些在容器中创建的文件将自动出现在我的本地环境中。
通过绑定挂载启动容器:
$ docker run -it -v ${PWD}:/models tpu-compiler
root@5125dcd1da4b:/# cd models
root@5125dcd1da4b:/models# ls
mobilenet_v1_50_160_quantized.tflite
在前一个命令中有几件事情在发生。首先,我使用PWD来指示当前工作目录,在这个目录中,mobilenet_v1_50_160_quantized.tflite文件存在于容器中的目标路径是/models。最后,我使用了带有标签tpu-compiler的构建容器来指定我需要的容器。如果你在构建镜像时使用了不同的标签,你需要更新命令中的这部分。启动容器后,我切换到/models目录,列出目录内容,并在本地机器上找到了下载的模型。环境现在已准备好使用编译器。
通过调用其帮助菜单来验证编译器是否工作:
$ edgetpu_compiler --help
Edge TPU Compiler version 15.0.340273435
Usage:
edgetpu_compiler [options] model...
接下来,对量化模型运行编译器:
$ edgetpu_compiler mobilenet_v1_50_160_quantized.tflite
Edge TPU Compiler version 15.0.340273435
Model compiled successfully in 787 ms.
Input model: mobilenet_v1_50_160_quantized.tflite
Input size: 1.30MiB
Output model: mobilenet_v1_50_160_quantized_edgetpu.tflite
Output size: 1.54MiB
Number of Edge TPU subgraphs: 1
Total number of operations: 31
Operation log: mobilenet_v1_50_160_quantized_edgetpu.log
See the operation log file for individual operation details.
这个操作少于一秒就运行完毕,并生成了一些文件,包括新编译的模型(mobilenet_v1_50_160_quantized_edgetpu.tflite),你现在可以用在边缘设备上。
最后,退出容器,返回本地机器,并列出目录的内容:
$ ls
mobilenet_v1_50_160_quantized.tflite
mobilenet_v1_50_160_quantized_edgetpu.log
mobilenet_v1_50_160_quantized_edgetpu.tflite
这是绕过工具操作系统需求的一个方便的解决方法。现在这个容器可以为边缘设备编译模型,可以通过脚本中的几行代码进一步自动化移植你所需的模型。记住,在这个过程中做了一些假设,你必须确保这些假设在编译时都是准确的。否则,你将会从编译器那里得到错误。这个过程是试图使用非量化模型与编译器配合的一个示例:
$ edgetpu_compiler vision_classifier_fungi_mobile_v1.tflite
Edge TPU Compiler version 15.0.340273435
Invalid model: vision_classifier_fungi_mobile_v1.tflite
Model not quantized
管理型 ML 系统的容器
在高级下一代 MLOps 工作流的核心是像 AWS SageMaker、Azure ML Studio 和 Google 的 Vertex AI 这样的管理型 ML 系统。所有这些系统都基于容器构建。容器是 MLOps 的一个秘密武器。没有容器化,开发和使用 AWS SageMaker 等技术就会更具挑战性。在图 3-3 中,请注意 EC2 容器注册表是推断代码镜像和训练代码的位置。

图 3-3. SageMaker 容器
这个过程至关重要,因为它允许 DevOps 最佳实践融入到创建这些镜像中——其中最重要的是持续集成和持续交付。容器通过减少复杂性来提高整个 ML 架构的质量,因为镜像已经“烘焙”好了。智力资源可以转移到其他问题,比如数据漂移,分析特征存储以寻找适合新模型的候选人,或评估新模型是否解决了客户需求。
在商业化 MLOps 中的容器
对于初创公司和大公司来说,Monetizing MLOps 是另一个关键问题。容器再次发挥了作用!在 SageMaker 的情况下,可以使用在 AWS Marketplace 中展示的算法或模型,如图 3-4 所示。它们是产品销售的交付方式。

图 3-4. SageMaker 卖家工作流程
作为产品,容器的优势在于其销售方式类似于在实体店销售的其他产品,比如花生酱、面粉或牛奶。在公司决定生产高质量有机花生酱的情况下,可能希望专注于生产花生酱,而不是建立销售花生酱的店铺网络。
同样地,在希望从机器学习中获利的公司中,容器是向客户交付模型和算法的理想封装。接下来,让我们看看如何通过容器一次构建多次运行。
一次构建,多次运行的 MLOps 工作流程
最终,MLOps 的容器化过程为产品和工程师提供了许多丰富的选择。在图 3-5 中,您可以看到从产品角度看,容器是实现知识产权商业化的理想封装。同样地,从工程角度看,容器可以用于提供预测结果、进行训练,或者部署到像 Coral TPU 或 Apple iPhone 这样的边缘设备上。

图 3-5. 一次构建,多次运行的 MLOps 容器
MLOps 和容器技术是互补的,因为容器帮助您交付业务价值。然后,MLOps 方法直接建立在这项技术之上,以优化生产效率并增加价值。接下来,让我们结束本章,总结容器在 MLOps 中的重要方面。
结论
在操作化 ML 模型时,您经常会遇到许多不同的部署可能性。越来越普遍地看到模型部署在手机和其他(小型)设备上,您可以将其插入任何带有 USB 端口的计算机。边缘推理提供的问题(如离线、远程和快速访问)可能会产生转型效应,特别是对于没有可靠电源和网络访问的偏远地区。与边缘设备类似,容器化使环境的再现更快速、更可靠。几年前,可再现的机器环境是一个具有挑战性的问题。在这种情况下,容器化尤为重要。通过容器,资源的快速扩展和从云提供商转换部署环境,甚至将工作负载从本地部署到云中,都变得更加容易。
接下来,我们的下一章将深入探讨机器学习模型的持续交付流程。
练习
-
重新编译模型以适应 TFHub 的 Coral Edge TPU。
-
使用 MobileNet V2 模型对其他对象进行推理,获得准确的结果。
-
创建一个新的容器镜像,基于 Flask 示例,用于服务一个模型,并在
GET请求中提供与模型交互的示例。创建另一个端点,提供关于模型的有用元数据。 -
将新创建的镜像发布到像 Docker Hub 这样的容器注册表。
批判性思维讨论问题
-
是否可以使用容器来使用类似 Coral 的边缘 TPU 设备进行在线预测?如何?或为什么不?
-
容器运行时是什么,它与 Docker 有什么关系?
-
创建 Dockerfile 时的三个良好实践方法是什么?
-
本章提到的 DevOps 的两个关键概念是什么?它们为什么有用?
-
创建一个定义,用你自己的话来描述“边缘”是什么。给出一些可以应用的机器学习示例。
第四章:机器学习模型的持续交付
作者:Alfredo Deza
自然哲学(我们现在称为科学)真的已经远离其起源了吗?它只留下了纸质学家——那些接受纸质信息、输出纸质信息的人,他们在阅读和写作时刻苦心避免与现实接触?他们是否认为直接接触数据是有害的?他们是否像小说《烟草之路》中的某些乡巴佬一样,对自己的无知感到自豪?
Dr. Joseph Bogen
作为一名职业运动员,我经常面对受伤问题。受伤有各种严重程度。有时可能只是一些轻微的问题,比如剧烈跨栏训练后左腿肌肉轻微紧缩。其他时候可能更严重,比如难以忍受的下背痛。高水平运动员在赛季中间不能有休息日。如果计划是每周七天锻炼,那么坚持这七天是至关重要的。错过一天会带来严重后果,可能会使直到那时的训练成果减弱(甚至完全消失)。锻炼就像推着一辆上坡的手推车,错过一次锻炼就意味着让手推车沿着山下滑行。这样做的后果是你需要回去重新推上山。你不能错过锻炼。
如果你受伤了,无法进行锻炼,那么尽快以最佳状态恢复上场是首要任务,就像找到替代锻炼一样重要。这意味着如果你的腿筋受伤了,无法跑步,那么看看是否可以去游泳池,保持有氧计划。明天无法进行上坡训练,因为你的脚趾头受伤了?那就试试骑自行车去应对同样的坡道。受伤需要战略规划;放弃不是选项,但如果必须撤退,那么首先考虑的是尽量减少撤退的可能性。如果我们不能开炮,那就派骑兵。总会有办法,创造力和完全康复同样重要。
恢复需要策略,但更重要的是持续评估。由于你在受伤的情况下尽可能多地进行锻炼,评估伤势是否加重是至关重要的。如果你因为无法跑步而骑自行车,你必须非常警觉,看看自行车是否加重了伤势。对于受伤的持续评估是一个相当简单的算法:
-
每天第一件事,评估伤势是否比前一天更糟,还是更好。
-
如果情况变得更糟,那么要进行调整以避免之前的锻炼或者改变它们。这些可能会妨碍恢复。
-
如果情况没有改变,将伤势与上周或上个月进行比较。问自己:“我是否感觉比上周更糟、更好还是一样?”
-
最后,如果你感觉好些了,那将极大地强化当前策略正在起作用的事实,并且你应该继续,直到完全康复。
对于某些受伤,我不得不更频繁地评估(而不是等到第二天早上)。持续评估的结果是恢复的关键。在某些情况下,我不得不评估特定行动是否对我有害。有一次我摔断了脚趾(撞在书架的角落上),我立即制定了策略:我能走路吗?如果跑步会感到疼痛吗?对于所有这些问题,答案都是肯定的。那天下午我试着去游泳。接下来的几周,我不断检查是否能够在没有疼痛的情况下行走。疼痛不是敌人。它是帮助你决定是继续做你正在做的事情,还是停下来重新思考当前策略的指示器。
不断评估、做出改变并根据反馈进行调整,以及应用新策略以实现成功,这正是持续集成(CI)和持续交付(CD)的核心内容。即使在如今,强大的部署策略信息易得,仍然经常遇到没有测试或测试策略薄弱的企业,以确保产品能够在新版本发布时准备就绪,甚至发布需要数周(甚至数月)的情况。我还记得,尝试发布一个重要的开源项目新版本时,有时需要接近一周的时间。更糟糕的是,质量保证(QA)负责人会给每位团队负责人发送电子邮件,询问他们是否准备好发布或者是否需要进行更多改动。
发送电子邮件并等待不同回复并不是发布软件的简单方法。它容易出错且高度不一致。CI/CD 平台和步骤赋予你和你的团队的反馈循环是无价的。如果发现问题,你必须自动化解决,并确保在下一次发布中不再成为问题。持续评估,就像高水平运动员受伤一样,是 DevOps 的核心支柱,对于成功的机器学习运营至关重要。
我喜欢将持续描述为一个过程的持久性或再现性的描述。在谈论构建、验证和部署工件的系统时,通常会一起提到 CI/CD。在本章中,我将详细介绍一个强大流程的外观,以及如何实施(或改进)流水线来将模型投入生产。
机器学习模型的打包
不久前,我第一次听说打包 ML 模型的事情。如果你以前从未听说过打包模型,没关系——这都是最近的事情,这里的打包并不意味着一些特殊类型的操作系统包,比如带有用于捆绑和分发的特殊指令的 RPM(Red Hat Package Manager)或 DEB(Debian Package)文件。这一切意味着将模型放入容器中,以利用容器化进程来帮助共享、分发和轻松部署。我已经在“容器”中详细描述了容器化,并解释了为什么在操作化机器学习时使用它们比使用其他策略如虚拟机更有意义,但值得重申的是,能够快速从容器中尝试模型,而不受操作系统影响,是一个梦想般的场景。
将 ML 模型打包到容器中有三个重要的特性需要讨论:
-
只要安装了容器运行时,本地运行容器就很简单。
-
有很多选项可以在云中部署容器,根据需要进行横向或纵向扩展。
-
其他人可以轻松尝试并与容器进行交互。
这些特性的好处在于维护变得不那么复杂,即使在本地或云服务中调试性能不佳的模型,也只需在终端输入几个命令即可。部署策略越复杂,故障排除和问题调查就会越困难。
对于本节,我将使用一个 ONNX 模型,并将其打包到一个容器中,用于提供执行预测的 Flask 应用。我将使用RoBERTa-SequenceClassification ONNX 模型,该模型有很好的文档支持。创建新的 Git 仓库后,第一步是确定所需的依赖项。创建 Git 仓库后,首先添加以下requirements.txt文件:
simpletransformers==0.4.0
tensorboardX==1.9
transformers==2.1.0
flask==1.1.2
torch==1.7.1
onnxruntime==1.6.0
接下来,创建一个 Dockerfile,在容器中安装所有内容:
FROM python:3.8
COPY ./requirements.txt /webapp/requirements.txt
WORKDIR /webapp
RUN pip install -r requirements.txt
COPY webapp/* /webapp
ENTRYPOINT [ "python" ]
CMD [ "app.py" ]
Dockerfile 复制了要求文件,创建了一个webapp目录,并将应用程序代码复制到一个名为app.py的文件中。创建webapp/app.py文件来执行情感分析。首先添加导入和创建 ONNX 运行时会话所需的所有内容:
from flask import Flask, request, jsonify
import torch
import numpy as np
from transformers import RobertaTokenizer
import onnxruntime
app = Flask(__name__)
tokenizer = RobertaTokenizer.from_pretrained("roberta-base")
session = onnxruntime.InferenceSession(
"roberta-sequence-classification-9.onnx")
文件的第一部分创建了 Flask 应用程序,定义了与模型一起使用的分词器,最后初始化了一个需要传递模型路径的 ONNX 运行时会话。有几个未使用的导入将在添加 Flask 路由以启用实时推理时使用:
@app.route("/predict", methods=["POST"])
def predict():
input_ids = torch.tensor(
tokenizer.encode(request.json[0], add_special_tokens=True)
).unsqueeze(0)
if input_ids.requires_grad:
numpy_func = input_ids.detach().cpu().numpy()
else:
numpy_func = input_ids.cpu().numpy()
inputs = {session.get_inputs()[0].name: numpy_func(input_ids)}
out = session.run(None, inputs)
result = np.argmax(out)
return jsonify({"positive": bool(result)})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
predict() 函数是 Flask 路由,当应用程序运行时,启用 /predict URL。该函数仅允许 POST HTTP 方法。尚未描述样本输入和输出,因为应用程序的一个关键部分尚未完成:ONNX 模型尚不存在。本地下载 RoBERTa-SequenceClassification ONNX 模型,并将其放置在项目的根目录。这是最终项目结构的样子:
.
├── Dockerfile
├── requirements.txt
├── roberta-sequence-classification-9.onnx
└── webapp
└── app.py
1 directory, 4 files
在构建容器之前,最后一件事情是没有指令将模型复制到容器中。app.py 文件要求模型 roberta-sequence-classification-9.onnx 存在于 /webapp 目录中。更新 Dockerfile 反映这一点:
COPY roberta-sequence-classification-9.onnx /webapp
现在项目已经具备一切所需,因此可以构建容器并运行应用程序。在构建容器之前,让我们再次检查一切是否正常工作。创建一个新的虚拟环境,激活它,并安装所有依赖项:
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
ONNX 模型存在于项目的根目录,但应用程序希望它位于 /webapp 目录中,因此将其移动到该目录中,以避免 Flask 应用程序抱怨(当容器运行时不需要此额外步骤):
$ mv roberta-sequence-classification-9.onnx webapp/
现在通过使用 Python 调用 app.py 文件在本地运行应用程序:
$ cd webapp
$ python app.py
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
接下来,应用程序已准备好接收 HTTP 请求。到目前为止,我还没有展示预期的输入是什么。这些将是 JSON 格式的请求和 JSON 格式的响应。使用 curl 程序发送一个示例负载以检测情感:
$ curl -X POST -H "Content-Type: application/JSON" \
--data '["Containers are more or less interesting"]' \
http://0.0.0.0:5000/predict
{
"positive": false
}
$ curl -X POST -H "Content-Type: application/json" \
--data '["MLOps is critical for robustness"]' \
http://0.0.0.0:5000/predict
{
"positive": true
}
JSON 请求是一个包含单个字符串的数组,响应是一个具有指示句子情感的“positive”键的 JSON 对象。现在您已经验证应用程序正在运行,并且实时预测功能正常工作,是时候在本地创建容器以验证所有工作情况了。创建容器,并标记它为有意义的东西:
$ docker build -t alfredodeza/roberta .
[+] Building 185.3s (11/11) FINISHED
=> [internal] load metadata for docker.io/library/python:3.8
=> CACHED [1/6] FROM docker.io/library/python:3.8
=> [2/6] COPY ./requirements.txt /webapp/requirements.txt
=> [3/6] WORKDIR /webapp
=> [4/6] RUN pip install -r requirements.txt
=> [5/6] COPY webapp/* /webapp
=> [6/6] COPY roberta-sequence-classification-9.onnx /webapp
=> exporting to image
=> => naming to docker.io/alfredodeza/roberta
现在在本地运行容器,以与直接使用 Python 运行应用程序时相同的方式进行交互。记得将容器的端口映射到本地主机:
$ docker run -it -p 5000:5000 --rm alfredodeza/roberta
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
以与之前相同的方式发送 HTTP 请求。您可以再次使用 curl 程序:
$ curl -X POST -H "Content-Type: application/json" \
--data '["espresso is too strong"]' \
http://0.0.0.0:5000/predict
{
"positive": false
}
我们经历了许多步骤来打包模型并将其放入容器中。其中一些步骤可能显得令人不知所措,但具有挑战性的过程正是自动化和利用持续交付模式的绝佳机会。在下一节中,我将使用持续交付来自动化所有这些,并将此容器发布到任何人都可以消费的容器注册表中。
用于 ML 模型持续交付的基础设施即代码
最近在工作中,我发现公共仓库中存在一些测试容器映像,这些映像被测试基础设施广泛使用。在容器注册表(如 Docker Hub)中托管映像已经是朝着可重复构建和可靠测试的正确方向迈出了一大步。我遇到了一个问题,其中一个测试容器中使用的库需要更新,因此我搜索用于创建这些测试容器的文件。但我找不到它们。某个工程师曾在本地构建这些映像并上传到注册表中。这带来了一个大问题,因为我无法简单地更改映像,因为用于构建映像的文件已丢失。
经验丰富的容器开发者可以找到一种方法,获取大多数(如果不是全部)文件以重建容器,但这并不是重点。在这种问题情境中迈出的一步是创建能够自动从已知源文件(包括Dockerfile)构建这些容器的自动化。重新构建或解决更新容器并重新上传到注册表的问题,就像在停电时找到蜡烛和手电筒,而不是拥有在电力中断时自动启动的发电机一样。当发生类似我刚描述的情况时,请保持高度分析性。与其互相指责和责怪他人,不如将其视为提升过程中自动化的机会。
机器学习中也存在同样的问题。我们往往很容易习惯于手动操作(和复杂的!),但始终存在自动化的机会。本节不会再重复所有容器化所需的步骤(已在“容器”中涵盖),但我将详细介绍自动化所需的细节。让我们假设我们处于与我刚描述的类似情况,并且有人已经创建了一个包含在 Docker Hub 中的模型的容器。没有人知道训练模型是如何进入容器的;没有文档,需要更新。让我们增加一些复杂性:模型不在任何可找到的仓库中,而是作为注册模型存在于 Azure 中。让我们进行一些自动化操作来解决这个问题。
警告
可能会有诱惑将模型添加到 GitHub 仓库中。尽管这确实是可能的,但 GitHub(截至本文写作时)有着 100 MB 的硬文件大小限制。如果您尝试打包的模型接近这个大小,可能无法将其添加到仓库中。此外,Git(版本控制系统)并不适合处理二进制文件的版本控制,这会导致仓库因此而变得庞大。
在当前的问题场景中,模型在 Azure ML 平台上是可用的并且先前已注册。我之前没有一个,因此我迅速使用 Azure ML Studio 注册了 RoBERTa-SequenceClassification。点击“模型”部分,然后如 图 4-1 所示,“注册模型”。

图 4-1. Azure 模型注册菜单
填写 图 4-2 中显示的表单,并附上必要的细节。在我的情况下,我将模型下载到本地,需要使用“上传文件”字段上传它。

图 4-2. Azure 模型注册表单
注意
如果想了解如何在 Azure 中注册模型的更多信息,我在 “注册模型” 中使用 Python SDK 讲解了如何做。
现在预训练模型已经在 Azure 中,让我们重用 “打包 ML 模型” 中相同的项目。所有执行(本地)实时推理的重活都已完成,因此创建一个新的 GitHub 仓库并添加项目内容,除了 ONNX 模型。请记住,GitHub 中的文件有大小限制,因此无法将 ONNX 模型添加到 GitHub 仓库中。创建一个 .gitignore 文件来忽略模型,防止误添加:
*onnx
在将不包含 ONNX 模型的 Git 仓库内容推送后,我们已准备好开始自动化模型的创建和交付。为此,我们将使用 GitHub Actions,在触发满足可配置条件的情况下,从 Azure 获取已注册的模型,创建容器,最后将其推送到容器注册表。首先,在项目的根目录下创建一个 .github/workflows/ 目录,然后添加一个 main.yml 文件,内容如下:
name: Build and package RoBERTa-sequencing to Dockerhub
on:
# Triggers the workflow on push or pull request events for the main branch
push:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
到目前为止的配置除了定义操作外什么也没做。您可以定义任意数量的作业,在这种情况下,我们定义一个 build 作业来整合所有内容。将以下内容附加到您之前创建的 main.yml 文件中:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Authenticate with Azure
uses: azure/login@v1
with:
creds: ${{secrets.AZURE_CREDENTIALS}}
- name: set auto-install of extensions
run: az config set extension.use_dynamic_install=yes_without_prompt
- name: attach workspace
run: az ml folder attach -w "ml-ws" -g "practical-mlops"
- name: retrieve the model
run: az ml model download -t "." --model-id "roberta-sequence:1"
- name: build flask-app container
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
push: false
tags: alfredodeza/flask-roberta:latest
构建作业有许多步骤。在这种情况下,每个步骤都有一个明确的任务,这是分离失败域的一个很好方法。如果所有内容都在单个脚本中,那么找出潜在问题将更加困难。第一步是当操作触发时检出仓库。接下来,由于 ONNX 模型在本地不存在,我们需要从 Azure 检索它,因此必须使用 Azure 动作进行身份验证。身份验证后,az 工具就可用了,您必须附加工作区和组的文件夹。最后,作业可以通过其 ID 检索模型。
注意
YAML 文件中的某些步骤具有 uses 指令,该指令标识了外部操作(例如 actions/checkout)及其版本。版本可以是存储库的分支或发布的标签。对于 checkout 来说,是 v2 标签。
一旦所有这些步骤完成,RoBERTa-Sequence 模型应该位于项目的根目录,从而使下一步能够正确构建容器。
工作流文件使用 AZURE_CREDENTIALS。这些与一个特殊语法一起使用,允许工作流检索为存储库配置的密钥。这些凭据是服务主体信息。如果您对服务主体不熟悉,可以在 “认证” 中找到相关信息。您需要配置有权访问模型所在的工作区和组的服务主体配置。通过转到设置,然后选择“秘密”,最后点击“新存储库秘密”链接,在 GitHub 存储库上添加密钥。图 4-3 显示了您在添加新密钥时会看到的表单。

图 4-3. 添加密钥
将更改提交并推送到您的存储库,然后转到操作选项卡。新的运行会立即计划,并应在几秒钟内开始运行。几分钟后,所有事情应该已经完成。在我的情况下,图 4-4 显示需要接近四分钟。

图 4-4. GitHub 动作成功
现在,完成一个成功的作业运行需要考虑多个移动部件。在设计一组新步骤(或管道,如我将在下一节中介绍的),一个好主意是列出这些步骤并确定贪婪步骤。这些 贪婪步骤 尝试做得太多,并且有很多责任。乍一看,很难识别可能存在问题的任何步骤。维护 CI/CD 作业的过程包括精细化步骤的责任并相应地调整它们。
一旦确定了步骤,您可以将它们分解为更小的步骤,这将帮助您更快地理解每个部分的责任。更快的理解意味着更容易调试,尽管这并不立即明显,但养成这种习惯会给您带来好处。
这些是我们打包 RoBERTa-Sequence 模型的步骤:
-
检出存储库的当前分支。
-
身份验证到 Azure 云。
-
配置自动安装 Azure CLI 扩展。
-
将文件夹附加到与工作区交互。
-
下载 ONNX 模型。
-
构建当前存储库的容器。
不过,还有一个最后缺少的项目,那就是在构建后发布容器。不同的容器注册表在此处需要不同的选项,但大多数都支持 GitHub Actions,这是令人耳目一新的。Docker Hub 非常简单,只需要创建一个令牌,然后将其保存为 GitHub 项目密钥,以及您的 Docker Hub 用户名。一旦设置完成,调整工作流文件以包括在构建之前的身份验证步骤:
- name: Authenticate to Docker hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
最后,更新构建步骤以使用push: true。
最近,GitHub 也发布了一个容器注册表提供,并且它与 GitHub Actions 的集成非常直接。相同的 Docker 步骤可以在进行轻微更改并创建 PAT(个人访问令牌)后使用。首先,通过转到您的 GitHub 账户设置,点击“开发者设置”,最后点击“个人访问”令牌来创建 PAT。一旦页面加载完成,点击“生成新的令牌”。在备注部分给它一个有意义的描述,并确保该令牌具有适当的权限,如我在图 4-5 中所做的那样。

图 4-5. GitHub 个人访问令牌
完成后,会显示一个新页面,并呈现实际的令牌。这是您在明文中看到令牌的唯一时刻,因此确保现在复制它。接下来,转到包含容器代码的存储库,并创建一个新的存储库密钥,就像您在 Azure 服务主体凭据中所做的那样。将新密钥命名为GH_REGISTRY,并粘贴在前一步骤中创建的 PAT 的内容。现在,您已准备好更新 Docker 步骤,以使用新令牌和 GitHub 的容器注册表发布包:
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_REGISTRY }}
- name: build flask-app and push to registry
uses: docker/build-push-action@v2
with:
context: ./
tags: ghcr.io/alfredodeza/flask-roberta:latest
push: true
在我的情况下,alfredodeza 是我的 GitHub 账号,所以我可以标记它以及flask-roberta仓库的名称。这些需要与您的账号和仓库相匹配。将更改推送到主分支(或合并拉取请求后),作业将被触发。模型应从 Azure 中拉取,打包到容器中,并最终作为 GitHub 包发布到其容器注册表中,看起来类似于图 4-6。

图 4-6. GitHub 包容器
现在,容器正在通过利用 GitHub 的 CI/CD 提供和容器注册表以完全自动化的方式打包和分发 ONNX 模型,我们解决了本章开始时我假设的问题情景:模型需要打包到容器中,但容器文件不可用。通过这种方式,您为其他人和过程本身提供了清晰度。它被分成了小步骤,允许对容器进行任何更新。最后,这些步骤将容器发布到选择的注册表中。
除了打包和发布容器外,CI/CD 环境还可以完成很多其他任务。CI/CD 平台是自动化和可靠结果的基础。在下一节中,我将介绍一些在任何平台上都适用的好的想法。通过了解其他平台中可用的一般模式,你可以利用这些功能,而不必担心实现细节。
使用云流水线
第一次听说流水线时,我认为它们比典型的脚本模式(代表构建的一组过程化指令)更高级。但事实上,流水线并不是什么高级概念。如果你在任何持续集成平台中处理过 shell 脚本,那么使用流水线将会非常直观。流水线只不过是一组步骤(或指令),可以在运行时实现特定目标,例如,在生产环境中发布模型。例如,一个包含三个步骤来训练模型的流水线可以像图 4-7 中所示那样简单。

图 4-7. 简单流水线
可以将同样的流水线表示为一个 shell 脚本,它可以同时执行所有三个操作。使用流水线将关注点分离有多个好处。当每个步骤都有特定的责任(或关注点)时,更容易理解。如果一个单步流水线用于获取数据、验证数据并训练模型失败了,那么失败的原因并不明显。确实,你可以深入细节,查看日志,并检查实际错误。如果你将流水线分成三个步骤,并且“训练模型”步骤失败了,你可以缩小失败范围,并更快地找到可能的解决方案。
提示
一个通用的建议是,你可以将其应用到机器学习的多个方面中,考虑让任何操作在未来失败的情况下更加简单化。避免急于快速部署和运行一个流水线(就像在这种情况下一样),因为这样做更容易。花些时间思考,什么会让你(和其他人)更容易构建机器学习基础设施。当失败发生时,如果你识别出问题的方面,回到实现并进行改进。你可以应用 CI/CD 的概念来进行改进:持续评估和改进流程是一个健全的策略,适用于稳健的环境。
云流水线与任何其他持续集成平台并无不同,只是它们由云提供商托管或管理。
有些 CI/CD 流水线的定义可能会试图严格定义流水线的元素或部分。实际上,我认为流水线的部分应该松散定义,不受定义的限制。RedHat 在这里有一个很好的解释,描述了五个常见元素:构建、测试、发布、部署和验证。这些元素主要用于混合和匹配,而不是严格包含在流水线中。例如,如果您正在构建的模型不需要部署,那么根本不需要执行部署步骤。同样地,如果您的工作流程需要提取和预处理数据,您需要将其实现为另一个步骤。
现在您已经了解到流水线基本上与具有多个步骤的 CI/CD 平台相同,将机器学习操作应用于可操作的流水线应该是直接的。图 4-8 显示了一个相当简单的假定流水线,但这也可以涉及其他几个步骤,正如我提到的那样,这些元素可以混合和匹配以执行任意数量的操作和步骤。

图 4-8. 涉及的流水线
AWS SageMaker 出色地提供了准备好使用的示例,用于创建涉及的流水线,其中包括运行多个步骤所需的所有内容。SageMaker 是一个专门的机器学习平台,不仅提供流水线中的步骤来完成发布模型等目标。由于它专门用于机器学习,您可以接触到对于将模型投入生产非常重要的功能。这些功能在其他常见平台如 GitHub Actions 中并不存在,或者如果存在的话,它们的考虑可能不如此周到,因为 GitHub Actions 或 Jenkins 等平台的主要目标不是训练机器学习模型,而是尽可能通用,以适应大多数常见用例。
另一个相当难以解决的关键问题是,专门用于训练的机器(例如,GPU 密集型任务)在通用流水线提供中要么根本不可用,要么难以配置。
打开 SageMaker Studio 并转到左侧边栏的“组件和注册表”部分,然后选择“项目”。显示多个 SageMaker 项目模板供您选择,如图 4-9 所示。

图 4-9. SageMaker 模板
注意
虽然这些示例旨在帮助您入门,并提供了 Jupyter Notebooks,但它们非常适合了解涉及的步骤以及如何更改和适应它们以满足您的特定需求。在 SageMaker 中创建流水线实例后,训练并最终注册模型,您可以浏览流水线的参数,就像在图 4-10 中展示的那样。

图 4-10. 流水线参数
另一个展示所有步骤的流水线的关键部分也是可用的,如 图 4-11 所示。

图 4-11. SageMaker 流水线
正如您所见,准备数据、训练、评估和注册模型都是流水线的一部分。主要目标是注册模型,以便稍后在打包后进行实时推断部署。并非所有步骤都需要在这个特定的流水线中捕获。您可以创建其他流水线,每当有新注册的模型可用时运行。这样,该流水线就不是针对特定模型的,而是可以重复使用于任何成功训练和注册的模型。在应用于 MLOps 时,组件的可重用性和自动化是 DevOps 的另一个关键组成部分。
现在流水线被揭秘,我们可以看到一些增强可以使它们更加健壮,通过手动控制模型的逐步部署,甚至切换推断从一个模型到另一个模型。
模型的控制性逐步部署
有一些从 Web 服务部署中映射到将模型部署到生产环境中的策略的概念,例如为可扩展性创建几个实例的实时推断应用程序,并逐步从旧模型切换到新模型。在深入讨论涵盖部署模型控制部分的细节之前,值得描述一下这些概念可以发挥作用的策略。
我将在本节详细讨论这两种策略。虽然这些策略相似,但它们有特定的行为,您在部署时可以利用:
-
蓝绿部署
-
金丝雀部署
蓝绿部署是一种策略,将新版本部署到与生产环境完全相同的暂存环境中。有时,此暂存环境与生产环境相同,但流量路由不同(或分开)。不详细介绍,Kubernetes 是一个允许进行这种类型部署的平台,因为您可以在同一个 Kubernetes 集群中拥有两个版本,但将流量路由到新版本的不同地址(“蓝色”),同时生产流量仍然进入较旧版本(“绿色”)。分离的原因在于它允许进一步测试和确保新模型按预期工作。一旦验证完成并且满足某些条件,您可以修改配置以将流量从当前模型切换到新模型。
蓝绿部署存在一些问题,主要与复制生产环境的复杂性有关。再次强调,这是 Kubernetes 完美适配的情况之一,因为集群可以轻松容纳同一应用的不同版本。
金丝雀部署策略稍微复杂,并且有一定的风险性。根据你的信心水平和根据约束逐步改变配置的能力,这是将模型发送到生产的合理方式。在这种情况下,流量逐步路由到新模型,同时之前的模型正在提供预测。因此,两个版本都是活动的并且同时处理请求,但是以不同的比例进行。选择百分比增长的原因是你可以启用指标和其他检查来实时捕捉问题,如果条件不利,可以立即回滚。
例如,假设一个新的精度更高且没有漂移的模型准备投入生产。在几个新版本实例可用以开始接收流量之后,进行配置更改以将所有流量的 10%发送到新版本。当流量开始路由时,你会注意到响应中存在大量的错误。HTTP 500 错误表明应用程序存在内部错误。经过一些调查,发现进行推理的一个 Python 依赖项尝试导入已移动的模块,导致异常。如果应用程序每分钟接收一百个请求,则只有十个请求会遇到错误条件。在注意到错误后,你迅速将配置更改为将所有流量发送到当前部署的旧版本。这个操作也被称为回滚。
大多数云提供商都可以针对这些策略进行受控发布模型的能力。虽然这不是一个完全功能的示例,但 Azure Python SDK 在部署时可以定义新版本的流量百分比:
from azureml.core.webservice import AksEndpoint
endpoint.create_version(version_name = "2",
inference_config=inference_config,
models=[model],
traffic_percentile = 10)
endpoint.wait_for_deployment(True)
难点在于金丝雀部署的目标是逐步增加,直到traffic_percentile达到 100%。增加必须同时满足应用程序健康和最小(或零)错误率的约束条件。
生产模型的监控、日志记录和详细指标(除了模型性能)对于强大的部署策略至关重要。我认为它们对部署至关重要,但它们是覆盖在第六章中的强大 DevOps 实践的核心支柱。除了拥有自己章节的监控、日志记录和指标外,还有其他有趣的内容可以用来进行持续交付的检查。在接下来的部分中,我们将看到一些有意义的内容,并增加将模型部署到生产环境的信心。
模型部署的测试技术
到目前为止,在本章构建的容器工作得非常好,确切地做了我们需要的事情:从一些带有精心制作的消息的 HTTP 请求中,JSON 响应预测了情感。一位经验丰富的机器学习工程师可能在进入模型打包阶段之前就已经放置了准确性和漂移检测(在第六章中详细讨论)。让我们假设已经这样,并集中精力在部署模型到生产之前可以执行的其他有用测试上。
当您向容器发送 HTTP 请求以生成预测时,从开始到结束需要通过几个软件层。在高层次上,这些是关键的:
-
客户端发送 HTTP 请求,带有 JSON 体,格式为单个字符串数组。
-
必须存在特定的 HTTP 端口(5000)和端点(predict),并进行路由。
-
Python Flask 应用程序必须接收 JSON 负载并将其加载到本机 Python 中。
-
ONNX 运行时需要消耗字符串并生成预测。
-
一个带有 HTTP 200 响应的 JSON 响应需要包含预测的布尔值。
每一个这些高级步骤都可以(而且应该)进行测试。
自动化检查
在为本章组装容器时,我遇到了onnxruntime Python 模块的一些问题:文档没有固定(确切的版本号),导致安装了最新版本,需要不同的输入参数。模型的准确性很好,但我无法检测到显著的漂移。然而,一旦请求被消耗,我部署的模型就完全破损了。
随着时间的推移,应用程序变得更加优秀和更加弹性。另一位工程师可能会添加错误处理以在检测到无效输入时响应错误消息,并可能使用适当的 HTTP 错误代码以及客户端可以理解的漂亮错误消息的 HTTP 响应。在允许模型进入生产之前,您必须测试这些类型的增加和行为。
有时不会出现 HTTP 错误条件,也不会出现 Python 的回溯。如果我对 JSON 响应进行如下更改会发生什么:
{
"positive": "false"
}
在不回顾之前章节的情况下,你能分辨出差异吗?这种变化将不会被注意到。金丝雀部署策略将达到 100%而没有检测到任何错误。机器学习工程师将对高准确率和无漂移感到满意。然而,这种变化完全破坏了模型的效果。如果你没有察觉到差异,那没关系。我经常遇到这些类型的问题,有时候可能需要花几个小时来检测问题:它使用的不是false(布尔值),而是"false"(字符串)。
这些检查任何时候都不应该是手动的;手动验证应该尽量减少。自动化应该是一个高优先级的任务,到目前为止我提出的建议都可以作为流水线的一部分添加进去。这些检查可以泛化到其他模型以供重复使用,但在高层次上,它们可以像图 4-12 所示的方式并行运行。

图 4-12. 自动化检查
代码检查
除了我提到的一些功能性检查,比如发送 HTTP 请求,还有更接近 Flask 应用程序中代码的其他检查,比如使用一个代码检查工具(我推荐 Python 使用 Flake8)。最好自动化所有这些检查,以避免在生产发布时陷入麻烦。无论你处于什么样的开发环境,我强烈建议启用代码的代码检查工具。在创建 Flask 应用程序时,我在适应 HTTP 请求时发现了错误。这里是代码检查工具输出的一个简短示例:
$ flake8 webapp/app.py
webapp/app.py:9:13: F821 undefined name 'RobertaTokenizer'
未定义的名称会破坏应用程序。在这种情况下,我忘记从transformers模块导入RobertaTokenizer。当我意识到这一点后,我添加了导入并修复了它。这不会花费我超过几秒钟的时间。
实际上,你能越早发现这些问题,效果越好。谈到软件安全时,通常会听到“软件供应链”,其中链就是从开发到将代码发布到生产的所有步骤。在这一系列事件中,有一个持续不断的推动向左移的趋势。如果你把这些步骤看作一个大链条,最左边的链接是开发者创建和更新软件,而链条的末端(最右边的)是已发布的产品,最终用户可以与之交互。
尽早左移错误检测,效果越好。这是因为这比等待到生产阶段再进行回滚更便宜和更快。
持续改进
几年前,我曾是一个大型开源软件的发布经理。这个软件的发布过程非常复杂,发布的时间从两天到整整一周不等。由于我还负责其他系统,所以很难做出改进。有一次,在试图发布时,按照发布包的许多不同步骤,一个核心开发者要求我最后再加一个改动。我没有立刻说“不行”,而是问道:“这个改动已经测试过了吗?”
回应完全出乎意料:“别荒谬,阿尔弗雷多,这只是一个一行的改动,而且是一个函数的文档注释。我们真的需要这个改动成为发布的一部分。” 推动这个改动来自最高层,我不得不让步。我添加了这个临时改动并发布了版本。
第二天早晨回来后,用户(尤其是客户)都抱怨最新版本完全不可用。虽然它安装了,但根本无法运行。罪魁祸首是一行代码的变更,尽管它只是一个函数内的注释,却被其他代码解析了。该注释中有一个意外的语法,导致应用程序无法启动。这个故事并不是要责备开发者。他并不知道更好的方法。整个过程对所有参与者来说都是一个学习时刻,现在大家都清楚这个一行代码变更有多昂贵。
随后出现了一系列破坏性事件。除了重新启动发布流程外,对一个变更的测试阶段又花费了额外的一天时间。最后,我不得不废弃已发布的包,并重新设置存储库,以便新用户获取先前的版本。
这是一个极为昂贵的事情。参与其中的人数和高影响力使得这是一个绝佳的机会,以明确表示这种情况不应再次发生——即使只是一行代码的更改。越早检测到,影响就越小,修复的成本也更低。
结论
持续交付和不断反馈的实践对于强大的工作流至关重要。正如本章所证明的那样,自动化和持续改进反馈循环的价值巨大。打包容器以及流水线和 CI/CD 平台的一般功能,旨在使添加更多检查和验证变得更加容易,这些都旨在增加模型交付到生产环境的信心。
将模型发布到生产环境是首要目标,但要在非常高的信心水平下完成这一过程,以及保证步骤的韧性,是您应该努力达到的。当流程一旦建立,您的任务并未结束。您必须不断寻找方法,通过问自己这样一个问题来感谢自己:今天我能添加什么来让生活更轻松,如果这个流程失败了?最后,我强烈建议以一种易于添加更多检查和验证的方式创建这些工作流程。如果难以实现,没有人会愿意碰它,从而打败了将模型交付到生产环境的强大流水线的初衷。
现在您已经很好地掌握了模型交付及其自动化的情况,我们将在下一章深入探讨 AutoML 和 Kaizen。
练习
-
在容器中创建您自己的 Flask 应用程序,将其发布到 GitHub 存储库,并进行详细文档化,同时添加 GitHub Actions 以确保其正确构建。
-
修改 ONNX 容器以便将其推送至 Docker Hub 而不是 GitHub Packages。
-
修改 SageMaker 流水线,使其在训练后在注册模型之前提示您。
-
使用 Azure SDK,创建一个 Jupyter 笔记本,以增加流量流向容器的百分位。
批判性思维讨论问题
-
至少命名四项关键检查,以验证容器中打包的模型是否正确构建。
-
金丝雀部署(canary deployment)和蓝绿部署(blue-green deployment)之间有什么区别?你更喜欢哪一种?为什么?
-
为什么与使用 GitHub Actions 相比,云管道(cloud pipelines)更有用?至少列举三个不同之处。
-
打包容器 的意思是什么?为什么这很有用?
-
机器学习模型打包的三个特征是什么?
第五章:自动机器学习(AutoML)与改善机器学习(KaizenML)
作者:Noah Gift
过于受规则束缚,部分想法无法绽放。没有思想的规则是监狱,没有规则的思想是混乱。盆景教给我们平衡。在生活的各个方面,平衡规则与创新是一个普遍存在的问题。我曾经看过一出名为《人生游戏》的戏剧。它传达的信息是,在任何人解释规则之前,人们常常被要求为高风险玩游戏。此外,要判断自己是否赢得了比赛也不是那么容易。通常情况下,初学者(尤其是年轻人)需要规则或广泛的理论作为指导。然后,随着经验的积累,许多例外和变化逐渐使规则失效,同时规则的需求也减少了。盆景相比于人生的一大优势在于人们可以从致命的错误中学习。
Joseph Bogen 博士
现在是参与构建机器学习系统的一个激动人心的时刻。机器学习,即从数据中学习,对解决从自动驾驶车辆到更有效的癌症筛查和治疗等问题具有明确的价值。与此同时,自动化在推动这一领域的进展中扮演了关键角色,包括模型创建的自动化、AutoML 和机器学习周围其他任务的自动化,我称之为 KaizenML。
虽然 AutoML 严格专注于从干净数据中创建模型,但 KaizenML 则致力于自动化机器学习过程中的一切并对其进行改进。让我们深入讨论这两个主题,从 AutoML 为何如此重要开始。
注意
像安德鲁·吴(Andrew Ng)这样的机器学习专家现在承认,数据中心的方法比模型中心的过程更有优势。另一种表述是,改善,即从数据到软件再到模型,再到来自客户的反馈循环的整个系统的持续改进,是至关重要的。在我看来,KaizenML 意味着你正在改善机器学习系统的所有方面:数据质量、软件质量和模型质量。
AutoML
作者厄普顿·辛克莱尔(Upton Sinclair)曾经著名地说过:“当一个人的薪水依赖于他不理解某事时,要让他理解某事是困难的。” 厄普顿·辛克莱尔的这句话在 Netflix 的纪录片《社交迷局》中所记录的社交媒体误导中得到了很好的体现。假设你在一家大规模传播误导信息并因此获得丰厚报酬的公司工作。在这种情况下,几乎不可能接受你是这一过程中的参与者,而且你的公司实际上从误导信息中获得了丰厚的利润。这为你提供了优厚的薪水和生活方式的贡献。
类似地,我想出了一些我称之为“自动化定律”的东西。一旦开始讨论自动化任务,最终自动化就会发生。一些例子包括用云计算取代数据中心和用机器取代电话总机操作员。许多公司紧紧抓住他们的数据中心,说云是世界上所有邪恶的根源。然而,最终,他们要么转向云,要么正在过渡到云中。
从大约 1880 年到 1980 年左右,完全自动化手动切换电话通话花了近 100 年时间,使一台机器可以完成这些任务,但这确实发生了。机器非常擅长自动化劳动密集型的手动任务。如果你的工作在 1970 年涉及切换电话通话,你可能会嘲笑自动化你所做的事情的想法,因为你理解这个任务有多么困难。今天,通过数据科学,可能我们正在像狂热地将超参数值推入 Python 函数并在 Kaggle 上分享结果的电话总机操作员一样,不知道所有这一切正在被自动化地进行中。
在书中,《我们如何知道什么不是真的》,托马斯·吉洛维奇指出了自我限制策略:
实际上有两类自我限制策略,真实和假装的。 “真实”自我限制涉及将可见的成功障碍放置在自己的道路上。这些障碍使成功的可能性较小,但它们为失败提供了一个现成的借口。在考试前不学习的学生或在试镜前饮酒的有抱负的演员都是很好的例子。有时候失败几乎是必然的,但至少不会被认为缺乏相关能力(或者至少希望如此)。
“假”自我限制,另一方面,从某些方面来说,是一种风险较小的策略,这种策略仅仅是声称在成功的道路上存在困难障碍。这种自我限制仅仅是为可能的糟糕表现找借口,无论是事前还是事后。
当数据科学计划失败时,很容易陷入其中任何一种自我限制策略。在数据科学中的一个例子可能是在项目的某些方面不使用 AutoML;这对项目的成功来说是一个“真正”的障碍。然而,软件工程的一个黄金法则是在手头任务中使用最好的工具。使用可用的最佳工具的原因是它们减少了开发的软件的复杂性。一个减少复杂性的“最佳类”工具的绝佳例子是 GitHub Actions,因为它可以简化自动化测试的创建。另一个例子是像 Visual Studio Code 这样的编辑器,因为它能够进行代码完成、语法高亮和最小配置的 linting。这两种工具通过简化软件创建的过程显著提高了开发人员的生产力。
在数据科学中,“使用最佳可用工具”的口号需要宣传。或者,如果一个数据科学项目失败(通常情况下会失败),自我限制的策略可能是说问题太具挑战性了。在任何情况下,当适用时,自动化的采纳是自我限制的解决方案。
让我们比较食物与机器学习在 图 5-1 中。请注意,食物有多种形式,从你在商店购买的面粉制作披萨到送货上门的披萨。即使其中一种比另一种复杂得多(例如,从头开始制作披萨与订购现成的热披萨),也并不意味着送货上门的选择就不算是食物。难度与否并不等同于完整性或真实性。
同样,不接受现实并不意味着事情并未发生。形容否认现实的另一种方式是称之为“魔幻思维”。许多魔幻思想家在 COVID-19 疫情初期说:“这只是流感”,试图安抚自己(及他人),认为危险并没有看起来那么严重。然而,2021 年的数据完全说明了不同的情况。在美国,COVID-19 的死亡人数接近所有形式心脏病的死亡人数的 75%,而心脏病目前是美国的主要死因。同样,贾斯汀·福克斯在一篇使用 CDC 数据的彭博新闻文章中指出,对大多数年龄段来说,这场大流行比流感致命多次。见 图 5-2。

图 5-1. 食物与 ML 的比较

图 5-2. COVID-19 与流感和肺炎的比较(来源:彭博新闻)
AutoML 对数据科学家来说是一个拐点,因为它与其他历史趋势(自动化和魔幻思维)类似。任何能自动化的都将自动化。接受这一趋势而不是与之抗争将会在机器学习的工作流程中大大提升生产力。特别是,AutoML 可能是全面实施 MLOps 理念中最关键的技术之一。
在 COVID-19 爆发之前,加州大学伯克利分校的研究科学家珍妮弗·道德纳博士与合作者埃玛纽埃尔·夏朋提尔博士致力于研究基因编辑的艰巨任务。当 COVID-19 疫情开始时,道德纳博士意识到她需要迅速将这项研究转化为一种开创性的方法,以加速疫苗的研发。因此,她开始了“拯救世界”的工作。
注
COVID-19 药物发现与 MLOps 有何关联?数据科学中的一个关键问题是将研究解决方案投入生产。同样,医学研究的一个基本问题是将发现带到从中受益的患者手中。
在《破译者:詹妮弗·道德纳、基因编辑与人类未来》(Simon & Schuster Australia)中,沃尔特·艾萨克森描述了道德纳博士现在“…现在强烈认为基础研究应与转化研究结合,将发现从实验室推广到床边…” 现在,诺贝尔奖获得者詹妮弗·道德纳博士与埃曼纽尔·夏朋蒂埃博士共同创造了导致基因编辑和 CRISPR 机制商业应用的研究。
她的竞争对手之一,最终参与竞争疫苗现代纳公司的冯·张博士提到,加州大学伯克利分校实验室并未致力于将其应用于人类细胞。他批评说,他的实验室正在利用 CRISPR 研究来针对人类细胞,而道德纳博士则专注于研究本身。
这种批评是关于谁应该声称这些发现的专利争议的核心,即多少工作是研究,多少是研究应用?这听起来有点像数据科学与软件工程的争论,不是吗?最终,道德纳博士确实“将其投入生产”形式化为辉瑞疫苗。我最近接种了这种疫苗,像许多人一样,我为其投入生产感到高兴。
如果我们像“运作”COVID-19 疫苗的科学家们一样急迫地解决问题,我们还能共同完成什么?当我在初创企业担任工程经理时,我喜欢问人们假设性问题。一些变体问题是“如果你必须在截止日期前拯救世界呢”?我喜欢这个问题,因为它能迅速切中要害。它迅速切入问题的本质,因为如果时钟在倒计时拯救数百万生命,你只会解决问题的基本要素。
Netflix 有一部令人难以置信的纪录片名为《第二次世界大战的彩色影像》。关于这部纪录片令人印象深刻的地方在于,它展示了那些历史悲剧事件的真实修复和上色的影像。这真的帮助你想象当时亲历那些事件是什么感觉。在这些线索中,想象一下自己身处一个需要解决能拯救世界的技术问题的情境。当然,如果你搞错了,你所知道的每个人都将遭受可怕的命运。然而,自动机器学习或任何形式的自动化结合问题的紧急性,只解决问题的必要组成部分,可以导致全球更好的结果:例如,药物发现和癌症检测。
这种情境思维方式为决定如何解决问题增加了一个澄清的组成部分。无论你在做什么,都很重要,否则就无关紧要。这与工作在 COVID-19 疫苗上的科学家们的方式非常相似。要么科学家们所做的导致了更快的 COVID-19 疫苗,要么没有。因此,每天的浪费都是更多全球人口被病毒夺去的一天。
同样,我记得在 2016 年左右去过旧金山湾区的一个时髦初创公司,并评论说他们从许多顶级风险投资公司获得了 3000 万美元的资金。首席运营官私下告诉我,他非常担心他们没有实际产品或赚钱的途径。多年后,他们甚至获得了更多的资金,但我仍然不确定他们的实际产品是什么。
因为这家公司无法创造收入,所以进行筹款。如果你无法筹款,那么你必须创造收入。同样,如果你无法将你的机器学习模型投入生产,你就会继续做“ML 研究”,即在 Kaggle 项目上不断调整超参数。因此,Kaggle 从业者应该问的一个好问题是,“我们确定我们不只是通过训练谷歌的 AutoML 技术来自动化调整超参数的工作吗?”
我们倾向于擅长的事情。专注于自己擅长的事情有许多美好之处,比如会带来成功的职业生涯。然而,有时候也需要挑战自己,暂时从解决问题的最紧急方式出发,就像 Doudna 博士和 Zhang 博士在 COVID-19 方面所做的那样。这会改变你的方法吗?例如,如果我有四个小时来训练一个模型并将其投入生产以拯救世界,我会尽量少写代码,并使用 Azure AutoML、Apple Create ML、Google AutoML、H20 或 Ludwig 等现成的自动化工具。在这种情况下,后续问题就变成了,为什么我要写任何代码,或者至少为所有机器学习工程项目写尽可能少的代码?
世界需要高质量的机器学习模型投入生产,特别是因为有许多紧急问题需要解决:寻找治愈癌症的方法,优化清洁能源,改进药物发现和创造更安全的交通。社会可以通过现在可以自动化的事物来集体做到这一点,并致力于找到今天无法自动化的事物的自动化方法。
AutoML 是与在干净数据上训练模型相关的任务自动化。然而,在现实世界中,并非所有问题都那么简单,因此与机器学习相关的所有事物都需要自动化。这就是 KaizenML 介入的地方。Kaizen 在日语中意味着持续改进。有了 KazienML,您可以持续改进和自动化作为开发机器学习系统的核心方式。接下来让我们深入探讨这个概念。
MLOps 工业革命
许多机器学习的学生和从业者认为 AutoML 是一个极具争议的话题。数据科学是一种行为,而 AutoML 是一种技术——它们只是构建 ML 系统的一小部分,它们是互补的。AutoML 之所以具有争议,是因为数据科学家们认为它会取代他们的工作,然而事实上,AutoML 只是自动化和持续改进的一个极小部分,即 MLOps/ML 工程/KaizenML,如图 5-3 所述。

图 5-3. AutoML 是 KaizenML 的一个微小部分
从 1760 年到 1840 年的工业革命是人类任务由蒸汽和煤炭机器驱动的自动化过程中的一段充满戏剧性的时期。这种自动化导致人口增长、国内生产总值和生活质量的提高。后来,大约在 1870 年,第二次工业革命发生了,允许大规模生产和新的电网系统。
在 Disney+ 上有一部名为 Made in a Day 的优秀系列。第一集展示了特斯拉如何使用机器人进行汽车开发阶段。机器人会螺丝紧东西、将东西螺栓在一起并焊接零件。当看到这个工厂时,我想到人类是如何协助机器人的。基本上,他们把那些他们自己尚不能完全自动化的工作交给了机器人。
同样,当观察到充满独特雪花配置的传统数据科学工作流程,并且人类“紧固”超参数时,让我想到第二次工业革命中早期的福特装配厂。最终,手动的人类任务被自动化,而第一件被自动化的事情是最容易自动化的。
人们还问的另一个问题是,机器学习技术的许多方面是否真的必要,比如手动调整超参数,即选择聚类数。想象一下去到一个充满先进机器人技术的特斯拉工厂,并告诉自动化工程师人类也可以焊接零件。这种说法是不合逻辑的。当然,我们人类可以执行比我们更好的机器任务,但我们应该吗?同样地,对于许多繁琐和手动的机器学习方面,机器表现得更好。
在机器学习和人工智能领域可能很快发生的事情是,技术本质上会成为商品化。相反,自动化本身及其执行能力才是关键。关于物理制造的电视节目名称为“Made in a Day”,因为汽车或吉他只需一天制造!许多从事机器学习的公司在整整一年内都无法建立一个基于软件的模型,尽管这可能是未来的过程。
我看到可能很快发生的一个场景是,至少 80%的数据科学手动训练模型将被商品化的开源 AutoML 工具或下载的预构建模型所取代。这一未来可能会因开源项目(如 Ludwig)或商业项目(如 Apple CreateML)的发展而实现。用于训练机器学习模型的软件可能会变成像 Linux 内核那样自由和无处不在。
如果按照现有形式进行,数据科学可能会呈双峰分布;要么你年薪百万美元,要么你是初级人员。大部分竞争优势都来自传统软件工程的最佳实践:数据/用户、自动化、执行力,以及坚实的产品管理和业务实践。数据科学家可能会成为一种标准技能,就像会计、写作或其他情况下的批判性思维一样,而不仅仅是一个职业头衔。你可以称之为 MLOps 工业革命。
图 5-4 是这一实践的一个例子。想象一下 Kaggle 就像一个反馈环路,谷歌利用它来改进其 AutoML 工具。为什么他们不利用人类数据科学家训练模型来提升他们的 AutoML 服务呢?在数据科学 1.0 中,人类手动“点击按钮”,就像过去的电话接线员一样。与此同时,如果他们愿意,谷歌可以利用这些人类来训练他们的 AutoML 系统来执行这些手动数据科学任务。在许多情况下已经出现的数据科学 2.0 中,自动化工具彻底训练了之前在 Kaggle 上训练过的模型。

图 5-4. Kaggle 自动化
在机器学习和数据科学中,随着机器在其中的角色日益增加,MLOps 工业革命正在我们眼前发生。如果这些变化正在进行中,你应该投资于哪些技能呢?无论从技术还是业务的角度来看,都要在自动化和执行方面达到世界级水平。此外,要结合坚实的领域专业知识。在《创新如何运作:为何在自由中蓬勃发展》(Harper)一书中,作者马特·里德利清楚地解释了创新并不是构想的基础,而是将构想结合到执行中的过程。本质上是,它是否有效,是否会有人为此付费?
Kaizen 与 KaizenML
谈论数据科学、AutoML 和 MLOps(KaizenML)存在一个问题,那就是人们经常误解它们各自的含义。数据科学不再是解决方案,就像统计学不是解决问题的方案一样;它是一种行为。AutoML 只是一种技术,就像持续集成(CI)一样;它自动化了琐碎的任务。因此,AutoML 并不直接与数据科学竞争;而像巡航控制或半自动驾驶这样的技术,也不会与司机竞争。司机仍然必须控制车辆,并充当发生事件的中央仲裁者。同样地,即使在机器学习中有了广泛的自动化,人类仍然必须就更大的局面做出执行决策。
KaizenML/MLOps 是一种系统方法论,导致模型投入生产。在图 5-5 中,您可以看到未来可能发生的假设的 MLOps 工业革命。数据及其有效处理的专业知识因为是一种稀缺资源而成为竞争优势。随着 AutoML 技术的进步,今天许多数据科学家做的事情可能会消失。现代车辆很少不配备某种形式的巡航控制或手动变速器。同样,将来数据科学家很少需要调整超参数也可能会不常见。那么当前的数据科学家可能会转变为 ML 工程师或领域专家,他们作为工作的一部分“从事数据科学”。

图 5-5. MLOps 工业革命
仅仅谈论 AutoML 与数据科学的问题在于,它淡化了更重要的自动化和持续改进问题。机器学习技术的自动化如此极化,以至于核心问题被忽视:一切都应该自动化,不仅仅是像超参数调优这样单调乏味的部分。通过持续改进的自动化使数据科学家、机器学习工程师和整个组织能够专注于重要的事情,即执行。正如您可以在图 5-6 中看到的那样,改善是日本的一个术语,意为持续改进。二战后,日本围绕这一概念建立了其汽车工业。本质上,如果发现了问题或未经优化的地方,就应该修复它。同样,通过 KaizenML,从特征工程到 AutoML,机器学习的每个方面都在不断改进。

图 5-6. Kaizen 或 AutoML
地球上的每个人都应该做数据科学和编程,因为这些都是批判性思维的形式。最近的大流行病是一个关于理解数据科学对个人生活至关重要的重要警示。许多人死于他们没有理解到显示 COVID-19 并不像流感那样仅仅是因为它更加致命的数据;同样,关于人们因错误计算疫苗对他们自己或其社区脆弱成员的风险相对于 COVID-19 对他们自己的风险的故事也充斥着。理解数据科学可以拯救你的生命,因此,任何人都应该有数据科学家所拥有的工具。
这些工具是“人权”,不应该只属于精英僧侣的手中。暗示“精英”人士才能写简单程序、理解机器学习或从事数据科学是不合逻辑的。自动化将使数据科学和编程变得足够简单,以至于每个人都可以做到,而且在许多情况下,他们甚至可以利用现有的自动化来做到。
KaizenML/MLOps 专注于通过机器学习和受 DevOps 影响的软件工程解决问题,以实现业务价值或改善人类条件,例如治愈癌症。
特征存储
所有复杂的软件系统都需要自动化和简化关键组件。DevOps 是关于自动化软件测试和部署。MLOps 不仅仅是这样,还要提高数据和机器学习模型的质量。我之前称这些对数据和机器学习模型的持续改进为 KaizenML。一种思考方式是,DevOps + KaizenML = MLOps。KaizenML 包括构建特征存储,即高质量机器学习输入的注册以及监测数据漂移、注册和服务化 ML 模型。
在图 5-7 中,请注意在手动数据科学中,一切都是定制的。因此,数据质量较低,甚至很难使工作模型进入生产并解决问题。然而,随着从数据到特征再到实际在生产中服务模型的自动化增加,这导致了更好的结果。

图 5-7. 特征存储作为 KaizenML 的一部分
与 KaizenML 密切相关的是特征存储的概念,即持续改进的机器学习。Uber 工程博客对特征存储解决的问题有很好的详细解释。根据 Uber 的说法,它解决了两个问题:
-
允许用户将构建的特征添加到共享的特征存储中。
-
一旦特征存储中有了特征,它们就很容易在训练和预测中使用。
在图 5-8 中,你可以看到数据科学是一种行为,但 AutoML 是一种技术。AutoML 可能只解决了自动化问题的 5%。数据本身需要通过 ETL 作业管理进行自动化。特征存储需要自动化以改进 ML 输入。最后,部署需要通过自动部署(CD)和云弹性使用的本地化使用进行自动化。所有复杂软件系统都需要自动化,特征存储只是许多 MLOps 组件中需要持续改进的一个,即 KaizenML。

图 5-8. 特征存储是系统方法论自动化的一部分
Feature Store 有许多实际用例。例如,Uber 解释称它 在 Feature Store 中使用了 10,000 个特征,以加速机器学习项目并构建 AutoML 解决方案。此外,像 Databricks 这样的平台已将 Feature Store 集成到其大数据系统中。例如,在 Figure 5-9 中,您可以看到原始数据是输入,经过转换后形成更精细和专业的特征注册表,能够解决批处理和在线问题。
在 Figure 5-10 中,请注意传统数据仓库与 MLOps 特征存储之间的相似性和差异。数据仓库主要用于高层的商业智能系统,而特征存储则为 ML 系统提供输入。机器学习数据处理包括数据归一化、数据清洗以及寻找能够改进 ML 模型的适当特征。创建特征存储系统是完全采用从构思到生产的机器学习自动化过程的又一种方式。

Figure 5-9. Databricks 特征存储

Figure 5-10. 数据仓库与特征存储
接下来,让我们离开理论,实践使用苹果 ML 生态系统构建机器学习模型的技术。我们将使用其高级 AutoML 框架 CreateML 进行操作。
苹果生态系统
苹果可能看起来不太可能进入机器学习工具领域,直到您深入了解。苹果在移动开发周围有着丰富的生态系统。根据 Statista 的数据,2019 年至 2020 年,苹果应用商店的全球总收入从 555 亿美元增长到了 723 亿美元。苹果受益于开发者在其应用商店中创建销售产品。
我记得曾经和一个相当轻视的教授讨论过“构建机器学习应用程序”的话题,可能是因为他偏向于复杂性和在研究中的发现。从某种意义上说,软件行业的思维方式与大学里的研究人员相反。撰写机器学习学术论文与将机器学习操作化为“构建应用程序”是两种截然不同的方向。这种“思想”与“执行”的分歧正如之前讨论的那样。
Apple 希望你在其应用商店中构建应用程序,因为它从每笔交易中抽取 15% 至 30% 的费用。苹果使开发者工具越好,应用程序在应用商店中生存的可能性就越大。在商学院有一句话:“在哪里建立汉堡王?在麦当劳旁边。”这句话的意思是说,你不需要花钱研究扩展到哪里,因为顶级竞争对手已经做了这项工作。你可以依赖他们的专业知识——同样,机器学习的从业者可以依赖苹果的研究。他们看到未来是高级自动化机器学习在专用硬件上运行。
类似地,为什么许多风险投资公司只投资已由顶级风投公司投资的公司?因为他们不需要做任何工作;他们可以从更有经验的公司的专业知识中获利。同样,苹果在设备上的机器学习领域有着巨大的投资。特别是苹果自己开发芯片,如 A 系列:A12-A14,如 Figure 5-11 所示,包括 CPU、GPU 和专用神经网络硬件。

Figure 5-11. Apple 的 A14 芯片。
此外,新的芯片包括 Apple M1 架构,苹果在移动设备、笔记本电脑和台式机上使用,如 Figure 5-12 所示。

Figure 5-12. Apple 的 M1 芯片。
开发环境通过 Apple 的模型格式 Core ML 使用此技术。还有一个 Python 包可以将从 TensorFlow 和 Keras 等第三方训练库训练的模型转换为 Core ML。
Core ML 针对设备性能进行了优化,并与苹果硬件协同工作。有几种不明显的工作流程需要考虑:
-
使用 Apple 的 Create ML 框架来制作 AutoML 解决方案。
-
下载预训练模型并可选择转换为 Core ML 格式。可以从 tfhub 下载模型的位置之一。
-
通过其他框架编写代码自己训练模型,然后使用 coremltools 转换为 Core ML。
让我们深入了解 Apple 的 AutoML。
Apple 的 AutoML:Create ML。
Apple 的 ML 平台的核心创新之一是,它将封装在直观 GUI 中的强大 AutoML 技术暴露给用户。Apple Create ML 让你可以做以下事情:
-
创建 Core ML 模型。
-
预览模型性能。
-
在 Mac 上训练模型(利用其 M1 芯片堆栈)。
-
使用训练控制:即暂停、保存和恢复训练。
-
使用外置 GPU(eGPU)。
此外,它处理各种领域,包括图像、视频、动作、声音、文本和表格。让我们通过 Apple 的 CreateML 深入探讨 AutoML。注意 Figure 5-13 中许多自动化机器学习形式的完整列表,以及它们最终如何收敛到在 iOS 上运行的同一 Core ML 模型。

Figure 5-13. Create ML。
要开始使用 Create ML,请执行以下操作:
-
下载 XCode。
-
打开 XCode 并右键点击图标以启动 Create ML(参见 Figure 5-14)。

Figure 5-14. 打开 Create ML
接下来,使用图像分类器模板(见 Figure 5-15)。

Figure 5-15. 图像分类器模板
您可以在 书籍的 GitHub 存储库 中获取“猫和狗”的较小版本的 Kaggle 数据集。将 cats-dogs-small 数据集拖放到 Create ML 的 UI 中(参见 Figure 5-16)。

Figure 5-16. 上传数据
还有,将测试数据 拖放到 Create ML 的测试部分。
接下来,通过点击训练图标来训练模型。请注意,您可以通过右键单击模型来源多次训练模型。您可能希望尝试这样做,因为它允许您使用“增强”(如噪声、模糊、裁剪、曝光、翻转和旋转)测试,这些将使您能够创建更具一般适用性的更健壮模型(见 Figure 5-17)。

Figure 5-17. 训练后的模型
这个小数据集只需几秒钟就可以训练模型(尤其是如果您有更新的 Apple M1 硬件)。您可以通过查找互联网上的猫和狗图片、下载并将它们拖到预览图标中进行测试(见 Figure 5-18)。

Figure 5-18. 预览
最后一步是下载模型并在 iOS 应用程序中使用。请注意,在 Figure 5-19 中,我使用 OS X Finder 菜单命名模型并保存到我的桌面。这一最终步骤可能是那些希望构建仅在其手机上运行的定制 iOS 应用程序的业余爱好者的终端步骤。保存模型后,您可以选择将其转换为另一种格式,例如 ONNX,然后在诸如 Microsoft Azure 的云平台上运行它。

Figure 5-19. 创建 ML 模型
很棒!您已经训练了您的第一个不需要任何代码的模型。随着更多这些工具的演进并进入消费者手中,未来将会非常美好。
可选的下一步骤:
-
您可以通过 下载更大的 Kaggle 数据集 来训练更复杂的模型
-
您可以尝试其他类型的 AutoML
-
您可以尝试使用增强技术
现在您已经了解如何使用 Create ML 训练模型,让我们深入了解如何进一步利用 Apple 的 Core ML 工具。
Apple 的 Core ML 工具
Apple 生态系统中更令人兴奋的工作流之一是通过 Python 库下载模型并将其转换为 Core ML 工具。有许多地方可以获取预训练模型,包括 TensorFlow Hub。
在本例中,让我们演示在 此 Colab 笔记本 中的代码。
首先,安装 coremltools 库:
!pip install coremltools
import coremltools
接下来,下载模型(基于 官方快速入门指南)。
导入 tensorflow 库:
# Download MobileNetv2 (using tf.keras)
keras_model = tf.keras.applications.MobileNetV2(
weights="imagenet",
input_shape=(224, 224, 3,),
classes=1000,
)
# Download class labels (from a separate file)
import urllib
label_url = 'https://storage.googleapis.com/download.tensorflow.org/\
data/ImageNetLabels.txt'
class_labels = urllib.request.urlopen(label_url).read().splitlines()
class_labels = class_labels[1:] # remove the first class which is background
assert len(class_labels) == 1000
# make sure entries of class_labels are strings
for i, label in enumerate(class_labels):
if isinstance(label, bytes):
class_labels[i] = label.decode("utf8")
转换模型并设置模型的元数据为正确的参数:
import coremltools as ct
# Define the input type as image,
# set preprocessing parameters to normalize the image
# to have its values in the interval [-1,1]
# as expected by the mobilenet model
image_input = ct.ImageType(shape=(1, 224, 224, 3,),
bias=[-1,-1,-1], scale=1/127)
# set class labels
classifier_config = ct.ClassifierConfig(class_labels)
# Convert the model using the Unified Conversion API
model = ct.convert(
keras_model, inputs=[image_input], classifier_config=classifier_config,
)
现在更新模型的元数据:
# Set feature descriptions (these show up as comments in XCode)
model.input_description["input_1"] = "Input image to be classified"
model.output_description["classLabel"] = "Most likely image category"
# Set model author name
model.author = "" # Set the license of the model
# Set the license of the model
model.license = ""# Set a short description for the Xcode UI
# Set a short description for the Xcode UI
model.short_description = "" # Set a version for the model
# Set a version for the model
model.version = "2.0"
最后,保存模型,在 Colab 下载并在 XCode 中打开进行预测(参见 图 5-20)。

Figure 5-20. 下载模型
# Save model
model.save("MobileNetV2.mlmodel")
# Load a saved model
loaded_model = ct.models.MLModel("MobileNetV2.mlmodel")
图 5-21 展示了一个预测示例。

Figure 5-21. 鳐鱼预测
这个过程的重要一点是,它比使用 AutoML 还要简单。因此,在许多情况下,下载由专家创建的模型(这些专家可以访问昂贵的计算集群)可能比自己训练模型更有意义。Apple 的 Core ML 框架允许使用定制的 AutoML 或预训练模型。
谷歌的 AutoML 和边缘计算机视觉
在过去几年里,我在顶尖数据科学大学教授了数百名学生一门名为“应用计算机视觉”的课程。课程的前提是使用最高级的工具快速构建解决方案,包括 Google AutoML 和边缘硬件,如包含 TPU 的 Coral.AI 芯片或 Intel Movidius。
图 5-22 展示了两个小型边缘机器学习解决方案的示例。

Figure 5-22. 边缘硬件
教授这门课程时,令人惊讶的是学生们多快能够采用“现成的”解决方案,将它们组合起来,并提出解决问题的方案。我见过在移动设备上运行的项目,包括口罩检测、车牌检测和垃圾分类应用,几乎没有编写代码。我们正处于一个新时代,MLOps 时代,将代码投入到工作应用程序中变得更加容易。
像苹果和谷歌一样,许多公司构建了一个垂直集成的堆栈,提供了机器学习框架、操作系统和专用硬件,如 ASIC(专用集成电路),用于执行特定的机器学习任务。例如,TPU 或 TensorFlow 处理单元正在积极开发中,定期更新芯片设计。边缘版本是一个专门设计的 ASIC,用于运行 ML 模型。这种紧密集成对于寻求快速创建真实世界机器学习解决方案的组织至关重要。
GCP 平台上有几种关键的计算机视觉方法(与其他云平台类似,服务名称不同)。这些选项按难度排序如下:
-
编写训练模型的机器学习代码
-
使用 Google AutoML Vision
-
从 TensorFlow Hub 或其他位置下载预训练模型
让我们检查一个 Google AutoML Vision 工作流程,该流程以部署到 iOS 设备的计算机视觉模型结束。无论您使用 Google 提供的样本数据集还是自己的数据集,该工作流程基本相同:
-
启动 Google Cloud 控制台并打开云 Shell。
-
启用 Google AutoML Vision API,并为项目授予权限;您需要设置
PROJECT_ID和USERNAME:gcloud projects add-iam-policy-binding $PROJECT_ID \ --member="user:$USERNAME" \ --role="roles/automl.admin" -
通过 CSV 文件将训练数据和标签上传到 Google Cloud Storage。
如果您设置了
${BUCKET}变量export BUCKET=$FOOBAR,那么只需三个命令就能复制 Google 的样本数据。这里以云分类(卷积云、积云、层云)为例。您可以在 Google Qwiklabs 中找到有关“使用 AutoML Vision 在云中对云图像进行分类”的详细步骤。在这个示例中,数据位于gs://spls/gsp223/images/位置,sed命令替换了具体路径:gsutil -m cp -r gs://spls/gsp223/images/* gs://${BUCKET} gsutil cp gs://spls/gsp223/data.csv . sed -i -e "s/placeholder/${BUCKET}/g" ./data.csv适用于 Google AutoML 的额外数据集
您可能还想尝试的其他两个数据集是 tf_flowers 数据 和 猫狗数据。另一个想法是上传您的数据。
-
视觉检查数据。
Google Cloud AutoML 系统的一个有价值的方面是使用高级工具检查数据,添加新标签或修复数据质量控制问题。请注意,在 图 5-23 中,您可以在不同的分类类别之间切换,这些分类类别恰好是花卉。
![pmlo 0523]()
图 5-23. 检查数据
-
训练模型并评估。
在控制台中点击按钮即可训练模型。Google 将这些选项汇集到其产品 Google Vertex AI 中。请注意,在 图 5-24 中,左侧面板上有一系列操作,从笔记本到批量预测。创建新的训练作业时,AutoML 和 AutoML Edge 都是选项。
![Google Vertex AI]()
图 5-24. Google Vertex AI
-
之后,使用内置工具评估训练好的模型(见 图 5-25)。
![pmlo 0525]()
图 5-25. 评估数据
-
处理模型:在线预测或下载。
使用 Google AutoML Vision,可以创建在线托管的端点或下载模型,并在边缘设备上进行预测:iOS、Android、Javascript、Coral 硬件或容器(见 图 5-26)。
![pmlo 0526]()
图 5-26. 下载模型
主要的要点是,Google Cloud 提供了一个经过验证的路径,从上传训练数据到无需或最小编码进行机器学习解决方案构建,可部署到边缘设备。这些选项都集成在 Google 的托管机器学习平台 Vertex AI 中。
接下来,让我们深入了解 Azure 的 AutoML 解决方案,与 Google 类似,有关于管理 MLOps 生命周期的完整故事。
Azure 的 AutoML
访问 Azure 自动机器学习有两种主要方法。一种是通过控制台,另一种是通过 AutoML 的 Python SDK 进行编程访问。让我们先看看控制台。
要在 Azure 上开始使用 AutoML,你需要启动一个 Azure ML Studio 实例,并选择自动化 ML 选项(见 图 5-27)。

图 5-27. Azure 自动机器学习
接下来,创建一个数据集,可以是上传的数据或使用公开数据集。在这个例子中,我使用了来自 Kaggle 社交力量 NBA 项目 的数据(见 图 5-28)。
然后,我启动了一个分类作业,预测基于数据集中的特征一个球员可能打哪个位置。请注意,有许多不同类型的机器学习预测可用,包括数值回归和时间序列预测。如果你还没有设置存储和集群,你需要设置它们(见 图 5-29)。

图 5-28. Azure 自动机器学习创建数据集

图 5-29. Azure 自动机器学习分类
作业完成后,你也可以要求 Azure ML Studio “解释”它是如何得出预测的。机器学习系统通过“可解释性”来解释模型生成预测的过程,这是自动机器学习系统中一个关键的即将推出的能力。你可以在 图 5-30 中看到这些解释能力。注意,这个平台技术通过与 ML Studio 解决方案的深度集成提供了广泛的感觉。

图 5-30. Azure 自动机器学习解释
让我们看看另一种方法。你可以使用 Python 调用 Azure ML Studio 控制台中可用的相同 API。这个官方的 Microsoft 教程 详细解释了它,但关键部分在这里展示:
from azureml.train.automl import AutoMLConfig
automl_config = AutoMLConfig(task='regression',
debug_log='automated_ml_errors.log',
training_data=x_train,
label_column_name="totalAmount",
**automl_settings)
AWS 自动机器学习
作为最大的云服务提供商,AWS 也提供了许多自动机器学习解决方案。最早的解决方案之一包括一个名字不太好听的工具,“机器学习”,虽然现在已经不再广泛使用,但它曾是一个自动机器学习解决方案。现在推荐的解决方案是 SageMaker AutoPilot(见 图 5-31)。你可以从官方文档中查看许多 SageMaker Autopilot 的示例。

图 5-31. SageMaker Autopilot
让我们一起来看看如何使用 AWS SageMaker 进行 Autopilot 实验。首先,如 图 5-32 所示,打开 SageMaker Autopilot 并选择一个新任务。

图 5-32. SageMaker Autopilot 任务
接下来,我将“NBA 球员数据 Kaggle 项目”上传到 Amazon S3。现在我有了可以使用的数据,我根据图 5-33 中显示的方式创建一个实验。注意,我选择的目标是选秀位置。这种分类是因为我想创建一个预测模型,显示 NBA 球员根据他们的表现应该获得的选秀位置。

图 5-33. 创建 Autopilot 实验
一旦我提交实验,SageMaker Autopilot 将经历一个预处理阶段,通过模型调优,如图 5-34 所示。
现在 AutoML 流水线正在运行,您可以在资源选项卡中看到它使用的资源,如图 5-35 所示。

图 5-34. 运行 Autopilot 实验

图 5-35. Autopilot 实例
当训练完成时,您可以看到一个模型列表及其准确度,如图 5-36 所示。请注意,SageMaker 能够创建一个准确率达到 .999945 的高精度分类模型。

图 5-36. 完成的 Autopilot 运行
最后,如图 5-37 所示,一旦任务完成,您可以右键点击要控制的模型,要么部署到生产环境,要么在详细模式中打开以检查可解释性和/或度量或图表。
SageMaker Autopilot 是一种完整的 AutoML 和 MLOps 解决方案,如果您的组织已经在使用 AWS,那么将这个平台集成到现有工作流中似乎是直接的。特别是在处理更大数据集和需要重现性关键的问题时,它显得特别有用。

图 5-37. Autopilot 模型
接下来,让我们讨论一些新兴的开源 AutoML 解决方案。
开源 AutoML 解决方案
我依然怀念在 2000 年在 Caltech 工作时使用 Unix 集群的日子。那个时候 Unix 正在过渡,尽管在许多情况下 Solaris 优于 Linux,但它无法与免费的 Linux 操作系统的价格竞争。
我看到开源 AutoML 解决方案也在发生类似的事情。使用高级工具训练和运行模型的能力似乎正在向商品化方向发展。因此,让我们看看一些开源选项。
Ludwig
开源 AutoML 中更有前途的方法之一是Ludwig AutoML。在图 5-38 中,从 Ludwig 运行的输出显示了评估模型强度有用的指标。开源的优势在于没有公司控制它!这里有一个示例项目,展示了使用Ludwig 通过 Colab 笔记本进行文本分类。
首先,安装 Ludwig 并设置下载:
!pip install -q ludwig
!wget https://raw.githubusercontent.com/paiml/practical-mlops-book/main/chap05/\
config.yaml
!wget https://raw.githubusercontent.com/paiml/practical-mlops-book/main/chap05/\
reuters-allcats.csv
接下来,模型只是一个命令行调用。然后训练模型:
!ludwig experiment \
--dataset reuters-allcats.csv \
--config_file config.yaml

图 5-38. Ludwig
你可以在官方文档中找到 Ludwig 的许多优秀示例:official documentation。
Ludwig 更令人兴奋的一个方面是它正在积极开发中。作为 Linux Foundation 的一部分,他们最近发布了版本 4,你可以在 Figure 5-39 中看到。它添加了许多额外功能,如与远程文件系统和分布式内存工具(如 Dask 和 Ray)的深度集成。最后,Ludwig 与 MLflow 有深入的集成。Ludwig 的路线图显示它将继续支持和增强这一集成。

图 5-39. Ludwig 版本 4
FLAML
另一个开源 AutoML 的新进入者是 FLAML。它的设计考虑了成本效益的超参数优化。你可以在 Figure 5-30_2 中看到 FLAML 的标志。

图 5-40. FLAML 来自微软研究
FLAML 的主要用例之一是仅用三行代码自动化整个建模过程。你可以在以下示例中看到:
from flaml import AutoML
automl = AutoML()
automl.fit(X_train, y_train, task="classification")
更详细的示例显示在 Jupyter 笔记本中,首先安装库 !pip install -q flaml,然后配置 AutoML 配置。然后启动训练作业以选择优化的分类模型:
!pip install -q flaml
from flaml import AutoML
from sklearn.datasets import load_iris
# Initialize an AutoML instance
automl = AutoML()
# Specify automl goal and constraint
automl_settings = {
"time_budget": 10, # in seconds
"metric": 'accuracy',
"task": 'classification',
}
X_train, y_train = load_iris(return_X_y=True)
# Train with labeled input data
automl.fit(X_train=X_train, y_train=y_train,
**automl_settings)
# Predict
print(automl.predict_proba(X_train))
# Export the best model
print(automl.model)
如你可以在 Figure 5-31 中看到,在多次迭代后,它选择了一个带有一组优化超参数的 XGBClassifier。

图 5-41. FLAML 模型选择的输出
这些开源框架的令人兴奋之处在于它们能够实现复杂的事情并自动化简单的事情。接下来,让我们看看模型解释在项目演示中是如何工作的。
注
开源 AutoML 框架不计其数。以下是一些用于 AutoML 的额外框架:
-
自动机器学习
模型解释能力
自动化机器学习中的一个重要方面是自动化模型解释能力。MLOps 平台可以使用这一能力作为团队工作期间的另一个仪表板。例如,一个 MLOps 团队在早晨开始工作时可能会查看服务器的 CPU 和内存使用情况以及他们昨晚训练的模型的解释性报告。
像 AWS SageMaker、Azure ML Studio 和 Google Vertex AI 等基于云的 MLOps 框架具有内置的模型可解释性,但您也可以使用开源软件自行实现。让我们通过一个模型可解释性 GitHub 项目来详细介绍这个工作流程:model explainability GitHub 项目。
注意
ELI5 和 SHAP 是两个流行的开源模型可解释性框架。以下是关于每个框架的更多信息。
ELI5
ELI5 代表“像我五岁那样解释”。它允许您可视化和调试机器学习模型,并支持包括 sklearn 在内的多个框架。
SHAP
SHAP 是解释机器学习模型输出的“博弈论”方法。特别是它具有出色的可视化和解释能力。
首先,使用Jupyter notebook,让我们导入 2016-2017 赛季的 NBA 数据,并使用head命令打印出前几行。此数据包含年龄、位置、场均命中率(FG)以及 Twitter 转发等社交媒体数据:
import pandas as pd
player_data = "https://raw.githubusercontent.com/noahgift/socialpowernba/\
master/data/nba_2017_players_with_salary_wiki_twitter.csv"
df = pd.read_csv(player_data)
df.head()
接下来,让我们创建一个名为winning_season的新特征,这使得我们可以预测球员是否会成为赛季胜利球队的一部分。例如,在图 5-42 中,您可以看到绘制 NBA 球员年龄与胜利次数的图表,以发现潜在的基于年龄的模式。

图 5-42. 赢球季节特征
现在,让我们继续建模并预测胜利。但首先,让我们稍微清理一下数据,并丢弃不必要的列和丢失的值:
df2 = df[["AGE", "POINTS", "SALARY_MILLIONS", "PAGEVIEWS",
"TWITTER_FAVORITE_COUNT","winning_season", "TOV"]]
df = df2.dropna()
target = df["winning_season"]
features = df[["AGE", "POINTS","SALARY_MILLIONS", "PAGEVIEWS",
"TWITTER_FAVORITE_COUNT", "TOV"]]
classes = ["winning", "losing"]
完成此清理后,shape命令打印出行数 239 和列数 7:
df2.shape
(239, 7)
接下来,让我们通过首先分割数据,然后使用逻辑回归来训练模型:
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(features, target,
test_size=0.25,
random_state=0)
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(solver='lbfgs', max_iter=1000)
model.fit(x_train, y_train)
您应该看到类似以下结果的输出,显示模型训练成功:
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, l1_ratio=None, max_iter=1000,
multi_class='auto', n_jobs=None, penalty='l2',
random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
warm_start=False)
现在,让我们继续解释模型如何提出其 SHAP 框架预测的有趣部分。但是,首先需要安装 SHAP:
!pip install -q shap
接下来,让我们使用xgboost,另一种分类算法,来解释模型,因为 SHAP 对它有出色的支持:
import xgboost
import shap
model_xgboost = xgboost.train({"learning_rate": 0.01},
xgboost.DMatrix(x_train, label=y_train), 100)
# load JS visualization code to notebook
shap.initjs()
# explain the model's predictions using SHAP values
# (same syntax works for LightGBM, CatBoost, and scikit-learn models)
explainer = shap.TreeExplainer(model_xgboost)
shap_values = explainer.shap_values(features)
# visualize the first prediction's explanation
shap.force_plot(explainer.expected_value, shap_values[0,:], features.iloc[0,:])
在图 5-43 中,您可以看到 SHAP 的力量图,显示红色特征推高预测值,而蓝色特征推低预测值。

图 5-43. SHAP 输出 xgboost
shap.summary_plot(shap_values, features, plot_type="bar")
在图 5-44 中,汇总图显示了驱动模型的特征绝对平均值。因此,例如,您可以看到“场外”指标如 Twitter 和薪水是为何模型以其方式预测胜利的重要因素。

图 5-44. SHAP 特征重要性
让我们看看另一个开源工具是如何工作的;这次,让我们使用 ELI5。首先,使用pip安装它:
!pip install -q eli5
接下来,排列重要性对先前创建的原始逻辑回归模型执行。这个过程通过测量去除特征后准确度的降低来工作:
import eli5
from eli5.sklearn import PermutationImportance
perm = PermutationImportance(model, random_state=1).fit(x_train, y_train)
eli5.show_weights(perm, feature_names = features.columns.tolist())
您可以在 图 5-45 中看到,原始的逻辑回归模型与 XGBoost 模型具有不同的特征重要性。特别要注意的是,球员的年龄与胜利之间存在负相关。

图 5-45. ELI5 排列重要性
可解释性是 MLOps 的一个重要方面。正如我们为软件系统拥有仪表板和指标一样,AI/ML 系统如何进行预测也应该有可解释性。这种可解释性可以导致业务利益相关者和业务本身的更健康的结果。
接下来,让我们总结本章涵盖的所有内容。
结论
AutoML 是任何进行 MLOps 的团队的重要新能力。AutoML 提高了团队将模型推向生产、处理复杂问题以及最终处理重要事务的能力。需要指出的是,自动建模,即 AutoML,并不是 KaizenML 或持续改进的唯一组成部分。在经常引用的论文 “Hidden Technical Debt in Machine Learning Systems” 中,作者提到建模在真实世界的 ML 系统中只是很少一部分工作量。同样,自动建模,即对建模的自动化,只是需要自动化的一小部分。从数据摄取到特征存储再到建模再到训练再到部署再到在生产环境中评估模型的所有工作都有可能进行全面自动化。KaizenML 意味着您正在不断改进机器学习系统的每一个部分。
就像自动变速器和巡航控制系统帮助专家驾驶员一样,生产机器学习系统的子组件的自动化使负责 ML 决策的人类变得更好。事情可以也应该自动化,包括建模方面、软件工程最佳实践、测试、数据工程以及其他重要组件。持续改进是一种文化变革,没有结束日期,适用于任何希望通过 AI 和机器学习进行有重大影响变革的组织。
最后的收获是,有许多免费或几乎免费的 AutoML 解决方案。正如全球开发人员使用免费或大致免费的高级工具(如构建服务器和代码编辑器)来改善软件一样,ML 从业者应该使用各种类型的自动化工具来提高他们的生产力。
接下来是关于监控和日志记录的章节。我称之为“运维中的数据科学”。在深入讨论这个主题之前,请看以下练习和关键思考问题。
练习
-
下载 XCode 并使用 Apple 的 Create ML 来从在 Kaggle 或其他开放数据集位置找到的样本数据集训练模型。
-
使用 Google 的 AutoML 计算机视觉平台来训练模型,并部署到Coral.AI 设备。
-
使用 Azure ML Studio 训练模型,并探索 Azure ML Studio 的可解释性功能。
-
使用ELI5来解释机器学习模型。
-
使用Ludwig来训练机器学习模型。
-
从官方 SageMaker 示例中选择一个 SageMaker 自动模型调优的示例,并在您的 AWS 账户上运行它。
批判性思维讨论问题
-
为什么 AutoML 只是现代机器学习自动化故事的一部分?
-
国立卫生研究院(NIH)如何利用特征存储来加快医学发现的速度?
-
到 2025 年,机器学习的哪些部分将完全自动化,哪些方面不会?到 2035 年,机器学习的哪些部分将完全自动化,哪些因素不会?
-
垂直集成的 AI 平台(芯片、框架、数据等)如何给特定公司带来竞争优势?
-
如何运用国际象棋软件行业的见解,深化 AI 和人类合作,以改善 AutoML 问题解决的结果?
-
数据中心的方法与模型中心的方法在机器学习中有何不同?对于 KaizenML 方法,其中数据、软件和建模都被同等重视,有何不同?
第六章:监控与日志记录
阿尔弗雷多·德扎
不仅大脑解剖学是双重的,而且不仅仅是一个半球足以形成意识;更重要的是,在隔离大脑半球后,已经显示出两个半球可以同时和独立地具有意识。正如纳格尔所说的分脑:“右半球独自完成的工作太复杂、太有意图性和太心理可理解,无法简单地看作是无意识的自动反应的集合。”
约瑟夫·博根博士
日志记录和监控都是 DevOps 原则的核心支柱,对健壮的机器学习实践至关重要。有效的日志记录和监控很难做到,尽管您可以利用云服务来处理繁重的工作,但决策和制定一个合理的策略仍然取决于您自己。大多数软件工程师倾向于编写代码,忽略测试、文档编写以及往往也忽略日志记录和监控等其他任务。
不要对能够“解决日志问题”的自动化解决方案的建议感到惊讶。通过深思熟虑地思考手头的问题,可以建立一个坚实的基础,使得产生的信息可用。我所描述的辛勤工作和坚实基础的理念在面对无用信息(它无法帮助叙述一个故事)或晦涩难懂(太难理解)时变得非常清晰。这种情况的一个完美例子是我在 2014 年提出的一个软件问题,从一个在线聊天关于产品的问题中捕捉到以下问题:
“有人能帮我解释一下这句话吗:
7fede0763700 0 -- :/1040921 >> 172.16.17.55:6789/0 pipe(0x7feddc022470 \
sd=3 :0 s=1 pgs=0 cs=0 l=1 c=0x7feddc0226e0).fault
当时我已经使用这款软件产品将近两年时间,对那意味着什么毫无概念。你能想出一个可能的答案吗?一位富有经验的工程师给出了完美的翻译:“您所在的机器无法与 172.16.17.55 的监视器联系。”我对日志声明的含义感到困惑。为什么我们不能做出改变来明确表达呢?截至本文撰写时,2014 年捕捉到此问题的工单仍然未解决。更令人不安的是,工程部门在该工单中回复:“日志消息没有问题。”
日志记录和监控是艰苦的工作,因为需要努力产生有意义的输出,帮助我们理解程序的状态。
我提到,拥有帮助我们叙述故事的信息至关重要。这对监控和日志记录都是如此。几年前,我曾在一个大型工程团队工作,该团队提供了全球最大的基于 Python 的 CMS(内容管理系统)之一。在提议将指标添加到应用程序后,普遍的感觉是 CMS 并不需要它。监控已经就位,运维团队已经有各种与警报阈值相关的实用工具。工程经理通过给工程师时间来奖励卓越表现(不仅仅是像某些著名科技公司那样的 20% 时间)。在开始相关项目之前,必须向整个管理团队提出理念以获得支持。轮到我时,我当然选择了向应用程序添加指标功能。
“Alfredo,我们已经有了指标,我们知道磁盘使用情况,并且我们有每台服务器的内存警报。我们不明白通过这个举措我们能得到什么。” 站在一个大型高级管理团队面前,并试图说服他们相信一些他们不相信的事情是很困难的。我的解释从网站上最重要的按钮开始:订阅按钮。那个订阅按钮负责生成付费用户并对业务至关重要。我解释说,“如果我们部署了一个新版本,其中有一个 JavaScript 问题使得这个按钮无法使用,哪个指标或警报可以告诉我们这是一个问题?” 当然,磁盘使用情况将保持不变,而内存使用可能根本不会改变。然而,应用程序中最重要的按钮将在无法使用的状态下被忽视。在这种特殊情况下,指标可以捕获该按钮的每小时、每天和每周的点击率。最重要的是,它可以帮助讲述今天网站如何比去年同月份产生更多(或者更少!)收入的故事。服务器的磁盘使用情况和内存消耗值得关注,但这不是最终目标。
这些故事并没有明确与机器学习或将训练模型交付到生产环境有关。然而,正如您将在本章中看到的那样,它将帮助您和您的公司讲述一个故事,并通过揭示重要问题和指出模型可能需要更好数据之前的原因来增强您的过程的信心。在 ML 运营中,随着时间的推移识别数据漂移和准确性至关重要。在生产环境中部署一个准确性显著变化的模型绝不能发生,并需要进行预防。一旦检测到这些问题,早期解决将更加经济。在生产中存在不准确的模型的后果可能是灾难性的。
观测性能用于云 MLOps
可以肯定地说,大多数机器学习是在云环境中进行的。因此,云提供商提供了特殊的服务来实现可观测性。例如,在 AWS 上,他们有Amazon CloudWatch,在 GCP 上有Google Cloud operations suite,在 Azure 上有Azure Monitor。
在图 6-1 中,Amazon CloudWatch 是展示这些监控服务如何工作的一个很好的例子。在高层次上,云系统的每个组件都将指标和日志发送到 CloudWatch。这些任务包括服务器、应用程序日志、用于机器学习作业的训练元数据以及生产机器学习端点的结果。

图 6-1. AWS CloudWatch
接下来,这些信息将成为许多不同工具的一部分,从仪表板到自动扩展。例如,在 AWS SageMaker 中,这意味着如果个别端点的 CPU 或内存超过其总量的 75%,生产 ML 服务将自动扩展。最后,所有这些可观测性都允许人类和机器分析生产 ML 系统的运行情况并采取行动。
经验丰富的云软件工程师已经知道,云可观测工具是云软件部署中不可选的组件。然而,在云中的 MLOps 独特之处在于新组件也需要精细的监控。例如,在图 6-2 中,ML 模型部署中发生了一系列全新的操作。请注意,CloudWatch 再次收集了这些新的指标。

图 6-2. AWS SageMaker 模型监控
随着本章的进展,请记住,在像 CloudWatch 这样的主系统上,在高层次上,它收集数据,路由警报,并与弹性扩展等云计算的动态组件进行交互。接下来,让我们深入了解更精细的可观测性成员:日志记录。
日志记录简介
大多数日志记录设施在其工作方式上具有共同的特征。系统定义了它们可以操作的日志级别,然后用户可以选择这些语句应出现在何种级别。例如,Nginx web 服务器默认配置为将访问日志保存在/var/log/nginx/access.log,错误日志保存在/var/log/nginx/error.log。作为 Nginx 用户,如果您在解决 Web 服务器问题,首先要做的事情就是进入这些文件并查看输出。
我在 Ubuntu 服务器上使用默认配置安装了 Nginx,并发送了一些 HTTP 请求。立即,访问日志开始获取一些信息:
172.17.0.1 [22/Jan/2021:14:53:35 +0000] "GET / HTTP/1.1" \
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:84.0) Firefox/84.0" "-"
172.17.0.1 [22/Jan/2021:14:53:38 +0000] "GET / HTTP/1.1" \
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:84.0) Firefox/84.0" "-"
长长的日志行包含了大量有用(可配置的)信息,包括服务器的 IP 地址、时间和请求类型,以及用户代理信息。在我的情况下,用户代理是运行在 Macintosh 计算机上的浏览器。然而,这些并不是错误。这些行显示的是对服务器的访问。为了强制 Nginx 进入错误条件,我改变了一个文件的权限,使其对服务器不可读。接下来,我发送了新的 HTTP 请求:
2021/01/22 14:59:12 [error] open() "/usr/share/nginx/html/index.html" failed \
(13: Permission denied), client: 172.17.0.1, server: localhost, \
request: "GET / HTTP/1.1", host: "192.168.0.200"
日志条目明确显示信息级别为“error”。这使得像我这样的消费者能够识别出 Web 服务器生成的信息的严重性。当我是系统管理员时,刚开始进行配置和部署生产环境等必要任务时,我并不清楚为什么这些级别很有用。对我来说,主要的一点是我可以从日志的信息内容中识别出错误。
虽然让消费者更容易识别错误是否有效的想法是有效的,但日志级别并不仅限于此。如果你以前尝试过调试程序,可能已经使用过print()语句来帮助获取运行程序的有用信息。有许多其他调试程序的方法,但使用print()仍然是有价值的。一个缺点是,一旦问题解决,你必须清理和删除所有print()语句。下次再需要调试同一个程序时,你又需要添加所有这些语句。这不是一个好策略,也是日志记录可以帮助解决的许多情况之一。
现在基本的日志记录原理已经清楚了,接下来我将讨论如何在应用程序中配置日志记录,这往往是复杂的,因为有很多不同的选项和决策需要做。
Python 中的日志记录
我将在 Python 中进行工作,但本节中的大多数概念应该可以干净地应用到其他语言和框架中。日志级别、输出重定向和其他功能在其他应用程序中通常也是可用的。为了开始应用日志记录,我将创建一个简短的 Python 脚本来处理一个 CSV 文件。没有函数或模块;示例脚本尽可能接近 Jupyter Notebook 单元格。
创建一个名为describe.py的新文件,看看日志记录如何帮助基本脚本:
import sys
import pandas as pd
argument = sys.argv[-1]
df = pd.read_csv(argument)
print(df.describe())
该脚本将从命令行上的最后一个参数中获取输入,并告诉 Pandas 库读取并描述它。这个想法是产生一个 CSV 文件的描述,但当你没有参数运行时并不会发生这种情况:
$ python derscribe.py
from os import path
count 5
unique 5
top print(df.describe())
freq 1
这里发生的是,示例中的最后一个参数是脚本本身,因此 Pandas 正在描述脚本的内容。这对于那些没有创建过脚本的人来说并不是非常有用,结果至少会让人感到震惊。让我们将这个脆弱的脚本向前推进一步,并传递一个不存在的路径作为参数:
$ python describe.py /bogus/path.csv
Traceback (most recent call last):
File "describe.py", line 7, in <module>
df = pd.read_csv(argument)
File "/../site-packages/pandas/io/parsers.py", line 605, in read_csv
return _read(filepath_or_buffer, kwds)
...
File "/../site-packages/pandas/io/common.py", line 639, in get_handle
handle = open(
FileNotFoundError: [Errno 2] No such file or directory: '/bogus/path.csv'
没有错误检查告诉我们输入是否有效以及脚本期望的内容。如果正在等待管道运行或某些远程数据处理作业的完成,并且出现这些类型的错误,问题会更加严重。一些开发人员试图通过捕获所有异常并掩盖实际错误来防范这些问题,使得无法了解正在发生的情况。脚本的稍作修改版本更突显了问题:
import sys
import pandas as pd
argument = sys.argv[-1]
try:
df = pd.read_csv(argument)
print(df.describe())
except Exception:
print("Had a problem trying to read the CSV file")
运行它会产生一个错误,在生产代码中看到这种情况会让我非常沮丧:
$ python describe.py /bogus/path.csv
Had a problem trying to read the CSV file
这个例子很琐碎,因为脚本只有几行长,你知道它的内容,所以指出问题并不那么困难。但如果这是在一个远程的自动化流水线中运行,你没有任何上下文信息,理解问题就变得更具挑战性。让我们使用 Python 日志模块来提供更多关于这个数据处理脚本正在发生的事情的信息。
首先要做的是配置日志记录。在这里我们不需要太复杂的东西,添加几行就足够了。修改describe.py文件以包括这些行,然后重新运行脚本:
import logging
logging.basicConfig()
logger = logging.getLogger("describe")
logger.setLevel(logging.DEBUG)
argument = sys.argv[-1]
logger.debug("processing input file: %s", argument)
重新运行它应该看起来类似于这样:
$ python describe.py /bogus/path.csv
DEBUG:describe:processing input file: /bogus/path.csv
Had a problem trying to read the CSV file
尽管目前还不是非常有用,但已经包含了信息。对于一些简单的print()语句也能达到的效果,可能会感觉有点冗长。当构建消息时,日志模块对于失败是具有弹性的。例如,打开 Python 解释器并尝试使用更少的参数来调用print:
>>> print("%s should break because: %s" % "statement")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: not enough arguments for format string
现在让我们使用日志模块进行相同的操作:
>>> import logging
>>> logging.warning("%s should break because: %s", "statement")
--- Logging error ---
Traceback (most recent call last):
...
File "/.../python3.8/logging/__init__.py", line 369, in getMessage
msg = msg % self.args
TypeError: not enough arguments for format string
Call stack:
File "<stdin>", line 1, in <module>
Message: '%s should break because: %s'
Arguments: ('statement',)
最后一个例子不会破坏生产应用程序。记录日志在任何运行时都不应该破坏任何应用程序。在这种情况下,日志模块试图在字符串中进行变量替换并失败,但它并没有导致程序崩溃,而是通告了问题然后继续执行。print语句无法做到这一点。事实上,我认为在 Python 中使用print()很像在 shell 脚本中使用echo。它没有控制性,很容易破坏生产应用程序,并且很难控制详细程度。
在日志记录时详细程度非常重要,除了 Nginx 示例中的错误级别外,它还具有多种设施来使日志消费者能够精细调节所需的信息。在深入探讨日志级别的细粒度和详细程度控制之前,给脚本的日志格式升级,使其看起来更加美观。Python 日志模块非常强大,允许大量配置。更新发生日志配置的describe.py脚本:
log_format = "[%(name)s][%(levelname)-6s] %(message)s"
logging.basicConfig(format=log_format)
logger = logging.getLogger("describe")
logger.setLevel(logging.DEBUG)
log_format是一个模板,其中包含构建日志行时使用的一些关键字。请注意,我以前没有时间戳,尽管我现在仍然没有它,但配置确实允许我包括它。目前,记录器的名称(在本例中为describe)、日志级别和消息都在那里,并使用方括号进行分隔,以便更好地阅读。再次运行脚本以检查输出如何变化:
$ python describe.py
[describe][DEBUG ] processing input file: describe.py
Had a problem trying to read the CSV file
日志的另一个超级功能是在错误信息中提供追溯信息。有时捕获(并显示)追溯信息是有用的,而不需要陷入错误条件。为了实现这一点,在except块中更新describe.py脚本:
try:
df = pd.read_csv(argument)
print(df.describe())
except Exception:
logger.exception("Had a problem trying to read the CSV file")
它看起来与之前的print()语句非常相似。重新运行脚本并检查结果:
$ python logging_describe.py
[describe][DEBUG ] processing input file: logging_describe.py
[describe][ERROR ] Had a problem trying to read the CSV file
Traceback (most recent call last):
File "logging_describe.py", line 15, in <module>
df = pd.read_csv(argument)
File "/.../site-packages/pandas/io/parsers.py", line 605, in read_csv
return _read(filepath_or_buffer, kwds)
...
File "pandas/_libs/parsers.pyx", line 1951, in pandas._libs.parsers.raise
ParserError: Error tokenizing data. C error: Expected 1 field in line 12, saw 2
追溯是错误的字符串表示,而不是错误本身。为了验证这一点,在except块后添加另一行日志:
try:
df = pd.read_csv(argument)
print(df.describe())
except Exception:
logger.exception("Had a problem trying to read the CSV file")
logger.info("the program continues, without issue")
重新运行以验证结果:
[describe][DEBUG ] processing input file: logging_describe.py
[describe][ERROR ] Had a problem trying to read the CSV file
Traceback (most recent call last):
[...]
ParserError: Error tokenizing data. C error: Expected 1 field in line 12, saw 2
[describe][INFO ] the program continues, without issue
修改日志级别
日志提供的这些信息设施与print()语句并不直接。如果你在编写一个 Shell 脚本,这几乎是不可能的。在这个例子中,我们现在有三个日志级别:debug、error 和 info。日志允许的另一件事情是选择性地设置我们感兴趣的级别。在更改之前,这些级别有与之相关联的权重,理解它们以便更改能够反映出优先级是很重要的。从最详细到最少详细,顺序如下:
-
debug -
info -
warning -
error -
critical
尽管这是 Python 的日志模块,但你应该期待其他系统中类似的加权优先级。debug日志级别将包含所有其他级别,以及debug级别本身。critical日志级别仅包含critical级别的消息。再次更新脚本将日志级别设置为error:
log_format = "[%(name)s][%(levelname)-6s] %(message)s"
logging.basicConfig(format=log_format)
logger = logging.getLogger("describe")
logger.setLevel(logging.ERROR)
$ python logging_describe.py
[describe][ERROR ] Had a problem trying to read the CSV file
Traceback (most recent call last):
File "logging_describe.py", line 15, in <module>
df = pd.read_csv(argument)
File "/.../site-packages/pandas/io/parsers.py", line 605, in read_csv
return _read(filepath_or_buffer, kwds)
...
File "pandas/_libs/parsers.pyx", line 1951, in pandas._libs.parsers.raise
ParserError: Error tokenizing data. C error: Expected 1 field in line 12, saw 2
此更改导致仅显示错误日志消息。当试图减少日志量以关注感兴趣的内容时,这是非常有用的。调试包含大多数(如果不是所有)消息,而错误和关键情况则要少得多且通常不会经常出现。我建议为新的生产代码设置调试日志级别,以便更容易捕捉潜在问题。在生成和部署新代码时,你应该预期会出现问题。当应用程序已经被证明是稳定的,并且没有太多令人惊讶的问题时,过于冗长的输出在长期内并不那么有用。逐步将级别调整为仅info或error是你可以做的事情。
记录不同的应用程序
到目前为止,我们已经看到一个试图加载 CSV 文件的脚本的日志级别。我还没有详细说明为什么记录器的名称(在过去的示例中为“describe”)很重要。在 Python 中,您导入模块和包,许多这些包都带有自己的记录器。日志记录设施允许您独立于应用程序为这些记录器设置特定选项。还有一个记录器的层次结构,其中 “root” 记录器是所有记录器的父记录器,并且可以修改所有应用程序和记录器的设置。
更改日志级别是您可以配置的众多功能之一。在一个生产应用程序中,我为同一应用程序创建了两个记录器:一个将消息输出到终端,而另一个将消息写入日志文件。这允许将用户友好的消息发送到终端,省略大型回溯和错误污染输出。同时,内部面向开发人员的日志记录将写入文件。这是另一个使用 print() 语句或 shell 脚本中的 echo 指令非常难以实现和复杂的示例。您拥有的灵活性越大,您就可以更好地构建应用程序和服务。
创建一个名为 http-app.py 的新文件,并保存以下内容:
import requests
import logging
logging.basicConfig()
# new logger for this script
logger = logging.getLogger('http-app')
logger.info("About to send a request to example.com")
requests.get('http://example.com')
脚本使用基本配置配置日志记录设施,默认情况下将消息输出到终端。然后尝试使用 requests 库发出请求。运行它并检查输出。执行脚本后在终端中什么也不显示可能会让人感到意外。在详细解释发生这种情况的确切原因之前,更新脚本:
import requests
import logging
logging.basicConfig()
root_logger = logging.getLogger()
# Sets logging level for every single app, the "parent" logger
root_logger.setLevel(logging.DEBUG)
# new logger for this script
logger = logging.getLogger('http-app')
logger.info("About to send a request to example.com")
requests.get('http://example.com')
重新运行脚本并注意输出:
$ python http-app.py
INFO:http-app:About to send a request to example.com
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): example.com:80
DEBUG:urllib3.connectionpool:http://example.com:80 "GET / HTTP/1.1" 200 648
Python 日志模块可以全局设置每个包和模块中的每个记录器的配置设置。这个 parent 记录器称为 root 记录器。有输出的原因是更改了根记录器级别为 debug。但是输出不止是单行 http-app 记录器。这是因为 urllib3 包也有自己的记录器。由于根记录器将全局日志级别更改为 debug,因此 urllib3 包现在正在发出这些消息。
可以配置多个不同的记录器,并调整级别的细粒度和详细程度(以及任何其他日志记录配置)。为了演示这一点,在 http-app.py 脚本的末尾添加以下行来更改 urllib3 包的日志级别:
# fine tune the urllib logger:
urllib_logger = logging.getLogger('urllib3')
urllib_logger.setLevel(logging.ERROR)
logger.info("About to send another request to example.com")
requests.get('http://example.com')
更新版本检索 urllib3 的日志记录器,并将其日志级别更改为错误。在调用 requests.get() 之前,脚本的日志记录器发出了一条新消息,而 requests.get() 又使用了 urllib3 包。再次运行脚本以检查输出:
INFO:http-app:About to send a request to example.com
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): example.com:80
DEBUG:urllib3.connectionpool:http://example.com:80 "GET / HTTP/1.1" 200 648
INFO:http-app:About to send another request to example.com
由于在上次请求之前更改了urllib3的日志级别,不再显示调试消息。信息级别的消息确实显示,因为该记录器仍然配置为调试级别。这些日志配置的组合非常强大,因为它允许您选择从其他可能导致输出“噪音”的信息中选择感兴趣的内容。
想象一下使用与云存储解决方案互动的库。您正在开发的应用程序通过下载、列出和上传内容到存储服务器来执行成千上万次的交互。假设应用程序的主要关注点是管理数据集并将其卸载到云提供商。您认为看到一条信息性消息,说一个请求即将发送到云提供商,会有趣吗?在大多数情况下,我会说这远非有用之处。相反,当应用程序未能执行与存储相关的特定操作时发出警报至关重要。此外,可能出现请求存储时超时的情况,此时改变日志级别以指示请求发生的时间差至关重要。
这一切都是关于灵活性和适应您应用程序生命周期需求的能力。今天有用的东西明天可能成为信息过载。日志记录与监控(在下一节中介绍)密切相关,谈论到可观察性也并非不寻常。这些是 DevOps 的基础,应该成为 ML 生命周期的一部分。
监控与可观察性
我曾是一名职业运动员,我的父亲,也是我的教练,为我增加了一项特别讨厌的任务:每天在日记中写关于刚刚完成的训练内容。日记条目需要包括以下内容:
-
计划的训练内容。例如,如果是以 42 秒的速度进行 10 次 300 米的重复训练。
-
训练结果。对于 300 米的距离,需要记录每次重复的实际完成时间。
-
在训练过程中和之后的感受。
-
其他相关信息,比如感觉生病或因伤痛感到疼痛等。
我 11 岁开始专业训练。作为一个十几岁的少年,这项写日记的任务比最糟糕的训练还要糟糕。我不明白为什么写日记如此重要,为什么必须详细记录训练内容。我有勇气告诉父亲,这似乎是他的工作,而不是我的。毕竟,我已经在做训练了。这个论点对我来说并不是一个很好的答复。问题在于我没有理解。这似乎是一项毫无用处的任务,没有任何好处。我无法看到或感受到其中的好处。
到了一天结束时,感觉更像是一个纪律性的任务,而不是一个关键的训练环节。在每天都记录日志几年后(我平均每周锻炼 14 次)并且在我身后有一个很棒的赛季后,我和父亲坐下来计划下一个赛季。而不是听我爸爸谈论接下来赛季要做什么几个小时,他启发了我,展示了记日志的强大之处。“好的,阿尔弗雷多,让我查看最近的两本期刊,看看我们做了什么以及你的感受,以便调整、增加并再次度过一个伟大的赛季。”
这些期刊中包含了我们计划所需的每一条信息。他用了一句话让我印象深刻多年,我希望这能说明为什么在任何情况下监控和指标都至关重要:“如果我们能够衡量,那么我们就能比较。而如果我们能比较,那么我们才能改进。”
机器学习运营也不例外。当一个模型的新迭代被送到生产中时,你必须知道它的表现是好还是差。不仅如此,信息必须是易于获取和简单明了的。流程可能会制造摩擦,使一切比应该慢。自动化可以消除愚蠢的流程,并轻松创建信息的可用性。
几年前,我在一家初创公司工作,销售主管会把一个 CSV 文件发送给我,让我“处理一些数据”,然后把结果以 PDF 格式发回给他。这太糟糕了,不具备可扩展性。也不会通过“巴士测试”。
巴士测试是如果今天我被公共汽车撞了,一切都应该还能正常运行,与我共事的每个人都能够接管我的工作。自动化和生成指标的所有努力对于将健壮的模型交付到生产中至关重要。
模型监控的基础
在机器学习运营中,监控意味着任何与将模型投入生产有关的一切——从捕获系统和服务信息到模型本身的性能。没有一种单一的灵丹妙药可以实施监控以使一切正常。监控和捕获指标有些类似于在确定要使用哪些特征和算法来训练模型之前了解数据。你了解的数据越多,你在训练模型时做出的决策就越好。
同样,你了解将模型投入生产所涉及的步骤越多,当捕获指标和设置监控警报时,你做出的选择就越好。对于“在训练模型时应该捕获什么指标?”的答案我并不喜欢,但在这种情况下确实如此准确:这取决于情况。
尽管度量和设置警报类型之间存在差异,但有一些有用的基础模式可以作为默认选择。这些模式将帮助您澄清要收集的数据,收集频率以及如何最佳可视化它们。最后,根据生成模型的步骤不同,关键的度量标准也会有所不同。例如,在收集数据并清理数据时,检测每列的空值数量以及处理数据时的完成时间可能非常重要。
上个星期我在处理系统漏洞数据。我不得不对负责读取 JSON 文件并将信息保存到数据库的应用程序进行一些更改。在一些更改之后,数据库的大小从几个千兆字节减小到了只有 100 兆字节。代码更改本意并不是为了减小大小,所以我立刻意识到自己犯了一个需要修正的错误。遇到这些情况是确定何时以及如何捕获这些度量的绝佳机会。
大多数度量捕获系统中可以找到的度量类型有几种:
计数器
正如其名称所示,这种类型的度量在计数任何类型的项时非常有用。在迭代项时尤为有用。例如,这可能对于计算每列的空单元格值非常有用。
计时器
当尝试确定某些动作需要多长时间时,计时器是非常好的选择。这对性能监控至关重要,因为其核心功能是测量动作期间所花费的时间。通常在托管 HTTP API 的监控图表中可以看到时间花费。如果您有托管模型,计时器将帮助捕获模型通过 HTTP 生成预测所需的时间。
值
当计数器和计时器不适合捕获度量时,值变得很有用。我倾向于将值视为代数方程的一部分:即使我不知道 X 是什么,我也希望捕获其值并持久化。重复利用在工作中处理 JSON 文件并将信息保存到数据库时,这种度量的合理用途可能是生成的数据库大小(以千兆字节为单位)。
有两种常见的与 ML 相关的操作,云服务提供商需要监视和捕获有用的度量。第一种是目标数据集。这可以是您用来训练模型的同一数据集,尽管需要特别注意确保特征的数量(和顺序)不变。第二种是基线。基线确定了在训练模型时可能(或可能不)接受的差异。将基线视为确定模型在生产环境中适用程度的可接受阈值。
现在基础知识已经清楚,并且理解了带有基线的目标数据集,让我们在训练模型时使用它们来捕获有用的度量。
使用 AWS SageMaker 监控漂移
正如我之前提到的,云提供商通常需要一个目标数据集和一个基线。AWS 也不例外。在本节中,我们将生成指标并从已部署的模型中捕获数据违规。SageMaker 是检查数据集、训练模型并将模型提供到生产环境的不可思议工具。由于 SageMaker 与其他 AWS 提供的服务(如 S3 存储)紧密集成,您可以利用保存目标信息,以便在其他地方快速处理并具有访问权限。
SageMaker 一个我特别喜欢的地方是它的 Jupyter Notebook 提供。界面不像 Google 的 Colab 那样精致,但它装备了 AWS SDK 预安装和大量的内核类型可供选择——从(现已弃用的)Python 2.7 到运行 Python 3.6 的 Conda 环境,如 图 6-3 所示。
注意
本节不涵盖部署模型的具体细节。如果您想深入了解 AWS 中的模型部署,请参阅 第 7 章。

图 6-3. SageMaker 内核
使用 SageMaker 笔记本完成本节内容。登录 AWS 控制台,并找到 SageMaker 服务。加载后,在左侧列找到“笔记本实例”链接并点击。创建一个具有有意义名称的新实例。无需更改任何默认设置,包括机器类型。我将笔记本命名为 practical-mlops-monitoring(见 图 6-4)。
在部署模型时,启用数据捕获非常重要。因此,请确保使用 DataCaptureConfig 类来执行此操作。以下是一个快速示例,将其保存到 S3 存储桶中:
from sagemaker.model_monitor import DataCaptureConfig
s3_capture_path = "s3://monitoring/xgb-churn-data"
data_capture_config = DataCaptureConfig(
enable_capture=True,
sampling_percentage=100,
destination_s3_uri=s3_capture_path
)

图 6-4. SageMaker 笔记本实例
在调用 model.deploy() 时,请使用 data_capture_config。在此示例中,我已经使用 Model() 类创建了一个 model 对象,并将数据捕获配置分配给它,因此当模型被执行时,数据将保存到 S3 存储桶中。
from sagemaker.deserializers import CSVDeserializer
predictor = model.deploy(
initial_instance_count=1,
instance_type="ml.m4.large",
endpoint_name="xgb-churn-monitor",
data_capture_config=data_capture_config,
deserializer=CSVDeserializer(),
)
模型部署并可用后,发送一些请求以开始进行预测。通过向模型发送请求,您会导致捕获配置保存必要的数据,以创建基线所需的关键数据。您可以以任何方式向模型发送预测请求。在本例中,我使用 SDK 使用来自文件的示例 CSV 数据发送一些请求。每行数据代表模型可以用来开始预测的数据。由于输入是数据,这就是我使用 CSV 反序列化程序的原因,以便端点了解如何处理该输入:
from sagemaker.predictor import Predictor
from sagemaker.serializers import CSVDeserializer, CSVSerializer
import time
predictor = Predictor(
endpoint_name=endpoint_name,
deserializer=CSVDeserializer(),
serializer=CSVSerializer(),
)
# About one hundred requests should be enough from test_data.csv
with open("test_data.csv") as f:
for row in f:
payload = row.rstrip("\n")
response = predictor.predict(data=payload)
time.sleep(0.5)
运行后,请仔细检查 S3 存储桶中是否有输出被捕获。您可以列出该存储桶的内容以确保实际存在数据。在本例中,我将使用 AWS 命令行工具,但您也可以使用 Web 界面或 SDK(在这种情况下,方法并不重要):
$ aws s3 ls \
s3://monitoring/xgb-churn-data/datacapture/AllTraffic/2021/02/03/13/
2021-02-03 08:13:33 61355 12-26-957-d5938b7b-fbd8-4e3c-9dbd-741f71b.jsonl
2021-02-03 08:14:33 1566 13-27-365-a59180ea-591d-4562-925b-6472d55.jsonl
2021-02-03 08:33:33 31548 32-24-577-20217dd9-8bfa-4ba2-a7f1-d9717ef.jsonl
2021-02-03 08:34:33 31373 33-25-476-0b843e95-5fe0-4b79-8369-b099d0e.jsonl
[...]
存储桶列出约 30 个项目,确认了预测请求成功并且数据已经被捕获并保存到 S3 存储桶中。每个文件都有一个包含一些信息的 JSON 条目。每个条目的具体内容很难理解。一个条目看起来像这样:
{
"captureData": {
"endpointInput": {
"observedContentType": "text/csv",
"mode": "INPUT",
"data": "92,0,176.3,85,93.4,125,207.2,107,9.6,1,2,0,1,00,0,0,1,1,0,1,0",
"encoding": "CSV"
},
[...]
}
再次,条目在整个过程中引用 CSV 内容类型。这对其他数据消费者正确消费信息至关重要。到目前为止,我们已经配置模型来捕获数据并将其保存到 S3 存储桶中。这都是在使用测试数据生成一些预测之后发生的。然而,还没有基线。在之前的步骤中捕获的数据需要用于创建基线。下一步需要一个目标训练数据集。如我之前提到的,训练数据集可以是用于训练模型的相同数据集。如果生成的模型没有发生很大变化,则可能接受数据集的子集。这个目标数据集必须具有与用于训练生产模型的数据集相同的特征(并且顺序相同)。
注意
在在线文档中经常会发现将基准数据集与目标数据集互换使用,因为它们最初可能相同。这在试图理解这些概念时可能会令人困惑。将基线数据集视为用于创建基准(基线)的数据,将任何更新的数据视为目标是很有用的。
SageMaker 通过依赖 S3 轻松保存和检索数据。我已经在 SDK 示例中定义了各个位置,对于基线设定,我也会这样做。首先创建一个监视器对象;这个对象能够生成一个基线并将其保存到 S3:
from sagemaker.model_monitor import DefaultModelMonitor
role = get_execution_role()
monitor = DefaultModelMonitor(
role=role,
instance_count=1,
instance_type="ml.m5.xlarge",
volume_size_in_gb=20,
max_runtime_in_seconds=3600,
)
现在监视器可用,使用 suggest_baseline() 方法为模型生成一个默认基线:
from sagemaker.model_monitor.dataset_format import DatasetFormat
from sagemaker import get_execution_role
s3_path = "s3://monitoring/xgb-churn-data"
monitor.suggest_baseline(
baseline_dataset=s3_path + "/training-dataset.csv",
dataset_format=DatasetFormat.csv(header=True),
output_s3_uri=s3_path + "/baseline/",
wait=True,
)
运行完成后,会产生大量输出。输出的开头应该类似于这样:
Job Name: baseline-suggestion-job-2021-02-03-13-26-09-164
Inputs: [{'InputName': 'baseline_dataset_input', 'AppManaged': False, ...}]
Outputs: [{'OutputName': 'monitoring_output', 'AppManaged': False, ...}]
在配置的 S3 存储桶中应该保存有两个文件:constraints.json 和 statistics.json。你可以使用 Pandas 库可视化约束:
import pandas as pd
baseline_job = monitor.latest_baselining_job
constraints = pd.json_normalize(
baseline_job.baseline_statistics().body_dict["features"]
)
schema_df.head(10)
这是 Pandas 生成的约束表的一个简短子集:
name inferred_type completeness num_constraints.is_non_negative
Churn Integral 1.0 True
Account Length Integral 1.0 True
Day Mins Fractional 1.0 True
[...]
现在,我们已经使用与训练生产模型相似的数据集制作了一个基线,并捕获了相关约束,现在是分析和监控数据漂移的时候了。到目前为止,已经涉及到几个步骤,但大多数这些步骤不会从这些示例到其他更复杂的示例进行很大变化,这意味着在这里有许多机会自动化和抽象化很多内容。首次收集数据发生在设置基线时,然后除非需要更改基准数据,否则不应更改。这种更改可能不经常发生,所以设置基线的繁重工作不应该感觉像负担。
要分析收集的数据,我们需要一个监控计划。示例计划将每小时运行一次,使用在前面步骤中创建的基线与流量进行比较:
from sagemaker.model_monitor import CronExpressionGenerator
schedule_name = "xgb-churn-monitor-schedule"
s3_report_path = "s3://monitoring/xgb-churn-data/report"
monitor.create_monitoring_schedule(
monitor_schedule_name=schedule_name,
endpoint_input=predictor.endpoint_name,
output_s3_uri=s3_report_path,
statistics=monitor.baseline_statistics(),
constraints=monitor.suggested_constraints(),
schedule_cron_expression=CronExpressionGenerator.hourly(),
enable_cloudwatch_metrics=True,
)
创建计划后,需要流量生成报告。如果模型已经在生产环境中,我们可以假设(并重复使用)现有流量。如果您像我在这些示例中做的那样,在测试模型上测试基线,则需要通过调用请求来生成流量到部署的模型。生成流量的一种简单方法是重用训练数据集以调用端点。
我部署的模型运行了几个小时。我使用这个脚本从之前部署的模型生成了一些预测:
import boto3
import time
runtime_client = boto3.client("runtime.sagemaker")
with open("training-dataset.csv") as f:
for row in f:
payload = row.rstrip("\n")
response = runtime_client.invoke_endpoint(
EndpointName=predictor.endpoint_name,
ContentType="text/csv",
Body=payload
)
time.sleep(0.5)
由于我配置了每小时运行一次的监控计划,SageMaker 不会立即将报告生成到 S3 存储桶中。两小时后,可以列出 S3 存储桶并检查是否有报告出现。
注意
尽管模型监控将每小时运行,但 AWS 有一个 20 分钟的缓冲区,可能会在整点后延迟高达 20 分钟。如果您看到其他调度系统,这种缓冲区可能会令人惊讶。这是因为在幕后,AWS 正在为调度平衡资源。
报告包括三个 JSON 文件:
-
contraint_violations.json
-
contraint.json
-
statistics.json
与监控和捕获漂移相关的有趣信息在constraint_violations.json文件中。在我的情况下,大多数违规看起来像这个条目:
feature_name: State_MI
constraint_check_type: data_type_check
description:
Data type match requirement is not met. Expected data type: Integral,
Expected match: 100.0%. Observed: Only 99.71751412429379% of data is Integral.
推荐的基准要求数据完整性达到 100%,而我们看到模型接近 99.7%。由于约束是达到 100%,所以会生成并报告违规情况。在我的情况下,这些数字大多数是相似的,除了一行:
feature_name: Churn
constraint_check_type: data_type_check
description:
Data type match requirement is not met. Expected data type: Integral,
Expected match: 100.0%. Observed: Only 0.0% of data is Integral.
在这里,0%是一个关键情况,这就是设置系统捕捉和报告这些预测变化的所有辛苦工作的地方。我想强调的是,尽管需要几个步骤和 AWS Python SDK 的样板代码,但自动化和开始为目标数据集生成这些报告并不复杂。我使用自动化建议创建了基线,这主要需要进行微调,以定义可接受的值,以防止生成没有用处的违规。
使用 Azure ML 监控漂移
MLOps 在云提供商中发挥着重要作用。监控和警报(DevOps 的核心支柱)被精心设计的服务所提供并不奇怪。Azure 可以分析数据漂移,并设置警报以捕获模型进入生产前的潜在问题。了解不同云提供商如何解决像数据漂移这样的问题总是有益的——视角是一种宝贵的资产,将使您成为更好的工程师。Azure 平台中的思考量、文档和示例数量使得入职过程更加顺利。找到学习资源并快速了解 Azure 的各种提供不需要花费太多的精力。
注意
在撰写本文时,Azure ML 上的数据漂移检测仍处于预览阶段,还有一些小问题需要解决,这些问题阻碍了提供可靠的代码示例来尝试。
在 Azure 中,数据漂移检测的工作方式类似于使用 AWS SageMaker。其目标是在训练数据集和服务数据集之间发生漂移时发出警报。与大多数机器学习操作一样,深入了解数据(因此也是数据集)至关重要。一个过于简单化的例子是一个捕捉了一年内泳衣销售的数据集:如果每周的销售量降至零,这是否意味着数据集已经漂移,并且不应在生产中使用?还是说这可能是冬季的中间,没有人购买任何东西?当数据的细节被充分理解时,这些问题很容易回答。
数据漂移有几个原因,其中许多原因可能是完全不可接受的。例如,值类型的变化(例如,华氏度到摄氏度),空值或空值,或者在泳衣销售的例子中,自然漂移,其中季节性变化可能会影响预测。
在 Azure 上设置和分析漂移的模式需要一个基线数据集、一个目标数据集和一个监视器。这三个要求共同作用以生成度量并在检测到漂移时创建警报。监控和分析工作流程允许您在数据集中有新数据时检测和警报数据漂移,同时允许随时间对新数据进行分析。与使用历史数据分析漂移检测相比,您可能不会经常使用历史分析,因为与检查几个月前相比,使用最新的比较点更为常见。然而,与上一年的表现相比较比与上个月相比更有意义,特别是对于受季节性事件影响的数据集。比较圣诞树销售与八月的指标并没有太多的用处。
要在 Azure 中设置数据漂移工作流程,您必须首先创建一个目标数据集。目标数据集需要设置一个时间序列,可以使用时间戳列或虚拟列。虚拟列是一个很好的功能,因为它可以从存储数据集的路径中推断出时间戳。这个配置属性称为分区格式。如果您正在配置一个数据集以使用虚拟列,您将在 Azure ML Studio 和 Python SDK 中看到引用的分区格式(参见图 6-5)。

图 6-5. Azure 分区格式
在这种情况下,我正在使用分区格式助手,以便 Azure 可以使用路径推断时间戳。这很好,因为它告诉 Azure 采用的惯例是设置标准的。例如路径 /2021/10/14/dataset.csv 意味着数据集将以虚拟列的形式得到 2021 年 10 月 14 日的日期。按照惯例进行配置是自动化的一大优势。每当您看到可以通过惯例来推断(或完全删除)配置(如本例中的路径)的机会时,您都应该利用它。较少的配置意味着较少的开销,这有助于加快工作流程。
一旦您拥有时间序列数据集,您可以通过创建数据集监视器来进行后续操作。为了使一切正常运行,您将需要一个目标数据集(在本例中是时间序列数据集)、基准数据集以及监视器设置。
基准数据集必须具有与目标数据集相同(或尽可能相似)的特性。一个令人兴奋的特性是选择一个时间范围,以便切片数据集中与监控任务相关的数据。监视器的配置是将所有数据集整合在一起的关键。它允许您创建一个运行计划,其中包含一组令人兴奋的特性,并设置可容忍的数据漂移百分比的阈值。
数据漂移结果将在 Azure ML Studio 的“资产”部分中的“数据集监视器”选项卡中显示。所有配置的监视器都会显示漂移的度量值以及漂移前几名特征的排序列表。我喜欢 Azure ML Studio 展示数据漂移的简单性,因为它可以快速呈现对决策有用的关键信息。
如果有必要深入了解指标的详细信息,您可以使用 Application Insights 查询与监视器相关的日志和指标。启用和设置 Application Insights 的步骤详见“应用洞察”。
结论
日志记录、监控和度量如此关键,以至于任何投入生产的模型在风险硬性恢复灾难性失败之前必须实施它们。对于可复现结果的稳健过程有高度信心需要所有这些组件。正如我在本章中多次提到的,您必须根据准确的数据做出决策,这些数据可以告诉您准确率是否一直如此高,或者错误数量是否显著增加。
许多示例可能难以理解,但它们都可以自动化和抽象化。在本书中,您将持续阅读 DevOps 的核心支柱及其如何与机器学习运作相关。自动化是将像日志记录和监控这样的支柱绑在一起的关键。设置日志记录和监控通常不是令人兴奋的工作,特别是如果想要最先进的预测模型进行出色工作的话。但是,如果基础不稳固,卓越的结果无法保持一致。
当我开始接受专业运动员训练时,我总是怀疑我的教练,他不允许我每天跳高。“在成为跳高运动员之前,你必须成为一个运动员。” 强大的基础支持强大的结果,在 DevOps 和 MLOps 中也是如此。
在接下来的章节中,您将有机会深入了解三大主要云服务提供商及其概念和机器学习产品。
练习
-
使用不同的数据集,在 AWS SageMaker 上创建一个违规漂移报告。
-
在脚本中添加 Python 日志记录,将错误记录到
STDERR,将信息语句记录到STDOUT,并将所有级别记录到文件中。 -
在 Azure ML Studio 上创建一个时间序列数据集。
-
在 Azure ML Studio 中配置数据集监视器,当检测到超出可接受阈值的漂移时发送电子邮件。
批判性思维讨论问题
-
为什么同时记录到多个源可能是可取的?
-
监视数据漂移为何至关重要?
-
列举使用日志记录设施与
print()或echo语句的三个优势。 -
列出最常见的五个日志级别,从最少详细到最详细。
-
在度量捕获系统中找到的三种常见度量类型是什么?
第七章:AWS 上的 MLOps
作者:Noah Gift
每个人都害怕他 [Dr. Abbott(因为他对每个人大喊大叫)]。当我在那里时,有一个叫做哈里斯的新住院医生。尽管哈里斯已经是主治医师并在那里工作了 5 年,但他仍然害怕他 [Dr. Abbott]。后来 [Dr. Abbott] 发生了心脏病发作,然后心脏停止跳动。一位护士大声喊道,“快,他刚发生了心脏骤停,快进来!” 于是哈里斯走进去……靠在胸骨上,打断了肋骨等。于是哈里斯开始给 Abbott 做心肺复苏,他醒了过来。他醒了过来!他抬头看着哈里斯说,“你!停下!” 于是哈里斯停了下来。这就是有关 Abbott 的最后一个故事。
Dr. Joseph Bogen
我经常从学生那里得到一个最常见的问题:“我应该选择哪个云服务?” 我告诉他们,安全的选择是亚马逊。它拥有最广泛的技术选择和最大的市场份额。一旦掌握了 AWS 云,就更容易掌握其他云服务,因为它们也假设你可能已经了解 AWS。本章介绍了 AWS 在 MLOps 中的基础,并探讨了实用的 MLOps 模式。
我有着与 AWS 合作的悠久而丰富的历史。在我担任 CTO 和总经理的体育社交网络上,AWS 是一个秘密武器,使我们能够扩展到全球数百万用户。我还在过去几年中从头开始参与了 AWS 机器学习认证的专家(SME)。我被认定为 AWS ML Hero,我还是 AWS Faculty Cloud Ambassador program 的一员,并在加州大学戴维斯分校、西北大学、杜克大学和田纳西大学教授了数千名学生的云计算证书。所以你可以说我是 AWS 的粉丝!
由于 AWS 平台的庞大规模,不可能涵盖 MLOps 的每一个方面。如果您想要更详尽地了解所有可用的 AWS 平台选项,您也可以查看 Chris Fregly 和 Antje Barth(O’Reilly)的 Data Science on AWS,我是其中的技术编辑之一。
相反,本章侧重于像 AWS Lambda 和 AWS App Runner 这样的高级服务。像 AWS SageMaker 这样的更复杂系统在本书的其他章节中有详细介绍。接下来,让我们开始构建 AWS MLOps 解决方案。
AWS 简介
AWS 之所以成为云计算领导者,原因包括其早期的启动和企业文化。2005 年和 2006 年,亚马逊推出了亚马逊网络服务,包括 MTurk(机械土耳其人)、Amazon S3、Amazon EC2 和 Amazon SQS。
直到今天,这些核心产品不仅仍然存在,而且每个季度和年度都有所改进。AWS 持续改进其产品的原因在于其文化。他们说在亚马逊,永远是“第一天”,这意味着每天都应该保持同样的能量和热情。他们还将客户置于他们所做的一切的“核心”。我已经使用这些基础组件构建了能够处理机器学习、计算机视觉和人工智能任务的成千上万个节点的系统。图 7-1 是在旧金山市中心共享工作空间展示的 AWS 云技术架构的实际白板绘图。

图 7-1. AWS 云架构在白板上的图示
这种文化类型与谷歌非常不同,后者在云计算方面遇到了困难。谷歌文化以研究为导向,重视学术招聘。这种文化的好处是开源项目,如 Kubernetes 和 TensorFlow。两者都是复杂的工程奇迹。缺点是在文化中客户不是第一位,这在云计算市场份额上伤害了谷歌。许多组织对于不购买专业服务并支持像云计算这样关键的事物感到犹豫不决。
接下来,让我们看看如何开始使用 AWS 服务。
开始使用 AWS 服务
要开始使用 AWS,最初只需要一个免费层账户。如果你在大学,你也可以使用AWS 学院和AWS Educate。AWS 学院提供实践认证材料和实验室。AWS Educate 为课堂提供沙盒环境。
一旦你有了账户,下一步是尝试各种服务。思考 AWS 的一种方式是将其与批量批发店进行比较。例如,根据statista,Costco 在全球有 795 个分店,遍布多个国家,大约五分之一的美国人在那里购物。同样,根据 2020 年的亚马逊网络服务白皮书,AWS 提供了一个基础设施,支持“全球 190 个国家数十万家企业”。
在 Costco,有三种方法被认为与 AWS 类比相关:
-
在第一种情况下,顾客可以走进去订购一份非常便宜但质量合理的批量披萨。
-
在第二种情况下,一位新来的 Costco 顾客需要找出所有的散装商品,以便调查最佳的 Costco 使用方式。他们需要走过整个商店,看看像糖这样的散装商品,看看他们可以用这些原料建造什么。
-
在第三种情况下,当 Costco 的客户了解到已准备好的餐点(如旋转烤鸡、比萨等)并知道他们可以购买的所有原材料时,他们可以理论上利用自己对 Costco 及其提供的服务的了解开办一个餐饮公司、当地熟食店或餐厅。
Costco 这样规模的大型零售商对已经准备好的食品收费更多,对生的食材收费更少。Costco 的客户可以根据他们组织的成熟度和他们想要解决的问题来选择不同的食品准备水平。图 7-2 说明了这些 Costco 选项与 AWS 选项的比较。

图 7-2. Costco 与 AWS
让我们以夏威夷著名海滩附近的一家当地鱼生碗摊位为例。店主可以批量购买 Costco 的现成鱼生并以大约两倍成本的价格出售。但相反,夏威夷的另一个成熟的烧烤餐厅,有能力烹饪和准备食物的员工,Costco 销售未加工的食品的价格低于已经准备好的鱼生。
像 Costco 一样,AWS 提供不同级别的产品,客户可以自行决定他们要利用多少。让我们深入了解这些选项。
使用“No Code/Low Code” AWS Comprehend 解决方案
最后一个例子展示了如何利用 Costco 可以使不同的餐厅业务受益,从没有员工的快餐摊位到更大规模的餐厅。Costco 在准备食物方面做的工作越多,购买它的顾客的收益就越高,食物的成本也越高。AWS 也适用同样的概念;AWS 为您做的工作越多,您就需要支付的费用就越高,并且您需要维护服务的人员就越少。
注:
在经济学中,比较优势理论认为你不应该直接比较某个东西的成本,而是应该比较自己做的机会成本。所有云服务提供商都将这种假设内在化,因为运行数据中心并在该数据中心上构建服务是他们的专长。进行 MLOps 的组织应该专注于为客户创造产生收入的产品,而不是重新创建云服务提供商做得很糟糕的事情。
对于 AWS,一个很好的起点是像 Costco 的客户那样批量订购 Costco 比萨。同样,一家财富 500 强公司可能有必要将自然语言处理(NLP)添加到其客户服务产品中,可以花费九个月到一年时间招聘团队并构建这些能力,也可以开始使用宝贵的高级别服务,比如AWS Comprehend for Natural Language Processing。AWS Comprehend 还使用户可以利用亚马逊 API 执行许多 NLP 操作,包括以下内容:
-
实体检测
-
关键词检测
-
PII
-
语言检测
-
情绪
例如,您可以将文本剪切并粘贴到亚马逊理解控制台中,AWS Comprehend 将找到所有文本的实体。在 图 7-3 的示例中,我抓取了勒布朗·詹姆斯维基百科传记的第一段,粘贴到控制台,点击分析,它会为我突出显示实体。

图 7-3. AWS Comprehend
其他用例,如审查医疗记录或确定客户服务响应的情绪,同样可以通过 AWS Comprehend 和 boto3 Python SDK 简单实现。接下来,让我们来介绍一个关于在 AWS 上进行 DevOps 的“hello world”项目,使用 Amazon S3 部署静态网站。
使用 Hugo 静态 S3 网站
在以下场景中,探索 AWS 的一个很好的方法是像第一次参观 Costco 时一样“四处走走”控制台。您可以首先查看 AWS 的基础组件,即 IaaS(基础设施即代码)。这些核心服务包括 AWS S3 对象存储和 AWS EC2 虚拟机。
在这里,我将向您介绍如何使用“hello world”示例,在 AWS S3 静态网站托管 上部署 Hugo 网站。选择使用 Hugo 进行 hello world 的原因是它相对简单设置,并且将帮助您更好地理解使用核心基础设施进行主机服务。这些技能在您学习使用持续交付部署机器学习应用程序时将非常有用。
注意
值得注意的是,亚马逊 S3 具有低成本和高可靠性。 S3 的定价接近每 GB 一分钱。这种低成本且高可靠性的基础设施是云计算如此引人注目的原因之一。
您可以在 GitHub 仓库 中查看整个项目。请注意,GitHub 是网站的真实来源,因为整个项目由文本文件组成:Markdown 文件,Hugo 模板和 AWS Code 构建服务器的构建命令。此外,您还可以在那里通过连续部署的屏幕录像来了解。图 7-4 显示了该项目的高级架构。

图 7-4. Hugo
该项目的工作原理简而言之是通过 buildspec.yml 文件的魔力。让我们来看看以下示例中的工作原理。首先,注意 hugo 二进制文件安装,然后运行 hugo 命令以从检出的存储库生成 HTML 文件。最后,由于 S3 存储桶托管的强大功能,aws 命令 aws s3 sync --delete public s3://dukefeb1 就是整个部署过程:
version: 0.1
environment_variables:
plaintext:
HUGO_VERSION: "0.79.1"
phases:
install:
commands:
- cd /tmp
- wget https://github.com/gohugoio/hugo/releases/download/v0.80.0/\
hugo_extended_0.80.0_Linux-64bit.tar.gz
- tar -xzf hugo_extended_0.80.0_Linux-64bit.tar.gz
- mv hugo /usr/bin/hugo
- cd
- rm -rf /tmp/*
build:
commands:
- rm -rf public
- hugo
post_build:
commands:
- aws s3 sync --delete public s3://dukefeb1
- echo Build completed on `date`
描述构建系统文件的另一种方式是它是一个配方。构建配置文件中的信息是在 AWS Cloud9 开发环境中执行相同操作的“如何”。
如第二章所述,AWS Cloud9 在解决特定问题方面具有特殊意义。基于云的开发环境使您能够在发生所有活动的确切位置进行开发。示例展示了这一概念的强大之处。查看代码,在云中进行测试,并验证相同工具的部署。在图 7-5 中,AWS Cloud9 环境调用了一个 Python 微服务。

图 7-5. Cloud9
注意
您可以在O'Reilly 平台上观看 Hugo 在 AWS 上的部署演示,并参考Pragmatic AI Labs 网站上的更详细指南。
有了持续交付的基础,让我们深入了解 AWS 平台上的无服务器。
无服务器烹饪书
无服务器是 MLOps 中至关重要的方法。在第二章中,我提到了 Python 函数的重要性。Python 函数是一个可以接收输入并可选择返回输出的工作单元。如果把 Python 函数比作是烤面包机,您放入面包,它加热面包,并弹出烤好的面包,那么无服务器就是电的源泉。
Python 函数需要在某处运行,就像烤面包机需要插电才能工作一样。这就是无服务器的概念;它使代码能够在云中运行。无服务器的最通用定义是无需服务器即可运行的代码。服务器本身被抽象化以便开发人员专注于编写函数。这些函数执行特定任务,可以将这些任务链接在一起构建更复杂的系统,例如响应事件的服务器。
在云计算中,函数是宇宙的中心。实际上,这意味着任何函数都可以映射到解决问题的技术中:容器、Kubernetes、GPU 或 AWS Lambda。正如您在图 7-6 中看到的,Python 中有一个丰富的解决方案生态系统直接映射到函数。
在 AWS 平台上执行无服务器的最低级别服务是 AWS Lambda。让我们来看看这个仓库中的几个例子。
首先,AWS 上编写的较为简单的 Lambda 函数之一是马可·波罗函数。马可·波罗函数接收一个包含名称的事件。例如,如果事件名称是“Marco”,它返回“Polo”。如果事件名称是其他内容,则返回“No!”。

图 7-6. Python 函数
注意
在 1980 年代和 1990 年代成长为青少年时,Marco Polo 是夏季游泳池中的一种典型游戏。当我在家附近的一个游泳池担任营地顾问时,这是我监督的孩子们最喜欢的游戏。游戏的方式是每个人都进入游泳池,有一个人闭上眼睛喊“Marco”,其他游泳者必须回答“Polo”。闭眼的人通过声音找到要标记的人。一旦有人被标记,他们就成了“它”。
这是 AWS Lambda 的 Marco Polo 代码;请注意 event 传递到 lambda_handler:
def lambda_handler(event, context):
print(f"This was the raw event: {event}")
if event["name"] == "Marco":
print(f"This event was 'Marco'")
return "Polo"
print(f"This event was not 'Marco'")
return "No!"
在无服务器云计算中,想象一下车库中的灯泡。灯泡可以通过手动开关或自动通过车库门打开事件等多种方式打开。同样,AWS Lambda 也会响应多种信号。
让我们列举灯泡和 Lambda 可以触发的方式:
-
灯泡
-
手动打开开关。
-
通过车库门开启器。
-
每晚安全定时器在午夜到上午 6 点之间打开灯光。
-
-
AWS Lambda
-
可以通过控制台、AWS 命令行或 AWS Boto3 SDK 手动调用。
-
响应 S3 事件,比如上传文件到一个存储桶中。
-
定时器每晚调用以下载数据。
-
还有更复杂的例子吗?使用 AWS Lambda,您可以简单地在所有新图像放入文件夹时集成 S3 触发器与计算机视觉标签。代码量微不足道:
import boto3
from urllib.parse import unquote_plus
def label_function(bucket, name):
"""This takes an S3 bucket and a image name!"""
print(f"This is the bucketname {bucket} !")
print(f"This is the imagename {name} !")
rekognition = boto3.client("rekognition")
response = rekognition.detect_labels(
Image={"S3Object": {"Bucket": bucket, "Name": name,}},
)
labels = response["Labels"]
print(f"I found these labels {labels}")
return labels
def lambda_handler(event, context):
"""This is a computer vision lambda handler"""
print(f"This is my S3 event {event}")
for record in event['Records']:
bucket = record['s3']['bucket']['name']
print(f"This is my bucket {bucket}")
key = unquote_plus(record['s3']['object']['key'])
print(f"This is my key {key}")
my_labels = label_function(bucket=bucket,
name=key)
return my_labels
最后,您可以通过 AWS Step Functions 将多个 AWS Lambda 函数串联起来:
{
"Comment": "This is Marco Polo",
"StartAt": "Marco",
"States": {
"Marco": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:561744971673:function:marco20",
"Next": "Polo"
},
"Polo": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:561744971673:function:polo",
"Next": "Finish"
},
"Finish": {
"Type": "Pass",
"Result": "Finished",
"End": true
}
}
}
您可以在 Figure 7-7 中看到此工作流程的实际操作。

图 7-7. 步骤函数
更有趣的是,您可以通过 CLI 调用 AWS Lambda 函数。这里是一个例子:
aws lambda invoke \
--cli-binary-format raw-in-base64-out \
--function-name marcopython \
--payload '{"name": "Marco"}' \
response.json
注意
重要的是始终参考最新的 AWS CLI 文档,因为它是一个活跃的目标。截至本书写作时,当前的 CLI 版本是 V2,但您可能需要根据未来的变化调整命令行示例。您可以在AWS CLI 命令参考网站找到最新的文档。
负载的响应如下:
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
(.venv) [cloudshell-user@ip-10-1-14-160 ~]$ cat response.json
"Polo"(.venv) [cloudshell-user@ip-10-1-14-160 ~]$
注意
欲了解更高级的 AWS Lambda 演练,请查看Pragmatic AI Labs YouTube 频道或O’Reilly 学习平台上的小型 Wikipedia 微服务演练。
AWS Lambda 可能是您用来为机器学习流水线提供预测或在 MLOps 进程中处理事件的最有价值和灵活的计算类型。这是因为开发和测试的速度。接下来,让我们谈谈一些 CaaS(容器即服务)的提供。
AWS CaaS
Fargate 是 AWS 提供的一种容器即服务(CaaS),允许开发人员专注于构建容器化的微服务。例如,在图 7-8 中,当此微服务在容器中工作时,整个运行时,包括部署所需的包,都将在一个新环境中工作。云平台处理其余的部署工作。

图 7-8. MLOps for CaaS
注意
容器解决了困扰软件行业的许多问题。因此,作为一个一般规则,对于 MLOps 项目来说,使用它们是个好主意。以下是容器在项目中的一些优点的部分列表:
-
允许开发人员在本地桌面上模拟生产服务
-
允许通过公共容器注册表(如 Docker Hub、GitHub Container Registry 和 Amazon Elastic Container Registry)轻松地将软件运行时分发给客户。
-
允许 GitHub 或源代码存储库成为“事实上的真实来源”,并包含微服务的所有方面:模型、代码、IaC 和运行时
-
允许通过 CaaS 服务轻松进行生产部署
让我们看看如何使用 Flask 构建一个返回正确找零的微服务。图 7-9 展示了在 AWS Cloud9 上的开发工作流程。Cloud9 是开发环境;一个容器被构建并推送到 ECR。稍后,该容器在 ECS 中运行。

图 7-9. ECS 工作流
以下是app.py的 Python 代码:
from flask import Flask
from flask import jsonify
app = Flask(__name__)
def change(amount):
# calculate the resultant change and store the result (res)
res = []
coins = [1,5,10,25] # value of pennies, nickels, dimes, quarters
coin_lookup = {25: "quarters", 10: "dimes", 5: "nickels", 1: "pennies"}
# divide the amount*100 (the amount in cents) by a coin value
# record the number of coins that evenly divide and the remainder
coin = coins.pop()
num, rem = divmod(int(amount*100), coin)
# append the coin type and number of coins that had no remainder
res.append({num:coin_lookup[coin]})
# while there is still some remainder, continue adding coins to the result
while rem > 0:
coin = coins.pop()
num, rem = divmod(rem, coin)
if num:
if coin in coin_lookup:
res.append({num:coin_lookup[coin]})
return res
@app.route('/')
def hello():
"""Return a friendly HTTP greeting."""
print("I am inside hello world")
return 'Hello World! I can make change at route: /change'
@app.route('/change/<dollar>/<cents>')
def changeroute(dollar, cents):
print(f"Make Change for {dollar}.{cents}")
amount = f"{dollar}.{cents}"
result = change(float(amount))
return jsonify(result)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
注意,Flask Web 微服务通过网址模式/change/<dollar>/<cents>响应更改请求。您可以查看这个Fargate 示例在 GitHub 存储库中的完整源代码。以下是步骤:
-
设置应用程序:virtualenv +
make all -
在本地测试应用程序:
python app.py -
使用 curl 进行测试:
curl localhost:8080/change/1/34 -
创建 ECR(Amazon 容器注册表)
在图 7-10,一个 ECR 存储库使后续的 Fargate 部署成为可能。
![pmlo 0710]()
图 7-10. ECR
-
构建容器
-
推送容器
-
在本地运行 docker:
docker run -p 8080:8080 changemachine -
部署到 Fargate
-
测试公共服务
注意
任何云服务都会在功能上迅速变化,因此最好阅读当前的文档。当前的Fargate 文档是了解部署到该服务的最新方式的好地方。
你也可以选择在O’Reilly 平台观看完整的 Fargate 部署演示。
另一个 CaaS 选项是 AWS App Runner,它进一步简化了事务。例如,您可以直接从源代码部署或指向一个容器。在图 7-11,AWS App Runner 创建了一个流线型工作流,连接源代码存储库、部署环境和最终的安全 URL。

图 7-11. AWS App Runner
此存储库可以轻松地转换为 AWS App Runner 方法,只需按照以下步骤在 AWS 向导中操作:
-
要构建该项目,请使用命令:
pip install -r requirements.txt。 -
要运行该项目,请使用:
python app.py。 -
最后,配置端口使用:
8080。
一个关键的创新,如图 7-12 所示,是能够将许多不同的 AWS 服务连接起来,即核心基础设施,如 AWS CloudWatch、负载均衡器、容器服务和 API 网关,形成一个完整的解决方案。

图 7-12. AWS App Runner 服务已创建
最终部署的服务在图 7-13 中显示了一个安全的 URL,并且能够调用端点并返回正确的更改。

图 7-13. AWS App Runner 部署完成
这种方法有何神奇之处?简言之,所有的逻辑,包括可能的机器学习模型,都在一个代码库中。因此,这是构建 MLOps 友好产品的一种引人注目的方式。此外,将机器学习应用程序交付到生产中的一个更复杂的方面是微服务部署。AWS App Runner 让大部分复杂性消失,为 MLOps 问题的其他部分节省时间。接下来,让我们讨论 AWS 如何处理计算机视觉。
计算机视觉
我在北西大学的研究生数据科学课程中教授应用计算机视觉课程。这门课非常有趣,因为我们做以下事情:
-
每周视频演示
-
焦点放在解决问题上,而不是编码或建模
-
使用高级工具如 AWS DeepLens,一个支持深度学习的视频摄像机
实际上,这允许一个快速的反馈循环,专注于解决问题而不是解决问题的技术。在使用的技术中,AWS Deep Lens 设备是一个例子,如图 7-14 所示。该设备是一个完整的计算机视觉硬件开发工具包,包含一个 1080p 摄像头、操作系统和无线功能。特别是,这解决了计算机视觉原型设计的问题。

图 7-14. DeepLens
一旦 AWS DeepLens 开始捕获视频,它将视频分成两个流。在图 7-15 中显示的项目流将实时注释添加到视频并将数据包发送到 MQTT(消息队列遥测传输)服务,这是一种发布-订阅网络协议。

图 7-15. 检测
在图 7-16 中,随着对象在流中被检测到,MQTT 数据包实时到达。
DeepLens 是一种“即插即用”的技术,因为它解决了构建实时计算机视觉原型系统中可能最具挑战性的问题——捕获数据并将其发送到某个地方。这种技术的“乐趣”在于使用 AWS DeepLens 轻松从零构建解决方案。接下来,让我们更具体地进入,超越仅构建微服务,进入构建部署机器学习代码的微服务。

图 7-16. MQTT
AWS 上的 MLOps
在 AWS 上开始 MLOps 的一种方法是考虑以下问题。当面临一个机器学习问题时,有三个约束条件——预测准确性、可解释性和操作性,你会侧重于哪两个以取得成功,并且顺序是什么?
许多以学术为重点的数据科学家立即跳到预测准确性。构建越来越好的预测模型是一种有趣的挑战,就像玩俄罗斯方块一样。此外,建模是一项光荣的工作方面,也是工作中令人向往的一部分。数据科学家喜欢展示他们能够使用日益复杂的技术训练学术模型的准确性。
Kaggle 平台的整体工作是提高预测准确性,并为精确模型提供经济奖励。另一种方法是专注于模型的运行化。这种方法的优势在于,随着软件系统的改进,模型的准确性也能得到提高。就像日本汽车工业专注于改善(Kaizen)或持续改进一样,一个机器学习系统可以专注于合理的初始预测准确性,并快速改进。
AWS 的文化支持“行动偏见”和“交付结果”这一概念。所谓“行动偏见”是指默认选择速度和交付结果,专注于业务的关键输入,并快速交付结果。因此,AWS 围绕机器学习的产品,如 AWS SageMaker,展现了这种行动和结果导向文化的精神。
持续交付(CD)是 MLOps 中的核心组成部分。在可以为机器学习自动化交付之前,微服务本身需要自动化。具体细节会根据涉及的 AWS 服务类型而变化。让我们从一个端到端的示例开始。
在以下示例中,一个 Elastic Beanstalk Flask 应用程序使用 AWS CodeBuild 到 AWS Elastic Beanstalk 实现持续部署。这种“堆栈”也非常适合部署 ML 模型。Elastic Beanstalk 是 AWS 提供的平台即服务技术,可以简化应用程序部署的大部分工作。
在 图 7-17 中,注意 AWS Cloud9 是开发的推荐起点。接下来,一个 GitHub 仓库保存项目的源代码,并在发生变更事件时触发云原生构建服务器 AWS CodeBuild。最后,AWS CodeBuild 进程运行持续集成,为 AWS Elastic Beanstalk 提供持续交付。

图 7-17. 弹性 Beanstalk
注意
以下链接包含源代码和此示例的详细步骤:
要复制此项目,请执行以下步骤:
-
如果你具备强大的命令行技能,可以在 AWS Cloud9 或 AWS CloudShell 中查看仓库。
-
创建 Python 虚拟环境并激活它,然后运行
make all:python3 -m venv ~/.eb source ~/.eb/bin/activate make all请注意,
awsebcli通过 requirements 安装,并且此工具从 CLI 控制 Elastic Beanstalk。 -
初始化新的
eb应用程序:eb init -p python-3.7 flask-continuous-delivery --region us-east-1可选择使用
eb init创建 SSH 密钥以登录运行实例。 -
创建远程 eb 实例:
eb create flask-continuous-delivery-env -
设置 AWS Code Build 项目。请注意,您的 Makefile 需要反映您的项目名称:
version: 0.2 phases: install: runtime-versions: python: 3.7 pre_build: commands: - python3.7 -m venv ~/.venv - source ~/.venv/bin/activate - make install - make lint build: commands: - make deploy
当您使用持续部署成功运行项目后,您已准备好进入下一步,部署机器学习模型。我强烈建议您先完成像这样的“Hello World”类型的项目,再深入学习复杂的机器学习项目。接下来,让我们看一个刻意简单的 MLOps Cookbook,它是许多新 AWS 服务部署的基础。
AWS 上的 MLOps Cookbook
基础组件准备好后,让我们看一个基本的机器学习示例,并将其应用于多种场景。注意,这个核心示例可部署到 AWS 和许多其他云环境的多个服务上。此后的 MLOps Cookbook 项目刻意简洁,重点是部署机器学习。例如,此项目根据体重输入预测美国职业棒球大联盟球员的身高。
在 图 7-18 中,GitHub 是信息源并包含项目脚手架。接下来,构建服务是 GitHub Actions,容器服务是 GitHub Container Registry。这两项服务都可以轻松替代云中的类似服务。特别是在 AWS 云上,你可以使用 AWS CodeBuild 进行 CI/CD 和 AWS ECR(Elastic Container Registry)。最后,一旦项目被“容器化”,它将面向多个部署目标开放。在 AWS 上,这些包括 AWS Lambda、AWS 弹性 Beanstalk 和 AWS App Runner。

图 7-18. MLOps Cookbook
以下文件在多个不同的方案中构建解决方案时都非常有用:
Makefile
Makefile 既是一系列配方的列表,也是调用这些配方的方法。查看 示例 GitHub 项目中的 Makefile。
requirements.txt
requirements 文件包含项目所需的 Python 包列表。通常这些包会固定版本号,以限制意外的包依赖。查看 示例 GitHub 项目中的 requirements.txt。
cli.py
这个命令行显示了如何从 CLI 调用 ML 库,而不仅仅是通过 Web 应用程序。查看example GitHub 项目中的 cli.py。
utilscli.py
utilscli.py 是一个实用工具,允许用户调用不同的端点,即 AWS、GCP、Azure 或任何生产环境。大多数机器学习算法需要数据进行缩放。这个工具简化了输入的缩放和输出的恢复。查看example GitHub 项目中的 utilscli.py。
app.py
应用程序文件是 Flask Web 微服务,通过/predict URL 端点接受并返回 JSON 预测结果。查看example GitHub 项目中的 app.py。
mlib.py
模型处理库在一个集中位置完成了大部分的繁重工作。这个库意在非常基础,不解决像缓存加载模型或其他生产环境独特的更复杂问题。查看example GitHub 项目中的 mlib.py。
htwtmlb.csv
CSV 文件对于输入缩放非常有帮助。查看example GitHub 项目中的 htwtmlb.csv。
model.joblib
这个模型是从 sklearn 导出的,但也可以很容易地转换为其他格式,比如 ONNX 或 TensorFlow。其他真实世界的生产考虑可能包括将这个模型保存在不同的位置,比如 Amazon S3、一个容器中,或者甚至由 AWS SageMaker 托管。查看example GitHub 项目中的 model.joblib。
Dockerfile
该文件使项目容器化,从而在 AWS 平台以及其他云上开放了许多新的部署选项。查看example GitHub 项目中的 Dockerfile。
Baseball_Predictions_Export_Model.ipynb
Jupyter 笔记本是机器学习项目中包括的关键工件。它展示了创建模型背后的思路,并为在生产中维护项目提供了宝贵的背景信息。查看example GitHub 项目中的 Baseball_Predictions_Export_Model.ipynb。
这些项目工件对于解释 MLOps 作为教育工具非常有帮助,但在独特的生产场景中可能会有所不同或更加复杂。接下来,让我们讨论 CLI(命令行界面)工具如何帮助操作化机器学习项目。
CLI 工具
在这个项目中有两个 CLI 工具。首先,主要的cli.py是服务预测输出的端点。例如,要预测 MLB 球员的身高,您可以使用以下命令创建一个预测:./cli.py --weight 180。请注意,在图 7-19 中,--weight命令行选项允许用户快速测试许多新的预测输入。

图 7-19. CLI 预测
所以这是如何工作的?大部分的“魔法”是通过一个库来完成的,该库负责扩展数据、进行预测,然后再进行逆转换:
"""MLOps Library"""
import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge
import joblib
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import logging
logging.basicConfig(level=logging.INFO)
import warnings
warnings.filterwarnings("ignore", category=UserWarning)
def load_model(model="model.joblib"):
"""Grabs model from disk"""
clf = joblib.load(model)
return clf
def data():
df = pd.read_csv("htwtmlb.csv")
return df
def retrain(tsize=0.1, model_name="model.joblib"):
"""Retrains the model
See this notebook: Baseball_Predictions_Export_Model.ipynb
"""
df = data()
y = df["Height"].values # Target
y = y.reshape(-1, 1)
X = df["Weight"].values # Feature(s)
X = X.reshape(-1, 1)
scaler = StandardScaler()
X_scaler = scaler.fit(X)
X = X_scaler.transform(X)
y_scaler = scaler.fit(y)
y = y_scaler.transform(y)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=tsize, random_state=3
)
clf = Ridge()
model = clf.fit(X_train, y_train)
accuracy = model.score(X_test, y_test)
logging.debug(f"Model Accuracy: {accuracy}")
joblib.dump(model, model_name)
return accuracy, model_name
def format_input(x):
"""Takes int and converts to numpy array"""
val = np.array(x)
feature = val.reshape(-1, 1)
return feature
def scale_input(val):
"""Scales input to training feature values"""
df = data()
features = df["Weight"].values
features = features.reshape(-1, 1)
input_scaler = StandardScaler().fit(features)
scaled_input = input_scaler.transform(val)
return scaled_input
def scale_target(target):
"""Scales Target 'y' Value"""
df = data()
y = df["Height"].values # Target
y = y.reshape(-1, 1) # Reshape
scaler = StandardScaler()
y_scaler = scaler.fit(y)
scaled_target = y_scaler.inverse_transform(target)
return scaled_target
def height_human(float_inches):
"""Takes float inches and converts to human height in ft/inches"""
feet = int(round(float_inches / 12, 2)) # round down
inches_left = round(float_inches - feet * 12)
result = f"{feet} foot, {inches_left} inches"
return result
def human_readable_payload(predict_value):
"""Takes numpy array and returns back human readable dictionary"""
height_inches = float(np.round(predict_value, 2))
result = {
"height_inches": height_inches,
"height_human_readable": height_human(height_inches),
}
return result
def predict(weight):
"""Takes weight and predicts height"""
clf = load_model() # loadmodel
np_array_weight = format_input(weight)
scaled_input_result = scale_input(np_array_weight)
scaled_height_prediction = clf.predict(scaled_input_result)
height_predict = scale_target(scaled_height_prediction)
payload = human_readable_payload(height_predict)
predict_log_data = {
"weight": weight,
"scaled_input_result": scaled_input_result,
"scaled_height_prediction": scaled_height_prediction,
"height_predict": height_predict,
"human_readable_payload": payload,
}
logging.debug(f"Prediction: {predict_log_data}")
return payload
接下来,Click 框架包装了对 mlib.py 的库调用,并提供了一个清晰的界面来提供预测服务。使用命令行工具与机器学习模型进行交互的主要优势有很多。快速开发和部署命令行机器学习工具的速度可能是最重要的:
#!/usr/bin/env python
import click
from mlib import predict
@click.command()
@click.option(
"--weight",
prompt="MLB Player Weight",
help="Pass in the weight of a MLB player to predict the height",
)
def predictcli(weight):
"""Predicts Height of an MLB player based on weight"""
result = predict(weight)
inches = result["height_inches"]
human_readable = result["height_human_readable"]
if int(inches) > 72:
click.echo(click.style(human_readable, bg="green", fg="white"))
else:
click.echo(click.style(human_readable, bg="red", fg="white"))
if __name__ == "__main__":
# pylint: disable=no-value-for-parameter
predictcli()
第二个 CLI 工具是 utilscli.py,它执行模型重新训练,并可以作为执行更多任务的入口点。例如,这个版本不会更改默认的 model_name,但你可以通过 forking this repo 添加该选项:
./utilscli.py retrain --tsize 0.4
注意,mlib.py 再次承担了大部分的重活,但 CLI 提供了一个便捷的方式来快速原型化一个 ML 模型:
#!/usr/bin/env python
import click
import mlib
import requests
@click.group()
@click.version_option("1.0")
def cli():
"""Machine Learning Utility Belt"""
@cli.command("retrain")
@click.option("--tsize", default=0.1, help="Test Size")
def retrain(tsize):
"""Retrain Model
You may want to extend this with more options, such as setting model_name
"""
click.echo(click.style("Retraining Model", bg="green", fg="white"))
accuracy, model_name = mlib.retrain(tsize=tsize)
click.echo(
click.style(f"Retrained Model Accuracy: {accuracy}", bg="blue",
fg="white")
)
click.echo(click.style(f"Retrained Model Name: {model_name}", bg="red",
fg="white"))
@cli.command("predict")
@click.option("--weight", default=225, help="Weight to Pass In")
@click.option("--host", default="http://localhost:8080/predict",
help="Host to query")
def mkrequest(weight, host):
"""Sends prediction to ML Endpoint"""
click.echo(click.style(f"Querying host {host} with weight: {weight}",
bg="green", fg="white"))
payload = {"Weight":weight}
result = requests.post(url=host, json=payload)
click.echo(click.style(f"result: {result.text}", bg="red", fg="white"))
if __name__ == "__main__":
cli()
图 7-20 是重新训练模型的一个示例。

图 7-20. 模型重新训练
你还可以查询已部署的 API,这很快会处理 CLI,允许你更改主机和传递到 API 的值。这一步骤使用 requests 库。它可以帮助构建一个“纯” Python 示例,用于预测工具,而不仅仅是通过 curl 命令进行预测。你可以在 图 7-21 中看到输出的一个示例:
./utilscli.py predict --weight 400

图 7-21. 预测请求
也许你已经被 CLI 工具作为快速部署 ML 模型的理想方式所吸引,因此成为真正的 MLOps 导向组织。还能做什么?这里有两个更多的想法。
首先,你可以构建一个更复杂的客户端,使其可以对部署的 Web 服务进行异步 HTTP 请求。这个功能是在纯 Python 中构建实用工具的一个优点之一。一个可以考虑用于异步 HTTPS 的库是 Fast API。
其次,你可以持续部署 CLI 本身。对于许多 SaaS 公司、大学实验室等场景,这可能是适应速度和敏捷性的理想工作流程。在这个示例 GitHub 项目中,有一个关于 如何将命令行工具容器化的简单示例。关键文件包括 Makefile、cli.py 和 Dockerfile。
注意,Makefile 使用 hadolint 轻松“lint” Dockerfile 的语法:
install:
pip install --upgrade pip &&\
pip install -r requirements.txt
lint:
docker run --rm -i hadolint/hadolint < Dockerfile
CLI 本身非常简洁,这是 Click 框架的一个宝贵特性之一:
#!/usr/bin/env python
import click
@click.command()
@click.option("--name")
def hello(name):
click.echo(f'Hello {name}!')
if __name__ == '__main__':
#pylint: disable=no-value-for-parameter
hello()
最后,Dockerfile 构建了容器:
FROM python:3.7.3-stretch
# Working Directory
WORKDIR /app
# Copy source code to working directory
COPY . app.py /app/
# Install packages from requirements.txt
# hadolint ignore=DL3013
RUN pip install --no-cache-dir --upgrade pip &&\
pip install --no-cache-dir --trusted-host pypi.python.org -r requirements.txt
要运行这个确切的容器,你可以执行以下步骤:
docker run -it noahgift/cloudapp python app.py --name "Big John"
输出如下:
Hello Big John!
这个工作流程非常适合基于 ML 的 CLI 工具!例如,要自己构建这个容器并推送它,你可以按照以下工作流程进行:
docker build --tag=<tagname> .
docker push <repo>/<name>:<tagname>
本节涵盖了如何构建机器学习项目的思路。特别是,有三个主要想法值得考虑:使用容器、构建 Web 微服务以及使用命令行工具。接下来,让我们更详细地讨论 Flask 微服务。
Flask 微服务
在处理 MLOps 工作流时,值得注意的是,Flask ML 微服务可以以多种方式运行。本节涵盖了许多这些示例。
让我们首先看一下下面示例中 Flask 机器学习微服务应用程序的核心。请注意,大部分繁重的工作是通过 mlib.py 库完成的。唯一的“真实”代码是 Flask 路由,执行以下 @app.route("/predict", methods=['POST']) 的 POST 请求。它接受一个类似 {"Weight": 200} 的 JSON 负载,然后返回一个 JSON 结果:
from flask import Flask, request, jsonify
from flask.logging import create_logger
import logging
from flask import Flask, request, jsonify
from flask.logging import create_logger
import logging
import mlib
app = Flask(__name__)
LOG = create_logger(app)
LOG.setLevel(logging.INFO)
@app.route("/")
def home():
html = f"<h3>Predict the Height From Weight of MLB Players</h3>"
return html.format(format)
@app.route("/predict", methods=['POST'])
def predict():
"""Predicts the Height of MLB Players"""
json_payload = request.json
LOG.info(f"JSON payload: {json_payload}")
prediction = mlib.predict(json_payload['Weight'])
return jsonify({'prediction': prediction})
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8080, debug=True)
这个 Flask Web 服务使用 python app.py 运行非常简单。例如,您可以使用 python app.py 命令来运行 Flask 微服务:
(.venv) ec2-user:~/environment/Python-MLOps-Cookbook (main) $ python app.py
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production...
Use a production WSGI server instead.
* Debug mode: on
INFO:werkzeug: * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
INFO:werkzeug: * Restarting with stat
WARNING:werkzeug: * Debugger is active!
INFO:werkzeug: * Debugger PIN: 251-481-511
要对应用程序进行预测服务,请运行 predict.sh。请注意,一个小的 bash 脚本可以帮助您调试应用程序,而无需输入所有 curl 的术语,这可能会导致语法错误:
#!/usr/bin/env bash
PORT=8080
echo "Port: $PORT"
# POST method predict
curl -d '{
"Weight":200
}'\
-H "Content-Type: application/json" \
-X POST http://localhost:$PORT/predict
预测结果显示,Flask 端点返回一个 JSON 负载:
(.venv) ec2-user:~/environment/Python-MLOps-Cookbook (main) $ ./predict.sh
Port: 8080
{
"prediction": {
"height_human_readable": "6 foot, 2 inches",
"height_inches": 73.61
}
}
请注意,早期的 utilscli.py 工具也可以向此端点发出 web 请求。您还可以使用 httpie 或者 postman 工具。接下来,让我们讨论容器化策略在这个微服务中的应用。
容器化的 Flask 微服务
下面是构建容器并在本地运行的示例。(您可以在 GitHub 上找到 predict.sh 的内容。)
#!/usr/bin/env bash
# Build image
#change tag for new container registery, gcr.io/bob
docker build --tag=noahgift/mlops-cookbook .
# List docker images
docker image ls
# Run flask app
docker run -p 127.0.0.1:8080:8080 noahgift/mlops-cookbook
添加容器工作流程非常简单,并且它能够支持更简单的开发方法,因为您可以与团队中的其他人共享容器。它还打开了将您的机器学习应用程序部署到更多平台的选项。接下来,让我们谈谈如何自动构建和部署容器。
通过 GitHub Actions 自动构建容器并推送到 GitHub 容器注册表
正如本书前面所述,GitHub Actions 的容器工作流对于许多场景都是一种有价值的成分。通过 GitHub Actions 自动构建容器并推送到 GitHub 容器注册表是有意义的。这一步骤可以同时作为容器构建过程和部署目标的测试,比如您要部署前面讨论过的 CLI 工具。下面是实际的代码示例。请注意,您需要更改 tags 来匹配您的容器注册表(如 图 7-22 所示):
build-container:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Loging to GitHub registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.BUILDCONTAINERS }}
- name: build flask app
uses: docker/build-push-action@v2
with:
context: ./
#tags: alfredodeza/flask-roberta:latest
tags: ghcr.io/noahgift/python-mlops-cookbook:latest
push: true

图 7-22. GitHub 容器注册表
SaaS(即服务软件)构建系统和 SaaS 容器注册表不仅仅在核心云环境之外也很有帮助。它们向开发人员验证,无论是内部还是外部的开发人员,容器工作流程都是有效的。接下来,让我们将本章的许多概念联系起来,使用一个高级 PaaS 提供。
AWS App Runner Flask 微服务
AWS App Runner 是一个高级服务,大大简化了 MLOps。例如,之前的 Python MLOps cookbook 配方只需几次 AWS App Runner 服务点击即可轻松集成。此外,如图 7-23 所示,AWS App Runner 指向一个源代码库,并且它会在每次 GitHub 更改时自动部署。
一旦部署完成,您可以打开 AWS Cloud9 或 AWS CloudShell,克隆 Python MLOps Cookbook 存储库,然后使用 utilscli.py 查询来自 App Runner 服务的端点。在 AWS CloudShell 中成功查询显示在图 7-24 中。

图 7-23. AWS App Runner

图 7-24. AWS App Runner 预测结果
简而言之,高级 AWS 服务使您能够更高效地进行 MLOps,因为较少的工作量用于 DevOps 构建流程。接下来,让我们转向 AWS 的另一个计算机选项,AWS Lambda。
AWS Lambda 配方
安装SAM(AWS 无服务器应用程序模型)如 AWS 文档所示。AWS Cloud9 已经安装了它。您可以在 GitHub 上找到配方。AWS Lambda 是重要的,因为它与 AWS 的深度集成。让我们探讨如何使用现代最佳实践部署无服务器 ML 模型。
一种有效且推荐的将软件部署到生产环境的方法是通过SAM。这种方法的创新结合了 Lambda 函数、事件源和其他资源作为部署过程和开发工具包。
特别是,根据 AWS 的说法,SAM 的关键优势包括单一部署配置、AWS CloudFormation 的扩展、内置最佳实践、本地调试和测试,以及与开发工具(包括我喜欢的 Cloud9)的深度集成。
要开始,首先应该安装 AWS SAM CLI。之后,可以参考官方指南获取最佳结果。
AWS Lambda-SAM Local
要开始使用 SAM Local,您可以尝试以下新项目的工作流程:
-
安装 SAM(如前所示)
-
sam init -
sam local invoke
注意
如果在 Cloud9 上构建,使用utils/resize.sh调整大小可能是个好主意:
utils/resize.sh 30
这个技巧可以为您提供更大的磁盘大小,以构建多个容器,使用 SAM local 或任何其他 AWS 容器工作流程。
这是一个典型的 SAM 初始化布局,对于 ML 项目来说稍有不同:
├── sam-app/
│ ├── README.md
│ ├── app.py
│ ├── requirements.txt
│ ├── template.yaml
│ └── tests
│ └── unit
│ ├── __init__.py
│ └── test_handler.py
有了这些基础知识,让我们继续深入了解如何使用 AWS Lambda 和 SAM。
AWS Lambda-SAM 容器化部署
现在让我们深入了解 SAM 的容器化工作流程,因为它支持注册 AWS Lambda 使用的容器。您可以在以下存储库中查看 容器化 SAM-Lambda 部署项目。首先,让我们介绍关键组件。部署到 SAM 的关键步骤包括以下文件:
-
App.py(AWS Lambda 入口点)
-
Dockerfile(用于构建并发送到 Amazon ECR 的内容)
-
Template.yaml(SAM 用于部署应用程序的模板)
Lambda 处理程序几乎没有做什么,因为所有的工作仍然在 mlib.py 库中进行。在使用 AWS Lambda 时需要注意的一个“陷阱”是,根据调用方式,Lambda 函数需要使用不同的逻辑处理。例如,如果 Lambda 通过控制台或 Python 调用,则没有 Web 请求体,但在与 API 网关集成的情况下,需要从事件的 body 中提取负载:
import json
import mlib
def lambda_handler(event, context):
"""Sample pure Lambda function"""
#Toggle Between Lambda function calls and API Gateway Requests
print(f"RAW LAMBDA EVENT BODY: {event}")
if 'body' in event:
event = json.loads(event["body"])
print("API Gateway Request event")
else:
print("Function Request")
#If the payload is correct predict it
if event and "Weight" in event:
weight = event["Weight"]
prediction = mlib.predict(weight)
print(f"Prediction: {prediction}")
return {
"statusCode": 200,
"body": json.dumps(prediction),
}
else:
payload = {"Message": "Incorrect or Empty Payload"}
return {
"statusCode": 200,
"body": json.dumps(payload),
}
项目包含一个 Dockerfile,用于构建位于 ECR 位置的 Lambda。注意,在较小的 ML 模型的情况下,它可以打包进 Docker 容器中,甚至可以通过 AWS CodeBuild 进行程序化的重新训练和打包:
FROM public.ecr.aws/lambda/python:3.8
COPY model.joblib mlib.py htwtmlb.csv app.py requirements.txt ./
RUN python3.8 -m pip install -r requirements.txt -t .
# Command can be overwritten by providing a different command
CMD ["app.lambda_handler"]
SAM 模板控制 IaC(基础设施即代码)层,使部署过程变得简单:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.8
Sample SAM Template for sam-ml-predict
Globals:
Function:
Timeout: 3
Resources:
HelloWorldMLFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
Events:
HelloWorld:
Type: Api
Properties:
Path: /predict
Method: post
Metadata:
Dockerfile: Dockerfile
DockerContext: ./ml_hello_world
DockerTag: python3.8-v1
Outputs:
HelloWorldMLApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World ML
function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.\
amazonaws.com/Prod/predict/"
HelloWorldMLFunction:
Description: "Hello World ML Predict Lambda Function ARN"
Value: !GetAtt HelloWorldMLFunction.Arn
HelloWorldMLFunctionIamRole:
Description: "Implicit IAM Role created for Hello World ML function"
Value: !GetAtt HelloWorldMLFunctionRole.Arn
剩下要做的步骤就是运行两个命令:sam build 和 sam deploy --guided,这允许您通过部署过程。例如,在 图 7-25 中,sam build 通知构建容器,然后提示您要么通过 sam local invoke 进行本地测试,要么执行引导部署。

图 7-25. SAM 构建
您可以使用以下命令 sam local invoke -e payload.json 调用本地测试:
{
"Weight": 200
}
注意,容器启动并发送负载返回。在将应用程序部署到 ECR 之前,这个测试过程非常有价值:
(.venv) ec2-user:~/environment/Python-MLOps-Cookbook/recipes/aws-lambda-sam/
sam-ml-predict
Building image.................
Skip pulling image and use local one: helloworldmlfunction:rapid-1.20.0.
START RequestId: d104cf8a-ce6b-4f50-9f2b-c82e99b8016f Version: $LATEST
RAW LAMBDA EVENT BODY: {'Weight': 200}
Function Request
Prediction: {'height_inches': 73.61, 'height_human_readable': '6 foot, 2 inches'}
END RequestId: d104cf8a-ce6b-4f50-9f2b-c82e99b8016f
REPORT RequestId: d104cf8a-ce6b-4f50-9f2b-c82e99b8016f Init
Duration: 0.08 ms Duration: 2187.82
{"statusCode": 200, "body": "{\"height_inches\": 73.61,\
\"height_human_readable\": \"6 foot, 2 inches\"}"}
在完成测试后,您可以进行引导式部署,如 图 7-26 所示。特别注意,如果选择此选项,提示将引导每个部署步骤。

图 7-26. SAM 引导部署
一旦部署了 Lambda,有多种方式可以使用和测试它。其中一种最简单的方式是通过 AWS Lambda 控制台验证图像(参见 图 7-27)。

图 7-27. 在控制台中测试 AWS Lambda
测试实际 API 的两种方法包括 AWS Cloud9 控制台和 Postman 工具。图示分别展示了 图 7-29 和 图 7-30 的示例。

图 7-28. 调用 Lambda Cloud9

图 7-29. 使用 Postman 测试 AWS Lambda
其他部署目标,考虑部署这个基础机器学习配方的包括 Flask Elastic Beanstalk 和 AWS Fargate。对这个配方的不同变体包括其他 HTTP 服务,如fastapi,或者使用通过 AWS Boto3 API 可用的预训练模型而不是训练您自己的模型。
AWS Lambda 是构建集成数据工程和机器学习工程的分布式系统中最激动人心的技术之一。接下来让我们谈一个真实案例研究。
将 AWS 机器学习应用于现实世界
让我们深入探讨如何在现实世界中使用 AWS 机器学习资源的示例。在本节中,几个 MLOps 负责实际公司进行机器学习的部分。
注意
有很多方法可以在 MLOps 中使用 AWS,以及几乎无数种服务的实际组合。以下是 AWS 上推荐的部分机器学习工程模式列表:
容器即服务(CaaS)
对于那些难以推动某些事情的组织来说,CaaS 是开始 MLOps 旅程的一个好地方。推荐的服务是 AWS App Runner。
SageMaker
对于拥有不同团队和大数据的大型组织,SageMaker 是一个出色的平台,因为它允许进行细粒度安全和企业级部署和训练。
使用 AI API 的无服务器方案
对于需要迅速推进的小型初创公司,一个极好的初始方法是使用预训练模型通过 API 以及像 AWS Lambda 这样的无服务器技术进行 MLOps。
结论
本章涵盖了 AWS 的熟悉领域和独特角落。一个关键的经验教训是 AWS 是最大的云平台。使用 AWS 技术解决问题的方法有很多,从像案例研究讨论的那样构建一家全面从事机器学习的公司,到使用高级计算机视觉 API。
特别是对于业务和技术领导者,我建议采用以下最佳实践,尽快启动 MLOps 能力:
-
与 AWS 企业支持互动。
-
让您的团队从 AWS 解决方案架构师或云从业者考试以及 AWS 认证机器学习专业人士开始认证。
-
通过使用像 AWS Comprehend、AWS Rekognition 这样的 AI API 以及像 AWS App Runner 或 AWS Lambda 这样的高级 PaaS 产品,获得快速成功。
-
专注于自动化,并确保您能自动化的所有内容,从数据摄入和特征存储到建模和 ML 模型的部署。
-
开始将 SageMaker 作为 MLOps 的长期投资,并将其用于更长期或更复杂的项目以及更易访问的解决方案。
最后,如果你想在个人层面认真使用 AWS,并希望作为 AWS 机器学习专家开始职业生涯,获得认证可能非常有益和有利可图。附录 B 为你提供了如何准备 AWS 认证考试的快速入门指南。最后推荐的任务是通过一些练习和批判性思维问题进一步练习 AWS 技能。
我们的下一章不再涉及 AWS,而是深入研究 Azure。
练习
-
使用 Elastic Beanstalk 为 Flask Web 服务构建一个机器学习持续交付管道。你可以参考这个GitHub 仓库作为起点。
-
启动一个 Amazon SageMaker 实例,并构建和部署美国人口普查数据用于人口分割示例。
-
使用 AWS Fargate 构建一个 CaaS 机器学习预测服务。你可以使用这个GitHub 仓库作为起点。
-
使用这个GitHub 仓库作为起点,构建一个无服务器数据工程原型。
-
使用这个GitHub 仓库作为起点,构建一个检测标签的计算机视觉触发器。
-
使用 MLOps Cookbook 基础项目,并部署到尽可能多的不同目标:容器化 CLI、EKS、Elastic Beanstalk、Spot 实例,以及你能想到的其他任何目标。
批判性思维讨论问题
-
为什么从事机器学习的组织会使用数据湖?它们解决了什么核心问题?
-
使用预构建模型如 AWS Comprehend 与训练自己的情感分析模型相比,有什么使用案例?
-
为什么组织会选择 AWS SageMaker 而不是 Pandas、sklearn、Flask 和 Elastic Beanstalk?它们各自的使用案例是什么?
-
什么是容器化机器学习模型部署流程的优势?
-
一位同事说他们对如何开始 AWS 上的机器学习感到困惑,因为提供了多种服务。你会如何推荐他们开始搜索?
第八章:MLOps for Azure
阿尔弗雷多·德萨
我们一家搬迁的第三个原因是,自从 1933 年大萧条期间我们被剥夺家园以来,我们从未有过一个合适的家。直到多年以后,我才明白我 6 岁时所喜爱的乡村天堂是如何消失的。我的父母无法偿还贷款。我的母亲放弃了她苦苦挣扎的诊所工作,找到了一份作为接待医生的工作,提供了一些钱和一个对我们所有人来说都太拥挤的公寓,所以我和我哥哥被送进了后来我们称之为“流亡”的地方。
约瑟夫·博根博士
Microsoft 在 Azure 中为机器学习持续投资正在取得成效。今天提供的功能数量使整个平台成为一个很好的选择。几年前,甚至还不清楚 Azure 是否会获得如此多高级工程和对其服务日益增长的兴趣。
如果你还没有尝试过 Azure,或者在 Microsoft 的云服务中还没有看到与机器学习相关的内容,我强烈建议你给它一个机会。像大多数云服务提供商一样,提供了试用期,具有足够的信用额度,可以自行尝试并作出判断。
我通常提到的一个例子是使用 Kubernetes。安装、配置和部署 Kubernetes 集群根本不是一件简单的任务。如果考虑将机器学习模型与潜在消费者的互动进行关联和扩展,情况会更加复杂。这是一个需要正确解决的具有挑战性的问题。如果你有机会查看一个训练模型的部署设置,将 Kubernetes 集群作为模型的目标,最终可以通过从下拉菜单中选择集群来完成。
除了像将模型部署到生产环境这样的复杂问题的所有功能和抽象之外,看到大量详细的文档是非常令人耳目一新的。尽管这一章集中讨论在 Azure 中进行机器学习操作,但它不可能涵盖所有令人兴奋的细节。主要文档资源是一个很好的地方,可以加为书签,深入了解这里未涵盖的更多信息。
在本章中,我介绍了 Azure 中一些有趣的选项,从训练模型到将其部署在容器或 Kubernetes 集群中。随着它们在机器学习服务中变得越来越普遍,我将深入研究管道。管道可以进一步增强自动化,即使自动化起源于 Azure 云之外。
在平台内执行机器学习任务有许多方法:Azure ML Studio 设计器、Jupyter Notebooks 和 AutoML,您可以上传 CSV 并立即开始训练模型。最后,大多数(如果不是全部)功能在 SDK 中都有相应的支持(在下一节中讨论)。这种灵活性非常重要,因为它允许您选择最适合您的解决方案。因此,没有必要遵循一种偏见的方式来使模型运作。
最后,我将涵盖一些实用建议,如使用 Azure 机器学习的核心原则,例如监控和日志记录。
注意
尽管本章讨论的是 Azure 机器学习,但不涉及创建和设置新帐户等基础知识。如果您尚未尝试过该服务,可以从这里开始。
Azure CLI 和 Python SDK
本章中的各种示例和代码片段假定您已经安装并在环境中可用 Azure 命令行工具和 Python SDK(软件开发工具包)。请确保安装 CLI 的最新版本,并在安装后确保机器学习扩展可用:
$ az extension add -n azure-cli-ml
大多数情况下,您需要从本地系统返回到 Azure 进行身份验证。此工作流程类似于其他云提供商。要将 Azure 帐户与当前环境关联起来,请运行以下命令:
$ az login
使用 Azure 的 Python SDK 的大多数 Python 示例都需要一个 Azure 帐户以及本地下载的与您的工作区关联的 config.json 文件。此文件包含将 Python 代码与工作区关联所需的所有信息。所有与 Python SDK 的交互应使用此文件配置运行时:
import azureml.core
from azureml.core import Workspace
ws = Workspace.from_config()
要检索 config.json 文件,请登录 Azure 的 ML Studio 并点击右上角的菜单(标记为更改订阅)。子菜单中将包含一个链接以下载 config.json 文件(参见图 8-1)。

图 8-1. Azure JSON 配置
如果当前工作目录中不存在 config.json 文件,则使用 SDK 将导致回溯:
In [2]: ws = Workspace.from_config()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
UserErrorException Traceback (most recent call last)
<ipython-input-2-e469111f639c> in <module>
----> 1 ws = Workspace.from_config()
~/.python3.8/site-packages/azureml/core/workspace.py in from_config
269
270 if not found_path:
--> 271 raise UserErrorException(
272 'We could not find config.json in: {} or in its parent directories.'
273 'Please provide the full path to the config file or ensure that '
现在我已经介绍了使用 Azure CLI 和 SDK 的一些基础知识,接下来将深入探讨更多身份验证细节以及您在 Azure 中可以使用的一些变体。
身份验证
在处理服务时,身份验证应该是自动化的核心部分之一。Azure 具有用于资源访问(和访问控制)的服务主体。在自动化服务和工作流程时,人们往往忽视甚至尝试简化一般的身份验证。很常见听到建议像:“只需使用 root 用户”或“只需更改文件权限以便任何人都能写入和执行”。经验丰富的工程师会最了解,但这是因为他们在接受这些松散的安全性和部署约束建议后经历了痛苦。我非常理解试图解决类似这种情况的问题。
在媒体机构担任系统管理员时,工程主管直接登录生产环境以使文件对任何人可读(PHP 应用程序所需)。最终我们发现了这一点。更改的结果意味着任何 HTTP 请求(以及任何互联网上的任何人)都可以读取、写入和执行该文件。有时,简单的修复可能很诱人,感觉是前进的最佳方式,但并非总是如此。特别是在安全性(以及身份验证在这种情况下)方面,您必须对移除安全约束的简单修复方法持怀疑态度。
始终确保正确进行身份验证,不要试图跳过或绕过这些限制,即使这是一种选择。
服务主体
在 Azure 中,创建服务主体涉及多个步骤。根据您需要的帐户和资源访问约束,这将因示例而异。适应以下内容以适应特定场景。首先,在使用 CLI 登录后,运行以下命令:
$ az ad sp create-for-rbac --sdk-auth --name ml-auth
该命令使用ml-auth名称创建服务主体。您可以选择任何名称,但最好有一些约定来记住这些名称所关联的内容。接下来,注意输出并检查"clientId"值,后续步骤将需要它:
[...]
Changing "ml-auth" to a valid URI of "http://ml-auth", which is the required
format used for service principal names
Creating a role assignment under the scope of:
"/subscriptions/xxxxxxxx-2cb7-4cc5-90b4-xxxxxxxx24c6"
Retrying role assignment creation: 1/36
Retrying role assignment creation: 2/36
{
[...]
"clientId": "xxxxxxxx-3af0-4065-8e14-xxxxxxxxxxxx",
[...]
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
现在使用"clientId"从新创建的服务主体中检索元数据:
$ az ad sp show --id xxxxxxxx-3af0-4065-8e14-xxxxxxxxxxxx
{
"accountEnabled": "True",
"appDisplayName": "ml-auth",
...
...
...
"objectId": "4386304e-3af1-4066-8e14-475091b01502",
"objectType": "ServicePrincipal"
}
要允许服务主体访问您的 Azure 机器学习工作区,您需要将其与工作区和资源组关联:
$ az ml workspace share -w example-workspace \
-g alfredodeza_rg_linux_centralus \
--user 4386304e-3af1-4066-8e14-475091b01502 --role owner
该命令是完成创建服务主体并将其与使用owner角色的机器学习帐户关联的最后一个命令。example-workspace是我在 Azure 中使用的工作区的名称,alfredodeza_rg_linux_centralus是该工作区中的资源组。非常不幸的是,在成功调用后运行该命令不会有任何输出。
一旦创建此帐户,您可以使用它启用具有身份验证的自动化,从而避免提示和持续验证。请确保将访问权限和角色限制为所需权限的最少数量。
注意
这些示例使用了服务主体的角色值 "owner",其权限广泛。--role 标志的默认值是 "contributor,",权限较为受限。根据你的环境(和使用情况),调整此值以适应更合适的设置。
API 服务的认证
根据手头的工作流程,其他环境可能不会从服务主体账户中受益。这些服务可以通过 HTTP 请求与部署的模型进行交互并向其提供服务。在这些情况下,你需要决定启用何种类型的认证。
在部署模型或甚至配置任何生产模型所需的设置之前,你需要对可用的不同安全功能有一个清晰的了解。
Azure 提供了与你需要交互的服务相关的不同认证方式。这些服务的默认设置也会根据部署类型而改变。基本上支持两种类型:密钥和令牌。Azure Kubernetes 服务(AKS)和 Azure 容器实例(ACI)对这些认证类型的支持有所不同。
基于密钥的认证:
-
AKS 默认启用基于密钥的认证。
-
ACI 默认禁用基于密钥的认证(但可以启用)。默认情况下未启用任何认证。
基于令牌的认证:
-
AKS 默认禁用基于令牌的认证。
-
ACI 不支持基于令牌的认证。
在将模型部署到生产环境之前,了解这些部署类型对于测试环境来说非常重要。即使是测试环境,也始终要启用认证以防止开发和生产之间的不匹配。
计算实例
Azure 对计算实例有一个定义,将其描述为科学家的 托管云工作站。基本上,它允许你快速启动所有在云中进行机器学习操作所需的内容。在开发概念验证或从教程尝试新事物时,你可以利用对 Jupyter 笔记本 的出色支持,预先安装并准备好大量依赖项。虽然你可以上传自己的笔记本,但我建议首先浏览现有的详尽示例,如 图 8-2 所示。

图 8-2. Azure 笔记本示例
当你试图启动笔记本并尝试调整模型时,最让人头疼的问题之一是设置环境。通过提供一些现成且专门为机器学习预配置的东西,你可以快速完成任务,从笔记本中的想法转向生产。
一旦你准备好从计算实例部署训练好的模型,你可以将计算实例用作训练集群,因为它支持作业队列、并行多个作业和多 GPU 分布式训练。这是调试和测试的理想组合,因为环境是可复制的。
你以前听过“但在我的机器上可以运行!”这样的说法吗?我肯定听过!即使是在测试或探索新想法时,可重现的环境也是规范开发的一种重要方式,可以减少意外情况,使其他工程师之间的协作更加顺畅。使用可重现的环境是 DevOps 中不可或缺的一部分,也适用于 ML。一致性和规范化需要大量的努力来正确实施,每当你发现有立即可用的工具或服务时,应立即利用它们。
作为系统管理员,我在规范化开发的生产环境方面投入了大量精力,这是一个难题。尽可能使用 Azure 计算实例!
你可以通过 Azure ML Studio 中的各种工作流之一创建计算实例,例如创建 Jupyter Notebook 时。在“管理”部分中更容易找到“计算”链接并在那里创建一个计算实例。加载后,会呈现多个选择(参见图 8-3)。一个经验法则是选择一个低成本的虚拟机来开始使用,这样可以避免因为可能不需要仅仅为了运行笔记本而产生的更高成本。

图 8-3. Azure 创建计算实例
在本例中,我选择了一个Standard_D2_v3。由于提供的各种机器名称经常变化,你得到的选择可能会有所不同。
部署
在 Azure 中有几种与模型交互的部署方式。如果你处理的数据量非常大,超出内存处理的合理范围,批量推断是更合适的选择。Azure 确实提供了大量有用的工具(此服务的普遍可用性在 2020 年宣布),帮助用户处理结构化和非结构化的 TB 级数据,并从中获取推断结果。
另一种部署方式是在线推断(有时称为即时推断)。当处理较小的数据集(而不是几 TB 的数据!)时,快速部署模型并通过 Azure 为你编程创建的 HTTP API 进行访问是非常有用的。为训练模型自动创建的 HTTP API 是你应该利用的另一项功能。
手工创建 HTTP API 并不麻烦,但是将这项工作外包给一个服务意味着你(以及你的团队)有更多时间处理数据质量或部署流程的稳健性等更重要的部分。
注册模型
Azure 文档将注册描述为可选项。的确,在部署模型时你并不需要它。但是随着你习惯于部署模型并将其发布到生产环境中,你会意识到省略诸如认证(或者在这种情况下是模型注册)等功能和约束会使事情起初更加容易,但后续可能会引发问题。
我强烈建议您注册您的模型并遵循相应的流程,如果这个过程是完全自动化的话会更好。当然,您可能不需要为每一个您使用的模型都进行注册,但是对于进入生产环境的选择模型,您绝对应该这样做。如果您熟悉Git(版本控制系统),那么对模型进行版本控制会感觉像是一种自然的方式来理解生产级模型的变更和修改。
模型版本控制的几个关键方面使其成为一个引人注目的功能:
-
您可以识别您正在使用的模型版本
-
您可以快速从各种版本中进行选择,并从描述中清晰了解。
-
您可以轻松进行回滚并选择不同的模型
有几种注册模型的方法。如果您在 Azure 内训练模型,那么您可以使用Python SDK与Run类的结果对象:
description = "AutoML trained model"
model = run.register_model(description=description)
# A model ID is now accessible
print(run.model_id)
但是您并非必须使用 Azure 来训练模型。也许您已经训练了几个模型,并且正在考虑迁移到 Azure 以将它们投入生产。Python SDK 还允许您注册这些模型。以下是如何使用在您系统中本地可用的ONNX模型的示例:
import os
from azureml.core.model import Model
# assumes `models/` is a relative directory that contains uncompressed
# ONNX models
model = Model.register(
workspace=ws,
model_path ="models/world_wines.onnx",
model_name = "world_wines",
tags = {"onnx": "world-wines"},
description = "Image classification of world-wide wine labels"
)
Azure 令人振奋的一点是其灵活性。Python SDK 并非注册模型的唯一方式。以下是使用 Azure CLI 的方法:
$ az ml model register --name world_wines --model-path mnist/model.onnx
您可以通过对前面示例进行一些修改,快速迭代并将多个模型注册到 Azure。如果您的模型通过 HTTP 可用,您可以通过编程方式下载它们并发送过去。使用额外的元数据填写标签和描述;良好的描述使得以后更容易识别。过程自动化程度越高,效果越好!在图 8-4 中,我使用 Azure ML Studio 直接上传和注册 ONNX 模型,这对处理单个模型也非常有用。

图 8-4. 在 Azure 中注册模型
数据集版本控制
与注册模型类似,版本化数据集的能力解决了当今 ML 中最大的问题之一:稍有不同的巨大数据集很难(甚至直到最近都是不可能的)进行良好的版本控制。像 Git 这样的版本控制系统不适合这项任务,尽管版本控制系统应该帮助解决问题。版本控制系统专注于源代码变更,而在处理巨大数据集时存在不匹配问题,这一直是生产可靠和可重现的模型的一个障碍。
云提供商如 Azure 通过功能(例如数据集版本控制)改进工作流程的另一个示例。数据是构建 ML 流水线时最重要的组成部分之一,因为数据可能经历多轮转换和清理,从原始数据到干净数据的每一步都至关重要。
首先检索数据集。在这个例子中,数据集是通过 HTTP 托管的:
from azureml.core import Dataset
csv_url = ("https://automlsamplenotebookdata.blob.core.windows.net"
"/automl-sample-notebook-data/bankmarketing_train.csv")
dataset = Dataset.Tabular.from_delimited_files(path=csv_url)
接下来,使用 dataset 对象进行注册:
dataset = dataset.register(
workspace=workspace,
name="bankmarketing_dataset",
description="Bankmarketing training data",
create_new_version=True)
create_new_version 将增量设置一个较新的数据版本,即使没有之前的版本(版本从 1 开始)。注册并创建数据集的新版本后,按名称和版本检索它:
from azureml.core import Dataset
# Get a dataset by name and version number
bankmarketing_dataset = Dataset.get_by_name(
workspace=workspace,
name="bankmarketing_dataset",
version=1)
注意
尽管看起来似乎是这样,但创建新的数据集版本 并不意味着 Azure 在工作区中复制整个数据集。数据集使用存储服务中数据的引用。
将模型部署到计算集群
在本节中,您将配置并部署模型到一个计算集群。虽然涉及几个步骤,但反复执行这个过程几次会很有用。
转到 Azure ML Studio 并通过在自动 ML 部分点击“新自动化 ML 运行”或者直接从主页点击“创建新”框并选择下拉菜单中的选项来创建一个新的自动化 ML 运行。在此过程的这一部分中,您将需要一个可用的数据集。如果您尚未注册数据集,可以下载一个,然后选择“从本地文件”注册它。按照步骤上传并在 Azure 中使其可用。
配置集群
回到“自动化 ML 运行”部分,选择可用的数据集来配置一个新的运行。配置的一部分需要一个有意义的名称和描述。到此时为止,您已经配置并使数据集可用,并确定了它应该如何使用和存储。但是,强大部署策略的关键组成部分之一是确保集群足够稳固以训练模型(参见 图 8-5)。

图 8-5. 配置自动 ML 运行
此时,帐户中不应该有任何集群可用。从表单底部选择“创建新的计算”。可以在配置运行以训练模型时创建新的集群,或者直接在“管理”部分的“计算”链接下进行。最终目标是创建一个强大的集群来训练您的模型。
值得强调的是,在 Azure ML 中的许多功能和产品中尽可能使用有意义的名称和描述。添加这些信息片段对于捕获(以及后来识别)底层特征非常关键。解决这个问题的一个有用方法是将这些视为字段,就像写电子邮件时一样:电子邮件主题应该捕捉有关主体将包含什么的一般想法。当您处理数百个模型(或更多)时,描述非常强大。在命名约定和描述中组织和清晰地表达非常重要。
有两种重要的集群类型:推断集群和计算集群。在了解底层系统之前,易于感到困惑:推断集群在幕后使用 Kubernetes 并使用虚拟机的计算集群。在 Azure ML Studio 中创建两者都是直接的。您所需要做的就是填写一个表单,然后就可以运行一个集群,准备训练一些模型。创建完成后,无论类型如何,它们都可用作训练模型的选项。
尽管 Kubernetes 集群(推断)可以用于测试目的,我倾向于使用计算集群来尝试不同的策略。无论选择的后端如何,将工作量匹配到执行工作的机器的大小(和数量)是至关重要的。例如,在计算集群中,您必须确定节点的最小数量以及节点的最大数量。在并行化模型的训练时,平行运行的数量不能超过节点的最大数量。正确确定节点数量、RAM 大小和足够数量的 CPU 核心更多地是一个反复试验的工作流程。对于测试运行,最好从少量开始,并根据需要创建更强大的集群(参见 图 8-6)。

图 8-6. 创建计算集群
在这个图中,我选择将节点的最小数量设为0,因为这样可以避免因空闲节点而产生费用。这将允许系统在集群闲置时缩减到0。我选择的机器类型性能并不是很好,但由于我正在测试运行,这并不是很重要;我随时可以回去创建一个更新的集群。
注意
用于训练模型的集群并非用于部署实时(或批量)推断的集群。这可能会让人感到困惑,因为训练和推断两种策略都使用“集群”术语来指代执行工作的节点组。
部署模型
与训练模型非常类似,当部署模型到生产环境时,您必须了解可用的集群选择。虽然有几种方法可以完成此操作,但部署的两种关键方法是必须的。根据部署使用情况(生产或测试),您必须选择最合适的方法。以下是关于这些资源及其最佳工作方式的一个很好的思路:
Azure 容器实例(ACI)
最适合测试和一般测试环境,特别是模型较小(大小不超过 1 GB)的情况。
Azure Kubernetes 服务(AKS)
所有 Kubernetes 的优点(特别是扩展性),适用于大于 1 GB 大小的模型。
这两种选择对于部署而言都相对直接。在 Azure ML Studio 中,转到资产部分内的模型,并选择先前训练过的模型。在 图 8-7 中,我选择了一个已注册的 ONNX 模型。
在表单中有几个关键部分;我选择了ACS作为计算类型,并启用了身份验证。这一点非常重要,因为一旦部署完成,如果不使用密钥进行身份验证的 HTTP 请求,将无法与容器进行交互。虽然启用身份验证不是必需的,但强烈建议以保持生产和测试环境之间的一致性。
在完成表单并提交后,模型部署的过程开始。在我的示例中,我启用了身份验证并使用了 ACS。这些选项并不会对与模型的交互产生太大影响,因此一旦部署完成,我就可以通过 HTTP 与模型进行交互,确保请求使用了密钥。

图 8-7. 部署模型
在 Endpoints 部分可以找到部署模型的所有详细信息。列出了用于部署的名称,该名称链接到部署详细信息的仪表板。该仪表板包含三个选项卡:详细信息、消耗和部署日志。所有这些都充满了有用的信息。如果部署成功完成,那么日志可能不会那么有趣。在详细信息选项卡中,一个部分将显示 HTTP API(显示为“REST 端点”)。由于我启用了身份验证,页面将显示“基于密钥的身份验证”的值为true,如图 8-8 所示。

图 8-8. REST 端点
任何可以与启用身份验证的 HTTP 服务进行通信的东西都可以使用。这是一个使用 Python 的示例(不需要 Azure SDK);输入是使用JSON(JavaScript 对象表示),而针对此特定模型的输入遵循一个严格的模式,这将取决于您要交互的模型。这个示例使用了Requests Python 库:
import requests
import json
# URL for the web service
scoring_uri = 'http://676fac5d-5232-adc2-3032c3.eastus.azurecontainer.io/score'
# If the service is authenticated, set the key or token
key = 'q8szMDoNlxCpiGI8tnqax1yDiy'
# Sample data to score, strictly tied to the input of the trained model
data = {"data":
[
{
"age": 47,
"campaign": 3,
"contact": "home",
"day_of_week": "fri",
"default": "yes",
"duration": 95,
"education": "high.school",
"nr.employed": 4.967,
"poutcome": "failure",
"previous": 1
}
]
}
# Convert to JSON
input_data = json.dumps(data)
# Set the content type
headers = {'Content-Type': 'application/json'}
# Authentication is enabled, so set the authorization header
headers['Authorization'] = f'Bearer {key}'
# Make the request and display the response
resp = requests.post(scoring_uri, input_data, headers=headers)
print(resp.json())
因为服务使用 HTTP 公开,这使得我可以以任何我选择的方式与 API 进行交互(就像在前面的 Python 示例中使用 Python 一样)。通过与模型交互的 HTTP API 是非常吸引人的,因为它提供了极大的灵活性,并且由于它是实时推理服务,所以可以立即得到结果。在我的情况下,创建部署并从 API 获取响应并不需要太长时间。这意味着我正在利用云基础设施和服务快速尝试一个可能最终进入生产的概念验证。使用样本数据尝试模型并在类似生产环境的环境中与其交互,是为后续稳健自动化铺平道路的关键。
一切顺利时,将返回一个 JSON 响应,其中包含来自预测的有用数据。但是当事情出错时会发生什么呢?了解可能出现问题的不同方式及其含义非常有用。在这个例子中,我使用了 ONNX 模型意外的输入:
{
"error_code": 500,
"error_message": "ONNX Runtime Status Code: 6\. Non-zero status code returned
while running Conv node. Name:'mobilenetv20_features_conv0_fwd'
Missing Input: data\nStacktrace:\n"
}
HTTP 状态码 500 表示服务由于无效输入而发生错误。确保使用正确的密钥和身份验证方法。大多数来自 Azure 的错误都很容易理解和修复;这里列举了几个示例:
请求中缺少或未知的 *Content-Type* 头字段
确保请求中使用和声明了正确的内容类型(例如 JSON)。
Method Not Allowed. 对于 HTTP 方法:GET 和请求路径:/score
当您尝试进行 GET 请求时,可能需要发送数据的 POST 请求。
Authorization 头部格式错误。头部应该是这种形式:“Authorization: Bearer
确保头部正确构造并包含有效的令牌。
故障排除部署问题
有效的 MLOps 的众多关键因素之一(当然继承自 DevOps 最佳实践,详见“DevOps 和 MLOps”)是良好的故障排除技能。调试或故障排除并非天生的技能,需要练习和坚持。我解释这种技能的一种方式是将其比作在新城市中四处走动并找到自己的路。有些人会说,如果我能轻松找到自己的方向,那么我一定具备某种天赋。但实际上并非如此。注意细节,定期复习这些心理细节,并质疑一切。不要假设。
在找到自己的方向时,我立即确定太阳的位置或者大海(这是东边?还是西边?),并在心里记下地标或显著建筑物。然后,我从头到尾地回顾每一步:我在酒店左转,直到大教堂右转,穿过美丽的公园,现在我在广场上。夜幕降临,那时太阳不再存在,我记不清该去哪里了。是在这里左转?还是右转?我询问朋友,他们都告诉我左转,但我不敢肯定他们是对的。信任,但要验证。“如果他们告诉我左转,那意味着我不会到达公园,我会走他们的路,走几个街区,如果没有公园,我会掉头走另一条路。”
绝不要假设事情。质疑一切。注意细节。信任,但要验证。如果您遵循这些建议,您的调试技能将在同行中显得自然。在本节中,我将深入探讨 Azure 中容器及容器化部署的若干细节,并介绍一些可能出现问题时会遇到的情况。但是,这些核心概念可以应用到任何地方。
检索日志
部署容器后,您有几种方法可以检索日志。这些方法在 Azure ML Studio 中以及命令行和 Python SDK 中都是可用的。使用 Python SDK 仅需几行代码初始化工作区后:
from azureml.core import Workspace
from azureml.core.webservice import Webservice
# requires `config.json` in the current directory
ws = Workspace.from_config()
service = Webservice(ws, "mobelinetv-deploy")
logs = service.get_logs()
for line in logs.split('\n'):
print(line)
运行该示例代码,使用先前部署的服务名称,会生成大量信息。有时日志并不那么有用,大部分都是重复的。您必须摆脱噪声并捕捉有用信息的亮点。在成功部署中,大多数输出并不会有太多意义。
这些是部署 ONNX 模型时的一些日志(为简洁起见删除了时间戳):
WARNING - Warning: Falling back to use azure cli login credentials.
Version: local_build
Commit ID: default
[info] Model path: /var/azureml-models/mobilenetv/1/mobilenetv2-7.onnx
[info][onnxruntime inference_session.cc:545 Initialize]: Initializing session.
[onnxruntime inference_session Initialize]: Session successfully initialized.
GRPC Listening at: 0.0.0.0:50051
Listening at: http://0.0.0.0:8001
应用洞察
另一个检索日志和调试的方面是有价值的是使用可观察性工具。可观察性指的是在任何给定时间点捕获系统(或系统)的状态的手段。听起来有些复杂,但简而言之,这意味着您依赖像仪表板、日志聚合、图形和警报机制这样的工具来整体可视化系统。因为可观察性的整体主题充满了工具和流程,通常很难将这样的东西投入到生产中。
可观察性至关重要,因为它不仅仅关乎应用程序日志;它是在问题出现时讲述应用程序故事的关键。当然,在日志中找到了 Python 的回溯,但这并不一定意味着 Python 代码需要修复。如果预期的输入是来自另一个系统的 JSON 负载,但外部系统却发送了一个空文件,怎么会发生这种情况?可观察性为看似混乱的系统带来了清晰度。在处理分布式系统时,问题变得非常复杂。
为将数据输入到涉及多个不同步骤的模型的管道系统并不少见。获取数据源,清理数据,删除垃圾列,归一化数据,并对这些新数据集进行版本控制。假设所有这些都是使用 Python SDK 进行,并利用 Azure 的触发器。例如,新数据进入存储系统,触发一个Azure Function执行一些 Python 代码。在这一系列事件中,如果没有工具支持,讲述一个故事将变得困难。
Azure 在 SDK 中提供了最简单的调用即可提供所需的工具支持。它被称为应用洞察,并在启用后立即提供所有有用的图形和仪表板。然而,它不仅仅是日志或漂亮的图形。一个高度可视化界面下提供了一整套关键数据。响应时间、失败率和异常情况——所有这些都会被聚合、时间戳记录和绘制。
这是如何在先前部署的服务中启用应用洞察的:
from azureml.core.webservice import Webservice
# requires `ws` previously created with `config.json`
service = Webservice(ws, "mobelinetv-deploy")
service.update(enable_app_insights=True)
当启用应用洞察时,ML Studio 中的 API 端点部分将显示如图 8-9 所示。

图 8-9. 已启用应用洞察
点击提供的链接进入服务仪表板。提供了大量各种图表和信息来源。此图像捕捉了部署模型在容器中接收的请求的一小部分图表,如图 8-10 所示。

图 8-10. 应用洞察
本地调试
在调试时遵循 DevOps 原则,您必须质疑一切,绝不能假设任何事情。一种有用于调试问题的技术是在不同环境中运行生产服务,或者在本例中,是在不同环境中运行训练好的模型。容器化部署及容器一般都提供了这种灵活性,可以在本地运行在 Azure 生产环境中运行的东西。在本地运行容器时,可以做很多事情,除了查看日志外。本地调试(可能在您的计算机上)是一种非常有价值的资产,可以利用,同时避免服务中断或灾难性服务中断。我曾经遇到过几种情况,唯一的选择是“登录到生产 Web 服务器查看发生了什么”。这是危险且极其棘手的。
对问题和困难持怀疑态度,尝试复制问题至关重要。复制问题是解决问题的关键。有时,为了复制客户面向的产品,我会从头开始重新安装操作系统,并在不同的环境中进行部署。开发人员开玩笑地使用“它在我的机器上运行”的说法,我敢打赌,几乎总是正确的——但实际上,这并不意味着什么。在这里可以应用“质疑一切”的建议:多次部署,在不同的环境中,包括本地环境,并尝试复制。
尽管希望你在本地运行 Azure 的 Kubernetes 提供的服务是不合理的,Python SDK 提供了一些功能,可以在本地暴露一个(本地)Web 服务,您可以在其中部署相同的生产级别模型,作为容器运行。这种方法有几个优点我已经提到过。但还有另一个至关重要的优点:大多数 Python SDK API 不仅可用于这种本地部署,而且还可以运行针对容器的所有工具命令来在运行时操作容器。诸如检索容器日志或进入容器检查环境等操作都是可能且无缝的。
注意
由于这些操作涉及容器化部署,因此需要在您的环境中安装并运行Docker。
在本地运行服务需要几个步骤。首先,您必须将模型注册到当前目录中:
from azureml.core.model import Model
model = Model.register(
model_path="roberta-base-11.onnx",
model_name="roberta-base",
description="Transformer-based language model for text generation.",
workspace=ws)
接下来,创建一个环境,安装所有必需的依赖项,使模型能够在容器内运行。例如,如果需要 ONNX 运行时,必须定义它:
from azureml.core.environment import Environment
environment = Environment("LocalDeploy")
environment.python.conda_dependencies.add_pip_package("onnx")
部署模型需要一个评分文件(通常命名为 score.py)。这个脚本负责加载模型,为该模型定义输入并对数据进行评分。评分脚本始终特定于模型,并且没有通用的方法为任何模型编写评分脚本。该脚本需要两个函数:init() 和 run()。现在,创建一个 推理配置,它将评分脚本和环境组合在一起:
from azureml.core.model import InferenceConfig
inference_config = InferenceConfig(
entry_script="score.py",
environment=environment)
现在,将所有内容放在一起需要使用 Python SDK 中的 LocalWebservice 类来将模型部署到本地容器:
from azureml.core.model import InferenceConfig, Model
from azureml.core.webservice import LocalWebservice
# inference with previously created environment
inference_config = InferenceConfig(entry_script="score.py", environment=myenv)
# Create the config, assigning port 9000 for the HTTP API
deployment_config = LocalWebservice.deploy_configuration(port=9000)
# Deploy the service
service = Model.deploy(
ws, "roberta-base",
[model], inference_config,
deployment_config)
service.wait_for_deployment(True)
启动模型将使用后台的容器,该容器将在端口 9000 上运行公开的 HTTP API。您不仅可以直接向 localhost:9000 发送 HTTP 请求,还可以在运行时访问容器。我的容器运行时在系统中没有准备好容器,但运行代码在本地部署时从 Azure 拉取了所有内容。
Downloading model roberta-base:1 to /var/folders/pz/T/azureml5b/roberta-base/1
Generating Docker build context.
[...]
Successfully built 0e8ee154c006
Successfully tagged mymodel:latest
Container (name:determined_ardinghelli,
id:d298d569f2e06d10c7a3df505e5f30afc21710a87b39bdd6f54761) cannot be killed.
Container has been successfully cleaned up.
Image sha256:95682dcea5527a045bb283cf4de9d8b4e64deaf60120 successfully removed.
Starting Docker container...
Docker container running.
Checking container health...
Local webservice is running at http://localhost:9000
9000
现在部署完成了,我可以通过运行 docker 来验证它:
$ docker ps
CONTAINER ID IMAGE COMMAND
2b2176d66877 mymodel "runsvdir /var/runit"
PORTS
8888/tcp, 127.0.0.1:9000->5001/tcp, 127.0.0.1:32770->8883/tcp
进入容器后,我可以确认我的 score.py 脚本和模型都在那里:
root@2b2176d66877:/var/azureml-app# find /var/azureml-app
/var/azureml-app/
/var/azureml-app/score.py
/var/azureml-app/azureml-models
/var/azureml-app/azureml-models/roberta-base
/var/azureml-app/azureml-models/roberta-base/1
/var/azureml-app/azureml-models/roberta-base/1/roberta-base-11.onnx
/var/azureml-app/main.py
/var/azureml-app/model_config_map.json
在尝试部署时,我在 score.py 脚本上遇到了一些问题。部署过程立即引发了错误,并提出了一些建议:
Encountered Exception Traceback (most recent call last):
File "/var/azureml-server/aml_blueprint.py", line 163, in register
main.init()
AttributeError: module 'main' has no attribute 'init'
Worker exiting (pid: 41)
Shutting down: Master
Reason: Worker failed to boot.
2020-11-19T23:58:11,811467402+00:00 - gunicorn/finish 3 0
2020-11-19T23:58:11,812968539+00:00 - Exit code 3 is not normal. Killing image.
ERROR - Error: Container has crashed. Did your init method fail?
在这种情况下,init() 函数需要接受一个参数,而我的示例不需要它。在本地调试并与部署在本地容器中的模型进行调试是非常有用的,也是在尝试在 Azure 中实施之前快速迭代不同设置和模型更改的绝佳方式。
Azure ML 管道
管道只不过是为实现所需目标而采取的各种步骤。如果您曾经使用过像 Jenkins 这样的持续集成(CI)或持续交付(CD)平台,那么“管道”工作流将会很熟悉。Azure 将其 ML 管道描述为适合三种不同场景:机器学习、数据准备和应用编排。它们具有类似的设置和配置,同时处理不同的信息来源和目标以完成任务。
与大多数 Azure 提供的服务一样,您可以使用 Python SDK 或 Azure ML Studio 创建管道。如我之前提到的,管道是实现目标的 步骤,您可以决定如何为最终结果排序这些步骤。例如,一个管道可能需要处理数据;我们在本章中已经涵盖了数据集,所以检索现有数据集以便管道步骤可以被创建。在这个例子中,数据集成为 Python 脚本的输入,形成一个独立的 管道步骤:
from azureml.pipeline.steps import PythonScriptStep
from azureml.pipeline.core import PipelineData
from azureml.core import Datastore
# bankmarketing_dataset already retrieved with `get_by_name()`
# make it an input to the script step
dataset_input = bankmarketing_dataset.as_named_input("input")
# set the output for the pipeline
output = PipelineData(
"output",
datastore=Datastore(ws, "workspaceblobstore"),
output_name="output")
prep_step = PythonScriptStep(
script_name="prep.py",
source_directory="./src",
arguments=["--input", dataset_input.as_download(), "--output", output],
inputs=[dataset_input],
outputs=[output],
allow_reuse=True
)
注意
Azure SDK 可能会经常更改,因此请务必查看官方的 Microsoft AzureML 文档。
示例使用的是PythonScriptStep,这是作为管道步骤可用的众多不同步骤之一。请记住:管道是为实现目标而工作的步骤,Azure 在 SDK 和 Azure ML Studio 中提供了不同的步骤来支持不同类型的工作。然而,这一步骤缺少一个关键部分:计算目标。但它已经包含了几乎完成数据准备所需的所有内容。首先,它使用数据集对象并调用as_named_input,这是PythonScriptStep用作参数的方法。脚本步骤是一个 Python 类,但它试图表示一个命令行工具,因此参数使用破折号,这些参数的值作为列表中的项目传递。这是如何使用 SDK 检索之前创建的计算目标的方法:
from azureml.core.compute import ComputeTarget, AmlCompute
from azureml.core import Workspace
ws = Workspace.from_config()
# retrieve the compute target by its name, here a previously created target
# is called "mlops-target"
compute_target = ws.compute_targets["mlops-target"]
除了我们在本章已经介绍过的计算目标外,您还可以选择提供一个运行时配置,允许设置环境变量来告诉 Azure 如何管理环境。例如,如果您想自己管理依赖项而不是让 Azure 来处理,那么运行时配置将是实现这一目标的方式。以下是设置特定选项的简化方法:
from azureml.core.runconfig import RunConfiguration
run_config = RunConfiguration()
# a compute target should be defined already, set it in the config:
run_config.target = compute_target
# Disable managed dependencies
run_config.environment.python.user_managed_dependencies = False
发布管道
我之前曾将像 Jenkins 这样的持续集成系统与管道进行比较:许多步骤协调工作以完成一个目标。但尽管其他 CI/CD 系统像 Jenkins 有这个能力,有一件非常棘手的事情很难实现,那就是将这些作业暴露在环境之外。Azure 有一种简单的方法来实现这一点,无论是通过 Azure ML Studio 还是使用 SDK。本质上,管道变得通过 HTTP 可用,以便世界上任何地方的任何系统都可以访问管道并触发它。
随后的可能性是无限的。您不再局限于在 Azure 中使用服务、管道和触发器。您的管道步骤可以从其他地方开始,也许是在公司的内部环境或像 GitHub 这样的公共源代码服务中。这是一个有趣的灵活性,因为它提供了更多的选择,云提供商的约束消失了。您不需要每次想要发布它时都创建新的管道。在尝试找出如何发布管道的过程中,您可能会在文档中遇到这种情况。在这个例子中,之前的实验和运行被检索出来以发布管道:
from azureml.core.experiment import Experiment
from azureml.pipeline.core import PipelineRun
experiment = Experiment(ws, "practical-ml-experiment-1")
# run IDs are unique, this one already exists
run_id = "78e729c3-4746-417f-ad9a-abe970f4966f"
pipeline_run = PipelineRun(experiment, run_id)
published_pipeline = pipeline_run.publish_pipeline(
name="ONNX example Pipeline",
description="ONNX Public pipeline", version="1.0")
现在您知道如何发布它,可以通过 HTTP 与其交互。这些 API 端点需要身份验证,但 SDK 提供了您需要获取身份验证标头以进行请求的一切内容:
from azureml.core.authentication import InteractiveLoginAuthentication
import requests
interactive_auth = InteractiveLoginAuthentication()
auth_header = interactive_auth.get_authentication_header()
rest_endpoint = published_pipeline.endpoint
response = requests.post(
rest_endpoint,
headers=auth_header,
json={"ExperimentName": "practical-ml-experiment-1"}
)
run_id = response.json().get('Id')
print(f"Pipeline run submitted with ID: {run_id}")
Azure Machine Learning 设计师
对于图形化倾向的人来说,Azure Machine Learning 设计师是在 Azure 上构建机器学习项目时抽象化复杂性的好选择。训练模型的过程如下所示:
-
登录 Azure ML Studio。
-
如 Figure 8-11 所示,选择设计师界面。
![pmlo 0811]()
Figure 8-11. Azure ML 设计师
-
选择一个样本项目进行探索,比如 Figure 8-12 中的汽车回归项目。请注意,有许多样本项目可供探索,或者您可以从头开始构建自己的 ML 项目。研究 ML 设计师样本项目的绝佳资源是官方的 Microsoft Azure 机器学习设计师文档。
![pmlo 0812]()
Figure 8-12. Azure ML 设计师汽车回归项目
-
要运行项目,请提交一个如 Figure 8-13 所示的管道作业。
![pmlo 0813]()
Figure 8-13. Azure ML 设计师提交
Azure ML 设计师可能看起来有点花哨,但在理解 Azure ML Studio 生态系统如何运作方面,它可以发挥关键作用。通过对样本项目进行“试水”,您将接触到 Azure ML Studio 的所有关键方面,包括 AutoML、存储、计算集群和报告。接下来,让我们讨论所有这些与 Azure 上的 ML 生命周期的关系。
ML 生命周期
最后,Azure 中的所有工具和服务都旨在帮助模型生命周期。这种方法论并非完全特定于 Azure,但理解这些服务如何帮助您将模型投入生产是很有用的,正如 Figure 8-14 所示,您可以从笔记本、AutoML 或 SDK 进行训练。然后,您可以使用 Azure Designer 或 Azure ML Studio 进行验证。在生产部署中,可以利用 Kubernetes 进行扩展,同时注意应用洞察中的问题。
Figure 8-14 试图清楚地表明这不是一个线性过程,并且在整个生产过程中的持续反馈循环可能需要返回到先前的步骤来解决模型中观察到的数据问题和其他常见问题。然而,反馈循环和持续调整对于健康的环境至关重要;仅仅勾选一个启用监控或者 Kubernetes 处理扩展的复选框是不够的。没有持续的评估,无论云提供商如何,成功都是不可能的。

Figure 8-14. ML 生命周期
结论
毫无疑问,Azure 已经在解决与操作机器学习相关的复杂问题,从注册和版本化数据集到促进监视和在可扩展集群上部署实时推断模型。这一切都感觉相对较新,整个 Azure 平台在功能上仍在努力赶超其他云服务提供商,但这并不重要。选择的平台(即使不是 Azure)必须能够轻松地支持工作流程;你必须利用现有资源。在本书中,关于利用技术和避免用不成熟的解决方案解决挑战的想法会反复强调。技术的复用将推动任何事业。记住,作为 MLOps 工程师最重要的是将模型部署到生产环境中,而不是重新发明云功能。
下一章将深入讨论 Google Cloud Platform。
练习
-
从公共来源检索一个 ONNX 模型,并使用 Python SDK 在 Azure 中注册它。
-
部署模型到 ACI,并创建一个 Python 脚本,从 HTTP API 返回模型的响应。
-
使用 Azure 的 Python SDK 在本地部署一个容器,并为实时推理生成一些 HTTP 请求。
-
发布一个新的流水线,然后触发它。触发应该在成功请求后显示
run_id的输出。 -
使用 Azure Python SDK 从 Kaggle 获取数据集,并使用 Azure AutoML 训练模型。
批判性思维讨论问题
-
在 Azure 平台上有许多训练模型的方式:Azure ML Studio Designer、Azure Python SDK、Azure Notebooks 和 Azure AutoML。各自的优缺点是什么?
-
为什么启用认证是个好主意?
-
可复现的环境如何帮助交付模型?
-
描述两个好的调试技术方面及其有用之处。
-
模型版本控制的一些好处是什么?
-
为什么数据集的版本控制很重要?
第九章:MLOps 适用于 GCP
作者:诺亚·吉夫
最优秀的盆景教师既精通现实,又能够不仅解释而且激发。约翰曾经说过,“把这部分用线缠绕,这样它就会干得漂亮。” “什么形状?”我问。 “你决定吧,”他回答,“我不是为你唱歌!”
约瑟夫·博根博士
谷歌云平台(GCP)与其竞争对手相比具有独特性。一方面,它在某种程度上专注于企业;另一方面,它拥有世界一流的研发,已经创建了领先类别的技术,包括 Kubernetes 和 TensorFlow 等产品。然而,谷歌云的另一个独特之处是通过 https://edu.google.com 提供给学生和职业人士丰富的教育资源。
让我们深入研究谷歌云,重点是如何利用它来进行 MLOps。
谷歌云平台概述
每个云平台都有其利弊,因此让我们从覆盖谷歌云平台的三个主要缺点开始。首先,由于谷歌落后于 AWS 和微软 Azure,使用谷歌的一个缺点是其拥有较少的认证从业者。在 图 9-1 中,您可以看到在 2020 年,AWS 和 Azure 控制了超过 50% 的市场份额,而谷歌云不到 9%。因此,招聘谷歌云平台的人才更具挑战性。
第二个缺点是谷歌是哈佛大学教授 Shoshana Zuboff 所称的监控资本主义的一部分,其中“硅谷和其他公司正在挖掘用户信息以预测和塑造他们的行为”。因此,技术监管理论上可能会影响未来的市场份额。

图 9-1. GCP 云市场份额
最后,谷歌因用户和客户体验差而声名狼藉,并频繁放弃产品,如 Google Hangouts 和 Google Plus 社交网络。如果在未来五年内仍然是第三选择,它会继续停止 Google Cloud 吗?
尽管这些是重大挑战,而且谷歌应该迅速解决导致这些问题的文化问题,但由于其文化,谷歌平台有许多独特的优势。例如,虽然 AWS 和微软是以客户服务为导向的文化,具有丰富的企业客户支持历史,但谷歌因其大多数产品没有电话支持而闻名。相反,它的文化侧重于紧张的“leet code”风格面试,只聘用“最优秀的”人才。此外,研发能够在“全球范围”内运行的令人惊叹复杂解决方案是其擅长的。特别是,谷歌的三个最成功的开源项目展示了这种文化优势:Kubernetes、Go 语言和深度学习框架 TensorFlow。
最终,使用 Google Cloud 的头号优势可能在于其技术非常适合多云战略。像 Kubernetes 和 TensorFlow 这样的技术在任何云上表现良好,并得到广泛采用。因此,使用 Google Cloud 对于希望检查其与 AWS 或 Azure 供应商关系实力的大公司来说可能是一个避险措施。此外,这些技术具有广泛的采用,因此相对容易为需要 TensorFlow 专业知识的职位招聘人才。
让我们看看 Google Cloud 的核心服务。这些服务分为四个主要类别:计算、存储、大数据和机器学习,如图 9-2 所示。

图 9-2. GCP 云服务
接下来,让我们定义 Google Cloud 的主要组件,从计算开始:
计算引擎
与其他云供应商(特别是 AWS 和 Azure)类似,GCP 提供虚拟机作为服务。Compute Engine 是一个服务,允许您在 Google 基础设施上创建和运行虚拟机。也许最重要的一点是,有许多不同类型的虚拟机,包括计算密集型、内存密集型、加速器优化和通用型。此外,还有可中断的 VM,可供使用长达 24 小时,适用于批处理作业,可以节省高达 80%的存储成本。
作为 MLOps 从业者,使用适合当前任务的适当类型的机器非常关键。在实际世界中,成本确实很重要,准确预测成本的能力可能会成就或毁掉一家进行机器学习的公司。例如,在深度学习中,使用加速器优化实例可能是最佳选择,因为它们可以利用 NVIDIA GPU 的额外大规模并行能力。另一方面,对于不能利用 GPU 的机器学习训练来说,使用这些实例将非常浪费。类似地,通过围绕用于批处理机器学习的可中断 VM 进行架构设计,组织可以节省高达 80%的成本。
Kubernetes 引擎和 Cloud Run
由于 Google 创建并维护 Kubernetes,通过其 GKE(Google Kubernetes Engine)执行 Kubernetes 上的工作的支持非常出色。另外,Cloud Run 是一个高级服务,抽象了运行容器的许多复杂性。对于希望以简单方式部署容器化机器学习应用程序的组织来说,Cloud Run 是 Google Cloud Platform 的一个很好的起点。
App Engine
Google App Engine 是一个完全托管的 PaaS。您可以使用多种语言编写代码,包括 Node.js、Java、Ruby、C#、Go、Python 或 PHP。MLOps 工作流可以使用 App Engine 作为完全自动化持续交付管道的 API 端点,使用 GCP Cloud Build 部署更改。
云函数
Google Cloud Functions 充当 FaaS(函数即服务)。FaaS 与事件驱动架构很搭配。例如,Cloud Functions 可以触发批量机器学习训练作业或者在事件响应中提供 ML 预测。
接下来,让我们谈谈 Google Cloud 上的存储。在 MLOps 方面,讨论的主要选项是其 Cloud Storage 产品。它提供无限存储、全球访问性、低延迟、地理冗余和高耐久性。这些事实意味着对于 MLOps 工作流程来说,数据湖是非结构化和结构化数据的存储位置,用于批处理机器学习训练作业。
与此服务密切相关的是 GCP 提供的大数据工具。许多服务可以帮助移动、查询和计算大数据。其中最受欢迎的是 Google BigQuery,因为它提供 SQL 接口、无服务器范式,并且可以在平台内进行机器学习。Google BigQuery 是在 GCP 上进行机器学习的绝佳起点,因为你可以从这个工具解决整个 MLOps 的价值链。
最后,机器学习和 AI 能力在一个名为 Vertex AI 的产品中协同工作。谷歌的一个优势是它从一开始就旨在成为 MLOps 解决方案。Vertex AI 的工作流程允许以结构化的方式进行 ML,包括以下内容:
-
数据集的创建和存储
-
训练一个 ML 模型
-
将模型存储在 Vertex AI 中
-
将模型部署到端点以进行预测
-
测试和创建预测请求
-
使用流量分割进行端点处理
-
管理 ML 模型和端点的生命周期
根据谷歌的说法,这些能力对 Vertex AI 如何实现 MLOps 起到了作用,如图 9-3 所示。这七个组件的中心是数据和模型管理,这是 MLOps 的核心元素。

图 9-3. Google 的 MLOps 七大组件
这种思维过程最终体现在谷歌对端到端 MLOps 的理念中,如图 9-4 所述。像 Vertex AI 这样的全面平台能够全面管理 MLOps。
简而言之,Google 云平台上的 MLOps 因 Vertex AI 和其系统的子组件(如 Google BigQuery)而变得简单明了。接下来,让我们更详细地探讨在 GCP 上的 CI/CD,这是 MLOps 的一个不可或缺的基础组件。

图 9-4. GCP 上的端到端 MLOps
持续集成和持续交付
项目中最重要但被忽视的一个领域是持续集成。测试是 DevOps 和 MLOps 的基本组成部分。对于 GCP,有两种主要的持续集成选项:使用 GitHub Actions 这样的 SaaS 解决方案,或者使用云原生解决方案Cloud Build。让我们看看这两个选项。您可以在这个gcp-from-zero GitHub 存储库中查看整个初始项目脚手架。
首先,让我们看看 Google Cloud Build。这是 Google Cloud Build 的配置文件示例,cloudbuild.yaml:
steps:
- name: python:3.7
id: INSTALL
entrypoint: python3
args:
- '-m'
- 'pip'
- 'install'
- '-t'
- '.'
- '-r'
- 'requirements.txt'
- name: python:3.7
entrypoint: ./pylint_runner
id: LINT
waitFor:
- INSTALL
- name: "gcr.io/cloud-builders/gcloud"
args: ["app", "deploy"]
timeout: "1600s"
images: ['gcr.io/$PROJECT_ID/pylint']
与 Google Cloud 一起工作的推荐方式是使用内置编辑器和终端一起工作,如第 9-5 图所示。请注意,Python 虚拟环境已激活。

第 9-5 图。GCP 编辑器
一个要点是,与 GitHub Actions 相比,Google Cloud Build 在测试和代码 linting 方面有些笨拙,但确实使像 Google App Engine 这样的服务部署变得容易。
现在让我们看看 GitHub Actions 的工作原理。您可以参考python-publish.yml 配置文件:
name: Python application test with GitHub Actions
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install dependencies
run: |
make install
- name: Lint with pylint
run: |
make lint
- name: Test with pytest
run: |
make test
- name: Format code
run: |
make format
两种方法之间的一个关键区别在于,GitHub 侧重于提供出色的开发者体验,而 GCP 侧重于云体验。一种策略是使用 GitHub Actions 进行开发者反馈,即代码的 linting 和测试,并使用 Google Cloud Build 进行部署。
在 GCP 上处理 CI/CD 系统后,让我们探讨一个核心 Google 计算技术,Kubernetes。
Kubernetes Hello World
关于 Kubernetes 的一种思考方式是将其视为一个“迷你云”或“盒中云”。Kubernetes 允许创建几乎无限的应用程序复杂性。使其成为 MLOps 理想选择的一些功能包括:
-
高可用架构
-
自动缩放
-
丰富的生态系统
-
服务发现
-
容器健康管理
-
Secrets 和配置管理
-
Kubeflow(用于 Kubernetes 的端到端 ML 平台)
第 9-6 图表明,从 TensorFlow 到 scikit-learn 的 ML 框架在核心 Kubernetes 架构之上进行协调。最后,正如前文所述,Kubernetes 可以在许多云端或您自己的数据中心中运行。

第 9-6 图。Kubeflow 架构
第 9-7 图中 Kubernetes 架构的基础显示了 Kubernetes 中涉及的核心操作包括以下内容:
-
创建一个 Kubernetes 集群。
-
部署应用程序到集群中。
-
暴露应用程序端口。
-
扩展应用程序。
-
更新应用程序。

第 9-7 图。Kubernetes 基础知识
第 9-8 图显示了一个 Kubernetes 控制节点,管理包含一个或多个容器的其他节点内的 pod。

第 9-8 图。Kubernetes 层级结构
注意
有两种主要方法:设置本地集群(最好使用 Docker Desktop)或提供云集群:通过 Amazon EKS 的 Amazon、通过 Google Kubernetes Engine GKE 的 Google 和通过 Azure Kubernetes Service (AKS) 的 Microsoft。
Kubernetes 的“杀手级”特性之一是通过水平 Pod 自动缩放器(HPA)设置自动缩放功能。Kubernetes HPA 将自动调整副本控制器、部署或副本集中的 Pod 数量(记住它们可以包含多个容器)。缩放使用 CPU 利用率、内存或在 Kubernetes Metrics Server 中定义的自定义指标。
在 图 9-9 中,Kubernetes 使用控制循环监视集群的度量,并根据收到的度量执行操作。

图 9-9. Kubernetes 自动缩放器
由于 Kubernetes 是 Google 平台的核心优势,以及 MLOps 多大部分运行在该平台上,让我们直接进入一个“你好世界” Kubernetes 示例。该项目使用一个简单的 Flask 应用程序作为基础项目(它返回正确的更改),并将其转换为 Kubernetes。您可以在 GitHub 上的完整源代码库中找到。
在 图 9-10 中,Kubernetes 节点连接到负载均衡器。

图 9-10. Kubernetes 你好世界
让我们看看存储库中的资产。
-
Makefile:构建项目
-
Dockerfile:容器配置
-
app.py:Flask 应用程序
-
kube-hello-change.yaml:Kubernetes YAML 配置
要开始,请执行以下步骤:
-
创建 Python 虚拟环境:
python3 -m venv ~/.kube-hello && source ~/.kube-hello/bin/activate -
运行
make all执行多个构建步骤,包括安装库、linting 项目和运行测试。
接下来,构建并运行一个 Docker 容器:
-
要在本地构建镜像,请执行以下操作:
docker build -t flask-change:latest .或者运行
make build,该命令具有相同的效果。 -
要验证容器运行,请运行
docker image ls -
要运行容器,请执行以下操作:
docker run -p 8080:8080 flask-change或者运行
make run,该命令具有相同的效果。 -
在单独的终端中,通过
curl调用 Web 服务,或者运行make invoke具有相同的命令:curl http://127.0.0.1:8080/change/1/34这里是输出:
$ kubectl get nodes [ { "5": "quarters" }, { "1": "nickels" }, { "4": "pennies" } ] -
使用 Ctrl+C 命令停止正在运行的 Docker 容器
接下来,在本地运行 Kubernetes:
-
通过 docker-desktop 上下文验证 Kubernetes 是否工作:
(.kube-hello) ➜ kubernetes-hello-world-python-flask git:(main) kubectl \ get nodes NAME STATUS ROLES AGE VERSION docker-desktop Ready master 30d v1.19.3 -
使用以下命令在 Kubernetes 中运行应用程序,该命令告诉 Kubernetes 设置负载平衡服务并运行它:
kubectl apply -f kube-hello-change.yaml或者运行
make run-kube,该命令具有相同的效果。您可以从配置文件中看到,负载均衡器和三个节点是配置的应用程序:
apiVersion: v1 kind: Service metadata: name: hello-flask-change-service spec: selector: app: hello-python ports: - protocol: "TCP" port: 8080 targetPort: 8080 type: LoadBalancer --- apiVersion: apps/v1 kind: Deployment metadata: name: hello-python spec: selector: matchLabels: app: hello-python replicas: 3 template: metadata: labels: app: hello-python spec: containers: - name: flask-change image: flask-change:latest imagePullPolicy: Never ports: - containerPort: 8080 -
验证容器是否正在运行:
kubectl get pods这里是输出:
NAME READY STATUS RESTARTS AGE flask-change-7b7d7f467b-26htf 1/1 Running 0 8s flask-change-7b7d7f467b-fh6df 1/1 Running 0 7s flask-change-7b7d7f467b-fpsxr 1/1 Running 0 6s -
描述负载平衡服务:
kubectl describe services hello-python-service您应该看到类似于此的输出:
Name: hello-python-service Namespace: default Labels: <none> Annotations: <none> Selector: app=hello-python Type: LoadBalancer IP Families: <none> IP: 10.101.140.123 IPs: <none> LoadBalancer Ingress: localhost Port: <unset> 8080/TCP TargetPort: 8080/TCP NodePort: <unset> 30301/TCP Endpoints: 10.1.0.27:8080,10.1.0.28:8080,10.1.0.29:8080 Session Affinity: None External Traffic Policy: Cluster Events: <none> -
调用
curl来访问端点:make invoke在接下来的部分,运行命令
make invoke查询微服务。该操作的输出如下所示:curl http://127.0.0.1:8080/change/1/34 [ { "5": "quarters" }, { "1": "nickels" }, { "4": "pennies" } ]
要清理部署,请运行 kubectl delete deployment hello-python 命令。
超越基本教程的下一步是使用 GKE(Google Kubernetes Engine)、Google Cloud Run(容器即服务)或 Vertex AI 部署机器学习端点。您可以使用 Python MLOps Cookbook 仓库 作为基础来完成这些操作。Kubernetes 技术是构建支持 ML API 的优秀基础,结合 GCP,如果从一开始就使用 Docker 格式的容器,可以选择多种选项。
通过 GCP 的基本计算方式,让我们讨论云原生数据库如 Google BigQuery 在采用 MLOps 过程中的长远发展。
云原生数据库选择与设计
Google Cloud 平台的一颗明珠是 Google BigQuery,原因有几个。其中之一是它的易用性,另一个是广泛可用的公共数据库。您可以在 这个 Reddit 页面 找到一份不错的 Google BigQuery 开放数据集列表。从 MLOps 的角度来看,Google BigQuery 的一个杀手级功能是能够在其平台内训练和托管 ML 模型。
在查看图 9-12 时,请注意 Google BigQuery 是一个 MLOps 管道的核心,可以将产品导出到商业智能和机器学习工程中,包括 Vertex AI。这种 MLOps 工作流是由于 DataOps(数据运营)输入(如公共数据集、流式 API 和 Google Dataflow 产品)的支持。Google BigQuery 在线进行机器学习,从而简化了大数据集的处理过程。

图 9-12. Google BigQuery MLOps 工作流程
此工作流的一个示例显示在图 9-13 中,其中在 Google BigQuery 中进行了 ML 建模后,结果导出到 Google Data Studio。从 BigQuery 创建的工件是 K-means 聚类分析,显示在此可共享报告中。

图 9-13. Google Data Studio K-means 聚类
作为在 GCP 平台上进行 MLOps 的起点,BigQuery 是一个优选的选择,因为该平台具有灵活性。接下来,让我们讨论在 GCP 平台上的 DataOps 和应用数据工程。
GCP 上的 DataOps:应用数据工程
数据是在规模上构建机器学习所需的输入,因此它是 MLOps 的关键组成部分。从某种意义上说,GCP 几乎可以通过各种方式自动化数据流。这一事实归功于可用的各种计算和存储选项,包括高级工具如 Dataflow。
为了简单起见,让我们使用无服务器方法来进行数据工程,使用 Cloud Functions。为此,让我们看看 Google Cloud Functions 如何工作,以及它们如何通过 AI API 调用作为 ML 解决方案或通过与Google Pub/Sub交流的 MLOps 管道服务。
让我们从一个故意简单的 Google Cloud Function 开始,返回正确的更改。您可以在这里找到完整示例。
要开始,请打开 Google Cloud 控制台,创建一个新的 Cloud Function,并将以下代码粘贴到内部,如图 9-14 所示。您还可以“取消”“要求身份验证”以“允许未经身份验证的调用”。
import json
def hello_world(request):
request_json = request.get_json()
print(f"This is my payload: {request_json}")
if request_json and "amount" in request_json:
raw_amount = request_json["amount"]
print(f"This is my amount: {raw_amount}")
amount = float(raw_amount)
print(f"This is my float amount: {amount}")
res = []
coins = [1, 5, 10, 25]
coin_lookup = {25: "quarters", 10: "dimes", 5: "nickels", 1: "pennies"}
coin = coins.pop()
num, rem = divmod(int(amount * 100), coin)
res.append({num: coin_lookup[coin]})
while rem > 0:
coin = coins.pop()
num, rem = divmod(rem, coin)
if num:
if coin in coin_lookup:
res.append({num: coin_lookup[coin]})
result = f"This is the res: {res}"
return result

图 9-14. Google Cloud Function
要通过gcloud命令行调用,请执行以下操作:
gcloud functions call changemachine --data '{"amount":"1.34"}'
要通过curl命令调用,请使用以下命令。
curl -d '{
"amount":"1.34"
}' -H "Content-Type: application/json" -X POST <trigger>/function-3
另一种方法是构建一个命令行工具来调用您的端点:
#!/usr/bin/env python
import click
import requests
@click.group()
@click.version_option("1.0")
def cli():
"""Invoker"""
@cli.command("http")
@click.option("--amount", default=1.34, help="Change to Make")
@click.option(
"--host",
default="https://us-central1-cloudai-194723.cloudfunctions.net/change722",
help="Host to invoke",
)
def mkrequest(amount, host):
"""Asks a web service to make change"""
click.echo(
click.style(
f"Querying host {host} with amount: {amount}", bg="green", fg="white"
)
)
payload = {"amount": amount}
result = requests.post(url=host, json=payload)
click.echo(click.style(f"result: {result.text}", bg="red", fg="white"))
if __name__ == "__main__":
cli()
最后,另一种方法是将您的 ML 模型上传到 Vertex AI 或调用执行计算机视觉、NLP 或其他与 ML 相关任务的现有 API 端点。您可以在 GitHub 上找到完整示例。在下面的示例中,让我们使用一个预先存在的 NLP API。您还需要通过编辑包含在 Google Cloud 脚手架中的requirements.txt文件来添加两个第三方库(见图 9-15)。

图 9-15. 添加需求
将此代码粘贴到 Google Cloud Shell 控制台中的main.py函数中:
import wikipedia
from google.cloud import translate
def sample_translate_text(
text="YOUR_TEXT_TO_TRANSLATE", project_id="YOUR_PROJECT_ID", language="fr"
):
"""Translating Text."""
client = translate.TranslationServiceClient()
parent = client.location_path(project_id, "global")
# Detail on supported types can be found here:
# https://cloud.google.com/translate/docs/supported-formats
response = client.translate_text(
parent=parent,
contents=[text],
mime_type="text/plain", # mime types: text/plain, text/html
source_language_code="en-US",
target_language_code=language,
)
print(f"You passed in this language {language}")
# Display the translation for each input text provided
for translation in response.translations:
print("Translated text: {}".format(translation.translated_text))
return "Translated text: {}".format(translation.translated_text)
def translate_test(request):
"""Takes JSON Payload {"entity": "google"}"""
request_json = request.get_json()
print(f"This is my payload: {request_json}")
if request_json and "entity" in request_json:
entity = request_json["entity"]
language = request_json["language"]
sentences = request_json["sentences"]
print(entity)
res = wikipedia.summary(entity, sentences=sentences)
trans = sample_translate_text(
text=res, project_id="cloudai-194723", language=language
)
return trans
else:
return f"No Payload"
要调用该函数,您可以从 Google Cloud Shell 调用它:
gcloud functions call translate-wikipedia --data\
'{"entity":"facebook", "sentences": "20", "language":"ru"}'
您可以在 Google Cloud Shell 终端中看到俄语翻译的输出,如图 9-16 所示。

图 9-16. 翻译
对于原型化数据工程工作流程,没有比 Google Cloud Functions 等无服务器技术更快的方法了。我的建议是,使用无服务器技术解决初始数据工程工作流,如果需要,再转向更复杂的工具。
重要说明是 Vertex AI 平台添加了许多额外的数据工程和 ML 工程组件,以增强更大型项目。特别是使用可解释 AI、跟踪模型质量和使用特征存储是全面 MLOps 解决方案中的有价值组件。接下来让我们深入探讨这些选项。
运营 ML 模型
每个主要的云平台现在都有一个 MLOps 平台。在 GCP 上,该平台是 Vertex AI,并整合了多年来开发的许多个别服务,包括 AutoML 技术。特别是,MLOps 平台的一些基本组件包括特征存储、可解释 AI 和跟踪模型质量。如果在较大公司启动 MLOps 项目,首选在 GCP 上的地方将是其 Vertex AI 平台,就像在 AWS 上的 SageMaker 或 Azure 上的 Azure ML Studio 一样。
另一个选项是将组件作为独立的解决方案在 GCP 平台上操作 ML 模型。其中一个可用的服务是prediction service,用于部署模型并接收请求。
例如,您可以使用类似以下命令来测试本地的 sklearn 模型:
gcloud ai-platform local predict --model-dir\
LOCAL_OR_CLOUD_STORAGE_PATH_TO_MODEL_DIRECTORY/ \
--json-instances LOCAL_PATH_TO_PREDICTION_INPUT.JSON \
--framework NAME_OF_FRAMEWORK
随后,您可以创建一个端点,然后从本章前面显示的示例中调用此端点,例如 Google Cloud Functions、Google Cloud Run 或 Google App Engine。
让我们通过一个示例来了解 Google App Engine 项目在 GCP 云上的外观,使用此存储库作为起点。首先,请注意在 GCP 上的持续交付的核心架构。要开始,请按照“轻量级”MLOps 工作流程在图 9-17 中所示的方式创建一个新的 Google App Engine 项目。
注意
请注意,这种轻量级工作流程允许透明且简单地部署 ML 模型,但如果需要像可解释 AI 这样的功能,则“重量级”流程可能会增加巨大的价值。

图 9-17. MLOps 轻量与重量工作流程
接下来,请按照图 9-18 中所示启用 Cloud Build API。

图 9-18. 云构建
cloudbuild.yml 文件只需一个部署命令:
steps:
- name: "gcr.io/cloud-builders/gcloud"
args: ["app", "deploy"]
timeout: "1600s"
唯一的其他要求是app.yaml、requirements.txt和main.py,这些文件都可以在此示例仓库中找到。使此应用程序执行任何形式的机器学习的最后一步是调用 ML/AI API 或使用 AI 平台端点托管。
简单方法的优势在于,最多只需一两个小时就能设置整个 MLOps 流水线。您还可以从 AI API、预测服务和 AutoML 端点中挑选和选择。
在 GCP 上进行 MLOps 有“轻量级”和“重量级”两种方法。本示例探讨了“轻量级”方法,但使用 Vertex AI 平台技术也有其优点,因为它包含许多企业所需的高级功能。
让我们结束本章,并讨论在 MLOps 中使用 GCP 的下一步。
结论
关于像 Vertex AI 这样的 MLOps 技术的最后一点体会是,它们比大多数组织自己解决复杂问题更有效。我记得曾与研究实验室的某人交谈,他们吹嘘云计算被高估了,因为他们拥有大量 GPU。然而,大量 GPU 并不会给您这些平台服务。这种说法基本上是对企业软件和初创公司工作方式的根本误解。在早期阶段的初创公司和财富 500 强公司中,比较优势至关重要。不要为了一些微不足道的成本而构建比您可以购买的更差的东西。
我建议将本章的所有内容应用到一个最终的机器学习项目中,该项目包括在 GCP 上构建一个云原生 ML 应用程序。这个项目应该使您能够使用现代技术创建实际可行的解决方案。
在开始之前,请确保阅读 Sculley 等人(2015)的论文,考虑机器学习系统中的技术债务。您的项目可能会受益于使用 Google BigQuery 数据集的公共数据。或者,如果使用 AutoML,数据可以是教程数据或自定义数据。
主要思想是创建一个展示您在 Google Cloud 上进行 ML 工程能力的组合项目。以下是建议的项目要求需要深入考虑:
-
源代码存储在 GitHub 中
-
从 CircleCI 进行持续部署
-
数据存储在 GCP(BigQuery、Google Cloud Storage 等)
-
创建和提供 ML 预测(AutoML、BigQuery、AI Platform 等。)
-
云原生监控
-
Google App Engine 通过 REST API 提供 HTTP 请求,并附带一个 JSON 负载
-
使用 Google Cloud Build 在 GCP 环境中部署
这里是添加到最终项目要求清单的一些项目:
-
应用程序是否进行了 ML 推断?
-
是否存在独立的环境?
-
是否有全面的监控和警报?
-
是否使用了正确的数据存储?
-
是否适用最少安全原则?
-
数据在传输过程中是否加密?
您可以在官方代码库中查看一些最近的学生和顶级数据科学项目。这些项目为您可以构建的内容提供了一个参考框架,欢迎您将来提交拉取请求以将您的项目添加进来。
下一章讨论了机器学习的互操作性以及它如何通过不同平台、技术和模型格式解决 MLOps 问题的独特性。
练习
-
使用 Google Cloud Shell Editor,在 GitHub 上创建一个新的存储库,使用 Makefile、linting 和测试来构建必要的 Python 脚手架。在您的 Makefile 中添加代码格式化步骤。
-
创建一个“Hello World”管道到 Google Cloud,调用基于 Python 的 Google App Engine (GAE) 项目,并返回一个 JavaScript Object Notation (JSON) 响应中的“hello world”。
-
使用 CSV 文件和 Google BigQuery 创建一个摄取到 ETL 管道。安排一个定期的 cron 作业批量更新数据。
-
在 Google AutoML Vision 上训练一个多类别分类模型,并部署到边缘设备。
-
创建一个生产和开发环境,并使用 Google Cloud Build 在这两个环境中部署项目。
批判性思维讨论问题
-
CI 系统解决了哪些问题,以及为什么 CI 系统是 SaaS 软件的重要组成部分?
-
为什么云平台是分析应用的理想目标,以及深度学习如何从云计算中受益?
-
像 Google BigQuery 这样的托管服务有哪些优势,以及 Google BigQuery 如何与传统 SQL 不同?
-
从 BigQuery 直接进行 ML 预测如何为 Google 平台增加价值,以及这对分析应用工程可能带来的优势是什么?
-
AutoML 为何具有较低的总体拥有成本(TCO),以及如何可能有较高的 TCO?
第十章:机器学习的互操作性
By Alfredo Deza
哺乳动物的大脑具有广泛的计算能力,但是某些特殊功能(例如主观性)通常需要专门的结构。这种假设的结构已经被马塞尔·金斯本戏称为“主观性泵”。对于我们中的一些人来说,这确实是我们正在寻找的。而主观性的机制是双重的,如解剖学的二重性所示,通过半球切除术的成功以及分裂脑的结果(在猫和猴子以及人类身上)。
Dr. Joseph Bogen
秘鲁有数千种土豆品种。作为一个在秘鲁长大的人,我发现这听起来令人惊讶。很容易假设大多数土豆的味道都有些类似,但事实并非如此。不同的菜肴需要不同的土豆品种。如果食谱要求使用Huayro土豆,而你想使用大多数美国超市都能找到的常见烘烤土豆,你可能会不想与秘鲁厨师争论。如果你有机会去南美(特别是秘鲁),尝试走进街头市场的经历。那里新鲜蔬菜的数量,包括几十种土豆品种,可能会让你眩晕。
我不再生活在秘鲁,我错过那里的土豆品种。我真的无法用本地超市购买的普通烘烤土豆来做出相同口感的一些菜肴。这完全不一样。我认为这种谦逊蔬菜所提供的多样性和不同口味对秘鲁的美食身份至关重要。你可能认为用常见的烘烤土豆烹饪还可以,但归根结底,这是关于选择和挑选。
在多样性和挑选适合自己的东西的能力中有一种赋权感。当涉及到最终产品——训练好的模型时,这种赋权感在机器学习中同样适用。
训练过的机器学习模型也有其独特的限制,大多数模型无法在未经专门定制支持的环境中工作。模型互操作性的主要概念是能够将模型从一个平台转换到另一个平台,从而提供了选择。本章论述的观点是,虽然使用云供应商提供的即用即得模型并不用过多担心供应商锁定是有道理的,但理解如何将模型导出为其他可以在具有不同约束的平台上工作的格式是非常重要的。就像容器只要有底层容器运行时就可以在大多数系统中无缝运行一样(正如我在“容器”章节中解释的那样),模型互操作性或将模型导出为不同格式对灵活性和增强至关重要。图 10-1 通过训练任何机器学习框架,在导出结果模型一次后,几乎可以在任何地方部署:从边缘设备到手机和其他操作系统。

图 10-1. 互操作性概述
这种情况就像制造可以与任何螺丝刀配合使用的螺钉,而不是只能与单一家装修店的螺丝刀配合使用的螺钉。在“边缘设备”章节中,我们已经遇到了一个模型无法与 Edge TPU 配合工作的问题,最终需要进行转换。目前有一些令人兴奋的选择,我对 ONNX 特别惊讶,这是一个由社区驱动的、采用开放标准的项目,旨在通过简化工具链减少与模型交互的复杂性。本章将深入探讨使 ONNX 成为引人注目的机器学习选择的一些细节,以及大多数云平台已经支持该格式的原因。
为何互操作性至关重要
在软件工程中,抽象复杂的过程和交互是一种典型模式。有时,这些抽象可能会变得非常复杂,导致抽象本身与其试图抽象的底层软件一样复杂。一个很好的例子是 Openstack(一个开源的基础设施即服务平台)及其安装程序。安装和配置基础设施平台可能非常复杂。不同的机器类型和网络拓扑创建了一个棘手的组合,需要用一个通用的安装程序解决。
创建了一个新的安装程序,以使安装 TripleO(Openstack On Openstack)变得更加容易。TripleO 生成了一个临时实例,进而安装 Openstack。该项目解决了许多与安装和配置相关的问题,但有人认为它仍然很复杂,并且需要进一步的抽象。这就是 QuintupleO(Openstack On Openstack On Openstack)的诞生。不参与 Openstack 的活动,我可以告诉你,部署它是很困难的,工程团队正在试图通用地解决这些问题。但我怀疑增加另一层是解决方案。
很容易被说服认为增加另一层会使事情变得更容易,但实际上,这很难做到既满足大家又做得好。我经常用来设计系统的一个开放性问题是:系统可以非常简单且专断,也可以灵活且复杂。你会选择哪个? 没有人喜欢这些选项,每个人都推崇简单且灵活。虽然可以创建这样的系统,但要达到这一点是具有挑战性的。
在机器学习中,多个平台和云服务提供商以不同和特定的方式训练模型。如果仅在平台内部保持并与模型交互,这并不重要,但如果您需要在其他地方运行训练好的模型,则可能会引起挫败感。
最近在 Azure 上使用 AutoML 训练数据集时,我在尝试本地推理时遇到了几个问题。Azure 具有 AutoML 的“无代码”部署,并且多种类型的训练模型支持这种部署方式。这意味着无需编写任何代码即可创建推理并提供响应。Azure 处理 API 文档,说明输入和预期输出是什么。我找不到训练模型的评分脚本,也找不到任何有助于在本地运行它的评分脚本的提示。没有明显的方法来理解如何加载和与模型交互。
模型的后缀暗示它使用 Python 的pickle模块,因此在尝试了几种不同方法后,我设法加载了它,但无法进行任何推理。接下来我得处理依赖关系。Azure 中的 AutoML 并不公布用于训练模型的确切版本和库。我目前正在使用 Python 3.8,但无法在系统中安装 Azure SDK,因为 SDK 仅支持 3.7 版本。我不得不安装 Python 3.7,然后创建一个虚拟环境,并在那里安装 SDK。
其中一个库(xgboost)在其最新版本中存在非向后兼容性(模块被移动或重命名),因此我不得不猜测一个允许特定导入的版本。结果证明是 2019 年的 0.90 版本。最后,在 Azure 的 AutoML 中训练模型时,似乎使用了当时最新版本的 Azure SDK。但这也没有进行宣传。也就是说,如果一个模型在一月份训练,而您在之后的一个月尝试使用它,并且 SDK 有几个版本更新,您无法使用最新的 SDK 版本。您必须回到 Azure 训练模型时的最新 SDK 版本。
这种情况绝不意味着对 Azure 的 AutoML 过度批评。这个平台使用起来非常出色,并且通过更好地宣传所使用的版本及如何与本地模型交互可以进一步改进。主要问题在于在过程中失去了控制粒度:低代码或无代码对速度来说很好,但在可移植性方面可能会变得复杂。我通过尝试进行本地推理时遇到了所有这些问题,但如果您的公司一般使用 AWS 进行机器学习,但使用 Azure 进行托管,则可能会出现相同情况。
经常出现的另一个问题是,科学家在一个平台上创建模型时不得不做一些假设,比如底层环境,包括计算能力、存储和内存。如果一个在 AWS 上表现良好的模型需要部署到完全不兼容的边缘 TPU 设备,会发生什么?假设你的公司已经解决了这种情况,并且为不同平台生成相同的模型。但是,边缘设备的结果模型达到了五千兆字节,超出了加速器的最大存储容量。
模型互操作性通过公开描述约束条件来解决这些问题,使得可以在享受所有主要云提供商支持的同时,轻松地将模型从一种格式转换为另一种格式。在下一节中,我将详细介绍 ONNX 作为更强大的互操作性项目的细节,以及如何构建自动化来轻松转换模型。
ONNX: 开放神经网络交换
正如我之前提到的,ONNX 不仅是模型互操作性的一个很好选择,也是朝着允许轻松切换框架的系统的第一个倡议。这个项目始于 2017 年,当时 Facebook 和 Microsoft 将 ONNX 作为人工智能模型互操作的开放生态系统,并共同开发了该项目和工具以推动其采纳。此后,该项目作为一个大型开源项目成长和成熟,具有包括特别兴趣组(SIGs)和工作组(working groups)在内的完整结构,涵盖不同领域如发布和培训。
除了互操作性之外,框架的普遍性允许硬件供应商针对 ONNX 并同时影响多个其他框架。通过利用 ONNX 表示,优化不再需要单独集成到每个框架中(这是一个耗时的过程)。尽管 ONNX 相对较新,但令人振奋的是它在各个云提供商中得到了良好的支持。不足为奇的是,Azure 甚至在其机器学习 SDK 中为 ONNX 模型提供了原生支持。
主要思想是在您喜欢的框架中进行一次训练,然后在任何地方运行:从云端到边缘设备。一旦模型以 ONNX 格式存在,您可以将其部署到各种设备和平台上。这包括不同的操作系统。实现这一点的努力是巨大的。不多的软件示例能够在多个不同的操作系统、边缘设备和云端上使用相同的格式运行。
尽管有几种支持的机器学习框架(随时添加更多),图 10-2 展示了最常见的转换模式。

图 10-2. 转换为 ONNX
您可以利用您最喜欢的框架的知识和功能,然后转换为 ONNX。然而,正如我在“苹果 Core ML”中所演示的,也可以(虽然不太常见)将 ONNX 模型转换为不同的运行时。这些转换也并非“免费”:当新功能尚不支持时(ONNX 转换器总是在赶上),或者旧模型不受新版本支持时,可能会遇到问题。我仍然相信普遍性和“随处运行”的想法是稳固的,并且在可能时利用它是有帮助的。
接下来,让我们看看您可以在哪里找到预训练的 ONNX 模型,以尝试其中一些。
ONNX 模型动物园
模型动物园 在讨论 ONNX 模型时经常被提及。尽管它通常被描述为一个准备好使用的 ONNX 模型注册表,但它主要是一个GitHub 上的信息库,其中包含社区贡献并在库中策划的几个预训练模型的链接。这些模型分为三类:视觉、语言和其他。如果您想要开始使用 ONNX 并进行一些推理,模型动物园是您可以前往的地方。
在“ML 模型打包”中,我使用了模型动物园中的RoBERTa-SequenceClassification模型。因为我想要在 Azure 中注册,所以我需要添加一些信息,比如 ONNX 运行时版本。图 10-3 显示了针对该特定模型在模型动物园中的所有内容。

图 10-3. 模型动物园
除了版本和大小信息外,页面通常还会提供一些关于如何与模型交互的示例,这对于快速创建概念验证至关重要。关于这些文档页面,我认为值得注意的另一点是获取来源(模型的真实来源)。在RoBERTa-SequenceClassification模型的情况下,它起源于PyTorch RoBERTa,然后转换为 ONNX 格式,并最终在模型动物园中提供。
弄清楚模型的来源和工作源头的重要性并不是显而易见的。每当需要进行更改或发现需要解决的问题时,最好准备好准确指出真相源头,以便可以放心地进行任何需要修改的操作。在我担任大型开源项目的发布经理时,我负责为不同的 Linux 发行版构建 RPM 和其他类型的软件包。有一天,生产库损坏了,我被要求重建这些软件包。在重建过程中,我找不到哪个脚本、流水线或 CI 平台生成了几十个这些软件包中包含的一个软件包。
在追踪找到那个软件包来源的各种步骤之后,我发现一个脚本正在从开发者的家目录(该开发者早已离开公司)中下载它,而这台服务器与构建软件包毫无关系。一个单独的文件坐落在一个与构建系统无关的服务器的家目录中是一个定时炸弹。我无法确定软件包的来源,如何对其进行任何更新,或者它以这种方式需要被包含的原因。这些情况并不少见。您必须准备好使一切井井有条,并且在确定生产管道中所有元素的真实来源时有一个坚实的答案。
当您从像模型动物园这样的地方采集模型时,请确保尽可能多地捕获信息,并将其包含在这些模型的目的地中。Azure 提供了几个字段供您在注册模型时使用此目的。正如您将在以下章节中看到的那样,一些模型转换器允许添加元数据。利用这看似不重要的任务可能对调试生产问题至关重要。两个有益的实践已经减少了调试时间,并加快了入职和维护的便利性,即使用有意义的名称和尽可能多的元数据。使用有意义的名称对于识别和提供清晰度至关重要。注册为“production-model-1”的模型并不告诉我它是什么或它是关于什么的。如果您配对此名称没有额外的元数据或信息,这将导致在弄清楚生产问题时引起沮丧和延迟。
将 PyTorch 转换为 ONNX
从不同框架开始始终令人生畏,即使底层任务是从数据集中训练模型。PyTorch 在包含可以快速帮助您入门的预训练模型方面表现出色,因为您可以尝试框架的不同方面,而无需处理数据集的策划和训练方法。许多其他框架(如 TensorFlow 和 scikit-learn)也在做同样的事情,这是一个很好的学习起步方式。在本节中,我使用 PyTorch 的预训练视觉模型,然后将其导出到 ONNX。
创建一个新的虚拟环境和一个类似下面的requirements.txt文件:
numpy==1.20.1
onnx==1.8.1
Pillow==8.1.2
protobuf==3.15.6
six==1.15.0
torch==1.8.0
torchvision==0.9.0
typing-extensions==3.7.4.3
安装依赖项,然后创建一个convert.py文件来首先生成 PyTorch 模型:
import torch
import torchvision
dummy_tensor = torch.randn(8, 3, 200, 200)
model = torchvision.models.resnet18(pretrained=True)
input_names = [ "input_%d" % i for i in range(12) ]
output_names = [ "output_1" ]
torch.onnx.export(
model,
dummy_tensor,
"resnet18.onnx",
input_names=input_names,
output_names=output_names,
opset_version=7,
verbose=True,
)
让我们逐步了解 Python 脚本执行的一些步骤,以生成一个 ONNX 模型。它创建一个使用三个通道填充随机数的张量(对于预训练模型至关重要)。接下来,我们使用torchvision库检索resnet18预训练模型。我定义了一些输入和输出,最后使用所有这些信息导出模型。
示例用例过于简单,只是为了证明一个观点。导出的模型一点也不健壮,充满了毫无意义的虚拟值。它的目的是展示 PyTorch 如何以一种简单的方式将模型导出到 ONNX。转换器作为框架的一部分确实让人放心,因为它负责确保这一过程完美无缺。尽管存在单独的转换器库和项目,我更喜欢像 PyTorch 这样提供转换功能的框架。
注意
export()函数中的opset_version参数至关重要。PyTorch 的张量索引可能会导致不支持的 ONNX opset 版本问题。某些索引器类型仅支持版本 12(最新版本)。始终双重检查版本是否符合您需要的支持功能。
运行convert.py脚本,它将创建一个resnet18.onnx文件。您应该看到类似于此的输出:
$ python convert.py
graph(%learned_0 : Float(8, 3, 200, 200, strides=[120000, 40000, 200, 1],
requires_grad=0, device=cpu),
%fc.weight : Float(1000, 512, strides=[512, 1], requires_grad=1, device=cpu),
%fc.bias : Float(1000, strides=[1], requires_grad=1, device=cpu),
%193 : Float(64, 3, 7, 7, strides=[147, 49, 7, 1], requires_grad=0,
现在,通过使用 PyTorch 脚本生成的 ONNX 模型,让我们使用 ONNX 框架验证生成的模型是否兼容。创建一个名为check.py的新脚本:
import onnx
# Load the previously created ONNX model
model = onnx.load("resnet18.onnx")
onnx.checker.check_model(model)
print(onnx.helper.printable_graph(model.graph))
从包含resnet18.onnx的同一目录中运行check.py脚本,并验证输出与此类似:
$ python check.py
graph torch-jit-export (
%learned_0[FLOAT, 8x3x200x200]
) optional inputs with matching initializers (
%fc.weight[FLOAT, 1000x512]
[...]
%189 = GlobalAveragePool(%188)
%190 = Flattenaxis = 1
%output_1 = Gemmalpha = 1, beta = 1, transB = 1
return %output_1
}
从对check_model()函数的调用的验证不应产生任何错误,证明了转换具有一定程度的正确性。为了确保转换模型的正确性,需要评估推理过程,捕捉任何可能的漂移。如果您不确定使用哪些指标或如何创建稳固的比较策略,请查看“模型监控基础”。接下来,让我们看看如何在命令行工具中使用相同的检查模式。
创建一个通用的 ONNX 检查器
现在我已经详细介绍了从 PyTorch 导出模型到 ONNX 并进行验证的细节,让我们创建一个简单且通用的工具,可以验证任何 ONNX 模型,而不仅仅是特定的模型。虽然我们会在下一章节中专门讨论构建强大的命令行工具(尤其是见“命令行工具”),但我们仍然可以尝试构建一个适用于此用例的工具。另一个概念来自 DevOps 和我作为系统管理员的经验,就是尽可能地自动化,并从最简单的问题开始。例如,我不会使用任何命令行工具框架或高级解析器。
首先,创建一个名为onnx-checker.py的新文件,其中包含一个名为main()的函数:
def main():
help_menu = """
A command line tool to quickly verify ONNX models using
check_model()
"""
print(help_menu)
if __name__ == '__main__':
main()
运行脚本,输出应该显示帮助菜单:
$ python onnx-checker.py
A command line tool to quickly verify ONNX models using
check_model()
脚本目前还没有做任何特殊操作。它使用main()函数生成帮助菜单,并且在 Python 终端执行脚本时,还使用 Python 中广泛使用的方法来调用特定函数。接下来,我们需要处理任意输入。命令行工具框架可以帮助解决这个问题,毫无疑问,但我们仍然可以通过最小的努力获得有价值的东西。为了检查脚本的参数(我们需要这些参数来知道要检查哪个模型),我们需要使用sys.argv模块。更新脚本,使其导入该模块并将其传递给函数:
import sys
def main(arguments):
help_menu = """
A command line tool to quickly verify ONNX models using
check_model()
"""
if "--help" in arguments:
print(help_menu)
if __name__ == '__main__':
main(sys.argv)
这一变化将导致脚本仅在使用--help标志时输出帮助菜单。脚本目前还没有执行任何有用的操作,所以让我们再次更新main()函数,以包括 ONNX 检查功能:
import sys
import onnx
def main(arguments):
help_menu = """
A command line tool to quickly verify ONNX models using
check_model()
"""
if "--help" in arguments:
print(help_menu)
sys.exit(0)
model = onnx.load(arguments[-1])
onnx.checker.check_model(model)
print(onnx.helper.printable_graph(model.graph))
函数有两个关键的变化。首先,在检查帮助菜单后现在调用sys.exit(0)以防止执行下一块代码。接下来,如果未满足帮助条件,则使用最后一个参数(无论是什么)作为要检查的模型路径。最后,使用来自 ONNX 框架的相同函数对模型进行检查。请注意,完全没有对输入进行清理或验证。这是一个非常脆弱的脚本,但如果你运行它,它仍然会证明是有用的:
$ python onnx-checker.py ~/Downloads/roberta-base-11.onnx
graph torch-jit-export (
%input_ids[INT64, batch_sizexseq_len]
) initializers (
%1621[FLOAT, 768x768]
%1622[FLOAT, 768x768]
%1623[FLOAT, 768x768]
[...]
%output_2 = Tanh(%1619)
return %output_1, %output_2
}
我使用的路径是 RoBERTa 基础模型,它在我Downloads目录中的一个单独路径下。这种自动化是一个构建模块:尽可能简单地进行快速检查,以便稍后在其他自动化中使用,比如 CI/CD 系统或云提供商工作流中的流水线。现在我们已经尝试了一些模型,让我们看看如何将在其他流行框架中创建的模型转换为 ONNX。
将 TensorFlow 转换为 ONNX
有一个专门从 TensorFlow 转换模型到 ONNX 的项目,存放在 ONNX GitHub 仓库中。它支持广泛的 ONNX 和 TensorFlow 版本。再次强调,确保选择的工具包含你的模型所需的版本,以确保成功转换为 ONNX。
找到适合进行转换的正确项目、库或工具可能会变得棘手。特别是对于 TensorFlow,您可以使用onnxmltools,它具有onnxmltools.convert_tensorflow()函数,或者tensorflow-onnx项目,它有两种转换方式:使用命令行工具或使用库。
本节使用tensorflow-onnx项目与一个可以用作命令行工具的 Python 模块。该项目允许您从 TensorFlow 主要版本(1 和 2)、tflite 和 tf.keras 进行模型转换。由于它允许在规划转换策略时使用更灵活的 ONNX opset 支持(从版本 7 到 13),因此其广泛的支持非常出色。
在进行实际转换之前,值得探讨如何调用转换器。tf2onnx项目使用了一个 Python 快捷方式,从一个文件中公开命令行工具,而不是将命令行工具与项目一起打包。这意味着调用需要您使用 Python 可执行文件和特殊标志。首先,在新的虚拟环境中安装库。创建一个requirements.txt文件,以确保所有适合本示例的版本都能正常工作:
certifi==2020.12.5
chardet==4.0.0
flatbuffers==1.12
idna==2.10
numpy==1.20.1
onnx==1.8.1
protobuf==3.15.6
requests==2.25.1
six==1.15.0
tf2onnx==1.8.4
typing-extensions==3.7.4.3
urllib3==1.26.4
tensorflow==2.4.1
现在使用pip安装所有依赖项的固定版本:
$ pip install -r requirements.txt
Collecting tf2onnx
[...]
Installing collected packages: six, protobuf, numpy, typing-extensions,
onnx, certifi, chardet, idna, urllib3, requests, flatbuffers, tf2onnx
Successfully installed numpy-1.20.1 onnx-1.8.1 tf2onnx-1.8.4 ...
注意
如果您安装tf2onnx项目时没有requirements.txt文件,工具将无法工作,因为它未将tensorflow列为依赖项。在本节的示例中,我使用的是版本为 2.4.1 的tensorflow。确保安装它以防止依赖问题。
运行帮助菜单以查看可用内容。请记住,调用看起来有些不寻常,因为它需要 Python 可执行文件来使用它:
$ python -m tf2onnx.convert --help
usage: convert.py [...]
Convert tensorflow graphs to ONNX.
[...]
Usage Examples:
python -m tf2onnx.convert --saved-model saved_model_dir --output model.onnx
python -m tf2onnx.convert --input frozen_graph.pb --inputs X:0 \
--outputs output:0 --output model.onnx
python -m tf2onnx.convert --checkpoint checkpoint.meta --inputs X:0 \
--outputs output:0 --output model.onnx
出于简洁起见,我省略了帮助菜单的几个部分。调用帮助菜单是确保库在安装后可以加载的可靠方法。例如,如果未安装tensorflow,则这是不可能的。我留下了帮助菜单中的三个示例,因为根据您执行的转换类型,您将需要这些示例。除非您对您尝试转换的模型的内部结构有很好的理解,否则这些转换都不会直截了当。让我们从不需要模型知识的转换开始,使转换能够即插即用。
首先,从tfhub下载ssd_mobilenet_v2模型(压缩成tar.gz文件)。然后创建一个目录并在那里解压:
$ mkdir ssd
$ cd ssd
$ mv ~/Downloads/ssd_mobilenet_v2_2.tar.gz .
$ tar xzvf ssd_mobilenet_v2_2.tar.gz
x ./
x ./saved_model.pb
x ./variables/
x ./variables/variables.data-00000-of-00001
x ./variables/variables.index
现在模型已解压到一个目录中,请使用tf2onnx转换工具将ssd_mobilenet转换到 ONNX。确保您使用 opset 13,以防止模型的不兼容特性。这是一个缩短的异常回溯,您可能会在指定不支持的 opset 时遇到:
File "/.../.../tf2onnx/tfonnx.py", line 294, in tensorflow_onnx_mapping
func(g, node, **kwargs, initialized_tables=initialized_tables, ...)
File "/.../.../tf2onnx/onnx_opset/tensor.py", line 1130, in version_1
k = node.inputs[1].get_tensor_value()
File "/.../.../tf2onnx/graph.py", line 317, in get_tensor_value
raise ValueError("get tensor value: '{}' must be Const".format(self.name))
ValueError: get tensor value:
'StatefulPartitionedCall/.../SortByField/strided_slice__1738' must be Const
使用--saved-model标志与模型提取路径,最终使转换工作。在这种情况下,我正在使用 opset 13:
$ python -m tf2onnx.convert --opset 13 \
--saved-model /Users/alfredo/models/ssd --output ssd.onnx
2021-03-24 - WARNING - '--tag' not specified for saved_model. Using --tag serve
2021-03-24 - INFO - Signatures found in model: [serving_default].
2021-03-24 - INFO - Using tensorflow=2.4.1, onnx=1.8.1, tf2onnx=1.8.4/cd55bf
2021-03-24 - INFO - Using opset <onnx, 13>
2021-03-24 - INFO - Computed 2 values for constant folding
2021-03-24 - INFO - folding node using tf type=Select,
name=StatefulPartitionedCall/Postprocessor/.../Select_1
2021-03-24 - INFO - folding node using tf type=Select,
name=StatefulPartitionedCall/Postprocessor/.../Select_8
2021-03-24 - INFO - Optimizing ONNX model
2021-03-24 - INFO - After optimization: BatchNormalization -53 (60->7), ...
Successfully converted TensorFlow model /Users/alfredo/models/ssd to ONNX
2021-03-24 - INFO - Model inputs: ['input_tensor:0']
2021-03-24 - INFO - Model outputs: ['detection_anchor_indices', ...]
2021-03-24 - INFO - ONNX model is saved at ssd.onnx
这些示例可能看起来过于简单,但这里的想法是,这些是构建模块,以便您可以通过了解在转换中可能发生的情况进一步探索自动化。现在我已经演示了转换 TensorFlow 模型所需的内容,让我们看看转换tflite模型所需的内容,这是tf2onnx支持的另一种类型之一。
从tfhub下载mobilenet模型的量化版本。tf2onnx中的tflite支持使调用略有不同。这是一个工具创建的案例,遵循一种标准(将 TensorFlow 模型转换为 ONNX),然后不得不支持其他不完全符合相同模式的东西。在这种情况下,您必须使用--tflite标志,该标志应指向已下载的文件:
$ python -m tf2onnx.convert \
--tflite ~/Downloads/mobilenet_v2_1.0_224_quant.tflite \
--output mobilenet_v2_1.0_224_quant.onnx
我再次运行命令时很快就遇到了麻烦,因为支持的操作集与默认设置不匹配。此外,这个模型是量化的,这是转换器必须解决的另一层。以下是尝试过程中的另一个简短的回溯摘录:
File "/.../.../tf2onnx/tfonnx.py", line 294, in tensorflow_onnx_mapping
func(g, node, **kwargs, initialized_tables=initialized_tables, dequantize)
File "/.../.../tf2onnx/tflite_handlers/tfl_math.py", line 96, in version_1
raise ValueError
ValueError: \
Opset 10 is required for quantization.
Consider using the --dequantize flag or --opset 10.
至少这次错误提示表明模型已量化,并且我应考虑使用不同的操作集(与默认操作集相比,显然不起作用)。
提示
不同的 TensorFlow 操作对 ONNX 的支持有所不同,如果使用不正确的版本可能会造成问题。在尝试确定使用的正确版本时,tf2onnx 支持状态页面可能非常有用。
当书籍或演示始终表现完美时,我通常会非常怀疑。回溯、错误以及陷入麻烦都有很大的价值——这在我试图使tf2onnx正常工作时发生。如果本章的示例向您展示一切“毫不费力”,您无疑会认为存在重大的知识差距,或者工具失灵,没有机会理解为什么事情没有完全按计划进行。我添加这些回溯和错误,因为tf2onnx具有更高的复杂度,使我可以轻易陷入破碎状态。
让我们修复调用,并为其设置一个操作集为 13(目前支持的最高偏移量),然后再试一次:
$ python -m tf2onnx.convert --opset 13 \
--tflite ~/Downloads/mobilenet_v2_1.0_224_quant.tflite \
--output mobilenet_v2_1.0_224_quant.onnx
2021-03-23 INFO - Using tensorflow=2.4.1, onnx=1.8.1, tf2onnx=1.8.4/cd55bf
2021-03-23 INFO - Using opset <onnx, 13>
2021-03-23 INFO - Optimizing ONNX model
2021-03-23 INFO - After optimization: Cast -1 (1->0), Const -307 (596->289)...
2021-03-23 INFO - Successfully converted TensorFlow model \
~/Downloads/mobilenet_v2_1.0_224_quant.tflite to ONNX
2021-03-23 INFO - Model inputs: ['input']
2021-03-23 INFO - Model outputs: ['output']
2021-03-23 INFO - ONNX model is saved at mobilenet_v2_1.0_224_quant.onnx
最后,量化的tflite模型被转换为 ONNX。还有改进的空间,就像我们在本节的先前步骤中看到的那样,深入了解模型的输入和输出以及模型的创建方式至关重要。在转换时,这种知识是至关重要的,您可以尽可能为工具提供更多信息,以确保成功的结果。现在我已经将一些模型转换为 ONNX,让我们看看如何在 Azure 上部署它们。
部署 ONNX 到 Azure
Azure 在其平台上具有非常好的 ONNX 集成,直接在其 Python SDK 中提供支持。您可以创建一个实验来训练一个使用 PyTorch 的模型,然后将其导出为 ONNX。正如我将在本节中展示的,您可以将该 ONNX 模型部署到集群以进行实时推理。本节不涵盖如何执行模型的实际训练;然而,我将解释如何简单地使用在 Azure 中注册的训练好的 ONNX 模型,并将其部署到集群。
在“打包 ML 模型”中,我详细介绍了将模型导入 Azure 并将其打包到容器中所需的所有细节。让我们重用一些容器代码来创建评分文件,这是 Azure 部署为 Web 服务所需的。本质上,它是相同的:脚本接收请求,知道如何转换输入以使用加载的模型进行预测,然后返回值。
注意
这些示例使用 Azure 的工作空间,定义为ws对象。在开始之前必须对其进行配置。详细内容请参见“Azure CLI 和 Python SDK”。
创建评分文件,称为score.py,并添加一个init()函数来加载模型:
import torch
import os
import numpy as np
from transformers import RobertaTokenizer
import onnxruntime
def init():
global session
model = os.path.join(
os.getenv("AZUREML_MODEL_DIR"), "roberta-sequence-classification-9.onnx"
)
session = onnxruntime.InferenceSession(model)
现在基本的评分脚本已经处理完毕,在 Azure 中运行时需要一个run()函数。通过创建run()函数来更新score.py脚本,使其能够与 RoBERTa 分类模型进行交互:
def run(input_data_json):
try:
tokenizer = RobertaTokenizer.from_pretrained("roberta-base")
input_ids = torch.tensor(
tokenizer.encode(input_data_json[0], add_special_tokens=True)
).unsqueeze(0)
if input_ids.requires_grad:
numpy_func = input_ids.detach().cpu().numpy()
else:
numpy_func = input_ids.cpu().numpy()
inputs = {session.get_inputs()[0].name: numpy_func(input_ids)}
out = session.run(None, inputs)
return {"result": np.argmax(out)}
except Exception as err:
result = str(err)
return {"error": result}
接下来,创建执行推理的配置。由于这些示例使用 Python SDK,您可以在 Jupyter Notebook 中使用它们,或者直接在 Python shell 中使用。首先创建一个描述您环境的 YAML 文件:
from azureml.core.conda_dependencies import CondaDependencies
environ = CondaDependencies.create(
pip_packages=[
"numpy","onnxruntime","azureml-core", "azureml-defaults",
"torch", "transformers"]
)
with open("environ.yml","w") as f:
f.write(environ.serialize_to_string())
YAML 文件完成后,设置配置如下:
from azureml.core.model import InferenceConfig
from azureml.core.environment import Environment
environ = Environment.from_conda_specification(
name="environ", file_path="environ.yml"
)
inference_config = InferenceConfig(
entry_script="score.py", environment=environ
)
最后,使用 SDK 部署模型。首先创建 Azure 容器实例 (ACI) 配置:
from azureml.core.webservice import AciWebservice
aci_config = AciWebservice.deploy_configuration(
cpu_cores=1,
memory_gb=1,
tags={"model": "onnx", "type": "language"},
description="Container service for the RoBERTa ONNX model",
)
使用 aci_config 部署服务:
from azureml.core.model import Model
# retrieve the model
model = Model(ws, 'roberta-sequence', version=1)
aci_service_name = "onnx-roberta-demo"
aci_service = Model.deploy(
ws, aci_service_name,
[model],
inference_config,
aci_config
)
aci_service.wait_for_deployment(True)
为了使部署工作,需要完成几件事情。首先,您定义了环境及所需的推理依赖项。然后配置了 Azure 容器实例,最后检索并使用 SDK 中的Model.deploy()方法部署了roberta_sequence ONNX 模型的版本 1。再次强调,本文未涉及模型的具体训练细节。在 Azure 中,您可以训练任何模型,导出为 ONNX,注册后可以继续使用本节进行部署过程。这些示例只需进行少量修改即可取得进展。可能需要使用不同的库,肯定需要不同的方式与模型交互。然而,这个工作流程使您能够从 PyTorch 自动化地将模型部署到 Azure 中的容器实例中,使用 ONNX(或直接使用先前注册的 ONNX 模型)。
在某些其他情况下,您将希望使用 ONNX 部署到其他非云环境,如移动设备。我在下一节中介绍了一些涉及苹果机器学习框架的详细信息。
Apple Core ML
苹果的机器学习框架在某种程度上是独特的,因为它支持将 Core ML 模型转换为 ONNX,同时也支持 将它们从 ONNX 转换为 Core ML。正如我在本章中已经提到的,您必须非常小心,并确保模型转换和版本获取的支持。目前,coremltools 支持 ONNX opset 版本 10 及更新版本。很容易陷入不支持模型和断裂发生的情况。除了 ONNX 支持之外,您必须了解目标转换环境以及该环境是否支持 iOS 和 macOS 发布版。
提示
参见“不同环境中支持的最低目标”,在 Core ML 文档中有支持。
除了在像 iOS 这样的目标环境中的支持之外,还有一个已知良好工作的 ONNX 测试模型的良好列表。从这个列表中,我将挑选 MNIST 模型尝试转换。去模型动物园找到 MNIST 部分。下载 最新版本(在我这里是 1.3)。现在创建一个新的虚拟环境和包含以下库的 requirements.txt,其中包括 coremltools:
attr==0.3.1
attrs==20.3.0
coremltools==4.1
mpmath==1.2.1
numpy==1.19.5
onnx==1.8.1
packaging==20.9
protobuf==3.15.6
pyparsing==2.4.7
scipy==1.6.1
six==1.15.0
sympy==1.7.1
tqdm==4.59.0
typing-extensions==3.7.4.3
安装依赖项,以便我们可以创建工具来进行转换。让我们创建尽可能简单的工具,就像在“创建通用 ONNX 检查器” 中一样,没有参数解析器或任何花哨的帮助菜单。从创建 main() 函数和文件末尾所需的特殊 Python 魔法开始,这样你就可以将其作为脚本在终端中调用:
import sys
from coremltools.converters.onnx import convert
def main(arguments):
pass
if __name__ == '__main__':
main(sys.argv)
在这种情况下,脚本尚未执行任何有用的操作,我还跳过了实现帮助菜单。您应始终在脚本中包含帮助菜单,以便他人在需要与程序交互时了解输入和输出。更新 main() 函数以尝试转换。我假设接收到的最后一个参数将代表需要转换的 ONNX 模型的路径:
def main(arguments):
model_path = arguments[-1]
basename = model_path.split('.onnx')[0]
model = convert(model_path, minimum_ios_deployment_target='13')
model.short_description = "ONNX Model converted with coremltools"
model.save(f"{basename}.mlmodel")
首先,该函数捕获最后一个参数作为 ONNX 模型的路径,然后通过去除 .onnx 后缀来计算基本名称。最后,它通过转换(使用最低 iOS 版本目标为 13)进行转换,包括描述,并保存输出。尝试使用先前下载的 MNIST 模型更新脚本:
$ python converter.py mnist-8.onnx
1/11: Converting Node Type Conv
2/11: Converting Node Type Add
3/11: Converting Node Type Relu
4/11: Converting Node Type MaxPool
5/11: Converting Node Type Conv
6/11: Converting Node Type Add
7/11: Converting Node Type Relu
8/11: Converting Node Type MaxPool
9/11: Converting Node Type Reshape
10/11: Converting Node Type MatMul
11/11: Converting Node Type Add
Translation to CoreML spec completed. Now compiling the CoreML model.
Model Compilation done.
最终操作应生成一个 mnist-8.mlmodel 文件,这是一个 Core ML 模型,您现在可以在安装了 XCode 的 macOS 计算机上加载。在您的 Apple 计算机上使用 Finder,双击新生成的 coreml 模型,然后再次双击。MNIST 模型应立即加载,并包含在转换器脚本中描述的内容,如 图 10-4 所示。

图 10-4. ONNX 到 Core ML
确认可用性部分显示的最低 iOS 目标为 13,就像转换器脚本设定的那样。预测部分提供了有关模型接受的输入和输出的有用信息,如 图 10-5 所示。

图 10-5. ONNX 到 CoreML 预测
最后,Utilities 部分提供了一些辅助工具,以使用模型存档部署该模型,该存档与 CloudKit(Apple 的 iOS 应用资源环境)集成,如 图 10-6 所示。

图 10-6. ONNX 转 Core ML 模型存档
看到 ONNX 在这种情况下对 OSX 以及其他框架和操作系统的广泛支持是令人兴奋的。如果您对 iOS 开发和部署模型感兴趣,您仍然可以使用您习惯的框架。然后,您可以将其转换为目标 iOS 环境进行部署。这个过程使得使用 ONNX 具有了强有力的理由,因为它允许您利用成熟的框架,并将其转换为目标环境。接下来,让我们看看一些其他边缘集成,进一步强调 ONNX 框架和工具的实用性。
边缘集成
最近,ONNX 宣布了一种名为 ORT 的新内部模型格式,它最小化了模型的构建大小,以便在嵌入式或边缘设备上进行最佳部署。模型的较小占用空间具有几个方面,有助于边缘设备。边缘设备的存储容量有限,并且大多数情况下,存储速度并不快。对小存储设备进行读写操作可能很快变得困难。此外,总体上,ONNX 继续努力支持更多和不同的硬件配置,包括各种 CPU 和 GPU;这不是一个容易解决的问题,但确实是一种受欢迎的努力。支持越广泛和更好,就越容易将机器学习模型部署到以前不可能部署的环境中。由于我已经在 “边缘设备” 中涵盖了大部分边缘的利益和关键方面,本节将集中讨论将 ONNX 模型转换为 ORT 格式的操作。
首先创建一个新的虚拟环境,激活它,然后按照 requirements.txt 文件中显示的步骤安装依赖项:
flatbuffers==1.12
numpy==1.20.1
onnxruntime==1.7.0
protobuf==3.15.6
six==1.15.0
目前没有可用于安装的单独工具,因此需要克隆整个onnxruntime存储库来尝试将其转换为 ORT。克隆后,您将使用tools/python目录中的convert_onnx_models_to_ort.py文件:
$ git clone https://github.com/microsoft/onnxruntime.git
$ python onnxruntime/tools/python/convert_onnx_models_to_ort.py --help
usage: convert_onnx_models_to_ort.py [-h]
[...]
Convert the ONNX format model/s in the provided directory to ORT format models.
All files with a `.onnx` extension will be processed. For each one, an ORT
format model will be created in the same directory. A configuration file will
also be created called `required_operators.config`, and will contain the list
of required operators for all converted models. This configuration file should
be used as input to the minimal build via the `--include_ops_by_config`
parameter.
[...]
ORT 转换器会从 ONNX 模型生成配置文件以及 ORT 模型文件。您可以将优化后的模型与独特的 ONNX 运行时构建一起部署。首先,尝试使用一个 ONNX 模型进行转换。在此示例中,我下载了mobilenet_v2-1.0 ONNX 模型到models/目录,并将其作为转换脚本的参数使用:
$ python onnxruntime/tools/python/convert_onnx_models_to_ort.py \
models/mobilenetv2-7.onnx
Converting optimized ONNX model to ORT format model models/mobilenetv2-7.ort
Processed models/mobilenetv2-7.ort
Created config in models/mobilenetv2-7.required_operators.config
在这里配置至关重要,因为它列出了模型所需的运算符。这样可以创建一个只包含这些运算符的 ONNX 运行时。对于转换后的模型,该文件看起来像这样:
ai.onnx;1;Conv,GlobalAveragePool
ai.onnx;5;Reshape
ai.onnx;7;Add
com.microsoft;1;FusedConv
您可以通过仅指定模型需求来减少运行时的二进制大小。我不会详细介绍如何从源代码构建 ONNX 运行时的所有具体内容,但是您可以将构建指南作为下一步的参考,因为我们将探索二进制减少中有用的标志和选项。
首先,您必须使用--include_ops_by_config标志。在本例中,此标志的值是从上一步生成的配置文件路径。在我的情况下,该路径是models/mobilenetv2-7.required_operators.config。我还建议您尝试使用--minimal_build,该选项仅支持加载和执行 ORT 模型(放弃对普通 ONNX 格式的支持)。最后,如果您的目标是 Android 设备,则使用--android_cpp_shared标志将通过使用共享的libc++库生成更小的二进制文件,而不是默认的静态库。
结论
像 ONNX 这样的框架(和一组工具)提供的共性有助于整个机器学习生态系统。本书的一个理念是提供实际的例子,以便以可重复和可靠的方式将模型投入生产。对于机器学习模型的支持越全面,工程师们尝试和利用机器学习所提供的一切就越容易。我特别对边缘应用感兴趣,尤其是对于无法连接到互联网或没有任何网络连接的远程环境。ONNX 正在降低在这些环境中部署的摩擦。我希望通过更多的工具和更好的支持持续努力,继续从集体知识和贡献中受益。虽然我们简要尝试了命令行工具,但在下一章中,我会更详细地介绍如何制作具有错误处理和定义良好标志的健壮命令行工具。此外,我还涵盖了 Python 打包和使用微服务,这将使您在解决机器学习挑战时可以尝试不同的方法。
练习
-
更新所有脚本,使用“创建一个通用 ONNX 检查器”中的脚本验证生成的 ONNX 模型。
-
修改 Core ML 转换器脚本,使用 Click 框架以更好地解析选项并提供帮助菜单。
-
将三个转换器分组到一个单一的命令行工具中,以便可以使用不同的输入进行转换。
-
改进tf2onnx转换器,使其包装在一个新脚本中,可以捕捉常见错误并用更用户友好的消息报告它们。
-
使用不同的 ONNX 模型进行 Azure 部署。
批判性思维讨论问题
-
ONNX 为何重要?至少给出三个理由。
-
创建一个没有命令行工具框架的脚本有什么用?使用框架的优点是什么?
-
ORT 格式有什么用处?在什么情况下可以使用它?
-
如果不存在可移植性,可能会遇到哪些问题?列出三个改善这些问题将如何改进机器学习的原因。
第十一章:构建 MLOps 命令行工具和微服务
Alfredo Deza
1941 年 12 月 7 日,日本轰炸了珍珠港,立即导致购买轮胎变得不可能,显然需要长途驾驶去加利福尼亚州。我父亲在全国四处寻找旧轮胎给福特车和我们小型房车使用,我们曾多次用它进行短途家庭旅行。1942 年 2 月,我们离开辛辛那提,车顶和房车顶上绑着 22 个轮胎,在抵达加利福尼亚之前,我们都用光了这些轮胎。
Joseph Bogen 博士
多年前,我开始使用 Python 时是通过构建命令行工具,我认为这是软件开发和机器学习的完美交集。我记得多年前,我作为系统管理员努力学习新的 Python 概念时感到非常陌生:函数、类、日志记录和测试。作为系统管理员,我主要接触到使用 Bash 编写从上到下的指令来完成任务。
使用 shell 脚本尝试解决问题存在几个困难。处理错误并进行直接报告、记录和调试,这些特性在像 Python 这样的其他语言中并不需要花费太多精力。其他几种语言如 Go 和 Rust 也提供了类似的功能,这就暗示了为什么使用非命令语言如 Bash 是个好主意。我倾向于推荐在几行代码就能完成任务时使用 shell 脚本语言。例如,这个 shell 函数会将我的公共 SSH 密钥复制到远程机器的授权密钥中,从而允许我的账号无需密码即可访问该远程服务器:
ssh-copy-key() {
if [ $2 ]; then
key=$2
else
key="$HOME/.ssh/id_rsa.pub"
fi
cat "$key" | ssh ${1} \
'umask 0077; mkdir -p .ssh; cat >> .ssh/authorized_keys'
}
注意
这个示例 Bash 函数可能在你的环境中表现不佳。如果你想尝试,路径和配置文件需要匹配。
使用 Python 等语言做同样的事情可能没有多大意义,因为它可能需要更多的代码行和可能还要安装一些额外的依赖。这个函数很简单,只做一件事,而且非常易于移植。我建议,当解决方案超过十几行时,不要仅限于使用 shell 脚本语言。
提示
如果你经常创建小的 shell 命令片段,建议创建一个存储库来收集它们。这个存储库的别名、函数和配置应该能为你提供一个很好的参考起点。
我学习 Python 的方法是通过使用命令行工具自动化繁琐重复的任务,从创建客户网站使用模板到在公司服务器上添加和删除用户。我建议你投入学习的方法是找到一个有直接利益的有趣问题。这种学习方式与我们在学校学习的方式完全不同,它并不一定适用于任何情况,但非常适合本章内容。
根据您创建命令行工具的方式,它们可以很容易地安装。例如,一个应该在任何 Unix 环境中轻松运行的 shell 命令的示例,它与容器和微服务具有相似性。因为容器将依赖项捆绑在一起,所以在启用适当运行时的任何系统中都可以正常工作。我们已经介绍了一些“容器”中的微服务组件,描述了与单片应用程序的一些关键区别。
但是微服务不仅限于容器,几乎所有云提供商都提供了无服务器解决方案。无服务器允许开发者专注于编写小型应用程序,而不必担心底层操作系统、其依赖项或运行时。尽管这种提供可能显得过于简单,但你可以利用这个解决方案来创建完整的 HTTP API 或类似管道的工作流。你可以将所有这些组件和技术与命令行以及云提供商的某些 ML 特性联系起来。这些技术的混合搭配意味着工程师可以用很少的代码为棘手的问题创造出创新的解决方案。每当你能自动化任务并增强生产力时,你都在应用扎实的运营技能来帮助你稳健地将模型投入到生产中。
Python 打包
许多有用的 Python 命令行工具最初都是一个单独的脚本文件,然后随着时间推移,它们往往会发展成更复杂的场景,涉及其他文件和可能的依赖项。直到脚本需要额外的库才不再将脚本保持不打包成为不可行的情况。Python 的打包并不是很好。这让我痛苦地说,经过十多年的 Python 经验之后,但是打包仍然是语言生态系统中充满棘手(未解决)问题的一个方面。
如果你正在尝试一些自动化且没有外部依赖的操作,那么一个单独的 Python 脚本就可以了,无需打包。然而,如果你的脚本需要其他依赖项并且可能由多个文件组成,那么无疑应该考虑打包。一个正确打包的 Python 应用程序的另一个有用特性是它可以发布到Python 软件包索引,这样其他人就可以使用像pip(Python 的软件包安装器)这样的工具安装它。
几年前,在系统上安装 Python 包是一个问题:它们是无法移除的。这在今天听起来难以置信,但这是“虚拟环境”火起来的许多原因之一。有了虚拟环境,修复依赖关系就像删除一个目录一样容易——同时保持系统包的完整性。如今,卸载 Python 包更加容易(并且可能!),但依赖关系解决仍然缺乏鲁棒性。因此,虚拟环境被建议作为工作在 Python 项目上的方式,因此环境是完全隔离的,你可以通过创建一个新环境来解决依赖问题。
这本书(以及 Python 生态系统的其他地方)推荐使用virtualenv模块,这并不奇怪。自从 Python 3 开始,创建和激活虚拟环境的常用方法是直接使用Python可执行文件:
$ python -m venv venv
$ source venv/bin/activate
要验证虚拟环境是否已激活,Python 可执行文件现在应与系统 Python 不同:
$ which python
/tmp/venv/bin/python
我建议使用适当的 Python 打包技术,这样当需要时你就做好了充分的准备。一旦你的命令行工具需要一个依赖,它就已经准备好被声明为一个要求。你的工具的使用者也将解决这些依赖关系,使得其他人更容易使用你的创作。
Requirements 文件
正如你将在本章后面的部分看到的,定义依赖关系的两种流行方式之一是使用requirements.txt文件。安装程序工具可以使用这个文件像pip一样从包索引安装依赖项。在这个文件中,依赖关系在单独的行上声明,并且可选地带有一些版本约束。在这个例子中,Click 框架没有约束,因此安装程序(pip)将使用最新版本。Pytest 框架被固定到一个特定版本,所以pip将始终尝试在安装时找到该特定版本:
# requirements.txt
click
pytest==5.1.0
要从requirements.txt文件安装依赖项,你需要使用pip:
$ pip install -r requirements.txt
虽然没有严格的命名规则,但你通常可以在名为requirements.txt的纯文本文件中找到依赖项。项目维护者也可以定义多个包含依赖项的文本文件。例如,当开发依赖项与生产中的依赖项不同时,这种情况更为普遍。正如你将在下一节看到的那样,还有一个可以安装依赖项的setup.py文件。这是 Python 打包和依赖管理状态的一个不幸副作用。这两个文件都可以实现为 Python 项目安装依赖项的目标,但只有setup.py可以打包 Python 项目以进行分发。由于setup.py文件在 Python 安装时执行,它允许除安装任务以外的任何操作。我不建议扩展setup.py以执行除打包任务以外的任何操作,以避免在分发应用程序时出现问题。
一些项目更喜欢在requirements.txt文件中定义它们的依赖项,然后重复使用该文件的内容到setup.py文件中。你可以通过读取requirements.txt并使用dependencies变量来实现这一点:
with open("requirements.txt", "r") as _f:
dependencies = _f.readlines()
区分这些打包文件并了解它们的背景对于防止混淆和误用是有用的。现在,你应该更容易分辨出一个项目是用于分发(setup.py)还是一个不需要安装的服务或项目。
命令行工具
Python 语言的一个特性是能够快速创建几乎包括任何你可以想象的应用程序,从发送 HTTP 请求到处理文件和文本,再到对数据流进行排序。可用的库生态系统非常庞大。科学界已将 Python 视为解决包括机器学习在内的工作负载的顶级语言之一,这似乎并不令人意外。
开发命令行工具的一个优秀方法是识别需要解决的特定情况。下次遇到稍微重复的任务时,试着构建一个命令行工具来自动化生成结果的步骤。自动化是 DevOps 的另一个核心原则,你应该尽可能(并且在合理的情况下)将其应用于 ML 中的任务。虽然你可以创建一个单独的 Python 文件并将其用作命令行工具,但本节的示例将使用适当的打包技术,使你能够定义所需的依赖关系,并使用像pip这样的 Python 安装程序安装工具。在第一个示例工具中,我将详细展示这些 Python 模式,以便理解可以应用于本章其余部分的命令行工具背后的思想。
创建数据集检查器
在创建本书时,我决定组合一个葡萄酒评分和描述的数据集。我找不到类似的数据,所以开始收集这个数据集的信息。一旦数据集有了足够多的条目,下一步就是可视化信息并确定数据的健壮性。与初始数据状态一样,这个数据集呈现了几个需要正确识别的异常情况。
一个问题是,在将数据加载为 Pandas 数据帧之后,很明显其中一列是无法使用的:几乎所有条目都是 NaN(也称为空条目)。另一个可能是最糟糕的问题是,我将数据集加载到 Azure ML Studio 中执行一些自动 ML 任务时,结果开始出现一些令人惊讶的结果。尽管数据集有六列,但 Azure 报告大约有四十列。
最后,pandas 在保存处理后的数据时添加了未命名的列,而我并不知情。该数据集可用于演示问题。首先将 CSV(逗号分隔值)文件加载为 pandas 数据框:
import pandas as pd
csv_url = (
"https://raw.githubusercontent.com/paiml/wine-ratings/main/wine-ratings.csv"
)
# set index_col to 0 to tell pandas that the first column is the index
df = pd.read_csv(csv_url, index_col=0)
df.head(-10)
pandas 的表格输出看起来很棒,但暗示着可能有一列是空的:
name grape region variety rating notes
... ... ... ... ... ... ...
32765 Lewis Cella... NaN Napa Valley... White Wine 92.0 Neil Young'..
32766 Lewis Cella... NaN Napa Valley... White Wine 93.0 From the lo..
32767 Lewis Cella... NaN Napa Valley... White Wine 93.0 Think of ou..
32768 Lewis Cella... NaN Napa Valley... Red Wine 92.0 When asked ..
32769 Lewis Cella... NaN Napa Valley... White Wine 90.0 The warm, v..
[32770 rows x 6 columns]
在描述数据集时,其中一个问题显而易见:grape 列中没有任何项目:
In [13]: df.describe()
Out[13]:
grape rating
count 0.0 32780.000000
mean NaN 91.186608
std NaN 2.190391
min NaN 85.000000
25% NaN 90.000000
50% NaN 91.000000
75% NaN 92.000000
max NaN 99.000000
删除有问题的列,并将数据集保存到新的 CSV 文件中,这样您就可以在无需每次下载内容的情况下操作数据:
df.drop(['grape'], axis=1, inplace=True)
df.to_csv("wine.csv")
重新读取文件显示了 pandas 添加的额外列。为了重现这个问题,重新读取本地的 CSV 文件,将其另存为新文件,然后查看新创建文件的第一行:
df = pd.read_csv('wine.csv')
df.to_csv('wine2.csv')
查看 wine2.csv 文件的第一行以发现新列:
$ head -1 wine2.csv
,Unnamed: 0,name,region,variety,rating,notes
Azure 的问题更为复杂,而且很难检测:Azure ML 将其中一个列中的换行符和回车符解释为新列。要找到这些特殊字符,我不得不配置我的编辑器以显示它们(通常情况下它们是看不见的)。在这个例子中,回车符显示为 ^M:
"Concentrated aromas of dark stone fruits and toast burst^M
from the glass. Classic Cabernet Sauvignon flavors of^M
black cherries with subtle hints of baking spice dance^M
across the palate, bolstered by fine, round tannins. This^M
medium bodied wine is soft in mouth feel, yet long on^M
fruit character and finish."^M
在删除没有项目的列、删除未命名列并消除回车符后,数据现在处于更健康的状态。现在我已经尽到了清理的责任,我希望能自动捕捉这些问题。也许一年后我会忘记处理 Azure 中的额外列或者一个带有无用值的列。让我们创建一个命令行工具来导入 CSV 文件并生成一些警告。
创建一个名为 csv-linter 的新目录,并添加一个像这样的 setup.py 文件:
from setuptools import setup, find_packages
setup(
name = 'csv-linter',
description = 'lint csv files',
packages = find_packages(),
author = 'Alfredo Deza',
entry_points="""
[console_scripts]
csv-linter=csv_linter:main
""",
install_requires = ['click==7.1.2', 'pandas==1.2.0'],
version = '0.0.1',
url = 'https://github.com/paiml/practical-mlops-book',
)
此文件允许 Python 安装程序捕获 Python 包的所有详细信息,如依赖关系以及在本例中名为csv-linter的新命令行工具的可用性。setup 调用中的大多数字段都很简单,但值得注意的是 entry_points 的细节。这是 setuptools 库的一个特性,允许在 Python 文件中定义一个函数,将其映射回命令行工具名称。在这种情况下,我命名了命令行工具为csv-linter,并将其映射到稍后我将在名为 csv_linter.py 的文件中创建的一个名为 main 的函数。虽然我选择了csv-linter作为工具的名称,但它可以用任何名称命名。在幕后,setuptools 库将使用此处声明的任何内容创建可执行文件。命名与 Python 文件相同并无限制。
打开一个名为 csv_linter.py 的新文件,并添加一个使用 Click 框架的单个函数:
import click
@click.command()
def main():
return
注意
即使示例中没有明确提到使用 Python 的虚拟环境,创建一个总是一个好主意。拥有虚拟环境是隔离依赖项和可能与系统中安装的其他库存在问题的强大方式。
这两个文件几乎是创建一个命令行工具所需的全部内容(目前)仅提供在 shell 路径中可用的可执行文件。接下来,创建一个虚拟环境并激活它以安装新创建的工具:
$ python3 -m venv venv
$ source venv/bin/activate
$ python setup.py develop
running develop
running egg_info
...
csv-linter 0.0.1 is already the active version in easy-install.pth
...
Using /Users/alfredo/.virtualenvs/practical-mlops/lib/python3.8/site-packages
Finished processing dependencies for csv-linter==0.0.1
setup.py 脚本有许多不同的方式可以调用,但您主要将使用我在示例中使用的 install 参数或 develop 参数之一。使用 develop 允许您对脚本源代码进行更改,并使这些更改自动在脚本中可用,而 install 则会创建一个独立的(或独立的)脚本,与源代码没有关联。在开发命令行工具时,我建议使用 develop 快速测试进展中的更改。调用 setup.py 脚本后,通过传递 --help 标志来测试新可用的工具:
$ csv-linter --help
Usage: csv-linter [OPTIONS]
Options:
--help Show this message and exit.
在不必编写帮助菜单的情况下获得一个是非常棒的功能,而这是其他几个命令行工具框架提供的特性。现在,该工具作为终端中的脚本可用,是时候添加一些有用的功能了。为了保持简单,此脚本将接受一个 CSV 文件作为单个参数。Click 框架具有内置的帮助程序,用于接受文件作为参数,确保文件存在,否则会产生有用的错误。更新 csv_linter.py 文件以使用此帮助程序:
import click
@click.command()
@click.argument('filename', type=click.Path(exists=True))
def main():
return
虽然脚本尚未使用文件做任何事情,但帮助菜单已更新以反映选项:
$ csv-linter --help
Usage: csv-linter [OPTIONS] FILENAME
仍然,没有做什么有用的事情。检查如果传递一个不存在的 CSV 文件会发生什么:
$ csv-linter bogus-dataset.csv
Usage: csv-linter [OPTIONS] FILENAME
Try 'csv-linter --help' for help.
Error: Invalid value for 'FILENAME': Path 'bogus-dataset.csv' does not exist.
通过使用传递给 main() 函数的 filename 参数进一步使用 Pandas 描述数据集的工具:
import click
import pandas as pd
@click.command()
@click.argument('filename', type=click.Path(exists=True))
def main(filename):
df = pd.read_csv(filename)
click.echo(df.describe())
脚本使用了 Pandas,以及另一个名为echo的 Click 助手,它允许我们轻松地将输出打印回终端。使用在处理数据集时保存的wine.csv文件作为输入:
$ csv-linter wine.csv
Unnamed: 0 grape rating
count 32780.000000 0.0 32780.000000
mean 16389.500000 NaN 91.186608
std 9462.915248 NaN 2.190391
min 0.000000 NaN 85.000000
25% 8194.750000 NaN 90.000000
50% 16389.500000 NaN 91.000000
75% 24584.250000 NaN 92.000000
max 32779.000000 NaN 99.000000
尽管如此,这并不是太有帮助,即使现在可以轻松地使用 Pandas 描述任何 CSV 文件。我们需要解决的问题是警告我们三个潜在问题:
-
检测零计数列
-
当存在
Unnamed列时发出警告 -
检查字段中是否有换行符
让我们从检测零计数列开始。Pandas 允许我们迭代其列,并有一个我们可以利用的count()方法来实现这个目的:
In [10]: for key in df.keys():
...: print(df[key].count())
...:
...:
32780
0
32777
32422
32780
32780
将循环调整为一个单独的函数,与csv_linter.py文件中的main()函数隔离开,以保持代码的可读性:
def zero_count_columns(df):
bad_columns = []
for key in df.keys():
if df[key].count() == 0:
bad_columns.append(key)
return bad_columns
zero_count_columns()函数接受来自 Pandas 的数据框作为输入,捕获所有零计数的列,并在最后返回它们。它是隔离的,并且尚未与main()函数协调输出。因为它返回列名的列表,所以在main()函数中遍历结果的内容:
@click.command()
@click.argument('filename', type=click.Path(exists=True))
def main(filename):
df = pd.read_csv(filename)
# check for zero count columns
for column in zero_count_columns(df):
click.echo(f"Warning: Column '{column}' has no items in it")
对相同的 CSV 文件运行脚本(请注意,我已经删除了.describe()调用):
$ csv-linter wine-ratings.csv
Warning: Column 'grape' has no items in it
在 19 行时,如果在将数据发送到 ML 平台之前使用了这个脚本,它已经为我节省了大量时间。接下来,创建另一个函数,循环遍历列以检查Unnamed列:
def unnamed_columns(df):
bad_columns = []
for key in df.keys():
if "Unnamed" in key:
bad_columns.append(key)
return len(bad_columns)
在这种情况下,该函数检查名称中是否存在字符串"Unnamed",但不返回名称(因为我们假设它们都是相似的甚至相同的),而是返回总计数。有了这些信息,扩展main()函数以包含计数:
@click.command()
@click.argument('filename', type=click.Path(exists=True))
def main(filename):
df = pd.read_csv(filename)
# check for zero count columns
for column in zero_count_columns(df):
click.echo(f"Warning: Column '{column}' has no items in it")
unnamed = unnamed_columns(df)
if unnamed:
click.echo(f"Warning: found {unnamed} columns that are Unnamed")
再次运行工具,针对相同的 CSV 文件检查结果:
$ csv-linter wine.csv
Warning: Column 'grape' has no items in it
Warning: found 1 column that is Unnamed
最后,也许最难检测的是在大文本字段内查找换行符。这个操作可能会很昂贵,具体取决于数据集的大小。虽然有更高效的方法来完成迭代,但下一个示例将尝试使用最直接的方法。创建另一个函数,在 Pandas 数据框上执行这项工作:
def carriage_returns(df):
for index, row in df.iterrows():
for column, field in row.iteritems():
try:
if "\r\n" in field:
return index, column, field
except TypeError:
continue
这个循环防止了TypeError的产生。如果函数对不同类型(如整数)进行字符串检查,那么将会引发TypeError。由于该操作可能代价高昂,函数在第一个换行符的迹象出现时跳出循环。最后,循环返回索引、列和整个字段,以供main()函数报告。现在更新脚本,包括对换行符的报告:
@click.command()
@click.argument('filename', type=click.Path(exists=True))
def main(filename):
df = pd.read_csv(filename)
for column in zero_count_columns(df):
click.echo(f"Warning: Column '{column}' has no items in it")
unnamed = unnamed_columns(df)
if unnamed:
click.echo(f"Warning: found {unnamed} columns that are Unnamed")
carriage_field = carriage_returns(df)
if carriage_field:
index, column, field = carriage_field
click.echo((
f"Warning: found carriage returns at index {index}"
f" of column '{column}':")
)
click.echo(f" '{field[:50]}'")
测试这个最后一个检查是棘手的,因为数据集不再有换行符了。本章的存储库包含一个带有换行符的示例 CSV 文件。将该文件下载到本地,并将csv-linter工具指向该文件:
$ csv-linter carriage.csv
Warning: found carriage returns at index 0 of column 'notes':
'Aged in French, Hungarian, and American Oak barrel'
为了防止在输出中打印出一个极长的字段,警告消息只显示前 50 个字符。这个命令行工具利用了 Click 框架进行命令行工具功能和 Pandas 进行 CSV 检查。尽管它只进行了三次检查且性能不是很好,但它对我来说防止使用数据集时出现问题是非常宝贵的。确保数据集处于可接受状态还有多种其他方法,但这是一个很好的示例,展示了如何自动化(和预防)您遇到的问题。自动化是 DevOps 的基础,命令行工具是开始自动化路径的一个很好的方式。
将命令行工具模块化
之前的命令行工具展示了如何使用 Python 的内部库从单个 Python 文件创建脚本。但是完全可以使用包含多个文件的目录来创建一个命令行工具。当单个脚本的内容开始变得难以阅读时,这种方法更为可取。在什么情况下应该将一个长文件分割为多个文件没有明确的硬性限制;我建议在代码共享通用职责的情况下进行分组和分离,尤其是在需要代码重用时。在某些情况下,可能没有代码重用的用例,但是分割一些代码片段仍然有助于提高可读性和维护性。
让我们重复使用 csv-linter 工具的示例,将单文件脚本调整为目录中的多个文件。第一步是创建一个带有 init.py 文件的目录,并将 csv_linter.py 文件移动到其中。使用 init.py 文件告诉 Python 将该目录视为一个模块。现在的结构应该如下所示:
$ tree .
.
├── csv_linter
│ ├── __init__.py
│ └── csv_linter.py
├── requirements.txt
└── setup.py
1 directory, 4 files
到目前为止,在 Python 文件中不再需要重复工具的名称,因此将其重命名为更模块化且不那么与工具名称相关的内容是没有必要的。我通常建议使用 main.py,因此请将文件重命名为:
$ mv csv_linter.py main.py
$ ls
__init__.py main.py
再次尝试使用 csv_linter 命令。由于文件被移动了,工具现在应该处于一个破碎的状态:
$ csv-linter
Traceback (most recent call last):
File ".../site-packages/pkg_resources/__init__.py", line 2451, in resolve
return functools.reduce(getattr, self.attrs, module)
AttributeError: module 'csv_linter' has no attribute 'main'
这是因为 setup.py 文件指向一个不再存在的模块。更新该文件,使其在 main.py 文件中找到 main() 函数:
from setuptools import setup, find_packages
setup(
name = 'csv-linter',
description = 'lint csv files',
packages = find_packages(),
author = 'Alfredo Deza',
entry_points="""
[console_scripts]
csv-linter=csv_linter.main:main
""",
install_requires = ['click==7.1.2', 'pandas==1.2.0'],
version = '0.0.1',
url = 'https://github.com/paiml/practical-mlops-book',
)
更改可能很难发现,但是 csv-linter 的入口现在是 csv_linter.main:main。这个变化意味着 setuptools 应该查找一个包含 main() 函数的 main 模块的 csv_linter 包。语法有点棘手(我总是不得不查一下),但掌握变化的细节有助于可视化事物是如何联系在一起的。安装过程仍然保留了所有旧的引用,因此您必须再次运行 setup.py 才能使其正常运行:
$ python setup.py develop
running develop
Installing csv-linter script to /Users/alfredo/.virtualenvs/practical-mlops/bin
...
Finished processing dependencies for csv-linter==0.0.1
现在csv-linter工具已经恢复正常,让我们将main.py模块拆分为两个文件,一个用于检查,另一个仅用于命令行工具工作。创建一个名为checks.py的新文件,并将从main.py移动到此新文件的检查函数:
# in checks.py
def carriage_returns(df):
for index, row in df.iterrows():
for column, field in row.iteritems():
try:
if "\r\n" in field:
return index, column, field
except TypeError:
continue
def unnamed_columns(df):
bad_columns = []
for key in df.keys():
if "Unnamed" in key:
bad_columns.append(key)
return len(bad_columns)
def zero_count_columns(df):
bad_columns = []
for key in df.keys():
if df[key].count() == 0:
bad_columns.append(key)
return bad_columns
现在更新main.py,从checks.py文件中导入检查函数。新更新的主模块现在应该像这样:
import click
import pandas as pd
from csv_linter.checks import
carriage_returns,
unnamed_columns,
zero_count_columns
@click.command()
@click.argument('filename', type=click.Path(exists=True))
def main(filename):
df = pd.read_csv(filename)
for column in zero_count_columns(df):
click.echo(f"Warning: Column '{column}' has no items in it")
unnamed = unnamed_columns(df)
if unnamed:
click.echo(f"Warning: found {unnamed} columns that are Unnamed")
carriage_field = carriage_returns(df)
if carriage_field:
index, column, field = carriage_field
click.echo((
f"Warning: found carriage returns at index {index}"
f" of column '{column}':")
)
click.echo(f" '{field[:50]}'")
模块化是保持代码简短和可读性的好方法。当工具以这种方式分离关注点时,更容易进行维护和推理。有许多次我不得不处理那些没有任何好理由长达数千行的旧脚本。现在脚本状态良好后,我们可以进一步探索微服务并深入了解这些概念。
微服务
正如我在本章开头提到的,微服务是一种与旧式单体应用完全对立的新型应用范式。特别是对于 ML 操作,尽可能地从将模型投入生产的过程中隔离责任非常关键。隔离组件可以为在其他地方的可重用性铺平道路,不仅限于单一模型的特定流程。
我倾向于将微服务和可重用组件视为俄罗斯方块拼图的一部分。一个单体应用就像是一个非常高的俄罗斯方块塔,许多部件一起工作以使其稳固,但存在一个主要缺陷:不要试图触及可能导致整体崩溃的任何东西。另一方面,如果这些部件被尽可能牢固地组合在一起(就像拼图游戏的开始),那么移除部件并将其重新用于不同位置就会变得简单。
软件工程师通常会快速创建与手头任务紧密耦合的实用工具。例如,一些逻辑从字符串中删除某些值,然后可以将其持久化到数据库中。一旦少数行代码证明其价值,思考如何为其他代码基础组件实现重用就变得非常有用。我倾向于在我的项目中有一个实用模块,其中包含常见的实用程序,以便应用程序中需要相同设施的其他部分可以导入和重用它们。
与容器化类似,微服务允许更多地集中精力解决问题本身(代码),而不是环境(例如操作系统)。创建微服务的一个优秀解决方案是使用无服务器技术。云提供商的无服务器产品有许多不同的名称(例如 lambda 和云函数),但它们都指的是同一件事情:创建一个带有一些代码的单个文件,并立即部署到云上——不需要担心底层操作系统或其依赖关系。只需从下拉菜单中选择一个运行时,比如 Python 3.8,并点击一个按钮。事实上,大多数云提供商允许您直接在浏览器中创建函数。这种类型的开发和供应方式是相当革命性的,并且它已经使得之前非常复杂的应用程序模式变得可行。
无服务器的另一个关键方面是,您可以轻松访问大多数云提供商的提供。对于 ML 来说,这是至关重要的:您需要执行一些计算机视觉操作吗?无服务器部署可以在不到十几行代码的情况下完成。这种在云中利用 ML 操作的方式为您提供了速度、稳健性和可重现性:这些都是 DevOps 原则的重要组成部分。大多数公司不需要从头开始创建自己的计算机视觉模型。短语“站在巨人的肩膀上”完美地描述了这些可能性。多年前,我曾在一个数字媒体机构工作过,与一个拥有十几名 IT 人员的团队一起决定自行运行他们的电子邮件服务器。运行电子邮件服务器(正确地)需要大量的知识和持续的努力。电子邮件是一个具有挑战性的问题。我可以告诉你,邮件经常会停止工作——事实上,这几乎是每月一次的事件。
最后,让我们看看在云提供商上构建基于 ML 的微服务有多少选择。它们通常从更多的 IaaS(基础设施即服务)到更多的 PaaS(平台即服务)范围不等。例如,在图 11-1 中,Kubernetes 是一个部署微服务的较低级和复杂技术。在其他场景中,像本书前面介绍的 AWS App Runner,您可以将您的 GitHub 存储库指向该服务,并点击几个按钮即可获得完全部署的持续交付平台。在中间某处是云函数。

图 11-1. 云 ML 微服务
您公司的核心竞争力是什么?如果不是最先进的计算机视觉模型,那就不要自己创建。同样,要聪明工作,而不是努力工作,并建立在像 AWS App Runner 或 Google Cloud Run 这样的高级系统之上。最后,抵制重复发明轮子的冲动,利用云微服务。
创建无服务器函数
大多数云提供商在其无服务器环境中公开其 ML 服务。计算机视觉、自然语言处理和推荐服务只是其中的几个。在本节中,您将使用翻译 API 利用全球最强大的语言处理服务之一。
注意
对于这个无服务器应用程序,我将使用谷歌云平台(GCP)。如果您之前没有注册过它,您可能会获得一些免费的信用额度来尝试本节的示例,尽管根据当前的限制,您仍然可以部署云函数而不会产生任何费用。
登录 GCP 后,从左侧边栏的“计算”部分下选择“云函数”,如图 11-2 所示。

图 11-2. 云函数侧边栏
如果您之前没有创建过函数,会显示一个欢迎消息,指引您创建一个链接。如果您已经部署了一个函数,否则将可用一个“创建函数”按钮。通过 UI 创建和部署函数仅涉及几个步骤。图 11-3 是您可以期待填写的表单。

图 11-3. 创建一个云函数
Basics 部分的默认值已足够。在这种情况下,表单预填了 function-1 作为名称,并使用 us-central1 作为区域。确保将触发器类型设置为 HTTP,并且需要进行身份验证。单击“保存”,然后单击页面底部的“下一步”按钮。
警告
虽然允许对函数进行未经身份验证的调用(并且只需在 Web 表单中选择该选项就可以),但我强烈建议您永远不要在未启用身份验证的情况下部署云函数。未经身份验证的公开服务可能会被滥用,这对于您的帐户和预算都会产生重大的财务影响。由于云函数的使用直接与您的帐户和预算相关,未经授权的使用可能会带来重大的财务影响。
进入“代码”部分后,您可以选择一个运行时和一个入口点。选择 Python 3.8,将入口点更改为使用 main,并更新函数名称以使用 main() 而不是 hello_world(),如图 11-4 所示。

图 11-4. 云函数代码
选择应用程序入口点的能力,打开了创建其他辅助主函数或确定与代码交互的其他命名约定的可能性。灵活性很好,但具有默认值和使用约定更有价值。完成必要的更改后,单击“部署”按钮将此功能部署到生产环境。一旦完成,该函数应显示在云函数仪表板上。
部署后,让我们通过发送 HTTP 请求与之交互。有许多方法可以实现这一点。要开始,请点击所选函数的“操作”,然后选择“测试函数”。加载新页面,尽管一开始可能很难看到,但“触发事件”部分是您添加要发送的请求主体的地方。由于函数正在寻找"message"键,因此请更新主体以包含像图 11-5 所示的消息,然后点击“测试函数”按钮。

图 11-5. 云函数代码—触发事件
应该仅需几秒钟即可获得输出,该输出应为"message"键的值。除了该输出外,还会显示一些日志,这使得与函数交互变得非常直接。唯一不需要的是进行任何身份验证步骤,尽管函数是启用了身份验证创建的。每当您进行调试并希望快速测试部署的函数时,这无疑是最简单的方式。
此函数接受 JSON(JavaScript 对象表示法)作为输入。尽管在测试时尚不清楚云函数是否使用 HTTP,但这是将输入传递给函数的方式。JSON 有时被称为 Web 开发的通用语言,因为编程语言和其他服务和实现可以将 JSON 转换为它们理解的本地结构。
虽然 HTTP API 可以限制请求的类型和主体格式,但通常使用 JSON 进行通信。在 Python 中,您可以将 JSON 加载到像列表和字典这样的本地数据结构中,这些结构易于使用。
在探索与函数交互的其他方法(包括身份验证)之前,让我们利用 Google 的 ML 服务,通过使用其翻译服务。默认情况下,所有来自 Google 云平台的 API 都是禁用的。如果您需要与诸如语言翻译之类的云服务进行交互,则必须在使用之前启用该 API。如果您创建了一个云函数(如本例)并且忘记这样做,这并不是什么大问题。结果会在日志中记录错误,并作为错误响应返回给发出请求的客户端,HTTP 500。
google.api_core.exceptions.PermissionDenied: 403 Cloud Translation API has not
been used in project 555212177956 before or it is disabled.
Enable it by visiting:
https://console.developers.google.com/apis/api/translate.googleapis.com/
then retry. If you enabled this API recently, wait a few minutes for the
action to propagate to our systems and retry."
在进一步修改函数之前,请启用Cloud Translation API。GCP 提供的大多数 API 都需要通过访问 API 和服务链接并在库页面中找到所需的 API 来启用。
注意
如果您不是 GCP 帐户上的管理员并且看不到可用的 API,则可能缺少启用 API 所需的权限。需要帐户管理员授予您适当的权限。
启用 API 后,点击函数名称返回其仪表板加载页面。在仪表板中,找到页面顶部的编辑按钮以更改源代码。编辑部分首先提供配置函数本身的选项,然后是代码。无需更改部署配置,请点击“下一步”最终进入源代码。点击 requirements.txt 链接打开该文件,添加与翻译服务交互所需的 API 库:
google-cloud-translate==3.0.2
现在点击 main.py 编辑内容。添加导入语句以引入翻译服务,并添加一个负责进行翻译的新函数:
from google.cloud import translate
def translator(text="YOUR_TEXT_TO_TRANSLATE",
project_id="YOUR_PROJECT_ID", language="fr"):
client = translate.TranslationServiceClient()
parent = f"projects/{project_id}/locations/global"
response = client.translate_text(
request={
"parent": parent,
"contents": [text],
"mime_type": "text/plain",
"source_language_code":"en-US",
"target_language_code":language,
}
)
# Display the translation for each input text provided
for translation in response.translations:
print(u"Translated text: {}".format(translation.translated_text))
return u"Translated text: {}".format(translation.translated_text)
这个新函数需要三个参数来与翻译 API 进行交互:输入文本、项目 ID 和翻译的目标语言(默认为法语)。输入文本默认为英语,但函数可以适应其他语言(例如西班牙语)作为输入和英语作为输出。只要支持该语言,函数就可以使用任意组合的输入和输出。
翻译请求的响应是可迭代的,因此在翻译完成后需要一个循环。
现在修改 main() 函数,将 "message" 的值传递给 translator() 函数。我正在使用自己的项目 ID(“gcp-book-1”),所以请确保在尝试下一个示例时更新自己的项目 ID:
def main(request):
request_json = request.get_json()
if request_json and 'message' in request_json:
return translator(
text=request_json['message'],
project_id="gcp-book-1"
)
else:
return f'No message was provided to translate'
main() 函数仍然需要在传入的 JSON 请求中分配一个 "message" 值,但现在会对其进行有用的操作。在控制台上使用示例 JSON 输入进行测试:
{"message": "a message that has been translated!"}
测试页面的输出(如 图 11-6 所示)应该几乎是即时的。

图 11-6. 翻译测试
认证 Cloud Functions
我将 HTTP 访问看作是访问民主主义:其他系统和语言都能通过其实现从远程位置的独立服务中使用 HTTP 规范进行交互,具有极大的灵活性。所有主要编程语言都可以构建 HTTP 请求并处理来自服务器的响应。利用 HTTP 将服务整合在一起可以使这些服务以新的方式工作,潜在地可以实现最初没有考虑到的功能。将 HTTP API 视为可插入任何连接到互联网的内容的可扩展功能。但是通过互联网连接存在安全影响,如使用经过身份验证的请求来防止未经授权的访问。
有几种远程与云函数交互的方式。我将从命令行开始,使用curl程序。虽然我倾向于不使用curl来进行身份验证请求的交互,但它确实提供了一个直接的方式来记录您需要成功提交请求的所有组件。为您的系统安装 Google Cloud SDK,然后确定您之前部署的项目 ID 和函数名称。以下示例使用 SDK 和curl进行身份验证:
$ curl -X POST --data '{"message": "from the terminal!"}' \
-H "Content-Type: application/json" \
-H "Authorization: bearer $(gcloud auth print-identity-token)" \
https://us-central1-gcp-book-1.cloudfunctions.net/function-1
在我的系统上,使用function-1 URL,我得到以下响应:
Translated text: du terminal!
这个命令看起来非常复杂,但它提供了一个更好的图像,说明了需要成功提交请求的所有组件。首先,它声明请求使用 POST 方法。当请求与负载相关联时,通常会使用此方法。在本例中,curl从参数向--data标志发送 JSON。接下来,命令添加了两个请求头,一个用于指示发送的内容类型(JSON),另一个用于指示请求提供令牌。令牌是 SDK 发挥作用的地方,因为它为请求创建了一个令牌,云函数服务需要验证请求是否经过身份验证。最后,云函数的 URL 被用作这个经过身份验证的 POST 请求的目标,发送 JSON。
尝试单独运行 SDK 命令,看看它的效果:
$ gcloud auth print-identity-token
aIWQo6IClq5fNylHWPHJRtoMu4IG0QmP84tnzY5Ats_4XQvClne-A9coqEciMu_WI4Tjnias3fJjali
[...]
现在您了解了请求所需的组件,请直接使用 SDK 来发出请求,以便您能够访问部署的云函数:
$ gcloud --project=gcp-book-1 functions call function-1 \
--data '{"message":"I like coffee shops in Paris"}'
executionId: 1jgd75feo29o
result: "Translated text: J'aime les cafés à Paris"
我认为像 Google 这样的云服务提供商包含其他与其服务交互的便利设施是一个很好的主意,就像这里的云函数一样。如果您只是知道 SDK 命令来与云函数交互,使用编程语言例如 Python 构造请求将会变得困难。这些选择提供了灵活性,环境越灵活,就越有可能以最合理的方式进行适应,以满足环境的需求。
现在让我们使用 Python 与翻译器函数进行交互。
注意
以下示例将直接调用 Python 使用gcloud命令,快速演示如何创建 Python 代码与云函数进行交互。然而,这并不是处理身份验证的稳健方式。您需要创建服务账号并使用google-api-python-client来正确地保护身份验证过程。
创建一个名为trigger.py的 Python 文件,并添加以下代码以从gcloud命令中检索令牌:
import subprocess
def token():
proc = subprocess.Popen(
["gcloud", "auth", "print-identity-token"],
stdout=subprocess.PIPE)
out, err = proc.communicate()
return out.decode('utf-8').strip('\n')
token()函数将调用gcloud命令并处理输出以发出请求。值得重申的是,这是一种快速演示从 Python 触发函数的方法。如果希望在生产环境中实现这一点,则应考虑从google-api-python-client创建服务帐户和 OAuth2。
现在使用该令牌创建请求,以与云函数通信:
import subprocess
import requests
url = 'https://us-central1-gcp-book-1.cloudfunctions.net/function-1'
def token():
proc = subprocess.Popen(
["gcloud", "auth", "print-identity-token"],
stdout=subprocess.PIPE)
out, err = proc.communicate()
return out.decode('utf-8').strip('\n')
resp = requests.post(
url,
json={"message": "hello from a programming language"},
headers={"Authorization": f"Bearer {token()}"}
)
print(resp.text)
请注意,我已经在脚本中添加了requests库(在我的情况下是版本 2.25.1),因此在继续之前,您需要安装它。现在运行trigger.py文件来测试它,确保您已经使用您的项目 ID 更新了脚本:
$ python trigger.py
Translated text: bonjour d'un langage de programmation
构建基于云的命令行界面(CLI)
现在您理解了构建命令行工具、打包和分发它以及利用云提供的 ML 服务的概念,很有意思看到这些内容如何结合在一起。在本节中,我将重复使用所有不同的部分来创建一个。创建一个新目录,并将以下内容添加到setup.py文件中,以便立即解决打包问题:
from setuptools import setup, find_packages
setup(
name = 'cloud-translate',
description = "translate text with Google's cloud",
packages = find_packages(),
author = 'Alfredo Deza',
entry_points="""
[console_scripts]
cloud-translate=trigger:main
""",
install_requires = ['click==7.1.2', 'requests==2.25.1'],
version = '0.0.1',
url = 'https://github.com/paiml/practical-mlops-book',
)
setup.py文件将创建一个cloud-translate可执行文件,映射到trigger.py文件中的一个main()函数。我们还没有创建那个函数,因此请添加在上一节中创建的trigger.py文件,并添加该函数:
import subprocess
import requests
import click
url = 'https://us-central1-gcp-book-1.cloudfunctions.net/function-2'
def token():
proc = subprocess.Popen(
["gcloud", "auth", "print-identity-token"],
stdout=subprocess.PIPE)
out, err = proc.communicate()
return out.decode('utf-8').strip('\n')
@click.command()
@click.argument('text', type=click.STRING)
def main(text):
resp = requests.post(
url,
json={"message": text},
headers={"Authorization": f"Bearer {token()}"})
click.echo(f"{resp.text}")
该文件与最初的trigger.py并没有太大的区别,它直接使用 Python 运行。Click 框架允许我们定义一个text输入,然后在完成时将输出打印到终端。运行python setup.py develop以确保所有东西都被连接在一起,包括依赖项。正如预期的那样,该框架为我们提供了帮助菜单:
$ cloud-translate --help
Usage: cloud-translate [OPTIONS] TEXT
Options:
--help Show this message and exit.
$ cloud-translate "today is a wonderful day"
Translated text: aujourd'hui est un jour merveilleux
机器学习 CLI 工作流程
两点之间的最短距离是直线。同样地,命令行工具通常是使用机器学习的最直接方法。在图 11-7 中,您可以看到有许多不同的 ML 技术风格。在无监督机器学习的情况下,您可以“即时训练”;在其他情况下,您可能希望使用每晚训练过的模型,并将其放置在对象存储中。然而,在其他情况下,您可能希望使用 AutoML、AI API 或第三方创建的模型等高级工具。
请注意,有许多不同的问题领域,在这些领域中,通过添加 ML 来增强 CLI 或使 CLI 的整个目的。这些领域包括文本、计算机视觉、行为分析和客户分析。
强调有许多部署命令行工具的目标,其中包括机器学习。像 Amazon EFS、GCP Filestore 或 Red Hat Ceph 这样的文件系统具有作为集中 Unix 挂载点的优势。bin目录可以包含通过挂载同一卷的 Jenkins 服务器传递的 ML CLI 工具。
其他交付目标包括 Python Package Repository(PyPI)和像 Docker、GitHub 和 Amazon 这样的公共容器注册表。还有更多的目标包括像 Debian 和 RPM 这样的 Linux 包。一个打包机器学习的命令行工具拥有比微服务甚至更广泛的部署目标收藏,因为一个命令行工具就是一个完整的应用程序。

图 11-7. 机器学习 CLI 工作流程
适合使用 CLI 进行机器学习的几个好例子包括以下资源:
DevML
DevML 是一个分析 GitHub 组织的项目,允许机器学习实践者创建自己的“秘密 ML” 预测,可以通过连接到 streamlit 或将开发者聚类报告包含在 Amazon QuickSight 中来实现。
Python MLOps Cookbook
Python MLOps Cookbook GitHub 仓库 包含一组实用工具作为简单的 ML 模型。本项目在 第七章 中有详细介绍。
Spot 价格机器学习
另一个 ML CLI 示例是关于 Spot 价格机器学习聚类的项目。在这个 GitHub 仓库中,使用 AWS Spot 实例的不同属性,包括内存、CPU 和价格,来创建类似的机器类型的聚类。
有了这些 CLI 工作流程,让我们继续总结本章。
结论
本章已经介绍了如何从头开始创建命令行工具,并使用框架快速进行自动化工具的创建。即使示例看起来可能很琐碎,但细节和各个部分如何协作是至关重要的方面。在学习新概念或通常让其他人望而却步的主题(如打包)时,很容易感到泄气并试图绕过它们。尽管 Python 在改进打包方面还有很长的路要走,但入门并不难,通过正确打包工具的繁重工作将使您在任何团队中都变得不可或缺。通过打包和命令行工具,您现在已经具备了将不同服务集成到自动化中的良好位置。
本章通过利用云及其众多机器学习服务,例如来自谷歌的强大翻译 API,来实现这一点。记住,没有必要从头开始创建所有模型,尽可能利用云服务提供者的服务,特别是当这不是你公司核心竞争力时。
最后,我想强调的是,能够为棘手的问题制定新解决方案是 MLOps 的超能力,这基于了解如何连接服务和应用程序。正如您现在所知,使用 HTTP、命令行工具和通过它们的 SDK 利用云服务提供者是在几乎任何生产环境中实现重大改进的坚实基础。
在下一章中,我们将深入探讨机器学习工程的其他细节,以及我最喜欢的主题之一:案例研究。案例研究是真实世界中的问题和情况,您可以从中提取有用的经验并今天应用它们。
练习
-
在 CLI 中添加一些使用云函数的选项,比如使 URL 可配置。
-
弄清楚服务帐户和 OAuth2 在 Google SDK 中的工作原理,并将其集成到trigger.py中,以避免使用subprocess模块。
-
通过翻译来自维基百科页面等其他来源来增强云函数。
-
创建一个新的云函数进行图像识别,并使其与命令行工具配合使用。
-
克隆Python MLOps Cookbook repository并构建一个稍微不同的容器化 CLI 工具,将其发布到像 DockerHub 或 GitHub Container Registry 这样的公共容器注册表中。
批判性思维讨论问题
-
未经身份验证的云函数可能会带来哪些可能的后果?
-
不使用虚拟环境的一些缺点是什么?
-
描述好的调试技术的两个方面及其有用性。
-
为什么了解打包很有用?打包的一些关键方面是什么?
-
使用云提供商现有模型是个好主意吗?为什么?
-
解释使用公共容器注册表部署由机器学习驱动的开源 CLI 工具与使用 Python 包仓库之间的权衡。
第十二章:机器学习工程和 MLOps 案例研究
Noah Gift
在陪同洛伊博士完成他的程序后,我在他的术后护理中花了更多时间。在那期间,他继续向我讲课。他在他的小 62 页书上签了我的副本,在他颤抖的手写的签名上面写着:“事实无理论是混乱,理论无事实是幻想。”
Dr. Joseph Bogen
现实世界中技术的一个基本问题是很难确定该听谁的建议。特别是像机器学习这样的多学科话题更是一个令人费解的挑战。你如何找到既有真实世界经验、又具备当前和相关技能、还能够有效解释的教学能力的正确结合?这种“独角兽”般的教学能力正是本章的目标。本章旨在从这些相关方面提炼出可操作的智慧,用于你的机器学习项目中,如图 12-1 所示。
其他领域也受到多学科领域带来的无限复杂性的困扰。例如营养科学、气候科学和混合武术。然而,一个共同的主题是开放系统与封闭系统的概念。一个主要封闭系统的玩具例子是隔热杯。在这个例子中,由于环境影响最小,所以更容易模拟冷液体的行为。但是如果同样的冷液体放在普通杯子外面,情况很快就变得混乱。外部的空气温度、湿度、风力和阳光照射单独就会在模拟这种冷液体行为中引起连锁复杂性。

图 12-1 独角兽式教学
本章探讨了 MLOps 如何利用这些其他领域的经验教训。它还探讨了封闭系统与开放系统如何影响特定领域的行为,并最终如何将其应用于机器学习的运营。
在构建机器学习模型时无知的不太可能的好处
在无知中存在许多不太可能的好处。无知给了你尝试挑战性事物的勇气,如果你知道它有多难,你可能永远不会去做。自 2013 年以来,无知在我同时做的两件事情中起了至关重要的作用:创建了一个涉及数百万美元、包括一家有 100 人的公司在内的生产机器学习模型;并且学习、训练并参与巴西柔术,与排名靠前的职业搏击手和摔跤、柔道奥运选手竞争。在某种程度上,这两件事情是如此交织在一起,以至于在我的心中很难分开它们。从 2010 年到 2013 年,我在旧金山的初创公司全职工作的同时,在加州大学戴维斯分校的 MBA 项目中,我花了三年时间参加了所有可能的统计学、概率论和建模课程。自 2017 年以来,我还在加州大学戴维斯分校的管理研究生院教授机器学习和云计算。毕业后,我准备担任总经理或首席技术官,并成为一家体育社交网络的首批技术员工,担任首席技术官和总经理。
公司文化的一部分是员工们会在一家混合武术健身房一起锻炼,这家健身房也提供一般健身课程。我偶然开始参加巴西柔术训练,因为我对职业搏击手的格斗很好奇。最终,不知不觉中,我与职业搏击手并肩工作,并学会了降服技的基础。有几次,我甚至在练习中意外被使劲压制到昏迷。我记得有一次我想,“我应该轻轻地放弃这个头臂锁。”后来,我想知道我在哪里;看起来像是一个健身房,我不知道是什么年份。当血流回到我的大脑时,我意识到,啊,我被锁喉了,我在加利福尼亚州圣罗莎的武术馆里。
在我最初几年的训练中,我还参加了两次比赛,赢得了第一场“新手”类别的比赛,然后在几年后输掉了一个“中级”类别的比赛。事后来看,老实说,我并不理解当时在做什么,面临着严重受伤的真实风险。我看到很多人在比赛中受到严重伤害,包括头部撞击、肩膀骨折和膝关节韧带撕裂。在我作为 40 岁的人参加第二次比赛时,我在 220 磅级别的体重级别上与一名 20 岁的大学橄榄球运动员竞争。在他的最后一场比赛中,他卷入了一场真正的打斗,被头部撞击,鼻子流血,非常愤怒。我对他情绪状态进入我们的比赛感到担忧,心想,“我到底报了什么名?”
我也感到庆幸,当时对于在三四十岁参加格斗运动的实际危险我是无知的。我仍然喜欢训练和学习巴西柔术,但如果我当时知道今天的知识,我可能不会以当时如此有限的技能去冒险。无知使我有勇气,坦率地说,去冒险,但也更快地学习。
同样,在 2014 年,我在开始为公司构建机器学习基础设施的旅程时和我一样无知。今天这被称为 MLOps。当时关于如何运营机器学习的信息并不多。就像巴西柔术一样,我急于但却不知道真正会发生什么以及涉及的风险。后来,在独自从头开始构建预测模型并负责数百万美元的过程中,我感到的恐惧远不及我在巴西柔术中的恐惧。
在第一年,2013 年,我们公司建立了一个体育社交网络和移动应用程序。但是,像许多初创公司的员工一样,构建软件只是初创公司挑战的一部分;另外两个关键挑战是获取用户和创造收入。到 2014 年初,我们拥有了一个平台,但没有用户和收入。所以我们需要快速吸引用户,否则这个初创公司将会破产。
对于软件平台来说,通常有两种方式来增加流量。一种方式是通过口碑传播建立有机增长。第二种方式是购买广告。购买广告的问题在于它可能很快成为永久的成本分配。我们的梦想情景是让我们的公司不用购买广告就能吸引用户来到我们的平台。我们与一些体育明星有关系,包括前 NFL 四分卫布雷特·法夫尔。这位最初的“超级”社交媒体影响者为我们提供了大量关于如何利用有机的“增长黑客”来扩展我们平台的见解。
在某个时候,这个机器学习反馈循环让我们达到了数百万月活跃用户。然后,Facebook 的法律团队向我们发送了一封通信,形容他们将“取消我们的平台”的意图。我们的“罪行”是创建了与我们平台链接的独特原创体育内容。关于大科技公司潜在的垄断力量以及我们影响算法的增长黑客力量引起了 Facebook 的关注,这是我们的预测系统在现实世界中成功的又一个数据点。接下来我们深入探讨如何做到这一点。
Sqor 体育社交网络的 MLOps 项目
从零开始建立一家初创公司——即零员工、零用户和零收入——是一项紧张的追求。从 2013 年到 2016 年,我在旧金山 Transamerica 大厦下面的公园里花了很多时间,直接在办公室下面策划这些事情。特别是认识论风险,即我未意识到的风险,即依靠机器学习预测投入数百万美元是令人恐惧的。但在很多方面,技术挑战比心理挑战更容易应对。
这里是系统运行方式的概述。简而言之,用户发布原创内容,然后我们将内容交叉发布到 Twitter 和 Facebook 等其他社交网络上。我们随后收集了为我们的网站生成的页面浏览量。这些页面浏览量成为我们 ML 预测系统的目标。图 12-2 展示了我们社交媒体公司的 MLOps 管道。

图 12-2. 运动社交网络的 MLOps 管道
后来,我们收集了这些用户的社交媒体信号,即他们的中位数转推数、中位数点赞数和维基百科页面浏览量。这些成为了特征,并帮助我们摆脱“假”粉丝等错误数据的干扰。然后,我们按照他们在我们网站上生成的参与度支付原创内容创作者,如康纳·麦格雷戈、布雷特·法弗尔、蒂姆·麦格劳和阿什林·哈里斯。结果证明,在这方面我们领先于游戏,并且Snapchat 为用户在其平台上发布原创内容支付了数百万美元。
问题最具挑战性的部分是可靠地收集数据,然后基于预测决定支付数百万美元。这两者都比我最初想象的复杂得多。所以让我们接下来深入研究这些系统。
机械土耳其数据标注
最初发现社交媒体信号给了我们足够的预测能力来“增长黑客”我们的平台是一个巨大的突破。但不幸的是,最艰巨的挑战还在后面等着我们。
我们需要可靠地收集成千上万个“名人”社交媒体用户的账号。不幸的是,最初并不顺利,最终以彻底失败告终。我们的第一个流程看起来像图 12-3。

图 12-3. 糟糕的特征工程
虽然一些实习生本身是未来的 NFL 球员,但关键问题在于他们没有接受过可靠地输入社交媒体账号的培训。结果,很容易混淆 NFL 球员安东尼·戴维斯和 NBA 球员安东尼·戴维斯,并交换 Twitter 账号。这种特征工程可靠性问题将影响我们模型的准确性。我们通过引入亚马逊机械土耳其的自动化解决了这个问题。我们培训了一群“土耳其人”来查找运动员的社交媒体账号,如果 7/9 的人同意,我们发现这大约等于 99.9999%的准确性。图 12-4 展示了我们社交媒体公司的机械土耳其标签系统。
注意
回到 2014 年,人们对数据工程和 MLOps 的了解较少。我们的标记系统项目涉及到了一位令人惊叹的运动员、程序员和前 UC 戴维斯大学毕业生Purnell Davis。Purnell 在与像 NFL 球员 Marshawn Lynch 或 300 磅级职业 MMA 格斗选手的公司一起午餐或黎明时分锻炼时,从零开始开发了这个系统。

图 12-4. 机械土耳其标签
影响力排名
一旦我们完成数据标记,我们就必须从社交媒体 API 收集数据。你可以在这个仓库中找到一个将其引入ML 引擎所需的代码示例。
例如,根据我们的数据收集 API,LeBron James 在 Twitter 上的统计数据如下:
Get status on Twitter
df = stats_df(user="KingJames")
In [34]: df.describe()
Out[34]:
favorite_count retweet_count
count 200.000000 200.000000
mean 11680.670000 4970.585000
std 20694.982228 9230.301069
min 0.000000 39.000000
25% 1589.500000 419.750000
50% 4659.500000 1157.500000
75% 13217.750000 4881.000000
max 128614.000000 70601.000000
In [35]: df.corr()
Out[35]:
favorite_count retweet_count
favorite_count 1.000000 0.904623
retweet_count 0.904623 1.000000
注意
如果你熟悉 HBO 的节目The Shop,你就会认识 Maverick Carter 和 LeBron James。我们“几乎”正式与他们合作过,但谈判并未成功。最终我们与拜仁慕尼黑建立了关键伙伴关系。
然后这些收集的数据输入到了一个预测模型中。我在 2014 年在R 湾区的 meetup上的演讲展示了我们的一些成果。最初的模型使用了 R 库Caret来预测页面浏览量。在图 12-5 中,用于找到影响力者的原始预测算法是用 R 完成的,这个基于 ggplot 的图表显示了预测的准确性。

图 12-5. 社交媒体影响者预测页面浏览量与实际页面浏览量
然后我们建立了一个以页面浏览量为目标指标的支付系统模型。最终,这导致了指数级的增长,推动了我们每月数百万的页面浏览量的快速扩展。但正如我之前提到的,令人害怕的是那些失眠的夜晚,担心我在帮助创建的预测上花费了数百万美元而导致公司破产。
运动员智能(AI 产品)
通过核心 MLOps 管道提供预测并推动我们的增长,而不是购买广告,我们开始将我们的产品发展成一个名为“Athlete Intelligence”的 AI 产品。我们有两位全职的 AI 产品经理负责管理这一产品。这一核心产品的一般理念是让“影响力者”了解他们可以期待的报酬,而品牌可以使用这个仪表板直接与运动员合作。在图 12-6 中,我们使用无监督机器学习来分类运动员的不同方面,包括他们的社交媒体影响力。

图 12-6. 运动员智能
额外功能包括无监督机器学习,“聚类”了类似的运动员社交资料。这一功能使我们能够将不同类型的运动员打包成影响力营销捆绑包(图 12-7)。

图 12-7. 运动员智能聚类仪表盘
最后,这种增长为我们的公司带来了两种新的收入产品。首先是基于 Shopify 构建的商品平台,发展成为年销售额 50 万美元的业务;第二是一个千万美元的影响力营销业务。我们直接联系品牌,并将他们与我们平台上的影响者联系起来。一个很好的例子是我们与Machine Zone 和 Conor McGregor合作的《Game of War》广告。
简言之,拥有有效的 MLOps 管道使我们同时获得了用户和收入。没有预测模型,我们将没有用户;没有用户,我们将没有收入;没有收入,我们将没有业务。实用的 MLOps 带来丰厚的回报。
让我们在下一节更多地讨论开放与封闭系统。在像学术数据科学、柔道馆内穿着 GI 的环境或理论上的营养建议(比如热量摄入与消耗)这样的受控环境中,事情看起来很简单。然而,现实世界或开放系统有许多额外的影响因素,这些因素要难得多地控制和利用。
完美技术与现实世界的对比
巴西柔术黑带在 GI(柔道服)内使用的臂锁(通过超伸肘部断裂手臂的攻击方式)对于没有穿 GI 的对手是否同样有效?同样地,在机器学习中,什么最重要?是准确性、技术,还是能够为客户提供价值或在特定情况下提供知识?
注意
如果你对在比赛中使用臂锁的场景感兴趣,可以搜索“Ronda Rousey armbar”,你将看到一个技术精湛的大师在工作。
在街头斗殴与角力比赛、UFC 比赛与角力比赛之间有什么区别?即使是非专家也会同意,在技术涉及的规则越多的环境中,它在没有或几乎没有规则的现实世界中成功的机会就越小。
我通过在商务出差期间游历美国的随机柔术馆来学到了一个教训。在多次情况下,一个精通穿着无 GI 进行巴西柔术的棕带或黑带专家会与我在无 GI 环境下切磋,并立即试图进行臂锁。我曾花费多年时间与专业的 NOGI 练习的职业选手们训练。在被这一技术收割数百次后,我学会了如何通过特定的方式轻松地摆脱它。我是如何理解这一点的呢?我只是模仿了职业选手们的做法和教导,并一遍又一遍地练习。
许多从黑带到棕带再到奥运奖牌得主的 GI 专家都说过类似的话。“你在 GI 中做不到这一点”,或者“我在 GI 中可以做到这一点”,或者“你不应该这样做”,等等,他们的表情既震惊又惊讶。他们的“完美模型”不起作用,他们的世界观受到了冲击。这是否意味着我和这些专家一样出色?不,这意味着他们在一个当前不适用的环境中拥有比我更好的技术,即不穿制服的摔跤。请注意在图 12-8 中如何通过关闭系统或添加规则来增加对外部世界的控制,却减少了现实感。

图 12-8. 现实世界中的武术技术
在《批判性思维》(MIT 出版社)一书中,乔纳森·哈伯提到埃隆大学的安·J·卡希尔和斯蒂芬·布洛克-舒尔曼如何将他们的高等教育课堂打造成武术馆:
在这样的[武术]课堂上,每个评估级别的学生还需要展示他们保持了在前几级黑带中获得的技能。重要的是,一位优秀的老师不会根据努力程度授予黑带:学生是否努力掌握某项动作并不重要。问题在于,学生能否成功打出这一拳?
注
当我在西北大学数据科学项目中教授混合武术(MMA)和机器学习理论时,我发现我的一位学生在 NFL 效力了 14 年。他指出,他喜欢 MMA 的原因是最优秀的运动员没有技术上的偏好,而是等待事态发展,并根据情况选择正确的技术应对。
像雅各布一样,这位前 MMA 黑带选手,我在现实世界的机器学习中看到了类似的问题。作为教授,我在顶尖大学教授机器学习,并且也是行业从业者。像sklearn和pandas这样的学术工具和 Kaggle 上的教育数据集就是“形式”。“形式”是必需的,以“剥离”真实世界的复杂性来教授材料。关键的下一步是让学生意识到,机器学习形式并非真实世界。真实世界要复杂得多,更加危险。
MLOps 的关键挑战
让我们讨论一些将机器学习引入生产中面临的具体挑战。三个主要挑战是伦理和意外后果、缺乏运营卓越以及专注于预测准确性而非大局观。在实施机器学习中,运营卓越比技术更重要。这一点在研究论文 "机器学习系统中的隐藏技术债务" 中得到了清晰表达。该论文的作者们发现,“在现实世界的 ML 系统中,经常会产生巨大的持续维护成本。”接下来让我们讨论一些这些问题。
伦理和意外后果
对伦理问题进行“半吊子”或自以为是的讨论是很容易让人反感的事情。这并不意味着这个话题不需要讨论。许多处理社交媒体的公司已经创造了大规模误导武器。据 Tristan Harris 表示,“64% 的极端组织的参与都是由于 Facebook 自己的推荐系统”,YouTube “推荐了亚历克斯·琼斯、InfoWars 和阴谋论视频达 150 亿次。这比华盛顿邮报、BBC、卫报和福克斯新闻的总和流量还要多”,而 YouTube 的 70% 观看时间来自推荐视频。
后果已不再是理论上的问题。许多社交网络和大型科技公司正在积极改变他们基于机器学习的系统的方法。这些更新包括暂停使用面部识别系统,更加密切地评估推荐引擎的结果。在现实世界中,伦理考量也必须成为 MLOps 解决方案的一部分,不仅仅是技术是否具有预测能力。
已经出现了一些解决信息茧房问题的方法,例如 IEEE Spectrum 文章 "智能算法冲破社交网络的“过滤泡泡”" 中提到的。他们继续说,“芬兰和丹麦的一个研究团队对社交媒体平台的工作方式有了不同的愿景。他们开发了一种新算法,增加社交网络上的暴露多样性,同时确保内容广泛分享。”
这种情况涉及到伦理问题。如果一家公司或机器学习工程师完全专注于提高用户参与度和利润,他们可能不愿意因为“拯救世界”而损失 10%的利润。这种伦理困境是外部性的一个经典案例研究。核能发电设施可能提供巨大的能源利益,但如果他们将核废料倾倒进海洋,其他无辜的受害者将为此付出代价却无法获得任何回报。
缺乏运营卓越
另一个机器学习工程的反模式是无法有效地维护机器学习模型。理解这种情况的一个好方法是考虑建造木制书架与种植无花果树之间的区别。如果你制造一本书架,你可能永远不会再修改它;一旦设计完成,这个项目就结束了。另一方面,无花果树是开放系统的一部分。它需要水、阳光、土壤、营养物质、风、修剪树枝以及对抗昆虫和疾病的保护。无花果树随着时间适应环境,但需要有人进行监督,以便稍后享用果实。换句话说,无花果树需要维护才能产生高质量的果实。机器学习系统,如任何软件系统一样,永远不像书架那样完工。相反,它需要像养护无花果树一样持续养护。
接下来,让我们想象一家公司为每位新家具店顾客预测信用额度。假设初始模型是在 2019 年 1 月开发的。此后,发生了很多变化。使用于创建模型的特征可能存在显著的数据漂移。我们知道,2020 年至 2021 年间许多零售店关闭,购物中心的商业模式面临严重威胁。COVID-19 加速了实体零售的下滑趋势,此外,底层数据也发生了其他更新。因此,数据“漂移”,即 COVID-19 后的购买模式大相径庭,原始静态机器学习模型可能无法按预期工作。
一个机器学习工程师明智的做法是通过在数据发生变化时设定警报来持续重新训练模型(养护无花果树)。此外,业务指标监控可以在逾期付款超过阈值时发出警报。例如,假设一家销售家具的公司通常有 5%的客户在其信用上逾期 30 天。如果突然有 10%的客户逾期 30 天支付,则底层 ML 模型可能需要更新。
对于机器学习的警报和监控概念对传统软件工程师而言并不奇怪。例如,我们监控单个服务器的 CPU 负载,移动用户的延迟以及其他关键性能指标。在机器学习操作中,同样的概念适用。
焦点应放在预测准确性与整体大局之间。
正如本章前面讨论的那样,即使是世界一流的从业者有时也会因为关注大局而忽略技术细节。但就像“完美的肩臂锁”在不同情况下可能不起作用一样,模型也可能同样脆弱。一场很好的拉锯战的例子是纳特·西尔弗与纳西姆·塔勒布的辩论。艾萨克·费伯在 Medium 文章中详细分析了关键要点。艾萨克指出,并非所有模型都是现实世界的完美复制品,因为它们无法剔除我们不知道的不确定性,即无法测量的风险。纳特·西尔弗与纳西姆·塔勒布之间的辩论归结为,是否可能对选举进行建模,或者认为我们可以对其进行建模是一种幻觉。2020 年的选举事件似乎支持纳西姆·塔勒布的观点。
黑带雅各布在指出 Kata 是实践技术的简化版本时也表达了同样的看法。现实世界的复杂性与训练练习之间存在固有的冲突。模型或技术可能是完美无缺的,但现实世界可能并不在乎。例如,在 2020 年总统选举中,即使是最好的建模者也没有预料到发生叛乱或州官员被迫作废选票的可能性。正如艾萨克描述的那样,这是随机和认识不确定性之间的差异。你可以测量随机风险,比如抛硬币的概率,但你无法测量认识风险,比如可能推翻总统选举的叛乱。
我在加州理工学院与之共事多年的史蒂文·库宁博士在他关于气候科学的书中也表达了类似的观点。就像选举预测、营养科学和其他复杂系统一样,气候科学的话题也是立即引起分歧的。在他的书《不稳定》(BenBella Books)中,库宁说,“我们不仅可能因为未能对气候长期大局进行全面的视角而受骗,还可能因为未能对地球进行全面的视角而受骗。”此外,他继续说,“未来气候和天气事件的预测依赖于明显不适合此目的的模型。”无论你是否相信他的说法,都值得考虑复杂系统现实建模以及可能出现的问题。
对于这个困境的一个很好的总结是要对预测或技术的信心保持谨慎。我曾与之一起训练过的一位最可怕和最有才华的擒拿手,戴夫·特雷尔,他曾为 UFC 冠军而战,告诉我,永远不要与多个对手交战。从业者拥有的技能越多,越能意识到认识风险的存在。即使你是世界一流的武术家,为什么要在街头与多人对抗中冒险生命?同样地,即使你预测选举、股票价格或自然系统,为什么要比你应有的更自信?
在实践中,解决生产中的这个问题的最佳方法是限制技术的复杂性,并假设对认知不确定性的了解较低。这个要点可能意味着传统的机器学习高可解释性比稍微准确率更高的复杂深度学习模型更好。
实施 MLOps 的最终建议
在我们结束之前,我们想带领您了解一些在组织中实施 MLOps 的技巧。首先,以下是一组最终的全球建议:
-
从小胜利开始。
-
使用云,不要与云对抗。
-
使您和您的团队在云平台和 ML 专业上获得认证。
-
从项目开始就自动化。优秀的初始自动化步骤是持续集成您的项目。另一种说法是,“如果不自动化,就是出了问题。”
-
实践改善,即通过您的流程进行持续改进。这种方法改善了软件质量、数据质量、模型质量和客户反馈。
-
在处理大团队或大数据时,专注于使用平台技术,如 AWS SageMaker、Databricks、Amazon EMR 或 Azure ML Studio。让平台为您的团队减轻负担。
-
不要只关注技术复杂性,即深度学习与使用任何有效工具解决问题的对比。
-
认真对待数据治理和网络安全。实现这一目标的一种方法是利用平台的企业支持,并定期审核您的架构和实践。
最后,在考虑 MLOps 时,有三个自动化法则需要考虑:
-
如果任务是关于自动化的,最终都会被自动化。
-
如果没有自动化,就会出问题。
-
如果人类在做,机器最终会做得更好。
现在,让我们深入探讨一些处理安全问题的技巧。
数据治理和网络安全
MLOps 中存在两个看似矛盾的问题:增加的网络安全问题和生产中缺乏机器学习。一方面,规则太多,什么也做不了。另一方面,对关键基础设施的勒索攻击正在增加,组织应着眼于如何管理其数据资源。
同时解决这两个问题的一种方法是拥有最佳实践的清单。以下是改进 MLOps 生产力和网络安全性的数据治理的部分最佳实践清单:
-
使用最小特权原则(PLP)。
-
对数据进行静态和传输加密。
-
假设未自动化的系统是不安全的。
-
使用云平台,因为它们具有共享的安全模型。
-
使用企业支持并参与季度架构和安全审计。
-
培训员工使用的平台,并使他们获得认证。
-
公司参与季度和年度的新技术和最佳实践培训。
-
创建一个健康的公司文化,具备卓越标准、胜任的员工和有原则的领导。
接下来,让我们总结一些对您和您的组织可能有用的 MLOps 设计模式。
MLOps 设计模式
以下示例展示了一部分推荐的 MLOps 设计模式列表:
CaaS
容器即服务(CaaS)对 MLOps 非常有帮助,因为它允许开发人员在他们的桌面或云编辑器上工作在 ML 微服务上,然后通过docker pull命令与其他开发人员或公众共享。此外,许多云平台提供高级 PaaS(平台即服务)解决方案来部署容器化项目。
MLOps 平台
所有云服务提供商都深度整合了 MLOps 平台。AWS 拥有 AWS SageMaker,Azure 拥有 Azure ML Studio,Google 拥有 Vertex AI。在团队庞大、项目庞大、大数据或以上因素同时存在的情况下,使用所用云端的 MLOps 平台将极大地节省构建、部署和维护 ML 应用程序的时间。
无服务器
AWS Lambda 等无服务器技术非常适合快速开发 ML 微服务。这些微服务可以调用云 AI API 来进行自然语言处理、计算机视觉或其他任务,或使用您自己开发的预训练模型或下载的模型。
以 Spark 为中心
许多处理大数据的组织已经具有使用 Spark 的经验。在这种情况下,使用由 Databricks 管理的 Spark 平台或通过云平台管理的 Spark,如 AWS EMR 的 MLOps 能力可能是有意义的。
Kubernetes 中心化
Kubernetes 是一个“云中的盒子”。如果您的组织已经在使用它,使用像 mlflow 这样的基于 Kubernetes 的 ML 技术可能是有意义的。
除了这些建议外,附录 B 中的许多额外资源讨论了从数据治理到云认证的主题。
结论
本书最初源于我在 Foo Camp 与 Tim O’Reilly 和 Mike Loukides 讨论如何使 ML 速度提升 10 倍的讨论。小组的共识是,是的,可以实现 10 倍速度提升!马特·里德利的书《How Innovation Works: And Why It Flourishes in Freedom》(Harper)阐明了一个非直观的答案,即创新是通过更好的执行重新组合想法。我的一位现任同事和前任教授安德鲁·哈加顿,在我在加州大学戴维斯分校攻读 MBA 期间首次向我介绍了这些想法。在他的书《How Breakthroughs Happen》(哈佛商业评论出版社)中,安德鲁提到,网络效应和想法的重新组合是至关重要的。
对于 MLOps 来说,这意味着现有想法的运营卓越是成功的秘诀。希望通过执行快速解决机器学习中的实际问题并迅速解决这些问题的公司可以通过执行创新。世界需要这种创新,以帮助我们通过预防医学如自动高准确率癌症筛查、自动驾驶汽车和适应环境的清洁能源系统来拯救更多生命。
什么也不应该“被排除在外”以提升运营卓越。如果自动机器学习(AutoML)加速了快速原型设计,那就使用它。如果云计算增加了机器学习模型部署的速度,那就实施它。正如最近的事件向我们展示的那样,通过 COVID-19 大流行及其带来的技术突破,比如 CRISPR 技术和 COVID-19 疫苗,我们可以在适当的紧迫感下做出不可思议的事情。MLOps 为这种紧迫感增添了严谨性,并允许我们逐个机器学习模型地帮助拯救世界。
练习
-
尽快构建一个连续训练和连续部署的机器学习应用程序。
-
使用 Kubernetes 堆栈部署一个机器学习模型。
-
使用 AWS、Azure 和 GCP 实现连续交付部署相同的机器学习模型。
-
使用云原生构建系统为机器学习项目创建自动化安全容器扫描。
-
使用基于云的 AutoML 系统和像 Create ML 或 Ludwig 这样的本地 AutoML 系统训练模型。
批判性思维讨论问题
-
您如何构建一个推荐引擎,其负面外部性不如当前社交媒体推荐引擎那么多?您会做出哪些改变,以及如何做?
-
有什么方法可以提高对于建模复杂系统(如营养、气候和选举)的准确性和可解释性?
-
运营卓越如何成为一家希望成为与机器学习相关的技术领导者的公司的秘密武器?
-
如果运营卓越对于 MLOps 至关重要,那么您的组织如何确定正确的人才的招聘标准?
-
解释在机器学习中企业对云计算的支持中运营卓越的角色?这是否重要,为什么?
附录 A. 关键术语
作者:Noah Gift
本节包含经常出现在教授云计算、MLOps 和机器学习工程中的选择性关键术语:
告警
告警是与之相关联有操作的健康度量。例如,当网络服务返回多个错误状态码时,可能会触发发送文本消息到软件工程师的告警。
Amazon ECR
Amazon ECR 是存储 Docker 格式容器的容器注册表。
Amazon EKS
Amazon EKS 是由亚马逊创建的托管 Kubernetes 服务。
自动缩放
自动缩放是根据节点使用的资源量自动缩放负载的过程。
AWS Cloud9
AWS Cloud9 是在 AWS 运行的基于云的开发环境。它具有特殊的钩子,用于开发无服务器应用程序。
AWS Lambda
由 AWS 提供的无服务器计算平台,具有 FaaS 能力。
Azure 容器实例(ACI)
Azure 容器实例是 Microsoft 提供的托管服务,允许您运行容器镜像而无需管理托管它们的服务器。
Azure Kubernetes Service(AKS)
Azure Kubernetes Service 是由 Microsoft 创建的托管 Kubernetes 服务。
black
black 工具能够自动格式化 Python 源代码的文本。
构建服务器
构建服务器是在软件测试和部署中都起作用的应用程序。流行的构建服务器可以是 SaaS 或开源的。以下是一些流行的选择:
-
Jenkins 是一个开源构建服务器,可以在 AWS、GCP、Azure 或 Docker 容器上运行,也可以在您的笔记本电脑上运行。
-
CircleCI 是一个 SaaS 构建服务,可以与像 GitHub 这样的流行 Git 托管提供商集成。
CircleCI
一个在 DevOps 工作流中使用的流行的 SaaS(软件即服务)构建系统。
云原生应用
云原生应用是利用云的独特能力(如无服务器)的服务。
容器
一个容器是与操作系统的其余部分隔离的一组进程。它们通常是几兆字节大小。
持续交付
持续交付是将经过测试的软件自动交付到任何环境的过程。
持续集成
持续集成是在提交到源代码控制系统后自动测试软件的过程。
数据工程
数据工程是自动化数据流的过程。
灾难恢复
灾难恢复是设计软件系统以在灾难中恢复的过程。此过程可能包括将数据归档到另一个位置。
Docker 格式容器
容器有多种格式。一种新兴的形式是 Docker,它涉及定义Dockerfile。
Docker
Docker 是一家创建容器技术的公司,包括执行引擎、通过 DockerHub 的协作平台以及名为Dockerfile的容器格式。
FaaS(函数即服务)
一种云计算类型,促进响应事件的函数。
Google GKE
Google GKE 是由 Google 创建的托管 Kubernetes 服务。
IPython
ipython 解释器是 Python 的交互式终端。它是 Jupyter 笔记本的核心。
JSON
JSON 代表 JavaScript 对象表示法,它是一种轻量级、人类可读的数据格式,在 Web 服务中被广泛使用。
Kubernetes 集群
Kubernetes 集群是部署 Kubernetes 的一个实例,包括节点、Pod、API 和容器的整个生态系统。
Kubernetes 容器
Kubernetes 容器是部署到 Kubernetes 集群中的 Docker 镜像。
Kubernetes pod
Kubernetes Pod 是一个包含一个或多个容器的组。
Kubernetes
Kubernetes 是一个用于自动化容器化应用操作的开源系统。谷歌在 2014 年创建并开源了它。
负载测试
负载测试是验证软件系统规模特性的过程。
Locust
Locust 是一个接受 Python 格式的负载测试场景的负载测试框架。
记录
记录是创建关于软件应用运行状态的消息的过程。
Makefile
Makefile 是包含用于构建软件的一组指令的文件。大多数 Unix 和 Linux 操作系统都内建支持这种文件格式。
指标
指标是为软件应用创建关键绩效指标(KPI)的过程。一个参数的示例是服务器使用的 CPU 百分比。
微服务
微服务是一个轻量级、松耦合的服务。它可以小到一个函数的大小。
迁移
迁移是将应用程序从一个环境迁移到另一个环境的能力。
Moore 定律
对于一段时间来看,微芯片上的晶体管数量每两年翻倍一次。
运维化
使应用程序准备好进行生产部署的过程。这些操作可能包括监控、负载测试和设置警报。
pip
pip 工具用于安装 Python 包。
端口
端口是网络通信端点。一个端口的示例是通过 HTTP 协议在端口 80 上运行的 Web 服务。
Prometheus
Prometheus 是一个带有高效时间序列数据库的开源监控系统。
pylint
pylint 工具检查 Python 源代码的语法错误。
PyPI
Python 包索引,发布的包可供工具如 pip 安装。
pytest
pytest 工具是用于在 Python 源代码上运行测试的框架。
Python 虚拟环境
Python 虚拟环境通过将 Python 解释器隔离到一个目录并在该目录中安装包来创建。Python 解释器可以通过 python -m venv yournewenv 执行此操作。
无服务器
无服务器是基于函数和事件构建应用程序的技术。
SQS 队列
由亚马逊构建的具有近无限读写能力的分布式消息队列。
Swagger
Swagger 工具是一个简化 API 文档创建的开源框架。
虚拟机
虚拟机是物理操作系统的仿真。它可能有几 GB 大小。
YAML
YAML 是一种人类可读的序列化格式,通常用于配置系统。它很容易转换成 JSON 格式。
附录 B. 技术认证
作者:Noah Gift
“MLOps”这个术语直接暗示着与 IT、运维以及其他传统技术学科通过“Ops”短语的牢固联系。技术认证在验证行业专业人员技能方面一直发挥着重要作用。认证专业人士的薪水令人印象深刻。根据 Zip Recruiter,2021 年 2 月,AWS 解决方案架构师的平均工资为 $155k。
从这个话题的角度考虑的一个方法是考虑“三重威胁”的概念。在篮球中,这意味着一个球员如此全面,他们在篮球比赛中取得了 10+个篮板、10+个助攻和 10+分。你可以将这种方法应用到 MLOps 的职业生涯中。拥有一系列你的工作示例、获得认证,并具有工作经验或相关学位。
AWS 认证
让我们来看看一些 AWS 认证选项。
AWS 云从业者和 AWS 解决方案架构师
我建议 AWS 云中的 MLOps 专家获得认证。AWS 云从业者是进入 AWS 认证世界的更温和的引子,类似于 AWS 解决方案架构师认证。我经常教授这个认证给不同类型的学生:数据科学硕士学生、非技术业务专业人员和现有的 IT 专业人员。以下是一些与这两种认证相关的常见问题及答案,特别是针对有机器学习背景的人士准备考试。即使你没有获得 AWS 认证,这些问题对于 MLOps 从业者也是至关重要的,值得测试你的知识水平。
Q:我在连接 RDS 并与一组人共享连接时遇到了问题。有没有更简单的方法?
A:你可能会发现使用 AWS Cloud9 作为连接到 RDS 的开发环境更为简单。你可以在亚马逊的 演示 中看到具体步骤。
Q:云服务模型很令人困惑。PaaS 是什么,它与其他模型有什么不同?
A:考虑云服务模型的一种方法是将其与食品行业进行比较。你可以在像 Costco 这样的商店批量购买食品。它有着可观的规模,并且可以将采购价格折扣传递给客户。作为顾客,你可能还需要将这些食品带回家中,准备并烹饪它们。这种情况类似于 IaaS。
现在让我们看看像 Grubhub 或 Uber Eats 这样的服务。你不仅不必驾车去商店取食物,而且食物已经被准备、烹饪并送到你手中。这种情况类似于 PaaS。你所需要做的就是享用食物。
如果你看 PaaS(平台即服务),它的意思是作为开发者,你可以专注于业务逻辑。因此,许多软件工程的复杂性都消失了。两个早期 PaaS 服务的绝佳例子是Heroku和Google App Engine。AWS 上一个完美的PaaS 是 AWS SageMaker。它解决了创建和部署机器学习涉及的许多基础设施问题,包括分布式训练和提供预测。
Q: 边缘位置的确切定义是什么?这并不明显。
A: AWS 边缘位置是世界上物理位置的实际位置。边缘位置与数据中心不同,因为它们服务更窄的目的。用户离服务器物理位置越近,请求的延迟就越低。这在内容传递(如流媒体视频和音乐)和游戏中至关重要。AWS 上最常提到的边缘服务是 CloudFront。CloudFront 是 CDN(内容分发网络)。通过 CDN,在世界各地的这些位置都有缓存或副本相同的电影文件。这使得用户都能够在流式传输内容时拥有良好的体验。
其他使用边缘位置的服务包括Amazon Route 53、AWS Shield、AWS Web 应用程序防火墙和Lambda@Edge。
Q: 如果一个可用性区域(AZ)中的一个数据中心受到火灾影响怎么办?数据中心在自然或人为灾难方面如何相互关联?系统应该如何架构以实现数据复制?
A: 作为共享安全模型的一部分,亚马逊负责云,客户负责云中的内容。这意味着数据在灾难性不可预见的失败(如火灾)中是安全的。此外,如果发生故障,该区域内的数据在故障期间可能不可用,但最终会恢复。
作为架构师,客户有责任利用多 AZ 架构。一个很好的例子是Amazon RDS 多 AZ 配置。如果一个区域出现故障,备用故障转移数据库将已复制的数据处理请求。
Q: HA 是什么?
A: 高可用性(HA,Highly Available)服务是专为可用性设计的服务。这意味着故障是预期的,并且设计支持数据和服务的冗余。一个优秀的 HA 服务的例子是Amazon RDS。此外,RDS 多 AZ 设计通过允许在可用区间复制数据库的多个版本,支持最小中断服务。
Q: 你如何在抢占式和按需之间做出决定?
A: 中断和按需实例都按固定金额计费的第一分钟,然后按秒计费。中断实例是最具成本效益的,因为它们可以节省高达 90%的费用。在任务运行时间或被中断不重要时使用中断实例。实际上,这为中断实例创建了一个关键的使用案例。以下是一些示例:
-
对 AWS 服务进行实验
-
训练深度学习或机器学习作业
-
扩展 Web 服务或其他服务,结合按需实例
当工作负载处于稳定状态时,按需实例可正常工作。例如,生产中的 Web 服务不应仅使用中断实例。相反,可以从按需实例开始,并在服务使用计算时(即 2 个 c4.large 实例)购买保留实例。
Q: 中断实例的休眠如何工作?
A: 有几个中断的实例的原因,包括价格(出价高于最高价格)、容量(没有足够的未使用的中断实例)和约束(即,可用区目标大小过大)。为了休眠,它必须有一个 EBS 根卷。
Q: EC2 中的标签是用来做什么的?
A: 在图 B-1 中,EC2 实例附有标签。这些标签可以将实例类型分组为逻辑组,如“Web 服务器”。

图 B-1. EC2 标签
使用 EC2 资源的标签的主要原因是将元数据附加到一组机器上。以下是一个场景。假设有 25 个 EC2 实例正在运行购物网站,并且它们没有标签。稍后,用户会再启动另外 25 个 EC2 实例来临时执行任务,例如训练机器学习模型。在控制台中,确定哪些机器是临时的(可以删除)和哪些是生产机器可能会有挑战。
不要猜测机器的角色,最好是分配标签以便用户可以快速识别角色。这个角色可以是:Key="role", Value="ml",或者可以是 Key="role", Value="web"。在 EC2 控制台中,用户可以按标签查询。然后,此过程允许批量操作,如终止实例。标签在分析成本中也扮演重要角色。如果机器角色包含标签,则成本报告可以确定某些机器类型是否过于昂贵或使用了太多资源。您可以阅读亚马逊的官方标签文档。
Q: 什么是 PuTTY?
A: 在图 B-2 中,PuTTY SSH 工具允许从 Windows 远程访问 Linux 虚拟机的控制台。

图 B-2. PuTTY
PuTTY 是在 Windows 操作系统上使用的免费 SSH 客户端。MacOS 和 Linux 内置支持 SSH。SSH 是什么?它是一种用于执行网络操作的加密网络协议。SSH 用于登录远程计算机并通过终端命令管理设备。
您可以在这里阅读官方 PuTTY 文档。
Q: Lightsail 如何与 EC2 或其他服务不同?
A: Lightsail 是一个 PaaS 或平台即服务。这意味着开发人员只需关心配置和开发 WordPress 应用程序。EC2 则是较低级别,被称为 IaaS(基础设施即服务)。云计算有一个光谱,低级别服务就像 Costco 的批量原料一样提供。这些批量原料可以创造出餐点,但需要技能。同样,一个人可以订购餐点送到家里。这些餐点更昂贵,但需要较少的专业知识。PaaS 类似;用户为更高级别的服务支付更多费用。
其他 PaaS 解决方案(除了 AWS 外)包括Heroku和Google App Engine。您可以在《Python for DevOps》(O’Reilly)的“云计算”章节中了解更多有关云服务类型的信息。
Q: 我读到一个 AMI 的用例,描述如下:“用它复制到一组机器(深度学习集群)。”什么是机器组?
A: 舰队的工作方式与租车公司类似。当您要求车辆预订时,他们会要求您选择一个组:紧凑型、轿车、豪华型或卡车。不能保证特定型号,只能保证特定组。同样,因为 spot 实例是一个开放市场,可能某个特定机器,比如 C3.8XLarge 不可用,但可能有类似的组合。您可以通过选择舰队请求相同 CPU、内存和网络能力的资源组。您可以在亚马逊的博客上阅读更多关于EC2 Fleet的信息。
Q: 对于按需实例大小,"spikey" 是什么意思?
A: “spikey” 工作负载可以是突然增加到 10 倍流量的网站。假设这个网站销售产品。一般情况下,一年中的流量是固定的,但到了十二月份,流量激增到原来的 10 倍。这种情况适合使用“按需”实例扩展以满足需求。预期的流量模式应使用预留实例,但对于这种峰值,应使用按需实例。您可以在亚马逊的博客上阅读更多关于spikey traffic和预留实例的信息。
Q: AWS Lambda 的“SUBSECOND”优势是什么意思?
A: 这意味着你可以设计一个高效的服务,并仅在每个请求的持续时间以 100ms 的间隔计费。这种情况与 EC2 实例不同,后者会按秒计费,因为它们是连续运行的。使用 Lambda 函数,你可以设计一个基于事件的工作流,其中 Lambda 仅响应事件而运行。一个好比喻是传统的手动开关灯和运动检测灯。更高效的方法是运动检测灯;它会根据运动情况自动开关。这种方法类似于 AWS Lambda;它根据事件开启、执行任务,然后退出。你可以在 Amazon 的文档中了解更多关于 Lambda 的信息。你还可以在 GitHub 上构建一个 Python AWS Lambda 项目。
Q: 对于 AWS S3,有几种存储类别。IA (Infrequent Access) 存储层包括标准 IA 和单区域 IA 吗?还是还有其他类型?我在 AWS 网站 的 INFREQUENT ACCESS 部分只看到标准和单区域。
A: 有两种 IA(低频访问)类型。标准 IA 存储在三个 AZ(可用区),而单区域则只有一个区域。单区域的一个关键区别是可用性。它的可用性为 99.5%,比三区域 IA 和标准 IA 都低。低成本反映了这种降低的可用性。
Q: Elastic File System (EFS) 是如何工作的?
A: EFS 的概念工作方式类似于 Google Drive 或 Dropbox。你可以创建一个 Dropbox 帐户,并与多台计算机或朋友分享数据。EFS 的工作方式非常类似。同一文件系统对于安装它的机器是可用的。这个过程与 EBS(弹性块存储)非常不同,后者一次只能属于一个实例。
Q: 对于 ELB 的使用情况,我不理解这两种用例:1)什么是“单点访问”?它是说,如果可以通过一个端口或服务器控制流量,那么安全性会更高吗?2)“解耦应用程序环境”是什么意思?
A: 让我们以网站为例。网站将在 443 端口运行,这是 HTTPS 流量的端口。这个站点可能是 https://example.com。ELB 是唯一向外界公开的资源。当 Web 浏览器连接到 https://example.com 时,它只与 ELB 通信。同时,ELB 将向其后面的 Web 服务器请求信息,然后将该信息返回给 Web 浏览器。
在现实世界中的类比是什么?就像驱动通道中的银行出纳员。你开车到窗口但只与银行出纳员联系。银行里有很多人在工作,但你只与一个人互动。你可以在 AWS 博客 上阅读有关 ELB 的博文。
Q: 为什么 ELB 的使用案例与经典负载均衡器相同?
A: 它们具有相同的特点:通过单一入口点访问,解耦的应用环境;它们提供高可用性和容错性,并增加了弹性和可伸缩性。
Elastic Load Balancing 是负载均衡器的一类。它包括应用负载均衡器、网络负载均衡器和经典负载均衡器。从高层次来看,经典负载均衡器是一种较旧的负载均衡器,功能较少,不如应用负载均衡器。它适用于已经在服务中使用的较旧的 EC2 实例。
这些被称为 EC2 经典实例。在新场景中,像 HTTP 服务这样的应用负载均衡器将是理想的选择。您可以阅读关于 ELB 功能比较在亚马逊文档中的博客文章。
AWS 认证机器学习专业人员
商学院和数据科学学院完全接受教学认证。在加州大学戴维斯分校,我教授学生传统的学分课程材料,如机器学习,包括 AWS 云从业者和 AWS 认证机器学习专业人员。与 AWS 一起,我与其组织的许多部分密切合作,包括 AWS Educate、AWS Academy 和 AWS ML Hero。
如您所料,我建议获得 AWS 机器学习认证。了解到许多参与创建 AWS ML 认证的人员,并可能在其创建中有所影响,我喜欢它重视 MLOps 的特点。因此,让我们更详细地深入研究它。
推荐的候选人 需要具备至少 1-2 年的开发、架构或运行 ML/深度学习工作负载的经验和知识。实际上,这意味着能够表达基本 ML 算法背后的直觉,执行基本的超参数优化,具有 ML 和深度学习框架的经验,遵循模型训练的最佳实践,并遵循部署和运营的最佳实践。简而言之,阅读这本书是准备获得认证的绝佳步骤!
考试结构分为几个领域:数据工程、探索性数据分析、建模以及机器学习实施与运营。特别是考试的最后一部分,实际上是关于 MLOps 的内容。因此,让我们深入探讨这些部分,看看它们如何应用于本书涵盖的概念。
数据工程
在 AWS 上进行数据工程、分析和机器学习的一个核心组件是数据湖,它也恰好是 Amazon S3。为什么要使用数据湖(图 B-3)?核心原因如下。首先,它提供处理结构化和非结构化数据的能力。数据湖还允许进行分析和 ML 工作负载。第三,您可以在不移动数据的情况下处理数据,这在处理大数据时非常重要。最后,它成本低廉。

图 B-3. 数据湖
AWS 数据工程中另一个重要的话题是批处理与流处理数据。让我们先定义流处理数据。流处理数据通常是从许多来源发送的小数据。例如日志文件、指标和时间序列数据,如股票交易信息。在 AWS 上处理流处理的主要服务是 Kinesis。以下是几个理想的应用场景:时间序列分析问题、实时仪表板和实时指标。
批处理与流处理对 ML 流水线开发有显著影响。在批处理中,可以更好地控制模型训练,因为可以决定何时重新训练 ML 模型。持续重新训练模型可能会提供更好的预测结果,但也增加了复杂性。例如,在 SageMaker 端点中可用于模型 A/B 测试。因此,这将需要考虑到架构中。
对于批处理,有几种工具可以作为批处理机制。这些工具包括 Amazon EMR/Spark、AWS Glue、AWS Athena、AWS SageMaker 和名为AWS 批处理服务的服务。特别是对于机器学习,AWS 批处理解决了一个独特的问题。例如,想象一下,您希望扩展成千上万个同时进行的独立 k-means 聚类作业。使用 AWS 批处理是一种方法。此外,您可以通过编写一个 Python 命令行工具来为批处理系统提供简单的界面。以下是一段代码片段,展示了实际应用中可能的情况:
@cli.group()
def run():
"""AWS Batch CLI"""
@run.command("submit")
@click.option("--queue", default="queue", help="Batch Queue")
@click.option("--jobname", default="1", help="Name of Job")
@click.option("--jobdef", default="test", help="Job Definition")
@click.option("--cmd", default=["whoami"], help="Container Override Commands")
def submit(queue, jobname, jobdef, cmd):
"""Submit a job to AWS Batch SErvice"""
result = submit_job(
job_name=jobname,
job_queue=queue,
job_definition=jobdef,
command=cmd
)
click.echo(f"CLI: Run Job Called {jobname}")
return result
处理数据工程的另一个关键方面是使用 AWS Lambda 处理事件。需要注意的是,AWS Lambda 是一个能够深度集成到大多数 AWS 服务中的工具。因此,在进行基于 AWS 的数据工程时,很可能会在某个时刻遇到 AWS Lambda。
AWS 的首席技术官认为“一个大小的数据库并不适合所有人”。他所说的内容在图 B-4 中有清晰的描述。

图 B-4. 一个大小的数据库
另一种表达这一点的方式是使用最适合工作的工具。可以是关系数据库;也可以是键/值数据库。以下示例展示了在 Python 中使用基于 DynamoDB 的 API 有多简单。大部分代码是日志记录。
def query_police_department_record_by_guid(guid):
"""Gets one record in the PD table by guid
In [5]: rec = query_police_department_record_by_guid(
"7e607b82-9e18-49dc-a9d7-e9628a9147ad"
)
In [7]: rec
Out[7]:
{'PoliceDepartmentName': 'Hollister',
'UpdateTime': 'Fri Mar 2 12:43:43 2018',
'guid': '7e607b82-9e18-49dc-a9d7-e9628a9147ad'}
"""
db = dynamodb_resource()
extra_msg = {"region_name": REGION, "aws_service": "dynamodb",
"police_department_table":POLICE_DEPARTMENTS_TABLE,
"guid":guid}
log.info(f"Get PD record by GUID", extra=extra_msg)
pd_table = db.Table(POLICE_DEPARTMENTS_TABLE)
response = pd_table.get_item(
Key={
'guid': guid
}
)
return response['Item']
在数据工程中讨论的另外三个重要事项是 ETL、数据安全性以及数据备份与恢复。在 ETL 中,关键服务包括 AWS Glue、Athena 和 AWS DataBrew。AWS DataBrew 是较新的服务,它解决了在构建生产机器学习模型中一个必要的步骤,即自动化数据清理中混乱的步骤。例如,在图 B-5 中,一个数据集“婴儿姓名”被轻松进行了概要分析,而没有写一行代码。

图 B-5. DataBrew
之后,同一数据集可能成为 MLOps 项目的重要组成部分。一个有用的功能是跟踪数据集的血统,它来自哪里,以及与数据集相关的操作。此功能可通过“数据血统”选项卡获得(参见图 B-6)。
数据治理是解决由于数据安全和数据备份与恢复而引起的关注的简明方式。AWS 通过 KMS(密钥管理服务)允许集成加密策略。这一步骤至关重要,因为它支持静态和传输中的加密实施,以及最小特权原则(PLP)。

图 B-6. DataBrew 血统
数据安全的另一个方面是记录和审计对数据的访问。定期审计数据访问是识别风险并加以减轻的一种方法。例如,您可能会标记一个用户定期查看与其工作无关的 AWS Bucket,然后意识到这造成了一个重大的安全漏洞,需要加以解决。

图 B-7. AWS Cloud-Trail
最后,数据备份和恢复可能是数据治理中最重要的方面之一。大多数 AWS 服务都具有快照功能,包括 RDS、S3 和 DynamoDB。为数据设计一个有用的备份、恢复和生命周期管理,以满足存档到 Amazon Glacier 的最佳实践合规性是至关重要的。
探索性数据分析(EDA)
在进行机器学习之前,首先需要对数据进行探索。在 AWS 上,有几种工具可以帮助。这些工具包括之前介绍过的 DataBrew 示例以及 AWS QuickSight。让我们来看看您可以在 AWS QuickSight 中完成的工作,参见图 B-8。

图 B-8. AWS QuickSight
注意,这种无代码/低代码方法揭示了社交媒体上胜利与受欢迎度之间的幂律关系,即来自维基百科的页面浏览量。粉丝可能最密切地与“赢家”接近,注意到他们,并希望阅读更多关于这些球员的信息。这一初步的 EDA 步骤可能会立即导向开发使用粉丝行为预测哪些队伍更有可能赢得 NBA 赛季的机器学习模型。
自行复制此图表非常简单:下载CSV 文件,告知 QuickSight 执行新分析,使用 CSV 文件创建新数据集,然后选择“创建分析”。
了解 EDA 在 MLOps 中的角色至关重要。EDA 帮助检测异常值,发现隐藏模式(通过聚类),查看数据分布并创建特征。在使用聚类时,重要的是记住数据需要进行缩放。缩放数据可以标准化大小。例如,如果两个朋友跑了“50”,重要性是需要考虑的关键因素。一个朋友可能跑了 50 英里,另一个可能只跑了 50 英尺。它们是非常不同的事情。如果不进行缩放,机器学习的结果会因一个变量或列的大小而失真。以下示例显示了缩放在实践中的效果:
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
scaler = StandardScaler()
print(scaler.fit(numerical_StandardScaler(copy=True,
with_mean=True, with_std=True)
# output
# [[ 2.15710914 0.13485945 1.6406603 -0.46346815]
EDA 中的另一个概念是数据预处理的概念。预处理是一个广泛的术语,可以适用于多种场景。例如,机器学习需要数据是数值型的,因此一种预处理形式是将分类变量编码为数值格式。
注意
编码分类数据是机器学习的重要组成部分。有许多不同类型的分类变量。
-
分类(Discreet)变量
- 有限的值集:{green, red, blue} 或
-
分类类型:
-
有序(Ordered):
-
名义(无序):
-
-
表示为文本
另一种预处理形式是创建新特征。让我们以 NBA 球员年龄为例。一个图表显示年龄呈正态分布,中位数约为 25 岁(见 图 B-9):
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
df = pd.read_csv(
r"https://raw.githubusercontent.com/noahgift/socialpowernba" \
r"/master/data/nba_2017_players_with_salary_wiki_twitter.csv")
sns.distplot(df.AGE)
plt.legend()
plt.title("NBA Players Ages")

图 B-9. NBA 球员年龄
我们可以利用这些知识创建一个新特征。该特征可以将年龄转换为几个类别:新秀、巅峰、巅峰后和退休前。这些类别可能会带来未来的洞察:
def age_brackets (age):
if age >17 and age <25:
return 'Rookie'
if age >25 and age <30:
return 'Prime'
if age >30 and age <35:
return 'Post Prime'
if age >35 and age <45:
return 'Pre-Retirement'
然后,我们可以使用这些新分类来对数据进行分组,并找出每个分组的中位数工资:
df["age_category"] = df["AGE"].apply(age_brackets)
df.groupby("age_category")["SALARY_MILLIONS"].media
请注意,中位数工资存在显著差异。球员刚开始时薪水最低,但在巅峰时期薪水大约会增加三倍。一旦退休,薪酬将急剧下降,为他们巅峰时期的一半:
age_category
Post-Prime 8.550
Pre-Retirement 5.500
Prime 9.515
Rookie 2.940
Name: SALARY_MILLIONS, dtype: float64
机器学习实施与运营(MLOps)
让我们在 AWS ML 认证考试的概念中讨论 MLOps 的一些关键组成部分。在构建模型时,以下关键概念至关重要:
-
监控
-
安全性
-
重新训练模型
-
A/B 测试
-
TCO(总体拥有成本)
MLOps 本身如何?以下是需要考虑的关键因素:
-
您是否使用了足够简单的模型?
-
您是使用数据湖还是直接连接到生产 SQL 数据库?
-
您是否已为预测阈值失败设置了警报?
-
您是否有开发、测试和生产环境?
最后,两个值得讨论的主题是故障排除生产部署和 ML 系统的效率。对于生产部署,这些概念包括使用 CloudWatch、搜索 CloudWatch 日志、对关键事件进行警报、使用自动缩放功能和使用企业支持。
在考试和实际应用中,了解以下关键概念对于 ML 系统的成本和效率至关重要:
-
Spot 实例(显示 spot 代码)
-
正确使用 CPU 与 GPU 资源
-
扩展和缩小规模
-
上市时间
-
AI API 与“自己动手”
其他云认证
除了 AWS,Azure 和 GCP 也有显著的认证。
Azure 数据科学家和 AI 工程师
Azure 有一些 值得关注的认证,包括 Azure 数据科学家 和 Azure AI 工程师。这些认证分为三个级别(按照专业水平排序):基础、关联和专家。此外,还有几条与 Azure AI 平台密切相关的 学习路径 与 MLOps 相关。
-
使用 Azure 机器学习创建无代码预测模型(链接)
一个良好的起点是浏览 培训指南,其中列出了一系列引人注目的旅程(例如数据和 AI 专业人员)和明确的认证,如 Azure AI 基础知识。这些 旅程 可让您决定最佳的实现目标的策略。
此外,如果您是 学生 或 教职工,还有免费(或大部分免费)的资源可帮助您在 Azure 上入门。这些服务通常会随时间变化,但您可以在不需要信用卡的情况下立即开始。如果您是教育工作者,还有其他 提供和资源 可供您使用,同样也能提供帮助。
GCP
一些值得关注的 MLOps 从业者认证包括 专业机器学习工程师 和 专业云架构师。最后,一个非常具体的与 MLOps 相关的认证是 TensorFlow 开发者证书,它允许您展示使用 TensorFlow 解决深度学习和 ML 问题的能力。
与 SQL 相关的认证
要成功进行 MLOps,需要有 SQL 的基本知识。稍后建议深入学习 SQL,理论上和应用上都要掌握。以下是一些推荐的资源:
-
Databricks 认证 Apache Spark 3.0 关联开发者
-
学习材料:Databricks 网站和 O’Reilly 学习平台
-
O’Reilly 学习平台:学习 Spark
-
O’Reilly 学习平台:Spark:权威指南
-
-
-
Microsoft Certified:Azure 数据基础
-
学习材料:Coursera、Microsoft Learn 和 O’Reilly 学习平台
-
O’Reilly 学习平台:考试参考 DP-900
-
-
-
学习材料:O’Reilly 学习平台和 Oracle
-
O’Reilly 学习平台:OCA Oracle Database SQL 考试指南
-
Oracle:Oracle Database SQL 认证专家认证
-
-
-
-
学习材料:Coursera 和 O’Reilly 学习平台
-
O’Reilly 学习平台:数据治理:权威指南
-
O’Reilly 学习平台:Google 云平台数据科学
-
O’Reilly 学习平台:Google BigQuery:权威指南
-
Coursera:Google 数据分析专业
-
-
你可以在 Coursera 找到更多参考资料。
附录 C. 远程工作
由 Noah Gift
在 COVID-19 后的世界中,拥有一个稳定的家庭办公室,可以让你完成工作至关重要。另一个因素是远程优先优化了工作成果。面对面环境的一个显著问题是“外表”与实际进展的差异。被拖入毫无结果的会议几个小时就是一个很好的例子。销售团队打扰在开放式办公计划中编写代码的开发人员是另一个问题。当专注于结果时,远程优先开始变得非常合理。
在过去的几年里,我一直在不断“改造”我的家庭办公室,以适应全球教学以及在主要顶级大学的工作,并进行远程软件工程和咨询。你可以在图 C-1 中看到我的设置。我想带领你了解如何设置你自己的工作空间,以便你可以高效工作。

图 C-1. 在家工作
远程工作设备
网络
如果你远程工作,以下是一些需要考虑的事项的简要非详尽列表。一个可靠的家庭网络可能是任何远程工作清单上最关键的项目。理想情况下,你可以以低于 100 美元的价格获得低成本的光纤连接。光纤是理想的,因为你的上行和下行速度是一样的。请注意,不仅在美国的许多地区 1GB 的光纤是标准,2GB 的光纤也越来越普遍。
在设置家庭网络时,有一些必要的细节需要注意。让我们接下来讨论这些细节。
家庭物理网络
最好将你的工作站通过以太网插入家庭光纤或有线网络。这一步骤可以消除干扰远程工作的一系列问题,特别是无线问题。实现这一目标的一个很好的方法是购买一个能够达到 2.5 GB 或更高速度的廉价网络交换机,并使用 Cat6 网络电缆连接(可提供高达 10 Gbps 的速度)。这样,如果你有 2GB 的光纤连接,你可以直接利用其全速度。
除了有线网络外,无线网格网络可以带来巨大的回报。建议的无线设置是使用网格 WiFi 6 路由器。这些路由器可以覆盖整个家庭,甚至覆盖面积超过 5,000 平方英尺的区域,并且无线速度可以超过 1 Gbps。现代网格网络还允许数百个同时连接,这对于家庭网络来说绰绰有余。
能源管理和家庭网络
当你的家变成一个实际的永久办公室时,有两个重要的事情需要考虑:成本和可靠性。例如,如果你经常停电,你可能会损失大量的业务收入。同样,你的电费可能会显著增加。
通过太阳能家庭设置,你可以帮助保护环境,降低家庭公用事业成本,并在停电期间解决业务连续性问题。首先,使用 UPS(不间断电源)处理风暴和电力故障,并将你重要的家庭办公和家庭网络设备插入其中。
最后,特斯拉 Powerwall 电池或类似的电池可以提供数天的备用电力,因为它能够从太阳能充电。这种设置使你能够在任何大风暴期间继续工作。此外,对于太阳能设计,还有显著的税收优惠,进一步增加其吸引力。
家庭工作区
站立式办公桌、宽屏显示器、良好的麦克风和优秀的摄像头能够大大提高工作效率,希望也能缩短工作时间。一个重要的考虑因素是购买你能负担得起的最好工具,因为它们能让你赚钱并提高生产力。
健康和工作区域
你能否引入站立式办公桌,减少腰部损伤并促进更多的身体活动?你是否每天通过五组每组 20 下做 100 个壶铃摆动?你能否在大脑开始感到超负荷时安排每天的散步?这些看起来很微小,但小事物确实能够显著改善你的健康、工作效率和幸福感。
最后,你能否消除大多数企业垃圾食品习惯,改为间歇性禁食结合健康食物?阅读附录 F 中的间歇性禁食,了解我的旅程和研究。
家庭工作空间虚拟工作室设置
拥有摄像机背景可以极大地提升专业水平。这种设置包括良好的照明,一些“布景”以及其他能够增强背景的物品。请注意,在这些设置中,语音自动化可能是一个巨大的优势,因为在开启摄像头前并不需要大灯。
在图 C-2 中,我的背景设置使视频会议呼叫看起来更吸引人。这是远程工作经常被忽视的一个方面。

图 C-2. 工作室背景
位置,位置,位置
你能否搬到一个低成本地区,那里既有自然环境又有好学校?在后疫情时代,我们可以预见到一种新的现实可能会让数据科学思维的远程工作者优化健康、生活质量和成本。一个推动因素是房屋所有权的成本。在美国的某些地区,比如旧金山湾区,建立工作力量是没有意义的。哈佛大学住房研究联合中心(JCHS)有许多互动数据可视化,详细解释了这一点。你是为了生活而工作还是为了工作而生活?
当你将生活优化为工作与生活平衡、家庭和健康时,你可能会发现,昂贵的抵押贷款可能对你的目标产生反效果。远程工作使你能够深思熟虑你最大的支出:你居住的地方。一个很好的网站,可以帮助你决定居住在哪里,是Numbeo;你可以考虑天气、生活成本、犯罪率、教育等因素。此外,在像纽约和旧金山这样的传统科技中心之外,还有许多令人难以置信的居住地点。
附录 D. 为你的职业像 VC 一样思考
作者:Noah Gift
在建立机器学习职业生涯时的一个关键考虑是像风险投资家(VC)那样思考。风险投资家与普通员工有什么不同?他们的一个做法是通过投资一系列公司来设计面向失败的策略。那么为什么不对你的职业也采取同样的策略呢?假设你从一开始就考虑了一个多公司策略。在那种情况下,你可以始终专注于长期获取更多技能、建立副业和作品集,以及除了工作之外的收入。
此外,理解收入和支出使你能够建立自主权——比如说,放下你正在做的事情,深入研究下一代 MLOps 技术。让我们深入探讨如何考虑收入和支出。
梨形收入策略
我们生活在一个新时代,在这个时代,只需一台笔记本电脑和一个互联网连接就可以开始一项业务。作为一名长期的顾问和企业家,我已经开发了一个对我有效的框架。在评估与谁合作以及选择什么项目时,我会考虑 PPEAR 或“梨形”:
-
P(被动)
-
P(积极)
-
E(指数)
-
A(自主权)
-
R(25%规则)
这是你如何利用这个“副业”框架。
被动
许多人跳槽追求更高的薪水,但薪水是固定的:无论你的工作有多出色,你的报酬依然相同。被动收入是投资于指数结果的一种形式。每个技术工作者都应该在薪水和某些能够提供指数结果的资产之间取得平衡。理想情况下,这种结果直接与你的工作相关:
-
这个行动是否会带来被动收入:书籍、产品、投资?
-
你拥有客户吗?理想情况下,你应该专注于持有客户。
-
什么是版税关系?
捕食者(20%或更低)
与捕食者合作应该有一个非常有说服力的理由。也许他们能为你带来曝光,或者他们愿意冒险与你合作。捕食者的缺点是他们经常有繁文缛节的流程。你必须与多少层人员互动才能完成任何事情?完成某件事需要多长时间?这可能比单独工作要长 10 到 100 倍。
合作伙伴(50%或更高)
关于平等伙伴关系,有很多令人喜欢的地方。伙伴在金钱和时间上都有“一份赌注”。
平台(80%或更高)
使用平台有利有弊。平台的优势在于,如果你自给自足,你可以保留大部分收入。缺点是你可能还没有标杆。你可能还没有一个“好”是什么的框架。你可能希望先与一位捕食者合作,看看他们是如何做事情的,然后再转向平台。
注意
并不是每个人都想成为作者或创作者,但每个人都可以成为投资者。也许将你的 W2 收入的 50%投入到指数基金或出租房屋中会更好。
积极的
在项目或与合作伙伴合作时,它必须是一个积极的体验。即使很好地支付最终也会变得老套,如果环境有毒的话。可以问一些问题:
-
我每天是否感到快乐?
-
我是否尊重每天与我共事的人?
-
我与我一起工作的人是否都是成就卓越并具有成功记录的人?
-
我的健康是否在睡眠、健身和营养方面得到了提高或保持?
-
由于你是与你花费最多时间的五个人的平均值,你如何与积极的人在一起?
指数
在项目或与合作伙伴合作时,另一个重要问题是指数潜力。也许你已经决定与一个具有指数潜力项目的捕食者伙伴合作。另一方面,如果你与一个捕食者合作,但项目没有指数潜力,那么也许这不是一个好项目。
这个项目或合作是否会导致收入、用户、流量、媒体曝光或声誉呈指数级增长?
自主性
在项目或与合作伙伴合作时,另一个重要问题是自主性。如果你擅长自己的工作,你需要自由。你知道什么是好的,但你的合作伙伴可能不知道。你有多少独立性?最终是否能够靠自己赌一把,或者成功掌握在别人手中?
一些示例问题包括:
-
这个行动是否增加了自主性或者创建了依赖关系?
-
我是否在学习和成长——掌握了新技能、获得了新的声望或品牌关联?
-
这个任务是可以自动化的还是需要手动操作?避免那些无法自动化的任务。
25%法则
你赚的钱是什么颜色?图表 D-1 展示了考虑收入的三种方式:员工、顾问或投资者。
作为员工可能对你有价值,因为你可以学习技能并建立人脉。但要记住,这是“红色”金钱。红色的钱随时可能消失。你没有控制权。

图表 D-1. 你的钱是什么颜色?
咨询是“黄色”金钱。这是朝正确方向迈出的一大步。你可以在做员工的同时进行一些咨询。这一行动减少了作为员工的风险。然而,作为顾问,你必须小心,永远不要让一个客户占到你总收入的 25%,最好不要让一个顾客占到你咨询收入的 25%。熟悉产生蔑视。最好的关系是当人们表现最好的时候,他们知道链接只是为了解决问题。
像房地产、指数基金和数字产品这样的投资是“绿色”金钱。这种收入流将永远为你付款。理想情况是用绿色金钱赚取 80%的收入,将咨询或就业限制在 20%的收入内。
注释
下面的注释和资源有助于完善这个附录:
-
沃伦·巴菲特在这个主题上有一句著名的话。他说:“如果你不找到一种方法在睡觉时赚钱,你将工作到死。”
-
文章中提供了有价值的相关建议,“1000 True Fans? Try 100”。
附录 E. MLOps 技术作品集的构建
作者是 Noah Gift
我建议每个人都成为“三重威胁”。在篮球中,“三重威胁”指的是能够得分、篮板或助攻的球员。防守者必须防范这三个选项。从职业角度看,你也可以成为“三重威胁”,意味着你能够通过三个方面来通过招聘门卫:技术作品集、相关认证以及工作经验或相关学位。
我在附录 B 中提到了云认证,但在这里我们将深入探讨你的技术作品集。构建作品集项目可以增强你的简历,其中将包含高质量的源代码链接和演示视频,而不是没有的简历。你会召唤哪位应聘者来面试?我会召唤那位在我们会面之前就已经向我展示了他们能力的候选人。
在构建项目时,选择你认为未来两年将会“推动事业”的项目。考虑以下几个想法:
-
GitHub 项目,包含源代码和一个README.md来解释项目。README.md应具有专业质量并使用商务写作风格。
-
100% 可重复的笔记本或源代码。
-
原创作品(不是 Kaggle 项目的复制品)。
-
真实的激情。
-
五分钟的最终演示视频展示其工作原理。
-
演示需要非常技术化,并准确展示如何完成任务,即需要一步一步地教给别人。 (想象一下烹饪节目中厨师演示如何制作巧克力曲奇饼干的程度。这种细节水平需要类似。)
-
视频应至少为 1080p,16:9 宽高比。
-
考虑使用低成本的外部麦克风录制。
在构建 Jupyter 笔记本时,考虑将数据科学项目的步骤分解。通常的步骤包括:
-
摄取
-
探索性数据分析 (EDA)
-
建模
-
结论
对于 MLOps 作品集项目,以下是一些想法,这些想法使学生能够在主要技术公司(即 FAANG)和前沿创业公司中作为 ML 工程师、数据工程师和数据科学家获得工作。
项目:在 PaaS 平台上持续交付 Flask/FastAPI 数据工程 API
测试你构建 API 知识的一个极好的方法是以下项目:
-
在云平台上创建一个 Flask 或 Fast 应用程序,并将源代码推送到 GitHub。
-
配置云原生构建服务器(如 AWS App Runner、AWS Code Build 等)以将更改部署到 GitHub。
-
创建一个真实的数据工程 API。
您可以参考这个O’Reilly walkthrough,了解如何使用 FastAPI 构建微服务函数,或者参考这个sample Github project,这是一个完整的 AWS App Runner 快速入门项目。
项目:Docker 和 Kubernetes 容器项目
许多云解决方案涉及 Docker 格式容器。让我们在以下项目中利用 Docker 格式容器:
-
创造一个定制的 Docker 容器,使用当前版本的 Python 部署一个 Python 机器学习应用程序。
-
将图像推送到 DockerHub、Amazon ECR、Google Container Registry 或其他一些云容器注册表中。
-
在云平台的云 Shell 上拉取镜像并运行它:Google Cloud Shell 或 AWS Cloud9。
-
部署一个应用程序到云托管的 Kubernetes 集群,如 GKE(Google Kubernetes Engine)或 EKS(Elastic Kubernetes Service)等。
项目:无服务器 AI 数据工程管道
复制示例无服务器数据工程项目的架构,如 图 E-1 所示。然后,通过扩展 NLP 分析功能来增强项目:添加实体提取、关键短语提取或其他 NLP 功能。

图 E-1. 无服务器数据工程
该项目的一个良好资源是以下存储库:https://github.com/noahgift/awslambda。
项目:构建边缘机器学习解决方案
使用书中涵盖的技术之一构建并部署基于边缘的计算机视觉解决方案:
-
Intel Movidius Neural Compute Stick 2
-
AWS DeepLens
-
Coral AI
-
智能手机(iOS、Android)
-
树莓派
这里是您项目的交付成果清单:
-
将您的结果发布为 Jupyter 笔记本、Colab 笔记本文件夹或两者都在 GitHub 仓库中。
-
编写一份两页的研究项目概述的 PDF 格式文档。
-
在您的两页简介中包含指向您已发布作品的链接。
-
创建一个 60 秒的演示视频,展示您的工作计算机视觉项目的推断(预测)。
-
将您的项目提交给 Intel 或 AWS 社区项目。
项目:构建云原生机器学习应用程序或 API
构建一个云原生分析应用程序,托管在 Google Cloud Platform(GCP)、AWS、Azure 或其他云或技术上(例如 Kubernetes)。该项目旨在让您能够创建与现代技术兼容的实际工作解决方案。
在开始之前,请阅读 “Hidden Technical Debt in Machine Learning Systems” by Sculley et al. (2015)。
限制复杂性的一个好方法是使用公共数据集。一个例子可以是使用 Google BigQuery 数据集中的公共数据项目(如果使用 GCP)。或者,如果使用 AutoML,数据可以是教程数据或自定义数据。
主要想法是让您考虑开始创建一个作品集。这是建议的 GCP 项目要求清单;您可以为不同的云修改技术堆栈。
-
源代码存储在 GitHub 上
-
从 CircleCI 进行持续部署
-
数据存储在 GCP 中(BigQuery、Google Cloud Storage 等)
-
创建并提供 ML 预测(AutoML、BigQuery 等)
-
安装 Stackdriver 进行监控
-
Google App Engine 通过 REST API 提供 HTTP 请求服务,使用 JSON 载荷
-
部署到 GCP 环境中
-
一份两页、单行间距的论文,描述项目如何在交接阶段由咨询顾问向客户介绍
以下是一个最终项目检查清单的示例,供参考。
-
是否执行 ML 预测/推断?
-
是否有独立的环境?
-
是否有全面的监控和警报?
-
是否使用了正确的数据存储,即关系、图、键/值?
-
是否适用最小特权原则?
-
数据在传输和静置时是否加密?
-
你有对应用程序进行负载测试以验证性能吗?
拿到工作:不要直接攻城,走后门
技术行业总是有一个“梦想”职称。这些职称来了又去。以下是一些例子:Unix 系统管理员、网络管理员、网站管理员、网页开发人员、移动开发人员、数据科学家。当这些职称出现时,公司们会为雇佣这些职位感到恐慌。
然后所有的进展都停滞不前,越来越多的障碍出现。一个经典的例子是在一项技术存在一年时,却要求一个人拥有十年经验。最终,进入“城堡”变得不可能。城堡的前面有守卫、热油、长矛和等待在护城河中的怪物。相反,考虑一个后门。通常后门是一个不那么高级的职称。
如何学习
与其他职业相比,与软件相关的职业是独特的。与软件相关的工作与职业运动员、武术家或音乐家有很多相似之处。实现精通的过程涉及接受痛苦和喜欢犯错。
创造自己的 20%时间
永远不要指望公司是你学习所需的唯一来源。你必须开辟自己的学习路径。其中一种方法是每天花几个小时养成学习新技术的习惯。把它看作是职业生涯的锻炼。
拥抱错误的心态
避免错误和追求完美是很常见的。例如,我们试图避免车祸、掉落的杂货和其他错误,大多数学生都希望在考试中获得完美的“A”成绩。
在成为一个称职的软件工程师的过程中,最好反其道而行之。不断犯错意味着你走在正确的道路上。这周你犯了多少错?今天你犯了多少错?威廉·布莱克在 1790 年最好地表达了这一点,他说:“如果愚人坚持愚蠢,他会变得聪明。”
寻找与学习平行的爱好
如果你询问有 10 年以上经验的成功软件工程师团体,你会听到类似这样的回答:“我是一个学习机器。”那么,一个学习机器如何变得更加优秀?一种方法是选择一项需要多年时间才能掌握的运动,而你在其中完全是个新手,并观察自己。
特别适合这种活动的两款游戏是攀岩和巴西柔术。巴西柔术还有一个额外的副作用,那就是教你实用的自卫技巧。你会发现,在学习中你有一些盲点,然后可以在“真实”的工作中修复它们。
附录 F. 数据科学案例研究:间歇性禁食
By Noah Gift
回到 20 世纪 90 年代初,我就读于加州理工大学圣路易斯奥比斯波分校,并主修营养科学。我选择这个学位是因为我痴迷于成为职业运动员。我觉得学习营养科学可以给我带来额外的优势。我首先找到了关于卡路里限制和衰老的研究。
我还参与了营养生物化学课上的自我实验。我们离心分离了我们的血液,并计算了低密度脂蛋白、高密度脂蛋白和总胆固醇水平。在同一课程中,我们补充了大剂量的维生素 C,然后收集了我们的尿液来查看被吸收了什么。结果显示,在健康的大学生群体中,身体智能地通过增加吸收敏感性来应对营养物质的吸收,当其水平较低时。维生素补充品通常是浪费钱的。
我学习了一年的解剖学和生理学,学会了解剖人体。我了解了克雷布斯循环以及糖原储存的工作方式。身体产生胰岛素来增加血糖,并将其储存到肝脏和肌肉组织中。如果这些区域“满了”,它会把糖原储存到脂肪组织中。同样地,当身体缺乏糖原或进行有氧活动时,脂肪组织是主要的燃料。这种储存就像我们的“备用”油箱一样。¹
我还在加州理工大学作为一个失败的一级田径运动员尝试了一年。我从中吃了不少苦头,其中一件事是过多的举重实际上对跑步等运动表现不利。当时我身高 6 英尺 2 英寸,体重 215 磅,可以一次卧推 225 磅,大约能和美国职业橄榄球联盟的线卫相媲美。我也跑 1500 米(大约一英里)用了 4 分 30 秒,经常在一级长跑运动员中带头进行三英里训练跑。我还能在罚球线附近灌篮,以及在 10.9 秒内跑完 100 米。
简言之,我是一个优秀的运动员,多才多艺,但多年来积极地从事错误类型的锻炼(健美)。我的职业道德水平非常高,但对我选择的运动来说,这种方式效果非常不佳,甚至具有反生产力。我还高估了自己在未曾进行多种活动(如撑竿跳)的情况下,能够加入一级体育项目的能力。我几乎也加入了队伍——只差一个人。但在我生活的这一部分,“几乎”并不算数。这次经历是我第一次全神贯注地投入并努力去做某事,最终却失败了。这是一个令人警醒的经历,早在生活的早期就从中得到了很好的教训。我学会了如何处理失败,在软件工程和数据科学中对我很有帮助。
作为一名前硅谷软件工程师,后来我发现了这种行为的一个词汇:YAGNI。YAGNI 代表“你根本不需要它”。就像我花了几年时间增加了 40 磅额外的肌肉,最终导致了我的运动表现下降一样,在软件项目中你可能在错误的事情上花费了精力。例如构建你在应用中不会使用的功能,或者过于复杂的抽象,如高级面向对象编程。这些技术实际上是“累赘”。它们是有害的,因为它们需要时间来开发,这些时间本可以用来做更有价值的事情,而且会永久地减慢项目的进度。就像在田径经验中一样,一些最有动力和才华横溢的人可能是在项目中添加不必要复杂性的最坏的滥用者。
营养科学领域也存在 YAGNI 问题,间歇性禁食是简化技术的一个很好的例子。它的工作方式很像把一个 2000 字的文章删除一半可以使它更好。事实证明,食物中数十年来增加的“复杂性”可以被忽略和删除:频繁的零食、早餐和超加工食品²。
你不需要吃早餐或零食。为了进一步简化,你不需要一天吃很多次。这是时间和金钱的浪费。你也不需要超加工食品:早餐麦片、蛋白棒或任何其他“人造”食品。事实证明,YAGNI 再次在我们的饮食中起作用。你也不需要购买一个特别的工具来健康饮食,比如书籍、补充剂或餐饮计划。
存在一个众所周知的问题叫做旅行推销员问题³,它提出了以下问题:给定一个城市列表和每对城市之间的距离,访问每个城市恰好一次并返回起始城市的最短可能路径是什么?这个问题很重要,因为没有完美的解决方案。用日常语言来说,这意味着一个解决方案在现实世界中太复杂而无法实现。此外,要解答这些数据会花费越来越长的时间。因此,计算机科学用启发式方法解决这些问题。我在研究生院写了一个启发式解决方案,虽然不是特别创新,但它给出了一个合理的答案⁴。它的工作原理是随机选择一个城市,然后当出现可能的路线时始终选择最短路线。在最后的解决方案中,计算总距离。然后,根据你有多少时间重新运行这个模拟,并选择最短的距离。
间歇性禁食如此有效是因为它也跳过了计算卡路里以减肥的不可解决的复杂性。间歇性禁食是一种有效的启发式方法。与其计算卡路里,你可以在一天中的某些时间段内不进食⁵。这些时间段可以如下设置:
每日禁食:
-
8 小时进食窗口或 16:8
-
中午 12 点至晚上 8 点
-
上午 7 点至下午 3 点
-
-
4 小时进食窗口或 20:4
-
下午 6 点至晚上 10 点
-
上午 7 点至 11 点
-
更长时间的复杂模式禁食:
-
5:2
- 五天正常饮食,两天热量限制,通常为 500 卡路里。
-
隔日禁食
- 一天正常饮食,另一天限制热量,通常为 500 卡路里。
我主要尝试了每日 16 小时或 20 小时的禁食。作为数据科学家、营养学家和依然竞争力十足的运动员,我也有数据。我有从 2011 年到 2019 年的体重数据。从 2019 年 8 月到 2019 年 12 月,我主要是采用 12:8 的间歇性禁食模式。
在图 F-1 中,我能够利用我的体重秤的数据收集来对自己的身体进行数据科学分析,找出什么有效,什么无效。

图 F-1. 体重
通过分析体重并尝试数据,我学到的一件事是,一些小事情可以产生重大影响:
-
避免“人类制造”的食物
-
获得 8 小时的睡眠(MBA 和创业公司通过失眠导致体重增加)
-
每日锻炼
-
间歇性禁食
-
不能通过运动来解决糟糕的饮食问题(心率在低 40)
图 F-2 显示了一餐符合 YAGNI 标准的示例。

图 F-2. 健康食品:鳄梨煎蛋卷
这里是一个带有鳄梨的蘑菇煎蛋卷的食谱:
-
鸡蛋
-
香菇
-
奶酪
-
鳄梨
-
莎莎酱
制作只需要几分钟,脂肪和整食使您感觉到饱腹,而且价格实惠。
当我“超重”的时候,是因为我没有遵循先前的建议:在创业公司疯狂加班,吃“人类制造”的食物。在空腹状态下锻炼需要一些适应时间,但我发现这提高了我在许多运动中的表现:攀岩、举重、高强度间歇训练和巴西柔术。同样地,我在编写软件、写书和进行知识性工作时非常高效。我主要的“技巧”是定期饮用纯冷萃咖啡和水。
我的结论是,间歇性禁食是显著改善一个人生活的最佳方式之一。它不花钱,操作简单,特别是如果你每天都坚持,还有科学支持。此外,许多人在寻找数据科学和机器学习项目时感到困惑。为什么不把自己作为测试案例,正如这个案例研究所展示的那样?
关于间歇性禁食、血糖和食物的笔记
从新英格兰医学杂志(NEJM):“证据表明,每 6 小时进食一次,空腹 18 小时可以触发从基于葡萄糖到基于酮的能量代谢转换,增加抗压能力,延长寿命,减少疾病发生,包括癌症和肥胖。”
根据《护士健康研究》(NHS),“几种生活方式行为可能会影响一个人是否能长期保持能量平衡。例如,摄入含糖饮料、甜食和加工食品可能会使这更加困难,而摄入全谷物、水果和蔬菜可能会使这更容易。”
这也展示了解决肥胖问题的数据科学和机器学习方法。增加坚果、水果和酸奶的摄入量。减少或消除薯片、土豆和含糖饮料(请注意超加工食品与胰岛素飙升之间的关联)。这些是导致体重增加的顶级食物:
-
薯片
-
土豆
-
含糖饮料
这些是与体重增加(减重)呈负相关的顶级食物:
-
坚果
-
水果
-
酸奶
当你已经看到背后的数据时,像时间限制饮食(IF)这样的生活方式改变显然更容易尝试!
¹ 参见维基百科上的柠檬酸循环页面 Wikipedia。
² 请看《哈佛健康出版》上的“吃更多超加工食品可能会缩短寿命” Harvard Health Publishing。
³ 请参阅维基百科上的描述 Wikipedia。
⁴ 这在 GitHub 上有提供。
⁵ 请看 DietDoctor 和《新英格兰医学杂志》 New England Journal of Medicine 了解更多。
⁶ 这在 GitHub 上有提供。
附录 G. 额外的教育资源
作者:Noah Gift
我们都知道,一本书、一门课程或一个学位不足以——相反,持续学习是保持更新的最佳方法。保持学习的一种方式是在您的工作、朋友或学校形成一个学习小组来继续成长。我教授的 ML 工程、MLOps 和应用计算机视觉课程提供的以下资源可以帮助您开始。
其他 MLOps 关键思考问题
-
一位前初创企业工程经理提到,“敏捷”项目管理单独不足以发布最小可行产品(MVP)。通常还需要一个为期 3 个月的每周计划(即瀑布式计划)。讨论您对这种观点的回应。
-
连续集成(CI)系统解决了哪些问题?
-
为什么 CI 系统是 SaaS 软件的重要组成部分?
-
为什么云平台是分析应用的理想目标?
-
深度学习如何从云计算中受益?
-
像 Google BigQuery 这样的托管服务的优势是什么?
-
Google BigQuery 与传统 SQL 有何不同?
-
从 BigQuery 直接进行机器学习(ML)预测如何为 Google 平台增加价值?
-
这对分析应用工程有什么优势?
-
自动机器学习(AutoML)如何具有较低的总体拥有成本(TCO)?
-
如何能够有更高的 TCO?
-
不同环境解决了什么问题?
-
不同环境创造了哪些问题?
-
如何正确管理云中的意外成本?
-
有哪三种工具可以帮助您管理 Google Cloud Platform 上的成本?
-
有哪三种工具可以帮助您管理 AWS 平台上的成本?
-
有哪三种工具可以帮助您管理 Azure 平台上的成本?
-
为什么 JavaScript 对象表示法(JSON)日志记录通常比非结构化日志记录更好?
-
提示频繁的警报有什么缺点?
-
通过回答以下问题创建终身学习计划:本季度您将学习哪些技能,为什么以及如何?到年底前您将学习哪些技能,为什么以及如何?到明年年底前您将学习哪些技能,为什么以及如何?到五年后您将学习哪些技能,为什么以及如何?
-
连续交付(CD)系统解决了哪些问题?
-
为什么持续交付系统是数据工程的重要组成部分?
-
连续集成和持续交付之间的关键区别是什么?
-
解释监控和日志记录在数据工程中扮演的关键角色。
-
解释健康检查可能出现的问题。
-
解释为什么“数据治理”是网络安全的“无名英雄”。
-
解释测试在数据工程中扮演的关键角色。
-
解释自动化和测试如何紧密相关。
-
选择一个喜欢的 Python 命令行框架,并写一个 hello-world 例子,并分享。您能解释为什么选择它吗?
-
解释云计算如何影响数据工程。
-
解释无服务器如何影响数据工程。
-
分享一个简单的 Python AWS Lambda 函数并解释其作用。
-
解释什么是机器学习工程。
-
创建并共享一个简单的
Dockerfile,用于运行 Flask 应用。解释它的工作原理。 -
解释什么是数据工程。
-
截断和混洗大数据集,加载到 Pandas 中,并分享您的工作。解释您使用的方法。
-
解释 DevOps 是什么,以及它如何增强数据工程项目。
-
考虑实际的计算机视觉问题时,云解决了什么问题?
-
如何使用 Colab 笔记本和 Jupyter 笔记本交换想法或构建研究组合?
-
生物视觉与机器视觉之间的一些关键区别是什么?
-
生成建模的一些实际用例是什么?
-
解释游戏机器如何使用计算机视觉来获胜。
-
使用计算机视觉 API 解决实际问题的利弊如何?
-
AutoML 如何现在影响数据科学,未来将如何影响它?
-
基于边缘的机器学习的真实用例是什么?
-
几个基于边缘的机器学习平台是什么?
-
集成平台如何融入现有公司的 ML 策略?
-
SageMaker 如何改变组织中的机器学习模型创建?
-
AWS Lambda 的实际用例是什么?
-
解释迁移学习的实际用例。请解释您如何在项目中使用它。
-
如何将您构建的 ML 模型推进到更高级的功能阶段?
-
IAC 是什么,它解决了什么问题?
-
公司在项目中如何决定使用何种级别的云抽象化:SaaS、PaaS、IaaS、MaaS、无服务器?
-
AWS 上网络安全的不同层次及其各自解决的独特问题是什么?
-
AWS Spot 实例解决了什么问题,你如何在项目中使用它们?
-
创建 Docker 格式的容器并推荐如何在项目中使用它们。
-
评估像 Kubernetes 和托管 Kubernetes 这样的容器管理服务,并与它们一起创建解决方案。
-
总结容器注册表及如何使用它们创建自定义容器。
-
容器是什么?
-
容器解决了什么问题?
-
Kubernetes 与容器之间的关系是什么?
-
准确评估分布式计算的挑战和机会,并将这些知识应用到实际项目中。
-
总结最终一致性在云原生应用中的作用。
-
CAP 定理在设计云时如何起作用?
-
Amdahl 定律对机器学习项目有什么影响?
-
为 ASIC 推荐适当的用例。
-
考虑摩尔定律的结束对项目的影响。
-
“一刀切”方法对关系数据库有什么问题?
-
类似 Google BigQuery 这样的服务如何改变你处理数据的方式?
-
像 Athena 这样的“无服务器”数据库解决了什么问题?
-
块存储和对象存储之间的关键区别是什么?
-
数据湖解决的基本问题是什么?
-
无服务器架构的权衡是什么?
-
使用 Cloud9 开发的优势是什么?
-
Google App Engine 解决了什么问题?
-
Cloud Shell 环境解决了什么问题?
其他 MLOps 教育材料
除了本书中的资源外,这些是更新频繁的额外资源,您可以利用它们继续改进:
-
O'Reilly 平台每周更新许多其他与 MLOps 相关的视频,由Pragmatic AI Solutions提供。您还可以在 O'Reilly 学习平台上查看 Katacodas。
-
在Pragmatic AI Labs 网站上有几本免费书籍可供阅读。
-
参与杜克大学和 Coursera 专业化课程构建大规模云计算解决方案。
教育颠覆
颠覆是指从已建立的模型或流程中的中断、干扰或变更。本节提供了我对教育颠覆以及其如何影响学习 MLOps 技术的思考。
在事后看到颠覆总是容易的。考虑出租车司机及当前服务如 Lyft 和 Uber 的问题。为什么要求司机支付一百万美元购买出租车牌照作为促进公共出租车服务的机制是合理的(Figure G-1)?

图 G-1. 出租车牌照¹
Lyft 和 Uber 等公司解决的问题是什么?
-
价格更低
-
推送与拉取(司机来找你)
-
可预测的服务
-
养成习惯的反馈循环
-
异步设计
-
数字与模拟
-
非线性工作流程
让我们在教育方面考虑同样的想法。
高等教育的当前状态将被打破
教育也正在进行类似的颠覆。学生债务创历史新高,根据 Experian 的数据,自 2008 年以来呈线性增长,如 Figure G-2 所示。

图 G-2. Experian 学生债务
与此同时,一个同样令人不安的趋势是一项同样令人不安的统计数据,即 2019 年有四分之一的大学毕业生从事了不需要他们学位的工作(Figure G-3)。

图 G-3. 需要学位的工作
这个过程是不可持续的。学生债务不能继续每年增长,同时产生几乎一半不直接通向工作的结果。如果结果相同,为什么一个学生不花四年时间学习像个人健身、运动、音乐或烹饪艺术这样的爱好呢?至少在那种情况下,他们不会负债,而且还有一个可以终身使用的有趣爱好。
在彼得·蒂尔(Currency)的书《从零到一》中,他提到了 10 倍规则。他指出,为了成功,一家公司需要比最接近的竞争对手强 10 倍。产品或服务能比传统教育好 10 倍吗?是的,可以。
10 倍更好的教育
那么实践中的 10 倍教育系统会是什么样子呢?
内置学徒制
如果一个教育项目的重点是工作,那么为什么不在学校期间就进行实习培训呢?
关注客户
当前大部分高等教育体系的关注点集中在教职员和教职员的研究上。谁为此买单?学生。这是一个重要的教育者标准,即在知名期刊上发表内容。与客户之间只有间接联系。
与此同时,像Udacity,Coursera,O’Reilly 和Edx这样的公司直接向客户提供这些产品。这种培训是针对工作的,比传统大学更新速度快得多。
可能教给学生的技能可能只专注于获得工作成果。大多数学生专注于找工作,而不是变得更好的人。有其他途径可以达到这个目标。
完成时间缩短
一个学位是否需要四年才能完成?如果学位的大部分时间用于非必要任务,可能需要那么长时间。为什么不能在一年或两年内完成学位?
成本更低
根据USNews,公立本州大学的四年平均学费为$10,116;公立外州大学为$22,577;私立大学为$36,801。自 1985 年以来(见图 G-4),四年制学位的总成本(通胀调整后)已经不断上升。

图 G-4。通胀调整后的学费
竞争对手能否提供比现在便宜 10 倍的产品?从 1985 年到 2019 年的起点可能是撤销已经发生的事情。如果产品没有改进,但成本却翻了三倍,那么这种情况很容易被打破。
异步和远程优先
许多软件工程公司已决定采用“远程优先”策略。在其他情况下,像 Twitter 这样的公司正在向分布式劳动力转移(https://oreil.ly/T3j9D)。在构建软件时,产出是数字产品。如果工作是数字化的,环境可以完全异步和远程化。异步和远程优先课程的优势在于规模化分发。
“远程优先”环境的一个优势是组织结构更专注于结果而非地点。许多软件公司由于不必要的会议、嘈杂的工作环境和长途通勤而遭受巨大的干扰和浪费。许多学生将进入“远程优先”工作环境,学习如何成功地掌握这些环境中的技能可能对他们有重大好处。
首先包容,而不是首先排除
许多大学公开表明有多少学生申请他们的项目,以及多少学生被录取。这种基于首先排除的方法旨在增加需求。如果出售的资产是物理的,比如马里布海滩的房子,那么根据市场情况价格会调整得更高。如果出售的产品是数字化且可无限扩展的,则排除是没有意义的。
虽然如此,严格的“训练营”风格课程也并非没有问题。特别是课程质量和教学质量不应该是事后的考虑。
非线性与串行
在数字技术出现之前,许多任务都是持续运营的。一个很好的例子是电视编辑技术。我曾在上世纪九十年代为 ABC 电视台担任编辑。您需要使用物理磁带进行编辑。不久后,视频磁带变成了硬盘上的数据,这开启了许多新的编辑技术。
同样,在教育方面,没有理由强制按计划学习某事。异步开放了许多新学习方式的可能性。妈妈可以晚上学习;现有员工可以在周末或午餐时间学习。
终身学习:毕业生可永久获取内容,并有持续的技能提升路径
教育机构重新考虑采用“远程优先”的另一个原因是,这将允许开设课程供校友学习(零成本或按需付费)。按需软件作为一种方法可以防止竞争对手的攻击。许多行业需要持续提升技能。技术行业就是一个很好的例子。
可以毫不夸张地说,任何技术工作者需要每六个月学习一项新技能。当前的教育产品没有考虑到这一点。为什么毕业生不可以有机会学习材料并获得这些材料的认证?增强的校友可能会带来更好的品牌。
将会受到打击的区域就业市场
作为前湾区软件工程师和房主,我不认为在当前成本结构下居住在该地区有任何未来优势(图 G-5)。超高成本地区的高生活成本导致了许多连锁问题:无家可归,通勤时间增加,生活质量急剧下降等等。

图 G-5. 美国的住房可负担性
危机就是机会。许多公司意识到,无论超高成本地区有什么好处,都不值得。相反,具有优秀基础设施和低生活成本的区域中心具有巨大的机会。作为作业增长中心的一些特征包括:可以访问大学,交通便利,住房价格低廉,以及对增长有利的良好政府政策。
像田纳西州这样的地区是一个很好的例子。它有免费的副学士学位项目,可以访问许多顶级大学,生活成本低,拥有像橡树岭国家实验室这样的顶尖研究机构。这些地区可以极大地颠覆现状,特别是如果它们接受远程优先和异步教育和劳动力。
颠覆招聘流程
美国的招聘过程准备进行颠覆。由于专注于直接的同步操作,它很容易被打破。图 G-6 显示,通过取消所有面试并用适当认证的个人自动招募来打破招聘是可能的。这是一个经典的分布式编程问题,通过将任务从串行且“错误”的工作流程转移到完全分布式的工作流程来修复瓶颈。公司应该从工作者那里“拉取”,而不是不断地依赖于陷入徒劳工作的资源。

图 G-6. 颠覆招聘
云计算之所以是如此重要的技能之一是它简化了软件工程的许多方面。它简化的问题之一是构建解决方案。许多解决方案通常涉及 YAML 格式文件和少量 Python 代码。
结论
教育不再是静态的。要在 MLOps 中保持相关性需要一种成长的心态。这种成长的心态意味着你必须在持续产生结果的同时不断学习。好消息是,如果他们利用技术培训机会的激增,有动力的人成功的机会越来越大。
¹ 出处:加利福尼亚,旧金山 1996 年 - 出租车牌照补充许可证 - Flickr - woody1778a.jpg。
附录 H. 技术项目管理
作者:Noah Gift
在高度技术的云计算课程的第一周,我提出了技术项目管理的问题,以及在附录中所描述的做法的重要性。一个学生在课堂上举手提问:“这是一个项目管理课吗?我以为这门课是关于云计算的?”
类似地,在现实世界中,很容易认为项目管理并不重要。然而,对 MLOps 来说,少量的技术项目管理知识是至关重要的。项目管理的三个重要组成部分如下:
-
尝试规划您的项目以完成时间,例如 12 周,使用每周的里程碑。
-
每周向您的团队进行演示,展示进展情况。
-
将任务分解为每四小时一次的时间块,并每周执行,使用像 Trello 或 GitHub 这样的简单任务跟踪系统进行跟踪。
现在,让我们简要介绍一些基础知识,以确保您的成功。
项目计划
关于项目管理,MLOps 并没有什么独特之处。不过,值得指出的是项目管理的一些非显而易见的亮点,这些亮点有助于构建机器学习项目。在构建 ML 解决方案时,一个强大的概念是以 10 到 12 周的时间表来思考,并尝试为每周制定个别的结果。在书籍的源代码存储库中,有一个示例模板供参考。
一个重要的收获是,计划创建了启动将机器学习代码部署到生产环境所必需的支撑结构,而每周的演示则创造了责任感。
在图 H-1 中,按周划分的项目计划允许对项目复杂性进行初步范围界定。

图 H-1. 项目计划
接下来,让我们讨论每周演示。
每周演示
在现实世界的两种场景中,当我与一个团队一起将代码部署到生产环境或在教育系统中时,每周演示和项目计划是减少项目从未见天日风险的关键组成部分。关于做演示的另一个隐藏事实是,让做演示的人理解他们正在做什么并传达信息。
古罗马谚语“Docendo discimus”表明“通过教学,我们学习”。这个谚语也与关于学习的学术研究相关,并且是我在行业和课堂上看到的更有效的教学方法之一。视频演示对于消费者和创作者都非常有力。
任务跟踪
你可以在线查看一个基础的公共 Trello 跟踪面板online。在图 H-2 中,注意到一个简单的方法,仅包括三列:待办、进行中和已完成。由于这是一个很好的工作单元来拆分任务,每个任务大约需要四个小时完成。

图 H-2. 使用 Trello 进行票务跟踪
最后,这些工单通常每周都会完成一次。这种工作流展示了每周的进展感。请注意,您可以使用任何基于面板的跟踪系统来完成同样的事情。主要的要点是保持简单!简单的项目管理系统能够长期使用,而复杂的则不能。





















浙公网安备 33010602011771号