如何重现 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值
R语言使用caret包对GBM模型参数调优(自定义调优的评估指标,例如ROC指标):抽取预测标签及类概率抽样ROC的指标并绘制密度图