该系列将整理机器学习相关知识。这篇博客主要讨论:
1 XGBoost的算法原理
2 XGboost参数调优
XGBoost是Extreme Gradient Boosting的简称,Gradient Boosting是论文"Greedy Function Approximation: A Gradient Boosting Machine"中介绍的梯度提升算法。Boosting Tree树数据挖掘和机器学习中常用算法之一,其对输入要求不敏感,效果好,在工业界用的较多(kaggle比赛必用)。
1 背景知识
1.1 Traing loss + Regularization
XGBoost用于监督学习问题(分类和回归)。监督学习的常用目标函数是:
通常目标函数包含两部分:训练误差和正则化
o
b
j
(
θ
)
=
L
(
θ
)
+
Ω
(
θ
)
obj(θ)=L(θ)+Ω(θ)
obj(θ)=L(θ)+Ω(θ)
其中L是损失函数,度量模型预测与真实值的误差。常用的损失函数:
预测问题的平方损失函数:
L
(
θ
)
=
∑
i
(
y
i
−
y
i
^
)
2
L(\theta) = \sum_{i}{(y_i-\hat{y_i})}^2
L(θ)=i∑(yi−yi^)2
logistic 损失:
L
(
θ
)
=
∑
i
[
y
i
l
n
(
1
+
e
−
y
i
^
)
+
(
1
−
y
i
)
l
n
(
1
+
e
y
i
^
)
]
L(\theta) = \sum_{i}[y_i ln(1+e^{-\hat{y_i}}) +(1-y_i) ln(1+e^{\hat{y_i}})]
L(θ)=i∑[yiln(1+e−yi^)+(1−yi)ln(1+eyi^)]
Ω
Ω
Ω是正则化项,度量模型的复杂度,避免过拟合,常用的正则化有L1 和L2正则化。
1.2 Tree模型融合(集成)
Boosting Tree 最基本的部分是回归树(GBDT中用到的也是回归树,而不是分类树),也即是CART(如下图),CART会把输入根据属性分配到各个叶子节点上,而每个叶子节点上面会对应一个分数值。下面的例子是预测一个人是否喜欢电脑游戏。将叶子节点表示为分数之后,可以做很多事情,比如概率预测,排序等等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TL0FhQqH-1576119372588)(http://7xnzwk.com1.z0.glb.clouddn.com/15149016431943.jpg)]
一个CART往往过于简单,而无法有效的进行预测,因此更加高效的是使用多个CART进行融合,使用集成的方法提升预测效率:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PtUiqY4M-1576119372590)(http://7xnzwk.com1.z0.glb.clouddn.com/15147876733214.jpg)]
假设有两颗回归树,则两棵树融合后的预测结果如上图。用公式表示为:
y
i
^
=
∑
k
=
1
K
f
k
(
x
i
)
,
f
k
∈
F
\hat{y_i}=\sum_{k=1}^{K}f_k(x_i),f_k\in\mathscr{F}
yi^=k=1∑Kfk(xi),fk∈F
其中, K是树的个数,
f
k
(
x
i
)
f_k(x_i)
fk(xi)是第k棵树对于输入
x
i
x_i
xi 输出的得分, $f_k $是相应函数,
F
\mathscr{F}
F 是相应函数空间。则目标函数为:
o
b
j
(
θ
)
=
∑
i
n
L
(
y
i
,
y
i
^
)
+
∑
k
=
1
K
Ω
(
f
k
)
obj(\theta)=\sum_i^n L(y_i,\hat{y_i})+\sum_{k=1}^K\Omega(f_k)
obj(θ)=i∑nL(yi,yi^)+k=1∑KΩ(fk)
函数
L
L
L描述
y
i
y_i
yi ,
y
i
^
\hat{y_i}
yi^之间的距离。
常用的模型融合方法是Random Foreast和Boosting Tree,这两种方法构造树的方式不同(参考系列前面的集成学习一节)。Tree Ensemble中,模型的参数是什么呢?其实就是指树的结构和叶子节点上面分数的预测。如何求参数?定义目标函数,通过优化目标函数进行求解。
1.3 Tree Boosting
假设每次迭代生成一棵树,则训练目标函数可以写成:
o
b
j
(
θ
)
t
=
∑
i
n
l
(
y
i
,
y
i
^
(
t
)
)
+
∑
k
=
1
t
Ω
(
f
k
)
obj(\theta)^{t}=\sum_i^n l(y_i,\hat{y_i}^{(t)})+ \sum_{k=1}^t\Omega(f_k)
obj(θ)t=i∑nl(yi,yi^(t))+k=1∑tΩ(fk)
其中第一部分是训练误差,第2部分是每棵树的复杂度。
y
i
^
(
t
)
\hat{y_i}^{(t)}
yi^(t)为第t步迭代的预测值,且有以下迭代关系:
y
i
^
(
0
)
=
0
\hat{y_i}^{(0)}=0
yi^(0)=0
y
i
^
(
1
)
=
f
1
(
x
i
)
=
y
i
^
(
0
)
+
f
1
(
x
i
)
\hat{y_i}^{(1)}=f_1(x_i) = \hat{y_i}^{(0)}+f_1(x_i)
yi^(1)=f1(xi)=yi^(0)+f1(xi)
⋯
\cdots
⋯
y
i
^
(
t
)
=
∑
k
=
1
t
(
f
k
(
x
i
)
)
=
y
i
^
(
t
−
1
)
+
f
t
(
x
i
)
\hat{y_i}^{(t)}= \sum_{k=1}^t(f_k(x_i))= \hat{y_i}^{(t-1)}+f_t(x_i)
yi^(t)=∑k=1t(fk(xi))=yi^(t−1)+ft(xi)
则训练目标函数可以写成:
o
b
j
(
t
)
=
∑
i
=
1
n
l
(
y
i
,
y
i
^
(
t
)
)
+
∑
i
=
1
t
Ω
(
f
i
)
obj^{(t)} =\sum_{i=1}^nl(y_i,\hat{y_i}^{(t)})+\sum_{i=1}^t\Omega(f_i)
obj(t)=i=1∑nl(yi,yi^(t))+i=1∑tΩ(fi)
o
b
j
(
t
)
=
∑
i
=
1
n
l
(
y
i
,
y
i
^
(
t
−
1
)
+
f
t
(
x
i
)
)
+
Ω
(
f
t
)
+
∑
i
=
1
t
−
1
Ω
(
f
i
)
obj^{(t)} =\sum_{i=1}^nl(y_i,\hat{y_i}^{(t-1)}+ f_t(x_i))+\Omega(f_t)+ \sum_{i=1}^{t-1}\Omega(f_i)
obj(t)=i=1∑nl(yi,yi^(t−1)+ft(xi))+Ω(ft)+i=1∑t−1Ω(fi)
对于第t步来说
∑
i
=
1
t
−
1
Ω
(
f
i
)
\sum_{i=1}^{t-1}\Omega(f_i)
∑i=1t−1Ω(fi)是已知的,因此有:
o
b
j
(
t
)
=
∑
i
=
1
n
l
(
y
i
,
y
i
^
(
t
−
1
)
+
f
t
(
x
i
)
)
+
Ω
(
f
t
)
+
c
o
n
s
t
a
n
t
obj^{(t)} =\sum_{i=1}^nl(y_i,\hat{y_i}^{(t-1)}+ f_t(x_i))+\Omega(f_t)+ constant
obj(t)=i=1∑nl(yi,yi^(t−1)+ft(xi))+Ω(ft)+constant
如果 l l l使用平方损失函数,则有
$obj^{(t)} =\sum_{i=1}^n [ y_i-(\hat{y_i}^{(t-1)}+ f_t(x_i)]^2+\Omega(f_t)+ constant $
=
∑
i
=
1
n
[
y
i
2
−
2
y
i
(
y
i
^
(
t
−
1
)
+
f
t
(
x
i
)
)
+
(
y
i
^
(
t
−
1
)
+
f
t
(
x
i
)
)
2
]
+
Ω
(
f
t
)
+
c
o
n
s
t
a
n
t
=\sum_{i=1}^n[y_i^2-2y_i(\hat{y_i}^{(t-1)}+ f_t(x_i))+(\hat{y_i}^{(t-1)}+ f_t(x_i))^2]+\Omega(f_t)+ constant
=∑i=1n[yi2−2yi(yi^(t−1)+ft(xi))+(yi^(t−1)+ft(xi))2]+Ω(ft)+constant
=
∑
i
=
1
n
[
y
i
2
−
2
y
i
y
i
^
(
t
−
1
)
−
2
y
i
f
t
(
x
i
)
+
(
y
i
^
(
t
−
1
)
)
2
+
2
y
i
^
(
t
−
1
)
f
t
(
x
i
)
+
(
f
t
(
x
i
)
)
2
]
+
Ω
(
f
t
)
+
c
o
n
s
t
a
n
t
=\sum_{i=1}^n[y_i^2-2y_i\hat{y_i}^{(t-1)}- 2y_if_t(x_i)+ (\hat{y_i}^{(t-1)})^2+2\hat{y_i}^{(t-1)}f_t(x_i) +(f_t(x_i))^2]+\Omega(f_t)+ constant
=∑i=1n[yi2−2yiyi^(t−1)−2yift(xi)+(yi^(t−1))2+2yi^(t−1)ft(xi)+(ft(xi))2]+Ω(ft)+constant
=
∑
i
=
1
n
[
2
(
y
i
^
(
t
−
1
)
−
y
i
)
f
t
(
x
i
)
+
(
f
t
(
x
i
)
)
2
]
+
∑
i
=
1
n
[
(
y
i
)
2
−
2
y
i
y
i
^
+
(
y
i
^
(
t
−
1
)
)
2
]
+
Ω
(
f
t
)
+
c
o
n
s
t
a
n
t
=\sum_{i=1}^n[2{(\hat{y_i}^{(t-1)} - y_i)}f_t{(x_i)} +(f_t(x_i))^2]+ \sum_{i=1}^n[{(y_i)}^2-2y_i\hat{y_i} + {(\hat{y_i}^{(t-1)})}^2 ]+\Omega(f_t)+ constant
=∑i=1n[2(yi^(t−1)−yi)ft(xi)+(ft(xi))2]+∑i=1n[(yi)2−2yiyi^+(yi^(t−1))2]+Ω(ft)+constant
其中对于第t步来说,
∑
i
=
1
n
[
(
y
i
)
2
−
2
y
i
y
i
^
+
(
y
i
^
(
t
−
1
)
)
2
]
\sum_{i=1}^n[{(y_i)}^2-2y_i\hat{y_i} + {(\hat{y_i}^{(t-1)})}^2 ]
∑i=1n[(yi)2−2yiyi^+(yi^(t−1))2]也是常数,所以 目标函数优化为:
o
b
j
(
t
)
=
Σ
i
=
1
n
[
2
(
y
i
^
(
t
−
1
)
−
y
i
)
f
t
(
x
i
)
+
(
f
t
(
x
i
)
)
2
]
+
Ω
(
f
t
)
+
c
o
n
s
t
a
n
t
obj^{(t)}=\Sigma_{i=1}^n[2{(\hat{y_i}^{(t-1)} - y_i)}f_t{(x_i)} +(f_t(x_i))^2]+\Omega(f_t)+ constant
obj(t)=Σi=1n[2(yi^(t−1)−yi)ft(xi)+(ft(xi))2]+Ω(ft)+constant
其中 ( y i ^ ( t − 1 ) − y i ) (\hat{y_i}^{(t-1)} - y_i) (yi^(t−1)−yi)一般叫做残差。
当使用平方损失函数时,拟合残差的步骤就是上次分享中的Adaboost算法。更加一般地,对于不是平方损失函数(当对于一般损失函数时,可以用一阶梯度拟合残差,对应的就是GBDT方法。
1.4 GBDT回顾(参考上一节的GBDT)
提升树利用加法模型和前向分步算法实现学习的优化过程。当损失函数时平方损失和指数损失函数时,每一步的优化很简单,如平方损失函数学习残差回归树。
当为一般的损失函数时,GBDT利用最速下降的近似方法,即利用损失函数的负梯度在当前模型的值,作为回归问题中提升树算法的残差的近似值,拟合一个回归树。
提升树利用加法模型和前向分步算法实现学习的优化过程。当损失函数时平方损失和指数损失函数时,每一步的优化很简单,如平方损失函数学习残差回归树。
当为一般的损失函数时,GBDT利用最速下降的近似方法,即利用损失函数的负梯度在当前模型的值,作为回归问题中提升树算法的残差的近似值,拟合一个回归树。
2 XGBoost
2.1 目标函数
更加一般地,对于一般损失函数,XGBoost会使用泰勒展开的形式进而用到二阶导数。目标函数:
o
b
j
(
t
)
=
∑
i
=
1
n
l
(
y
i
,
y
i
^
(
t
−
1
)
+
f
t
(
x
i
)
+
Ω
(
f
t
)
+
c
o
n
s
t
a
n
t
obj^{(t)} =\sum_{i=1}^nl(y_i,\hat{y_i}^{(t-1)}+ f_t(x_i)+\Omega(f_t)+ constant
obj(t)=i=1∑nl(yi,yi^(t−1)+ft(xi)+Ω(ft)+constant
用泰勒展开来近似目标函数:
(在GBDT中,使用梯度,只是使用了一阶导数的形式)
- 泰勒展开: f ( x + Δ x ) ≈ f ( x ) + f ′ ( x ) Δ x + 1 2 f ′ ′ Δ x 2 f(x+ \Delta x)\approx f(x) + f^{'}(x)\Delta x + \frac{1}{2} f^{''} \Delta x^2 f(x+Δx)≈f(x)+f′(x)Δx+21f′′Δx2
- 定义:
g i = ∂ y ^ ( t − 1 ) l ( y i , y ^ ( t − 1 ) ) g_i = \partial_{ \hat{y}^{(t-1)}} l(y_i,\hat{y}^{(t-1)}) gi=∂y^(t−1)l(yi,y^(t−1))
h i = ∂ y ^ ( t − 1 ) 2 l ( y i , y ^ ( t − 1 ) ) h_i = \partial_{ \hat{y}^{(t-1)}}^2 l(y_i,\hat{y}^{(t-1)}) hi=∂y^(t−1)2l(yi,y^(t−1))
则有:
o b j ( t ) = ∑ i = 1 n [ l ( y i , y ^ ( t − 1 ) + g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) + c o n s t a n t obj^{(t)} = \sum_{i=1}^n [l(y_i,\hat{y}^{(t-1)}+g_i f_t(x_i) +\frac{1}{2} h_if_t^2(x_i)] + \Omega(f_t)+ constant obj(t)=i=1∑n[l(yi,y^(t−1)+gift(xi)+21hift2(xi)]+Ω(ft)+constant
移除常量:
l
(
y
i
,
y
i
^
(
t
−
1
)
)
l(y_i,\hat{y_i}^{(t-1)})
l(yi,yi^(t−1)),则目标函数为:
o
b
j
(
t
)
=
∑
i
=
1
n
[
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
obj^{(t)} = \sum_{i=1}^n [g_i f_t(x_i) +\frac{1}{2} h_if_t^2(x_i)] + \Omega(f_t)
obj(t)=i=1∑n[gift(xi)+21hift2(xi)]+Ω(ft)
有了这个更新后的目标函数后,可以看出这个目标函数仅仅依赖一阶似然的一阶和二阶导数。对于Adaboost,通常只能用用平方损失函数和通过拟合残差的方式来求解。GBDT在ADAboost基础上做了一部优化,用释然函数在每一个树处的负梯度来近似残差。
有了上述通用的形式后,就可以使用任意(可以求1阶和2阶)形式的损失函数。
2.2 树的复杂度
当目前为止,讨论了模型中训练误差的部分。下面来探讨模型复杂度
Ω
(
f
t
)
\Omega(f_t)
Ω(ft)的表示方式。
重新定义每棵树,将树f
拆分成树结构q
和叶子权重部分w
两部分。结构函数q
把输入映射到叶子的索引上。而w
给定了每个索引号对应叶子的分数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8N70srlB-1576119372591)(http://7xnzwk.com1.z0.glb.clouddn.com/15147940969973.jpg)]
当给定了如上图的树定义后,每颗树的复杂度可以定义为下面公式,这个公式里定义了树中叶子节点的个数和每颗树叶子节点的输出分数的L2正则项。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-frpWiZy3-1576119372592)(http://7xnzwk.com1.z0.glb.clouddn.com/15147943145691.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ZCxZfKS-1576119372593)(http://7xnzwk.com1.z0.glb.clouddn.com/15147943314091.jpg)]
在这种定义下,我们可以把目标函数改写为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtKBld4E-1576119372594)(http://7xnzwk.com1.z0.glb.clouddn.com/15147944731145.jpg)]
其中,I被定义为每个叶子上面样本的集合: I j = ( i ∣ q ( x i ) = j ) I_j= \left( i|q(x_i)=j \right) Ij=(i∣q(xi)=j)。这个目标函数包含了T个独立的单变量二次函数。
上述目标函数对
w
j
w_j
wj求导并令导数为0,可以求得:
w
j
∗
=
−
G
j
H
j
+
λ
w_j* = -\frac{G_j}{H_j+\lambda}
wj∗=−Hj+λGj
O
b
j
=
−
1
2
∑
j
=
1
T
G
j
2
H
j
+
λ
+
γ
T
Obj = -\frac{1}{2}\sum_{j=1}^T \frac{G_j^2}{H_j+\lambda } + \gamma T
Obj=−21j=1∑THj+λGj2+γT
计算举例:
Obj代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少。我们可以把它叫做结构分数(structure score)。你可以认为这个就是类似吉尼系数一样更加一般的对于树结构进行打分的函数。下面是一个具体的打分函数计算的例子
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wGfYgwAT-1576119372595)(http://7xnzwk.com1.z0.glb.clouddn.com/15148066903621.jpg)]
所以目标很明确,不断的枚举树结构,然后利用上述打分函数来寻找一个最优结构的树,加入到我们的模型中,再重复这个操作。枚举树结构计算消耗太大,常用的是贪心法,每一次尝试对已经的叶子加入一个分割,对一个具体的分割方案,我们可以获得分割后的增益为:
G
a
i
n
=
1
2
[
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
−
(
G
L
+
G
R
)
2
H
L
+
H
R
+
λ
]
−
γ
Gain = \frac{1}{2} \left[\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gamma
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ
如果Gain<0,则此节点不应该split成左右两支。
对于每次扩展,我们还是要枚举所有可能的分割方案,如何高效地枚举所有的分割呢?我假设我们要枚举所有 x<ax<a
这样的条件,对于某个特定的分割aa我们要计算左边和右边的导数和。
实际应用中,先将
g
i
g_i
gi 从小到大排序,然后进行遍历,看每个结点是否需要split。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3i53u2t-1576119372596)(http://7xnzwk.com1.z0.glb.clouddn.com/15148069572544.jpg)]
我们可以发现对于所有的aa,我们只要做一遍从左到右的扫描就可以枚举出所有分割的梯度和GL和GR。然后用上面的公式计算每个分割方案的分数就可以了。
2.3 其他优化方法
- shrinkage and Column Subsampling
除了在目标函数中添加正则化外,还有两种防止过拟合的方法:Shrinkage and Column Subsampling
Shrinkage : 在每一步生成boosting树时添加一个删减参数:η ,通SGD中的学习率类似,这个参数可以减少单颗树的作用
Column(Feature) Subsampling:这个技术在Random Foreast构建子树时使用(RF决策树的每个节点,随机在属性集合中选择k个属性子集用做划分属性)。XGBoost在构建子树时也引入了相似的优化方法。
- SPLIT FINDING ALGORITHM
2.4 对比GBDT
(1)xgboost相比传统gbdt有何不同?xgboost为什么快?xgboost如何支持并行?
- 传统GBDT以CART作为基分类器,xgboost还支持线性分类器,这个时候xgboost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。
- 传统GBDT在优化时只用到一阶导数信息,xgboost则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。顺便提一下,xgboost工具支持自定义代价函数,只要函数可一阶和二阶求导。
- xgboost在代价函数里加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、每个叶子节点上输出的score的L2模的平方和。从Bias-variance tradeoff角度来讲,正则项降低了模型的variance,使学习出来的模型更加简单,防止过拟合,这也是xgboost优于传统GBDT的一个特性。
- Shrinkage(缩减):相当于学习速率(xgboost中的eta)。xgboost在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。实际应用中,一般把eta设置得小一点,然后迭代次数设置得大一点。(补充:传统GBDT的实现也有学习速率)
- 列抽样(column subsampling):xgboost借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算,这也是xgboost异于传统gbdt的一个特性。
- 对缺失值的处理。对于特征的值有缺失的样本,xgboost可以自动学习出它的分裂方向。XGBoost对于确实值能预先学习一个默认的分裂方向
- xgboost工具支持并行。boosting不是一种串行的结构吗?怎么并行的?注意xgboost的并行不是tree粒度的并行,xgboost也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含了前面t-1次迭代的预测值)。xgboost的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),xgboost在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。
- 直方图算法: 树节点在进行分裂时,我们需要计算每个特征的每个分割点对应的增益,即用贪心法枚举所有可能的分割点。当数据无法一次载入内存或者在分布式情况下,贪心算法效率就会变得很低,所以xgboost还提出了一种可并行的近似直方图算法,用于高效地生成候选的分割点
(2)机器学习算法中GBDT和XGBOOST的区别有哪些?