六、集成学习四、集成学习(Boosting、二)

三、XGBoost

    XGBoost也是GBDT的一种,也是加法模型和前向优化算法。在监督学习中,也可以分为:模型,参数,目标函数与学习方法:

模型:给定输入x后预测输出y的方法,如回归、分类、排序等。

参数:模型中的参数,比如线性回归中的权值和偏置。

目标函数:损失函数、代价函数,包含正则化项。

学习方法:给定目标函数后求解模型和参数的方法,比如:梯度下降法、数学推导等。这四方面的内容也指导着XGBoost系统的设计。

给定数据集:D= (x_i,y_i)(\vert D \vert =n,x_i\in  R^m,y_i\in R)XGBoost利用前向分布算法,学习到包含K棵树的加法模型:

\hat{y_i} =  \sum\nolimits_{t=1}^K f_t(x_i),f_t\in F

其中有K棵树,f是回归树,而F对应回归树组成的函数空间。那怎么得到这些树,也就是的结构和叶子节点的预测结果?

定义目标函数,包含正则项:

Obj(\Theta ) = \sum_{i=1}^Nl(y_i,\hat{y_i} ) + \sum_{j=1}^t \Omega (f_j),f_j\in F

因为f是决策树,而不是数值型的向量,所以不能使用梯度下降法进行优化。

XGBoost是前向分布算法,通过贪心算法寻找局部最优解:

\hat{y_i}^{(t)}  =  \sum\nolimits_{j=1}^t f_j(x_i)=\hat{y_i}^{(t-1)}+f_t(x_i)

每一次迭代,寻找使代价函数降低最大的f(CART树),因此目标函数可改写为:

Obj^{(t)} = \sum_{i=1}^Nl(y_i,\hat{y_i}^{(t)} ) + \sum_{j=1}^t \Omega (f_j)

=\sum_{i=1}^Nl(y_i,\hat{y_i}^{(t-1)} + f_t(x_i)) +\Omega (f_t)+constant

=\sum_{i=1}^Nl(y_i,\hat{y_i}^{(t-1)} + f_t(x_i)) +\Omega (f_t)

constant:在t轮时,前t-1次迭代正则项看作是常数

然后使用泰勒展开对目标参数进行近似:

Obj^{(t)}=\sum_{i=1}^Nl(y_i,\hat{y_i}^{(t-1)} + f_t(x_i)) +\Omega (f_t)

=\sum_{i=1}^Nl(y_i,\hat{y_i}^{(t-1)} + g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i) ) +\Omega (f_t)

其中,g_i = \frac {\partial l(y_i,\hat{y_i}^{(t-1)} )}{\partial \hat{y_i}^{(t-1)}} ,  h_i= \frac {\partial^2 l(y_i,\hat{y_i}^{(t-1)} )} {\partial^2 \hat{y_i}^{(t-1)}}

移除对第t轮迭代来说的常数项l(y_i,\hat{y_i}^{(t-1)} )得到:

Obj^{(t)}=\sum_{i=1}^N (g_if_t(x_i) + \frac{1}{2} h_if_t^2(x_i)) +\Omega (f_t)

所以目标函数只依赖于每条数据在代价函数上的一阶导数和二阶导数。

XGBoost中正则项用来衡量树的复杂度:树的叶子节点个数T和每棵树的叶子节点输出分数W的平方和(相当于L2正则化):

\Omega (f_t) = \gamma T+ \frac{1}{2}\lambda  \sum_{j=1}^T w_j^2

eg:


\Omega =\gamma 3+\frac{1}{2} \lambda (4+0.01+1)

XGBoost的目标函数改写为:

Obj^{(t)}=\sum_{i=1}^N (g_if_t(x_i) + \frac{1}{2} h_if_t^2(x_i)) +\gamma T+ \frac{1}{2}\lambda  \sum_{j=1}^T w_j^2

\sum_{i=1}^N (g_if_t(x_i) + \frac{1}{2} h_if_t^2(x_i)) :对样本的累加,\sum_{j=1}^T w_j^2:对叶节点的累加。

定义q函数将输入x映射到某个叶子节点上,则有:

f_t(x) = w_{q(x)},w\in R^T,q:R^d\rightarrow \left\{1,2,...,T\right\}


q(A) = 1,  q(E) = 3

定义每个叶子节点j上的样本集合为I_j=\left\{ i|q(x_i)=j \right\},则目标函数可以改写为:

Obj^{(t)}=\sum_{i=1}^N (g_if_t(x_i) + \frac{1}{2} h_if_t^2(x_i)) +\gamma T+ \frac{1}{2}\lambda  \sum_{j=1}^T w_j^2

=\sum_{i=1}^N (g_iw_q(x_i) + \frac{1}{2} h_iw_q^2(x_i)) +\gamma T+ \frac{1}{2}\lambda  \sum_{j=1}^T w_j^2

=\sum_{j=1}^T (\sum_{i\in I_j}g_iw_j + \frac{1}{2} \sum_{i\in I_j}h_iw_j^2) +\gamma T+ \frac{1}{2}\lambda  \sum_{j=1}^T w_j^2

=\sum_{j=1}^T(G_jw_j+\frac{1}{2} (H_j+\lambda )w_j^2)+\gamma T

这就是目标函数的最终的结果,其中,G_j=\sum\nolimits_{i\in I_j}g_i, H_j=\sum\nolimits_{i\in I_j}h_i

接下来我们进行目标函数的优化,即计算第t轮时使目标函数最小的叶节点的输出分数w,直接对w求导,使得倒数为0,得:

w_j=-\frac{G_j}{H_j+\lambda }

将其带入代价函数中:

Obj^{(t)} = \sum_{j=1}^T(G_jw_j+\frac{1}{2} (H_j+\lambda )w_j^2)+\gamma T

=\sum_{j=1}^T(-\frac{G_j^2}{H_j+ \lambda }+\frac{1}{2} \frac{G_j^2}{H_j+ \lambda })+\gamma T

上式越小越好,即目标函数越小。

确定树结构:

    采用贪心算法,每次尝试分裂一个叶节点,计算分裂后的增益,选择增益最大的。类似在ID3中的信息增益和CART树中的基尼指数,XGBoost的损失函数为:

-\frac{1}{2} \sum_{j=1}^T (\frac{G_j^2}{H_j+\lambda })  + \gamma T

\frac{G_j^2}{H_j+\lambda }衡量了叶子节点对总体损失的贡献,目标函数越小越好,则该函数越大越好,在XGBoost中增益计算方法是:

Gain=\frac12 [\frac{G_L^2}{H_L+\lambda }  +  \frac{G_R^2}{H_R+\lambda }  -  \frac{(G_L+G_R)^2}{H_L+H_R+\lambda }]-\gamma

\frac{G_L^2}{H_L+\lambda }:左子树分数

\frac{G_R^2}{H_R+\lambda }:右子树分数

\frac{(G_L+G_R)^2}{H_L+H_R+\lambda }:分裂前分数

\gamma :新叶节点复杂度

Gain值越大,说明分裂后能使目标函数减小得越多,也就是越好。

树结构的确定——精确贪心算法:

枚举所有的特征与特征值,计算树的分裂方式

输入:I,instance set of current node当前节点数据集

输入:d,feature dimension特征维度

gain\leftarrow 0增益为0

G\leftarrow \sum\nolimits_{i\in I} g_i,H\leftarrow \sum\nolimits_{i\in I}h_i一阶导的和与二阶导的和

for k = 1 to m do遍历树

    G_L\leftarrow 0,H_L \leftarrow 0

    for j in sorted(I,by\ \ X_{jk}) do

        G_L\leftarrow G_L+g_i,H_L \leftarrow H_L+h_j

        G_R\leftarrow G-G_L,H_R \leftarrow H-H_L        得到左右子树的一阶导与二阶导            

        score \leftarrow max(score,\frac{G_L^2}{H_L+ \lambda}+ ,    \frac{G_R^2}{H_R+ \lambda} - \frac{G^2}{H+ \lambda})

        end

    end

Output:Split with max score

当数据量庞大,无法全部存入内存中时,精确算法很慢所以引入近似算法

近似算法

    根据特征k的分布确定l个候选切分点S_k=\left\{   s_{k1},s_{k2},...,s_{kl}  \right\},然后根据候选切分点把相应的样本放入对应的“桶”中,对每个桶的G,H进行累加,在候选切分点集合上进行精确贪心查找。算法描述如下:

for k = 1 to m do遍历树

    Propose S_k=\left\{   s_{k1},s_{k2},...,s_{kl}  \right\} by percentiles on feature k

    Proposal can be done per tree (global),or per split(local)

end

for k = 1 to m do

    G_{kv} \leftarrow = \sum\nolimits_{j \in \left\{  j \vert s_{k,v}\geq x_{jk}>s_{k,v}-1 \right\}} g_i

    H_{kv} \leftarrow = \sum\nolimits_{j \in \left\{  j \vert s_{k,v}\geq x_{jk}>s_{k,v}-1 \right\}} h_i

end

Follow same step as in previous section to find max score only among proposed splits

简单例子


何时选取切分点?全局策略(Global)和局部策略(Local)

-全局策略:学习每棵树前,提出候选的切分点,当切分点数足够多时,和精确的贪心算法性能相当

-局部策略:树节点分裂时,重新提出候选切分点,切分点个数不需要那么多,性能与精确贪心算法相差不多

如何选取切分点?XGBoost并没有采用简单的分位数方法,而是提出了以二阶梯度h为权重的分位数算法(Weighted Quantile Sketh)。对特征k构造multi-set数据集:

    D_k = (x_{1k},h_1),(x_{2k},h_2),...,(x_{nk},h_n)

其中,x_{ik}表示样本i的特征k的取值,而h_i是对应的二阶梯度。定义一个rank function,表示第k个特征小于z的样本比例:

    r_k(z) = \frac{1}{\sum\nolimits_{(x,h)\in D_k}h }  \sum_{(x,h)\in D_k,x<z} h

切分点\left\{   s_{k1},s_{k2},...,s_{kl}  \right\}应该满足:

    |r_k(s_{k,j})-r_k(s_{k,j+1})|<\varepsilon ,s_{k1}=min\ x_{ik},s_{kl}=max\ x_{ik}

也就是说相邻的两个候选切分点相差不超过某个值\varepsilon

为何要用二阶梯度?将目标函数的泰勒展开推导如图:


由上图,目标函数是真实值为-g_i/h,权重为hi的平方损失,所以用二阶梯度加权

稀疏值处理

稀疏值产生的原因:

    数据缺失值,大量的零值,One-hot编码

XGBoost能对缺失值自动进行处理,思想是对于缺失值自动学习出它该划分的方向


简单来说:

-将特征k的缺失值都放在右树,枚举划分点,计算最大的gain

-将特征k的缺失值都放在左子树,枚举划分点,计算最大的gain,最后求出最大增益,确定缺失值的划分



步长

在XGBoost中也加入的步长\eta ,用于加法模型中,也叫做收缩率shrinkage:

    \hat{y_{i}}^t  = \hat{y_{i}}^{(t-1)}+\eta f_t(x_i)

    这有助于防止过拟合,步长\eta 通常取0.1



列采样

列抽样技术:一种是按层随机,另一种是按树随机(构建树前就随机选择特征)。

-按层随机,在每次分裂一个结点的时候,对同一层内的每个结点分裂之前,先随机选择一部分特征,这时候只需要遍历一部分特征,来确定最后分割点。

-按树随机,即构建树结构之前就随机选择特征,之后所有叶子节点的分裂都只使用这部分特征。





系统设计


分块并行

    在建树过程中,最耗时是找最优的切分点,而这个过程中,最耗时的部分是将数据排序。为了减少排序的时间,提出Block结构存储数据。

    -Block中的数据以稀疏格式CSC进行存储

    -Block中的特征进行排序(不对缺失值排序)

    -Block中特征还需存储指向样本的索引,这样才能根据特征的值来取梯度

    -一个Block中存储一个或多个特征的值


    只需要在建树前排序一次,后面节点分裂时可以直接根据索引得到梯度信息。

    -在精确算法中,将整个数据集存放在一个Block中,这样,复杂度降低,这样就能省去了每一步中的排序开销。

    -在近似算法中,使用多个Block,每个Block对应原来数据的子集。不同的Block可以在不同的机器上计算。该方法对Local策略尤其有效,因为Local策略每次分支都重新生成候选切分点。

缺点:

    取梯度的时候,是通过索引来获取的,而这些梯度的获取顺序是按照特征的大小顺序的。这将导致非连续的内存访问,可能使得CPU cache缓存命中率低,从而影响算法效率

缓存优化

    对于精确算法,使用缓存预取。具体来说,对每个线程分配一个连续的buffer,读取梯度信息并存入Buffer中(实现了非连续到连续的转化),然后再统计梯度信息。该方式在训练样本数大的情况下很有效。

    对于近似算法,对Block的大小进行了合理的设置。定义Block的大小为Block中最多的样本数。设置合适的大小是很重要的,过大易导致命中率低,过小则容易导致并行化效率不高。经过实验,一般2^{16}比较好

Out of core Computation

    当数据量太大不能全部放入主存的时候,为了使得out-of-core计算成为可能,将数据划分为多个Block并存放在磁盘上。

    -计算的时候,使用独立的线程预先将Block放入主存,因此可以在计算的时候同时读取磁盘

    -Block压缩

    -Block Sharding,将数据划分到不同的硬盘上,提高吞吐率

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一.AdaBoost的算法 在学习adaboost算法前先要弄清楚前向分布算法,因为AdaBoost是前向分布加法...
    ZAK_ML阅读 5,768评论 0 0
  • 之前介绍过梯度下降法与牛顿法,GBDT与XGBoost就与这两种方法有关。 boosting(包括GBDT、XGB...
    小松qxs阅读 24,523评论 0 31
  • 随机森林 1. 原理 随机森林属于Bagging的扩展变体 Bagging:有放回抽样,多数表决(分类)或简单平均...
    Manfestain阅读 4,048评论 0 0
  • 1.引子 XGBoost在机器学习领域可谓风光无限,作为从学术界来的模范生,帮助工业界解决了许多实际问题,真可...
    散落一地的蓝阅读 9,022评论 1 28
  • R字母因为既构成元音组合又是辅音字母,所以很多学生切单词很纠结。不必纠结,Tyger讲R的切分问题总结为两句话:能...
    Tyger老师阅读 4,455评论 4 2

友情链接更多精彩内容