XGBoost库学习
一、前言
XGboost 梯度加速决策树集成学习,是在GBDT后出现的一个应用广泛且框架稳定的模型.其出现源于原梯度加速模型在大型数据上计算量大,运行速度缓慢;二是随着数据库的积累与丰富,对更高性能,精度与效率的模型需求越来越高;两大原因催生了该模型的出现.接下来我们将首先了解熟悉该模型的原理,优化点;然后是对应的库的实现方法,相应参数,重要属性使用,与实践中的一些注意事项.
1、原理
经典的GBDT原理在另一篇博文【python库学习】sklearn集成学习ensemble模块学习,这里就不再展开叙述.只需要了解其本质思想是构建多个基学习器使用加法模型获得最终输出,学习前面基学习器的结果与真实值的偏差,通过多个学习器的学习,不断降低模型值和实际值的差。XGBoost模型与其思想一致,实现细节差别在于, XGBoost是基于损失函数,进行泰勒二阶展开,得到损失函数,通过对损失函数的求导确定节点的最佳输出(权重),再通过损失函数在不同节点(待划分节点与划分后的节点)的值确定树的结构,不断迭代,得到多颗树以及最终模型。所以XGBoost不是通过拟合残差实现的,而是计算损失函数直接得到树结构。
其中第i个样本的预测如下:
o
u
t
p
u
t
(
y
i
′
)
=
F
(
x
i
)
=
∑
k
K
f
k
(
x
i
)
−
−
(
1
)
output(y_i^{'})=F(x_i)=\sum_{k}^{K}f_k(x_i) \space\space\space\space--(1)
output(yi′)=F(xi)=k∑Kfk(xi) −−(1)
1)、损失函数
XGBoost损失函数如下,前面是损失函数,后面部分是正则化部分,接下来对两部分进行展开.
L
=
∑
i
l
(
y
i
,
y
i
′
)
+
∑
k
Ω
(
f
k
)
−
−
(
2
)
L=\sum_{i}^{} l(y_i,y_i^{'})+\sum_{k}^{} \Omega (f_k) \space\space\space\space--(2)
L=i∑l(yi,yi′)+k∑Ω(fk) −−(2)
,其中
y
i
y_i
yi为第
i
i
i个样本实际值,损失函数计算样本预测与实际之间的偏差,正则部分为多个基模型的正则和,其中
Ω
(
f
k
)
\Omega (f_k)
Ω(fk)为第
k
k
k个基学习器的正则.
正则部分:
Ω
f
(
k
)
=
γ
T
+
1
2
λ
∑
j
=
1
T
∥
w
j
∥
2
\Omega f(k)=\gamma T+\frac{1}{2} \lambda\sum_{j=1}^{T} \left \| w_j \right \| ^{2}
Ωf(k)=γT+21λj=1∑T∥wj∥2
,其中
γ
\gamma
γ与
λ
\lambda
λ为超参数,控制惩罚力度,
T
T
T为当前树的叶节点数,
∑
j
=
1
T
∥
w
j
∥
2
\sum_{j=1}^{T} \left \| w_j \right \| ^{2}
∑j=1T∥wj∥2为每个叶节点输出值平方加和.
从模型的顺序训练可知,其k以前的树模型已经训练完毕,因此其正则也已确定,由此正则项便为下面式子:
∑
k
Ω
(
f
k
)
≈
Ω
(
f
k
)
\sum_{k}^{} \Omega (f_k)\approx \Omega (f_k)
k∑Ω(fk)≈Ω(fk)
损失部分:
为了适用更多损失函数类型,以及更高模型精度,使用二阶泰勒展开式对损失函数进行展开.
泰勒二阶展开公式
:
f
(
x
)
≈
f
(
x
0
)
+
f
(
x
o
)
′
(
x
−
x
0
)
+
1
2
f
(
x
o
)
′
′
(
x
−
x
0
)
2
泰勒二阶展开公式:f(x)\approx f(x_0)+f(x_o)^{'}(x-x_0)+\frac{1}{2} f(x_o)^{''}(x-x_0)^{2}
泰勒二阶展开公式:f(x)≈f(x0)+f(xo)′(x−x0)+21f(xo)′′(x−x0)2
由公式(1)可知预测值的构成,由顺序训练拟合基模型可知,k以前的树模型输出已确定,因此预测值可以表示为如下形式:
y
i
′
=
∑
j
k
−
1
f
j
(
x
i
)
+
f
k
(
x
i
)
y_i^{'}=\sum_{j}^{k-1} f_j(x_i)+f_k(x_i)
yi′=j∑k−1fj(xi)+fk(xi)
则泰勒展开公式中的
x
0
x_0
x0是
∑
j
k
−
1
f
j
(
x
i
)
\sum_{j}^{k-1} f_j(x_i)
∑jk−1fj(xi),
(
x
−
x
0
)
(x-x_0)
(x−x0) 是
f
k
(
x
i
)
f_k(x_i)
fk(xi),损失函数中y是标签值也已知,因此损失函数二阶展开如下:
l
(
y
i
,
y
i
′
)
=
l
(
y
i
,
∑
j
k
−
1
f
j
(
x
i
)
)
+
g
i
f
k
(
x
i
)
+
1
2
h
i
f
k
(
x
i
)
2
l(y_i,y_i^{'})=l(y_i,\sum_{j}^{k-1} f_j(x_i))+g_if_k(x_i)+\frac{1}{2} h_if_k(x_i)^{2}
l(yi,yi′)=l(yi,j∑k−1fj(xi))+gifk(xi)+21hifk(xi)2
,其中是
g
i
g_i
gi一阶导,
h
i
h_i
hi是二阶导.第一项是常数项,忽略,可得到近似损失函数如下:
L
≈
∑
i
n
[
g
i
f
k
(
x
i
)
+
1
2
h
i
f
k
(
x
i
)
2
]
+
γ
T
+
1
2
λ
∑
j
=
1
T
w
j
2
L\approx\sum_{i}^{n}[g_if_k(x_i)+\frac{1}{2} h_if_k(x_i)^{2}]+\gamma T+\frac{1}{2} \lambda\sum_{j=1}^{T}w_j^{2}
L≈i∑n[gifk(xi)+21hifk(xi)2]+γT+21λj=1∑Twj2
损失函数求解与应用:
我们知道树的决策边界不是连续的,其每个样本会落在具体的叶节点上,该叶节点的输出即为该样本的输出,定义
I
j
=
{
i
∣
q
(
x
i
)
=
w
j
}
I_j=\left \{ i|q(x_i)=w_j \right \}
Ij={i∣q(xi)=wj} 是落在叶节点j的样本集,则损失函数可以改写为如下形式:
L
=
∑
j
=
1
T
[
∑
i
∈
I
j
(
g
i
)
w
j
+
1
2
(
∑
i
∈
I
j
h
i
+
λ
)
w
j
2
]
+
γ
T
L=\sum_{j=1}^{T}[\sum_{i\in{I_j} }^{} (g_i)w_j+\frac{1}{2}(\sum_{i\in{I_j}}h_i+\lambda)w_j^{2}]+\gamma T
L=j=1∑T[i∈Ij∑(gi)wj+21(i∈Ij∑hi+λ)wj2]+γT
当树结构确定时,我们可以求导得到最佳叶节点输出(损失函数是二阶函数为凸函数),由此最佳值也可以计算得到:
w
j
∗
=
∑
i
∈
I
j
(
g
i
)
∑
i
∈
I
j
h
i
+
λ
w_{j}^{*} =\frac{\sum_{i\in{I_j} }^{} (g_i)}{\sum_{i\in{I_j}}h_i+\lambda}
wj∗=∑i∈Ijhi+λ∑i∈Ij(gi)
L
(
q
)
=
γ
T
−
1
2
∑
j
=
1
T
(
∑
i
∈
I
j
g
i
)
2
∑
i
∈
I
j
h
i
+
λ
L(q) =\gamma T-\frac{1}{2}\sum_{j=1}^{T} \frac{(\sum_{i\in{I_j} }^{} g_i)^2}{\sum_{i\in{I_j}}h_i+\lambda}
L(q)=γT−21j=1∑T∑i∈Ijhi+λ(∑i∈Ijgi)2
在寻找划分点时,结构已知(划分后添加左右结构),则可以借助最佳值进行划分前后的衡量,其计算公式如下(划分后左右最佳值的和减去划分前的最佳值):
L
s
p
l
i
t
=
1
2
[
(
∑
i
∈
I
L
g
i
)
2
∑
i
∈
I
L
h
i
+
λ
+
(
∑
i
∈
I
R
g
i
)
2
∑
i
∈
I
R
h
i
+
λ
−
(
∑
i
∈
I
g
i
)
2
∑
i
∈
I
h
i
+
λ
]
−
γ
L_{split} =\frac{1}{2}\left [ \frac{(\sum_{i\in{I_L} }^{} g_i)^2}{\sum_{i\in{I_L}}h_i+\lambda}+\frac{(\sum_{i\in{I_R} }^{} g_i)^2}{\sum_{i\in{I_R}}h_i+\lambda}-\frac{(\sum_{i\in{I} }^{} g_i)^2}{\sum_{i\in{I}}h_i+\lambda} \right ]-\gamma
Lsplit=21[∑i∈ILhi+λ(∑i∈ILgi)2+∑i∈IRhi+λ(∑i∈IRgi)2−∑i∈Ihi+λ(∑i∈Igi)2]−γ
2)、划分搜寻算法
由前面的损失函数可以知道,损失函数值可以用于衡量划分点.经典的划分算法是贪心算法,每一次划分都挨个计算所有候选点,比较其收益,取最佳收益的候选点为划分点.该方法保证了精度,但是在数据量较大的情况下会有计算资源需求过大,运行缓慢的问题.因此XGBoost还推出了一个近似算法,其具体原理如下:
1、构建多维数据集
D
k
=
{
(
x
1
k
,
h
1
)
,
(
x
2
k
,
h
2
)
,
.
.
.
,
(
x
n
k
,
h
n
)
}
D_k=\left \{ (x_{1k},h_1),(x_{2k},h_2),...,(x_{nk},h_n) \right \}
Dk={(x1k,h1),(x2k,h2),...,(xnk,hn)},代表样本第k个特征,以及样本对应的二阶导数.
2、定义排序函数
r
k
(
z
)
=
∑
(
x
,
h
)
∈
D
k
,
x
<
z
h
∑
(
x
,
h
)
∈
D
k
h
,
r
k
≥
0
r_k(z)=\frac{ {\textstyle \sum_{(x,h)\in D_k,x<z}^{}h}}{\sum_{(x,h)\in D_k}^{}h } , r_k\ge 0
rk(z)=∑(x,h)∈Dkh∑(x,h)∈Dk,x<zh,rk≥0,特征k小于z下的样本对应的二阶导和占全部二阶导的比例.
3、根据一个近似因子
ϵ
\epsilon
ϵ,将特征从小到大排序后,通过排序函数计算各样本排序累计取值,由近似因子进行划分得到1/
ϵ
\epsilon
ϵ个候选划分点
{
s
k
1
,
s
k
2
,
…
,
s
k
l
}
\left \{ s_{k1},s_{k2},\dots ,s_{kl} \right \}
{sk1,sk2,…,skl}.候选划分点计算公式如下:
∣
r
k
(
s
k
,
j
)
−
r
k
(
s
k
,
j
+
1
)
∣
<
ϵ
,
s
k
1
=
m
i
n
(
x
i
k
)
,
s
k
l
=
m
a
x
(
x
i
k
)
\left | r_k(s_k,j)-r_k(s_k,j+1)\right |<\epsilon ,s_{k1}=min(x_{ik}),s_{kl}=max(x_{ik})
∣rk(sk,j)−rk(sk,j+1)∣<ϵ,sk1=min(xik),skl=max(xik)
其中样本二阶导作为权重的原因是其在损失函数中起到权重作用(将损失函数改写为如下方式更好理解):
L
≈
∑
i
n
1
2
h
i
(
f
k
(
x
i
)
−
g
i
/
h
i
)
2
+
γ
T
+
1
2
λ
∑
j
=
1
T
w
j
2
L\approx \sum_{i}^{n}\frac{1}{2}h_i (f_k(x_i)-g_i/h_i)^2+\gamma T+\frac{1}{2} \lambda\sum_{j=1}^{T}w_j^{2}
L≈i∑n21hi(fk(xi)−gi/hi)2+γT+21λj=1∑Twj2
应用方式:
1、全局应用:在建立第k棵树的时候利用样本的二阶梯度对样本进行排序,每一维的特征都得到候选划分点。在建树的过程中,我们就重复利用这些划分点进行分支判断。
2、局部应用:每次进行分支时,重新计算每个候选特征的排序函数取值进行重排序后得到新的候选划分点,再进行分支判断。
其中近似因子设置在不同应用中,同效果下,局部应用近似算法可以设置更大的近似因子(得到更少候选点),全局应用需要设置更小的近似因子(得到更多候选点).
3)、稀疏处理策略
稀疏情况可能由如下情况导致:
- 样本中的缺失值
- 样本特征统计中0值频率大
- 特征工程中如onehot操作导致大量0值产生
在寻找划分点时,样本若有缺失,在划分时,默认划分至收益最大的那个分支,其具体原理如下:
- 获取剔除缺失值后的数据集 I k I_k Ik
- 已知待划分点的一阶导统计值 G G G,二阶导统计值 H H H
- 采用近似法对 I k I_k Ik计算候选划分点
- 选定某一候选划分点,将缺失值样本划入左子树,则可以计算右子树的一阶导统计值 G R G_R GR,二阶导统计值 H R H_R HR,使用待划分点的一阶导统计值 G G G,二阶导统计值 H H H减去右子树的,得到左子树的一阶导统计值 G L G_L GL,二阶导统计值 H L H_L HL.由此得到左右子树的统计值后,可以计算出该划分点的增益为 g a i n = G R 2 H R + λ + G L 2 H L + λ − G 2 H + λ gain=\frac{G_R^2}{H_R+\lambda}+\frac{G_L^2}{H_L+\lambda}-\frac{G^2}{H+\lambda} gain=HR+λGR2+HL+λGL2−H+λG2
- 计算该特征所有候选划分点的增益,取最大增益gain_L
- 重复4~5点计算该特征所有候选划分点下,缺失值样本划入右子树的最大增益gain_R
- 比较max(gain_L,gain_R )得到该特征最佳划分点与划分方式(划入左子树/右子树)
- 重复4~7点计算所有候选特征,得到各特征最佳划分点与划分方式下的增益,取最大增益对应的特征与划分点,划分方式为该待划分节点的最终划分.
该模式不光可用于缺失,稀疏的其它情况也可以使用该模式处理;此外该模式的计算复杂度是非缺失样本数的线性关系.
2、框架设计
1)、block结构
整个树学习中最耗时的部分为数据排序过程,为降低该过程耗时,提出了内存单元的数据存储,简称为block.每个block存储排序后的特征列以及指向梯度统计值的索引.
-
搜寻划分点
只需扫描一次预排序的block结构即可得到候选点划分后的分支所有统计值,由此得到候选点的划分增益;因此使用该结构可快速完成划分点的寻找. -
候选点近似算法
排序函数计算可以通过多个block并行计算
2)、缓存处理
由block结构可知,在根据排序特征获取统计值时需要根据指针去获取,因此该过程不是到连续内存中获取统计值,计算时去获取统计值时可能存在cpu内存不够导致寻找失败问题.因此在计算之前加一道缓存处理,可以有效缓解该问题.
对于绝对贪心算法,我们给每一个线程分配一个中间缓存区,在计算前先获取到统计值放入中间缓存区中,这样可以有效避免边计算边读取导致依赖时间过长,运行超时,寻找失败问题.
对于近似算法,有block结构,重点是设置好该结构的大小,block过小,工作负载低,每个线程运行不够充分,导致并行效率低下;block过大又容易导致致依赖时间过长,运行超时,寻找失败问题.经实验得到block设置为2^16大小时可以较好的兼顾两者
3)、核外运算
除了进程与内存的使用,利用好磁盘空间对未在主内存中的数据进行计算也很关键.xgboost实现中使用了两种技术来加快核外运算.
block压缩:控制好压缩与解压耗时,当装载进主内存时采用独立线程进行处理.
block分片:将数据分布到多个磁盘上,在读取时每个磁盘一个线程读取到内存缓冲中.然后训练线程从每个缓冲器中读取数据。这有助于增加多个磁盘的磁盘读取吞吐量.
3、计算复杂度
假定样本数n,非缺失样本数为m,树的最大深度为D,树一共K颗,则原梯度提升算法的时间复杂度为O(DKmlogn),
使用block结构后,只需要预处理搭建block,后续计算是线性级别的,扫描一次即可得,因此时间复杂度为O(DKm+mlogn),其中mlogn为预处理计算复杂度.
使用近似算法,定义候选点为q个,则时间复杂度为O(DKmlogq)
同时使用block结构与近似算法,则时间复杂度为O(DKm+mlogB),其中B是block结构中最大行数.
二、XGBoost 库
1、参数配置
在使用该库时,通常需要配置三种参数,一是选择何种基模型,二是对基模型相关参数的配置,三是学习任务的相关配置(如回归任务与排序任务配置就会有所不同).
该库支持三种基模型决策树,DArt,线性模型,不同基模型有对应的参数设置,这里不再展开,可以看其文档.
学习任务的相关参数配置如下:
序号 | 参数 | 参数含义 | 说明 |
---|---|---|---|
1 | objective | 目标函数 | 基本都支持,实际使用根据任务类型而定 |
2 | base_score | 基本分数 | 所有样本的初始预测,代表全局偏差;由选定目标函数后自动估算 |
3 | eval_metric | 评估方式 | 跟目标函数相关,如分类任务选定目标函数binary:logistic可以看auc |
4 | seed | 随机种子 | 无需多言,与其它算法作用类似 |
5 | seed_per_iteration | 每次迭代随机种子 | 随迭代次数不同,随机种子不同,由迭代次数决定其具体值 |