树算法系列之四:XGBoost

1.CART树回顾

在正式讲Xgboost之前,我们先回顾一下CART树。
CART树的生成过程,就是递归构建二叉树的过程,本质上是在某个特征维度对样本空间进行划分。这种空间划分是一种NP Hard问题,因此一般都是用启发式的方式去求解,即求某个特征j的且分点s,使得
min ⁡ j , s [ min ⁡ c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + min ⁡ c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] \min_ { j , s } \left[ \min _ { c _ { 1 } } \sum _ { x _ { i } \in R _ { 1 } ( j , s ) } \left( y _ { i } - c _ { 1 } \right) ^ { 2 } + \min _ { c _ { 2 } } \sum _ { x _ { i } \in R _ { 2 } ( j , s ) } \left( y _ { i } - c _ { 2 } \right) ^ { 2 } \right] j,sminc1minxiR1(j,s)(yic1)2+c2minxiR2(j,s)(yic2)2

2.XGBoost叶子节点取值推导

XGBoost的大体思路跟GBDT一样,也是通过不断进行特征分裂添加树,来学习一个新的弱学习器,来拟合上次预测结果的残差。

首先来看一下XGBoost的损失函数
L ( ϕ ) = ∑ i l ( y i , y ^ i ) + ∑ k Ω ( f k ) , 其 中 Ω ( f k ) = γ T + 1 2 λ ∣ ∣ w ∣ ∣ 2 L ( \phi ) = \sum _ { i } l \left( y _ { i } , \hat { y } _ { i } \right) + \sum _ { k } \Omega \left( f _ { k } \right) ,\quad 其中 \quad \Omega \left( f _ { k } \right) = \gamma T + \frac { 1 } { 2 } \lambda | | w | | ^ { 2 } L(ϕ)=il(yi,y^i)+kΩ(fk),Ω(fk)=γT+21λw2

很容易可以看出来
∑ i l ( y i , y ^ i ) \sum\limits_i l \left( y _ { i } , \hat { y } _ { i } \right) il(yi,y^i)是代表预测值与真实值的偏差,而 ∑ k Ω ( f k ) \sum\limits_k \Omega \left( f _ { k } \right) kΩ(fk)为正则项。

其中, y i y_i yi为样本的真实值, y ^ i \hat y_ i y^i为预测值。 f k f_k fk表示第k棵树, γ \gamma γ为叶子树的正则项,起到剪枝的作用,T为树的叶子节点个数, w w w为叶子节点的分数, λ \lambda λ为叶子节点分数的正则项,起到防止过拟合的作用。

前面树系列的文章提到过很多次,boost系列算法的核心思想是用新生成的树拟合上次预测的残差(对于GBDT来说是上次预测的负梯度方向)。生成第t棵树后,预测值可以写为
y ^ i = y ^ i t − 1 + f t ( x ) \hat y_i = \hat y_i ^{t-1} + f_t(x) y^i=y^it1+ft(x)

此时,损失函数可以表示为

L t = ∑ i = 1 l ( y i t , y ^ i t − 1 ) + f t ( x i ) + Ω ( f t ) L^t = \sum _{i=1} l ( y _i^t , \hat y_i ^{t-1}) + f_t(x_i) + \Omega (f _t) Lt=i=1l(yit,y^it1)+ft(xi)+Ω(ft)

于是我们要找一个 f t f_t ft让损失函数最小。对上面的式子进行泰勒展开
L ( t ) ≃ ∑ i = 1 n [ l ( y i , y ^ ( t − 1 ) ) + g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) \mathcal { L } ^ { ( t ) } \simeq \sum _ { i = 1 } ^ { n } \left[ l \left( y _ { i } , \hat { y } ^ { ( t - 1 ) } \right) + g _ { i } f _ { t } \left( \mathbf { x } _ { i } \right) + \frac { 1 } { 2 } h _ { i } f _ { t } ^ { 2 } \left( \mathbf { x } _ { i } \right) \right] + \Omega \left( f _ { t } \right) L(t)i=1n[l(yi,y^(t1))+gift(xi)+21hift2(xi)]+Ω(ft)

其中,
g i = ∂ l ( y i , y ^ i t − 1 ) ∂ y ^ i t − 1 g_i = \frac{\partial l(y_i, \hat y_i ^{t-1})} {\partial \hat y_i ^{t-1}} gi=y^it1l(yi,y^it1)
h i = ∂ 2 l ( y i , y ^ i t − 1 ) ∂ ( y ^ i t − 1 ) 2 h_i = \frac{\partial ^ 2 l(y_i, \hat y_i ^{t-1})} {\partial (\hat y_i ^{t-1})^2} hi=(y^it1)22l(yi,y^it1)

其中 g i , h i g_i, h_i gi,hi的含义可以按如下方式理解
假设目前已经有t-1棵数,这t-1棵树组成的模型对第i个训练样本有一个预测值 y ^ i \hat y_i y^i y ^ i \hat y_i y^i与真实值 y i y_i yi肯定有差距,这个差距可以用 l ( y i , y ^ i ) l(y_i, \hat y_i) l(yi,y^i)这个损失函数来衡量,所以此处的 g i g_i gi h i h_i hi就是对于该损失函数的一阶导和二阶导。(参考文献1)

搞定了前面的损失函数部分,接下来再观察一下正则项
Ω ( f t ) = γ T + 1 2 λ ∑ j = 1 T w j 2 \Omega (f_t) = \gamma T + \frac{1}{2} \lambda \sum_{j=1}^T w_j^2 Ω(ft)=γT+21λj=1Twj2

对于上面的正则项,我们最简单的理解方式为:要使模型尽可能简单,那就是叶子节点的个数T要小,同时叶子节点上的值也尽可能小。

因为前t-1棵树的预测分数与y的残差对目标函数优化不影响, 可以直接去掉,所以损失函数可以写成

L ( t ) ≃ ∑ i = 1 n [ g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) \mathcal { L } ^ { ( t ) } \simeq \sum _ { i = 1 } ^ { n } \left[ g _ { i } f _ { t } \left( \mathbf { x } _ { i } \right) + \frac {1} {2} h_i f _ { t } ^ { 2 } \left( \mathbf { x } _ { i } \right) \right] + \Omega \left( f _ { t } \right) L(t)i=1n[gift(xi)+21hift2(xi)]+Ω(ft)

上面式子的含义是将每个样本的损失函数相加,而每个样本最终都会落到一个叶子节点上,所以我们可以将所有同一个叶子结点的样本重组起来
L ^ ( t ) = ∑ i = 1 n [ g i f t ( x i ) + 1 2 h t f t 2 ( x i ) ] + Ω ( f t ) = ∑ i = 1 n [ g i f t ( x i ) + 1 2 h t f t 2 ( x i ) ] + γ 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 = 1 2 ∑ j = 1 T ( H j + λ ) ( w j + G j H j + λ ) 2 + γ T − 1 2 ∑ j = 1 T G j 2 H j + λ \begin{aligned} \hat{ \mathcal { L }}^{(t)} & =\sum_{i=1}^n [g_if_t(x_i) + \frac12h_tf_t^2(x_i)] + \Omega(f_t) \\ & =\sum_{i=1}^n [g_if_t(x_i) + \frac12h_tf_t^2(x_i)] + \gamma T+\frac{1}{2}\lambda\sum\limits_{j=1}^{T}w_j^2 \\ & = \sum\limits_{j=1}^{T} [(\sum\limits_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum\limits_{i\in I_j}h_i+\lambda) w_j^2]+\gamma T \\ & = \frac{1}{2}\sum\limits_{j=1}^{T} (H_j+\lambda)(w_j + \frac{G_j}{H_j+\lambda})^2+\gamma T -\frac{1}{2}\sum\limits_{j=1}^{T}\frac{G_j^2}{H_j+\lambda} \end{aligned} L^(t)=i=1n[gift(xi)+21htft2(xi)]+Ω(ft)=i=1n[gift(xi)+21htft2(xi)]+γT+21λj=1Twj2=j=1T[(iIjgi)wj+21(iIjhi+λ)wj2]+γT=21j=1T(Hj+λ)(wj+Hj+λGj)2+γT21j=1THj+λGj2

其中, G j = ∑ i ∈ I j g i G_j = \sum _{i \in I_j} g_i Gj=iIjgi是落入叶子节点i所有样本一阶梯度的总和,而 H j = ∑ i ∈ I j h i H_j = \sum _{i \in I_j} h_i Hj=iIjhi是落入叶子节点i所有样本二阶梯度的总和。

基于中学数学的原理,我们可以得知:

w j ∗ = − G j H j + λ w_j^* = -\frac{G_j}{H_j+\lambda} wj=Hj+λGj
时,损失函数最小。此时最终的损失函数大小为
L ^ ∗ = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T \hat{\mathcal { L }}^{*} =-\frac{1}{2}\sum\limits_{j=1}^{T}\frac{G_j^2}{H_j+\lambda}+\gamma T L^=21j=1THj+λGj2+γT

3.与牛顿法的关系

上面我们推导过程中,用到了二阶导数。而常见的基于二阶导数的优化方法就是牛顿法,下面我们看看与牛顿法的关系。
假设有一个函数 f ( x ) f(x) f(x)二阶可微,我们对其进行泰勒展开到二阶,有
f ( x ) = f ( x k ) + f ′ ( x k ) ( x − x k ) + f ′ ′ ( x k ) ( x − x k ) 2 f(x) = f(x_k) + f'(x_k)(x-x_k) + f''(x_k)(x-x_k)^2 f(x)=f(xk)+f(xk)(xxk)+f(xk)(xxk)2
上面的式子对x求导,并令导数等于0
f ′ ( x k ) + f ′ ′ ( x k ) ( x − x k ) = 0 f'(x_k) + f''(x_k)(x-x_k) = 0 f(xk)+f(xk)(xxk)=0

可以得到x的迭代公式为
x = x k − f ′ ( x k ) f ′ ′ ( x k ) x = x_k - \frac{f'(x_k)}{f''(x_k)} x=xkf(xk)f(xk)
这就是牛顿法的迭代公式。

对比一下叶子节点的取值方式
w j ∗ = − G j H j + λ w_j^* = -\frac{G_j}{H_j+\lambda} wj=Hj+λGj
与牛顿法唯一的区别在于,分母上二阶导多了一个正则项 λ \lambda λ,形式上完全一致,就是一阶导除以二阶导,与牛顿法的形式完全一致。

4.XGBoost的树分裂方式

上面推导了这么一大串,都是在分析叶子节点怎么取值。提升树系列的算法,还有很重要的一点是树的分裂方式。
首先我们回顾一下其他算法比如GBDT是怎么进行分裂的。标准的做法是选择MSE作为损失函数,采用启发式的方式来选择某个特征j的切分点s进行分裂。这个分裂的过程,并不一定总与损失函数相关。比如在分类树中,我们一般都会用对数损失函数(交叉熵)来评估模型的最终效果,这个时候树分裂的结果与损失函数就没有直接的关系。

XGBoost的树分裂是直接与损失函数相关的。在分裂的时候,会计算损失函数的增益Gain
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} \left[\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} \right] - \gamma Gain=21[HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2]γ

上面这个式子其实很容易理解
( G L + G R ) 2 H L + H R + λ \frac{(G_L + G_R)^2}{H_L + H_R+\lambda} HL+HR+λ(GL+GR)2是分裂前最终的损失函数值,而
G L 2 H L + λ , G R 2 H R + λ \frac{G_L^2}{H_L+\lambda}, \frac{G_R^2}{H_R+\lambda} HL+λGL2,HR+λGR2
分别为分裂后的左右子树的损失函数值

那树进行分裂的时候,自然希望损失函数减少得越多越好。
前面我们推导出了损失函数最终的表达式

L ^ ∗ = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T \hat{\mathcal { L }}^{*} =-\frac{1}{2}\sum\limits_{j=1}^{T}\frac{G_j^2}{H_j+\lambda}+\gamma T L^=21j=1THj+λGj2+γT

那我们想要的结果就是分列前的损失函数减去分裂后的损失函数,差值最大,注意前面的负号,最后的分裂准则为
m a x ( G L 2 H L + λ + G R 2 H R + λ − ( G L + G R ) 2 H L + H R + λ ) max\left(\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}\right) max(HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2)

5.XGBoost的各种Tricks总结

防止过拟合方法:
1.加入了正则项,对叶子节点数量,叶子节点分数加入了正则惩罚项。
2.加入了学习率,减少了单棵树影响,给后面树的优化留了更多空间。
3.列采样(特征采样),与随机森林类似,不仅来带来更好的效果,还可以提高运行速度。

缺失值的处理
在别的算法中,一般会使用中位数,均值或者两者进行融合计算的方式去处理缺失值。但是xgboost能处理缺失值,模型允许缺失值存在。
在寻找分裂点的时候,不对该特征缺失的样本进行遍历,只遍历该特征有的样。具体实现时,将该特征值缺失的样本分别分配到左叶子节点与右叶子节点,分别计算增益,然后选择增益较大的方向进行分裂即可。如果训练样本中没有缺失值,而预测时有缺失,默认将缺失值划分到右子节点。

分裂点的选择
并不把所有特征值间的间隔点作为候选分裂点,而是用分位数的方法将其作为分裂的候选集。

并行处理
XGBoost中虽然树之间是串行关系,但是同层级的节点可以并行。对于某个节点,节点内选择最佳分裂点与计算分裂点的增益是可以并行的。

基学习器(弱学习器)
传统的GBDT与CART树作为基分类器,XGBoost不仅支持CART树,还支持线性分类器,这个时候xgboost就相当于带L1与L2正则项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。

6.XGBoost 与GBDT的对比

主要的区别其实就是上面的Tricks。

参考文献

1.https://www.jianshu.com/p/ac1c12f3fba1

你可能感兴趣的:(ml,algorithm,XGBoost,过拟合,调参,牛顿法)