XGBoost基本原理

8,722 阅读7分钟

XGBoost的实现,我觉得主要还是在于对GBDT的改良上。对于GBDT还是不太熟悉的朋友,请看我这一篇文章《GBDT》。我个人认为这两者区别主要还是在于细节上,理解了GBDT我认为就差不多等于理解了XGBoost。

我重点比较一下XGBoost与GBDT两种算法的不同:

XGBoost的目标函数与GBDT存在泰勒展开项的不同:

最基本的差距就在于XGBoost比GBDT多了两项泰勒展开式。具体这个泰勒展开式是怎么得到的,是对于什么展开的呢?我们看:
XGBoost算法可以看成是由K棵树组成的加法模型:

XGBoost加法模型
XGBoost加法模型

其中F为所有树组成的函数空间(这里的回归树也就是一个分段函数,不同分段的不同取值就构成了一颗树),与一般机器学习算法不同的是,加法模型不是学习d维空间的权重,而是直接学习决策树的集合。
上述加法模型的目标函数定义为:

目标函数
目标函数

其中Ω表示决策树的复杂度,那么该如何定义树的复杂度呢?比如,可以考虑树的节点数量、树的深度或者叶子节点所对应的分数的L2范数等等。
如何来学习加法模型呢?
解这一优化问题,可以用前向分布算法(forward stagewise algorithm)。有了之前GBDT的基础,我们知道,加法模型的学习器每次都用函数来拟合上一棵树没有拟合完整的残差,最后将这些残差全部加起来就会得到对于目标完整的预测,这也叫做Boosting。具体地,我们从一个常量预测开始,每次学习一个新的函数,过程如下:

加法学习器的Boosting
加法学习器的Boosting

这个公式看起来还是比较拗口,想要理解的话建议看我之前的文章《GBDT》,了解了工作模式这公式就好理解了。

这就会产生一个新的问题,那个新加入的函数f到底怎么得到的呢?这个原则还是最小化目标函数。我们可以将我们的目标函数写为:

目标函数变式
目标函数变式

我们再用平方误差来衡量我们的损失函数:

平方误差衡量损失函数
平方误差衡量损失函数

其中 就是我们所谓的残差(residual)。我们每一次使用平方函数的时候,算法都是为了拟合这个残差。
可能有的朋友对于泰勒公式不是非常熟悉,我将基本的泰勒公式用法写在这:

泰勒公式基本形式
泰勒公式基本形式

我们都知道,泰勒级数展开其实是有无穷多项的,在无穷多项形式里是严格等于,这里我们暂且只取了前三项省略了后面,所以就是约等于。
那有了泰勒公式的基础,我们将前面的目标函数变式可以转化为:

目标函数泰勒级数展开三项
目标函数泰勒级数展开三项

其中,g与h分别是损失函数的一阶偏导数和二阶偏导数,具体数学形式如下:

泰勒展开的一次微分项与二次微分项
泰勒展开的一次微分项与二次微分项

我们也可以将常数项直接去掉,并不会影响,那就使得目标函数是这个样子:

去掉常数项的目标函数
去掉常数项的目标函数

由于要学习的函数仅仅依赖于目标函数,从“去掉常数项的目标函数”可以看出只需为学习任务定义好损失函数,并为每个训练样本计算出损失函数的一阶导数和二阶导数,通过在训练样本集上最小化目标函数即可求得每步要学习的函数,从而根据加法模型可得最终要学习的模型。

GBDT的目标函数
GBDT的目标函数

就简单提一句GBDT与XGBoost的区别,明显可以看出,GBDT没有采用二次泰勒展开,这个看似很简单的区别,实际上带来更快的拟合,也大大缩减了生成树的规模,减少了运行时间。

XGBoost相比于GBDT加入了正则化项(Regularization)

我们使用损失函数优化是为了避免欠拟合,而使用正则化项就是为了避免过拟合。正则化项与损失函数共同组成了我们的目标函数。XGBoost比GBDT多添加了以树复杂度构成的正则化项,也是XGBoost实际表现更为优秀的原因之一

何为正则化项?正则化项的作用是什么?
我们都知道,我们在优化目标函数的时候,总是希望它更加的“小”,也就是优化一般是最小化的意思。现在我们如果给目标函数加入一个变量的平方,那么如果这个变量一旦变大,那么目标函数为了“最小化”,一定很不喜欢这个变量变大的事实,选择的时候就会刻意避开会使变量变大的路径。这大概就是正则化的简单解释了。在XGBoost中,我们是将树的复杂度作为正则项加入,那么优化器在工作的时候,会尽量不让这个树更加复杂,也就达到我们的效果。

我们假设XGBoost决策树的叶子节点个数为T,该决策树是由所有叶子节点对应的值组成的向量w,以及一个把特征向量映射到叶子节点索引(Index)的函数 组成的,我们将树可以写成:
,我们也可以将决策树的复杂度定义成正则项:

决策树复杂度定义的正则化项
决策树复杂度定义的正则化项

则目标函数我们可以写成:

完整正则项的目标函数
完整正则项的目标函数

用G与H代换一下原来的式子,我们就得到了简化后的式子:

简化后的目标函数
简化后的目标函数

假设树的结构是固定的,即函数q(x)为固定的,令目标函数的一阶导数为0,则可以求出叶子节点j对应的值为:

叶子节点j对应的值
叶子节点j对应的值

于是在这种条件下,目标函数的值就变成了:

目标函数的值
目标函数的值

为什么要计算这两个值呢?
是为了给大家描述单棵决策树的生成过程:

  1. 枚举所有可能的树的结构q
  2. 用目标函数值为每个q计算对应的分数Obj,分数越小说明结构越好
  3. 根据上一步结果,找到分数最小的子节点,生成新的分支,并为每个子节点计算预测值

XGBoost的分裂增益与GBDT的比较

树结构数量是无穷的,所以实际上我们不可能枚举所有可能的树结构。通常情况下,我们采用贪心策略来生成决策树的每个节点。

我们来看看这个贪心算法是怎么工作的:

  1. 从深度为0的树开始,对每个叶节点枚举所有的可用特征
  2. 针对每个特征,把属于该节点的训练样本根据该特征值升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的最大收益(采用最佳分裂点时的收益)
  3. 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,把该节点生长出左右两个新的叶节点,并为每个新节点关联对应的样本集
  4. 回到第1步,递归执行到满足特定条件为止

如何计算每次分裂的收益呢?假设当前节点记为C,分裂之后左孩子节点记为L,右孩子节点记为R,则该分裂获得的收益定义为当前节点的目标函数值减去左右两个孩子节点的目标函数值之和:Gain=ObjC-ObjL-ObjR,具体地,根据目标函数值公式可得:

XGBoost的增益
XGBoost的增益