如何编写整洁的机器学习管道

哈Ha



机器学习的流水线化和并行化主题已经存在很长时间了。特别是,我想知道一本专注于Python的专业书籍是否足以满足此需要,或者是否需要更多概述和可能的复杂文献。我们决定翻译一篇有关机器学习管道的介绍性文章,涵盖体系结构和更多应用方面的考虑。让我们讨论一下此方向的搜索是否相关。





您是否曾经编写过花费很长时间才能运行的机器学习管道?或更糟糕的是:您是否已到达需要将管道的中间部分保存到磁盘以便可以依靠检查点一次研究管道的各个阶段的阶段?或更糟糕的是:您是否曾经尝试过将如此令人作呕的机器学习代码重构,然后再将其投入生产,并发现它花了几个月的时间?是的,长期从事机器学习流水线工作的任何人都必须解决这个问题。那么,为什么不建立一个良好的管道来给我们足够的灵活性,并能够轻松地重构代码以便以后交付生产呢?



首先,让我们定义一个机器学习管道并讨论在管道阶段之间使用断点的想法。然后,让我们看看如何实现这样的断点,以免在将管道投入生产时陷入困境。我们还将讨论数据流以及与在指定超参数时必须在管道中进行的与面向对象编程(OOP)封装相关的权衡。



什么是输送机?



输送带是数据转换中的一系列步骤。它是根据旧的管道和过滤器设计模式创建的(例如,请记住带有“ |”管道或“>”重定向运算符的unix bash命令)。但是,管道是代码中的对象。因此,您可以为每个过滤器(即管道中的每个阶段)都有一个类,以及一个用于将所有这些阶段组合到完成的管道中的另一个类。一些管线可以将其他管线串联或并联组合,具有许多输入或输出等。将机器学习管道认为是方便的:



  • 通道和过滤器管道处理数据的各个阶段以及这些阶段管理其内部状态,这些状态可以从数据中学习。
  • . ; , . , – .
  • (DAG). , . : , , , , (, fit_transform ), , ( RNN). , , .








输送机方法输送机(或管道阶段)必须具有以下两种方法:



  • 适合”用于训练数据并获取状态(例如,这种状态是神经网络的权重)
  • 用于实际处理数据并生成预测的转换”(或“预测”)。
  • 注意:如果管道阶段不需要这些方法之一,则该阶段可以从NonFittableMixin或NonTransformableMixin继承,它们将默认提供这些方法之一的实现,因此它什么也不做。




可以管道阶段中选择性地定义以下方法:



  • fit_transform” , , , .
  • Setup ”将在管道的每个阶段中调用“ setup”方法。例如,如果管道阶段包含TensorFlow,PyTorch或Keras神经网络,则这些阶段可以创建自己的神经图,并在拟合之前以“设置”方法注册与GPU一起使用。不建议在拟合之前直接在阶段构造函数中创建图;有几个原因。例如,在开始之前,作为自动机器学习算法的一部分,可以使用不同的超参数多次复制这些步骤,该算法为您搜索最佳的超参数。
  • 拆解”,此方法在功能上与“设置”相反:它拆解资源。




默认情况下提供了以下方法,它们提供了超参数控制:







管道重新拟合,迷你批处理和在线学习



对于使用迷你批处理的算法(例如,训练深度神经网络(DNN))或在线学习的算法(例如,强化学习(RL)),适用于管道或其阶段最好将多个呼叫链接在一起,以使它们准确地接一个接一个地进行,并在运行中将它们调整为迷你批次的大小。在某些管道中以及在管道的某些阶段中都支持此功能,但是由于将再次调用“ fit”方法,因此在某些阶段可能会重置已实现的拟合。这完全取决于您如何对流水线阶段进行编程。理想情况下,仅应在调用“ teardown”方法然后调用“setup”直到下一次试穿为止,并且两次试穿之间或转换期间均未刷新数据。



在传送器



中使用检查点好的做法是在管道中使用断点,直到您需要将此代码用于其他目的并更改数据为止。如果您未在代码中使用正确的抽象,则可能是脚踏实地。



在管道中使用检查点的利弊:



  • 如果编程和调试步骤在管道的中间或结尾,则断点可以加快工作流程。这样就无需每次都重新计算管道的第一阶段。
  • ( , ), , . , , – . , , , , , .
  • 也许您的计算资源有限,并且唯一可行的选择是一次在可用硬件上运行一个步骤。您可以使用一个断点,然后在其后添加一些其他步骤,然后,如果要重新执行整个结构,将从您中断的地方开始使用数据。




在管道中使用断点的缺点:



  • 这会使用磁盘,因此如果操作错误,代码可能会变慢。为了加快速度,您至少可以使用RAM磁盘或将缓存文件夹安装到RAM。
  • 这会占用大量磁盘空间。使用安装到RAM的目录时,可能会占用大量RAM空间。
  • 磁盘上存储的状态更难管理:程序具有使代码运行更快所需的额外复杂性。请注意,从功能编程的角度来看,您的功能和代码将不再干净,因为您需要管理与磁盘使用相关的副作用。与管理磁盘状态(您的缓存)有关的副作用可能是各种奇怪错误的温床
...



众所周知,编程中一些最困难的错误是由缓存失效问题引起的。



在计算机科学中,只有两件非常棘手的事情:缓存无效和实体命名。-菲尔·卡尔顿




有关如何正确管理管道中的状态和缓存的建议。



众所周知,编程框架和设计模式可能是一个限制因素,原因很简单,它们控制着某些规则。希望这样做是为了使您的代码管理任务尽可能简单,从而避免自己犯错,并且代码不会变得混乱。这是我关于在管道和状态管理的上下文中进行设计的五分钱:



传送器阶段不得控制由以下人员发布的数据中的测试点设置




为了解决这个问题,必须使用特殊的流水线库来为您完成所有这些工作。



为什么?



为什么管道阶段不应该控制检查点在它们产生的数据中的放置?基于同样的理由,您在工作时使用库或框架,并且自己不要重现相应的功能:



  • 您将有一个简单的拨动开关,可以轻松地在部署到生产环境之前完全激活或停用断点。
  • , , , : , , . , .
  • / (I/O) . , . : , . ?
  • , , – . , , .
  • , , , , , , . , . .
  • , , (, , ) . , ( , ) . , , , , , , . , . , .




这很酷。有了正确的抽象,您现在就可以对机器学习管道进行编程,以大大加快超参数调整阶段。为此,您需要缓存每个测试的中间结果,并在中间管道阶段的超参数保持不变的情况下,一次又一次地跳过管道阶段。此外,当您准备将代码发布到生产环境中时,可以立即关闭整个缓存,而不必为此花整整一个月的时间来重构代码。



不要撞到这堵墙。



机器学习传送器



中的数据流并行处理理论指出,管道是一种数据流工具,可让您并行化管道阶段。洗衣的例子很好地说明了这个问题及其解决方案。例如,流水线的第二阶段可能开始处理流水线第一阶段的部分信息,而第一阶段继续计算新数据。而且,对于传送机的第二阶段来说,不需要第一阶段完全完成其处理所有数据的阶段。让我们将这些特殊的管道称为流式传输(请参阅此处此处)。



别误会,使用scikit-learn管道非常有趣。但是它们不适合流媒体播放。不仅是scikit-learn,而且大多数现有的流水线库在可能的时候都没有利用流功能。整个Python生态系统中都存在多线程问题。在大多数流水线库中,每个阶段都是完全阻塞的,需要立即转换所有数据。只有少数流媒体库可用。



激活流可以像使用类StreamingPipeline而不是使用类一样简单Pipeline链接各个阶段。同时,指示了迷你批处理的大小和队列的大小(为了避免过多的RAM消耗,这可以确保生产中更稳定的工作)。理想情况下,这种结构还需要多线程信号量队列,如供应商和消费者问题所述:组织信息从管道的一个阶段到另一阶段的传输。



在我们公司,Neuraxle已经比scikit-learn做得更好:它是关于可以使用MiniBatchSequentialPipeline类使用的顺序管道... 到目前为止,这还不是多线程的(但这已在计划中)。在收集结果之前,它至少已在拟合或转换过程中以迷你批的形式将数据传递到管道,这允许像scikit-learn中一样使用大型管线,但是这次使用的是迷你批处理,以及还有许多其他可能性,包括:超参数空间,安装方法,自动机器学习等。



我们的并行流Python解决方案



  • 可以连续多次调用拟合和/或变换方法,以改进与新的迷你批处理的拟合。
  • , -. , , .
  • , . , setup. , , . , TensorFlow, , , , C++, Python, GPU. joblib . .
  • , . , – , , , , .
  • . , , ; , . , , , , ( Joiner). , . , , , .




此外,我们要确保Python中的任何对象都可以在线程之间共享,以便可序列化和重新加载。在这种情况下,即使必要的代码本身不在该工作程序上,也可以在任何工作程序(可以是另一台计算机或进程)上动态发送代码以进行处理。这是通过使用特定于每个类的串行器链来完成的,这些序列化器体现了流水线阶段。默认情况下,每个步骤都有一个序列化程序,可让您处理常规的Python代码;对于更复杂的代码,请使用GPU并以其他语言导入代码。使用保存程序将模型简单地序列化然后重新装入工人。如果worker是本地的,则可以将对象序列化到RAM中的磁盘或RAM中安装的目录。



绝妙的折衷



在大多数用于流水线机器学习的库中,存在另一种烦人的事情。这是关于如何处理超参数的。以scikit-learn为例。通常需要在管道外部指定超参数空间(又称超参数值的统计分布),并在管道的各个阶段之间使用下划线作为分隔符。而随机搜索网格搜索允许您探索scipy分布中定义的超参数网格或超参数概率空间,scikit-learn本身不为每个分类器和转换器提供默认的超参数空间。可以将执行这些功能的职责分配给管道中的每个对象。因此,对象将是自给自足的,并将包含其自己的超参数。这不违反单一职责原则,打开/关闭原则和SOLID面向对象编程的原则。



兼容性和集成在对



机器学习管道进行编码时,切记它们必须与许多其他工具(尤其是scikit-learn)兼容是很有用的TensorFlowKerasPyTorch,和其他许多机器和深度学习库。

例如,我们编写了一种方法.tosklearn(),可以将流水线阶段或整个流水线转换BaseEstimator为scikit-learn库的基础对象。对于其他机器学习库,任务归结为编写一个继承自我们的类的新类,BaseStep并在特定代码中覆盖拟合和转换操作以及可能的设置和拆卸操作。您还需要定义一个将保存和加载模型的保护程序。这是该类文档BaseStep及其示例。



结论



总而言之,我们注意到准备投入生产的机器学习管道的代码必须满足许多质量标准,如果您坚持正确的设计模式并很好地构建代码,这是可以实现的。请注意以下几点:



  • 在机器学习代码中,使用管道并将管道的每个阶段定义为类的实例是有意义的。
  • 然后可以使用断点优化整个结构,以帮助找到最佳的超参数并在相同数据上重复执行代码(但可能使用不同的超参数或修改的源代码)。
  • , RAM. , .
  • , – BaseStep, , .



All Articles