经常参加机器学习相关竞赛的同学肯定对 GBDT
算法并不陌生。GBDT
全称为梯度提升决策树,是传统机器学习算法中对真实分布拟合最好的算法之一,是工业界和竞赛屡试不爽的杀器之一。然而,网上关于 GBDT
算法的介绍却千篇一律,尚未深入精髓。因此,博主撰写此博客希望能对读者有所帮助,并与大家共同进步。
简介
与 Adaboost
算法相同,GBDT
算法也是集成学习 Boost
家族的成员之一。然而在 Adaboost
中,我们是利用前一轮迭代弱学习器的误差率来更新训练集的权重。 GBDT
却使用了前向分布算法,并且基学习器也限定为 CART
回归树模型,同时迭代思路和Adaboost也有所不同。
基于残差的GBDT
假设我们有样本集 { ( 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
算法中,假设我们前一轮得到的强分类器为 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
的算法思想已经有了一定的了解。那么,我们到底如何拟合呢?
负梯度拟合
通过前一小结的介绍,我们可以得知,GBDT
算法可以看成是由 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 范数等等。
回到原先的问题,我们如何学习加法模型呢?答案就是前向分布算法。因为学习的是加法模型,如果能够从前往后,每一步只学习一个基函数及其系数(即 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)。感兴趣的读者可以将之前的平方损失函数代入以检验其正确性。
最后,我们去除无关变量,即有
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算法
通过前一小节,我们对加法模型和前向分布算法有了一定的了解。接下来,我们对 GBDT
算法继续探索。
Ω ( 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
因此,在 GBDT
算法中,单棵决策树的生成过程为
- 枚举所有可能的树结构 q q q;
- 根据最终的目标函数为每个树结构 q q q 计算得分 O b j Obj Obj,且分数越小说明树的结构越好;
- 根据上一步的结果,找到最佳的树结构 q ∗ q^* q∗,并为树的每个叶子节点 j j j 计算预测值 ω j \omega_j ωj。
然而,树的结构是无穷的,因此,我们无法枚举所有的树结构。为了解决这一问题,我们常采用贪心策略生成决策树的每个节点
- 从深度为0的树开始,对每个叶节点枚举所有的可用特征;
- 针对每个特征,把属于该节点的训练样本根据该特征值升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的最大收益(采用最佳分裂点时的收益);
- 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,把该节点生长出左右两个新的叶节点,并为每个新节点关联对应的样本集;
- 回到第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=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 −γ 表示因为增加了树的复杂性(该分裂增加了一个叶子节点)带来的惩罚。
最终,GBDT
算法可以总结为
- 算法每次迭代生成一颗新的决策树;
- 在每次迭代开始之前,计算损失函数在每个训练样本点的一阶导数 g i g_i gi 和二阶导数 h i h_i hi;
- 通过贪心策略生成新的决策树,并计算每个叶子节点对应的预测值;
- 将新生成的决策树 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^it−1+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^it−1+ϵft(xi)