(1)正则化目标函数(Regularized Learning Objective)
假设m维特征n个样本的数据集
D
=
{
(
x
i
,
y
i
)
}
D=\{(x_i,y_i)\}
D={(xi,yi)},其中,
∣
D
∣
=
n
,
x
i
∈
R
m
,
y
i
∈
R
|D|=n,x_i \in R^m,y_i \in R
∣D∣=n,xi∈Rm,yi∈R,使用K棵树进行预测:
y
i
^
=
∑
k
=
1
K
f
k
(
x
i
)
,
f
k
∈
Γ
(1)
\hat {y_i}=\sum_{k=1}^K {f_k(x_i)},f_k \in \varGamma \tag{1}
yi^=k=1∑Kfk(xi),fk∈Γ(1)
其中,
Γ
=
{
f
(
x
)
=
w
q
(
x
)
}
,
q
:
R
m
→
T
,
w
∈
R
T
\varGamma=\{f(x)=w_{q(x)}\},q:R^m \to T,w \in R^T
Γ={f(x)=wq(x)},q:Rm→T,w∈RT,q表示样本映射到树叶节点索引的映射关系,T表示叶节点的类别数,
w
i
w_i
wi表示第i个叶节点的回归值,对于每一棵树
f
k
f_k
fk都有自己的q和w,不同树之间的q和w是相互独立的。对于一个给定的样本i,我们可以通过q确定其落到哪个叶节点,再通过w得到该样本在树k上的预测值,最后通过把所有决策树的预测值累加起来得到该样本最终的预测值。
接下来,为了训练这K棵树,最小化以下正则化目标函数:
L
=
∑
i
=
1
n
l
(
y
i
,
y
i
^
)
+
∑
k
=
1
K
Ω
(
f
k
)
(2)
L=\sum_{i=1}^n l(y_i,\hat{y_i}) + \sum_{k=1}^K \Omega(f_k) \tag{2}
L=i=1∑nl(yi,yi^)+k=1∑KΩ(fk)(2)
w
h
e
r
e
Ω
(
f
)
=
γ
T
+
1
2
λ
∑
j
=
1
T
w
2
where \space \Omega(f)=\gamma T+\frac{1}{2}\lambda \sum_{j=1}^T w^2
where Ω(f)=γT+21λj=1∑Tw2
其中,等式右边的第一项为损失函数,第二项为模型复杂度,
Ω
(
f
k
)
\Omega(f_k)
Ω(fk)表示第k棵树的模型复杂度,
γ
,
λ
\gamma,\lambda
γ,λ分别为叶节点个数和叶节点取值的权重。当正则化项等于0时,目标函数就是传统的梯度提升树(traditional gradient tree boosting)。
(2)梯度提升树(Gradient Tree Boosting)
当训练第t棵树时,
y
i
^
(
t
−
1
)
\hat{y_i}^{(t-1)}
yi^(t−1)和
∑
k
=
1
t
−
1
Ω
(
f
k
)
\sum_{k=1}^{t-1}\Omega(f_{k})
∑k=1t−1Ω(fk)可以看作是常数,我们需要优化以下目标函数:
L
(
t
)
=
∑
i
=
1
n
l
(
y
i
,
y
i
^
(
t
−
1
)
+
f
t
(
x
i
)
)
+
Ω
(
f
t
)
(3)
L^{(t)} =\sum_{i=1}^n l(y_i,\hat{y_i}^{(t-1)}+f_t(x_i)) + \Omega(f_t) \tag{3}
L(t)=i=1∑nl(yi,yi^(t−1)+ft(xi))+Ω(ft)(3)
使用泰勒级数近似目标函数,按二阶泰勒级数展开:
泰勒展开式: f ( x + Δ x ) = f ( x ) + f ′ ( x ) 1 ! Δ x + f ′ ′ ( x ) 2 ! ( Δ x ) 2 + . . . + f ( n ) ( x ) n ! ( Δ x ) n + R n f(x+\Delta x)=f(x)+\frac{f'(x)}{1!} \Delta x+\frac{f''(x)}{2!}(\Delta x)^2+...+\frac{f^{(n)}(x)}{n!}(\Delta x)^n+R_n f(x+Δx)=f(x)+1!f′(x)Δx+2!f′′(x)(Δx)2+...+n!f(n)(x)(Δx)n+Rn,其中, R n = o [ ( Δ x ) n ] R_n=o[(\Delta x)^n] Rn=o[(Δx)n]
L
(
t
)
≃
∑
i
=
1
n
[
l
(
y
i
,
y
i
^
(
t
−
1
)
)
+
g
i
f
t
(
x
i
)
)
+
1
2
h
i
f
t
2
(
x
i
)
)
]
+
Ω
(
f
t
)
(4)
L^{(t)} \simeq \sum_{i=1}^n [l(y_i,\hat{y_i}^{(t-1)})+g_i f_t(x_i))+\frac{1}{2}h_i f^2_t(x_i))] + \Omega(f_t) \tag{4}
L(t)≃i=1∑n[l(yi,yi^(t−1))+gift(xi))+21hift2(xi))]+Ω(ft)(4)
其中,
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(y_i,\hat y^{(t-1)}),h_i=\partial_{\hat y^{(t-1)}}^2l(y_i,\hat y^{(t-1)})
gi=∂y^(t−1)l(yi,y^(t−1)),hi=∂y^(t−1)2l(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
)
(5)
\tilde L^{(t)} = \sum_{i=1}^n [g_i f_t(x_i))+\frac{1}{2}h_i f^2_t(x_i))] + \Omega(f_t) \tag{5}
L~(t)=i=1∑n[gift(xi))+21hift2(xi))]+Ω(ft)(5)
定义
I
j
=
{
i
∣
q
(
x
i
)
=
j
}
I_j=\{i|q(x_i)=j\}
Ij={i∣q(xi)=j},表示第j类叶节点的样本集合
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
=
∑
i
=
1
n
[
g
i
w
q
(
x
i
)
+
1
2
h
i
w
q
(
x
i
)
2
]
+
γ
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
+
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
(6)
\begin{aligned} \tilde L^{(t)} &= \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2}{h_i}f_t^2(x_i)] + \gamma T+\frac{1}{2}\lambda \sum_{j=1}^T w_j^2 \\ &= \sum_{i=1}^n [g_i w_{q(x_i)} + \frac{1}{2}{h_i}w_{q(x_i)}^2] + \gamma T+\frac{1}{2}\lambda \sum_{j=1}^T w_j^2 \\ &= \sum_{j=1}^T [(\sum_{i \in I_j}g_i) w_j + \frac{1}{2} (\sum_{i \in I_j}h_i) w_j^2] + \gamma T+\frac{1}{2}\lambda \sum_{j=1}^T w_j^2 \\ &= \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 \\ \end{aligned} \tag{6}
L~(t)=i=1∑n[gift(xi)+21hift2(xi)]+γT+21λj=1∑Twj2=i=1∑n[giwq(xi)+21hiwq(xi)2]+γT+21λj=1∑Twj2=j=1∑T[(i∈Ij∑gi)wj+21(i∈Ij∑hi)wj2]+γT+21λj=1∑Twj2=j=1∑T[(i∈Ij∑gi)wj+21(i∈Ij∑hi+λ)wj2]+γT(6)
当
q
(
x
)
q(x)
q(x)是已知的,也就是说,树的形状是已知的,那么此时决策树的最优解及其目标值为:
w
j
∗
=
−
∑
i
∈
I
j
g
i
∑
i
∈
I
j
h
i
+
λ
(7)
w_j^*=-\frac{\sum_{i \in I_j}g_i}{\sum_{i \in I_j}h_i +\lambda} \tag{7}
wj∗=−∑i∈Ijhi+λ∑i∈Ijgi(7)
L ~ ( t ) ∗ = − 1 2 ∑ j = 1 T ( ∑ i ∈ I j g i ) 2 ∑ i ∈ I j h i + λ + γ T (8) \tilde L^{(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} + \gamma T \tag{8} L~(t)∗=−21j=1∑T∑i∈Ijhi+λ(∑i∈Ijgi)2+γT(8)
目标值类似于信息熵的概念,可以用来衡量该决策树的优劣。由于我们不可能列举出
q
(
x
)
q(x)
q(x)的所有可能取值,所以这里可以采用贪心算法求解,也就是说,选择一个叶节点,在此基础上选择是否分支,假定
I
L
I_L
IL和
I
R
I_R
IR分别是该叶节点分支后左子节点和右子结点的样本集合,即
I
=
I
L
∪
I
R
I=I_L\cup I_R
I=IL∪IR,那么分支后损失函数的减少为:
L
s
p
l
i
t
=
1
2
[
∑
j
=
1
T
(
∑
i
∈
I
L
g
i
)
2
∑
i
∈
I
L
h
i
+
λ
+
∑
j
=
1
T
(
∑
i
∈
I
R
g
i
)
2
∑
i
∈
I
R
h
i
+
λ
−
∑
j
=
1
T
(
∑
i
∈
I
g
i
)
2
∑
i
∈
I
h
i
+
λ
]
−
γ
(9)
L_{split}=\frac{1}{2} [\sum_{j=1}^T \frac{(\sum_{i \in I_L}g_i)^2}{\sum_{i \in I_L}h_i+\lambda}+ \sum_{j=1}^T \frac{(\sum_{i \in I_R}g_i)^2}{\sum_{i \in I_R}h_i+\lambda} - \sum_{j=1}^T \frac{(\sum_{i \in I}g_i)^2}{\sum_{i \in I}h_i+\lambda} ] - \gamma \tag{9}
Lsplit=21[j=1∑T∑i∈ILhi+λ(∑i∈ILgi)2+j=1∑T∑i∈IRhi+λ(∑i∈IRgi)2−j=1∑T∑i∈Ihi+λ(∑i∈Igi)2]−γ(9)
若分支前目标值大于分支后目标值,则进行分支,否则不进行分支。
除了可以采用正则化目标函数避免过拟合之外,还有两种避免过拟合的技巧:1)收缩率,在每一步的提升树之后新增加的权重都会按 η \eta η缩减,类似于动态优化的学习率,收缩率减少了每棵独立树的权重,为未来树留出提升的空间;2)列采样,随机森林的特征选择就是列采样,一般来说,列采样优于行采样。引入列采样可以提升并行算法的计算效率。
(3)分割点搜索算法(Split Finding Algorithm)
- 基于精确的贪心算法(Exact Greedy Algorithm)
穷举所有特征的所有可能切分点,找出最佳划分特征及其最佳切分点。假设有m个特征,对于每个特征先把所有可能值升序排列,遍历所有可能切分点j,找到最佳切分点,然后遍历m个特征找到最佳特征及其最佳切分点。
- 基于近似的贪心算法(Approximate Algorithm)
精确的贪心算法不能有效地处理数据量大和分布式场景,因此提出近似的贪心算法。近似的贪心算法,不再是穷举所有的切分点,而是只选几个切分点作为候选切分点,具体来说,就是先根据特征分布的分位数选出 l l l个候选切分点(除了分位数策略,还可以用其他的分桶策略),再根据这些候选点把特征的所有取值进行分桶,计算第v桶的一阶导数和二阶导数,即 G k v G_{kv} Gkv和 H k v H_{kv} Hkv,最后按照前面的方法计算损失函数的减少,以此判断是否分支。近似的贪心算法有两种形式:1)全局算法(global),在开始构建决策树之前先确定好候选切分点,在树的生成过程中使用同样的候选切分点进行分支;2)局部算法(local),每次分支时都重新确定候选切分点。全局算法确定候选点的次数少,但需要更多的候选点,局部算法确定候选点的次数多,但允许更少的候选点,可以到达更深的决策树。
下面是在Higgs 10M数据集上测试集AUC的对比。eps参数表示分桶的精度,如果eps=0.05,表示样本按照选定的特征分为1/0.05组,即分桶。
-
跟全局算法相比,局部算法达到同样的效果所需要的候选点更少
-
全局算法只要分桶够多,便能达到局部算法的效果
-
近似贪心算法可以达到精确贪心算法同样的准确度,只要分桶策略足够合理。
-
加权分位法(Weighted Quantile Sketch)
在近似贪心算法中,很重要的一步是确定候选切分点,通常使用的是分位法。我们定义 D k = { ( x 1 k , h 1 ) , ( x 2 k , h 2 ) , . . . , ( x n k , h n ) } D_k=\{(x_{1k},h_1),(x_{2k},h_2),...,(x_{nk},h_n)\} Dk={(x1k,h1),(x2k,h2),...,(xnk,hn)}, x i k x_{ik} xik表示样本i第k个特征的取值, h i h_i hi是其对应的二阶梯度统计值,即(4)式的 h i h_i hi,定义排序函数 r k r_k rk:
r k ( z ) = 1 ∑ ( x , h ) ∈ D k h ∑ ( x , h ) ∈ D k , x < z h (10) r_k(z)=\frac{1}{\sum_{(x,h) \in D_k} h} \sum_{(x,h) \in D_k,x<z} h \tag{10} rk(z)=∑(x,h)∈Dkh1(x,h)∈Dk,x<z∑h(10)
它表示第k个特征的取值小于等于z的样本占比。我们的目标是从第k个特征的所有取值中找到 l l l个候选切分点,即候选切分点 { s k 1 , s k 2 , . . . , s k l } \{s_{k1},s_{k2},...,s_{kl}\} {sk1,sk2,...,skl}满足:
∣ r k ( s k , j ) − r k ( s k , j + 1 ) ∣ < ϵ , s k 1 = min i x i k , s k l = max i x i k (11) |r_k(s_{k,j})-r_k(s_{k,j+1})|<\epsilon,s_{k1}=\min_i \space x_{ik},s_{kl}=\max_i \space x_{ik} \tag{11} ∣rk(sk,j)−rk(sk,j+1)∣<ϵ,sk1=imin xik,skl=imax xik(11)
其中, ϵ \epsilon ϵ是近似参数,值越小,选取的候选点越多,候选点最多有 1 ϵ \frac{1}{\epsilon} ϵ1个。这里,每个样本的权重都是 h i h_i hi,至于为什么 h i h_i hi表示样本权重,我们等式(5)重写为:
L ~ ( t ) = ∑ i = 1 n 1 2 h i ( f t ( x i ) + g i / h i ) 2 + Ω ( f t ) + c o n s t a n t (12) \tilde L^{(t)} = \sum_{i=1}^n \frac{1}{2}h_i (f_t(x_i)+g_i/h_i)^2 + \Omega(f_t) + constant \tag{12} L~(t)=i=1∑n21hi(ft(xi)+gi/hi)2+Ω(ft)+constant(12)
此时,目标函数可以看作是 f t ( x i ) f_t(x_i) ft(xi)和 − g i / h i -g_i/h_i −gi/hi带权重 h i h_i hi的平方损失函数。当每个样本权重相同时,可以使用分位法,当权重不同时,需要引入加权分位法。 -
稀疏性自适应分割算法(Sparsity-aware Split Finding)
在真实世界中,输入数据常常是稀疏的,稀疏性原因可能有:1)输入数据存在缺失;2)统计数据中存在大量0值;3)特征工程的产物,比如one-hot编码。所以,分支搜索算法需要考虑到稀疏性的情况,因此引入稀疏性自适应分割算法。
稀疏性感知算法的大致思路:在结点处先利用非缺失数据进行分支,然后把缺失值当作一种取值,为缺失值寻找最优划分方向(要么在左子树要么在右子树)。引入稀疏性感知的算法与非缺失数据是线性时间复杂度,也就是说,稀疏性感知算法的执行效率还是较好的。
(4)系统设计
-
分块并行(Column Block for Parallel Learning)
训练决策树最耗时的部分是对数据进行排序,为了减少排序成本,我们提出“block”的概念,即数据存储的内存单元,每个block的数据都是以压缩列(CSC)格式存储,每列都是按照对应的特征值排序存储,也就是说,先把每一列特征提前进行排序,以块(Block)的形式储存在缓存中,并以索引将特征值和梯度统计量 g i , h i g_i,h_i gi,hi对应起来,每次节点分裂时会重复调用排好序的块。而不同特征会分布在独立的块中,因此可以进行分布式或多线程的计算。
【时间复杂度分析】假设 d d d是树的最大深度, K K K是树的数量。对于精确的贪心算法,原始的稀疏性自适应分割算法时间复杂度是 O ( K d ∣ ∣ x ∣ ∣ 0 log n ) O(Kd||x||_0 \log n) O(Kd∣∣x∣∣0logn)(注:快排的时间复杂度为 O ( n log n ) ) O(n\log n)) O(nlogn)),这里 ∣ ∣ x ∣ ∣ 0 ||x||_0 ∣∣x∣∣0是数据集非零实体个数,假设特征i有 ∣ ∣ x ∣ ∣ 0 i ||x||_{0i} ∣∣x∣∣0i个非零值,那么 ∣ ∣ x ∣ ∣ 0 = ∑ i = 1 m ∣ ∣ x ∣ ∣ 0 i ||x||_0=\sum_{i=1}^m ||x||_{0i} ∣∣x∣∣0=∑i=1m∣∣x∣∣0i,而block结构的时间复杂度是 O ( K d ∣ ∣ x ∣ ∣ 0 + ∣ ∣ x ∣ ∣ 0 log n ) O(Kd||x||_0 +||x||_0 \log n) O(Kd∣∣x∣∣0+∣∣x∣∣0logn),其中, ∣ ∣ x ∣ ∣ 0 log n ||x||_0 \log n ∣∣x∣∣0logn是一开始特征排序的时间复杂度,每个特征最多有n个取值,如果我们使用block结构时,由于在一开始我们就已经对特征进行了排序,之后就不需要在每个节点都排序,只需要通过一次扫描就可以得到各个结点的最优划分点,于是时间复杂度降为 O ( K d ∣ ∣ x ∣ ∣ 0 ) O(Kd||x||_0) O(Kd∣∣x∣∣0)。对于近似的贪心算法,原始的稀疏性自适应分割算法时间复杂度为 O ( K d ∣ ∣ x ∣ ∣ 0 log q ) O(Kd||x||_0 \log q) O(Kd∣∣x∣∣0logq),q是最大候选点数量,block结构的时间复杂度是 O ( K d ∣ ∣ x ∣ ∣ 0 + ∣ ∣ x ∣ ∣ 0 log B ) O(Kd||x||_0 +||x||_0 \log B) O(Kd∣∣x∣∣0+∣∣x∣∣0logB),B是每个block的最大行数。
-
缓存访问(Cache-aware Access)
尽管block结构能够帮助优化分割点搜索的时间复杂度,但新算法是通过索引来获取梯度统计值的,这是非连续的内存访问,这可能会使CPU缓存命中率低,从而影响算法效率。因此,在精确的贪心算法中,使用缓存预取(cache-aware prefetching algorithm),也就是先为每个线程分配一个连续的buffer(内存缓冲区),然后读取梯度统计值到buffer里(实现非连续到连续的转化),最后以小批量方式执行累加操作。我们发现,精确的贪心算法中加入缓存预取比原始版本的效率要快两倍。而在近似的贪心算法中,使用适合的block大小来缓解该问题,我们定义block大小为block中最大的样本数,block大小太小,会导致并行化效率低效,block大小太大会导致缓存丢失,所以需要平衡好这两个因素。
-
核外块计算(Blocks for Out-of-core Computation)
系统的目标除了使得运行效率和内存资源利用最大化以外,还要对磁盘空间利用最大化。为了保证核外块的计算,我们把数据划分为多个block,然后把block存储到磁盘上。在计算时,使用独立线程把block读取到buffer上,这样使得计算操作和读取操作能够同时进行,但这个并不能很好地解决问题,因为磁盘读取操作还是占据了大量的处理时间,所以减少读取数据的开销是非常重要,我们主要是使用两种方法来提高核外块的计算效率:1)block压缩,即block先被列压缩,当加载到主内存中时被独立线程解压缩,相当于用解压缩的计算时间换取了磁盘读取成本,较为常见的列压缩算法是记录block的第一个行索引,然后记录每一个行索引与第一个行索引的偏移量offset,一共用16个bits来存储,所以每个block最多有 2 16 2^{16} 216个样本;2)block拆分,即把数据拆分到多个磁盘上,为每个磁盘分配一个预取线程,把数据读取到内存缓冲区,然后训练线程再从每个内存缓冲区读取数据,这有助于提高磁盘的吞吐量。