经常参加机器学习相关竞赛的同学肯定对 XGBoost
算法并不陌生。它是 GBDT
(梯度提升决策树)的一种高效实现,是传统机器学习算法中对真实分布拟合最好的算法之一,是工业界和竞赛屡试不爽的杀器之一。因此,本文将阐述 XGBoost
算法的基本原理和数学论证,希望能帮助大家了解 GBDT
算法和 XGBoost
算法。
与 Adaboost
算法相同,GBDT
算法也是集成学习 Boost
家族的成员之一。然而在 Adaboost
中,我们是利用前一轮迭代弱学习器的误差率来更新训练集的权重。 GBDT
却使用了前向分布算法,并且基学习器也限定为 CART
回归树模型,同时迭代思路和Adaboost也有所不同。而作为 GBDT
算法的高效实现,XGBoost
算法又做了以下几方面的优化:
模型优化:在弱学习器模型,与 GBDT
算法只支持决策树不同,XGBoost
算法还支持很多其他的弱学习器;在损失函数方面,除了自身损失以外,XGBoost
算法还添加了正则化部分,以避免过拟合;在优化方式方面,GBDT
算法的损失函数只对误差部分做负梯度(一阶泰勒)展开,而 XGBoost
算法则对误差部分做二阶泰勒展开,更加准确;
运行效率:在弱学习器的选择方面,XGBoost
算法先对所有的特征的值进行排序分组,以便利弱学习器的并行选择;在分组特征方面,XGBoost
算法会选择合适的分组大小,使用 CPU
缓存进行读取加速,将各个分组保存到多个硬盘以提高 IO
速度。
健壮性:在处理含有缺失值的特征方面,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 t−1 轮 训练得到模型 F t − 1 ( x ) F_{t-1}(x) Ft−1(x) 来拟合或者分类这些数据。经过学习过后,我们发现 F t − 1 ( x ) F_{t-1}(x) Ft−1(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 Ft−1(x1)=0.45。我们当然可以通过调整参数的形式继续训练模型以达到理想结果。可是,我们可以在不更改原先模型 F t − 1 ( x ) F_{t-1}(x) Ft−1(x) 的参数的基础上,进一步提升模型的效果吗?
答案自然是可以的。既然我们无法修改原先模型 F t − 1 ( x ) F_{t-1}(x) Ft−1(x) 的参数,那么不如换一种思路——训练新的模型 h ( x ) h(x) h(x) 来拟合 F t − 1 ( x ) F_{t-1}(x) Ft−1(x) 与真实数据的残差,即 y − F t − 1 ( x ) y - F_{t-1}(x) y−Ft−1(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,y1−Ft−1(x1)),(x2,y2−Ft−1(x2)),…,(xm,ym−Ft−1(xm))}。
因此,在 GBDT
和 XGBoost
算法中,假设我们前一轮得到的强分类器为 F t − 1 ( x ) F_{t-1}(x) Ft−1(x),损失函数为 L ( y , F t − 1 ( x ) ) L(y, F_{t-1}(x)) L(y,Ft−1(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,Ft−1(x)+h(x)) 最小。
举个例子,假如我们预测一名中年男子的年龄(其真实年龄为30岁)。我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。(源自博客梯度提升树(GBDT)原理小结)
想必至此,大家对 GBDT
和 XGBoost
的算法思想已经有了一定的了解。那么,我们到底如何拟合呢?
通过前一小结的介绍,我们了解了GBDT
和 XGBoost
算法的基本思想。那么,这一小节,我们就将以数学公式推导的方式进一步探索。值得一提的是,因为 XGBoost
算法是 GBDT
的优化版本,所以,接下来只推导 XGBoost
算法即可。
其实,根据前一小结的学习,我们可以得知,GBDT
和 XGBoost
算法可以看成是由 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=1∑Tft(xi),ft∈F
其中, 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^i2⋮y^iT=0=y^i0+f1(xi)=y^i1+f2(xi)=y^iT−1+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^it−1+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=1∑nl(yi,y^it)+i=1∑tΩ(fi)=i=1∑nl(yi,y^it−1+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=1∑n[yi−(y^it−1+ft(xi))]2+Ω(ft)+constant=i=1∑n[2(y^t−1−yi)ft(xi)+ft(xi)2]+Ω(ft)+constant
细心的读者不难看出, y ^ t − 1 − y i \hat{y}^{t-1} - y_i y^t−1−yi即为我们之前所说的残差。因此,使用平方损失函数时,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^t−1 看作 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=1∑n[l(yi,y^it−1)+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^t−1l(yi,y^t−1), 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^t−12l(yi,y^t−1)。感兴趣的读者可以将之前的平方损失函数代入以检验其正确性。此外,值得注意的是,在 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) Objt≃i=1∑n[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={ 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 ω 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} Objt≃i=1∑n[gift(xi)+21hift2(xi)]+Ω(ft)=i=1∑n[giωq(xi)+21hiωq2(xi)]+γT+21λj=1∑Tωj2=j=1∑T[(i∈Ij∑gi)ωj+21(i∈Ij∑hi+λ)ω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=∑i∈Ijgi,Hj=∑i∈Ijhi,则可改写为
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=1∑T[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=1∑THj+λGj2+γT
因此,在 XGBoost
算法中,单棵决策树的生成过程为
然而,树的结构是无穷的,因此,我们无法枚举所有的树结构。为了解决这一问题,我们常采用贪心策略生成决策树的每个节点
那么,又如何计算特征收益呢?假设当前节点记为 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=ObtC−ObtL−ObtR,具体地
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]−γ
其中, − γ -\gamma −γ 表示因为增加了树的复杂性(该分裂增加了一个叶子节点)带来的惩罚。
最终,XGBoost
算法可以总结为
通常,为了避免模型过拟合,我们还可以添加学习率 ϵ \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^it−1+ϵft(xi)
至此,GBDT
算法和 XGBoost
算法已经讲述完毕(再往深我也就不会了!)。作为在深度学习大热之前,最受机器学习竞赛者喜欢的算法,值得大家了解学习其算法思想。最后,不妨对它们的优缺点进行总结
可以灵活处理各种类型的数据,包括连续值和离散值。
在相对少的调参时间情况下,预测的准确率也可以比较高。
使用一些健壮的损失函数,对异常值的鲁棒性非常强。