决策树是一种基本分类和和回归方法,本篇主要讨论分类决策树,主要从决策树的构造、决策树的修剪等方面进行介绍,本文主要参考《机器学习实战》、《统计学习方法》和网上的一些帖子,进行的总结学习。


分类算法 - 决策树

  • 1.概念
  • 2. 决策树的构造
  • 2.1 特征选择
  • 2.1.1 信息增益
  • 2.1.2 信息增益率
  • 2.1.3 基尼指数
  • 2.2 决策树的生成
  • 2.2.1 ID3算法
  • 2.2.2 C4.5算法
  • 2.2.3 CART算法
  • 2.3 剪枝
  • 2.3.1 预剪枝
  • 2.3.2 后剪枝
  • 3. 总结
  • 4. 参考


1.概念

基于java的决策树算法 决策树编程_基于java的决策树算法

决策树是一种通过抓取数据中所隐含的信息,通过最小化损失函数,递归创建规则用于决策的机器学习算法。决策树根据特征选择的计算方式不同,可以分为: ID3、C4.5和CART决策树。

2. 决策树的构造

决策树的构造是递归选择最优特征队训练数据进行分割,使得各子数据集都有一个最优分类直至每个叶节点属于同一分类,伪代码如下:

def create_decision_tree():
 if (每个叶节点中所有子项都属于同一分类):
  return 类标签
 else
  寻找最优的分裂特征
  划分数据集
  创建分支节点
   for 每个划分的子集:
     调用create_decision_tree()
  return 分支节点

因此构造决策主要是分为特征选择、决策树的生成,为防止过拟合,通常还会对决策树进行剪枝。

2.1 特征选择

划分数据集最大原则是:将无需的数据变更加有序,信息论中给出了多种划分数据及的方法,各自有自身的优缺点,主要是3种:信息增益、信息增益率和基尼指数。

2.1.1 信息增益

ID3决策树就是利用信息增益进行数据子集的分裂

【a. 信息熵】
在信息论中,信息量的定义为:
基于java的决策树算法 决策树编程_信息增益_02
信息量的定义保证了发生概率越低,带来的信息量越大并且两个独立事件同时发生带来的信息量是两个事件单独带来的信息量之和,即
基于java的决策树算法 决策树编程_信息增益_03
信息熵是随机变量不确定性或者说纯度的一个度量,是信息量期望值
基于java的决策树算法 决策树编程_决策树_04
如果系统越复杂,出现不同种类的越多,带来的信息越多,信息熵越大,只有一个种类时信息熵最小,等于0。

对于样本集合D来说,如果有K个类别基于java的决策树算法 决策树编程_信息熵_05,每个类别出现的概率为基于java的决策树算法 决策树编程_信息熵_06,对于样本集合D来说,信息熵为:
基于java的决策树算法 决策树编程_信息熵_07

【b. 条件熵】
基于java的决策树算法 决策树编程_基于java的决策树算法_08表示在已知随机变量X的条件下随机变量Y的不确定性,类似全概率公式,定义条件熵为
基于java的决策树算法 决策树编程_机器学习_09

【c. 信息增益】
信息增益衡量了得知特征A的信息而使得的集合D的信息不确定性减少的程度,定义为
基于java的决策树算法 决策树编程_信息熵_10

对于数据集D而言,信息增益依赖于特征,不同的特征往往具有不同的信息增益,信息增益大的特征具有更强的分类能力。

【信息增益算法流程】

  • 计算数据集D的信息熵H(D):
    基于java的决策树算法 决策树编程_信息熵_11
  • 计算特征特征A对数据集D的条件熵基于java的决策树算法 决策树编程_信息增益_12
    基于java的决策树算法 决策树编程_机器学习_13
  • 计算信息增益
    基于java的决策树算法 决策树编程_机器学习_14

【例子】

为了更好理解信息增益的计算过程,我们进行实例化。下表是《统计学习方法》中给的一个例子,内容为贷款申请样本数据表,类别有2种,有9个样本分类结果为“是”,6个样本结果为“否”,我们的目标是寻找到最优的分裂特征

基于java的决策树算法 决策树编程_信息熵_15


以特征基于java的决策树算法 决策树编程_信息熵_16年龄为例:

(1) 计算H(D)
基于java的决策树算法 决策树编程_基于java的决策树算法_17
(2) 计算条件熵,基于java的决策树算法 决策树编程_决策树_18表示每个年龄(青年、中年、老年)对应的样本集合
基于java的决策树算法 决策树编程_信息增益_19
(3) 计算信息增益
基于java的决策树算法 决策树编程_基于java的决策树算法_20

同样步骤计算其他几个特征的信息增益,得到
基于java的决策树算法 决策树编程_机器学习_21
基于java的决策树算法 决策树编程_基于java的决策树算法_22
基于java的决策树算法 决策树编程_信息增益_23

最后,比较各特征的信息增益,基于java的决策树算法 决策树编程_基于java的决策树算法_24的信息增益最大,因此选择基于java的决策树算法 决策树编程_基于java的决策树算法_24为分裂特征。

2.1.2 信息增益率

信息增益存在一个缺点,利用信息增益进行数据集划分会更倾向于取值更多的特征

考虑一种极端的情形,各样本的ID如果也作为一个特征时,通过ID进行分裂,每个叶子节点只有1个元素,信息增益最大,但这种划分方式是没有意义的。

为了避免出现这个情况,C4.5算法对信息增益算法进行的优化,在信息增益的基础上乘上一个惩罚,信息增益率的定义如下:
基于java的决策树算法 决策树编程_机器学习_26

上述例子中,以年龄为例
基于java的决策树算法 决策树编程_决策树_27

而信息增益率偏向取值较少的特征,基于信息增益和信息增益率的优缺点,一般在特征选择中,先找出信息增益高于平均水平的特征,侯然在这些特征中再选择信息增益率最高的特征

2.1.3 基尼指数

CART数采用基尼指数进行数据集划分。

基尼指数表示样本集合中一个随机选中的样本被错分的概率。对于一个集合而言,如果gini指数越小,意味着集合随机一个样本被误分的概率越小,集合的纯度越高。

基尼指数用于衡量被误分概率,定义为
基于java的决策树算法 决策树编程_基于java的决策树算法_28

基于java的决策树算法 决策树编程_信息熵_29表示选中样本属于k类的概率,那么被误分的概率为基于java的决策树算法 决策树编程_基于java的决策树算法_30

CART数为二叉树,使用某个特征进行分裂时只将样本划分为2个集合,划分后,计算集合的Gini指数,即
基于java的决策树算法 决策树编程_机器学习_31

还是上面的例子,如果以年龄为青年作为划分依据,

基于java的决策树算法 决策树编程_基于java的决策树算法_32


青年:基于java的决策树算法 决策树编程_基于java的决策树算法_33

非青年:基于java的决策树算法 决策树编程_决策树_34

基于java的决策树算法 决策树编程_信息熵_35

2.2 决策树的生成

2.2.1 ID3算法

ID3算法的核心是在决策树各节点上应用信息增益来进行特征选择,递归构建决策树:具体步骤如下:

  • 从根节点开始,计算所有可能的特征的信息增益
  • 选择信息增益最大的特征作为节点的特征,建立子节点
  • 对子节点递归采用上述步骤构建决策树,直到所有特征信息增益很小或没有特征可以选择为止

【存在问题】

  • 无法处理连续值
  • 无法处理空值
  • 偏向取值较多的特征
  • 可能会出现过拟合

对于无法处理连续值,可以将连续数据离散化,并采用二元切分法,大于给定值的样本进入左子树,否则走右子树,但这种处理会损失连续型变量所隐含的内部信息

2.2.2 C4.5算法

C4.5算法采用信息增益率做特征选择,上一小节对于ID3的不足,C4.5做了一定的优化。

连续值:

  • 先对连续特征的取值进行排序
  • 两个特征取值之间的中点作为可能的分裂点,将数据集分成两部分,计算信息增益(计算量很大时,改进方法为:只有在分类结果发生变化的特征值上计算该点的信息增益)
  • 以增益最大的特征值作为该特征的分裂点,计算信息增益率

缺失值:
C4.5是通过概率权重的方式来处理的,对于有缺失值的属性,其信息增益是无缺失样本占比乘以无缺失值样本子集的信息增益。

对于数据集D,假设其特征A无缺失样本基于java的决策树算法 决策树编程_信息熵_36占比为基于java的决策树算法 决策树编程_信息熵_37,那么该属性的信息增益为:基于java的决策树算法 决策树编程_信息增益_38

例子: 计算含有缺失值特征“色泽”的信息增益

基于java的决策树算法 决策树编程_决策树_39


基于java的决策树算法 决策树编程_信息增益_40

对于含有缺失值的样本,会全部进入到各分支中,权重调整为无缺失样本占总样本数的比例。

参考上例,“纹理”的信息增益最大,因此将纹理作为切分属性,划分结果为:“纹理=稍糊”分支:{7,9,13,14,17},“纹理=清晰”分支:{1,2,3,4,5,6,15},“纹理=模糊”分支:{11,12,16}。

基于java的决策树算法 决策树编程_信息熵_41

对于含有缺失值的样本8和样本10,划分结果如下:


基于java的决策树算法 决策树编程_基于java的决策树算法_42


再根据各节点划分情况继续向下划分,但此时样本8、10的权重更新为5/15。由于权重有变化,给出计算为例稍糊节点的划分过程

基于java的决策树算法 决策树编程_基于java的决策树算法_43


其他特征的计算同理,属性“敲声”的增益值最大,因此选择“敲声”作为划分属性,划分后的决策树如下图所示:


基于java的决策树算法 决策树编程_基于java的决策树算法_44

对于测试样本含有缺失值,计算方式为同时探查所有分支,然后计算每个类别的概率,取概率最大的类别,如下面这个例子

例子:含有缺失值的测试样本
训练样本如下,样本12第一个属性含有缺失值

基于java的决策树算法 决策树编程_信息增益_45


构造好的决策树如下,括号内为样本12权重

基于java的决策树算法 决策树编程_决策树_46


测试样本为:outlook=sunny, temperature=70, humidity=?, windy=false


由于outlook走最左边


yes(play):2.0/5.38 * 100% + 3.38/5.38 * 11.3% = 44.27%


no(don’t play): 3.38/5.38 * 88.7% = 55.73%


因此no的概率更大,所以该测试样本的类别被指派为no,即don’t play。

【偏向取值较多的特征】
由于C4.5分裂采用信息增益率,有效改进偏向取值较多的特征的问题。

【过拟合】
C4.5引入了正则化系数进行初步剪枝,后面会讲,此处暂时不讨论。

处理连续值案例:
C4.5决策树分裂详解(离散属性和连续属性) 处理缺失值值案例:
决策树(decision tree)(四)——缺失值处理

2.2.3 CART算法

CART算法实际上也是在C4.5的基础上进行改进:

  • CART使用二叉树代替C4.5多叉树,提高了树的生成效率
  • CART既能用于分类又能用于回归
  • CART使用gini系数作为不纯度的依据,减少了大量的对数运算
  • CART采用代理测试来预估缺失值,C4.5以不同权重的方式划分到不同节点中
  • CART采用基于代价复杂度剪枝,C4.5采用悲观剪枝
  • ID3 和 C4.5 层级之间只使用一次特征,CART 可多次重复使用特征

缺失值
对于缺失值,cart和c4.5的处理方式类似,缺失特征的gini是在非缺失数据上先划分然后根据非缺失值的比例前面乘上相应系数,降低存在缺失值特征的gini然后和其它特征分裂之后的gini增益进行比较。

如果缺失特征恰好是gini增益最大的特征,那么要在有缺失值的特征上分裂,需要采用代理特征分裂(surrogate splits),分为两种情况:

  • 情况1: 遍历剩余特征,在没有缺失值的特征上选择增益最大的特征进行分裂
  • 情况2: 事先设置了一定的标准仅仅选择仅仅选择差异性在一定范围内的特征作为代理特征,如果依旧没有满足的特征,缺失样本默认进入个数最大的叶子节点

显然这种缺失值的处理方式的计算量是非常大的,我们需要遍历其它的特征来进行代理特征选择,这个在数据量很大的情况下开销太大,而带来的性能提升确很有限,所以后来就不怎么用这种处理方式,xgb和lgb中就是直接将缺失值划分到增益大的节点里,这样在处理上要快速的多,而且在gbm的框架下一点点的误差其实影响不大。

2.3 剪枝

为防止过拟合,决策树需要进行剪枝。

2.3.1 预剪枝

预剪枝,是在节点的划分前通过计算判断是否需要进行继续增长的剪枝策略,是一种贪心策略,主要方法有:

  • 节点内样本数量低于某一阈值
  • 所有节点特征均已分裂
  • 节点划分前准确率比划分后准确率更高

由于是基于贪心策略,很可能会造成欠拟合。

2.3.2 后剪枝

在已经生成的决策树上进行剪枝,得到简化版的决策树。后剪枝欠拟合的风险不大,而泛化性能往往优于预剪枝,但是训练时间会更长。

其中后剪枝有以下几种常用的方法:错误率降低剪枝、悲观错误剪枝、代价复杂度剪枝、基于错误的剪枝

Reduced-Error Pruning(REP,错误率降低剪枝)
错误率降低剪枝是最简单的后剪枝方法之一,其思路是当树生成后,从下往上将子树替换为其叶节点,类别按叶节点最多的类,计算剪枝前后的错误率,判断是否剪枝

例如节点5,将子树替换成类别最多的类别作为节点,即“好瓜”,观察剪枝后的精度或错误率,决定是否需要剪枝。

基于java的决策树算法 决策树编程_决策树_47

缺点:由于使用独立的验证集,如果训练集中存在未在测试集中出现的稀少训练集样本,可能会导致过度修剪
[ML]降低错误率剪枝算法 Reduce Error Pruning

Pesimistic-Error Pruning(PEP,悲观错误剪枝)
悲观错误剪枝也是根据剪枝前后的错误率来决定是否剪枝,和REP不同的是,PEP不需要使用验证样本,并且PEP是自上而下剪枝的

对于每个节点,剪枝后错分率一定是会上升的,因此在计算错分率时需要加上一个经验性的惩罚因子

对于一颗叶子节点,它覆盖了N个样本,其中有E个错误,惩罚因子为0.5,那么该叶子节点的错误率为(E+0.5)/N,那么对于一颗子树,其错误的计算公式为:(分数线不知道为什么显示不出来)
基于java的决策树算法 决策树编程_信息熵_48

悲观剪枝引入了统计学置信区间的思想,由于误判次数满足伯努利分布,误判次数的期望和标准差如下:
基于java的决策树算法 决策树编程_信息增益_49
基于java的决策树算法 决策树编程_信息熵_50

我们利用一倍标准差对误判次数的范围进行预估,如果剪枝后的误判次数比预估区间最小值还要小,那么就执行剪枝操作,即
E(err_cnt) - std(err_cnt) > E(leaf_err_cnt)

例子:以T4节点为例

基于java的决策树算法 决策树编程_基于java的决策树算法_51


基于java的决策树算法 决策树编程_决策树_52


由于6+2.05>7,因此根据PEP判断节点T4需要剪枝。


模型算法基础——决策树剪枝算法(二)

Cost-Complexity Pruning(CCP,代价复杂度剪枝)
这种方法会生成一系列的树,每棵树都是通过前面的树都是通过前面树的某个或某些子树替换为一个叶节点而得到的,具体介绍如下:

首先我们将最大树称为基于java的决策树算法 决策树编程_信息熵_53, T为任意子树,C(T)为预测误差,|T|为子树叶子节点个数,基于java的决策树算法 决策树编程_基于java的决策树算法_54为参数,我们希望减少树的大小来防止过拟合,同时不希望减少节点带来误差率上升太多,于是定义损失函数为
基于java的决策树算法 决策树编程_信息熵_55
按照上述定义,对于节点t,对其剪枝后的损失函数为
基于java的决策树算法 决策树编程_决策树_56
对于每一个固定的基于java的决策树算法 决策树编程_基于java的决策树算法_54,我们都能找到最优的子树基于java的决策树算法 决策树编程_信息增益_58,随着基于java的决策树算法 决策树编程_基于java的决策树算法_54的增大,可以得到一系列的子树,基于java的决策树算法 决策树编程_决策树_60

Breiman 证明1:将 基于java的决策树算法 决策树编程_基于java的决策树算法_54从小增大,在每个区间 [基于java的决策树算法 决策树编程_决策树_62中,子树,基于java的决策树算法 决策树编程_机器学习_63是这个区间里最优的,这也是代价复杂度剪枝的核心思想。

Breiman 证明2: 一定存在一个 基于java的决策树算法 决策树编程_基于java的决策树算法_54,使得基于java的决策树算法 决策树编程_信息增益_65,此时基于java的决策树算法 决策树编程_决策树_66

由以上两个证明可以得到,当 基于java的决策树算法 决策树编程_基于java的决策树算法_54大于这个值时,一定有基于java的决策树算法 决策树编程_基于java的决策树算法_68,那么此时剪枝更优。

剪枝流程如下

基于java的决策树算法 决策树编程_信息熵_69


例子:下图为一棵子树,总数据量为40,误判情况:node3、node3、node3误判个数分别为1、4、1,剪枝后误判数为8个


基于java的决策树算法 决策树编程_决策树_70


基于java的决策树算法 决策树编程_基于java的决策树算法_71


求出其他子树的表面误差率增益值就可以对决策树进行剪枝。


机器学习CART及CCP剪枝原理

EBP(Error-Based Pruning)(基于错误的剪枝)
参考:决策树-剪枝算法(二)

C4.5采用PEP剪枝,CART算法采用CCP剪枝

3. 总结

基于java的决策树算法 决策树编程_机器学习_72

4. 参考

1.决策树—ID3、C4.5、CART 2. 决策树–信息增益,信息增益比,Geni指数的理解 3. 决策树 ID3 Gini详细分析 4. 决策树 ID3 C4.5 cart 总结 5. 决策树(上)——ID3、C4.5、CART(非常详细)