之前在博文 决策树归纳 中,我介绍了用决策树进行分类的算法,包括ID3和C4.5。然而决策树不仅可以用来做数据分类,也可用于做数据回归。1984年Breiman,Friedman,Olshen等人出版了著作《Classification and Regression Trees》(简称CART)介绍了二叉决策树的产生。他们给出了用二叉决策进行树数据分类和回归的方法。
在阅读本文之前,我假设读者已经了解了我之前写的“决策树归纳”中的知识点,包括决策树的基本构建方法,以及相关术语。然后我会分别介绍用于分类和回归两类计算任务的CART。
用于分类的CART的构建方法与之前ID3和C4.5算法是非常类似的,都是采用贪心策略,自顶而下分治地构建树。只不过此处判断分裂后子集元素“纯”度的标准不是ID3中用的信息增益,也不是C4.5中用的增益率,而是用基尼指数。
基尼指数按照如下的计算方式度量一个训练集 D D 的不纯度:
其中 pi p i 是某个元组属于类 Ci C i 的概率,可以用 |Ci||D| | C i | | D | 估计。而经过分裂之后,可以依据新得到的子节点,按照如下公式计算分裂后的基尼指数。
其中 k k 为分裂后的子节点的个数, A A 是用于分裂的属性(即分裂准则)。以二叉决策树为例,就是:
这样,对于一个节点中包含的数据集 D D ,我们的目标是去寻找最合适的分裂准则以及分裂值,使得经过分裂之后的基尼指数与分裂之前的基尼指数之差最大。这个差值如下计算得到:
所以,CART作为分类树的算法步骤如下:
CART的构建本来到此就算是结束了。但是所有的决策树构建算法(包括我之前介绍的ID3和C4.5)都有可能存在过拟合的问题(尤其是训练数据存在一些噪声和离群点时)。虽然可以通过阈值控制终止条件,避免树形结构分支过细,还有随机森林这样一些方法防止过拟合,但是一般情况下最常用的还是剪枝。剪枝的操作我在之前介绍ID3和C4.5的时候并没有讲到,但它是决策树算法中非常关键的一环,是不可以跳过的。所以我放到本文中也算是对之前文章的一个补充。
如果我们先建好决策树再进行剪枝(即所谓的后剪枝),那需要在初始构建决策树时让树完全增长直到所有的叶子节点都是纯的且具有零训练误差。然后找到过拟合的子树并剪掉它们。同时,剪枝导致决策树高度更低,分支更少,这也在一定程度上提高了数据分类(回归)的速度。
如果一棵子树需要被剪枝,那么剪枝之后这棵子树就变成了一个叶子节点,其类标记为原先子树的元组中出现最多次的类标记。比如下图中的左图,假如要把 A3 A 3 分裂点为根的子树剪枝,而这棵子树中的数据类标号最多的是class B,那么剪枝后的决策树就变成下图中的右图模样。
现在剩下的问题就是在什么情况下需要对决策树的某个子树剪枝。简单想想就能想到,是否剪枝的评估标准肯定是子树的误差和子树复杂度(实际上就是叶子节点数)之间的平衡关系。根据这个原理,我们可以定义子树的损失函数:
其中 E(τ) E ( τ ) 为子树的预测误差(比如用基尼指数计算), λ λ 为罚因子, |τ| | τ | 为子树的叶子节点数,相当于是对子树复杂度的评估。这个函数其实跟我之前介绍损失函数时提到的结构风险最小损失函数是一致的(可以参考博文 SVM解释:四、线性不可分的情况),都是“误差+惩罚复杂度”
现在我们设最初得到的决策树为 τ0 τ 0 ,那么对于 τ0 τ 0 的任意内部节点(记为 c c )可以计算它的单节点损失函数:
也可以计算得到以 c c 为根的子树的损失函数:
由此我们不难得到:当 λ λ 充分小时, J(c)>J(τc) J ( c ) > J ( τ c ) ,随着 λ λ 增大, J(c) J ( c ) 和 J(τc) J ( τ c ) 之间的大小关系不断接近,当 λ λ 大到一定程度时, J(c)=J(τc) J ( c ) = J ( τ c ) ,随着 λ λ 继续增大,最终 J(c)<J(τc) J ( c ) < J ( τ c ) .
所以,如果找到了使得 J(c)=J(τc) J ( c ) = J ( τ c ) 的 λ λ ,那此时单节点和子树的损失函数一致,单节点却结构更加简单,我们就选择剪枝。由公式(2),(3),我们可以推出 J(c)=J(τc) J ( c ) = J ( τ c ) 时的 λ λ 为:
上式中的 λ λ 实际上是罚因子的阈值。因此,我们可以对于 τ0 τ 0 中的每个内部节点 c c ,计算他们的罚因子阈值。我们从小到大排列每个内部节点的罚因子阈值。先找到罚因子阈值最小的节点 c c ,将以 c c 为根的子树剪枝,剪枝之后的树记为 τ1 τ 1 ,那么 τ1 τ 1 就是罚因子在区间 [0,λ1) [ 0 , λ 1 ) 内的最优决策树。以此类推,再对罚因子阈值次最小的节点 c′ c ′ 剪枝,得到 λ2 λ 2 和 τ2 τ 2 , τ2 τ 2 是 [λ1,λ2) [ λ 1 , λ 2 ) 之间的最优决策树。。。直到剩下根节点单独存在,也就是不能再剪枝了。最终,假设一共得到 n+1 n + 1 个最优决策树 {τ0,τ1,τ2,…,τn} { τ 0 , τ 1 , τ 2 , … , τ n } ,每个最优树对应着一个罚因子区间,其中 τ0 τ 0 为初始决策树, τn τ n 为只有单个节点的树。
接下来,我们利用独立的验证数据集,测试子树序列 {τ1,τ2,…,τn} { τ 1 , τ 2 , … , τ n } 中各棵子树的基尼指数或平方误差(平方误差是用于分类的CART的误差计算标准)。平方误差或基尼指数最小的决策树被认为是最优的决策树。
综上所述,我们可以这样描述决策树剪枝的过程:
设初始决策树为 τ0 τ 0 , k=0 k = 0 , λ=+∞ λ = + ∞
自下而上,依次计算初始决策树的每个内部节点的罚因子阈值,计算公式如下:
自上而下,依次检查内部节点,若某节点 c c 满足 g(c)=λk g ( c ) = λ k (也就是说这个节点在本轮迭代中是罚因子阈值最小的),那么对 c c 为根的子树剪枝,剪枝后得到新的一棵树 τk τ k :
如果得到了只有单个节点的树,则结束迭代;否则,继续执行步骤2,3;
结束迭代后,得到一组最优决策树 {τ0,τ1,τ2,…,τn} { τ 0 , τ 1 , τ 2 , … , τ n } 。采用交叉验证法选择最优子树,选择的依据是分别计算这些子树的误差大小(比如基尼指数或平方误差),选择误差最小的子树为最终的剪枝结果。
训练回归树其实和训练分类树没有本质不同,也是自上而下,不断分裂节点的过程。不同之处在于对节点分裂的分裂准则和分裂值的选择方法上。我下面将一步步推导回归树的构建算法。
设有训练数据集 D={(X1,y1),(X2,y2),…,(Xn,yn)} D = { ( X 1 , y 1 ) , ( X 2 , y 2 ) , … , ( X n , y n ) } 。我们知道决策树最终将数据元组划分到了多个叶子节点中,比如分类树的每个叶子就有一个类标号,作为元组的预测类。回归树的思路类似,我将数据空间划分成了 m m 个区域 {R1,R2,…,Rm} { R 1 , R 2 , … , R m } ,实际上就是将训练元组的分区。然后赋给每个区域有一个固定的代表值 Ci C i (类似于分类树中的类标号),这样,回归树的模型可以表示成公式(4)的形式:
其中 I(X∈Ri)=1 I ( X ∈ R i ) = 1 ,如果 X∈Ri X ∈ R i ;否则, I(X∈Ri)=0 I ( X ∈ R i ) = 0 。这么看来,公式(4)的含义是:先判断 X X 属于哪个区域,然后返回这个区域的代表值。
这样,我们可以写出对于某个区域 Ri R i 回归模型的损失函数:
其中, gi g i 为这个区域的代表值(这里用 gi g i 而不用 Ci C i 的原因是我要表达 gi g i 是分裂中间的某个区域的代表值,而 Ci C i 为最终完成分裂之后的叶子节点的代表值), gi g i 的计算方法一般是这个区域中的元组的 y y 值的均值(取均值时,损失函数达到最优)。
其中, Ni N i 为这个区域内数据元组的总数。初始时,所有的数据元组都在一个区域内,我们按照公式(5)计算损失函数,如果计算得到的误差值太大,不能满足要求,则寻找最佳的分裂准则(属性)和分裂值将数据集一分为二。这里的关键在于怎样的分裂准则和分裂值对于回归树来说是最佳的。其实很明显,“最佳”指我按照这样的属性和属性值将数据集一分为二后,使得分裂得到的两个子区域的误差和最小。
综上所述,回归树的计算步骤如下:
初始时,将所有训练元组放置于根节点中,计算损失函数得到误差值,倘若误差大于事先定好的参数,那么执行下面的2~4步;
选择用于分裂区域的属性 A A 和对应的属性值 s s ,将当前节点的数据元组划分到以下两个区域 R1,R2 R 1 , R 2 中。:
剪枝。