过去的二十年让我们对软件开发有了一些深刻的理解。其中很大一部分原因是 DevOps 的出现及其在整个行业中的广泛运用。
领先的软件公司都遵循相同的模式:首先是软件开发中的快速迭代,然后是持续集成、持续交付、持续部署。每个人工制品都要经过测试,看其提供价值的能力如何,而且软件始终要处于就绪的状态,并且通过自动化方法进行部署。
机器学习这个领域虽不同于传统的软件开发,但我们也能从软件开发行业汲取很多实用的经验教训。过去几年里,我们一直在开发生产型机器学习项目。我们的目标并不只是概念验证,而是与软件开发一样,具有可重复性,可复现性。因此,我们构建了一个机器学习管道编排器、具有强大的自动化能力并建立了一个工作流来实现这一目标。
为什么不直接使用 Jupyter Notebook?从头开始构建一组包含所有处理步骤的Notebook需要多长时间?新成员加入团队的难易程度如何?你现在可以快速复现两个月前的结果吗?你能将今天的结果和历史结果进行对比吗?你能在训练过程中提供数据的来源吗?如果你的模型过时了又会发生什么?
我们遇到过所有的这些问题。现在,我们将这些经验进行了归纳总结,得到了成功构建生产型机器学习的 12 个要素(类似于软件开发中的十二要素)。
1. 版本控制
对软件工程师来说,版本控制基本上是理所当然需要做的,但是这一方法论还尚未被数据科学家广泛接受。让我引用 Gitlab 上一些人的说法作为快速入门:
版本控制促进了整个软件开发团队之间的协调、共享和协作。版本控制软件使团队能够在分布式和异步环境中工作,管理代码和文件的更改和版本,并解决合并冲突和相关异常。
简单来说,版本控制能让你安全地管理软件开发中会变化的部分。
机器学习其实是一种特殊的软件开发,有着自己独特的需求。首先,机器学习中会变化的部分不止一种,而是两种:代码和数据。其次,机器学习其实是一种特殊的软件开发,有着自己特定的要求。其次,模型训练的方式是(快速)迭代,并且代码中的差异会很大(比如数据分割、预处理、模型初始化)。
一旦数据发生变化,就需要对其进行版本控制,以便能够可重复地进行实验和训练模型。简单粗暴的版本控制(硬拷贝)具有很大的改进空间,不过(尤其是)在团队共享的情况下,能够保持不变的版本控制是至关重要的。
代码的版本控制更为关键。除了上面的引述之外,预处理代码不仅在训练阶段很重要,而且在服务阶段也很重要,并且需要有与模型保持不变的相关性。为了在数据科学家的工作流程和投入生产的需求之间建立一种中台,无服务架构可以提供一种易于访问的方式。
总结:你需要对代码进行版本控制,也需要对数据进行版本控制。
2. 显式特征依赖
在一个完美的世界中,无论产生什么输入数据,都将永远产生完全相同的数据,至少在结构上是这样。但是世界并不完美,你从上游服务获取的数据也是由人类构建的,因此可能会发生变化。最终,特征也可能发生改变。最好的情况是,您的模型会直接故障报错,彻底失败,但最坏的情况是,你的模型悄悄的继续工作,但得到的结果都是垃圾。
明确定义的特性依赖关系能够尽早发现故障。设计良好的系统在持续训练和服务期间都能适应特性依赖。
总结:在您的代码中明确您的特性依赖项。
3. 具有描述性的训练和预处理
好的软件都有优良的描述和注释 --- 无需阅读每一行代码就可以轻松阅读和理解代码功能。
尽管机器学习是一类特殊的软件开发,但它并不能免除从业者遵循既定的编码指南。 代码编码要求中,最基本的一条就是只需很少的努力和很短的时间就可以对代码有基本理解。
预处理和模型训练与预测的代码应遵循 PEP8。代码中应当使用有意义的对象名并包含有助于理解的注释。遵循 PEP8 规范可提升代码的可读性,降低复杂度并加快调试速度。诸如 SOLID 之类的编程范式提供了思想准则,使代码在未来的用例中更易于维护、易于理解和灵活。
配置应该与代码分开。不要将数据分配比例硬编码到代码之中,而是通过配置方式提供,以便在运行时修改。人们在超参数调节方面已经熟知这一点了:使用分离的配置文件可以显著加快迭代速度,并且让代码库可以重复使用。
总结:提升代码可读性并且将代码和配置分开。
4. 训练结果可重现
如果你不能重现训练结果,你就不能相信结果。尽管这是本文的主题,但在可复现性方面有一些细节需要说明。不仅是你自己需要能复现训练结果,你的整个团队都要能做到这一点。不管是在 PC 还是AWS 虚拟机上的 Jupyter Notebook 中难以捉摸的训练结果都与可复现性背道而驰。
通过使用管道来训练模型,整个团队都可以透明地访问已执行的实验和已运行的训练。通过绑定可复用的代码库以及分离的配置文件,每个人都可在任何时间成功重新训练。
总结:使用管道自动化工作流程。
5. 测试
测试有多种形式。举两个例子:
- 单元测试是在原子级别上进行测试 - 每个功能都根据自己的特定标准单独进行测试。
- 集成测试采用相反的方法 - 将代码库的所有元素都放到一起进行测试,同时还会测试上下游服务的克隆版本或模拟版本。
这两种范式都适应于机器学习。
预处理代码必须要进行单元测试 - 给定各种输入,转换是否会产生正确的结果?
模型是集成测试的一个很好的用例 - 在生产环境中提供模型服务时,你的模型的表现是否与评估时相当?
总结:测试你的代码,测试你的模型。
6. 模型漂移/持续训练
在生产场景中,模型发生漂移是合理存在的问题。只要数据存在变化的可能性(例如用户输入、上游服务不稳定),你就需要考虑模型漂移的可能性。对于此问题存在的风险,有两种可以采取的措施:
- 监控生产系统中的数据。建立自动化报告机制,在数据发生变化时通知团队,这种变化甚至可能超过明确定义的特征依赖关系。
- 对新输入的数据进行持续训练。自动化良好的管道可以在新记录的数据上重新运行,并提供与历史训练结果进行对比,以显示性能退化情况,并提供一种快速方法将新训练的模型推向生产,从而让模型表现更好。
总结:如果你的数据会发生变化,请采用一种持续训练的管道化流程。
7. 实验结果跟踪
Excel 并非是一种跟踪实验结果的好方法。而且还不只是 Excel,任何分散的人工跟踪形式得到的信息都是不够权威的,也是不可信的。
正确的做法是以一种中心化的数据存储方式自动记录训练结果。自动化可确保对每次训练进行可靠跟踪,从而方便之后比较每次训练的结果。对结果进行中心化存储,能为跨团队提供透明的信息,并实现持续分析。
总结:通过自动化的方法跟踪实验结果。
8. 实验模型与生产模型
理解数据集需要付出努力。通常来说,我们会通过实验来实现理解,尤其是当我们关注的领域具备大量隐含领域知识时。创建一个 Jupyter Notebook,将部分/全部数据导入 Pandas Dataframe,进行几个小时无序研究,训练第一个模型,然后评估结果 - 任务完成。但不幸的是,现实并非如此。
实验在机器学习的生命周期中是有目的的。但是,这些实验的结果不是模型,而是理解。基于探索性 Jupyter Notebook 的模型是为了理解,而不是为生产开发的成品。理解之后,还需要进一步开发和适应,才能开始打造用于生产的训练流程。
不过,所有与领域特定的知识无关的理解都可以自动化。为您使用的每个数据版本生成统计信息,从而可以跳过那些你在 Jupyter Notebook 中做过的一次性的临时探索工作,并直接进入第一个管道。您越早在管道中进行实验,您就可以越早就中间结果进行协作,也就能更早地实现可投入生产的模型。
总结:Notebook不能投入生产,因此要尽早在管道式流程中进行实验。
9. 训练偏差
训练偏差(Training-Serving-Skew) 是指训练时的表现和实际生成环境运行时的表现的差别。 这种偏差可能由以下因素引起:
- 在训练时和在实际生成环境工作流中用不同的方式处理数据。
- 训练中的数据和在实际运行中的不同。
- 模型和算法之间存在反馈循环。
为了避免训练环境和生产环境的偏差,通常需要正确地将所有数据预处理嵌入到模型服务环境中。这当然是正确的,你也需要遵守这一原则。但是,这也是对训练偏差(Training-Serving-Skew)的过于狭隘的解释。
先来简单看一段古老的 DevOps 历史:2006 年,亚马逊的CTO Werner Vogels 创造了一个说法「You build it, you run it」(你构建的东西你要运行)。这是一个描述性短语,意思是开发者的责任不只是写程序,还需要运行它们。
机器学习项目也需要类似的机制——理解上游的数据生成以及下游的模型使用都在数据科学家的职责范围内。你训练用的数据是通过什么系统生成的?它会出问题吗?该系统的服务等级目标(SLO)是什么?这与实际生产环境服务的目标一致吗?你的模型的服务方式是怎样的?运行时环境是怎样的?怎样在服务过程时对函数进行预处理?这些都是数据科学家需要理解和解答的问题。
总结:正确地将预处理嵌入到生产环境的模型服务之中,确保你理解数据的上下游。
10. 可比较性
从为项目引入第二个训练脚本开始,可比较性就成了未来工作的重要组成部分。如果第二个模型的结果无法与第一个模型的结果进行比较,则整个过程就浪费了,其中至少有一个是多余的,甚至可能两个都多余。
根据定义,所有试图解决同一问题的模型训练都需要可以比较,否则它们就不是在解决同一问题。尽管迭代过程可能导致所要比较的东西发生变化,但是,在技术上,实现模型训练的可比较性需要一开始就作为首要功能内置于训练架构之中。
总结:构建你自己的管道式流程,以便你可以轻松比较各个流程的训练结果。
11. 监控
粗略地说,机器学习的目标应该是通过学习数据来解决问题。为了解决这个问题,需要分配计算资源。首先是分配给模型的训练,然后是分配给模型的服务。负责在训练期间提供资源的不管是人还是部门,都需要负责将这些资源转移给服务。模型在使用过程中可能出现很多性能下降问题。数据可能漂移,模型可能成为整体性能的瓶颈,偏差也是一个真实存在的问题。
模型的效果:数据科学家和团队负责监控他们生成的模型。他们并不一定要负责实施监控,尤其是当组织结构很大时,但他们肯定需要负责理解和解释监控的数据。至少,需要监控的内容包括输入数据、推理的时间开销、资源使用情况(如CPU、RAM)和输出数据。
总结:「You build it, you run it(你构建的模型你要保证它可稳定运行)」。监控生产环境中的模型是数据科学工作的一部分。
12. 模型的可部署性
从技术层面讲,每个模型训练流程都需要生成可部署到生产环境中的制品。毫无疑问,这些模型结果可能很糟糕,但它需要做成可以部署到生产环境的制品。
这是软件开发中的常见模式,也叫做持续交付(Continuous Delivery)。团队需要能够随时部署他们的软件,为了满足这个目标,迭代周期需要足够快。
机器学习也需要采用类似的方法。这样才能迫使团队首先考虑现实与期望之间的平衡。所有利益相关者都应当清楚,在模型结果方面,哪些结果是理论上可能的。所有利益相关者都应当在模型的部署方式以及如何与更大的软件架构整合上达成一致。同时,这也需要更强大的自动化,并且必然会采用前面段落中概述的大多数要素。
总结:每个训练流水线都需要得到可部署的制品,而不「只是」模型。
结语
这绝不是一个详尽的清单。它结合了我们的经验,欢迎您将其用作基准,测试您的生产体系结构的样板,或者用作设计您自己的产品体系结构的蓝图。