GBDT详解

254 阅读4分钟

1. 引言

GBDT(Gradient Boosting Decision Tree)是Gradient Boosting的一种实现,以决策树(通常是CART回归树)作为弱学习器。GBDT在各类机器学习竞赛和实际应用中表现优异,因其高精度和灵活性而广受欢迎。下面我将用通俗易懂的方式详细解释GBDT的工作原理、训练过程和关键细节。

2. GBDT核心思想与直观理解

想象一个场景:我们要预测一个人的年龄。第一个模型(一棵树)根据“收入”和“职业”进行预测,得到了一个初步结果,但误差较大。

  • 第一步:我们不再关注原始年龄,而是去分析第一个模型预测的残差(真实值 - 预测值) 。比如,真实年龄是40岁,第一个模型预测为35岁,那么残差就是+5岁。
  • 第二步:我们训练第二棵树,但它的目标不再是预测年龄,而是去学习并预测第一步中产生的残差。它的输入特征可能包括“教育年限”、“发量”等。假设第二棵树预测这个残差为+3岁。
  • 第三步:我们将第一棵树和第二棵树的预测结果相加:35 + 3 = 38岁。这个结果比第一步的35岁更接近真实值40岁了,残差从+5岁减少到了+2岁。

GBDT的精妙之处在于,它将这个“拟合残差”的思想,推广到了任意可微的损失函数上,即“拟合负梯度” 。当损失函数是平方误差时,负梯度就是残差。因此,GBDT可以应对更复杂的任务,如分类、排序等。

3. 前向分布算法

GBDT是一个加法模型,其最终模型F(x)F(x)MM个基学习器(这里是决策树)相加而成:

FM(x)=m=1Mfm(x)F_M(x)=\sum^M_{m=1}f_m(x)

其中,fm(x)f_m(x)是第mm棵决策树。

为了学习这个模型,GBDT采用前向分布算法:每一步只学习一个基学习器,并保持之前的所有学习器固定。

Fm(x)=Fm1(x)+fm(x)F_m(x)=F_{m-1}(x)+f_m(x)

我们的目标是:在每一步mm,找到一个新的fm(x)f_m(x),使得加入后,总模型的损失LL最小化。

4. 梯度提升:从最速下降到函数空间

在参数空间的优化中,梯度下降是寻找函数极小值的经典方法。对于参数θ\theta,我们通过不断沿负梯度方向更新来最小化损失L(θ)L(\theta)

θm=θm1ρmθL(θm1)\theta_m=\theta_{m-1} - \rho_m \cdot \nabla_{\theta}L(\theta_{m-1})

其中,θL\nabla_{\theta}L是梯度,ρm\rho_m是步长(学习率)。

GBDT将这一思想从参数空间推广到了函数空间。在这里,我们的“参数”是整个函数F(x)F(x)本身。我们希望最小化的目标是所有训练样本上的损失之和:

L=i=1NL(yi,F(xi))\mathcal{L} = \sum^N_{i=1} L(y_i,F(x_i))

其中NN是样本数量。

为了在函数空间中执行梯度下降,我们需要计算损失函数LL关于函数F(xi)F(x_i)的梯度(实际上是泛函导数)。对于每个数据点xix_i,这个梯度量:

gm(xi)=[L(yi,F(xi))F(xi)]F(x)=Fm1(x)g_m(x_i)= \left[ \frac{\partial L(y_i,F(x_i))}{\partial F(x_i)} \right]_{F(x)=F_{m-1}(x)}

注意:在Fm1(x)F_{m-1}(x)处,函数FF沿着负梯度方向gm(xi)-g_m(x_i)下降最快。但是,我们的新基学习器fmf_m是定义在整个特征空间上的函数(一棵树),而我们只有NN个数据点上的梯度值。因此,一个很自然的想法是:让新的树fmf_m去拟合这个负梯度。这样,新树的输出方向就是损失函数下降最快的方向。

5. GDBT算法流程(回归问题)

我们以最经典的平方误差损失为例

5.1. 问题定义与损失函数

训练数据:{(xi,yi)}i=1N\{ (x_i,y_i) \}^N_{i=1},其中xix_i是特征向量,yiy_i是连续型目标量。

损失函数:

L(yi,F(xi))=12(yiF(xi))2L(y_i,F(x_i))=\frac{1}{2}(y_i-F(x_i))^2

其中1/2是为了求导后形式简洁,不影响优化

目标:学习一个加法模型FM(x)F_M(x),使得总损失i=1NL(yi,FM(xi))\sum^N_{i=1}L(y_i,F_M(x_i))最小。

5.2. 算法步骤

5.2.1. 初始化模型(第0棵树)

通常用一个常数值初始化,即一个“盲猜”的树。对于平方损失,最优的初始值是目标值的均值。

F0(x)=arg minγi=1NL(yi,γ)=arg minγi=1N12(yiγ)2F_0(x)=\underset{\gamma}{\argmin} \sum^N_{i=1}L(y_i, \gamma) = \underset{\gamma}{\argmin}\sum^N_{i=1}\frac{1}{2}(y_i - \gamma)^2

令导数等于零:

γi=1N12(yiγ)2=i=1N(yiγ)=0i=1NyiNγ=0γ=1Ni=1Nyi\frac{\partial}{\partial \gamma} \sum^N_{i=1} \frac{1}{2}(y_i - \gamma)^2 = -\sum^N_{i=1}(y_i - \gamma) = 0 \\ \Rightarrow \sum^N_{i=1}y_i - N\gamma = 0 \Rightarrow \gamma = \frac{1}{N} \sum^N_{i=1}y_i

所以,

F0(x)=yˉF_0(x)=\bar{y}

5.2.2. 对于m=1m=1MM(构建每一棵树)

步骤一:计算伪残差(负梯度)

对于每一个样本i=1,2,...,Ni=1,2,...,N

rim=[L(yi,F(xi))F(xi)]F(x)=Fm1(x)r_{im} = -\left[ \frac{\partial L(y_i,F(x_i))}{\partial F(x_i)} \right]_{F(x)=F_{m-1}(x)}

代入平方损失函数和当前模型Fm1(x)F_{m-1}(x)

rim=F(xi)[12(yiF(xi))2]F(x)=Fm1(x)=[122(yiF(xi))(1)]F(x)=Fm1(x)=(yiFm1(xi))\begin{align} r_{im} &= -\frac{\partial}{\partial F(x_i)} \left[ \frac{1}{2}(y_i - F(x_i))^2 \right]_{F(x)=F_{m-1}(x)} \\ &= - \left[ \frac{1}{2} \cdot 2 \cdot (y_i - F(x_i)) \cdot (-1) \right]_{F(x)=F_{m-1}(x)} \\ &= (y_i - F_{m-1}(x_i)) \end{align}

推导结论:对于平方损失函数,负梯度rimr_{im}正好等于残差yiFm1(xi)y_i - F_{m-1}(x_i)。这就是为什么我们说“拟合残差”是GBDT在平方损失下的特例。

步骤二:拟合一棵新的决策树

我们用一棵回归树来拟合这些伪残差{rim}i=1N\{ r_{im} \}^N_{i=1}

这棵树的输入是特征xix_i,输出目标是rimr_{im}

假设这棵树有JmJ_m个叶子节点,我们将这棵树记为fm(x)f_m(x),它将输入空间划分成了JmJ_m个互不相交的区域R1m,R2m,...,RJmmR_{1m},R_{2m},...,R_{J_mm}

步骤三:为每个叶子结点计算最优输出值

在步骤二中,我们得到了树的结构(即如何划分区域)。现在,我们需要为每个叶子节点jj(即每个区域RjmR_{jm})计算一个最优的输出值γjm\gamma_{jm}

这个γjm\gamma_{jm}是在该区域内,能最大程度减小损失的值。这是一个单变量优化问题

γjm=arg minγxiRjmL(yi,Fm1(xi)+γ)\gamma_{jm} = \underset{\gamma}{\argmin} \sum_{x_i \in R_{jm}} L(y_i,F_{m-1}(x_i) + \gamma)

对于平方损失,我们可以直接求解:

γjm=arg minγxiRjm12(yiFm1(xi)+γ)2=arg minγxiRjm12(rimγ)2\begin{align} \gamma_{jm} &= \underset{\gamma}{\argmin} \sum_{x_i \in R_{jm}} \frac{1}{2}(y_i-F_{m-1}(x_i)+\gamma)^2 \\ &= \underset{\gamma}{\argmin} \sum_{x_i \in R_{jm}} \frac{1}{2}(r_{im} - \gamma)^2 \end{align}

因为yiFm1(xi)=rimy_i - F_{m-1}(x_i)=r_{im}

令导数等于零:

γxiRjm12(rimγ)2=0xiRjm(rimγ)=0xiRjmrimnjmγ=0γjm=1njmxiRjmrim\begin{align} \frac{\partial}{\partial \gamma} \sum_{x_i \in R_{jm}} \frac{1}{2}(r_{im} - \gamma)^2 &= 0 \\ -\sum_{x_i \in R_{jm}}(r_{im} - \gamma) &= 0 \\ \sum_{x_i \in R_{jm}} r_{im} - n_{jm} \gamma &= 0 \\ \gamma_{jm} = \frac{1}{n_{jm}} \sum_{x_i \in R_{jm}} r_{im} \end{align}

其中,njmn_{jm}是区域RjmR_{jm}中的样本数。

推导结论:对于平方损失,叶子节点的最优输出值就是落入该叶子节点的所有伪残差(即残差)的均值

步骤四:更新模型

我们将这棵新树加入到模型中。通常,我们会引入一个学习率(Shrinkage)v(0<v1)\mathcal{v}(0<v\leq 1)来控制每棵树的影响,防止过拟合。

Fm(x)=Fm1(x)+vfm(x)F_m(x)=F_{m-1}(x)+\mathcal{v} \cdot f_m(x)

其中,fm(x)=j=1JmγjmI(xRjm)f_m(x)=\sum^{J_m}_{j=1} \gamma_{jm}I(x \in R_{jm})II是指示函数。

5.3. 输出最终模型

FM(x)=F0(x)+vm=1Mfm(x)F_M(x)=F_0(x)+\mathcal{v}\sum^M_{m=1}f_m(x)

6. 损失函数与分类问题

GBDT的强大之处在于它可以处理任何具有一阶导数的损失函数。对于分类问题(例如二分类),我们通常使用对数似然损失(Log Loss)

6.1. 二分类问题

目标值:yi{0,1}y_i \in \{0,1\}。我们模型F(x)F(x)输出的是对数几率(Log Odds) 。最终的概率通过Logistic函数转换:

P(y=1x)=σ(F(x))=11+eF(x)P(y=1|x)=\sigma(F(x))=\frac{1}{1+e^{-F(x)}}

损失函数为负对数函数

L(y,F(x))=ylog(p)(1y)log(1p)L(y,F(x))=-ylog(p)-(1-y)log(1-p)

其中p=σ(F(x))p=\sigma(F(x))。可以化简为:

L(y,F(x))=log(1+eyF(x))L(y,F(x))=log(1+e^{-yF(x)})

其中y~{1,+1}\tilde{y} \in \{ -1,+1 \}是另一种编码方式,但本质相同。

6.2. 计算负梯度

我们依然按照之前的流程。在第mm步,计算损失函数关于FF的负梯度。

rim=L(yi,F(xi))F(xi)F(x)=Fm1(x)r_{im} = -\frac{\partial L(y_i, F(x_i))}{\partial F(x_i)}_{F(x)=F_{m-1}(x)}

对于对数损失函数L=log(1+eyF(x))(这里L=log(1+e^{-yF(x)})(这里y \in { -1,+1 }$),其导数为:

LF=yeyF(x)1+eyF(x)=y11+eyF(x)\frac{\partial L}{\partial F} = \frac{-ye^{-yF(x)}}{1+e^{-yF(x)}}=-y \cdot \frac{1}{1+e^{yF(x)}}

所以,负梯度为:

rim=(yi11+eyiFm1(xi))=yi11+eyiFm1(xi)r_{im} = -\left( -y_i \cdot \frac{1}{1+e^{y_iF_{m-1}(x_i)}} \right)=y_i \cdot \frac{1}{1+e^{y_iF_{m-1}(x_i)}}

yi=1y_i=1时,rim=1/(1+eFm1(xi))r_{im}=1/(1+e^{-F_{m-1}(x_i)});当yi=1y_i=-1时,rim=1/(1+eFm1(xi))r_{im}=-1/(1+e^{F_{m-1}(x_i)})。这个形式看起来复杂,但它本质上可以看作是观测值与预测概率之间的残差的一种形式。

核心要点: 对于任何损失函数,GBDT的流程都是一样的:

  • 计算负梯度(伪残差);
  • 用一棵回归树去拟合这个负梯度。(注意:即使是分类问题,这里也是在拟合回归树);
  • 通过线搜索等方法,确定每个叶子节点的最优输出值γjm\gamma_{jm}以最小化损失;
  • 更新模型。

7. 关键超参数

  • n_estimators:树的数量(MM)。太大容易过拟合,太小可能欠拟合;
  • learning_rate(v\mathcal{v}):学习率/收缩率。减小学习率意味着需要更多的树,但通常泛化能力更好。通常与n_estimators一起调参;
  • max_depth:每棵决策树的最大深度。用于控制树的复杂度,是防止过拟合最重要的参数之一;
  • min_samples_split/min_samples_leaf:分裂内部节点/成为叶子节点所需的最小样本数;
  • subsample:子采样比例。每次迭代时使用多少比例的训练样本来训练基学习器(类似于随机森林的思想)。