GBDT算法的核心为:先构造一个(决策)树,然后不断在已有模型和实际样本输出的残差上再构造一颗树,依次迭代。
目录
GBDT也是集成学习Boosting家族的成员,但是却和传统的Adaboost有很大的不同。
回顾下AdaBoost(集成学习-Boosting集成学习算法AdaBoost),我们是利用前一轮迭代弱学习器的误差率来更新训练集样本的权重,这样一轮轮的迭代下去。
GBDT也是迭代,但是是基于迭代累加的决策树算法,它通过构造一组弱的学习器(树),并把多颗决策树的结果累加起来作为最终的预测输出,弱学习器限定了只能使用CART回归树模型。GBDT这个算法有很多名字,但都是同一个算法:
GBRT (Gradient Boost Regression Tree) 渐进梯度回归树
GBDT (Gradient Boost Decision Tree) 渐进梯度决策树
MART (MultipleAdditive Regression Tree) 多决策回归树
GBDT算法主要由三个部分组成:
Decistion Tree(即DT 回归树)
Gradient Boosting(即GB 梯度提升)
Shrinkag(渐变)
Decistion Tree(决策树)
提起决策树(DT,Decision Tree) 绝大部分人首先想到的就是C4.5分类决策树。但如果一开始就把GBDT中的树想成分类树,那就是错误的认识,所以说千万不要以为GBDT是很多棵分类树。
决策树分为两大类,回归树和分类树。前者用于预测实数值,如明天的温度、用户的年龄、网页的相关程度;后者用于分类标签值,如晴天/阴天/雾/雨、用户性别、网页是否是垃圾页面。这里要强调的是,前者的结果加减是有意义的,如10岁+5岁-3岁=12岁,后者则无意义,如男+男+女=到底是男是女? GBDT的核心在于累加所有树的结果作为最终结果,就像前面对年龄的累加(-3是加负3),而分类树的结果显然是没办法累加的,所以GBDT中的树都是回归树,不是分类树,这点对理解GBDT相当重要(尽管GBDT调整后也可用于分类但不代表GBDT的树是分类树)。
分类树
分类树在每次分枝时,是穷举每一个特征的每一个特征值,找到使得熵最大的特征分裂方式,按照该标准分枝得到两个新节点,用同样方法继续分枝直到所有人都被分入性别唯一的叶子节点,或达到预设的终止条件,若最终叶子节点中的类别不唯一,则以多数人的类别作为该叶子节点的类别。
回归树
回归树总体流程也是类似,不过在每个节点(不一定是叶子节点)都会得一个预测值,以年龄为例,该预测值等于属于这个节点的所有人年龄的平均值。分枝时穷举每一个feature的每个阈值找最好的分割点,但衡量最好的标准不再是最大熵,而是最小化均方差,比如(每个人的年龄-预测年龄)^2 的总和 / N,或者说是每个人的预测误差平方和除以 N。这很好理解,被预测出错的人数越多,错的越离谱,均方差就越大,通过最小化均方差能够找到最靠谱的分枝依据。分枝直到每个叶子节点上人的年龄都唯一(这太难了)或者达到预设的终止条件(如叶子个数上限),若最终叶子节点上人的年龄不唯一,则以该节点上所有人的平均年龄做为该叶子节点的预测年龄。
分类树和回归树基本流程一致都是根据feature找阈值(分裂值),但是阈值的衡量标准不同,即选择的优化函数不同。分类树选择阈值的标准是实现每个分枝都尽可能只有一个分类,即性别分类中,男性分枝上尽可能多的是男性,女性尽可能少;女性分枝上尽可能多的是女性,男性尽可能少。用数学表示即男女比例原理1:1。回归树选择阈值的标准是实现预测值和真实值的最小化均方差。
回归和分类从实现上,分别用不同的方法实现了"动态确定样本权值"这一目标。回归是用拟合残差,分类是用错误率来调整样本权值。
Gradient Boosting(梯度提升)
提升的方法基于这样一个思想:对于一个复杂任务来说,将多个专家的判断进行适当的综合所得出的判断,要比其中任何一个专家单独的判断好。这种在函数域的梯度提升观点对机器学习的很多领域有深刻影响。
提升是一个机器学习技术,可以用于回归和分类问题,它每一步产生一个弱预测模型(如决策树),并加权累加到总模型中,最终得带一个强预测模型;如果每一步的弱预测模型生成都是依据损失函数的梯度方向,则称之为梯度提升(Gradient boosting)。
梯度提升算法首先给定一个目标损失函数,它的定义域是所有可行的弱函数集合(基函数);提升算法通过迭代的选择一个负梯度方向上的基函数来逐渐逼近局部极小值。
梯度提升算法实际上和梯度下降算法是一样的,只不过看问题的角度不同,比如在线性回归中,我们通过梯度下降来优化参数 ,使损失函数能达到(局部)最小值;如果我们换个角度,我们优化的不是参数,而是这个函数,再通过沿梯度方向下降的方法达到损失函数(局部)最小值,就变成了梯度提升算法。
如下图是梯度提升算法和梯度下降算法的对比情况。可以发现,两者都是在每一轮迭代中,利用损失函数相对于模型的负梯度方向的信息来对当前模型进行更新,只不过在梯度下降中,模型是以参数化形式表示,从而模型的更新等价于参数的更新。而在梯度提升中,模型并不需要进行参数化表示,而是直接定义在函数空间中,从而大大扩展了可以使用的模型种类。
原始的Boost算法(例如Adaboost)是在算法开始的时候,为每一个样本赋上一个权重值,初始的时候,大家都是一样重要的。在每一步训练中得到的模型,会使得数据点的估计有对有错,我们就在每一步结束后,增加分错的点的权重,减少分对的点的权重,这样使得某些点如果老是被分错,那么就会被“严重关注”,也就被赋上一个很高的权重。然后等进行了N次迭代(由用户指定),将会得到N个简单的分类器(basic learner),然后我们将它们组合起来(比如说可以对它们进行加权、或者让它们进行投票等),得到一个最终的模型。
GBDT的每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。比如A的真实年龄是18岁,但第一棵树的预测年龄是12岁,差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去学习。如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄。如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学习,这就是Gradient Boosting在GBDT中的意义。
而Gradient Boost与传统的Boost的区别是,每一次的计算是为了减少上一次的残差(residual),而为了消除残差,我们可以在残差减少的梯度(Gradient)方向上建立一个新的模型。所以说,在Gradient Boost中,每个新的模型的建立是为了使得之前模型的残差往梯度方向减少,与传统Boost对正确、错误的样本进行加权有着很大的区别。
在线性回归中,我们希望找到一组参数使得模型的残差最小化。如果只使用一次项来解释二次曲线,那么就会存有大量残差,此时就可以用二次项来继续解释残差,所以可在模型中加入这个二次项。同样的,GB是先根据初始模型计算伪残差,之后建立一个学习器来解释伪残差,该学习器是在梯度方向上减少残差。再将该学习器乘上权重系数(学习速率)和原来的模型进行线性组合形成新的模型。这样反复迭代就可以找到一个使损失函数的期望达到最小的模型。
总的来说,对于一个样本,最理想的梯度是越接近0的梯度。所以,我们要能够让函数的估计值能够使得梯度往反方向移动(>0的维度上,往负方向移动,<0的维度上,往正方向移动)最终使得梯度尽量=0),并且该算法在会严重关注那些梯度比较大的样本,跟Boost的意思类似。得到梯度之后,就是如何让梯度减少了。这里是用的一个迭代+决策树的方法,当初始化的时候,随便给出一个估计函数F(x)(可以让F(x)是一个随机的值,也可以让F(x)=0),然后之后每迭代一步就根据当前每一个样本的梯度的情况,建立一棵决策树。就让函数往梯度的反方向前进,最终使得迭代N步后,梯度越小。这里建立的决策树和普通的决策树不太一样,首先,这个决策树是一个叶子节点数J固定的,当生成了J个节点后,就不再生成新的节点了。
Shrinkage(渐变)
Shrinkage是GBDT的第三个基本概念,中文含义为“缩减”。它的基本思想就是:每次走一小步逐渐逼近结果的效果,要比每次迈一大步很快逼近结果的方式更容易避免过拟合。换句话说缩减思想不完全信任每一个棵残差树,它认为每棵树只学到了真理的一小部分,累加的时候只累加一小部分,只有通过多学几棵树才能弥补不足。
Shrinkage不是Gradient的步长,Shrinkage只是一种大步变小步的逐步求精方法。这点看起来和Gradient目标=Gradient单位方向*步长挺像。但其实很不同:
(1)shrinkage的处理对象不一定是Gradient方向,也可以是残差,可以是任何增量,即目标=任何东西*shrinkage步长。
(2)shrinkage决定的是最终走出的一步大小,而不是希望走出的一步大小。前者是对于已有的学习结果打折,后者是在决定学习目标时对局部最优方向上走多远负责。
(3)shrinkage设小了只会让学习更慢,设大了就等于没设,它适用于所有增量迭代求解问题;而Gradient的步长设小了容易陷入局部最优点,设大了容易不收敛。它仅用于用梯度下降求解。这两者其实没太大关系。LambdaMART中其实两者都用了,而外部可配的参数是shrinkage而不是Gradient步长。
即Shrinkage仍然以残差作为学习目标,但对于残差学习出来的结果,只累加一小部分(step*残差)逐步逼近目标,step一般都比较小,如0.01~0.001(注意该step非gradient的step),导致各个树的残差是渐变的而不是陡变的。直觉上这也很好理解,不像直接用残差一步修复误差,而是只修复一点点,其实就是把大步切成了很多小步。本质上,Shrinkage为每棵树设置了一个weight,累加时要乘以这个weight,但和Gradient并没有关系。这个weight就是step。就像Adaboost一样,Shrinkage能减少过拟合发生也是经验证明的,目前还没有看到从理论的证明。
GBDT算法流程
回归算法
输入:训练样本
迭代次数(基学习器数量):T
损失函数:L
输出:强学习器H(x)
二分类算法
多分类算法
GBDT损失函数
这里我们再看看GBDT分类算法,GBDT的分类算法从思想上和GBDT的回归算法没有区别,但是由于样本输出不是连续的值,而是离散的类别,导致我们无法直接从输出类别去拟合类别输出的误差。
为了解决这个问题,主要有两个方法,一个是用指数损失函数,此时GBDT退化为Adaboost算法。另一种方法是用类似于逻辑回归的对数似然损失函数的方法。也就是说,我们用的是类别的预测概率值和真实概率值的差来拟合损失,本文仅讨论用对数似然损失函数的GBDT分类。
GBDT正则化
针对GBDT正则化,我们通过子采样比例方法和定义步长v方法来防止过拟合。
-
**子采样比例:**通过不放回抽样的子采样比例(subsample),取值为(0,1]。如果取值为1,则全部样本都使用。如果取值小于1,利用部分样本去做GBDT的决策树拟合。选择小于1的比例可以减少方差,防止过拟合,但是会增加样本拟合的偏差。因此取值不能太低,推荐在[0.5, 0.8]之间。
-
**定义步长v:**针对弱学习器的迭代,我们定义步长v,取值为(0,1]。对于同样的训练集学习效果,较小的v意味着我们需要更多的弱学习器的迭代次数。通常我们用步长和迭代最大次数一起来决定算法的拟合效果。
GBDT的两个不同版本
目前GBDT有两个不同的描述版本,两者各有支持者,读文献时要注意区分。
残差版本把GBDT说成一个残差迭代树,认为每一棵回归树都在学习前N-1棵树的残差,在ELF开源软件实现中用的也是这一版本。
Gradient版本把GBDT说成一个梯度迭代树,使用梯度下降法求解,认为每一棵回归树在学习前N-1棵树的梯度下降值,之前leftnoteasy的博客中介绍的为此版本,umass的源码实现中用的则是这一版本(准确的说是LambdaMART中的MART为这一版本,MART实现则是前一版本)。
总的来说两者相同之处在于,都是迭代回归树,都是累加每颗树结果作为最终结果(Multiple Additive Regression Tree),每棵树都在学习前N-1棵树尚存的不足,从总体流程和输入输出上两者是没有区别的;两者的不同主要在于每步迭代时,是否使用Gradient作为求解方法。前者不用Gradient而是用残差,残差是全局最优值,Gradient是局部最优方向*步长,即前者每一步都在试图让结果变成最好,后者则每步试图让结果更好一点。
两者优缺点。看起来前者更科学一点,有绝对最优方向不学,为什么舍近求远去估计一个局部最优方向呢?原因在于灵活性。前者最大问题是,由于它依赖残差,cost function一般固定为反映残差的均方差,因此很难处理纯回归问题之外的问题。而后者求解方法为梯度下降,只要可求导的cost function都可以使用,所以用于排序的LambdaMART就是用的后者。
GBDT中的Boost是样本目标的迭代,不是re-sampling的迭代,也不是Adaboost。
Adaboost中的boosting指从样本中按分类对错,分配不同的weight,计算cost function时使用这些weight,从而让“错分的样本权重越来越大,直到它们被分对”。Bootstrap也有类似思想,只不过它可以利用不同的weight作为sample概率对训练样本集做re-sample,让错分的样本被进一步学习,而分类正确的样本就不用再学了。但GBDT中的boost完全不同,跟上述逻辑没有任何关系,GBDT中每步boost的样本集都是不变的,变的是每个样本的回归目标值。
sklearn参数
GradientBoostingRegressor(loss=’ls’, learning_rate=0.1, n_estimators=100,
subsample=1.0, criterion=’friedman_mse’, min_samples_split=2,
min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_depth=3,
min_impurity_decrease=0.0, min_impurity_split=None, init=None,
random_state=None, max_features=None, alpha=0.9, verbose=0,
max_leaf_nodes=None, warm_start=False, presort=’auto’,
validation_fraction=0.1, n_iter_no_change=None, tol=0.0001)
参数说明:
- n_estimators:弱学习器的最大迭代次数,也就是最大弱学习器的个数。
- learning_rate:步长,即每个学习器的权重缩减系数a,属于GBDT正则化方化手段之一。
- subsample:子采样,取值(0,1]。决定是否对原始数据集进行采样以及采样的比例,也是GBDT正则化手段之一。
- init:我们初始化的时候的弱学习器。若不设置,则使用默认的。
- loss:损失函数,可选{'ls'-平方损失函数,'lad'绝对损失函数-,'huber'-huber损失函数,'quantile'-分位数损失函数},默认'ls'。
- alpha:当我们在使用Huber损失"Huber"和分位数损失"quantile"时,需要指定相应的值。默认是0.9,若噪声点比较多,可适当降低这个分位数值。
- criterion:决策树节搜索最优分割点的准则,默认是"friedman_mse",可选"mse"-均方误差与'mae"-绝对误差。
- max_features:划分时考虑的最大特征数,就是特征抽样的意思,默认考虑全部特征。
- max_depth:树的最大深度。
- min_samples_split:内部节点再划分所需最小样本数。
- min_samples_leaf:叶子节点最少样本数。
- max_leaf_nodes:最大叶子节点数。
- min_impurity_split:节点划分最小不纯度。
- presort:是否预先对数据进行排序以加快最优分割点搜索的速度。默认是预先排序,若是稀疏数据,则不会预先排序,另外,稀疏数据不能设置为True。
- validationfraction:为提前停止而预留的验证数据比例。当n_iter_no_change设置时才能用。
- n_iter_no_change:当验证分数没有提高时,用于决定是否使用早期停止来终止训练。
GBDT算法优缺点
GBDT的优点
(1)防止过拟合;
(2)每一步的残差计算其实变相地增大了分错instance的权重,而已经分对的instance则都趋向于0;
(3)残差作为全局最优的绝对方向;
(4)使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。
GBDT的缺点
(1)Boost是一个串行过程,不好并行化,而且计算复杂度高,比较耗时(大数据量非常耗时);
(2)同时不太适合高维特征,如果数据维度较高时会加大算法的计算复杂度,具体的复杂度可以参考XGBoost、lightGBM的改进点;
参考链接:https://zhuanlan.zhihu.com/p/58105824
参考链接:https://www.jianshu.com/p/b954476a00d9
参考链接:https://www.zybuluo.com/Dounm/note/1031900
参考链接:https://zhuanlan.zhihu.com/p/30736738
参考链接:https://blog.csdn.net/XiaoYi_Eric/article/details/80167968