引言
XGBoost 自诞生以来,就屡次在国际机器学习大赛中摘取桂冠,风头隐隐有超过深度学习之势,堪称机器学习的"大杀器"。今天我们就来揭开 XGBoost 的神秘面纱,瞧瞧它的庐山真面目。
一、XGBoost 简介
XGBoost 跟上一篇的 AdaBoost 都属于集成学习的范畴,即利用多个弱学习器组成最终的强学习器。
说到 XGBoost 不得不提 GBDT(Gradient Boosting Decision Tree), XGBoost 是 GBDT 思想的一种实现,把速度和性能提升到了极致,所以XGBoost 的 “X” 是 “Extreme” 的意思。
二、XGBoost 的基本思路
回想决策树中,我们用不同的属性划分分支,最终每个样本都会到达叶子节点,所以叶子节点代表了样本的分类结果。
我们知道决策树不仅可以解决分类问题(叶子节点代表一个类别), 也可以解决回归问题(叶子节点代表样本的分值),但总体思路是一样的。
举个例子,比如我们要预测某个人是否喜欢玩游戏,可以建立如下这样一棵决策树。
这是一个回归问题,每个样本的分值等于其所在叶子节点的分值,分值正负表示是否喜欢玩游戏,分值大小表示喜欢玩游戏的程度。
上图中我们用 “年龄” 属性建立了决策树,得出了 “age < 20” 的人喜欢玩游戏程度为 “+2”, “age >= 20” 的人喜欢玩游戏的程度为 “-1”。
但是只用一个属性建立的决策树太片面了,所以我们又用 “是否每天用电脑” 建立了第二棵决策树。
最后,我们把样本在两棵决策树中分值加起来表示样本的最后分值,例如图中小朋友的分值为 2 + 0.9 = 2.9,老爷爷的分值为 -1 + (-0.9) = -1.9
总结一下,如果我们建立 K 棵树,每个样本 xi 的预测分值 y^i 为该样本在每棵决策树叶子节点的分值之和。
y^i=t=1∑Kft(xi)
其中 ft(xi) 表示样本 xi 第 t 棵树中所在叶子的分值。
好了,现在思考一下:每次添加一棵新树时,如何评判新添加的树对总体而言是好的还是不好的?
评判标准就是:样本 xi 的预测值 y^i 与样本真实值 yi 之间的差异是否越来越小了。
举个例子:在贷款额度评估模型中,样本小王的真实贷款额度为30万, 我们看看什么是好的建树过程。
- 第一棵树:小王所在叶子分值为 20万, 此时差异 ∣y^(1)−y∣=∣f1(x)−y∣=10万
- 第二棵树:小王所在叶子分值为 15万, 此时差异 ∣y^(2)−y∣=∣f1(x)+f2(x)−y∣=5万
- 第三棵树:小王所在叶子分值为 -3万, 此时差异 ∣y^(3)−y∣=∣f1(x)+f2(x)+f3(x)−y∣=2万
可以看出,每添加一棵树,样本分值的和 y^(t)=∑t=1Kft(x) 与真实值 y 之间的差异都在变小。换言之,每棵新树分值 ft(x) 的目标不是 y 本身, 而是为了弥补之前剩下的差异 ∣y^(t−1)−y∣ 。
可以看到,预测值 y^ 等于所有决策树的分值总和,每棵新树 t 都在上一次分值之和 y^(t−1) 的基础上加上自己的分值 ft,构成新的预测值。
书归正传,XGBoost 就是用的这种思路,每棵新树都在逐步弥补预测值与真实值之间的差异。
现在目标清楚了,问题在于 XGBoost 是如何添加新树使得新树可以逐步弥补样本差异的呢?
三、XGBoost 的原理探究
3.1 提出目标函数
现在再总结一下刚才的过程:
- 最初没有树,预测值 y^(0) 为 0
- 每添加一棵树 ft, 预测值 y^i(t) 为之前的预测值 y^i(t−1) 与 新树分值 ft(xi) 之和
这是一个递归加和的过程, 希望大家能够理解。
有了预测值 y^,想求 ft,需要给出我们的目标函数。
一方面,我们想让预测值 y^ 和 真实值 y 之间的差异 loss 最小,这里不同算法评估差异的方式不同。比如:
- 线性回归:loss(yi,y^i)=(yi−y^i)2
- 逻辑回归:loss(yi,y^i)=yiln(1+e−y^i)+(1−yi)ln(1+ey^i)
其他算法的 loss 计算方式可能又有不同,真正用哪种方式应该根据解决的具体问题而定,这里统称为 l(yi,y^i)。
另一方面,为了防止过拟合和决策树过于复杂,我们需要为每棵树添加惩罚项 Ω,常用的惩罚项有以下几种:
- L1 正则化:Ω=λ∣∣w∣∣1=λ∑i=1n∣wi∣
- L2 正则化:Ω=λ∣∣w∣∣2=21λ∑i=1nwi2
- 惩罚叶子节点个数 T: Ω=γT
其中
-
λ 和 γ 为用户可以调节的参数
- w 为权重,这里就是叶子的分值
- T 表示每棵决策树的叶子节点个数,叶子个数越多,决策树越复杂,所以需要惩罚
在 XGBoost 中我们同时使用 L2正则化和叶子节点数作为惩罚项 Ω:
Ω=γT+λ∣∣w∣∣2
所以我们的目标函数 Obj 等于每个样本的 loss 之和再加上每棵树的惩罚项之和。
Obj=i=1∑nl(yi,y^i)+t=1∑KΩ(ft)
因为在计算第 t 棵树时,前 t-1 棵树已经是已知常量,所以前 t-1 棵树的 Ω 也已经是常量,因为常量对目标函数求梯度是没有作用的, 所以这一部分可以统一表示为 constant,此时目标函数变为:
Obj=i=1∑nl(yi,y^i)+Ω(ft)+constant
接下来就是如何求解目标函数了。
3.2 求解目标函数
3.2.1 关于 ft
对于第 t 棵树,函数 ft(xi) 表示样本 xi 的分值,而样本的分值等于其所在叶子节点的分值,所以 ft 可以表示为每个叶子节点分值组成的向量,这里叶子的分值又叫做权重,用 wj 表示。如果有 T 个叶子节点, 则 ft:
ft={w1,w2,...,wT}
对于 ft(xi),如果 xi 落到了第 3 个叶子节点上,则 ft(xi)=w3
3.2.2 loss 部分泰勒展开
对于第 t 棵树,目标函数:
Obj(t)=i=1∑nl(yi,y^i(t))+Ω(ft)+constant
注意到 $ \hat y_i^{(t)} = \hat y_i^{(t-1)} + f_t(x_i)$, 所以
l(yi,y^i(t))=l(yi,y^i(t−1)+ft(xi))
注意这里 yi 是已知的常量,变量为 y^i(t−1)+ft(xi)
我们发现新树 ft 相当于在原来 t-1 棵树的基础上加了一个增量。
而对于此类加增量的问题 f(x+Δx),可以用泰勒展开来求解。
泰勒公式二次展开:
f(x+Δx)≈f(x)+f′(x)Δx+21f′′(x)Δx
把 l(yi,y^i(t−1)) 看成 f(x), 把 y^i(t−1) 看成 x, 把 ft(xi) 看成 Δx,同时用 g 表示一阶导数 f′(x), 用 h 表示二阶导数 f′′(x),目标函数变为:
Obj(t)=i=1∑n[l(yi,y^i(t−1))+gift(xi)+21hift2(xi)]+Ω(ft)+constant
又注意到目标函数中 l(yi,y^i(t−1)) 在算第 t 棵树时也是一个常量,可以归到 constant 中,所以目标函数变成
Obj(t)=i=1∑n[gift(xi)+21hift2(xi)]+Ω(ft)+constant
导数求解举例
以线性回归为例,loss 为,
loss(yi,y^i(t−1))=(yi−y^i(t−1))2
- 则一阶导数 gi=2(yi−y^i(t−1))
- 二阶导数 hi=2
- 对于其他loss, 同理可求 gi 和 hi
3.2.3 Ω 部分化简展开
目标函数的惩罚项部分
Ω(ft)==γT+λ∣∣wj∣∣2γT+21λi=1∑nwj2
对于图中的决策树,惩罚项为:
γ3+21λ(4+0.01+1)
3.2.4 从按样本统计到按叶子统计
因为每个样本的权重就是所在叶子节点的权重 w,那么所有样本的权重之和就等于每个叶子节点的权重乘以叶子所含样本的个数然后再求和。
i=1∑nwi=j=1∑Twj∗dj
其中 dj 表示第 j 个叶子节点所含的样本数。
同理, 样本的一阶导数 gi 和 二阶导数 hi 也只与其所在的叶子节点有关,所以有
i=1∑ngi=j=1∑Tgj∗dj=Gj
i=1∑nhi=j=1∑Thj∗dj=Hj
这里分别用 Gj 和 Hj 表示每个叶子节点上样本导数的和。
代入到目标函数:
Obj(t)=i=1∑n[gift(xi)+21hift2(xi)]+Ω(ft)+constant=i=1∑n[gift(xi)+21hift2(xi)]+γT+21λj=1∑Twj2+constant=j=1∑T[Gjwj+21Hjwj2+21λwj2]+γT+constant=j=1∑T[Gjwj+21(Hj+λ)wj2]+γT+constant
这里 ft(xi)=wj 因为样本的分值就是其所在的叶子节点的权重。
注意到常数项对目标函数求梯度没有作用,可以舍弃,最终的目标函数为:
Obj(t)=j=1∑T[Gjwj+21(Hj+λ)wj2]+γT
3.2.5 目标函数求解
目标函数中只有 wj 是变量,对目标函数求梯度计算最小值,可以得到最优的wj
wj=−Hj+λGj
有了 wj, 就有了 ft(xi),因为
ft={w1,w2,...,wT}
此时的最优目标函数为
Obj(t)=−21j=1∑THj+λGj2+γT
下图例子中,5 个样本分到了 3 个叶子节点上,计算出的目标函数越小越好。
3.3 树的内部如何分支
至此,我们第 t 棵为每个叶子节点求出了最佳的 wj,这有一个前提,在计算的过程中我们假设了第 t 棵树的叶子节点数为 T, 那么 T 如何确定,也就是第 t 棵树如何通过层层分支建立起来,以获得最优的叶子节点个数 T。
这里我们用了贪婪算法。
回想我们最初建立决策树时用熵的变化计算分支前后的信息增益来决定如何分支,这里我们也用类似的方式。
最优目标函数
Obj(t)=−21j=1∑THj+λGj2+γT
我们将分支前后最优目标函数的变化叫做增益。
对每一个叶节点进行分支,左分支最优目标函数:
ObjL=−21HL+λGL2+γTL
右分支最优目标函数:
ObjR=−21HR+λGR2+γTR
分支之前的叶子节点最优目标函数应该为:
Obj=−21H+λG2+γT=−21(HL+HR)+λ(GL+GR)2+γ(TL+TR−1)
用分支前的 Obj 减去分支后的 ObjL+ObjR 作为增益 Gain,如果 Gain > 0, 说明分支后最优目标函数减小了,那么就应该分支。
Gain=Obj−(ObjL+ObjR)=21[HL+λGL2+HR+λGR2−(HL+HR)+λ(GL+GR)2]−γ
现在有了要不要分支,那如果需要分的话从哪里分?比如属性有 n 个值,那么应该从哪里分呢?
首先需要将 n 个属性排序,然后尝试从任意两个相邻属性之间分支,有 n-1 种可能,我们依次计算这 n-1 种可能的每一种可能的 Gain, 最后从 Gain 最大处分支。
比如下图中我们对"年龄"属性进行分支,发现从 a 分支 Gain 最大,那就从 a 处分支。
四、XGBoost 一些优化点
- 快速停止
如果连续几次的 Gain 都小于等于 0,说明分支无益,可以提前停止。
- 步长收缩
通常不直接使用 y^(t)=y^(t−1)+ft(xi),而是采用
y^(t)=y^(t−1)+ϵft(xi), 其中 ϵ 叫做步长因子或收缩因子,通常为 0.1,这样做是为了不在每一步试图全局最优,而是给未来留下优化的空间,避免过拟合。
后记
XGBoost 就聊到这里了,作为大杀器的存在看起来有些复杂,我尽力用通俗的语言来描述、把所有过程推导细节都给出来,希望大家可以吃透,其实理解了思路,推导起来就简单多了。
XGBoost 是《决策树系列》的最后一篇文章,希望这个系列对大家有所帮助,哪怕只有一丢丢也很值得我为此开心。
好了,废话不多说了,从下一次开始我们将聊聊《神经网络系列》,从逻辑回归(LR)、深度神经网络(DNN)到卷积神经网络(CNN),以及每种网络的正向传播和反向传播的推导,希望到时候还能和您一起,在大数据茶馆边喝茶边聊天~
欢迎关注本人公众号《大数据茶馆》,用大白话畅聊大数据。
来的都是客,欢迎您常来坐坐~