XGBoost原理
学习目标
1、数据:
D
=
{
(
x
i
,
y
i
)
}
,
(
∣
D
∣
=
n
,
x
i
∈
R
m
,
y
i
∈
R
)
D=\{(x_{i}, y_{i})\}, (|D|=n, x_{i}∈R^m, y_{i}∈R)
D={(xi,yi)},(∣D∣=n,xi∈Rm,yi∈R)。
2、模型:
y
^
i
=
∑
k
=
1
K
f
k
(
x
i
)
,
f
k
∈
F
,
\hat{y}_{i}=\sum_{k=1}^{K} f_{k}\left(x_{i}\right), \quad f_{k} \in \mathcal{F},
y^i=k=1∑Kfk(xi),fk∈F,
其中,
F
=
{
f
(
x
)
=
w
q
(
x
)
}
(
q
:
R
m
→
T
,
w
∈
R
T
)
\mathcal{F}=\{f(x)=w_{q}(x)\}(q:R^m→T,w∈R^T)
F={f(x)=wq(x)}(q:Rm→T,w∈RT)是一个树模型(CART树)空间,
T
T
T是叶节点的个数、
w
w
w是叶节点的权重。模型由
K
K
K个树模型进行加法模型生成,每棵树去拟合上一棵树的残差,每个
f
k
f_{k}
fk都有独立的树结构
q
q
q和叶节点权重
w
w
w。
3、目标函数:
L
(
ϕ
)
=
∑
i
l
(
y
^
i
,
y
i
)
+
∑
k
Ω
(
f
k
)
where
Ω
(
f
)
=
γ
T
+
1
2
λ
∥
w
∥
2
\begin{aligned} \mathcal{L}(\phi)=\sum_{i} l\left(\hat{y}_{i}, y_{i}\right)+\sum_{k} \Omega\left(f_{k}\right) \\ \text { where } \Omega(f)=\gamma T+\frac{1}{2} \lambda\|w\|^{2} \end{aligned}
L(ϕ)=i∑l(y^i,yi)+k∑Ω(fk) where Ω(f)=γT+21λ∥w∥2
损失函数:
l
(
y
i
,
y
^
i
)
l\left(y_{i}, \hat{y}_{i}\right)
l(yi,y^i) ,计算实际值
y
i
y_{i}
yi与
y
^
i
\hat{y}_{i}
y^i之前的差异;
正则项:惩罚模型复杂度,控制叶节点的个数(即
γ
\gamma
γ);通过平滑最终的学习权重来避免过拟合(即
λ
\lambda
λ)。
该目标函数以函数作为参数,所以不能直接使用传统的的优化方法进行优化。
学一棵树的目标是在每个节点以哪个特征、该特征的何值来划分节点。
模型介绍
1、损失函数的改写。令
y
^
i
\hat{y}_{i}
y^i为第
i
i
i个样本在第
t
t
t棵树的预测值。则
y
^
i
t
\hat{y}_{i}^{t}
y^it=
y
^
i
(
t
−
1
)
\hat{y}_{i}^{(t-1)}
y^i(t−1)+
f
t
(
x
i
)
f_{t}(x_{i})
ft(xi)(新的预测值为上一次预测值与最新的函数值之和)。当训练第
t
t
t棵树的时候,前
t
−
1
t-1
t−1棵树的参数都固定了,与其相关的量都是常量。
因此:
L
(
t
)
=
∑
i
l
(
y
i
,
y
^
i
t
)
+
Ω
(
f
t
)
=
∑
i
l
(
y
i
,
y
^
i
(
t
−
1
)
+
f
t
(
x
i
)
)
+
Ω
(
f
t
)
(
1
)
\mathcal{L}^{(t)}=\sum_{i} l\left(y_{i}, \hat{y}^{t}_{i}\right)+\Omega\left(f_{t}\right) =\sum_{i} l\left(y_{i}, \hat y^{(t-1)}_{i}+f_t(x_i)\right)+\Omega\left(f_{t}\right) \quad (1)
L(t)=i∑l(yi,y^it)+Ω(ft)=i∑l(yi,y^i(t−1)+ft(xi))+Ω(ft)(1)
由式(1)可知,目标值与前一次预测值都是已知的,即通过找到
f
t
f_t
ft来最小化目标函数,这意味着贪婪地添加最能改善模型的
f
t
f_t
ft。对损失函数进行二阶泰勒展开得:
L
(
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
)
(
2
)
\mathcal{L}^{(t)} \simeq \sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}^{(t-1)}\right)+g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\Omega\left(f_{t}\right) \quad (2)
L(t)≃i=1∑n[l(yi,y^(t−1))+gift(xi)+21hift2(xi)]+Ω(ft)(2)
其中,
g
i
=
∂
y
^
(
t
−
1
)
l
(
y
i
,
y
^
(
t
−
1
)
)
(
一
阶
导
数
)
,
h
i
=
∂
y
^
(
t
−
1
)
2
l
(
y
i
,
y
^
(
t
−
1
)
)
(
二
阶
导
数
)
g_{i}=\partial_{\hat{y}^{(t-1)}} l\left(y_{i}, \hat{y}^{(t-1)}\right)(一阶导数), \quad h_{i}=\partial_{\hat{y}^{(t-1)}}^{2} l\left(y_{i}, \hat{y}^{(t-1)}\right)(二阶导数)
gi=∂y^(t−1)l(yi,y^(t−1))(一阶导数),hi=∂y^(t−1)2l(yi,y^(t−1))(二阶导数)。
如果损失函数为平方损失,则
l
(
y
i
,
y
^
(
t
−
1
)
)
l(y_{i}, \hat{y}^{(t-1)})
l(yi,y^(t−1))已知,
g
i
g_i
gi已知,
h
i
h_i
hi也是已知,未知的是新函数
f
t
f_t
ft。此时每棵树就是
f
t
(
x
i
)
f_t(x_i)
ft(xi),每棵树的叶节点输出值就是函数的值,叶节点可以枚举,目标函数是求出叶节点输出值,使目标最优。
(注意:泰勒二阶展开式为:
f
(
x
+
Δ
x
)
≃
f
(
x
)
+
f
′
(
x
)
Δ
x
+
1
2
f
′
′
(
x
)
Δ
x
2
f(x+\Delta x) \simeq 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)
因为
∑
i
=
1
n
(
y
i
,
y
^
(
t
−
1
)
)
\sum_{i=1}^{n}\left(y_{i}, \hat{y}^{(t-1)}\right)
∑i=1n(yi,y^(t−1))是常数,因此可将目标函数改写为:
L
~
(
t
)
≃
∑
i
=
1
n
[
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
(
3
)
\tilde{\mathcal{L}}^{(t)} \simeq \sum_{i=1}^{n}\left[g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\Omega\left(f_{t}\right) \quad \quad (3)
L~(t)≃i=1∑n[gift(xi)+21hift2(xi)]+Ω(ft)(3)
由式(3)可知,现在目标函数明确,并且工程上可以很容易在实现的时候进行损失函数的替换。
2、重新定义树。定义一个由叶节点分数值组成的向量表示一棵树,并且由一个map函数来映射一个样本和叶节点的关系。
f
t
(
x
)
=
w
q
(
x
)
,
w
∈
R
T
,
q
:
R
d
→
{
1
,
2
,
⋯
,
T
}
f_{t}(x)=w_{q(x)}, w \in \mathbf{R}^{T}, q: \mathbf{R}^{d} \rightarrow\{1,2, \cdots, T\}
ft(x)=wq(x),w∈RT,q:Rd→{1,2,⋯,T}
其中,
w
w
w代表了树中叶节点权重,
q
q
q代表了树的结构。输入一个样本会落在
q
q
q的一个叶节点上,会获得一个叶节点编号,输入一个叶节点编号,可以在
f
t
(
x
i
)
f_t(x_i)
ft(xi)中获得对于叶节点的权重。
f
t
(
x
i
)
f_t(x_i)
ft(xi)等价于求出
w
q
(
x
i
)
w_q(x_i)
wq(xi)的值(每一个样本所在叶子索引的分数)。
3、定义树的复杂度。正则化部分:
Ω
(
f
t
)
=
γ
T
+
1
2
λ
∑
j
=
1
T
w
j
2
\Omega\left(f_{t}\right)=\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2}
Ω(ft)=γT+21λ∑j=1Twj2,主要用于惩罚模型复杂度,使用到叶节点数和叶节点分数值的
L
2
L_2
L2正则化项来表示模型的复杂度。
T
T
T是叶节点个数,
T
T
T越小,说明树结构越简单,从而不容易过拟合;
w
w
w是树中叶节点的权重(也就是叶节点的值),限制
w
w
w,即保证每颗残差树都是弱分类器,防止过拟合(试想,假如某棵树的
w
w
w很大,则这棵残差树在所有残差树中的占比很大,容易导致过拟合)。
4、重新审视目标函数。定义
I
j
=
{
i
∣
q
(
x
i
)
=
j
}
I_j=\{i|q(x_i)=j\}
Ij={i∣q(xi)=j}作为叶节点
j
j
j的样本集合,可将(3)式通过扩展惩罚项
Ω
\Omega
Ω,根据叶节点重新组合目标函数为:
L
~
(
t
)
=
∑
i
=
1
n
[
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
γ
T
+
1
2
λ
∑
j
=
1
T
w
j
2
=
∑
j
=
1
T
[
(
∑
i
∈
I
j
g
i
)
w
j
+
1
2
(
∑
i
∈
I
j
h
i
+
λ
)
w
j
2
]
+
γ
T
(
4
)
\begin{aligned} \tilde{\mathcal{L}}^{(t)} &=\sum_{i=1}^{n}\left[g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2} \\ &=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T \end{aligned} \quad \quad (4)
L~(t)=i=1∑n[gift(xi)+21hift2(xi)]+γT+21λj=1∑Twj2=j=1∑T⎣⎡⎝⎛i∈Ij∑gi⎠⎞wj+21⎝⎛i∈Ij∑hi+λ⎠⎞wj2⎦⎤+γT(4)
式(4)如何推导得来?它主要体现了将样本的遍历转换为对树的叶节点的遍历(其实做的是一件事情)。假设一共有10个样本,3个叶节点,其中共有3个样本在叶节点1,3个样本在叶节点2,4个样本在叶节点3,式(4)第一行显示直接统计各个样本的值,式(4)第二行显示直接遍历叶节点(每个叶节点上都有对应的样本),同样可以访问到所有的样本,只是在叶节点上遍历更加方便计算(并行计算??)。
式(3)的损失函数描述的是每个样本的损失值之和,式(4)的损失函数描述的是每个叶节点包含的样本权重对应的损失值之和。此时每个叶节点权重未知,其他参数已知,则便可以将式(4)看成是关于
w
j
w_j
wj的一元二次方程,即求解该方程的最小值。
5、叶节点权重与最终目标函数值计算。定义
G
i
=
∑
i
∈
I
j
g
i
G_i={\sum_{i∈I_j}g_i}
Gi=∑i∈Ijgi,
H
i
=
∑
i
∈
I
j
h
i
H_i={\sum_{i∈I_j}h_i}
Hi=∑i∈Ijhi,则对于一个固定的结构
q
(
x
)
q(x)
q(x),可以计算叶节点
j
j
j的最优权重
w
j
∗
w^*_j
wj∗(根据一元二次不等式求对称轴公式):
w
j
∗
=
−
∑
i
∈
I
j
g
i
∑
i
∈
I
j
h
i
+
λ
=
−
G
j
H
j
+
λ
(
5
)
w_{j}^{*}=-\frac{\sum_{i \in I_{j}} g_{i}}{\sum_{i \in I_{j}} h_{i}+\lambda}=-\frac{G_j}{H_j+\lambda} \quad \quad (5)
wj∗=−∑i∈Ijhi+λ∑i∈Ijgi=−Hj+λGj(5)
假如树的结构固定,每个叶节点的权重值为落在该节点的样本数对应的上一次预测损失函数的一阶导与二阶导的值,并且通过计算对应的最优值通过式(6)计算得来(求一元二次不等式极值):
L
~
(
t
)
(
q
)
=
−
1
2
∑
j
=
1
T
(
∑
i
∈
I
j
g
i
)
2
∑
i
∈
I
j
h
i
+
λ
+
γ
T
(
6
)
\tilde{\mathcal{L}}^{(t)}(q)=-\frac{1}{2} \sum_{j=1}^{T} \frac{\left(\sum_{i \in I_{j}} g_{i}\right)^{2}}{\sum_{i \in I_{j}} h_{i}+\lambda}+\gamma T \quad \quad (6)
L~(t)(q)=−21j=1∑T∑i∈Ijhi+λ(∑i∈Ijgi)2+γT(6)
最终目标函数的值就是这棵树每个叶节点的样本对应的一阶导和二阶导对应的值计算得到。式(6)可作为一个评分函数来度量树结构
q
q
q的质量,该评分函数是针对更广泛的目标函数得出的,类似于评估决策时的杂质评分。式(5)和式(6)可改写为:
w
j
∗
=
−
G
j
H
j
+
λ
,
L
~
(
t
)
(
q
)
=
−
1
2
∑
j
=
1
T
(
G
j
2
H
j
+
λ
)
+
γ
T
w_{j}^{*}=-\frac{G_{j}}{H_{j}+\lambda}, \quad \tilde{\mathcal{L}}^{(t)}(q)=-\frac{1}{2} \sum_{j=1}^{T}\left(\frac{G_{j}^{2}}{H_{j}+\lambda}\right)+\gamma T
wj∗=−Hj+λGj,L~(t)(q)=−21j=1∑T(Hj+λGj2)+γT
如下图所示,只需要将每个叶节点的一阶梯度与二阶梯度相加,按应用评分公式来获得质量分数:
6、贪心学习。通常不可能枚举所有可能的树结构
q
q
q,一种贪心的算法被代替:从单个叶节点开始(树深度为0),迭代的将分支添加到树中。
分割前,只有一个叶节点(
T
=
1
T=1
T=1),
−
1
2
(
G
L
+
G
R
)
2
H
L
+
H
R
+
λ
+
γ
T
-{\frac {1}{2}}\frac {(G_L+G_R)^2}{H_L+H_R+\lambda}+\gamma T
−21HL+HR+λ(GL+GR)2+γT
分割后,变成两个叶节点(
T
=
2
T=2
T=2),
−
1
2
[
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
]
+
γ
T
-{\frac {1}{2}}[\frac {G^2_L}{H_L+\lambda}+ \frac{G^2_R}{H_R+\lambda}]+\gamma T
−21[HL+λGL2+HR+λGR2]+γT
假设
I
L
I_L
IL和
I
R
I_R
IR(
I
=
I
L
+
I
R
I=I_L+I_R
I=IL+IR)是切分后左右节点的样本集,拆分之后的损失减少为:
L
split
=
G
a
i
n
=
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
+
λ
]
−
γ
=
1
2
[
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
−
(
G
L
+
G
R
)
2
H
L
+
H
R
+
λ
]
−
γ
(
7
)
\mathcal{L}_{\text {split}}=Gain=\frac{1}{2}\left[\frac{\left(\sum_{i \in I_{L}} g_{i}\right)^{2}}{\sum_{i \in I_{L}} h_{i}+\lambda}+\frac{\left(\sum_{i \in I_{R}} g_{i}\right)^{2}}{\sum_{i \in I_{R}} h_{i}+\lambda}-\frac{\left(\sum_{i \in I} g_{i}\right)^{2}}{\sum_{i \in I} h_{i}+\lambda}\right]-\gamma\\ =\frac{1}{2}[\frac {G^2_L}{H_L+\lambda}+ \frac{G^2_R}{H_R+\lambda}-\frac {(G_L+G_R)^2}{H_L+H_R+\lambda}]-\gamma \quad(7)
Lsplit=Gain=21[∑i∈ILhi+λ(∑i∈ILgi)2+∑i∈IRhi+λ(∑i∈IRgi)2−∑i∈Ihi+λ(∑i∈Igi)2]−γ=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ(7)
式(7)被用来评估分割候选集(选择最优分割点),其中前两项分别为切分后左右子树的分支之和,第三项是未切分前该父节点的分数值,最后一项是引入额外的叶节点导致的复杂度。选择
G
a
i
n
Gain
Gain最小的切割点进行分支。
注意:在这里,虽然XGBoost底层用的是CART树,其实所强调的是基学习器为二叉树,与决策树中CART树的划分特征和划分节点的选择没有关系。
7、如何高效的找到合适的分裂点。通过式(7)对比分裂前后的目标函数值,分裂后目标值最小的分裂为最佳分裂点。为高效的找到分裂点,只需要求导分裂点左右的样本对应的一阶导和二阶导之和,然后计算增益。
8、分裂过程及时间复杂度。(1)连续特征。每个节点,都需要经过所有的特征计算(exact greedy algorithm)。对每个特征,按照样例的取值进行排序,从走往右线性扫描找到该特征的最佳分裂点(可优化,见下模型技巧第2点)。(2)时间复杂度样本数为n,特征数为m,对于生成一个深度为d的树的时间复杂度为
n
∗
m
∗
d
∗
l
o
g
(
n
)
n*m*d*log(n)
n∗m∗d∗log(n),对每一层、每一个特征,需要
n
∗
l
o
g
(
n
)
n*log(n)
n∗log(n)的时间去排序,共有m个特征,d层, 则总的时间复杂度为
n
∗
m
∗
d
∗
l
o
g
(
n
)
n*m*d*log(n)
n∗m∗d∗log(n)。(3)离散特征。与连续特征的处理方式截然不同,将离散特征使用One-Hot编码转换为数字向量,该向量是稀疏的,算法更倾向于处理稀疏数据。
9、相近算法。由8可知,分裂过程开销很大,而且如果数据量过大(不足以完全写入内存时),8的分裂过程不够高效。近似算法将根据每个特征的分位数选出候选分割点,通过这些候选分割点将连续特征分箱,汇总统计信息并找到最好的方案(有两种变体-全局变体与局部变体)。
模型技巧
1、防止过拟合技巧。模型介绍部分提到了加入正则项来降低过拟合,XGBoost还使用了另外两种技术来防止过拟合,即收缩(Shinkage)与列特征采样(Column Subsampling)。
收缩:在树提升的每个步骤之后,收缩比例通过因子
η
η
η新增加权重,与随机优化中的学习速率类似,收缩降低了每颗独立树的影响,并未将来的树留出空间来优化模型。
列特征采样:借鉴了随机森林的做法,支持列抽样,降低过拟合、减少计算。
2、分裂查找算法。
(1)精确的贪心算法:上述第8点提到的对所有的特征的所有可能分割,称之为精确的贪心算法,大多数现有的单机树提升实现以及XGBoost的单机版本都支持。虽然它非常强大,因为它贪婪的枚举了所有可能的分割点,但是当数据不能完全地送入内存时,不可能有效地这样做,引出了一个近似算法。
(2)近似算法。近似算法的思想是,先根据每个特征分布的百分位数选出候选分割点(具体标准或者优化方法见),然后将连续特征映射到由这些候选点分割的桶中,汇总统计信息并根据汇总的信息在提案中找到最佳解决方案。该算法有两种变体,具体取决于提议的时间(全局变体:在树构建的初始阶段提出所有候选分割;局部变体:在每次分裂之后重新提出。全局变体需要更少的提议步骤,但是全局变体需要更多的候选点。当给予足够多的候选点,全局和局部能达到一样的效果)。在给定合理近似水平下,分位数策略可以获得和精确贪心算法相同的精度。
3、分布式加权直方图。精确的贪心算法枚举所有可能的分割点,但当数据无法一次载入内存或者分布式情况下,贪心算法效率就会变得很低,故XGBoost提出一种可并行的近似直方图算法,用于高效地生成候选点的分割点。通常,特征的百分位数用于使候选点均匀地分布在数据上,对于大型数据集,找到满足条件的候选分割是非常重要的。但是对于加权数据集而言,不存在现有的分布式直方图,因此大多数现有的近似算法要么对随机数据子集进行排序(但是可能出错),要么使用没有理论保证的启发式算法进行排序。
为了解决这个问题,一种新颖的分布式加权直方图算法能够处理加权数据,且具有一个可证明的理论保证。
4、稀疏感知分裂发现。数据稀疏是很常见的,常见的原因有1)数据缺失、2)统计中频繁的零项、3)特征工程如One-Hot编码等。因此使算法感知稀疏模式非常重要。在每个树节点中添加一个默认方向,当稀疏矩阵中缺少一个值时,样本被分类为默认方向。
每个分支中有两种默认方向选择,最佳默认方向是从数据中学来的。当不存在对应于用户指定值时,也可以通过将枚举限制为一致的解决方案来应用相同的算法。XGBoost以统一的方式处理所有稀疏模式,利用稀疏性使计算复杂度与输入中的非缺失条目的数量呈线性关系。
5、列块存储。XGBoost的并行不是树的并行,而是特征力度上的并行,决策树中最耗时的部分是将数据按照特征值进行排序,为了降低排序成本,XBGoost将数据存储在内存单元中,称之为块(Block),每个块中的数据以压缩列(CSC)格式存储,每列按相应的特征值排序。
输入数据布局仅需要在训练之前计算一次,后续的迭代中可重复使用,大大减少了计算量,这个Block结构使的并行变得可能,在进行节点的分裂时,需要计算每个特征的增益,最终选择增益最大的特征进行分裂,那么各个特征的增益计算就可以多进程进行。
在精确的贪心算法中,将整个数据集存储在一个块中,并通过线性扫描预先排序的条目来运行差分搜索算法,对所有叶节点集体进行拆分查找,故对块进行一次扫描将收集所有叶分支中的划分候选者的统计数据。
在近似算法中,使用多个块,每个块对应于数据集中行的子集。不同的块可以跨机器分布,也可以在核外设置中存储在磁盘上,使用排序结构,对列排序进行线性扫描,这对于候选者经常在每个分支处生成的局部提议算法尤其有用,其中直方图聚合中的二分搜索也变为线性时间合并样式算法。
收集每列的统计数据可以并行化,提供了一种用于拆分查找的并行算法,而且列块结构还支持列子采样。
6、缓存感知访问。虽然块结构有助于优化分裂查找的计算复杂度,但需要通过行索引间接提取梯度统计,由于这些值是按特征的顺序访问,这是一种非连续的内存访问。当梯度统计信息不适合CPU缓存并发生缓存未命中时,这会减慢拆分查找速度。
对于精确的贪心算法,可以通过缓存感知预取算法来缓解这个问题。在每个线程中分配一个内部缓冲区,获取梯度统计信息到其中,然后以小批量方式执行累积,此预取将直接read/write依赖性更改为更长的依赖性,并在有大量行数时帮助减少运行开销。
对于近似算法,通过选择正确的块大小来缓解问题,将块大小定义为块中包含的最大示例数,因为这反应了梯度统计的高速缓存成本。选择过小的块大小会导致每个线程的工作量很小,并导致低效的并行化,另一方面,过大的块会导致缓存未命中,因为梯度统计信息不送入CPU缓存。块大小的良好选择平衡这两个因素,经验表明每个块选择
2
16
2^{16}
216个示例可以平衡缓存属性和并行化。
7、用于核外计算的块。除处理器和内存外,利用磁盘空间处理没有送入内存的数据也很重要。将数据分成多个块,并将每个块存储在磁盘上,在计算过程中,使用独立的线程将块预取到主内存缓冲区,因此计算和磁盘读取同时发生,但是磁盘读取占用了大部分计算时间,减少开销并增加磁盘IO的吞吐十分重要。两种改进技术如下:
(1)块压缩。该块由列压缩,并在加载到主存中有独立线程动态解压缩。这有助于将解压缩中的一些计算与磁盘读取成本进行交换,使用通用压缩算法来压缩特征值,每块需要
2
16
2^{16}
216个样本。
(2)块分片。将数据分片到多个磁盘上,为每个磁盘分配一个预取程器线程,并将数据提取到内存缓冲区中,然后训练线程交替得从每个缓冲区读取数据,这有助于在多个磁盘可用时增加磁盘读取的吞吐量。
XGBoost的并行机制
XGBoost的并行机制值得一说。XGBoost的树模型训练是串行的,即下一棵树的拟合是基于上一棵树的残差。但XGBoost中确实运用到了并行机制,如下分析。
- 在每个节点中并行遵照最佳分裂特征(并行处理多个特征),如果节点中实例数目较少,并行化的好处不足以弥补多线程中上下文切换、thread join等带来的损失(每个节点,并行处理多个特征);
- 对于每个特征,并行寻找每个节点的最佳分裂点,再接着并行处理多个特征(每个特征,并行寻找每个节点的最佳分裂点);
- 确定好分裂特征和分裂点后,各节点中实例已经确定,然后并行建立节点(包括后续的节点)(建立节点的过程,即会对不同的特征以及寻找不同的最佳分裂点),此时会有个不同节点中实例个数不平衡的问题。
综上,既然XGBoost在树之间是串行的,那么并行机制仅存在于一棵树内,在一棵树的构建过程中,重要的是对每个节点,确定好分裂特征和分裂点,那么并行机制主要作用在这两个方面。
并行机制在性能上已经提高了。同时,Block结构和分布式加权直方图给了并行机制以进一步加速。加速对分裂特征和分裂点的选择过程。
XGBoost与GBDT的对比
1、GBDT将CART树作为基分类器,XGBoost不仅支持CART树(只是想强调二叉树)为基分类器,还支持线性分类器。可通过参数设置。
2、最重要的是,GBDT对损失函数使用了一阶泰勒展开,而XGBoost使用了二阶泰勒展开,即同时考虑了一阶导数和二阶导数,主要好处有:(1)由于MSE的形式是平方误差,所以XGBoost使用二阶泰勒展开,形式上统一,以便于后续自定义损失函数;(2)一阶导数考虑了梯度方向,二阶导数体现的是梯度方向的变化,即XGBoost不仅考虑了梯度的方向,还考虑梯度方向的变化,加快求解速度。
3、XGBoost在目标函数中加入了正则项,用于控制模型的复杂度(分别是控制叶节点个数、平滑叶节点权重),使得最终模型更加鲁棒。
4、新增了收缩(类似于学习率)和列特征采样(类似于RF中),降低过拟合。
5、采用了Block结构,利于并行化和子采样,也利于缓存感知访问和核外计算。
XGBoost的参数
一般参数
booster[default=gbtree],选择基分类器,gbtree/gblinear(树和线性分类器);
silent[default=0],是否输出详细信息(0不输出,1输出);
nthread[default 线程数最大];
Tree Booster参数
eta[default=0.3],即shrinkage参数,用于更新叶节点权重时,乘以该系数,避免步长过大,参数值越大越可能无法收敛。eta越小,使得后面的学习更加仔细。
min_child_weight[default=1],代表了每个叶节点中的
h
h
h的和至少是多少,对正负样本不均衡时的0/1分类而言,即假设
h
h
h在0.01附近,min_child_weight=1意味着叶节点中最少包含100个样本,这个参数非常影响结果,控制叶节点中的二阶导的和的最小值,参数值越小,越容易过拟合。
max_depth[default=6],每棵树的最大深度,值越大,越容易过拟合。
gamma[default=0],在树的叶节点上作进一步分区所需要的最小损失减少,越大,算法越保守。
max_delta_step[default=0],该参数表示在更新步骤中起作用,如果取0表示没有约束,如果取正数,则使得更新步骤更加保守,可以防止太大的更新步子,更加平缓。通常,这个参数是不重要的,但它可能有助于logistic回归。设置它的值为1-10,可能有助于控制更新。
subsample[default=1],样本随机采样,较低的值使得算法更加保守,防止过拟合,但是太小的值也会造成欠拟合。
colsample_bytree[default=1],列采样,对每棵树的生成用的特征进行列采样。一般设置为 0.5-1。
lambda[default=1],控制模型复杂度的权重值的L2正则化项参数,参数越大,模型越不容易过拟合。
alpha[default=0],控制模型复杂程度的权重值的L1正则项参数,参数值越大,模型越不容易过拟合。
scale_pos_weight[default=1],如果取值大于0的话,在类别样本不平衡的情况下有助于快速收敛。
tree_method[default=’auto’],可选 {‘auto’, ‘exact’, ‘approx’} ,即贪心算法(小数据集)/近似算法(大数据集)。
学习任务参数
objective[default=reg:linear],定义最小化损失函数类型。常用的值有:
binary:logistic,二分类的逻辑回归,返回预测的概率(不是类别);
multi:softmax,使用softmax的多分类器,返回预测的类别(不是概率);此时需要设置一个num_class(类别数目);
multi:softprob和multi:softmax参数一样,但是返回的是每个数据属于各个类别的概率(不是类别)。
seed[default=0]随机种子;
eval_metric[根据目标objective默认],对于有效数据的度量方法,分回归问题和分类问题。
回归问题:
rmse(均方根误差),mae(平均绝对误差);logloss(负对数似然函数值)
分类问题:
error(二分类错误率,阈值为0.5),merror(多分类错误类),mlogloss(多分类logloss损失函数),auc(曲线下面积)
命令行参数
num_round,迭代次数/树的个数
有不对的地方,欢迎大家批评指正!