算法原理
想要了解XGBoost算法的原理,首先需要理解Boosting算法。简单来说,Boosting算法是将个体学习器集成为更复杂学习器的机器学习方法,它更强调个体学习器之间存在强依赖关系,因此也可认为是串行集成学习方法。相比下,Bagging算法则属于并行集成学习方法。
Boosting算法的基本原理是:首先用初始样本训练一个基学习器,根据学习表现对样本分布进行调整,使得表现差的样本获得更多的关注,然后不断迭代用调整分布后的样本训练下一个基学习器,直到基学习器数量达到指定数目。
Boosting算法族中最著名的即是Freund于1997年提出的AdaBoost算法,可将其理解为“加性模型”,即最终的学习器为 T T T个基学习器的“加权和”模型,每次使用训练错误的样本训练新的基学习器,并通过权重调整降低之前表现不佳的学习器的影响,最终降低集成学习器的结果偏差。
2001年,Freund又提出了Gradient Boosting框架,将损失函数扩展到更一般的情况,即通过梯度计算回归拟合的残差(准确应称之为伪残差),基于该残差生成新的基学习器,并计算其最优的叠加权重值。如果选择基分类器为决策树(如CART树),则对应为GBDT算法。
CART树是分类树,并不满足GB回归损失函数的要求,因此需要将分类GINI系数指标替换为最小均方差,即当所有分枝的预测结果唯一或是达到叶节点数量上限时,以该树所有节点的预测均值作为该分类器的预测结果。此外,GBDT还借鉴了Bagging集成学习的一些思想,例如通过随机抽样提高模型的泛化能力,通过交叉验证选择最优参数等。
2014年,陈天奇博士提出了XGBoost算法,它可认为是在GBDT算法基础上的进一步优化。首先,XGBoost算法在基学习器损失函数中引入了正则项,控制减少训练过程当中的过拟合;其次,XGBoost算法不仅使用一阶导数计算伪残差,还计算二阶导数可近似快速剪枝的构建新的基学习器;此外,XGBoost算法还做了很多工程上的优化,例如支持并行计算、提高计算效率、处理稀疏训练数据等等。
目标函数
xgboost是GBDT的一个变种,它对损失函数进行泰勒展开时,取得是二阶展开而非一阶展开,因此与实际
O
b
j
Obj
Obj函数的值更接近(前提条件是损失函数二阶可导)
泰勒公式的二阶展开为:
f
(
x
+
Δ
x
)
≈
f
(
x
)
+
f
′
(
x
)
Δ
x
+
1
2
f
′
′
(
x
)
Δ
x
2
f(x+\Delta x) \approx f(x)+f^{\prime}(x) \Delta x+\frac{1}{2} f^{\prime \prime}(x) \Delta x^{2}
f(x+Δx)≈f(x)+f′(x)Δx+21f′′(x)Δx2
由此可得目标函数的公式为:
O
b
j
(
t
)
≈
∑
i
=
1
n
[
l
(
y
i
,
y
^
i
t
−
1
)
+
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
g
i
是
l
(
y
i
,
y
^
i
t
−
1
)
关于
y
^
t
−
1
的一阶导
,
h
i
是
l
(
y
i
,
y
^
i
t
−
1
)
关于
y
^
t
−
1
的二阶导
O b j^{(t)} \approx \sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}_{i}^{t-1}\right)+g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right)\\ g_i \text{是$l\left(y_{i}, \hat{y}_{i}^{t-1}\right)$关于$\hat{y}^{t-1}$的一阶导},h_i \text{是$l\left(y_{i}, \hat{y}_{i}^{t-1}\right)$关于$\hat{y}^{t-1}$的二阶导}
Obj(t)≈i=1∑n[l(yi,y^it−1)+gift(xi)+21hift2(xi)]+Ω(ft)gi是l(yi,y^it−1)关于y^t−1的一阶导,hi是l(yi,y^it−1)关于y^t−1的二阶导
对这个 o b j obj obj进行优化就得到第 t t t步的 f t ( x ) f_t(x) ft(x), 然后将每一步的 f ( x ) f(x) f(x)加在一起就得到了整体模型。
用决策树来表示
f
t
(
x
)
f_{t}(x)
ft(x)和
O
b
j
(
t
)
O b j^{(t)}
Obj(t),
f
t
(
x
)
=
w
q
(
x
)
q
(
x
)
代表样本
x
位于哪个叶子结点
,
w
q
代表该叶子结点的取值
f_{t}(x)=w_{q(x)}\\ q(x) \text{代表样本 $x$ 位于哪个叶子结点},w_q \text{代表该叶子结点的取值}
ft(x)=wq(x)q(x)代表样本 x 位于哪个叶子结点,wq代表该叶子结点的取值
若决策树的复杂度定义为:
Ω
(
f
t
)
=
γ
T
+
1
2
λ
∑
j
=
1
T
w
j
2
\Omega\left(f_{t}\right)=\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2}
Ω(ft)=γT+21λj=1∑Twj2
假设
I
j
=
{
i
∣
q
(
x
i
)
=
j
}
I_{j}=\left\{i | q\left(x_{i}\right)=j\right\}
Ij={i∣q(xi)=j} 为第
j
j
j 个叶子结点的样本集合,则目标函数就变为:
O
b
j
(
t
)
≈
∑
i
=
1
n
[
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
=
∑
i
=
1
n
[
g
i
w
q
(
x
i
)
+
1
2
h
i
w
q
(
x
i
)
2
]
+
γ
T
+
1
2
λ
∑
j
=
1
T
w
j
2
=
∑
j
=
1
T
[
(
∑
i
∈
I
j
g
i
)
w
j
+
1
2
(
∑
i
∈
I
j
h
i
+
λ
)
w
j
2
]
+
γ
T
=
∑
j
=
1
T
[
G
j
w
j
+
1
2
(
H
j
+
λ
)
w
j
2
]
+
γ
T
\begin{array}{c}{\quad O b j^{(t)} \approx \sum_{i=1}^{n}\left[g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right)} \\ {=\sum_{i=1}^{n}\left[g_{i} w_{q\left(x_{i}\right)}+\frac{1}{2} h_{i} w_{q\left(x_{i}\right)}^{2}\right]+\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2}} \\ {=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T}\end{array}\\ =\sum_{j=1}^{T}\left[G_{j} w_{j}+\frac{1}{2}\left(H_{j}+\lambda\right) w_{j}^{2}\right]+\gamma T
Obj(t)≈∑i=1n[gift(xi)+21hift2(xi)]+Ω(ft)=∑i=1n[giwq(xi)+21hiwq(xi)2]+γT+21λ∑j=1Twj2=∑j=1T[(∑i∈Ijgi)wj+21(∑i∈Ijhi+λ)wj2]+γT=j=1∑T[Gjwj+21(Hj+λ)wj2]+γT
令:
G
j
=
∑
i
∈
I
j
g
i
,
H
j
=
∑
i
∈
I
j
h
i
G_{j}=\sum_{i \in I_{j}} g_{i}, H_{j}=\sum_{i \in I_{j}} h_{i}
Gj=∑i∈Ijgi,Hj=∑i∈Ijhi
其中 G j G_j Gj 和 H j H_{j} Hj 是固定的
令 O b j Obj Obj 函数关于 w j w_j wj 的一阶导为 0, 即可求的最优的 w j w_j wj:
w j ∗ = − G j H j + λ w_{j}^{*}=-\frac{G_{j}}{H_{j}+\lambda} wj∗=−Hj+λGj
则针对于结构固定的决策树,最优的 O b j Obj Obj 是
O b j = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T O b j=-\frac{1}{2} \sum_{j=1}^{T} \frac{G_{j}^{2}}{H_{j}+\lambda}+\gamma T Obj=−21j=1∑THj+λGj2+γT
分裂结点算法
xgboost中用的却是预剪枝。在目标函数 o b j obj obj 中既考虑了 l o s s loss loss函数拟合训练数据,又考虑了正则项 Ω \Omega Ω 来减少模型复杂度,故无需剪枝。
具体步骤如下:
- 从深度为0的书开始,对每个叶节点枚举所有的可用特征
- 针对每个特征,把属于该节点的训练样本的该特征值升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并采用最佳分裂点时的收益
- 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,把该节点生成出左右两个新的叶子结点,并为每个新节点关联新的样本集
- 回到第一步,继续递归直到满足特定条件。
分类前的针对于该子节点的最优目标函数是: O b j = − 1 2 ( G L + G R ) 2 ( H L + H R ) + λ + γ O b j=-\frac{1}{2} \frac{\left(G_{L}+G_{R}\right)^{2}}{\left(H_{L}+H_{R}\right)+\lambda}+\gamma Obj=−21(HL+HR)+λ(GL+GR)2+γ,分裂后则变成了 O b j = − 1 2 [ G L 2 H L + λ + G R 2 H R + λ ] + 2 γ O b j=-\frac{1}{2}\left[\frac{G_{L}^{2}}{H_{L}+\lambda}+\frac{G_{R}^{2}}{H_{R}+\lambda}\right]+2 \gamma Obj=−21[HL+λGL2+HR+λGR2]+2γ,那么对于该目标函数来说,分裂后的收益为
Gain
=
1
2
[
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
−
(
G
L
+
G
R
)
2
(
H
L
+
H
R
)
+
λ
]
−
γ
\operatorname{Gain}=\frac{1}{2}\left[\frac{G_{L}^{2}}{H_{L}+\lambda}+\frac{G_{R}^{2}}{H_{R}+\lambda}-\frac{\left(G_{L}+G_{R}\right)^{2}}{\left(H_{L}+H_{R}\right)+\lambda}\right]-\gamma
Gain=21[HL+λGL2+HR+λGR2−(HL+HR)+λ(GL+GR)2]−γ
于是便可以用上式来决定最优分裂特征和最优特征分割点。
寻找到最优分割点,常见的方法是枚举所有可能的分割点,称之为 exact greedy algorithm。但exact greedy algorithm计算量过大,而且当数据量较大没法全填入内存时,会很慢。因此xgboost引入了 approximate算法。
该算法对于某个特征 X k X_{k} Xk,首先通过特征分布来确定若干值域分界点 { s k 1 , s k 2 , … , s k l } \left\{s_{k 1}, s_{k 2}, \dots, s_{k l}\right\} {sk1,sk2,…,skl}。然后根据这些值域分界点把样本分入桶中,对每个桶内的样本统计值G,H进行累加,记为分界点的统计量。最后在分界点集合上进行贪心查找,得到的结果就是最佳分裂点的近似。
如何寻找值域分界点
{
s
k
1
,
s
k
2
,
…
,
s
k
l
}
\left\{s_{k 1}, s_{k 2}, \dots, s_{k l}\right\}
{sk1,sk2,…,skl}?XGBoost中介绍了一种方法,叫加权分位数略图 Weighted Quantile Sketch。
为了尽可能的逼近最佳分裂点,我们需要保证采样后的数据分布和原始数据分布尽可能一直。令
D
k
=
{
(
x
1
k
,
h
1
)
,
(
x
2
k
,
h
2
)
,
…
,
(
x
n
k
,
h
n
)
}
D_{k}=\left\{\left(x_{1 k}, h_{1}\right),\left(x_{2 k}, h_{2}\right), \ldots,\left(x_{n k}, h_{n}\right)\right\}
Dk={(x1k,h1),(x2k,h2),…,(xnk,hn)} 表示每个训练样本的第
k
k
k 维特征的值和对应的二阶导数。然后定义排序函数为
r
k
(
z
)
=
∑
(
x
,
h
)
∈
D
k
,
x
<
z
h
∑
(
x
,
h
)
∈
D
k
h
即函数特征值小于
z
的样本分布占比,二阶导
h
是权重
r_{k}(z)=\frac{\sum_{(x, h) \in D_{k}, x<z} h}{\sum_{(x, h) \in D_{k}} h}\\ \text{即函数特征值小于 $z$ 的样本分布占比,二阶导 $h $ 是权重}
rk(z)=∑(x,h)∈Dkh∑(x,h)∈Dk,x<zh即函数特征值小于 z 的样本分布占比,二阶导 h 是权重
然后找到一组点
{
s
k
1
,
s
k
2
,
…
,
s
k
l
}
\left\{s_{k 1}, s_{k 2}, \dots, s_{k l}\right\}
{sk1,sk2,…,skl} 满足:
∣
r
k
(
s
k
,
j
)
−
r
k
(
s
k
,
j
+
1
)
∣
<
ε
| r_k(s_{k, j}) - r_k(s_{k, j+1})| < \varepsilon
∣rk(sk,j)−rk(sk,j+1)∣<ε
其中,
s
k
1
=
m
i
n
i
x
i
k
,
s
k
l
=
m
a
x
i
x
i
k
,
ε
s_{k1} = min_i x_{ik}, s_{kl}= max_i x_{ik}, \varepsilon
sk1=minixik,skl=maxixik,ε是采样率,因为
0
<
r
k
(
z
)
<
1
0 < r_k(z) < 1
0<rk(z)<1,所以我们会得到
1
ε
\frac{1}{\varepsilon}
ε1个分界点。
正则化
目标函数中正则化项存在的原因是为了限制模型的复杂度,让模型在训练集上能够取得较好的结果的前提下尽可能地简单。从前面的推导中可以知道,在 XGBoost 中,对于采用前向分布方法一步步迭代的优化时,我们模型的复杂度就是当前要定义的决策树的复杂度。
对缺失值处理
对于这些缺失值,xgboost将样本分类到默认分支上去,而默认分支是由non-missing value学习得到的。具体算法如下:
由上可见,该算法只考虑non-missing entries I k I_k Ik,因此计算复杂度是数据中的非缺失值个数的线性比值,对于稀疏数据来说,计算的很快。
对于缺失值的样本,算法分别将它们放到左节点和右节点,选取增益最大的一侧作为默认分类。
优缺点
XGBoost是DMLC开源在Github的Gradient Boosting框架,主要作者是陈天奇。它支持C++/Java/Python/R等语言,同样也支持Hadoop/Spark/Flink等分布式处理框架,且在数据竞赛中拥有优异的表现。
相比于普通的GBDT,XGBoost主要优点在于:
- 不仅支持决策树作为基分类器,还支持线性分类器
- 用到了 l o s s loss loss 的二阶泰勒展开,因此与损失函数更接近,收敛更快 - 在代价函数中加入了正则项,用于控制模型复杂度。正则项里包括了树的叶子节点个数和叶子结点输出值的L2范数,可以防止模型过拟合。
- Shrinkage,就是前面所述的
η
\eta
η主要用于削弱每科树的影响,让后面有更大的学习空间。实际应用中,一般把
设小点,迭代次数设大点。 - 列抽样(column sampling)。xgboost从随机森林算法中借鉴来的,支持列抽样可以降低过拟合,并且减少计算。
- 支持对缺失值的处理。对于特征值缺失的样本,xgboost可以学习这些缺失值的分裂方向。
- 支持并行。在对每颗树进行节点分裂时,需要计算每个特征的增益,选择最大的那个特征作为分裂特征,各个特征的增益计算可以开多线程进行
- 近似算法(Approximate Algorithm),树节点在分裂时,需要枚举每个可能的分割点。当数据没法一次性载入内存时,这种方法会很慢。xgboost提出了一种近似的方法去高效的生成候选分割点。