原文链接:Apache Spark 2.0 Preview: Machine Learning Model Persistence
作者:Joseph Bradley
译者:郭芮(guorui@csdn.net)
简介
研究机器学习用例:
- 数据科学家建立了一个ML模型,并交给了一个工程团队在生产环境部署。
- 数据工程师将使用Python的模型训练工作流和Java模型服务工作流整合。
- 数据科学家专门设立岗位来训练后期需要被保存和评估的ML模型。
在所有的这些例子中,如果有了模型的持久性,那么保存和加载模型的问题将变得更容易解决。在即将到来的2.0版本中,通过基于DataFrame的API,Spark机器学习库MLlib将实现几乎完整的ML持久性支持。本文将提前透露有关代码示例,以及MLlib API持久性的一些细节。
ML持久性的关键特性包括:
- Spark支持所有语言的API:Scala、Java、Python和R
- 基于DataFram的API几乎支持所有的ML算法
- 支持单一模型和完整的Pipelines,不管是训练或者未训练的
- 使用可互换的格式来实现分布式存储
感谢所有为MLlib带来巨大发展的社区贡献者们!在JIRAs中可以看到为Scala,Java, Python和R做出贡献的完整人员名单。
了解API
在Apache Spark 2.0里,对于MLlib来说基于DataFrame的API在关于Spark的ML中占据了首要位置。该API模仿被人们所熟知的Spark Data Source API,提供保存和加载模型的功能。
下面将采用流行的MNIST数据集进行手写体数字识别,并在几种语言上演示保存和加载模型的功能(LeCun等著,1998;可从LIBSVM数据页面获取)。这个数据集包含了手写数字0-9,以及地面实况标签。这里有些例子:
我们的最终目的是为了拍摄新的手写数字图像并进行数字识别。在下面的笔记中就完整地演示了数据载入,以及模型训练、保存和加载的代码。
保存和加载单一模型
首先将展示如何保存和加载单一模型以促进语言共享,例子中首先会通过Python来训练一个Random Forest Classifier并保存下来,然后再利用Scala加载相同的模型。
training = sqlContext.read... # data: features, label
rf = RandomForestClassifier(numTrees=20)
model = rf.fit(training)
为了简化,这里将保存模型称为save方法,把加载模型称为load方法:
model.save("myModelPath")
sameModel = RandomForestClassificationModel.load("myModelPath")
我们还可以将同样的模型(已保存在Python的)加载到一个Scala或者Java应用程序中:
// Load the model in Scala
val sameModel = RandomForestClassificationModel.load("myModelPath")
这个方法既可以用于小型的本地模型例如K-Means模型(为了集群),也可以用于大型的分布式模型例如ALS模型(为了推荐)。因为加载的模型具有相同的参数设置和数据,所以即使加载的是一个完全不同的Spark部署,它也会给出相同的预测。
保存和加载完整Pipelines
到目前为止只演示了单一ML模型的保存和加载,但在实际过程中,ML的工作流其实包含着许多阶段,从特征的提取和转换到模型的训练和调优都在其中。MLlib还提供了Pipelines来帮助用户更好地构建这些工作流。
同时,MLlib还允许用户保存和加载整个Pipelines。下面通过一个Pipeline案例看一下它是采用了哪些步骤实现的:
- 特征提取:使用Binarizer将图像转换成黑白色
- 模型训练:使用Random Forest Classifier拍摄图像和预测数字0–9
- 调优:使用交叉验证(Cross-Validation)来优化森林中树的深度
下面是建立Pipeline的一个片段:
// Construct the Pipeline: Binarizer + Random Forest
val pipeline = new Pipeline().setStages(Array(binarizer, rf))
// Wrap the Pipeline in CrossValidator to do model tuning.
val cv = new CrossValidator().setEstimator(pipeline) ...
在管道训练之前,我们会演示将整个工作流保存下来的过程(训练前)。而且这个工作流可以在另一个数据集上,或者是在另一个Spark集群上等地方加载运行。
cv.save("myCVPath")
val sameCV = CrossValidator.load("myCVPath")
最后,我们就可以进行Pipeline训练,再将其保存和加载。这不仅可以节省特征提取的步骤,还可以省去使用Cross-Validation调整Random Forest模型以及从模型调优中提取数据的过程。
val cvModel = cv.fit(training)
cvModel.save("myCVModelPath")
val sameCVModel = CrossValidatorModel.load("myCVModelPath")
了解细节
Python调优
很遗憾,Python调优将缺席Spark 2.0版本。就目前情况来看,Python还不支持保存和加载用于优化hyperparameters模型的CrossValidator和TrainValidationSplit;这个问题也正是Spark 2.1版本需要解决的。但是,它仍然有可能被用来保存Python中的CrossValidator和TrainValidationSplit结果。例如,使用Cross-Validation来调整Random Forest并将调整过程中发现的最好模型保存起来。
# Define the workflow
rf = RandomForestClassifier()
cv = CrossValidator(estimator=rf, ...)
# Fit the model, running Cross-Validation
cvModel = cv.fit(trainingData)
# Extract the results, i.e., the best Random Forest model
bestModel = cvModel.bestModel
# Save the RandomForest model
bestModel.save("rfModelPath")
可交换的存储格式
在内部,我们可以把模型的元数据和参数保存为JSON,把数据保存为Parquet。这些存储格式是可交换的,还可以使用其他库读取。Parquet不仅可以存储小的模型(例如Naive Bayes for classification),还可以存储大型的分布式模型(例如ALS for recommendation)。任何被Dataset/DataFrame支持的URI 都可以保存和加载存储路径,包括S3路径、本地存储等等。
语言的跨平台兼容性
利用Scala、Java和Python可以很容易地保存和加载模型,但是R却有两个局限性。一方面,R并不是支持所有的MLlib模型,其他语言所训练的模型也不是都可以被加载到R。另一方面,目前的R模型格式需要存储一些配合R使用的数据,这样给其他语言加载R所训练和存储的模型增加了困难。相信更好的跨语言支持R会在不久的将来被补足。
结论
随着2.0版本的即将发布,DataFrame-based MLlib API将几乎完全覆盖持久化的模型和Pipelines。对于团队间共享模型、多语言ML工作流创建以及将模型用于生产这些,持久性发挥着至关重要的作用。这个特性也将会推动MLlib API(DataFrame-based)最终转变为Apache Spark机器学习的重要API。
接下来呢?
未来的话,更高优先级的项目将会包括完整的持久性覆盖、Python模型优化算法以及R和其他语言API之间的兼容性改进。