前言
Spark的Mllib机器学习工具包括两个扩展,一是Mllib,其算法都是围绕RDD这个数据结构来实现的;二是ML,其基于Pipeline提供了一整套建立在DataFrame上的高级API,将每一个操作定义为一个Stage,能够帮助用户创建和优化机器学习流程。本文关注ML扩展中的Pipeline,并就如何自定义Stage模型进行讨论。
一、 Pipeline介绍
Pipeline直译过来就是管道、工作流。在Spark中,它的目的是将数据挖掘、机器学习的各个阶段封装成一个工作流模型。我们知道一个完整的数据挖掘流程大致包括数据清洗、特征工程、算法模型建立、算法模型评估等几个步骤,如果我们要针对每个步骤单独开发模型,并手动将他们串接,无疑是十分低效的。特别是在Spark中,基本的数据结构RDD太过抽象,要基于RDD来做数据挖掘的整套工作非常的不便。
而通过使用Pipeline,我们能够很好地完成这个任务。这是因为基于Pipeline的机器学习工作是围绕DataFrame来开展的,这是一种我们能够更加直观感受的数据结构。其次,它定义机器学习的每个阶段Stage,并抽象成Transformer和Estimator两类,我们能够基于这两个类抽样封装自己算法模型,并通过Pipeline自动串联的能力,将每一步连成自己的工作流。
二、自定义Transformer模型
Transformer模型仅仅对数据做转换操作,不涉及到模型训练。现在假设我们有一份人群身高数据,需要自己定义一个Transformer模型,实现一个简单的数据转换功能。期望的效果是给数据新增一列“h170”表示该人身高是否大于170,是的话赋值1,否则赋值0,具体操作如下:
1. 构造基本的transformer类
这里我们创建Mytransformer类,并继承Transformer,同时定义两个参数inputCol和outputCol分别代表Transformer所操作DateFrame的输入列和输出列。
class Mytransformer (override val uid: String) extends Transformer {
final val inputCol= new Param[String](this, "inputCol", "The input column")
final val outputCol = new Param[String](this, "outputCol", "The output column")
def setInputCol(value: String): this.type = set(inputCol, value)
def setOutputCol(value: String): this.type = set(outputCol, value)
def this() = this(Identifiable.randomUID("Mytransformer "))
def copy(extra: ParamMap): Mytransformer = {
defaultCopy(extra)
}
}
2. 重写transformSchema方法
TransformSchema方法确定Transformer操作中,所操作的DateFrame对象schema的变化。因为我们要会增加一列“h170”表示身高是否大于170,所以这里我们首先要判断待计算列的类型是否是Double,不是的话报错。其次是要返回变化后的表结构,即增加了一列“h170”的StructType。
override def transformSchema(schema: StructType): StructType = {
val idx = schema.fieldIndex("height")
val field = schema.fields(idx)
if (field.dataType != DoubleType) {
throw new Exception(s"Input type ${field.dataType} did not match input type DoubleType")
}
schema.add(StructField($(outputCol), DoubleType, false))
}
3. 实现transform方法
transform方法实现Transformer对DataFrame所做的具体操作
class MyEstimator(override val uid: String) extends Estimator[MyEstimatorModel] with MyEstimatorParams with DefaultParamsWritable {
def setInputCol(value: String) = set(inputCol, value)
def setOutputCol(value: String) = set(outputCol, value)
def this() = this(Identifiable.randomUID("MyEstimatorParams"))
override def copy(extra: ParamMap): MyEstimator = {
defaultCopy(extra)
}
}
4. 实现可读写
1)继承DefaultParamsWritable
class Mytransformer(override val uid: String) extends Transformer with DefaultParamsWritable
2)实现Transformer的伴生对象
这里有两步
- 实现伴生对象,并继承DefaultParamsReadable
- 伴生对象重写load方法,实现读Transformer实例的功能
object Mytransformer extends DefaultParamsReadable[Mytransformer] {
override def load(path: String): Mytransformer = super.load(path)
}
三、测试Transformer模型
通过前面的步骤,Transformer的基本功能就已经实现了,这里我们写个程序测试一下:
val dataset = spark.createDataFrame(Seq( ("mike", 166.0), ("tom", 175.0), ("wade", 163.0))).toDF("name", "height")
val mytransformer = new Mytransformer
mytransformer.setInputCol("height").setOutputCol("h170")
val pipeline = new Pipeline().setStages(Array(mytransformer)).fit(dataset)
val r = pipeline.transform(dataset)
r.show
结果如下:
这样,我们自定义Pipeline Transformer模型的工作就全部做完了。