赞
踩
在XGBoost中,使用的目标函数相比于GBDT,添加一个新的正则项,主要目的是衡量模型的复杂程度,直接在损失函数中直接控制树的复杂度。
O
b
j
=
∑
i
=
1
N
L
(
y
i
,
F
(
x
i
)
)
⏟
训练损失
+
∑
m
=
1
M
Ω
(
h
m
)
⏟
树的复杂度
,
Obj=\sum_{i=1}^N \underbrace{L\left(y_i, F(\mathbf{x}_i)\right)}_{\text {训练损失}}+\sum_{m=1}^M \underbrace{\Omega\left(h_{m}\right)}_{\text {树的复杂度}},
Obj=i=1∑N训练损失
L(yi,F(xi))+m=1∑M树的复杂度
Ω(hm),
Ω
(
h
)
=
λ
J
J
+
1
2
λ
w
∥
w
∥
2
.
\Omega(h)=\lambda_J J+\frac{1}{2} \lambda_w\|w\|^{2}.
Ω(h)=λJJ+21λw∥w∥2.
其中,
λ
J
\lambda_J
λJ与
λ
w
\lambda_w
λw为惩罚项系数,
J
J
J为叶子数。我们定义每个叶子节点都会有一个权重得分
w
j
w_j
wj(可以理解为前文所述的
ρ
j
m
\rho_{jm}
ρjm),
∥
w
∥
2
\|w\|^{2}
∥w∥2为叶子得分的L2范数。目标函数所加的正则化帮助平滑最终学习的权重避免过拟合。正则化对象将倾向于挑选出一个简单、可预测的函数。我们的目标函数和相应的学习算法更为简单,更容易并行化。当正则化参数设置为0时,目标函数将变为传统的GBDT框架。
下面我们考虑二阶Taylor展开近似估计损失函数。首先回忆二阶泰勒展开:当函数
f
(
x
)
f(x)
f(x)在点
x
x
x处可导时,在其邻域内,恒有:
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.
我们的损失函数又可写为下述形式,
O
b
j
(
m
)
=
∑
i
=
1
N
L
(
y
i
,
F
m
−
1
(
x
i
)
+
h
m
(
x
i
)
)
+
Ω
(
h
m
)
.
O b j^{(m)}=\sum_{i=1}^{N} L\left(y_i, F_{m-1}(\mathbf{x}_i)+h_m(\mathbf{x }_i)\right)+\Omega\left(h_{m}\right).
Obj(m)=i=1∑NL(yi,Fm−1(xi)+hm(xi))+Ω(hm).
对上式进行泰勒展开,可得:
O
b
j
(
m
)
≈
∑
i
=
1
n
[
L
(
y
i
,
F
m
−
1
(
x
i
)
)
+
f
i
,
m
−
1
h
m
(
x
i
)
+
1
2
g
i
,
m
−
1
h
m
2
(
x
i
)
]
+
Ω
(
h
m
)
,
O b j^{(m)} \approx \sum_{i=1}^{n}\left[L\left(y_{i}, F_{m-1}(\mathbf{x}_i)\right)+f_{i,m-1} h_{m}\left(\mathbf{x}_{i}\right)+\frac{1}{2} g_{i,m-1} h_{m}^{2}\left(\mathbf{x}_{i}\right)\right]+\Omega\left(h_{m}\right),
Obj(m)≈i=1∑n[L(yi,Fm−1(xi))+fi,m−1hm(xi)+21gi,m−1hm2(xi)]+Ω(hm),
其中
f
i
,
m
−
1
=
∂
L
(
y
i
,
F
m
−
1
(
x
i
)
)
∂
F
m
−
1
(
x
i
)
,
g
i
,
m
−
1
=
∂
2
L
(
y
i
,
F
m
−
1
(
x
i
)
)
∂
F
m
−
1
2
(
x
i
)
.
f_{i,m-1}=\dfrac{\partial L\left(y_{i}, F_{m-1}(\mathbf{x}_i)\right)}{\partial F_{m-1}(\mathbf{x}_i)} , \quad g_{i,m-1}=\dfrac{\partial^2 L\left(y_{i}, F_{m-1}(\mathbf{x}_i)\right)}{\partial F_{m-1}^2(\mathbf{x}_i)}.
fi,m−1=∂Fm−1(xi)∂L(yi,Fm−1(xi)),gi,m−1=∂Fm−12(xi)∂2L(yi,Fm−1(xi)).
若使用平方损失,则:
f
i
,
m
−
1
=
∂
(
y
i
−
F
m
−
1
(
x
i
)
)
2
∂
F
m
−
1
(
x
i
)
=
2
(
F
m
−
1
(
x
i
)
−
y
i
)
,
f_{i,m-1}=\dfrac{\partial \left(y_{i} - F_{m-1}(\mathbf{x}_i)\right)^2}{\partial F_{m-1}(\mathbf{x}_i)}=2\left(F_{m-1}(\mathbf{x}_i)-y_{i}\right),
fi,m−1=∂Fm−1(xi)∂(yi−Fm−1(xi))2=2(Fm−1(xi)−yi),
g
i
,
m
−
1
=
∂
2
(
y
i
−
F
m
−
1
(
x
i
)
)
2
∂
F
m
−
1
2
(
x
i
)
=
2.
g_{i,m-1}=\dfrac{\partial^2 \left(y_{i} - F_{m-1}(\mathbf{x}_i)\right)^2}{\partial F_{m-1}^2(\mathbf{x}_i)}=2.
gi,m−1=∂Fm−12(xi)∂2(yi−Fm−1(xi))2=2.
下面我们移除常数项
L
(
y
i
,
F
m
−
1
(
x
i
)
)
L\left(y_{i}, F_{m-1}(\mathbf{x}_i)\right)
L(yi,Fm−1(xi)),并且定义
I
j
I_j
Ij为叶子节点
j
j
j里面的所有样本,进行函数化简:
O
b
j
~
(
m
)
=
∑
i
=
1
n
[
f
i
,
m
−
1
h
m
(
x
i
)
+
1
2
g
i
,
m
−
1
h
m
2
(
x
i
)
]
+
Ω
(
h
m
)
=
∑
i
=
1
n
[
f
i
,
m
−
1
h
m
(
x
i
)
+
1
2
g
i
,
m
−
1
h
m
2
(
x
i
)
]
+
λ
J
J
+
1
2
λ
w
∑
j
=
1
J
w
j
2
=
∑
j
=
1
J
[
(
∑
i
∈
I
j
f
i
,
m
−
1
)
w
j
+
1
2
(
∑
i
∈
I
j
g
i
,
m
−
1
+
λ
w
)
w
j
2
]
+
λ
J
J
.
由于上述中括号内是关于叶子节点权值
w
j
w_j
wj的开口向上的二次方程,因此有最小值,我们可以计算每个叶子节点
j
j
j的最优权重
w
j
∗
w_{j}^{*}
wj∗:
w
j
∗
=
−
∑
i
∈
I
j
f
i
,
m
−
1
∑
i
∈
I
j
g
i
,
m
−
1
+
λ
w
,
w_{j}^{*}=-\frac{\sum_{i \in I_{j}} f_{i,m-1}}{\sum_{i \in I_{j}} g_{i,m-1}+\lambda_w},
wj∗=−∑i∈Ijgi,m−1+λw∑i∈Ijfi,m−1,
而后将其代入原目标函数,得到:
O
b
j
~
(
m
)
=
−
1
2
∑
j
=
1
J
(
∑
i
∈
I
j
f
i
,
m
−
1
)
2
∑
i
∈
I
j
g
i
,
m
−
1
+
λ
w
+
λ
J
J
.
\widetilde{Obj}^{(m)}=-\frac{1}{2} \sum_{j=1}^{J} \frac{\left(\sum_{i \in I_{j}} f_{i,m-1}\right)^{2}}{\sum_{i \in I_{j}} g_{i,m-1}+\lambda_w}+\lambda_J J.
Obj
(m)=−21j=1∑J∑i∈Ijgi,m−1+λw(∑i∈Ijfi,m−1)2+λJJ.
上式可以作为一个评估方程去评价一棵树的结构。这个打分就像评估决策树的不纯度得分,不同的是它是为了更广泛的目标函数所导出。但通常来说我们不可能枚举出所有的树结构,而是使用贪心算法,从一个叶子节点开始分裂,反复给树添加分支。假设
I
L
I_L
IL和
I
R
I_R
IR是一个叶子节点分裂后左右节点中包含的样本集合,满足叶子节点的样本集合
I
=
I
L
∪
I
R
I=I_L \cup I_R
I=IL∪IR。我们可以类似决策树的分裂,定义一个收益函数,用于评价候选分裂节点的好坏:
Gain
=
1
2
[
(
∑
i
∈
I
L
f
i
)
2
∑
i
∈
I
L
g
i
+
λ
w
+
(
∑
i
∈
I
R
f
i
)
2
∑
i
∈
I
R
g
i
+
λ
w
−
(
∑
i
∈
I
f
i
)
2
∑
i
∈
I
g
i
+
λ
w
]
−
λ
J
.
\text {Gain}=\frac{1}{2}\left[\frac{\left(\sum_{i \in I_{L}} f_{i}\right)^{2}}{\sum_{i \in I_{L}} g_{i}+\lambda_w}+\frac{\left(\sum_{i \in I_{R}} f_{i}\right)^{2}}{\sum_{i \in I_{R}} g_{i}+\lambda_w}-\frac{\left(\sum_{i \in I} f_{i}\right)^{2}}{\sum_{i \in I} g_{i}+\lambda_w}\right]-\lambda_J.
Gain=21[∑i∈ILgi+λw(∑i∈ILfi)2+∑i∈IRgi+λw(∑i∈IRfi)2−∑i∈Igi+λw(∑i∈Ifi)2]−λJ.
XGBoost中的一个关键问题在于寻找每个分裂时的最优划分点。一种最简单的方法是寻找所有特征的所有划分点,也就是精确贪心算法(Exact Greedy Algorithm)。其需首先对所有样本的所有变量进行排序,而后再进行遍历搜索。
当数据量过大时,传统算法便不再适用,这是由于需要遍历每个分割点,其需要大的内存,以及耗费大量的时间。因此,XGBoost中提出了另外一种近似算法,其在几乎不影响算法精度的前提下,能加快运行的时间。
此算法主要思想为:枚举所有特征,根据特征的分布情况,(例如:第 k k k个特征的分布的分位数来决定出 l l l个候选切分点),根据候选切分点将相应的样本映射到对应的桶中,进行分桶(一个桶为一个区间段),而后对每个桶的 ν , ξ \nu, \xi ν,ξ 进行累加,最后在候选切分点集合上按照前文所述算法进行贪心查找。
注意这里有两种分桶的方式,一种是全局的,一种是局部的。
\begin{itemize}
\item 全局的是在建树之前就构建相应的
S
k
S_k
Sk,之后的每次分割都在全局的
S
k
S_k
Sk中寻找划分点;
\item 局部的方法是在每次节点分裂之后重新更新
S
k
S_k
Sk。
\end{itemize}
通常而言,全局选择 S k S_k Sk的方法需要设置更小的分位数间隔,才能取得比较好的效果,而局部的方法可以设置更大的分位数间隔,从而产生更少的桶.每次特征分裂查找基于较少的候选点,计算速度快,但是需要每次节点分裂后重新执行。
下面我们将介绍关于分位数的具体选取方式。首先我们继续用样本的方式对原本的目标函数进行分解。
O
b
j
~
(
m
)
≈
∑
i
=
1
n
[
f
i
,
m
−
1
h
m
(
x
i
)
+
1
2
g
i
,
m
−
1
h
m
2
(
x
i
)
]
+
Ω
(
h
m
)
=
∑
i
=
1
N
1
2
g
i
,
m
−
1
(
2
f
i
,
m
−
1
g
i
,
m
−
1
h
m
(
x
i
)
+
h
m
2
(
x
i
)
)
+
Ω
(
h
m
)
=
∑
i
=
1
N
1
2
g
i
,
m
−
1
(
f
i
,
m
−
1
2
g
i
,
m
−
1
2
+
2
f
i
,
m
−
1
g
i
,
m
−
1
h
m
(
x
i
)
+
h
m
2
(
x
i
)
)
+
Ω
(
h
m
)
−
f
i
,
m
−
1
2
2
g
i
,
m
−
1
=
∑
i
=
1
N
1
2
g
i
,
m
−
1
(
h
m
(
x
i
)
−
(
−
f
i
,
m
−
1
g
i
,
m
−
1
)
)
2
+
Ω
(
h
m
)
−
f
i
,
m
−
1
2
2
g
i
,
m
−
1
=
∑
i
=
1
N
1
2
g
i
,
m
−
1
(
h
m
(
x
i
)
−
(
−
f
i
,
m
−
1
g
i
,
m
−
1
)
)
2
+
Ω
(
h
m
)
+
constant
上式可视为标签为 − f i , m − 1 g i , m − 1 -\dfrac{f_{i,m-1}}{g_{i,m-1}} −gi,m−1fi,m−1,权重为二阶梯度 g i , m − 1 g_{i,m-1} gi,m−1的平方误差。我们将权重 g i , m − 1 g_{i,m-1} gi,m−1引入 S k S_k Sk的选取中,使用加权分位数的方式来确定 S k S_k Sk。
下面考虑另一个问题,当数据为稀疏的情形。许多真实世界中的问题,数据可能是稀疏的,主要原因如:存在缺失值、大量的0项、特征工程中one-hot等编码。因此,对于数据的稀疏模式感知(Sparsity-aware)非常重要。本文算法提出了在每个树节点提添加一个默认方向。即当一个实例值是缺失时,该实例会被分类到默认方向,当一个值是缺失值时,我们就把他分类到默认方向,每个分支有两个选择,具体的选择需要通过枚举向左和向右的情况,根据score的大小进行确定。
利用树进行学习的一些模型算法,大多数时间消耗在获取数据的排序上。为了降低排序成本,我们将数据存储在内存单元中,称为block。每个block的数据以列压缩CSC形式存储,每列通过对应的特征值排序。输入数据仅需要在训练之前计算一次,之后的迭代中可以重复使用。
在精确贪心中,我们存储整个数据集在一个block中,通过对预排序的样本线性扫描运行分割搜索算法。我们对所有的叶子节点做分割点查找,因此对block的一次扫描可以收集所有叶子分支上分裂候选点的统计信息。
block结构对近似算法也是有帮助的。可以使用多个block,每个block对应数据集中行的子集,不同的block可以分布在不同的机器上或者核外硬盘上。使用排序结构,分位数查找步骤变成对排序后的列进行线性查找。这对局部近似变体是非常有价值的,因为该变体在每个分支频繁的产生候选点。
搜集每列的统计信息也可以是并行化的,更重要的是,列block结构支持列采样,其可使在每个block中搜集列子集的统计信息变得非常容易。
虽然提出的block结构能够帮助优化划分查找的计算复杂度,但由于在顺序访问特征值时,访问的是一块连续的内存空间。需要间接通过行索引获取样本梯度信息,这并不是连续的内存空间。
对于精确贪心算法,我们使用缓存预取算法来进行优化。即我们在每个线程中分配一个缓存,读取梯度信息存储在其中。这种预取策略能够有效降低当数据量过大时的程序运行时间。
而在近似算法中,XGBoost对block的大小进行了合理的设置,其为block中最多的样本数。设置合适的大小非常重要,设置过大则容易导致缓存丢失,因为梯度统计数据没有进入CPU缓存;过小则会导致并行化效率不高。经过实验,发现比较好。
系统目标在于充分利用机器资源进行可扩展的学习。除了处理器和内存之外,利用磁盘空间处理不能放入内存的数据是很重要的。为了确保核外计算,我们将数据划分为多个block,并将block存入磁盘。在计算中使用独立线程预读取block到缓存。因此计算和磁盘读取可以同步进行。但是由于磁盘读取耗时过长,降低开销和增加磁盘IO的吞吐量非常重要。XGBoost主要使用两种技术来改进核外计算。
数据块压缩:对数据块按列压缩,在将数据传输到内存中,最后再由独立的线程解压缩。即将磁盘的读取消耗转换为解压缩所消耗的计算资源。
数据块分区:将数据分片到多个磁盘,每个磁盘分配一个预取线程,将数据拿到in-memory 缓存,再从各个缓存中读取数据。训练线程交替读取多块缓存的同时,计算任务也在运转,提升了硬盘总体的吞吐量。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。