\quad\quadboosting算法是目前工业界广泛使用的基于树的集成算法之一,该算法有诸多的优势,包括对过拟合的处理,对缺失数据的处理,对大多数损失函数的支持等等。不过该算法有诸多的变体,有些也比较容易混淆,故本文选取若干主要的boosting算法,尽可能的说明各种boosting算法的来龙去脉。
\quad\quad本文将从最原始的boosting tree算法讲起,然后扩展到GBDT,最后再介绍xgboost,catboost,lightgbm三种在工业界最受欢迎的算法。

一.boosting tree

\quad\quad提到boosting算法就不得不先说bagging算法,而最典型的bagging算法就是随机森林。随机森林是强学习器的集成,每棵树的深度都会比较深,因此单棵树的偏差比较小,从而使得方差很大,因此随机森林采用bootstrap的方法对样本和特征进行抽样,构建多棵树,然后将多棵树的结果整合起来,比如如果是回归问题就是多棵树的结果的平均,如果是分类问题就是出现最多的类别。树越多,方差就会减少的越多,但是整个随机森林的方差减少了,偏差就会增大,虽然不会造成过拟合问题,但是预测值和真实期望之间存在较大偏差的问题。而boosting算法正好和bagging算法相反。boosting算法的思想就希望集成弱学习器,通过降低模型偏差的来拟合数据,每个弱学习器只拟合一部分,方差会小很多,然后将弱学习器没有拟合到的部分交给下一个弱学习器继续拟合,不断减少模型偏差,不过带来的后果就是整个模型的方差将会增大,由此可见boosting算法是会过拟合的,需要采用一定的策略来防止过拟合。通常弱学习器是深度较浅的决策树,故而称为boosting tree算法。
\quad\quad接下来详细介绍boosting tree算法过程。假设当前已训练好的模型为fk−1f_{k-1}fk−1​,待训练模型为gkg_kgk​,那么当前的目标就是:
mingkCost(gk)=mingk∑i=1NL(fk−1(xi)+gk(xi),yi)(1.1)min_{g_k}Cost(g_k) = min_{g_k}\sum_{i=1}^NL(f_{k-1}(x_i)+g_k(x_i),y_i) \tag{1.1}mingk​​Cost(gk​)=mingk​​i=1∑N​L(fk−1​(xi​)+gk​(xi​),yi​)(1.1)
如果模型训练到第k步完成,那么最终的模型就是f(x)=fk−1(x)+gk(x)f(x)=f_{k-1}(x)+g_k(x)f(x)=fk−1​(x)+gk​(x)
训练过程如下:
\quad\quad首先,初始化模型f0f_0f0​,然后训练一棵决策树g1g_1g1​,使得
∑i=0NL(f0(xi)+g1(xi),yi)\sum_{i=0}^NL(f_{0}(x_i)+g_1(x_i),y_i)∑i=0N​L(f0​(xi​)+g1​(xi​),yi​)最小化,训练一棵树,首先是需要确定好最优的split特征,以及最优的分割点,我以训练单层的二叉决策树为例来说明,假设落入左子节点的样本集为SlS_{l}Sl​,落入右子节点的样本集为SrS_{r}Sr​,那么左子节点因为是叶节点,所以需要一个得分(比如在回归树中,叶子结点得分就是落入该叶子节点的样本的因变量Y的平均),该得分的计算方式如下:
γ1,l=argminγ∑i∈SlL(f0(xi)+γ,yi)\gamma_{1,l} = argmin_{\gamma}\sum_{i \in S_l}L(f_0(x_i)+\gamma,y_i)γ1,l​=argminγ​i∈Sl​∑​L(f0​(xi​)+γ,yi​)
注意这里的γ1,l\gamma_{1,l}γ1,l​ 为一个值,而不是未知函数,即g1(x)=γ1,lg_1(x)=\gamma_{1,l}g1​(x)=γ1,l​ ,当x∈Slx \in S_lx∈Sl​ ,同理计算右叶子结点得分γ1,r\gamma_{1,r}γ1,r​,那么当前的分割方式的增益为
gain−v=∑i=1NL(f0(xi),yi)−∑i∈SlL(f0(xi)+γ1,l,yi)−∑i∈SrL(f0(xi)+γ1,r,yi)gain-v =\sum_{i =1}^NL(f_0(x_i),y_i)-\sum_{i \in S_l}L(f_0(x_i)+\gamma_{1,l},y_i)-\sum_{i \in S_r}L(f_0(x_i)+\gamma_{1,r},y_i) gain−v=i=1∑N​L(f0​(xi​),yi​)−i∈Sl​∑​L(f0​(xi​)+γ1,l​,yi​)−i∈Sr​∑​L(f0​(xi​)+γ1,r​,yi​)
然后选取最优的gain−vgain-vgain−v对应的分割方式作为二叉树的生成方法。然后令f1(x)=f0(x)+g1(x)f_1(x)=f_0(x)+g_1(x)f1​(x)=f0​(x)+g1​(x),依此进行剩余树的生成。
\quad\quadboosting tree的一个缺陷就是,并不适用于全部的损失函数,拿回归树来举例,在进行最优分割点计算的时候,采用的算法称为pre-sorting,假设当前待处理的特征为feature,首先针对所有样本的feature值进行从小到大的排序,记录两个列表,列表A存储排序后的feature值,另一个列表B记录排序后的样本的index值(也就是需要知道是哪个样本排在了哪个位置),然后计算列表A相邻两个元素的平均数,这个就是分割点。然后按从小到大的顺序遍历分割点,计算每一个分割点的分割增益,取分割增益最大的分割点为这个特征的最优的分割点。在对分割点进行遍历的时候,需要分别计算左子节点,右子节点的得分,针对MSE来说,就是计算结点内的样本因变量的平均,对于MAE来说,我们需要计算的是中位数。当损失函数是MSE时,我们是可以复用上一个分割点的信息的。具体做法如下:只需在子节点中记录当前的样本的因变量的和,以及样本个数,这样就可以计算叶子结点的得分,即平均值,在遍历下一个分割点的时候,本质上将一个样本从右子节点移动到左子节点,只需要使用O(1)O(1)O(1)的时间复杂度就可以完成两个子节点中样本的因变量的和的更新。但是反观MAE,这点就不行,因为将一个样本从右子节点移动到左子节点之后,仍然需要再进行一次结点内的排序,然后才能找到中位值,时间复杂度是远大于O(1)O(1)O(1)的,无法找到快速的最优分割点计算方法的。为了解决boosting tree这个缺陷,才诞生了GBDT算法。

二. GBDT(Gradient Boosted Decission Tree)

\quad\quadGBDT算法就是为了解决boosting tree算法适用性较窄的缺陷而产生的算法。目前网上关于GBDT的介绍也有不少,不过多数的介绍都是从回归问题中拟合残差这个角度来写,本部分尝试从另一个角度来介绍,当然这个角度也并非是自己的原创,只不过是查阅了boosting的相关论文和原著,再结合自己的理解汇总而成。
\quad\quad 其实有监督学习的主要目的是要通过自变量XXX来构建一个模型fff,对因变量YYY进行拟合,从而使得拟合值Y^\hat{Y}Y^和真实值YYY尽可能的接近。 如果用式子来表达的话,也就是:
minfCost(f)=minf∑i=1NL(yi,f(xi))(2.1)min_f Cost(f)=min_f\sum_{i=1}^N L(y_i,f(x_i)) \tag{2.1}minf​Cost(f)=minf​i=1∑N​L(yi​,f(xi​))(2.1)
其中L(.)L(.)L(.)是损失函数,fff就是待拟合的函数(或者模型),即f(xi)=yi^f(x_i)=\hat{y_i}f(xi​)=yi​^​,Cost(.)Cost(.)Cost(.)函数其实就是样本损失函数的和。
\quad\quad我们在训练模型的时候,都是使用有限多的样本作为训练集,假设样本数为NNN,其实单从拟合的角度来讲,我们只需要求解出NNN个样本所对应的因变量的拟合值(y1^,...,yN^)(\hat{y_1},...,\hat{y_N})(y1​^​,...,yN​^​)即可,也就是说并不需要求解一个显性的完整的fff,只需要它在NNN个样本处的取值即可。那这问题就简单的多了,我们只需要令f(xi)=yif(x_i)=y_if(xi​)=yi​,这样一定会最小化式(2.1),而且是完全拟合因变量YYY。不过我们似乎并没有构建一个模型,只是直接把因变量Y当做拟合值Y^\hat{Y}Y^,这样做虽然简单,但是会有如下两个问题:

  • 这个模型是过拟合的,它将训练集中的噪声也完全拟合
  • 这个模型是无法对训练集外的数据进行预测,没有泛化能力

接下来我们来解决这两个问题。


1.这个模型是过拟合的,它将训练集中的噪声也完全拟合

\quad\quad将因变量作为拟合值使得Cost(f)Cost(f)Cost(f)一下子减少至最小值,虽然完美的解决了问题(2.1),但是导致了过拟合,是否可以分成多步,每一步都只对问题(2.1)做一点点优化,也就是说每一步我只让Cost(f)Cost(f)Cost(f)减少一点点,然后在适当的步数之后,停止优化,从而减少对噪声的拟合,以此来防止过拟合。这个过程很容易让人想到梯度下降法。具体过程如下:

\quad\quad首先,对(f(x1),...,f(xN))(f(x_1),...,f(x_N))(f(x1​),...,f(xN​))进行初始化,记为f0⃗=(f0(x1),...,f0(xN))\vec{f_0}=(f_0(x_1),...,f_0(x_N))f0​​=(f0​(x1​),...,f0​(xN​)),然后对f(xi)f(x_i)f(xi​)求导,即:
g1,i=[∂L(yi,f(xi))∂f(xi)]f(xi)=f0(xi)(2.2)g_{1,i}=[\frac{\partial{L(y_i,f(x_i))}}{{\partial f(x_i)}}]_{f(x_i)=f_{0}(x_i)} \tag{2.2}g1,i​=[∂f(xi​)∂L(yi​,f(xi​))​]f(xi​)=f0​(xi​)​(2.2)
从而我们可以得到梯度值g1⃗=(g1,1,...g1,N)\vec{g_1}=(g_{1,1},...g_{1,N})g1​​=(g1,1​,...g1,N​)
\quad\quad然后,根据梯度下降的思想,f0⃗\vec{f_0}f0​​需要沿着梯度负方向移动一段距离,从而完成一次对问题(2.1)的优化,我们记为:
f1⃗=f0⃗−ρg1⃗\vec{f_1}=\vec{f_0}-\rho\vec{g_1}f1​​=f0​​−ρg1​​
其中ρ\rhoρ就是学习率,用来控制学习速度。从而获取到第一步的拟合值f1⃗\vec{f_1}f1​​
\quad\quad这个过程可以继续进行下去,也就是说我们记第k步的拟合为fk⃗\vec{f_k}fk​​,然后参照(2.2)在fk⃗\vec{f_k}fk​​出求导,计算出梯度gk+1⃗\vec{g_{k+1}}gk+1​​,那么第k+1k+1k+1步的拟合值就为
fk+1⃗=fk⃗−ρgk+1⃗(2.3)\vec{f_{k+1}}=\vec{f_{k}}-\rho\vec{g_{k+1}} \tag{2.3}fk+1​​=fk​​−ρgk+1​​(2.3)
在有限步之后,就会达到Cost函数的最小值点,为了避免过度拟合训练集,显然我们需要在达到最小点之前停止拟合。到此第一个问题得到了解决。这便是GBDT中Gradient boosted的由来,从式子(2.3)可以看出boosted的痕迹,当前拟合一个较弱的模型,然后和之前的模型进行加和。


这个模型是无法对训练集外的数据进行预测

\quad\quad很显然,就算采用梯度下降的思想,把拟合过程分段进行,但是整个过程还是只针对训练集的NNN个样本,如果拿出一个训练集之外的数据,是没办法进行预测的,也就是说我们需要一个机制,计算Cost函数在训练集之外的数据处的梯度,这样就可以解决预测问题。其实这还是一个拟合问题,我们需要构建一个模型用来拟合梯度值。这里回归树便是一种很好的选择,在拟合回归树时,可以直接使用MSE作为损失函数,就可以得到第k步拟合的模型gkg_kgk​。这样,只需输入测试集的自变量值,就可以用训练好的回归树预测出其对应的梯度拟合值。这样模型就有了泛化能力。这便是GBDT中Decision Tree的由来。问题二得到解决。
\quad\quad从以上的过程我们很容易看到只需要变换损失函数,只需要损失函数存在梯度,我们就可以完成对回归问题,二分类问题,多分类问题的解决。

三. xgboost

\quad\quad我们再来看问题(1.1)中的Cost(gk)=∑i=1NL(fk−1(xi)+gk(xi),yi)Cost(g_k) =\sum_{i=1}^NL(f_{k-1}(x_i)+g_k(x_i),y_i) Cost(gk​)=i=1∑N​L(fk−1​(xi​)+gk​(xi​),yi​)
陈天奇大佬对L(.)动了手脚,在fk−1(xi)f_{k-1}(x_i)fk−1​(xi​)处进行泰勒展开,保留到二次项,那么Cost(gk)Cost(g_k)Cost(gk​)就变成:
Cost(gk)=∑i=1NL(fk−1(xi),yi)+gk(xi)∂L(xi)+12gk2(xi)∂2L(xi)Cost(g_k)=\sum_{i=1}^N L(f_{k-1}(x_i),y_i)+g_k(x_i)\partial L(x_i) +\frac{1}{2}g_k^2(x_i)\partial^2L(x_i)Cost(gk​)=i=1∑N​L(fk−1​(xi​),yi​)+gk​(xi​)∂L(xi​)+21​gk2​(xi​)∂2L(xi​)
其中
∂L(xi)=∂L(f(xi),yi)∂f(xi)∣f(xi)=fk−1(xi)\partial L(x_i) =\frac {\partial L(f(x_i),y_i)}{\partial f(x_i)}|_{f(x_i)=f_{k-1}(x_i)}∂L(xi​)=∂f(xi​)∂L(f(xi​),yi​)​∣f(xi​)=fk−1​(xi​)​
∂2L(xi)=∂2L(f(xi),yi)∂f(xi)2∣f(xi)=fk−1(xi)\partial ^2L (x_i)=\frac {\partial^2 L(f(x_i),y_i)}{\partial f(x_i)^2}|_{f(x_i)=f_{k-1}(x_i)}∂2L(xi​)=∂f(xi​)2∂2L(f(xi​),yi​)​∣f(xi​)=fk−1​(xi​)​
那么第k步的目标就变成
mingk∑i=1Ngk(xi)∂L(xi)+12gk2(xi)∂2L(xi)min_{g_k} \sum_{i=1}^N g_k(x_i)\partial L(x_i) +\frac{1}{2}g_k^2(x_i)\partial^2L(x_i)mingk​​i=1∑N​gk​(xi​)∂L(xi​)+21​gk2​(xi​)∂2L(xi​)
好吧,大佬真的厉害,就是使用这种简单的泰勒展开,硬是把L(.)L(.)L(.)干掉了,只要求L(.)L(.)L(.)二阶可导,这样就可以使用于比boosting tree更广泛的损失函数。其实到这里很容易想到这东西像极了牛顿法求最优值,而且从后续的推导也可以看出求解叶子结点的得分其实就是类似于牛顿法中的一次迭代,而再对比GBDT中在生成树过程中,是对梯度的估计,其实也就是梯度下降法中的一次迭代。故而也可以说xgboost是GBDT的一个变种。
\quad\quad大佬同时又添加了正则项,以此来控制模型,防止过度拟合。添加的正则化项有两个,一个是树的叶节点的个数,另一个是树的叶节点得分。那么第k步的目标就是:
mingk∑i=1N[gk(xi)∂L(xi)+12gk2(xi)∂2L(xi))]+αW+12β∑w=1Wγw2min_{g_k} \sum_{i=1}^N [g_k(x_i) \partial L(x_i)+\frac {1}{2} g_k^2(x_i) \partial^2L(x_i))]+ \alpha W + \frac {1}{2}\beta \sum_{w=1}^W \gamma_w^2mingk​​i=1∑N​[gk​(xi​)∂L(xi​)+21​gk2​(xi​)∂2L(xi​))]+αW+21​βw=1∑W​γw2​
其中WWW是叶节点个数,γw\gamma_wγw​表示叶节点w的得分,也就是说如果 x落入叶节点www,那么gk(x)=γwg_k(x)=\gamma_wgk​(x)=γw​,再进一步改写第k步的目标:
minγ⃗∑w=1W(∑i∈wγw∂L(xi)+α)+∑w=1W(∑i∈w12γw2∂2L(xi)+12βγw2)min_{\vec{\gamma}} \sum_{w=1}^W(\sum_{i \in w} \gamma_w \partial L(x_i)+\alpha)+\sum_{w=1}^W(\sum_{i \in w}\frac{1}{2}\gamma_w^2 \partial ^2L(x_i)+ \frac {1}{2}\beta \gamma_w^2)minγ​​w=1∑W​(i∈w∑​γw​∂L(xi​)+α)+w=1∑W​(i∈w∑​21​γw2​∂2L(xi​)+21​βγw2​)
其中γ⃗=(γ1,...,γW)\vec{\gamma}=(\gamma_1,...,\gamma_W)γ​=(γ1​,...,γW​),然后就可以按照boosting tree算法的思路,完成树的生长过程。求解叶节点的得分,
γw=−∑i∈w∂L(xi)∑i∈w∂2L(xi)+β\gamma_w = -\frac {\sum_{i \in w} \partial L(x_i)} {\sum_{i \in w}\partial^2 L(x_i)+\beta}γw​=−∑i∈w​∂2L(xi​)+β∑i∈w​∂L(xi​)​
从而得到在第k步的cost函数的最小值
Cost(gk)~=αW−12∑w=1W(∑i∈w∂L(xi))2∑i∈w∂2L(xi)+β\widetilde{Cost(g_k)}=\alpha W -\frac{1}{2} \sum_{w=1}^W \frac{(\sum_{i \in w} \partial L(x_i))^2}{\sum_{i \in w} \partial^2L(x_i)+\beta}Cost(gk​)​=αW−21​w=1∑W​∑i∈w​∂2L(xi​)+β(∑i∈w​∂L(xi​))2​
这样在进行树分裂寻找最优分割点时,信息增益计算为:
Cost(gk)~parent−Cost(gk)~left−Cost(gk)~right\widetilde{Cost(g_k)}_{parent}-\widetilde{Cost(g_k)}_{left}-\widetilde{Cost(g_k)}_{right} Cost(gk​)​parent​−Cost(gk​)​left​−Cost(gk​)​right​
=12(∑i∈left∂L(xi)∑i∈left∂2L(xi)+β+∑i∈right∂L(xi)∑i∈right∂2L(xi)+β= \frac{1}{2}(\frac {\sum_{i \in left} \partial L(x_i)}{\sum_{i \in left} \partial^2L(x_i)+\beta}+\frac {\sum_{i \in right} \partial L(x_i)}{\sum_{i \in right} \partial^2L(x_i)+\beta}=21​(∑i∈left​∂2L(xi​)+β∑i∈left​∂L(xi​)​+∑i∈right​∂2L(xi​)+β∑i∈right​∂L(xi​)​
−∑i∈parent∂L(xi)∑i∈parent∂2L(xi)+β)−α(3.1)-\frac {\sum_{i \in parent} \partial L(x_i)}{\sum_{i \in parent} \partial^2L(x_i)+\beta}) -\alpha \tag{3.1}−∑i∈parent​∂2L(xi​)+β∑i∈parent​∂L(xi​)​)−α(3.1)
其中下标parent,left,rightparent,left,rightparent,left,right分别表示父节点,左子节点,右子节点。
\quad\quad到此,其实xgboost并没有结束,接下来就要介绍xgboost的工程实现上所做的优化,其实像lightgbm,catboost都是主要对GBDT在工程上存在的问题进行了优化,所以工程上的实现才是重中之重。


 优化1 Approximate Algorithm for Split Finding

\quad\quad这里涉及的还是在树生长过程中最优分割点生成算法。首先在实现xgboost过程中,如果采用传统的最优分割点生成算法,即 Exact Greedy Algorithm for Split Finding,应该是如下过程:
\quad\quad针对特征feature1, 首先将所有样本按照feature1值从小到大进行排列,用列表存储排列之后的结果,一共存储两列数据,第一列是已经排好序的feature1的值,第二列是对应样本的编号。将列表中第一列中相邻两个元素的平均值作为分割点。我们从最小分割点开始,遍历所有的分割点,每经过一个分割点,计算一次分割增益,选取分割增益最大的作为feature1的最优分割点。其实仔细看公式(3.1),
在计算一个分割点的分割增益时,我们需要知道父节点中全部样本的一阶导的和,二阶导的和,以及两个子节点中样本的一阶导和,二阶导和,所以我们假设当前要处理的分割点为feature1_split1,父节点的一阶导和记为Gparent=∑i∈parent∂L(xi)G_{parent}=\sum_{i \in parent} \partial {L(x_i)}Gparent​=i∈parent∑​∂L(xi​),二阶导和记为Hparent=∑i∈parent∂2L(xi)H_{parent} = \sum_{i \in parent} \partial^2 {L(x_i)}Hparent​=i∈parent∑​∂2L(xi​),左子节点中样本一阶导的和为Gleft=∑i∈left∂L(xi)G_{left} = \sum_{i \in left} \partial{L(x_i)}Gleft​=i∈left∑​∂L(xi​),左子节点中样本二阶导的和为Hleft=∑i∈left∂2L(xi)H_{left} = \sum_{i \in left} \partial^2{L(x_i)}Hleft​=i∈left∑​∂2L(xi​),右子节点中样本一阶导和为Gright=∑i∈right∂L(xi)G_{right}=\sum_{i \in right} \partial{L(x_i)}Gright​=i∈right∑​∂L(xi​),二阶导和记为Hright=∑i∈right∂2L(xi)H_{right}=\sum_{i \in right} \partial^2{L(x_i)}Hright​=i∈right∑​∂2L(xi​)
通过公式(4)就可以计算出该节点的信息增益gain_split1,然后处理下一个分割点的分割增益时,就可以直接使用如下公式计算更新:
Gleft=Gleft+∂L(xnew)G_{left}=G_{left} +\partial L(x_{new})Gleft​=Gleft​+∂L(xnew​)
Hleft=Hleft+∂2L(xnew)H_{left}=H_{left}+\partial^2 L(x_{new})Hleft​=Hleft​+∂2L(xnew​)
Gright=Gright−∂L(xnew)G_{right}=G_{right} -\partial L(x_{new})Gright​=Gright​−∂L(xnew​)
Gright=Gright−∂2L(xnew)G_{right} = G_{right}-\partial^2 L(x_{new})Gright​=Gright​−∂2L(xnew​)
原因就在于处理下一个分割点的本质就是将一个样本从右子节点移动到左子节点,Gparent,HparentG_{parent},H_{parent}Gparent​,Hparent​不做处理。
很显然传统的最优子节点算法需要遍历的分割点非常多,特别是针对数值型变量,如果样本量特别大,光是寻找最优分割点就需要耗费很多时间,陈天奇就首先对这个问题进行了优化,提出了 Approximate Algorithm for Split Finding算法,
算法的思想是比较简单的,既然传统的最优化子节点算法需要遍历全部可能的分割点,那能否减少遍历的分割点呢?陈天奇采用分位数的策略,首先仍然将待处理的特征按照样本大小的顺序排列,然后分割点的选取是取lll个分位数,比如可以选择0%,10%,20%,…90%,100%分位数,然后计算每两个分位数之间的样本的一阶导的和,二阶导的和,然后再遍历所有的分割点,这样显然是要是传统的方法好很多,但是同样会存在一个问题,应该如何选择分位数,才能够保证分割效果尽可能接近Exact Greedy Algorithm的效果呢?陈天奇提出了weight Quantile sketch算法。Exact Greedy Algorithm算法每次计算一个新的分割点的分割增益时,是把右子树的一个样本移动到左子树,然后再计算分割的分割增益。其实如果说样本不重要的话,其实是没有必要一次移动一个样本的,可以一次移动多个样本点。那么在这里度量一个样本重要性的指标就是∂2L(xi)\partial ^2L(x_i)∂2L(xi​)。Weight Quantile Sketch算法过程如下: 假设当前的分割点候选集为x1,....,xl{x_1,....,x_l}x1​,....,xl​,我们需要保证
Percent(xi−1<x<xi)<ϵPercent(x_{i-1}<x<x_i) < \epsilonPercent(xi−1​<x<xi​)<ϵ
其中Percent(xi−1<x<xi)Percent(x_{i-1}<x<x_i)Percent(xi−1​<x<xi​)就表示落入xi−1x_{i-1}xi−1​和xix_{i}xi​之间的样本的权重和,ϵ\epsilonϵ越小,效果越接近Exact Greedy算法。


 优化2 Sparsity-aware Split Finding

\quad\quad如果一个特征下的样本有许多的缺失值,比如在对分类型特征进行one-hot编码之后,就会导致许多的缺失值,传统的决策树处理缺失值是把缺失值对应的样本划分到左子节点,或者右子节点,这里并没有进一步说明到底是分到左子节点好,还是分到右子节点好,陈天奇就给出了Sparsity-aware Split Finding 算法,大概思路就是将缺失值放在左子节点,计算出最优的分割增益,然后再将缺失值放在右子节点中,计算最优分割增益,然后取两次最大的分割增益,这样就可以判断出缺失值放在哪个子节点为最优。


 优化3 Parallel Learning

\quad\quadxgboost对boosting算法的一个重要改进就是这个并行化学习。其实在确定最优分割点的时候,是完全可以进行并行化处理的,首先对所有的特征进行预排序,然后存储起来。不同的线程来完成不同特征的最优分割点的确定,然后再把结果汇总,从而确定全局最优分割点。

\quad\quad除了以上所说的优化之外,其实还有Cache-aware Access,Blocks for Out-of-core Computation等等,不再详细说明。有兴趣的读者可以直接看原文。

三.catboost

\quad\quad本部分介绍的catboost和第四部分介绍的lightgbm,都是在GBDT的基础上进行的工程实现上的优化。因此详细的算法过程将不做介绍,参考GBDT即可。下面将详细介绍catboost区别于其他boosting的算法的究竟提现在哪些方面。


 优化1  symmetric or oblivious trees

\quad\quad这个优化可以快速对测试集数据进行预测,lightgbm,xgboost在对一个新数据点进行预测的时候,需要从一棵树的根节点开始,串行走到叶节点,但是catboost却可以实现从根节点并行走到叶节点,树的每一层都可以独立判断,然后把每一层的判断结果整合起来,确定一个index,使用index来找到新节点的预测值。举个简单的例子。树的根节点是学生还是在工作,两个子节点都是关注机器会学习没有?树的结构如下图所示(图片借用了知乎用户李大猫的图,侵删):

在给定一个新样本时,我们可以分别根据树的每一层来做出判断,例如新数据是学生,那么就标为0,是已工作的,就标为1,新数据关注机器会学习,就标为0,没有关注,就标为1,然后综合每一层的判断结果,有四种可能性,分别为 00,01,10,11,可以把他们看成二进制数字,然后转换成十进制数字就是0,1,2,3,作为树的叶节点的index,分别对应于一个预测值,比如,新数据是学生,没有关注机器会学习,那么就是01,预测值就是2.4。


优化二: ordered boosting

\quad\quadordered boosting其目的是为了解决GBDT算法中存在的梯度估计有偏性问题。我们知道GBDT中每一棵树的构建过程,都是对当前的样本的梯度进行拟合,重新看式(2.2),在第一次样本的梯度为g1⃗\vec{g_1}g1​​,然后需要构建一棵树对g1⃗\vec{g_1}g1​​进行拟合,假设拟合的梯度为g1⃗^\hat{\vec{g_1}}g1​​^​,第二次计算梯度就应该在Y−g1⃗^Y-\hat{\vec{g_1}}Y−g1​​^​处求梯度g2⃗\vec{g_2}g2​​,然后再对g2⃗\vec{g_2}g2​​构建一棵树进行拟合,依次类推。每一个训练集样本点即需要参与到树的构建过程,又需要使用这些树来对自身的梯度进行拟合,以便参与到下一棵树的构建,而对测试集来说,测试集只是使用了这些树对自身梯度进行拟合,这样就会导致模型是有偏的。catboost就是希望训练集样本和测试集样本一样,都只是使用这些树对自身梯度进行拟合,以此来减少模型偏差。catboost于是就使用了ordered boosting,具体步骤如下:
\quad\quad首先对训练集进行一次重排列,在对第KKK个样本进行梯度估计的时候使用前K−1K-1K−1个样本拟合一棵树TKT_KTK​,然后使用TKT_KTK​对第KKK棵树进行梯度估计,如果是测试集的样本,那么就用全部的训练集样本拟合一棵树,然后对测试集的样本进行梯度估计。这样就解决了样本即参与到树的生成过程,又需要使用这棵树来预测自身梯度的问题。Catboost为了解决树构造过多的问题,就使用前K-1个样本拟合一棵树,然后使用这棵树来预测第K到第2K-1个样本的梯度估计,这样大大减少了树的数量。


优化三: handling categorical features

\quad\quad对分类变量的处理涉及两个方面,一个无target leakage的target statistics,即ordered target statistics。另一个就是分类变量的自动组合。
ordered target statistics. catboost的一个重要优化就是对分类变量的优化。catboost本质是还是对分类变量进行了编码,只不过是带有了因变量的信息。具体编码方式如下:
\quad\quad首先将所有的样本重新排序,假设当前要处理的分类变量为变量A,有三种值,分别为a,b,c。假设排完序的样本如下所示:

样本编码 特征A Y
1 a 1.2
2 a 1.5
3 b 2
4 b 3.4
5 b 4
6 c 5

那么针对第1个样本,特征A的编码就为
1.2+1.5+2+3.4+4+56\frac {1.2+1.5+2+3.4+4+5}{6}61.2+1.5+2+3.4+4+5​
针对第2个样本,特征A的编码为
1.21.21.2,
第三个样本编码为
1.2+1.5+2+3.4+4+56\frac {1.2+1.5+2+3.4+4+5}{6}61.2+1.5+2+3.4+4+5​
第四个样本编码为
222
第五个样本编码为
2+3.42\frac {2+3.4} {2}22+3.4​
第六个样本编码为
1.2+1.5+2+3.4+4+56\frac {1.2+1.5+2+3.4+4+5}{6}61.2+1.5+2+3.4+4+5​
归总起来就是说,第i个样本的特征A为值q,那么其编码就是在这个样本之前的特征A也为q的样本的Y的期望,用公式表示就为:
∑j<i1xA,i=q∗yi+dp∑j<i1xA,i=q+d\frac {\sum_{j<i}1_{x_{A,i}=q}*y_i+dp}{\sum_{j<i}1_{x_{A,i}=q}+d}∑j<i​1xA,i​=q​+d∑j<i​1xA,i​=q​∗yi​+dp​
其中q是先验信息,一般取全部样本的Y的平均即可。
分类变量的自动组合.catboost在当前树生成过程中,会将已经参与树分叉的分类变量 和未参与树分叉的分类变量进行组合,形成新的分类特征。比如当前已经参与树分叉的两个分类特征feature1,feature2,未参与树分叉的分类变量有feature3,feature4 ,那么组合形成两个新的分类变量feature1+feature2+feature3 ,feature1+feature2+feature4,这两个特征也加入到最优分割变量,最优分割点的计算中。所以可以看出catboost对于处理特征多是分类变量的数据是极具优势的,有效的减少了人工组合特征所耗费的时间。

四.lightgbm

lightgbm对GBDT的优化更多体现在对其工程实现上的优化,使得GBDT算法更加使用于海量数据。主要的优化点有如下三条:

  • Gradient-based One-Side Sampling(GOSS)
  • Exclusive Feature Bundling(EFB)
  • 对类别特征的直接支持
    当然,本部分也会介绍用于lightgbm的其他通用的优化策略,比如:
  • 基于Histogram的决策树算法
  • leaf-wise决策树生长策略

优化一: 基于Histogram的决策树分裂点算法

在前面第一部分boosting tree ,第三部分xgboost部分都提到了常用的决策树最优分裂点的生成算法,pre-sorted算法,针对每个特征都需要变量一遍训练集才能找到最优分割点,虽然xgboost提出了优化策略,但是时间复杂度和空间复杂度还是比较大。一种通用的最优分裂点生成算法称为基于histogram的分裂点寻找算法。
其本质就是将连续变量离散化,首先确定需要多少个bin,然后将特征放入到对应的bin中,其值也转化成对应bin的值。举个例子,针对特征A,样本的取值为[1.1,1.5,2.4,2.6,3.7,3.9],其最大值为3.9,最小值为1.1,如果希望的bin数为3,那么step就是(3.9-1.1)/3=0.93,可以将step取为1,然后分割点就是1.1,2.1,3.1,4.1,为了节省存储空间,分割点会进一步取为整数,最终的分割点就是1,2,3,4,然后将[1.1,2.1] 映射为[1,1] ,将 [2.4,2.6] 映射为[2,2],将[3.7,3.9]映射为[3,3]。那么候选分裂点就是2,3,然后计算每个分裂点的信息信息增益,选出增益最大的作为该特征的最优分裂点。这样分裂点个数相比于pre-sorted大大减少,同时采用int8类型存储离散化之后的值,相比最初的采用float-32 ,float-64更加的节省空间。在实际过程中可以自由设定bin数。


优化二: leaf-wise决策树生长策略

决策树的生长过程存在一个是否要对当前的叶子结点进行再分裂的问题,通常情况下会有限定一个叶子结点中的样本量分裂所需要样本的最小值,或者是树的深度,或者是信息增益量的大小,从而保证一些不必要的分裂。不过这样做仍然会存在一些不必要的分裂。leaf-wise的做法是在同一层的叶节点中选择一个信息增益最大的节点,然后只对这个节点进行分裂,通过限定树的深度来避免过拟合。这样会减少更多不必要的节点分裂,从而加快计算速度。


优化三: Gradient-based One-Side Sampling(GOSS)

想要减少模型的计算量,首先一个策略就是能否减少样本量,当然还必须是在保证模型的准确性的前提下进行。lightgbm的思路就是利用了梯度较小的样本对于信息增益影响比较小,可以适当的剔除这样本,以此来减少模型的计算量。同时保留梯度较大的样本,这就是所谓的One-Side sampling 。过程如下: 首先是对所有样本的梯度绝对值按照从大到小的顺序排列,然后保留前a%的样本,在剩下的样本中随机筛选b%的样本,然后对于那b%得样本,其梯度乘以一个系数(1-a/100)/(b/100),乘以这个系数直白一点的解释是样本虽然少了,但是让样本的梯度适当增大一点值,从而能够代表全部的样本,lightgbm的作者也给出了这样做不会减少模型准确度的理论依据,这里就不再详细说明,详细证明请参考原论文。


优化四: Exclusive Feature Building (EFB)

\quad\quad既然可以通过GOSS减少样本量来减少计算量,同样通过减少特征数来减少计算量,lightgbm提出的EFB便是为了解决这个问题。但是EFB主要还是用来解决有大量稀疏特征的数据。
\quad\quadEFB其实是把部分特征绑定在一起从而减少特征数量的,那么这就涉及到两个问题,如何将特征绑定在一起,其依据是什么?绑定在一起之后,新特征的值又该如何确定?
绑定策略.对于两个稀疏特征来说,如果一个特征取非零值,另一个特征也取非零值,这样将两个特征绑定在一起,是要损失信息的,如果一个特征取值为0,另一个特征取值为非零,那么两个特征绑定在一起是没有信息损失的(这里的0表示缺失值)。看来第一步需要确定一下两个特征的冲突系数,也就是两个特征同时取非零值的样本数,我们把冲突系数较小的特征绑定在一起,就可以尽可能减少因为绑定特征而造成的信息损失。lightgbm的绑定策略就是遍历全部的特征,首先令bundles={},遍历全部的特征,将特征和所有bundle的特征进行冲突系数计算,如果冲突系数小于a,那么就进入该bundle中,如果冲突系数大于a,就新生成一个bundle,然后将这个bundle追加到bundles中。
绑定特征值计算.如何将一个bundle中的特征合并为一个新特征呢?因为lightgbm使用的是Histogram算法来寻找最优分割点,因此这个特征合并也是和Histogram密切相关的,假设一个bundle中有三个特征A,B,C,都已经通过Histogram的方式离散化,假设bins数是NAN_ANA​,B的bins数是NBN_BNB​,C的bins数是NCN_CNC​,那么遍历所有的样本,对于当前的样本i,设定这个样本的新编码为Newi=0New_i=0Newi​=0,遍历该bundle中的所有特征,在这里也就是特征A,B,C
Newi=Newi+1valueA,i≠0∗valueA,iNew_i =New_i + 1_{value_{A,i} \neq 0}*value_{A,i}Newi​=Newi​+1valueA,i​​=0​∗valueA,i​
Newi=Newi+1valueB,i≠0∗(valueB,i+NA)New_i =New_i + 1_{value_{B,i} \neq 0}*(value_{B,i}+N_A)Newi​=Newi​+1valueB,i​​=0​∗(valueB,i​+NA​)
Newi=Newi+1valueC,i≠0∗(valueC,i+NB)New_i =New_i + 1_{value_{C,i} \neq 0}*(value_{C,i}+N_B)Newi​=Newi​+1valueC,i​​=0​∗(valueC,i​+NB​)
其实新编码就是把特征原有的bins并起来,做偏置,得到新特征。其中ValueA,iValue_{A,i}ValueA,i​就表示样本i的特征A的取值,其他类似。


优化五: 对类别特征的直接支持

\quad\quad在catboost中提到了其对类别特征的支持,同样在lightgbm中,其也是直接对类别特征直接支持的。

Boosting算法相关推荐

  1. xgboost 正则项_深入理解Boosting算法(4)-XGBoost

    导语:记得第一次使用XGBoost的时候还是2016年,那会的XGBoost工具还不完善,深度模型还没有怎么样火起来,大家用的最多的还是Sklearn里面的GBDT,或者R语言的GBM.后来在工作中也 ...

  2. [机器学习] Boosting算法4 --- LightGBM介绍与分布式

    [机器学习] Boosting算法1 --- AdaBoost [机器学习] Boosting算法2 --- GBDT [机器学习] Boosting算法3 --- XGBoost [机器学习] Bo ...

  3. [机器学习] Boosting算法2 --- GBDT

    [机器学习] Boosting算法1 --- AdaBoost [机器学习] Boosting算法2 --- GBDT [机器学习] Boosting算法3 --- XGBoost [机器学习] Bo ...

  4. [机器学习] Boosting算法1 --- AdaBoost

    [机器学习] Boosting算法1 --- AdaBoost [机器学习] Boosting算法2 --- GBDT [机器学习] Boosting算法3 --- XGBoost [机器学习] Bo ...

  5. boosting算法_集成学习:boosting、BDT、GBDT的概括理解

    boosting是一种集成学习的方法,与bagging并列形成俩中不同的集成学习算法,本文主要概括boosting方法. boosting在训练过程中,它通过改变训练样本的权重,学习多个学习器,然后将 ...

  6. Boosting算法与假设间隔

    Boosting算法与假设间隔 间隔概念 AdaBoost算法 AdaBoost 平均间隔 参考资料 间隔概念 间隔是一种几何度量,能够用于度量分类器预测的可信程度.间隔的两种定义:①样本间隔: 被预 ...

  7. 机器学习 —— Boosting算法

    Boosting算法(提升法) 算法的三个要素 (1)函数模型:Boosting的函数模型是叠加型的,即 F(x)=∑i=1kfi(x;θi)F(x)=∑i=1kfi(x;θi) F(x)=\sum_ ...

  8. boosting算法的分类

    https://www.cnblogs.com/liuq/p/9947764.html 另外高维回归,L2boosting的结果和lasso相近,可能说明L2boosting可以解释为某种L1正则化的 ...

  9. python 梯度提升树_梯度提升方法(Gradient Boosting)算法案例

    GradientBoost算法 python实现,该系列文章主要是对<统计学习方法>的实现. 完整的笔记和代码以上传到Github,地址为(觉得有用的话,欢迎Fork,请给作者个Star) ...

最新文章

  1. .NET Core的日志[2]:将日志输出到控制台
  2. MDX Step by Step 读书笔记(五) - Working with Expressions (MDX 表达式)
  3. TypeError: ‘method‘ object is not subscriptable
  4. android外置传感器,Android中外接键盘的检测的实现
  5. ST_Curve --- 一个专业的曲线绘制控件
  6. 深度学习与自然语言处理之五:从RNN到LSTM
  7. 检测和语义分割_分割和对象检测-第1部分
  8. Android之基于BaseAdapter和SimpleAdapter的GridView
  9. 高德面试官问我:JVM内存溢出后服务还能运行吗,我一顿操作行云流水
  10. 面向视频的全新AI架构 —— 阿里云智能视觉技术全解
  11. php面试宝典1000题,【PHP面试宝典1000题】HTTP中的请求头(深圳小美网络科技)
  12. 工具推荐--正则表达式
  13. 基于DotNet构件技术的企业级敏捷软件开发平台 - AgileEAS.NET - 权限管理
  14. 计算机研究生可以参加哪些比赛?
  15. ViewPager异常,对ViewPager源码分析
  16. lamp php的ssl,ssh支持
  17. Android开发之来电电话挂断实现
  18. 第5-5课:最大流问题(图文篇)
  19. flexPaper制作在线文库阅读器思路
  20. 安川工业机器人实训心得_安川MOTOMAN工业机器人编程与操作(3)

热门文章

  1. 在Ubuntu上如何卸载nginx
  2. Android蓝牙开发【六】hfp连接
  3. CES展观察:海尔智能家居全场景生态,苹果谷歌迎来劲敌
  4. java 日志开发规范
  5. oracle 取分组第一行,oracle分组后取每组第一条数据
  6. RocketMQ异常:sendDefaultImpl call timeout
  7. TFN FT-7光纤熔接机 高性价比的干线机器 性能不输进口机器!
  8. RabbitMQ学习笔记:Monitoring(监控)
  9. 项目经理提升领导力的6个重点
  10. oracle return rowid,Oracle rowid 详解