竞赛利器——XGBoost学习笔记

经常参加机器学习相关竞赛的同学肯定对 XGBoost 算法并不陌生。它是 GBDT (梯度提升决策树)的一种高效实现,是传统机器学习算法中对真实分布拟合最好的算法之一,是工业界和竞赛屡试不爽的杀器之一。因此,本文将阐述 XGBoost 算法的基本原理和数学论证,希望能帮助大家了解 GBDT 算法和 XGBoost 算法。

简介

Adaboost 算法相同,GBDT 算法也是集成学习 Boost 家族的成员之一。然而在 Adaboost 中,我们是利用前一轮迭代弱学习器的误差率来更新训练集的权重。 GBDT 却使用了前向分布算法,并且基学习器也限定为 CART 回归树模型,同时迭代思路和Adaboost也有所不同。而作为 GBDT 算法的高效实现,XGBoost 算法又做了以下几方面的优化:

  1. 模型优化:在弱学习器模型,与 GBDT 算法只支持决策树不同,XGBoost 算法还支持很多其他的弱学习器;在损失函数方面,除了自身损失以外,XGBoost 算法还添加了正则化部分,以避免过拟合;在优化方式方面,GBDT 算法的损失函数只对误差部分做负梯度(一阶泰勒)展开,而 XGBoost 算法则对误差部分做二阶泰勒展开,更加准确;

  2. 运行效率:在弱学习器的选择方面,XGBoost 算法先对所有的特征的值进行排序分组,以便利弱学习器的并行选择;在分组特征方面,XGBoost 算法会选择合适的分组大小,使用 CPU 缓存进行读取加速,将各个分组保存到多个硬盘以提高 IO 速度。

  3. 健壮性:在处理含有缺失值的特征方面,XGBoost 算法通过枚举所有缺失值在当前节点是进入左子树还是右子树来决定缺失值的处理方式;此外,XGBoost 算法还加入了L1和L2正则化项,可以防止过拟合,鲁棒性更好,泛化能力更强。

残差拟合

在前一小节中,我们对 GBDT 算法和 XGBoost 算法的基本概念有了简单介绍。那么,它们的基本思想又是怎样的呢?

我们不妨假设有样本集 { ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x m , y m ) } \{(x_1, y_1), (x_2, y_2), \dots, (x_m, y_m)\} { (x1,y1),(x2,y2),,(xm,ym)},并且在第 t − 1 t - 1 t1 轮 训练得到模型 F t − 1 ( x ) F_{t-1}(x) Ft1(x) 来拟合或者分类这些数据。经过学习过后,我们发现 F t − 1 ( x ) F_{t-1}(x) Ft1(x) 的效果虽然较好,但是与真实数据仍然存在差距。例如, y 1 = 0.5 y_1 = 0.5 y1=0.5,但是 F t − 1 ( x 1 ) = 0.45 F_{t-1}(x_1) = 0.45 Ft1(x1)=0.45。我们当然可以通过调整参数的形式继续训练模型以达到理想结果。可是,我们可以在不更改原先模型 F t − 1 ( x ) F_{t-1}(x) Ft1(x) 的参数的基础上,进一步提升模型的效果吗?

答案自然是可以的。既然我们无法修改原先模型 F t − 1 ( x ) F_{t-1}(x) Ft1(x) 的参数,那么不如换一种思路——训练新的模型 h ( x ) h(x) h(x) 来拟合 F t − 1 ( x ) F_{t-1}(x) Ft1(x) 与真实数据的残差,即 y − F t − 1 ( x ) y - F_{t-1}(x) yFt1(x)。所以,对于每个样本来说,拟合的数据集即为
{ ( x 1 , y 1 − F t − 1 ( x 1 ) ) , ( x 2 , y 2 − F t − 1 ( x 2 ) ) , … , ( x m , y m − F t − 1 ( x m ) ) } \{(x_1, y_1 - F_{t-1}(x_1)), (x_2, y_2 - F_{t-1}(x_2)), \dots, (x_m, y_m - F_{t-1}(x_m))\} { (x1,y1Ft1(x1)),(x2,y2Ft1(x2)),,(xm,ymFt1(xm))}

因此,在 GBDTXGBoost 算法中,假设我们前一轮得到的强分类器为 F t − 1 ( x ) F_{t-1}(x) Ft1(x),损失函数为 L ( y , F t − 1 ( x ) ) L(y, F_{t-1}(x)) L(y,Ft1(x)),那么为了拟合与真实数据的残差,我们希望找一个基分类器 h t ( x ) h_t(x) ht(x),使得本轮的损失函数 L ( y , F t − 1 ( x ) + h ( x ) ) L(y, F_{t-1}(x) + h(x)) L(y,Ft1(x)+h(x)) 最小。

举个例子,假如我们预测一名中年男子的年龄(其真实年龄为30岁)。我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。(源自博客梯度提升树(GBDT)原理小结)

想必至此,大家对 GBDTXGBoost 的算法思想已经有了一定的了解。那么,我们到底如何拟合呢?

负梯度拟合

通过前一小结的介绍,我们了解了GBDTXGBoost 算法的基本思想。那么,这一小节,我们就将以数学公式推导的方式进一步探索。值得一提的是,因为 XGBoost 算法是 GBDT 的优化版本,所以,接下来只推导 XGBoost 算法即可。

其实,根据前一小结的学习,我们可以得知,GBDTXGBoost 算法可以看成是由 t t t 棵树组成的加法模型,

y ^ i = ∑ t = 1 T f t ( x i ) , f t ∈ F \hat{y}_i = \sum_{t=1}^Tf_t(x_i), \quad f_t \in F y^i=t=1Tft(xi),ftF

其中, F F F是所有决策树组成的函数空间。与一般的机器学习算法不同的是,该加法模型的参数为 { f 1 , f 2 , … , f T } \{f_1, f_2, \dots, f_T\} { f1,f2,,fT}。加法模型不是学习权重,而是直接学习函数(决策树)集合。

上述加法模型的目标函数可以定义为 ∑ i = 1 n l ( y i , y ^ i ) + ∑ t = 1 T Ω ( f k ) \sum_{i=1}^n l(y_i, \hat{y}_i) + \sum_{t=1}^T\Omega(f_k) i=1nl(yi,y^i)+t=1TΩ(fk)。其中, Ω \Omega Ω表示决策树的复杂度,例如树的节点数量、树的深度或者叶子节点所对应的分数的 L 2 L2 L2 范数等等。注意,上式中的正则项是 XGBoost 算法特有的部分。

回到原先的问题,我们如何学习加法模型呢?答案就是前向分布算法。因为学习的是加法模型,如果能够从前往后,每一步只学习一个基函数及其系数(即 GBDT 中的决策树),逐步逼近优化目标函数,那么就可以简化复杂度。这一学习过程称之为 Boosting。具体地,我们从一个常量预测开始,每次学习一个新的函数,过程如下:

y ^ i 0 = 0 y ^ i 1 = y ^ i 0 + f 1 ( x i ) y ^ i 2 = y ^ i 1 + f 2 ( x i ) ⋮ y ^ i T = y ^ i T − 1 + f T ( x i ) \begin{aligned} \hat{y}_i^0 &= 0 \\ \hat{y}_i^1 &= \hat{y}_i^0 + f_1(x_i) \\ \hat{y}_i^2 &= \hat{y}_i^1 + f_2(x_i) \\ \vdots \\ \hat{y}_i^T &= \hat{y}_i^{T-1} + f_{T}(x_i) \\ \end{aligned} y^i0y^i1y^i2y^iT=0=y^i0+f1(xi)=y^i1+f2(xi)=y^iT1+fT(xi)

那么,在每一步如何决定哪一个函数,或者说决策树被加入呢?自然是最小化目标函数。在第 t t t 轮迭代中,模型对 x i x_i xi 的预测为 y ^ i t = y ^ i t − 1 + f t ( x i ) \hat{y}_i^t = \hat{y}_i^{t-1} + f_{t}(x_i) y^it=y^it1+ft(xi)。其中, f t ( x i ) f_{t}(x_i) ft(xi) 为这一轮中,我们需要学习的函数,或者说决策树。因此,我们可以写出目标函数

O b j t = ∑ i = 1 n l ( y i , y ^ i t ) + ∑ i = 1 t Ω ( f i ) = ∑ i = 1 n l ( y i , y ^ i t − 1 + f t ( x i ) ) + Ω ( f t ) + c o n s t a n t \begin{aligned} Obj^t &= \sum_{i=1}^n l(y_i, \hat{y}_i^t) + \sum_{i=1}^{t}\Omega(f_i) \\ &= \sum_{i=1}^n l(y_i, \hat{y}_i^{t-1} + f_{t}(x_i)) + \Omega(f_t) + constant \end{aligned} Objt=i=1nl(yi,y^it)+i=1tΩ(fi)=i=1nl(yi,y^it1+ft(xi))+Ω(ft)+constant

假设目标函数为平方损失函数,则有

O b j t = ∑ i = 1 n [ y i − ( y ^ i t − 1 + f t ( x i ) ) ] 2 + Ω ( f t ) + c o n s t a n t = ∑ i = 1 n [ 2 ( y ^ t − 1 − y i ) f t ( x i ) + f t ( x i ) 2 ] + Ω ( f t ) + c o n s t a n t \begin{aligned} Obj^t &= \sum_{i=1}^n [y_i - (\hat{y}_i^{t-1} + f_{t}(x_i))]^2 + \Omega(f_t) + constant \\ &= \sum_{i=1}^n [2(\hat{y}^{t-1} - y_i)f_t(x_i) + f_t(x_i)^2] + \Omega(f_t) + constant \end{aligned} Objt=i=1n[yi(y^it1+ft(xi))]2+Ω(ft)+constant=i=1n[2(y^t1yi)ft(xi)+ft(xi)2]+Ω(ft)+constant

细心的读者不难看出, y ^ t − 1 − y i \hat{y}^{t-1} - y_i y^t1yi即为我们之前所说的残差。因此,使用平方损失函数时,GBDT 算法的每一步在生成决策树时只需要拟合前面的模型的残差。

那么,更一般地,如果损失函数是其他形式,我们又如何求解呢?答案便是大名鼎鼎的泰勒公式了。

我们知道,根据泰勒公式,我们对函数 f ( x + Δ x ) f(x + \Delta{x}) f(x+Δx) 在点 x x x 处展开,则有

f ( x + Δ x ) ≃ f ( x ) + f ′ ( x ) Δ x + 1 2 f ′ ′ ( x ) Δ x 2 f(x + \Delta{x}) \simeq f(x) + f^{'}(x)\Delta{x} + \frac{1}{2}f^{''}(x)\Delta{x}^2 f(x+Δx)f(x)+f(x)Δx+21f(x)Δx2

因此,如果我们将原目标函数中的变量 y ^ t − 1 \hat{y}^{t-1} y^t1 看作 x x x,把变量 f t ( x i ) f_{t}(x_i) ft(xi) 看作 Δ x \Delta{x} Δx,则有

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 ) + c o n s t a n t Obj^t = \sum_{i=1}^{n}[l(y_i, \hat{y}_i^{t-1}) + g_if_t(x_i) + \frac{1}{2}h_if^2_t(x_i)] + \Omega(f_t) + constant Objt=i=1n[l(yi,y^it1)+gift(xi)+21hift2(xi)]+Ω(ft)+constant

其中, g i g_i gi 为损失函数的一阶导 ∂ y ^ t − 1 l ( y i , y ^ t − 1 ) \partial_{\hat{y}^{t-1}}l(y_i, \hat{y}^{t-1}) y^t1l(yi,y^t1) h i h_i hi 为损失函数的二阶导 ∂ y ^ t − 1 2 l ( y i , y ^ t − 1 ) \partial_{\hat{y}^{t-1}}^2l(y_i, \hat{y}^{t-1}) y^t12l(yi,y^t1)。感兴趣的读者可以将之前的平方损失函数代入以检验其正确性。此外,值得注意的是,在 GBDT 算法中,只对损失函数进行了一阶泰勒展开。

最后,我们去除无关变量,即有

O b j t ≃ ∑ i = 1 n [ g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) Obj^t \simeq \sum_{i=1}^{n}[g_if_t(x_i) + \frac{1}{2}h_if^2_t(x_i)] + \Omega(f_t) Objti=1n[gift(xi)+21hift2(xi)]+Ω(ft)

因为要学习的函数仅仅依赖于目标函数,所以我们只需为学习任务定义好损失函数,并为每个训练样本计算出损失函数的一阶导数和二阶导数,通过在训练样本集上最小化最终的目标函数即可求得每步要学习的函数,从而可得最终要学习的模型。

正则项

通过前一小节,我们对加法模型和前向分布算法有了一定的了解。接下来,我们对 GBDT 算法 和 XGBoost 算法继续探索。

我们不妨再进一步地分析一下损失函数中的 Ω ( f t ) \Omega(f_t) Ω(ft)。假设有一棵叶子节点个数为 T T T的决策树,该决策树是由所有叶子节点对应的值组成的向量 ω ∈ R T \omega \in R^T ωRT, 以及一个把特征向量映射到叶子节点索引的函数 q : R d → { 1 , 2 , … , T } q : R^d \rightarrow \{1, 2, \dots, T\} q:Rd{ 1,2,,T} 组成的。因此,该决策树可以定义为 f t ( x ) = ω q ( x ) f_t(x) = \omega_q(x) ft(x)=ωq(x)。其中, d d d 表示特征向量的维度。

此外,决策树的复杂度可以由正则项 Ω ( f ) = γ T + 1 2 λ ∑ j = 1 T ω j 2 \Omega(f) = \gamma T + \frac{1}{2}\lambda\sum_{j=1}^T\omega_j^2 Ω(f)=γT+21λj=1Tωj2 表示,即决策树模型的复杂度由生成的树的叶子节点数量和叶子节点对应的值向量的L2范数决定。

定义集合 I j = { i ∣ q ( x i ) = j } I_j = \{i|q(x_i)=j\} Ij={ iq(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 ω q ( x i ) + 1 2 h i ω q 2 ( x i ) ] + γ T + 1 2 λ ∑ j = 1 T ω j 2 = ∑ j = 1 T [ ( ∑ i ∈ I j g i ) ω j + 1 2 ( ∑ i ∈ I j h i + λ ) ω j 2 ] + γ T \begin{aligned} Obj^t &\simeq \sum_{i=1}^{n}[g_if_t(x_i) + \frac{1}{2}h_if^2_t(x_i)] + \Omega(f_t) \\ &= \sum_{i=1}^{n}[g_i\omega_q(x_i) + \frac{1}{2}h_i\omega_q^2(x_i)] + \gamma T + \frac{1}{2}\lambda\sum_{j=1}^T\omega_j^2 \\ &= \sum_{j=1}^{T}[(\sum_{i \in I_j}g_i)\omega_j + \frac{1}{2}(\sum_{i \in I_j}h_i + \lambda)\omega_j^2] + \gamma T \end{aligned} Objti=1n[gift(xi)+21hift2(xi)]+Ω(ft)=i=1n[giωq(xi)+21hiωq2(xi)]+γT+21λj=1Tωj2=j=1T[(iIjgi)ωj+21(iIjhi+λ)ωj2]+γ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=iIjgi,Hj=iIjhi,则可改写为

O b j t = ∑ j = 1 T [ G j ω j + 1 2 ( H j + λ ) ω j 2 ] + γ T Obj^t = \sum_{j=1}^{T}[G_j\omega_j + \frac{1}{2}(H_j + \lambda)\omega_j^2] + \gamma T Objt=j=1T[Gjωj+21(Hj+λ)ωj2]+γT

假如树的结构是固定的,即函数 q ( x ) q(x) q(x) 确定,则令目标函数 O b j t Obj^t Objt 的一阶导数为0,即可求得叶子节点 j j j 对应的值为,

w j ∗ = − G j H j + λ w_j^* = - \frac{G_j}{H_j + \lambda} wj=Hj+λGj

因此,有

O b j t = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T Obj^t = -\frac{1}{2}\sum_{j=1}^T\frac{G_j^2}{H_j + \lambda} + \gamma T Objt=21j=1THj+λGj2+γT

因此,在 XGBoost 算法中,单棵决策树的生成过程为

  1. 枚举所有可能的树结构 q q q;
  2. 根据最终的目标函数为每个树结构 q q q 计算得分 O b j Obj Obj,且分数越小说明树的结构越好;
  3. 根据上一步的结果,找到最佳的树结构 q ∗ q^* q,并为树的每个叶子节点 j j j 计算预测值 ω j \omega_j ωj

然而,树的结构是无穷的,因此,我们无法枚举所有的树结构。为了解决这一问题,我们常采用贪心策略生成决策树的每个节点

  1. 从深度为 0 0 0 的树开始,对每个叶节点枚举所有的可用特征;
  2. 针对每个特征,把属于该节点的训练样本根据该特征值升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的最大收益(采用最佳分裂点时的收益);
  3. 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,把该节点生长出左右两个新的叶节点,并为每个新节点关联对应的样本集;
  4. 回到第1步,递归执行到满足特定条件为止

那么,又如何计算特征收益呢?假设当前节点记为 C C C,分裂之后左孩子节点记为 L L L,右孩子节点记为 R R R,则该分裂获得的收益定义为当前节点的目标函数值减去左右两个孩子节点的目标函数值之和: G a i n = O b t C − O b t L − O b t R Gain = Obt_C - Obt_L - Obt_R Gain=ObtCObtLObtR,具体地

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+λGR2HL+HR+λ(GL+GR)2]γ

其中, − γ -\gamma γ 表示因为增加了树的复杂性(该分裂增加了一个叶子节点)带来的惩罚。

最终,XGBoost 算法可以总结为

  1. 算法每次迭代生成一颗新的决策树;
  2. 在每次迭代开始之前,计算损失函数在每个训练样本点的一阶导数 g i g_i gi 和二阶导数 h i h_i hi;
  3. 通过贪心策略生成新的决策树,并计算每个叶子节点对应的预测值;
  4. 将新生成的决策树 f t ( x ) f_t(x) ft(x) 添加到模型中: y ^ i t = y ^ i t − 1 + f t ( x i ) \hat{y}_i^t = \hat{y}_i^{t-1} + f_t(x_i) y^it=y^it1+ft(xi)

通常,为了避免模型过拟合,我们还可以添加学习率 ϵ \epsilon ϵ,即模型更新公式为 y ^ i t = y ^ i t − 1 + ϵ f t ( x i ) \hat{y}_i^t = \hat{y}_i^{t-1} + \epsilon f_t(x_i) y^it=y^it1+ϵft(xi)

总结

至此,GBDT 算法和 XGBoost 算法已经讲述完毕(再往深我也就不会了!)。作为在深度学习大热之前,最受机器学习竞赛者喜欢的算法,值得大家了解学习其算法思想。最后,不妨对它们的优缺点进行总结

优点

  1. 可以灵活处理各种类型的数据,包括连续值和离散值。

  2. 在相对少的调参时间情况下,预测的准确率也可以比较高。

  3. 使用一些健壮的损失函数,对异常值的鲁棒性非常强。

缺点

  1. 由于弱学习器之间存在依赖关系,难以并行训练数据。

参考文献

  1. GBDT算法原理深入解析
  2. 机器学习-一文理解GBDT的原理
  3. 刘建平 梯度提升树(GBDT)原理小结
  4. 刘建平 XGBoost算法原理小结

你可能感兴趣的:(机器学习,机器学习,算法,集成学习,GBDT,XGBoost)