机器学习系统中隐藏的技术负债

  |  

摘要: 机器学习系统中的技术负债

【对数据分析、人工智能、金融科技、风控服务感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:潮汐朝夕
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


本文是谷歌在 NIPS 2015 的一篇文章,虽然年代比较久远了,但今天回看这篇文章依然很有意义。

作者从软件工程角度看机器学习系统,发现不少隐藏的债务问题,并给出了一些解决方案。

$1 Introduction

什么是技术负债

文章标题中提到了技术负债,技术负债也称为设计负债(design debt)、代码负债(code debt),是软件工程的概念。
指开发人员为了加速软件开发,在应该采用最佳方案时进行了妥协,改用了短期内能加速软件开发的方案,从而在未来给自己带来的额外开发负担。这种技术上的选择,就像一笔债务一样,虽然眼前看起来可以得到好处,但必须在未来偿还。软件工程师必须付出额外的时间和精力持续修复之前的妥协所造成的问题及副作用,或是进行重构,把架构改善为最佳实现方式。

偿还债务的方式

  • 代码重构
  • 增强单元测试
  • 删除无效代码
  • 降低依赖
  • 紧缩APIs
  • 改进文档

为什么要偿还技术负债

偿还技术负债并不能增加系统的功能,为什么还要这么吃力不讨好呢。因为偿还技术负债之后,偿还债务使得未来的改进或改变成为可能,增强可维护性。

如果逾期不偿还,则债务会越滚越大。(当你欠钱的时候,复利是很可怕的)

关于机器学习系统的技术负债,作者将话题分成了几个部分。

  1. Complex Models Erode Boundaries, 复杂模型侵蚀抽象边界
  2. Data Dependencies Cost More than Code Dependencies, 数据依赖比代码依赖更昂贵
  3. Feedback Loops, 反馈回路
  4. ML-System Anti-Patterns, 机器学习系统的反面教材
  5. Configuration Debt, 配置债务
  6. Dealing with Changes in the External World, 外部世界的改变
  7. Other Areas of ML-related Debt, 其它领域
  8. Conclusions: Measuring Debt and Paying it Off, 总结

$2. 复杂模型侵蚀抽象边界

传统软件开发可以通过封装和模块化设计,形成很强的抽象边界,形成可维护的代码,模块内部可以独立地改变和提升而不影响模块间的交互。

比如 A 和 B 两个类,B 需要调用 A 中的方法,即 B 依赖 A,只要 A 暴露给 B 的 API 不改变,那么 A 和 B 就可以各自独立地进行优化,这就是抽象边界的作用。

然而不幸的是,机器学习系统中很难找到抽象边界。找不到一个好的方式用传统的软件工程的逻辑来描述机器学习系统的期望行为。

这种边界的被侵蚀会显著增加机器学习系统的技术负债。

$2-1 Entanglement,信号纠缠

信号的纠缠,例如一个模型使用特征 x1, x2, …, xn,对应权重 w1, w2, …, wn,如果改变特征 x1 的输入分布,不管是否重新训练模型,剩下的 n-1 个特征的重要性,权重都会改变。

增加和减少特征也会有相似的影响,所以没有输入是真正独立的。

作者称这种现象为 Change Anything, Change Everything。即 CACE 而 CACE 这种牵一发而动全身的现象不止出现在特征或输入信号上,还有超参数,学习设置,采样方法,数据选择,收敛阈值等任何可能的调整。

一种可能的策略是集成学习,另一种可能的方法就是检测预测行为的变化。

$2-2 Correction Cascades, 矫正链

当我们有模型 modelA, 可以解决 a 问题,此时有一个相似的问题 b, 一种可能的方法是利用模型 modelA 的输出去学习 a 和 b 的误差矫正(correction),然而这种矫正就在 b 问题上引入了对模型 a 的依赖,使得未来对模型 modelA 的分析和改进十分昂贵。尤其是形成 Cascades 之后,还有可能会引入改进死锁(Improvement Deadlock):

整个 Cascades 上,任何一个模型的独立改进可能对整个系统来说都有副作用。缓解这个问题,只能增强模型 modelA,通过增加特征直接学习矫正(Correction),或者重新训练模型 modelA。

$2-3 神秘的消费者

模型的预测结果通常是广泛地可访问的,无论是在运行时,还是在日志中,之后都可能会被其他系统使用到,如果没有访问控制的话,就有可能出现神秘的消费者,

悄悄地使用这个模型的输出作为其它系统的输入,有时候这个神秘的消费者甚至不知道这是一个机器学习系统,它只是知道日志中有这么一行数据可以作为输入信号拿来使用。

在传统的软件工程中,称为可见性债务。这种可见性债务,软件工程理论认为在最好的情况下,它是昂贵的,而在最坏的情况下,它甚至是危险的。因为它对这个模型创建了一个隐式的紧耦合,对该模型的任何改变都可能影响到其它系统。有的时候,这个神秘的消费者还会创建隐式的反馈回路,参考 $4。

神秘的消费者在实际系统中很难检测,除非系统设置严格的访问权限。

$3 数据依赖

在经典软件工程中,依赖债务是代码复杂度和技术负债的主要来源,在机器学习系统中,数据依赖对技术负债的贡献相比代码以来有过之无不及。

在业务中非常容易构建大型的数据依赖链。对于代码依赖,我们有编译器,链接器可以进行静态分析,而数据依赖,我们缺乏对应的工具,这使得数据依赖非常难以检测。

$3-1 不稳定的数据依赖

为了加快研发速度,我们倾向于用其它组的信号作为特征,然而这些信号是不稳定的,它们随时可能发生改变(量变甚至质变),可能是隐式的,例如用于计算 TF/IDF 的有数据以来的查询表,也可能是显式的,比如其它组突然切断了访问权限。任何一个信号的改变都可能对系统带来位置的影响。一个解决方法是对该信号创建副本,然而这也是有代价的,比如

过时的特征,比如版本管理的成本。

$3-2 未充分利用的数据依赖

在传统软件工程中,代码中未充分利用的依赖指不需要的软件包。在机器学习系统中,未充分利用的数据依赖指那些对模型提升非常小的输入信号,我们应该移除它们而不用担心它们的改变对系统的影响。

  • 历史遗留特征:因缺少检测工具,冗余的旧特征依然存在在系统里
  • 打包特征:由于时间紧迫,一组特征被打包加入到系统里,这其中有一部分特征对模型有比较好的正向效果,然而有一些对模型的影响微乎其微,这些特征应该被移除。
  • 研究员特征:一些由机器学习研究员加入的特征,对系统复杂度的提升远大于对模型的提升,这些特征也是没必要的
  • 相关的特征:有时两个特征十分相关,机器学习系统很难检测到这种特征相关性,从而带来冗余特征。

可能的解决办法:每次移除一个特征然后进行评估,判断这个特征是否对模型有贡献。

$4 反馈回路

  • 直接反馈回路:一个模型可以直接影响到未来的训练数据的选取,不如部分训练数据是通过当前版本模型在其它数据集上挖掘所得
  • 隐式反馈回路:两个或者两个以上的模型通过外部世界互相影响对方

$5 机器学习系统的反面教材

在机器学习系统中,真正用于学习和预测的代码仅占很小比重,如图。常见的系统设计反面教材。

  1. 胶水代码:从各个渠道获取通用软件包,然后用胶水语言将它们整合到一起,通常是 Python。就会出现大量支持代码,这些大量的支持代码使得替换这些软件包变得不可行。

一个解决方案是用通用 API 去包装这些黑盒软件包。

  1. 管道丛林:胶水代码的特例,经常出现在数据准备中,数据过滤,组合,采样等,这其中伴随着中间文件的输出。为例得到不同的训练数据,不同的管道被创建,伴随着整块代码的复制粘贴,这些管道的管理,错误检查,从失败中恢复都是困难的。为了测试这些管道有时还要运行昂贵的集成测试。

摆脱这种管道丛林的唯一方式就是从整体上思考数据集和特征,并从底层开始重新设计。

  1. 无效的实验代码

$6 配置债务

任何大型机器学习系统都有大量可配置项:使用哪些特征,学习参数,预处理,后处理,验证方式。

每行配置都有可能是潜在的错误,比如两个配置是相关的,只改了其中一个而没改另一个会导致系统异常。

作者总结了一套原则用于创建良好的配置系统:能够通过简单修改上个版本的配置文件来获得新的配置文件,并增加自动验证机制,使得手动错误变得不可能。可视化对比配置文件,配置文件的版本控制。

$7 和外部世界打交道

机器学习系统可以直接和外部世界交互,然而外部世界是不稳定的,这大大增加了机器学习系统的维护成本。

动态系统中的固定阈值

这个阈值通常是人工选取的,如果训练数据发生变化,阈值将不再适用,必须重新选取。然而对上千个模型手工选阈值显然不可行,一个可行的方案:预留验证数据进行模型评估,自动确定阈值

监控和测试

单元测试和端到端测试虽然有价值,但是无法应对改变中的世界。复杂的实时系统的行为监控+自动应对长期的系统可靠度是很关键的。但这里有个核心问题:监控什么,作者给出了几个切入点:

  1. 预测偏差:预测的标签分布与观察到的标签分布出现分歧,可能是外部世界发生剧烈变化
  2. 行动极限:比如通常每天有 100 个标记为坏,今天突然标记了 1000,一种可能是外部世界发生变化,也可能是上游生产者出现问题(机器学习的很多信号是其它模型产生的,称为上有系统),必须紧密监控上游系统,确保上有系统的警告可以传播到所有的下游消费者,当出现异常时可以快速确定是自己系统的问题,还是上游的问题。

$8 其它领域

  1. 数据测试债务:机器学习系统中,数据取代了代码,类似代码的测试,数据也是要测试的
  2. 可复现债务:能重现实验并得到相似的结果是十分重要的
  3. 过程管理债务:本文到这里之前都在将如何管理一个模型,但是一个成熟的机器学习系统有成百上千个模型,如何管理,包括配置文件更新,资源分配,数据可视化,灾难恢复等待
  4. 文化债务:机器学习科学家和机器学习工程师有一条看不见的鸿沟,应该构建良好的团队文化,奖励那些减少技术负债的工程师。

结论:评估债务并开始偿还它

如何评估系统中的技术负债呢,一个方法是看你的团队是否还能快速移动(Moving fast)。如果可以,说明你们团队在当前的债务水平还比较低,或者遵循了良好的实践。

但快速移动通常也伴随着债务积累,这是需要考虑下列问题。

  1. 以全尺寸测试一个全新的算法的难易度是多少
  2. 所有数据依赖的传递闭包(Transitive Closure)是什么
  3. 可以多精确地测量一个新的改变对系统的影响
  4. 一个信号或模型的改进是否会降低其它模型的性能
  5. 一个新成员需要多久可以开始全速工作

评估债务并偿还,往往需要依靠团队文化的转变来实现,这对机器学习团队的长期健康是很重要的。以上就是文章全部内容。


Share