谈谈机器学习模型的部署

随着机器学习的广泛应用,如何高效的把训练好的机器学习的模型部署到生产环境,正在被越来越多的工具所支持。我们今天就来看一看不同的工具是如何解决这个问题的。

上图的过程是一个数据科学项目所要经历的典型的过程。从数据采集开始,经历数据分析,数据变形,数据验证,数据拆分,训练,模型创建,模型验证,大规模训练,模型发布,到提供服务,监控和日志。诸多的机器学习工具如Scikt-Learn,Spark, Tensorflow, MXnet, PyTorch提供给数据科学家们不同的选择,同时也给模型的部署带来了不同的挑战。

我们先来简单的看一看机器学习的模型是如何部署,它又会遇到那些挑战。

模型持久化

模型部署一般就是把训练的模型持久化,然后运行服务器加载模型,并提供REST或其它形式的服务接口。我们以RandomForestClassification为例,看一下Sklearn,Spark和Tensorflow是如何持久化模型。

Sklearn

我们使用Iris数据集,利用RandomForestClassifier分类。


训练的代码如上。这里模型导出的代码在最后一句。joblib.dump(),参考这里。Sklearn的模型到处本质上是利用Python的Pickle机制。Python的函数进行序列化,也就是说把训练好的Transformer函数序列化并存为文件。

要加载模型也很简单,只要调用joblib.load()就好了。

Sklearn对Pickle做了一下封装和优化,但这并不能解决Pickle本身的一些限制,例如:

版本兼容问题,不同的Python,Pickle,Sklearn的版本,生成的序列化文件并不兼容

安全性问题,例如序列化的文件中被人注入恶意代码

扩展问题,你自己写了一个扩展类,无法序列化,或者你在Python中调用了C函数

模型的管理,如果我生成了不同版本的模型,该如何管理

Spark

Spark的Pipeline和Model都支持Save到文件,然后可以很方便的在另一个Context中加载。

训练的代码如下:

模型加载的代码如下:

调用model的toDebugString方法可以看到分类器的内部细节。


下图是Spark存储的Piple模型的目录结构:

我们可以看到,它包含了元数据Pipeline的五个阶段的数据,这里的文件都是二进制的数据,只有Spark自己可以加载。

Tensorflow

最后我们来看一下Tensorflow。Tensorflow提供了tf.train.Saver来导出他的模型到元图(MetaGraph)。

导出的模型会包含以下文件:

其中checkpoint是元数据,包含其它文件的路径信息。还包含了一个Pickle文件和其它几个checkpiont文件。可以看出,Tensorflow也利用了Python的Pickle机制来存储模型,并在这之外加入了额外的元数据。

模型加载的代码如下:

这里要注意的是,RandomForest不是tensforflow的核心包,所以在模型加载的时候必须tensorflow.contrib.tensor_forest.python.tensor_forest, 否则模型是无法成功加载的。因为不加载的话tensor_forest中定义的一些属性会缺失。

另外就是Tensorflow也可以存储计算图,调用tf.train.write_graph()方法可以把图定义存储下来。当然也可以在TesnsorBoard中展示该图。

好了,我们看到,Sklearn,Spark和Tensorflow都提供了自己的模型持久化的方法,那么简单来说,只要使用一个web服务器例如Flask,加一些模型加载和管理的方法,然后暴露REST API就可以提供预测服务了,是不是很简单呢?

其实要在生产环境下提供服务,还需要面对很多其它的挑战,例如:

在云上如何扩展和伸缩

如何进行性能调优

如何管理模型的版本

安全性

如何持续集成和持续部署

如何支持AB测试

为了解决模型部署的挑战,不同的组织开发了一些开源的工具,例如:ClipperSeldonMFlowMLeapOracle GraphpipeMXnet model server 等等,我们就选其中几个看个究竟。

Clipper

Clipper是由UC BerkeleyRISE Lab开发的, 在用户应用和机器学习模型之间的一个提供预测服务的系统,通过解耦合用户应用和机器学习系统的方式,简化部署流程。

它有以下功能:

利用简单标准化的REST接口来简化机器学习系统的集成,支持主要的机器学习框架。

使用开发模型相同的库和环境简化模型部署

利用可适配的Batching,缓存等技术改善吞吐量

通过智能选择和合并模型来改善预测的准确率

Clipper的架构如下图:

Clipper使用了容器和微服务技术来构架架构。使用Redis来管理配置,Prometheus来进行监控。Clipper支持使用Kubernetes或者本地的Docker来管理容器。

Clipper支持以下几种模型:

纯Python函数

PyShark

PyTorch

Tensorflow

MXnet

自定义

Clipper模型部署的基本过程如下,大家可以参考我的这个notebook

创建Clipper集群(使用K8s或者本地Docker)

创建一个应用

训练模型

调用Clipper提供的模型部署方法部署模型,这里不同的工具需要调用不同的部署方法。部署时,会把训练好的Estimator利用CloudPickle之久化,本地构建一个容器镜像,部署到Docker或者K8s。

把模型和应用关联到一起,相当于发布模型。然后就可以调用对应的REST API来做预测了。

我试着把之前的三种工具的RomdomForest的例子用Clipper发布到我的Kubernetes集群,踩到了以下的坑坑:

我本地的Cloudpickle的版本太新,导致模型不能反序列化,参考这个Issue

Tensorflow在Pickle的时候失败,应该是调用了C的code

我的K8s运行在AWS上,我在K8S上使用内部IP失败,clipper连接一直在使用外部的域名,导致无法部署PySpark的模型。

总之,除了Sklearn成功部署之外,Tensorflow和Spark都失败了。

Seldon

Seldon是一家创办于伦敦的公司,致力于提供对于基于开源软件的机器学习系统的控制。Seldon Core是该公司开源的提供在Kubernetes上部署机器学习模型的工具。它拥有以下功能:

Python/Spark/H2O/R 的模型支持

REST API和gRPC接口

部署基于Model/Routers/Combiner/Transformers的图的微服务

利用K8S来提供扩展,安全性,监控等等DevOps的功能

Seldon的使用过程如上图,

首先在K8s上安装Seldon Core,Seldon利用ksonnet,以CRD的形式安装seldon core

利用S2i(s2i是openshift开源的一款工具,用于把代码构建成容器镜像),构建运行时模型容器,并注册到容器注册表

编写你的运行图,并提交到K8s来部署你的模型

Seldon支持基于四种基本单元,Model,Transformer, Router, Combiner来构建你的运行图,并按照该图在K8s创建对应的资源和实例,来获得AB测试,模型ensemble的功能。

例如下图的几个例子:

AB 测试

模型ensemble

复杂图

图模式是Seldon最大的亮点,可以训练不同的模型,然后利用图来组合出不同的运行时,非常方便。

笔者尝试在K8S上利用Seldon部署之前提到的三种工具生成的模型,都获得了成功(代码在这里)。这里分享一下遇到的几个问题:

Seldon支持Java的Python,然而用运行PySpark,这两个都需要,所以我不得不自己构建了一个镜像,手工在Python镜像上安装Java

因为使用CDR的原因,我没有找到有效改变容器的liveness和readiness的设置,因为Spark初始化模型在Hadoop上,加载模型需要时间,总是readiness超时导致容器无法正常启动,K8s不断的重启容器。所以我只好修改代码,让模型加载变成Lazy Load,但是这样第一次REST Call会比较耗时,但是容器和服务总算是能够正常启动。

MLflow

MLflow是Databricks开发的开源系统,用于管理机器学习的端到端的生命周期。

MLflow提供跟踪,项目管理和模型管理的功能。使用MLFlow来提供一个基于Sklearn的模型服务非常简单,

调用mlflow.sklearn.log_model(), MLflow创建以下的目录来管理模型:

我们看到在artifacts目录下有Python的pickle文件和另一个元数据文件,MLModel。

使用 mlflow sklearn serve -m model 就可以很方便的提供基于sklearn的模型服务了。

虽然MLFlow也号称支持Spark和Tensorflow,但是他们都是基于Python来做,我尝试使用,但是文档和例子比较少,所以没能成功。但原理上都是使用Pickle➕元数据的方式。大家有兴趣的可以尝试一下。

关于部署功能,MLFlow的一个亮点是和SagemakerAzureML的支持。

MLeap

MLeap的目标是提供一个在Spark和Sklearn之间可移植的模型格式,和运行引擎。它包含:

    基于JSON的序列化

    运行引擎

    Benchmark

MLeap的架构如下图:

这是一个使用MLeap导出Sklearn模型的例子:

导出的模型结构如下图所示:

这个是randonforest的模型json

我们可以看出MLeap把模型完全序列化成与代码无关的JSON文件,这样就可以在不同的运行时工具Spark/Sklearn之间做到可移植。

MLeap对模型提供服务,不需要依赖任何Sklearn或者Spark的代码。只要启动MLeap的Server,然后提交模型就好了。

下面的代码用Scala在Spark 上训练一个同样的Randonforest分类模型,并利用MLeap持久化模型。

导出的模型和之前的Sklearn具有相同的格式。

MLeap的问题在于要支持所有的算法,对于每一个算法都要实现对应的序列化,这也使得它的需要很多的开发来支持客户自定义的算法

总结

Seldon Core和K8S结合的很好,它提供的运行图的方式非常强大,它也是我实验中唯一一个能够成功部署Sklearn,Spark和Tensorflow三种模型的工具,非常推荐!

Clipper提供基于K8s和Docker的模型部署,它的模型版本管理做得不错,但是代码不太稳定,小问题不少,基于CloudPickle也有不少的限制,只能支持Python也是个问题。推荐给数据科学家有比较多的本地交互的情况。

MLFlow能够提供很方便的基于Python的模型服务,但是缺乏和容器的结合。但是它能够支持和Sagemaker,AzureML等云的支持。推荐给已经在使用这些云的玩家。

MLeap的特色是支持模型的可交互性,也就是说我可以把sklearn训练的模型导出在Spark上运行,这的功能很有吸引力,但是要支持全部的算法,它还有很长的路要走。关于机器学习模型标准化的问题,大家也可以关注PMML。现阶段各个工具对PMML的支持比较有限,随着深度学习的广泛应用,PMML何去何从还未可知。

下表是对以上几个工具的简单总结,供大家参考

 Model PersistentML ToolsKubernetest IntegrationVersionLicenseImplementation

Seldon CoreS2i + PickleTensorflow, SKlearn, Keras, R, H2O, Nodejs, PMMLYes0.3.2ApacheDocker + K8s CRD

ClipperPicklePython, PySpark, PyTorch, Tensorflow, MXnet, Customer ContainerYes0.3.0ApacheCPP / Python

MLFlowDirectory + MetadataPython, H2O, Kera, MLeap, PyTorch, Sklearn, Spark, Tensorflow, RNoAlphaApachePython

MLeap JSONSpark,Sklearn, TensorflowNo0.12.0ApacheScala/Java

推荐阅读更多精彩内容