模型服务、监控与维护
在本章中,我们讨论在生产环境中服务(serving)、监控和维护模型的最佳实践。这是机器学习项目生命周期的最后三个阶段:
图 1:机器学习项目生命周期。

具体来说,我们将刻画机器学习运行时(runtime,即输入数据被应用于模型的环境)的属性,以及模型服务的模式(modes),例如批处理(batch)和按需(on demand)。此外,我们还将考虑在现实世界中服务模型所面临的三大挑战:错误、变化和人性。我们将描述在生产环境中应该监控什么,以及何时以及如何更新模型。
9.1 模型服务运行时的属性
模型服务运行时是将模型应用于输入数据的环境。运行时属性由模型部署模式(deployment pattern)决定。然而,一个有效的运行时还应具备我们在这里讨论的几个额外属性。
9.1.1 安全性与正确性
运行时负责认证(authenticate)用户的身份,并授权(authorize)他们的请求。需要检查的事项包括:
- 特定用户是否拥有运行其想运行的模型的授权访问权限,
- 传入的参数名称和值是否与模型的规范(specification)相符,以及
- 这些参数及其值当前是否对用户可用。
9.1.2 易于部署
运行时必须允许以最小的代价更新模型,并且理想情况下不影响整个应用程序。如果模型以 Web 服务的形式部署在物理服务器上,那么模型更新必须简单到只需将一个模型文件替换为另一个模型文件,然后重启 Web 服务即可。
如果模型是以虚拟机实例或容器的形式部署的,那么运行旧版本模型的实例或容器应该可以逐步替换:逐渐停止正在运行的实例,并从新的镜像启动新实例。同样的原则也适用于编排(orchestrated)容器。
通常,基于流式处理(streaming-based)的模型应用程序是通过流式传输新版本的模型来更新的。为此,流式应用程序必须是有状态的(stateful)。一旦新版本及相关组件(如特征提取器和评分代码)被流式传输到应用程序中,应用程序的状态就会改变,现在包含这些资产的新版本。现代流处理引擎支持有状态应用程序。所描述的架构如图 2 所示。
图 2:模型流式传输的高层架构。

9.1.3 模型有效性的保证
一个有效的运行时会自动确保它所执行的模型是有效的。此外,它还要确保模型、特征提取器和其他组件保持同步。它必须在 Web 服务或流式应用程序每次启动时进行验证,并在运行期间定期验证。如第 8 章第??节所述,每个模型部署时应附带以下四项资产:端到端测试集(end-to-end set)、置信测试集(confidence test set)、性能指标(performance metric)及其可接受取值范围(range of acceptable values)。
在以下两种情况下,模型不应在生产环境中提供服务(如果正在运行,必须立即停止):
- 至少有一个端到端测试示例没有被正确评分,或
- 在置信测试集示例上计算的指标值不在可接受范围内。
9.1.4 易于恢复
有效的运行时允许通过回滚到以前的版本来轻松地从错误中恢复。
从失败的部署中恢复应该以与部署更新模型相同的方式、相同的容易程度进行。唯一的区别是,不是部署新模型,而是部署之前正常工作的版本。
9.1.5 避免训练/服务偏差
强烈建议避免使用两套不同的代码库,一套用于训练模型,另一套用于生产环境中的评分。就特征提取(feature extraction)而言,特征提取器代码两个版本之间哪怕微小的差异,都可能导致模型性能欠佳甚至不正确。
工程团队可能出于多种原因在生产环境中重新实现特征提取器代码。最常见的原因是数据分析师的代码效率低下或与生产生态系统不兼容。
因此,运行时应该允许方便地访问特征提取代码,以满足各种需求,包括模型再训练(retraining)、临时模型调用(ad-hoc model calls)和生产环境。实现方式之一是将特征提取对象包装成一个独立的 Web 服务。
如果无法避免使用两套不同的代码库来生成训练和生产所用的特征,那么运行时应该允许记录在生产环境中生成的特征值。这些值随后应被用作训练值。
9.1.6 避免隐藏的反馈回路
在第 4 章第??节中,我们看到了一个隐藏反馈回路(hidden feedback loop)的例子。模型 \(m_B\) 使用模型 \(m_A\) 的输出作为特征,却不知道模型 \(m_A\) 也使用了模型 \(m_B\) 的输出作为其特征。
另一种隐藏的反馈回路只涉及一个模型。假设我们有一个将收到的电子邮件分类为垃圾邮件或非垃圾邮件的模型。假设用户界面允许用户将邮件标记为垃圾邮件或非垃圾邮件。显然,我们希望利用这些被标记的邮件来改进模型。然而,这样做,我们就有创建隐藏反馈回路的风险,原因如下。
在我们的应用程序中,用户只有在看到邮件时才会将其标记为垃圾邮件。然而,用户只能看到我们的模型分类为非垃圾邮件的邮件。此外,用户也不太可能定期去垃圾邮件文件夹,把一些邮件标记为非垃圾邮件。因此,用户的行为受到我们模型的显著影响,这使得我们从用户那里获得的数据产生偏差:我们影响了自己学习的现象。
为了避免这种偏差,将一小部分示例标记为"留出"(held-out),并在不预先应用模型的情况下把它们全部展示给用户。然后只使用这些留出示例作为额外的训练示例,包括用户没有反应的示例。
在更一般的情况下,一个模型可以间接影响用于训练另一个模型的数据。假设一个模型决定图书的展示顺序,而另一个模型决定每本书旁边展示哪些评论。如果第一个模型把某本书的一条评论放在列表底部,用户对第二个模型评论的缺失反应可能是由其页面位置过低造成的,而不是评论的质量问题。
9.2 模型服务模式
机器学习模型以批处理或按需模式提供服务。在按需模式下,模型可以服务于人类客户端或机器。
9.2.1 批处理模式服务
当模型应用于大量输入数据时,通常以批处理模式提供服务。一个例子是模型被用来详尽地处理产品或服务的所有用户的数据。或者,当它系统性地应用于所有传入的事件,如推文或在线文章的评论时。与按需模式相比,批处理模式更具资源效率,并且在可以容忍一定延迟时使用。
在批处理模式下,模型通常一次接受一百到一千个特征向量。通过实验找到速度最优的批大小。典型的批大小是 2 的幂:32、64、128 等。
批处理的输出通常保存到数据库中,而不是发送给特定的消费者。你可以使用批处理模式来:
- 为音乐流媒体服务的所有用户生成每周新歌推荐列表,
- 将在线新闻文章和博客文章传入的评论流分类为垃圾邮件或非垃圾邮件,
- 从搜索引擎索引的文档中提取命名实体,等等。
9.2.2 按需服务给人类用户
按需向人类用户提供模型服务的六个步骤如下:
- 验证请求,
- 收集上下文(context),
- 将上下文转换为模型输入,
- 将模型应用于输入,并获得输出,
- 确保输出有意义,
- 将输出呈现给用户。
在为来自用户的请求在生产环境中运行模型之前,可能有必要验证该用户是否拥有使用该模型的正确权限。
上下文代表用户向机器学习系统发送请求时所处的情境,用户也将在这个情境中接收系统的响应。
用户可以显式或隐式地向机器学习系统发送请求。显式请求的一个例子是音乐流媒体服务的用户请求推荐与给定歌曲相似的歌曲。另一方面,隐式请求由即时通讯应用程序发送,为最近收到的消息建议回复。
好的上下文可以实时或近实时地收集。它将包含特征提取器生成模型所期望的所有特征值所需的信息。它还包含足够的调试信息,足够紧凑以便保存到日志中,并且包含将用于随时间改进模型的信息。
让我们看几个问题的好上下文示例。
设备故障
在检测设备故障时,一个好的上下文包含振动和噪声水平、设备执行的任务、用户 ID、固件版本、自制造和上次维护以来经过的时间,以及自制造和上次维护以来的使用次数。
急诊室住院
要决定新病人是否应该住进重症监护病房,一个好的上下文应包括年龄、血压、体温、心率、脉搏血氧饱和度、全血细胞计数、生化检验、动脉血气分析、血液酒精含量、病史和怀孕情况。
信用风险评估
要对信用卡申请做出批准/拒绝决定,一个好的上下文应包括年龄、教育程度、就业状况、国家居留身份、年薪、家庭状况、未偿债务、是否拥有其他信用卡、该人是房主还是租户、该人是否宣布过破产,以及该人是否错过过往信用卡还款、错过多少次。即使某些信息不是特征提取所必需的,它们对日志记录和调试仍然很重要:客户 ID、日期和一天中的时间。
广告展示
要决定是否应该向网站用户展示某个特定的广告,一个好的上下文应包括网页标题、用户在网页上的位置、屏幕分辨率、网页上的文本和用户可见的文本、用户如何到达该网页,以及在网页上花费的时间。出于日志记录和调试的目的,上下文可能包括浏览器版本、操作系统版本、连接信息以及日期和时间。
特征提取器将上下文转换为模型输入。有时,特征提取器是机器学习流水线(pipeline)的一部分,正如我们在第 5 章第??节中讨论的那样。然而,通常的做法是把特征提取器构建为一个独立的对象。
当评分结果要提供给人类客户端时,很少直接呈现。通常,评分代码将模型的预测转换为更容易解释、对客户端更有价值的形式。
在将模型服务给人类之前,通常要衡量预测的置信度分数。如果置信度低,你可以决定不呈现任何内容:用户往往对看不见的错误抱怨较少。或者,如果用户期望得到输出,则告知他们置信度较低。然后提示:“你确定吗?”
当系统可能根据预测发起行动时,提示尤其重要。如果你能够估计错误可能造成的代价,并且预测置信度以 (0, 1) 为界,那么将 \(1 - \text{confidence}\) 乘以代价,就可以看到做出错误行动的潜在影响。例如,假设做出错误的代价估计为 1000 美元,模型输出的置信度分数等于 0.95,那么预期的错误代价值为 \((1 - 0.95) \times 1000 = 50\) 美元。你可以为模型推荐的不同行动设定预期代价值的阈值,如果预期代价超过阈值,就提示用户。
除了衡量模型的置信度之外,还要计算预测值是否有意义。在第 9.3 节中,我们将进一步详述要检查什么,以及如果输出没有意义,系统应该做出什么反应。
记录模型服务时的上下文以及用户的反应是很方便的。这既有助于调试可能存在的问题,也有助于通过创建新的训练示例来改进模型。
图 3:使用消息代理的按需模型服务。

9.2.3 按需服务给机器
虽然构建 REST API 在许多情况下是合适的,但我们经常通过流式传输来服务机器。事实上,机器的数据需求通常是标准且预先确定的。设计良好的固定拓扑的流式应用程序可以有效利用可用资源。
按需服务,无论是机器还是人类,都可能很棘手。需求可能变化很大,白天非常高,夜间非常低。如果你在云中使用虚拟资源,自动扩缩容(autoscaling)可以在需要时添加更多资源,然后在需求下降时释放它们。然而,自动扩缩容不够敏捷,无法应对意外的流量激增。
为了处理这种情况,按需架构包含一个消息代理(message broker),如 RabbitMQ 或 Apache Kafka。消息代理允许一个进程将消息写入队列,另一个进程从该队列读取。按需请求被放入输入队列。模型运行时进程定期连接到代理。它从输入队列中读取一批输入数据元素,并以批处理模式为每个元素生成预测。然后它将预测写入输出队列。另一个进程定期连接到代理,从输出队列读取预测,并将其推送给发送请求的用户(图 3)。除了允许我们应对需求激增之外,这种方法也更具资源效率。
9.3 现实世界中的模型服务
当真实的人在现实世界中与软件系统交互时,模型服务就变得复杂了。通常不可能预测所有用户的行为和反应。面向现实世界的软件系统架构必须准备好应对三种现象:错误、变化和人性。
9.3.1 为错误做好准备
错误在任何软件中都是不可避免的。在基于机器学习的软件中,错误是解决方案不可分割的一部分:没有完美的模型。因为我们无法修复所有错误,唯一的选择就是拥抱它们。
拥抱错误意味着设计软件系统的方式是:当错误发生时,系统继续正常运行。
有三个"不能"是我们必须接受和拥抱的:
- 我们不能总是解释错误为什么会发生。
- 我们不能可靠地预测它何时会发生,甚至高置信度的预测也可能是错误的。
- 我们不能总是知道如何修复某个特定的错误。如果它可以修复,需要什么样的、多少训练数据?
此外,当错误发生时,我们不能总是期望错误的预测至少接近或类似于正确的预测。错误可能是任意"疯狂"的。例如,一个自动驾驶汽车的模型,在以 120 公里/小时(约 74 英里/小时)的速度行驶且没有障碍物的情况下,可能预测最佳行动是停下来并向后行驶。
上下文的微小变化可能导致意想不到的错误模式。例如,识别工厂车间危险情况的模型可能在相机附近更换灯泡后开始出错。以前的灯泡是白炽灯,新的是荧光灯。
即使是罕见的错误也可能影响用户,如果用户数量很大的话。假设模型的准确率为 99%。如果你有一百万用户,百分之一的预测错误将影响数千人。
修复模型中的一个错误很少会带来新的错误。然而,这并不能保证。
在不可避免的错误面前如何设计系统?
9.3.2 处理错误
首先,要有一个至少能部分缓解你的系统看起来或表现得"愚蠢"的策略。例如,如果你的系统与用户对话,比如个人助理或聊天机器人,说"我不知道"比说一些随机的话要好。如果错误将对用户直接可见,则按上述方法计算错误的预期代价(expected cost),如果代价超过阈值,则不向用户显示预测。
或者,训练第二个模型 \(m_B\),它预测对于某个输入,第一个模型 \(m_A\) 很可能在该输入上出错。“保护"模型 \(m_B\) 的存在在模型 \(m_A\) 用于关键任务(mission critical)系统时尤其重要。
错误的可见性是决定是否以及如何隐藏它的重要因素。例如,考虑一个从互联网下载网页并从中提取某些实体的系统。假设用户对在检测到某种实体时收到警报感兴趣。模型可能犯两类错误:1) 即使文档不包含实体也提取出实体(假阳性,false positive,FP),以及 2) 不提取文档中存在的实体(假阴性,false negative,FN)。当前者发生时,用户收到无关的警报并感到沮丧。如果是后者,用户不会收到任何警报,不知道错误发生,从而避免沮丧。在这种情况下,你可能更愿意为精确率(precision)优化模型,同时保持召回率(recall)合理地高。
当你训练模型时,决定你最想避免哪种错误,然后相应地优化你的超参数,包括预测阈值。
当你对最佳预测的置信度较低时,考虑呈现多个选项。这就是为什么 Google 一次呈现 10 条搜索结果。最相关的链接出现在这 10 条搜索结果中的几率,远高于它出现在第一位。
另一种避免用户对模型错误感到沮丧的方法是控制用户接触模型的剂量。衡量你的模型犯的错误数量,并估计用户每分钟(天、周或月)愿意容忍多少错误。然后限制用户与模型的交互,使感知到的错误数量保持在该水平以下。
对于错误已经发生且可能被用户感知的情况,为用户提供报告错误的途径。收到报告后,记录使用模型时的上下文以及模型的预测。向用户解释将采取什么行动来防止类似错误在未来发生。
衡量用户与系统的互动、记录所有交互,然后离线分析可疑交互是合适的。这包括:
- 用户与系统的交互是否比以前少,
- 用户是否忽略了某些推荐,以及
- 用户是否在各种设置中花费了足够的时间。
为了进一步减少错误的负面影响,如果系统允许,给用户一个撤销系统推荐行动的选择。如果可能的话,将此扩展到系统代表用户执行的任何自动化操作。
代表用户行事的软件应用程序尤其必须限制其可能采取的行动。回想一下,机器学习模型的错误可能是任意"疯狂"的,就像自动驾驶汽车的例子一样,它可能突然决定向后行驶。在其他涉及健康、安全和金钱的关键场景中必须保持谨慎,比如拍卖竞价或处方开药。如果模型预测买卖的股票数量超过移动平均线加一个标准差,那么发送警报并暂停本来"自动"的行动是一个好主意。同样的逻辑也应适用于模型预测给病人开出不合理高剂量的药物,或者将汽车速度改变为远高于或低于通常值的情况。
如果你的系统可以自动拒绝模型的预测,那么最好实现某种回退(fallback)策略,同时告知用户失败(图 4)。一个不那么复杂的模型或手工设计的启发式规则可以用作回退。当然,回退策略的输出也应该被验证,如果看起来不合理也应被拒绝。在这种情况下,应该向用户发送错误消息。
图 4:现实世界模型服务流程图。

9.3.3 为变化做好准备并加以处理
基于机器学习的系统的性能通常会随时间变化。在某些应用中,它可能近实时地变化。
模型变化有两种类型:
- 其质量可能变得更好或更差。
- 对某些输入的预测可能会变得不同。
模型性能随时间下降的一个典型原因是概念漂移(concept drift),我们已经在第 3 章第??节中考虑过。什么是正确预测的概念可能会因为用户的偏好和兴趣而改变。这将需要使用更新的标注数据重新训练模型。
有些变化可能被用户视为积极的。有时,即使从工程角度来看系统的性能提高了,变化也可能被负面感知。你可能添加了训练示例,重新训练了模型,并观察到更好的性能指标值。然而,通过添加新数据,你无意中引入了数据不平衡。某些类别现在的代表性不足。对这些类别预测感兴趣的用户会看到性能下降,并抱怨甚至放弃你的系统。
用户会习惯于某些行为。他们可能知道向搜索引擎提交什么查询才能得到经常使用的文档或 Web 应用程序。那个查询不一定是对该目的最理想的,但它有效。假设你改进了搜索结果排序算法的相关性。现在那个查询不再返回那个特定的文档或应用程序,或者把它放在搜索结果的第二页。用户再也找不到曾经容易找到的资源,并感到沮丧。
如果你预计用户可能会负面感知变化,给他们时间适应。教育用户了解这些变化以及对新模型的期望。或者,可以通过逐步引入变化来完成。你可以混合旧模型和新模型的预测,并慢慢降低旧模型的比例。或者,你可以并行运行新模型和旧模型,让用户在一段时间内切换到旧模型,然后再将其退役(sunset)。
9.3.4 为人性做好准备并加以处理
人性是使有效的系统工程如此困难的原因。人类是不可预测的,往往是非理性的、前后矛盾的,并且期望不明确。一个健壮的软件系统必须预见到这一点。
避免困惑
系统的设计必须使用户在与系统交互时不会感到困惑。模型的输出必须以直观的方式呈现,不能假设用户了解机器学习和 AI 的任何知识。事实上,许多用户会假设他们正在使用典型的软件,并对看到错误感到惊讶。
管理期望
另一方面,一些用户的期望过高。主要原因在于广告。为了吸引注意力,基于机器学习的产品或系统在广告中经常被描绘为"智能的”。例如,Apple Siri、Google Home 和 Amazon Alexa 等个人助理在广告中经常被描绘为拥有人类智能。事实上,当输入被精心选择时,任何基于机器学习的系统都可能看起来非常智能。用户可能会看到这样的广告,并把所看到的推广到系统并未被设计为有效运行的情境中。
用户期待非凡表现的另一个常见原因(即使没有被承诺)是他们曾使用过(在他们看来)看起来"非常智能"的类似系统。这些用户会期望你的系统具有相同水平的"智能"。
赢得信任
一些用户,尤其是经验丰富的用户,如果知道系统包含一些"智能",就会不信任任何系统。这种不信任的主要原因是过去的经验。大多数所谓的智能系统都未能兑现承诺,因此一些用户在第一次遇到你的系统时就预期失败。
因此,你的系统必须赢得每个用户的信心,而且必须尽早做到。
有"智能"系统使用经验的用户很可能会对你的系统能力做几个简单的测试。如果你的系统失败,用户将不会信任它。例如,如果你的系统是一个搜索引擎,用户会查询自己的名字或他们撰写的文档来测试你的系统。或者,如果你的系统向企业客户提供关于组织的情报,用户会检查你的系统对他们组织的了解程度,以及情报是否有意义。自动驾驶汽车的驾驶员很可能会测试"启动发动机"、“跟随那辆车”、“保持当前速度"或"把车停在那条街上"等命令。根据服务的性质,你应该预料到这样的简单测试,并确保你的系统能通过这些测试。
管理用户疲劳
用户疲劳可能是你看到对系统兴趣下降的另一个原因。确保系统不会用推荐或审批请求过度打扰用户体验。避免一次性展示所有要展示的内容。只要有可能,让用户明确表达他们的兴趣。
此外,并非系统可以自动处理的所有行动都必须以这种方式处理。例如,如果系统自动化用户与他人的互动,它可能会发送私人或受限数据作为电子邮件回复,或者将其发布到开放的论坛上。在代表用户分享之前,评估信息的敏感性是有意义的。使用一个训练好的模型来检测这类潜在敏感的文本和图像。在另一个极端,系统可能过于保守,自动过滤掉相关信息,或者要求用户确认太多决定,这可能导致用户疲劳。
警惕惊悚效应
当用户与学习系统交互时,存在一种被称为惊悚效应(creep factor)的现象。它意味着用户认为模型的预测能力过高。用户会感到不舒服,尤其是当预测涉及他们非常私密的细节时。确保系统不会让人感觉像"老大哥”(Big Brother),也不会承担太多责任。
9.4 模型监控
已部署的模型必须持续监控。监控有助于确保:
- 模型被正确服务,并且
- 模型的性能保持在可接受的范围内。
9.4.1 可能出什么问题?
监控的设计应能对生产环境中模型的问题提供早期预警。具体来说,这包括:
- 用于更新模型的新训练数据使其性能变差;
- 生产环境中的实时数据发生了变化,但模型没有;
- 特征提取代码被大幅更新,但模型没有适应;
- 生成特征所需的资源发生变化或变得不可用;
- 模型被滥用或正受到对抗性攻击(adversarial attack)。
额外的训练数据并不总是好的。标注者可能错误地解释了标注说明。或者,一个标注者的决定可能与另一个标注者矛盾。自动收集以改进模型的数据可能是有偏的。原因可能包括,例如,第 9.1.6 节中讨论的隐藏反馈回路,或第 3 章第??节中讨论的系统性值失真(systematic value distortion)。
有时,生产环境中数据的属性逐渐变化,但模型没有适应。它仍然基于较旧的数据,而这些数据不再具有代表性。原因之一是我们在第 9.3 节中讨论的概念漂移。
软件工程师可以修复特征提取代码中的 bug,并在生产环境中更新特征提取器。但如果工程师没有同时更新生产模型,性能可能会以不可预测的方式变化。
即使特征提取和模型保持同步,某些资源(数据库连接、数据库表或外部 API)的消失或变化也可能影响由该特征提取器生成的某些特征。
一些模型,尤其是部署在电子商务和媒体平台上的模型,经常成为对抗性攻击的目标。恶意行为者,如不公平的竞争对手、欺诈者、犯罪分子和外国政府,可能会积极寻找模型的弱点并相应调整他们的攻击。如果你的机器学习系统从用户的行为中学习,那么有些人可能会采取行动,使模型行为向有利于他们的方向改变。
此外,攻击者可能想检查训练好的模型,以获取关于模型训练数据的信息。这些训练数据可能包含关于个人和组织的机密信息。
另一种滥用形式,可能是最难防止的,是模型的双重使用(dual use)。与任何软件一样,机器学习模型可以被用于好的目的(如你所愿)或坏的目的(通常未经你的同意)。例如,你可能创建并公开发布一个让人的声音听起来像卡通人物的模型。欺诈者可能会改造你的成果,伪造银行客户的声音,并代表他们执行电话交易。或者,你可能创建一个识别街上行人的模型。自动武器制造商可能会利用你的模型在战场上检测人员。
9.4.2 监控什么以及如何监控
监控必须让我们能够确保模型在应用于置信测试集时产生合理的性能指标。这个集合应该定期用新数据更新,以避免可能的分布偏移(distribution shift)。此外,模型必须定期在端到端测试集的示例上进行测试。
虽然准确率(accuracy)、精确率和召回率显然是监控的好候选,但有一个指标对于衡量随时间的变化特别有用:预测偏差(prediction bias)。
在一个什么都不变的静态世界里,预测类别的分布大致等于观察到的类别的分布。当模型被良好校准(well-calibrated)时尤其如此。如果你观察到相反的情况,模型就表现出预测偏差。后者可能意味着训练数据标签的分布和生产环境中当前的类别分布现在不同了。你必须调查这种变化的原因,并做出必要的调整。
监控使我们能够对废弃或改作他用的数据源保持警惕。一些数据库列可能停止填充。某些列中数据的定义或格式可能改变,而未适应的模型仍然假设以前的定义和格式。为了避免这种情况,必须监控从数据库表中提取的每个特征的值分布是否发生显著偏移。特征值和预测值两者的分布偏移都可以通过应用统计检验来检测,如卡方独立性检验(Chi-square independence test)和 Kolmogorov-Smirnov 检验。如果检测到显著的分布偏移,必须向利益相关者发送警报。
模型的数值稳定性也应该被监控。如果观察到 NaN(非数值,not-a-number)或无穷大,应触发警报。
监控机器学习系统的计算性能很重要。无论是急剧的还是缓慢泄漏式的性能退化都应该被检测到,并且必须发送警告。
当使用量波动看起来可疑时,进行监控并发送警报。具体来说:
- 监控一小时内模型服务的次数,并将其与一天前计算的相应值进行比较。如果该数字变化了 30% 或更多,向利益相关者发送警告警报。这个阈值必须针对你的使用场景进行调整,以避免产生过多的警告;
- 监控每日模型服务次数,并将其与一周前计算的相应值进行比较。如果该数字变化了 15% 或更多,向利益相关者发送警告警报。针对你的使用场景调整该值。
监控这些数字有助于检测不良变化:
- 预测值的最小值和最大值,
- 给定时间段内预测值的中位数、平均值和标准差,
- 调用模型 API 时的延迟,以及
- 执行预测时的内存消耗和 CPU 使用率。
此外,为防止分布偏移,监控自动化必须:
- 在一段时间内随机留出一些输入来积累它们,
- 将这些输入送去标注,
- 运行模型,并计算性能指标的值,
- 如果性能显著下降,向利益相关者发出警报。
推荐系统需要额外的监控。这些模型向网站或应用程序用户提供推荐。监控点击率(click-through rate,CTR),即点击了推荐的用户数与从该模型收到推荐的总用户数之比,可能很有用。如果 CTR 在下降,模型必须更新。
重要的是要注意,在过于保守与频繁向利益相关者发送关于指标微小变化的警报之间存在着棘手的权衡。如果你警报太频繁,人们可能会厌倦接收警报,最终开始忽略它们。在非关键任务的情况下,允许利益相关者定义他们自己的触发警报的阈值可能是合适的。
记录监控事件,使整个过程可追溯。为了可视化模型性能分析,监控工具的用户界面应该提供趋势图,显示模型退化如何随时间演变。
监控工具的一个属性应该是能够在数据切片(slice)上计算和可视化指标。切片是数据的一个子集,只包含特定属性具有特定值的示例。例如,一个切片可以只包含州属性为佛罗里达州的示例;另一个切片可以只包含女性的数据,等等。模型的退化可能只在某些切片中观察到,而在其他切片中仍然不显著。
除了实时监控之外,记录以下数据也很重要:
- 可能有助于找到问题根源的数据,
- 无法实时分析的数据,或
- 有助于改进现有模型或训练新模型的数据。
9.4.3 记录什么
重要的是记录足够的信息,以便在未来的分析中重现任何不稳定的系统行为。如果模型服务于前端用户,如网站访问者或移动应用程序用户,那么值得保存模型服务时用户的上下文。如第 9.2 节所讨论的,上下文可能包括:网页内容(或应用程序的状态)、用户在网页上的位置、一天中的时间、用户来自哪里,以及在模型预测被提供之前他们点击了什么。
此外,包含模型输入(即从上下文提取的特征)以及生成这些特征所花的时间也是有用的。
日志还可以包括:
- 模型的输出,以及生成它所花的时间,
- 用户观察到模型输出后的新上下文,
- 用户对输出的反应。
用户的反应是观察到模型输出后立即采取的行动:点击了什么,以及在输出被提供后过了多长时间。
在拥有数千用户的大型系统中,模型每天为每个用户服务数百次,记录每个事件可能是不可行的。更实际的做法是进行分层抽样(stratified sampling)。你先决定要记录哪些事件组,然后只记录每个组中一定百分比的事件。这些组可以是用户组或上下文组。用户可以按年龄、性别或与服务的关系年限(新客户 vs. 老客户)分组。上下文组可以是清晨、工作日和深夜交互。
当你在日志中存储用户的活动数据时,用户应该知道存储了什么、何时、如何存储,以及存储多久。如果可能的话,数据应该在不妨碍实用性的情况下被匿名化或聚合。对敏感数据的访问必须仅限于被指派在特定时间段内解决特定问题的人员。避免让任何分析师访问敏感数据来解决无关的业务问题。这可能导致法律问题。
确保用户可以退出对其活动数据的记录和分析。不同的数据保留策略适用于不同的国家。每个国家都对可以存储或用于分析的关于其公民的内容施加自己的限制。
9.4.4 监控滥用
一些个人或组织可能会将你的模型用于自己的业务。这样的用户可能每天发送数百万个请求,而典型用户可能只发送十几个。或者,一些用户可能想对训练数据进行逆向工程,或者学习如何让模型产生期望的输出。
防止此类滥用的方法包括:
- 让用户按请求付费,
- 在响应请求之前创建越来越长的暂停,甚至
- 阻止某些用户。
为了实现自己的业务目标,一些攻击者可能试图操纵你的模型。攻击者可能提交数据,以只对攻击者有利的方式改变模型。结果,模型的整体质量可能会下降。
防止此类滥用的方法包括:
- 除非相似的数据来自多个用户,否则不信任来自某个用户的数据,
- 为每个用户分配一个声誉分数,并且不信任从低声誉用户获得的数据,以及
- 将用户行为分类为正常或异常,并且不接受来自表现出异常行为的用户的数据。
攻击者会通过调整自己的行为来试图绕过你的防御。为了有效防御你的系统,定期更新你的模型。添加新的数据和检测欺诈交易的新特征。
9.5 模型维护
大多数生产模型必须定期更新。更新频率取决于几个因素:
- 模型犯错的频率以及错误的严重程度,
- 模型应该多"新鲜"才能有用,
- 新的训练数据多久可用一次,
- 重新训练一个模型需要多少时间,
- 部署模型的成本有多高,以及
- 模型更新对产品和用户目标的实现有多大贡献。
在本节中,我们讨论模型维护:模型在生产环境中部署后,何时以及如何更新。
9.5.1 何时更新
当模型首次部署到生产环境时,它往往远非完美。不可避免地,模型会犯预测错误。其中一些可能是关键的,因此模型需要更新。随着时间的推移,模型可能会变得更加稳健,需要的更新更少。然而,有些模型应该不断更新,可以说,始终保持"新鲜"。
模型的新鲜度取决于业务需求和用户需求。电子商务网站上的推荐模型必须在每次购买后更新。如果用户利用模型在新闻网站上获取推荐内容,模型可能需要每周更新。另一方面,语音识别/合成或机器翻译模型可以更新得没那么频繁。
新训练数据的可用速度也影响模型更新的频率。即使新数据来得很快,比如热门网站上的评论流,获得标注数据也可能需要时间并需要大量投入。有时,标注是自动化的但延迟的,就像流失预测(churn prediction)一样,用户决定留在还是离开服务发生在遥远的未来。
有些模型需要大量时间来构建,特别是如果需要超参数搜索的话。等待几天甚至几周才能获得新版本的模型并不罕见。使用可并行化的机器学习算法和图形处理单元(GPU)来加速训练。现代库,如 thundersvm 和 cuML,允许分析师在 GPU 上运行浅层学习算法,训练时间显著缩短。如果你无法承受等待几天或几周才能获得更新模型,那么使用不太复杂(因此也不太准确)的模型可能是你唯一的选择。
如果更新成本高昂,你可能决定不那么频繁地更新模型。例如,在医疗保健领域,由于法规、隐私问题和昂贵的医学专家,获得标注示例既复杂又昂贵。
并非所有模型都值得部署。有时,潜在的性能提升不值得用户可能产生的挫败感。然而,如果用户干扰可控,部署成本不高,那么即使很小的改进,从长远来看也可能带来显著的业务成果。
9.5.2 如何更新
正如所讨论的,理想情况下,你的软件允许部署新版本的模型而无需停止整个系统。在虚拟化或容器化基础设施中,可以通过在仓库中替换虚拟机(VM)或容器的镜像,逐步关闭旧的 VM/容器,并让自动扩缩器从更新的镜像实例化 VM/容器来实现。
机器学习部署和维护自动化的架构如图 5 所示。这里我们有三个仓库:数据、代码和模型;三个仓库都是有版本的。我们还有两个运行时:模型训练和生产。模型在生产运行时中运行,该运行时是负载均衡和自动扩缩的。当需要更新模型时,模型训练运行时分别从数据仓库和代码仓库拉取训练数据以及模型训练代码。然后它训练新模型并将其保存到模型仓库中。
图 5:机器学习部署和维护自动化架构。

图 6:使用消息代理的按需模型服务和更新。

一旦新版本的模型被放入仓库,生产运行时就会拉取:
- 来自模型仓库的新模型;
- 来自数据仓库的测试数据;以及
- 来自代码仓库的将模型应用于测试数据的代码。
如果新模型通过测试,旧模型就从生产环境中撤下。它按照适当的部署策略被新模型替换,如第??节所述。A/B 测试或多臂老虎机(multi-armed bandit)算法可以帮助做出替换决定。
分布偏移控制数据库积累模型接收的输入及其评分结果。一旦积累了足够数量的示例,这些数据就会被发送给人类 1 进行验证,目的是检测分布偏移。
在模型流式传输场景中,模型更新发生在流处理器的状态更新时(参见第 9.1.2 节和图 2)。
使用消息代理架构的按需模型服务中的模型更新类似于模型流式传输(参见第 9.2.3 节和图 3)。
图 6 展示了一种基于消息代理的架构,它不仅允许服务和更新模型,而且将人类标注者纳入循环。标注者接收未标注的示例,抽样其中一些,为抽样的示例分配标签,并将标注后的示例发送回消息代理。模型训练模块从队列中读取标注示例。当它们的数量足以显著更新模型时,它就训练一个新模型,将其保存到模型仓库,并向代理发送"模型就绪"消息。模型服务进程从仓库拉取新版本的模型,并丢弃当前模型。
让我们概述一些成功进行模型维护的额外注意事项。
许多公司使用持续集成(continuous integration)工作流,一旦新的训练数据可用,模型就自动训练。建议从零开始重新训练模型,使用全部训练数据,而不是仅在新的示例上微调现有模型。
对于每个训练示例,建议存储标注者的身份。此外,将用于在生产数据库中生成特定值的模型版本附加到该值上。如果发现模型版本存在问题,知道它生成了哪些数据库值,就可以只重新处理那些特定的值。
如果模型被频繁重新训练,将流水线的超参数存储在配置系统中是很方便的。Google 推荐 2 一个好的配置系统应该满足以下几点:
- 应该很容易将配置指定为相对于先前配置的变化。
- 应该很难出现人工错误、遗漏或疏忽。
- 应该很容易在视觉上看到两个模型之间配置的差异。
- 应该很容易自动断言和验证关于配置的基本事实:使用的特征数量、数据依赖等。
- 应该能够检测未使用或冗余的设置。
- 配置应该经过完整的代码审查,并签入仓库。
确保运行时环境有足够的硬盘空间和 RAM 来容纳更新后的模型。不要指望旧版本的模型和新版本只会在性能上有所不同。准备好新模型比以前的模型大得多的情况。同样,不要指望新模型会像以前的模型一样快地运行。特征提取代码的低效、流水线中额外的阶段或不同的算法选择都可能显著影响预测速度。
模型将不可避免地产生预测错误。然而,对业务或客户来说,有些错误比其他错误代价更高。一旦部署了新版本的模型,验证它不会产生比以前模型明显更高代价的错误。
检查错误是否在用户类别之间均匀分布。如果新模型负面影响更多来自少数群体或特定地区的用户,那是不受欢迎的。
如果上述任何验证失败,不建议部署新模型。如果在部署后检测到失败,回滚它并启动调查。如第 9.1 节所讨论的,回滚到以前的模型必须和部署新模型一样容易。
警惕模型级联(model cascading)。如第 6 章第??节所讨论的,如果一个模型的输出成为另一个模型的输入,改变一个模型会影响另一个模型的性能。如果你的系统使用模型级联,一定要更新级联中的所有模型。
9.6 小结
一个有效的运行时具有以下属性。它是安全和正确的,确保易于部署和恢复,并提供模型有效性的保证。此外,它避免了训练/服务偏差和隐藏的反馈回路。
机器学习模型以批处理或按需模式提供服务。在按需模式下,模型可以服务于人类客户端或机器。当模型将应用于大数据并且可以容忍一定延迟时,通常以批处理模式提供服务。
当按需服务给人类时,模型通常被包装成 REST API。机器的数据需求通常是标准且预先确定的,所以我们经常通过流式传输来服务机器。
面向现实世界的软件系统架构必须准备好应对三种现象:错误、变化和人性。
部署到生产环境的模型必须持续监控。监控的目标是确保模型被正确服务,并且模型的性能保持在可接受的范围内。
生产环境中的模型可能会出现各种问题,特别是:
- 额外的训练数据使模型性能变差;
- 生产数据的属性发生了变化,但模型没有;
- 特征提取代码被大幅更新,但模型没有适应;
- 生成特征所需的资源发生变化或变得不可用;
- 模型被滥用或正受到对抗性攻击。
自动化必须计算对业务至关重要的性能指标的值,如果这些指标的值显著变化或低于阈值,则向适当的利益相关者发送警报。此外,监控必须揭示分布偏移、数值不稳定性和计算性能下降。
重要的是记录足够的信息,以便在未来的分析中重现任何不稳定的系统行为。如果模型服务于前端用户,那么记录模型服务时用户的上下文很重要。此外,包含模型输入(即从上下文提取的特征)以及生成这些特征所花的时间也是有用的。日志还可以包括从模型获得的输出及其生成时间,用户观察到模型输出后的新上下文,以及用户对输出的反应。
一些用户可以将你的模型用作自己业务的基础。他们可能对训练数据进行逆向工程,或者学习如何"欺骗"你的模型。为防止滥用:
- 除非相似的数据来自多个用户,否则不要信任来自某个用户的数据,
- 为每个用户分配声誉分数,并且不信任从低声誉用户获得的数据,
- 将用户行为分类为正常或异常,
- 让用户按请求付费,
- 创建越来越长的暂停,以及
- 阻止某些用户。
大多数机器学习模型必须定期或偶尔更新。更新的频率取决于几个因素:
- 模型犯错的频率以及错误的严重程度,
- 模型应该多"新鲜"才能有用,
- 新的训练数据多久可用一次,
- 重新训练一个模型需要多少时间,
- 训练和部署模型的成本有多高,以及
- 模型更新对实现用户目标的贡献有多大。
模型更新后,一个好的做法是让模型针对端到端测试集和置信测试集中的示例运行。重要的是确保输出要么与以前相同,要么变化符合预期。同样重要的是验证新模型不会产生比以前模型明显更高代价的错误。
还要检查错误是否在用户类别之间均匀分布。如果新模型负面影响来自少数群体或特定地区的用户,那是不受欢迎的。
机器学习工程
Andriy Burkov
“从理论上讲,理论与实践之间没有区别。但在实践中,是有区别的。” ——Benjamin Brewster
“完美的项目计划是可能的,只要首先记录一份所有未知事物的清单” ——Bill Langley
“筹款时,它是 AI。招聘时,它是 ML。实施时,它是线性回归。调试时,它是 printf()。” ——Baron Schwartz
本书以"先读后买"的原则发行。