决策树

前言

这篇文章主要讲述一下决策树的基本算法,不要以为简单,其实呀!决策树这个算法说起来很简单,思路也很简单明了。但是如果你深入了解一下,里面的内容也相当的丰富,能细讲的也很多。这一次我就以我最近复习的内容和面试时遇到的一些问题为线索,一一来解析决策树里面深入一点的东西。后续我会接着把决策树这一块的算法全部讲完,从决策树到Random Forest, GBDT,XGBOOST。当然由于我的水平也十分有限,写文章的时候一般不怎么喜欢翻书全是自己大脑里面理解的一些东西(其实是我嫌翻书太麻烦),很多内容可能和你所看到的书上的内容有所不同,当然也会存在一些错误。望指正!

正文

分类树

说到决策树我们首先会想到的是classification tree这是决策树用到最广的一个方面,这一块的工作讲起来也很容易理解。我也不打算在这里花费大量的时间,毕竟太简单的东西讲起来没什么意义。我主要提几点大家可能容易忽视掉的,没有深入想的点。把这些点搞清楚,一般的分类决策树基本不会有很大的问题。

分类决策树的核心思想就是在一个数据集中找到一个最优特征,然后从这个特征的选值中找一个最优候选值(这段话稍后解释),根据这个最优候选值将数据集分为两个子数据集,然后递归上述操作,直到满足指定条件为止。

上述这段话是我对分类决策树的一个简单概述,里面有几个需要注意的点:

1.最优特征怎么找?这个问题其实就是决策树的一个核心问题了。我们常用的方法是更具信息增益或者信息增益率来寻找最优特征,信息增益这东西怎么理解呢!搞清这个概念我们首先需要明白熵这个东西!熵简单的讲就是说我们做一件事需要的代价,代价越高肯定就越不好了。放到机器学习的数据集中来讲就是我们数据的不确定性,代价越高对应的不确定就越高,我们用决策树算法的目的就是利用数据的一些规则来尽可能的降低数据集的不确定性。好了,有了这个思想我们要做的事就很简单了,给定一批数据集,我们可以很容易得到它的不确定性(熵),然后呢!我们要想办法降低这个不确定性,我们需要挖掘数据集中有用的特征,在某个特征的限制下,我们又能得到数据集的不确定性(这个其实就是书上的条件熵),一般而言给定了一个有用的特征,数据的不确定性肯定降低了(因为有一个条件约束,比没有条件约束效果肯定会好一点,当然你的特征没有用,那就另说了)。我们用两次的值作差,这个结果的含义很明了,给定了这个特征,让我们数据集的不确定性降低了多少,当然降低的越多这个特征肯定就越好了。而我们讲了这么多就是要找到那一个让数据集不确定性降低最多的特征。我们认为这个特征是当前特征中最好的一个。

2.我们找到了最优特征,为什么还要找最优特征的最优候选值?其实呀,找不找主要看我们面对的问题,一般的二分类问题确实没必要找(因为总共就两个类),但对于多分类问题,这个还是建议找,为什么要找呢?我们来分析一下:假如我们的某个最优特征有三个类别:我们如果不找就直接分为三个子节点了。这样会出现一个问题,就是我们的这个分类对特征值会变得敏感,为什么这么说,我们来讲一个很简答的例子:我们平时考试规定了60分及格,这个控制对于大多数学生很好把控,因为就一个条件,相当于一个二分类。但是如果我们把条件控制严格一些,比如超过60分,不超过80分为优秀学生(当然有点扯蛋了)。这个控制很多学霸就不好控制了,对于多分类问题也是这个道理,如果我们一下子从父节点直接分了多个子节点,那么我们的数据肯定会对这个控制很敏感,敏感就会导致出错。出错不是我们希望看到的,所以我们建议对多分类问题找最优候选值来转化为二分类问题,同样多个二分类问题其实也是一个多分类问题,只是多了几个递归过程而已。
3.什么时候停止?停止条件是什么?这个问题其实书上也讲得很简单,基本都是一笔带过的,我来稍微详细说一下:我们从问题的根源出发去想一下,我们构造一颗决策树的目的是什么?当然是为了能在数据集上取得最好的分类效果,很好这就是一个停止标准呀!当我们检测到数据的分类效果已经够好了,我们其实就可以停止了。当然我们常用的是控制叶节点,比如控制叶节点的样本数目,比如当某个子节点内样本数目小于某一个指定值,我们就决定不再分了。还有叶节点的纯度,我们规定叶节点样本必须属于同一类才停止,这也是一个停止条件。还有最大树的深度,比如我们规定树的最大深度为某一个值,当树深度到达这个值我们也要停止。还有比如:分裂次数,最大特征数等等。总之停止条件不是死的,我们可以更具自己的问题来随意控制,开心就好!

讲完上面几个问题,基本分类决策树的思想应该差不多了。这里为了阅读方便我还是帖几个公式仅供参考:
熵:$entropy(D) = -\sum_{i=1}^n P_i*log_2 P_i$
其中D为数据集,i为数据集D的可能分类标签,$P_i$为该标签的概率
条件熵:$entropy(D,A) = \sum_{i=1}^k \frac {D_{A_i}}{D} * log_2D_{A_i}$
其中A表示约束特征,k表示A特征的种类
信息增益:$gain(D,A) = entropy(D) - entropy(D,A)$
信息增益率: $gain_rate(D,A) = gain(D,A)/entropy(D,A)$

这里我们对于最基本的决策树做了简单的描述,对其中的几个可能理解不深刻的点进行了详细的说明,当然很多人觉得决策树学的差不多了。确实,决策树的思想就是这么简单,但是接下来的内容才是决策树的核心内容。首先介绍两个决策树的核心算法ID3和C4.5

ID3算法其实就是我们一般所理解的决策树算法,其基本步骤就是我们上面所讲的步骤,这个算法的核心思想就是用信息增益来选择最优分类特征,信息增益的公式上面也给出了,这个公式我们仔细分析一下会发现一个问题:我们需要找到gain(D,A)最大的特征,对于一个数据集entropy(D)是给定的,也就是说我们需要entropy(D,A)最小,意思就是我们所选的特征是那些分完后子节点的纯度最高的特征,什么样的特征分完后子节点的特征纯度比较高(熵比较小),该特征的子类别很多,即可取值很多的这一类特征。总结一下就是信息增益偏向于去那些拥有很多子类的特征。这也是这个算法的一大致命缺点,任何带有主观偏向性的算法都不是一个好的算法,当然ID3算法的另一个缺点也显而易见,它只能处理那些分类的特征,对于连续值特征毫无办法(其实我们可以人为的把连续属性给离散化,但是人为必然会导致可能不准确)。因此就有了下面的这个算法

C4.5是对ID3算法的一个改进,主要改进点就是解决了ID3的几个缺点。首先C4.5算法用的是信息增益率来代替ID3中的信息增益。从表达式可以看出来,这么做其实就是弱化那些偏向,让选择最优特征时更加公平。
另外C4.5的改进就是可以处理连续值(这个方法其实和上面我提到的连续值离散化很类似,可以理解为单点逐一离散化),具体步骤如下:
1.首先对于连续值属性的值进行排序(A1,A2......An);
2.我们可以在每两个值之间取一个点,用这个点就可以把该组连续值分为两部分,比如我们去一个点a1($A1<a1<A2$),则小于a1的部分(A1)大于a1的部分(A2......An)。但是这个a1好吗?不知道呀!那我们loop一下所有这样的a(共有n-1个),每一个ai我们可以算出分裂前后的信息增益率,然后我们求一个max即可。很简单吧!思路很好,但是呀很显然有个问题,时间开销有点大。
3.缺失值的处理,ID3不能直接处理(需要我们人工处理,比如单独赋值或赋一个平均值),C4.5给出了一个很优雅的方式,为什么说优雅,这样讲吧!我们每一个特征的取值都有若干个,根据训练集每个可能的取值都有一个概率,我们用这个概率来表示这个确实值得可能取值。这样就显得很合理了。

C4.5各方面完胜ID3,所以C4.5一出现就被广泛应用,后面为了解决这个算法的时间开销问题,推出了这个算法的改进版C5.0。国外有一篇比较C4.5和C5.0性能的blog写的很好,可以搜一下。大体上C5.0的速度是C4.5的10倍以上。

我们讲了这么多其实都没逃脱classification tree。我们还有很大一块问题没有讲,那就是regression。这里我们在来讲讲regression tree。讲到regression tree我们就不能不提大名鼎鼎的CART(classification and regression tree)树了,这个才是最为核心的决策树。为什么这么说,因为呀我们以后使用到的random forest,gbdt, xgboost里面的base estimator 都是CART树。所以这个东西我来认真讲讲。

CART树

CART树既可以用于分类问题也可以用于回归问题,用于分类问题的思想和我们上面介绍的ID3,C4.5其实是一样的,唯一的不同就是CART树用的是基尼指数来确定最优划分点的。
基尼指数: $gini(D) = \sum_{i=1}^n p_k *(1-p_k)$
基尼指数的通俗解释就是:表示一件事物的不确定性,基尼指数越大不确定性越大。我们要找基尼指数小的特征,这样的特征对于划分数据集的准确性会更高(不确定性低嘛)
类似的有一个条件基尼指数:$gini(D,A) = \sum_{i=1}^k \frac {D_{A_i}}{D} *gini(D_{A_i})$
整体思路跟信息增益一样,我就不浪费时间了。

对于回归问题:首先简单描述一下决策树处理回归问题的流程:对于一个数据集我们可以将其分为m个子区间(R1,R2......Rm)对于每一区间我们可以产生一个对应的输出cm.我们的最终输出$f(x)=\sum_{i=1}^mc_mI(x \in R_m)$.对于一个给定的回归树我们用平方误差来表示每个单元的损失$\sum_{x_i \in R_m}(y_i-f(x_i))^2$,那么我们每个单元的最优输出就是使该单元的损失函数最小。每个单元的最终输出可以表示为$C = avg(y_i|x_i)(x_i \in R_m)$(区间$R_m$ 上所有$x_i$ 的输出$y_i$的均值)

对于回归问题,我们面临的问题也是如何确定划分点(决策树的核心)。这里CART树的处理方式和C4.5处理连续变量的方式有点类似,即对于每个特征的取值,我们从中找一个点j,这个点j可以将该特征分为两部分$R_1 = ({ x|x_i <j })$和$R_2=(x|x_i>j)$.我们的目标是使切分后的损失函数最小,即:
$$min { min (y_i - c_i)^2(x_i \in R_1) + min (y_i - c_i)^2(x_i \in R_2)} $$
我们loop一次该特征的所有取值,一定能找到一个使上式最小的j,这个j就是最优切分点,对应的特征就是最优特征(当然这个特征其实也需要loop一次才能找到),我们将数据集分为两部分,然后递归直到停止。

这里我们必须强调一下,我们在使用决策树寻找每一步的最优切分点时,常用的是贪心算法,贪心算法有一个问题就是局部最优,而不是全局最优。所以我们一定要记住,决策树在选择特征及切分点时考虑的是一个局部最优问题。

好了!上面基本就是决策树最常用的三个算法,我们先介绍了分类树及分类树中两个经典算法ID3和C4.5,然后我们又介绍了回归树CART树,这个树在目前是主流的决策树,使用很广泛,必须十分熟悉其中的一些关键问题。

剪枝

我们现在有了算法,可以生成一棵决策树,但是这可决策树对于未知数据能获得到很好的性能吗?出现过拟合该怎么办?这些问题我们其实还没有解决。对于任何一个机器学习问题,我们不光要找到一个好的算法,还要保证这个算法有很强的鲁棒性,也就是泛化能力要很强。接下来我们讲一讲决策树处理过拟合保证树的泛化能力的方法,决策树的剪枝。
我们首先看一下我们十分熟悉的机器学习损失函数表达式$f(x)=f_1(x) + \epsilon J(x)$
其中$f_1(x)$是模型的损失函数,$J(x)$是模型的复杂度。
一个机器学习模型的最终损失函数就是要既保证模型对数据集的损失函数最小,同时模型复杂度要低,但显然这两个之间是矛盾的,道理很简单,模型越复杂意味着约束规则越多,分类效果肯定会更好,$f_1(x)$自然会更小,但是$J(x)$就会相应较大。而参数$\epsilon$就是控制两个之间的均衡。我们所能做到的就是在两者之间保持均衡。这些问题可以看看有关bias-variance的相关知识(理解这一点对学习机器学习算法很重要)。
接下来我们来看一下决策树的整体损失函数:
$$C(x) =C_a(x) + \epsilon |T| $$
这个损失函数跟机器学习的损失函数很像,前一部分是树的损失函数,后部分是树模型的复杂度,$\epsilon$是一个控制参数
而$C_a(x) = \sum_{t=1}^k N_t H(t)$这个式子的$N_t$表示树的叶节点数,$H(t)$是树的叶节点的熵。整体含义就是表示每个叶节点的不确定性。在回过头来看树的损失函数就是:树整体的不确定性加上树模型的复杂度。我们的优化目标就是保证树的整体不确定性尽量低,同时树模型的复杂度也要低。

有了这个思路我们在来分析如何降低树整体的损失函数:
1.减少第一部分树的不确定性:我们可以增加一些特征,增加特征,意味着约束条件变强了,决策树的分类效果自然而然的会变好。但是对于给定的数据集我们是很难再继续添加新特征了,所以这个方式虽然可取但不可行。
2.减少第二部分树的整体复杂度:我们需要对生成的树进行剪枝。剪枝完后树的规模剪小了,但是树整体的不确定性可能会增大,这时我们就要寻找一个比较优雅的剪枝策略,在降低树模型复杂度的情况下,尽量小的增加树的不确定性。

最简单的剪枝策略的介绍可以看看西瓜书上的介绍,分为预剪枝和后剪枝,书上的介绍过于简单,没有深究。只是给我们提供了一个树剪枝的思路,这里我就不再赘述。如果尚不清楚的,可以翻一翻书。我这里主要讲的是CART树的剪枝策略,这个算法在《统计学习方法》中有一定篇幅的介绍,但感觉还是不够细,光看这本书上的介绍,想要完全弄清楚也是比较难,这里我参考了几篇论文和blog的介绍结合自己的理解来详细讲一讲CART树的剪枝。
我们首先还是来看一下决策树的损失函数:$$C(x) =\sum_{t=1}^k N_t H(t) + \epsilon |T| $$
对于参数$\epsilon$,当其为0,意味着树的损失函数即为树整体的不确定性,当$\epsilon$无穷大时,树的规模就必须很小,此时的树即为根节点一个点。这样的树就是我们所谓的decision tree stump(决策树桩).而对于CART树的剪枝过程,一般情况使用的是后剪枝法,即先生成一颗足够大的树,然后在这个树上进行适当剪枝,以提高树的泛化能力.
假设我们已经生成了一颗足够大的树,我们接下来需要对树进行剪枝,具体怎么做.首先我们假设我们对树进行了一次剪枝,得到了一颗子树T1,这个树一定对于一个唯一的参数$\epsilon$,但这棵子树是不是最好子树我们并不能确定,我们首先是一个随机剪的过程,对于这个随机剪的过程,我们可以生成大量的子树,到底那一颗子树才是最优子树T1,我们需要进行数学上的定量计算.假设剪枝节点为t.
剪枝前: $C_\alpha(T) = C(T) + \epsilon |T|$
剪枝后: $C_\alpha(T_t) = C(T_t) + \epsilon|t|$(其中t为剪枝后的叶节点,其规模为1)
上述两个式子中,已知的变量为$C(T),C(T_t),|T|,|t|$,未知的变量只剩$\epsilon$,我们假设$\epsilon$从0开始增大,当$\epsilon$为0时,这个时候由于剪枝前树的损失函数肯定小于剪枝后,所以有$C_\alpha(T) < C_\alpha(T_t)$,当$\epsilon$缓慢增大时,由于|T|>|t|所以$C_\alpha(T)$增长速度更快,那么一定存在一个临界点使$C_\alpha(T)=C_\alpha(T_t)$.这个时候由于t的规模比T小所以,我们认为剪枝对树整体的损失函数没有影响,所以进行剪枝.我们可以用$g(t) = \frac{C(t)-C(T)}{|T|-1}$来衡量剪枝的整体损失函数的减少程度,剪枝后整体损失函数减少的越小,我们认为这个剪枝越好.
有了上述思路,我们第一次随机选一个点剪枝,每次选则不同的点都能得到一个子树T和对应的参数$\epsilon$,我们在所有剪枝中找到一个最小的g(t),其对应的剪枝点即为第一次剪枝的最优剪枝点,对应的子树即为第一次剪枝的最优子树T1,我们然后对子树T1递归的按照上述思路剪枝下去,直到到达根节点位置,这样我们就能找到一个最优子树序列{T1,T2,......Tn}以及各个最优子树对应的参数$\epsilon$.
但是这个序列中到底哪一个才是我们最终剪枝完成的所得到的最优子树呢?这时我们需要用一批验证数据来分别对这个最优子树序列的每一个子树进行验证,找到相对于剪之前树的整体损失函数减少的最小的那个Ti即为我们对这棵树进行剪枝后得到的最优子树了.(验证方式对于大规模数据一般用trian_test_split,小规模数据用cross_validation).
上述过程就是CART树的剪枝过程,这个是Breiman的经典版本,后续也有其他一些剪枝算法,有兴趣可以参考相关论文.

总结

讲到这里,决策树的整个流程基本讲的差不多了.从分类树到回归树再到CART树,从ID3到C4.5.基本常见的决策树基础算法,都有所涉及,这些都是我个人对于决策树的理解,肯定也存在一些错误,望交流指正.接下来我还会继续写关于Random Forest,gbdt,xgboost的相关文章,喜欢的话可以关注一下!

posted @ 2017-10-18 09:32  OverFitting  阅读(58608)  评论(2编辑  收藏  举报