决策树——ID3、C4.5、CART

本篇开始总结一下以决策树为基础的模型,当然本篇的内容就是决策树了,决策树可以用来分类也可以用来回归,用作分类的应该更多一些,我们也先从分类问题讲起。

1 决策树思想

决策树的分类思想很好理解,就是很多个if-then规则的集合,形成了一个从根结点出发的树状结构的模型,每一个if-then都对应着树上的一个分支,如下图是一个用来判断是不是加班的决策树示例,树中的每个内部结点代表一个特征,每个叶子结点代表一个类别:

看到这里我们程序员们可能会想,这个东西也能做成一个算法?我分分钟写100个if-else。试想,如果有成千上万个特征呢,我们怎么知道这些特征判断的顺序怎么样会比较高效、准确呢?哪些特征可能根本没什么用呢?哪些特征不做判断更有利于模型的泛化呢?要解决这些问题,靠人工是很困难的,还是得用决策树算法来操作。

要实现上述特征选择的功能有多种方法,我们知道,信息越多我们做出分类判断的不确定性就越小,这显然是我们追求的目标,针对这一目标先后出现了多种决策树算法。

2 决策树ID3分类算法

决策树ID3算法的核心是:使用信息增益在各个结点上选择特征。
使用信息增益来选择特征的思想就是:哪个特征的信息增益值最大,就认为其提供的信息的多,我们就选这个特征进行分支。

1)信息增益(information gain)

信息增益表示得知特征的信息而使得类的信息的不确定性减少的程度。特征对训练数据集的信息增益定义为:集合的经验熵(香农熵)与特征给定条件下的经验条件熵之差,即

这个差又称为互信息。信息增益大的特征具有更强的分类能力。

信息增益的计算:
输入:训练数据集和特征;
输出:特征对训练数据集的信息增益
(1)计算数据集的经验熵,有个类别

def calc_shannon_ent(dataset):
    num_entires = len(dataset)                       #数据集总数
    label_counts = {}                                #保存每个Label(类别)出现次数
    for row in dataset: 
        current_label = row [-1]                     #提取Label信息
        label_counts[current_label] = label_counts.get(current_label,0)+1    #Label计数
    shannon_ent = 0.0                                #经验熵(香农熵)
    for key in label_counts:    
        prob = float(label_counts[key]) / num_entires #该Label的概率
        shannon_ent -= prob * log(prob, 2)
    return shannon_ent 

(2)计算特征对数据集的经验条件熵,为特征的取值在中对应的子集

def calc_cond_ent(dataset, fea_i, fea_i_vals):
    condition_ent = 0.0
    for value in fea_vals:
        sub_dataset = dataset[dataset[columns[fea_i]]==fea_i_vals]  # 假设数据集是一个DataFrame
        prob = len(sub_dataset ) / float(len(dataset))  # 极大似然估计概率
        conditionEnt += prob * calc_shannon_ent(sub_dataset )  # 条件熵的计算
    return condition_ent

(3)计算信息增益

2)使用信息增益的决策树ID3

决策树ID3算法的过程为:

输入:训练数据集,每个样本有个离散特征,特征集合为;
输出:决策树。

  • 1 初始化信息增益的阈值;
  • 2 判断样本是否为同一类输出,如果是则返回单节点树,标记类别为;
  • 3 判断特征是否为空,如果是,则返回单节点树,标记类别为样本中输出类别中实例数最多的类别;
  • 4 计算中的各个特征对输出的信息增益(计算方法如上节所述),选择信息增益最大的特征;
  • 5 如果的信息增益小于阈值,则返回单节点树,标记类别为样本中输出类别实例数最多的类别;
  • 6 否则,按特征的不同取值将对应的样本输出分成不同的类别。每个类别产生一个子节点。对应特征值为。返回增加了节点的数;
  • 7 对于所有的子节点,令递归调用2-6步,得到子树并返回。

在这个过程中,如果达到了终止条件,则生成过程结束(这也算是预剪枝了,可以防止过拟合),得到最终的决策树,其中,终止条件包括:

  • 结点的数据样本小于阈值(提前设定),因为数据量较少时,再做分裂容易强化噪声数据的作用;二是降低树生长的复杂性,这样有利于减少过拟合的风险;
  • 每个叶结点的数据样本都只属于一个分类,数据无法再分裂
  • 树的层数大于阈值(提前设定),同样可以减小决策树的复杂度,减少过拟合的风险。
  • 所有特征已经使用完毕,不能再继续分裂;

3)决策树ID3的缺点

根据以上过程就可以得到一个决策树ID3算法模型,不过决策树ID3算法存在一些不足之处,其缺点为:

  • 采用信息增益,在相同条件下,取值多的特征倾向于比取值少的特征信息增益大(并不是一定有这个规律),主要是因为特征的取值越多,相当于对数据集的进一步细分,那么自然是消除了更多的数据的不确定性,可能越接近1,导致越小,即信息增益越大;
  • 只能处理离散型特征,不能处理连续型特征,限制了其适用性;
  • 缺少对缺失值的处理;
  • 容易产生过拟合。

3 决策树C4.5分类算法

为了解决上述决策树ID3算法的缺点,使用信息增益比来选择特征的决策树C4.5算法出现了。

1)信息增益比(information gain ratio)

特征对训练数据集的信息增益比定义为其信息增益与训练数据集关于特征的值的熵之比,即

其中,,是特征取值的个数。

显然,信息增益比比信息增益多了一个,特征的取值越多,越大,这个规律与相反,所以信息增益比相当于抵消了信息增益对取值多特征的倾向性,也就优化了ID3的缺点1。

2)使用信息增益比的决策树C4.5

决策树C4.5算法的过程为:

输入:训练数据集,每个样本有个离散特征,特征集合为;
输出:决策树。

  • 1 初始化信息增益的阈值;
  • 2 判断样本是否为同一类输出,如果是则返回单节点树,标记类别为;
  • 3 判断特征是否为空,如果是,则返回单节点树,标记类别为样本中输出类别中实例数最多的类别;
  • 4 计算中的各个特征对输出的信息增益比(计算方法如上节所述),选择信息增益比最大的特征;
  • 5 如果的信息增益小于阈值,则返回单节点树,标记类别为样本中输出类别实例数最多的类别;
  • 6 否则,按特征的不同取值将对应的样本输出分成不同的类别。每个类别产生一个子节点。对应特征值为。返回增加了节点的数;
  • 7 对于所有的子节点,令递归调用2-6步,得到子树并返回。

根据以上过程就可以得到一个决策树C4.5算法模型,看起来跟ID3的区别仅仅在于信息增益比,实际上还有一些对ID3缺点的特殊处理。

3)决策树C4.5对连续特征、缺失值的处理

(1)针对ID3缺点2不能处理连续型特征

C4.5使用二分法进行连续特征离散化,其特点在于离散化中的分类点不是随便给的,要选择信息增益最大的点作为该连续特征的二元离散分类点,比如在一系列连续值中取到的增益最大的点为(取排序后的相邻两样本值的平均数作为分类点),则小于的值为类别1,大于的值为类别2;

(2)针对缺点3缺少对缺失值的处理

C4.5从两个方面对其进行处理:

  • 训练数据的特征值有缺失的情况下,如何选择进行分裂的特征:C4.5的方案是对该有缺失值的特征,只使用其非缺失值的样本子集进行判断,比如10个样本,特征存在两个缺失值,那么就把这两个样本剔除,其他8个样本组成样本子集,在上按正常方法计算的信息增益,乘0.8(无缺失值样本所占比例)得到的信息增益。
  • 已确定了分裂特征,存在缺失值,这些缺失值样本分到哪个分支:C4.5的方案是将缺失值的样本划分到所有分支子结点中,然后为该样本在不同的子结点中赋予不同的权重,这个权重大概就是概率的意思,子结点中样本数多的权重就大,例如有三个取值,样本量分别为1,2,3,则一个缺失值在这三个子结点中的权值分别为:1/6,2/6,3/6。

4)决策树C4.5的剪枝

针对缺点4容易产生过拟合问题,C4.5引入了正则化系数进行初步的剪枝,控制树的复杂度。剪枝即把决策树的分支剪掉来简化模型,剪枝通过最小化决策树的整体损失函数来实现,设决策树的叶子结点数量为,决策树的整体损失函数为:

表示模型对训练数据的预测误差,表示决策树的复杂度(叶子结点越多越复杂),用来控制二者的关系,越大,树越简单。

剪枝算法:

输入:已经生成的决策树,参数
输出:剪枝后的决策树

  • 计算每个结点的经验熵;
  • 递归的从树的叶结点向上回缩,如果回缩后的决策树损失函数值变小,则进行剪枝——将父结点变为子结点;
  • 回到第二步,继续剪枝至得到损失最小的决策树。

5)决策树C4.5的缺点

决策树C4.5对ID3的缺点做了一定的改进,不过其仍然存在一些不足:

  • 只能用于分类,不能处理回归问题;
  • C4.5生成的是多叉树,计算效率不够高;
  • C4.5生成过程中有很多对数计算,计算效率不够高。

3 分类、回归都行的决策树CART

CART(Classification And Regression Tree),分类与回归树,顾名思义,其既可以分类又可以回归,好神奇呦,它是最常用的决策树算法,CART同样由特征选择、树的生成及剪枝组成,为了优化C4.5的缺点,在分类问题中,CART使用基于基尼系数的特征选择,并使用二叉树的结构来简化计算。

3.1 分类树

1)基尼系数

基尼系数代表了数据集的不纯度,特征分类后的数据集基尼系数越小,则不纯度越低,该特征分类效果越好。在分类问题中,假设数据集有个类别,第个类别的概率为, 则基尼系数的表达式为:

可以通过下图来理解基尼系数随类别个数变化的关系,各个类别概率之和为1,每个类别的概率的平方相当于边长为的正方形面积,类别越多,边长为的正方形越多,如下左图所示,蓝色区域面积越小,表示数据的不纯度越高,当只有一个类别时蓝色面积达到最大值1,此时基尼系数为0,表示数据的不纯度最低:

为了简化问题,我们将决策树的结构设定为二叉树,即对任意特征来说,都只根据其取值将样本划分为两个类别,对于样本D,如果根据特征A的某个值a,把D分成D1和D2两部分,则在特征A的条件下,D的基尼系数表达式为:

用基尼系数来判断特征分裂能力的好坏,如果D1和D2两部分都包含的类别数变少,即纯度增加,则基尼系数变小,所以越小的特征越好,这其实跟使用信息熵是差不多的,下图可以发现,在二分类的计算中,基尼系数和熵之半的曲线非常接近(对熵进行一阶泰勒展开可以发现二者的相似之处),基尼系数可以做为熵模型的一个近似替代:

二分类中基尼指数、熵之半、分类误差率之间的关系

2)使用基尼系数的决策树CART

决策树CART的生成过程:

输入:训练集D,终止条件(如基尼系数的阈值,样本个数阈值)
输出:CART决策树

从根节点开始,用训练集递归的建立CART树;

  1. 对于当前节点的数据集为,如果样本个数小于阈值或者没有特征,则返回决策子树,当前节点停止递归;
  2. 计算样本集的基尼系数,如果基尼系数小于阈值,则返回决策树子树,当前节点停止递归;
  3. 计算当前节点现有的各个特征的各个特征值对数据集的基尼系数,对于离散值和连续值的处理方法和基尼系数的计算见第二节。缺失值的处理方法和上篇的C4.5算法里描述的相同;
  4. 在计算出来的各个特征的各个特征值(与ID3、C4.5不同,他们不需要找最佳特征值)对数据集的基尼系数中,选择基尼系数最小的特征和对应的特征值。根据这个最优特征和最优特征值,把数据集划分成两部分和,同时建立当前节点的左右节点,做节点的数据子集为,右节点的数据子集为;
  5. 对左右的子节点递归的调用1-4步,生成决策树。

CART树的生成效率高,一是因为选择基尼系数作为分类标准,主要是乘法,没有对数运算,提高了计算效率;二是因为CART是二叉树,简化了决策树结构。

还有一点区别是CART树中的一特征可以在多处重复使用来分裂,而ID3和C4.5的离散特征只能使用一次,C4.5的连续型特征可能使用多次,本质上来说这也不算什么区别,只不过是树的结构变成了二叉树的结果,因为一个特征包含多种取值,在ID3和C4.5中因为是多叉树,所以一次使用就已经穷尽了这个特征的所有分类信息,即该特征已经信息耗尽,如果再重复用他去分类是已经没有意义的了,而二叉树的CART不同,一次使用并不一定能将一个特征的信息用完,所以以后还可以重复使用,同理可以知道为什么C4.5的连续型特征可能使用多次,因为连续型特征在离散化的时候实际上是用的二分法,没有将其信息用尽,所以以后还可以用,所以是不是很好理解,并不是什么特别的设计。

3)决策树CART的剪枝

我们已经知道决策树的整体损失函数:

用来平衡经验损失和树的复杂度,越大,被剪掉的分支就越多,树越简单,C4.5中我们把当做参数来人工输入,这显然是还可以继续优化的,所以CART的剪枝过程有两个问题需要解决:1、怎么确定来使得复杂度和拟合度达到最好的平衡?2、把哪些分支剪掉?

首先我们来思考一下对树的影响,举个例子,假设有个决策树共4个叶子结点,树上有个分支包含两个叶子结点,这个分支没剪掉之前,如果剪掉这个分支,经验损失升高,现在我们从0开始来增大,当时,决策树的整体损失,如果在此时减去这个分支得到子树,决策树的整体损失变为,即剪枝前后整体损失相等,此时就是剪枝的临界点,如果不剪枝整体损失就大于剪枝的子树了,当然,剪枝之后经验损失(误差)也增大了,反映了误差增大的大小,因此被称作“误差增益”。如果继续增大,发现到时又需要剪枝了,显然是在的区间中的最优子树,每个可以通过下式计算:

随着的不断增大,我们会得到子树序列,每个剪去一个分支变成,如果我们从树的角度来想的话,就是每一个分支结点,都对应着一个,这个就是将它剪去的刽子手,所以我们实际操作中是根据每个结点计算,而不是真的通过增大来得到子树,因为作为连续数值要遍历的话是不可能的。

在我们得到所有的子树后,对每棵树进行交叉验证(即用测试集验证),结果最好的即为泛化能力最好的决策树。总结CART决策树剪枝的过程:

输入:CART算法生成的决策树;
输出:剪枝后的最优决策树。

(1)设;
(2)设;
(3)自下而上对各内部结点计算以及

式中,表示已为根结点的子树,是其对训练数据的预测误差,是叶子结点个数;
(4) 对的内部结点进行剪枝,并对叶结点用多数表决法确定其类别,得到树;
(5)设;
(6)如果不是由根结点和两个叶子结点构成的树,则回到步骤(2);否则令。
(7)采用交叉验证法在子树序列中选取最优子树。

分析一下这个过程,一开始设置为无穷大,然后计算所有内部节点的,然后取一个最小的值,最小的应该对应着最少的剪枝,保存剪枝后的树;剪枝后,全部结点的都会变化,此时重新计算所有的,重复上面的操作,这一次得到的最小的会比之前的大,一直重复这一套操作直到根节点结束。

不知道大家有没有这个疑问,直接遍历各种剪枝的可能不行吗,这里计算并保存有什么用呢?难道这样按从小到大剪枝会比遍历的运算效率高吗,搞不懂,我猜可能是最小的没准对应的不一定是第一层,可能是第二层第三层,如果这样的话,那确实一下剪掉就提高了效率,纯属猜测,哪天有空了可以设计个决策树试试看。。。

3.2 回归树

所谓回归树,就是其输出不再是离散型的类别数据,而是连续型的数值,回归树与分类树的区别的根源也就在此,分类树中的label是离散型的类别,所以我们使用信息熵啊、基尼系数啊来衡量被分开的两波数据中类别数是不是变得更少,也就是所谓的数据更纯粹、信息更多;回归树的label是各种数值,这样肯定就行不通啦,我们需要衡量的是被分开的两波数据是不是数值更相近了,也就是方差,所以回归树与分类树的区别就是:1、数据分裂的评价指标不同;2、最终预测结果的获取方式不同(要把输出归一到一个数值上)

  • 数据分裂选择特征的指标:特征中取值点将数据集划分成数据子集和,使和的均方差分别最小,同时使和的均方差之和最小,那么为分裂特征,为分裂点,表达式为:

其中,为数据集的样本输出均值,为数据集的样本输出均值。

  • 预测结果的获取方式:叶子结点中数据label的均值或者中位数。

除此之外,回归树的生成方式与分类树一致。

4 决策树总结

之前我们已经知道了逻辑回归、SVM等多种分类算法,相比之下:

  • 决策树的思路简单易懂,浅层的树可解释性很好,并且易于可视化,这种特点使得其颇受一些传统行业的青睐;
  • 同时,决策树对数据分布没有假设,且可以处理离散型数据和连续型数据,而之前那几种分类器显然对连续型数据更友善;
  • 决策树可以直接实现多分类;
  • 对批量数据预测速度比较快,因为CART二叉树的结果,其预测的时间复杂度为,其中为数据量。

决策树还是有其局限性及缺点的,包括:

  • 决策树每次用一个特征分裂,容易忽略特征间的相互关联,这个问题可以通过多变量决策树来优化;
  • 决策树容易发生过拟合,需要花很大功夫来调整其结构如剪枝等来控制;
  • 决策树容易因为样本的小的变化而过分改变其结构,使模型稳定性和抗震荡性差,这个问题可使用集成模型来优化;
  • 对缺失值的处理不够好等。

你可能感兴趣的:(决策树——ID3、C4.5、CART)