gbdt 回归 特征重要性 排序_集成方法(二):随机森林与GBDT

gbdt 回归 特征重要性 排序_集成方法(二):随机森林与GBDT_第1张图片

在上一篇文章中,我们从整体上介绍了集成方法中Bagging、Boosting和Stacking这三种方式的主要思想,这里我们将介绍其中代表性的算法实例:随机森林与以XGBoost/LightGBM为代表的GBDT。

随机森林

这又是一个名字起得非常好的算法:随机森林,森林是树的聚集,随机是Bagging思想的关键。结合我们在集成方法(一):综述中介绍的Bagging方法,以及在非线性分类模型(二):决策树中介绍的决策树方法,可以发现它们的确很般配:决策树方法不稳定,容易过拟合;Bagging方法通过构造相互独立同分布的数据子集,学习出不相关的多个模型进而综合使用来降低方差。可以想象,这两者的结合将会产生很好的效果,随机森林就是这样一种算法,此外,它不仅是简单原生决策树和Bagging方法的结合,在这之上它更进了一步:

  1. 随机森林中的决策树完全生长而不进行剪枝。在讨论决策树算法时我们介绍过对完全生长形成的决策树进行后剪枝是减少决策树过拟合的重要方法,那么为什么在随机森林中就不需要了呢?要从两个层面来看,一是随机森林的“随机”以及“集成森林”的方法本身就是为了防止过拟合,那么额外的过拟合是不是重复了呢,又或者是不是防止过拟合做得越多越好呢?这就要从第二点来看了,随机森林之所以把防止过拟合的任务放到“随机”和“集成森林”上而非单棵树上,是因为后者赋予了新的任务:那就是期望每一棵决策树能够非常好地拟合分配给它的特征和数据,也就是说准确地拟合某个子场景,然后通过聚合覆盖整体场景,如果这一点做不到,那么方差的确是降下来了,但是偏差却肯定上去了,因此可以理解为了保证泛化性能,随机森林让每棵决策树完全生长而不剪枝来保证偏差,通过“随机”和”集成森林“来保证方差。
  2. 随机森林的随机不仅体现在数据的随机抽样,还体现在决策树构造过程中特征选择候选集的随机抽样。回顾我们介绍的Bagging方法,严格来说是一种数据的有放回抽样,在抽样形成的数据子集上构造决策树时,随机森林在每次特征选择和划分的时候,对原始特征集进行无放回的抽样形成一个特征子集,然后在这个子集上计算最佳的划分方式。进行特征抽样的原因是为了进一步减小森林中不同决策树之间的相关性,比如某些特征跟目标值非常强相关,那么每棵决策树在构造过程中都倾向于选择这样的特征,使得树之间很相似,相关性很大,这就违背了我们Bagging集成方法的初心和实现良好效果的基础。

综合来看,随机森林的构建过程为:用N来表示训练集中训练样本的个数,M表示特征个数,R表示预定义的森林中树的个数,按照如下的过程建立每一棵树:

  1. 从N个训练样本的训练集中有放回地抽样N次,形成一个训练子集,对于未被抽中的样本称为这个训练子集或者对应树的袋外数据(OOB, out-of-bag);
  2. 在决策树结点分裂时,随机选择m个特征,其中
    (一般地,对于分类问题
    ,对于回归问题,
    ), 从这m个特征中计算最佳特征和分裂方式(也有方法在这里并没有计算最佳分裂方式,而是进一步引入随机性,如抽样选择有限的分裂方式作为候选集);
  3. 完全生长,直至无法进行进一步的分裂;

随机森林有一个很好的优点就是没有必要保留一个额外的测试集或者进行交叉验证来估计它的泛化误差,而是可以在生成的过程中内部评估,使用out-of-bag误差来代替泛化误差,具体的做法是:对于每一个样本,可能有很多棵树在构造时的训练子集不包含它,即该样本是其训练子集的袋外数据,计算这些以它为袋外数据的树的输出然后进行多数投票或平均作为该样本的输出,遍历所有样本得到整体out-of-bag误差,可以认为这就是其泛化误差的无偏估计,如分类问题中将所有误分类样本的个数占总样本的比例作为随机森林的out-of-bag误分率。

不仅如此,随机森林还可以排序特征的重要性。回顾我们之前所说,我们已经通过降维、方差、信息增益增等不同的角度和方法来度量过一个特征对于目标值的区分度,也就是它的重要性,现在我们来看随机森林是如何刻画特征的重要性的呢?具体来说通过比较两个随机森林的out-of-bag误差来实现的:一个是正常的原始误差,另一个是将该特征的值随机打乱(如设随机值)之后的out-of-bag误差,可以容易地理解,后者与前者的差值越高说明这个特征越重要;还有一种方法则是通过计算森林中涉及该特征分裂时统计指标变化的量。

随机森林的重要性和广泛应用源于它的很多点,如前面所说它可以通过out-of-bag误差近似地估计泛化误差,可以排列和比较特征的重要性;此外,随机森林通过对数据和特征随机性的引入,不仅使得其不容易陷入过拟合,而且提高了本身的抗噪声能力,对于高维数据也可以相对较好地处理;并且随机森林继承了决策树和Bagging方法的优点,实现简单,可解释性强,容易并行化,计算效率高。正是由于这些优点使得随机森林在一般场景中应用得非常广泛,也取得了很好地效果。

GBDT

梯度提升决策树(Gradient Boosting Decision Tree),也有别名Gradient Boosting Machine以及Gradient Boosting Regression Tree,鉴于它的可解释性强,计算效率高和泛化性能好的优点,可能是应用最广泛的统计机器学习方法了,下面我们就来看看它的工作原理。回顾我们在集成方法(一):综述中介绍的Gradient Boosting思想,它通过顺序地沿着之前损失函数梯度下降方向构建模型,使得组合加法模型的整体损失函数值下降,也就是整体模型的性能提升,GBDT就是一种以决策树为基类模型,并应用Gradient Boosting得到的集成方法。这里值得注意的是,GBDT的的决策树一般是二叉树,而且通常是回归树(CART),结合以前我们介绍过的内容可以知道回归模型不仅可以应用于解决回归问题,同样可以解决分类问题、排序问题等,这一切只取决于我们如何定义输出和损失函数。

打开GBDT的正确姿势应该是沿着它的历史发展,Boosting的目的是通过顺序地构建模型使得组合的加法模型的损失函数下降,方法一是改变样本的权重系数,二是改变样本的目标值。Gradient Boosting中所谓的Gradient又是怎么来的呢?其实它的前身提升树(Boosting Tree),一开始的思路是最小化损失函数值,发现在回归问题中最小平方误差下可以导出负梯度就是残差,即真实值减去当前组合加法模型的值。如下所示,当构建当前树时考虑的是整体损失函数值最小:

当采用平方误差损失函数

时,上式中损失变为:

欲使其最小,有:

后者即为截止到前一个弱模型为止组合加法模型对比样本真实目标值的残差。这个发现使得回归提升树的计算和理解都变得很简单:通过拟合残差来构建新树。遗憾的是,这只是针对平方误差损失下的回归树而言的,其它损失函数呢? 后来有人提出了参照最速下降法来使用负梯度来近似残差,这就是梯度提升的由来。

GBDT之所以应用广泛,不仅在于理论上,还在于有很多优秀的工程实现,做出了很多工程上的创新,进一步提高了GBDT的性能,其中最重要的代表莫过于XGBoost和LightGBM了。

XGBoost

XGBoost作为GBDT的一种系统性实现,它做出了很多工程上的创新,我们可以从避免过拟合尝试、函数优化和计算效率这几个角度来一窥究竟:

  1. 避免过拟合
    1. Regularizaion,XGBoost在目标函数中增加了正则项,如决策树剪枝时的介绍类似,使用树的复杂度作为正则化项,相当于在训练时进行了剪枝。我们提到过衡量树的复杂度的方法有很多,比如树的深度、内部结点个数、叶子结点个数T,回归树叶子结点的分数w等,具体地,XGBoost采用的正则化项是:
    2. Subsampling,XGBoost参照随机森林在构建每棵树时所能看到的数据是抽样样本数据形成训练子集,区别是这里进行的是无放回的;同样地,在进行叶子结点分裂时,能看到的特征也是抽样特征集合形成特征子集。
    3. Shrinkage,XGBoost在原有加法模型的基础上给每一个新模型都加上了一个系数(相当于叶子结点的权重/输出值乘上系数),一般是0.1,可以参照优化过程中的学习速率,将学习速率调小,缩小每棵树的影响转而增加迭代次数和更多的树来减少过拟合。
  2. 函数优化。首先我们来看XGBoost的目标函数,假设有K棵树,则组合加法模型定义为:
    目标函数定义为误差函数与正则项的和:
    在顺序的构造过程中,在考虑第t轮时,目标函数为:
    1. 区别于GBDT中用负梯度来近似残差,XGBoost通过二阶泰勒展开来近似上述的目标函数,即将目标函数改写为
      其中
      ,比如我们以平方损失函数为例,那么原始损失函数可以等价地改写为:
      其中
      。继续地我们来看如果我们将在叶子结点j的示例集合定义为
      ,则二阶泰勒展开近似后的
      目标函数可以继续改写为
      因此,如果树的结构
      是定了的,那么最优的参数,也就是回归树叶子结点的输出值也就可以极值成立条件求得:
      进而此时的
      目标函数值为
    2. XGBoost的分裂不同于之前决策树中介绍的信息增益或者基尼指数,而是通过度量分裂前后目标函数的得差来选择哪一个特征的哪一种分裂方式能够使得目标函数值下降得最快,这同样是一种启发式的方法,具体地,在考察每个叶子结点是否分裂以及如何分裂时,定义增益为
      其中第一项为分裂后左孩子结点的目标函数值,第二项为分裂后右孩子结点的目标函数值,第三项为未分裂前当前的结点的目标函数值,注意这里将其它叶子结点的对应项视为不变量,因此只考察当前结点的样本集合即可。可以设置增益的阈值,只有当增益大于阈值才进行分裂,相当于预剪枝。

3. 计算效率,XGBoost作为优秀的工程实现,具体体现在:

  1. 同时支持比那里所有可能特征值的精确贪心算法和以二阶导数为权重的特定分位数作为候选集的近似算法,相对于前者,后者减小了计算复杂度。对于近似算法,这里有两个问题,一是如何选择分位点?二是为什么用二阶导数为权重。前者有两种不同的策略,一种是在一开始就为整棵树的所有特征的特征值进行预先选择,然后每次分裂的时候照用就好,称为全局策略,另一种是每次分裂的时候针对当前结点样本的特征值进行选择,称为本地策略。一般来如果采用本地策略可以选择更少的分位数候选集,相当于全局策略时更多的分位数候选集。第二个问题,使用二阶导数为权重则是因为目标函数可以整理为
    的形式,可以看出这里等价地为样本增加
    的权重来进行选择是合理的。
  2. 对于数据缺失、One-Hot编码或者应用数据本身特点带来的数据稀疏问题,XGBoost会对这些缺失值进行相对更佳的默认处理,在每个叶子结点分裂时,它通过对比将缺失值实例都放到左孩子结点或右孩子结点计算出来的Gain,选择将其Gain最大的划分方向(都放到左或放到右)。为了支持这一特性,它额外引入了同非缺失值样本数量呈线性关系的计算代价。
  3. 分块并行,XGBoost最耗时的部分是寻找最优划分点,实现中通过Block结构存储数据,每个Block中对特征值进行预先排序,并建立每个特征值到样本的索引,每个Block存储一个或多个特征的值,使得只需要在建树前排序一次即可。原作者通过试验建议
    的Block大小。
  4. 缓存优化,Block结构带来的一个问题是,每次通过特征值顺序取样本时,特征值索引虽然在一起,但对应的样本很有可能是分散的,这样会造成不连续的访存,降低CPU Cache的命中率,这对于加权分位数的近似方法没有太多影响,对精确贪心算法却影响很大。对于后者,优化的方法是为每一个线程分配一段缓存区,预先读取数据并计算将要用到的梯度信息,放置到缓存区中备用;
  5. Out-of-core Computing,当数据量太大而无法全部加载进内存时,XGBoost采用来各种策略来进行这种大数据量训练:一是进行Block压缩,读取的时候通过另外的线程来解压缩;二是进行Block拆分,将数据划分到不同磁盘,每个磁盘有一个单独的线程来交替地数据读入缓冲区供训练线程使用。

我们这里所介绍的更多是其主要的创新点,它的更多实现细节还需要进一步地在其论文与源码中寻找了。

LightGBM

LightGBM是微软在2017年提出了一个新的GBDT的系统性实现,在XGBoost一统GBDT江湖的情形下它还能够脱颖而出,证明了它的优秀之处。其实无论从它的名字Light Gradient Boosting Machine还是从其论文题目A Highly Effcient Gradient Boosting Decision Tree都可以看出,它主打的就是轻量级和高效率,当然这是在保证准确率的基础上的。所以我们接下来就从如何“降本增效”的角度来看LightGBM的创新之处。

在了解如何做之前我们首先要进行的还是“需求分析”,也就是哪些地方还有很大的提升空间,接下来再看怎么做。LightGBM并没有脱离GBDT的理论范围,因此我们这里的模型能力更多是从工程角度考虑,而非其建模和分析的能力,那么一般来说应用场景对模型工程能力提出挑战的地方有那些呢?一是数据量,数据太少你做不好我不怪你,数据太多的话你能不能撑住呢?二是特征,特征太少你做不好我也不怪你,可能是特征工程没做好,那么特征太多或者太稀疏你能不能很好地支持呢?总的来说,如何处理好大数据量和高维稀疏特征是工程实现上的重要问题,它会很大地影响计算效率,我们具体来看LightGBM是如何在这些地方进行创新的。

GOSS(Gradient-based One-Side Sampling)

LightGBM其中一个创新算法称为基于梯度的单边采样,旨在提高大数据量下叶子结点分裂时寻找最优划分点的的效率,这也是决策树算法时间耗费最大的地方。回顾我们说的贪婪精确算法,在结点分裂时在第一个for循环会遍历所有特征,在内部的第二个for循环,它会遍历该特征所有可能的特征值,然后继续在第三个for循环它会将所有样本都按照选定的划分点特征值进行划分到左孩子结点或右孩子结点,然后基于这种划分计算增益指标。所以说有三种潜在的地方可以提高效率:第一个循环中是不是能够不遍历所有的特征?XGBoost进行的特征采样就是针对于此,当然它的出发点更多还是避免过拟合;第二个循环是不是能够不遍历所有特征值?XGBoost可选择的加权分位数近似算法也就是针对于此;第三个循环是不是能够不遍历所有样本呢?XGBoost也会进行的样本无放回采样也会影响这一循环,而GOSS也正是针对于此的一种更高效的算法。这里值得说明的是,样本量的减少也会副作用地导致对应特征值的减少,为分析简便我们将它们按照实现分开了。

GOSS是如何减少每次结点分裂时样本量呢?其实更准确地讲,它减少的是每棵树建立是考虑的样本量,也就是这棵树每次结点分裂时考虑的样本都是一样的,比原始样本集合少。LightGBM的作者们观察到GBDT在顺序地构建每棵树时,拟合的目标是当前组合加法模型在所有数据上的负梯度,而数据集中每个数据有不同的梯度,有的大有的小,这一特点对采样很有用,梯度小表示对于这部分样本实例模型已经学习得不错了,减少样本数据量最直观的方法那就是忽略这部分样本要忽略梯度大的那部分样本实例好得多,但是考虑到直接忽略会改变数据的分布,影响训练模型的准确率,GOSS就是在此之上提出的算法。GOSS保留所有梯度较大的样本实例(Top a),而在剩余的梯度较小的样本实例上(n-a)进行随机采样(b),这两部分数据(a+b)也就是构建当前树所能看到的全部样本数据量了。为了抵消对这种有偏采样对数据分布的影响,在计算增益指标的时候,GOSS对小梯度的数据引入常量乘数($frac{1-a}{b}$),这样使得算法更加关注训练不足的样本实例,而不改变原数据集的分布。我们可以看到,这里其实跟AdaBoost的思想有些类似,AdaBoost是对训练不足的样本实例增加权重,影响其在误差函数中的占比,而这里是通过类似带权重的采样。此外,可以看到当$a=0$时GOSS退化为随机采样,在多数情况下这种采样的性能优于随机采用。

EFB(Exclusive Feature Bundling)

LightGBM第二个创新算法称为互斥特征绑定,旨在缓解高维特征稀疏的问题。针对高维稀疏特征的一个好理解的思想就是把稀疏的特征合并到一起,新合并生成的特征就会更稠密起来,并且特征数量的减少有助于提高计算效率,EFB的出发点就是如此。具体来说它的创新之处在于:一是合并哪些特征,二是怎么合并特征值。针对前一个问题,EFB的作者们观察到一个数据特点,即在高维稀疏特征空间中,很多特征是互斥的,也就是它们通常不同时为非零值,EFB就是想把这样互斥的一组特征合并到一起作成一个特征。并且进一步地,EFB将寻找最优捆绑这一NP难问题转化为图着色问题来近似求解(具体可参见原论文);针对后一个问题,也就是怎么合并特征,关键在于原始特征值可以从合并后的特征中区分出来,方法简单说来就是将各个取值空间串联累加起来,原始特征各自占据其中一个片段取值范围,比如原先特征A的取值范围为[0,10],特征B的取值范围为[0, 20],那么合并二者之后特征A的取值不变,特征B的取值统一偏移10为[10, 30]。

综合来说,LightGBM通过GOSS和EFB的创新算法使得其对大数据量和高维稀疏特征很友好,并且在这种情形下计算效率和内存消耗上都明显优于XGBoost。

总结

本篇文章延续了上一篇集成方法(一):综述中的Bagging和Boosting思想,介绍了其中代表性的算法:随机森林和GBDT,其中随机森林在常规Bagging和决策树之外引入了特征随机抽样以及树完全生长,其中out-of-bag的概念对其误差度量和特征重要性排序都很大用途;以XGBoost和LightGBM为代表的GBDT的优秀实现,在工程上做出了很多优秀的创新,很好地支持了众多实际应用场景中大数据量、高维稀疏特征、复杂特征类型等问题,使得GBDT成为了统计机器学习众多方法中应用最广泛也最优秀的方法。

你可能感兴趣的:(gbdt,回归,特征重要性,排序,在envi做随机森林,随机森林原始论文)