1、前言
上个月我说要写关于梯度提升树和XGBoost的博客,结果写比赛文档和做视频耽搁了,期间还专门读了读陈天齐的原论文(很简单的一篇论文,只需要一点英语功底就可以读起来毫不费劲),磨磨蹭蹭,终于可以开始写博客了。这篇博客我会简单介绍GBDT,和XGBoost的推导,毕竟XGBoost是基于GBDT的改进,还有XGBoost相对于CBDT的提升与工程化运用。
本篇博客参考
机器学习-一文理解GBDT的原理
GBDT算法原理深入解析
【机器学习】决策树(下)——XGBoost、LightGBM(非常详细)
从GBDT到XGBoost 算法介绍
原版论文(en)
2、GBDT
介绍
我们知道,Boosting模型通过弱分类器的不断相加得到最终的强学习器,Adaboost是通过边训练边修改样本权重分布和弱学习器分类权重的算法,每一次训练都是通过上一次迭代的弱分类器的误差来更新样本权重分布;但是GBDT(Gradient Boosting Decision Tree)与其不同,它不涉及样本权重更新,而且根据梯度下降进行下一次迭代,他的基学习器只能使用CART回归树模型。
为什么限定于CART回归树?
这与决策树算法自身的优点有很大的关系。决策树可以认为是if-then规则的集合,易于理解,可解释性强,预测速度快。同时,决策树算法相比于其他的算法需要更少的特征工程,比如可以不用做特征标准化,可以很好的处理字段缺失的数据,也可以不用关心特征间是否相互依赖等。(摘自参考博客)
基于残差与基于负梯度的GBDT
有很多同学分不清基于残差和基于负梯度的区别,现在我们就来理一理。
首先假设我们有训练数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x n , y n ) } D = \left \{ (x_{1},y_{1}), (x_{2},y_{2}),...,(x_{n},y_{n}) \right \} D={(x1,y1),(x2,y2),...,(xn,yn)},我们需要迭代训练T轮,每一轮生成一个弱分类器 f t , t = 1 , 2 , . . . , T f_{t},\ t=1,2,...,T ft, t=1,2,...,T,强学习器 F t = f t + F t − 1 F_{t} =f_t+F_{t-1} Ft=ft+Ft−1
那么,何为残差?
而残差其实就是 y i − F t − 1 ( x i ) y_i-F_{t-1}(x_i) yi−Ft−1(xi),即上一次迭代生成的强学习器预测值与真实值之间的差距,对于一个新的模型 f t f_t ft来说,他获得的训练集就变成了 D = { ( x 1 , y 1 − F t − 1 ( x 1 ) ) , ( x 2 , y 2 − F t − 1 ( x 2 ) ) , . . . , ( x n , y n − F t − 1 ( x n ) ) } D = \left \{ (x_{1},y_{1}-F_{t-1}(x_1)), (x_{2},y_{2}-F_{t-1}(x_2)),...,(x_{n},y_{n}-F_{t-1}(x_n)) \right \} D={(x1,y1−Ft−1(x1)),(x2,y2−Ft−1(x2)),...,(xn,yn−Ft−1(xn))}
可以认为是前面的强学习器生成后存在的缺陷,交由下一次迭代的弱分类器来弥补。
其基本流程如下:
- 输入训练集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x n , y n ) } D = \left \{ (x_{1},y_{1}), (x_{2},y_{2}),...,(x_{n},y_{n}) \right \} D={(x1,y1),(x2,y2),...,(xn,yn)}
- 1.初始化 F 0 ( x ) , f 0 ( x ) F_0(x),f_0(x) F0(x),f0(x)
- 2.计划生成T棵树(迭代T次),
t
=
1
,
2
,
.
.
T
t=1,2,..T
t=1,2,..T
- 2.1计算残差 y i − F t − 1 ( x i ) , i = 1 , 2 , . . . n y_i-F_{t-1}(x_i),i=1,2,...n yi−Ft−1(xi),i=1,2,...n
- 2.2拟合残差学习生成一颗新的树,得到 f t ( x ) f_t(x) ft(x)
- 2.3. F t = F t − 1 + f t ( x ) F_{t} = F_{t-1}+f_t(x) Ft=Ft−1+ft(x)
- 3.得到强学习器 F ( x ) = ∑ t = 1 T f t ( x ) F(x) = \sum_{t=1}^{T}f_t(x) F(x)=∑t=1Tft(x)
其实基于残差的GBDT是基于负梯度的一种特殊情况,仅仅在损失函数为平方损失函数 L ( y , F ( x ) ) = 1 2 ( y − F ( x ) ) 2 L(y,F(x))= \frac{1}{2}(y-F(x))^2 L(y,F(x))=21(y−F(x))2,也就是目标函数 J = ∑ i = 0 n 1 2 ( y i − F ( x i ) ) 2 J =\sum_{i=0}^{n} \frac{1}{2}(y_i-F(x_i))^2 J=∑i=0n21(yi−F(xi))2时成立。
为什么?基于梯度的优化我们都清楚是对目标函数求一阶导得到梯度,那么对平方损失函数求一阶导可知
∂
J
∂
F
(
x
i
)
=
∂
∑
i
L
(
y
i
,
F
(
x
i
)
)
∂
F
(
x
i
)
=
∂
L
(
y
i
,
F
(
x
i
)
)
∂
F
(
x
i
)
=
F
(
x
i
)
−
y
i
(2.1)
\frac{\partial J}{\partial F(x_i)} = \frac{\partial \sum_{i}^{}L(y_i,F(x_i))}{\partial F(x_i)} = \frac{\partial L(y_i,F(x_i))}{\partial F(x_i)} = F(x_i)-y_i \tag{2.1}
∂F(xi)∂J=∂F(xi)∂∑iL(yi,F(xi))=∂F(xi)∂L(yi,F(xi))=F(xi)−yi(2.1)
所以可以知道,残差
y
i
−
F
t
−
1
(
x
i
)
y_i-F_{t-1}(x_i)
yi−Ft−1(xi),其实就是负梯度方向
−
∂
J
∂
F
(
x
i
)
=
y
i
−
F
t
−
1
(
x
i
)
(2.2)
-\frac{\partial J}{\partial F(x_i)} = y_i-F_{t-1}(x_i) \tag{2.2}
−∂F(xi)∂J=yi−Ft−1(xi)(2.2)
所以基于残差的GBDT只是一个目标函数为平方损失函数的特殊情况,要知道的是,我们不是基于残差对数据进行拟合,而是在梯度下降时正好得出了负梯度方向为残差。
以上我们知道基于残差其实是基于梯度下降的一种特殊形式,然而对于不同的损失函数,它的负梯度方向也有所不同,通用的公式为
−
[
∂
L
(
y
i
,
f
(
x
i
)
)
∂
f
(
x
i
)
]
F
(
x
)
=
F
t
−
1
(
x
)
(2.3)
-[\frac{\partial L(y_i,f(x_i))}{\partial f(x_i)}] _{F(x)=F_{t-1}(x)} \tag{2.3}
−[∂f(xi)∂L(yi,f(xi))]F(x)=Ft−1(x)(2.3)
这就是损失函数的负梯度在当前模型的值。
3、XGBoost
介绍
XGBoost可以简单地理解为GBDT的进阶版,对GBDT进行了优化和工程化实现,简单来说它具有正则化,行或列的上采样,精确贪婪,稀疏值处理,并行生成,缓存感知,核外计算等优点,我会根据论文一一介绍,如果感兴趣的同学可以直接看论文,这篇论文简单易懂https://arxiv.org/abs/1603.02754,天地良心,我真的没有骗你们。
接下来我会对论文中的符号进行一些小小的变形,以符合我个人的推导习惯。
推导
有数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x n , y n ) } D = \left \{ (x_{1},y_{1}), (x_{2},y_{2}),...,(x_{n},y_{n}) \right \} D={(x1,y1),(x2,y2),...,(xn,yn)}
由于XGBoost为加性模型,有K棵树,那么进行K次相加,它的预测可写为下式
y ^ i = F ( x i ) = ∑ k = 1 K f k ( x i ) , f k ∈ F (3.1) \widehat{y}_i = F(x_i) = \sum_{k=1}^{K}f_k(x_i),f_k\in F \tag{3.1} y i=F(xi)=k=1∑Kfk(xi),fk∈F(3.1)
其中F是所有回归树的空间,也就是CART树
我们定义目标函数为
L
(
F
)
=
∑
i
l
(
y
^
i
,
y
i
)
+
∑
t
Ω
(
f
k
)
Ω
(
f
k
)
=
γ
T
+
1
2
λ
∥
w
∥
2
(3.2)
L(F) = \sum_{i}l(\widehat{y}_i,y_i)+\sum_{t}\Omega(f_k) \\ \Omega(f_k) =\gamma T + \frac{1}{2}\lambda\parallel w\parallel ^2 \tag{3.2}
L(F)=i∑l(y
i,yi)+t∑Ω(fk)Ω(fk)=γT+21λ∥w∥2(3.2)
l l l 是可微的凸损失函数,而第二项 Ω \Omega Ω 是对模型的复杂度的惩罚,也就是正则项。树模型的复杂度由生成的树的叶子节点数量 T T T 和叶子节权重向量 w w w 的L2范数决定的,该附加的正则化项有助于平滑最终学习的权重,以避免过拟合。
对于上述目标函数,其添加了函数作为,并且无法使用传统的欧式空间传统优化算法进行优化,于是我们使用加性方式,在第 t 次迭代时,添加一个新的弱分类器 f t f_t ft来最小化下面的目标函数, y ^ i ( t − 1 ) \widehat{y}_i^{(t-1)} y i(t−1)也就是上一次迭代的预测结果。
L ( t ) = ∑ i = 1 n l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + Ω ( f t ) (3.3) L^{(t)} = \sum_{i=1}^{n}l(y_i,\widehat{y}_i^{(t-1)}+f_t(x_i))+\Omega(f_t) \tag{3.3} L(t)=i=1∑nl(yi,y i(t−1)+ft(xi))+Ω(ft)(3.3)
此时我们对目标函数进行二阶泰勒展开,它将被用于快速优化目标函数
L
(
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
)
(3.4)
L^{(t)} \simeq \sum_{i=1}^{n} [l(y_i,\widehat{y}_i^{(t-1)})+g_if_t(x_i)+\frac{1}{2}h_i f_t^2(x_i)]+\Omega(f_t) \tag{3.4}
L(t)≃i=1∑n[l(yi,y
i(t−1))+gift(xi)+21hift2(xi)]+Ω(ft)(3.4)
其中 g i g_i gi是 f t f_t ft生成之前集成的的强分类器的目标函数的一阶导数,为 g i = ∂ y ^ ( t − 1 ) l ( y i , y ^ ( t − 1 ) g_i = \partial_{\widehat{y}^{(t-1)} }l(y_i,\widehat{y}^{(t-1)} gi=∂y (t−1)l(yi,y (t−1),而 h i h_i hi则是二阶导数,为 h i = ∂ y ^ ( t − 1 ) 2 l ( y i , y ^ ( t − 1 ) h_i = \partial^2_{\widehat{y}^{(t-1)} }l(y_i,\widehat{y}^{(t-1)} hi=∂y (t−1)2l(yi,y (t−1)。
对于常量,我们认为他在目标函数最小化时不起作用,所以可以进行移除简化,由于已计算的损失
l
l
l 为常数,所以可以移除,移除后简化为
L
~
(
t
)
≃
∑
i
=
1
n
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
(3.5)
\widetilde{L}^{(t)} \simeq \sum_{i=1}^{n} g_if_t(x_i)+\frac{1}{2}h_i f_t^2(x_i)]+\Omega(f_t) \tag{3.5}
L
(t)≃i=1∑ngift(xi)+21hift2(xi)]+Ω(ft)(3.5)
由于要学习的函数仅依赖于目标函数,我们只需要计算得出损失函数的一阶导和二阶导即可求得每一迭代所要生成的 f t f_t ft
基于决策树的目标函数
假设一棵树有T个叶子节点,此时定义
I
j
=
{
i
∣
q
(
x
i
)
=
j
}
I_j = \left\{ i|q(x_i)=j \right\}
Ij={i∣q(xi)=j}为叶子节点
j
j
j 的样本集合,于是上式可重写为展开
Ω
\Omega
Ω的式子。
L
~
(
t
)
=
∑
i
=
1
n
[
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
γ
T
+
1
2
λ
w
j
2
=
∑
j
=
1
T
[
(
∑
i
∈
I
j
g
i
)
w
j
+
1
2
(
∑
i
∈
I
j
h
i
+
λ
)
w
j
2
]
+
γ
T
(3.6)
\begin{aligned} \widetilde{L}^{(t)}&= \sum_{i=1}^{n} [g_if_t(x_i)+\frac{1}{2}h_i f_t^2(x_i)]+\gamma T + \frac{1}{2}\lambda w_j ^2 \\ &= \sum_{j=1}^{T}[(\sum_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum_{i\in I_j}h_i+\lambda)w^2_j]+\gamma T \end{aligned}\tag{3.6}
L
(t)=i=1∑n[gift(xi)+21hift2(xi)]+γT+21λwj2=j=1∑T[(i∈Ij∑gi)wj+21(i∈Ij∑hi+λ)wj2]+γT(3.6)
为什么会写成 ∑ i ∈ I j g i , ∑ i ∈ I j h i \sum_{i\in I_j}g_i,\sum_{i\in I_j}h_i ∑i∈Ijgi,∑i∈Ijhi的形式,是因为不论数据样本在树结构中怎么分配,他是最终一定会落在叶子节点上,一个叶子节上具有多个样本的集合。
为了确定
q
(
x
)
q(x)
q(x),这里的
q
(
x
)
q(x)
q(x)代表了该样本在哪个叶子结点上,也就是树的结构,我们通过令目标函数一阶导为0,确定叶子节点
j
j
j 的权重
w
j
∗
w_j^*
wj∗,此时
w
j
∗
=
−
∑
i
∈
I
j
g
i
∑
i
∈
I
j
h
i
+
λ
(3.7)
w_j^* = - \frac{\sum_{i\in I_j} g_i}{\sum_{i \in I_j} h_i +\lambda} \tag{3.7}
wj∗=−∑i∈Ijhi+λ∑i∈Ijgi(3.7)
同时计算目标函数的值
L ~ ( t ) ( q ) = − 1 2 ∑ j = 1 J ∑ i ∈ I j g i ∑ i ∈ I j h i + λ (3.8) \widetilde{L}^{(t)}(q) = - \frac{1}{2} \sum_{j=1}^{J}\frac{\sum_{i\in I_j} g_i}{\sum_{i \in I_j} h_i +\lambda} \tag{3.8} L (t)(q)=−21j=1∑J∑i∈Ijhi+λ∑i∈Ijgi(3.8)
上式可作为评分函数来测量树结构q的质量,值越小,树的质量越高。
通过以上推导我们可以大概总结弱分类器生成的步骤
- 列举所有可能的树结构q
- 使用公式 3.8 计算目标函数的值,根据大小判断树的好坏
- 在所有列举出来的结构中选择最优的树,并根据 3.7 计算每一个叶子节点的预测值。
事实上,树结构可能是无穷的,我们不可能列举出所有的树结构,于是XCBoost采用贪婪算法来帮助生成树的结构 q ( x ) q(x) q(x),算法从单个叶子开始,然后迭代地将分支添加到树中。
树节点分裂寻找算法
精确贪婪算法
精确贪婪算法类似于CART中最优特征与切分点的查找,通过遍历每个特征下的每个可能的切分点取值,计算切分后的增益,选择增益最大的特征及切分点
对于每次分裂的收益,我们首先假设当前节点为M,分裂后的左节点为L,分裂后的右节点为R,则此次分裂的收益为当前节点的目标函数减去左右两节点目标函数之和。
分裂前的目标函数为
−
1
2
[
(
G
L
+
G
R
)
2
H
L
+
H
R
+
λ
]
+
γ
-\frac{1}{2} [\frac{(G_L + G_R)^2}{H_L + H_R + \lambda}] + \gamma
−21[HL+HR+λ(GL+GR)2]+γ
分裂后的目标函数为
−
1
2
[
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
]
+
γ
-\frac{1}{2} [ \frac{G_L^2}{H_L + \lambda}+ \frac{G_R^2}{H_R + \lambda} ] + \gamma
−21[HL+λGL2+HR+λGR2]+γ
对于目标函数而言,分裂后的收益为分裂前-减去分裂后
G
a
i
n
=
1
2
[
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
−
(
G
L
+
G
R
)
2
H
L
+
H
R
+
λ
]
−
γ
Gain = \frac{1}{2} [ \frac{G_L^2}{H_L + \lambda}+ \frac{G_R^2}{H_R + \lambda} - \frac{(G_L + G_R)^2}{H_L + H_R + \lambda}] - \gamma
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ
精确贪婪算法的问题在于,他非常强大,但是他要遍历枚举所有可能的分裂点,如果数据量过于庞大,数据不能完全容纳到内存中时,那么精确贪心算法会非常耗时,且效率不高。
近似算法
为了弥补这一缺点,XGBoost给出了解决方案——近似算法。
该算法有两种变体,即global全局策略和local本地策略。
- global策略:全局变量建议在树构建的初始阶段提出所有候选分裂点,同时每次分裂都采取同样的分割。也就是一开始就决定好了,每次都是在同样的候选中挑选。
- local策略:每次分裂后都会重新提出局部变量,也就是重新提出候选分裂点。
global因为一开始就决定好了,且节点不进行划分,通常需要更多的候选分裂点,而local会在分裂后优化候选节点,更适合于较深的树木。
结语
其实这篇博客写到贪婪算法和近似算法的时候停了一周左右,原本想把XGBoost工程化部分比如稀疏感知,核外计算等讲一遍,由于搬家和考试的缘故,同时也有点心不在焉,于是就耐着性子写完近似算法就决定暂时结束,免得自己一直记挂着。
前面的算法推导的确是下了功夫写的,自己也实打实推算过,写的时候主要还是参照论文,别的博客主要是在边边角角进行补充,主要作为我自己的学习参考。
如果大家看到有些话语和别的博客大同小异,别急着说谁抄谁,有可能大家都是对论文进行解读,我就发现了不止一篇的论文解读博客,严格说起来大家都是搬运工。
如果以后能抽出时间,我应该会把后面的写完。