Spark MLlib机器学习库(一)决策树和随机森林案例详解

Spark MLlib机器学习库(一)决策树和随机森林案例详解

1 决策树预测森林植被

1.1 Covtype数据集

数据集的下载地址: https://www.kaggle.com/datasets/uciml/forest-cover-type-dataset

该数据集记录了美国科罗拉多州不同地块的森林植被类型,每个样本包含了描述每块土地的若干特征,包括海拔、坡度、到水源的距离、遮阳情况和土壤类型,并且给出了地块对应的已知森林植被类型。

很自然地,我们把该数据解析成 DataFrame,因为 DataFrame 就是 Spark 针对表格数据的抽象,它有定义好的模式,包括列名和列类型。

package com.yyds

import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.sql.functions._
import org.apache.spark.ml.classification.{DecisionTreeClassificationModel, DecisionTreeClassifier}
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
import org.apache.spark.mllib.evaluation.MulticlassMetrics
import org.apache.spark.ml.{Model, Pipeline, PipelineModel, Transformer}
import org.apache.spark.ml.tuning.{ParamGridBuilder, TrainValidationSplit, TrainValidationSplitModel}


object DecisionTreeTest {

  Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)


  def main(args: Array[String]): Unit = {


    // 构建SparkSession实例对象,通过建造者模式创建
    val spark: SparkSession = {
      SparkSession
        .builder()
        .appName(this.getClass.getSimpleName.stripSuffix("$"))
        .master("local[1]")
        .config("spark.sql.shuffle.partitions", "3")
        .getOrCreate()
    }


    // 利用Spark内置的读取CSV数据功能
    val dataWithHeader = spark.read
                              .option("inferSchema", "true") // 数据类型推断
                              .option("header", "true") // 表头解析
                              .csv("D:\\kaggle\\covtype\\covtype.csv")

    // 检查一下列名,可以清楚地看到,有些特征确实是数值型。
    // 但是“荒野区域(Wilderness_Area)”有些不同,因为它横跨4列,每列要么为 0,要么为 1。
    // 实际上荒野区域是一个类别型特征,而非数值型。采用了one-hot编码
    // 同样,Soil_Type(40列)也是one-hot编码。
    dataWithHeader.printSchema()
    dataWithHeader.show(10)
    
  }

}

解释一下one-hot编码

one-hot编码:一个有N个不同取值的类别型特征可以变成 N 个数值型特征,变换后的每个数值型特征的取值为 01。在这 N 个特征中,有且只有一个取值为 1,其他特征取值都为 0。

比如,类别型特征“天气”可能的取值有“多云”“有雨”或“晴朗”。
在one-hot 编码中,它就变成了 3 个数值型特征:
多云用 1,0,0 表示,
有雨用 0,1,0 表示,
晴朗用 0,0,1 表示。


不过,这并不是将分类特性编码为数值的唯一方法。
另一种可能的编码方式是为类别型特征的每个可能取值分配一个不同数值,比如多云 1.0、有雨 2.0 等。目标“Cover_Type”本身也是
类别型值,用 1~7 编码。


在编码过程中,将类别型特征当成数值型特征时要小心。类别型特征值原本是没有大小顺序可言的,但被编码为数值之后,它们就“显得”有大小顺序
了。被编码后的特征若被视为数值,算法在一定程度上会假定有雨比多云大,而且大两倍,这样就可能导致不合理的结果。

1.2 建立决策树模型

1.2.1 原始特征组合为特征向量

    // 划分训练集和测试集
    val Array(trainData, testData) = dataWithHeader.randomSplit(Array(0.9, 0.1))
    trainData.cache()
    testData.cache()    

    // 输入的DataFrame包含许多列,每列对应一个特征,可以用来预测目标列。
    // Spark MLlib 要求将所有输入合并成一列,该列的值是一个向量。
    // 我们可以利用VectorAssembler将特征转换为向量
    val inputCols: Array[String] = trainData.columns.filter(_ != "Cover_Type")
    val assembler: VectorAssembler = new VectorAssembler()
      .setInputCols(inputCols) // 除了目标列以外,所有其他列都作为输入特征,因此产生的DataFrame有一个新的“featureVector”
      .setOutputCol("featureVector")

    val assembledTrainData: DataFrame = assembler.transform(trainData)

    assembledTrainData.select(
      col("featureVector")
    ).show(10,truncate = false)
+----------------------------------------------------------------------------------------------------+
|featureVector                                                                                       |
+----------------------------------------------------------------------------------------------------+
|(54,[0,1,2,3,4,5,6,7,8,9,13,15],[1859.0,18.0,12.0,67.0,11.0,90.0,211.0,215.0,139.0,792.0,1.0,1.0])  |
|(54,[0,1,2,3,4,5,6,7,8,9,13,15],[1860.0,18.0,13.0,95.0,15.0,90.0,210.0,213.0,138.0,780.0,1.0,1.0])  |
|(54,[0,1,2,3,4,5,6,7,8,9,13,15],[1861.0,35.0,14.0,60.0,11.0,85.0,218.0,209.0,124.0,832.0,1.0,1.0])  |
|(54,[0,1,2,3,4,5,6,7,8,9,13,15],[1866.0,23.0,14.0,85.0,16.0,108.0,212.0,210.0,133.0,819.0,1.0,1.0]) |
|(54,[0,1,2,3,4,5,6,7,8,9,13,15],[1867.0,20.0,15.0,108.0,19.0,120.0,208.0,206.0,132.0,808.0,1.0,1.0])|
|(54,[0,1,2,3,4,5,6,7,8,9,13,15],[1868.0,27.0,16.0,67.0,17.0,95.0,212.0,204.0,125.0,859.0,1.0,1.0])  |
|(54,[0,1,2,3,4,5,6,7,8,9,13,18],[1871.0,22.0,22.0,60.0,12.0,85.0,200.0,187.0,115.0,792.0,1.0,1.0])  |
|(54,[0,1,2,3,4,5,6,7,8,9,13,15],[1871.0,36.0,19.0,134.0,26.0,120.0,215.0,194.0,107.0,797.0,1.0,1.0])|
|(54,[0,1,2,3,4,5,6,7,8,9,13,15],[1871.0,37.0,19.0,120.0,29.0,90.0,216.0,195.0,107.0,759.0,1.0,1.0]) |
|(54,[0,1,2,3,4,5,6,7,8,9,13,18],[1872.0,12.0,27.0,85.0,25.0,60.0,182.0,174.0,118.0,577.0,1.0,1.0])  |
+----------------------------------------------------------------------------------------------------+
  • 输出看起来不是很像一串数字,这是因为它显示的是向量的原始表示,也就是 Sparse Vector(稀疏向量) 的实例,这样做可以节省存储空间。由于这 54 个值中的大多数值都是 0,它仅存储非零值及其索引。

  • VectorAssembler是当前 Spark MLlib管道(Pipeline) API 中的一个 Transformer 示例。 VectorAssembler可以将一个 DataFrame 转换成另外一个 DataFrame,并且可以和其他 Transformer 组合成一个管道。在后面,我们将这些转换操作将连接成一个真正的管道。

1.2.2 构建决策树

  val classifier = new DecisionTreeClassifier()
                        .setSeed(Random.nextLong())  // 随机数种子
                        .setLabelCol("Cover_Type")  // 目标列
                        .setFeaturesCol("featureVector") // 准换后的特征列
                        .setPredictionCol("prediction") // 预测列的名称
    
    // DecisionTreeClassificationModel本身就是一个转换器
    // 它可以将一个包含特征向量的 DataFrame 转换成一个包含特征向量及其预测结果的 DataFrame
    val model: DecisionTreeClassificationModel = classifier.fit(assembledTrainData)

    println(model.toDebugString)
DecisionTreeClassificationModel: uid=dtc_54cb31909b32, depth=5, numNodes=51, numClasses=8, numFeatures=54
  If (feature 0 <= 3048.5)
   If (feature 0 <= 2559.5)
    If (feature 10 <= 0.5)
     If (feature 0 <= 2459.5)
      If (feature 3 <= 15.0)
       Predict: 4.0
      Else (feature 3 > 15.0)
       Predict: 3.0
     Else (feature 0 > 2459.5)
      If (feature 17 <= 0.5)
       Predict: 2.0
      Else (feature 17 > 0.5)
       Predict: 3.0
    Else (feature 10 > 0.5)
     If (feature 9 <= 5129.0)
      Predict: 2.0
     Else (feature 9 > 5129.0)
      If (feature 5 <= 569.5)
       Predict: 2.0
      Else (feature 5 > 569.5)
       Predict: 5.0
       ......

依据上面模型表示方式的输出信息,我们可以发现模型的一些树结构。它由一系列针对特征的嵌套决策组成,这些决策将特征值与阈值相比较。

构建决策树的过程中,决策树能够评估输入特征的重要性。也就是说,它们可以评估每个输入特征对做出正确预测的贡献值。从模型中很容易获得这个信息。

// 把列名及其重要性(越高越好)关联成二元组,并按照重要性从高到低排列输出。 
// Elevation 似乎是绝对重要的特征;其他的大多数特征在预测植被类型时几乎没有任何作用!
model.featureImportances.toArray.zip(inputCols).sorted.reverse.foreach(println)
(0.8066003452907752,Elevation)
(0.04178573786315329,Horizontal_Distance_To_Hydrology)
(0.03280245080822316,Wilderness_Area1)
(0.030257284101934206,Soil_Type4)
(0.02562302586398405,Hillshade_Noon)
(0.023493741973492223,Soil_Type2)
(0.016910986928613186,Soil_Type32)
(0.011741228151910562,Wilderness_Area3)
(0.005884894981433861,Soil_Type23)
(0.0027811902118641293,Hillshade_9am)
(0.0021191138246161745,Horizontal_Distance_To_Roadways)
(0.0,Wilderness_Area4)
(0.0,Wilderness_Area2)
(0.0,Vertical_Distance_To_Hydrology)
(0.0,Soil_Type9)
......

1.2.3 预测

    // 比较一下模型预测值与正确的覆盖类型
    val predictions = model.transform(assembledTrainData)
    
    predictions
      .select("Cover_Type", "prediction", "probability")
      .show(10, truncate = false)

在这里插入图片描述

  • 输出还包含了一个probability列,它给出了模型对每个可能的输出的准确率的估计。

  • 尽管只有 7 种可能的结果,而概率向量实际上有 8 个值。向量中索引 1~7 的值分别表示结果为 1~7 的概率。然而,索引 0 也有一个值,它总是显示概率为“0.0”。我们可以忽略它,因为 0 根本就不是一个有效的结果。

  • 决策树分类器的实现有几个超参数需要调整,这段代码中使用的都是默认值。

1.2.4 评估模型训练

     // 评估训练质量
    val evaluator = new MulticlassClassificationEvaluator()
                      .setLabelCol("Cover_Type")
                      .setPredictionCol("prediction")


    println("准确率:" + evaluator.setMetricName("accuracy").evaluate(predictions))
    println("f1值:" + evaluator.setMetricName("f1").evaluate(predictions))
准确率:0.7007016671765066
f1值:0.6810479157002327

混淆矩阵

单个的准确率可以很好地概括分类器输出的好坏,然而有时候混淆矩阵(confusion matrix)会更有效。

混淆矩阵是一个 N× N 的表, N 代表可能的目标值的个数。因为我们的目标值有 7 个分类,所以是一个 7× 7 的矩阵,每一行代表数据的真实归属类别,每一列按顺序依次代表预测值。第 i 行和第 j 列的条目表示数据中真正归属第 i 个类别却被预测为第 j 个类别的数据总量。因此,正确的预测是沿着对角线计算的,而非对角线元素代表错误预测。

   // 混淆矩阵,Spark 提供了用于计算混淆矩阵的代码;不幸的是,这个代码是基于操作 RDD的旧版 MLlib API 实现的
    val predictionRDD = predictions
                      .select("prediction", "Cover_Type")
                      .as[(Double,Double)] // 转换成 Dataset,需要导入隐式准换 import spark.implicits._
                      .rdd // 准换为rdd

    val multiclassMetrics = new MulticlassMetrics(predictionRDD)
    println("混淆矩阵:")
    println(multiclassMetrics.confusionMatrix)
混淆矩阵:
130028.0  55161.0   187.0    0.0    0.0  0.0  5175.0   
50732.0   196315.0  7163.0   53.0   0.0  0.0  762.0    
0.0       2600.0    29030.0  600.0  0.0  0.0  0.0      
0.0       0.0       1487.0   967.0  0.0  0.0  0.0      
12.0      7743.0    755.0    0.0    0.0  0.0  0.0      
0.0       3478.0    11812.0  387.0  0.0  0.0  0.0      
7923.0    193.0     60.0     0.0    0.0  0.0  10275.0  

对角线上的次数多是好的。但也确实出现了一些分类错误的情况,比如分类器甚至没有将任何一个样本类别预测为 5。

     //  当然,计算混淆矩阵之类,也可以直接使用 DataFrame API 中一些通用的操作,而不再需要依赖专门的方法。
    //  透视Pivot
    //  透视操作简单直接,逻辑如下
    //  1、按照不需要转换的字段分组,本例中是Cover_Type;
    //  2、使用pivot函数进行透视,透视过程中可以提供第二个参数来明确指定使用哪些数据项;
    //  3、汇总数字字段

    val confusionMatrix = predictions
      .groupBy("Cover_Type")
      .pivot("prediction", (1 to 7)) //透视可以视为一个聚合操作,通过该操作可以将一个(实际当中也可能是多个)具有不同值的分组列转置为各个独立的列
      .count()
      .na.fill(0.0)   // 用 0 替换 null
      .orderBy("Cover_Type")


    confusionMatrix.show()
+----------+------+------+-----+---+---+---+-----+
|Cover_Type|     1|     2|    3|  4|  5|  6|    7|
+----------+------+------+-----+---+---+---+-----+
|         1|130028| 55161|  187|  0|  0|  0| 5175|
|         2| 50732|196315| 7163| 53|  0|  0|  762|
|         3|     0|  2600|29030|600|  0|  0|    0|
|         4|     0|     0| 1487|967|  0|  0|    0|
|         5|    12|  7743|  755|  0|  0|  0|    0|
|         6|     0|  3478|11812|387|  0|  0|    0|
|         7|  7923|   193|   60|  0|  0|  0|10275|
+----------+------+------+-----+---+---+---+-----+

70% 的准确率是用默认超参数取得的。如果在决策树构建过程中试试超参数的其他值,准确率还可以提高。

1.3 决策树的超参数

决策树的重要的超参数如下:

  • 最大深度

    • 最大深度只是对决策树的层数做出限制,它是分类器为了对样本进行分类所做的一连串判
      断的最大次数。限制判断次数有利于避免对训练数据产生过拟合
  • 最大桶数

    • 决策树算法负责为每层生成可能的决策规则,这些决策规则类似“重量≥ 100”或者“重量≥ 500”。

    • 决策总是采用相同形式:对数值型特征, 决策采用特征≥值的形式;对类别型特征,决策采用特征在(值 1, 值 2,…)中的形式。因此,要尝试的决策规则集合实际上是可以嵌入决策规则中的一系列值。

    • Spark MLlib 的实现把决策规则集合称为“桶”(bin)。桶的数目越多,需要的处理时间越多,但找到的决策规则可能更优

  • 不纯性度量

    • 好规则把训练集数据的目标值分为相对是同类或“纯”(pure)的子集。

    • 选择最好的规则也就意味着最小化规则对应的两个子集的不纯性(impurity)。

    • 不纯性有两种常用的度量方式: Gini不纯度(spark默认参数)或熵

  • 最小信息增益

    • 利于避免过拟合

1.4 决策树超参数调优

采用哪个不纯性度量所得到的决策树的准确率更高,或者最大深度或桶数取多少合适,我们可以让 Spark 来尝试这些值的许多组合。

首先,有必要构建一个管道,用于封装与上面相同的两个步骤。创建 VectorAssembler 和DecisionTreeClassifier,然后将这两个 Transformer 串起来,我们就可以得到一个单独的Pipeline 对象,这个 Pipeline 对象可以将前面的两个操作表示成一个 。

   val newAssembler = new VectorAssembler()
                        .setInputCols(inputCols)
                        .setOutputCol("featureVector")

    // 在这里我们先不设置超参数
    val newClassifier = new DecisionTreeClassifier()
                        .setSeed(Random.nextLong())
                        .setLabelCol("Cover_Type")
                        .setFeaturesCol("featureVector")
                        .setPredictionCol("prediction")

    // 组合为Pipeline
    val pipeline = new Pipeline().setStages(Array(newAssembler, newClassifier))

    // 使用 SparkML API 内建支持的 ParamGridBuilder 来测试超参数的组合
    val paramGrid = new ParamGridBuilder() // 4个超参数来说,每个超参数的两个值都要构建和评估一个模型,共计16种超参数组合,会训练出16个模型
                      .addGrid(newClassifier.impurity, Seq("gini", "entropy"))
                      .addGrid(newClassifier.maxDepth, Seq(1, 20))
                      .addGrid(newClassifier.maxBins, Seq(40, 300))
                      .addGrid(newClassifier.minInfoGain, Seq(0.0, 0.05))
                      .build()

    // 设定评估指标  准确率
    val multiclassEval = new MulticlassClassificationEvaluator()
                              .setLabelCol("Cover_Type")
                              .setPredictionCol("prediction")
                              .setMetricName("accuracy")

    // 这里也可以用 CrossValidator 执行完整的 k 路交叉验证,但是要额外付出 k 倍的代价,并且在大数据的情况下意义不大。
    // 所以在这里 TrainValidationSplit 就够用了
    val validator = new TrainValidationSplit()
                        .setSeed(Random.nextLong())
                        .setEstimator(pipeline)           // 管道
                        .setEvaluator(multiclassEval)     // 评估器
                        .setEstimatorParamMaps(paramGrid) // 超参数组合
                        .setTrainRatio(0.9)               // 训练数据实际上被TrainValidationSplit 划分成90%与10%的两个子集

    val validatorModel: TrainValidationSplitModel = validator.fit(trainData)

    // validator 的结果包含它找到的最优模型。
    val bestModel = validatorModel.bestModel

    // 打印最优模型参数
    // 手动从结果 PipelineModel 中提取 DecisionTreeClassificationModel 的实例,然后提取参数
    println(bestModel.asInstanceOf[PipelineModel].stages.last.extractParamMap)
{
	dtc_1d4212c56614-cacheNodeIds: false,
	dtc_1d4212c56614-checkpointInterval: 10,
	dtc_1d4212c56614-featuresCol: featureVector,
	dtc_1d4212c56614-impurity: entropy,
	dtc_1d4212c56614-labelCol: Cover_Type,
	dtc_1d4212c56614-leafCol: ,
	dtc_1d4212c56614-maxBins: 40,
	dtc_1d4212c56614-maxDepth: 20,
	dtc_1d4212c56614-maxMemoryInMB: 256,
	dtc_1d4212c56614-minInfoGain: 0.0,
	dtc_1d4212c56614-minInstancesPerNode: 1,
	dtc_1d4212c56614-minWeightFractionPerNode: 0.0,
	dtc_1d4212c56614-predictionCol: prediction,
	dtc_1d4212c56614-probabilityCol: probability,
	dtc_1d4212c56614-rawPredictionCol: rawPrediction,
	dtc_1d4212c56614-seed: 2458929424685097192
}

这包含了很多拟合模型的信息:

  • “熵”作为不纯度的度量是最有效的

  • 最大深度 20 比 1 好,也在我们意料之中。

  • 最好的模型仅拟合到 40 个桶(bin),这一点倒可能让人有些意外,但这也可能意味着 40 个桶已经“足够好了”,而不是说拟合到 40 个桶比300 个桶“更好”。

  • 最后, minInfoGain 的值为 0,这比不为零的最小值要更好,因为这可能意味着模型更容易欠拟(underfit),而不是过拟合(overfit)

超参数和评估结果分别用 getEstimatorParamMaps 和 validationMetrics获得

    // 超参数和评估结果分别用 getEstimatorParamMaps 和 validationMetrics 获得。
    // 我们可以获取每一组超参数和其评估结果
 
    val paramsAndMetrics = validatorModel.validationMetrics.zip(validatorModel.getEstimatorParamMaps).sortBy(-_._1)
    
    
    paramsAndMetrics.foreach { case (metric, params) =>
      println(metric)
      println(params)
      println()
    }
0.9083158925519863
{
	dtc_5c5081d572b6-impurity: entropy,
	dtc_5c5081d572b6-maxBins: 40,
	dtc_5c5081d572b6-maxDepth: 20,
	dtc_5c5081d572b6-minInfoGain: 0.0
}
......
    // 这个模型在交叉验证集中达到的准确率是多少?最后,在测试集中能达到什么样的准确率?
    println("交叉验证集上最大准确率:" + validatorModel.validationMetrics.max)
    println("测试集上的准确率:" + multiclassEval.evaluate(bestModel.transform(testData)))
交叉验证集上最大准确率:0.9083158925519863
测试集上的准确率:0.9134603776838838

2 随机森林

  • 在决策树的每层,算法并不会考虑所有可能的决策规则。如果在每层上都要考虑所有可能的决策规则,算法的运行时间将无法想象。对一个有 N 个取值的类别型特征,总共有 2^N –2 个可能的决策规则(除空集和全集以外的所有子集)。即使对于一个一般大的 N,这也将创建数十亿候选决策规则。

  • 决策树在选择规则的过程中也涉及一些随机性;每次只考虑随机选择少数特征,而且只考虑训练数据中一个
    随机子集。在牺牲一些准确率的同时换回了速度的大幅提升,但也意味着每次决策树算法构造的树都不相同。

  • 但是树应该不止有一棵,而是有很多棵,每一棵都能对正确目标值给出合理、独立且互不相同的估计。这些树的集体平均预测应该比任一个体预测更接近正确答案。正是由于决策树构建过程中的随机性,才有了这种独立性,这就是随机决策森林的关键所在。

  • 随机决策森林的预测只是所有决策树预测的加权平均。

    • 对于类别型目标,这就是得票最多的类别,或有决策树概率平均后的最大可能值。
    • 随机决策森林和决策树一样也支持回归问题,这时森林做出的预测就是每棵树预测值的平均。
package com.yyds


import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
import org.apache.spark.ml.{Model, Pipeline, PipelineModel, Transformer}
import org.apache.spark.ml.tuning.{ParamGridBuilder, TrainValidationSplit, TrainValidationSplitModel}
import org.apache.spark.ml.classification.RandomForestClassifier
import org.apache.spark.ml.classification.RandomForestClassificationModel
import scala.util.Random


object ForestModelTest {

  Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)

  def main(args: Array[String]): Unit = {


    // 构建SparkSession实例对象,通过建造者模式创建
    val spark: SparkSession = {
      SparkSession
        .builder()
        .appName(this.getClass.getSimpleName.stripSuffix("$"))
        .master("local[1]")
        .config("spark.sql.shuffle.partitions", "3")
        .getOrCreate()
    }


    // 利用Spark内置的读取CSV数据功能
    val dataWithHeader: DataFrame = spark.read
      .option("inferSchema", "true") // 数据类型推断
      .option("header", "true") // 表头解析
      .csv("D:\\kaggle\\covtype\\covtype.csv")


    // 划分训练集和测试集
    val Array(trainData, testData) = dataWithHeader.randomSplit(Array(0.9, 0.1))
    trainData.cache()
    testData.cache()

    // 输入的特征列
    val inputCols: Array[String] = trainData.columns.filter(_ != "Cover_Type")

    
    val newAssembler = new VectorAssembler()
      .setInputCols(inputCols)
      .setOutputCol("featureVector")


    // 随机森林分类器
    val newClassifier = new RandomForestClassifier()
      .setSeed(Random.nextLong())
      .setLabelCol("Cover_Type")
      .setFeaturesCol("featureVector")
      .setPredictionCol("prediction")


    // 组合为Pipeline
    val pipeline = new Pipeline().setStages(Array(newAssembler, newClassifier))

    // 使用 SparkML API 内建支持的 ParamGridBuilder 来测试超参数的组合
    val paramGrid = new ParamGridBuilder() // 4个超参数来说,每个超参数的两个值都要构建和评估一个模型,共计16种超参数组合,会训练出16个模型
      .addGrid(newClassifier.impurity, Seq("gini", "entropy"))
      .addGrid(newClassifier.maxDepth, Seq(1, 20))
      .addGrid(newClassifier.maxBins, Seq(40, 300))
      .addGrid(newClassifier.numTrees, Seq(10, 20)) // 要构建的决策树的个数
      .build()

    // 设定评估指标  准确率
    val multiclassEval = new MulticlassClassificationEvaluator()
      .setLabelCol("Cover_Type")
      .setPredictionCol("prediction")
      .setMetricName("accuracy")

    // 这里也可以用 CrossValidator 执行完整的 k 路交叉验证,但是要额外付出 k 倍的代价,并且在大数据的情况下意义不大。
    // 所以在这里 TrainValidationSplit 就够用了
    val validator = new TrainValidationSplit()
      .setSeed(Random.nextLong())
      .setEstimator(pipeline)           // 管道
      .setEvaluator(multiclassEval)     // 评估器
      .setEstimatorParamMaps(paramGrid) // 超参数组合
      .setTrainRatio(0.9)               // 训练数据实际上被TrainValidationSplit 划分成90%与10%的两个子集

    val validatorModel: TrainValidationSplitModel = validator.fit(trainData)

    // validator 的结果包含它找到的最优模型。
    val bestModel = validatorModel.bestModel

    // 打印最优模型参数
    // 手动从结果 PipelineModel 中提取 DecisionTreeClassificationModel 的实例,然后提取参数
    println(bestModel.asInstanceOf[PipelineModel].stages.last.extractParamMap)

    // 随机森林分类器有另外一个超参数:要构建的决策树的个数。
    // 与超参数 maxBins 一样,在某个临界点之前,该值越大应该就能获得越好的效果。然而,代价是构造多棵决策树的时间比建造一棵的时间要长很多倍。
    val forestModel = bestModel.asInstanceOf[PipelineModel].stages.last.asInstanceOf[RandomForestClassificationModel]

    // 我们对于特征的理解更加准确了
    println("特征重要性:")
    println(forestModel.featureImportances.toArray.zip(inputCols).sorted.reverse.foreach(println))


    // 这个模型在交叉验证集中达到的准确率是多少?最后,在测试集中能达到什么样的准确率?
    println("交叉验证集上最大准确率:" + validatorModel.validationMetrics.max)
    println("测试集上的准确率:" + multiclassEval.evaluate(bestModel.transform(testData)))


    // 预测
    // 得到的“最优模型”实际上是包含所有操作的整个管道,其中包括如何对输入进行转换以适于模型处理,以及用于预测的模型本身。
    // 它可以接受新的 DataFrame 作为输入。它与我们刚开始时使用的 DataFrame 数据的唯一区别就是缺少“Cover_Type”列
    bestModel
      .transform(testData.drop("Cover_Type"))
      .select("prediction")
      .show(10)

  }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/75817.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【ChatGPT 指令大全】怎么使用ChatGPT来辅助知识学习

目录 概念解说 简易教学 深度教学 教学与测验 解释一个主题的背后原理 总结 在当今信息时代&#xff0c;互联网的快速发展为我们获取知识提供了前所未有的便利。而其中&#xff0c;人工智能技术的应用也为我们的学习和交流带来了新的可能性。作为一种基于自然语言处理的人…

Labview控制APx(Audio Precision)进行测试测量(六)

用 LabVIEW 驱动 VIs生成任意波形 在 APx500 应用程序中&#xff0c;默认波形类型为正弦。这是指 APx 内置的正弦发生器&#xff0c;根据信号路径设置&#xff0c;许多测量还允许其他内置波形&#xff0c;如方波&#xff0c;分裂正弦波或分裂相位&#xff0c;以及使用导入的。w…

商城-学习整理-高级-全文检索-ES(九)

目录 一、ES简介1、网址2、基本概念1、Index&#xff08;索引&#xff09;2、Type&#xff08;类型&#xff09;3、Document&#xff08;文档&#xff09;4、倒排索引机制4.1 正向索引和倒排索引4.2 正向索引4.3 倒排索引 3、相关软件及下载地址3.1 Kibana简介3.2 logstash简介…

2023年国赛数学建模思路 - 案例:感知机原理剖析及实现

文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法&#xff0c;其…

C 语言的 ctype.h 头文件

C 语言的 ctype.h 头文件包含了很多字符函数的函数原型, 可以专门用来处理一个字符, 这些函数都以一个字符作为实参. ctype.h 中的字符测试函数如表所示: 这些测试函数返回 0 或 1, 即 false 或 true. ctype.h 中的字符映射函数如表所示: 字符测试函数不会修改原始实参, 只会…

构建高性能小程序:优化技巧和最佳实践

第一章&#xff1a;引言 随着移动互联网的快速发展&#xff0c;小程序成为了用户获取信息和进行业务交流的重要平台之一。然而&#xff0c;小程序由于受限于硬件资源和网络环境&#xff0c;开发者需要更加关注性能优化&#xff0c;以提供流畅、高效的用户体验。本文将介绍一些构…

DNS部署与安全详解(下)

文章目录 前言一、指定区域解析配置二、DNS服务器对外名称显示配置三、转发器使用配置四、配置辅助&#xff08;备份&#xff09;服务器五、如何让虚拟机可以真实上网六、为DNS服务器配置别名 前言 上一篇博客我们已经在Windows server2003的虚拟机上下载了DNS软件&#xff0c;…

流量日志分析--实操

[鹤城杯 2021]流量分析 <--第一道流量分析不难,主要就是布尔盲注的流量包分析,直接查看http请求包即可我们可以通过观察看到注入成功的响应长度不同,这里成功的为978字节,失败的994字节.不要问为什么.其实也可以直接判断.978的流量比994的少了非常多 显然就是成功的(因为这里…

HTML详解连载(2)

HTML详解连载&#xff08;2&#xff09; 专栏链接 [link](http://t.csdn.cn/xF0H3)下面进行专栏介绍 开始喽超链接作用代码示例解释经验分享 音频标签代码示例注意强调 视频标签代码示例注意强调 列表作用&#xff1a;布局内容排列整齐的区域。分类&#xff1a;无序列表&#x…

kafka基本概念及操作

kafka介绍 Kafka是最初由Linkedin公司开发&#xff0c;是一个分布式、支持分区的&#xff08;partition&#xff09;、多副本的 &#xff08;replica&#xff09;&#xff0c;基于zookeeper协调的分布式消息系统&#xff0c;它的最大的特性就是可以实时的处理大量数据以满足各…

uniapp安卓ios打包上线注意事项

1、安卓包注意事项 隐私政策弹框提示 登录页面隐私政策默认不勾选隐私政策同意前不能获取用户权限APP启动时&#xff0c;在用户授权同意隐私政策前&#xff0c;APP及SDK不可以提前收集和使用IME1、OAID、IMS1、MAC、应用列表等信息 ios包注意事项 需要有注销账号的功能 3、安…

PHP8的字符串操作2-PHP8知识详解

今日继续分享《php8的字符串操作》昨天一天都没有写多少&#xff0c;内容多&#xff0c;今天继续&#xff1a; 昨天分享的是1、使用trim()、rtrim()和ltrim()函数去除字符串首尾空格和特殊字符。2、使用strlen()函数和mb_strlen()函数获取字符串的长度。 3、截取字符串 PHP对…

Spring Boot(六十四):SpringBoot集成Gzip压缩数据

1 实现思路 2 实现 2.1 创建springboot项目 2.2 编写一个接口,功能很简单就是传入一个Json对象并返回 package com.example.demo.controller;import com.example.demo.entity.Advertising; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springf…

【论文阅读】基于深度学习的时序预测——FEDformer

系列文章链接 论文一&#xff1a;2020 Informer&#xff1a;长时序数据预测 论文二&#xff1a;2021 Autoformer&#xff1a;长序列数据预测 论文三&#xff1a;2022 FEDformer&#xff1a;长序列数据预测 论文四&#xff1a;2022 Non-Stationary Transformers&#xff1a;非平…

嵌入式学习之C语言指针部分复习

今天主要把C语言的指针部分再次认真的复习了一下&#xff0c;对于指针的整体框架有了更加深刻的理解&#xff0c;特别要重点区分函数指针&#xff0c;指针函数&#xff0c;数组指针&#xff0c;指针数组部分&#xff0c;对于这部分的应用回非常的重要&#xff0c;而且C语言指针…

Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理

Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理 目录 Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理 一、简单介绍 二、实现原理 三、实现步骤 四、关键代码 一、简单介绍 Unity 工具类&#xff0c;自己整理的一些游戏开发可…

WSL2 Ubuntu子系统安装cuda+cudnn+torch

文章目录 前言一、安装cudncudnn安装pytorch 前言 确保Windows系统版本高于windows10 21H2或Windows11&#xff0c;然后在Windows中将显卡驱动升级到最新即可&#xff0c;WSL2已支持对显卡的直接调用。 一、安装cudncudnn 配置cuda环境&#xff0c;WSL下的Ubuntu子系统的cu…

Flink 流式读写文件、文件夹

文章目录 一、flink 流式读取文件夹、文件二、flink 写入文件系统——StreamFileSink三、查看完整代码 一、flink 流式读取文件夹、文件 Apache Flink针对文件系统实现了一个可重置的source连接器&#xff0c;将文件看作流来读取数据。如下面的例子所示&#xff1a; StreamExe…

emqx-5.1.4开源版使用记录

emqx-5.1.4开源版使用记录 windows系统安装eqmx 去官网下载 emqx-5.1.4-windows-amd64.zip&#xff0c;然后找个目录解压 进入bin目录,执行命令启动emqx 执行命令 emqx.cmd start使用emqx 访问内置的web管理页面 浏览器访问地址 http://localhost:18083/#/dashboard/overv…

H3C交换机MIB库

非常齐全的官方MIB库 为Zabbix监控华三交换机提供诸多方便。 如下信息提供下载链接和下载账号: MIB清单下载:交换机-新华三集团-H3C MIB库:MIB-新华三集团-H3C