Spark MLlib框架详解
1. 概述
1.1 功能
MLlib是Spark的机器学习(machine learing)库,其目标是使得机器学习的使用更加方便和简单,其具有如下功能:
- ML算法:常用的学习算法,包括分类、回归、聚类和过滤;
- 特征:特征萃取、转换、降维和选取;
- Pipelines:其是一个工具,目标是用于构建、测量和调节;
- 使用工具:包括线性代数、统计学习和数据操作等等。
1.2 API架包
MLlib有两个API架包:
2. Pipelines Components
MLlib标准化机器学习算法的API,使得更容易将多个算法组合成到单个管道(工作流)。其设计思想是受到Scikit-learn项目的启发。
- DataFrame:MLlib的数据使用Spark SQL中的DataFrame结构来存储,即用户的数据集和模型的输出标签都是以此结构存储,包括Pipeline内部数据的传输都是以此结构存储;
- Transformer:MLlib将算法模型用Transformer结构来表示,其以一个DataFrame数据作为输入,通过模型计算后转换为一个DataFrame数据;
- Estimator:Estimator结构也表示一种算法,但其以一个DataFrame数据作为输入,通过模型计算后转换为一个Transformer对象,而不是DataFrame数据;
- Pipeline:MLlib使用Pipeline来组织多个ML模型,即其内部有多个Transformer和Estimator对象,从而组成一个算法工作流;
- Parameter:MLlib使用Parameter结构来存储参数,用户通过这些参数来配置和调节模型。即在一个Pipeline对象内的所有Transformer和Estimator对象都共享一个Parameter对象。
2.1 DataFrame
机器学习中数据集是由一个个样本组成,而每个样本其实是一条有多个特征组成的记录,从而数据集其实是一个矩阵结构。而Spark SQL中的DataFrame结构也拥有类似的结构,DataFrame内部有一行行的数据Row组成,每个Row对象内部也可以由多个属性组成。从而MLlib使用DataFrame来描述机器学习中的数据集正好不过了。
Spark SQL的DataFrame其实一种Dataset类型,只是存储的是Row元素,如下Spark源码所示:
Package object sql{ …… type DataFrame = Dataset[Row] } |
2.2 Pipeline
MLlib使用Pipeline来组织多个ML模型,其内部有多个Transformer和Estimator对象,从而组成一个算法工作流。在Spark ML中与Pipeline相关联的类如图 1所示。从图中可明显看出Transformer和Estimator都是PipelineStage抽象类的子类;并且Pipeline类内部有一个stages数组来存储PipelineStage对象,即存放Transformer和Estimator对象;当用户调用Pipeline的fit()方法时,将产生一个PipelineModel对象;PipelineModel类有一个transform()方法能返回一个DataFrame对象。
图 1
3. 工作机制
Pipeline是由一系列stage组成,这些stage有两种类型:Transformer和Estimator。Stage在Pipeline的运行是有序的,而且输入的DataFrame会在stage中被转换和传递。若stage是Transformer类型,则对条用Transformer对象的transform()方法将输入的DataFrame转换为另一种DataFrame;若stage是Estimator类型,则会调用Estimator对象的fit()方法产生Transformer对象,调用该Transformer对象的transform()方法一样会产生一个DataFrame。
可以将上述这一段,详细解释为两个过程:模型训练和模型预测,如下所示:
3.1 模型训练
Pipeline对象内部有一个stages容器,存放多个Transformer对象和一个Estimator对象。当用户调用Pipeline对象的fit()方法时,会接收输入的DataFrame,然后在这些stage中被转换和传递。当传递到最后一个stage(Estimator对象)时,将生成一个PipelineModel对象(Transformer子类),如图 2所示。
图 2
用户调用上图中Pipeline的fit()时,会将stages容器存放的所有Transformer对象和Estimator对象生成的Transformer对象都添加到PipelineModel对象中,该对象有一个stages容器(Array[Transformer]类型),其能够存放Transformer对象。
通过Spark源码,可以查看Pipeline类中的fit()内容如下所示:
override def fit(dataset: Dataset[_]): PipelineModel = { transformSchema(dataset.schema, logging = true) val theStages = $(stages) … var curDataset = dataset val transformers = ListBuffer.empty[Transformer] theStages.view.zipWithIndex.foreach { case (stage, index) => if (index <= indexOfLastEstimator) { val transformer = stage match { case estimator: Estimator[_] =>//若是Estimator对象,则调用fit()方法生成一个Transformer estimator.fit(curDataset) case t: Transformer =>//若是Transformer对象,则直接返回 t case _ => throw new IllegalArgumentException( s"Does not support stage $stage of type ${stage.getClass}") } if (index < indexOfLastEstimator) { curDataset = transformer.transform(curDataset)//如果不是最后的对象,则调用transformer对象的transform方法,生成一个DataFrame } transformers += transformer //将生成的所有Transformer对象都添加到一个list中 } else { transformers += stage.asInstanceOf[Transformer] } } new PipelineModel(uid, transformers.toArray).setParent(this) //最后创建PipelineModel对象,并传递上述的Transformer列表。 } |
3.2 模型预测
在模型训练阶段会通过向Pipeline的fit()方法传递DataFrame数据来训练模型,从而生成一个PipelineModel对象(Transformer子类),该对象内部有一个stages容器,存放了所有Transformer对象。
当进行模型预测时,即通过向PipelineModel对象的transform传递一个DataFrame数据来预测时,会依序调用其stages容器中的Transformer对象,每个Transformer对象都有一个DataFrame输入和一个DataFrame的输出,最后生成一个DataFrame作为用户的输出,如图 3所示。
图 3
类似,可以查看PipelineModel对象的transform()方法,如下所示:
override def transform(dataset: Dataset[_]): DataFrame = { transformSchema(dataset.schema, logging = true) stages.foldLeft(dataset.toDF)((cur, transformer) => transformer.transform(cur)) } |
stages.foldLeft(dataset.toDF)((cur, transformer) => transformer.transform(cur))语句正是图 3的实现,即第一次输入数据是dataset.toDF,然后每次调用transformer.transform(cur))方法,产生的DataFrame输出作为下一次的输入。
3.3 关系总结
通过上述Pipeline工作机制的分析,现在从机器学习的角度总结一下Pipeline、Transformer和Estimator三者之间的关系,如图 4所示。
图 4
- Transformer:是对数据进行预处理,如特征向量萃取、向量转换或降维;
- Estimator:机器学习的某种算法,如线性回归、贝叶斯或支持向量机;
- Pipeline:是一种算法组织者,将Transformer和Estimator组织成有序的执行过程。
4. Examples
4.1 Estimator、Transformer和Param
本节以Estimator类为例,没有使用Pipeline结构来组织Estimator和Transformer对象。Estimator类可以单独使用,不需要Pipeline结构也能工作,此时Estimator类似Scikit-learn框架。首先,用户直接调用Estimator对象的fit()方法来训练数据;然后,根据fit()方法返回的Transformer对象,用户接着调用Transformer的transform()方法来预测或测试;
如下所示的完整程序:
// scalastyle:off println package org.apache.spark.examples.ml // $example on$ 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 // $example off$ import org.apache.spark.sql.SparkSession object EstimatorTransformerParamExample { def main(args: Array[String]): Unit = { val spark = SparkSession .builder .appName("EstimatorTransformerParamExample") .getOrCreate() // $example on$ // 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") } // $example off$ spark.stop() } } |
其实Estimator类的单独使用,也可以理解为Pipeline对象只有一个Estimator对象。上述的程序来自:\src\main\scala\org\apache\spark\examples\ml\ ElementwiseProductExample.scala
4.2 Pipeline
输入的Dataframe经过PipelineStage对象处理后悔输出新的DataFrame,此时输出的DataFrame会增加一些列,即增加了一些特征,而具体增加什么列,需要看具体是什么PipelineStage对象。
如下所示,输入DataFrame只有三列"id"、"text"、"label",但输出DataFrame不仅保存了输入列,同时增加了一些列。
package org.apache.spark.examples.ml // $example on$ 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 // $example off$ import org.apache.spark.sql.SparkSession object PipelineExample { def main(args: Array[String]): Unit = { val spark = SparkSession .builder .appName("PipelineExample") .getOrCreate() // $example on$ // 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. //Tokenizer功能是对输入的DataFrame某一列进行分割,分割后将数据添加到DataFrame的新列种 val tokenizer = new Tokenizer() .setInputCol("text") //设置输入DataFrame中要处理的列名字 .setOutputCol("words") //设置输出的DataFrame中增加列的名字 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") } // $example off$ spark.stop() } } |
5. 参考文献
Spark MLlib框架详解的更多相关文章
- Spark2.1.0——内置Web框架详解
Spark2.1.0——内置Web框架详解 任何系统都需要提供监控功能,否则在运行期间发生一些异常时,我们将会束手无策.也许有人说,可以增加日志来解决这个问题.日志只能解决你的程序逻辑在运行期的监控, ...
- Spark2.1.0——内置RPC框架详解
Spark2.1.0——内置RPC框架详解 在Spark中很多地方都涉及网络通信,比如Spark各个组件间的消息互通.用户文件与Jar包的上传.节点间的Shuffle过程.Block数据的复制与备份等 ...
- jQuery Validate验证框架详解
转自:http://www.cnblogs.com/linjiqin/p/3431835.html jQuery校验官网地址:http://bassistance.de/jquery-plugins/ ...
- mina框架详解
转:http://blog.csdn.net/w13770269691/article/details/8614584 mina框架详解 分类: web2013-02-26 17:13 12651人 ...
- lombok+slf4j+logback SLF4J和Logback日志框架详解
maven 包依赖 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lomb ...
- [Cocoa]深入浅出 Cocoa 之 Core Data(1)- 框架详解
Core data 是 Cocoa 中处理数据,绑定数据的关键特性,其重要性不言而喻,但也比较复杂.Core Data 相关的类比较多,初学者往往不太容易弄懂.计划用三个教程来讲解这一部分: 框架详解 ...
- iOS 开发之照片框架详解(2)
一. 概况 本文接着 iOS 开发之照片框架详解,侧重介绍在前文中简单介绍过的 PhotoKit 及其与 ALAssetLibrary 的差异,以及如何基于 PhotoKit 与 AlAssetLib ...
- Quartz.NET作业调度框架详解
Quartz.NET作业调度框架详解 http://www.cnblogs.com/lmule/archive/2010/08/28/1811042.html
- mapreduce框架详解
hadoop 学习笔记:mapreduce框架详解 开始聊mapreduce,mapreduce是hadoop的计算框架,我学hadoop是从hive开始入手,再到hdfs,当我学习hdfs时候,就感 ...
随机推荐
- sql server 类oracle vm_contact() 函数创建
CREATE FUNCTION dbo.fun_orgname(@id int)RETURNS varchar(8000)AS BEGIN DECLARE @str varchar(8000 ...
- php调用python
test_python.php <?php $k = $_REQUEST['k']; if (!empty($k)) { $k = trim($k); //$a 用来捕获输出 // 这里的arr ...
- 创建标题栏,UINavigationBar的使用
IOS 开发有关界面的东西不仅可以使用代码来编写,也可以使用Interface Builder可视化工具来编写.今天有个朋友问我这两个有什么区别,首先说说IB ,使用它编辑出来的控件其实底层还是调用代 ...
- vim命令行模式
1. 激活命令行模式 : 进入命令行模式 <Esc> 退出命令行模式 2. 常用命令 :p 打印 (:print) :e 读入文件 (:edit) :w 写入文件 (:write) :t ...
- Win8运行金山词霸2005的问题
一般是以下几种状况: 1.运行进入假死 2.取词模块报错 3.词库突然丢失 原因: 文件权限和注册表权限问题 解决方法: 进入"..\Kingsoft\PowerWord 2005\&quo ...
- CocoaPods报错:The dependency `` is not used in any concrete target
内容提要: podfile升级之后到最新版本,pod里的内容必须明确指出所用第三方库的target,否则会出现The dependency `` is not used in any concrete ...
- ORA-24408: could not generate unique server group name
一台新虚拟机,CentOS 6.5系统,用lnmp一键安装包安装好Nginx + PHP环境,再安装Oracle客户端,准备搭建PHP连接Oracle,访问oracle.php,测试连接Oracle的 ...
- XSD文件详解(二)
<?xml version="1.0" encoding="gb2312"?> <studentlist> <student ...
- IP地址、子网掩码、网关的关系
网络管理中的IP地址.子网掩码和网关是每个网管必须要掌握的基础知识,只有掌握它,才能够真正理解TCP/IP协议的设置.以下我们就来深入浅出地讲解什么是子网掩码. IP地址的结构 要想理解什么是子网掩码 ...
- python第三方库地址
python第三方库的地址: requests: http://docs.python-requests.org/zh_CN/latest/user/quickstart.html beautifus ...