本文主要是介绍CART,GBDT,XGBoost,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
回归树=》GBDT=》XGBoost
回归树(Reression Decision Tree)
一个例子
训练数据中臂长,年龄,体重为特征变量X,身高为标签值Y,下面开始种树
臂长(m) | 年龄(岁) | 体重(kg) | 身高(m)(标签值) |
---|---|---|---|
0.5 | 5 | 20 | 1.1 |
0.7 | 7 | 30 | 1.3 |
0.9 | 21 | 70 | 1.7 |
回归树算法流程
补充:
梯度提升树( Gradient Boosting Decision Tree,GBDT)
GBDT中的树都是回归树,不是分类树,无论是处理回归问题还是二分类以及多分类,这点对理解GBDT相当重要(尽管GBDT调整后也可用于分类但不代表GBDT的树是分类树)
为什么不用分类树呢?因为GBDT每次迭代要拟合的是梯度值,是连续值所以要用回归树。
因为单棵 树的能力有限,GBDT 通过构造多棵回归树去预测,每棵树都预测前面所有树预测之后的残差(损失函数为平方误差时),因此残差越来越小,预测的精度也就越来越高。
具体形式可以如下表示:
一个例子:
年龄预测,简单起见训练集只有4个人,A,B,C,D,他们的年龄分别是14,16,24,26。其中A、B分别是高一和高三学生;C,D分别是应届毕业生和工作两年的员工。如果是用一棵传统的回归决策树来训练,会得到如下图1所示结果:
现在我们使用GBDT来做这件事,由于数据太少,我们限定叶子节点做多有两个,即每棵树都只有一个分枝,并且限定只学两棵树。我们会得到如下图2所示结果:
在第一棵树分枝和图1一样,由于A,B年龄较为相近,C,D年龄较为相近,他们被分为两拨,每拨用平均年龄作为预测值。此时计算残差(残差的意思就是: A的预测值 + A的残差 = A的实际值),所以A的残差就是16-15=1(注意,A的预测值是指前面所有树累加的和,这里前面只有一棵树所以直接是15,如果还有树则需要都累加起来作为A的预测值)。进而得到A,B,C,D的残差分别为-1,1,-1,1。然后我们拿残差替代A,B,C,D的原值,到第二棵树去学习,如果我们的预测值和它们的残差相等,则只需把第二棵树的结论累加到第一棵树上就能得到真实年龄了。这里的数据显然是我可以做的,第二棵树只有两个值1和-1,直接分成两个节点。此时所有人的残差都是0,即每个人都得到了真实的预测值。
换句话说,现在A,B,C,D的预测值都和真实年龄一致了。Perfect!:
A: 14岁高一学生,购物较少,经常问学长问题;预测年龄A = 15 – 1 = 14
B: 16岁高三学生;购物较少,经常被学弟问问题;预测年龄B = 15 + 1 = 16
C: 24岁应届毕业生;购物较多,经常问师兄问题;预测年龄C = 25 – 1 = 24
D: 26岁工作两年员工;购物较多,经常被师弟问问题;预测年龄D = 25 + 1 = 26
那么哪里体现了Gradient呢?其实回到第一棵树结束时想一想,无论此时的cost function是什么,是均方差还是均差,只要它以误差作为衡量标准,残差向量(-1, 1, -1, 1)都是它的全局最优方向,这就是Gradient。
问题:既然图1和图2 最终效果相同,为何还需要GBDT呢?
答案是过拟合。过拟合是指为了让训练集精度更高,学到了很多”仅在训练集上成立的规律“,导致换一个数据集当前规律就不适用了。其实只要允许一棵树的叶子节点足够多,训练集总是能训练到100%准确率的(大不了最后一个叶子上只有一个instance)。在训练精度和实际精度(或测试精度)之间,后者才是我们想要真正得到的。
我们发现图1为了达到100%精度使用了3个feature(上网时长、时段、网购金额),其中分枝“上网时长>1.1h” 很显然已经过拟合了,这个数据集上A,B也许恰好A每天上网1.09h, B上网1.05小时,但用上网时间是不是>1.1小时来判断所有人的年龄很显然是有悖常识的;
相对来说图2的boosting虽然用了两棵树 ,但其实只用了2个feature就搞定了,后一个feature是问答比例,显然图2的依据更靠谱。(当然,这里是LZ故意做的数据,所以才能靠谱得如此狗血。实际中靠谱不靠谱总是相对的) Boosting的最大好处在于,每一步的残差计算其实变相地增大了分错instance的权重,而已经分对的instance则都趋向于0。这样后面的树就能越来越专注那些前面被分错的instance。
补充:
GBDT的思想就是在每次迭代中拟合残差来学习一个弱学习器。而残差的方向即为我们全局最优的方向。但是当损失函数不为平方损失时,我们该如何拟合弱学习器呢?
大牛Friedman提出使用损失函数负梯度的方向代替残差方向,我们称损失函数负梯度为伪残差。而伪残差的方向即为我们局部最优的方向。所以在GBDT中,当损失函数不为平方损失时,用每次迭代的局部最优方向代替全局最优方向。
我们可以证明,当损失函数为平方损失时,叶节点中使平方损失误差达到最小值的是叶节点中所有值的均值;而当损失函数为绝对值损失时,叶节点中使绝对损失误差达到最小值的是叶节点中所有值的中位数。相关证明将在最后的附录中给出
为什么基于残差的gbdt不是一个好的选择
基于残差的gbdt在解决回归问题上不算是一个好的选择,一个比较明显的缺点就是对异常值过于敏感。我们来看一个例子:
很明显后续的模型会对第4个值关注过多,这不是一种好的现象,所以一般回归类的损失函数会用绝对损失或者huber损失函数来代替平方损失函数:
GBDT的损失函数
在sklearn中梯度提升回归树有四种可选的损失函数,分别为’ls:平方损失’,‘lad:绝对损失’,‘huber:huber损失’,‘quantile:分位数损失’;而在sklearn中梯度提升分类树有两种可选的损失函数,一种是‘exponential:指数损失’,一种是‘deviance:对数损失’。下面分别介绍这几种损失函数。
梯度提升回归树损失函数介绍:
GBDT回归算法描述
平方损失GBDT算法描述
绝对损失GBDT算法描述
huber损失GBDT算法描述
GBDT和XGBoost的区别
-
xgboost损失函数是用泰勒展式二项逼近,而不是像gbdt里的就是一阶导数
-
对树的结构进行了正则化约束,防止模型过度复杂,降低了过拟合的可能性
-
节点分裂的方式不同,gbdt利用最小平方误差作为分裂准则,xgboost是经过优化推导后的
XGBoost
一个例子:
我们要预测一家人对电子游戏的喜好程度:
训练出了2棵树tree1和tree2,类似之前gbdt的原理,两棵树的结论累加起来便是最终的结论,所以小孩的预测分数就是两棵树中小孩所落到的结点的分数相加:2 + 0.9 = 2.9。爷爷的预测分数同理:-1 + (-0.9)= -1.9。具体如下图所示:
损失函数
恩,你可能要拍案而起了,惊呼,这不是跟上文介绍的gbdt乃异曲同工么?
事实上,如果不考虑工程实现、解决问题上的一些差异,xgboost与gbdt比较大的不同就是目标函数的定义。xgboost的目标函数如下图所示:
其中
• 红色箭头所指向的L 即为损失函数(比如平方损失函数或logistic损失函数)
• 红色方框所框起来的是正则项(包括L1正则、L2正则)
T表示叶子节点的个数,w表示叶子节点的分数。直观上看,目标要求预测误差尽量小,且叶子节点T尽量少(γ控制叶子结点的个数),节点数值w尽量不极端(λ控制叶子节点的分数不会过大),防止过拟合。
• 红色圆圈所圈起来的为常数项
• 对于f(x),xgboost利用泰勒展开三项,做一个近似
我们可以很清晰地看到,最终的目标函数只依赖于每个数据点的在误差函数上的一阶导数和二阶导数。
考虑到我们的第t 颗回归树是根据前面的t-1颗回归树的残差得来的,相当于t-1颗树的值是已知的。换句话说,对目标函数的优化不影响,可以直接去掉,且常数项也可以移除,从而得到如下一个比较统一的目标函数。
我们可以把之前的目标函数进行如下变形:
其中被定义为每个叶节点j上面样本下标的集合,g是一阶导数,h是二阶导数。
接着,我们可以定义:
分裂节点
Obj代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少。我们可以把它叫做结构分数(structure score),可以认为这个就是类似基尼系数一样更加一般的对于树结构进行打分的函数。
现在的情况是只要知道树的结构,就能得到一个该结构下的最好分数,那如何确定树的结构呢?
从树深度0开始,每一节点都遍历所有的特征,比如年龄、性别等等,然后对于某个特征,先按照该特征里的值进行排序,然后线性扫描该特征进而确定最好的分割点,最后对所有特征进行分割后,我们选择所谓的增益Gain最高的那个特征,而Gain如何计算呢?
换句话说,对于所有的特征x,我们只要做一遍从左到右的扫描就可以枚举出所有分割的梯度和GL和GR。然后用计算Gain的公式计算每个分割方案的分数就可以了。
然后后续则依然按照这种划分方法继续第二层、第三层、第四层、第N层的分裂。
第二个值得注意的事情就是引入分割不一定会使得情况变好,所以我们有一个引入新叶子的惩罚项。优化这个目标对应了树的剪枝, 当引入的分割带来的增益小于一个阀值γ的时候,则忽略这个分割。
换句话说,当引入某项分割,结果分割之后得到的分数 - 不分割得到的分数得到的值太小(比如小于我们的最低期望阀值γ),但却因此得到的复杂度过高,则相当于得不偿失,不如不分割。即做某个动作带来的好处比因此带来的坏处大不了太多,则为避免复杂 多一事不如少一事的态度,不如不做。
得情况变好,所以我们有一个引入新叶子的惩罚项。优化这个目标对应了树的剪枝, 当引入的分割带来的增益小于一个阀值γ的时候,则忽略这个分割。
换句话说,当引入某项分割,结果分割之后得到的分数 - 不分割得到的分数得到的值太小(比如小于我们的最低期望阀值γ),但却因此得到的复杂度过高,则相当于得不偿失,不如不分割。即做某个动作带来的好处比因此带来的坏处大不了太多,则为避免复杂 多一事不如少一事的态度,不如不做。
附录
这篇关于CART,GBDT,XGBoost的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!