spark.ml机器学习模块

ml模块机器学习方法接收的输入一般都是DataFrame:带列名称的数据集。(mllib模块是旧的接收的输入是RDD)

spark机器学习的几个概念类:

Transformer(转换器):

很多类都是Transformer类的子类(如训练模型类),此类有transform()方法,接受的输入参数和输出的对象都是df(DataFrame对象),它能够根据输入df_in的某个或某些列进行计算,产生另一些列,然后增加到输出df_out上(输入df_in不会变)。如一个分类模型能够根据输入df_in的特征列(默认为”feature”列,可以通过featuresCol(value: String)方法设置具体哪是特征列),为输出df_out产生新的一些列,如预测结果列等等。

Estimator(模型学习器):

Estimator类有一个fit()方法,能够接受 df 参数来输出 Transformer对象。例如Classifier类就是一个 Estimator的子类,能够根据df输出一个分类模型(Transformer的子类)。

Pipeline(管道):

Pipeline 将多个 Transformer 和 Estimator 绑在一起形成一个工作流。一个管道包含一系列的转换、学习等步骤,也就是这个管道对象封装了机器学习的整个过程。
此外一个Pipeline对象本身也是一个Estimator,其fit方法能够产生一个PipelineModel对象(Transformer的子类),这个模型对象记录了这个管道封装的所有操作,其transform方法就可以接收原始的df,然后产生预测结果,这样能够保证学习过程和测试、预测过程的统一。

Parameter(参数):

所有的 Transformer和 Estimator 都可以使用标准的 API 来指定参数(就是使用spark.ml.param包下的类)。其中三个重要的类:Param(描述一个参数,如参数maxIter表示最大迭代次数),ParamPair(一个参数和参数的值,相当于键值对),ParamMap(一组ParamPair,可以用put方法放入ParamPair)。
指定Transformer和Estimator的参数一般有两种方法:用原始的set方法;还有一种统一的方法:往transform()或者fit()方法传入df的同时传入ParamMap对象来指定。
Param对像属于特定的Transformer或Estimator对象实例, 如我们有两个 LogisticRegression 实例对象 lr1 和 lr2,lr1.maxIter和lr2.maxIter返回的是不同的Param对象。Param对象有->方法,能返回ParamPair对象,如可以这样:

val pm = new ParamMap().put(lr1.maxIter -> 20)

例子:

不使用管道(涉及 Estimator、Transformer和 Param的概念)

import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.ml.linalg.Vector
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.ml.param.ParamMap
import org.apache.spark.sql.Row
//Estimator或Transformer都会有以输入df的一些列作为输入,具体是那些列、输出的列名等都可以通过参数来指定。
//如分类器的默认输入特征列列名就为"features", 默认类别名为"label"
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")  
/*
training.show
    +-----+--------------+
    |label|      features|
    +-----+--------------+
    |  1.0| [0.0,1.1,0.1]|
    |  0.0|[2.0,1.0,-1.0]|
    |  0.0| [2.0,1.3,1.0]|
    |  1.0|[0.0,1.2,-0.5]|
    +-----+--------------+
​
training.printSchema
    root
     |-- label: double (nullable = false)
     |-- features: vector (nullable = true)
*/

val lr = new LogisticRegression()
println("LogisticRegression学习类的参数和说明:\n" + lr.explainParams() + "\n")
​
lr.setMaxIter(10).setRegParam(0.01)  // 采用set方法设置参数:最大迭代次数,正则化参数值val model = lr.fit(training)
println("使用如下参数进行模型训练model: " + model.parent.extractParamMap) 
// model.parent方法返回产生这个模型的Estimator实例(这里就是lr对象),此处打印所有lr的参数当前值

// 另一种定义参数的方式
val paramMap = ParamMap(lr.maxIter -> 20) // ParamMap伴生对象的apply方法接受ParamPair对象作参数返回ParamMap对象
    .put(lr.maxIter, 30)
    .put(lr.regParam -> 0.1, lr.threshold -> 0.55)
​
val paramMap2 = ParamMap(lr.probabilityCol -> "myProbability") // 定义转换后新增列(之一)的列名
val paramMapCombined = paramMap ++ paramMap2  // 合并两个参数组val model2 = lr.fit(training, paramMapCombined)  // 指定参数,会覆盖之前用set设置的参数。
println("使用如下参数进行模型训练model2: " + model2.parent.extractParamMap)
​
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")
​
model2.transform(test). //用model2对test进行转换(只会使用test的特征列,默认为"features"列),会生成预测列、概率列等新列
     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")
    }
/*
    ([-1.0,1.5,1.3], 1.0) -> prob=[0.0019457211894662078,0.9980542788105338], prediction=1.0
    ([3.0,2.0,-0.1], 0.0) -> prob=[0.995741257377319,0.004258742622680961], prediction=0.0
    ([0.0,2.2,-1.5], 1.0) -> prob=[0.012313218764735448,0.9876867812352644], prediction=1.0
*/

使用管道:

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
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")
/*
training.show(false)
    +---+----------------+-----+
    | id|            text|label|
    +---+----------------+-----+
    |  0| a b c d e spark|  1.0|
    |  1|             b d|  0.0|
    |  2|     spark f g h|  1.0|
    |  3|hadoop mapreduce|  0.0|
    +---+----------------+-----+
*/// 定义一个Transformer:将输入的text列全部转为小写,然后以空格进行分割,生成的新列名为words
val tokenizer = new Tokenizer().setInputCol("text").setOutputCol("words")
// 定义一个Transformer:以tokenizer的输出列为输入进行处理,生成的新列名为features,具体怎么处理后面进行说明
val hashingTF = new HashingTF().setNumFeatures(1024).setInputCol(tokenizer.getOutputCol).setOutputCol("features")
// 定义一个Estimator:会以默认的features和label列为输入,生成逻辑回归模型(此例用管道,这个模型会封装在管道模型里,作为管道模型的最后一个stage)
val lr = new LogisticRegression().setMaxIter(10).setRegParam(0.001)
// 配置一个管道学习器对象,其由三个阶段(stages)组成: tokenizer, hashingTF, and lr.
val pipeline = new Pipeline().setStages(Array(tokenizer, hashingTF, lr))
​
// 使用管道学习器对训练数据进行学习,生成管道模型
val pipelineModel = pipeline.fit(training)
​
// 保存管道模型
pipelineModel.write.overwrite().save("/tmp/spark-logistic-regression-model")
​
// 保存管道学习器
pipeline.write.overwrite().save("/tmp/unfit-lr-model")
​
// 加载管道模型
val sameModel = PipelineModel.load("/tmp/spark-logistic-regression-model")
​
val test = spark.createDataFrame(Seq((4L, "spark i j k"),
    (5L, "l m n"),
    (6L, "spark hadoop spark"),
    (7L, "apache hadoop"))).toDF("id", "text")
​
// 对测试数据集进行预测转换
pipelineModel.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")
    }
/*
    (4, spark i j k) --> prob=[0.1596407738787413,0.8403592261212588], prediction=1.0
    (5, l m n) --> prob=[0.8378325685476613,0.16216743145233864], prediction=0.0
    (6, spark hadoop spark) --> prob=[0.06926633132976279,0.9307336686702373], prediction=1.0
    (7, apache hadoop) --> prob=[0.9821575333444208,0.017842466655579155], prediction=0.0
*/

保存模型(或学习器)其实就是保存的一系列文本文件,如上面的lr学习器和其生成的模型的存储文件内容如下:

//学习器
{
    "class":"org.apache.spark.ml.classification.LogisticRegression",
    "timestamp":1518060953377,
    "sparkVersion":"2.2.1",
    "uid":"logreg_4277d3627598",
    "paramMap":{
        "maxIter":10,
        "elasticNetParam":0,
        "featuresCol":"features",
        "labelCol":"label",
        "rawPredictionCol":"rawPrediction",
        "aggregationDepth":2,
        "regParam":0.001,
        "probabilityCol":"probability",
        "tol":0.000001,
        "family":"auto",
        "standardization":true,
        "predictionCol":"prediction",
        "fitIntercept":true,
        "threshold":0.5
    }
}
//模型(同上面参数一样)
{
    "class":"org.apache.spark.ml.classification.LogisticRegressionModel",
    "timestamp":1518060952613,
    "sparkVersion":"2.2.1",
    "uid":"logreg_4277d3627598",
    "paramMap":{
        "maxIter":10,
        "elasticNetParam":0,
        "featuresCol":"features",
        "labelCol":"label",
        "rawPredictionCol":"rawPrediction",
        "aggregationDepth":2,
        "regParam":0.001,
        "probabilityCol":"probability",
        "tol":0.000001,
        "standardization":true,
        "family":"auto",
        "predictionCol":"prediction",
        "fitIntercept":true,
        "threshold":0.5
    }
}
​
// 保存模型比学习器多存储了data文件夹,里面是parquet文件,用spark SQL加载后内容如下:
+----------+-----------+--------------------+--------------------+-------------+
|numClasses|numFeatures|     interceptVector|   coefficientMatrix|isMultinomial|
+----------+-----------+--------------------+--------------------+-------------+
|         2|       1024|[-1.6421889526563...|1 x 1024 CSCMatri...|        false|
+----------+-----------+--------------------+--------------------+-------------+

猜你喜欢

转载自blog.csdn.net/xuejianbest/article/details/80405931