机器学习的持续交付-CD4ML
端到端自动化构建部署机器学习应用
原文:Continuous Delivery for Machine Learning - CD4ML
关键字:持续部署、持续交付、机器学习、CD4ML
作者:Danilo Sato, Arif Wider, Christoph Windheuser
译者:周冠龙 Guanlong Zhou
写在前面
ThoughtWorks在最新一期的技术雷达中,将CD4ML推荐为“实验”评级,我所在的team恰好在给客户做一套与ML相关的系统,用到很多CD4ML中的概念和实践。由于文章较新,网上还没有合适的译文,故自己翻译以便与大家共同学习。不过我也是第一次翻译此类文章,如有不当,敬请指正,不胜感激。
如今,机器学习(ML)在各行各业中变得越来越普及,与传统软件(例如Web服务或移动应用)相比,ML的开发、部署和持续优化会更加复杂。复杂性源于三个维度的可变性:代码本身、机器学习的模型和数据。它们的变化通常很复杂又难以预测,而且不容易测试,难以解释并且难以改进。机器学习的持续交付(CD4ML)将持续交付的原理和实践引入到机器学习应用(程序),给我们提供一些可以遵循的基本准则。
简介和定义
2015年,Sculley等人在Google发表著名论文“Hidden Technical Debt in Machine Learning Systems“,其中强调指出,在现实世界里的机器学习系统中,真正的机器学习代码只占整个工程的一小部分。系统中存在大量围绕ML相关的基础设施架构和流程用于支持ML系统演进。在论文中,他们还探讨了许多此类系统中积累的技术债,比如和数据依赖性、模型复杂性、可再现性、测试、监控以及应对外部世界的变化相关的问题。
许多传统软件系统中也存在类似的问题,所以持续交付引入一个基于自动化流程、质量保证和规范,可靠并可复用地将软件部署到生产环境的方法。
杰兹和戴夫曾在他们的开创性著作《持续交付》中指出:
“持续交付是一种能够以可持续的方式安全快速地将所有的变更--包括功能添加、配置更改、错误修复和实验 -- 投入生产或交付用户的能力。”
-杰兹·汉布尔(Jez Humble)和戴夫·法利(Dave Farley)
除了代码之外,ML模型和训练数据的变化是另一种类型的变更,我们需要对它们进行管理并将其纳入软件交付过程中(图1)。
考虑到这些方面,我们可以扩展“持续交付”的定义,以纳入现实世界机器学习系统中存在的新元素和挑战,我们将这种方法成为“机器学习的持续交付(CD4ML)”。
CD4ML是一种软件工程方法论,使用此方法,跨功能团队可基于代码、数据和模型小步且安全地增量训练机器学习应用,并且这个机器学习应用可以在任何的时间点更加可靠地被快速重现和部署。
下面对定义中的关键字进行逐一解释
软件工程方法:它使团队能够有效地产出高质量的软件。
跨功能团队:一群来自数据工程、数据科学、机器学习工程、开发、运维和其他知识领域,具有不同技能和工作方式的专家协作在一起,充分发挥每个团队成员的技能和优势。
基于代码、数据和机器学习模型开发的应用(软件): ML软件生产过程的所有产物都需要通过不同的工具和工作流程,进行相应的版本管理。
小而安全的增量(部署):一个软件应用程序的发布被分为了多个小步增量迭代发布,这将使每次发布所产生的变化具有可见性和可控制性,从而增加了部署过程的安全性。
可重现和可靠的软件发布:虽然模型的输出结果具有不确定性并且难以重现,但将ML软件发布到生产中的过程是可靠且可重现的,在此过程中,应当尽可能提高自动化程度。
不受限于时间:可以随时将ML应用发布到生产环境至关重要。即使组织不想一直交付软件,它也应始终处于可发布状态。何时发布应该是由业务决策决定,而不是技术决策。
适应周期短:短周期意味着开发周期只有数天,甚至几小时,而不是几周,也不是几个月甚至几年。可靠的自动化流程是实现这一目标的关键。通过使用生产环境中获得的结果来调整ML模型可以形成一个快速的反馈循环。
在本文中,我们将实现CD4ML,并展示一系列机器学习应用程序里的重要技术组件,同时解释如何将不同的工具结合使用以实现完整的端到端过程。在适当的时候,我们将介绍一些我们选用的其他工具。随着在我们行业中的实践,我们还将讨论更多的开发和研究领域。
一个预测销售数据的机器学习应用
自2016年以来,我们就开始思考如何将持续交付运用到机器学习系统中,我们发布了一个用于展示ML的案例研究,这是我们与AutoScout合作构建的一个项目,用于预测在其平台上发售的汽车价格。
但是,我们没有被允许使用真实的代码作为示例,所以我们决定使用面向大众公开的问题和数据集构建一个ML示例应用,以阐述CD4ML的实现过程。这个机器学习应用程序解决了许多零售商面临的一个常见预测问题:尝试根据历史数据预测给定产品在未来的销售量。我们为厄瓜多尔大型杂货零售商Corporación Favorita发布在Kaggle上的问题构建了简化的解决方案。出于我们的目的,我们组合并简化了它们的数据集,因为我们的目标不是找到最佳预测结果(您的数据科学家能做得更好),而是演示如何实现CD4ML。
基于监督学习算法和流行的Scikit-learn Python库,我们使用具有标签的输入数据训练得到预测模型。将这个模型集成到简单的Web应用程序中,然后将其部署到云生产环境中。图2简略展示了此流程。
部署完成后,用户可以在Web应用程序( 图3 )中选择产品种类和预测日期,然后预测模型将输出对该日选定产品销量的预测结果。
共同的挑战
前面的讨论给我们开了个好头,但是在开展实施端到端部署的实践过程时,我们遇到了两个挑战。第一个挑战源于组织结构:不同的团队可能参与流程中不同的部分,并且存在交接(通常是“甩锅”)的问题,而对如何跨越这些边界却没有明确的期望( 图4 )。数据工程师可能正在构建使数据可被访问的流水线,而数据科学家正在为如何构建和提升ML模型性能发愁。然后,机器学习工程师或开发人员将不得不为如何集成该模型并发布到生产环境而担忧。
扼杀了将机器学习应用程序部署到生产环境-端到端流程自动化的能力
这将导致项目的延迟交付以及团队之间产生摩擦。一个常见的问题是模型只能停留在实验室,而永远不会离开概念验证阶段。还有一种情况,如果他们以手工方式将其投入生产环境,ML模型在之后将会过时且很难被更新。
第二个挑战是技术层面的:如何确保此流程是可重复和可被审核的。由于这些团队使用的工具不尽相同并遵循不同的工作流程,端到端的自动化很难实现。除代码外,还需要管理许多其他组件,对其进行版本控制并不容易。在这些组件中,有些体积很大,需要用到更加复杂的工具才能对他们进行有效的存取。
尽管解决组织挑战不在本文讨论范围之内,但是我们可以借鉴Agile和DevOps的经验,建立以结果为导向的且囊括不同学科的领域专家的跨职能团队,从而构造一个端到端的ML系统。如果您的组织无法做到这一点,则至少鼓励打破团队间的障碍,并在整个过程中尽早进行协作。
本文的其余部分将探讨我们针对技术挑战得出的解决方案。我们将深入探究每个技术组件,并逐步改进和扩展端到端流程以使其更加完善。
CD4ML的技术组件
当我们考虑如何使用机器学习解决预测问题时,第一步是了解数据集。在这种情况下,数据存储在一系列CSV文件里,其中包含以下信息:
产品,例如它们的分类以及是否易腐
商店,例如它们的位置以及它们如何聚集在一起
特殊事件,例如公共假期,季节性事件或2016年厄瓜多尔发生的7.8级地震
销售交易记录,包括给定产品的销售数量、日期和位置
在这个阶段,数据分析师和数据科学家通常将执行某种探索性数据分析(EDA),以了解数据的“形状”,并确定粗粒度模式和异常值。例如,我们发现某些产品的销售单位数为负,我们将其解释为退货。而我们仅打算探索销售数据,因此退货数据将从训练数据集中删除。
在许多组织中,训练ML模型所需的数据可能不会完全按照数据科学家所需的方式来构造。因此,它着重强调了我们的第一个技术组件:可被发现和可被访问的数据。
可被发现和可被访问的数据
最常见的数据源是你的核心交易系统。但是,从组织外部引入其他数据源的价值也不容忽略。我们找到了一些用于数据收集和使数据可用的常见模式,例如Data Lake架构、更传统的数据仓库体系、实时数据流集合,和最近我们正在尝试构建的去中心化的Data Mesh架构。
无论你使用哪种体系和架构,都需要保证你的数据是可被发现和可被访问的。对数据科学家而言,寻找所需数据花费的精力越多,他们建立有用的模型所花费的时间就越长。除此以外,他们会希望在现有输入数据之上产生新的特征数据,这可能有助于改善其模型的性能。
在我们的示例里,进行初始探索性数据分析(EDA)之后,我们决定将多个文件合并成为单个CSV文件,并清理无关数据点和可能在模型中引入有害噪声的数据点(例如负销售)。然后,我们将输出的文件放云存储系统中。
使用此文件作为输入训练数据的快照,基于文件夹结构和文件命名约定,我们能够设计出一种简单的方法来对数据集进行版本控制。数据版本控制是一个相对较大的话题,源于它可以在两个不同维度上进行改变:数据结构的变化和随时间推移实际采样数据内容的变化·。我们的数据科学家Emily Gorcenski 在其博客文章中更详细地介绍了此主题。不过在本文的后面,我们将讨论随着时间的推移对数据集进行版本控制的其他方法。
值得注意的是,在现实世界中,你可能会搭建更复杂的data pipelines(数据流水线),便于数据科学家可以轻松的从多个数据源获取数据并使用。
可重复训练的模型
当数据准备好之后,下面我们将开始迭代数据科学的工作流程以构建模型。我们通常将数据分为训练集和验证集,尝试组合使用不同的算法以及调整其参数和超参数。这样就产生了一个可以被验证集评估的模型,以评估模型预测的质量。一步一步迭代训练模型的过程,我们称之为机器学习(ML)流水线。
在图5展示的是针对销售预测问题构建的ML流水线,其中包含了不同的代码、数据和模型组件。输入数据、中间训练结果和验证数据集以及输出模型文件可能很大,我们不希望将他们存储在代码仓库中。而且,流水线的各个阶段会经常需要被修改,这使得其很难在数据科学家的本地环境之外被复制。
为了使模拟训练过程能够代码化,我们使用了一个叫做DVC(Data Science Version Control)的开源工具。它提供了与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 repo来重现整个ML流水线。
一旦找到合适的模型,我们会将其视为需要版本化的工件(artifact),并将它部署至生产环境。在此,我们使用dvc push和dvc pull命令来发布和获取它。
有一些其他开源工具可以解决这些问题:Pachyderm使用容器技术执行流水线中的各个步骤,并通过追踪数据提交,解决了数据版本控制和记录数据出处问题,优化了流水线的执行。MLflow项目定义了一种新的文件格式,用于配置环境和流水线各个步骤,并提供了API和CLI工具,可以在本地或远程运行项目。我们之所以选择DVC,是因为它是一个非常简单的CLI工具,可以更好地解决这上述问题。
模型的供给
当确定了合适的模型,我们需要决定如何在生产环境中使用。我们已经发现了一些实现它的方法:
嵌入式模型:这是略简单的方法,您可以将模型视为在消费应用程序中构建和打包的依赖。从现在开始,您可以将应用程序代码和所选模型的打包组合成为一个整体应用。
将模型部署为单独的服务:在这种方法中,模型被包装在可以独立部署的应用服务中。这样可以对模型独立发布更新,但是由于每次预测都需要进行远程调用,这会引入一些延迟。
以数据形式发布模型:使用这种方法,模型也被独立发布,不同的是运行中的其它应用会将其作为数据摄取。我们已经在流处理/实时处理方案中看到了这种用法。在这种方案中,应用程序可以订阅模型发布事件,在读取新模型到内存中的之前,继续使用旧版本的模型。蓝绿部署或金丝雀部署之类的软件部署模式也可以在这种情况下使用。
在我们的示例中,鉴于我们使用的应用程序也是由Python编写的,因此我们决定使用相对简单的嵌入模型方法。我们的模型将导出为序列化对象( pickle文件),并由DVC推送到存储。在构建应用程序时,我们将其拉出并嵌入到Docker容器中。从此,Docker镜像将包含我们的应用程序+模型,该镜像会被版本化并部署到生产中。
除了使用pickle序列化模型对象,还有其他工具可用于实现嵌入式模型模式。MLeap为导出导入Spark、scikit-learn和Tensorflow模型提供了一种通用的序列化格式。还有与语言无关的交换格式可以用于模型共享,例如PMML、PFA和ONNX。其中的一些序列化方法也适用于实现“model as data”。
此外,还可以使用例如H2O之类的工具将模型作为POJO导出至Jar库中,然后可以将它作为项目依赖添加到应用程序中。使用这种方法的好处是,你可以使用数据科学家熟悉的语言(Python或R)训练模型,并将模型导出为在不同目标环境(JVM)中运行的已编译二进制文件,这样可以减少时间干涉。
为了实现“model as data”模式,许多云服务供应商都提供了工具和SDK用于将你的模型包装并部署至他们的MLaaS(Machine Learning as a Service)平台中,如Azure Machine Learning、AWS Sagemaker和Google AI Platform。另一个选择是使用像Kubeflow这种工具,旨在Kubernetes上部署机器学习工作流,尽管它想尝试解决的不仅仅是模型供给部分的问题。
MLflow Models尝试提供一种标准来打包不同“口味”的模型,以供被下游的各种工具所使用,一些把模型作为独立的服务,另一些将模型嵌入在应用程序中。可以说,这是当前开发领域、各种工具和厂商正在努力简化的重点工作,换句话说,这意味着现在还没有(公开或私下)明确的标准被证明是最佳实践,因此你需要根据你自己的条件去评估,从而选择满足你需求的实践。
值得注意的是,无论你决定使用哪种模式,训练出的模型和它的消费者之间始终存在隐式契约。此模型期待输入数据符合指定形态,如果数据科学家改变了契约,那么就需要重新输入数据或特征值,则可能出现集成问题,导致使用该模型的应用程序损坏。因此,测试是我们必须考虑的。
机器学习中的质量测试
ML工作流程中可以使用到的测试种类繁多。尽管机器学习系统在某些方面天生具有不确定性且难以自动化,但是增加不同类型的自动化测试仍然具有价值并且可以提高机器学习系统的整体质量:
验证数据:我们可以添加用于验证输入数据的测试来验证数据格式和结构,或验证我们对有效值的假设。例如,他们在某特定范围内或不为空值。对于工程特征值,我们可以编写单元测试来检验他们是否计算正确。例如,对数字特征进行缩放或归一化,”one-hot”编码的结果包涵全0和一个1,或使用恰当值替换缺失值。
验证组建间集成:我们可以使用类似的方法测试不同服务间的集成。这里使用“契约测试”来验证预期的模型接口与使用中的应用程序的兼容性。同时还存在另一种测试,用于确保将模型以不同格式导出发布后,仍然可以产出相同的结果。这可以通过针对相同的验证数据集,运行原始模型和生产模型,并对比他们的结果来实现。
验证模型质量:虽然机器学习的模型性能具有不确定性,但数据科学家通常会收集和监控一系列指标对模型进行评估,例如错误率、准确率、精确率、召回率、混淆矩阵、AUC、ROC等。他们在调参优化过程中也很有用。作为简单的质量控制,我们在流水线引入阈值测试和棘轮。
验证模型偏差和公平性:虽然我们会在总体测试和验证数据集上获得不错的结果,但是检查模型在特定数据集上的基准表现也尤其重要。举个例子,在训练数据中可能存在固有的偏差,对比该数据集和真实世界中数据的实际分布,会发现在某些给定特征值(例如:种族、性别和地区)上存在更多的数据点,因此对跨不同域数据的检查工作也变得尤其重要。像Facets之类的工具可以帮助你对数据进行切片可视化,从而帮助发现数据集中不同特征的值分布。
在我们的示例应用程序里,Favorita定义的评估指标使用一个归一化的错误率。我们编写了一个简单的PyUnit阈值测试,如果错误率超过80%,测试就会中断,并且测试被安置在发布新版本的模型之前,以演示如何阻止不良模型的部署。
虽然我们给出的测试示例相对简单,但是要全面的对模型质量进行评估,并不容易。随着时间流逝,如果我们始终基于相同的数据集计算指标,那么将开始遇到过度拟合的问题。同时,当其他模型已被发布,你需要保证新版本的模型不会对不可见数据进行降级。因此,对测试数据的管理和整理变得尤其重要。
当你在分发模型和导出模型以供其他程序使用时,你会发现在训练时和服务时,特征向量的计算结果不同。解决此问题的一种方法是将保留数据集和模型打包件一起分发,并允许下游的应用开发团队在集成了保留数据集之后,根据保留数据集重新评估模型的性能。这相当于传统软件开发中广泛被使用的集成测试。
除了上面提到的,还有一些其他类型的自动化测试可以考虑,但是我们认为有必要在流水线中加入一些(人为)手工的阶段,以展示有关模型的具体参数和信息,并让人们决定是否对它进行升级部署。这个过程,实现了你对机器学习流程的监管,也引入了对模型偏误和模型公平性的人为检查,同时为人们理解当前模型表现的优劣提供了可被解释的信息。
通过加入更多类型的测试,将使你重新思考测试金字塔的构成:如图6所示,你可以为每种产出(代码、模型和数据)设计单独的金字塔,并考虑如何组合他们。相对而言,机器学习系统的测试和质量控制更加复杂,可以作为一个主题另写一篇文章深入探讨。
实验追踪
为了支撑起这个治理过程,对各个实验信息的捕获和追踪变得尤其重要,人们将用它决定该将哪种模型部署推广到生产环境当中。而数据科学家的工作重心主要在研究上,经常会存在多个实验共存的情况,而其中许多实验可能从未投入到生产环境。
在研究阶段,这种实验方法不同于传统的软件开发流程,因为我们预期这些实验中的许多代码都将被丢弃,只有其中部分代码被认为值得投入生产。因此,需要定义一种追踪他们的方法。
在示例中,我们决定遵循DVC建议的方法,即使用不同的Git分支来追踪源代码中的不同实验。尽管这违背了我们在单个主干上实践持续集成的偏好。DVC可以从不同分支和标签的实验中调取和显示指标,从而可以轻松地在它们之间切换。
在传统的软件开发中,使用git分支作为功能分支存在一个缺点,如果分支的寿命过长,那么可能导致分支合并困难。因为对代码的变更会产生较广泛的影响,所以这会组织开发团队尽可能的对代码进行重构。同时,这种做法迫使对每条分支分别设置作业任务,直到分支代码被合并到主干后,才能适当地进行和其它系统的集成,这种做法有违持续集成实践。
对于机器学习的实验,我们期望绝大部分的分支代码都不会被用于集成,并且各个实验之间的代码差异通常很小。从自动化持续集成的角度上看,我们实际上希望为每个实验训练多个模型,并收集指标,以告知我们哪种模型可以被用于下一阶段部署。
除了DVC之外,MLflow Tracking是另一个可以帮助我们进行实验跟踪的工具。它可以作为托管服务进行部署,同时提供了API和Web界面用于追踪不同实验的运行以及他们的参数和性能指标,如图7所示。
为了更好地支持实验过程,你可能需要多种(有时需要制定的硬件)环境。用于培训,使用具有弹性的基础设施的好处变得非常明显。基于云的基础架构非常适合这种场景,许多云服务供应商都在构建服务和解决方案以支持这个流程。
模型部署
在我们提供的简单示例里,我们只尝试构建一个模型,将该模型嵌入到应用程序中一同部署。而在现实世界中,复杂的场景会使用多种部署方式:
多组模型:有时你可能有多个模型用于执行同样的任务。例如,我们可以对每一种产品训练一个模型。在这种情况下,可以将模型部署为单独的服务,使应用程序通过独立的API调用获取预测结果。然后,你可以在之后决定哪些模型在发布的接口中被使用。
影子模型:在生产环境中替换模型时,会用到此模式。你可以将新的模型与当前模型共同编排部署,把新的模型为影子模型。发送相同的生产数据到这两个模型,收集并观察他们的表现,并以此决定是否使用新的模型。
竞争模型:这种模型适用稍微复杂一点的场景,当你尝试在生产环境中使用模型的多个版本(如A/B测试)时,试图找出哪个版本更好。这里的难点在于如何确保流量被重新定向到正确的模型,同时你还需花费一些时间,以确保收集足够的数据来保证作出的决策具有统计学意义。
在线学习模型:与我们前面讨论的模型不同,在线学习模型是经过离线训练产生,而用于在线预测。在线学习模型使用的算法和技术可以随着新数据的到来而不断提高其自身性能,所以可以在生产环境中不断学习。这带来了额外的复杂性,因为如果不向模型提供相同的数据,本地版本化的静态模型工件将会产生不同的预测结果。你不仅需要对训练数据进行版本控制,还要对将影响模型性能的生产数据进行版本控制。
在此,我想重申一次,为了支持更复杂的部署方案,使用基于弹性基础设施的架构将使你受益匪浅。除了可以在生产环境中同时运行上述多种模型以外,弹性基础设施可以让你按需进行横向扩展和纵向伸缩,从而提高系统整体的可靠性和可扩展性。
持续交付的编排
在所有主要构件模块都安装配置到位之后,有必要将他们串到一起,这就就要用到持续交付的编排工具。在这个领域中,有很多工具可供选择,他们中的大部分都提供了一些方法,用于配置和执行部署流水线以构建软件并将其发布到生产环境。在CD4ML中,我们还有一些其他需求:配置搭建基础设施,执行ML流水线,训练并捕获来自不同模型实验的指标;data-pipeline的构建、测试和部署;不同模型的测试、验证和对比,以确定将要采用的模型;基础架构的配置搭建以及将模型部署至生产环境。
我们选择使用GoCD作为我们的持续交付工具,因为它设计之初,就确定了将流水线概念作为首要设计目标。不仅如此,GoCD提供了多种触发器和提级机制,允许我们将不同流水线组合编排,从而实现更加复杂的工作流程。
在我们的简化示例中,我们还没有构建任何复杂的data-pipeline或配置基础设施,我们仅演示了如何组合使用两条GoCD流水线,如图8所示:
机器学习流水线:使用GoCD代理执行模型训练和评估,以及执行基本的阈值测试用于决定是否推广模型。
应用程序部署流水线:用于构建和测试应用程序代码,用于使用dvc pull从上游流水线获得合适的模型,将模型和应用程序打包为Docker镜像,并将它发布到Kubernetes生产集群。
随着项目的推进,我们可以扩展ML流水线以并行执行多个实验(此功能依赖于GoCD的fan-out/fan-in模式),同时我们需要定义模型监管流程,用于检查模型的偏差性、正确性和公正性。还有一些其它种类的步骤,用于决定哪些模型可以被推广和部署到生产环境中。
最后,持续集成的调度还包含另一个方面,就是为了防止将表现不佳的模型部署到生产环境后,能够及时撤销的回滚机制。它在整个流程中起到安全网的作用。
模型的监控与观察
既然我们的模型已经部署上线,我们就需要了解它在实际生产环境中的表现,并打通数据反馈流程,构成完整的循环。在此,我们可以复用现有应用程序和服务中已有的监控和观察所需的基础设施。
实时系统通常会使用日志聚合和指标收集工具来捕获系统中重要的数据,例如业务KPIs、软件可靠性、性能指标、故障信息和异常情况下触发报警的其他指标。我们同样可以使用这些工具来捕捉数据,用于理解我们模型的表现。数据包含:
模型的输入:通过分析采集到模型的数据,查看模型训练是否存在偏斜。模型的输出:分析模型从输入中得到的预测和建议,以了解模型在真实数据上的表现。
模型的可解释性输出:例如模型的各项系数、 ELI5或LIME输出之类的度量标准,允许进一步的研究以了解模型如何进行预测,用来识别训练期间未发现的潜在过度拟合或偏差。
模型的输出与决策:基于生产环境的输入数据,观察我们的模型将进行哪些预测,以及使用这些预测进行哪些决策。有时,应用程序可能会选择忽略模型并直接根据预定义的规则做出决定(或避免将来出现偏差)。
用户的行为与奖励:基于进一步对用户行为的追踪,我们可以捕获“奖励指标”以了解模型是否达到了预期的效果。例如,如果我们显示产品推荐,可以跟踪用户何时决定购买推荐产品作为奖励指标。
模型的公平性:针对可能存在偏颇的已知特征(例如种族,性别,年龄,收入群体等)分析输入数据和输出预测。
在我们的示例中,我们使用EFK Stack做监视和观察,它主要由3个工具组成:
Elasticsearch:开源搜索引擎。
FluentD:用于统一日志记录的开源数据收集器。
Kibana:可轻松浏览和可视化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的网页界面对其进行查询和分析,如图9所示,
还有很多其它用于监控和观察的常用工具,例如ELK Stack(使用Logstash而非FluentD进行日志提取和转发)、Splunk等等。
当你在生产环境中部署了多个模型后,对数据的收集、监控和观察变得尤为重要。例如,你可能需要评估一个影子模型,或是正在进行拆分测试,亦或是使用多组模型进行多臂赌博机实验(multi-armed bandit)。
这同样适用于以下场景,如果你需要进行终端实验,例如在用户的移动设备上训练模型或进行模型融合,或者部署多组在线学习模型,这些模型会不断学习生产环境中的新数据,而随时间发生改变。
通过捕获此数据,可以形成一个完整的数据反馈环。这可以通过收集更多的真实数据(例如在定价引擎或推荐引擎)实现,也可以通过添加分析人员,让他们分析从生产环境中捕获的新数据,创建用于制作新模型或改进模型的新训练数据集而实现。数据反馈的闭环是CD4ML的一大优势,因为它使得我们能够根据从实际生产数据中获得的经验来调整模型,从而实现持续改进的目的。
端到端的CD4ML流程
通过逐步攻克各项技术难题,并使用一系列的工具和新技术,我们设法搭建端到端的CD4ML流程如图10所示,该流程管理三个维度的工件(代码、模型和数据)。
实际上,我们需要一种简单的方法来管理、发现、访问和版本化我们的数据。然后,我们可以自动化模型的构建和训练工作,使这部分的流程可被重用。从而,我们可以进行多组模型的实验和训练。
接下来怎么办?
我们将本文中使用示例应用程序和代码上传到了的Github仓库,用于在各种会议和我们给客户做的workshop。我们将不断探索关于如何实现CD4ML。在本小节中,我们将重点介绍一些workshop中未涉及的领域,以及一些开放性领域以待在将来可以进一步探索。
数据版本控制
在持续交付中,我们将每次代码提交作为一个release的候选,这将触发部署流水线的执行一次构建部署。如果它通过了流水线的所有阶段,就可以被部署到生产环境中。在谈论CD4ML时,我们经常遇到的一个问题是“当数据变化时,如何触发流水线?”
在我们的例子中,机器学习流水线起始于download_data.py脚本如图5所示,它负责从共享区下载训练数据集。如果我们更改了共享区数据集的内容,这并不会立即出发流水线,因为程序的代码没有发生变化,并且DVC也检测不到它。为了能够将数据版本化,我们必须创建一个新的文件或更改文件名,这反过来需要我们更新download_data.py代码使用新的数据路径,并为此提交一次代码。
有一种更好的办法,允许DVC对文件内容进行追踪,并替换我们之前写的代码:
dvc add data/raw/store47-2016.csv ➊
这将对我们的机器学习流水线稍作修改,如图11所示
这会创建一个元数据文件,用于追踪和校验我们提交到Git的文件。当文件内容变化时,DVC会更新元数据文件,这个变更将会触发一次流水线。
虽然这允许我们在数据更改时重新训练模型,但这并不能解决有关数据版本控制的全部问题。一方面是数据历史:理想情况,你需要保留数据变更的完整历史记录,但是这并不总是可行的,取决于数据发生变化的频率。另一方面是数据出处:了解数据处理中的哪部分引起数据发生了变化,和该变化在不同数据集间是如何传播的。还有一个问题,随着时间的推移,数据的结构和类型可能发生变化,这些变化是否可以向前和向后兼容。
在流媒体领域,数据的版本化控制将会变得更加复杂,因此我们希望在这个领域中有更多的实践、工具和技术能够得到发展。
Data-Pipeline
目前为止,我们还未涉及的另一个方面,是关于如何对data-pipeline本身进行版本化、测试、部署和监视。在现实世界中,在某一些工具选型中使用CD4ML会比另一些好。举个例子,许多ETL工具需要你在图形界面里定义转换和处理数据的步骤,这样的工具通常会在版本控制、测试于发布至混合环境时带来麻烦。其中有一部分工具可以生成代码,并作为工件放置在部署流水线中。
我们更倾向于使用允许我们使用代码定义data-pipeline的开源工具,因为它在版本控制、测试与发布过程中带来了很大的便利。比如说,如果使用了Spark,则你的data-pipeline可以使用Scala编写,配合ScalaTest或spark-testing-base进行测试,把job打包成一个版本化的JAR工件,并通过GoCD的部署流水线进行部署。
通常情况,data-pipeline的入口会使用一个批处理作业或长时间运行的流处理应用,我们没有将它们包含在图10的端到端CD4ML流程图中,但是因为他们的输出可能发生变化,而下游用于接收的模型和程序没有做相应的修改,于是这可能带来一个潜在的集成问题。因此,将集成和数据的契约测试作为我们部署流水线中的一部分,可以有效的捕捉到这部分的错误。
与data-pipeline有关的另一种测试类型是数据质量检查,但这个主题设计的内容较广,可以单独作为另一篇文章讨论。
平台化思维
你可能注意到了,我们使用了多种不同的工具和技术以实现CD4ML。如果你有多个团队尝试开展这个实践,那么他们最终可能会去做大量重复的工作。这里,平台化思维可以帮助我们。并不是说一个独立的团队专门去做这件事会成为拖慢速度,而是说将注意力集中在搭建一个与领域无关的工具,它来解决潜在复杂的实现,从而能更快更容易地被其他团队尝试和使用。我们的同事Zhamak Dehghani在她的Data Mesh文章里对此进行了更详细的介绍。
演进中的无偏差智能系统
当你的第一个机器学习系统部署到生产环境时,他将开始预测并被用于于看不见的数据,它甚至可以替换你之前使用的基于规则的系统。为此所需的训练数据集和模型验证是基于一系列之前系统产生的历史数据,而这些历史数据可能被先前系统存在的固有偏差所影响。此外,机器学习系统对你用户产生的影响,也会影响到你将来的训练数据。
为了能更好的理解,让我们思考两种情况。第一种,是我们本文中探讨的需求预测解决方案。假设有一个应用程序可以预测需求,以确定需要订购和提供给客户产品的确切数量。如果预测的数量低于实际的需求,你将没有足够的物品用于出售,因此该产品的销量会减少。如果仅使用这些新交易的数据作为改进模型的训练数据集,那么随着时间的推移,需求预测的结果将不断减少。
第二种情况,试想你正在搭建一个异常检测模型,用来确定客户的信用卡交易是否为欺诈行为。如果应用程序采纳了模型的决策阻止了交易,随着时间推移,欺诈行为越来越少,被允许的交易越来越多。由于训练数据变得偏向“良好”交易,此模型的性能也会下降。
想解决这个问题并不容易。在第一种情况中,零售商有时还考虑了缺货的情况,并订购了比预期更多的商品以弥补潜在的短缺。对于欺诈监测方案,有时可以使用一些概率分布来忽略或覆盖模型的原本输出。同时,我们还需识到很多数据集都是时间数据集,即他们的分布会随时间发生变化。许多验证方法使用了随机数据分割,这都基于了他们符合i.i.d.(独立均匀分布)的假设,然而一旦将时间因素考虑在内,这个假设将不再成立。
因此,我们不仅要重视捕捉模型的输入和输出,还需要注重应用程序是否使用模型的决策而作出的最终判断。这里你可以对数据进行标注,从而避免在之后的训练数据中产生这种偏差。当你遇到这些问题的时候,允许人员通过管理系统对数据进行治理,是必不可少的。
随着时间的推移,智能系统的发展和机器学习模型性能的提升将被视为“元学习”问题。该领域很多最新的研究都集中在这类问题上。例如,强化学习的应用,或生产环境中的在线学习。我们期望我们在如何最好的构建、部署和监控这类机器学习系统方面的知识和经验也可以不断的演进。
结语
随着机器学习技术不断的进步,我们对管理和交付此类应用的知识也在不断发展。通过引入和延伸持续交付中的原理和实践,我们可以更好地控制更新发布引入的风险,从而安全可靠地部署机器学习应用程序。
通过解读销售预测示例应用程序,我们展示了一系列CD4ML的技术组件,并讨论了一些实现它们的方法。我们相信,随着ML这项技术继续发展,新工具将会不断出现和消失,但是持续交付的核心原理,将为你在以后搭建自己的机器学习应用程序中提供重要指导。