*本文8千多字,约需要16分钟阅读时间。
机器学习作为时下最为火热的技术之一受到了广泛的关注。我们每天打开公众号都能收到各种前沿进展、论文解读、最新教程的推送。这些文章中绝大多数内容都跟酷炫的新模型、高大上的数学推导有关。但是Peter Norvig说过,“We don’t have better algorithms. We just have more data.”。在实际机器学习应用中,对最终结果起到决定性作用的往往是精心收集处理的高质量数据。
从表面看好像也不难,但略微深究就会发现机器学习系统与传统的软件工程项目有着非常大的差异。除了广受瞩目的模型算法,良好的工程化思考与实现是最终达到机器学习项目成功的另一大关键因素。
谷歌在2015年发表的论文《Hidden Technical Debt in Machine Learning Systems》中就很好的总结了机器学习工程中的各种不佳实践导致的技术债问题。主要有以下几种:
系统边界模糊
在传统的软件工程中,一般会进行细致的设计和抽象,对于系统的各个组成部分进行良好的模块划分,这样整个系统的演进和维护都会处于一个比较可控的状态。但机器学习系统天然就与数据存在一定程度的耦合,加上天然的交互式、实验性开发方式,很容易就会把数据清洗、特征工程、模型训练等模块耦合在一起,牵一发而动全身,导致后续添加新特征,做不同的实验验证都会变得越来越慢,越来越困难。
数据依赖难以管理
传统的软件工程开发中,可以很方便的通过编译器,静态分析等手段获取到代码中的各种依赖关系,快速发现不合理的耦合设计,然后借助于单元测试等手段快速重构改进。在机器学习系统中这类代码耦合分析同样不可或缺。除此之外还多了数据依赖问题。
比如销售预测系统可能会对接终端POS系统数据,也会引入市场部门的营销数据,还有仓储、运输等等多种数据来源。在大多数情况下这些数据源都是不同部门维护的,不受数据算法团队的控制,指不定哪天就悄悄做了一个变更。如果变更很大,可能在做数据处理或者模型训练时会直接抛出错误,但大多数情况下你的系统还是能正常运行,而得到的训练预测结果很可能就有问题了。
在一些复杂业务系统中,这些数据本身还会被加工成各种中间数据集,同时被几个数据分析预测任务共享,形成复杂的依赖关系网,进一步加大了数据管理的难度。
机器学习系统的反模式
胶水代码:随着各种开源项目的百花齐放,很多机器学习项目都会调用各种开源库来做数据处理、模型训练、参数调优等环节。于是自然而然在整个项目中大量的代码都是为了把这些不同的开源库粘合在一起的胶水代码,同样会导致之前提到过的边界模糊,与特定的库紧耦合,难以替换模块快速演进等问题。
流水线丛林:在数据处理特征工程与模型调优的迭代演进过程中,稍不注意你的整个系统流水线就会变得无比冗长,各种中间结果的写入和前后依赖极其复杂。这时候想添加一个新特征,或是调试某个执行失败都变得如此困难,逐渐迷失在这混乱的丛林中……如果只具备机器学习知识而缺少工程经验,用这种做实验的方式来开发系统显然是不靠谱的,必须有良好的工程化思维,从总体上把控代码模块结构,才能更好的平衡实验的灵活性与系统开发效率,保证整体的高效运作。
**失效的实验性代码路径:**这一点也是承接前面,很多时候如果以跑实验的心态来给系统“添砖加瓦”,很可能到后面各种小径交叉的代码库就这么出现了,谁都搞不清楚哪些代码有用哪些是不会执行到的。如何复现别人的实验结果,要用哪些数据集和特征,设置哪些变量、做哪些微调都会成为难解之谜。
缺乏好的系统抽象:个人觉得sklearn的各种API设计还算蛮好的,现在很多其它库的高层API都参考了这个准业界标准来实现。文中主要提到在分布式训练中缺乏一个很好的业界标准,比如MapReduce显然是不行的,Parameter Server看起来还算靠谱但也还未成为标准。没有好的抽象标准也就导致了各种库在功能、接口设计时不一致,从而有了以上提到的一系列边界模糊,胶水代码等问题。
配置项技术债
相对于传统软件系统,机器学习系统的配置项往往会更多更复杂。比如要使用哪些特征、各种数据选择的规则、复杂的预处理和后置处理、模型本身的各种参数设置等等。因此除了工程代码外,配置项的精心设计、评审也成了一个不容忽视的点。否则很容易造成系统在实际运行中频繁出错,难以使用。
变化无常的外部世界
机器学习系统很多时候都是直接与外部世界的数据做交互,而外部世界总是变化无常。而且机器学习系统本身的输出也会影响到外部世界,从而进一步回馈到机器学习系统的输入中来。比如推荐系统给用户展示的内容会影响用户的点击行为,而这些点击浏览行为又会成为训练数据输入到