这几天一直在研究XGboost的基本原理与代码调参,其原理是在GBDT的基础上进行优化,但也有很多的不同之处;所以自己准备更新两篇博客分为XGBoost原理与实例
和XGBoost实战与调参优化
来巩固这方面的知识。
一、XGBoost原理分析
在机器学习的问题中,目标函数(objective function)随处可见,目标函数一般由损失函数+正则化项。
Obj(Θ)=L(Θ)+Ω(Θ)
L(Θ)(损失函数):用于描述模型拟合数据的程度;
Ω(Θ)(正则化项):用于描述模型复杂度的程度。
- 训练数据的损失函数:
L=Σi=1nl(yi,yi^),如:
- Square loss:
l(yi,yi^)=(yi−yi^)2
- Logistic loss:
l(yi,yi^)=yiln(1+e−yi^)+(1−yi)ln(1+eyiˉ)
- 正则化项:
-
L2 norm:
Ω(ω)=λ∥ω∥2
- (
L2正则化是指权值向量
ω各个元素的平方和然后再求根号;可以防止模型过拟合overfitting)
-
L1 norm:
Ω(ω)=λ∥ω∥1
- (
L1正则化是指权值向量
ω各个元素的绝对值之和;通常能产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择,一定程度上也能防止过拟合)
若想对正则化有一个较为清晰的理解,请参考博客机器学习中正则化项L1和L2的直观理解
我们训练的基学习器是CART回归树,因此模型的复杂度与树的深度、叶子结点的个数、叶子结点的输出值(XGBoost里称为叶子结点的权值)有关。那么假设我们有
K颗树,那么模型的输出值为:
yi^=Σt=1Tft(xi)
因为每一棵的参数包括它的结构与叶子结点的值,所以我们不妨将每一颗树
fk作为参数来进行优化,因此我们可以表示为
Ω={f1,f2,f3,...,fT},所以我们的目标函数现在可以改写为:
Obj=Σi=1nl(yi,yi^)+Σi=1TΘ(ft)
现在的问题是我们如何训练目标函数,学习基学习器,当然,XGBoost既然是GBDT的优化,自然大部分的思想是相通的,所以我们也采用前向分布算法(additive model),即我们把学习的过程分解为先学习第一颗树,然后基于学习好的第一颗树再去学习第二棵树,以此类推,直到通过第
t−1颗树来学习第
T颗树为止…
yi^0=constant
yi^1=yi^0+f1(xi)
yi^2=yi^1+f2(xi)
yi^3=yi^2+f3(xi)
…
yi^T=yi^T−1+fT(xi)
假设我们预测第
T轮,有:
yi^T=yi^T−1+fT(xi)
注意,上式中蓝色是一个定值,而红色的树是我们这轮要决定的,即我们要找到这个一个树,使下面的目标函数最小:
Obj(T)=Σi=1nl(yi,yi^T)+Σt=1TΩ(ft)
=Σi=1nl(yi,yi^T−1+fT(xi))+Ω(ft)+constant
那么我们如何找到这颗最优树呢?在GBDT中,我们采用启发式的思想,以平均最小误差为准则,用负梯度的值来近似代替残差,拟合一个回归树。而在XGBoost中,是通过对损失函数进行二阶泰勒公式展开的思想,回顾一下二阶泰勒公式:
f(x+Δx)≃f(x)+f′(x)Δx+21f′′(x)Δx2
那么对损失函数进行二阶泰勒公式展开:
Σi=1nl(yi,yi^T−1+fT(xi))=Σi=1n[l(yi,yi^T−1)+l′(yi,yi^T−1)fT(xi)+21l′′(yi,yi^T−1)fT2(xi)]
另:
gi=l′(yi,yi^T−1),
hi=l′′(yi,yi^T−1)
则目标函数可表示为:
Obj(T)=Σi=1n[l(yi,yi^T−1)+gifT(xi)+hifT2(xi)]+Ω(ft)+constant
去除常数项,目标函数可简化为:
Obj(T)=Σi=1n[gifT(xi)+hifT2(xi)]+Ω(ft)
回想一下GBDT中如何选取划分点的呢?GBDT是用平方误差最小化准则来选取最佳划分点,换句话说,无论我们选取哪种损失函数(平方损失、对数损失、指数损失、0-1损失、绝对损失等),寻找划分点的方法是不变的,即我们选取得损失函数对划分点的选取没有取到干涉作用,从直观上理解,这显然是GBDT的不足之处;因此,我们之所以花费那么大的努力将损失函数二阶泰勒展开,是将损失函数对划分点的选取起到干涉的作用。
接下来,我们定义叶节点的权值(输出值)函数:
ft(x)=wq(x),w∈RT,q:Rd→{1,2,3,...,T}
上图的
q(x)是叶子结点的编号函数,把一个样本输入到
q(x)中,那么输出值就为该样本所在的叶子编号,例如上图中有5个人物,衣服颜色各不相同,若我们将蓝色人物输入
q(x)中,那么输出的结果为1,说明蓝色人物所在的叶子结点编号为1,那么蓝色人物的权值就是
wq(蓝色人物)=w1=2.
有了这样的思路,那么我就可以将正则化引入到树的复杂度上去了,在一个基学习器(CART)中,影响其复杂度的因素主要有叶子结点数、叶子的权值,因此我们树复杂度函数可以写为:
Ω(ft)=γT+21λΣj=1Twj2
上式中,红色的式子表示对叶节点数
T复杂度的惩罚项,蓝色的式子表示对叶节点权值的
L2正则化,下图对该树的复杂度的计算做出简要解释:
假设上图是第
t颗树,其叶节点的个数
T=3,
w1=2,w2=0.1,w3=−1,带入上面公式得:
Ω=γ∗3+21λ(4+0.01+1)
定义叶子结点
j的样本集合为:
Ij={i∣q(xi)=j}
这时,目标函数可以改写为:
Obj(T)=Σi=1n[gifT(xi)+hifT2(xi)]+Ω(ft)
=Σi=1n[giwq(xi)+hiwq(xi)2]+γT+21λΣj=1Twj2
=Σj=1T[Σi∈Ijgiwj+Σi∈Ijhiwj2+21λwj2]+γT
=Σj=1T[Σi∈Ijgiwj+(Σi∈Ijhi+21λ)wj2]+γT
=Σj=1T[Gjwj+(Hj+21λ)wj2]+γT
其中定义:
Gj=Σi∈Ijgi,Hj=Σi∈Ijhi
现在我们要找到最佳的
wj使得上面的橙色式子最小,这个问题等价于下面简单的求导、找最值的例子:
argminxGx+21Hx2=−HG
minxGx+21Hx2=−21HG2
与上面例子类似,如果我们假设叶子结点已经确定,那么每个叶节点的最优权值与目标函数的最优解分别为:
wj∗=−HJ+λGj
Obj=−21Σj=1THj+λGj2+γT
下图展示了叶节点权值与目标函数的计算过程:
回顾下GBDT对划分点分类的方法,无论选取哪个损失函数,都是用平方误差最小准则划分,但XGBoost是计算增益来找最优划分点,并且划分的准则是与选取的损失函数相关的,划分时,对树的每个叶子结点尝试着去划分,然后由下面给出的增益准则决定是否划分该结点!
划分的增益准则为:
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ
红色式子表示:划分后左叶子结点的分值
蓝色式子表示:划分后右叶子结点的分值
绿色式子表示:划分前该结点的分值
粉色式子表示:将该结点划分为叶子结点的复杂度代价
上式其实就是说划分前的得分与划分后的得分谁大,而
λ相当于阈值,若划分后与划分前的差值大于
λ,那么就划分;若划分后与划分前的差值小于
λ,那么就不进行划分。比如说,我们以年龄是否小于a对上面的图例进行划分,那么规则如下图:
接下来就是枚举所有可能得划分点,重复上面的操作,然后划分叶子结点的样本集合,这就是XGboost的基本原理,实际上其步骤主要分为这么几步:
- 损失函数后加入正则化项;
- 将损失函数二阶泰勒公式展开;
- 根据增益准则划分结点,构建
T颗树。
XGBoost的算法流程总结如下:
1、精确贪婪划分点查找算法
解释说明:
-
m为当前结点中样本特征个数;
- 第二个for 循环为:遍历由第
k个特征的第
j个特征值排序后的样本编号,计算
Gain
2、寻找划分点的近似算法
如果样本很大,穷举其特征与特征值的暴力算法显然不是一个好方法,因此近似算法流程图如下:
解释说明:
- 第一个for循环是根据第
k个特征的百分比推荐该特征下的候选划分点集合
Sk;
- 第二个for循环就是计算
Gk,Hk,然后去精确贪婪一样计算
Gain
至于推荐划分点的具体流程,我看了论文,也是有些不懂,如果有兴趣的小伙伴可以参考这篇文章
3、稀疏感知划分查找(缺失值的划分)
在现实生活中,我们输入的样本矩阵
X往往是稀疏的,出现这种现象的原因主要为:
- 数据中缺失值的普遍存在
- 对离散特征进行独热编码处理
- 统计中经常零项
因此,XGBoost在每个树结点都建议一个默认的方向,当稀疏矩阵
X存在缺失值时,将该样本分类为默认方向,每一个结点有2个默认方向,XGBoost从数据中习得最佳的方向,然后将该样本划入最佳结点中,具体流程算法如下:
解释说明:
- 输入的样本集合有俩个分别为
I(包含缺失值的样本集合)与
Ik(不包含缺失值的样本集合),并在循环之前计算
I的总
G、H,当然也包含缺失值样本。
- 内嵌的第一个for循环是将缺失值的样本放入右结点,那么首先按特征值
xk升序,然后遍历
Ik中的编号,将大于
j的样本放入左节点,我们发现缺失值的样本因为始终没有在
Ik中,因此每次都将其放在右节点!
- 第二个for循环类似!
二、XGBoost实例演练
上面的算法流程有些抽象,所以我们还是以实例来一步一步的实现XGBoost,数据集如下表:
ID |
x1 |
x2 |
y |
1 |
1 |
-5 |
0 |
2 |
2 |
5 |
0 |
3 |
3 |
-2 |
1 |
4 |
1 |
2 |
1 |
5 |
2 |
0 |
1 |
6 |
6 |
-5 |
1 |
7 |
7 |
5 |
1 |
8 |
6 |
-2 |
0 |
9 |
7 |
2 |
0 |
10 |
6 |
0 |
1 |
数据集中有10个样本,两个特征
x1,x2,为了简单起见,我们预定义树的深度为1(max_depth=1),树的颗数为2(num_boost_round=2),学校率为0.1(eta=0.1),正则化参数
λ=1,γ=0,损失函数(logloss)。
我们选取的损失函数为:
l(yi,yi^)=yiln(1+e−yi^)+(1−yi)ln(1+eyiˉ)
注意:这里的
yi^是没有映射为概率的原始值,即
yi^=ωT∗xi+b;
那么映射后,预测为1的概率为:
P1i=1+e−yi^1
后面我们需要用到logloss的一阶、二阶导数,有必要先写出其导数:
gi=l′(yi,yi^)=P1i−yi;
hi=l′′(yi,yi^)=P1i(1−P1i)
下面我们利用XGboost拟合数据集
1、生成第一颗树
回顾我们上面的原理分析,对该结点是否划分的准则是计算增益:
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ
那么在求
GL,GR时需要用到上一颗树的预测值,也就是我们在GBDT中一样的初始值,在XGBoost用base_score表示初始值,默认为base_score=0.5
值得一提的是,base_score是一个经过映射后的值,可以理解为预测为1的概率值,因为在建立第二颗树时会用到,所以应该留意此处。
对每一个样本求出其一阶、二阶导数的值:
ID |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
gi |
0.5 |
0.5 |
-0.5 |
-0.5 |
-0.5 |
-0.5 |
-0.5 |
0.5 |
0.5 |
-0.5 |
hi |
0.25 |
0.25 |
0.25 |
0.25 |
0.25 |
0.25 |
0.25 |
0.25 |
0.25 |
0.25 |
计算步骤如下:
对于ID=1的样本(其他样本计算类似)
g1=l′(y1,y1^)=P11−y1=0.5−0=0.5;
h1=l′′(y1,y1^)=P11(1−P11)=0.5(1−0.5)=0.25
接下来我们需要在特征x1、x2中寻找最佳划分点.
以x1为例:我们需要将x1的特征值从小到大排列,一共有
{1,2,3,6,7} 5中取值。
当以特征值为1作为划分点时(x1<1):
左子树集合为
Ileft={}
右子树集合为
Iright={1,2,3,4,5,6,7,8,9,10}
计算
GL=Σi∈Ileftgi=0(因为左子树为空集)
计算
HL=Σi∈Ilefthi=0
计算
GR=Σi∈Irightgi=−1
计算
HR=Σi∈Irighthi=2.5
最后计算增益:
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ=0
这是显然的,因为左子树是空集,相当于没有对数据集进行划分
当以特征值为2作为划分点时(x1<2):
左子树集合为
Ileft={1,4}
右子树集合为
Iright={2,3,5,6,7,8,9,10}
计算
GL=Σi∈Ileftgi=0
计算
HL=Σi∈Ilefthi=0.5
计算
GR=Σi∈Irightgi=−1
计算
HR=Σi∈Irighthi=2
最后计算增益:
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ=0.023809
依次算出x1的各个特征值的参数,如下表:
Split_point |
1 |
2 |
3 |
6 |
7 |
GL |
0 |
0 |
0 |
-0.5 |
-1.0 |
HL |
0 |
0.5 |
1.0 |
1.25 |
2.5 |
GR |
-1.0 |
-1.0 |
-1.0 |
-0.5 |
0.0 |
HR |
2.5 |
2 |
1.5 |
1.25 |
0.0 |
Gain |
0.0 |
0.023809 |
0.057142 |
-0.031746 |
0.0 |
因此x1的特征下的最佳划分点为x1<3,此时得到的增益最大。
若以x2为例:x2的可能取值排序为
{−5,−2,0,2,5}
当以特征值为-5作为划分点时(x2<-5):
左子树集合为
Ileft={}
右子树集合为
Iright={1,2,3,4,5,6,7,8,9,10}
计算
GL=Σi∈Ileftgi=0
计算
HL=Σi∈Ilefthi=0.0
计算
GR=Σi∈Irightgi=−1.0
计算
HR=Σi∈Irighthi=2.5
最后计算增益:
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ=0
当以特征值为-5作为划分点时(x2<-2):
左子树集合为
Ileft={1,6}
右子树集合为
Iright={2,3,4,5,7,8,9,10}
计算
GL=Σi∈Ileftgi=0.0
计算
HL=Σi∈Ilefthi=0.5
计算
GR=Σi∈Irightgi=−1.0
计算
HR=Σi∈Irighthi=2.0
最后计算增益:
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ=0.023809
然后也是依次计算出特征x2的各个划分点的参数与增益值,列表如下:
Split_point |
-5 |
-2 |
0 |
2 |
5 |
GL |
0 |
0 |
0 |
-0.5 |
-1.0 |
HL |
0 |
0.5 |
1.0 |
1.25 |
2.5 |
GR |
-1.0 |
-1.0 |
-1.0 |
-0.5 |
0.0 |
HR |
2.5 |
2 |
1.5 |
1.25 |
0.0 |
Gain |
0.0 |
0.023809 |
0.057142 |
-0.031746 |
0.0 |
因此对特征x2而言,最佳划分点位x2<0
因为x1与x2的最佳划分点的增益值相同,所以我们选取x1<3作为最佳划分点即可。
那么划分后左子树的样本集合为:
Ileft={1,2,4,5},Iright={3,6,7,8,9,10}
故左叶子结点的权值为:
w1左∗=−H+λGη=−1+10∗0.1=0
w1右∗=−H+λGη=−1.5+1−1.0∗0.1=0.04
η是学习率,防止过拟合。
至此第一颗树建立完毕,如果深度为2,那么就需在左节点与右节点分别重复上面的步骤即可。
2、生成第二棵树
回想一下,我们在生成第一颗树的时候用到了
f0(xi),这颗初始的树是我们自己设置的(base_score=
P11=0.5)
假设模型只有这一颗树(T=1),那么模型对样本
xi进行预测的值是什么呢?
由加法模型知:
yiT=Σt=0Tft(xi)
yi1=f0(xi)+f1(xi)
f1(xi)是我们样本
xi落在第一颗树上的某个节点的值,而
f0(xi)就是前面提到的base_score经过sigmod映射后的值,因为我们选择的是logloss做损失函数,所以概率为
P=1+e−x1
所以我们需要将0.5做逆运算
x=ln1−yy后可以得到
f0(xi)=0.
因此第一颗树预测的结果就是:
yi1=f0(xi)+f1(xi)=0+ωq(xi)
官方文档有说明,当生成的树较多时(T很大),那么初始值几乎不起什么作用。
yi^2=yi^1+f2(xi)
因此我们需要对第一颗树的结果做映射,映射后的值就是我们计算logloss的一阶、二阶导数中的
P12
第二颗logloss的一阶、二阶导数为:
gi=l′(yi,yi^)=P12−yi;
hi=l′′(yi,yi^)=P12(1−P12)
则
P12的值如下表:
ID |
P12 |
1 |
0.5 |
2 |
0.5 |
3 |
0.5099 |
4 |
0.5 |
5 |
0.5 |
6 |
0.5099 |
7 |
0.5099 |
8 |
0.5099 |
9 |
0.5099 |
10 |
0.5099 |
后面的树的生成与第一颗树一模一样,记得最后需要对预测值做映射。
下一篇介绍下XGBoost的在数据集上跑出来的效果,简单的小入门,与调参的基本步骤!!!
[URL2]:https://arxiv.org/pdf/1603.02754.pdf