一 从集成学习说起
首先我们会有个疑问:什么是集成学习呢?通常的有监督机器学习任务可能就是做个分类或者回归,整个过程就是在学习一个函数,用于拟合训练数据。那相对应的,集成学习的目标就是通过多个函数,从而更好的拟合训练数据,达到更加精确的目的,所谓众人拾柴火焰高。
二 集成学习之流派
集成学习常见的分法可以分为Bagging和Boosting两大类。先说Bagging,代表方法是随机森林,这里每棵树的目标都是学习拟合目标数据,相当于随机森林就是同时学习多个决策树,并且最终的结果会考虑每棵树的影响,即如果是分类的目的,可以考虑把较多棵树的预测结果作为最终的类别;如果是回归的目的,可以考虑把所有树的结果求个平均。很明显,Bagging的目的并不是让其中某个单独的模型很强,而是想从整体合成的大模型上令最终的结果非常准,因此它想做的是:降低整体的方差。对于Boosting,比较经典的代表算法有:gbdt、xgb等模型。对于Boosting集成学习,它的思路是这样的:第一个模型尽量拟合训练数据,而第二个模型不去拟合原始的训练数据,而是拟合第一个模型在拟合训练数据时产出的误差,而后续所有的模型也是同理,都是拟合前一个模型的残差。因此Boosting实现是串行式的,这也是它与Bagging的本质区别。
三 XGB的原理与实现细节
1 公式推导
XGBoost和GBDT两者都是boosting方法,除了工程实现、解决问题上的一些差异外,最大的不同就是损失函数的定义。因此,我们从目标函数开始探究XGBoost的基本原理。
L
=
∑
i
n
(
y
′
i
,
y
i
)
+
∑
k
Ω
(
f
k
)
L=\sum_{i}^n(y{'}_i,y_i)+\sum_{k}\Omega(f_k)
L=∑in(y′i,yi)+∑kΩ(fk)
其中,n为样本数量;k是树的个数,
Ω
(
f
k
)
\Omega(f_k)
Ω(fk)是第k棵树的复杂度。
我们知道模型的预测精度由模型的偏差和方差共同决定,损失函数的前半部分代表了模型的偏差;想要方差小则需要在目标函数中添加正则项,用于防止过拟合。所以目标函数的后半部分是抑制模型复杂度的正则项 ,这也是XGBoost和GBDT的差异所在。
上个公式中,树的复杂度计算公式如下:
Ω
(
f
)
=
γ
T
+
2
1
λ
∣
∣
w
∣
∣
2
\Omega(f)=\gamma T+^{1}_{2}\lambda||w||^2
Ω(f)=γT+21λ∣∣w∣∣2
其中, T为树的叶子节点个数,
w
j
w_j
wj为第j个节点的权重。叶子节点越少模型越简单,此外叶子节点也不应该含有过高的权重。
L
(
t
)
=
∑
i
n
l
(
y
i
,
y
′
i
(
t
−
1
)
+
f
t
(
x
i
)
)
+
Ω
(
f
t
)
L(t)=\sum_{i}^nl(y_i,y{'}_i(t-1)+f_t(x_i))+\Omega(f_t)
L(t)=∑inl(yi,y′i(t−1)+ft(xi))+Ω(ft)
按照二阶泰特展开,
L
(
t
)
≈
∑
i
n
[
l
(
y
i
,
y
′
i
(
t
−
1
)
)
+
g
i
f
t
(
x
i
)
+
0.5
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
L(t)\approx\sum_{i}^n[l(y_i,y{'}_i(t-1))+g_if_t(x_i)+0.5h_if_t^2(x_i)]+\Omega(f_t)
L(t)≈∑in[l(yi,y′i(t−1))+gift(xi)+0.5hift2(xi)]+Ω(ft)
由于前k-1个树的结果已经得到,所以可以把他们作为常量进行简化,即:
L
′
(
t
)
=
∑
i
n
[
g
i
f
t
(
x
i
)
+
0.5
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
L'(t)=\sum_{i}^n[g_if_t(x_i)+0.5h_if_t^2(x_i)]+\Omega(f_t)
L′(t)=∑in[gift(xi)+0.5hift2(xi)]+Ω(ft)
其中
g
i
g_i
gi是损失函数
l
(
y
i
,
y
′
i
(
t
−
1
)
l(y_i,y{'}_i(t-1)
l(yi,y′i(t−1)对
y
′
i
(
t
−
1
)
y{'}_i(t-1)
y′i(t−1)的一阶导, 就是在梯度提升树中第t棵树要拟合的那个值。
h
i
h_i
hi是二阶导,XGBoost 引入二阶导一方面是为了增加精度,另一方面也是为了能够自定义损失函数,二阶泰勒展开可以近似大量损失函数。顺便提一下,xgboost工具支持自定义代价函数,只要函数可一阶和二阶求导。
进一步化简,
m
i
n
(
o
b
j
)
=
−
2
1
∑
j
=
1
T
G
j
2
/
(
H
j
2
+
λ
)
+
γ
T
min(obj)=-^1_{2}\sum_{j=1}^T {G_j^2/({H_j^2+\lambda)}} +\gamma T
min(obj)=−21∑j=1TGj2/(Hj2+λ)+γT
其中,T为这棵树的叶节点个数,
G
j
G_j
Gj为节点 j所含所有样本的一阶导数
g
j
g_j
gj 之和;
H
j
H_j
Hj为节点 j所含所有样本的二阶导数
h
j
h_j
hj之和。
2分裂算法
下面分别通过原论文中的两个伪代码略微讲一下节点分裂的依据。
分裂算法1:贪心
XGBoost使用了和CART回归树一样的想法,利用贪婪算法,将所有特征升序排序,然后遍历所有特征的所有特征划分点,不同的是使用的目标函数不一样。具体做法就是求分裂后的目标函数值比分裂前的目标函数的增益。
分裂算法2:近似
贪心算法可以得到最优解,但当数据量太大时则无法读入内存进行计算,近似算法则大大降低了计算量,给出了近似最优解。
对于每个特征,只考察分位点可以减少计算复杂度。近似算法简单来说,就是对每个特征 k 都确定l个候选切分点 ,然后根据这些候选切分点把相应的样本放入对应的桶中,对每个桶的
g
i
,
h
i
g_i,h_i
gi,hi进行累加。最后在候选切分点集合上贪心查找。因此相比贪心来说,在计算上会少很多。
3缩减和列抽样
除了对树复杂度的正则项之外,作者还选用了两个正则化方法。
一是缩减。XGBoost 在进行完一次迭代后,会将新加进来的树的叶子节点权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。传统GBDT的实现也有学习速率;步长越小,越有可能找到更精确的最佳值,更多的空间被留给了后面建立的树,但迭代速度会比较缓慢。
二是列抽样。XGBoost借鉴了随机森林的做法,每次分裂的时候随机选择m个特征计算增益,而不是全部特征都考虑。不仅能降低过拟合,还能减少计算。这也是XGBoost异于传统GBDT的一个特性。
4并行计算
前面说过Boosting是一种串行的结构,因此XGBoost的并行不是树粒度的并行,XGBoost也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含了前面t-1次迭代的预测值)。XGBoost的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点)。而XGBoost在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。
四 总结
XGBoost是集成学习中的大杀器,因为高效准确和易用,不仅在各种机器学习竞赛中经常出现,甚至工业中也不乏它的身影。但是XGBoost的细节远不知上面提到的这些,后续有机会再做详细学习整理。