如何编写简洁的机器学习流水线

2020-01-18  本文已影响0人  unbuilt

译自:How to Code Neat Machine Learning Pipelines
说明:翻译只是为了加强思考,请阅读原文或者对照阅读。

编写机器学习流水线,此乃正解。

你是否曾经编写过这样的机器学习流水线:它运行时间非常很长?或者更悲剧的,你是否到过这样的节点:你需要把中间结果保存在磁盘上以便使用检查点一次把精力集中在一个步骤上?或者更悲惨的,你是否尝试重构写得不好的机器学习代码以便把它放到线上环境,而这花费了你数月时间?好吧,我们都在搭建机器学习流水线上工作过很长时间。我们应该如何构建一个流水线,既有一定的自由度,又可以在日后轻松重构为线上代码呢?

首先,我们将定义机器学习流水线,并探索在流水线步骤间使用检查点的想法。接着,我们来看一下如何实现这样的检查点,以免在把代码放到线上时弄得搬起石头砸到自己的脚。我们也讨论了数据流,以及在流水线中选择超参数时采用面向对象编程封装的权衡。

什么的是流水线

流水线就是一系列用于转换数据的步骤。它来自于古老的“管道和过滤器”设计模式(举例来说,你可以认为它是Unix的命令中的管道符“|”和重定向操作符“>”)。只是,流水线是代码中的对象。所以,每个过滤器有一个类(或者说是每个流水线步骤),然后另外一个类把这些步骤组合最终的流水线。有些流水线会串行的或者并行的包含其他流水线,拥有多个输入或输出,等等。我把机器学习流水线视作:

流水线的方法

流水线(或者流水线中的步骤)必须有下面的两个方法

注意:如果流水线中的一个步骤不需要这里面的某个方法,它可以从NonFittableMixin或者NonTransformableMixin继承得到这个方法的默认实现,当然默认实现并不做什么事。

很可能,流水线或者它们的步骤会定义以下可选方法

为了管理超参数,下面这些方法是默认提供的

重训练流水线、小批量以及在线学习

对于像用于训练深度神经网络(DNN)的小批量算法,或者像强化学习(RL)算法中的在线学习算法,最理想的是,流水线或者流水线步骤能够通过一个接一个链接多次对于fit的调用来更新他们自己,在现场在小批量上进行重训练。一些流水线和流水线步骤支持这样做,但是,有些步骤当再次被调用到fit就会重置自己。这取决于你是怎样为你的流水线步骤编码的。理想的情况是你的流水线步骤只在调用teardown方法时进行重置,在下次训练前再次setup,而在每次训练之间不要重置,在转换期间也不要重置。

在流水线中使用检查点

在流水线中使用检查点是个好想法 - 直到你需要重用这些代码做其他事或者变更数据。如果你在代吗中没有使用合理的抽象,你可能会搬起石头砸了自己的脚。

在流水线中使用检查点的好处

在流水线中使用检查点的坏处

计算机科学中有两个难题:缓存失效和起名字。 --- Phil Karlton

流水线中合理管理状态和缓存的建议

程序框架和设计模式会强制一些设计原则,以希望通过这样简单的方式来替你管理一些事情,以免你自己犯错或者写出一堆乱糟糟的代码。这里是我对于流水线和状态管理的建议:

这应该由一个单独的函数库管理。

为什么?

为什么流水线步骤不应该管理它们的检查点数据输出?下面这些原因会告诉你应该使用函数库或者框架而不是自己做:

这很酷。使用合适的抽象,你现在能够编写这样的流水线,在超参数调参时,它可以通过缓存每次实验的中间结果,当超参数不变时跳过流水线步骤,从而大大提速。不仅如此,一旦你做好准备把代码放到线上,你现在可以很容易的完全关掉缓存,而无须花一个月时间去重构代码。避免撞墙。

关于用于竞赛的代码

很常见的,从Kaggle竞赛来的代码缺少正确的抽象用于把流水线部署到生产环境。这有其合理的原因,参加竞赛者没有为把代码部署到生产环境而作准备的动机,他们唯一的需求是获得最好的结果去赢得竞赛。大多数为发论文而研究的代码也是这样的,通常代码编写者追求的是打败基线指标。也有时候,机器学习程序员在深入机器学习之前没有学过如何恰当的编写代码,对于他们以及他们的雇用者,这会使事情变得困难。

这里有一些坏模式的例子:

这些坏模式不仅仅出现在编程比赛的代码中(比如这里是我匆忙中中写的---当然,在身负重压时我还可能这样做)。这里是一些使用磁盘设置检查点的代码,以及我的一点分析:

有时候,公司可能从Kaggle代码中得到启发,我建议在产品最终上线版中自己编写流水线,使用这些比赛代码是有风险的。有一种说法是,公司不应使用比赛代码,甚至也不应雇用比赛获奖的人,因为他们会写糟糕的代码。

我不会这么极端(因为我自己在大多数时间也在编码比赛中赚积分),倒不是说比赛代码的目的只是获胜而不考虑将来使用。出乎意料的是,在那个时候去读Clean CodeClean Coder是很重要的。使用好的流水线抽象能够帮助机器学习项目存活。

这里是当你要构建准备在产品线上使用的机器学习流水线时想要的东西:

机器学习流水线中的数据流

在并行处理理论中,流水线是一种流式处理数据的方法,这样流水线步骤就能并行运算。洗衣房的例子很好的展现了这个问题及其解决办法。举例来说,一个流式流水线的第二步能够处理第一步的部分结果,同时第一步还在处理更多数据,而不用第一步处理完全部数据第二步再开始。我们把这些特别的流水线称为流式流水线(参见 streaming 101, streaming 102)。

不要误解,scikit-learn流水线是很好用的。但是,它不允许流式处理数据。不仅仅是scikit-learn,大多数机器学习流水线程序库没有使用流式数据,尽管他们可以使用。整个Python生态系统有线程问题。在多数流水线程序库中,每一步是完全阻塞的,必须一次处理完所有数据。只有少数库允许流式处理。

使用流式数据处理很简单,只要使用StreamingPipeline类而不是Pipeline类来连接流水线步骤,并且在步骤之间提供一个小批量的大小和队列大小(避免占用太多内存,这会使线上环境更加稳定)。这也需要使用在生产者-消费者问题中描述的带信号量的多线程队列来在流水线步骤之间传递信息。

Neuraxle比scikit-learn做的好的一件事是拥有序列化的流水线,可以通过MiniBatchSequentialPipeline类来使用。它还没有线程化,但在我们的计划当中。至少,我们已经可以在训练或者处理时可以在收集结果前传递小批量数据到流水线,这允许大的流水线用小批量数据使用像scikit-learn一样的流水线。再加上我们另外的功能,例如超参数空间、初始化方法、自动机器学习等等。

我们对Python中并行数据流的解决方案

不仅如此,在Python使用每个对象线程化的方法会使它们可序列化和重新加载。在Neuraxle,我们很快就会实现它们。这样就可以动态发送代码到远程工作进程中去执行。

对封装的权衡

在多数机器学习流水线库中,还有一件事仍在困扰着我们。这就是如何处置超参数。拿scikit-learn做例子,超参数空间(例如超参数值的统计分布)往往需要在流水线之外在步骤与步骤之间或者流水线与流水线之间用带下划线方式指定。正如在scipy分布中定义出的,随机搜索和网格搜索能够搜索超参数网格和超参数概率空间,scikit-learn并没有为每个分类器和转换器提供默认超参数空间。这理应是流水线中每个对象的职责。这样的话,一个对象就是自包含的,也包含它的超参数,这不会破坏面向对象编程(OOP)中SOLID原则中的单一职责原则(SRP)开放关闭原则(OCP)。使用Neuraxle是不破坏这些OOP原则的一个好办法。

兼容性和集成

在编写机器学习流水线时,最好能想着让流水线兼容尽量多的东西。Neuraxle就兼容scikit-learnTensorFlowKerasPyTorch,以及许多其他机器和深度学习库。

例如,neuraxle有一个方法叫作.tosklearn(),它允许流水线步骤或者整个流水线变成一个scikit-learn的BaseEstimator,这是一个基本的scikit-learn对象。对于其他机器学习库,也很简单,只要创建一个新的类,让它继承自Neuraxle的BaseStep,并至少重写你自己的 fit、transform,也许还有setup和teardown方法,再定义一个保存器来保存和加载你的模型。可以阅读BaseStep的文档学习怎么做,也可以阅读文档中相关的Neuraxle样例。

结论

总结一下,编写产品级别的机器学习流水线需要很多质量标准,如果在代码中使用好的设计模型和好的结构,这些问题很可能都会解决。总的来说:

上一篇 下一篇

猜你喜欢

热点阅读