集成学习

集成学习的目的是通过结合多个基学习器的预测结果来改善单个学习器的泛化能力和鲁棒性。
目前主流方法有三种:
1.Boosting方法:包括Adaboost,GBDT, XGBoost等
2.Bagging方法:典型的是Random Forest
3.Stacking算法

RF

Random Forest(随机森林)是Bagging的扩展变体。
Bagging可以简单的理解为:放回抽样(自助法),多数表决(分类)或简单平均(回归),同时Bagging的基学习器之间属于并列生成,不存在强依赖关系。随机森林在以决策树为基学习器构建Bagging集成的基础上,进一步在决策树的训练过程中引入了随机特征选择,因此可以概括 RF包括四个部分:1、随机选择样本(放回抽样);2、随机选择特征;3、构建决策树;4、随机森林投票(平均)。
在构建决策树的时候,RF的每棵决策树都最大可能的进行生长而不进行剪枝;在对预测输出进行结合时,RF通常对分类问题使用简单投票法,回归任务使用简单平均法。
RF的重要特性是不用对其进行交叉验证或者使用一个独立的测试集获得无偏估计,它可以在内部进行评估,也就是说在生成的过程中可以对误差进行无偏估计,由于每个基学习器只使用了训练集中约63.2%的样本,剩下约36.8%的样本可用做验证集来对其泛化性能进行 “包外估计”

  • 特征选择
    在随机森林中某个特征X的重要性的计算方法如下:
    (1)对于随机森林中的每一颗决策树,使用相应的\(OOB\)(袋外)数据计算它的袋外数据误差,记为\(errOOB1\)
    (2)随机地对袋外数据所有样本的特征X加入噪声干扰(就可以随机的改变样本在特征X处的值),再次计算它的袋外数据误差,记为\(errOOB2\)
    (3)假设随机森林中有Ntree颗树,那么对于特征X的重要性$ =\sum\frac{err00B2-errOOB1}{Ntree}$,之所以可以用这个表达式来作为相应特征的重要性的度量值是因为:若给某个特征随机加入噪声之后,袋外的准确率大幅度下降,则说明这个特征对于样本的分类结果影响很大,也就是说它的重要程度比较高。

  • sklearn-RandomForest参数
    RandomForestClassifier(
    n_estimators=10, //树的棵树
    criterion='gini', //分类标准
    max_depth=None, //最大深度
    min_samples_split=2, //最少分裂几个子节点
    min_weight_fraction_leaf=0.0,
    max_leaf_nodes=None,
    bootstrap=True,
    n_jobs=1, //指定并行使用的进程数
    random_state=None,
    verbose=0,
    warm_start=False,
    class_weight=None //类别权重,样本不均衡时很重要
    )

  • RandomForest特点
    RF和Bagging对比:RF的起始性能较差,特别当只有一个基学习器时,随着学习器数目增多,随机森林通常会收敛到更低的泛化误差。随机森林的训练效率也会高于Bagging,因为在单个决策树的构建中,Bagging使用的是‘确定性’决策树,在选择特征划分结点时,要对所有的特征进行考虑,而随机森林使用的是‘随机性’特征数,只需考虑特征的子集。
    优点:1、在数据集上表现良好,相对于其他算法有较大的优势(训练速度、预测准确度);2、能够处理很高维的数据,并且不用特征选择,而且在训练完后,给出特征的重要性;3、容易做成并行化方法。
    缺点:在噪声较大的分类或者回归问题上会过拟合。
    对于不平衡数据集平衡误差是因为随机森林在损失函数里为不同的类设置了不同的权重。就是代价敏感 样本数目比较少的类设置更高的权重,也就是更高的错误惩罚,样本多的反之。

AdaBoost

Boosting, 也称为增强学习或提升法,是一种重要的集成学习技术, 能够将预测精度仅比随机猜度略高的弱学习器增强为预测精度高的强学习器,这在直接构造强学习器非常困难的情况下,为学习算法的设计提供了一种有效的新思路和新方法。
Boosting算法主要针对难以区分的样本,弱学习机通过在分类错误的样本上进行学习来提高继承分类器的分类性能。 Boosting与Bagging不同,在Boosting的初始化阶段采用的是无返回抽样从训练样本中随机抽取一个子集,而Bagging采用的是有放回的抽取。
AdaBoost算法与Boosting算法不同,它是使用整个训练集来训练弱学习机,其中训练样本在每次迭代的过程中都会重新被赋予一个权重,在上一个弱学习机错误的基础上进行学习来构建一个更加强大的分类器。AdaBoost是英文"Adaptive Boosting"(自适应增强)的缩写, 它的自适应在于:前一个基本分类器被错误分类的样本的权值会增大,而正确分类的样本的权值会减小,并再次用来训练下一个基本分类器。同时,在每一轮迭代中,加入一个新的弱分类器,直到达到某个预定的足够小的错误率或达到预先指定的最大迭代次数才确定最终的强分类器。

  • Adaboost算法可以简述为三个步骤:
    (1)首先,是初始化训练数据的 权值分布\(D1\)。假设有N个训练样本数据,则每一个训练样本最开始时,都被赋予相同的权值:\(w1=1/N\)
    (2)然后,训练弱分类器hi。具体训练过程中是:如果某个训练样本点,被弱分类器hi准确地分类,那么在构造下一个训练集中,它对应的权值要减小;相反,如果某个训练样本点被错误分类,那么它的权值就应该增大。权值更新过的样本集被用于训练下一个分类器,整个训练过程如此迭代地进行下去。
    (3)最后,将各个训练得到的弱分类器组合成一个强分类器。各个弱分类器的训练过程结束后,加大分类误差率小的弱 分类器的权重,使其在最终的分类函数中起着较大的决定作用,而降低分类误差率大的弱分类器的权重,使其在最终的分类函数中起着较小的决定作用。换而言之,误差率低的弱分类器在最终分类器中占的权重较大,否则较小。
    Adaboost算法中有两种权重,一种是数据的权重,另一种是弱分类器的权重。其中,数据的权重主要用于弱分类器寻找其分类误差最小的决策点,找到之后用这个最小误差计算出该弱分类器的权重(发言权),分类器权重越大说明该弱分类器在最终决策时拥有更大的发言权。最终,通过所有弱分类器进行加权投票表决的方法得到最终预测输出

Adaboost的理解:是一种加法模型,损失函数为指数函数,学习算法为前向分步算法

  • 优点和缺点
    优点:
    1、Adaboost提供一种框架,在框架内可以使用各种方法构建子分类器。可以使用简单的弱分类器,不用对特征进行筛选,也不存在过拟合的现象。
    2、Adaboost算法不需要弱分类器的先验知识,最后得到的强分类器的分类精度依赖于所有弱分类器。无论是应用于人造数据还是真实数据,Adaboost都能显著的提高学习精度。
    3、Adaboost算法不需要预先知道弱分类器的错误率上限,且最后得到的强分类器的分类精度依赖于所有弱分类器的分类精度,可以深挖分类器的能力。Adaboost可以根据弱分类器的反馈,自适应地调整假定的错误率,执行的效率高。
    缺点:
    1、在Adaboost训练过程中,Adaboost会使得难于分类样本的权值呈指数增长,训练将会过于偏向这类困难的样本,导致Adaboost算法易受噪声干扰。
    2、Adaboost依赖于弱分类器,而弱分类器的训练时间往往很长。

GBDT

GBDT是通过采用加法模型(即基函数的线性组合),以及不断减小训练过程产生的残差来达到将数据分类或者回归的算法。
GBDT与传统的Boosting区别较大,它的每一次计算都是为了减少上一次的残差,而 为了消除残差,我们可以在残差减小的梯度方向上建立模型,所以说,在GradientBoost中,每个新的模型的建立是为了使得之前的模型的残差往梯度下降的方法,与传统的Boosting中关注正确错误的样本加权有着很大的区别。
弱分类器一般会选择为CART TREE(也就是分类回归树)。由于Boosting框架对于高偏差和简单的要求,每个分类回归树的深度不会很深。最终的总分类器是将每轮训练得到的弱分类器加权求和得到的(也就是加法模型)。
模型最终可以描述为:

\[F_m(x)=\sum_{m=1}^{M}T(x;\theta_m) \]

模型一共训练M轮,每轮产生一个弱分类器\(T(x;\theta_m)\)。弱分类器的损失函数

\[\hat\theta_m=\arg_{\theta_m}min\sum_{i=1}^NL(y_i,F_{m-1}(x_i)+T(x_i;\theta_m)) \]

\(F_{m−1}(x)\)为当前的模型,GBDT 通过经验风险极小化来确定下一个弱分类器的参数。具体到损失函数本身的选择也就是L的选择,有平方损失函数,0-1损失函数,对数损失函数等等。如果我们选择平方损失函数,那么这个差值其实就是我们平常所说的残差。
利用损失函数的负梯度在当前模型的值作为回归问题提升树算法中的残差的近似值去拟合一个回归树。GBDT 每轮迭代的时候,都去拟合损失函数在当前模型下的负梯度。这样每轮训练的时候都能够让损失函数尽可能快的减小,尽快的收敛达到局部最优解或者全局最优解。

  • GBDT常用损失函数

    • GBDT分类算法的损失函数
      (a)指数损失函数(其实就是Adaboost算法),损失函数表达式为:

    \[L(y,f(x))=\exp(-yf(x)) \]

    (b)对数损失函数,分为二元分类和多元分类两种

    • GBDT回归算法的损失函数
      (a)均方差,最常用,损失函数表达式为:

    \[L(y,f(x))=(y-f(x))^2 \]

    (b)绝对损失,损失函数表达式为:

    \[L(y,f(x))=|y-f(x))| \]

    对应负梯度误差为:

    \[sign(y_i-f(x_i)) \]

    (c) Huber损失,它是均方差和绝对损失的折衷产物,对于远离中心的异常点,采用绝对损失,而中心附近的点采用均方差。这个界限一般用分位数点度量。损失函数如下:

    \[L(y,f(x))= \begin{cases} \frac{1}{2}(y-f(x))^2& {|y-f(x)|\leq {\delta} } \\ \delta(|y-f(x)|-\frac{\delta}{2})& {|y-f(x)|>\delta} \end{cases}\]

    (d) 分位数损失,它对应的是分位数回归的损失函数,表达式为:

    \[L(y,f(x))=\sum_{y \geq f(x)}\theta|y-f(x)|+\sum_{y<f(x)}(1-\theta)|y-f(x)| \]

    对于Huber损失和分位数损失,主要用于健壮回归,也就是减少异常点对损失函数的影响。

  • 损失函数最小化方法
    1)指数函数
    当损失函数为指数函数时,比如AdaBoost算法的损失函数是指数函数,这时通过 前向分步算法来解决。
    前向分布算法在每轮迭代时,通过将上一轮所生成的模型所产生的损失函数最小化的方法来计算当前模型的参数。指数损失仅能用于二分类的情况。
    2)对数损失函数
    对于二元GBDT,如果用类似于逻辑回归的对数似然损失函数,则损失函数为:

\[L(y,f(x)) = log(1+\exp(-yf(x))) \]

其中\(y∈\{−1,+1\}\)。则此时的负梯度误差为

\[-\frac{\partial L(y,f(x_i))}{\partial f(x_i)}=\frac{y_i}{1+\exp(y_if(x_i))} \]

多元GBDT要比二元GBDT复杂一些,对应的是多元逻辑回归和二元逻辑回归的复杂度差别。假设类别数为K,则此时我们的对数似然损失函数为:

\[L(y,f(x))=-\sum_{k=1}^K y_k\log{p_k(x)} \]

其中如果样本输出类别为k,则\(y_k=1\)。第k类的概率\(p_k(x)\)的表达式为:

\[p_k(x)=\exp(f_k(x))/\sum_{l=1}^{K}\exp(f_l(x)) \]

集合上两式,我们可以计算出第\(t\)轮的第\(i\)个样本对应类别\(l\)的负梯度误差为

\[-\frac{\partial L(y,f(x_i))}{\partial f(x_i)}=y_{il}-p_{l,t-1}(x_i) \]

观察上式可以看出,其实这里的误差就是样本\(i\)对应类别\(l\)的真实概率和\(t−1\)轮预测概率的差值。
除了负梯度计算和叶子节点的最佳负梯度拟合的线性搜索,多元GBDT分类和二元GBDT分类以及GBDT回归算法过程相同。
3)平方误差损失函数
在回归树提升算法中,遍历当前输入样例的可能取值,将每种可能值计算一遍损失函数,最终选择损失函数最小的值。
在计算平方损失误差时,可能出现残差项\((y-f_{m-1}(x))\),此时可以通过如下方法来进行优化:每一轮迭代是为了减小上一步模型的残差,为了减少残差,每次在残差的负梯度方向建立一个新的模型,这样一步一步的使得残差越来越小。
4)一般损失函数
对于一般损失函数,可以通过梯度下降的方法来使得损失函数逐步减小,每次向损失函数的负梯度方向移动,直到损失函数值到达谷底。

  • 优点和缺点
    在Gradient Boosting算法中,关键就是利用损失函数的负梯度方向在当前模型的值作为残差的近似值,进而拟合一棵CART回归树。
    GBDT会累加所有树的结果,而这种累加是无法通过分类完成的,因此GBDT的树都是CART回归树,而不是分类树(尽管GBDT调整后也可以用于分类但不代表GBDT的树为分类树)。
    GBDT的决策边界可能是很多条线。GBDT并不一定总是好于线性回归或逻辑回归。根据没有免费的午餐原则,没有一个算法是在所有问题上都能好于另一个算法的。根据奥卡姆剃刀原则,如果GBDT和线性回归或逻辑回归在某个问题上表现接近,那么我们应该选择相对比较简单的线性回归或逻辑回归。具体选择哪一个算法还是要根据实际问题来决定。
    最后总结下GBDT的优缺点。
    GBDT主要的优点有:
    1.可以灵活处理各种类型的数据,包括连续值和离散值。
    2.在相对少的调参时间情况下,预测的准确率也可以比较高。这个是相对SVM来说的。
    3.使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。
    GBDT的主要缺点有:
    1.由于弱学习器之间存在依赖关系,难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行。
  • AdaBoost V.S. GBDT
    和AdaBoost一样,Gradient Boosting也是重复选择一个表现一般的模型并且每次基于先前模型的表现进行调整。不同的是,AdaBoost是通过提升错分数据点的权重来定位模型的不足而Gradient Boosting是通过算梯度(gradient)来定位模型的不足。 因此相比AdaBoost, Gradient Boosting可以使用更多种类的目标函数,而当目标函数是均方误差时,计算损失函数的负梯度值在当前模型的值即为残差。
    GBDT每一轮训练时所关注的重点是本轮产生结果的残差,下一轮以本轮残差作为输入,尽量去拟合这个残差,使下一轮输出的残差不断变小。所以GBDT可以做到每一轮一定向损失函数减小的梯度方向变化,而传统的boosting算法只能是尽量向梯度方向减小,这是GBDT与传统boosting算法最大的区别,这也是为什么GBDT相比传统boosting算法可以用更少的树个数与深度达到更好的效果。

XGBoost

Xgboost是GB算法的高效实现,xgboost中的基学习器除了可以是CART(gbtree)也可以是线性分类器(gblinear)。

  • 算法原理
    本部分主要参考陈天奇的introduction to xgboost
    首先,模型的目标函数定义为:

\[obj = \sum_{i=1}^n l( y_i, \hat{y}_i^{ (t) } ) + \sum_{i=1}^t \Omega(f_i) \]

这个目标函数同样包含两部分,第一部分就是损失函数,第二部分就是正则项,这里的正则化项由K棵树的正则化项相加而来。
可以发现,我们需要学习的目标是\(f_i\),包括每棵树的结构和各个叶子节点上的分数。
可以采用加法训练,目标不再是直接优化整个目标函数,而是分步骤优化目标函数,首先优化第一棵树,完了之后再优化第二棵树,直至优化完t棵树。整个过程如下所示:

\[\begin{aligned} \hat{y}_i^{(0)} &= 0\\ \hat{y}_i^{(1)} &= f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ \hat{y}_i^{(2)} &= f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ &\dots\\ \hat{y}_i^{(t)} &= \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) \end{aligned} \]

在第t步时,我们添加了一棵最优的CART树\(f_t\),这棵最优的CART树\(f_t\)是怎么得来的呢?非常简单,就是在现有的t-1棵树的基础上,使得目标函数最小的那棵CART树,如下所示:

\[\begin{aligned}\text{obj}^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + \mathrm{constant}\end{aligned} \]

其中constant就是前t-1棵树的复杂度,假如我们使用的损失函数时MSE,那么上述表达式会变成这个样子:

\[\begin{aligned}\text{obj}^{(t)} & = \sum_{i=1}^n (y_i - (\hat{y}_i^{(t-1)} + f_t(x_i)))^2 + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n [2(\hat{y}_i^{(t-1)} - y_i)f_t(x_i) + f_t(x_i)^2] + \Omega(f_t) + \mathrm{constant}\end{aligned} \]

这个式子非常漂亮,因为它含有\(f_t(x_i)\)的一次式和二次式,而且一次式项的系数是残差。你可能好奇,为什么有一次式和二次式就漂亮,因为它会对我们后续的优化提供很多方便
Note:\(f_t(x_i)\)是什么?它其实就是\(f_t\)某个叶子节点的值。之前我们提到过,叶子节点的值是可以作为模型的参数的。
对于其他的损失函数,我们未必能得出如此漂亮的式子,所以,对于一般的损失函数,我们需要将其作泰勒二阶展开,如下所示:

\[\text{obj}^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + \mathrm{constant} \]

其中:

\[\begin{aligned}g_i &= \partial_{\hat{y}_i^{(t-1)}} l(y_i, \hat{y}_i^{(t-1)})\\ h_i &= \partial_{\hat{y}_i^{(t-1)}}^2 l(y_i, \hat{y}_i^{(t-1)})\end{aligned} \]

去除常数项,可得

\[\sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) \]

为什么一次式和二次式显得那么漂亮,就是因为这些一次式和二次式的系数是\(g_i\)\(h_i\),而\(g_i\)\(h_i\)可以并行地求出来。而且,\(g_i\)\(h_i\)是不依赖于损失函数的形式的,只要这个损失函数二次可微就可以了。这有什么好处呢?好处就是xgboost可以支持自定义损失函数,只需满足二次可微即可。

下面,定义后面的正则项,先对CART树作一番定义,如下所示:

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

需要解释下这个定义,首先,一棵树有\(T\)个叶子节点,这\(T\)个叶子节点的值组成了一个\(T\)维向量\(w\)\(q(x)\)是一个映射,用来将样本映射成1到\(T\)的某个值,也就是把它分到某个叶子节点,\(q(x)\)其实就代表了CART树的结构。\(w_q(x)\)自然就是这棵树对样本\(x\)的预测值了。
xgboost使用了如下的正则化项:

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

Note:这里出现了\(γ\)\(λ\),这是xgboost自己定义的,在使用xgboost时,你可以设定它们的值,显然,\(γ\)越大,表示越希望获得结构简单的树,因为此时对较多叶子节点的树的惩罚越大。\(λ\)越大也是越希望获得结构简单的树。
至此,我们关于第t棵树的优化目标已然很清晰,下面我们对它做如下变形

\[\begin{aligned}\text{obj}^{(t)} &\approx \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^T_{j=1} [(\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} \]

其中\(I_j\)代表一个集合,集合中每个值代表一个训练样本的序号,整个集合就是被第\(t\)棵CART树分到了第\(j\)个叶子节点上的训练样本。
进一步,我们可以做如下简化:

\[\text{obj}^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T \]

对于第\(t\)棵CART树的某一个确定的结构(可用\(q(x)\)表示),所有的\(G_j\)\(H_j\)都是确定的。而且上式中各个叶子节点的值\(w_j\)之间是互相独立的。上式其实就是一个简单的二次式,我们很容易求出各个叶子节点的最佳值以及此时目标函数的值。如下所示:

\[\begin{aligned}w_j^\ast &= -\frac{G_j}{H_j+\lambda}\\ \text{obj}^\ast &= -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T\end{aligned} \]

其中\({obj}^\ast\)表示了这棵树的结构有多好,值越小,代表这样结构越好!也就是说,它是衡量第\(t\)棵CART树的结构好坏的标准。注意,这个值仅仅是用来衡量结构的好坏的,与叶子节点的值可是无关的。\({obj}^\ast\)只和\(G_j\)\(H_j\)和T有关,而它们又只和树的结构\(q(x)\)有关,与叶子节点的值无关。
有了评判树的结构好坏的标准,我们就可以先求最佳的树结构,这个定出来后,最佳的叶子结点的值实际上在上面已经求出来了。问题是:树的结构近乎无限多,一个一个去测算它们的好坏程度,然后再取最好的显然是不现实的。所以,我们仍然需要采取一点策略,这就是逐步学习出最佳的树结构。这与我们将K棵树的模型分解成一棵一棵树来学习是一个道理,只不过从一棵一棵树变成了一层一层节点而已。
每次,我们在叶子节点上,找出所有的切分点。对每一个确定的切分点,我们衡量切分好坏的标准如下:

\[Gain = \frac{1}{2} \left[\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}\right] - \gamma \]

这个\(Gain\)实际上就是单节点的\({obj}^\ast\)减去切分后的两个节点的树\({obj}^\ast\)\(Gain\)如果是正的,并且值越大,表示切分后\({obj}^\ast\)越小于单节点的\({obj}^\ast\),就越值得切分。同时,我们还可以观察到,\(Gain\)的左半部分如果小于右侧的γ,则\(Gain\)就是负的,表明切分后\(obj\)反而变大了。\(γ\)在这里实际上是一个临界值,它的值越大,表示我们对切分后\(obj\)下降幅度要求越严。这个值也是可以在xgboost中设定的。
扫描结束后,我们就可以确定是否切分,如果切分,对切分出来的两个节点,递归地调用这个切分过程,我们就能获得一个相对较好的树结构。
Note:xgboost的切分操作和普通的决策树切分过程是不一样的。普通的决策树在切分的时候并不考虑树的复杂度,而依赖后续的剪枝操作来控制。xgboost在切分的时候就已经考虑了树的复杂度,就是那个\(γ\)参数。所以,它不需要进行单独的剪枝操作。

  • GBDT和XGBoost区别
    • 传统的GBDT以CART树作为基学习器,XGBoost还 支持线性分类器,这个时候XGBoost相当于L1和L2正则化的逻辑斯蒂回归(分类)或者线性回归(回归);
    • 传统的GBDT在优化的时候只用到一阶导数信息,XGBoost则对代价函数进行了 二阶泰勒展开,得到一阶和二阶导数;(二阶导数有利于梯度下降的更快更准. 使用泰勒展开取得二阶倒数形式, 可以在不选定损失函数具体形式的情况下用于算法优化分析.本质上也就把损失函数的选取和模型算法优化/参数选择分开了. 这种去耦合增加了xgboost的适用性。
    • XGBoost在代价函数中加入了 正则项,用于控制模型的复杂度。从权衡方差偏差来看,它降低了模型的方差,使学习出来的模型更加简单,放置过拟合,这也是XGBoost优于传统GBDT的一个特性;
    • shrinkage(缩减),相当于 学习速率(XGBoost中的eta)。XGBoost在进行完一次迭代时,会将叶子节点的权值乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。(GBDT也有学习速率);
    • 列抽样。XGBoost借鉴了随机森林的做法,支持列抽样,不仅防止过 拟合,还能减少计算;
    • 缺失值的处理。对于特征的值有缺失的样本,XGBoost还可以自动学习出它的分裂方向;
    • XGBoost工具支持 并行。Boosting不是一种串行的结构吗?怎么并行的?注意XGBoost的并行不是tree粒度的并行,XGBoost也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含了前面t-1次迭代的预测值)。 XGBoost的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),XGBoost在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代 中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。

Lightgbm

LightGBM它是对于XGB提升性能的版本。而LightGBM相对于其他GBM来说具有相近的准确率而且是其训练速度20倍。

  • 算法特性

    • Histogram Algorithm(直方图)
      直方图算法的基本思想是先把连续的特征值离散化成k个整数,同时构造一个宽度为k的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。具体的说,它将数据根据大小分为数个箱子,默认是256个,这样可以只用一个byte来表示,节约内存,这样原来要遍历所有样本来找最优分割,现在就只需要遍历所有的箱子就好了。大大节约计算量,显然,这样计算很可能找不到最优分割,然而实验表明,最终差异很小,有时候甚至优于全局搜索(这是可能的,决策树对分割点的精度不敏感,也可以把这种分割当作一种特别的正则)
    • Gradient-based One-Side Sampling(GOSS)
      GOSS(基于梯度的单边采样)方法的主要思想就是,梯度大的样本点在信息增益的计算上扮演着主要的作用,也就是说这些梯度大的样本点会贡献更多的信息增益,因此为了保持信息增益评估的精度,当我们对样本进行下采样的时候保留这些梯度大的样本点,而对于梯度小的样本点按比例进行随机采样即可。
    • Exclusive Feature Bundling(EFB)
      Lightgbm实现中不仅进行了数据采样,也进行了特征抽样,使得模型的训练速度进一步的减少。但是该特征抽样又与一般的特征抽样有所不同,是将互斥特征绑定在一起从而减少特征维度。主要思想就是,通常在实际应用中高纬度的数据往往都是稀疏数据(如one-hot编码),这使我们有可能设计一种几乎无损的方法来减少有效特征的数量。尤其,在稀疏特征空间中许多特征都是互斥的(例如,很少同时出现非0值)。这就使我们可以安全的将互斥特征绑定在一起形成一个特征,从而减少特征维度。但是怎样的将互斥特征绑定在一起了?Lightgbm作者使用的是基于直方图(histograms)的方法。
    • Leaf-wise的决策树生长策略
      大部分决策树的学习算法通过 level-wise 策略生长树,记一次分裂同一层的叶子,不加区分的对待同一层的叶子,而实际上很多叶子的分裂增益较低没必要进行分裂,带来了没必要的开销。LightGBM 通过 leaf-wise 策略来生长树。每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。但是,当样本量较小的时候,leaf-wise 可能会造成过拟合。 所以,LightGBM 可以利用额外的参数 \(max\_depth\) 来限制树的深度并避免过拟合。
    • 类别特征值的最优分割
      对于类别型的数据,我们通常将类别特征转化为one-hot/哑变量编码。 然而,对于学习树来说这不是个好的解决方案。 原因是,对于一个基数较大的类别特征,学习树会生长的非常不平衡,并且需要非常深的深度才能来达到较好的准确率。
      事实上,最好的解决方案是将类别特征划分为两个子集,总共有\(2^{k-1}-1\)种可能的切分。比如有一个颜色特征,每个样本的颜色特征是{红、黄、蓝、绿}四种类别中的一种,如果使用LightGBM的切分策略,就是将红、黄、蓝、绿对应的四类样本分为两类的所有可能策略,比如:红黄一类,蓝绿一类。那么就会有\(2^{k-1}-1\)种策略,这样才能充分的挖掘该维特征所包含的信息,找到最优的分割策略。 但是这样寻找最优分割策略的时间复杂度就会很大。对于回归树有个有效的解决方案。为了寻找最优的划分需要大约\(k*\log(k)\)。基本的思想是根据训练目标的相关性对类别进行重排序。 更具体的说,根据累加值(\(sum_{gradient}/sum_{hessian}\))重新对(类别特征的)直方图进行排序,然后在排好序的直方图中寻找最好的分割点。
  • 优化总结

  1. 速度和内存使用的优化
    LightGBM 利用基于 histogram 的算法,通过将连续特征(属性)值分段为 discrete bins 来加快训练的速度并减少内存的使用,相比于xgboost的pre-sort算法,复杂度从O(N)降到O(bin) 。 xgBoost使用的是pre-sorted算法(对所有特征都按照特征的数值进行预排序,在遍历分割点的时候用O(data)的代价找到一个特征上的最好分割点),能够更精确的找到数据分隔点; LightGBM使用的是histogram算法,占用的内存更低,数据分隔的复杂度更低。
  2. 基于直方图的稀疏优化
    对于稀疏的特征仅仅需要 O(2 * #non_zero_data) 来建立直方图
  3. 准确率的优化
    Leaf-wise (Best-first) 的决策树生长策略
    XGBoost采用的是level-wise生长策略,能够同时分裂同一层的叶子,从而进行多线程优化,不容易过拟合;但不加区分的对待同一层的叶子,带来了很多没必要的开销(因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂);LightGBM采用leaf-wise生长策略,每次从当前所有叶子中找到分裂增益最大(一般也是数据量最大)的一个叶子,然后分裂,如此循环;但会生长出比较深的决策树,产生过拟合(因此 LightGBM 在leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合)。当生长相同的 leaf,leaf-wise 算法可以比 level-wise 算法减少更多的损失。另一个比较巧妙的优化是 histogram 做差加速。一个容易观察到的现象:一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到。
  4. 直方图做差优化
    生长时,可以用父节点减去某一子节点得到另一个子节点,这样就可以只计算一个子节点
  5. 直接支持类别特征
  6. 多线程优化
posted @ 2019-05-27 21:05  Jamest  阅读(913)  评论(0编辑  收藏  举报