GBDT算法复习小结

如果要挑选我认为目前最重要的机器学习算法的话,我个人认为GBDT应该排名很靠前的,而且在实际应用中也经常用到,著名的xgboost和lightgbm开源算法都是基于GBDT的实现。由于我记性实在是不好,GBDT的算法原理总是看了又忘,所以这次落于博客上面,希望加深记忆。
本篇博客主要参考李航老师的《统计学习方法》以及该博主的这篇文章内容博客地址
GBDT属于boosting(提升方法)大家族的一员,boosting方法的主要思想是通过训练几个弱分类器组合成为强分类器。GBDT的特点是,其使用的每一轮的弱分类器都是一个CART决策树,并且使用了损失函数的负梯度在当前模型的值作为提升树算法中的残差的近似值。
下面具体的讲解一下我对于GBDT的理解。

使用负梯度进行残差拟合

假设我们第t-1轮迭代得到的学习器表示为ft1(x){{f}_{t-1}}(x),损失函数为L(y,ft1(x))L(y,{{f}_{t-1}}(x)),那么本轮我们构建一棵CART树拟合残差ht(x){{h}_{t}}(x) ,从而使得本轮得到的学习器ft(x)=ft1(x)+ht(x){{f}_{t}}(x)={{f}_{t-1}}(x)+{{h}_{t}}(x)的损失函数L(y,ft1(x)+ht(x))L(y,{{f}_{t-1}}(x)+{{h}_{t}}(x))最小。但是拟合残差在大部分的时候,优化残差不是特别方便,所以Freidman提出了利用损失函数的负梯度在当前模型的值作为残差的近似值拟合一棵树。也因此提出了梯度提升算法。

根据李航老师的书上的GBDT算法流程如下

输入:训练数据集T={(x1,y1),(x2,y2),.(xN,yN)}T=\{({{x}_{1}},{{y}_{1}}),({{x}_{2}},{{y}_{2}}),.\ldots \ldots ({{x}_{N}},{{y}_{N}})\} ,损失函数L(y,f(x))L(y,f(x))
输出: 回归树f^(x)\hat{f}(x)
P.S 下面算法中参考李航老师书中算法,将m作为轮次编号,i作为样本编号,j作为叶节点编号。

  1. 初始化一棵树f0(x)=argminc i=1NL(yi,c){{f}_{0}}(x)=\arg \underset{c}{\mathop{\min }}\,\sum\limits_{i=1}^{N}{L({{y}_{i}},c)}
  2. 对m = 1,2,3……M
    a) 对i= 1,2,……N,计算这一轮的残差的梯度
    rmi=[L(yi,f(xi))f(xi)]f(x)=fm1(x){{r}_{mi}}=-{{\left[ \frac{\partial L({{y}_{i}},f({{x}_{i}}))}{\partial f({{x}_{i}})} \right]}_{f(x)={{f}_{m-1}}(x)}}
    b) 对rmi{{r}_{mi}}拟合一棵CART树,得到第m棵树的叶节点区域Rmj{{R}_{mj}},j=1,2,……J
    c) 对j=1,2,……,J,计算最佳拟合值, cmj=argminc L(yi,fm1(xi)+c){{c}_{mj}}=\arg \underset{c}{\mathop{\min }}\,L({{y}_{i}},{{f}_{m-1}}({{x}_{i}})+c)
    d) 更新fm(x)=fm1(x)+j=1JcmjI(xRmj){{f}_{m}}(x)={{f}_{m-1}}(x)+\sum\limits_{j=1}^{J}{{{c}_{mj}}I(x\in {{R}_{mj}})}
  3. 当轮次到达指定轮次M次的时候得到回归树
    f^(x)=fM(x)=m=1Mj=1JcmjI(xRmj)\hat{f}(x)={{f}_{M}}(x)=\sum\limits_{m=1}^{M}{\sum\limits_{j=1}^{J}{{{c}_{mj}}I(x\in {{R}_{mj}})}}

对于上面的第2步骤中的第c)小点,有同学可能跟我一样有疑问,明明建CART的树时候,叶子节点就应该有输出了啊,为什么还要重新确定一遍最佳拟合值呢?我个人觉得,这个可以从两个方面来理解。

  • 一方面,首先我们知道,我们建CART树的时候,拟合的毕竟是梯度,不是实际的残差,实际的残差和梯度之间是有一定的差距的,当我们最终合并这一轮建好的树和之前的树的时候需要重新确定一下叶节点的输出。
  • 另一方面,我们可以这样理解,我们假设第m轮建好的拟合梯度的树叶子节点输出为{bmj}\{ b_{mj}\},其中m是轮次,j是第j个叶子节点。不妨一般来讲,我们使用bmj=avexiRjm yib_{mj} = ave_{x_i \in R_{jm}} \ y_i,也就是平均值表示该叶子节点拟合的值。由于这个时候我们拟合的是梯度,所以我们可以理解为这个值只是确定了下一步优化的方向,有了优化的方向,我们还需要最好的步长,所以实际优化的时候,我们优化的是:
        fm(x)=fm1(x)+ρmj=1JbmjI(xRmj)f_m(x) = f_{m-1}(x) + \rho_m \sum_{j=1}^J b_{mj}I(x \in R_{mj}) ,II为指示函数
    其中ρm\rho_m 就是我们的选择的步长,当然此刻我们是做成两步,先找出优化方向再找出所有的步长,我们把这两步结合起来做成一步:
        fm(x)=fm1(x)+j=1JγmjI(xRmj)f_m(x) = f_{m-1}(x)+\sum_{j=1}^J \gamma_{mj}I(x \in R_{mj}),其中,γmj=ρmbmj\gamma_{mj}=\rho_mb_{mj} ,这个是什么,这个其实就是c)中的那一步了。

下面我们使用一组数据,建立一个回归问题,看一个简单的GBDT例子,x为输入特征变量,y为需要拟合的值。
GBDT算法复习小结
GBDT算法复习小结
从上面的算法流程来看,GBDT本身是个“串行”的方式,每一轮的迭代是建立在之前建好的树的基础上,如果我们要进行并行化操作,一种简单化思路就是进行采样,用不同的采样数据建立多棵树,这个应该就是lightgbm之类的可以并行化的包里面的方法吧,我猜的。

另外,GBDT也可以用来构建特征,具体思路参考这篇博客,这个方法根据我室友项目中实际数据实测,效果提升非常明显。