前言
本文总结xgboost,我们的举例大部分都是针对二分类场景,毕竟实际工作中分类的场景是比较多的,我们就顺着分类场景这条线讲。大佬们有问题的欢迎留言交流和指教,一起学习吧。
☆☆☆ 算法目录导航页,包含基础算法、高级算法、机器学习算法等☆☆☆
一、决策树
决策树是树模型中最简单最基础的,其他的树模型都是在这基础衍生出来的,所以先讲下什么是决策树。顾名思义,决策树首先是一棵树的样子,树如何生长即如何分裂,即为决策,它可以解决分类问题,最终的分类结果是取决于落到的那个叶子节点。
然后,我们从一个实际问题入手了解决策树的应用: 老师让你统计下班里男女的数量,你会怎么区分男女?很快,你会审视每一个学生,头发长的是女生,头发短的是男生,通过 头发长短 迅速将学生分开。但是,头发长短 也不一定准确吧,很多男生也留长发啊。OK,那就用有无喉结来划分吧,好像有的女生也有喉结,这可怎么办呢,纠结死了,到底是用‘有无喉结’还是‘头发长短’来划分呢?
此时,我们如何判断哪种划分是最优的?而且需要量化它。用信息熵来量化到底是选【头发长短】还是【有无喉结】。
什么是熵(Entropy)?就是不纯度的一种度量指标。假定当前样本集合D中第k类样本所占的比例为p_k,则D的信息熵定义为:
E n t ( D ) = − ∑ k = 1 ∣ Y ∣ p k log 2 p k Ent(D)=-\sum_{k=1}^{|\mathcal{Y}|} p_{k} \log _{2} p_{k} Ent(D)=−k=1∑∣Y∣pklog2pk
信息熵约定:若p=0,则plogp=0,Ent(D)最小值为0,最大值为log|y|。Ent(D)的值越小,纯度越高。
那具体怎么使用这个熵呢?我们用gain增益,注意:这个增益在整个树模型中都通用,针对熵这种量化指标可以这样计算增益: g a i n ( D , 长 短 ) = Ent ( D ) − ∑ y = 1 n ∣ D v ∣ ∣ D ∣ Ent ( D v ) gain(D,长短) = \operatorname{Ent}(D)-\sum_{y=1}^{n} \frac{\left|D^{v}\right|}{|D|} \operatorname{Ent}\left(D^{v}\right) gain(D,长短)=Ent(D)−y=1∑n∣D∣∣Dv∣Ent(Dv)
其中,D为总的样本集合,Ent(D)为D的熵,D_v为根据某一个特征分桶后的子样本集合,Ent(D_v)为子样本集合的熵。
举例:
计算过程:
① 划分之前, 正样本数3,负样本数5,则划分前的信息熵为: E n t ( D ) = − 3 8 log 2 3 8 = 0.1597 Ent(D) = -\frac{3}{8} \log _{2} \frac{3}{8}=0.1597 Ent(D)=−83log283=0.1597
② 根据头发长短划分:
头发长的子集{1,2,3,7,8}共5个样本, 其中正样本为1个,则: E n t ( D l o n g ) = − 1 5 log 2 1 5 = 0.1398 Ent(D^{long}) = -\frac{1}{5} \log _{2} \frac{1}{5}=0.1398 Ent(Dlong)=−51log251=0.1398
头发短的子集{4,5,6}共3个样本, 其中正样本为2个,则: E n t ( D s h o r t ) = − 2 3 log 2 2 3 = 0.1174 Ent(D^{short}) = -\frac{2}{3} \log _{2} \frac{2}{3}=0.1174 Ent(Dshort)=−32log232=0.1174
头发长的比例为5/8,短的比例3/8,则对上面两个熵加权求和得: E n t ( D s h o r t + l o n g ) = 5 8 ∗ E n t ( D l o n g ) + 3 8 ∗ E n t ( D s h o r t ) = 0.1314 Ent(D^{short+long}) = \frac{5}{8} *Ent(D^{long}) +\frac{3}{8} * Ent(D^{short}) =0.1314 Ent(Dshort+long)=85∗Ent(Dlong)+83∗Ent(Dshort)=0.1314
则增益为: g a i n ( D , 长 短 ) = Ent ( D ) − E n t ( D s h o r t + l o n g ) = 0.0283 gain(D,长短) = \operatorname{Ent}(D)-Ent(D^{short+long}) = 0.0283 gain(D,长短)=Ent(D)−Ent(Dshort+long)=0.0283
③ 同理,我们可以计算 gain(D,喉结),然后选择gain较大的去划分即可。OK,通透了。
决策树的分裂算法通常有3种,基于信息熵的ID3和C4.5, 基于基尼指数的CART:
① ID3
上面我们计算过程就是ID3算法,基于信息增益。
缺点:优先选择有较多属性值的特征。比如学号,45个学生,把学号考虑进来,最终会有45个桶(1-45号),新来一个同学是46号则无法通过模型来判断46号是什么样的学生,所以泛化能力差,模型学的过拟合了。
② C4.5
这个算法是基于信息增益率,是对ID3的改进。
C4.5优点:产生的分类规则易于理解,准确率较高。
C4.5缺点:在构造树的过程中,需要对数据集进行多次的顺序扫描和排序,因而导致算法的低效。此外,C4.5只适合于能够驻留于内存的数据集,当训练集大得无法在内存容纳时程序无法运行。
C4.5公式: g a i n r = Gain ( D , A ) Splitlnformation ( D , A ) gain_{r}= \frac{\operatorname{Gain}(D, A)}{\text {Splitlnformation}(D, A)} gainr=Splitlnformation(D,A)Gain(D,A)
其中, Splitlnformation ( D , A ) = − ∑ i = 1 n ∣ D v ∣ ∣ D ∣ log 2 ∣ D v ∣ ∣ D ∣ \text {Splitlnformation}(D, A)= -\sum_{i=1}^{n} \frac{\left|D^{v}\right|}{|D|} \log _{2} \frac{\left|D^{v}\right|}{|D|} Splitlnformation(D,A)=−i=1∑n∣D∣∣Dv∣log2∣D∣∣Dv∣
③ CART,分类回归树,基于基尼指数,最小化不纯度,而不是最大化信息增益。也就是说CART是朝着基尼系数减小的方向生长,而ID3和C4.5是朝着增益大的方向生长。不管是分类还是回归,原理都差不多,只不过分裂时选择损失不同,分类可选某种增益,回归可选MSE等。
另外,xgboost的基学习器就是CART。
二、集成学习 ensemble learning
集成学习 (ensemble learning) 是通过构建并结合多个基学习器来完成学习任务 。一般结构为:
① 产生一组基学习器;
② 通过某种策略将它们结合起来;
集成学习通过将多个学习器进行组合,通常可以获得比单一学习器显著优越的泛化性能。
常见的方法有:Bagging、Boosting、Stacking,除此之外还有Voting、Averaging、Blending。
① Voting
Voting是一种很思想很简单的集成策略。主要应用于分类问题
。
几种常见的Voting方法有:
1、绝对多数投票法(majority voting):也就是若某个标记的得票超过了半数,则预测为该标记,否则拒绝预测。
2、相对多数投票法(plurality voting): 即预测为得票最多的标记,若同时有多个标记,那么从中随机选取一个。
3、加权投票法(weighted voting)即为不同的标记赋予不同的权重,最后再按照相对多数投票法来预测。
② Averaging
Averaging即是一种取平均的思想,从整体的角度上来提高预测的鲁棒性,通常用于回归问题
。
几种常见的Averaging方法:
1、简单平均法(simple averaging)
2、加权平均法(weighted averaging)
③ Bagging
代表为RF。
具体实现:对样本进行有放回的随机采样,基学习器间并行计算,相互独立,各自输出一个结果,最后通过投票的方式输出最终结果。目的是减小方差,减小波动性,使得模型更稳定,泛化能力更强。调参的时候要考虑模型的精度。
④ Boosting 提升
代表为AdaBoost、GBDT、XGBoost 等。
每一步生成一个弱模型(基学习器),加权累加到总模型中。其实上面我们已经说过了,集成学习就两点,1是先生成多个基学习器,2再通过某种策略组合起来。所以,boosting区别于bagging的是多个基学习器之间有依赖关系,它是串行的。
AdaBoost:Adaptive Boosting,自适应增强,很简单,就是通过样本加权的方式,达到错误率小或者预先设置的迭代次数。每一步迭代中将分错的样本权重增强。
GBDT、xgboost: 与AdaBoost不同,GBDT每一次的计算都是为了减少上一次的残差,进而在残差减少(负梯度)的方向上建立一个新的模型。
算法过程举例(adaboost): ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/439c023e0df570042010bfe923b196af.png)
boosting Adaboost详细求解过程参考
⑤ Stacking
Stacking的策略为将t-1个基分类器的输出y作为第t个基分类器的输入x。它是一种分层的结构,如下图:
与boosting和bagging的区别:boosting和bagging中一般都为相同的模型,比如基学习器都是CART。而Stacking是不同的模型,比如M2可以是xgboost、KNN、SVM等,M3可以是LR等。
⑥ Blending
blending和stacking类似,我的理解是Stacking是堆叠,Blending是混合,如下图:
同样的,图中M可以是KNN、SVM等。
三、xgboost直观理解
xgboost的算法通俗来说就是不断地添加树,不断地进行特征分裂来生长一棵树,每次添加一个树,其实是学习一个新函数,去拟合上次预测的残差。
xgb预测结果为每棵树预测得分的累加和,如下图:
四、xgboost如何建模( 定义目标函数 )
xgboost到底要做什么呢?上面已经提过: 每次添加一颗树( f t f_t ft), 然后去拟合前一棵树预测的残差。
已知第t-1颗树,求第t颗树的结构
f
t
f_t
ft,目标函数(可看成【单颗树的结构分数】,模型预测的最终结果要把每棵树的这个得分加起来
)可写成:
J ( f t ) = S t r u c t u r e _ S c o r e ( f t ) = ∑ i = 1 n L ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + Ω ( f t ) + C ( 式 ① ) J\left(f_{t}\right)=Structure\_Score(f_t)=\sum_{i=1}^{n} L\left(y_{i}, \hat{y}_{i}^{(t-1)}+f_{t}\left(x_{i}\right)\right)+\Omega\left(f_{t}\right)+\text {C } \ (式①) J(ft)=Structure_Score(ft)=i=1∑nL(yi,y^i(t−1)+ft(xi))+Ω(ft)+C (式①)
其中,i为第i个样本,L为某种损失函数,
y
i
y_i
yi为实际值,
y
^
i
(
t
−
1
)
\hat{y}_{i}^{(t-1)}
y^i(t−1)为第t-1颗树的预测值,
f
t
(
x
i
)
f_t(x_i)
ft(xi)为第t颗树的样子,
Ω
(
f
t
)
Ω(f_t)
Ω(ft)为正则项,
f
t
f_t
ft,C为常数项。注意:这里不同于其他算法的地方在于参数是函数空间,即把函数f当成另一个函数的参数。因此,目标函数其实是参数为
f
t
f_t
ft的函数。
五、目标函数简化
此时的参数是指函数空间 f t f_t ft,我们先化简我们的目标函数:
① 利用泰勒二阶展开近似原目标函数
泰勒二阶展开: f ( x + Δ x ) ≈ f ( x ) + f ′ ( x ) Δ x + 1 2 f ′ ′ ( x ) Δ x 2 f(x+\Delta x) \approx f(x)+f^{\prime}(x) \Delta x+\frac{1}{2} f^{\prime \prime}(x) \Delta x^{2} f(x+Δx)≈f(x)+f′(x)Δx+21f′′(x)Δx2
我们令:
g i = ∂ L ( y i , y ^ i ( t − 1 ) ) ∂ y ^ i ( t − 1 ) h i = ∂ 2 L ( y i , y ^ i ( t − 1 ) ) ∂ y ^ i ( t − 1 ) g_{i}=\frac{\partial L\left(y_{i}, \hat{y}_{i}^{(t-1)}\right)}{\partial \hat{y}_{i}^{(t-1)}} \quad h_{i}=\frac{\partial^{2} L\left(y_{i}, \hat{y}_{i}^{(t-1)}\right)}{\partial \hat{y}_{i}^{(t-1)}} gi=∂y^i(t−1)∂L(yi,y^i(t−1))hi=∂y^i(t−1)∂2L(yi,y^i(t−1))
利用泰勒二阶展开近似目标函数得:
J ( f t ) ≈ ∑ i = 1 n [ L ( y i , y ^ i ( t − 1 ) ) + g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) + C J\left(f_{t}\right) \approx \sum_{i=1}^{n}\left[L\left(y_{i}, \hat{y}_{i}^{(t-1)}\right)+g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right)+C J(ft)≈i=1∑n[L(yi,y^i(t−1))+gift(xi)+21hift2(xi)]+Ω(ft)+C
因为,第t-1颗树的预测值是已知的,所以 y ^ i ( t − 1 ) \hat{y}_{i}^{(t-1)} y^i(t−1)是已知的,则 L ( y i , y ^ i ( t − 1 ) ) L\left(y_{i}, \hat{y}_{i}^{(t-1)}\right) L(yi,y^i(t−1))就是已知的,所以将它移入常数项C得:
J ( f t ) = ∑ i = 1 n [ g i f t ( x i ) + 1 2 h i f f 2 ( x i ) ] + Ω ( f t ) + C ( 式 ② ) J(f_{t})=\sum_{i=1}^{n}\left[g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{f}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right)+C (式②) J(ft)=i=1∑n[gift(xi)+21hiff2(xi)]+Ω(ft)+C(式②)
② 正则项
Ω ( f t ) = γ ⋅ T + λ ⋅ 1 2 ∑ j = 1 T ( w j 2 ) ( 式 ③ ) \Omega\left(f_{t}\right)=\gamma \cdot T+\lambda \cdot \frac{1}{2} \sum_{j=1}^{T}\left(w_{j}^{2}\right)(式③) Ω(ft)=γ⋅T+λ⋅21∑j=1T(wj2)(式③)
其中,T为第t颗树叶子节点的数量,w为叶权值,γ和λ都为超参数。 γ ⋅ T \gamma \cdot T γ⋅T相当于预剪枝。
我们把树看成是一个函数 f ( x i ) f(x_i) f(xi),输出就是判定的结果,这个结果最终是由第q个叶子节点的权值 w q w_q wq决定,所以 f ( x i ) = w q ( x i ) ( 式 ④ ) f(x_i) = w_q(x_i)(式④) f(xi)=wq(xi)(式④)
将③ ④带入②得:
J ( f t ) = ∑ i = 1 n [ g i w q ( x i ) + 1 2 h i w q ( x i ) 2 ] + γ ⋅ T + λ ⋅ 1 2 ∑ j = 1 T w j 2 + C J(f_{t})=\sum_{i=1}^{n}\left[g_{i} w_{q\left(x_{i}\right)}+\frac{1}{2} h_{i} w_{q\left(x_{i}\right)}^{2}\right]+\gamma \cdot T+\lambda \cdot \frac{1}{2} \sum_{j=1}^{T} w_{j}^{2}+C J(ft)=i=1∑n[giwq(xi)+21hiwq(xi)2]+γ⋅T+λ⋅21j=1∑Twj2+C
n个样本落在T个不同的叶子上,那么可以等价的按不同的叶子将样本进行区分,即按T将落在同一个叶子上的样本进行求和,又可继续得:
J ( f t ) = ∑ j = 1 T [ ( ∑ i ∈ I g i ) w j + 1 2 ( ∑ i ∈ I j h i ) w j 2 ] + γ ⋅ T + λ ⋅ 1 2 ∑ j = 1 T w j 2 + C J(f_{t})=\sum_{j=1}^{T}\left[\left(\sum_{i \in I} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}\right) w_{j}^{2}\right]+\gamma \cdot T+\lambda \cdot \frac{1}{2} \sum_{j=1}^{T} w_{j}^{2}+C J(ft)=j=1∑T⎣⎡(i∈I∑gi)wj+21⎝⎛i∈Ij∑hi⎠⎞wj2⎦⎤+γ⋅T+λ⋅21j=1∑Twj2+C
令 G j = ( ∑ i ∈ I j g i ) , H j = ( ∑ i ϵ I j h i ) G_{j}=\left(\sum_{i \in I_{j}} g_{i}\right), H_{j}=\left(\sum_{i \epsilon I_{j}} h_{i}\right) Gj=(∑i∈Ijgi),Hj=(∑iϵIjhi)
目标函数最终化简为:
J ( f t ) = ∑ j = 1 T [ G j w j + 1 2 ( H j + λ ) w j 2 ] + γ ⋅ T + C J\left(f_{t}\right)=\sum_{j=1}^{T}\left[G_{j} w_{j}+\frac{1}{2}\left(H_{j}+\lambda\right) w_{j}^{2}\right]+\gamma \cdot T+C J(ft)=j=1∑T[Gjwj+21(Hj+λ)wj2]+γ⋅T+C
六、目标函数优化
目标函数是一个分数,分数越小,树结构 f t f_t ft越好,最小化的通用套路,求偏导等于0:
∂ J ( f t ) ∂ w j = G j + ( H j + λ ) w j = 0 \frac{\partial J\left(f_{t}\right)}{\partial w_{j}}=G_{j}+\left(H_{j}+\lambda\right) w_{j}=0 ∂wj∂J(ft)=Gj+(Hj+λ)wj=0
w j = − G j H j + λ w_{j}=-\frac{G_{j}}{H_{j}+\lambda} wj=−Hj+λGj
带入目标函数,最终得到:
S t r u c t u r e _ S c o r e ( f t ) = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ ⋅ T Structure\_Score(f_t)=-\frac{1}{2} \sum_{j=1}^{T} \frac{G_{j}^{2}}{H_{j}+\lambda}+\gamma \cdot T Structure_Score(ft)=−21j=1∑THj+λGj2+γ⋅T
由此可以看出,目标函数只跟一阶、二级导数、T有关,也就是说目标函数是衡量树结构好坏的标准: 分数score越低,树结构
f
t
f_t
ft越好。
但是,目标函数跟w没关系啊,怎么求w啊?注意:这个地方可能比较迷惑人,上面我们说过了xgb是对函数空间的优化,也就是说【xgboost不是求参数w哦,而是求
f
t
f_t
ft】。
打分举例(图中用Obj表示Score):
七、计算 - 找出最优的树结构(通过节点分裂计算得到)
说了半天,那 f t f_t ft到底长啥样,怎么求? (求 f t f_t ft就是求树如何分裂的问题)
既然我们的目标函数公式都有了,那不就好办了么,套路:求增益呀。
(1)利用贪婪学习树(说的通俗一点吧,穷举所有情况,找到最优)。直接上公式(其中γ对应树的剪枝
):
(2)近似算法
除了穷举法求最大增益,陈天奇大佬还提到一种找最优划分的算法,主要针对数据太大,不能直接进行计算
,Time Complexity growing a tree of depth K:
后来还想加速才有了histogram直方图的做法。
这就是xgb优于传统GBDT的一个点:近似 - 直方图算法(可并行)
。
OK ,我们就推导到这吧!
八、xgboost相对于GBDT的优化(一共7个点,加上分裂方式不同的话共8个
)
① 传统GBDT以CART作为基分类器,xgboost还支持线性分类器,这个时候xgboost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。分裂方式不同
,由于GBDT是用CART,所以分裂方式是基于gini系数,而xgb是经过优化推导后的。
② 传统GBDT在优化时只用到一阶导数信息,xgboost则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。顺便提一下,xgboost工具支持自定义代价函数,只要函数可一阶和二阶求导。
③ xgboost在代价函数里加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、每个叶子节点上输出的score的L2模的平方和。从Bias-variance tradeoff角度来讲,正则项降低了模型的variance,使学习出来的模型更加简单,防止过拟合,这也是xgboost优于传统GBDT的一个特性。
④ 列抽样(column subsampling)。xgboost借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算,这也是xgboost异于传统gbdt的一个特性。
⑤ 对缺失值的处理。对于特征的值有缺失的样本,xgboost可以自动学习出它的分裂方向。
⑥ xgboost工具支持并行。boosting不是一种串行的结构吗?怎么并行的?注意xgboost的并行不是tree粒度的并行,xgboost也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含了前面t-1次迭代的预测值)。xgboost的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),xgboost在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。
⑦ 可并行的近似直方图算法。树节点在进行分裂时,我们需要计算每个特征的每个分割点对应的增益,即用贪心法枚举所有可能的分割点。当数据无法一次载入内存或者在分布式情况下,贪心算法效率就会变得很低,所以xgboost还提出了一种可并行的近似直方图算法,用于高效地生成候选的分割点。
【详细介绍和讨论】
【RF、GBDT、XGBOOST常见面试算法整理】