这篇应该是你见过的讲xgboost的文章里最细的。
简单介绍
首先需要介绍GBDT,它是一种基于boosting增强策略的加法模型,训练的时候采用前向分布算法进行贪婪的学习,每次迭代都学习一棵CART树来拟合之前 t-1 棵树的预测结果与训练样本真实值的残差。
在核心思想不变的情况下,XGBoost对GBDT进行了一系列优化,主要是损失函数进行了二阶泰勒展开,另外还有目标函数加入正则项、支持并行和默认缺失值处理等,在可扩展性和训练速度上有了巨大的提升。
模型核心思想
- Boosting提升方法
- 使用二阶泰勒展开来近似拟合残差:相对于GBDT的一阶导数,XGBoost采用二阶泰勒展开,可以更为精准的逼近真实的损失函数
- 贪婪方法分裂节点
模型前提设定
训练数据集D={(xi,yi)}i=1n,其中xi∈Rm,yi∈R。又有实例x。
01 - 单棵决策树模型表示方法
假设某棵决策树有T个叶子节点,则单棵决策树模型可记为:
f(x)=wq(x)
其中q:Rm→{1,…,T}是由输入x向叶子节点编号的映射,其本质是树的分支结构;而w∈RT是叶子权重向量。

例如实例 x 落在了决策树的第 j 个节点,则其输出值即为 wj 的值。而叶子节点向量即为
w=(w1,⋯,wj,⋯,wT)
02 - 加法模型结构
使用Boosting思想,对于实例x,总模型的预测输出为
y^=ϕ(x)=k=1∑Kfk(x)
其中,fk(x) 为第 k 棵决策树。
核心生成原理
03 - XGBoost的正则化目标函数及其变量解释
XGBoost的正则化目标函数由损失函数和正则化项两部分组成,定义如下:
L=i=1∑nl(yi,y^i)+k=1∑KΩ(fk)
以下逐一解释各个参数:
-
l表示损失函数。由于模型将使用二阶泰勒展开,因此要求损失函数一阶和二阶可导。常见的损失函数有:
- 用于回归的平方损失函数:l(y,y^)=(y−y^)2
- 用于分类的逻辑回归损失函数:l(y,y^)=yln(1+e−y^)+(1−y)ln(1+ey^)
-
y^i表示第i个样本xi的预测值。作为加法模型,预测得分是每棵树打分的累加之和:
y^i=k=1∑Kfk(xi),fk∈F
如上式,此模型中共有 k 棵树,fk 为第 k 棵树的函数(模型)。
-
Ω(f)为单棵数的复杂度。模型将全部 k 棵树的复杂度进行求和,添加到目标函数中作为正则化项,以防止模型过拟合:
k=1∑KΩ(fk)
04 - 学习第 t 棵树
假设在第 t 轮,也是生成第 t 棵树时,要训练的树模型为 ft ,则在第 t 轮迭代后,实例 x 的预测结果 y^(t) 为:
y^(t)=k=1∑tfk(x)=y^(t−1)+ft(x)
公式中 y^(t−1) 为前 t−1 棵树的预测结果。
注意,在第 t 轮迭代时,前 t−1 棵树已知,因此 y^(t−1) 也是一个已知常量。
将上式代入模型的基本目标函数,得到第 t 轮的目标函数为
L(t)=i=1∑nl(yi,y^i(t))+k=1∑tΩ(fk)=i=1∑nl(yi,y^i(t−1)+ft(xi))+Ω(ft)+ constant
后半部分同理。因为在第 t 轮迭代时,前 t−1 棵树已知,因此前 t−1 棵树的复杂度都是已知常量,可以记为 constant:
k=1∑tΩ(fk)=Ω(ft)+k=1∑t−1Ω(fk)=Ω(ft)+ constant
而yi为已知标签,因此在第 t 轮的目标函数中,只有唯一的变量,即第 t 棵树 ft 。
因为常量不影响目标函数的优化,我们去除常量,得到第 t 轮的目标函数为
L(t)=i=1∑nl(yi,y^i(t−1)+ft(xi))+Ω(ft)
05 - 泰勒公式及目标函数的泰勒展开
泰勒公式是XGBoost模型的核心要点之一,也是核心亮点。相比较于GBDT模型,二阶泰勒展开逼近更加有效。
泰勒公式是将一个在x=x0处具有n阶导数的函数f(x)利用(x−x0)的n次多项式来逼近函数的方法。
泰勒公式形式众多,从本文的实用角度出发,采用以下形式的泰勒公式的二阶展开形式:
f(x+Δx)≃f(x)+f′(x)Δx+21f′′(x)Δx2
第 t 轮目标函数 L(t) 在 yi^(t−1) 处的二阶泰勒展开为
L(t)=i=1∑nl(yi,y^i(t−1)+ft(xi))+Ω(ft)≃i=1∑n[l(yi,yi^(t−1))+∂yi^(t−1)l(yi,yi^(t−1))ft(xi)+21∂yi^(t−1)2l(yi,yi^(t−1))ft2(xi)]+Ω(ft)=i=1∑n[l(yi,yi^(t−1))+gift(xi)+21hift2(xi)]+Ω(ft)
其中 gi=∂yi^(t−1)l(yi,yi^(t−1)),hi=∂yi^(t−1)2l(yi,yi^(t−1))
以下逐步解释:
-
形式。比较我们选择的泰勒公式的形式与损失函数的形式
l(y,y^(t−1)+ft(x))
上节已经说过,yi 和 y^(t−1) 为都为常数,相当于 x;而第 t 棵树 ft 不确定,相当于 Δx。
-
偏导。求和中的每一项都对本项的 y^i(t−1) 求偏导,如果使用以下记号来标记损失函数 l 关于 y^i(t−1) 的一阶偏导和二阶偏导
gi=∂y^(t−1)l(yi,y^(t−1)),hi=∂y^(t−1)2l(yi,y^(t−1))
那么,将每一项的损失函数在 y^i(t−1) 处泰勒二阶展开,则得下式:
l(yi,y^i(t−1)+ft(xi))≃l(yi,y^i(t−1))+gift(xi)+21hift2(xi)
-
常数。l(yi,y^i(t−1))是前 t−1 棵树对此项带来的损失,因此是常数; 在这一轮中,因为gi 和 hi 已经是上一轮的产物,因此也是常数。
如果再次去掉所有常数项,将得到
L(t)≃i=1∑n[gift(xi)+21hift2(xi)]+Ω(ft)
其中 gi=∂yi^(t−1)l(yi,yi^(t−1)),hi=∂yi^(t−1)2l(yi,yi^(t−1)) 。
06 - 定义树的复杂度
我们使用两个部分来定义一棵树 ft 的复杂度:
- 叶子节点的数量 T
- 叶子节点权重向量 w 的 L2 范数
即:
Ω(ft)=γT+21λj=1∑Twj2其中 γ 和 λ 都是超参数。

07 - 根据叶子节点重组累加
设属于第j个叶子节点的所有样本下标的集合为
Ij={i∣q(xi)=j}
使用此种表示方法,参考模型前提设定,可将目标函数改换为按照叶子节点累加的形式
L(t)≃i=1∑n[gift(xi)+21hift2(xi)]+Ω(ft)=i=1∑n[giwq(xi)+21hiwq(xi)2]+λ21j=1∑Twj2+γT=j=1∑T⎣⎡⎝⎛i∈Ij∑gi⎠⎞wj+21⎝⎛i∈Ij∑hi+λ⎠⎞wj2⎦⎤+γT=j=1∑T[Gjwj+21(Hj+λ)wj2]+γT
其中
Gj=i∈Ij∑giHj=i∈Ij∑hi
到此,对目标函数的化简结束。
以下逐行解释:
- 第二行将正则化项引入,同时,将ft(xi)改写为wq(xi),以便于下一行改换为按照节点累加的形式。
- 第三行将按照样本累加改换为按照节点累加。
- 首先,累加的都是下标。原来按照样本下标累加,改换为按照叶子节点下标累加。将 ft(xi) 改写为 wq(xi) ,也是为了适应叶子节点下标的累加写法以改写成wj。
- 其次,如果按照叶子节点累加,那么在同一个叶子节点 j 中的样本的输出值都是一样的,也就是其在权重向量 w 中对应的权重值 wj ,因此原来的 wq(xi) 也可直接改写成 wj,xi 彻底消失。
- 再次,因为每个样本的 gi 和 hi 都是不同的,而且外层的累加使用叶子节点使用下标标记 i ,因此这里需要使用 Ij 来再次叠加。
- 最后,如果中括号中的 wq(xi) 都直接改写成 wj ,则可将其与树复杂度的第一项组合起来,得到中括号中的第二项。
- 第四行引入新的记号。Gj 和 Hj 分别是叶子节点 j 中样本的一阶偏导和二阶偏导累加之和,都是常量。
- 目标函数中只有权重向量 w 中的各个权重值为未知量。
08 - 参数学习
将目标函数作为各个 wj 的二次凸函数来求解 wj∗
wj∗=wjargminL(t)
令 L(t) 对 wj 求偏导,并令其等于零
∂wj∂L(t)=Gj+(Hj+λ)wj=0
解得每个叶子节点的权重值为
wj∗=−Hj+λGj
代入即得最优化的目标函数值
L(t)∗=−21j=1∑THj+λGj2+γT
也可使用二次函数求顶点坐标的方法求得。

核心生长细节
09 - 如何分裂节点
在实际训练的第 t 轮,模型都使用贪心法进行树节点的分裂:
-
对树中的每个叶子结点尝试进行分裂;
-
每次分裂后,原来的一个叶子结点继续分裂为左右两个子叶子结点,原叶子结点中的样本集将根据该结点的判断规则分散到左右两个叶子结点中;
-
新分裂一个结点后,我们需要检测这次分裂是否会给损失函数带来增益,增益的定义如下:
Lsplit=LL+R−(LL+LR)=[−21HL+HR+λ(GL+GR)2+γ]−[−21(HL+λGL2+HR+λGR2)+2γ]=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ
如果Lsplit>0,或者大于设定的阈值,即分裂后目标函数值下降了,那么可以考虑此次分裂的结果。
10 - 如何寻找最佳分裂点
但是一般都是有多个候选分割点,寻找最佳分割点的理论最佳步骤如下:
- 遍历每个结点的每个特征;
- 对每个特征,按特征值大小将特征值排序;
- 线性扫描,找出每个特征的最佳分裂特征值;
- 在所有特征中找出最好的分裂点,即分裂后增益最大的特征及特征值
以上为全局扫描法,是一种贪心的方法,每次进行分裂尝试都要遍历一遍全部候选分割点,但不适用数据量过大的条件,如内存无法一次载入或者分布式的情况。
基于此,XGBoost提出了一系列加快寻找最佳分裂点的方案:
-
特征预排序+缓存:XGBoost在训练之前,预先对每个特征按照特征值大小进行排序,然后保存为block结构,后面的迭代中会重复地使用这个结构,使计算量大大减小。
-
分位点近似法:对每个特征按照特征值排序后,采用类似分位点选取的方式,仅仅选出常数个特征值作为该特征的候选分割点,在寻找该特征的最佳分割点时,从候选分割点中选出最优的一个。
-
并行查找:由于各个特性已预先存储为block结构,XGBoost支持利用多个线程并行地计算每个特征的最佳分割点,这不仅大大提升了结点的分裂速度,也极利于大规模训练集的适应性扩展。
11 - 如何停止生长
一棵树不会一直生长下去,除了决策树基础的下面是一些常见的限制条件。
- 设定分裂阈值,当新引入的一次分裂所带来的增益 Lsplit 小于阈值时,放弃当前的分裂。
- 当树达到最大深度时,停止建树,因为树的深度太深容易出现过拟合,这里需要设置一个超参数 max_depth
- 当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果存在一个叶子结点的样本权重低于某一个阈值,也会放弃此次分裂。这涉及到一个超参数:最小样本权重和,是指如果一个叶子节点包含的样本数量太少也会放弃分裂,防止树分的太细,这也是过拟合的一种措施。
外围技术简介
12 - XGBoost为什么可以并行训练
XGBoost的并行,并不是说每棵树可以并行训练,XGB本质上仍然采用boosting思想,每棵树训练前需要等前面的树训练完成才能开始训练。XGBoost的并行,指的是特征维度的并行:在训练之前,每个特征按特征值对样本进行预排序,并存储为Block结构,在后面查找特征分割点时可以重复使用,而且特征已经被存储为一个个block结构,那么在寻找每个特征的最佳分割点时,可以利用多线程对每个block并行计算。
13 - XGBoost为什么快
-
分块并行:训练前每个特征按特征值进行排序并存储为Block结构,后面查找特征分割点时重复使用,并且支持并行查找每个特征的分割点
-
候选分位点:每个特征采用常数个分位点作为候选分割点
-
CPU cache 命中优化: 使用缓存预取的方法,对每个线程分配一个连续的buffer,读取每个block中样本的梯度信息并存入连续的Buffer中。
-
Block 处理优化:Block预先放入内存;Block按列进行解压缩;将Block划分到不同硬盘来提高吞吐
14 - XGBoost防止过拟合的方法
-
目标函数添加正则项:叶子节点个数+叶子节点权重的L2正则化
-
列抽样:训练的时候只用一部分特征(不考虑剩余的block块即可)
-
子采样:每轮计算可以不使用全部样本,使算法更加保守
-
shrinkage: 可以叫学习率或步长,为了给后面的训练留出更多的学习空间
15 - XGBoost如何处理缺失值
- 在特征k上寻找最佳 split point 时,不会对该列特征 missing 的样本进行遍历,而只对该列特征值为 non-missing 的样本上对应的特征值进行遍历,通过这个技巧来减少了为稀疏离散特征寻找 split point 的时间开销。
- 在逻辑实现上,为了保证完备性,会将该特征值missing的样本分别分配到左叶子结点和右叶子结点,两种情形都计算一遍后,选择分裂后增益最大的那个方向(左分支或是右分支),作为预测时特征值缺失样本的默认分支方向。
- 如果在训练中没有缺失值而在预测中出现缺失,那么会自动将缺失值的划分方向放到右子结点。
XGBoost如何处理不平衡数据
参考:
https://mp.weixin.qq.com/s/wLE9yb7MtE208IVLFlZNkw