Spark2.1.0入门:模型选择和超参数调整

大数据技术原理与应用

【版权声明】博客内容由厦门大学数据库实验室拥有版权,未经允许,请勿转载!
[返回Spark教程首页]

## 模型选择和超参数调整

在机器学习中非常重要的任务就是模型选择,或者使用数据来找到具体问题的最佳的模型和参数,这个过程也叫做调试(Tuning)。调试可以在独立的估计器中完成(如逻辑斯蒂回归),也可以在包含多样算法、特征工程和其他步骤的工作流中完成。用户应该一次性调优整个工作流,而不是独立的调整PipeLine中的每个组成部分。

1、 交叉验证和训练-验证切分

    MLlib支持交叉验证(CrossValidator)和训练验证分割(TrainValidationSplit)两个模型选择工具。使用这些工具要求包含如下对象:

1.估计器:待调试的算法或管线。

2.一系列参数表(ParamMaps):可选参数,也叫做“参数网格”搜索空间。

3.评估器:评估模型拟合程度的准则或方法。

模型选择工具工作原理如下:

1.将输入数据划分为训练数据和测试数据。

2. 对于每个(训练,测试)对,遍历一组ParamMaps。用每一个ParamMap参数来拟合估计器,得到训练后的模型,再使用评估器来评估模型表现。

3.选择性能表现最优模型对应参数表。

更具体的,交叉验证CrossValidato将数据集切分成k折叠数据集合,并被分别用于训练和测试。例如,k=3时,CrossValidator会生成3个(训练数据,测试数据)对,每一个数据对的训练数据占2/3,测试数据占1/3。为了评估一个ParamMap,CrossValidator 会计算这3个不同的(训练,测试)数据集对在Estimator拟合出的模型上的平均评估指标。在找出最好的ParamMap后,CrossValidator 会使用这个ParamMap和整个的数据集来重新拟合Estimator。也就是说通过交叉验证找到最佳的ParamMap,利用此ParamMap在整个训练集上可以训练(fit)出一个泛化能力强,误差相对小的的最佳模型。

交叉验证的代价比较高昂,为此Spark也为超参数调优提供了训练-验证切分TrainValidationSplit。TrainValidationSplit创建单一的(训练,测试)数据集对。它使用trainRatio参数将数据集切分成两部分。例如,当设置trainRatio=0.75时,TrainValidationSplit将会将数据切分75%作为数据集,25%作为验证集,来生成训练、测试集对,并最终使用最好的ParamMap和完整的数据集来拟合评估器。相对于CrossValidator对每一个参数进行k次评估,TrainValidationSplit只对每个参数组合评估1次。因此它的评估代价没有这么高,但是当训练数据集不够大的时候其结果相对不够可信。

2、使用交叉验证进行模型选择

使用CrossValidator的代价可能会异常的高。然而,对比启发式的手动调优,这是选择参数的行之有效的方法。下面示例示范了使用CrossValidator从整个网格的参数中选择合适的参数。

首先,导入必要的包:

  import org.apache.spark.ml.linalg.{Vector,Vectors}
  import spark.implicits._
  import org.apache.spark.ml.feature.{HashingTF, Tokenizer}
  import org.apache.spark.ml.tuning.{CrossValidator, ParamGridBuilder}
  import org.apache.spark.sql.Row
  import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
  import org.apache.spark.ml.feature.{IndexToString, StringIndexer, VectorIndexer}
  import org.apache.spark.ml.classification.{LogisticRegression,LogisticRegressionModel}
  import org.apache.spark.ml.{Pipeline,PipelineModel}

接下来,读取Irisi数据集,分别获取标签列和特征列,进行索引、重命名,并设置机器学习工作流。交叉验证在把原始数据集分割为训练集与测试集。值得注意的是,只有训练集才可以用在模型的训练过程中,测试集则必须在模型完成之后才被用来评估模型优劣的依据。此外,训练集中样本数量必须够多,一般至少大于总样本数的 50%,且两组子集必须从完整集合中均匀取样。

 scala> case class Iris(features: org.apache.spark.ml.linalg.Vector, label: String)

 scala> val data = spark.sparkContext.textFile("file:///usr/local/spark/iris.txt").map(\_.split(",")).map(p => Iris(Vectors.dense(p(0).toDouble,p(1).toDouble,p(2).toDouble, p(3).toDouble), p(4).toString())).toDF()

 scala>val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))

 scala>val labelIndexer = new StringIndexer().setInputCol("label").setOutputCol("indexedLabel").fit(data)

scala> val featureIndexer = new VectorIndexer().setInputCol("features").setOutputCol("indexedFeatures").fit(data)

 scala> val lr = new LogisticRegression().setLabelCol("indexedLabel").setFeaturesCol("indexedFeatures").setMaxIter(50)

 scala>val labelConverter = new IndexToString().setInputCol("prediction").setOutputCol("predictedLabel").setLabels(labelIndexer.labels)


 scala> val lrPipeline = new Pipeline().setStages(Array(labelIndexer, featureIndexer, lr, labelConverter))

可以使用ParamGridBuilder方便构造参数网格。其中regParam参数定义规范化项的权重;elasticNetParam是Elastic net 参数,取值介于0和1之间。elasticNetParam设置2个值,regParam设置3个值。最终将有(3 * 2) = 6个不同的模型将被训练。

scala> val paramGrid = new ParamGridBuilder().
      addGrid(lr.elasticNetParam, Array(0.2,0.8)).
      addGrid(lr.regParam, Array(0.01, 0.1, 0.5)).
      build()


  paramGrid: Array[org.apache.spark.ml.param.ParamMap] =
  Array({
  logreg_cd4ae130834c-elasticNetParam: 0.2,
  logreg_cd4ae130834c-regParam: 0.01
  }, {
  logreg_cd4ae130834c-elasticNetParam: 0.2,
  logreg_cd4ae130834c-regParam: 0.1
  }, {
  logreg_cd4ae130834c-elasticNetParam: 0.2,
  logreg_cd4ae130834c-regParam: 0.5
  }, {
  logreg_cd4ae130834c-elasticNetParam: 0.8,
  logreg_cd4ae130834c-regParam: 0.01
  }, {
  logreg_cd4ae130834c-elasticNetParam: 0.8,
  logreg_cd4ae130834c-regParam: 0.1
  }, {
  logreg_cd4ae130834c-elasticNetParam: 0.8,
  logreg_cd4ae130834c-regParam: 0.5
  })

再接下来,构建针对整个机器学习工作流的交叉验证类,定义验证模型、参数网格,以及数据集的折叠数,并调用fit方法进行模型训练。其中,对于回归问题评估器可选择RegressionEvaluator,二值数据可选择BinaryClassificationEvaluator,多分类问题可选择MulticlassClassificationEvaluator。评估器里默认的评估准则可通过setMetricName方法重写。

scala> val cv = new CrossValidator().
 setEstimator(lrPipeline).
  setEvaluator(new MulticlassClassificationEvaluator().setLabelCol("indexedLabel").setPredictionCol("prediction")).
  setEstimatorParamMaps(paramGrid).
  setNumFolds(3) // Use 3+ in practice
cv: org.apache.spark.ml.tuning.CrossValidator = cv_6a92251c1281

scala>val cvModel = cv.fit(trainingData)
cvModel: org.apache.spark.ml.tuning.CrossValidatorModel = cv_6a92251c1281

接下来,调动transform方法对测试数据进行预测,并打印结果及精度。

scala>  val lrPredictions=cvModel.transform(testData)
lrPredictions: org.apache.spark.sql.DataFrame = [features: vector, label: string ... 6 more fields]

scala> lrPredictions.select("predictedLabel", "label", "features", "probability").
  collect().
  foreach{
    case Row(predictedLabel: String, label:String,features:Vector, prob:Vector) => 
    println(s"($label, $features) --> prob=$prob, predicted Label=$predictedLabel")     
  }
(Iris-setosa, [4.4,2.9,1.4,0.2]) --> prob=[0.0363183436604630=&gh2=$pator(lrPipelinob=$ache.sp(Iris-s
 s诐g, labs]

scaame"ato	 ne)68交叉验_-> prob=[la>or3--> prob=[0.036318342ipe989551813,8.133697579147449E-5sp(I7681883882ob=0 labs]

scaame"ato	 ne)68交叉验_-> prob=54,03tor3--&3t; prob=[0.0363183295171926064505sp(00ode21581
68nob39sp(70/big9216786864labs]

scaame"ato	 ne)68交叉验_-> prob=7la&2tor3--> prob=[0.0363183416215Pip15044659la&62193ip15640408E-5sp(I83748150519397labs]

scaame"ato	 ne)68交叉验_)

接下杜rossVa还练'签刡型倂焯蒂回归),伌再使甹查看其,交彑格:re>scala> val lrPredibestransf=l = cv.f.bestransf.asInd heceOf[eModel} id). eew I(Predrop) ( single }, ,t-path"> }, {)gt; lrPrediPlr },2e>

可以使用P看出回归闑格,以及其型定义觋于0是m: 0.5 =}, {NetParam: 0.8, =},2

首先​