从ID3到LGB

梳理一下树模型算法,从三种最基础的tree到lgb的全过程笔记

基于信息增益(Information Gain)的ID3算法

ID3算法的核心是在数据集上应用信息增益准则来进行特征选择,以此递归的构建决策树,以信息熵和信息增益为衡量标准,从而实现对数据的归纳分类。
ID3算法需要解决的问题是如何选择特征作为划分数据集的标准。在ID3算法中,选择信息增益最大的属性作为当前的特征对数据集分类

信息增益

信息增益需要涉及到熵,条件熵这2个概念,先通俗的理解一下:

  • 熵:表示随机变量的不确定性。
  • 条件熵:在一个条件下,随机变量的不确定性。
  • 信息增益:熵 - 条件熵。表示在一个条件下,信息不确定性减少的程度。

比如:太阳明天从东方升起 ,这句话的信息熵等于0,因为这是确定的事件,信息无价值

对于信息增益,举个例子,通俗地讲,假设\(X\)(明天下雨)是一个随机变量,\(X\)的熵假设等于2, \(Y\)(明天阴天)也是随机变量,在阴天情况下下雨的信息熵我们如果也知道的话(此处需要知道其联合概率分布或是通过数据估计)即是条件熵。\(X\)的熵减去\(Y\)条件下\(X\)的熵,就是信息增益。
具体解释:原本明天下雨的信息熵是2,条件熵是0.01(因为如果知道明天是阴天,那么下雨的概率很大,信息量少),这样相减后为1.99就是信息增益。其含义就是在获得阴天这个信息后,下雨信息不确定性减少了1.99,不确定减少了很多,所以信息增益大。也就是说,阴天这个信息对明天下午这一推断来说非常重要。所以在特征选择的时候常常用信息增益,如果IG(信息增益大)的话那么这个特征对于分类来说很关键,决策树就是这样来找特征的。具体到数据集上,信息增益需要结合特征和对应的label来计算。

信息增益与熵(entropy)有关,在概率论中,熵是随机变量不确定性的度量,熵越大,随机变量的不确定性就越大;假设\(X\)是取有限个值的离散随机变量,其概率分布为:

\[P(X=x_i)=p_i,i=1,2,3,...,n \]

则,熵的定义为:

\[H(X)=-\sum_{i=1}^{n}p_i*\log{p_i} \]

一般取自然对数\(e\)为底数,值得注意的是,熵实际上是随机变量\(X\)的分布的泛函数,它并不依赖\(X\)的实际取值,而仅仅依赖\(X\)的概率分布,所以它又可以被记作:

\[H(p)=-\sum_{i=1}^{n}p_i*\log{p_i} \]

其中, \(n\)表示\(X\)\(n\)种不同的取值, 这个值一般是离散的. \(p_i\)表示为\(X\)取到值为\(i\)的概率.\(log\)一般是自然底数
例子:

条件熵

多个变量的熵叫联合熵, 比如两个变量\(X,Y\)的联合熵就表示为:

\[H(X,Y)=-\sum_{i=1}^{n}p_{(x_i,y_i)}\log p_{(x_i,y_i)} \]

类似于条件概率,熵同样也存在着条件熵, 条件熵描述了知道某个变量以后, 剩下的变量的不确定性, 其表达式如下:

\[H(X|Y)=-\sum_{i=1}^{n}p_{(x_i,y_i)}\log p(x_i|y_i) \]

信息增益

\(H(X)\)度量了\(X\)的不确定性, \(H(X|Y)\)度量了知道\(Y\)后,\(X\)的不确定性, 那么\(H(X)-H(X|Y)\)度量的可以理解为:知道\(Y\)的基础上, \(X\)不确定性减少的程度,我们记为\(I(X,Y)\),如图:

更多理解

假定当前样本集合\(D\)中,第\(k\)类样本所占比例为\(p_k(k=1,2,3...,|y|)\), 则\(D\)的信息熵定义为:

\[Ent(D)=-\sum_{k=1}^{|y|}p_k \log p_k \]

假定离散属性\(a\)\(V\)个可能的取值\({a^1,a^2...a^v}\), 若使用\(a\)来对样本集进行划分,则会产生\(V\)个分支结点, 也就是说, ID3构建的决策树, 是多叉树, 那么它的信息增益就是

\[Gain(D,a) = Ent(D)-\sum_{v=1}^{V} \frac{|D^v|}{|D|}Ent(D^v) \]

比如: 一个二分类数据集, 包含17个样本, 其中正例为8,反例为9,那么, 数据集\(D\)的信息熵为:

\[Ent(D)=-(\frac{8}{17}\log \frac{8}{17}+\frac{9}{17}\log \frac {9}{17}) \]

对于变量\(a\),他有三个取值, 那么它可以将数据集划分为三个子集: \(D^1,D^2,D^3\), 其样本里分别为6,6,5, 这三个自数据集中, 正负样本分别为(3,3) (4,2),(1,4), 这三个分支结点的信息熵为为:

\[Ent(D^1)=-(\frac{3}{6} \log \frac{3}{6}+\frac{3}{6} \log \frac{3}{6}) \]

\[Ent(D^2)=-(\frac{4}{6} \log \frac{4}{6}+\frac{2}{6} \log \frac{2}{6}) \]

\[Ent(D^3)=-(\frac{1}{5} \log \frac{1}{5}+\frac{4}{5} \log \frac{4}{5}) \]

那么变量\(a\)的信息增益为:

\[Gain(D,a)=Ent(D)-\sum_{v=1}^{3}\frac{|D^v|}{|D|}Ent(D^v) \]

ID3 步骤

ID3使用信息增益来决策当前树结点该使用那个变量来构建决策树, 显然,信息增益越大的, 就越能更有效的区分特征(变量)与预测标签之间的关系.
输入\(m\)个样本,每个样本有\(n\)个离散的特征,令特征集合为\(A\),输出决策树\(T\)

  • 判断样本是否为同一类别, 如果是, 则返回树T
  • 判断特征是否为空, 是, 则返回树T
  • 计算A中, 各个特征的信息增益,选择最大的信息增益特征,记为\(i\)
  • 按特征\(i\)的不同取值, 将对应的样本分成不同类别,每个类别产生一个子结点,对应的特征值为\(i_j\)
  • 重复上述步骤直到结束

显然,ID3是一个多叉树,且其只能解决分类问题

ID3算法的缺点

  1. 无法处理连续的特征,遇到连续的特征的话,就得做连续数据离散化了,可以考虑分桶等策略
  2. 采用信息增益更大的特征优先建立决策树, 但相同的数据集下, 取值较多的特征值比取值较少的特征值信息增益更大,即信息增益偏向取值较多的特征。
  3. 没有考虑缺失值,当然大部分算法都不支持含有missing value的数据集,尽管理论上算法可以支持,比如gbdt,但大部分gbdt的实现都不支持missing value,目前常用的算法,只有xgb,lgb支持
  4. 过拟合问题,id3没有考虑过拟合的对抗策略,相当于是在

ID3算法的优点

  1. 可解释性较强

C4.5: ID3的改进版本

首先,C4.5和ID3一样都是多叉树,重点是连续特征处理+特征选择的方式不同。
ID3算法存在的不足, 在C4.5里面有了改进

有那些改进

  • 不能处理连续特征的问题

C4.5的思路是将连续特征离散化. 比如m个样本, 特征A有m个连续的值, 取两个样本值的中位数,这样一共可以得到m-1个划分点, 对每个划分点计算信息增益.

  • 信息增益偏向于值较多的特征的问题

因为信息增益准测对取值较多的特征有偏好, 所以这个问题的解决方案是信息增益比, 它是信息增益和特征熵的比值,记为\(IR(X,Y)\)

\[IR(D,a)=\frac{I(D,a)}{IV(A)} \]

其中,\(IV(A)=\sum_{v=1}^{V}\frac{|D^v|}{|D|} \log \frac{|D^v|}{|D|}\), 其称为属性A的"固有值", 属性取值的个数越多, IV就越大

  • 不能处理缺失值的问题

对于某一个有缺失特征值的特征A。C4.5的思路是将数据分成两部分,对每个样本设置一个权重(初始可以都为1),然后划分数据,一部分是有特征值A的数据D1,另一部分是没有特征A的数据D2.
然后对于没有缺失特征A的数据集D1来和对应的A特征的各个特征值一起计算加权重后的信息增益比,最后乘上一个系数,这个系数是无特征A缺失的样本加权后所占加权总样本的比例。对于第二个子问题,可以将缺失特征的样本同时划分入所有的子节点,不过将该样本的权重按各个子节点样本的数量比例来分配。比如缺失特征A的样本a之前权重为1,特征A有3个特征值A1,A2,A3。 3个特征值对应的无缺失A特征的样本个数为2,3,4.则a同时划分入A1,A2,A3。

C4.5的缺点

  • 显然, 当遇到连续值时, 计算量会增大, 特别是涉及到很多的对数操作
  • 树模型亦然存在容易过拟合的现象

分类回归树CART

CART树是后面所有模型的基础,也是核心树
在ID3算法中我们使用了信息增益来选择特征,信息增益大的优先选择。在C4.5算法中,采用了信息增益比来选择特征,以减少信息增益容易选择特征值多的特征的问题。但是无论是ID3还是C4.5,都是基于信息论的熵模型的,这里面会涉及大量的对数运算。为了简化模型同时也不至于完全丢失熵模型, CART分类树算法使用基尼系数来代替信息增益比,基尼系数代表了模型的不纯度,基尼系数越小,则不纯度越低,特征越好。这和信息增益(比)是相反的。
CART既可以适应分类任务, 又可以适应回归任务, 不同的任务, 特征的选择方式不一样

分类任务

假设有\(K\)个类,第\(k\)个类的概率为\(p_k\), 则基尼系数的表达式为:

\[Gini(p)=\sum Kp_k(1-p_k)=1-\sum_{k=1}Kp_k^2 \]

对于二分类问题, 则公式可以简化为: \(Gnini(p)=2p(1-p)\), p代表属于第一类样本的概率
对于给定的样本集合\(D\), \(K\)个类, 第\(k\)个类别的数量为\(C_k\), 则样本\(D\)的基尼系数为:

\[Gini(D)=1-\sum_{k=1}^{k}K(\frac{|C_k|}{|D|})^2 \]

显然, 对于集合\(D\),假设属性\(A\)的某个值\(a\)将数据集D切分为\(D_1,D_2\),则在特征A的条件下, D的基尼系数表达式为:

\[Gini(D,A)=\frac{|D_1|}{|D|}Gini(D_1)+\frac{|D_2|}{|D|}Gini(D_2) \]

相比于复杂的对数运算, 基尼系数的运算简单很多, 对于连续值得处理, CART和C4.5是相同的:连续的二分离散特征

回归任务

在CART分类树中, 其与ID3,C4.5并没有太大的差别, 而回归则不一样:

  • 预测的方式不同
  • 连续值得处理方式不同

回归树模型采用均方差度量: 对于任意划分的特征A, 和一个任意划分的点s(该点s其实是特征A里面的某个值), 将数据集D划分为\(D_1,D_2\), 这个点s要使\(D_1,D_2\)各自集合的均方差的最小,公式为:

\[min [min \sum_{x_i \in D_1(A,s)}(y_i-c_1)^2 + min \sum_{x_i \in D_2(A,s)}(y_i-c_2)^2 ] \]

其中, \(c\)为样本输出均值, 其实就是对应数据集的label的均值
那么最终这棵树的方程为:

\[f(x)=\sum_{m=1}^{M} c_m I (x \in R_m) \]

其中,\(c_m\)为对应区域的均值, 类似于这样
图片来源于CSDN

CART树的主要开销就在为每个特征寻找最优切分点\(s\)

GBDT

GBDT是boosting系列算法的代表之一,其核心是 梯度+提升+决策树。

GBDT回归问题

通俗的理解:
先来个通俗理解:假如有个人30岁,我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。最后将每次拟合的岁数加起来便是模型输出的结果。

决策树用的是cart树,准确来说是用的cart树中的回归树。之所以要用回归树,是因为gbdt其实去拟合的不仅仅是原数据的标签,还有每一个树经过学习之后留下的残差:所以gbdt不管是分类还是回归,都是拟合的残差值。GBDT的过程用文字描述是这样的:

假设我拿到一批数据,标签是连续值,我拿来做回归任务。
那么拿到数据之后,gbdt首先开始初始化它的第0颗树,一般而言吗,第0颗树不做什么特殊的操作,输出值就是标签的均值。
初始化好了之后,我们就可以计算每一个样本的第一批残差,严格来说这个值是损失函数对第0颗树求偏导的结果,我们用平方损失作为loss的话就是刚刚训练的那颗树减去标签的值,拿到残差后,,残差作为本次训练样本的新标签,开始建立第一课CART树:

  1. 遍历每个特征下所有可能的切分点,寻找最优的切分点之后将样本一分为2,大于这个切分点的样本丢到左子树,小于等于的放右子树,当然也可以反着来,这个看具体的工程实现。
  2. 这个时候,我们左右节点都有了样本,那么此刻左右节点还可以接着再分,接着开始按上面的套路接着在所有的特征上再找一遍最优切分点,又把数据一分为2。这个过程直到满足样本不可再分的情况下为止,具体来说,我们可以定义树的深度,每个叶子节点含有的最少样本数等等来限制树的生长过程,不限制的话,基本是就是一直分到只剩下一个样本落到叶子节点上为止。
  3. 现在我们已经到了叶子节点了,那么这个叶子节点的值就是其所包含的样本的标签的均值,或者是是餐叉的均值
  4. 现在第一颗树已经ok了,那么这颗树的输出值就是: 前面所有树的输出+本次树的输出*学习率,得到最终的输出值
  5. 好了之后,我们开始计划训练下一颗树,步骤就是通过再一次计算梯度得到残差,更新这批训练样本的标签,以同样的套路再训练一遍,又得到一颗树。
  6. 一直训练,假设我们需要100颗树,那就这样训练100次
  7. 最终,预测的时候,我们丢一条样本,进去,每颗树都能得到一个输出值,我们把这些输出值加起来作为最终的预测结果

GBDT的前夜--提升树

提升树算法也是用的残差去拟合下一颗树,它和gbdt的不同是:他真的是残差,而gbdt则是近似残差。那么它的算了步骤是这样的

而且,与gbdt相比,其第0课树一般输出是0

GBDT残差近似

其实就是提升树的部分,把残差改成了损失函数的梯度,用来近似提升树的残差。所以,当使用平方损失的时候,这个梯度就是真正的残差,如果boosting算法每一步的弱分类器生成都是依据损失函数的梯度方向,则称之为梯度提升(Gradient boosting)

GBDT 分类问题

这里我们再看看GBDT分类算法,GBDT的分类算法从思想上和GBDT的回归算法没有区别,核心还是用残差去拟合树。但是由于样本输出不是连续的值,而是离散的类别,导致我们无法直接从输出类别去拟合类别输出的误差。
为了解决这个问题,主要有两个方法,一个是用指数损失函数,此时GBDT退化为Adaboost算法。另一种方法是用类似于逻辑回归的对数似然损失函数的方法。也就是说,我们用的是类别的预测概率值和真实概率值的差来拟合损失。这里讨论用对数似然损失函数的GBDT分类。而对于对数似然损失函数,我们又有二元分类和多元分类的区别。

二分类

回归问题上,我们用的损失可能是平方损失,那么在二分类问题上,我们用的损失就是非常经典的逻辑回归的损失,也就是而分类交叉熵损失(BCE loss),也叫对数似然损失。在预测类别的时候,每棵树我们都能拿到一个叶子节点,叶子节点上存着的和gbdt回归一样,是属于该叶子节点的样本的残差的均值,也就是label的均值,为什么分类也是label的均值?因为分类也是用的回归树,回归树划分叶子节点的损失是平方损失。
同样的,这些树在做预测的时候,也是加法模型,但是最终的预测结果,需要做一件事,激活:显然,我们可以用lr的激活方式也就是sigmoid做激活,拿来搞定二分类问题

损失函数

损失函数拿来做梯度计算,拿到下一批样本的残差即是label,二分类损失函数为:

\[L(Y,f(x))=-\frac{1}{n}\sum [y\ln f(x)+(1-y)\ln (1-f(x))] \]

激活用sigmoid函数
整个过程描述为:

多分类

与二分类的套路一样,不过需要注意的是,多分类时候,有多少个类,一般一轮都会建多少颗树。
损失函数的话,多分类最长使用的损失函数就是交叉熵了:

\[L(Y,f(x))=-\frac{1}{n}\sum y_i ln f(x) \]

激活函数用的softmax

那么整个过程有:

GBDT总结

GBDT主要的优点有

  • 可以灵活处理各种类型的数据,包括连续值和离散值。
  • 在相对少的调参时间情况下,预测的准备率也可以比较高。这个是相对SVM来说的。
  • 使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。

GBDT的主要缺点有

  • 由于弱学习器之间存在依赖关系,难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行。

参考链接

gbdt是如何进行多分类的
gbdt多分类
gbdt二分类
gbdt step by step

GB的高效实现--XGBoost

xgb paper
XGBoost的全称是eXtreme Gradient Boosting,是GBDT的更高阶的版本实现,因为他或多或少还是存在一些gbdt的影子,其建树的过程的cart树是相似的,gbdt的话用的也是cart树,确切的说是cart回归树,其次拟合每一颗树的时候还是根据残差去做拟合,只是各自内部的方法不一样。那么其实只要去了解xgb和gbdt这些不同的地方,就能知道xgb是怎么一回事了。

xgboost本身还是属于boosting家族,不过跟adaboost还是不太一样的,adaboost的话是通过更新样本权重这样的一种boosting,而像xgb和gbdt的话,是通过残差这种形式。
xgb在目标函数中使用了对损失函数进行二阶泰勒展开来最小化损失,加入了正则来最小化树的结构风险,展开后可以得到在上次训练的那颗树下,每一个样本的一阶导数和二阶导数,这些导数后面会用来计算分裂节点的收益。

那最关心的还是怎么去生成这个树,总体上来说,他生成树的过程是这样的:
结构上其实和cart差不多

  • 寻找最佳分裂点的时候,使用了预排序算法, 对所有特征都按照特征的数值进行预排序, 然后遍历所有特征上的所有分裂点位,计算按照这些候选分裂点位分裂后的全部样本的目标函数增益,找到最大的那个增益对应的特征和候选分裂点位,从而进行分裂。这个增益的话,他不像gbdt那也计算平方误差,而是和损失函数挂钩的,确切的说就是计算按这个特征值分裂后,计算分裂前和分裂后的增益。他推导出的一个和2个导数值相关公式可以直接计算这个增益。
  • 叶子节点的值,或者说权重其实也是通过这两个梯度,外加一个参数lambda定义下来的。

这样一层一层的完成建树过程, xgboost训练的时候,是通过加法的方式进行训练,也就是每一次通过聚焦残差训练一棵树出来, 最后的预测结果是所有树的加和表示

XGB-损失函数

xgb详细的公式推断

节点如何分裂

xgb支持2种分裂算法

贪心算法

其步骤如下:

  1. 从树的深度为0开始:
  2. 对每个叶节点枚举所有的可用特征;
  3. 针对每个特征,把属于该节点的训练样本根据该特征值进行升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的分裂收益;
    选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,在该节点上分裂出左右两个新的叶节点,并为每个新节点关联对应的样本集;
  4. 回到第1步,递归执行直到满足特定条件为止;

如何计算收益最大的特征?
对比GBDT, 其如何选择feature, 本质上是CART如何选择feature. CART选择feature用Gini系数, 其并不和GBDT的损失函数挂钩, 而xgb则与损失函数挂钩, XGB分裂的时候, 同样先计算信息增益, 分裂好以后, 根据下式计算分裂后的损失

近似算法

贪心算法可以得到最优解,但当数据量太大时则无法读入内存进行计算,近似算法主要针对贪心算法这一缺点给出了近似最优解。
对于每个特征,只考察分位点可以减少计算复杂度。

  1. 该算法首先根据特征分布的分位数提出候选划分点,然后将连续型特征映射到由这些候选点划分的桶中,然后聚合统计信息找到所有区间的最佳分裂点。
    在提出候选切分点时有两种策略:
    .Global:学习每棵树前就提出候选切分点,并在每次分裂时都采用这种分割;
    Local:每次分裂前将重新提出候选切分点。直观上来看,Local策略需要更多的计算步骤,而Global策略因为节点已有划分所以需要更多的候选点。

XGB的优缺点

优点

  • 精度更高: GBDT 只用到一阶泰勒展开,而 XGBoost 对损失函数进行了二阶泰勒展开。XGBoost 引入二阶导一方面是为了增加精度,另一方面也是为了能够自定义损失函数,二阶泰勒展开可以近似大量损失函数;
  • GBDT 以 CART 作为基分类器,XGBoost 不仅支持 CART 还支持线性分类器,使用线性分类器的 XGBoost 相当于带 \(L1\)\(L2\)正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。此外,XGBoost 工具支持自定义损失函数,只需函数支持一阶和二阶求导;
  • 正则化: XGBoost 在目标函数中加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、叶子节点权重的\(L2\)范式。正则项降低了模型的方差,使学习出来的模型更加简单,有助于防止过拟合,这也是XGBoost优于传统GBDT的一个特性。
  • 相当于学习速率。XGBoost 在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。传统GBDT的实现也有学习速率;
  • XGBoost 借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算。这也是XGBoost异于传统GBDT的一个特性
  • 缺失值处理: 对于特征的值有缺失的样本,XGBoost 采用的稀疏感知算法可以自动学习出它的分裂方向;

缺点

虽然利用预排序和近似算法可以降低寻找最佳分裂点的计算量,但在节点分裂过程中仍需要遍历数据集;
预排序过程的空间复杂度过高,不仅需要存储特征值,还需要存储特征对应样本的梯度统计值的索引,相当于消耗了两倍的内存。

XGB工程实现

列块并行学习

在树生成过程中,最耗时的一个步骤就是在每次寻找最佳分裂点时都需要对特征的值进行排序。而 XGBoost 在训练之前会根据特征对数据进行排序,然后保存到块结构中,并在每个块结构中都采用了稀疏矩阵存储格式(Compressed Sparse Columns Format,CSC)进行存储,后面的训练过程中会重复地使用块结构,可以大大减小计算量。
作者提出通过按特征进行分块并排序,在块里面保存排序后的特征值及对应样本的引用,以便于获取样本的一阶、二阶导数值。具体方式如图:

通过顺序访问排序后的块遍历样本特征的特征值,方便进行切分点的查找。此外分块存储后多个特征之间互不干涉,可以使用多线程同时对不同的特征进行切分点查找,即特征的并行化处理。在对节点进行分裂时需要选择增益最大的特征作为分裂,这时各个特征的增益计算可以同时进行,这也是 XGBoost 能够实现分布式或者多线程计算的原因。

缓存访问

列块并行学习的设计可以减少节点分裂时的计算量,在顺序访问特征值时,访问的是一块连续的内存空间,但通过特征值持有的索引(样本索引)访问样本获取一阶、二阶导数时,这个访问操作访问的内存空间并不连续,这样可能造成cpu缓存命中率低,影响算法效率。
为了解决缓存命中率低的问题,XGBoost 提出了缓存访问算法:为每个线程分配一个连续的缓存区,将需要的梯度信息存放在缓冲区中,这样就实现了非连续空间到连续空间的转换,提高了算法效率。此外适当调整块大小,也可以有助于缓存优化。

核外计算

当数据量非常大时,我们不能把所有的数据都加载到内存中。那么就必须将一部分需要加载进内存的数据先存放在硬盘中,当需要时再加载进内存。这样操作具有很明显的瓶颈,即硬盘的IO操作速度远远低于内存的处理速度,肯定会存在大量等待硬盘IO操作的情况。针对这个问题作者提出了“核外”计算的优化方法。具体操作为,将数据集分成多个块存放在硬盘中,使用一个独立的线程专门从硬盘读取数据,加载到内存中,这样算法在内存中处理数据就可以和从硬盘读取数据同时进行。此外,XGBoost 还用了两种方法来降低硬盘读写的开销:
块压缩(Block Compression)。论文使用的是按列进行压缩,读取的时候用另外的线程解压。对于行索引,只保存第一个索引值,然后用16位的整数保存与该block第一个索引的差值。作者通过测试在block设置为 个样本大小时,压缩比率几乎达到 。
块分区(Block Sharding )。块分区是将特征block分区存放在不同的硬盘上,以此来增加硬盘IO的吞吐量。

xgb一些需要注意的点

xgb如何处理缺失值?

在某列特征上寻找分裂节点时,不会对缺失的样本进行遍历,只会对非缺失样本上的特征值进行遍历,这样减少了为稀疏离散特征寻找分裂节点的时间开销。
另外,为了保证完备性,对于含有缺失值的样本,会分别把它分配到左叶子节点和右叶子节点,然后再选择分裂后增益最大的那个方向,作为预测时特征值缺失样本的默认分支方向。
如果训练集中没有缺失值,但是测试集中有,那么默认将缺失值划分到右叶子节点方向

XGB中树停止生长的条件

  • 当树达到最大深度时,停止建树,因为树的深度太深容易出现过拟合,这里需要设置一个超参数max_depth。
  • 当新引入的一次分裂所带来的增益Gain<0时,放弃当前的分裂。这是训练损失和模型结构复杂度的博弈过程。
  • 当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果任一个叶子结点的样本权重低于某一个阈值,也会放弃此次分裂。这涉及到一个超参数:最小样本权重和,是指如果一个叶子节点包含的样本数量太少也会放弃分裂,防止树分的太细。

XGB如何处理类别特征?

可以参看这副图,既有离散值,又有连续值。离散值如果one-hot的话,那么分裂点非0即1

数据常常是多种类别混在一起的,比如离散的,连续的的特征,还有一些未被编码的类别特征
下面是一篇总结的比较好的文章
XGB如何处理类别(离散)特征

XGB如何评价特征重要性

XGBoost中有三个参数可以用于评估特征重要性:

  • weight :该特征在所有树中被用作分割样本的总次数。
  • gain :该特征在其出现过的所有树中产生的平均增益。
  • cover :该特征在其出现过的所有树中的平均覆盖范围。覆盖范围这里指的是一个特征用作分割点后,其影响的样本数量,即有多少样本经过该特征分割到两个子节点。

XGB如何进行特征剪枝

  • 在目标函数中增加了正则项:使用叶子结点的数目和叶子结点权重的L2模的平方,控制树的复杂度。
  • 在结点分裂时,定义了一个阈值,如果分裂后目标函数的增益小于该阈值,则不分裂。
  • 当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果任一个叶子结点的样本权重低于某一个阈值(最小样本权重和),也会放弃此次分裂。
  • XGBoost 先从顶到底建立树直到最大深度,再从底到顶反向检查是否有不满足分裂条件的结点,进行剪枝。

XGB如何选择分裂点

XGB分裂点的分裂方式和CART很不一样,主要是我们要在每个分裂点上算出一 二阶导数来计算分裂的增益。XGBoost在训练前预先将特征按照特征值进行了排序,并存储为block结构,以后在结点分裂时可以重复使用该结构。
因此,可以采用特征并行的方法利用多个线程分别计算每个特征的最佳分割点,根据每次分裂后产生的增益,最终选择增益最大的那个特征的特征值作为最佳分裂点。如果在计算每个特征的最佳分割点时,对每个样本都进行遍历,计算复杂度会很大,这种全局扫描的方法并不适用大数据的场景。XGBoost还提供了一种直方图近似算法,对特征排序后仅选择常数个候选分裂位置作为候选分裂点,极大提升了结点分裂时的计算效率。

XGB与GBDT有何不同?

既然是GBDT的更高阶实现,那么他和gbdt有很多的相似之处:

相同点

  1. 他们都有基于CART树的实现
  2. 他们都是前向加法模型
  3. 他们都拟合的是残差

不同点

1.对于基学习器CART树,XGB显示的添加了正则项用于控制树的复杂度,能有效的避免过拟合
2.节点分裂方式不同,GBDT本质上遵循的还是CART本身的节点分裂规则,而XGB会与它的损失函数挂钩,具体的是与一阶和二阶的残差挂钩
3. GBDT求损失梯度的时候只是使用了一阶导,二XGB则使用了二阶导,可以更加逼近损失的真实值
4. GBDT训练的时候,建每一棵树都是用的全部的数据,而xgb则和rf一样支持列采样
5. XGB支持缺失值,虽然理论上GBDT也是支持的,毕竟这个东西在C4.5里面就已经支持,更为优秀的CART也是支持的。但是像常用的scikit-learn里源码的实现其实是不支持的。
其次有一个地方要小心的是spark版本中,XGB的缺失值处理在java/scala和python版本中是不一样的,scala中是0都是缺失值,而py版本中NaN才是。
6. XGB支持特征并行,XGBoost预先将每个特征按特征值排好序,存储为块结构,分裂结点时可以采用多线程并行查找每个特征的最佳分割点,极大提升训练速度。
7. XGB存在有early stoping机制,当我们的metric在val set上经历了我们设置的step数量后没有得到提升,XGB就会停止训练避免过拟合

LightGBM

lightGBM总结

posted @ 2020-07-18 11:56  real-zhouyc  阅读(289)  评论(0编辑  收藏  举报