PySpark RandomForest 实现中的 rawPrediction 是如何计算的?

Posted

技术标签:

【中文标题】PySpark RandomForest 实现中的 rawPrediction 是如何计算的?【英文标题】:How is rawPrediction calculated in PySpark RandomForest implementation? 【发布时间】:2020-07-21 17:10:18 【问题描述】:

我在包含 15 个示例的训练集上训练了一个 RF 模型(包含 3 棵树,深度为 4 棵树)。下面是三棵树的外观图像。我有两个班级(比如 0 和 1)。

阈值在左侧分支中提到,而圆圈中的数字(例如 7、3 是特征 2 的 阈值的示例数,即 f2)。

现在,当我尝试将模型应用于 10 个示例的测试集时,我不确定原始预测是如何计算的。

+-----+----+----+----------+-------------------------------------------------------------------
|prediction|features                                                                                                                                                                                                                                                                                                   |rawPrediction|probability                            |
+-----+----+----+----------+-----------------------------------------------------------------------------------------------------------+-------------+---------------------------------------+
|1.0       |[0.07707524933080619,0.03383458646616541,0.017208413001912046,9.0,2.5768015000258258,0.0,-1.0,-1.0,0.0,-1.0,-1.0,-1.0,-1.0,-1.0,0.0014143059186559938,0.0,0.6666666666666667,7.076533785087878E-4,0.0014163090128755495,0.9354143466934853,0.9333333333333333,0.875,0.938888892531395,7.0]                 |[1.0,2.0]    |[0.3333333333333333,0.6666666666666666]|

我已经通过以下链接进行了解,但我无法理解这一点。

https://forums.databricks.com/questions/14355/how-does-randomforestclassifier-compute-the-rawpre.html

https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/classification/RandomForestClassifier.scala

我确信这并不像您想象的那么简单。例如,根据我的理解,它不像 - 例如,如果两棵树预测为 0,而一棵树预测为 1,那么原始预测将是 [2, 1]。情况并非如此,因为当我在 500 个示例上训练模型时,我看到同一示例的原始预测为 [0.9552544653780279,2.0447455346219723]。

有人可以向我解释一下这是如何从数学上计算出来的吗?任何帮助都会在这里受到赞赏,因为它有点基本,我想直接了解它是如何工作的。再次提前非常感谢,如果需要任何其他信息来帮助解决此问题,请发布。

编辑: 也从模型中添加数据:

+------+-----------------------------------------------------------------------------------------------------+
|treeID|nodeData                                                                                             |
+------+-----------------------------------------------------------------------------------------------------+
|0     |[0, 0.0, 0.5, [9.0, 9.0], 0.19230769230769235, 1, 4, [2, [0.12519961673586713], -1]]                 |
|0     |[1, 0.0, 0.42603550295857984, [9.0, 4.0], 0.42603550295857984, 2, 3, [20, [0.39610389610389607], -1]]|
|0     |[2, 0.0, 0.0, [9.0, 0.0], -1.0, -1, -1, [-1, [], -1]]                                                |
|0     |[3, 1.0, 0.0, [0.0, 4.0], -1.0, -1, -1, [-1, [], -1]]                                                |
|0     |[4, 1.0, 0.0, [0.0, 5.0], -1.0, -1, -1, [-1, [], -1]]                                                |
|1     |[0, 1.0, 0.4444444444444444, [5.0, 10.0], 0.4444444444444444, 1, 2, [4, [0.9789660448762616], -1]]   |
|1     |[1, 1.0, 0.0, [0.0, 10.0], -1.0, -1, -1, [-1, [], -1]]                                               |
|1     |[2, 0.0, 0.0, [5.0, 0.0], -1.0, -1, -1, [-1, [], -1]]                                                |
|2     |[0, 0.0, 0.48, [3.0, 2.0], 0.48, 1, 2, [20, [0.3246753246753247], -1]]                               |
|2     |[1, 0.0, 0.0, [3.0, 0.0], -1.0, -1, -1, [-1, [], -1]]                                                |
|2     |[2, 1.0, 0.0, [0.0, 2.0], -1.0, -1, -1, [-1, [], -1]]                                                |
+------+-----------------------------------------------------------------------------------------------------+

【问题讨论】:

【参考方案1】:

原始预测是每棵树的预测类别概率,对森林中的所有树求和。 对于单个树的类概率,在所选叶节点中属于每个类的样本数很重要。

在代码中,我们可以在RandomForestClassifier类here中看到这个程序,这里引用了相关代码:

override protected def predictRaw(features: Vector): Vector = 
  // TODO: When we add a generic Bagging class, handle transform there: SPARK-7128
  // Classifies using majority votes.
  // Ignore the tree weights since all are 1.0 for now.
  val votes = Array.fill[Double](numClasses)(0.0)
  _trees.view.foreach  tree =>
    val classCounts: Array[Double] = tree.rootNode.predictImpl(features).impurityStats.stats
    val total = classCounts.sum
    if (total != 0) 
      var i = 0
      while (i < numClasses) 
        votes(i) += classCounts(i) / total
        i += 1
      
    
  
  Vectors.dense(votes)

对于每棵树,我们找到输入特征对应的叶子节点,并找到每个类的计数(类计数对应于训练期间分配给叶子节点的类的训练样本数)。类计数除以节点的总计数以获得类概率。

现在,对于每棵树,我们都有属于每个类的输入特征的概率。这些概率相加得到原始预测。


这个问题更具体,据我了解,主要缺失的元素是类数(以及由此而来的类概率)。要计算原始预测,这些是必不可少的组成部分。在图像中,您需要在训练期间添加分配给每个叶子的样本数,而不是“Pred 0”和“Pred 1”(“Pred 0”意味着来自类 0 的样本数是多数,反之亦然)。当你知道类数和类概率时,将所有树的这些相加,你就会得到原始预测。

【讨论】:

@shaido-reinstate-monica 这很有帮助。我保存了模型,然后读取了数据和 treeData。从数据中,我找出了杂质统计数据。我已经用这些细节更新了这个问题。我能够理解 rawPrediction 是如何计算的。但现在的问题是 - 查看 impurityStats 数组,我不知道它是如何生成的。杂质统计数组的总数超过了我无法理解的训练示例的数量。如果您能提供帮助 - 那就太好了。谢谢! @yguw:您发布的数据不仅是杂质统计数据,还包含大量信息。在您添加的数据中,每一行代表树中的一个节点(第一棵树有 5 个节点,另外两棵树各有 3 个节点)。随机森林分类器将考虑为每棵树随机选择样本。从数据中我们可以看到,第一棵树考虑了 18 个样本(每类 9 个),第二棵树考虑了 15 个样本(每类 5 和 10 个),最后一棵树考虑了 5 个样本(3 和 2)。 Gini 杂质通常用于 RF,它是针对每个节点计算的。 由于节点的纯度在树中越低,基尼杂质就越少。可以在这里找到如何使用/计算它的一个很好的解释:victorzhou.com/blog/gini-impurity @shaido-reinstate-monica 非常感谢。现在,我唯一的问题是,当我只使用 15 个示例来训练我的数据时,为什么第一棵树的样本大小为 18。基本上,我的训练数据是 15 个样本,那么为什么第一棵树考虑了 18 个样本?如果你能帮助我理解这一点 - 我现在就完成了。非常感谢。 @yguw:正确,查看代码,如果树的数量 > 1 (link),Spark 2.4 及更早版本会自动使用引导。对于 Spark 3.0,可以设置一个布尔参数“bootstrap”来控制此行为。

以上是关于PySpark RandomForest 实现中的 rawPrediction 是如何计算的?的主要内容,如果未能解决你的问题,请参考以下文章

如何访问 Spark RandomForest 中的单个预测?

如何使用 pySpark 决定将 numClasses 参数传递给 SPark MLlib 中的随机森林算法

Pyspark - 获取使用 ParamGridBuilder 创建的模型的所有参数

在 PySpark mllib 中使用随机森林的非法参数异常

R中RandomForest包中的RandomForest函数中的参数'classwt'代表啥?

Numpy实现RandomForest