xgboost基本原理

https://juejin.im/post/5a13c9a8f265da43333e0648

GBDT(Gradient Boosting Decision Tree)基本原理

GBDT相对于经典的决策树,算是一种比较成熟而且可以实际应用的决策树算法了。
首先我们要了解到,DBDT是一种回归树
分类树在每一次分支的时候,穷举每一个特征的每一个阈值,然后按照大于或者小于阈值的方式将其相互分开。这就是分类树的算法。
回归树也十分类似,但是在每个节点都会得到一个预测值。

以年龄为例,该预测值等于属于这个节点的所有人年龄的平均值。分枝时穷举每一个feature的每个阈值找最好的分割点,但衡量最好的标准不再是最大熵,而是最小化均方差--即(每个人的年龄-预测年龄)^2 的总和 / N,或者说是每个人的预测误差平方和 除以 N。这很好理解,被预测出错的人数越多,错的越离谱,均方差就越大,通过最小化均方差能够找到最靠谱的分枝依据。分枝直到每个叶子节点上人的年龄都唯一(这太难了)或者达到预设的终止条件(如叶子个数上限),若最终叶子节点上人的年龄不唯一,则以该节点上所有人的平均年龄做为该叶子节点的预测年龄。

BDT(Boosting Decision Tree 提升决策树)

这其实是一个很好的思维方式。
Boosting,提升。这涉及到机器学习中的集成学习。原理就是通过多个特征生成多个树,来决策一件事情。也就是“三个臭皮匠,顶一个诸葛亮”的原理。这是如何实现的呢?我们举一个简单的例子。BDT的核心就在于,每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。比如A的真实年龄是18岁,我们训练的时候,第一棵树的拟合过后预测年龄是12岁,我们与真实数据比对差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去拟合学习,如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄;如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学。
如果还有疑问,请看下面这个例子:
还是年龄预测,简单起见训练集只有4个人,A,B,C,D,他们的年龄分别是14,16,24,26。其中A、B分别是高一和高三学生;C,D分别是应届毕业生和工作两年的员工。如果是用一棵传统的回归决策树来训练,会得到如下图1所示结果:

传统回归树模型(图片来自网络)传统回归树模型(图片来自网络)

这是一棵普通的回归树,我们可以看到,我们通过年龄平均值将少年和青年分开
,再用上网时长将每个分支继续细分到不能分割或者达到要求为止。
接下来看BDT实现:

BDT模型(图片来自网络)BDT模型(图片来自网络)

在第一棵树分枝和图1一样,由于A,B年龄较为相近,C,D年龄较为相近,他们被分为两拨,每拨用平均年龄作为预测值。此时计算残差(残差的意思就是: A的预测值 + A的残差 = A的实际值),所以A的残差就是16-15=1(注意,A的预测值是指前面所有树累加的和,这里前面只有一棵树所以直接是15,如果还有树则需要都累加起来作为A的预测值)。进而得到A,B,C,D的残差分别为-1,1,-1,1。然后我们拿残差替代A,B,C,D的原值,到第二棵树去学习,如果我们的预测值和它们的残差相等,则只需把第二棵树的结论累加到第一棵树上就能得到真实年龄了。这里的数据显然是我可以做的,第二棵树只有两个值1和-1,直接分成两个节点。此时所有人的残差都是0,即每个人都得到了真实的预测值。

换句话说,现在A,B,C,D的预测值都和真实年龄一致了:

  1. 14岁高一学生,购物较少,经常问学长问题;预测年龄A = 15 – 1 = 14
  2. 16岁高三学生;购物较少,经常被学弟问问题;预测年龄B = 15 + 1 = 16
  3. 24岁应届毕业生;购物较多,经常问师兄问题;预测年龄C = 25 – 1 = 24
  4. 26岁工作两年员工;购物较多,经常被师弟问问题;预测年龄D = 25 + 1 = 26

GBDT核心思想就是这样,但是既然普通的树和GBDT结果一样,那为什么还需要GBDT呢?
原因就是过拟合。过拟合就是模型在训练数据集上表现的过于好,分的过于细。以致于容错能力很低,也可以称作”泛化能力“低。这就会导致在实际测试数据中表现明显差很多。我们发现图1为了达到100%精度使用了3个feature(上网时长、时段、网购金额),其中分枝“上网时长>1.1h” 很显然已经过拟合了,这个数据集上A,B也许恰好A每天上网1.09h, B上网1.05小时,但用上网时间是不是>1.1小时来判断所有人的年龄很显然是有悖常识的;
相对来说图2的boosting虽然用了两棵树 ,但其实只用了2个feature就搞定了,后一个feature是问答比例,显然图2的依据更靠谱(这是杜撰的数据,为了显示效果夸张的一下,实际有实验证明以上论点)。

GB(Gradient Boosting 梯度提升)

我们前面一直都在讨论GBDT的基本原理,是从宏观上对于这种算法有个感性的认识。但是我们要想进一步理解GBDT,就得知道负梯度拟合,或者我们也叫梯度提升。
我们都知道,在机器学习算法中,为了降低误差函数,我们会有一个方法叫做梯度下降法。其做法是误差函数中,每一步都走当前的梯度方向,也就是下降最快的方向进行下降,这样多次迭代步长之后,误差函数就会降到一个局部最低点(凸函数中也是全局最低点),也就得到了最小的误差函数。
我们尝试使用这种方法去训练一棵回归树,或者更精确的说,是一棵CART,如有朋友不太熟悉CART,请看我之前的文章《CART》。正如前面所提到,我们GBDT每次训练的时候都是拟合上一棵树的残差,那么我们使用损失函数来拟合这棵树:

损失函数拟合一棵决策树损失函数拟合一棵决策树

使用损失函数的负梯度来拟合它的残差:

这样将所有的负梯度加起来之后就会得到一个整体的梯度下降。使得整个系统的误差函数最小。
我贴出梯度提升算法以辅助理解,内容来自李航的《统计学习方法》:


梯度提升算法过程梯度提升算法过程

我们来看看这梯度提升算法的流程:

  1. 我们先初始化第一棵回归树,使这个分界点让整体误差最小。具体细节上为什么初始化成这种形式,请看我之前写过的文章《CART》;
  2. 我们每生成一棵树之后,就将这棵树的每一条数据的损失函数的梯度求出来;损失函数我们一般写作是均方误差,每个数据与切分点之间的均方差就是损失函数;
  3. 求出每个数据的负梯度之后,我们依据已有数据和每个数据的负梯度,生成一个新树出来,我们先将每个数据的负梯度当作新的数据的yi,这样就得到了一组新数据,也确定了新数据的空间划分。然后再计算每一条数据的误差函数,取误差函数最小的那个点做为下一个分支切分点,这也就生成了一颗新树;
  4. 我们将新树加到原来那棵树上;
  5. 最后总和得到一棵树;

如果不够理解,请将这个负梯度拟合的残差直接带入之前文章《CART》例子中的残差数据中去,这样就有一个例子可供参考辅助理解。

XGBoost基本原理

XGBoost的实现,我觉得主要还是在于对GBDT的改良上。

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

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

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

XGBoost加法模型XGBoost加法模型

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

目标函数目标函数

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

加法学习器的Boosting

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

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

目标函数变式

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

平方误差衡量损失函数

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

泰勒公式基本形式

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

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

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

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

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

去掉常数项的目标函数

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

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的增益

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页