上篇文章主要通过论文阅读、数学推导,基本掌握了XGBoost的原理。于是开始阅读XGBoost源码,并总结了几处自己认为比较重要的方面。如有错误,请指正:
1. 总体框架:
cli_main.cc 是程序的入口,main函数所在的文件。除了有main函数以外,还有训练参数的结构体。如模型保存路径,数据路径,迭代次数等。这个源码注释的很清楚,不再赘述。
通过调用main() -> CLIRunTask() -> CLITrain(),这里我们主要看函数CLITrain()的流程
CLITrain主要分了几步:
1.加载数据
2.初始化learner
3.调用learner->InitModel()、learner->Configure初始化Model,确定目标函数和模型,初始化目标函数结构体和模型
4.根据模型参数param.num_round迭代调用UpdateOneIter()来建树
2.learner
上一节,在learner中初始化目标函数和模型,主要赋值给了这两个变量。
/include/xgboost/learner.h
/*! \brief objective function */
std::unique_ptr<ObjFunction> obj_;
/*! \brief The gradient booster used by the model*/
std::unique_ptr<GradientBooster> gbm_;
ObjFunction是基类,定义了很多虚函数。类RegLossObj等都继承于此类,主要实现了根据不同的模型和loss,将一阶二阶导数计算出来。
以线性回归模型为例
src\objective\regression_obj.cc
定义平方损失函数:
// linear regression
struct LinearSquareLoss {
static bst_float PredTransform(bst_float x) { return x; }
static bool CheckLabel(bst_float x) { return true; }
static bst_float FirstOrderGradient(bst_float predt, bst_float label) { return predt - label; }
static bst_float SecondOrderGradient(bst_float predt, bst_float label) { return 1.0f; }
static bst_float ProbToMargin(bst_float base_score) { return base_score; }
static const char* LabelErrorMsg() { return ""; }
static const char* DefaultEvalMetric() { return "rmse"; }
}
class RegLossObj : public ObjFunction{
...
void GetGradient(const std::vector<bst_float> &preds,
const MetaInfo &info,
int iter,
std::vector<bst_gpair> *out_gpair) override {
...
out_gpair->resize(preds.size());
// check if label in range
bool label_correct = true;
// start calculating gradient
const omp_ulong ndata = static_cast<omp_ulong>(preds.size());
#pragma omp parallel for schedule(static)
for (omp_ulong i = 0; i < ndata; ++i) {
bst_float p = Loss::PredTransform(preds[i]);
bst_float w = info.GetWeight(i);
if (info.labels[i] == 1.0f) w *= param_.scale_pos_weight;
if (!Loss::CheckLabel(info.labels[i])) label_correct = false;
out_gpair->at(i) = bst_gpair(Loss::FirstOrderGradient(p, info.labels[i]) * w,
Loss::SecondOrderGradient(p, info.labels[i]) * w);
}
...
}
可以发现在计算一阶导和二阶导的时候,采用了并行处理。以一条数据作为一个粒度。
GradientBooster是基类,有两个模型继承于此类。XGBoost中除了有Tree模型(GBTree),同时也实现了线性模型(GBLinear)。与梯度下降和牛顿法不同的是,在每次迭代的过程中,每个属性单独计算,采用类似于一维的牛顿法来更新一个属性。这里不是重点,有兴趣的同学可以看\src\gbm\gblinear.cc。
3.UpdateOneIter
这里可以看出每次迭代的操作,主要有:
void UpdateOneIter(int iter, DMatrix* train) override {
this->LazyInitDMatrix(train);
this->PredictRaw(train, &preds_); //获取上一轮预测值
obj_->GetGradient(preds_, tr