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)由M个基学习器(这里是决策树)相加而成:
FM(x)=m=1∑Mfm(x)
其中,fm(x)是第m棵决策树。
为了学习这个模型,GBDT采用前向分布算法:每一步只学习一个基学习器,并保持之前的所有学习器固定。
Fm(x)=Fm−1(x)+fm(x)
我们的目标是:在每一步m,找到一个新的fm(x),使得加入后,总模型的损失L最小化。
4. 梯度提升:从最速下降到函数空间
在参数空间的优化中,梯度下降是寻找函数极小值的经典方法。对于参数θ,我们通过不断沿负梯度方向更新来最小化损失L(θ):
θm=θm−1−ρm⋅∇θL(θm−1)
其中,∇θL是梯度,ρm是步长(学习率)。
GBDT将这一思想从参数空间推广到了函数空间。在这里,我们的“参数”是整个函数F(x)本身。我们希望最小化的目标是所有训练样本上的损失之和:
L=i=1∑NL(yi,F(xi))
其中N是样本数量。
为了在函数空间中执行梯度下降,我们需要计算损失函数L关于函数F(xi)的梯度(实际上是泛函导数)。对于每个数据点xi,这个梯度量:
gm(xi)=[∂F(xi)∂L(yi,F(xi))]F(x)=Fm−1(x)
注意:在Fm−1(x)处,函数F沿着负梯度方向−gm(xi)下降最快。但是,我们的新基学习器fm是定义在整个特征空间上的函数(一棵树),而我们只有N个数据点上的梯度值。因此,一个很自然的想法是:让新的树fm去拟合这个负梯度。这样,新树的输出方向就是损失函数下降最快的方向。
5. GDBT算法流程(回归问题)
我们以最经典的平方误差损失为例
5.1. 问题定义与损失函数
训练数据:{(xi,yi)}i=1N,其中xi是特征向量,yi是连续型目标量。
损失函数:
L(yi,F(xi))=21(yi−F(xi))2
其中1/2是为了求导后形式简洁,不影响优化
目标:学习一个加法模型FM(x),使得总损失∑i=1NL(yi,FM(xi))最小。
5.2. 算法步骤
5.2.1. 初始化模型(第0棵树)
通常用一个常数值初始化,即一个“盲猜”的树。对于平方损失,最优的初始值是目标值的均值。
F0(x)=γargmini=1∑NL(yi,γ)=γargmini=1∑N21(yi−γ)2
令导数等于零:
∂γ∂i=1∑N21(yi−γ)2=−i=1∑N(yi−γ)=0⇒i=1∑Nyi−Nγ=0⇒γ=N1i=1∑Nyi
所以,
F0(x)=yˉ
5.2.2. 对于m=1到M(构建每一棵树)
步骤一:计算伪残差(负梯度)
对于每一个样本i=1,2,...,N:
rim=−[∂F(xi)∂L(yi,F(xi))]F(x)=Fm−1(x)
代入平方损失函数和当前模型Fm−1(x):
rim=−∂F(xi)∂[21(yi−F(xi))2]F(x)=Fm−1(x)=−[21⋅2⋅(yi−F(xi))⋅(−1)]F(x)=Fm−1(x)=(yi−Fm−1(xi))
推导结论:对于平方损失函数,负梯度rim正好等于残差yi−Fm−1(xi)。这就是为什么我们说“拟合残差”是GBDT在平方损失下的特例。
步骤二:拟合一棵新的决策树
我们用一棵回归树来拟合这些伪残差{rim}i=1N;
这棵树的输入是特征xi,输出目标是rim;
假设这棵树有Jm个叶子节点,我们将这棵树记为fm(x),它将输入空间划分成了Jm个互不相交的区域R1m,R2m,...,RJmm。
步骤三:为每个叶子结点计算最优输出值
在步骤二中,我们得到了树的结构(即如何划分区域)。现在,我们需要为每个叶子节点j(即每个区域Rjm)计算一个最优的输出值γjm。
这个γjm是在该区域内,能最大程度减小损失的值。这是一个单变量优化问题:
γjm=γargminxi∈Rjm∑L(yi,Fm−1(xi)+γ)
对于平方损失,我们可以直接求解:
γjm=γargminxi∈Rjm∑21(yi−Fm−1(xi)+γ)2=γargminxi∈Rjm∑21(rim−γ)2
因为yi−Fm−1(xi)=rim
令导数等于零:
∂γ∂xi∈Rjm∑21(rim−γ)2−xi∈Rjm∑(rim−γ)xi∈Rjm∑rim−njmγγjm=njm1xi∈Rjm∑rim=0=0=0
其中,njm是区域Rjm中的样本数。
推导结论:对于平方损失,叶子节点的最优输出值就是落入该叶子节点的所有伪残差(即残差)的均值。
步骤四:更新模型
我们将这棵新树加入到模型中。通常,我们会引入一个学习率(Shrinkage)v(0<v≤1)来控制每棵树的影响,防止过拟合。
Fm(x)=Fm−1(x)+v⋅fm(x)
其中,fm(x)=∑j=1JmγjmI(x∈Rjm),I是指示函数。
5.3. 输出最终模型
FM(x)=F0(x)+vm=1∑Mfm(x)
6. 损失函数与分类问题
GBDT的强大之处在于它可以处理任何具有一阶导数的损失函数。对于分类问题(例如二分类),我们通常使用对数似然损失(Log Loss) 。
6.1. 二分类问题
目标值:yi∈{0,1}。我们模型F(x)输出的是对数几率(Log Odds) 。最终的概率通过Logistic函数转换:
P(y=1∣x)=σ(F(x))=1+e−F(x)1
损失函数为负对数函数
L(y,F(x))=−ylog(p)−(1−y)log(1−p)
其中p=σ(F(x))。可以化简为:
L(y,F(x))=log(1+e−yF(x))
其中y~∈{−1,+1}是另一种编码方式,但本质相同。
6.2. 计算负梯度
我们依然按照之前的流程。在第m步,计算损失函数关于F的负梯度。
rim=−∂F(xi)∂L(yi,F(xi))F(x)=Fm−1(x)
对于对数损失函数L=log(1+e−yF(x))(这里y \in { -1,+1 }$),其导数为:
∂F∂L=1+e−yF(x)−ye−yF(x)=−y⋅1+eyF(x)1
所以,负梯度为:
rim=−(−yi⋅1+eyiFm−1(xi)1)=yi⋅1+eyiFm−1(xi)1
当yi=1时,rim=1/(1+e−Fm−1(xi));当yi=−1时,rim=−1/(1+eFm−1(xi))。这个形式看起来复杂,但它本质上可以看作是观测值与预测概率之间的残差的一种形式。
核心要点: 对于任何损失函数,GBDT的流程都是一样的:
- 计算负梯度(伪残差);
- 用一棵回归树去拟合这个负梯度。(注意:即使是分类问题,这里也是在拟合回归树);
- 通过线搜索等方法,确定每个叶子节点的最优输出值γjm以最小化损失;
- 更新模型。
7. 关键超参数
- n_estimators:树的数量(M)。太大容易过拟合,太小可能欠拟合;
- learning_rate(v):学习率/收缩率。减小学习率意味着需要更多的树,但通常泛化能力更好。通常与n_estimators一起调参;
- max_depth:每棵决策树的最大深度。用于控制树的复杂度,是防止过拟合最重要的参数之一;
- min_samples_split/min_samples_leaf:分裂内部节点/成为叶子节点所需的最小样本数;
- subsample:子采样比例。每次迭代时使用多少比例的训练样本来训练基学习器(类似于随机森林的思想)。