如何重现 H2o GBM 类概率计算

Posted

技术标签:

【中文标题】如何重现 H2o GBM 类概率计算【英文标题】:How to reproduce the H2o GBM class probability calculation 【发布时间】:2017-11-27 20:45:50 【问题描述】:

我一直在使用 h2o.gbm 解决分类问题,并想进一步了解它如何计算类别概率。作为起点,我尝试重新计算只有 1 棵树的 gbm 的类概率(通过查看叶子中的观察结果),但结果非常混乱。

假设我的正类变量是“buy”,负类变量是“not_buy”,我有一个名为“dt.train”的训练集和一个名为“dt.test”的单独测试集。

在正常的决策树中,新数据行(测试数据)的“购买”类概率 P(has_bought="buy") 的计算方法是用“购买”类叶子中的所有观察值除以叶中的观察总数(基于用于生长树的训练数据)。

但是,h2o.gbm 似乎做了一些不同的事情,即使我模拟“正常”决策树(将 n.trees 设置为 1,将 alle sample.rates 设置为 1)。我认为说明这种困惑的最佳方式是逐步讲述我所做的事情。

第 1 步:训练模型

我不关心过度拟合或模型性能。我想让我的生活尽可能轻松,所以我将 n.trees 设置为 1,并通过设置所有 sample.rate 参数确保所有训练数据(行和列)用于每棵树并进行拆分to 1. 下面是训练模型的代码。

    base.gbm.model <- h2o.gbm(
      x = predictors,
      y = "has_bought",
      training_frame = dt.train,
      model_id = "2",
      nfolds = 0,
      ntrees = 1,
      learn_rate = 0.001,
      max_depth = 15,
      sample_rate = 1,
      col_sample_rate = 1,
      col_sample_rate_per_tree = 1,
      seed = 123456,
      keep_cross_validation_predictions = TRUE,
      stopping_rounds = 10,
      stopping_tolerance = 0,
      stopping_metric = "AUC",
      score_tree_interval = 0
    )

第 2 步:获取训练集的叶分配

我想要做的是使用用于训练模型的相同数据,并了解它们最终位于哪个叶子中。H2o 为此提供了一个函数,如下所示。

    train.leafs <- h2o.predict_leaf_node_assignment(base.gbm.model, dt.train)

这将返回训练数据中每一行的叶节点分配(例如“LLRRLL”)。由于我们只有 1 棵树,因此该列称为“T1.C1”,我将其重命名为“leaf_node”,并与训练数据的目标变量“has_bought”绑定。这会产生下面的输出(从这里开始称为“train.leafs”)。

第 3 步:对测试集进行预测

对于测试集,我想预测两件事:

    模型本身的预测 P(has_bought="buy")

    根据模型分配叶节点。

    test.leafs <- h2o.predict_leaf_node_assignment(base.gbm.model, dt.test)
    test.pred <- h2o.predict(base.gbm.model, dt.test)
    

找到这个后,我使用 cbind 将这两个预测与测试集的目标变量结合起来。

    test.total <- h2o.cbind(dt.test[, c("has_bought")], test.pred, test.leafs)

这个结果,就是下表,从这里称为“test.total”

不幸的是,我没有足够的代表点来发布超过 2 个链接。但是如果你点击“表”test.total“结合手册 概率计算”在第5步,基本上是同一张表 没有“manual_prob_buy”列。

第 4 步:手动预测概率

理论上,我现在应该能够自己预测概率。我通过编写一个循环来做到这一点,该循环遍历“test.total”中的每一行。对于每一行,我都会进行叶节点分配。

然后我使用该叶节点分配来过滤表“train.leafs”,并检查有多少观察具有正类 (has_bought == 1) (posN) 以及总共有多少观察 (totalN)在与测试行关联的叶子中。

我执行(标准)计算 posN / totalN,并将其存储在测试行中作为名为“manual_prob_buy”的新列,这应该是该叶子的 P(has_bought="buy") 概率。因此,落在这个叶子中的每个测试行都应该得到这个概率。 这个for循环如下所示。

    for(i in 1:nrow(dt.test))
      leaf <-  test.total[i, leaf_node] 
      totalN <- nrow(train.leafs[train.leafs$leaf_node == leaf])
      posN <- nrow(train.leafs[train.leafs$leaf_node == leaf & train.leafs$has_bought == "buy",])
      test.total[i, manual_prob_buy :=  posN / totalN]
    

第 5 步:比较概率

这就是我感到困惑的地方。下面是更新后的“test.total”表,其中“buy”表示根据模型的概率 P(has_bought="buy"),“manual_prob_buy”表示第 4 步手动计算的概率。据我所知,这些概率应该是相同的,因为我知道我只使用了 1 棵树并且我已将 sample.rates 设置为 1。

表“test.total”结合手动概率计算

问题

我只是不明白为什么这两个概率不一样。据我所知,我已经将参数设置为它应该就像一个“正常”分类树。

所以问题是:有谁知道为什么我发现这些概率存在差异?

我希望有人能指出我可能做出错误假设的地方。我真的希望我做了一些愚蠢的事情,因为这让我发疯。

谢谢!

【问题讨论】:

【参考方案1】:

您观察到的概率与 h2o 的预测之间存在巨大差异的主要原因是您的学习率。正如您拥有learn_rate = 0.001 一样,gbm 正在将概率从总体比率中调整为相对较小的量。如果将其调整为learn_rate = 1,您将得到更接近决策树的结果,并且 h2o 的预测概率将更接近每个叶节点中的速率。

由于您的概率仍然不完全匹配,因此会出现次要差异。这是由于逻辑损失函数上的梯度下降(GBM中的G)的方法,而不是每个叶节点中的观察次数。

【讨论】:

【参考方案2】:

与其将 R 的 h2o.predict() 的结果与您自己的手写代码进行比较,我建议您与应该匹配的 H2O MOJO 进行比较。

在此处查看示例:

http://docs.h2o.ai/h2o/latest-stable/h2o-genmodel/javadoc/overview-summary.html#quickstartmojo

你可以自己运行那个简单的例子,然后根据你自己的模型和新的数据行来修改它来预测。

一旦你能做到这一点,你就可以查看代码并在 Java 环境中调试/单步执行它,以准确了解预测是如何计算的。

你可以在这里找到 github 上的 MOJO 预测代码:

https://github.com/h2oai/h2o-3/blob/master/h2o-genmodel/src/main/java/hex/genmodel/easy/EasyPredictModelWrapper.java

【讨论】:

感谢您的评论,不知道这是可能的。会调查的。

以上是关于如何重现 H2o GBM 类概率计算的主要内容,如果未能解决你的问题,请参考以下文章

为啥 gbm() 在这个最小示例中给出的结果与 h2o.gbm() 不同?

R:从 h2o.randomForest() 和 h2o.gbm() 绘制树

R语言基于h2o包构建二分类模型:使用h2o.randomForest构建随机森林模型使用h2o.auc计算模型的AUC值

R语言基于h2o包构建二分类模型:使用h2o.glm构建正则化的逻辑回归模型使用h2o.auc计算模型的AUC值

如何在高性能计算中安装 H2O 无人驾驶 AI?

R语言使用caret包对GBM模型参数调优(自定义调优的评估指标,例如ROC指标):抽取预测标签及类概率抽样ROC的指标并绘制密度图