【知识】决策树

Decision Tree 决策树


决策树基础

决策树(decision tree)是一种基本的分类与回归方法。决策树模型呈树结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以被认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布,如下图1。

其主要优点是:模型具有可读性,分类速度快。
决策树学习通常包含2个步骤:特征选择(树的生成)和剪枝。
用决策树分类,从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到子节点,如此递归的进行测试和分配,直至达到叶节点,最后将实例分到叶节点的类中。
用决策树回归,和分类类似,只不过最后的叶节点对应的是回归的结果(可能是一些离散的数,由于叶子节点很多,数的间隔较小,整体上就可以理解为是一定程度连续的,进而达到回归预测的效果)。TODO
由于回归决策树的思想和分类决策树极其相近,所以下面就以分类决策树的角度介绍决策树的基本知识了。回归决策树可以对比得到。

特征选择

特征选择就是在特征集中(一堆特征中),选取对训练数据分类能力最强的一个特征,通过这个特征先将训练数据分为多个子类。然后在剩余的特征中递归使用上面的思想。这样由于靠近根节点的特征分类能力最强(区分样本类别的能力),我们学习得到的决策树的精度就会高一些。换个角度思考,如果用一个特征去分类,得到的精度还不如瞎猜,那不如放弃这个特征。
通常特征选择的准则有:信息增益,信息增益比,基尼指数。

信息增益

信息增益(information gain)表示得知特征X的信息而使得类Y的信息不确定性减少的程度。
特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差:

\[g(D,A)=H(D)-H(D|A) \]

一般地,熵H(Y)与条件熵H(Y|X)之差称为互信息(mutual information),决策树学习中的信息增益等价于训练数据集中类与特征的互信息。
关于熵相关知识,可以参考我的Entropy笔记
然后给出信息增益的计算方法:(直接贴书上的截图了)



信息增益比

特征A对训练数据集D的信息增益比定义为其信息增益与训练数据集D关于特征A的经验熵之比:

\[g_R(D,A)=\frac{g(D,A)}{H_A(D,A)}\\ H_A(D,A)=-\sum\limits_{i=1}^N\frac{|D_i|}{|D|}log\frac{|D_i|}{|D|} \]

其中i表示,特征A的取值i,\(D_i\)表示特征A取值为i的数据子集。
注意统计学习方法里信息增益比的公式写错了。

信息增益的大小是相对于训练数据集而言的,并没有绝对意义,在分类问题困难时,即训练数据集的经验熵大的时候,信息增益会偏大,反之会偏小。即信息增益偏向于选择取值较多的特征,容易过拟合。我们可以想对于信息增益,第一部分经验熵对所有的特征都是一样的,然后减去条件熵,因为条件熵里面还有负号,所以相当于加上一个log,而log在[0,1]是一个递增函数。特征取值越多,\(\frac{|D_{ik}|}{|D_{i}|}\)的分母就越小,值就越大,于是log就越大,于是整体的信息增益就越大。所以偏向于取值较多的特征。
例如对:

信用级别A 工资级别B 是否逾期Y
1 1 1
2 1 0
3 2 1
4 2 0
特征信用级别可以把样本分为4个子集,工资级别可以把样本分为2个子集。分别对这两个特征进行信息增益的计算:

\[H(Y|A) = (-0.5log\frac{2}{4}-0.5log\frac{2}{4})-(-0.25*log\frac{1}{1}-0.25*log\frac{1}{1}-0.25*log\frac{1}{1}-0.25*log\frac{1}{1})=1\\ H(Y|B) = (-0.5log\frac{2}{4}-0.5log\frac{2}{4})-(-0.5*(\frac{1}{2}log\frac{1}{2}+\frac{1}{2}log\frac{1}{2})-0.5*(\frac{1}{2}log\frac{1}{2}+\frac{1}{2}log\frac{1}{2}))=0 \]

基尼指数

信息增益和信息增益比从信息论的角度考虑决策树构建中特征选择的标准,基尼指数则从另一种角度来进行特征选择。
数据集D的纯度可以用基尼指数来度量。数据集D的基尼指数定义为:

\[Gini(D) = \sum_{k=1}^{K}p_k(1-p_k)=1-\sum_{k=1}^{K}p^2_k \]

其中k为第k类,共有K类,\(p_k\)就是样本属于第k类的概率。
直观来说,Gini(D)反映了从数据集D中随机抽取两个样本,其类别标记不一致的概率。因此Gini(D)越小,数据集D的纯度越高。
对于给定样本的集合,其基尼指数为:

\[Gini(D) = 1-\sum\limits_{k=1}^{K} (\frac{|D_k|}{|D|})^2 \]

基尼指数也可以理解为表示集合D的不确定性。Gini(D,A)则表示经A分割后集合D的不确定性。基尼指数越大,样本集合的不确定性也就越大,这点与熵是类似的。
如果样本可以被特征A分割成多个子集,那么在特征A的条件下,数据集D的基尼指数定义为:

\[Gini(D,A)=\sum_{i=1}^N\frac{|D_i|}{|D|}Gini(D_i) \]

剪枝

决策树生成算法(ID3,C4.5,etc.)递归地产生决策树,直到不能继续下去为止。这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没有那么准确,即出现过拟合现象。过拟合的原因也很简单,学习时过多地考虑了如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。而解决这个问题的方法就是,对这些已生成的决策树进行剪枝简化。
剪枝不是一个锦上添花的步骤,而是一个必要的步骤,它可以有效的降低树模型的过拟合程度。那么剪枝和生成(最优特征和最优划分选取)有什么区别呢?其中好像都用到了数据集的经验熵。
在生成时,我们的目的是找出最优特征和最优划分选取,考虑的是让信息增益最大,即经验熵和经验条件熵的差更大,这时确实是考虑到了经验熵,而且选取了最优特征和划分后,一般熵会减少,但训练的过程更多考虑的是“经验熵和经验条件熵的差”这一联动的关系,而不是单独考虑经验熵。在剪枝时,目的是为了避免过拟合,考虑的就是让经验熵减小,顺便让树的复杂度降低。所以虽然都用到了经验熵,但用的方法是不一样的。

预剪枝

剪枝一般分为两种,预剪枝和后剪枝。预剪枝是在构建树时剪枝。
即在选取完最优特征和划分后,计算一个新的经验熵,和不划分该节点时的经验熵做对比,若熵减,或者熵减程度大于阈值,则可以划分,否则剪枝(不划分,停止生长)。
另外还可以设置最大树深和叶子节点最少样本数,来在构建时让树提前停止生长。
这些做法在我们熟悉的XGBoost里面也很常见。

后剪枝

决策树的后剪枝往往通过极小化决策树整体的损失函数来实现。设树T的结点个数为|T|,t是树T的叶节点,N_t代表叶节点t上的样本数,其中第k类的样本点有N_{tk}个,H_t(T)为叶节点t上的经验熵,\alpha为超参数,则决策树的损失函数可以定义为:

\[C_{\alpha}(T) = C(T)+\alpha|T|=\sum\limits_{t=1}^{|T|}N_tH_t(T)+\alpha|T| \\ H_t(T) = -\sum\limits_k\frac{N_{tk}}{N_t}log\frac{N_{tk}}{N_t} \]

其中原始损失函数C(T)也可以不是经验熵,用别的损失函数。
上式左项用每个叶节点的经验熵之和表示模型对训练数据的预测误差,即模型与训练数据的拟合程度。而右项则是对模型复杂度添加一个约束,约束大小由超参\alpha控制。
具体的做法是,bottom-up的方式从下到上遍历所有子树,对比将其剪枝和不剪时整体损失函数哪个小,若剪枝能让损失函数更小则剪枝。实际应用时计算只需计算该子树的损失函数,因为整体损失是由所有子树损失求和而来,而对该子树剪枝并不影响其他子树。
后剪枝还分为Reduced-Error Pruning (REP,错误率降低剪枝)和Pessimistic Error Pruning (PEP,悲观剪枝)。我们上面说的方法是悲观剪枝。
REP则是搞一个测试集,用训练集训练决策树,用测试集来剪枝,具体操作和上面PEP一样。

连续值缺失值处理

连续属性处理

现实学习任务中,特征不会只有离散的,也会有连续的。这种情况下,我们的决策树该如何选取最优特征呢?答案很显然,离散化嘛。
常用的离散化方法就是采用二分法对连续属性进行处理。给定数据集D和连续属性A,a在D中出现了n个不同的取值,排序得到{a_1,a_2,...,a_n}。选取一个点,将这些值分成两部分,则有n-1种选择,比如选取a_1,a_2中间的分割点,则将属性分割成{a_1},{a_2,...}两部分,则相当于离散化了属性A。
分别对n-1中分割点分割离散化后的数据集D和A属性A计算信息增益或者基尼指数,选取效果最好的那个当作属性A的标准,和其他属性对比选出最优属性即可。
不过连续属性和离散属性的区别还有:若连续属性B已经作为最优特征被用于中间节点的生成了,该属性依然可以作为其后代节点的划分属性。

缺失值处理

现实任务中我们还会遇到不完整样本。如果简单的对缺失样本进行抛弃,那么是对数据的极大的浪费。尤其在完整样本很稀疏的情况下。
所以我们有必要利用有缺失属性值的样本来进行学习。
那么我们需要解决两个问题:如何在属性值缺失的情况下进行最优特征的选取?以及给定最优特征,若样本的该值缺失,如何对它进行划分。
对于问题1,我们通过重新定义信息增益来解决。对于问题2,我们通过对每条样本进行权重赋值来解决。
我们给每个样本x赋予一个权重w_x,在根节点中个样本权重初始化为1,并定义:

\[\alpha = \frac{\sum\limits_{x\in\hat{D}}w_x}{\sum\limits_{x\in D}w_x} \\ \hat{p_k} = \frac{\sum\limits_{x\in\hat{D_k}}w_x}{\sum\limits_{x\in\hat{D}}w_x} \\ \hat{r_i} = \frac{\sum\limits_{x\in\hat{D^i}}w_x}{\sum\limits_{x\in\hat{D}}w_x} \]

其中,\(\hat{D}\)表示无缺失值样本集合,\(\hat{D_k}\)表示无缺失值样本集合中第k类的样本集合,\(\hat{D^i}\)表示无缺失值样本集合中属性A取\(a^i\)值的样本集合。
\(\alpha\)表示无缺失值样本所占比例,\(\hat{p_k}\)表示无缺失值样本集合中第k类样本的比例,\(\hat{r^i}\)表示无缺失值样本集合中属性A取\(a^i\)值样本的比例。
进而有:

\[Gain(D,A)=\alpha Gain(\hat{D},A)=\alpha*(H(\hat{D})-H(\hat{D}|A))\\ H(\hat{D})=-\sum\limits_{k=1}^K\hat{p_k}log\hat{p_k} \\ H(\hat{D}|A)=\sum\limits_{i=1}^V\hat{r_i}H(\hat{D}|A=a_i)=\sum\limits_{i=1}^V\hat{r_i}H(\hat{D^i}) \]

通过上式定义的新的信息增益,即可解决问题一。
对于问题二,若样本x在特征A上的取值已知,则该怎么划分怎么划分,且样本权值不变,送入子节点。若未知,则将样本x同时划分到所有子节点中去,并将其子节点中的权值更新为\(\hat{r^i} w_x\),也就是让一个样本以不同的概率划入所有的子节点中。

经典决策树生成算法

ID3算法

ID3算法的核心思想就是在决策树各个节点上应用信息增益选择特征,递归地构建决策树。直到所有特征信息增益都小于一个超参阈值或者无特征可选为止。即使用了预剪枝。
相当于用极大似然法进行概率模型的选择。
具体算法过程无须赘述。

C4.5算法

C4.5算法则是在ID3算法的基础上改进:使用信息增益比选择特征,并加入连续特征值和缺失值处理方法。提出了后剪枝。
具体算法过程无须赘述。

CART算法

分类与回归树(classification and regression tree)既可以用于分类也可以用于回归。
将它用于分类时,使用Gini指数选择特征。并且每次只分两类(使用二叉树)。停止条件是节点中的样本数量小于预定阈值,或样本集的基尼指数小于预定阈值(基本都属于同一类),或没有更多特征。

CART用于回归


CART剪枝


我们每次剪枝剪的都是某个内部节点的子节点,也就是将某个内部节点的所有子节点回退到这个内部节点里,并将这个内部节点作为新的叶子节点。因此在计算整体的损失函数时,只有这个内部节点的局部损失函数改变了,因此我们无需计算全局损失函数,只需要计算内部节点剪枝前后的损失函数。
对于节点t,以t为根节点的子树为\(T_t\)。那么他们对应的损失函数:

\[C_{\alpha}(T_t)=C(T_t)+\alpha |T|\\ C_{\alpha}(t)=C(t)+\alpha \]

在两个损失函数相等的时候,我们可以得到:(为什么取相等时的值以后再讨论,应该是从\(\alpha\)的值和两个损失函数大小关系出发分析)

\[\alpha^{'} = g(t) = \frac{C(t)-C(T_t)}{|T_t|-1} \]

这个东西可以叫做误差减小率,表示子树中多的每个叶子节点可以带来多少误差上的减少。
由于是在损失相等的时候得到的,所以这时是可以剪枝的。
对此刻的完整树\(T_i\)的所有子树计算g(t),取最小的g(t)对应的子树\(T_t\)剪掉。
取最小是因为,这颗子树每个叶子节点带来的损失减少量最小,所以这些叶子节点相对目前保留的其他叶子节点们最没价值。故剪去。
然后得到树\(T_{i+1}\)。循环上面过程。
最终得到N棵树,交叉验证选取一棵最好的。
CART剪枝具体参考

GBDT 梯度提升决策树

GBDT(Gradient Boosting Decision Tree) 又叫 MART(Multiple Additive Regression Tree),是一种迭代的决策树算法,该算法由多棵决策树(基分类器一般选择CART)组成,所有树的结论累加起来做最终答案。它在被提出之初就和SVM一起被认为是泛化能力较强的算法。 GBDT通过采用加法模型(即基函数的线性组合),以及不断减小训练过程产生的残差来达到将数据分类或者回归的算法(分类也是通过回归做的)。GBDT通过多轮迭代,每轮迭代产生一个弱分类器,每个分类器在上一轮分类器的残差基础上进行训练。对弱分类器的要求一般是足够简单,并且是低方差和高偏差的。因为训练的过程是通过降低偏差来不断提高最终分类器的精度。弱分类器一般会选择为CART(也就是分类回归树)。由于上述高偏差和简单的要求 每个分类回归树的深度不会很深。最终的总分类器 是将每轮训练得到的弱分类器加权求和得到的(也就是加法模型)。
注意,GBDT中的树都是回归树,而不是分类树!!
GBDT主要由三个概念组成:Regression Decistion Tree(即DT),Gradient Boosting(即GB),Shrinkage (算法的一个重要演进分枝,目前大部分源码都按该版本实现)。搞定这三个概念后就能明白GBDT是如何工作的。

DT: 回归树 Regression Decision Tree

下面我们以对人的性别判别/年龄预测为例来说明,每个instance都是一个我们已知性别/年龄的人,而feature则包括这个人上网的时长、上网的时段、网购所花的金额等。
作为对比,先说分类树,我们知道C4.5分类树在每次分枝时,是穷举每一个feature的每一个阈值,找到使得按照feature<=阈值,和feature>阈值分成的两个分枝的熵最大的feature和阈值(熵最大的概念可理解成尽可能每个分枝的男女比例都远离1:1),按照该标准分枝得到两个新节点,用同样方法继续分枝直到所有人都被分入性别唯一的叶子节点,或达到预设的终止条件,若最终叶子节点中的性别不唯一,则以多数人的性别作为该叶子节点的性别。
回归树总体流程也是类似,不过在每个节点(不一定是叶子节点)都会得一个预测值,以年龄为例,该预测值等于属于这个节点的所有人年龄的平均值。分枝时穷举每一个feature的每个阈值找最好的分割点,但衡量最好的标准不再是最大熵,而是最小化均方差--即(每个人的年龄-预测年龄)^2 的总和 / N,或者说是每个人的预测误差平方和 除以 N。这很好理解,被预测出错的人数越多,错的越离谱,均方差就越大,通过最小化均方差能够找到最靠谱的分枝依据。分枝直到每个叶子节点上人的年龄都唯一(这太难了)或者达到预设的终止条件(如叶子个数上限),若最终叶子节点上人的年龄不唯一,则以该节点上所有人的平均年龄做为该叶子节点的预测年龄。

GB:梯度提升Gradient Boosting

Boosting,提升方法,即通过迭代多棵树来共同决策。
这怎么实现呢?难道是每棵树独立训练一遍,比如A这个人,第一棵树认为是10岁,第二棵树认为是0岁,第三棵树认为是20岁,我们就取平均值10岁做最终结论?当然不是!且不说这是投票方法并不是GBDT,只要训练集不变,独立训练三次的三棵树必定完全相同,这样做完全没有意义。
GBDT是把所有树的结论累加起来做最终结论的,所以可以想到每棵树的结论并不是年龄本身,而是年龄的一个累加量。GBDT的核心就在于,每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。比如A的真实年龄是18岁,但第一棵树的预测年龄是12岁,差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去学习,如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄;如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学。
这就是Gradient Boosting在GBDT中的意义。拟合的是负梯度,在使用MSE的回归问题($0.5(y-F(x))^2)中与残差等价。
下面是正常的(非GBDT中)的GB:
我们知道Boosting的思想就是递归的产生多个弱分类器,最终把它们的结果合成最终结果。 Gradient boosting的思想是和Boosting一脉相承的:迭代生多个(M个)弱的模型,然后将每个弱模型的预测结果相加,后面的模型F_{m+1}(x)基于前面学习模型的F_m(x)的效果生成的。
定义F_m(x)为第m轮产生的模型,h_m(x)为第m轮产生的梯度提升树,则

\[F_m(x)=\sum_{i=1}^mh_m(x) \]

GB算法的思想很简单,关键是怎么生成h_m(x)?
如果目标函数是回归问题的均方误差,很容易想到最理想的h(x)应该是能够完全拟合y-h_m(x),这就是常说基于残差的学习。残差学习在回归问题中可以很好的使用,但是为了一般性(分类,排序问题),实际中往往是基于loss Function 在函数空间的的负梯度学习,对于回归问题$0.5(y-F(x))^2残差和负梯度也是相同的。
GB算法的步骤:

Shrinkage: 缩减

Shrinkage(缩减)的思想认为,每次走一小步逐渐逼近结果的效果,要比每次迈一大步很快逼近结果的方式更容易避免过拟合。即它不完全信任每一个棵残差树,它认为每棵树只学到了真理的一小部分,累加的时候只累加一小部分,通过多学几棵树弥补不足。用方程来看更清晰,即
没用Shrinkage时:(yi表示第i棵树上y的预测值, y(1~i)表示前i棵树y的综合预测值)
y(i+1) = 残差(y1~yi), 其中: 残差(y1~yi) = y真实值 - y(1 ~ i)
y(1 ~ i) = SUM(y1, ..., yi)
Shrinkage不改变第一个方程,只把第二个方程改为:
y(1 ~ i) = y(1 ~ i-1) + step * yi
即Shrinkage仍然以残差作为学习目标,但对于残差学习出来的结果,只累加一小部分(step*残差)逐步逼近目标,step一般都比较小,如0.01~0.001(注意该step非gradient的step),导致各个树的残差是渐变的而不是陡变的。直觉上这也很好理解,不像直接用残差一步修复误差,而是只修复一点点,其实就是把大步切成了很多小步。本质上,Shrinkage为每棵树设置了一个weight,累加时要乘以这个weight,但和Gradient并没有关系。这个weight就是step。就像Adaboost一样,Shrinkage能减少过拟合发生也是经验证明的,目前还没有看到从理论的证明。

GBDT实例

还是年龄预测,简单起见训练集只有4个人,A,B,C,D,他们的年龄分别是14,16,24,26。其中A、B分别是高一和高三学生;C,D分别是应届毕业生和工作两年的员工。如果是用一棵传统的回归决策树来训练,会得到如下图1所示结果:

现在我们使用GBDT来做这件事,由于数据太少,我们限定叶子节点做多有两个,即每棵树都只有一个分枝,并且限定只学两棵树。我们会得到如下图2所示结果:

在第一棵树分枝和图1一样,由于A,B年龄较为相近,C,D年龄较为相近,他们被分为两拨,每拨用平均年龄作为预测值。此时计算残差(残差的意思就是: A的预测值 + A的残差 = A的实际值),所以A的残差就是16-15=1(注意,A的预测值是指前面所有树累加的和,这里前面只有一棵树所以直接是15,如果还有树则需要都累加起来作为A的预测值)。进而得到A,B,C,D的残差分别为-1,1,-1,1。然后我们拿残差替代A,B,C,D的原值,到第二棵树去学习,如果我们的预测值和它们的残差相等,则只需把第二棵树的结论累加到第一棵树上就能得到真实年龄了。这里的数据显然是我可以做的,第二棵树只有两个值1和-1,直接分成两个节点。此时所有人的残差都是0,即每个人都得到了真实的预测值。
换句话说,现在A,B,C,D的预测值都和真实年龄一致了。Perfect!:
A: 14岁高一学生,购物较少,经常问学长问题;预测年龄A = 15 – 1 = 14
B: 16岁高三学生;购物较少,经常被学弟问问题;预测年龄B = 15 + 1 = 16
C: 24岁应届毕业生;购物较多,经常问师兄问题;预测年龄C = 25 – 1 = 24
D: 26岁工作两年员工;购物较多,经常被师弟问问题;预测年龄D = 25 + 1 = 26
那么哪里体现了Gradient呢?其实回到第一棵树结束时想一想,无论此时的cost function是什么,是均方差还是均差,只要它以误差作为衡量标准,残差向量(-1, 1, -1, 1)都是它的全局最优方向,这就是Gradient。

一些疑问

1)既然图1和图2 最终效果相同,为何还需要GBDT呢?

答案是过拟合。过拟合是指为了让训练集精度更高,学到了很多”仅在训练集上成立的规律“,导致换一个数据集当前规律就不适用了。其实只要允许一棵树的叶子节点足够多,训练集总是能训练到100%准确率的(大不了最后一个叶子上只有一个instance)。在训练精度和实际精度(或测试精度)之间,后者才是我们想要真正得到的。
我们发现图1为了达到100%精度使用了3个feature(上网时长、时段、网购金额),其中分枝“上网时长>1.1h” 很显然已经过拟合了,这个数据集上A,B也许恰好A每天上网1.09h, B上网1.05小时,但用上网时间是不是>1.1小时来判断所有人的年龄很显然是有悖常识的;
相对来说图2的boosting虽然用了两棵树 ,但其实只用了2个feature就搞定了,后一个feature是问答比例,显然图2的依据更靠谱。(当然,这里是LZ故意做的数据,所以才能靠谱得如此狗血。实际中靠谱不靠谱总是相对的) Boosting的最大好处在于,每一步的残差计算其实变相地增大了分错instance的权重,而已经分对的instance则都趋向于0。这样后面的树就能越来越专注那些前面被分错的instance。就像我们做互联网,总是先解决60%用户的需求凑合着,再解决35%用户的需求,最后才关注那5%人的需求,这样就能逐渐把产品做好,因为不同类型用户需求可能完全不同,需要分别独立分析。如果反过来做,或者刚上来就一定要做到尽善尽美,往往最终会竹篮打水一场空。

2)Gradient呢?不是“G”BDT么?
如果目标函数是回归问题的均方误差,很容易想到最理想的h(x)应该是能够完全拟合y-h_m(x),这就是常说基于残差的学习。残差学习在回归问题中可以很好的使用,但是为了一般性(分类,排序问题),实际中往往是基于loss Function 在函数空间的的负梯度学习,对于回归问题$0.5(y-F(x))^2残差和负梯度也是相同的。

3)这不是boosting吧?Adaboost可不是这么定义的。
这是boosting,但不是Adaboost。GBDT不是Adaboost Decistion Tree。就像提到决策树大家会想起C4.5,提到boost多数人也会想到Adaboost。Adaboost是另一种boost方法,它按分类对错,分配不同的weight,计算cost function时使用这些weight,从而让“错分的样本权重越来越大,使它们更被重视”。Bootstrap也有类似思想,它在每一步迭代时不改变模型本身,也不计算残差,而是从N个instance训练集中按一定概率重新抽取N个instance出来(单个instance可以被重复sample),对着这N个新的instance再训练一轮。由于数据集变了迭代模型训练结果也不一样,而一个instance被前面分错的越厉害,它的概率就被设的越高,这样就能同样达到逐步关注被分错的instance,逐步完善的效果。Adaboost的方法被实践证明是一种很好的防止过拟合的方法,但至于为什么则至今没从理论上被证明。GBDT也可以在使用残差的同时引入Bootstrap re-sampling,GBDT多数实现版本中也增加的这个选项,但是否一定使用则有不同看法。re-sampling一个缺点是它的随机性,即同样的数据集合训练两遍结果是不一样的,也就是模型不可稳定复现,这对评估是很大挑战,比如很难说一个模型变好是因为你选用了更好的feature,还是由于这次sample的随机因素。

4)GBDT提取特征怎么做?
答案一般就是GBDT+LR,可以看10分钟了解GBDT+LR模型的来龙去脉
这里涉及了模型组合策略中的“学习法”Stacking:Stacking先从数据集训练出一个初级学习器,然后生成一个新数据集(一般是同一个样本生成了新的特征),用来训练次级学习器。在这个新数据集中,初级学习器的输出被当作样例特征送入次级学习器,而label不变。
另外还有很多很好的面试向的问题:机器学习算法GBDT的面试要点总结-上篇

XGBoost

经过前面的学习,我们已经知道,GBDT是一种基于集成思想下的Boosting学习器,并采用梯度提升的方法进行每一轮的迭代最终组建出强学习器,这样的话算法的运行往往要生成一定数量的树才能达到令我们满意的准确率。当数据集大且较为复杂时,运行一次极有可能需要几千次的迭代运算,这将对我们使用算法造成巨大的计算瓶颈。
针对这一问题,华盛顿大学的陈天奇博士开发出了XGBoost(eXtreme Gradient Boosting),它是Gradient Boosting Machine的一个c++实现,并在原有的基础上加以改进,从而极大地提升了模型训练速度和预测精度。可以说,XGBoost是Gradient Boosting的高效实现。
GBDT无论在理论推导还是在应用场景实践都是相当完美的,但有一个问题:第n颗树训练时,需要用到第n-1颗树的(近似)残差。从这个角度来看,gbdt比较难以实现分布式(ps:虽然难,依然是可以的,换个角度思考就行),而xgboost从下面这个角度着手:

即对GB的目标函数在\(\hat{y_i}=\hat{y_i}^{(t-1)}\)处进行泰勒展开,得到XGBoost的目标函数。利用泰勒展开三项,做一个近似,我们可以很清晰地看到,最终的目标函数只依赖于每个数据点的在误差函数上的一阶导数和二阶导数。而GB,GBDT都是只利用了一阶导数。

XGBoost和GBDT的区别有

  • xgboost中的基学习器除了可以是CART(gbtree)也可以是线性分类器(gblinear);
  • xgboost在目标函数中显示的加上了正则化项,基学习为CART时,正则化项与树的叶子节点的数量T和叶子节点的值有关;
  • GB中使用Loss Function对f(x)的一阶导数计算出伪残差用于学习生成fm(x),xgboost不仅使用到了一阶导数,还使用二阶导数;
  • 模仿RF,加入了列抽样
  • 在寻找最佳分割点时,考虑传统的枚举每个特征的所有可能分割点的贪心法效率太低,xgboost实现了一种近似的算法。大致的思想是根据百分位法列举几个可能成为分割点的候选者,然后从候选者中根据上面求分割点的公式计算找出最佳的分割点;TODO
  • 特征列排序后以块的形式存储在内存中,在迭代中可以重复使用;虽然boosting算法迭代必须串行,但是在处理每个特征列时可以做到并行;TODO
  • xgboost 还考虑了当数据量比较大,内存不够时怎么有效的使用磁盘(主要是结合多线程、数据压缩、分片的方法)尽可能的提高算法的效率。

XGBoost的其他优点

  • XGBoost在目标函数里加入正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数和每棵树叶子节点上面输出分数的L_2模平方。从偏差方差权衡的角度来讲,正则项降低了模型的variance,使学习出来的模型更加简单,防止过拟合
  • 列抽样(column subsampling):XGBoost借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算,这也是XGBoost异于传统GBDT的一个特性 TODO

XGBoost 的目标函数公式推导

我们可以定义XGBoost的原始目标函数为(已知\(\hat{y_i^{t-1}}\)优化\(f_t\)时的目标函数):

\[J(f_t) =\sum\limits_{i=1}^ML(y_i,\hat{y_i}^{t})+\Omega(f_t)+C= \sum\limits_{i=1}^ML(y_i,\hat{y_i}^{(t-1)}+f_t(x_i))+\Omega(f_t)+C \]

其中L是某种常见的损失函数,如MSE。对之在\(,\hat{y_i}^{t}=\hat{y_i}^{t-1}=y_0\)处 进行二阶泰勒展开:

\[J(f_t)\approx\sum\limits_{i=1}^M[L(y_i,\hat{y_i}^{t})|_{\hat{y_i}^{t}=\hat{y_i}^{t-1}}+g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i)]+\Omega(f_t)+C \\ g_i = \frac{\partial L(y_i,\hat{y_i}^{t})}{\partial f_t(x_i)}|_{\hat{y_i}^{t}=\hat{y_i}^{t-1}}\quad,\quad h_i = \frac{\partial^2 L(y_i,\hat{y_i}^{t})}{\partial f_t^2(x_i)}|_{\hat{y_i}^{t}=\hat{y_i}^{t-1}} \]

注意其中:

\[\hat{y_i}^{t}=\hat{y_i}^{(t-1)}+f_t(x_i) \]

\(f_t(x_i)\)有关。

考虑叶子节点中:

\[f_t(x)=w_{q(x)}\\ \Omega(f_t)=\gamma |T_t|+\frac{1}{2}\lambda w_j^2 \]

\(L(y_i,\hat{y_i}^{t})|_{\hat{y_i}^t}\)为常数则有

\[J(f_t)=\sum\limits_{i=1}^M[g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i)]+\Omega(f_t)+C\\ =\sum\limits_{i=1}^M[g_iw_{q(x_i)}+\frac{1}{2}h_iw_{q(x_i)}^2)]+\gamma|T_t|+\frac{1}{2}\lambda\sum\limits_{j=1}^Tw_j^2+C\\ \]

进一步,n个样本落在T个不同的叶子上,那么可以等价的按不同的叶子将样本进行区分,即按T,将落在同一个叶子上的样本进行求和,可得:

\[J(f_t)=\sum\limits_{j=1}^T[(\sum_{i\epsilon I_j}g_i)w_j+\frac{1}{2}(\sum_{i\epsilon I_j}h_j)w_j^2]+\gamma|T_t|+\frac{1}{2}\lambda\sum\limits_{j=1}^Tw_j^2+C\\ =\sum\limits_{j=1}^T[(\sum_{i\epsilon I_j}g_i)w_j+\frac{1}{2}(\sum_{i\epsilon I_j}h_j+\gamma)w_j^2]+\gamma|T_t|+C\\ =\sum\limits_{j=1}^T[G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2]+\gamma|T_t|+C\\ \]

\(G_j,H_j\)分别代表每个叶子节点上的样本的一阶导数或二阶导数之和。

\[\frac{\partial J(f_t)}{\partial w_j}=G_j+(H_j+\lambda)w_j==0\\ \Rightarrow w_j = -\frac{G_j}{H_j+\lambda} \]

代入目标函数公式,得到最终的目标函数:

\[J(f_t)=-\frac{1}{2}\sum\limits_{j=1}^T\frac{G_j^2}{H_j+\lambda}+\gamma |T_t| \]

即,XGBoost每轮是以这个目标函数为基准,进行特征选择以及预剪枝的。

XGBoost API以及调参

import xgboost as xgb
label = Y_train
dtrain = xgb.DMatrix(X_train,Y_train)
dtest = xgb.DMatrix(X_test)

param = {'booster':'gbtree','max_depth':3,'eta':0.05,'n_estimators':100,'silent':1,'gamma':0.02}

model_xgb = xgb.train(param,dtrain)

Y_pred = model_xgb.predict(dtest)
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score

xgb = XGBClassifier()
xgb.fit(X_train,Y_train)

Y_pred = xgb.predict(X_test)
Y_temp = xgb.predict(X_train)
predictions = [round(value) for value in Y_temp] #xgboost 的结果是每个样本属于第一类的概率,需要用 round 将其转换为 0 1 值
accuracy = accuracy_score(Y_train, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))

两种用法,一种是XGBoost风格,一种是Sklearn风格。

调参可以参考,主要参数有:

n_estimators : boosting的CART树或者线性分类器的个数

min_child_weight : 每个叶子节点最小的weight,小于这个weight就不再分裂了

max_depth : 弱分类器子树的最大深度

gamma : 最小的目标函数值,目标函数小于该值就不用再分裂了

subsample : 采样率(结合了随机森林样本扰动Bagging的思想)

colsample_bytree : 列采样率(特征扰动)

reg_alpha , reg_lambda : L1,L2正则权重

learning_rate :学习率,每棵树的结果乘上学习率相加得到最终结果

scale_pos_weights:控制正负样本均衡的参数,sum(negative instances) / sum(positive instances)

Random Forests 随机森林

Bagging

集成学习(ensemble learning)通过构建并结合多个学习器来完成学习任务。一般分为Boosting和Bagging。

Boosting我们前面了解很多了,其基本思想是:先训练出一个基学习器,再根据基学习器的结果对训练样本分布进行调整(更改label或权重等方法),使得先前基学习器预测错误的样本得到更多后续的注意,之后递归训练下一个基学习器。最后将所有基学习器的结果加权求和。

Bagging则是并行式集成学习的代表。它采用自助采样法(bootstrap sampling)对样本进行采样(对含n个样本的数据集有放回的采样n次,得到一个新的数据集。初始训练集中的样本有63.2%的概率出现在采样集中)。我们可以采样出T个采样集,然后基于每个采样集训练出一个基学习器,再将这些基学习器结合。Bagging通常对分类问题采用简单投票法,对回归问题采用简单平均法。

随机森林

随机森林(random forests,RF)则是Bagging的一种拓展变体,RF在以决策树为基学习器构建Bagging集成的基础上,进一步引入了随机属性选择,即在选择最优属性时,在原始属性集中随机选择k个属性作为采样的属性集,进而进行最优属性的选择。一般推荐k=log_2d。k为人工设置超参,d为原特征数。

随机森林不仅有样本扰动,还有属性扰动,泛化性能强,计算开销小。

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

LightGBM

面试常见问题

XGBoost相对于GBDT,为什么能够加速训练

注意xgboost的并行不是tree粒度的并行,xgboost也是一次迭代完成才能进行下一次迭代的(第t次迭代的代价函数里面包含了前面t-1次迭代的预测值)。
xgboost的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点)。于是xgboost在训练之前,预先对每个特征内部进行了排序找出候选切割点,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。(具体block的结构可以参考原论文,有空再研究)。
在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行,即在不同的特征属性上采用多线程并行方式寻找最佳分割点。

为什么GBDT的树深度较RF通常都比较浅/gbdt与rf想要达到相同的效果,谁的参数更多

对于机器学习来说,泛化误差可以理解为两部分,分别是偏差(bias)和方差(variance);偏差指的是算法的期望预测与真实预测之间的偏差程度,反应了模型本身的拟合能力;方差度量了同等大小的训练集的变动导致学习性能的变化,刻画了数据扰动所导致的影响。当模型越复杂时,拟合的程度就越高,模型的训练偏差就越小;但此时如果换一组数据可能模型的变化就会很大,即模型的方差很大,所以模型过于复杂的时候会导致过拟合。对于RF来说由于并行训练很多不同的分类器的目的就是降低这个方差(variance)。所以对于每个基分类器来说,目标就是如何降低这个偏差(bias),所以我们会采用深度很深甚至不剪枝的决策树。而对于GBDT来说由于利用的是残差逼近的方式,即在上一轮的基础上更加拟合原数据,所以可以保证偏差(bias),所以对于每个基分类器来说,问题就在于如何选择 variance 更小的分类器,即更简单的分类器,所以我们选择了深度很浅的决策树。
上面一段可能有点绕。首先来回答第二种问法的问题吧。答案是:RandomForest的参数更多。
为什么呢?
泛化误差分为两部分,偏差和方差。偏差是指模型的拟合能力,即拟合的好不好(训练误差);方差则是指模型的泛化性能,即防过拟合能力/抗数据扰动能力强不强(测试误差)。
我们用单个基分类器做训练和测试时,这两个误差难以两全。因为要想拟合的好,参数搞多、树搞深,但是参数多了,又容易过拟合。
我们用集成学习(ensemble),就是为了解决这个问题,在偏差和方差之间获得两全。
对于RF(Bagging),它每棵树的任务就是尽可能的拟合得更好,于是它的每棵树都是低偏差的,树深,于是高方差,而Bagging了很多树,最后简单投票和平均一下,就可以降低方差,于是整体保证了低偏差低方差。
对于GBDT(Boosting),它每棵树的任务是拟合一部分的数据,于是它每棵树都是高偏差,低方差的(树相对浅,参数量少),而Boosting解决了偏差问题,最后整体也保证了低偏差低方差。
所以GBDT的基分类器树浅于RF。同样效果下,RF参数更多。

冷门知识

多变量决策树

我们平常使用的都是单变量决策树,即每次只选取一个特征来做分类。其对应的分类边界都是垂直于坐标轴的直线。

而如果使用多变量决策树,即每次可以选取多个特征来做分类。其对应的分类边界就可以是曲线了。这样,非叶节点不再是对某个特征,而是对特征的线性组合做测试。换言之,每个非叶节点就变成了一个个线性分类器。而多变量决策树的学习过程则是试图建立一个合适的线性分类器。

Reference

https://www.cnblogs.com/luban/p/9412339.html
一篇很好的讲GBDT的博客
又一篇很好的讲GBDT的博客
https://blog.csdn.net/zhangbaoanhadoop/article/details/82193954 面试问题汇总
https://blog.csdn.net/anshuai_aw1/article/details/85093106

posted @ 2019-03-22 11:00  大胖子球花  阅读(760)  评论(0)    收藏  举报