GB、GBDT、XGboost理解

 GBDT和xgboost在竞赛和工业界使用都非常频繁,能有效的应用到分类、回归、排序问题,虽然使用起来不难,但是要能完整的理解还是有一点麻烦的。本文尝试一步一步梳理GB、GBDT、xgboost,它们之间有非常紧密的联系,GBDT是以决策树(CART)为基学习器的GB算法,xgboost扩展和改进了GDBT,xgboost算法更快,准确率也相对高一些。    

1. Gradient boosting(GB)

      机器学习中的学习算法的目标是为了优化或者说最小化loss Function, Gradient boosting的思想是迭代生多个(M个)弱的模型,然后将每个弱模型的预测结果相加,后面的模型Fm+1(x)基于前面学习模型的Fm(x)的效果生成的,关系如下:

1 \le m \le M     F_{m+1}(x) = F_m(x) + h(x)

      GB算法的思想很简单,关键是怎么生成h(x)?

      如果目标函数是回归问题的均方误差,很容易想到最理想的h(x)应该是能够完全拟合y - F_m(x) ,这就是常说基于残差的学习。残差学习在回归问题中可以很好的使用,但是为了一般性(分类,排序问题),实际中往往是基于loss Function 在函数空间的的负梯度学习,对于回归问题\frac{1}{2}(y - F(x))^2残差和负梯度也是相同的。L(y, f)中的f,不要理解为传统意义上的函数,而是一个函数向量\! f(x_1), \ldots, f(x_n),向量中元素的个数与训练样本的个数相同,因此基于Loss Function函数空间的负梯度的学习也称为“伪残差”。

GB算法的步骤:

  1.初始化模型为常数值:

    F_0(x) = \underset{\gamma}{\arg\min} \sum_{i=1}^n L(y_i, \gamma).

  2.迭代生成M个基学习器

    1.计算伪残差

      r_{im} = -\left[\frac{\partial L(y_i, F(x_i))}{\partial F(x_i)}\right]_{F(x)=F_{m-1}(x)} \quad \mbox{for } i=1,\ldots,n.

    2.基于\{(x_i, r_{im})\}_{i=1}^n生成基学习器\! h_m(x)

    3.计算最优的\! \gamma_m

      \gamma_m = \underset{\gamma}{\operatorname{arg\,min}} \sum_{i=1}^n L\left(y_i, F_{m-1}(x_i) + \gamma h_m(x_i)\right).

        4.更新模型

      F_m(x) = F_{m-1}(x) + \gamma_m h_m(x).

 2. Gradient boosting Decision Tree(GBDT)

  GB算法中最典型的基学习器是决策树,尤其是CART,正如名字的含义,GBDT是GB和DT的结合。要注意的是这里的决策树是回归树,GBDT中的决策树是个弱模型,深度较小一般不会超过5,叶子节点的数量也不会超过10,对于生成的每棵决策树乘上比较小的缩减系数(学习率<0.1),有些GBDT的实现加入了随机抽样(subsample 0.5<=f <=0.8)提高模型的泛化能力。通过交叉验证的方法选择最优的参数。因此GBDT实际的核心问题变成怎么基于\{(x_i, r_{im})\}_{i=1}^n使用CART回归树生成\! h_m(x)

  CART分类树在很多书籍和资料中介绍比较多,但是再次强调GDBT中使用的是回归树。作为对比,先说分类树,我们知道CART是二叉树,CART分类树在每次分枝时,是穷举每一个feature的每一个阈值,根据GINI系数找到使不纯性降低最大的的feature以及其阀值,然后按照feature<=阈值,和feature>阈值分成的两个分枝,每个分支包含符合分支条件的样本。用同样方法继续分枝直到该分支下的所有样本都属于统一类别,或达到预设的终止条件,若最终叶子节点中的类别不唯一,则以多数人的类别作为该叶子节点的性别。回归树总体流程也是类似,不过在每个节点(不一定是叶子节点)都会得一个预测值,以年龄为例,该预测值等于属于这个节点的所有人年龄的平均值。分枝时穷举每一个feature的每个阈值找最好的分割点,但衡量最好的标准不再是GINI系数,而是最小化均方差--即(每个人的年龄-预测年龄)^2 的总和 / N,或者说是每个人的预测误差平方和 除以 N。这很好理解,被预测出错的人数越多,错的越离谱,均方差就越大,通过最小化均方差能够找到最靠谱的分枝依据。分枝直到每个叶子节点上人的年龄都唯一(这太难了)或者达到预设的终止条件(如叶子个数上限),若最终叶子节点上人的年龄不唯一,则以该节点上所有人的平均年龄做为该叶子节点的预测年龄。

3. Xgboost

  Xgboost是GB算法的高效实现,xgboost中的基学习器除了可以是CART(gbtree)也可以是线性分类器(gblinear)。下面所有的内容来自原始paper,包括公式。

  (1). xgboost在目标函数中显示的加上了正则化项,基学习为CART时,正则化项与树的叶子节点的数量T和叶子节点的值有关。

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

    第t次的loss:

    对上式做二阶泰勒展开:g为一阶导数,h为二阶导数

  (3). 上面提到CART回归树中寻找最佳分割点的衡量标准是最小化均方差,xgboost寻找分割点的标准是最大化,lamda,gama与正则化项相关

   xgboost算法的步骤和GB基本相同,都是首先初始化为一个常数,gb是根据一阶导数ri,xgboost是根据一阶导数gi和二阶导数hi,迭代生成基学习器,相加更新学习器。

xgboost与gdbt除了上述三点的不同,xgboost在实现时还做了许多优化

  • 在寻找最佳分割点时,考虑传统的枚举每个特征的所有可能分割点的贪心法效率太低,xgboost实现了一种近似的算法。大致的思想是根据百分位法列举几个可能成为分割点的候选者,然后从候选者中根据上面求分割点的公式计算找出最佳的分割点。
  • xgboost考虑了训练数据为稀疏值的情况,可以为缺失值或者指定的值指定分支的默认方向,这能大大提升算法的效率,paper提到50倍。
  • 特征列排序后以块的形式存储在内存中,在迭代中可以重复使用;虽然boosting算法迭代必须串行,但是在处理每个特征列时可以做到并行。
  • 按照特征列方式存储能优化寻找最佳的分割点,但是当以行计算梯度数据时会导致内存的不连续访问,严重时会导致cache miss,降低算法效率。paper中提到,可先将数据收集到线程内部的buffer,然后再计算,提高算法的效率。
  • xgboost 还考虑了当数据量比较大,内存不够时怎么有效的使用磁盘,主要是结合多线程、数据压缩、分片的方法,尽可能的提高算法的效率。

   参考资料:

  1. 维基百科 Gradient boosting

  2. XGBoost: A Scalable Tree Boosting System

  3. 陈天奇的slides

  4. xgboost导读和实战.pdf

原文来自https://www.cnblogs.com/wxquare/p/5541414.html

 

  Boosting方法实际上是采用加法模型与前向分布算法。在上一篇提到的Adaboost算法也可以用加法模型和前向分布算法来表示。以决策树为基学习器的提升方法称为提升树(Boosting Tree)。对分类问题决策树是CART分类树,对回归问题决策树是CART回归树。

1、前向分布算法

  引入加法模型

  

  在给定了训练数据和损失函数L(y, f(x)) 的条件下,可以通过损失函数最小化来学习加法模型

  

  然而对于这个问题是个很复杂的优化问题,而且要训练的参数非常的多,前向分布算法的提出就是为了解决模型的优化问题,其核心思想是因为加法模型是由多各模型相加在一起的,而且在Boosting中模型之间又是有先后顺序的,因此可以在执行每一步加法的时候对模型进行优化,那么每一步只需要学习一个模型和一个参数,通过这种方式来逐步逼近全局最优,每一步优化的损失函数:

  

  具体算法流程如下:

  1)初始化f0(x) = 0;

  2)第m次迭代时,极小化损失函数

  

  3)更新模型,则fm(x):

  

  4)得到最终的加法模型

  

  Adaboost算法也可以用前向分布算法来描述,在这里输入的数据集是带有权重分布的数据集,损失函数是指数损失函数。

2、GBDT算法

  GBDT是梯度提升决策树(Gradient Boosting Decision Tree)的简称,GBDT可以说是最好的机器学习算法之一。GBDT分类和回归时的基学习器都是CART回归树,因为是拟合残差的。GBDT和Adaboost一样可以用前向分布算法来描述,不同之处在于Adaboost算法每次拟合基学习器时,输入的样本数据是不一样的(每一轮迭代时的样本权重不一致),因为Adaboost旨在重点关注上一轮分类错误的样本,GBDT算法在每一步迭代时是输出的值不一样,本轮要拟合的输出值是之前的加法模型的预测值和真实值的差值(模型的残差,也称为损失)。用于一个简单的例子来说明GBDT,假如某人的年龄为30岁,第一次用20岁去拟合,发现损失还有10岁,第二次用6岁去拟合10岁,发现损失还有4岁,第三次用3岁去拟合4岁,依次下去直到损失在我们可接受范围内。

  以平方误差损失函数的回归问题为例,来看看以损失来拟合是个什么样子,采用前向分布算法:

  

  在第m次迭代时,我们要优化的损失函数:

  

  此时我们采用平方误差损失函数为例:

  

  则上面损失函数变为:

  

  问题就成了对残差r的拟合了

  然而对于大多数损失函数,却没那么容易直接获得模型的残差,针对该问题,大神Freidman提出了用损失函数的负梯度来拟合本轮损失的近似值,拟合一个回归树

  

  关于GBDT一般损失函数的具体算法流程如下:

  1)初始化f0(x):

  

  2)第m次迭代时,计算当前要拟合的残差rmi

   

  以rmi为输出值,对rmi拟合一个回归树(此时只是确定了树的结构,但是还未确定叶子节点中的输出值),然后通过最小化当前的损失函数,并求得每个叶子节点中的输出值cmj,j表示第j个叶子节点

  

  更新当前的模型fm(x)为:

  

  3)依次迭代到我们设定的基学习器的个数M,得到最终的模型,其中M表示基学习器的个数,J表示叶子节点的个数

  

  GBDT算法提供了众多的可选择的损失函数,通过选择不同的损失函数可以用来处理分类、回归问题,比如用对数似然损失函数就可以处理分类问题。大概的总结下常用的损失函数:

  1)对于分类问题可以选用指数损失函数、对数损失函数。

  2)对于回归问题可以选用均方差损失函数、绝对损失函数。

  3)另外还有huber损失函数和分位数损失函数,也是用于回归问题,可以增加回归问题的健壮性,可以减少异常点对损失函数的影响。

3、GBDT的正则化

  在Adaboost中我们会对每个模型乘上一个弱化系数(正则化系数),减小每个模型对提升的贡献(注意:这个系数和模型的权重不一样,是在权重上又乘以一个0,1之间的小数),在GBDT中我们采用同样的策略,对于每个模型乘以一个系数λ (0 < λ ≤ 1),降低每个模型对拟合损失的贡献,这种方法也意味着我们需要更多的基学习器。

  第二种是每次通过按比例(推荐[0.5, 0.8] 之间)随机抽取部分样本来训练模型,这种方法有点类似Bagging,可以减小方差,但同样会增加模型的偏差,可采用交叉验证选取,这种方式称为子采样。采用子采样的GBDT有时也称为随机梯度提升树(SGBT)。

  第三种就是控制基学习器CART树的复杂度,可以采用剪枝正则化。

4、GBDT的优缺点

  GBDT的主要优点:

  1)可以灵活的处理各种类型的数据

  2)预测的准确率高

  3)使用了一些健壮的损失函数,如huber,可以很好的处理异常值

  GBDT的缺点:

  1)由于基学习器之间的依赖关系,难以并行化处理,不过可以通过子采样的SGBT来诗选部分并行。

5、XGBoost算法

  事实上对于树模型为基学习器的集成方法在建模过程中可以分为两个步骤:一是确定树模型的结构,二是确定树模型的叶子结点中的输出值。

5.1 定义树的复杂度

  首先把树拆分成结构部分q和叶子节点权重部分w,在这里w是一个向量,表示各叶子节点中的输出值。在这里就囊括了上面提到的两点,确定树结构q和叶子结点的输出值w。

  

  具体表现示例如下,引入正则化项 Ω(ft) 来控制树的复杂度,从而实现有效的控制模型的过拟合,这是xgboost中的第一个重要点。

  

5.2 XGBoost中的Boosting Tree模型

  

  和GBDT方法一样,XGBoost的提升模型也是采用残差,不同的是分裂结点选取的时候不一定是最小平方损失,其损失函数如下,较GBDT其根据树模型的复杂度加入了一项正则化项:

  

5.3 对目标函数进行改写

  

  在GBDT中我们通过求损失函数的负梯度(一阶导数),利用负梯度替代残差来拟合树模型。在XGBoost中直接用泰勒展开式将损失函数展开成二项式函数(前提是损失函数一阶、二阶;连续可导,而且在这里计算一阶导和二阶导时可以并行计算),假设此时我们定义好了树的结构(在后面介绍,和GBDT中直接用残差拟合不同),假设我们的叶节点区域为:

  

  则我们的目标优化函数可以转换成:

  

  此时我们对wj求导并令导数为0,可得:

    

5.4 树结构的打分函数

  上面的Obj值代表当指定一个树结构时,在目标上面最多减少多少,我们可以把它称为结构分数。可以认为这是一个类似与基尼指数一样更一般的对树结构进行打分的函数。如下面的例子所示

  

  对于求得Obj分数最小的树结构,我们可以枚举所有的可能性,然后对比结构分数来获得最优的树结构,然而这种方法计算消耗太大,更常用的是贪心法(事实上绝大多数树模型都是这样的,只考虑当前节点的划分最优),每次尝试对已经存在的叶节点(最开始的叶节点是根节点)进行分割,然后获得分割后的增益为:

  

  在这里以Gain作为判断是否分割的条件,如果Gain < 0,则此叶节点不做分割,然而这样对于每次分割还是需要列出所有的分割方案(对于特征的值的个数为n时,总共有2^n - 2 种划分)。而实际中是采用近似贪心方法,我们先将所有样本按照gi从小到大排序,然后进行遍历,查看每个节点是否需要分割(对于特征的值的个数为n时,总共有n-1种划分),具体示例如下:

  

  最简单的树结构就是一个节点的树。我们可以算出这棵单节点的树的好坏程度obj*。假设我们现在想按照年龄将这棵单节点树进行分叉,我们需要知道:

  1)按照年龄分是否有效,也就是是否减少了obj的值

  2)如果可分,那么以哪个年龄值来分。

  此时我们就是先将年龄特征从小到大排好序,然后再从左到右遍历分割

  这样的分割方式,我们就只要对样本扫描一遍,就可以分割出GL,GR,然后根据Gain的分数进行分割,极大地节省了时间

5.5 XGBoost中其他的正则化方法

  1)像Adaboost和GBDT中一样,对每一个模型乘以一个系数λ(0 < λ ≤ 1),用来降低每个模型对结果的贡献

  2)采用特征子采样方法,和RandomForest中的特征子采样一样,可以降低模型的方差

6、XGBoost和GBDT的区别

   1)将树模型的复杂度加入到正则项中,来避免过拟合,因此泛化性能会由于GBDT

   2)损失函数是用泰勒展开式展开的,同时用到了一阶导和二阶导,可以加快优化速度

  3)和GBDT只支持CART作为基分类器之外,还支持线性分类器,在使用线性分类器的时候可以使用L1,L2正则化

  4)引进了特征子采样,像RandomForest那样,这种方法既能降低过拟合,还能减少计算

  5)在寻找最佳分割点时,考虑到传统的贪心算法效率较低,实现了一种近似贪心算法,用来加速和减小内存消耗,除此之外还考虑了稀疏数据集和缺失值的处理,对于特征的值有缺失的样本,XGBoost依然能自动找到其要分裂的方向

  6)XGBoost支持并行处理,XGBoost的并行不是在模型上的并行,而是在特征上的并行,将特征列排序后以block的形式存储在内存中,在后面的迭代中重复使用这个结构。这个block也使得并行化成为了可能,其次在进行节点分裂时,计算每个特征的增益,最终选择增益最大的那个特征去做分割,那么各个特征的增益计算就可以开多线程进行。

原文来自:https://www.cnblogs.com/jiangxinyang/p/9248154.html

猜你喜欢

转载自www.cnblogs.com/USTC-ZCC/p/10005490.html