Spark ML 2.1 --Pipelines



首先要引入概念 机器学习管道(ML pipelines),ML pipelines 提供基于DataFrames的高级API , 此API可以帮忙开发者创建和实践ML 管道。



一 ML pipelines 基本概念
Spark MLlib  API 可以很方便的将多种算法串联成一个管道或工作流。 本章节覆盖ML 管道 API中关键概念 , 这引起概念是受scikit-learn 项目启发。
 
1 DataFrame :  ML API 使用Spark SQL 的DataFrame  作为ML 的数据集,这样可以接受任意数据集。 例如, DataFrame 可以接受各种各样的例,如文本,特征向量,真值标签和预测输出 。

2 Transformer: 变换是ML的算法 ,它将原始的DataFrame 变换为更一个DataFrame数据 , 例如, ML 模型将原始特征向量的DataFrame 变换为预测结果的DataFrame

3 Estimator:  变换的估计实际也是一个变换,它将DataFrame做变换后,得出一个对变换的接受度值 ,如 , 学习算法是一个估计算法,它训练DataFrame 后得到模型。

4 Pipeline:  管道是将多个变换和估计串联在一起,形成一个ML 流。

5 Parameter: 对于特定的参数, 所有变换和估计使用相同的接口


1.1 DataFrame
机器学习可以应用到各种数据类型,如向量、文本、图片和结构数据。 Spark SQL 中的DataFrame 同样也适用于各种数据类型。

DataFrame支持多种基本数据类型,同样也支持结构化数据类型;详见: Spark SQL datatype reference( http://spark.apache.org/docs/latest/sql-programming-guide.html#data-types)  
中支持的数据类型。除了Spark SQL 指南中列出来的数据类型外, Dataframe 也支持ML向量类型。

DataFrame 可以从RDD 数据中隐式,或显示创建为DataFrame。见Spark SQL programming guide  中示例代码。

DataFrame的列数据是有名称的,后面的代码使用名字 text , feature 和label 。

1.2 Pipeline components 管道的组成 

1.2.1 Transformers

变换包含特征变换和模型学习,从编程来说,变换需要实现transform() 方法,此方法需要将DataFrame 转化为另一种数据DataFrame . 例如,

1> 特征变换会读取DataFrame 数据的每一列, 映射出新的列数据(如特征向量) , 输出带此输出列的新的DataFrame 

2> 模型学习读取DataFrame 中包含特征向量的列,对每个特征向量预测一个类标签, 将有类标签的新的DataFrame输出

1.2.2 Estimators
估计函数抽象了学习模型, 或者评估哪些数据适用于哪些算法,从编程上来说, Estimator 需要实现 fit() 方法, 此方法需要一个参数 DataFrame, 并返回一个Model, 也就是一个Transformer。 例如,
学习算法,如线性回归 LogisticRegression 也是一个估计函数, 并且运行git() 方法生成一个LogisticRegressionModel , 即可以看作一个Model , 也是一个Transformer。  


1.2.3 管道组成的属性
Transformer.transformer() 和  Estimator.fit() 都是无状态的, 未来会支持更多有状态的算法 
每个Transformer和 Estimator 有一个唯一的ID , 在确认参数时这个ID 会有用处。


1.3 Pipeline
在机器学习中,通常会使用一系列算法来处理和学习数据,例如, 一个简单的文本文档的处理会有很多流程: 
1 >  将文档中每个单词拆分出来 
2 >  将每个单词转化为数据型特征向量
3 > 使用特征向量和类标签学习预测模型

MLib中一个workflow 称为一个pipeline, 每个Pipeline是由PipelineStages( 管道的处理段组成,可以是transformers 或estimators ) 按一定顺序排列。下一节会以一个简单的workflow为例讲解。

1.3.1  工作原理
Pipeline 实际是一序列处理段,每一个处理段或者是Transformer 或 Estimator。 这些处理段是顺序执行,这样对输入数据DataFrame 逐步进行处理。 对于Transformer 处理段, 对DataFrame 调用
transform() 方法, 而到了Estimator 处理段,fit() 方法调用并输出给Transformer ( which becomes part of the  PipelineModel , or fitted  Pipeline), 即对DataFrame 调用Transformer 的transform()方法。

下图展现一个文档的workflow , 图中说明pipeline的训练。 


图中第一行展现Pipeline 表示三个处理段,前两个处理段分别是(Tokenizer 和 HashingTF) 分别 是Transformers , 第三个处理段是(LogisticRegression) 回归, 也就是Estimator 。
第二行展现数据流,其中圆柱形表示DataFrames, Pipeline.fit() 方法处理原始DataFrame ,这个数据是原始文本和类标签的二元组 。Tokenizer.transform() 方法将原始文本拆分成单词,
HashingTF.transform() 方法将单词向量转化为特征向量,并添加一个类标签到特征向量。 因为LogisticRegress 是一种Estimator,所以 Pipeline 首先调用LogisticRegression.fit() 方法输出
一个LogisticRegressionModel 模型。 假使此pipeline 再有多一些的处理段,可以调用LogisticRegressionModel 的 transform() 方法对DataFrame做处理后传给下一个处理段。

pipeline 也可以只有一个estimator , 这样一旦pipeline运行完fit()方法后,就可以输出 PipelineModel模型, 这个模型就是Transformer. 以下图就是验证PilelineModel 模型。
在上图中, PipelineModel 模型和前述模型有相同的处理段, 但前述模型中的Estimator ,在这个模型中变成Transformer 。 当PipelineModle 的transform() 方法在验证数据集时, 数据集会输送给待验证的模型workfllow, 每个处理段 transform()方法更新数据集,并将数据传到下一个处理段。

Pipelines 和 PipelineModel  的训练 和验证过程,数据都会流转到特征处理段来实现。

1.3.2 细节
DAG  Pipeline :  Pipeline 的处理段是一个有序地队列。 本节中例子给出的是线性Pipeline, 如, 每一步的Pipeline处理段输出结果传给下一个处理段 ,同样也可以构造一个非线性Pipeline , 只需要将数据处理段
排列成一个有向无环图(DAG) 。 用每一个处理段的输入和输出名称(column name)可以确定隐式一个图, 如果Pipeline 是一个DAG , 那么这些处理段需要按拓扑顺序排除。

runtime checking  : 因为pipeline 可以对Dataframe进行各种操作,这样就没法使用编译期检查, Pipeline 和PipelineModel  只能在运行前进行运行期检查,这种检查是通过检查DataFrame的schema 实现 ,也就是检查DataFrame 每个属性的类型是否和描述匹配。

Unique Pipeline Stages :  Pipeline的处理段应该有唯一的初阶 , 例如:  每一个pipeline 中不能出现myHashingTF 实例两次及两次以上, 因为每个处理段有唯一一个ID 标识。 尽管这样, 不同的初阶myHashingTF1和myHashingTF2 可以在同一个pipeline中出现, 因为不同的实例有不同地ID标识。

1.4 参数
MLlib Estimator 和Transformer 针对某些参数使用一致的API 。
Param 是参数名,  ParamMap 是 (参数, 值)对。
有两种主要方式将参数传递给算法 : 
 1 对实例设置参数  。 例如,  lr 是LogisticRegression 的实例,可以调用lr.setMaxIter(1)) 来设置 lr.fit() 至多使用10次迭代。这种方式多见sark.mllib包。 

 2 将ParamMap 传递给fit() 或transfor() 方法, 这种情况下,传递的ParamMap 参数会覆盖之前的 setter方法设置的参数 。 

对 于Estimator 和Transformer 都有的参数如何修改,如 , LogisticRegression 实例 lr1, lr2 , 可以用两个实例的maxIter参数构造一个ParamMap : ParamMap(lr1.maxIter -> 10 , lr2.maxIter -> 20) ,此时Pipeline中使用的两个算法都有maxIter 参数时,这样很方便。

1.5 保存和加载pipeline
很多时候 ,我们需要保存一个模型,或Pipeline 用于下次使用。 在Spark 1.6中, pipeline API 中添加了模型的导入和导出功能。 很多基本的变换,以及基本的ML模型都支持这个功能, 详见算法API 文档查询是否支持保存和加载 pipeline 


2 代码示例
本节给出演示的代码示例, 详见scala API 文本。

2.1 例: Estimator , Transformer , 和Param
下例展示Estimator , Transformer 和param 的使用
Estimator 详见 scala 文档, 详见:  api/scala/index.html#org.apache.spark.ml.Estimator
Transformer Scala文档, 详见:  api/scala/index.html#org.apache.spark.ml.Transformer
Param scala文档,详见:  api/scala/index.html#org.apache.spark.ml.param.Params

import  org.apache.spark.ml.classification.LogisticRegression
import  org.apache.spark.ml.linalg. { Vector ,  Vectors }
import  org.apache.spark.ml.param.ParamMap
import  org.apache.spark.sql.Row

// Prepare training data from a list of (label, features) tuples.
val training  = spark .createDataFrame ( Seq (
  ( 1.0 ,  Vectors .dense ( 0.0 ,  1.1 ,  0.1 )),
  ( 0.0 ,  Vectors .dense ( 2.0 ,  1.0 ,  - 1.0 )),
  ( 0.0 ,  Vectors .dense ( 2.0 ,  1.3 ,  1.0 )),
  ( 1.0 ,  Vectors .dense ( 0.0 ,  1.2 ,  - 0.5 )) )).toDF ( "label" ,  "features" )

// Create a LogisticRegression instance. This instance is an Estimator.
val lr  =  new  LogisticRegression ()
// Print out the parameters, documentation, and any default values.
println ( "LogisticRegression parameters:\n"  + lr .explainParams ()  +  "\n" )

// We may set parameters using setter methods.
lr .setMaxIter ( 10 )
  .setRegParam ( 0.01 )

// Learn a LogisticRegression model. This uses the parameters stored in lr.
val model1  = lr .fit (training )
// Since model1 is a Model (i.e., a Transformer produced by an Estimator),
// we can view the parameters it used during fit().
// This prints the parameter (name: value) pairs, where names are unique IDs for this
// LogisticRegression instance.
println ( "Model 1 was fit using parameters: "  + model1 .parent .extractParamMap )

// We may alternatively specify parameters using a ParamMap,
// which supports several methods for specifying parameters.
val paramMap  =  ParamMap (lr .maxIter  ->  20 )
  .put (lr .maxIter ,  30 )  // Specify 1 Param. This overwrites the original maxIter.
  .put (lr .regParam  ->  0.1 , lr .threshold  ->  0.55 )  // Specify multiple Params.

// One can also combine ParamMaps.
val paramMap2  =  ParamMap (lr .probabilityCol  ->  "myProbability" ) 
// Change output column name.
val paramMapCombined  = paramMap  ++ paramMap2

// Now learn a new model using the paramMapCombined parameters.
// paramMapCombined overrides all parameters set earlier via lr.set* methods.
val model2  = lr .fit (training , paramMapCombined )
println ( "Model 2 was fit using parameters: "  + model2 .parent .extractParamMap )

// Prepare test data.
val test  = spark .createDataFrame ( Seq (
  ( 1.0 ,  Vectors .dense (- 1.0 ,  1.5 ,  1.3 )),
  ( 0.0 ,  Vectors .dense ( 3.0 ,  2.0 ,  - 0.1 )),
  ( 1.0 ,  Vectors .dense ( 0.0 ,  2.2 ,  - 1.5 )) )).toDF ( "label" ,  "features" )

// Make predictions on test data using the Transformer.transform() method.
// LogisticRegression.transform will only use the 'features' column.
// Note that model2.transform() outputs a 'myProbability' column instead of the usual
// 'probability' column since we renamed the lr.probabilityCol parameter previously.
model2 .transform (test )
  .select ( "features" ,  "label" ,  "myProbability" ,  "prediction" )
  .collect ()
  .foreach  {  case  Row (features :  Vector , label :  Double , prob :  Vector , prediction :  Double )  =>
    println ( s"( $features $label ) -> prob= $prob , prediction= $prediction " )
  }
完整例子见:  examples/src/main/scala/org/apache/spark/examples/ml/EstimatorTransformerParamExample.scala


2.2 例子: Pipeline
下例给出pipeline的使用

Pipeline scala 文档详见:  api/scala/index.html#org.apache.spark.ml.Pipeline
import  org.apache.spark.ml. { Pipeline ,  PipelineModel }
import  org.apache.spark.ml.classification.LogisticRegression
import  org.apache.spark.ml.feature. { HashingTF ,  Tokenizer }
import  org.apache.spark.ml.linalg.Vector
import  org.apache.spark.sql.Row

// Prepare training documents from a list of (id, text, label) tuples.
val training  = spark .createDataFrame ( Seq (
  ( 0L ,  "a b c d e spark" ,  1.0 ),
  ( 1L ,  "b d" ,  0.0 ),
  ( 2L ,  "spark f g h" ,  1.0 ),
  ( 3L ,  "hadoop mapreduce" ,  0.0 ) )).toDF ( "id" ,  "text" ,  "label" )

// Configure an ML pipeline, which consists of three stages: tokenizer, hashingTF, and lr.
val tokenizer  =  new  Tokenizer ()
  .setInputCol ( "text" )
  .setOutputCol ( "words" )
val hashingTF  =  new  HashingTF ()
  .setNumFeatures ( 1000 )
  .setInputCol (tokenizer .getOutputCol )
  .setOutputCol ( "features" )
val lr  =  new  LogisticRegression ()
  .setMaxIter ( 10 )
  .setRegParam ( 0.001 )
val pipeline  =  new  Pipeline ()
  .setStages ( Array (tokenizer , hashingTF , lr ))

// Fit the pipeline to training documents.
val model  = pipeline .fit (training )

// Now we can optionally save the fitted pipeline to disk
model .write .overwrite ().save ( "/tmp/spark-logistic-regression-model" )

// We can also save this unfit pipeline to disk
pipeline .write .overwrite ().save ( "/tmp/unfit-lr-model" )

// And load it back in during production
val sameModel  =  PipelineModel .load ( "/tmp/spark-logistic-regression-model" )

// Prepare test documents, which are unlabeled (id, text) tuples.
val test  = spark .createDataFrame ( Seq (
  ( 4L ,  "spark i j k" ),
  ( 5L ,  "l m n" ),
  ( 6L ,  "spark hadoop spark" ),
  ( 7L ,  "apache hadoop" ) )).toDF ( "id" ,  "text" )

// Make predictions on test documents.
model .transform (test )
  .select ( "id" ,  "text" ,  "probability" ,  "prediction" )
  .collect ()
  .foreach  {  case  Row (id :  Long , text :  String , prob :  Vector , prediction :  Double )  =>
    println ( s"( $id $text ) --> prob= $prob , prediction= $prediction " )
  }
详见:  examples/src/main/scala/org/apache/spark/examples/ml/PipelineExample.scala

2.3  模型选择 (hyperparameter  超参数优化) 
使用ML Pipeline的好处是实现超参数优化, 详见:ML Tuning Guide :  http://spark.apache.org/docs/latest/ml-tuning.html     查阅更多关于自动模型选择的信息







 








 

 













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值