zoukankan      html  css  js  c++  java
  • 决策树预测森林植被

    1. 决策树和决策森林

    1. 决策树算法家族能自然地处理类别型和数值型特征
    2. 决策树算法容易并行化
    3. 它们对数据中的离群点(outlier)具有鲁棒性(robust),这意味着一些极端或可能错误的数据点根本不会对预测产生影响

    2. Covtype数据集

    https://archive.ics.uci.edu/ml/machine-learning-databases/covtype/

    wget https://archive.ics.uci.edu/ml/machine-learning-databases/covtype/covtype.data.gz

    wget https://archive.ics.uci.edu/ml/machine-learning-databases/covtype/covtype.info

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

    3. labeledPoint

    Spark Mllib 将特征向量抽象为 LabeledPoint,它由一个包含多个特征值的 Spark Mllib Vector 和一个称为 label 的目标值组成。该目标为Double 类型,而 Vector 本质上是由多个 Double 类型值的抽象。

    这说明 LabeledPoint 只适用于数值型特征。但只要经过适当编码,LabeledPoint 也可用于类别型特征。

    4. one-hot 编码

    one-hot 编码或 1-of-n 编码。在这种编码中,一个有 N 个不同取值的类别型特征可以变成 N 个数值型特征,变换后的每个数值型特征的取值为 0 1

    比如,类别型特征“天气”可能的取值有“多云”,“有雨”或“晴朗”。在 1-of-n 编码中,它就变成了三个数值型特征:多云用 1, 0, 0 表示,“有雨”用 0, 1, 0 表示。就可以为这三个数值型特征分别取名:is_cloudyis_rainy is_clear

    5. 第一颗决策树

    决策树(DecisionTree)的实现,以及Spark Mllib 中其他几个实现,都要求输入必须是 LabeledPoint 对象格式:

     val data = rawData.map{ line =>

       val values = line.split(',').map(_.toDouble)

       val featureVector = Vectors.dense(values.init)

       val label = values.last - 1

       LabeledPoint(label, featureVector)

       }

    init 返回除最后一个值之外的所有值;最后一列是label

    决策树要求label 0开始,所以要减一 value.last - 1

    6. 评估模型

    这里我们使用精确度为评价指标

    首先我们将数据分成完整三部分:训练集、交叉检验集(CV)和测试集

    训练集占80%,交叉检验集和测试集各占10%

    val Array(trainData, cvData, testData) = data.randomSplit(Array(0.8, 0.1, 0.1))

    trainData.cache

    cvData.cache

    testData.cache

    DecisionTree 实现也有几个超参数,我们需要为它选择值;

    训练集和cv集用于给这些超参数选择一个合适的值。而测试集,用于对基于选定超参数的模型期望准确度做无偏估计。

    7. 第一棵决策树

    scala> val model = DecisionTree.trainClassifier( trainData, 7, Map[Int,Int](), "gini", 4, 100)

    model: org.apache.spark.mllib.tree.model.DecisionTreeModel = DecisionTreeModel classifier of depth 4 with 31 nodes

    def getMetrics(model: DecisionTreeModel, data: RDD[LabeledPoint]):

    MulticlassMetrics = {

    val predictionsAndLabels = data.map(example =>

          (model.predict(example.features), example.label)

          )

        new MulticlassMetrics(predictionsAndLabels)

        }

    上面两个函数,第一个定义了决策树的模型生成函数;

    第二个getMetrics 定义了:使用生成的模型,对输入数据做预测,然后比对模型预测的结果与真实结果。

    MulticlassMetrics 以不同方式计算分类器预测质量的标准指标,这里我们用cvData来查看预测结果metrics

    scala> val metrics = getMetrics(model, cvData)

    8. 混淆矩阵

    scala> metrics.confusionMatrix

    res3: org.apache.spark.mllib.linalg.Matrix =

    14421.0 6333.0   10.0                  1.0         0.0 0.0 378.0

    5691.0   22125.0 388.0                32.0       0.0 0.0 40.0

    0.0         432.0    3070.0  82.0       0.0 0.0 0.0

    0.0         0.0         155.0   114.0  0.0 0.0 0.0

    0.0         960.0    31.0       0.0         9.0 0.0 0.0

    0.0         472.0    1178.0  95.0       0.0 0.0 0.0

    1130.0   22.0     0.0         0.0         0.0 0.0 929.0

    因为目标类别的取值有7个,所以混淆矩阵是一个 7 x 7 的矩阵,矩阵每一行对应一个实际的正确类别值,矩阵每一列按顺序对应预测值。

    i 行第 j 列的元素代表一个正确类别为 i 的样本被预测为类别为 j 的次数。

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

    9. 精确度和召回率

    scala> metrics.accuracy

    res8: Double = 0.6999896726221212

    scala> metrics.recall

    res11: Double = 0.6999896726221212

    准确度(accuracy)是二分类问题中一个常用的指标。二分类问题中的目标类别只有两个可能的取值,一个类代表正,另一类代表负,精确度就是被标记为“正”而且确实是“正”的样本占所有标记为“正”的样本的比例。

    召回率:指被分类器标注为“正”而且确实为“正”的样本与所有本来就是“正”的样本比率。

    比如:假设数据集有50 个样本,其中20 个为正。分类器将50 个样本中的10 个标记为正,在这10个被标记为“正”的样本中,只有4个确实是“正”(也就是 4 个分类正确)。所以这里的精确度是 4/10 = 0.4,召回率就是 4/20 = 0.2

    我们可以把这些概念应用到多元分类问题,把每个类别单独视为“正”,所有其他类型视为“负”。比如,要计算每个类别相对其他类别的精确度:

    scala> (0 until 7).map(

          cat => (metrics.precision(cat), metrics.recall(cat))

          ).foreach(println)

    (0.6788908765652951,0.6820697157451638)

    (0.7291392037964671,0.7824656952892912)

    (0.6353476821192053,0.8565848214285714)

    (0.35185185185185186,0.42379182156133827)

    (1.0,0.009)

    (0.0,0.0)

    (0.6896807720861173,0.44641999038923597)

    可以看到每个类型的准确度都各不相同。就本例而言,我们没道理认为某个类型的准确度要比其他类型的准确度更重要,因此用一个多元分类的总体精确度就可以较好地度量分类准确度。

    10. 决策树的超参数

    控制决策树选择过程的超参数为最大深度、最大桶数和不纯性度量。

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

    决策树算法负责为每层生成可能的决策规则。对数值型特征,采用特征大于等于等形式;对类别型特征,决策采用特征在(值1,值2)中的形式。Spark Mllib 的实现把决策规则集合称为“桶”(bin)。桶的数目越多,需要处理的时间越多但找到的决策规则可能最优。

    11. 不纯度

    在决策树中,好的规则把训练数据的目标值分为相对是同类或“纯”(pure)的子集。选择最好的规则也就意味着最小化规则对应的两个子集的不纯性(impurity)。

    不纯性有两种常用的度量方式:Gini 不纯度、熵

    Gini不纯度:

    Gini 不纯度直接和随机猜测分类器的准确度相关。在每个子集中,它就是对一个随机挑选的样本进行随机分类时分类错误的概率(随机挑选样本和随机分类时要参照子数据集的类别分布)。

    也就是 1 减去每个类别的比例与自身的乘积之和。假设子数据集包含 N 个类别的样本,pi 是类别 I 的样本所占的比例,于是可以得到如下 Gini 不纯度公式:

    如果子数据集中所有的样本都属于一个类别,则 Gini 不纯度的值为 0,因为这个子数据集完全是纯的。当子数据集中的样本来自 N 个不同的类别时,Gini 不纯度的值大于 0,并且是在每个类别的样本数都相同时达到最大,也就是最不纯的情况。

    信息熵:

    熵代表了子集中目标取值集合的不确定程度。如果子集只包含一个类别,则是完全确定的,熵为 0

    熵可以用以下熵计算公式定义:

    不确定性是有单位的。由于取自然对数(以 e 为底),熵的单位是纳特(nat)。相对于以 e 为底的纳特,我们更熟悉它对应的比特(以 2 为底取对数即可得到)。它实际上度量的信息,因此在使用熵的决策树中,我们也常说决策规则的信息增益。

    不同的数据集上对于挑选好的决策规则方面,这两个度量指标各有千秋。Spark 的实现默认采用 Gini 不纯度。

    12. 决策树的调优

    采用哪个不纯性度量所得到的决策树的准确度更高,或者最大深度或桶数取多少合适,从数据上看,回答这些问题是困难的。

    不过我们可以让Spark 来尝试这些值的许多组合并报告结果:

    val evaluations = for (impurity <- Array("gini", "entropy");

                                 depth <- Array(1, 20);

                                 bins <- Array(10, 300))

         yield {

                                 val model = DecisionTree.trainClassifier(

                                 trainData, 7, Map[Int, Int](), impurity, depth, bins)

                                 val predictionsAndLabels = cvData.map(example =>

                                               (model.predict(example.features), example.label))

                                 val accuracy = new MulticlassMetrics(predictionsAndLabels).accuracy

                                 ((impurity, depth, bins), accuracy)

                  }

    scala> evaluations.sortBy(_._2).reverse.foreach(println)

    ((entropy,20,300),0.9098076857152652)

    ((gini,20,300),0.9050083987521854)

    ((entropy,20,10),0.8989921497377532)

    ((gini,20,10),0.8900106269925611)

    ((gini,1,10),0.6373967296287408)

    ((gini,1,300),0.6361454869562236)

    ((entropy,1,300),0.4895101299235542)

    ((entropy,1,10),0.4895101299235542)

    通过比较最后的准确率,可以发现从这些组合中,最优的为:entropy20300

    从上面的结果可以看到:

    1. 最大深度为1 太小,得到的结果比较差
    2. 增加桶数帮助并不大
    3. 两种不纯性度量的效果差不多
    4. 增加最大深度效果比较好(一般最大深度会有拐点,超过此点,即使再增加,也不会有太大用处)

    到目前为止,我们用到的只是 train data cv data,一直没用到 test data

    如果说 cv 集的目的是为了评估适合训练集的参数,那么测试集的目的是评估适合 cv 集的超参数。也就是说,测试集保证了对最终选定的超参数及模型准确度的无偏估计。

    到目前为止,超参数的最佳选择是:entropy20300。精准度为91%

    cv集只是帮助了我们选择这些超参数,但是最重要的是,我们要评估的是这个最佳模型在将来的样本上的表现,也就是 test 集上的表现。

    最后,我们将cv 集加入到训练集,并使用之前获得的最佳超参数:

    val model = DecisionTree.trainClassifier(trainData.union(cvData), 7, Map[Int, Int](), "entropy", 20, 300)

    最终对 test 集上的正确率为:91.6%

    13. Map[Int, Int]()

    这个 Map 中元素的键是特征在输入向量 Vector 中的下标,Map 元素中的值是类别型特征的不同取值个数。

    参数取为空 Map(),则表示算法不需要把任何特征作为类别型,也就是说,所有的特征都是数值型。

    上面的例子里没有类别型变量,因为数据集对部分特征已经做了one-hot 编码。但是对树模型并不友好,且会消耗更多的内存并减慢决策速度。

    如果我们不使用 one-hot 编码?

    val data = rawData.map{ line =>

     val values = line.split(',').map(_.toDouble)

     val wilderness = values.slice(10, 14).indexOf(1.0).toDouble

     val soil = values.slice(14, 54).indexOf(1.0).toDouble

     val featureVector = Vectors.dense(values.slice(0, 10) :+ wilderness :+ soil)

     val label = values.last - 1

     LabeledPoint(label, featureVector)

     }

    val Array(trainData, cvData, testData) = data.randomSplit(Array(0.8, 0.1, 0.1))

    trainData.cache

    cvData.cache

    testData.cache

    val evaluations =

     for (impurity <- Array("gini", "entropy");

     depth <- Array(10, 20, 30);

     bins <- Array(40, 300))

     yield {

    val model = DecisionTree.trainClassifier(trainData, 7, Map(10 -> 4, 11 -> 40), impurity, depth, bins)

    val trainAccuracy = getMetrics(model, trainData).accuracy

     val cvAccuracy = getMetrics(model, cvData).accuracy

    ((impurity, depth, bins), (trainAccuracy, cvAccuracy))

    }

    scala> evaluations.sortBy(_._2._2).reverse.foreach(println)

    ((entropy,30,300),(0.9998000644953241,0.9417916609329847))

    ((entropy,30,40),(0.9995463828872406,0.9369925691482042))

    ((gini,30,300),(0.9995098355369235,0.9352380624741984))

    ((gini,30,40),(0.99941309255079,0.9337415714875464))

    ((entropy,20,300),(0.9728474685585295,0.9272051740745837))

    ((gini,20,300),(0.9701451144792003,0.9239025732764552))

    ((entropy,20,40),(0.9692787272922713,0.9225264896105683))

    ((gini,20,40),(0.9684488874556595,0.9207719829365626))

    ((gini,10,300),(0.7954552294958616,0.7914545204348424))

    ((gini,10,40),(0.7891389874234118,0.78765308930783))

    ((entropy,10,40),(0.7802816295818553,0.7756639603687904))

    ((entropy,10,300),(0.7808642373427926,0.7754919499105546))

    从上面的结果可以看到:

    1. 深度为30时,准确率最高
    2. 在使用类别型变量后,准确率得到提升

    14. 随机森林

    在决策树的每层,算法并不会考虑所有可能的决策规则,因为这会让算法的运行时间长到无法想象。

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

    团体智慧比个体预测要准确:

    考虑一个场景,猜测一下一个外卖多少钱?

    假设正确答案是26,我猜测是20,稍微有些偏差。

    而如果我问一下办公室里所有同事,然后取平均值,结果会更接近一个准确值。

    但是这基于一个条件:就是所有猜测都相互独立。任何两个人之间都没有信息交互,如果有了交互,可能会影响一个人的预测。

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

    val forest = RandomForest.trainClassifier(trainData, 7, Map(10 -> 4, 11 -> 40), 20, "auto", "entropy", 30, 300)

    这里多了两个参数:

    第一个代表构建多少棵树,这里是 20。所以它构建的时间会较长。

    第二个是特征决策树每层的评估特征选择策略,这里设置为“auto”。随机森林在实现过程中决策规则不会考虑全部特征,而只考虑全部特征的一个子集。所以这个参数是控制算法如何选择该特征子集。只检查少数特征速度明显要更快,并且由于速度快,随机森林才得以构造多棵决策树。

    在构造随机森林的时候,每棵树没必要用到全部训练数据,同时,每棵树的输入数据可以随机选择。

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

    15. 预测

    scala> val predictionAndLabels = testData.map( example =>

     (forest.predict(example.features), example.label)

    val accuracy = new MulticlassMetrics(predictionAndLabels).accuracy

    accuracy: Double = 0.9633613969441845

  • 相关阅读:
    Linux平台不同解压缩命令的使用方法
    poj 1274 The Perfact Stall
    Experience Design for Sexable Forum
    JavaScript中的Array对象方法调用
    iOS9适配小结
    [Servlet&amp;JSP] HttpSession会话管理
    Android中的跨进程通信方法实例及特点分析(一):AIDL Service
    OpenCV——PS滤镜算法之Spherize 球面化(凸出效果)
    《Effective Modern C++》翻译--条款4:了解怎样查看推导出的类型
    Android开发系列之ListView
  • 原文地址:https://www.cnblogs.com/zackstang/p/9463655.html
Copyright © 2011-2022 走看看