
基于我在2019年Highload++和明斯克数据节上的演讲。
如今,电子邮件已成为许多人网络生活中不可或缺的一部分。我们用它来进行商务沟通,存储各种重要的财务信息、酒店预订、订单处理等等。2018 年年中,我们制定了一项电子邮件开发的产品战略。现代电子邮件应该是什么样的?
邮件必须是 聪明的也就是说,帮助用户驾驭日益增长的信息量:筛选、整理并以最便捷的方式呈现信息。它应该是 有用这样一来,您就可以直接在收件箱中处理各种任务,例如支付罚款(不幸的是,我经常使用这项功能)。当然,与此同时,电子邮件也应该提供信息安全保障,过滤垃圾邮件并防止黑客攻击——也就是说,它应该是…… 安全.
这些领域涵盖了一系列关键挑战,其中许多挑战可以通过机器学习有效解决。以下列举了该战略中已开发的几个功能示例——每个领域一个。
- 智能回复邮件应用拥有智能回复功能。神经网络会分析邮件内容,理解其含义和目的,然后推荐三种最合适的回复选项:正面、负面和中性。这能显著节省回复邮件的时间,而且通常还能让你以独特而有趣的方式回复。
- 分组字母这与网店订单相关。我们经常网购,而商家通常会针对每个订单发送多封邮件。例如,最大的电商平台速卖通(AliExpress)会收到大量订单相关的邮件,我们计算得出,在极端情况下,邮件数量可能高达 29 封。因此,我们利用命名实体识别模型,从邮件文本中提取订单号和其他信息,并将所有邮件归类到一个邮件线程中。此外,我们还在单独的文本框中显示基本的订单信息,以便用户更轻松地处理此类邮件。

- 反网络钓鱼网络钓鱼是一种特别危险的欺诈性电子邮件,攻击者利用它窃取财务信息(包括银行卡详细信息)和登录凭证。这些电子邮件模仿正规服务发送的合法邮件,包括视觉效果。因此,我们利用计算机视觉技术识别主要公司(例如 Mail.ru、Sber、Alfa)的电子邮件徽标和设计风格,并将这些特征与文本和其他指标一起纳入我们的垃圾邮件和网络钓鱼邮件分类器中。
机器学习
简单介绍一下电子邮件中的机器学习。电子邮件系统负载很高:我们的服务器平均每天要处理 1,5 亿封电子邮件,服务于 30 万日活跃用户。所有必要的功能和特性都由大约 30 个机器学习系统提供支持。
每封邮件都要经过一套全面的分类流程。首先,我们会过滤掉垃圾邮件,保留正常邮件。用户通常不会注意到反垃圾邮件系统在工作,因为95%到99%的垃圾邮件甚至都无法进入相应的文件夹。垃圾邮件识别是我们系统的关键部分,也是最复杂的部分,因为不同的反垃圾邮件系统会不断相互适应,这对我们的团队来说是一个持续的工程挑战。
接下来,我们将邮件区分为人工邮件和机器人邮件。人工邮件最为重要,因此我们为其提供智能回复等功能。机器人邮件则分为两类:事务性邮件——来自服务的重要邮件,例如购买确认、酒店预订或财务信息——以及信息性邮件——商业推广和折扣信息。
我们认为交易邮件与私人信件同等重要。它们应该随时可用,因为我们经常需要快速查找订单或航班预订信息,而查找这些邮件会浪费大量时间。因此,为了方便起见,我们自动将它们分为六大类:旅行、订单、财务、票务、注册以及罚款。
新闻邮件数量最多,但或许也是最不重要的邮件类别,无需用户立即关注,因为即使不阅读,也不会对用户的生活产生任何重大影响。在我们的新界面中,我们将新闻邮件合并为两个主题:社交媒体和新闻邮件,从而在视觉上简化收件箱,只保留重要信息。

开发
许多系统都面临着严峻的运行挑战。模型会像任何软件一样随着时间的推移而退化:功能失效、机器故障、部署了错误代码。此外,数据也在不断变化:新增数据、用户行为模式演变等等。因此,缺乏适当支持的模型性能会随着时间的推移而逐渐下降。
同样重要的是要记住,机器学习对用户生活渗透得越深,它对生态系统的影响就越大,因此,市场参与者可能遭受的经济损失或利润也就越大。因此,越来越多的领域的参与者正在适应机器学习算法(经典例子包括广告、搜索和前面提到的反垃圾邮件)。
机器学习任务还有一个独特的特点:系统的任何改动,无论多么微小,都可能导致大量的模型维护工作,例如数据处理、重新训练和部署,这些工作可能持续数周甚至数月。因此,模型运行环境变化越快,维护所需的精力就越多。一个团队可以创建许多系统并为此感到高兴,但最终却几乎将所有资源都投入到维护这些系统上,而没有机会实现任何新功能。我们曾在反垃圾邮件团队中遇到过这种情况。我们当时就意识到,维护工作必须实现自动化。
自动化
哪些可以自动化?几乎所有东西都可以。我总结了构成机器学习基础设施的四个方面:
- 数据采集;
- 进修;
- 部署;
- 测试与监控。
如果环境不稳定且不断变化,那么围绕模型构建的整个基础设施远比模型本身重要得多。即使是传统的线性分类器,如果能够正确地输入特征并获得良好的用户反馈,其性能也会远胜于那些功能齐全的尖端模型。
反馈回路
这个周期包含了数据收集、进一步训练和部署——基本上就是整个模型更新周期。为什么这很重要?请查看邮件注册时间表:

一位机器学习开发者实现了一个反机器人模型,可以阻止机器人注册邮箱服务。邮件数量图表显示,邮件数量下降到只剩下真实用户。一切看起来都很完美!但四个小时过去了,机器人运营者修改了脚本,一切又恢复了原状。在这个版本中,开发者花了一个月的时间添加功能并重新训练模型,但垃圾邮件发送者只用了四个小时就适应了。
为了避免这种痛苦不堪的局面以及日后不得不重头再来,我们需要从一开始就考虑反馈回路的运作方式,以及环境变化时我们该如何应对。让我们从数据收集开始——它是我们算法的燃料。
数据收集
很显然,现代神经网络拥有的数据越多越好,而这些数据本质上是由产品用户生成的。用户可以通过标注数据来帮助我们,但我们不应该过度依赖这种方式,因为用户最终会厌倦训练模型,转而使用其他产品。
最常见的错误之一(这里我指的是吴恩达)是过分依赖测试数据集上的指标,而忽略了用户反馈。事实上,用户反馈才是衡量质量的主要标准,因为我们是为用户打造产品。如果用户不理解或不喜欢模型的性能,那么一切努力都将付诸东流。
因此,用户应始终拥有投票权,并应为其提供反馈工具。如果我们认为收到的消息与财务相关,则应将其标记为“财务”,并提供一个按钮供用户点击以表明该消息与财务无关。
反馈质量
我们来谈谈用户反馈的质量。首先,你和用户对同一个概念的理解可能不同。例如,你和产品经理可能认为“财务”指的是银行发来的邮件,而用户则认为奶奶发来的关于养老金的邮件也属于财务范畴。其次,有些用户会不假思索地点击按钮,没有任何逻辑思考。第三,用户的结论可能存在严重的错误。我们实践中一个明显的例子就是分类器的实现。 这是一种颇为滑稽的垃圾邮件,邮件中声称用户可以从一位突然在非洲发现的远房亲戚那里获得数百万美元。实施此分类器后,我们检查了这些邮件中“非垃圾邮件”的点击情况,结果发现其中 80% 都是诱人的尼日利亚垃圾邮件,这表明用户可能非常容易上当受骗。
而且别忘了,能点击按钮的不只有人,还有伪装成浏览器的机器人。所以,原始反馈对学习毫无用处。我们该如何利用这些信息呢?
我们采用两种方法:
- 来自相关机器学习的反馈例如,我们有一个在线反机器人系统,正如我之前提到的,它基于有限的特征快速做出决策。还有一个速度较慢的后续系统,它掌握更多关于用户及其行为的数据。因此,它能做出更明智的决策,从而拥有更高的准确率和完整性。这两个系统性能上的差异可以作为训练数据反馈给第一个系统。这样,较简单的系统就能不断努力,使其性能始终与更复杂的系统相匹配。
- 点击分类您可以简单地对每次用户点击进行分类,评估其有效性和可用性。我们在电子邮件反垃圾邮件系统中就采用了这种方法,利用用户属性、历史记录、发件人属性、文本内容以及分类器结果。最终形成一个能够验证用户反馈的自动化系统。由于它所需的重新训练次数显著减少,因此其工作可以成为所有其他系统的基础。该模型的首要目标是提高精确度,因为使用不准确的数据训练模型可能会造成严重后果。
在清理数据和优化机器学习系统的同时,我们不能忽视用户。对我们而言,图表上成千上万甚至数百万的错误只是统计数据,但对用户来说,每一个 bug 都是一场灾难。用户不仅要忍受产品的 bug,而且在收到反馈后,他们还期望类似的情况在未来能够得到消除。因此,始终值得赋予用户投票权,并让他们能够修正机器学习系统的行为,例如,为每次反馈点击创建个性化的启发式规则。在电子邮件方面,这可以包括让用户能够根据发件人和主题筛选出与特定用户相似的邮件。
此外,还需要根据报告或支持请求,以半自动或手动方式对模型进行调整,以避免其他用户遇到类似问题。
学习启发式方法
这些启发式方法和捷径存在两个问题。首先,不断增长的捷径数量难以维护,更遑论其质量和长期性能。其次,错误可能并不频繁,几次点击不足以重新训练模型。应用以下方法似乎可以显著缓解这两个看似无关的问题。
- 我们先做一个临时的拐杖。
- 我们将从中获取的数据输入到模型中,该模型会定期进行训练,包括使用接收到的数据进行训练。当然,这里重要的是启发式算法必须非常精确,以避免降低训练集中数据的质量。
- 然后我们设置了拐杖激活监控,如果一段时间后拐杖不再激活且完全被模型覆盖,我们就可以安全地将其移除。这个问题不太可能再次发生。
所以,拐杖大军非常有用。关键是,它们的服务是暂时的,而不是永久的。
进一步培训
重新训练是指将通过用户或其他系统反馈获得的新数据添加到现有模型中,并以此训练模型的过程。重新训练可能会带来以下几个挑战:
- 该模型可能根本不支持重新训练,而只能从头开始学习。
- 重新培训并不一定能提高生产效率。事实上,情况往往恰恰相反,甚至可能变得更糟。
- 变化往往难以预料。我们发现,这一点相当微妙。即使新模型在 A/B 测试中与现有模型的结果相似,也不意味着它们的性能完全相同。它们的性能差异可能仅为 1%,这可能导致新的 bug 出现,或者使之前修复的 bug 再次出现。我们和用户都已经习惯了与现有 bug 共存,因此,当出现大量新 bug 时,用户可能会感到困惑,因为他们期望的是可预测的行为。
因此,重新训练最重要的就是保证模型能够改进,或者至少不会变差。
提到模型重训练,我们首先想到的就是主动学习方法。这是什么意思呢?例如,一个分类器判断一封邮件是否与金融相关,我们在它决策边界附近添加一组带标签的样本。这种方法在广告领域非常有效,因为广告领域反馈丰富,模型可以进行在线训练。然而,如果反馈较少,我们得到的样本相对于生产数据分布就会存在严重的偏差,导致无法评估模型在生产环境中的性能。

事实上,我们的目标是保留原有的模式和已有的模型,并开发新的模式。延续性至关重要。我们之前一直难以推广的模型现在已经运行良好,因此我们可以专注于提升其性能。
我们在邮件中使用了多种模型:决策树、线性网络和神经网络。我们为每种模型都开发了自己的重训练算法。在重训练过程中,我们不仅会获取新数据,而且通常还会获取新的特征,这些特征将在下文的所有算法中加以考虑。
线性模型
假设我们有一个逻辑回归模型。我们将使用以下组件创建一个损失模型:
- 对新数据进行对数损失计算;
- 我们对新特征的权重进行正则化(旧特征的权重保持不变);
- 我们从旧数据中学习,以保留旧模式;
- 而且,或许最重要的是:我们添加了调和正则化,按照惯例,这保证了权重与旧模型相比不会发生显著变化。
由于每个损失分量都有系数,我们可以使用交叉验证或根据产品要求来选择适合我们任务的最佳值。

Деревья
接下来我们来看决策树。我们提出了以下用于重新训练决策树的算法:
- 生产环境运行着一个由 100-300 棵树组成的森林,这些树是用旧数据集训练的。
- 最后,我们移除 M = 5 个片段,并添加 2M = 10 个新片段,这些新片段是在整个数据集上训练的,但对新数据赋予较高的权重,这自然保证了模型的增量变化。
显然,随着时间的推移,树的数量会显著增加,因此需要定期修剪以满足截止日期要求。为此,我们使用了现在应用广泛的知识蒸馏(KD)技术。以下是其工作原理的简要概述。
- 我们目前有一个“复杂”的模型。我们在训练数据集上运行该模型,并获得输出类别的概率分布。
- 接下来,我们训练学习器模型(在本例中是树较少的模型),以使用类别分布作为目标变量来复制模型的性能。
- 需要注意的是,我们不使用任何数据集标签,因此可以使用任意数据。当然,我们会从生产流中抽取一部分数据作为学习模型的训练集。这样,训练集可以确保模型的准确性,而来自生产流的样本则保证了模型在实际生产环境中的类似性能,从而弥补了训练集的偏差。

这两种技术的结合(增加树的数量,并定期使用知识蒸馏来减少树的数量)确保了新模式的引入和完全的连续性。
利用知识密度估计(KD),我们还对模型特征进行特征操作,例如特征移除和缺失值处理。在我们的案例中,数据库中存储了一些重要的统计特征(例如发件人、文本哈希值、URL 等),这些特征容易出现故障。当然,模型并未针对这种情况进行优化,因为训练集不会出现故障。在这种情况下,我们将知识密度估计与数据增强技术相结合:在训练数据子集时,我们移除或将必要的特征置零,而标签(当前模型的输出)保持不变,学习模型则学习如何复制这种分布。

我们注意到,模型操纵越严重,所需的样品流量百分比就越高。
特征移除是最简单的操作,仅需占用总吞吐量的一小部分,因为只更改了几个特征,而且当前模型是在相同的数据集上训练的——差异微乎其微。简化模型(将树的数量减少数倍)则需要占用 50/50 的吞吐量。而遗漏重要的统计特征会严重影响模型性能,因此需要更高的吞吐量来确保新的、具有遗漏鲁棒性的模型在所有电子邮件类型上都能保持一致的性能。

快速文本
接下来我们来看 FastText。回顾一下,词嵌入由该词的词嵌入及其所有字母 N 元语法(通常是三元语法)之和构成。由于三元语法的数量可能相当多,因此使用了桶哈希,它将整个空间转换为一个固定的哈希表。最终得到的权重矩阵是内部层的大小乘以词数加上桶数。
在重新训练过程中,会引入新的特征:单词和三元组。Facebook 的标准重新训练流程并没有进行任何实质性的改进。它只是使用交叉熵对旧权重进行重新训练,而没有使用新特征。因此,这种方法自然会存在之前提到的所有缺点,即模型在生产环境中的不可预测性。为此,我们对 FastText 进行了一些修改。我们添加了所有新的权重(单词和三元组),使用交叉熵重新训练整个权重矩阵,并添加了类似于线性模型的调和正则化,从而保证旧权重的变化微乎其微。

CNN
卷积神经网络稍微复杂一些。如果只训练 CNN 的最后几层,当然可以应用调和正则化来确保连续性。但是,如果整个网络需要进一步训练,则无法对所有层都应用这种正则化。不过,可以使用三元组损失来训练互补嵌入().
三重态损失
让我们以反钓鱼任务为例,来看一下三元组损失算法。我们将使用我们自己的徽标,以及其他公司徽标的正面和负面示例。我们将最小化正面和负面示例之间的距离,最大化负面示例之间的距离,并留出少量间隙以确保类别更加紧凑。

如果我们重新训练网络,度量空间将完全改变,与之前的空间完全不兼容。这对使用向量的任务来说是一个严重的问题。为了克服这个问题,我们将在训练过程中混合使用旧的向量嵌入。
我们已向训练集添加了新数据,并从头开始训练模型的第二个版本。在第二阶段,我们对网络进行微调:首先训练最后一层,然后解冻整个网络。在构建三元组的过程中,只有部分嵌入使用已训练的模型计算,其余嵌入则使用旧模型计算。因此,在微调过程中,我们确保了度量空间 v1 和 v2 之间的兼容性。这是调和正则化的一种独特形式。

建筑整体
如果我们以反垃圾邮件系统为例来考察整个系统,就会发现各个模型并非孤立存在,而是相互嵌套。我们采集图像、文本和其他特征,并使用卷积神经网络(CNN)和快速文本搜索(Fast Text)生成嵌入向量。然后,我们基于这些嵌入向量应用分类器,为各种类别(例如邮件类型、垃圾邮件、是否存在徽标)生成分数。最后,我们将这些分数和特征输入到决策树森林中进行最终决策。这种方案中独立的分类器能够更好地解释系统结果,并在出现问题时更有针对性地重新训练组件,而不是将所有原始数据直接输入决策树。

最终,我们保证了各个层面的一致性。在底层,我们在 CNN 和 FastText 中使用了调和正则化;对于中间层分类器,我们也使用调和正则化和分数校准来确保概率分布的一致性。提升树采用增量式训练或知识蒸馏法进行训练。
通常来说,维护这种嵌套式机器学习系统非常麻烦,因为底层任何组件的更新都需要对上层整个系统进行相应的调整。但由于我们设置中的每个组件都只做细微的改动,并且与之前的组件兼容,因此整个系统可以分阶段更新,而无需重新训练整个结构,从而实现低成本的维护。
部署
现在我们已经介绍了不同类型模型的数据收集和进一步训练,接下来我们将把它们部署到生产环境中。
A/B 测试
正如我之前提到的,在数据收集过程中,我们通常会获得一个有偏差的样本,这使得评估模型在实际生产环境中的性能变得不可能。因此,在部署模型时,必须将其与之前的版本进行比较,以了解实际情况,即进行 A/B 测试。实际上,模型部署流程和图表分析相当常规,非常适合自动化。我们会逐步将模型部署到 5%、30%、50% 和 100% 的用户群体中,并收集所有可用的模型响应指标和用户反馈。如果出现任何显著的异常值,我们会自动回滚模型;对于其余情况,在收集到足够多的用户点击后,我们会决定提高部署比例。最终,我们会将新模型完全自动地部署到 50% 的用户群体中,而向所有用户群体的部署则需要人工审核批准,尽管这一步骤也可以自动化。
然而,A/B 测试流程仍有很大的优化空间。任何 A/B 测试都相当耗时(在我们的案例中,根据反馈量的不同,耗时 6 到 24 小时),因此成本较高且资源有限。此外,为了有效加快 A/B 测试的整体运行速度,需要测试流程中相当大比例的样本(收集用于评估指标的统计显著性样本可能需要很长时间),这使得 A/B 测试的测试次数极其有限。显然,我们只需要测试最有前景的模型,而我们在额外的训练过程中已经获得了相当多的这类模型。
为了解决这个问题,我们训练了一个专门的分类器来预测 A/B 测试的成功率。为此,我们使用训练集、保留集和实际测试样本的决策统计量、精确率、召回率和其他指标作为特征。我们还使用启发式方法将该模型与当前的生产模型进行比较,并考虑模型的复杂度。利用所有这些特征,基于测试历史训练的分类器会对候选模型(在本例中为决策树森林)进行评分,并决定在 A/B 测试中使用哪个模型。

在实施过程中,这种方法使我们能够将成功的 A/B 测试数量增加数倍。
测试与监控
令人惊讶的是,测试和监控并不会损害我们的健康;相反,它们还能改善健康并减轻不必要的压力。测试有助于预防故障,而监控则有助于及早发现故障,从而减少对用户的影响。
重要的是要明白,你的系统迟早都会出错——这是任何软件开发周期的必然结果。在系统开发的初期,总会存在很多bug,直到一切稳定下来,主要的创新阶段完成。但随着时间的推移,熵增效应会逐渐显现,错误也会再次出现——正如我开头提到的,这是由于周围组件的性能下降和数据变化造成的。
在此,我想指出,任何机器学习系统都应该从其整个生命周期的盈利能力来考虑。下图展示了一个系统捕获一种罕见垃圾邮件的例子(图中的线接近于零)。有一天,由于缓存错误,系统出现故障。不幸的是,系统没有对异常触发进行监控,导致在决策点将大量邮件保存到了垃圾邮件文件夹。尽管纠正了这些错误,但系统已经犯了太多错误,以至于五年内都无法收回成本。从模型生命周期的角度来看,这是一个彻底的失败。

因此,像监控这样看似简单的操作,对于模型的生命周期而言至关重要。除了标准且显而易见的指标外,我们还会计算模型响应和得分的分布,以及关键特征值的分布。利用KL散度,我们可以将当前分布与历史分布进行比较,或者将A/B测试中的值与流程中的其他值进行比较,从而能够发现模型中的异常情况并及时回滚更改。
大多数情况下,我们会在系统初始版本中使用简单的启发式方法或模型,这些方法或模型之后会用于监控。例如,我们会监控命名实体识别(NER)模型对特定在线商店的正则表达式的识别率,如果分类器的覆盖率下降,我们会调查原因。这又是启发式方法的一个实用应用!
结果
让我们再回顾一下文章的主要观点。
- 菲布德克我们始终以用户为中心:他们会如何看待我们的错误,以及如何反馈这些错误。我们谨记,用户反馈并非训练模型的纯粹来源,必须借助辅助机器学习系统进行净化。如果无法直接从用户收集信号,我们会寻找其他反馈来源,例如互联系统。
- 进一步培训保持模型的连续性至关重要,因此我们沿用当前的生产模型。我们训练新模型时,会通过调和正则化等技巧,确保新模型与旧模型不会出现显著偏差。
- 部署基于指标的自动部署能够显著缩短模型部署时间。监控统计数据、决策分布和用户后续反馈对于确保睡眠质量和周末高效工作至关重要。
希望您所读到的内容能够帮助您更快地改进机器学习系统,加快其上市速度,并提高其可靠性,从而减轻其工作带来的压力。
来源: habr.com

