机器学习的持续交付
自动化机器学习应用程序的端到端生命周期
机器学习应用程序在我们的行业中越来越流行,但是开发、部署和持续改进它们的流程比更传统的软件(如 Web 服务或移动应用程序)更复杂。它们在三个轴上会发生变化:代码本身、模型和数据。它们的行为通常很复杂,难以预测,而且更难测试、更难解释,也更难改进。机器学习的持续交付 (CD4ML) 是将持续交付原则和实践应用于机器学习应用程序的学科。
2019 年 9 月 19 日
介绍和定义
在 Sculley 等人于 2015 年发表的《机器学习系统中的隐藏技术债务》著名谷歌论文中,他们强调,在现实世界的机器学习 (ML) 系统中,只有很小一部分是实际的 ML 代码。为了支持它们的演变,存在大量的周边基础设施和流程。他们还讨论了在这些系统中积累的技术债务的许多来源,其中一些与数据依赖关系、模型复杂性、可重复性、测试、监控以及处理外部世界变化有关。
许多相同的担忧也存在于传统的软件系统中,而持续交付一直是将自动化、质量和纪律引入创建可靠且可重复的流程以将软件发布到生产环境中的方法。
在他们的开创性著作《持续交付》中,Jez Humble 和 David Farley 指出
“持续交付是指能够以安全、快速且可持续的方式将所有类型的更改(包括新功能、配置更改、错误修复和实验)投入生产或交付给用户”。
除了代码之外,对 ML 模型和用于训练它们的数据的更改是需要管理并纳入软件交付流程的另一种类型的更改(图 1)。
图 1:机器学习应用程序中变化的三个轴——数据、模型和代码——以及它们发生变化的一些原因
考虑到这一点,我们可以扩展持续交付定义,以包含现实世界机器学习系统中存在的新元素和挑战,我们称这种方法为“机器学习的持续交付 (CD4ML)”。
机器学习的持续交付 (CD4ML) 是一种软件工程方法,其中跨职能团队基于代码、数据和模型以小而安全的增量方式生成机器学习应用程序,这些增量可以随时以短的适应周期进行复制和可靠地发布。
此定义包含所有基本原则
软件工程方法:它使团队能够有效地生产高质量的软件。
跨职能团队:来自数据工程、数据科学、机器学习工程、开发、运营和其他知识领域的具有不同技能集和工作流程的专家以协作的方式共同工作,强调每个团队成员的技能和优势。
基于代码、数据和机器学习模型生成软件:ML 软件生产过程的所有工件都需要不同的工具和工作流程,这些工具和工作流程必须相应地进行版本控制和管理。
小而安全的增量:软件工件的发布被分成小的增量,这使得能够围绕其结果的方差水平进行可见性和控制,从而在流程中增加安全性。
可复制且可靠的软件发布:虽然模型输出可能是非确定性的,难以复制,但将 ML 软件发布到生产环境的过程是可靠且可复制的,尽可能地利用自动化。
随时发布软件:重要的是,ML 软件可以随时交付到生产环境中。即使组织不希望一直交付软件,它也应该始终处于可发布状态。这使得关于何时发布的决定成为商业决策,而不是技术决策。
短的适应周期:短周期意味着开发周期以天甚至小时为单位,而不是以周、月甚至年为单位。流程的自动化以及内置的质量是实现这一目标的关键。这将创建一个反馈循环,使您能够通过从其在生产环境中的行为中学习来调整您的模型。
在本文中,我们将描述我们在实施 CD4ML 时发现的重要技术组件,使用一个示例 ML 应用程序来解释这些概念,并演示如何将不同的工具一起使用来实施完整的端到端流程。在适当的情况下,我们将重点介绍我们选择的工具的替代选择。我们还将讨论进一步的开发和研究领域,因为这种实践在我们的行业中不断成熟。
用于销售预测的机器学习应用程序
自 2016 年以来,我们开始思考如何将持续交付应用于机器学习系统,我们发布并展示了我们与 AutoScout 合作构建的客户项目的案例研究,以预测其平台上发布的汽车的价格。
但是,我们决定基于一个公共问题和数据集构建一个示例 ML 应用程序,以说明 CD4ML 的实现,因为我们不允许使用来自真实客户代码的示例。此应用程序解决了许多零售商面临的常见预测问题:尝试根据历史数据预测未来将销售多少特定产品。我们构建了一个简化的解决方案,用于解决 Corporación Favorita(一家总部位于厄瓜多尔的大型杂货零售商)发布的Kaggle 问题。为了我们的目的,我们合并并简化了他们的数据集,因为我们的目标不是找到最佳预测——这项工作最好由您的数据科学家完成——而是演示如何实施 CD4ML。
使用监督学习算法和流行的 scikit-learn Python 库,我们使用标记的输入数据训练预测模型,将该模型集成到一个简单的 Web 应用程序中,然后将其部署到云中的生产环境中。图 2 显示了高级流程。
图 2:训练我们的 ML 模型、将其与 Web 应用程序集成并部署到生产环境中的初始流程
部署后,我们的 Web 应用程序(图 3)允许用户选择产品和未来的日期,模型将输出对该产品在该天将销售多少单位的预测。
图 3:演示我们的模型运行的 Web UI
常见挑战
虽然这是一个很好的起点,但端到端实施此流程已经可以带来两个挑战。第一个挑战是组织结构:不同的团队可能拥有流程的不同部分,并且存在交接——或者通常是“扔过墙”——没有明确的期望如何跨越这些边界(图 4)。数据工程师可能正在构建管道以使数据可访问,而数据科学家则担心构建和改进 ML 模型。然后,机器学习工程师或开发人员将不得不担心如何集成该模型并将其发布到生产环境中。
图 4:大型组织中常见的职能孤岛会造成障碍,阻碍将 ML 应用程序部署到生产环境的端到端流程的自动化能力
这会导致延迟和摩擦。一个常见的症状是拥有仅在实验室环境中工作的模型,并且从未离开概念验证阶段。或者,如果它们以手动、临时的方式进入生产环境,它们就会变得陈旧,难以更新。
第二个挑战是技术挑战:如何使流程可复制且可审计。由于这些团队使用不同的工具并遵循不同的工作流程,因此难以对其进行端到端自动化。除了代码之外,还有更多工件需要管理,对其进行版本控制并不简单。其中一些可能非常大,需要更复杂的工具来有效地存储和检索它们。
解决组织挑战超出了本文的范围,但我们可以借鉴敏捷和 DevOps 的经验,构建跨职能和结果导向的团队,其中包括来自不同学科的专家,以交付端到端的 ML 系统。如果您的组织无法做到这一点,至少鼓励打破这些障碍,并让他们在整个流程中尽早且经常地进行协作。
本文的其余部分将探讨我们为技术挑战找到的解决方案。我们将深入探讨每个技术组件,并逐步改进和扩展端到端流程,使其更加健壮。
CD4ML 的技术组件
当我们考虑如何使用机器学习来解决预测问题时,第一步是了解数据集。在这种情况下,它是一组 CSV 文件,包含有关以下方面的信息
- 产品,例如它们的分类以及它们是否易腐
- 商店,例如它们的位置以及它们如何聚集在一起
- 特殊事件,例如公共假期、季节性事件或 2016 年袭击厄瓜多尔的 7.8 级地震
- 销售交易,包括特定产品、日期和地点的销售单位数量
在此阶段,数据分析师和数据科学家通常会执行某种探索性数据分析 (EDA),以了解数据的形状,并识别广泛的模式和异常值。例如,我们发现产品销售单位数量为负数,我们将其解释为退货。由于我们只打算探索销售,而不是退货,因此我们将其从我们的训练数据集中删除。
在许多组织中,训练有用的 ML 模型所需的数据可能不会完全按照数据科学家需要的方式进行结构化,因此它突出了第一个技术组件:可发现和可访问的数据。
可发现和可访问的数据
数据最常见的来源是您的核心交易系统。但是,从组织外部引入其他数据源也有价值。我们发现了一些用于收集和提供数据的常见模式,例如使用数据湖架构、更传统的数据仓库、实时数据流的集合,或者最近,我们正在尝试使用去中心化的数据网格架构。
无论您使用哪种架构,重要的是数据易于发现和访问。数据科学家越难找到他们需要的数据,构建有用模型所需的时间就越长。我们还应该考虑他们可能希望在输入数据的基础上设计新的特征,这可能有助于提高模型的性能。
在我们的示例中,在进行初始探索性数据分析后,我们决定将多个文件反规范化为单个 CSV 文件,并清理与模型无关或可能将不必要的噪声引入模型的数据点(例如负销售额)。然后,我们将输出存储在云存储系统中,例如 Amazon S3、Google Cloud Storage 或 Azure 存储帐户。
使用此文件来表示输入训练数据的快照,我们能够设计一种简单的方法来根据文件夹结构和文件命名约定对数据集进行版本控制。数据版本控制是一个广泛的主题,因为它可以在两个不同的轴上发生变化:其模式的结构变化,以及数据随时间的实际采样。我们的数据科学家 Emily Gorcenski 在这篇博文中更详细地介绍了这个主题,但稍后在本文中,我们将讨论其他方法来对数据集进行版本控制。
值得注意的是,在现实世界中,您可能需要更复杂的数据管道来将数据从多个来源移动到数据科学家可以访问和使用的地方。
可重复的模型训练
数据可用后,我们将进入模型构建的迭代数据科学工作流程。这通常涉及将数据分成训练集和验证集,尝试不同的算法组合,并调整其参数和超参数。这将生成一个模型,可以根据验证集对其进行评估,以评估其预测的质量。此模型训练过程的逐步过程成为机器学习管道。
在图 5中,我们展示了如何为我们的销售预测问题构建 ML 管道,重点介绍了不同的源代码、数据和模型组件。输入数据、中间训练和验证数据集以及输出模型可能都是大型文件,我们不想将其存储在源代码控制存储库中。此外,管道的各个阶段通常处于不断变化中,这使得在数据科学家的本地环境之外很难重现它们。
图 5:我们销售预测问题的机器学习管道,以及使用 DVC 自动化的 3 个步骤
为了在代码中形式化模型训练过程,我们使用了一个名为DVC(数据科学版本控制)的开源工具。它提供了与 Git 相似的语义,但也解决了一些特定于 ML 的问题
- 它具有多个后端插件,可以在源代码控制存储库之外的外部存储上获取和存储大型文件;
- 它可以跟踪这些文件的版本,允许我们在数据发生变化时重新训练模型;
- 它跟踪用于执行 ML 管道的依赖关系图和命令,允许在其他环境中重现该过程;
- 它可以与 Git 分支集成,以允许多个实验共存;
例如,我们可以使用三个dvc run
命令来配置图 5中的初始 ML 管道(-d
指定依赖项,-o
指定输出,-f
是记录该步骤的文件名,-M
是结果指标)
dvc run -f input.dvc \ ➊ -d src/download_data.py -o data/raw/store47-2016.csv python src/download_data.py dvc run -f split.dvc \ ➋ -d data/raw/store47-2016.csv -d src/splitter.py \ -o data/splitter/train.csv -o data/splitter/validation.csv python src/splitter.py dvc run ➌ -d data/splitter/train.csv -d data/splitter/validation.csv -d src/decision_tree.py \ -o data/decision_tree/model.pkl -M results/metrics.json python src/decision_tree.py
每次运行都会创建一个相应的文件,该文件可以提交到版本控制,并允许其他人通过执行dvc repro
命令来重现整个 ML 管道。
一旦我们找到合适的模型,我们将将其视为需要进行版本控制并部署到生产环境的工件。使用 DVC,我们可以使用dvc push
和dvc pull
命令将其发布到外部存储并从外部存储中获取它。
您可以使用其他开源工具来解决这些问题:Pachyderm使用容器来执行管道的不同步骤,并且还通过跟踪数据提交和根据数据提交优化管道执行来解决数据版本控制和数据来源问题。MLflow 项目定义了一种文件格式来指定环境和管道的步骤,并提供 API 和 CLI 工具来在本地或远程运行项目。我们选择 DVC 是因为它是一个简单的 CLI 工具,可以很好地解决问题的这一部分。
模型服务
找到合适的模型后,我们需要决定如何在生产环境中提供和使用它。我们已经看到了一些实现此目的的模式
- 嵌入式模型:这是更简单的方法,您将模型工件视为在使用应用程序中构建和打包的依赖项。从这一点开始,您可以将应用程序工件和版本视为应用程序代码和所选模型的组合。
- 作为独立服务部署的模型:在这种方法中,模型被包装在一个服务中,该服务可以独立于使用应用程序进行部署。这允许独立发布对模型的更新,但它也可能在推理时引入延迟,因为每次预测都需要某种远程调用。
- 作为数据发布的模型:在这种方法中,模型也被独立对待和发布,但使用应用程序将在运行时将其作为数据摄取。我们已经看到它在流式/实时场景中使用,在这些场景中,应用程序可以订阅发布新模型版本时发布的事件,并在继续使用先前版本进行预测的同时将其摄取到内存中。蓝绿部署或金丝雀发布等软件发布模式也可以应用于此场景。
在我们的示例中,我们决定使用更简单的嵌入模型方法,因为我们的使用应用程序也是用 Python 编写的。我们的模型被导出为序列化对象(pickle 文件)并由 DVC 推送到存储。在构建应用程序时,我们将其拉取并嵌入到同一个 Docker 容器中。从那时起,Docker 镜像成为我们的应用程序+模型工件,该工件将被版本控制并部署到生产环境。
除了使用 pickle 序列化模型对象之外,还有其他工具选项可以实现嵌入式模型模式。MLeap提供了一种通用的序列化格式,用于导出/导入 Spark、scikit-learn 和 Tensorflow 模型。还有一些与语言无关的交换格式可以共享模型,例如PMML、PFA和ONNX。其中一些序列化选项也适用于实现“模型作为数据”模式。
另一种方法是使用H2O之类的工具将模型导出为POJO,它是一个 JAR Java 库,然后您可以将其添加为应用程序的依赖项。这种方法的好处是,您可以在数据科学家熟悉的语言(如 Python 或 R)中训练模型,并将模型导出为在不同目标环境(JVM)中运行的编译二进制文件,这在推理时可能更快。
为了实现“模型作为服务”模式,许多云提供商都有工具和 SDK 来包装您的模型,以便将其部署到他们的 MLaaS(机器学习即服务)平台中,例如 Azure 机器学习、AWS Sagemaker 或 Google AI Platform。另一种选择是使用Kubeflow之类的工具,这是一个旨在将 ML 工作流程部署到 Kubernetes 上的项目,尽管它试图解决的不仅仅是模型服务问题的一部分。
MLflow 模型正在尝试提供一种标准方法来打包不同类型的模型,以便由不同的下游工具使用,一些在“模型作为服务”中,一些在“嵌入式模型”模式中。可以说,这是一个正在开发的领域,各种工具和供应商正在努力简化这项任务。但这意味着还没有明确的标准(开放或专有)可以被认为是明确的赢家,因此您需要评估最适合您需求的选项。
值得注意的是,无论您决定使用哪种模式,模型与其使用者之间始终存在一个隐式契约。模型通常会期望输入数据具有特定的形状,如果数据科学家更改该契约以要求新的输入或添加新的特征,您可能会导致集成问题并破坏使用它的应用程序。这将我们引入了测试的主题。
机器学习中的测试和质量
在 ML 工作流程中可以引入不同类型的测试。虽然某些方面本质上是非确定性的,难以自动化,但仍有许多类型的自动化测试可以增加价值并提高 ML 系统的整体质量
- 验证数据:我们可以添加测试来根据预期模式验证输入数据,或者验证我们对其有效值的假设——例如,它们落在预期的范围内,或者不为空。对于工程特征,我们可以编写单元测试来检查它们是否被正确计算——例如,数值特征被缩放或归一化,独热编码向量包含所有零和单个 1,或者缺失值被适当替换。
- 验证组件集成:我们可以使用类似的方法来测试不同服务之间的集成,使用契约测试来验证预期模型接口是否与使用应用程序兼容。当您的模型以不同的格式投入生产时,另一种相关的测试类型是确保导出的模型仍然产生相同的结果。这可以通过对相同验证数据集运行原始模型和生产模型,并比较结果是否相同来实现。
- 验证模型质量:虽然 ML 模型性能是非确定性的,但数据科学家通常会收集和监控一些指标来评估模型的性能,例如错误率、准确率、AUC、ROC、混淆矩阵、精确率、召回率等。它们在参数和超参数优化期间也很有用。作为一个简单的质量关口,我们可以使用这些指标来引入阈值测试或棘轮到我们的管道中,以确保新模型不会相对于已知的性能基线下降。
- 验证模型偏差和公平性:虽然我们可能在整体测试和验证数据集上获得良好的性能,但检查模型在特定数据切片上相对于基线的性能也很重要。例如,你的训练数据中可能存在固有偏差,其中某个特征值(例如种族、性别或地区)的数据点数量远远多于现实世界中的实际分布,因此检查不同数据切片上的性能非常重要。像 Facets 这样的工具可以帮助你可视化这些切片以及数据集中的特征值分布。
在我们的示例应用程序中,评估指标 由 Favorita 定义,是一个归一化的错误率。我们编写了一个简单的 PyUnit 阈值测试,如果错误率超过 80%,则会中断,此测试可以在发布新模型版本之前执行,以演示如何防止不良模型被推广。
虽然这些是更容易自动化的测试示例,但更全面地评估模型的质量则比较困难。随着时间的推移,如果我们始终针对相同的数据集计算指标,我们可能会开始过拟合。当你有其他模型已经上线时,你需要确保新模型版本不会在未见数据上退化。因此,管理和维护测试数据变得更加重要 [2].
当模型被分发或导出以供其他应用程序使用时,你还会发现一些问题,即在训练和服务时间之间,工程特征的计算方式不同。一种帮助捕获此类问题的方法是将保留数据集与模型工件一起分发,并允许使用应用程序团队在模型集成后重新评估模型在保留数据集上的性能。这相当于传统软件开发中的广泛 集成测试。
还可以考虑其他类型的测试,但我们认为在部署管道中添加一些手动阶段也很重要,以显示有关模型的信息,并允许人类决定是否应该将其推广。这使你能够模拟机器学习治理流程,并引入对模型偏差、模型公平性的检查,或收集可解释性信息,以便人类了解模型的行为方式。
随着更多类型的测试,你将重新思考你的 测试金字塔 的形状:你可以考虑为每种类型的工件(代码、模型和数据)创建单独的金字塔,还可以考虑如何将它们组合起来,如 图 6 所示。总的来说,ML 系统的测试和质量更加复杂,应该成为另一篇深入、独立的文章的主题。
图 6:CD4ML 中如何将数据、模型和代码的不同测试金字塔组合在一起的示例
实验跟踪
为了支持此治理流程,重要的是要捕获和显示信息,这些信息将允许人类决定是否以及应该将哪个模型推广到生产环境。由于数据科学流程非常以研究为中心,因此你通常会并行进行多个实验,其中许多实验可能永远不会投入生产。
研究阶段的这种实验方法不同于更传统的软件开发流程,因为我们预计许多这些实验的代码将被丢弃,只有少数实验被认为值得投入生产。因此,我们需要定义一种方法来跟踪它们。
在我们的案例中,我们决定遵循 DVC 建议的方法,使用不同的 Git 分支在源代码管理中跟踪不同的实验。即使这违背了我们在单个主干上实践 持续集成 的偏好。DVC 可以从运行在不同分支或标签中的实验中获取和显示指标,从而可以轻松地在它们之间导航。
传统软件开发中在 功能分支 上进行开发的一些缺点是,如果分支的生命周期很长,会导致合并问题;它可能会阻止团队更积极地重构,因为更改可能会影响代码库的更广泛区域;它会阻碍 持续集成 (CI) 的实践,因为它迫使你为每个分支设置多个作业,并且正确的集成要等到代码合并回主线后才会进行。
对于 ML 实验,我们预计大多数分支永远不会被集成,并且实验之间的代码差异通常并不显著。从 CI 自动化角度来看,我们实际上确实希望为每个实验训练多个模型,并收集指标,这些指标将告诉我们哪个模型可以进入部署管道的下一阶段。
除了 DVC 之外,我们用来帮助进行实验跟踪的另一个工具是 MLflow 跟踪。它可以作为托管服务部署,并提供 API 和 Web 界面来可视化多个实验运行,以及它们的参数和性能指标,如 图 7 所示。
图 7:MLflow 跟踪 Web UI 用于显示实验运行、参数和指标
为了支持这种实验过程,突出弹性基础设施的好处也很重要,因为你可能需要多个环境可用——有时还需要专门的硬件——用于训练。基于云的基础设施非常适合这种情况,许多公共云提供商正在构建服务和解决方案来支持此过程的各个方面。
模型部署
在我们的简单示例中,我们只进行实验来构建单个模型,该模型被嵌入并与应用程序一起部署。在现实世界中,部署可能会带来更复杂的场景
- 多个模型:有时你可能有多个模型执行相同的任务。例如,我们可以训练一个模型来预测每种产品的需求。在这种情况下,将模型作为单独的服务部署可能更适合使用应用程序通过单个 API 调用获取预测。你以后可以根据需要调整该 发布接口 后面的模型数量。
- 影子模型:当考虑替换生产中的模型时,此模式很有用。你可以将新模型与当前模型并排部署,作为影子模型,并将相同的生产流量发送到该模型,以收集有关影子模型在推广之前如何执行的数据。
- 竞争模型:一个稍微复杂的情况是,当你在生产中尝试多个版本的模型时——就像 A/B 测试——以找出哪个模型更好。这里增加的复杂性来自确保流量被重定向到正确的模型所需的架构和路由规则,以及你需要收集足够的数据才能做出具有统计意义的决策,这可能需要一些时间。评估多个竞争模型的另一种流行方法是 多臂老虎机,它也要求你定义一种方法来计算和监控与使用每个模型相关的奖励。将此应用于 ML 是一个活跃的研究领域,我们开始看到一些工具和服务出现,例如 Seldon core 和 Azure Personalizer .
- 在线学习模型:与我们迄今为止讨论的模型不同,这些模型是在离线训练并在在线上用于提供预测,在线学习 模型使用可以随着新数据的到来不断提高其性能的算法和技术。它们在生产中不断学习。这带来了额外的复杂性,因为如果模型没有被提供相同的数据,则将模型作为静态工件进行版本控制将不会产生相同的结果。你需要对训练数据和生产数据进行版本控制,这些数据会影响模型的性能。
再次强调,为了支持更复杂的部署场景,你将受益于使用弹性基础设施。除了使你能够在生产中运行这些多个模型之外,它还允许你通过在需要时启动更多基础设施来提高系统的可靠性和可扩展性。
持续交付编排
有了所有主要构建块,就需要将它们全部整合在一起,这就是我们的持续交付编排工具发挥作用的地方。在这个领域有很多工具选项,其中大多数工具提供配置和执行 部署管道 的方法,用于构建和发布软件到生产环境。在 CD4ML 中,我们有额外的要求需要编排:基础设施的供应以及执行机器学习管道以训练和捕获来自多个模型实验的指标;构建、测试和部署数据管道的过程;决定推广哪些模型的不同类型的测试和验证;基础设施的供应以及将模型部署到生产环境。
我们选择使用 GoCD 作为我们的持续交付工具,因为它是在管道作为一等公民的概念下构建的。不仅如此,它还允许我们通过组合不同的管道、它们的触发器以及定义管道阶段之间的手动或自动推广步骤来配置复杂的流程和依赖项。
在我们的简化示例中,我们还没有构建任何复杂的数据管道或基础设施供应,但我们演示了如何组合两个 GoCD 管道,如 图 8 所示
- 机器学习管道:在 GoCD 代理中执行模型训练和评估,以及执行基本阈值测试以决定是否可以推广模型。如果模型良好,我们将执行
dvc push
命令将其作为工件发布。 - 应用程序部署管道:构建和测试应用程序代码,使用
dvc pull
从上游管道获取推广的模型,打包包含模型和应用程序的新的组合工件作为 Docker 镜像,并将它们部署到 Kubernetes 生产集群。
图 8:在 GoCD 中组合机器学习管道和应用程序部署管道
随着时间的推移,ML 管道可以扩展为并行执行多个实验(GoCD 的扇出/扇入模型支持的功能),并定义你的模型治理流程以检查偏差、公平性、正确性以及其他类型的门,以便对推广和部署到生产环境的模型做出明智的决策。
最后,持续交付编排的另一个方面是定义回滚流程,以防部署的模型在生产环境中表现不佳或不正确。这为整个流程增加了另一层安全保障。
模型监控和可观察性
现在模型已经上线,我们需要了解它在生产环境中的表现,并关闭数据反馈循环。在这里,我们可以重复使用可能已经为你的应用程序和服务部署的所有监控和可观察性基础设施。
日志聚合和指标收集工具通常用于捕获来自实时系统的数据,例如业务 KPI、软件可靠性和性能指标、用于故障排除的调试信息以及其他可能在出现异常情况时触发警报的指标。我们还可以利用这些相同的工具来捕获数据,以了解我们的模型是如何运行的,例如
- 模型输入:哪些数据被提供给模型,从而了解任何训练-服务偏差。模型输出:模型从这些输入中做出了哪些预测和推荐,以了解模型在真实数据中的表现。
- 模型可解释性输出: 指标,例如模型系数,ELI5 或 LIME 的输出,这些输出允许进一步调查以了解模型如何进行预测,从而识别训练期间未发现的潜在过拟合或偏差。
- 模型输出和决策: 模型根据生产输入数据进行的预测,以及根据这些预测做出的决策。 有时,应用程序可能会选择忽略模型,并根据预定义规则做出决策(或避免未来偏差)。
- 用户行为和奖励: 基于用户的进一步行为,我们可以捕获奖励指标,以了解模型是否产生了预期效果。 例如,如果我们展示产品推荐,我们可以跟踪用户何时决定购买推荐产品作为奖励。
- 模型公平性: 分析输入数据和输出预测,以针对可能存在偏差的已知特征,例如种族、性别、年龄、收入群体等。
在我们的示例中,我们使用 EFK 栈进行监控和可观测性,它由三个主要工具组成
- Elasticsearch:一个开源搜索引擎。
- FluentD:一个开源数据收集器,用于统一日志层。
- Kibana:一个开源 Web UI,可以轻松地探索和可视化 Elasticsearch 索引的数据。
我们可以对应用程序代码进行检测,以便将模型输入和预测作为事件记录到 FluentD 中
predict_with_logging.py…
df = pd.DataFrame(data=data, index=['row1'])
df = decision_tree.encode_categorical_columns(df)
pred = model.predict(df)
logger = sender.FluentSender(TENANT, host=FLUENTD_HOST, port=int(FLUENTD_PORT))
log_payload = {'prediction': pred[0], **data}
logger.emit('prediction', log_payload)
然后,该事件会被转发并索引到 ElasticSearch 中,我们可以使用 Kibana 通过 Web 界面查询和分析它,如 图 9 所示。
图 9:在 Kibana 中分析模型预测与真实输入数据的关系
还有其他流行的监控和可观测性工具,例如 ELK 栈(使用 Logstash 而不是 FluentD 进行日志摄取和转发的变体)、Splunk 等。
当你在生产环境中部署多个模型时,收集监控和可观测性数据变得更加重要。 例如,你可能有一个影子模型需要评估,你可能正在执行拆分测试,或者使用多个模型运行多臂老虎机实验。
这在你在边缘(例如,在用户的移动设备上)训练或运行联邦模型时也很重要,或者如果你正在部署 在线学习模型,这些模型会随着时间的推移而发生变化,因为它们从生产环境中的新数据中学习。
通过捕获这些数据,你可以关闭数据反馈循环。 这是通过收集更多真实数据(例如,在定价引擎或推荐系统中)或通过在循环中添加一个人来分析从生产环境中捕获的新数据,并对其进行整理以创建新的训练数据集来实现新的和改进的模型。 关闭此反馈循环是 CD4ML 的主要优势之一,因为它允许我们根据从真实生产数据中获得的经验教训来调整模型,从而创建一个持续改进的过程。
端到端 CD4ML 流程
通过逐步解决每个技术挑战,并使用各种工具和技术,我们成功地创建了 图 10 所示的端到端流程,该流程管理着跨三个轴(代码、模型和数据)的工件推广。
图 10:机器学习持续交付端到端流程
在基础部分,我们需要一种简单的方法来管理、发现、访问和版本化我们的数据。 然后,我们自动化模型构建和训练过程,使其可重复。 这使我们能够实验和训练多个模型,这带来了衡量和跟踪这些实验的必要性。 一旦我们找到合适的模型,我们就可以决定如何将其生产化和提供服务。 由于模型在不断发展,我们必须确保它不会破坏与消费者的任何契约,因此我们需要在部署到生产环境之前对其进行测试。 一旦进入生产环境,我们就可以使用监控和可观测性基础设施来收集新数据,这些数据可以被分析并用于创建新的训练数据集,从而关闭持续改进的反馈循环。
持续交付编排工具协调端到端 CD4ML 流程,按需配置所需的 基础设施,并管理模型和应用程序如何部署到生产环境。
我们下一步该怎么做?
我们在本文中使用的示例应用程序和代码可以在我们的 Github 代码库 中找到,它被用作我们在各种会议和客户处举办的半天研讨会的基础。 我们一直在不断发展我们关于如何实施 CD4ML 的想法。 在本节中,我们将总结一些在研讨会材料中没有反映的改进领域,以及一些需要进一步探索的开放领域。
数据版本控制
在持续交付中,我们将每个代码提交视为一个发布候选,它会触发部署管道的重新执行。 假设提交通过了所有管道阶段,它就可以部署到生产环境。 当谈到 CD4ML 时,我们经常被问到的一个问题是“当数据发生变化时,我如何触发管道?”
在我们的示例中, 图 5 中的机器学习管道从 download_data.py
文件开始,该文件负责从共享位置下载训练数据集。 如果我们更改共享位置中数据集的内容,它不会立即触发管道,因为代码没有更改,DVC 也无法检测到它。 为了版本化数据,我们必须创建一个新文件或更改文件名,这反过来又需要我们在 download_data.py
脚本中更新新路径,从而创建一个新的代码提交。
改进这种方法的方法是允许 DVC 为我们跟踪文件内容,方法是将我们手写的下载脚本替换为
dvc add data/raw/store47-2016.csv ➊
这将略微改变我们的机器学习管道的第一步,如 图 11 所示。
图 11:更新第一步以允许 DVC 跟踪数据版本并简化 ML 管道
这将创建一个元数据文件,该文件跟踪文件的校验和,我们可以将其提交到 Git。 现在,当文件内容发生更改时,校验和将发生更改,DVC 将更新该元数据文件,这将是我们需要触发管道执行的提交。
虽然这使我们能够在数据发生变化时重新训练模型,但它并没有说明数据版本化的全部内容。 一个方面是数据历史:理想情况下,你希望保留所有数据更改的完整历史记录,但这并不总是可行的,具体取决于数据更改的频率。 另一个方面是数据来源:了解是什么处理步骤导致数据发生更改,以及它是如何跨不同数据集传播的。 还有一个关于跟踪和随着时间的推移演变数据模式的问题,以及这些更改是否向后和向前兼容。
如果你处于流式世界中,那么数据版本化的这些方面在推理时会变得更加复杂,因此,我们预计会有更多实践、工具和技术在这个领域发展。
数据管道
到目前为止,我们还没有介绍如何版本化、测试、部署和监控数据管道本身。 在现实世界中,一些工具选项比其他选项更适合启用 CD4ML。 例如,许多需要你通过 GUI 定义转换和处理步骤的 ETL 工具通常不容易版本控制、测试或部署到混合环境。 其中一些可以生成代码,你可以将其视为工件并将其放入部署管道中。
我们倾向于使用开源工具,这些工具允许我们在代码中定义数据管道,这更容易版本控制、测试和部署。 例如,如果你使用的是 Spark,你可能使用 Scala 编写了数据管道,你可以使用 ScalaTest 或 spark-testing-base 对其进行测试,你可以将作业打包为 JAR 工件,该工件可以在 GoCD 中的部署管道中进行版本化和部署。
由于数据管道通常以批处理作业或长时间运行的流式应用程序的形式运行,因此我们没有将它们包含在 图 10 中的端到端 CD4ML 流程图中,但它们也是另一个潜在的集成问题来源,如果它们更改了模型或应用程序期望的输出。 因此,在我们的部署管道中使用集成和数据 契约测试 来捕获这些错误是我们努力的目标。
数据管道相关的另一种测试是数据质量检查,但这可能成为另一个广泛的讨论主题,最好在单独的文章中介绍。
平台思维
你可能已经注意到,我们使用了各种工具和技术来实现 CD4ML。 如果你有多个团队试图做到这一点,他们最终可能会重新发明东西或重复工作。 这就是平台思维可以发挥作用的地方。 不是将所有工作集中在一个成为瓶颈的单一团队中,而是将平台工程工作集中在构建领域无关的工具上,这些工具隐藏了底层复杂性,并加快了试图采用它的团队的速度。 我们的同事 Zhamak Dehghani 在她的 数据网格 文章中更详细地介绍了这一点。
将平台思维应用于 CD4ML 是我们看到人们对机器学习平台和其他试图提供管理端到端机器学习生命周期的单一解决方案的产品越来越感兴趣的原因。 许多主要的科技巨头都开发了自己的内部工具,但我们认为这是一个活跃的研究和开发领域,预计会出现新的工具和供应商,提供可以更广泛采用的解决方案。
发展无偏见智能系统
一旦你的第一个机器学习系统部署到生产环境,它就开始进行预测,并被用于处理看不见的数据。 它甚至可能取代你之前使用的基于规则的系统。 重要的是要意识到,你执行的训练数据和模型验证是基于历史数据的,这些数据可能包含基于先前系统行为的固有偏差。 此外,你的 ML 系统对用户产生的任何影响都会影响你未来的训练数据。
让我们考虑两个例子来了解影响。 首先,让我们考虑我们在整篇文章中探讨的需求预测解决方案。 假设有一个应用程序根据预测的需求来决定要订购和提供给客户的商品的确切数量。 如果预测需求低于实际需求,你将没有足够的商品出售,因此该产品的交易量会下降。 如果你只使用这些新的交易作为训练数据来改进你的模型,随着时间的推移,你的需求预测会下降。
对于第二个例子,假设你正在构建一个异常检测模型,以决定客户的信用卡交易是否欺诈。 如果你的应用程序根据模型的决定来阻止他们,随着时间的推移,你将只拥有模型允许的交易的“真实标签”,以及更少的欺诈性交易来进行训练。 模型的性能也会下降,因为训练数据变得偏向于“良好”交易。
这个问题没有简单的解决方案。 在我们的第一个例子中,零售商还会考虑缺货情况,并订购比预测需求更多的商品以弥补潜在的短缺。 对于欺诈检测场景,我们可以忽略或有时覆盖模型的分类,使用一些概率分布。 重要的是要意识到,许多数据集是时间相关的,即它们的分布会随着时间的推移而变化。 许多执行数据随机拆分的验证方法假设它们是 i.i.d.(独立同分布),但一旦你考虑时间的影响,情况就不再如此。
因此,重要的是不仅要捕获模型的输入/输出,还要捕获消费应用程序做出的最终决策,即使用或覆盖模型的输出。 这使你能够对数据进行标注,以避免在未来的训练回合中出现这种偏差。 管理训练数据并拥有允许人类对其进行整理的系统是你在遇到这些问题时所需的另一个关键组件。
随着时间的推移,不断演进智能系统以选择和改进机器学习模型也可以被视为元学习问题。许多该领域最先进的研究都集中在这些类型的问题上。例如,使用强化学习技术,如多臂老虎机,或在线学习在生产中。我们预计,我们关于如何最好地构建、部署和监控这些类型的机器学习系统的经验和知识将继续发展。
结论
随着机器学习技术的不断发展和执行更复杂的任务,我们对如何管理和将这些应用程序交付到生产环境的知识也在不断发展。通过引入和扩展持续交付的原则和实践,我们可以更好地管理在安全可靠的方式下发布机器学习应用程序变更的风险。
本文以一个示例销售预测应用程序为例,展示了 CD4ML 的技术组件,并讨论了我们如何实现这些组件的几种方法。我们相信这项技术将继续发展,新的工具会不断出现和消失,但持续交付的核心原则仍然适用,您应该在自己的机器学习应用程序中考虑这些原则。
致谢
首先,感谢 Martin Fowler 帮助我们重新定义本文的叙述和结构,并为其提供托管服务。
特别感谢许多 ThoughtWorks 的现任和前任员工,他们通过我们的研讨会和客户工作帮助我们创建和提炼本文中的想法,包括 Arun Manivannan、Danni Yu、David Tan、Emily Gorcenski、Emma Grasmeder、Jin Yang、Jonathan Heng 和 Juan López。
还要感谢以下早期审阅者,他们在本文初稿中提供了宝贵的反馈:Chris Ford、Fabio Kung、Fernando Meyer、Guilherme Silveira、Kyle Hodgson 和 Rodrigo Kumpera。
脚注
1: 围绕 AutoML 的努力旨在创建方法和工具来自动化机器学习开发过程中的许多步骤。
2: 目前正在积极研究 ease.ml/ci 和 ease.ml/meter 等工具和系统,以帮助管理和了解何时需要新的测试数据,或何时模型过拟合。
重大修订
2019 年 9 月 19 日: 发布最终部分
2019 年 9 月 18 日: 发布关于数据版本控制和数据管道的部分
2019 年 9 月 11 日: 发布关于编排和可观察性的部分。
2019 年 9 月 9 日: 发布关于实验跟踪和模型部署的部分。
2019 年 9 月 6 日: 发布关于模型服务和测试的部分
2019 年 9 月 4 日: 发布关于可发现数据和可重复训练的部分
2019 年 9 月 3 日: 发布第一部分:介绍和挑战