注: 本文基于spark-2.1.1
Spark MLlib是Spark的重要组成部分,也是最早推出的库之一,其基于RDD的API,算法比较丰富,比较稳定,也比较好用。但是如果目标数据集结构复杂需要多次处理,或者是对新数据需要结合多个已经训练好的单个模型进行综合计算时,使用MLlib将会让程序结构复杂,甚至难于理解和实现。为改变这一局限性,从Spark 1.2 版本之后引人了ML Pipeline,经过多个版本的发展,Spark ML克服了MLlib在处理复杂机器学习问题的一些不足(如工作比较复杂,流程不清晰等),向用户提供基于DataFrame之上的更加高层次的API库,以更加方便的构建复杂的机器学习工作流式应用,使整个机器学习过程变得更加易用、简洁、规范和高效。Spark 的Pipeline 与Scikit 中Pipeline的功能相近、理念相同。
一、Pipeline简介
ML提倡使用Pipeline,-般翻译为流水线,以便将多种算法更容易地组合成单个流水线或工作流程。一个Pipeline在结构上会包含一个或多个Stage,每一个 Stage都会完成一个任务,如数据处理、数据转化、模型训练、参数设置或数据预测等,其中两个主要的Stage为Transformer和Estimator。Transformer 主要是用来操作一个DataFrame数据并生成另外一个DataFrame数据,比如决策树模型、一个特征提取工具,都可以抽象为一个
Transformer。Estimator 则主要是用来做模型拟合,用来生成一个Transformer。这些Stage有序组成一个Pipeline。与Pieline 相关的概念有: DataFrame、 Transformer、Estimator、Parameter等。
1.1、DataFrame
说到DataFrame,使用过Pandas、R的朋友应该比较熟悉,它是一种类似于关系型数据库的表,使用和维护都非常方便。Spark 的DataFrame是基于早期版本中的SchemaRDD,所以使用的是分布式大数据处理的场景。Spark DataFrame以RDD为基础,但是带有Schema信息,可以从隐式或显式地从常规的RDD创建DataFrame,它类似于传统数据库中的二维表格。它被ML Pipeline用来存储源数据。DataFrame可以保存各种类型的数据,如我们可以把特征向量存储在DataFrame的一列中,这样用起来非常方便。
package com.chb.test
import org.apache.spark.sql.SparkSession
/**
* Created by chb on 2019/5/25.
*/
object Test {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("test").master("local[*]")
.config("spark.some.config.option", "some-value")
.getOrCreate()
val df = spark.read.option("header", true).csv("hdfs://192.168.179.14:8020/mlDataSet/catering_sale.csv")
val df1 = df.select(df("sale_date").cast("String"), df("sale_amt").cast("Double"))
df1.printSchema()
df1.createOrReplaceTempView("catering_sale")
val sql = spark.sql("SELECT sale_date, sum(sale_amt) as total_sale FROM catering_sale GROUP BY sale_date")
sql.show()
}
}
1.2、Pipeline的组件
1.2.1、Transformer
Transformer一般翻译成转换器,是一个Pipeline Stage,转换器包含特征变化和学习模型。从技术上来说,转化器通过方法transform(),在原始数据上增加一列或者多列来将一个DataFrame转为另一个DataFrame。如:
- 1)一个特征转换器输入一个DataFrame,读取一个文本列,将其映射为新的特征向量列。输出一个新的带有特征向量列的DataFrame。
- 2)一个学习模型转换器输入一个DataFrame,读取包括特征向量的列,预测每一个特征向量的标签。输出一个新的带有预测标签列的DataFrame。
1.2.2 、 Estimator
Estimator可以被翻译成评估器或适配器,在Pipeline里通常是被用来操作DataFrame数据并生产一个Transformer,一个分类算法就是一个Estimator, 因为它可以通过训练特征数据而得到一个分类模型。估计器用来拟合或者训练数据的学习算法或者任何算法。从技术上来说,估计器通过调用fit()方法,接受一个DataFrame产生一个模型,这个模型就是一个Transformer。比如,逻辑回归就是一个评估器 ,通过调用fit()来产生一个逻辑回归模型。
1.2.3、Pipeline
要构建一个Pipeline,首先需要定义Pipeline中的各个Stage,如指标提取和转换模型训练等。有了这些处理特定问题的Transformer和Estimator,我们就可以按照具体的处理逻辑来有序地组织Stage并创建一个Pipeline。流水线由一系列有顺序的阶段指定,每个状态的运行是有顺序的,输人的DataFrame通过每个阶段进行改变。在转换器阶段,transform() 方法被调用于DataFrame上。对于估计器阶段,fit()方法被调用来产生一个转换器,然后该转换器的transform()方法被调用在DataFrame上。下图简单说明了文档处理工作流的运行过程。
在上图中,第一行代表流水线处理的三个阶段。第一、二个阶段是转换器,第三个逻辑回归是估计器。底下一行代表流水线中的数据流,圆简指DataFrame。pipeline.fit()
方法被调用于源DataFrame中,里面包含原始的文档和标签。Tokenizer.transform()
方法将原始文档分为词语,添加新的词语列到DataFrame中。HashingTF.transform()
方法将词语列转换为特征向量,添加新的向量列到DataFrame中。然后,因为逻辑回归是估计器,流水线先调用逻辑回归的fit()方法来产生逻辑回归模型。如果流水线还有其他更多阶段,在将DataFrame传入下一个阶段之前,流水线会先调用逻辑回归模型的transform()方法。整个流水线是一个估计器。所以当流水线的fit()方法运行后,会产生一个流水线模型,流水线模型是转换器。流水线模型会在测试时被调用,下0图说明了它的用法。
在上图中,流水线模型和原始流水线有同样数目的阶段,然而原始流水线中的估计器此时变为了转换器。当流水线模型的transform()方法被调用于测试数据集时,数据依次经过流水线的各个阶段。每个阶段的transform)方法更新数据集,并将之传到下个阶段。流水线和流水线模型有助于确认训练数据和测试数据经过同样的特征处理流程。以上两图如果合并为一图,可用下图来表达。
1.2.4、Parameters
所有的EstimatorandTransformer使用同一个 API 来指定参数。一个 Param 是被定义好的已命名参数。一个 ParamMap 是一组“参数-值” (parameter, value) 对。
向一个算法传参的方法主要有两种:
- 为实例设置参数。例如,lr 是 LogisticRegression 的一个实例,我们可以通过调用 lr.setMaxIter(10) 方法来设定 lr.fit() 最多进行10次迭代。这个 API 和 spark.mllib 包中的 API 相似。
- 通过 ParamMap 给 fit() 和 transform() 传参。ParamMap 中的参数将会覆盖之前通过 setter 方法设置过的参数。
如果我们有两个 LogisticRegression 实例 lr1 和 lr2,可以创建一个包含两个 maxIter 的 ParamMap:ParamMap(lr1.maxIter -> 10, lr2.maxIter -> 20)。如果一个 Pipeline 里有两个带有 maxIter 的算法,这种方法比较实用。
1.3、实例
1.3.1、Estimator, Transformer, and Param
参考 org.apache.spark.examples.ml.EstimatorTransformerParamExample
1.3.2、Pipeline实例