XGBoost (3) C++ 训练模型源码分析之一 ——求预测值

本文深入探讨XGBoost的C++源码,聚焦于训练模型的预测值计算过程。从`Boosting::DoBoost`函数开始,解析线性模型`GBLinear`和树模型`GBTree`的细节,包括模型得分计算、并行执行和决策树的结构。通过对源码的分析,揭示了XGBoost预测背后的逻辑。
摘要由CSDN通过智能技术生成

回顾与本文目标

还记得上一篇博文中,我们回顾了 python 的训练接口,本文我们深挖 XGBoostC++ 源代码。从训练迭代内核 XGBoosterUpdateOneIter() 函数开始讨论。争取对训练过程再有更多感性认识。以下引用的代码,为了行文简洁起见,把一些明显用于记录时间、注释行、检查操作等删除。

进行一步迭代 XGBoosterUpdateOneIter()

我们在 c_api.cc 中找到了 XGBoosterUpdateOneIter() 的源代码。不难发现,它也是一件外衣。穿着这件外衣的是 learner()->UpdateOneIter()。函数前的 XGB_DLL 意味着它是可以调用的函数接口,也与 python 端的接口一致。

XGB_DLL int XGBoosterUpdateOneIter(BoosterHandle handle, int iter, DMatrixHandle dtrain)
{
   
  API_BEGIN();
  auto* bst = static_cast<Booster*>(handle);
  auto *dtr = static_cast<std::shared_ptr<DMatrix>*>(dtrain);

  bst->LazyInit();
  bst->learner()->UpdateOneIter(iter, dtr->get());
  API_END();
}

这里的 leaner() 函数就定义在 c_api.cc 文件中,如下:

inline Learner* learner()
{
   
    return learner_.get();
}

它是 Booster 类的一个方法。Booster 类中有一个Learner 类的成员变量 learner_,定义在 learner.h 中,具体实现在 learner.cc 中。函数 UpdateOneIter() 也在这个文件中,如下:

void UpdateOneIter(int iter, DMatrix* train) override
{
   
    if (tparam_.seed_per_iteration || rabit::IsDistributed()) {
   
        common::GlobalRandom().seed(tparam_.seed * kRandSeedMagic + iter);
    }
    this->PerformTreeMethodHeuristic(train);
    this->PredictRaw(train, &preds_);
    obj_->GetGradient(preds_, train->Info(), iter, &gpair_);
    gbm_->DoBoost(train, &gpair_, obj_.get());
}

这里的 monitor_ 是用于记录训练过程的日志信息(其实现在 src/common/timer.h 中,也就是说,这主要是用来计时的)。如果除去检查部分和并行代码随机种子部分,剩下的代码只有:

  1. this->PerformTreeMethodHeuristic(train); 这部分是配置不同的 树方法 (TreeMethod)。
  2. this->PredictRaw(train, &preds_); 它是 gbm_->PredictBatch() 的外衣。
  3. obj_->GetGradient(preds_, train->Info(), iter, &gpair_);TODO
  4. gbm_->DoBoost(train, &gpair_, obj_get());TODO

到这里,我们可以看出,越来越接近实在干活的代码了。从代码的格式,可以猜测,带 & 符号的变量应该是输出变量。注意,这里支持两种模型:线性模型和树模型。我们首先来看一下线性模型的具体实现。

learner.cc 中的 PerformTreeMethodHeuristic()

  • 树方法有: kAuto 自动的、kHist 柱状图的、 kApprox 近似的、kExact 精确的、kGPUHist 柱状图和 kGPUExact GPU精确的。如果 gbm 不是 gbtree,那么训练目前还没有 GPU 分布式的算法。
  • 函数会将树方法传递到 cfg_,进而传入 gbm_

learner.cc 中的 PredictRaw()

调用方式如下

inline void PredictRaw(DMatrix* data, HostDeviceVector<bst_float>* out_preds,
    unsigned ntree_limit = 0) const
{
   
	gbm_->PredictBatch(data, out_preds, ntree_limit);
}    

而在接口方面,这个方法定义于 include\xgboost\gbm.h

virtual void PredictBatch(DMatrix* dmat, HostDeviceVector<bst_float>* out_preds,
    unsigned ntree_limit = 0) = 0;

可以看出,这是一个虚方法,有两个对应的实现:src/gbm/gblinear.ccsrc/gbm/gbtree.cc。这两个方法使用了不同的路径。(下面我们详细讨论这两个函数)

gblinear.cc 中的 PredictBatch()

源代码如下:

void PredictBatch(DMatrix *p_fmat, HostDeviceVector<bst_float> *out_preds,
    unsigned ntree_limit) override
{
   
	auto it = cache_.find(p_fmat);
	if (it != cache_.end() && it->second.predictions.size() != 0) {
   
	  std::vector<bst_float> &y = it->second.predictions;
	  out_preds->Resize(y.size());
	  std::copy(y.begin(), y.end(), out_preds->HostVector().begin());
	} else {
   
	  this->PredictBatchInternal(p_fmat, &out_preds->HostVector());
	}
}

其执行逻辑基本上是:

  • 如果已经有了预测结果,则把预测结果复制到指定未知。
  • 否则调用 PredictBatchInternal() 函数。所以,这个函数基本上也就是件外衣。

  • 这里的 p_fmat 就是原来的 data
  • 这里的 out_preds 就是原来的 preds_

所以,我们还需要看一下 PredictBatchInternal() 的实现,它在 gblinear.cc 文件中:

void PredictBatchInternal(DMatrix *p_fmat, std::vector<bst_float> *out_preds) {
   
	model_.LazyInitModel();
	std::vector<bst_float> &preds = *out_preds;
	const auto& base_margin = p_fmat->Info().base_margin_.ConstHostVector();
	const int ngroup = model_.param.num_output_group;
	preds.resize(p_fmat->Info().num_row_ * ngroup);
	for (const auto &batch : p_fmat->GetRowBatches()) {
   
		const auto nsize = static_cast<omp_ulong>(batch.Size());
		#pragma omp parallel for schedule(static)
		for (omp_ulong i = 0; i < nsize; ++i) {
   
			const size_t ridx = batch.base_rowid + i;
			for (int gid = 0; gid < ngroup; ++gid) {
   
		  		bst_float margin =  (base_margin.size
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值