提升树(Boosting tree)算法总结

本文是综合了之前的以往多个笔记汇总而成,内容包含:

一、Boosting基本概念
二、前向分步加法模型
    1. 加法模型
    2. 前向分步算法
三、AdaBoost
    1. 算法解析
    2. 模型构建
    3. 算法缺点
四、二叉分类树
五、回归分类树
    1. 算法解析
    2. 模型构建
六、梯度提升树(GBDT)
    1. 算法解析
    2. 模型构建
七、XGBoost
    1. 原理详解
    2. 目标函数
    3. 学习过程
    4. 损失函数
    5. 正则化
    6. 决策树的构建
    7. 流程步骤
    8. 优缺点
八、总结
    1. Boosting家族
    2. AdaBoost
    3. 回归提升树和AdaBoost
    4. GBDT和回归提升树   
    5. XGBoost和GBDT
    6. 参考文献

提升(Boosting)是集成学习方法里的一个重要方法,其主要思想是将弱分类器组装成一个强分类器。在 PAC(概率近似正确)学习框架下,则一定可以将弱分类器组装成一个强分类器。

提升树模型实际采用加法模型(即基函数的线性组合)与前向分步算法,以决策树为基函数的提升方法称为提升树(Boosting Tree)。

对分类问题决策树是二叉分类树对回归问题决策树是二叉回归树

提升树模型可以表示为决策树的加法模型:
$$f_M(x)=∑^M_{i=1}T(x;\Theta _m)$$其中$T(x;\Theta _m)$表示决策树;$\Theta_m$表示决策树的参数;$M$为树的个数。

不同问题的提升树学习算法,其主要区别在于损失函数不同。平方损失函数常用于回归问题,用指数损失函数用于分类问题,以及绝对损失函数用于决策问题

由于树的线性组合可以很好的拟合训练数据,即使数据中的输入和输出之间的关系很复杂也是如此,所以提升树是一个高功能的学习算法。

一、基本概念

提升(Boosting)方法是一类应用广泛且非常有效的统计学习方法。

它基于这样一种思想:对于一个复杂任务来说,将多个专家的判断进行适当的综合所得出的判断,要比其中任何一个专家单独的判断好。实际上,就是“三个臭皮匠顶个诸葛亮”的道理。

强可学习:如果存在一个多项式的学习算法能够学习它,并且正确率很高,那么就称这个概念是强可学习的

弱可学习:如果存在一个多项式的学习算法能够学习它,学习的正确率仅比随机猜测略好,那么就称这个概念是弱可学习的

AdaBoost算法:那么如何将弱学习算法提升为强学习算法呢?关于提升方法的研究很多,有很多算法被提出。最具代表性的是AdaBoost算法(AdaBoost algorithm)

Boosting算法的两个核心问题

  1. 在每一轮如何改变训练数据的权值或概率分布

    通过提高那些在前一轮被弱分类器分错样例的权值,减小前一轮分对样例的权值,来使得分类器对误分的数据有较好的效果。

  2. 如何将弱分类器组合成一个强分类器

    通过加法模型将弱分类器进行线性组合,比如 AdaBoost 通过加权多数表决的方式,即增大错误率小的分类器的权值,同时减小错误率较大的分类器的权值。

AdaBoost的巧妙之处就在于它将这些想法自然且有效地实现在一种算法里。
AdaBoost算法是损失函数为指数函数时的Boosting算法

二、前向分步加法模型(Forward Stagewise Additive Modeling)

1. 加法模型

(形为$Y=I+U+T+K$的模型为加法模型)
$$f(x)=∑^M_{m=1}\beta _mb(x;\gamma _m)$$ 其中,$b(x;\gamma_m)$为基函数,$\beta_m$为基函数的系数。

2. 前向分步算法

在给定训练数据及损失函数$L(y,f(x))$的条件下,学习加法模型$f(x)$称为经验风险极小化,即损失函数极小化的问题:
$$min_{(\beta_m,\gamma_m)}∑^N_{i=1}L(y_i,∑^M_{m=1}\beta _mb(x_i;\gamma_m )) $$

通常这是一个复杂的优化问题。前向分布算法(forward stagwise algorithm)求解这一优化问题的思路是:因为学习的是加法模型,如果能够从前向后,每一步只学习一个基函数及其系数,逐步逼近优化目标函数式$f(x)=∑^M_{m=1}\beta _mb(x;\gamma _m)$,那么就可以简化优化的复杂度。

具体地,每步只需优化如下损失函数:$$min_{\beta, \gamma}∑^M_{i=1}L(y_i,\beta b(x_i;\gamma))$$

前向分布算法步骤如下:

输入:训练数据集$D={(x_1,y_1),(x_2,y_2),(x_3,y_3),…,(x_N,y_N)}$;损失函数$L(y,f(x))$;基函数集$(\beta (x;\gamma))$;
输出:加法模型$f(x)$

(1)初始化$f_0(x)=0$

(2)对于$k=1,2,…,K$
(a)极小化损失函数,得到$\beta_m ,\gamma_m$:
$$(\beta _m,\gamma_m)=argmin_{(\beta,\gamma)}∑^N_{i=1}L(y_i,f_{m-1}(x_i)+\beta b(x_i,\gamma))$$
(b)更新
$$f_m(x)=f_{m-1}(x)+\beta _mb(x;\gamma_m)$$

(3)得到加法模型
$$f(x)=f_M(x)=∑^M_{m=1}\beta_mb(x;\gamma_m)$$
这样。前向分步算法将同时求解从$m=1$到$m=M$所有参数$\beta_m,\gamma_m$的优化问题简化为逐次求解$\beta_m,\gamma_m$的优化问题。
前向分布算法学习的是加法模型,当基函数为基本分类器是,该加法模型等价于Adaboost的最终分类器。(AdaBoost算法参数迭代公式就是由此而来)

基学习器:基函数为同一类型;反之称为组件学习器或直接成个体学习器

三、AdaBoost

AdaBoost算法是前向分步算法的特例,其模型是由基本分类器组成的加法模型,损失函数是指数函数。

1. AdaBoost算法解析

AdaBoost模型是弱分类器的线性组合:
$$f(x)=∑^M_{m=1}\alpha _mG_m(x)$$

  • $M$表示该提升树共有$M$个弱分类器组成
  • $G_m(x)$表示第$m$个弱分类器
  • $\alpha_m$为第$m$个弱分类器的参数(反应该分类器的重要性)

Adaboost算法在分类问题中的主要特点:通过改变训练样本的权重,学习多个分类器,并将这些分类器进行线性组合,提高分类性能。 AdaBoost-算法描述(伪代码)如下:

输入:训练数据集$T={(x_1,y_1),(x_2,y_2),…,(x_N,y_N)}$,其中$x_i\in \chi ⊆R^n,y_i\in Y={-1,1}$,弱学习算法$G_m(x)$;
输出:最终强化算法分类器$G(x)$
(1)初始化训练数据总和为1的权值分布:(初始权重为归一化后的均值既$\frac 1N$)
$$D_1=(w_{11},…,w_{1i},…w_{1N}),w_{1i}=\frac 1N, i=1,2,…N$$
(2)对$m=1,2,…M$:(弱分类器的个数)
(a)使用具有权值分布的$D_m$的训练数据集学习,得到基本分类器:(数据集$X$到{-1,1}的映射)
$$G_m(x):X->{-1,1}$$
(b)计算$Gm(x)$在训练数据集上的分类误差率:(公式不够简洁明了,其实总结下来非常好理解:误差率$e_m$=误分类样本的权值之和)
$$e_m=∑^N_{i=1}P(G_m(x_i)≠y_i)=∑^N_{i=1}w_{mi}I(G_m(x_i)≠y_i)$$
我们来考虑下误差$e_m$的取值空间:由于训练集权制之和为1,因此误差$0≤e_m≤1$。但是这样还不够。因为我们在选择分裂阈值的时候会选择一个最优或局部最优的取值来分裂,且当$e_m=0.5$是表明该分裂阈值对预测无贡献。因此最终得到的$e_m$的实际取值应小于$e_m≤0.5$。
所以最终:$0≤e_m≤0.5$,且每次迭代误差$e_m$递减。这点对下面的参数理解很重要。
(c)计算$G_m(x)$的系数:(这里对数为自然对数)
$$\alpha_m=\frac 12log\frac{1-e_m}{e_m} $$
那么问题来了,为什么要用这个公式来计算更新每个基分类器的参数?我们先画个图出来观察下这个函数。(其中y轴为$\alpha _m$,x轴为误差$e_m$)

由(2-b)我们得到误差$e_m$的取值范围为$0≤e_m<0.5$,结合该图可以可知$0<\alpha_m<+\infty $。
另外可以发现,通过该函数的转换,弱分类器$G_m(x)$的误差的越小,参数$\alpha_m$越大。即实现了给分类误差率小的基本分类器以大的权值,给分类误差率大的基本分类器以小的权值
(d)更新训练数据集的权值分布:(该权值决定数据集的重要性,并让误差的计算变得简单)
$$D_{m+1}=(w_{m+1,1},…,w_{m+1,i},…w_{m+1,N})$$
$$w_{m+1,i}=\frac {w_{mi}}{Z_m}exp(-\alpha_my_iG_m(x-i)),i=1,2,…N$$
这里$y_i={-1,1} $为真实值,$G_m(x_i)={-1,1}$为预测值。当预测正确时$y_iG_m(x_i)$为1,反之为-1。
令$\delta_{m_i}=\alpha_my_iG_m(x_i)$,$\theta_{mi}=\frac {w_{mi}}{Z_m}$(把它看作一个用于归一化权值的加权平均常数)。权重$w_{m+1,i}$的更新函数可以简化为$$w_{m+1,i}=\theta_{mi}exp(\delta {mi}),i=1,2,…N$$画出$y=w{m+1,i}=exp(\delta_{mi})$的图形来看一下:

由于$0<\alpha_m<1$,所以$-1<\delta_{m,i }<1$。且使得预测错误的数据集样本点更新后的权重变大,预测正确的权值变小,然后对所有权值进行归一化。这就是该函数实现的作用。(图中y=1是当$\alpha$无限接近于0时的情况:解释为,当$\alpha_m$权值越大,权重$w_{m+1,i}$更新改变的效果越明显。)
这里,$Z_m$是规范化因子,目的是使各数据集的权重进行归一化。理解为$Z_m$=更新后的各数据集权重之和。
$$Z_m=∑^N_{i=1}w_{mi}exp(-\alpha_my_iG_m(x_i))$$
(3)构建基本分类器的新型组合$f(x)=∑^M_{m=1}\alpha_mG_m(x)$,即:
$$G(x)=sign(f(x))=sign(∑^M_{m=1}\alpha_mG_m(x))$$
函数$sign()$的意义是将正数判别为1,负数判别为-1,最终达到分类的目的。如图:

2. AdaBoost构建步骤

上面解释了AdaBoost算法的具体内容。这里写出它的分布实现步骤再对上文算法加深下理解:
(1)假设训练数据集具有均匀的权值分布,即每个训练样本在基本分类器的学习中作用相同,这一假设保证第1步能够在原始数据上学习基本分类器$G_1(x)$。
(2)AdaBoost反复学习基本分类器,在每一轮$m=1,2,…,M$顺次地执行下列操作:
(a)使用当前分布$D_m$加权的训练数据集,学习基本分类器$G_m(x)$
(b)计算基本分类器$G_m(x)$再加权训练数据集上的分类误差率(即误分类样本的权值之和。这里要注意$w_{mi}$表示第$m$轮中第$i$个实例的权值,且权值之和为1,即$∑^N_{i=1}w_{mi}=1$):
$$e_m=P(G_m(x_i)≠y_i)=∑_{G_m(x_i)≠y_i}w_{mi}$$
(c)计算基本分类器$G_m (x)$的系数$\alpha_m$。$alpha_m$表示$G_m(x)$在最终分类器中的重要性。由上面(2-c)可知,当$e_m≤1/2$时,$alpha_m≥0$,并且$\alpha_m$随着$e_m$的减小而增大,所以分类误差率越小的分类器在最终分类器中的作用越大。
(d)更新训练数据的权值分布为下一轮作准备。式(2-d)的权重更新函数可以写成:

由此可知,被基本分类器$G_m (x)$误分类样本的权值得以扩大,而被正确分类样本的权值却得以缩小。两相比较,误分类样本的权值被放大$e^{(2\alpha_m)}=\frac{e_m}{1-e_m} $倍。因此,误分类样本在下一轮学习中起更大的作用。不改变所给的训练数据,而不断改变训练数据权值的分布,使得训练数据在基本分类器的学习中起不同的作用,这是AdaBoost的一个特点。
(3)线性组合$f(x)$实现$M$个基本分类器的加权表决。系数$\alpha_m$ 表示了基本分类器$G_m (x)$的重要性,这里,所有$\alpha_m$ 之和并不为1。$f(x)$的符号决定实例x的类,$f(x)$的绝对值表示分类的确信度。利用基本分类器的线性组合构建最终分类器是AdaBoost的另一特点。

3. AdaBoost算法缺点

1. 常规AdaBoost算法只能处理二分类问题

MultiBoost工具的侧重点不同于XGBoost,是Adaboost算法的多分类版本实现,更偏向于解决multi-class / multi-label / multi-task的分类问题。

2. 对异常值敏感

指数损失存在的一个问题是不断增加误分类样本的权重(指数上升)。如果数据样本是异常点(outlier),会极大的干扰后面基本分类器学习效果;

3. 模型无法用于概率估计

对于取值为$\hat y \in {-1,1}$的随机变量说,$e^{-\hat yf}$不是任何概率密度函数的对数形式,模型$f(x)$的结果无法用概率解释。
MLAPP中的原话:$e^{-\hat y f}$is not the logarithm of any pmf for binary variables $\hat y \in {-1,1}$; consequently we cannot recover probability estimate from $f(x)$.”

四、二叉分类提升树(如AdaBoost)

对于二类分类问题,提升树算法只需要将AdaBoost算法例子中的基本分类器限制为二叉分类树即可,可以说此时的决策树算法时AdaBoost算法的特殊情况

二叉分类树中用基尼指数作为最优特征选择的度量标准。

在实际操作中,通过遍历所有特征(如果是连续值,需做离散化)及其取值,选择基尼指数最小所对应的特征和特征值。

五、二叉回归提升树

二叉回归树采用平方误差最小化作为特征选择和切分点选择的依据

下面要解决的问题是:如何划分特征空间?

一个启发式的方式就是选择特征空间中第$m$个特征$f_m$和它的取值$s$,作为划分特征和划分点,然后寻找最优划分特征$f_m$和最优划分点$s$。

具体操作就是遍历所有未划分过的特征集合和对应的取值(集合)求解得出另损失函数最小的参数$f_m和s$。

1. 回归问题提升树算法解析

对于二类分类问题,提升树算法只需将AdaBoost算法中的基本分类器限制为二类分类树即可,可以说这时的提升树算法是AdaBoost算法的特殊情况,这里不再细述。

已知一个训练数据集$T={(x_1,y_1),(x_2,y_2),…,(x_N,y_N)},x_1 \in \chi ⊆ R^n$,$ \chi $为输入空间,$y_i \in Y ⊆ R$,$Y$为输出空间。如果将输入空间$\chi $划分为$J$个互不相交的区域$R_1,R_2,…R_J$,并且在每个区域上确定输出的敞亮$c_j$,那么树可以表示为:$$T(x; \Theta)=∑^J_{j=1}c_jI(x \in R_j)$$

其中参数$\Theta ={(R_1,c_1),(R_3,c_2),…,(R_J,c_J)}$表示树的区域划分和各区域上的常数。$J$是回归树的复杂度即叶节点的个数。

$f_0(x)=0$
$f_m(x)=f_{m-1}(x)+T(x;\Theta_m),m=1,2,…,M$
$f(x)=f_M(x)=∑^M_{m=1}T(x;\Theta_m)$

在前向分步算法的第$m$步,给定当前模型$f_{m-1}(x) $,需求解:

$Pred (\Theta_m)$
$=argmin_{(\Theta_m)}∑^N_{i=1}L(y_i,f_m(x_i))$
$=argmin_{(\Theta_m)}∑^N_{i=1}L(y_i,f_{m-1}(x_i)+T(x_i;\Theta_m ))$
得到$Pred(\Theta _m)$,即第$m$棵树的参数。

当采用平方误差损失函数$L(y,f(x))=(y-f(x))^2$时,其损失变为:

$L(y,f_{m-1}(x)+T(x;\Theta_m ) )$
$=[y-f_{m-1}(x)-T(x;\Theta_m )]^2$
$=[\gamma -T(x;\Theta_m)]^2$

这里$\gamma=y-f_{m-1}(x)$是当前模型拟合数据的残差(这点很重要)。所以,对回归问题的提升算法来说,只需要简单地拟合当前模型的残差。这样算法是很简单地。

2. 回归提升树构建步骤

输入:训练数据集$T={(x_1,y_1),(x_2,y_2),…,(x_N,y_N)},x_1 \in \chi ⊆ R^n$,$y_i \in Y ⊆ R$;
输出:回归提升树$f(x)$;
(1)初始化$f_0(x)=0 $

(2)对$m=1,2,…,M$

(a)计算或更新残差
$$\gamma _{mi}=y_i-f_{m-1}(x_i),i=1,2,…,N$$
(b)拟合残差$\gamma_{mi}$学习一个回归树,得到$T(x; \Theta_m)$
(c)更新$f_m(x)=f_{m-1}(x)+T(x;\Theta_m)$
(d)重复步骤(a~c)知道满足终止条件

(3)得到回归问题提升树
$$f(x)=f_M(x)=∑^M_{m=1}T(x;\Theta_m)$$

六、梯度提升树(GBDT)

1. 梯度提升树(GBDT)算法解析

Gradient boosting 就是通过加入新的弱学习器,来努力纠正前面所有弱学习器的残差,最终这样多个学习器相加在一起用来进行最终预测,准确率就会比单独的一个要高。之所以称为 Gradient,是因为在添加新模型时使用了梯度下降算法来最小化的损失。
利用损失函数的负梯度在当前模型的值作为回归问题提升树算法中残差的近似值,拟合一个回归树。

损失函数的负梯度为:

也就是说梯度提升树(GBDT)本质上和提升回归树类似,唯一不同的是使用损失函数的负梯度在当前模型的值取近似替代回归提升树中的残差去拟合回归树。这里算法解析部分可以参考上文回归提升树。

2. 梯度回归树(GBDT)构建步骤

输入:训练数据集$T={(x_1,y_1),(x_2,y_2),…,(x_N,y_N)},x_1 \in \chi ⊆ R^n$,$y_i \in Y ⊆ R$;
输出:梯度提升树$\hat{f}(x) $;(因为这里用损失函数负梯度的值去近似残差,因此使用$\hat{f}(x) $更严谨些)
(1)初始化:这里初始化与回归提升树略有不同
$$f_0(x)=argmin_c∑^N_{i=1}L(y_i,c)$$

(2)对$m=1,2,…,M$
(a)对$i=1,2,…,M$计算损失函数在当前模型的值作为残差$\gamma_{mi}$的近似

(b)对$\gamma_{mi}$拟合一个回归树,得到第$m$棵树的叶节点区域$R_{mj}$,$j=1,2,…,J$
(c)对$j=1,2,..,J$,计算
$$c_{mj}=argmin_{(c)}∑_{(x_i \in R_{mj})}$$
(d)更新$f_m(x)=f_{m-1}+∑^J_{j=1}c_{mj}I(x\in R_{mj})$

(3)得到回归树
$$\hat{f}(x)=f_M(x)=∑^M_{m=1}∑^J_{j=1}c_{mj}I(c\in R_{mj})$$

算法解释

  1. 第(1)步初始化,估计使用损失函数极小化的常数值,它是只有一个根结点的树。
  2. 第(2a)步计算损失函数的负梯度在当前模型的值,将它作为残差的估计。对于平方损失函数,就是通常所说的残差;对于一般损失函数,它就是残差的近似值。
  3. 第(2b)步估计回归树叶节点区域,以拟合残差的近似值。
  4. 第(2c)步利用线性搜索估计叶节点区域的值,是损失函数极小化
  5. 第(2d)步更新回归树,然后输出最终模型$\hat{y}(x)$。

3. GBDT缺点

效率低:gradient boosting 的实现是比较慢的,因为每次都要先构造出一个树并添加到整个模型序列中。所以就有了XGBoost

七、XGBoost

1. XGBoost原理详解

前面介绍了提升树算法,其实 XGBoost 就是一种特殊的提升树算法,准确的说它是一种梯度提升决策树(GBDT ,Gradient Boosting Decision Trees)。GBDT 与前面介绍的提升树方法主要的区别就是利用损失函数的负梯度在当前模型的值作为回归问题提升树算法中残差的近似值,来拟合一颗回归树,即:

XGBoost 就是对 GBDT 的实现,但是一般来说,gradient boosting 的实现是比较慢的,因为每次都要先构造出一个树并添加到整个模型序列中。

而 XGBoost 的特点就是计算速度快模型表现好,这两点也正是这个项目的目标。

2. XGBoost的目标函数

传统 GBDT 算法的目标函数只有损失函数这一项,而 XGBoost 在此基础上进行了改进,增加了正则项以防止模型过度复杂:

$$Obj =∑^N_{i=1}L(y_i,\hat{y}i)+∑{m=1}^M\Omega(f_m), f_m \in F$$

在这里我们不能够使用 SGD 算法进行优化,因为我们需要寻找的新的函数 f 是一棵树,而不仅仅是一个数值向量。解决方案也是和提升树一样,采用 Boosting 的思想,从一个常量(通常是0)进行预测,每次添加一个新的预测残差的函数
$f_0(x)=0$
$f_m(x)=f_{m-1}(x)+T(x;\Theta_m)$
$f_M(x)=∑^M_{m=1}T(x;\Theta_m),m=1,2,…,M$

由以上公式我们可以知道,我们所要做的唯一的一件事就是寻找一个方法来确定第$m$轮的函数$f_m(x_i)$。但是怎么确定每一轮迭代的函数$f_m(x)$呢,答案是优化!

3. XGBoost算法学习过程

第$m$轮迭代的预测结果为:
$$\hat{y}_m(x_i)=\hat{y}_{m-1}(x_i)+f_m(x_i)$$

目标函数为:

$Obj_m =∑^N_{i=1}L(y_i,f_m(x_i))+∑_{m=1}^M\Omega(f_m)$

$=∑^N_{i=1}L(y_i,f_{m-1}(x)+f_m(x_i)+\Omega(f_m))+const$

推导到这里,应该就清楚了,我们的目标就是最小化上式去除常量的部分。那么我们首先来考虑一下损失函数为平方(Square)损失的情况,即:

$Obj_m =∑^N_{i=1}(y_i,f_{m-1}(x_i)+f_m(x_i))^2+\Omega(f_m)$

$=∑^N_{i=1}[2(f_{m-1}-y_i)f_m(x_i)+f_m(x_i)^2]+\Omega(f_m)+const$

有上述分析,我们知道对于目标函数当我们将损失函数设定为平方损失的时候,目标函数最终可转化为一个关于 $f_m(x)$ 的二次函数,这时候我们很容易对其进行优化,甚至可以求出它的解析解。

但是,为了算法的通用性和可扩展性的考虑,XGBoost 并没有指定损失函数为平方损失函数,此时我们会发现其实目标函数的表达是还是相当复杂的:

$$Obj_m=∑^N_{i=1}L(y_i,f_{m-1}(x_i)+f_m(x_i))+\Omega(f_m)+const$$

这时候该怎么做呢,陈天奇大神想出了我们高数中学过的泰勒展开式,具体怎么做的呢?

4. 泰勒展开时近似损失函数

为了更好的介绍 XGBoost 中是如何使用泰勒展开式来近似损失函数的,首先让我们回顾一下泰勒展开式的二阶形式:

$$f(x+\Delta x)=f(x)+f’(x)\Delta x+\frac 12f’’(x)\Delta x^2+R(\Delta x)$$

其中$R(\Delta x)$表示$\Delta x$的高阶无穷小。因此,有:
$$f(x+\Delta x)\approx f(x)+f’(x)\Delta x+\frac 12f’’(x)\Delta x^2$$

有了泰勒公式,我们给出如下定义:

$$g_i=\partial _{f_{m-1}(x_i)}L(y_i, f_{m-1}(x_i))$$
$$h_i=\partial^2 _{f_{m-1}(x_i)}L(y_i, f_{m-1}(x_i))$$

这里我们把$L(y_i,f_{m-1}(x_i))$看成是$f_{m-1}(x_i)$为自变量的函数,因此$g_i$和$h_i$为其一阶导和二阶导数(其实是偏导),并且我们将目标函数中的$f_m(x_i)$看成上式中自变量的增量$\Delta x$,因此将目标函数按$f_{m-1}(x_i)$进行泰勒展开,得到:

$Obj_m=∑^N_{i=1}L(y_i,f_{m-1}(x_i)+f_m(x_i))+ \Omega(f_m)+const$
$\approx ∑^N_{i=1}[L(y_i,f_{m-1}(x_i))+g_if_m(x_i)+\frac 12h_if^2_m(x_i)] + \Omega(f_m)+const$

去除掉常量部分,我们可以得到新的目标函数:
$$Obj_m=∑^N_{i=1}[g_if_m(x_i)+\frac 12 h_if^2_m(x_i)]+\Omega(f_m) $$

这样做的好处是:

理论上的好处:使得我们更加清楚的知道我们在学习什么,以及更好的收敛性;
工程上的好处

  1. $g_i$和$h_i$都来自于损失函数的定义
  2. 函数的学习过程仅仅通过$g_i$和$h_i$依赖于目标函数
  3. 可以利用不同的模块分开实现不同的损失函数,例如平方损失函数和 logistic 损失函数,这样损失函数不会受限制。

5. 正则化的处理

目标函数中正则化项存在的原因是为了限制模型的复杂度,让模型在训练集上能够取得较好的结果的前提下尽可能地简单。而前面我们也提到了,在 XGBoost 中,对于采用前向分布方法一步步迭代的优化时,我们模型的复杂度就是当前要定义的决策树的复杂度。

决策树函数的定义

为此,我们首先重新定义树:我们将树定义为一个该树中所有叶子节点的值的向量。并且,每个叶子的索引映射函数映射实例到每个叶子节点上:

定义决策树的复杂度

我们将决策树的复杂度,也就是目标函数定义如下:
$$\Omega (f_m)=\gamma T+\frac 12\lambda∑^T_{j=1}w_j^2$$

其中,$T$树中叶子结点的个数计算如下:

新目标函数的优化

首先,我们对叶子节点$j$中的实例进行如下定义:
$$I_j={i|q(x_i)=j }$$
此时目标函数为:
$$Obj_m\approx ∑^N_{i=1}[g_if_m(x_i)+\frac 12 h_if_m^2(x_i)]+\Omega(f_m)+const$$
$$=∑^N_{i=1}[g_iw_{q(x_i)}+\frac 12h_iw^2_{q(x_i)}]+\gamma T+\frac 12 \lambda ∑^T_{j=1}w^2_j$$
$$=∑^T_{j=1}[(∑_{i\in I_j}g_i)w_i+\frac 12(∑_{i \in I_j}h_i+\lambda)w^2_j]+\gamma T $$

首先,我们进行如下定义:

$$G_j=∑_{i \in I_j}g_i,H_j=∑_{i \in I_j}h_i$$

进一步简化目标函数:
$$Obj_m=∑^T_{j=1}[G_jw_j+\frac 12(H_i+\lambda)w^2_j]+\gamma T$$

众所周知,对于一元二次函数,由如下两条结论:
$$arg \min _xG_x + \frac 12Hx^2=-\frac GH,H>0$$
$$\min _xG_x+\frac 12Hx^2=- \frac {G^2}{2H}$$

因此对于目标函数进行最小化,当$w_j=-\frac {G_j}{H_j+\lambda }$时,我们得到:
$$\min _{Obj_m}=∑^T_{j=1}[G_jw_j+\frac 12(H_j+\lambda)w^2_j]+\gamma T$$
$$=-\frac 12 ∑^T_{j=1}\frac {G^2_j}{H_j+\lambda }+\gamma T$$

至此,对于第 t 轮的一颗已经分裂好的决策树,我们能够求出其对应的最小化的目标函数。但是到目前为止,到底如何进行分裂我们还不知道具体的做法,接下来让我们一起学习 XGBoost 是如何寻找分裂点的。

6. 决策树构建

决策树的生成策略

对于回归决策树来说,在目标函数已经确定的情况下,接下来我们的问题是如何寻找对于当前训练样本的最优的决策树结构。当然,我们最容易想到的是穷举法。如果按照穷举法,我们列出所有的可能的决策树的结构$q$,然后基于决策树结构$q$去计算它的目标函数值,在计算完所有可能的决策树结构后,选择目标函数值最小的决策树结构$\hat q$作为最终决策树。

虽然理论上来说,穷举法能够寻找到最优的决策树结构,但是在有限的时间内我们无法去寻找到最优的决策树结构,因为可能的树结构有无穷多种。因此,在应用中我们采用的是贪心的策略来一步步地增长树的结构。也就是从根节点开始,不断地进行递归地分裂,直至在给定的准则下无法进行分裂为止。所以,接下来我们需要知道递归时如何进行合适并有效地分裂当前节点。

寻找最优分裂点

对于一个叶子节点,如何进行分裂我们需要遵循的原则是分裂后的两颗子树的最优目标函数值之和要小于未分裂之前的父节点,为了叙述方便我们定义如下的目标函数值的 “增益”:\
$$Gain=\frac 12[\frac {G^2_L}{H_L+\lambda } + \frac {G^2_R}{H_R+\lambda } - \frac {(G_L+G_R)^2}{H_L+H_R+\lambda } -\gamma ]$$
上式表示的是在某个节点分裂前的目标函数值与分裂后的目标函数值的差值,由于我们的目标是寻找到最优的决策树,也就是说只有当$Gain$的值为正时我们才会选择进行分裂。

分裂点寻找方法

  • 对每一个待分裂节点,枚举出所有的特征;
  • 对于每一个特征,根据该特征将所有的实例进行排序;
  • 使用线性扫描的方法计算该特征的每个可能的值作为分裂点时对应的 $Gain$对所有特征,使用上述扫描过程中找到的 $Gain$ 值大时特征及其对应的取值作为分裂点,将当前节点一分为二。

离散变量处理

传统的 GBDT 算法对连续型变量和离散型变量是进行分开处理的。例如 Spark 中的 GBDT 就是这样的,当我们的实例特征中有离散型变量的时候,就需要通过参数指定该离散型变量的种类,这样使得算法的用户友好性变得十分的糟糕。

而 XGBoost 在设计时就考虑到了这一点。实际上,我们不需要将离散型特征变量进行分开处理,XGBoost 使用 one-hot 编码的方式对离散型变量进行处理。

剪枝策略

  • 前剪枝
    • 当最优分裂点对应的增益值为负时停止分裂
    • 但是这也会存在问题,即将来的某个时刻能够获取更高的增益
  • 后剪枝
    • 将决策树增长到它的最大深度,递归的进行剪枝,剪去那些使得增益值为负值的叶子节点。

前向分步的步长

在 XGBoost 提升过程中,每产生一颗对当前残差最优化的决策树 $f_m(x)$时,并不是直接将决策树$f_m(x)$加入到模型中,而是对它乘以一个固定的算法参数 $\eta$之后才加入到模型中:

$$f_m(x)=f_{m-1}(x)+\eta f_m(x)$$

这样做的好处是防止单步决策树过拟合,以减少每棵树对最终木星的影响。

7. XGBoost构建步骤流程

输入:训练数据集$T={(x_1,y_1),(x_2,y_2),…,(x_N,y_N)},x_1 \in \chi ⊆ R^n$,$y_i \in Y ⊆ R$;
输出:梯度提升树$\hat{f}(x) = \hat f_M(x)$;

(1)初始化$f_0(x)=0$

(2)对$m=1,2,…,M$,依次进行循环迭代:

(a)对每个样本点,分别计算:
$$g_i= \partial _{f_{m-1}(x_i)}L(y_i,f_{m-1}(x_i))$$
$$h_i= \partial ^2_{f_{m-1}(x_i)}L(y_i,f_{m-1}(x_i))$$
(b)使用贪心策略构建一棵树$f_m(x)$,以使得下列的目标函数最小化:
$$Obj=-\frac 12 ∑^T_{j=1}\frac {G_j^2}{H_j+\lambda }+\gamma T$$
(c)更新:$f_M(x)=f_{m-1}(x)+\eta f_m(x)$
(3)得到XGBoost提升树:
$$f(x)=f_M(x)=∑^M_{m=1}\eta f_m(x)$$

8. XGBoost优缺点

优点

  1. 正则化

XGBoost 在代价函数里加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、每个叶子节点上输出的 score 的 L2 模的平方和。从 Bias-variancetradeoff 角度来讲,正则项降低了模型的 variance,使学习出来的模型更加简单,防止过拟合,这也是 XGBoost 优于传统 GBDT 的一个特性。

  1. 并行处理

XGBoost 工具支持并行。Boosting 不是一种串行的结构吗?怎么并行的?注意 XGBoost 的并行不是 tree 粒度的并行,XGBoost 也是一次迭代完才能进行下一次迭代的(第 t 次迭代的代价函数里包含了前面 t-1 次迭代的预测值)。XGBoost 的并行是在特征粒度上的。

我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),XGBoost 在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个 block 结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。

  1. 灵活性

XGBoost 支持用户自定义目标函数和评估函数,只要目标函数二阶可导就行。

  1. 缺失值的处理

对于特征的值有缺失的样本,XGBoost 可以自动学习出它的分裂方向。

  1. 剪枝

XGBoost 先从顶到底建立所有可以建立的子树,再从底到顶反向进行剪枝。比起 GBM,这样不容易陷入局部最优解。

  1. 内置交叉验证

XGBoost 允许在每一轮 Boosting 迭代中使用交叉验证。因此,可以方便地获得最优 Boosting 迭代次数。而 GBM 使用网格搜索,只能检测有限个值。

缺点

虽然说 XGBoost 在 Kaggle 比赛中获得了不错的成绩,但并不代表 XGBoost 是一个完美的算法,它当然也有自己的缺点和不足之处:

  1. 算法参数过多

调参复杂,需要对 XGBoost 原理十分清楚才能很好的使用 XGBoost。

  1. 只适合处理结构化数据

相对于深度学习算法来说,XGBoost 算法只适合处理结构化的特征数据,而对于类似图像中的目标检测等任务重的非结构化数据没有很好的处理能力。

  1. 不适合处理超高维特征数据

XGBoost 算法对于中低维数据具有很好的处理速度和精度,但是对于例如大规模图像物体识别,或者是推荐算法的某些场景中会出现的超高维特征的数据就无能为力了,这时候我们就需要借助于深度学习等算法。

八、 总结

1. Boosting家族

提升(Boosting)是集成学习方法里的一个重要方法,其主要思想是将弱分类器组装成一个强分类器。在 PAC(概率近似正确)学习框架下,则一定可以将弱分类器组装成一个强分类器。

AdaBoost、GBDT、XGBoost都属于(加法模型、前向分步、指数损失函数)家族,都实现了通过将多个弱学习器组合成强学习器已达到提升预测准确度的目的。实现的过程略有不同且适用于不同场景。
Boosting并非是一个方法,而是一类方法。这里按照损失函数的不同,将其细分为若干类算法,下表给出了4种不同损失函数对应的Boosting方法:

2. Adaboost

AdaBoost是一种典型的分类回归树,适合解决二分类问题。多分类问题解决起来比较麻烦(参考上文)

AdaBoost是一种采用加法模型、前向分步算法、指数损失函数的提升树。可以表示为 Boosting 的前向分布算法的一个特例。 更多内容请参考

3. 回归提升树和 Adaboost

回归提升树AdaBoot不同的地方在于它使用的是平方损失函数来解决回归的问题,通过计算残差发现模型的不足,并不断拟合更新残差来构建新树。(这里残差的实现的功能与Adaboost有些类似)

4. GBDT 和回归提升树

  1. 梯度提升树可以看作是特殊的一种回归提升树。它通过计算损失函数的负梯度在当前模型的值来近似回归树中的“残差”发现模型的不足,并通过拟合该“残差”构建新树。
  2. 回归提升树初始化另$f_0(x)=0$;GBDT初始化时令$f_{ 0 }(x)=arg\min _{ c } \sum _{ i=1 }^{ N }{ L(y_i,c) } $估计是损失函数极小化的常数值。
  3. 回归提升树通过平方损失函数计算残差,GBDT通过计算负梯度作为伪残差。

5. XGBoost 和 GBDT

  • Xgboost 是 GB 算法的高效实现,xgboost 中的基学习器除了可以是CART(gbtree)也可以是线性分类器(gblinear)

  • xgboost在目标函数中显示的加上了正则化项

  • GB 中使用 Loss Function 对 f(x) 的一阶导数计算出伪残差用于学习生成$f_m$,xgboost 不仅使用到了一阶导数,还使用二阶导数

  • CART 回归树中寻找最佳分割点的衡量标准是最小化均方差,xgboost 寻找分割点的标准是最大化,lamda,gama 与正则化项相关

6. 参考文献

[1] 《统计学习方法》 ——李航 2012 清华大学出版社
[2] 《机器学习》 ——周志华 2016 清华大学出版社
[3] http://ihoge.cn/2018/adaboost.html
[4] https://www.jianshu.com/nb/7305482
[5] http://www.52caml.com/head_first_ml/ml-chapter6-boosting-family/#Gradient_Boosting
[6] https://www.jianshu.com/p/d55f7aaac4a7
[7] http://gitbook.cn/gitchat/column/5ac2f0509e924a1dc029dd84/topic/5ac9e0e5dbd50e7493d35d3f