搭建容易维护难!谷歌机器学习系统血泪教训
2014 年,谷歌一篇探讨机器学习背后暗藏高额技术债的论文曾火爆一时。今天,这篇论文又出现了知名技术社区 HackerNews 的头条。看来,即使过了 4 年时间,人工智能进入新的春天,但困扰着机器学习研究者的问题还是类似的问题。
本文作者表示,希望这篇论文能够为在生产环境中采用机器学习系统的开发者与维护者提供一些实用建议。作者警告称,虽然从零搭建机器学习系统还算轻松,但随后的改进却可能存在出乎意料的困难。在阅读本文时,读者会清晰感受到其中的经验源自作者在谷歌公司任职期间来之不易的艰辛积累。
AI 前线将带大家一起重温这篇经典文章,文章部分经验强调“与机器学习相关,并不代表着能够彻底放弃良好软件工程实践”,也有部分内容属于特定于机器学习的常见陷阱。考虑到所有希望建立“X+AI”业务的初创企业都面临着这类潜在挑战,因此文中提出的建议非常值得大家认真考量。
本文提到的机器学习系统带来的主要技术债类别包括:信息隐藏以及变化封闭带来的挑战 ; 胶水代码与配置;以及不断变化的外部世界以及分析模型结论对这一世界的理解偏差。
本文中最重要的见解之一,在于技术债务应当成为工程师与研究人员们需要注意的问题。以大规模增加系统复杂性为代价进行解决方案研究显然不是什么明智的作法。即使仅仅增加一到两种看似无害的数据依赖关系,也有可能减缓进一步发展速度。虽然减少技术债务并不像证明新定理那样令人振奋,但其仍然是保持强劲创新能力的重要动力。事实上,为复杂的机器学习系统开发出全面且优雅的解决方案是一项极具现实意义的工作。
信息隐藏与变化封装
……机器学习模型是一种创造复杂纠缠状态的机器,且不可能有效地对其改进工作进行隔离分配。作为证明,假设旁谝惶啄P偷敝惺褂锰卣 x1,……Xn,那么如果我们改变 x1 中的值输入分布,那么其重要性、权重或者剩余特征可能全部发生改变——无论模型是以批处理形式进行完全重新训练,还是以在线方式进行逐步适应,情况都是如此。而添加新特征 xn+1 或者删除任何特征 xj,也都可能导致类似的变化。没有任何输入内容是真正独立存在的。牧羊这将其称为 CACE 原则:即任何改变都将改变一切。
如果我们了解到先验的重要性,就不需要利用机器学习进行重复证明!因此,模型有点像一台巨大的搅拌机器,我们向其中投入大量信息并获取结果,但对输入内容中的各类变化的敏感度却难以预测,且几乎无法进行影响隔离。面对这样的难题,我们该怎么办?虽然不存在百试百灵的方案,但作者给出了三种可能有所帮助的策略。
- 你可以隔离模型,转而提供总体结论。不过,纠缠问题仍然存在于每一个模型当中。此外,“在规模化情况下,这种策略可能很难长期维持”。
- 制定方法以深入理解模型预测的行为。你需要通过投资令模型不再像黑匣子般难以捉摸——例如为其配备更多可视化工具。此外,我还和多家企业进行过交流,其中一部分公司表示其能够解释机器学习模型作出的决定——甚至包括监管要求,而这种能力亦是其商业模式中的重要组成部分。因此,请大家认真考虑这方面需求与实现途径。
- 使用更为复杂的正则化方法,以便在训练当中使用的目标函数能够反映出任何效能预测成本的上升迹象。“这种方法可能有用,但也只是可能。此外,其也许会增加系统复杂性进而带来更多债务,这反而背离了我们减少纠缠度的初衷。”
另一种寻找偶然耦合的方式在于建立隐藏反馈回路,这一点在未申报消费方当中体现得尤为明显。通过未申报消费方,系统只是在单纯消费建模产出的输出结果,而我们几乎意识不到这些过程的存在。如果其根据影响模型的输入参数信息采取了某些行动,则这种隐藏的反馈回路很容易引发以下问题:
想象一下,在我们的新闻标题点击率预测系统当中,系统中的某一组件负责以“智能化方式”确定用于标题的字体大小。如果此字体大小模块开始将点击率作为输入信号使用,且字体大小确实会影响用户的点击倾向,那么字体大小当中将会包含一个由点击率添加的新的隐藏反馈回路。可以想象,这样一套系统会逐渐无休止地增加所有标题字号情况。
数据依赖性问题
……尽管代码依赖性可以通过静态分析以及链接图等方法相对轻松地进行识别,但具备数据依赖性处理功能的分析工具却很少见。因此,我们可以难以构建起能够解决大型数据依赖链的方案。
举例来说,某些输入信号会随着时间推移而改变行为。遵循 CACE 原则,即使将这些变化作为改进方向,也很难对后果作出预测。另一种数据依赖性则是模型中的特征集合,其中一些能够在准确性方面提供非常有限的增量。我们可以通过多种方式利用原本未充分利用的依赖性——包括一部分已遭弃用的早期遗留特征,一次性添加的多项特征组合(而非仅仅挑出那些真正有作用的特征),或者那些为了追求准确率而添加、且无法证明自身对复杂性的影响的特征。定期对特征进行清除将非常重要。
举例来说,假设在团队合并之后,为了简化而进行了一轮由旧产品编号到新产品编号的转换,那么两套方案都将在系统中作为特征。其中新产品只能获得一个新编号,但旧产品可能同时拥有两个编号。机器学习算法当然也会将旧编号纳入依赖关系当中。一年之后,有管理人员清理了使用旧编号填充的数据库代码。这种变化不会被回归测试所检测到,因为清理之后旧编号直接不再使用。这对机器学习系统的维护者而言显然不是什么好消息……
有能力理解数据依赖性的工具,将帮助我们顺利搞定特征清理工作。谷歌公司的一支团队就构建出一款自动化特征管理工具:
自从采用以来,该方案帮助谷歌团队每个季度安全删除数千行与特征相关的代码,同时自动验证其中的版本及其它问题。该系统还能够有效防止意外使用新模型中不推荐或已经损坏的特征。
最后一种数据依赖性管理方法,在于建立“校正”机制以重新利用现有模型。通过这种方式,你能够快速获得初步成果 ; 但在另一方面,你未来对该模型的分析与改进将面临更高的成本。
其它 95%(胶水代码与配置)
令人惊讶的是,学术界意识到在大多数机器学习系统当中,只有很小一部分代码在实际进行“机器学习”。事实上,一套成熟的系统最终可能最多只有 5% 的代码负责机器学习,而其余 95% 甚至更多代码只是起到粘合作用,从而通过重新实现(而非重新使用)改善原本笨拙的 API……
这里需要解决的问题在于,很多机器学习库都被封装成了独立的工件,这无疑会引入大量胶水代码(例如从 Java 转换至 R 或者 matlab)。如果大家无法在更为广泛的系统架构内找到适合自己的资源选项,那么重新实现算法(5% 部分的代码)可能更有意义,且能够有效减少胶水代码的数量。
一大相关问题在于管道丛林——即过于复杂的数据准备管道。
管道丛林问题只能通过全面审视数据收集与特征提取的方式来避免。清除管道丛林并从头开始设计清理方法,确实是工程设计层面的一项重大投资,但这同时也能够显著降低持续成本并加速进一步创新活动。
一旦系统因胶水代码与管道丛林问题而变得僵化,很多朋友会忍不住调整生产代码中的实验代码路径以执行额外实验。这样做当然比较方便,但一旦频率过高,其只会引发更大的混乱。
作为典型实例,谷歌公司最近在对一套重要的机器学习系统进行清理时,发现其中存在着数以万计的未使用实验性代码行。在利用更紧密的 API 进行重写之后,这部分“遗产”能够大幅降低工作量、生产风险并控制系统复杂性,从而为新算法的实验铺平道路。
在本节的最后,“配置往往是现实世界的混乱对美丽算法造成干扰的载体:”
请考虑以下例子。特征 A 在 9 月 14 日到 9 月 17 日之间发生了记录错误。特征 B 直到 10 月 7 日才正式上线。由于记录格式发生了变化,用于计算特征 C 的代码必须对 11 月 1 日之前及之后的数据进行更改。特征 D 并未用于生产,因此在现场调协中进行模型查询时,必须使用替代性的 D’与 D”。如果特征 Z 被使用,那么所有训练相关任务必须获得额外的内存配额,否则其训练效率将显著降低。最后,由于延迟限制,特征 Q 排除掉了特征 R。所有这些混乱的条件使得配置难以正确修改且难以推理。此外,配置错误还可能引发高昂的代价——包括严重的时间浪费、计算资源损耗或者生产问题。
配置变更应该与代码变更一样得到谨慎处理,并交由同行进行评审。
世界还将带来怎样的变化?
经验表明,外部世界很少保持稳定。事实上,真实世界的性质变化正是机器学习当中技术债务的一大重要来源。
请不要手动设置决策阈值(例如显示或不显示广告),而应考虑通过评估现有验证数据以发现阈值此外,因果不明的相关特征也可能引发问题:
这似乎并不是什么主要问题:如果两个特征总是相关,但只有其中一个特征属于真正的因果关系,那么似乎仍可以将信用归于两者并通过观察其共同现象得出结论。然而,如果外部世界中这两种特征的共生性突然消失,那么预测行为可能发生显著变化。用于区分相关效应的全面机器学习策略也将超出我们的讨论范围 ;[Bottou 2013] 就此给出了一些极好的建议与参考。结合本文的关注点,我们注意到非因果关系属于隐藏债务的另一种来源。