//转自 《XGBoost 与 Boosted Tree | 我爱计算机》node
1. 前言
应 @龙星镖局 兄邀请写这篇文章。做为一个很是有效的机器学习方法,Boosted Tree是数据挖掘和机器学习中最经常使用的算法之一。由于它效果好,对于输入要求不敏感,每每是从统计学家到数据科学家必备的工具之一,它同时也是kaggle比赛冠军选手最经常使用的工具。最后,由于它的效果好,计算复杂度不高,也在工业界中有大量的应用。git
2. Boosted Tree的若干同义词
说到这里可能有人会问,为何我没有听过这个名字。这是由于Boosted Tree有各类马甲,好比GBDT, GBRT (gradient boosted regression tree),MART11,LambdaMART也是一种boosted tree的变种。网上有不少介绍Boosted tree的资料,不过大部分都是基于Friedman的最先一篇文章Greedy Function Approximation: A Gradient Boosting Machine的翻译。我的以为这不是最好最通常地介绍boosted tree的方式。而网上除了这个角度以外的介绍并很少。这篇文章是我我的对于boosted tree和gradient boosting 类算法的总结,其中不少材料来自于我TA UW机器学习时的一份讲义22。github
3. 有监督学习算法的逻辑组成
要讲boosted tree,要先从有监督学习讲起。在有监督学习里面有几个逻辑上的重要组成部件33,初略地分能够分为:模型,参数 和 目标函数。算法
i. 模型和参数
模型指给定输入xixi如何去预测 输出 yiyi。咱们比较常见的模型如线性模型(包括线性回归和logistic regression)采用了线性叠加的方式进行预测y^i=∑jwjxijy^i=∑jwjxij 。其实这里的预测yy能够有不一样的解释,好比咱们能够用它来做为回归目标的输出,或者进行sigmoid 变换获得几率,或者做为排序的指标等。而一个线性模型根据yy的解释不一样(以及设计对应的目标函数)用到回归,分类或排序等场景。参数指咱们须要学习的东西,在线性模型中,参数指咱们的线性系数ww。机器学习
ii. 目标函数:损失 + 正则
模型和参数自己指定了给定输入咱们如何作预测,可是没有告诉咱们如何去寻找一个比较好的参数,这个时候就须要目标函数登场了。通常的目标函数包含下面两项分布式
常见的偏差函数有L=∑nil(yi,y^i)L=∑inl(yi,y^i) 好比平方偏差 l(yi,y^i)=(yi−y^i)2l(yi,y^i)=(yi−y^i)2 ,logistic偏差函数(l(yi,y^i)=yiln(1+e−y^i)+(1−yi)ln(1+ey^i)l(yi,y^i)=yiln(1+e−y^i)+(1−yi)ln(1+ey^i) )等。而对于线性模型常见的正则化项有L2L2正则和L1L1正则。这样目标函数的设计来自于统计学习里面的一个重要概念叫作Bias-variance tradeoff44。比较感性的理解,Bias能够理解为假设咱们有无限多数据的时候,能够训练出最好的模型所拿到的偏差。而Variance是由于咱们只有有限数据,其中随机性带来的偏差。目标中偏差函数鼓励咱们的模型尽可能去拟合训练数据,这样相对来讲最后的模型会有比较少的 bias。而正则化项则鼓励更加简单的模型。由于当模型简单以后,有限数据拟合出来结果的随机性比较小,不容易过拟合,使得最后模型的预测更加稳定。ide
iii. 优化算法
讲了这么多有监督学习的基本概念,为何要讲这些呢? 是由于这几部分包含了机器学习的主要成分,也是机器学习工具设计中划分模块比较有效的办法。其实这几部分以外,还有一个优化算法,就是给定目标函数以后怎么学的问题。之因此我没有讲优化算法,是由于这是你们每每比较熟悉的“机器学习的部分”。而有时候咱们每每只知道“优化算法”,而没有仔细考虑目标函数的设计的问题,比较常见的例子如决策树的学习,你们知道的算法是每一步去优化gini entropy,而后剪枝,可是没有考虑到后面的目标是什么。函数
4. Boosted Tree工具
i. 基学习器:分类和回归树(CART)
话题回到boosted tree,咱们也是从这几个方面开始讲,首先讲模型。Boosted tree 最基本的组成部分叫作回归树(regression tree),也叫作CART55。学习
上面就是一个CART的例子。CART会把输入根据输入的属性分配到各个叶子节点,而每一个叶子节点上面都会对应一个实数分数。上面的例子是一个预测一我的是否会喜欢电脑游戏的 CART,你能够把叶子的分数理解为有多可能这我的喜欢电脑游戏。有人可能会问它和decision tree的关系,其实咱们能够简单地把它理解为decision tree的一个扩展。从简单的类标到分数以后,咱们能够作不少事情,如几率预测,排序。
ii. Tree Ensemble
一个CART每每过于简单没法有效地预测,所以一个更增强力的模型叫作tree ensemble。
在上面的例子中,咱们用两棵树来进行预测。咱们对于每一个样本的预测结果就是每棵树预测分数的和。到这里,咱们的模型就介绍完毕了。如今问题来了,咱们常见的随机森林和boosted tree和tree ensemble有什么关系呢?若是你仔细的思考,你会发现RF和boosted tree的模型都是tree ensemble,只是构造(学习)模型参数的方法不一样。第二个问题:在这个模型中的“参数”是什么。在tree ensemble中,参数对应了树的结构,以及每一个叶子节点上面的预测分数。
最后一个问题固然是如何学习这些参数。在这一部分,答案可能千奇百怪,可是最标准的答案始终是一个:定义合理的目标函数,而后去尝试优化这个目标函数。在这里我要多说一句,由于决策树学习每每充满了heuristic。 如先优化吉尼系数,而后再剪枝啦,限制最大深度,等等。其实这些heuristic的背后每每隐含了一个目标函数,而理解目标函数自己也有利于咱们设计学习算法,这个会在后面具体展开。
对于tree ensemble,咱们能够比较严格的把咱们的模型写成是:
y^i=∑Kk=1fk(xi),fk∈Fy^i=∑k=1Kfk(xi),fk∈F
其中每一个ff是一个在函数空间66(FF)里面的函数,而FF对应了全部regression tree的集合。咱们设计的目标函数也须要遵循前面的主要原则,包含两部分
Obj(Θ)=∑nil(yi,y^i)+∑Kk=1Ω(fk)Obj(Θ)=∑inl(yi,y^i)+∑k=1KΩ(fk)
iii. 模型学习:additive training
其中第一部分是训练偏差,也就是你们相对比较熟悉的如平方偏差, logistic loss等。而第二部分是每棵树的复杂度的和。这个在后面会继续讲到。由于如今咱们的参数能够认为是在一个函数空间里面,咱们不能采用传统的如SGD之类的算法来学习咱们的模型,所以咱们会采用一种叫作additive training的方式(另外,在我我的的理解里面77,boosting就是指additive training的意思)。每一次保留原来的模型不变,加入一个新的函数$f$到咱们的模型中。
如今还剩下一个问题,咱们如何选择每一轮加入什么ff呢?答案是很是直接的,选取一个ff来使得咱们的目标函数尽可能最大地下降88。
这个公式可能有些过于抽象,咱们能够考虑当ll是平方偏差的状况。这个时候咱们的目标能够被写成下面这样的二次函数99:
更加通常的,对于不是平方偏差的状况,咱们会采用以下的泰勒展开近似来定义一个近似的目标函数,方便咱们进行这一步的计算。
当咱们把常数项移除以后,咱们会发现以下一个比较统一的目标函数。这一个目标函数有一个很是明显的特色,它只依赖于每一个数据点的在偏差函数上的一阶导数和二阶导数1010。有人可能会问,这个材料彷佛比咱们以前学过的决策树学习难懂。为何要花这么多力气来作推导呢?
由于这样作使得咱们能够很清楚地理解整个目标是什么,而且一步一步推导出如何进行树的学习。这一个抽象的形式对于实现机器学习工具也是很是有帮助的。传统的GBDT可能你们能够理解如优化平法aa残差,可是这样一个形式包含可全部能够求导的目标函数。也就是说有了这个形式,咱们写出来的代码能够用来求解包括回归,分类和排序的各类问题,正式的推导可使得机器学习的工具更加通常。
iv. 树的复杂度
到目前为止咱们讨论了目标函数中训练偏差的部分。接下来咱们讨论如何定义树的复杂度。咱们先对于f的定义作一下细化,把树拆分红结构部分qq和叶子权重部分ww。下图是一个具体的例子。结构函数qq把输入映射到叶子的索引号上面去,而ww给定了每一个索引号对应的叶子分数是什么。
当咱们给定了如上定义以后,咱们能够定义一棵树的复杂度以下。这个复杂度包含了一棵树里面节点的个数,以及每一个树叶子节点上面输出分数的$L2$模平方。固然这不是惟一的一种定义方式,不过这必定义方式学习出的树效果通常都比较不错。下图还给出了复杂度计算的一个例子。
v. 关键步骤
接下来是最关键的一步1111,在这种新的定义下,咱们能够把目标函数进行以下改写,其中I被定义为每一个叶子上面样本集合 IjIj = { i|q(xi)=ji|q(xi)=j}
这一个目标包含了TT个相互独立的单变量二次函数。咱们能够定义
Gj=∑i∈IjgiHj=∑i∈IjhiGj=∑i∈IjgiHj=∑i∈Ijhi
那么这个目标函数能够进一步改写成以下的形式,假设咱们已经知道树的结构qq,咱们能够经过这个目标函数来求解出最好的ww,以及最好的ww对应的目标函数最大的增益
Obj(t)=∑Tj=1[(∑i∈Ijgi)wj+12(∑i∈Ijhi+λ)w2j]+γT=∑Tj=1[Gjwj+12(Hj+λ)w2j]+γTObj(t)=∑j=1T[(∑i∈Ijgi)wj+12(∑i∈Ijhi+λ)wj2]+γT=∑j=1T[Gjwj+12(Hj+λ)wj2]+γT
这两个的结果对应以下,左边是最好的ww,右边是这个ww对应的目标函数的值。到这里你们可能会以为这个推导略复杂。其实这里只涉及到了如何求一个一维二次函数的最小值的问题1212。若是以为没有理解不妨再仔细琢磨一下
w∗j=−GjHj+λObj=−12∑Tj=1G2jHj+λ+γTwj∗=−GjHj+λObj=−12∑j=1TGj2Hj+λ+γT
vi. 打分函数计算举例
Obj表明了当咱们指定一个树的结构的时候,咱们在目标上面最多减小多少。咱们能够把它叫作结构分数(structure score)。你能够认为这个就是相似吉尼系数同样更加通常的对于树结构进行打分的函数。下面是一个具体的打分函数计算的例子
vii. 枚举全部不一样树结构的贪心法
因此咱们的算法也很简单,咱们不断地枚举不一样树的结构,利用这个打分函数来寻找出一个最优结构的树,加入到咱们的模型中,再重复这样的操做。不过枚举全部树结构这个操做不太可行,因此经常使用的方法是贪心法,每一次尝试去对已有的叶子加入一个分割。对于一个具体的分割方案,咱们能够得到的增益能够由以下公式计算
对于每次扩展,咱们仍是要枚举全部可能的分割方案,如何高效地枚举全部的分割呢?我假设咱们要枚举全部 x<ax<a 这样的条件,对于某个特定的分割aa咱们要计算aa左边和右边的导数和。
咱们能够发现对于全部的aa,咱们只要作一遍从左到右的扫描就能够枚举出全部分割的梯度和GLGL和GRGR。而后用上面的公式计算每一个分割方案的分数就能够了。
观察这个目标函数,你们会发现第二个值得注意的事情就是引入分割不必定会使得状况变好,由于咱们有一个引入新叶子的惩罚项。优化这个目标对应了树的剪枝, 当引入的分割带来的增益小于一个阀值的时候,咱们能够剪掉这个分割。你们能够发现,当咱们正式地推导目标的时候,像计算分数和剪枝这样的策略都会天然地出现,而再也不是一种由于heuristic而进行的操做了。
讲到这里文章进入了尾声,虽然有些长,但愿对你们有所帮助,这篇文章介绍了如何经过目标函数优化的方法比较严格地推导出boosted tree的学习。由于有这样通常的推导,获得的算法能够直接应用到回归,分类排序等各个应用场景中去。
5. 尾声:xgboost
这篇文章讲的全部推导和技术都指导了xgboost https://github.com/dmlc/xgboost 的设计。xgboost是大规模并行boosted tree的工具,它是目前最快最好的开源boosted tree工具包,比常见的工具包快10倍以上。在数据科学方面,有大量kaggle选手选用它进行数据挖掘比赛,其中包括两个以上kaggle比赛的夺冠方案。在工业界规模方面,xgboost的分布式版本有普遍的可移植性,支持在YARN, MPI, Sungrid Engine等各个平台上面运行,而且保留了单机并行版本的各类优化,使得它能够很好地解决于工业界规模的问题。有兴趣的同窗能够尝试使用一下,也欢迎贡献代码。
注解和连接: