1 引言¶
梯度提升树算法(Gradient Boosting Decision Tree,GBDT)是一个非常经典的机器学习算法,和我们前面介绍过的Adaboost算法一样,都是提升学习系列算法中的一员。从“梯度提升树”这个算法名称中我们也可以看出,这又是一个决策树的集成算法,更进一步地说,这个算法是以CART决策树算法作为基学习算法的一种集成算法。对于CART决策树算法,在之前的博客中已经有详细的介绍,在阅读本文之前请务必先理解CART决策树算法。接下来,本文将会从提升树开始,逐渐深入的介绍GBDT算法。
2 提升树¶
关于提升树,我们可以从字面上进行理解,可以认为提升树就是一种以决策树作为基学习算法的一种提升学习算法,可以用来解决分类问题,也可以用于解决回归问题。当用于分类问题时,提升树算法只需将AdaBoost算法中的基本学习器限制为二类分类树即可,可以说这时的提升树算法是AdaBoost算法的特殊情况,本文不再细述,且本文主要介绍的GBDT算法无论是在分类问题中还是回归问题汇总,都是从回归提升树的优化而来,所以下面叙述内容主要以回归提升树为主。
提升树模型可以看做是使用加法模型对决策树的线性组合: $${f_M}(x) = \sum\limits_{m = 1}^M {T(x;{\Theta _m})} \tag {1}$$ 式中,${f_M}(x)$是提升树的数学模型,$M$表示决策树的数量,${T(x;{\Theta _m})}$表示第$m$棵决策树,${{\Theta _m}}$表示决策树的参数。对于参数${{\Theta _m}}$的集合$\Theta = \{ {\Theta _1},{\Theta _2}, \cdots ,{\Theta _M}\} $,我们选取的准则是对于集合$D = \{ ({x_1},{y_1}),({x_2},{y_2}), \cdots ,({x_N},{y_N})\} $使得损失函数$\sum {L\left( {{y_i},{f_M}({x_i})} \right)} $最小化,即: $$\arg \mathop {\min }\limits_\Theta \sum\limits_{i = 1}^N {L\left( {{y_i},{f_M}({x_i})} \right)} = \arg \mathop {\min }\limits_\Theta \sum\limits_{i = 1}^N {L\left( {{y_i},\sum\limits_{m = 1}^M {T(x_i;{\Theta _m})} } \right)} \tag {2}$$ 在前面介绍Adaboost算法博文中,我们说过,提升学习系列算法采用前向分步算法来迭代训练个体学习器,提升树当然也不例外,所以提升树的迭代训练过程可以表示为: $${f_m}(x) = {f_{m - 1}}(x) + T(x;{\Theta _m}),m = 1,2, \cdots ,M \tag {3}$$ 在这个迭代训练过程中,每一次迭代都训练完成一颗新的决策树${T(x;{\Theta _m})}$,在这一过程中,也必须满足总体损失函数最小化,也就是说,对于第$m$次迭代所确定的决策树${T(x;{\Theta _m})}$的参数${{\Theta _m}}$有: $${{\hat \Theta }_m} = \arg \mathop {\min }\limits_\Theta \sum\limits_{i = 1}^N {L\left( {{y_i},{f_m}({x_i})} \right)} \tag {4}$$ 将式(3)代入式(4)中,有: $${{\hat \Theta }_m} = \arg \mathop {\min }\limits_\Theta \sum\limits_{i = 1}^N {L\left( {{y_i},{f_{m - 1}}({x_i}) + T({x_i};{\Theta _m})} \right)} \tag {5}$$
在提升树算法中,一般采用平方损失函数,于是: $$L\left( {{y_i},{f_{m - 1}}({x_i}) + T({x_i};{\Theta _m})} \right) = {\left[ {{y_i} - {f_{m - 1}}({x_i}) - T({x_i};{\Theta _m})} \right]^2} \tag {6}$$ 式(6)中,${{y_i} - {f_{m - 1}}({x_i})}$是上一轮迭代中预测值与真实值之间的偏差,这个偏差在提升树中称为残差,用${{r_{m,i}}}$表示,于是式(6)可以表示为: $$L\left( {{y_i},{f_{m - 1}}({x_i}) + T({x_i};{\Theta _m})} \right) = {\left[ {{r_{m,i}} - T({x_i};{\Theta _m})} \right]^2} \tag {7}$$ 这就是第$m$次迭代训练第$m$棵决策树时的损失函数。可以看出,第$m$次迭代其实是对上一次迭代的残差进行拟合,而不是类似Adaboost算法中利用上一轮迭代中的误差率来更新样本权重,这就是提升树算法与Adaboost算法的根本性区别。因此,每一次迭代结束后,提升树算法使用训练样本$x_i$与当前的残差${{r_{m,i}}}$组成新的训练样本集${\{ (xi,{r_{m,i}})\} _{i = 1,2, \cdots ,N}}$来作为下一轮决策树的训练样本集。
用一个例子来加深理解,假如要预测人的年龄,假设真实年龄为30岁,第一棵决策树预测结果为20岁,那么有10岁的残差;第二棵树对10岁的残差进行拟合,输出结果为6岁,有4岁的残差;第3棵树继续对4岁的残差进行拟合……重复这个过程,知道最终的残差在可接受范围,最终的输出结果是每一棵树的预测结果之和。
总结一下提升树的流程:
输入:数据集$D = \{ ({x_1},{y_1}),({x_2},{y_2}), \cdots ,({x_N},{y_N})\} $
(1)初始化个体学习器${f_0}(x) = 0$;
(2)进行$M$次迭代,其中第$m$次迭代的具体过程如下:
(a)针对数据集$D$中每一个样本$(x_i,y_i)$计算残差${r_{m,i}} = {y_i} - {f_{m - 1}}({x_i})$;
(b)利用${\{ (xi,{r_{m,i}})\} _{i = 1,2, \cdots ,N}}$来训练一棵新的决策树${T(x;{\Theta _m})}$;
(c)更新组合:${f_m}(x) = {f_{m - 1}}(x) + T(x;{\Theta _m})$;
(3)对$M$次迭代获得的$M$棵决策树进行集成,得到最终的提升树模型:${f_M}(x) = \sum\limits_{m = 1}^M {T(x;{\Theta _m})} $。
上述过程中,我们使用的是平方误差作为损失函数,这时候对残差的计算会比较简单,但是在很多情况下,我们可能会使用其他的损失函数,这时候对残差的计算就变得复杂起来。为了更好地解决这一问题这一问题,也就有了梯度提升树。
3 梯度提升树¶
从梯度提升树这个算法的名称,我们容易想到梯度下降法,在我看来,梯度提升树中确实也应用了梯度下降法的思想,在梯度下降法中是对模型的权重参数进行拟合,而在梯度提升树中,是将整个决策树模型作为参数进行拟合,可以证明,模型对梯度的负方向进行拟合,总能达到收敛的目的,而且是近似最速的方向,根据这一思想,Fredman提出了梯度提升算法,梯度提升法的核心思想就是利用损失函数的负梯度在当前模型的具体值来替代提升树算法中的残差来拟合一棵回归树,使得提升树思想在更加一般化的损失函数中也能快速优化求解,具有更高的适用性。
梯度提升法与提升树算法的结合,就是本篇的主角——梯度提升树算法。需要注意的是,在梯度提升树中,无论是用于回归问题还是分类问题,所使用的的基学习算法都是CART回归树,区别在于所使用的的损失函数有所区别。
下面分别介绍梯度提升树是如何解决回归问题和分类问题的。
3.1 回归问题¶
先来看看梯度提升树算法在解决回归问题时的步骤流程。
输入:数据集$D = \{ ({x_1},{y_1}),({x_2},{y_2}), \cdots ,({x_N},{y_N})\} $
(1)初始化个体学习器 $${f_0}(x) = \arg \mathop {\min }\limits_c \sum\limits_{i = 1}^N {L\left( {{y_i},c} \right)} $$
(2)进行$M$次迭代,其中第$m$次迭代的具体过程如下:
(a)针对数据集$D$中每一个样本$(x_i,y_i)$计算负梯度在当前模型的值: $${r_{m,i}} = - {\left[ {\frac{{\partial L\left( {{y_i},f({x_i})} \right)}}{{\partial f({x_i})}}} \right]_{f(x) = {f_{m - 1}}(x)}}$$ (b)利用${\{ (xi,{r_{m,i}})\} _{i = 1,2, \cdots ,N}}$来训练一棵新的回归树$T({x_i};{\Theta _m}) = \sum\limits_{j = 1}^J {{c_{m,j}}I(x \in {R_{m,j}})} $;
(c)对回归树${T(x;{\Theta _m})}$确定其每一个叶节点${R_{m,j}},j = 1,2, \cdots ,J$上的最优输出值${c_{m,j}}$:
(d)更新组合:${f_m}(x) = {f_{m - 1}}(x) + \sum\limits_{j = 1}^J {{c_{m,j}}I(x \in {R_{m,j}})} $;
(3)对$M$次迭代获得的$M$棵决策树进行集成,得到最终的提升树模型:${f_M}(x) = \sum\limits_{m = 1}^M {\sum\limits_{j = 1}^J {{c_{m,j}}I(x \in {R_{m,j}})} } $。
对比第2章节中的提升树与本节讲的梯度提升树流程,可以发现,主要的区别就在于在提升提升树中使用负梯度进行拟合而不再是残差。另外,由于在梯度提升树中损失函数不在限制于平方损失函数,因此在步骤(2)(c)中确定每一个叶节点上样本输出时,需要结合实际使用的损失函数来进行确定,例如当使用平方损失函数时,叶节点上的输出值是该节点所有样本的均值,当使用绝对值损失函数时,输出值为该节点样本的中位数。
3.2 分类问题¶
无论是分类问题还是回归问题,本质都是对损失函数进行优化,区别就在于使用何种损失函数。在多数分类算法中,损失函数都是使用最大似然估计来构造,也包括本文的梯度提升树。
(1)二分类问题
在二分类问题中,损失函数为:
$$L(y,f(x)) = \log \left( {1 + \exp ( - yf(x)} \right) \tag {8}$$ 对式(8)进行求导,可得负梯度: $${r_{mi}} = - {\left[ {\frac{{\partial L({y_i},f({x_i}))}}{{\partial f({x_i})}}} \right]_{f(x) = {f_{m - 1}}(x)}} = \frac{{{y_i}}}{{1 + \exp \left( {{y_i}f({x_i})} \right)}} \tag {9}$$ 在每一次迭代构建一棵决策树时,都需要确定在每一个分支节点上的最优输出值。第$m$棵决策树的第$j$个节点输出值为:
$${c_{mj}} = \arg \mathop {\min }\limits_c \sum\limits_{{x_i} \in {R_{mj}}} {\log \left( {1 + \exp ( - {y_i}({f_{m - 1}}({x_i}) + c))} \right)} \tag {10}$$ 一般而言,由于式(10)计算量比较大,难以优化,所以更多是通过下式来代替式(10):
$${c_{mj}} = \frac{{\sum\limits_{{x_i} \in {R_{mj}}} {{r_{mi}}} }}{{\sum\limits_{{x_i} \in {R_{mj}}} {\frac{{|{r_{mi}}|}}{{1 - |{r_{mi}}|}}} }} \tag {11}$$ 总结一下在二分类问题上,梯度提升树算法流程:
输入:数据集$D = \{ ({x_1},{y_1}),({x_2},{y_2}), \cdots ,({x_N},{y_N})\} $ ,其中${y_i} \in \{ - 1, + 1\} $
(1)初始化个体学习器 $${f_0}(x) = \arg \mathop {\min }\limits_c \sum\limits_{i = 1}^N {L\left( {{y_i},p} \right)} $$
(2)进行$M$次迭代,其中第$m$次迭代的具体过程如下:
(a)针对数据集$D$中每一个样本$(x_i,y_i)$根据式(9)计算负梯度在当前模型的值${r_{mi}}$;
(b)利用${\{ (x_i,{r_{mi}})\} _{i = 1,2, \cdots ,N}}$来训练一棵新的回归树$T({x_i};{\Theta _m}) = \sum\limits_{j = 1}^J {{c_{m,j}}I(x \in {R_{mj}})} $;
(c)对回归树${T(x;{\Theta _m})}$利用式(10)或者式(11)确定其每一个叶节点${R_{mj}},j = 1,2, \cdots ,J$上的最优输出值${c_{mj}}$;
(d)更新组合:${f_m}(x) = {f_{m - 1}}(x) + \sum\limits_{j = 1}^J {{c_{m,j}}I(x \in {R_{mj}})} $;
(3)对$M$次迭代获得的$M$棵决策树进行集成,得到最终的提升树模型:${f_M}(x) = \sum\limits_{m = 1}^M {\sum\limits_{j = 1}^J {{c_{m,j}}I(x \in {R_{m,j}})} } $;
可以看出,出了负梯度计算和确定叶节点输出值外,其他所有过程基本是和回归问题中的流程是一样的。
(2)多分类问题 梯度提升树算法在处理多分类问题时过程要比处理二分类问题复杂一些。假设数据集样本类别数量为$K$,则梯度提升树算法中损失函数可以表示为:
$$L(y,f(x)) = - \sum\limits_{k = 1}^K {{y_k}\log {p_k}(x)} \tag {12}$$ 其中,$p_k(x)$为样本$x$属于第$k$类的概率,有: $${p_k}(x){\text{ = }}\frac{{\exp \left( {{f_k}(x)} \right)}}{{\sum\limits_{l = 1}^K {\exp \left( {{f_l}(x)} \right)} }} \tag {13}$$ 结合式(12)(13)进行求导可得在第$m$次迭代时,第$i$个样本在类别$l$上的负梯度为:
$${r_{mil}} = - {\left[ {\frac{{\partial L({y_i},f({x_i}))}}{{\partial f({x_i})}}} \right]_{{f_k}(x) = {f_{l,m - 1}}(x)}} = {y_{il}} - {p_{l,m - 1}}({x_i}) \tag{14}$$ 从式(14)可以看出,负梯度其实就是真实值与预测概率的差值。
确定每一个树节点的输出值:
$${c_{mjl}} = \arg \mathop {\min }\limits_{{c_{jl}}} \sum\limits_{i = 0}^m {\sum\limits_{k = 1}^K {L\left( {{y_k},{f_{m - 1}}_{,l}(x) + \sum\limits_{j = 0}^J {{c_{jl}}I({x_i} \in {R_{mjl}}} } \right)} } \tag {15}$$ 对于式(15),也有一个更加简化的计算用于替代:
$${c_{mjl}} = \frac{{K - 1}}{K}\frac{{\sum\limits_{{x_i} \in {R_{mjl}}} {{r_{mil}}} }}{{\sum\limits_{{x_i} \in {R_{mil}}} {|{r_{mil}}|} (1 - |{r_{mil}}|)}} \tag {16}$$ 明白这些,就可以来梳理一下在多分类问题中,梯度提升树算法流程。
输入:数据集$D = \{ ({x_1},{y_1}),({x_2},{y_2}), \cdots ,({x_N},{y_N})\} $
(1)初始化个体学习器 $${f_{k0}} = 0,k = 1,2, \ldots ,K$$ (2)进行$M$次迭代,其中第$m$次迭代的具体过程如下:
(a)通过式(13)计算样本点属于每一个类别的概率;
(b) 对$k = 1,2, \ldots ,K$进行迭代:
1)通过式(14)计算负梯度;
2)利用负梯度$\{ ({x_1},{r_{k1}}), \cdots ,({x_N},{r_{kN}})\} $拟合一棵决策树;
3)使用式(15)或(16)确定决策树每一个节点上的输出值;
4)更新组合:${f_{mk}}(x) = {f_{m - 1,k}}(x) + \sum\limits_{j = 1}^J {{c_{mkj}}I(x \in {R_{mkj}})} $;
(c)对回归树${T(x;{\Theta _m})}$利用式(10)或者式(11)确定其每一个叶节点${R_{mj}},j = 1,2, \cdots ,J$上的最优输出值${c_{mj}}$;
(d)更新组合:${f_m}(x) = {f_{m - 1}}(x) + \sum\limits_{j = 1}^J {{c_{m,j}}I(x \in {R_{mj}})} $;
(3)得到最终的提升树模型:${f_{Mk}}(x) = \sum\limits_{m = 1}^M {\sum\limits_{j = 1}^J {{c_{mkj}}I(x \in {R_{mkj}})} } $。
纵观整个过程,其实无论是回归问题、二分类问题还是现在说的多分类问题,思路和流程都是基本一样的。对于多分类问题,区别就在于对每一个类别都需要单独建立一棵决策树。上面的描述可能还不够直观,下面通过更加直白的描述来进一步分析多分类问题中的梯度提升树算法。
假设我们现在需要分类的数据集有3个类别标签,即$k=3$,分别用$(A, B, C)$表示。在正式开始训练之前,我们需要对每个样本的类别标签进行独热编码转换为向量的形式,例如,某个属于第二个分类样本$X=(x, B)$独热编码转换后类别可以用向量表示为[0, 1, 0],我们可以将向量每一个维度看做是属于对应类别的概率,即第一位和第三位的0分别表示该样本属于第一个类别和第三类别的概率为0,第二位上的1表示该样本属于第二类的概率为1,这样的话可以方便得将样本$X$拆分为3个样本:
$X_1=(x, 0)$表示样本$x$属于第1类的概率;
$X_2=(x, 1)$表示样本$x$属于第2类的概率;
$X_3=(x, 0)$表示样本$x$属于第3类的概率。
用这三个样本,我们可以去训练三棵回归树,假设三棵树的拟合值分别为${f_1}(x)$、${f_2}(x)$、${f_3}(x)$,通过式(13)可以将拟合值转换为属于三个类别的概率$p_1$、$p_2$、$p_3$,再通过式(14)求负梯度,即:
$${r_{111}} = 0 - {p_1}$$ $${r_{112}} = 0 - {p_2}$$ $${r_{113}} = 0 - {p_3}$$ 有了负梯度之后,可以继续第二轮训练,第二轮训练时在第一轮的基础上继续训练三棵树,新的三棵树分别用$(x,{r_{111}}$、$(x,{r_{112}}$、$(x,{r_{113}}$进行训练。这一过程重复迭代$M$次,每次获得3棵决策树。最终我们可以获得三个树的集成,每个集成树的最终输出都对应一个类别的输出。