目录
一 梯度提升树的基本思想
1 梯度提升树 pk AdaBoost
2 GradientBoosting回归与分类的实现
二 梯度提升树的参数
1 迭代过程
1.1 初始预测结果H0的设置
1.2 使用回归器完成分类任务
1.3 GBDT的8种损失函数
2 弱评估器结构
2.1 梯度提升树种的弱评估器复杂度
2.2 弗里德曼均方误差
3 梯度提升树的提前停止机制
4 梯度提升树的袋外数据
5 缺失参数class_weight与n_jobs
三 梯度提升树的参数空间与自动优化
1 GBDT的参数空间
2 基于TPE对GBDT进行优化
四 原理进阶:梯度提升回归树的求解流程
1 GBDT的基本数学流程
2 初始化H0过程中的常数C是什么?
3 伪残差、残差与梯度有什么关系?
4 证明:拟合伪残差的合理性
一 梯度提升树的基本思想
1 梯度提升树 pk AdaBoost
梯度提升树(Gradient Boosting Decision Tree,GBDT)是提升法中的代表性算法,它即是当代强力的XGBoost、LGBM等算法的基石,也是工业界应用最多、在实际场景中表现最稳定的机器学习算法之一。在最初被提出来时,GBDT被写作梯度提升机器(Gradient Boosting Machine,GBM),它融合了Bagging与Boosting的思想、扬长避短,可以接受各类弱评估器作为输入,在后来弱评估器基本被定义为决策树后,才慢慢改名叫做梯度提升树。受Boosting算法首个发扬光大之作AdaBoost的启发,GBDT中自然也包含Boosting三要素:
损失函数𝐿(𝑥,𝑦)L(x,y) :用以衡量模型预测结果与真实结果的差异
弱评估器𝑓(𝑥)f(x) :(一般为)决策树,不同的boosting算法使用不同的建树过程
综合集成结果𝐻(𝑥)H(x):即集成算法具体如何输出集成结果
同时,GBDT也遵循boosting算法的基本流程进行建模:
但与AdaBoost不同的是,GBDT在整体建树过程中做出了以下几个关键的改变:
弱评估器
GBDT的弱评估器输出类型不再与整体集成算法输出类型一致。对于AdaBoost或随机森林算法来说,当集成算法执行的是回归任务时,弱评估器也是回归器,当集成算法执行分类任务时,弱评估器也是分类器。但对于GBDT而言,无论GBDT整体在执行回归/分类/排序任务,弱评估器一定是回归器。GBDT通过sigmoid或softmax函数输出具体的分类结果,但实际弱评估器一定是回归器。
损失函数𝐿(𝑥,𝑦)
在GBDT当中,损失函数范围不再局限于固定或单一的某个损失函数,而从数学原理上推广到了任意可微的函数。因此GBDT算法中可选的损失函数非常多,GBDT实际计算的数学过程也与损失函数的表达式无关。
拟合残差
GBDT依然自适应调整弱评估器的构建,但却不像AdaBoost一样通过调整数据分布来间接影响后续弱评估器。相对的,GBDT通过修改后续弱评估器的拟合目标来直接影响后续弱评估器的结构。
具体地来说,在AdaBoost当中,每次建立弱评估器之前需要修改样本权重,且用于建立弱评估器的是样本𝑋以及对应的𝑦y,在GBDT当中,我们不修改样本权重,但每次用于建立弱评估器的是样本𝑋以及当下集成输出𝐻(𝑥𝑖)H(xi)与真实标签𝑦的差异𝑦−𝐻(𝑥𝑖)。这个差异在数学上被称之为残差(Residual),因此GBDT不修改样本权重,而是通过拟合残差来影响后续弱评估器结构。
抽样思想
GBDT加入了随机森林中随机抽样的思想,在每次建树之前,允许对样本和特征进行抽样来增大弱评估器之间的独立性(也因此可以有袋外数据集)。虽然Boosting算法不会大规模地依赖于类似于Bagging的方式来降低方差,但由于Boosting算法的输出结果是弱评估器结果的加权求和,因此Boosting原则上也可以获得由“平均”带来的小方差红利。当弱评估器表现不太稳定时,采用与随机森林相似的方式可以进一步增加Boosting算法的稳定性。
除了以上四个改变之外,GBDT的求解流程与AdaBoost大致相同。因此,如果你对AdaBoost的流程相当熟悉,GBDT的建模过程并不难懂。sklearn当中集成了GBDT分类与GBDT回归,我们使用如下两个类来调用它们:
class sklearn.ensemble.GradientBoostingClassifier(*, loss='deviance', 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, init=None, random_state=None, max_features=None, verbose=0, max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)
class sklearn.ensemble.GradientBoostingRegressor(*, loss='squared_error', 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, init=None, random_state=None, max_features=None, alpha=0.9, verbose=0, max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)
比起AdaBoost,GBDT的超参数数量增加了不少,但与其他集成算法一样,GBDT回归器与GBDT分类器的超参数高度一致(实际上,对GBDT来说,是完全一致)。在课程当中,我们将重点介绍GBDT独有的参数,以及GBDT分类器与GBDT回归器中表现不一致的参数。
二 梯度提升树的参数
class sklearn.ensemble.GradientBoostingClassifier(*, loss='deviance', 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, init=None, random_state=None, max_features=None, verbose=0, max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)
class sklearn.ensemble.GradientBoostingRegressor(*, loss='squared_error', 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, init=None, random_state=None, max_features=None, alpha=0.9, verbose=0, max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)
与随机森林一样,由于GBDT超参数数量较多,因此我们可以将GBDT的参数分为以下5大类别,其中标注为绿色的参数包括了我们未曾学过的知识、需要重点讲解:
1 迭代过程
之前我们提到过,GBDT的整体建模流程与AdaBoost高度相似,因此GBDT当中也有设置具体迭代次数(弱评估器次数)的参数n_estimators与学习率参数learning_rate,这两个参数的含义、以及对集成算法的影响与AdaBoost当中完全一致。
具体地来说,对于样本𝑥𝑖,集成算法当中一共有𝑇T棵树,则参数n_estimators的取值为T。假设现在正在建立第𝑡个弱评估器,则则第𝑡个弱评估器上𝑥𝑖的结果可以表示为𝑓𝑡(𝑥𝑖)。假设整个Boosting算法对样本𝑥𝑖输出的结果为𝐻(𝑥𝑖),则该结果一般可以被表示为t=1~t=T过程当中,所有弱评估器结果的加权求和:
其中,𝜙𝑡为第t棵树的权重。对于第𝑡次迭代来说,则有:
在这个一般过程中,每次将本轮建好的决策树加入之前的建树结果时,可以在权重𝜙前面增加参数𝜂,表示为第t棵树加入整体集成算法时的学习率,对标参数learning_rate。
该学习率参数控制Boosting集成过程中𝐻(𝑥𝑖)的增长速度,是相当关键的参数。当学习率很大时,𝐻(𝑥𝑖)增长得更快,我们所需的n_estimators更少,当学习率较小时,𝐻(𝑥𝑖)增长较慢,我们所需的n_estimators就更多,因此boosting算法往往会需要在n_estimators与learning_rate中做出权衡。
这两个参数的使用方法与AdaBoost中也完全一致,故此处不再赘述,后续我们会直接使用这两个参数进行调参。
1.1 初始预测结果𝐻0的设置
在上述过程中,我们建立第一个弱评估器时有:
由于没有第0棵树的存在,因此𝐻0(𝑥𝑖)的值在数学过程及算法具体实现过程中都需要进行单独的确定,这一确定过程由参数init确定。
参数init:输入计算初始预测结果𝐻0的估计器对象。
在该参数中,可以输入任意评估器、字符串"zero"、或者None对象,默认为None对象。
当输入任意评估器时,评估器必须要具备fit以及predict_proba功能,即我们可以使用决策树、逻辑回归等可以输出概率的模型。如果输出一个已经训练过、且精细化调参后的模型,将会给GBDT树打下坚实的基础。
填写为字符串"zero",则代表令𝐻0=0来开始迭代。
不填写,或填写为None对象,sklearn则会自动选择类DummyEstimator中的某种默认方式进行预测作为𝐻0的结果。DummyEstimator类是sklearn中设置的使用超简单规则进行预测的类,其中最常见的规则是直接从训练集标签中随机抽样出结果作为预测标签,也有选择众数作为预测标签等选项。
一般在GBDT类的使用过程中,我们不会主动调节参数init,但是当我们有足够的算力支持超参数搜索时,我们可以在init上进行选择。
1.2 使用回归器完成分类任务
GBDT与AdaBoost及随机森林的关键区别之一,是GBDT中所有的弱评估器都是回归树,因此在实际调用梯度提升树完成分类任务时,需要softmax函数或sigmoid函数对回归树输出的结果进行处理。因此,对于二分类情况来说,集成算法对样本𝑥𝑖输出的结果为:
其中𝜎是sigmoid函数,当𝑝(𝑦̂𝑖=1|𝑥𝑖)大于0.5时,样本𝑥𝑖的预测类别为1,反之则为0。
而对多分类来说,情况就比较复杂了。在讲解AdaBoost时我们说明过,二分类当中我们只需求解一个概率𝑃(𝑌=1),因为𝑃(𝑌=0)=1−𝑃(𝑌=1),因此𝑃(𝑌=1)大于0.5时预测标签为1,否则预测标签为0。但在多分类当中,我们必须求解出所有标签类别所对应的概率,在所有这些概率当中,最大概率所对应的标签才是多分类的预测标签。GBDT对于多分类也只能输出集成算法回归结果𝐻(𝑥𝑖),因此我们需要使用softmax函数帮助我们将回归值转化为概率,而Softmax函数是接受K个连续型结果,并输出K个相对概率的函数。
一般我们在使用softmax函数时,3分类问题则需要向softmax函数输入3个值,4分类问题则需要向softmax函数输入4个值,以此类推,最终softmax函数输出的是与输入值同等数量的相对概率,而多分类算法的预测标签是相对概率最高的类别。因此,在使用softmax函数前,我们需要准备好与类别数量相当的𝐻(𝑥𝑖)。
具体来说,当现在的问题是𝐾分类、且每个类别为[1,2,3...𝑘]时,我们则分别按照𝑦=1,𝑦=2,...,𝑦=𝑘进行建模,总共建立𝐾棵树,每棵树输出的结果为:
总共𝐾个输出结果。然后,我们分别将𝐻1(𝑥𝑖)到𝐻𝑘(𝑥𝑖)的结果输入softmax,来计算出每个标签类别所对应的概率。具体地来说,softmax函数的表达式为:
其中𝑒为自然常数,𝐻是集成算法的输出结果,𝐾表示标签中的类别总数为𝐾,如三分类时𝐾=3,四分类时𝐾=4,𝑘表示任意标签类别,𝐻𝑘则表示以类别𝑘为真实标签进行训练而得出的𝐻。不难发现,Softmax函数的分子时多分类状况下某一个标签类别的H(x)的指数函数,而分母时多分类状况下所有标签类别的H(x)的指数函数之和,因此Softmax函数的结果代表了样本的预测标签为类别𝑘的概率。假设现在是三分类[1,2,3],则样本𝑖被分类为1类的概率为:
最终得到𝐾个相对概率𝑝𝑘(𝑥𝑖),并求解出相对概率最高的类别。不难发现,当执行多分类时,这一计算流程中涉及到的计算量以及弱评估器数量都会远远超出二分类以及回归类问题。实际上,在执行多分类任务时,如果我们要求模型迭代10次,模型则会按照实际的多分类标签数n_classes建立10 * n_classes个弱评估器。对于这一现象,我们可以通过属性n_estimators_以及属性estimators_查看到。
1.3 GBDT的8种损失函数
作为基于AdaBoost改进的Boosting算法,GBDT的功绩之一是将损失函数从有限的指数损失、MSE等推广到了任意可微函数,因此GBDT的损失函数选择异常丰富,因此我们可以在调参时加入损失函数作为需要调整的参数进行考量。在sklearn中,控制具体损失函数的参数为loss。
GBDT中的损失函数因GBDT具体执行的预测任务而存在区别,同时也因标签的分布而存在区别。对于梯度提升分类树来说,loss的备选项有如下几种:
分类器中的loss:字符串型,可输入"deviance", "exponential",默认值="deviance"
其中"deviance"直译为偏差,特指逻辑回归的损失函数——交叉熵损失,而"exponential"则特指AdaBoost中使用的指数损失函数。对任意样本𝑖i而言,𝑦𝑖为真实标签,𝑦𝑖^为预测标签,𝐻(𝑥𝑖)为集成算法输出结果,𝑝(𝑥𝑖)为基于𝐻(𝑥𝑖)和sigmoid/softmax函数计算的概率值。则各个损失的表达式为:
二分类交叉熵损失——
注意,log当中输入的一定是概率值。对于逻辑回归来说,概率就是算法的输出,因此我们可以认为逻辑回归中𝑝=𝐻(𝑥)p=H(x),但对于GBDT来说,𝑝(𝑥𝑖)=𝑆𝑖𝑔𝑚𝑜𝑖𝑑(𝐻(𝑥𝑖)),这一点一定要注意。
多分类交叉熵损失,总共有K个类别——
其中,𝑃𝑘(𝑥)是概率值,对于多分类GBDT来说,𝑝𝑘(𝑥)=𝑆𝑜𝑓𝑡𝑚𝑎𝑥(𝐻𝑘(𝑥))。𝑦∗是由真实标签转化后的向量。例如,在3分类情况下,真实标签𝑦𝑖为2时,𝑦∗为[𝑦∗1,y2∗,y3∗],取值分别为:
这一转化过程与AdaBoost中多分类指数损失中的转化高度相似。
二分类指数损失——
多分类指数损失,总共有K个类别——
需要注意,指数损失中的𝑦∗与交叉熵损失中的𝑦∗不是同样的向量。我们已经在逻辑回归的章节中详解过交叉熵损失,在AdaBoost的章节当中详解过指数损失,因此这里便不再展开赘述了。需要注意的是,一般梯度提升分类器默认使用交叉熵损失,如果使用指数损失,则相当于执行没有权重调整的AdaBoost算法。
对于梯度提升回归树来说,loss的备选项有如下几种:
回归器中的loss:字符串型,可输入{"squared_error", "absolute_error", "huber", "quantile"},默认值="squared_error"
其中'squared_error'是指回归的平方误差,'absolute_error'指的是回归的绝对误差,这是一个鲁棒的损失函数。'huber'是以上两者的结合。'quantile'则表示使用分位数回归中的弹球损失pinball_loss。对任意样本𝑖i而言,𝑦𝑖为真实标签,𝐻(𝑥𝑖)为预测标签,则各个损失的表达式为:
平方误差——
绝对误差——
Huber损失——
quantile损失——
其中𝛼是需要我们自己设置的超参数,由参数alpha控制。在huber损失中,alpha是阈值,在quantile损失中,alpha用于辅助计算损失函数的输出结果,默认为0.9。
如何选择不同的损失函数?
GBDT是工业应用最广泛的模型,工业数据大部分都极度偏态、具有长尾,因此GBDT必须考虑离群值带来的影响。数据中的离群值会极大程度地影响模型地构建,当离群值在标签当中、而我们是依赖于减小损失函数来逐渐构建算法时,这种影响会前所未有地大。因此Boosting是天生更容易被离群值影响的模型、也更擅长学习离群值的模型。
举例来说,若离群值的标签为1000,大部分正常样本的标签在0.1~0.2之间,算法一定会异常努力地学习离群值的规律,因为将离群值预测错误会带来巨大的损失。在这种状况下,最终迭代出的算法可能是严重偏离大部分数据的规律的。同样,我们也会遇见很多离群值对我们很关键的业务场景:例如,电商中的金额离群用户可能是VIP用户,风控中信用分离群的用户可能是高风险用户,这种状况下我们反而更关注将离群值预测正确。不同的损失函数可以帮助我们解决不同的问题——
当高度关注离群值、并且希望努力将离群值预测正确时,选择平方误差
这在工业中是大部分的情况。在实际进行预测时,离群值往往比较难以预测,因此离群样本的预测值和真实值之间的差异一般会较大。MSE作为预测值和真实值差值的平方,会放大离群值的影响,会让算法更加向学习离群值的方向进化,这可以帮助算法更好地预测离群值。
努力排除离群值的影响、更关注非离群值的时候,选择绝对误差
MAE对一切样本都一视同仁,对所有的差异都只求绝对值,因此会保留样本差异最原始的状态。相比其MSE,MAE对离群值完全不敏感,这可以有效地降低GBDT在离群值上的注意力。
试图平衡离群值与非离群值、没有偏好时,选择Huber或者Quantileloss
Huberloss损失结合了MSE与MAE,在Huber的公式中,当预测值与真实值的差异大于阈值时,则取绝对值,小于阈值时,则取平方。在真实数据中,部分离群值的差异会大于阈值,部分离群值的差异会小于阈值,因此比起全部取绝对值的MAE,Huberloss会将部分离群值的真实预测差异求平方,相当于放大了离群值的影响(但这种影响又不像在MSE那样大)。因此HuberLoss是位于MSE和MAE之间的、对离群值相对不敏感的损失。
2 弱评估器结构
在讲解决策树时,我们已经系统地讲解过弱评估器相关的一系列减枝参数,而在讲解随机森林时,我们又明确了这些减枝参数如何影响集成算法。因此我们对于Boosting算法中控制弱评估器的参数可谓非常熟悉:
这些参数在随机森林中的用法与默认值与决策树类DecisionTreeRegressor中完全一致,专门用于对决策树进行剪枝、控制单个弱评估器的结构,考虑到大家在决策树中已经充分掌握这些参数,我们不再对这些参数一一进行详细说明了。在这里,需要重点说明的有两部分内容,一部分梯度提升树中默认的弱评估器复杂度所带来的问题,另一部分则是梯度提升树独有的不纯度衡量指标。
2.1 梯度提升树中的弱评估器复杂度
max_depth
虽然我们非常熟悉控制弱评估器结构的各个参数,但在实际应用任意Boosting算法时,我们还需进一步了解算法在这些参数上的默认值,以了解该算法留给我们的调参余地是否较大。
在随机森林中我们讲到,森林中任意控制过拟合的参数基本都处于“关闭状态”,例如max_depth的默认值为None,表示不限深度,min_samples_splits的默认值为2,等同于不限制分枝,因此随机森林中长出的树都是剪枝前的树,也因此当随机森林算法处于过拟合状态时,我们可以使用粗或精的方法对弱评估器进行大刀阔斧的剪枝,当随机森林中的树被剪掉之后,可以很好的限制过拟合。然而这种情况并不适用于任何集成算法,尤其是以AdaBoost为基础的Boosting算法一族。
在原始AdaBoost理论中,AdaBoost中使用的弱分类器都是最大深度为1的树桩或最大深度为3的小树苗,因此基于AdaBoost改进的其他Boosting算法也有该限制,即默认弱评估器的最大深度一般是一个较小的数字。对GBDT来说,无论是分类器还是回归器,默认的弱评估器最大深度都为3,因此GBDT默认就对弱评估器有强力的剪枝机制。
当随机森林处于过拟合状态时,还可通过降低弱评估器复杂度的手段控制过拟合,但GBDT等Boosting算法处于过拟合状态时,便只能从数据上下手控制过拟合了(例如,使用参数max_features,在GBDT中其默认值为None),毕竟当max_depth已经非常小时,其他精剪枝的参数如min_impurity_decrease一般发挥不了太大的作用。
也因此,通常认为Boosting算法比Bagging算法更不容易过拟合,一般在相似的数据上,Boosting算法表现出的过拟合程度会较轻。
2.2 弗里德曼均方误差
不纯度衡量指标criterion
criterion是树分枝时所使用的不纯度衡量指标。在sklearn当中,GBDT中的弱学习器𝑓f是CART树,因此每棵树在建立时都依赖于CART树分枝的规则进行建立。CART树每次在分枝时都只会分为两个叶子节点(二叉树),它们被称为左节点(left)和右节点(right)。在CART树中进行分枝时,我们需要找到令左右节点的不纯度之和最小的分枝方式。通常来说,我们求解父节点的不纯度与左右节点不纯度之和之间的差值,这个差值被称为不纯度下降量(impurity decrease)。不纯度的下降量越大,该分枝对于降低不纯度的贡献越大。
对GBDT来说,不纯度的衡量指标有2个:弗里德曼均方误差friedman_mse与平方误差squared_error。其中平方误差我们非常熟悉,弗里德曼均方误差是由Friedman在论文《贪婪函数估计:一种梯度提升机器》(GREEDY FUNCTION APPROXIMATION: A GRADIENT BOOSTING MACHINE)中提出的全新的误差计算方式。遗憾的是,在论文当中,Friedman并没有提供弗里德曼均方误差的公式本身,而只提供了使用弗里德曼均方误差之后推导出的不纯度下降量的公式。该公式如下:
其中𝑤是左右叶子节点上的样本量,当我们对样本有权重调整时,𝑤则是叶子节点上的样本权重。𝑟𝑖大多数时候是样本i上的残差(父节点中样本i的预测结果与样本i的真实标签之差),也可能是其他衡量预测与真实标签差异的指标,𝑦𝑖^是样本i在当前子节点下的预测值。所以这个公式其实可以解读成:
左右叶子节点上样本量的调和平均 * (左叶子节点上均方误差 - 右叶子节点上的均方误差)^2
根据论文中的描述,弗里德曼均方误差使用调和平均数(分子上相乘分母上相加)来控制左右叶子节点上的样本数量,相比普通地求均值,调和平均必须在左右叶子节点上的样本量/样本权重相差不大的情况下才能取得较大的值(F1 score也是用同样的方式来调节Precision和recall)。这种方式可以令不纯度的下降得更快,让整体分枝的效率更高。
同时,在决策树进行分枝时,一般不太可能直接将所有样本分成两个不纯度非常低的子集(分别位于两片叶子上),相对的,树会偏向于建立一个不纯度非常非常低的子集,然后将剩下无法归入这个低不纯度子集的样本全部打包成另外一个子集。因此直接使用两个子集之间的MSE差距来衡量不纯度的下降量非常聪明,如果两个子集之间的MSE差异很大,则说明其中一个子集的MSE一定很小,对整体分枝来说是更有利的。同样非常遗憾的是,Friedman并没有在为我们提供完整数学证明,以佐证刚才所说的观点。
除了Friedman_mse之外,我们也可以使用普通的平方误差作为不纯度的衡量。使用普通平方误差时,我们可以直接计算父节点的平方误差与子节点平方误差的加权求和之间的差异。
大部分时候,使用弗里德曼均方误差可以让梯度提升树得到很好的结果,因此GBDT的默认参数就是Friedman_mse。不过许多时候,我们会发现基于平方误差的分割与基于弗里德曼均方误差的分割会得到相同的结果。
3 梯度提升树的提前停止
在学习机器学习理论与方法时,我们极少提及迭代的提前停止问题。在机器学习中,依赖于迭代进行工作的算法并不算多,同时课程中的数据量往往也比较小,因此难以预见需要提前停止迭代以节省计算资源或时间的情况。但对于工业界使用最广泛的GBDT而言,提前停止是需要考虑的关键问题。
对于任意需要迭代的算法,迭代的背后往往是损失函数的最优化问题。例如在逻辑回归中,我们在进行梯度下降的迭代时,是希望找到交叉熵损失函数的最小值;而在梯度提升树中,我们在一轮轮建立弱评估器过程中,也是希望找到对应损失函数的最小值。理想状态下,无论使用什么算法,只要我们能够找到损失函数上真正的最小值,那模型就达到“收敛”状态,迭代就应该被停止。
然而遗憾的是,我们和算法都不知道损失函数真正的最小值是多少,而算法更不会在达到收敛状态时就自然停止。在机器学习训练流程中,我们往往是通过给出一个极限资源来控制算法的停止,比如,我们通过超参数设置允许某个算法迭代的最大次数,或者允许建立的弱评估器的个数。因此无论算法是否在很短时间内就锁定了足够接近理论最小值的次小值、或者算法早已陷入了过拟合状态、甚至学习率太低导致算法无法收敛,大多数算法都会持续(且无效地)迭代下去,直到我们给与的极限资源全部被耗尽。对于复杂度较高、数据量较大的Boosting集成算法来说,无效的迭代常常发生,因此作为众多Boosting算法的根基算法,梯度提升树自带了提前停止的相关超参数。另外,逻辑回归看起来会自然停止,是因为逻辑回归内置提前停止机制。
我们根据以下原则来帮助梯度提升树实现提前停止:
当GBDT已经达到了足够好的效果(非常接近收敛状态),持续迭代下去不会有助于提升算法表现
GBDT还没有达到足够好的效果(没有接近收敛),但迭代过程中呈现出越迭代算法表现越糟糕的情况
虽然GBDT还没有达到足够好的效果,但是训练时间太长/速度太慢,我们需要重新调整训练
第三种情况可以通过参数verbose打印结果来观察,如果GBDT的训练时间超过半个小时,建树平均时长超出1分钟,我们就可以打断训练考虑重调参数。前两种情况则比较复杂,我们首先必须理解什么叫做“足够好的效果”。在GBDT迭代过程中,只要损失函数的值持续减小、或验证集上的分数持续上升,我们就可以认为GBDT的效果还有提升空间。在实际训练过程中,刚开始训练时,测试集和训练集上的损失一般都很高(有时,训练集上的损失甚至比测试集上的损失还高,这说明模型严重欠训练),但随着训练次数的增多,两种损失都会开始快速下降,一般训练集下降得更快,测试集下降得缓慢。直到某一次迭代时,无论我们如何训练,测试集上的损失都不再下降,甚至开始升高,此时我们就需要让迭代停下。
如下图所示,下图中横坐标为迭代次数,纵坐标为损失函数的值。当测试集上的损失不再下降、持续保持平稳时,满足条件1,继续训练会浪费训练资源,迭代下去模型也会停滞不前,因此需要停止(左图)。当测试集上的损失开始升高时,往往训练集上的损失还是在稳步下降,继续迭代下去就会造成训练集损失比测试集损失小很多的情况,也就是过拟合(右侧),此时满足条件2,也需要提前停止。在过拟合之前及时停止,能够防止模型被迭代到过拟合状况下。
在实际数据训练时,我们往往不能动用真正的测试集进行提前停止的验证,因此我们需要从训练集中划分出一小部分数据,专用于验证是否应该提前停止。那我们如何找到这个验证集损失不再下降、准确率不再上升的“某一时间点”呢?此时,我们可以规定一个阈值,例如,当连续n_iter_no_change次迭代中,验证集上损失函数的减小值都低于阈值tol,或者验证集的分数提升值都低于阈值tol的时候,我们就令迭代停止。此时,即便我们规定的n_estimators或者max_iter中的数量还没有被用完,我们也可以认为算法已经非常接近“收敛”而将训练停下。这种机制就是提前停止机制Early Stopping。这种机制中,需要设置阈值tol,用于不断检验损失函数下降量的验证集,以及损失函数连续停止下降的迭代轮数n_iter_no_change。在GBDT当中,这个流程刚好由以下三个参数控制:
validation_fraction:从训练集中提取出、用于提前停止的验证数据占比,值域为[0,1]。
n_iter_no_change:当验证集上的损失函数值连续n_iter_no_change次没有下降或下降量不达阈值时,则触发提前停止。平时则设置为None,表示不进行提前停止。
tol:损失函数下降的阈值,默认值为1e-4,也可调整为其他浮点数来观察提前停止的情况。
需要注意的是,当提前停止条件被触发后,梯度提升树会停止训练,即停止建树。因此,当提前停止功能被设置打开时,我们使用属性n_estimators_调出的结果很可能不足我们设置的n_estimators,属性estimators_中的树数量也可能变得更少:
什么时候使用提前停止呢?一般有以下几种场景:
当数据量非常大,肉眼可见训练速度会非常缓慢的时候,开启提前停止以节约运算时间
n_estimators参数范围极广、可能涉及到需要500~1000棵树时,开启提前停止来寻找可能的更小的n_estimators取值
当数据量非常小,模型很可能快速陷入过拟合状况时,开启提前停止来防止过拟合
总结:
4 梯度提升树的袋外数据
在讲解梯度提升树的基本原理时,我们提到梯度提升树结合了Boosting和Bagging中的重要思想。受到随机森林的启发,梯度提升树在每次建树之前,也允许模型对于数据和特征进行随机有放回抽样,构建与原始数据集相同数据量的自助集。在梯度提升树的原理当中,当每次建树之前进行随机抽样时,这种梯度提升树叫做随机提升树(Stochastic Gradient Boosting)。相比起传统的梯度提升树,随机提升树输出的结果往往方差更低,但偏差略高。如果我们发现GBDT的结果高度不稳定,则可以尝试使用随机提升树。
在GBDT当中,对数据的随机有放回抽样比例由参数subsample确定,当该参数被设置为1时,则不进行抽样,直接使用全部数据集进行训练。当该参数被设置为(0,1)之间的数字时,则使用随机提升树,在每轮建树之前对样本进行抽样。对特征的有放回抽样比例由参数max_features确定,随机模式则由参数random_state确定,这两个参数在GBDT当中的使用规则都与随机森林中完全一致。
需要注意的是,如果subsample<1,即存在有放回随机抽样时,当数据量足够大、抽样次数足够多时,大约会有37%的数据被遗漏在“袋外”(out of bag)没有参与训练。在随机森林课程当中,我们详细地证明了37%的由来,并且使用这37%的袋外数据作为验证数据,对随机森林的结果进行验证。在GBDT当中,当有放回随机抽样发生时,自然也存在部分袋外数据没有参与训练。这部分数据在GBDT中被用于对每一个弱评估器的建立结果进行验证。
具体地来说,每建立一棵树,GBDT就会使用当前树的袋外数据对建立新树后的模型进行验证,以此来对比新建弱评估器后模型整体的水平是否提高,并保留提升或下降的结果。这个过程相当于在GBDT迭代时,不断检验损失函数的值并捕捉其变化的趋势。在GBDT当中,这些袋外分数的变化值被储存在属性oob_improvement_中,同时,GBDT还会在每棵树的训练数据上保留袋内分数(in-bag)的变化,且储存在属性train_score_当中。也就是说,即便在不做交叉验证的情况下,我们也可以简单地通过属性oob_improvement与属性train_score_来观察GBDT迭代的结果。
总结一下,与弱评估器训练数据相关的参数有:
5 缺失的class_weight与n_jobs
到这里,我们已经讲解完毕了梯度提升回归树以及梯度提升分类树中的所有参数。需要注意的是,作为最常用的集成算法之一,sklearn中的GBDT分类器并没有提供调节样本不均衡问题的参数class_weights,也不存在并行参数n_jobs。
不在样本不均衡问题上做文章,或许跟GBDT的弱评估器都是回归器有关,又或许是因为GBDT拥有非常强的学习能力,因此不会轻易被样本不均衡问题左右,也可能是因为sklearn在配置GBDT时存在一些失误。但务必要注意,如果样本存在严重不均衡的状况,那我们可能会考虑不使用梯度提升树,或者先对数据进行样本均衡的预处理后,再使用梯度提升树。
GBDT中的树必须一棵棵建立、且后面建立的树还必须依赖于之前建树的结果,因此GBDT很难在某种程度上实现并行,因此sklearn并没有提供n_jobs参数给Boosting算法使用。更加先进的Boosting算法们已经实现了分枝并行,但sklearn还无法实现这个功能,因此GBDT的计算速度难以得到加速,这是sklearn实现GBDT无法跨越的一个缺陷。
三 GBDT的参数空间与超参数优化
1 确定GBDT优化的参数空间
丰富的超参数为集成算法提供了无限的可能,以降低偏差为目的的Boosting算法们在调参之后的表现更是所向披靡,因此GBDT的超参数自动优化也是一个重要的课题。对任意集成算法进行超参数优化之前,我们需要明确两个基本事实:
1、不同参数对算法结果的影响力大小
2、确定用于搜索的参数空间
对GBDT来说,我们可以大致如下排列各个参数对算法的影响:
如果你熟悉随机森林的超参数,你会发现GBDT中大部分超参数的影响力等级都非常容易理解。树的集成模型们大多拥有相似的超参数,例如抗过拟合、用来剪枝的参数群(max_depth、min_samples_split等),又比如对样本/特征进行抽样的参数们(subsample,max_features等),这些超参数在不同的集成模型中以相似的方式影响模型,因此原则上来说,对随机森林影响较大的参数对GBDT也会有较大的影响。
然而你可能注意到,在随机森林中非常关键的max_depth在GBDT中没有什么地位,取而代之的是Boosting中特有的迭代参数学习率learning_rate。在随机森林中,我们总是在意模型复杂度(max_depth)与模型整体学习能力(n_estimators)的平衡,单一弱评估器的复杂度越大,单一弱评估器对模型的整体贡献就越大,因此需要的树数量就越少。在Boosting算法当中,单一弱评估器对整体算法的贡献由学习率参数learning_rate控制,代替了弱评估器复杂度的地位,因此Boosting算法中我们寻找的是learning_rate与n_estimators的平衡。同时,Boosting算法天生就假设单一弱评估器的能力很弱,参数max_depth的默认值也往往较小(在GBDT中max_depth的默认值是3),因此我们无法靠降低max_depth的值来大规模降低模型复杂度,更难以靠max_depth来控制过拟合,自然max_depth的影响力就变小了。
可见,虽然树的集成算法们大多共享相同的超参数,都由于不同算法构建时的原理假设不同,相同参数在不同算法中的默认值可能被设置得不同,因此相同参数在不同算法中的重要性和调参思路也不同。
在讲解随机森林时我们提到过,精剪枝工具的效用有限,剪枝一般还是大刀阔斧的粗剪枝更有效。在GBDT中,由于max_depth这一粗剪枝工具的默认值为3,因此在Boosting算法中通过削减模型复杂度来控制过拟合的思路就无法走通。特别地,参数init对GBDT的影响很大,如果在参数init中填入具体的算法,过拟合可能会变得更加严重,因此我们需要在抑制过拟合、控制复杂度这一点上令下功夫。
如果无法对弱评估器进行剪枝,最好的控制过拟合的方法就是增加随机性/多样性,因此max_features和subsample就成为Boosting算法中控制过拟合的核心武器,这也是GBDT中会加入Bagging思想的关键原因之一。依赖于随机性、而非弱评估器结构来对抗过拟合的特点,让Boosting算法获得了一个意外的优势:比起Bagging,Boosting更加擅长处理小样本高维度的数据,因为Bagging数据很容易在小样本数据集上过拟合。
需要注意的是,虽然max_depth在控制过拟合上的贡献不大,但是我们在调参时依然需要保留这个参数。当我们使用参数max_features与subsample构建随机性、并加大每一棵树之间的差异后,模型的学习能力可能受到影响,因此我们可能需要提升单一弱评估器的复杂度。因此在GBDT当中,max_depth的调参方向是放大/加深,以探究模型是否需要更高的单一评估器复杂度。相对的在随机森林当中,max_depth的调参方向是缩小/剪枝,用以缓解过拟合。
那在调参的时候,我们应该选择哪些参数呢?与随机森林一样,我们首先会考虑所有影响力巨大的参数(5星参数),当算力足够/优化算法运行较快的时候,我们可以考虑将大部分时候具有影响力的参数(4星)也都加入参数空间,如果样本量较小,我们可能不选择subsample。除此之外,我们还需要部分影响弱评估器复杂度的参数,例如max_depth。如果算力充足,我们还可以加入criterion这样或许会有效的参数。在这样的基本思想下,考虑到硬件与运行时间因素,我将选择如下参数进行调整,并使用基于TPE贝叶斯优化(HyperOpt)对GBDT进行优化——
在此基础上,我们需要进一步确认参数空间。幸运的是,GBDT的参数空间几乎不依赖于树的真实结构进行调整,且大部分参数都有固定的范围,因此我们只需要对无界的参数稍稍探索即可。
其中,我们在《二 4 袋外数据》部分已经对n_estimators进行过探索(如果你没有经过这个探索,则需要绘制绘制学习曲线进行探索),在当前默认参数下,我们大约只需要50次迭代就能够让损失函数收敛,因此我们可以就50这个数字向两侧展开来设置n_estimators的搜索范围。
另外,原则上来说需要用Tree对象来探索min_impurity_decrease的值,但由于我们这个参数只能放大、不能缩小,且我们知道精剪枝对于GBDT的影响较小,因此就不必大费周章地给与该参数一个精细的范围了,按照(0,C)结构走即可,其中C为任意常数。
四 原理进阶:GBDT的求解流程
作为当代众多经典算法的基础,GBDT的求解过程可谓十分精妙,它不仅开创性地舍弃了使用原始标签进行训练的方式,同时还极大地简化了Boosting算法的运算流程,让Boosting算法本该非常复杂的运算流程变得清晰简洁。当我们学过完整的AdaBoost流程后,我们会发现GBDT的数学流程非常简明、美丽,同时这一美丽的流程也是我们未来所有Boosting高级算法的数学基础。与任意Boosting算法一致,对GBDT我们需要回答如下问题:
损失函数𝐿(𝑥,𝑦)的表达式是什么?损失函数如何影响模型构建?
弱评估器𝑓(𝑥)是什么,当下boosting算法使用的具体建树过程是什么?
综合集成结果𝐻(𝑥)是什么?集成算法具体如何输出集成结果?
同时,还可能存在其他需要明确的问题,例如:
是加权求和吗?如果是,加权求和中的权重如何求解?
训练过程中,拟合的数据𝑋与𝑦分别是什么?
模型训练到什么时候停下来最好?
对于GBDT,由于我们存在提前停止机制以及资源控制,因此我们一般不去在意模型停止相关的问题,但除此之外的每个问题我们都需要仔细研究。
假设现有数据集𝑁,含有形如(𝑥𝑖,𝑦𝑖)的样本𝑀个,𝑖i为任意样本的编号,单一样本的损失函数为𝑙(𝑦𝑖,𝐻(𝑥𝑖)),其中𝐻(𝑥𝑖)H(xi)是𝑖i号样本在集成算法上的预测结果,整个算法的损失函数为𝐿(𝑦,𝐻(𝑥)),且总损失等于全部样本的损失之和:𝐿(𝑦,𝐻(𝑥))=∑𝑖𝑙(𝑦𝑖,𝐻(𝑥𝑖))。同时,弱评估器为回归树𝑓,总共学习𝑇轮。则GBDT回归的基本流程如下所示:
1) 初始化数据迭代的起点H0(x)。sklearn当中,我们可以使用0、随机数或者任意算法的输出结果作为H0(x),但在最初的论文中,Friedman定义了如下公式来计算H0:
其中𝑦𝑖为真实标签,𝐶为任意常数。以上式子表示,找出令∑𝑀𝑖=1𝑙(𝑦𝑖,𝐶)最小的常数𝐶值,并输出最小的∑𝑀𝑖=1𝑙(𝑦𝑖,𝐶)作为𝐻0(𝑥)的值。需要注意的是,由于𝐻0(𝑥)是由全部样本的𝑙计算出来的,因此所有样本的初始值都是𝐻0(𝑥),不存在针对某一样本的单一初始值。
开始循环,for t in 1,2,3...T:
2) 在现有数据集𝑁中,抽样M * subsample个样本,构成训练集𝑁𝑡
3) 对任意一个样本𝑖,计算伪残差(pseudo-residuals)𝑟𝑖𝑡,具体公式为:
不难发现,伪残差是一个样本的损失函数对该样本在集成算法上的预测值求导后取负的结果,并且在进行第t次迭代、计算第t个伪残差时,我们使用的前t-1次迭代后输出的集成算法结果。在t=1时,所有伪残差计算中的𝐻𝑡−1(𝑥𝑖)都等于初始𝐻0(𝑥),在t>0时,每个样本上的𝐻𝑡−1(𝑥𝑖)都是不同的取值。
4) 求解出伪残差后,在数据集(𝑥𝑖,𝑟𝑖𝑡)上按照CART树规则建立一棵回归树𝑓𝑡,训练时拟合的标签为样本的伪残差𝑟𝑖𝑡。
5) 将数据集𝑁𝑡上所有的样本输入𝑓𝑡进行预测,对每一个样本,得出预测结果𝑓𝑡(𝑥𝑖)。在数学上我们可以证明,只要拟合对象是伪残差𝑟𝑖𝑡,则𝑓𝑡(𝑥𝑖)的值一定能让损失函数最快减小。
6) 根据预测结果𝑓𝑡(𝑥𝑖)迭代模型,具体来说:
假设输入的步长为𝜂,则𝐻𝑡(𝑥)应该为:
对整个算法则有:
7)循环结束,输出𝐻𝑇(𝑥)的值作为集成模型的输出值。
以上就是GBDT完整的数学流程,不难发现,这个流程是比AdaBoost的流程更简洁的。当然,整体流程当中可能有不少令人困惑的地方,我们来一一解明:
2 初始化𝐻0过程中的常数C是什么?
在最初的论文中,Friedman定义了如下公式来计算𝐻0:
其中𝑦𝑖为真实标签,𝐶C为任意常数。以上式子表示,找出令整体损失𝐿(𝑦𝑖,𝐶)最小的常数𝐶C值,并输出最小的𝐿(𝑦𝑖,𝐶)作为𝐻0(𝑥)的值。在刚观察这个式子时,大家可能很难理解𝐶这个常数的作用,但这个式子实际上很简单——
首先,𝑙l是损失函数,损失函数衡量两个自变量之间的差异,因此𝑙(𝑦𝑖,𝐶)衡量样本𝑖的真实标签𝑦𝑖yi与常数C之间的差异,因此𝐿(𝑦𝑖,𝐶)是所有样本的真实标签与常数C之间的差异之和。现在我们要找到一个常数C,令所有样本的真实标签与常数C的差异之和最小,请问常数C是多少呢?这是一个典型的求极值问题,只需要对∑𝑀𝑖=1𝑙(𝑦𝑖,𝐶)求导,再令导数为0就可以解出令∑𝑀𝑖=1𝑙(𝑦𝑖,𝐶)最佳的C。假设𝑙是squared_error,每个样本的平方误差,则有:
对上述式子求导,并令一阶导数等于0:
所以:
可知,当L是平方误差squared error时,令𝐿(𝑦𝑖,𝐶)最小的常数C就是真实标签的均值,这个过程与我们在学习Kmeans时证明各点到质心(均值)的距离就是最小SSE完全一致。因此,式子𝐻0=𝑎𝑟𝑔𝑚𝑖𝑛𝐶∑𝑀𝑖=1𝑙(𝑦𝑖,𝐶)H0=argminC∑i=1Ml(yi,C)的本质其实是求解𝐶=𝑚𝑒𝑎𝑛(𝑦𝑖)C=mean(yi)时的损失函数,并以此损失函数作为𝐻0H0的值。当然,如果我们选择了其他的损失函数,我们就需要以其他方式(甚至梯度下降)进行求解,𝐶C的值可能也就不再是均值了。
3 伪残差与残差、梯度有什么关系?
在讲解GBDT与AdaBoost之间的差异时,我们曾提到,AdaBoost是拟合原始数据𝑋与真实标签𝑦𝑖,而GBDT拟合的是原始数据𝑋与残差(𝑦𝑖−𝐻(𝑥𝑖)),但在上述数学流程中,我们拟合的对象伪残差既不像真实标签,也不像𝐻(𝑥)与𝑦𝑖的差异,它到底是什么呢?
从数学上来看,伪残差是一个样本的损失函数对该样本在集成算法上的预测值求导后取负的结果。假设现在损失函数是平方误差Squared error,则该求导过程很明显就是:
不难发现,虽然伪残差看着与残差完全不相关,但其本质与残差非常相似。它是残差的某种变形,它的值不完全等同于残差的值,但是它衡量的差异与残差衡量的差异完全一致。因此,我可以让新建立的弱评估器拟合伪残差,这样算法就会更多地学习当下𝐻𝑡(𝑥𝑖)与𝑦𝑖之间的差异,新建立的弱评估器预测出的结果也更有可能抹平这种差异。从直觉上来说,𝐻𝑡(𝑥𝑖)与𝑦𝑖之间的差异越小,整体损失函数值就会越小,因此GBDT拟合伪残差是在向着损失函数最小化(偏差最小化)的方向拟合。
除此之外,伪残差是损失函数求导后取负的结果。一个函数对自变量求导后得到的结果称为梯度,代表字母为𝑔,因此伪残差也被称为负梯度,也因此,GBDT被称为“拟合负梯度”的算法。这一说法拓展开来,我们可以说GBDT拟合负梯度、拟合伪残差、拟合损失函数在预测标签上的负导数。无论这些说法如何变化,其实指的都是同一个数学过程。不过,在最初的梯度提升机器(Gradient Boosting Machine)中,拟合的的确是残差𝑦−𝐻(𝑥),只不过在后来改进的梯度提升树中,拟合残差过程被修改为拟合伪残差了。
需要注意的是,由于伪残差/负梯度都是针对单一样本计算的,所以一般在数学公式当中,梯度会被表示为𝑔𝑖,其中𝑖为样本量。对GBDT来说则有:
4 证明:拟合伪残差可以令损失函数最快地减小
从直觉上来看,拟合伪残差可以降低𝐻𝑡(𝑥𝑖)与𝑦𝑖之间的差异,从而降低整体损失函数的值,但这个行为在数学上真的可行吗?毕竟,GBDT可以使用任意可微函数作为损失函数,不同损失函数求导后的结果即便与残差相似,也未必能代替真正的残差的效果。因此,不仅在直觉上需要理解拟合伪残差的作用,我们还需要从数学上证明:只要拟合对象是伪残差𝑟𝑖𝑡,则弱评估器的输出值𝑓𝑡(𝑥𝑖)一定是让损失函数减小最快的值。
直观类比
假设现在有包含𝑀个样本的数据集𝑁𝑡,无论我们以什么规则建立新的弱评估器𝑓𝑡,我们一定是希望𝑓𝑡满足以下条件的:
上式表示,本轮弱评估器的输出值𝑓𝑡应该是令整体损失𝐿L最小化的𝑓𝑡ft。即,无论弱评估器𝑓𝑡是什么结构、什么规则、如何建立、如何拟合,其最终的输出值𝑓𝑡(𝑥𝑖)必须是令整体损失函数𝐿最小化的𝑓𝑡(𝑥𝑖)。如果我们能保证这个条件成立,那随着算法逐步迭代,损失函数必然是会越来越小的。那我们如何保证这一点成立呢?在这里,我们需要使用论文《梯度下降式提升算法》中所提到的函数式梯度下降了——我们可以直接对整体损失函数进行梯度下降,找出当前最小值以及最小值对应的𝑓𝑡(𝑥𝑖)。
具体来说,回忆我们在逻辑回归中执行的梯度下降过程,当时我们的损失函数为𝐿(𝒘),其中𝑤是逻辑回归的系数向量,且迭代𝑤的方法如下:
公式中𝜂为学习率,𝑔𝑡为第𝑡次迭代中的梯度向量,包含了全部𝑤的梯度[𝑔1,𝑔2,𝑔3...𝑔𝑛]。通过在𝑤上直接增加学习率*负梯度,我们可以保证损失函数𝐿(𝒘)在𝑤迭代过程中一定是越来越小的,因为在学习梯度下降时我们证明过,负梯度的方向就是损失函数下降最快的方向。那相同的思路也可以被用到GBDT当中。
在GBDT中,我们的损失函数为𝐿(𝑦𝑖,𝐻𝑡(𝑥)),并且我们的𝐻𝑡(𝑥)是按以下方式迭代的:
其中𝐻𝑡(𝑥)是,第𝑡次迭代中全部样本在算法上的输出值,𝑓𝑡(𝑥)则是第𝑡t次迭代中全部样本在新弱评估器上输出的𝑓𝑡(𝑥𝑖)。原则上来说,对标传统梯度下降,只要让𝑓𝑡(𝑥)=−𝑔𝑡,即让𝑓𝑡(𝑥𝑖)=−𝑔𝑖,就一定能够保证损失函数𝐿(𝑦𝑖,𝐻𝑡(𝑥))是随迭代下降的。
当我们已经知道能够令损失函数最小的𝑓𝑡(𝑥𝑖)就是−𝑔𝑖之后,如何逼迫新建立的弱评估器输出−𝑔𝑖这个数字呢?答案是,让新建立的弱评估器拟合(𝑥𝑖,−𝑔𝑖)。所以你现在应该已经猜到了,每个样本的伪残差𝑟𝑖(负梯度−𝑔𝑖)其实就是能够令损失函数减小最快的𝑓𝑡(𝑥𝑖)的值。
严谨证明
当然,上述过程只是类比,并非严谨的数学证明。如果我们想要证明负梯度就是让损失函数减小最快的值,则需要借助泰勒级数来帮助我们。
在数学中,泰勒级数使用无限个项的连加式来表示一个函数。实际应用当中,我们一般取有限项的连加式来逼近一个函数。当总共有N项时,连加式被叫做N阶泰勒展开(Nth-order Taylor approximation)。假设现在存在函数𝑓(𝑥),则有:
阶数越大,泰勒展开的值越接近𝑓(𝑥)的真实值。
我们可以对损失函数进行泰勒展开。对单一样本而言,我们有损失函数𝑙(𝑦𝑖,𝐻𝑡−1(𝑥𝑖)+𝑓𝑡(𝑥𝑖)),其中𝑦𝑖是已知的常数,因此损失函数可以被看做是只有𝐻𝑡−1(𝑥𝑖)+𝑓𝑡(𝑥𝑖)一个自变量的函数,从而简写为𝑙(𝐻𝑡−1(𝑥𝑖)+𝑓𝑡(𝑥𝑖))。
根据一阶泰勒展开,已知:
令泰勒展开中的 x = 𝐻𝑡−1(𝑥𝑖)+𝑓𝑡(𝑥𝑖),令泰勒展开中的a = 𝐻𝑡−1(𝑥𝑖),则损失函数𝑙(𝐻𝑡−1(𝑥𝑖)+𝑓𝑡(𝑥𝑖))可以被表示为:
不难发现,该式子中𝐻𝑡−1(𝑥𝑖)是常数,因此第一部分𝑙(𝑦𝑖,𝐻𝑡−1(𝑥𝑖))也是一个常数。同时,第二部分由导数和𝑓𝑡组成,其中导数就是梯度,可以写作𝑔𝑖,所以式子可以化简为:
现在,如果要令𝑙最小,𝑓𝑡(𝑥𝑖)应该等于多少呢?回到我们最初的目标,找出令损失函数𝑙最小的𝑓𝑡值:
常数无法被最小化,因此继续化简:
现在,𝑔𝑡是包含了所有样本梯度的向量,𝑓𝑡(𝑥)ft(x)是包含了所有样本在𝑓𝑡ft上预测值的向量,两个向量对应位置元素相乘后求和,即表示为向量的内积,由尖括号⟨⟩⟨⟩表示。现在我们希望求解向量内积的最小值、并找出令向量内积最小的𝑓𝑡(𝑥)的取值,那就必须先找出𝑓𝑡(𝑥)的方向,再找出𝑓𝑡(𝑥)的大小。
方向
𝑓𝑡(𝑥)ft(x)的方向应该与𝑔𝑡完全相反。向量的内积⟨𝑔𝑡𝑓𝑡(𝑥)⟩=|𝑔𝑡||𝑓𝑡(𝑥)|𝑐𝑜𝑠(𝛼),其中前两项为两个向量的模长,𝛼是两个向量的夹角大小。模长默认为整数,因此当且仅当两个向量的方向完全相反,即夹角大小为180度时,𝑐𝑜𝑠(𝛼)的值为-1,才能保证两个向量的内积最小。假设向量 a = [1,2],向量b是与a的方向完全相反的向量。假设a和b等长,那向量b就是[-1,-2]。因此,与𝑔𝑡方向完全相反且等长的向量就是−𝑔𝑡,𝑓𝑡(𝑥)的方向也正是−𝑔𝑡的方向。
大小
对于向量a,除了[-1,-2]之外,还存在众多与其呈180度夹角、但大小不一致的向量,比如[-2,-4], [-0.5,-1],每一个都可以与向量a求得内积。并且我们会发现,当方向相反时,向量b中的元素绝对值越大,b的模长就越长,向量a与b的内积就越小。因此不难发现,⟨𝑔𝑡𝑓𝑡(𝑥)⟩是一个理论上可以取到无穷小的值,那我们的𝑓𝑡(𝑥)应该取什么大小呢?答案非常出乎意料:任何大小都无所谓。
无论𝑓𝑡(𝑥)的大小是多少,我们都可以通过步长𝜂η对其进行调整,只要能够影响𝐻(𝑥),我们就可以影响损失迭代过程中的常数的大小。因此在数学上来说,𝑓𝑡(𝑥)的大小可以是−𝑔𝑡的任意倍数,这一点在梯度下降中其实也是一样的。为了方便起见,同时为了与传统梯度下降过程一致,我们通常让𝑓𝑡(𝑥)就等于一倍的−𝑔𝑡,但也有不少论文令𝑓𝑡(𝑥)等于其他值的。在GBDT当中:
这就是我们让GBDT当中的弱评估器拟合伪残差/负梯度的根本原因。拟合负梯度其实为GBDT带来了非常多的特点——
首先,通过直接拟合负梯度,GBDT避免了从损失函数找“最优”的过程,即避免了上述证明中求解𝑓𝑡=𝑎𝑟𝑔𝑚𝑖𝑛𝑓∑𝑀𝑖=1𝑙(𝐻𝑡−1(𝑥𝑖)+𝑓𝑡(𝑥𝑖))的过程,从而大大地简化了计算。
其次,通过拟合负梯度,GBDT模拟了梯度下降的过程,由于结合了传统提升法Boosting与梯度下降,因此才被命名为梯度提升法(Gradient Boosting)。这个过程后来被称为函数空间上的梯度下降(Gradient Descent in Function Space),这是视角为Boosting算法的发展奠定了重要的基础。
最后,最重要的一点是,通过让弱评估器拟合负梯度,弱评估器上的结果可以直接影响损失函数、保证损失函数的降低,从而指向Boosting算法的根本目标:降低偏差。这一过程避免了许多在其他算法中需要详细讨论的问题:例如,每个弱评估器的权重𝜙是多少,以及弱评估器的置信度如何。
在AdaBoost算法当中,损失函数是“间接”影响弱评估器的建立,因此有的弱评估器能够降低损失函数,而有的弱评估器不能降低损失函数,因此要在求和之前,需要先求解弱评估器的置信度,然后再给与置信度高的评估器更高的权重,权重𝜙存在的根本意义是为了调节单一弱评估器对𝐻(𝑥)的贡献程度。但在GBDT当中,由于所有的弱评估器都是能够降低损失函数的,只不过降低的程度不同,因此就不再需要置信度/贡献度的衡量,因此就不再需要权重𝜙。
如果去翻阅早期梯度提升机器资料,我们会发现梯度提升树最开始是有求解权重的过程的。当拟合完伪残差之后,我们不直接求解令𝐿最小的𝑓𝑡值,而求解令整体𝐿L最小的权重𝜙:
求解迭代过程中弱评估器𝑓𝑡所需的权重𝜙𝑡,具体公式为:
与求解𝐻0的式子相似,上述式子表示,找出令∑𝑀𝑖=1𝐿(𝑦𝑖,𝐻𝑡−1(𝑥𝑖)+𝜙𝑡𝑓𝑡(𝑥𝑖))最小的常数𝜙𝑡值。同样,由于𝜙𝑡是针对全部样本计算出来的,因此𝑓𝑡上所有样本的预测值前的权重都是𝜙𝑡,并不存在针对于某一样本的权重。
接着,再根据求解出的权重𝜙𝑡迭代模型,具体来说:
在此基础上,Friedman甚至还提出了单独针对决策树的权重计算方法。但我们之前推导中讲解过,只要𝑓𝑡(𝑥)的方向正确,𝑓𝑡(𝑥)具体的取值并没有那么重要,毕竟可以通过学习率𝜂进行调整。在有𝜂、同时又不需要衡量弱评估器置信度的情况下,权重𝜙的意义就很小了。因此现在我们在实现梯度提升树时,已经不再使用带权重的版本,但带权重版本的数学过程与不带权重版本是高度类似的。