Spark MLlib之管道

一.ML管道

ML管道提供基于DataFrame的统一高级API集,可帮助用户创建和调整实用的机器学习管道。

二.管道中的主要概念

MLlib对用于机器学习算法的API进行了标准化,从而使将多种算法组合到单个管道或工作流中变得更加容易。

  • DataFrame:此ML API使用DataFrame作为ML数据集,可以保存各种数据类型。例如,一个DataFrame可能有不同的列,用于存储文本,特征向量,真实标签和预测。
  • Transformer:一个Transformer是一种算法,其可以将一个DataFrame转换为另一个DataFrame。例如,ML的一种Transformer模型是将一个具有特征的DataFrame转换为一个具有预测的DataFrame的模型。
  • Estimator:Estimator是一种算法,可以适合DataFrame产生Transformer。例如,学习算法是在Estimator上训练DataFrame并生成模型的算法。
  • Pipeline:Pipeline将多个Transformers和Estimators链接在一起以指定ML工作流程。
  • Parameter:所有Transformer和Estimator现在共享一个用于指定参数的通用API。

三.管道组件
Transformers
Transformer是一个包含特征转换器和学习模型的抽象。从技术上讲,一种Transformer工具实现了一种transform()方法,DataFrame通常通过附加一个或多个列将其转换为另一列的方法。例如:

  • 特征转换器可以采用DataFrame,读取一列(例如,文本),将其映射到新列(例如,特征向量),并输出DataFrame附加了映射列的新列。
  • 学习模型可能采用DataFrame,读取包含特征向量的列,预测每个特征向量的标签,然后输出带有预测标签的新DataFrame。

估算器
一个Estimator抽象学习算法的概念或算法。从技术上讲,Estimator实现一种fit()方法,该方法接受一个DataFrame并产生一个Model,即一个Transformer。例如,学习算法(例如LogisticRegression)Estimator和调用 fit()训练一个LogisticRegressionModel。
管道组件的属性
Transformer.transform()和Estimator.fit()都是无状态的。将来,可通过替代概念来支持有状态算法。

Transformer或Estimator的每个实例都有一个唯一的ID,该ID在指定参数中很有用。
管道
在机器学习中,通常需要运行一系列算法来处理数据并从中学习。例如,一个简单的文本文档处理工作流程可能包括几个阶段:

  1. 将每个文档的文本拆分为单词。
  2. 将每个文档的单词转换为数字特征向量。
  3. 使用特征向量和标签学习预测模型。

MLlib将这样的工作流程表示为Pipeline,其中包含要按特定顺序运行的一系列 PipelineStages(Transformers和Estimators)。
这个怎么运作
A Pipeline被指定为一个阶段序列,每个阶段是a Transformer或an Estimator。这些阶段按顺序运行,并且输入DataFrame在通过每个阶段时都会进行转换。对于Transformer阶段,该transform()方法在DataFrame上调用。对于Estimator阶段,将调用fit()方法来生成Transformer(成为PipelineModel或一部分Pipeline),Transformer在DataFrame上调用transform方法。

下图是的训练时间用法Pipeline。
Spark MLlib之管道_第1张图片
上方的第一行代表Pipeline具有三个阶段。前两个(Tokenizer和HashingTF)是Transformers(蓝色),第三个(LogisticRegression)是Estimator(红色)。最下面的行表示流经管道的数据,其中圆柱体表示DataFrames。Pipeline.fit()在原始DataFrame文件上调用此方法,原始文件包含原始文本文档和标签。该Tokenizer.transform()方法将原始文本文档拆分为单词,然后向添加带有单词的新列DataFrame。该HashingTF.transform()方法将words列转换为特征向量,并将带有这些向量的新列添加到DataFrame。由于LogisticRegression为Estimator,因此Pipeline第一个调用LogisticRegression.fit()产生一个LogisticRegressionModel。如果Pipeline有更多Estimator,它将调用LogisticRegressionModel的transform() 方法,DataFrame然后再传递DataFrame到下一个阶段。

A Pipeline是一个Estimator。因此,运行Pipeline的fit()方法后,它会产生一个PipelineModel,即一个 Transformer。这PipelineModel是在使用测试时间 ; 下图说明了这种用法。
Spark MLlib之管道_第2张图片
在上图中,PipelineModel具有与原始相同的阶段数Pipeline,但是原始中的所有Estimators Pipeline已变为Transformer。当测试数据集在PipelineModel的transform()方法上被使用时,该数据是为了通过拟合管道传递。每个阶段的transform()方法都会更新数据集,并将其传递到下一个阶段。

Pipeline和PipelineModel有助于确保训练和测试数据经过相同的特征处理步骤。

四.细节

DAG Pipeline:Pipeline的级被指定为一个有序阵列。线性Pipelines,即Pipeline每个阶段使用前一阶段产生的数据。只要数据流图形成有向无环图(DAG),就可以创建非线性Pipeline。基于当前每个阶段的输入和输出列名称(通常指定为参数)隐式指定该图。如果Pipeline形成DAG,则必须按拓扑顺序指定阶段。

运行时检查:由于Pipelines可以对DataFrames进行各种类型的操作,因此它们不能使用编译时类型检查。 Pipelines和PipelineModels会在实际运行之前进行运行时检查Pipeline。使用DataFrame 模式完成类型检查,模式是DataFrame中列的数据类型描述。

唯一的管道阶段:A Pipeline的阶段应该是唯一的实例。例如,同一实例 myHashingTF不应插入Pipeline两次,因为Pipeline阶段必须具有唯一的ID。但是,由于将使用不同的ID创建不同的实例,因此可以将不同的实例myHashingTF1和myHashingTF2(类型均为HashingTF)放入同一Pipeline实例。

五.参量

MLlib Estimator和Transformers使用统一的API来指定参数。

A Param是具有独立文件的命名参数。A ParamMap是一组(参数,值)对。

将参数传递给算法的主要方法有两种:

  • 设置实例的参数。例如,如果lr是LogisticRegression的一个实例,可以调用lr.setMaxIter(10),使lr.fit()最多10次迭代计算。该API类似于spark.mllib软件包中使用的API 。
    将传递ParamMap给fit()或transform()。ParamMap中的任何参数都将覆盖先前通过setter方法指定的参数。
  • 参数属于Estimators和Transformers的特定实例。例如,如果我们有两个LogisticRegression实例lr1和lr2,则可以ParamMap使用两个maxIter指定的参数构建一个ParamMap(lr1.maxIter -> 10, lr2.maxIter -> 20)。如果Pipeline有两个算法使用maxIter参数,这将很有用。

六.ML持久性:保存和加载管道

通常,将模型或管道保存到磁盘以供以后使用是值得的。在Spark 1.6中,模型导入/导出功能已添加到管道API。从Spark 2.3开始,基于DataFrame的API spark.ml增加pyspark.ml。

ML持久性可跨Scala,Java和Python使用。但是,R当前使用修改后的格式,因此保存在R中的模型只能重新加载到R中。以后应该修复此问题,并在SPARK-15572中进行跟踪。

七.向后兼容以实现ML持久性

通常,MLlib为ML持久性保持向后兼容性。即,如果在一个版本的Spark中保存ML模型或管道,则应该能够将其加载回去并在以后的Spark版本中使用。但是,有极少数例外,如下所述。

模型的持久性:是否可以通过Y版本的Spark加载使用Spark ML在Spark版本X中的持久性保存的模型或管道?

  • 主要版本:不保证,但尽力而为。
  • 次要版本和补丁版本:是;这些是向后兼容的。
  • 关于格式的注意事项:不保证稳定的持久性格式,但是模型加载本身被设计为向后兼容。

模型行为:Spark版本X中的模型或管道在Spark版本Y中的行为是否相同?

  • 主要版本:不保证,但尽力而为。
  • 次要版本和修补程序版本:除错误修复外,行为相同。
  • 对于模型持久性和模型行为,Spark版本发行说明中都会报告次要版本或修补程序版本中的所有重大更改。如果发行说明中未报告损坏,则应将其视为要修复的错误。

八.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")
  }

九.EstimatorTransformerParam代码实战

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(s"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(s"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(s"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: Long, prob: Vector, prediction: Double) =>
    println(s"($features, $label) -> prob=$prob, prediction=$prediction")
  }

你可能感兴趣的:(Spark,机器学习,大数据,spark,ml)