ID3、C4.5、CART决策树生成算法总结

简介

决策树模型是最常见的机器学习方法之一,也是入门机器学习必须掌握的知识。决策树模型呈现树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的概率分布。其主要优点是可解释性强、具有可读性、分类速度快。树模型通常分为分类树和回归树。分类树主要针对目标变量是离散变量(一般是二元变量)的情形,回归树针对目标变量是连续变量的情形。无论是分类树还是回归树,都需要从给定的训练数据中构建一棵决策树模型出来,使它能够对实例进行正确的分类或回归。本文主要介绍在决策树生成过程中需要用到的特征选择算法,包含ID3、C4.5、CART等,通过具体的实例来介绍这些算法的原理以及具体使用方法,作为树模型学习的归纳总结。

决策树生成步骤

以分类树为例,分类决策树算法的本质是从训练数据集中归纳出一组分类规则。与训练数据集不相矛盾的决策树可能有多个,也可能一个都没有。我们需要找出一个与训练数据集矛盾最小的决策树,同时还需要具有较好的泛化能力。
决策树学习的算法通常是一个递归地选择最优特征,并根据该特征对训练数据进行分割,使得对各个子数据集有一个最好的分类的过程。这一过程对应着对特征空间的划分,也对应着决策树的构建。开始,先构造根节点,将所有训练数据都放在根节点。选择一个最优特征,按照这个最优特征将训练数据集划分为子集,使得各个子集有一个在当前条件下最好的分类。如果这些子集已经能够被正确分类,那么构建叶节点,并将这些子集分到所对应的叶节点中去;如果还有子集不能被基本正确分类,那么就对这些子集选择新的最优特征,继续对齐进行分割,构建相应的接地单。如此递归地进行下去,直至所有训练数据子集都被正确分类,或者没有合适的特征为止(比如剩下的特征空间的特征取值都是一样的,但是分类类别还存在差异,此时也需要停止继续划分子集)。最后每一个子集都被分到了叶节点上,即有了明确的分类,这就形成了一棵决策树。
下面举个小例子,假如我们有一个小型数据集,里面一共包含了3个特征,分别是‘Weather’、‘Time’、‘Hungry’,目标变量是'Walk'和‘Bus’,是一个二分类问题。我们需要根据这3个特征来决定是走路出行还是坐公交出行,一棵可能的决策树如下。

决策树示意图
上图的决策树,就是将数据集先根据'Weather'划分为3个子集,第一个子集再根据‘Time’进行划分,第二个子集根据‘Hungry’划分,第三个子集由于已经同属一个分类,就不再划分了。
但是为什么我们需要这样划分呢?依据是什么?否则针对上图的情况而言,我们难道不可以首先通过'Time'来进行分割,或者'Hungry',一定需要是‘Weather’么?针对这个问题,我们发现决策树生成中最关键的步骤在于如何进行分支,即如何选择最优的特征和划分点依据,这需要统一的指标来衡量。这里就涉及到本文的主题了,即ID3、C4.5、CART等决策树生成算法,它们的主要作用就是来帮助我们挑选出最佳的特征对数据集进行划分,所谓最佳特征指的是挑选出来的特征对训练数据具有分类能力的特征。如果利用一个特征进行分类的结果与随机分类的结果没有太大差别,则称这个特征没有区分能力。那这些算法挑选特征的依据是什么呢?这就是接下来要重点学习的熵、信息增益和基尼指数,它们是这些算法挑选特征所依据的基本准则。

熵、信息增益、信息增益比、基尼指数

熵(Entropy)是一个热力学概念,在信息论中,熵用来衡量随机变量的不确定性。一个系统越有序,熵越小;越混乱,熵越大。因此熵用来衡量一个系统的有序程度。在机器学习的的过程中,变量的不确定越大,熵越大。设是一个取有限个值的离散随机变量,其概率分布为:

则随机变量的熵定义为:

在上式中,log函数通常以2或者自然对数e为底。熵的大小与的取值无关,只取决于的分布。对于样本集合D来说,随机变量是样本的类别,假设样本中有个类别,每个类别的概率是,样本集合D的经验熵为式:

熵越大,随机变量的不确定性就越大。从定义验证:

当随机变量只取两个值时,比如0,1时,则的分布为:

熵为:

我们将熵随着概率的变化曲线绘制下来,代码如下:

import numpy as np
import matplotlib.pyplot as plt

p = np.linspace(0, 1, 1000)
# 熵的计算公式
entropy = -p * np.log2(p) - (1-p) * np.log2(1-p)
plt.plot(p, entropy)
plt.grid()
plt.xlabel('p')
plt.ylabel('entropy')
plt.show()

二分类问题中,熵和概率得到关系
可以看到当 或者 时,随机变量的取值是固定的,即没有不确定性,熵值最小。而当 时,随机变量的取值的不确定性最大,此时熵值最大。可以看到,用熵值的大小来衡量系统的不确定性,也是比较符合人们的直觉的。

信息增益

接着我们来学习一下信息增益。设有随机变量,其联合概率分布为:

条件熵表示在已知随机变量的条件下随机变量的不确定性。在随机变量给定的条件下,随机变量得到条件熵为,定义在给定条件下的条件概率分布的熵对的数学期望为:

当熵和条件熵中的概率由数据估计出来(极大似然估计),所对应的熵与条件熵分别为经验熵和经验条件熵。此时,如果概率为0,则。
信息增益表示得到特征的信息而使得类的信息的不确定性减少的程度。
听起来实在是拗口,说的通俗一点就是,在数据集进行划分前,计算一次分类的信息熵,然后选择一个特征,将数据集一分为二,分别计算划分后的子集的信息熵,再累加起来得到划分后进行分类的熵值,然后两者做差就得到信息增益。
不过这里还是给出信息增益的数学定义:

特征A对的训练数据集D的信息增益,定义为集合D的经验熵与特征A给定条件下D的条件经验熵之差,即:

这里给出信息增益算法的具体步骤:
设有训练集,代表其样本容量,即样本个数。设有个类代表属于类得到样本个数。设特征A有个不同的取值,根据特征A的取值将D划分为n个子集代表样本集的样本个数。记子集中属于类得到样本的集合为,即元素既属于样本子集同时又属于类别。于是信息增益算法如下:

  1. 计算数据集D的经验熵
  2. 计算特征A对数据集D的条件经验熵
  3. 计算信息增益

下面以一个简单的例子来介绍一下信息增益的计算方式:
假如我们有以下数据集D,它只有一个特征'x',标签‘y'。假如我们以特征'x=3'来作为特征x的分隔点,则划分后带来的信息增益的计算如下:

data = [[1,0],[2,1],[3,0],[4,1],[5,1]]
df = pd.DataFrame(data, columns=['x','y'])
df

原始数据
首先我们来计算分隔前的经验熵 ,如下:

接下来计算特征x对数据集D的条件经验熵 :
H(D|x) = P_1H(x < 3) + P_2H(x \ge 3) \\ P_1H(x < 3) = \frac {3}{5} * -\lbrace \frac {2}{3} * log_2 \frac {2}{3} + \frac {1}{3} * log_2 \frac {1}{3} \rbrace = 0.551\\ P_2H(x \ge 3) = \frac {2}{5} * -\lbrace \frac {2}{2} * log_2 \frac {2}{2} + \frac {0}{2} * log_2 \frac {0}{2} \rbrace = 0 \\ H(D|x) = P_1H(x < 3) + P_2H(x \ge 3) = 0.551
故信息增益为:

信息增益比

如果以信息增益来作为训练数据集的特征,存在偏向于选择取值较多的特征的问题。使用信息增益比可以对这一问题进行校正。这是特征选择的另一准则。定义如下:

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

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

基尼指数

基尼指数最初用于经济学中,用来衡量收入分配公平的程度。基尼指数越大,代表贫富差距越大,反之差距越小。在机器学习中,基尼指数越大,样本集合的不确定越大。在二分类问题中,用基尼指数衡量变量的重要程度,基尼指数也称为基尼不纯度,表示样本集合中一个随机选中的样本被分错的概率。基尼指数越小表示分错的概率越小,即集合的纯度越高;反之,集合越不纯。
在分类问题中,假设有K个类,样本点属于第k类的大概率为,则概率分布的基尼指数定义为:

对于二分类问题,若样本点属于第1个类的概率为p,则概率分布的基尼指数为:

对于给定的样本集合D,其基尼指数为:

这里,是D中属于第k类的样本子集,K是类别的个数。
如果样本集合D根据特征A是否取某一可能值a被分割成和两部分,即:

即在特征A得到的条件下,集合D的基尼指数定义为:

基尼指数表示集合D的不确定性,基尼指数表示经分割后集合D的不确定性。基尼指数越大,样本集合的不确定性就越大,这一点与熵类似。

ID3和C4.5算法

终于要开始进入正题了,首先我们来介绍ID3算法,如果我们对上一节的信息增益的计算很熟悉的话,那么ID3算法也就没有什么难点了。ID3算法的核心是在决策树的各个结点上应用信息增益准则选择特征,递归地构建决策树。具体方法是:从根节点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子节点;再对子节点递归地调用以上方法,构建决策树;直到所有的特征的信息增益均很小或者没有特征可以选择为止,最后得到一颗决策树。
下面是李航老师的《统计学习方法》里的贷款的例子,以这个例子来重点介绍一下ID3算法的工作流程:

import pandas as pd
from io import StringIO

data = "年龄,有工作,有自己的房子,信贷情况,类别\n\
        青年,否,否,一般,否\n\
        青年,否,否,好,否\n\
        青年,是,否,好,是\n\
        青年,是,是,一般,是\n\
        青年,否,否,一般,否\n\
        中年,否,否,一般,否\n\
        中年,否,否,好,否\n\
        中年,是,是,好,是\n\
        中年,否,是,非常好,是\n\
        中年,否,是,非常好,是\n\
        老年,否,是,非常好,是\n\
        老年,否,是,好,是\n\
        老年,是,否,好,是\n\
        老年,是,否,非常好,是\n\
        老年,否,否,一般,否"

df = pd.read_csv(StringIO(data))
df

贷款例子

接下来我们来计算一下经验熵 :

然后计算各个特征对数据集D的信息增益,分别以A1,A2,A3,A4表示年龄,有工作,有自己的房子和信贷情况四个特征。
以A1也就是年龄为例,计算信息增益 ,如下:
g(D,A_1) = H(D) - [\frac {5}{15}H(D_1) + \frac {5}{15}H(D_2) + \frac {5}{15}H(D_3) ] \\ =0.971 - [\frac {5}{15} * \lbrace -\frac {2}{5} * log_2 \frac {2}{5} - \frac {3}{5} * log_2 \frac {3}{5} \rbrace + \frac {5}{15} * \lbrace -\frac {2}{5} * log_2 \frac {2}{5} - \frac {3}{5} * log_2 \frac {3}{5} \rbrace + \frac {5}{15} * \lbrace -\frac {4}{5} * log_2 \frac {4}{5} - \frac {1}{5} * log_2 \frac {1}{5} \rbrace] = 0.971 - 0.888 = 0.083
同理可以计算得出 。对比可知以 作为特征进行划分得到的信息增益是最大的,因此我们选择特征 作为根节点的特征。它将训练集D又划分为两个子集 (即 取'是')和 (即 取’否‘)。
紧接着,递归对子集进行特征划分。由于 只有同一类的样本,所以他成为一个叶节点,节点的的类标记为’是‘。对 则需要从特征 和 和 中选择最佳特征,首先我们还是计算此时的经验熵:

同样计算各个特征的信息增益,分别为 ,可以看出 的信息增益最大,因此选 作为划分节点。此时由于对 按照特征 划分之后,所有的样本都属于同一类,因此算法结束。生成的树如下:
决策树
这是我们手动计算出来生成的树结构,接下来我们使用sklearn中自带的算法来演示一下使用过程:
首先是对数据进行预处理,

from sklearn import preprocessing

le = preprocessing.LabelEncoder()
df['年龄'] = le.fit_transform(df['年龄'].values)
df['有工作'] = le.fit_transform(df['有工作'].values)
df['有自己的房子'] = le.fit_transform(df['有自己的房子'].values)
df['信贷情况'] = le.fit_transform(df['信贷情况'].values)
df['类别'] = le.fit_transform(df['类别'].values)
df
数据预处理

接着构造训练决策树模型,然后将其可视化出来:

from sklearn.tree import DecisionTreeClassifier

X_train, Y_train = df.loc[:, ['年龄','有工作','有自己的房子','信贷情况']], df['类别']

clf_tree = DecisionTreeClassifier(criterion='entropy', max_depth=4, random_state=1)
clf_tree.fit(X_train, Y_train)

import matplotlib.pyplot as plt
from sklearn import tree
fig, ax = plt.subplots(figsize=(10, 10))
tree.plot_tree(clf_tree, fontsize=10)
plt.show()

决策树模型

可以看出ID3算法其实就是在计算各个特征划分后的信息增益,然后挑选信息增益最大的特征作为划分点,递归进行,直到满足停止条件。如果明白了ID3算法,那么C4.5算法就很容易理解了,C4.5算法是对ID3算法的改进,只是将计算信息增益的地方,改成了计算信息增益比,其余步骤类似。好处是在一定程度上避免了由于特征值较多而被选中。
为了让读者明白C4.5算法的计算方法,这里还是以这个例子为例,说明一下信息增益比的计算方法。我们在第一步中计算出了各个特征的信息增益,特征 的信息增益为 ,那么我们来计算一下以特征 划分的信息增益比,由信息增益比公式

可知,我们现在需要计算 , 对于特征 来说,计算过程如下:

则信息增益比为:

每一步选择信息增益比最大的特征继续划分,其他步骤跟ID3算法一致,就不再赘述了。

CART算法

CART全称为classification and regression tree,即分类与回归树。CART特征选择算法既可以用于分类树也可以用于回归树。CART是在给定输入随机变量X条件下输出随机变量Y的条件概率分布的学习方法。CART假设决策树是二叉树,内部节点特征的取值为“是”和“否”。这样的决策树等价于递归地二分每个特征,将输入空间即特征空间划分为有限个单元,并在这些单元上确定预测的概率分布,也就是在输入给定的条件下输出的条件概率分布。

CART分类树

CART分类树采用基尼指数最小化原则来进行特征选择,生成二叉树。本质上与上一节讲的ID3、C4.5算法类似,只不过CART分类树将划分指标换成了基尼指数,基尼指数的计算上一节在文章开头已经讲过了,这里直接以上一节的贷款例子来演示一下计算过程。
首先计算各个特征的基尼指数,选择最优特征及其最优切分点,仍然以A1,A2,A3,A4表示年龄,有工作,有自己的房子和信贷情况四个特征,并且以1,2,3表示年龄的青年、中年和老年,以1,2表示有工作和有自己的房子的是否,以1,2,3表示信贷情况的非常好、好和一般。
求特征的基尼指数:
Gini(D,A_1 =1) = \frac {5}{15}(2 \times \frac {2}{5} \times (1 - \frac {2}{5})) + \frac {10}{15}(2 \times \frac {7}{10} \times (1 - \frac {7}{10})) = 0.44 \\ Gini(D,A_1 =2) = 0.48 \\ Gini(D,A_1 =3) = 0.44
由于相等且最小,故,即‘青年’和‘老年’都可以用来作为年龄这一特征的最优划分点。同理,我们可以求出的基尼指数:

因为都只有2个取值,故只能找出一个切分点,并且它们就是最佳切分点。的基尼指数如下:

比较所有特征划分后的基尼指数,由于最小,故最佳切分特征是,切分点为,即以‘有自己的房子’为最佳特征划分点。在sklearn中的决策树算法,默认是用的是信息增益来作为特征选择准则,但是也可以设置成基尼指数,下面演示一下:

clf_tree = DecisionTreeClassifier(criterion='gini', max_depth=4, random_state=1)
clf_tree.fit(X_train, Y_train)

fig, ax = plt.subplots(figsize=(10, 10))
tree.plot_tree(clf_tree, fontsize=10)
plt.show()
CART分类树

对于这个例子而言,使用ID3算法和CART分类树,最后构建出来的决策树是一样的。

CART回归树

CART回归树用平方误差最小化准则来进行特征筛选。假设X和Y分别为输入和输出变量,并且Y是连续变量,给定训练数据集:

一棵回归树对应着输入空间(即特征空间)的第一个划分以及在划分的单元上的输出值。假设已将输入空间划分为M个单元,并且在每个单元上,有一个固定的输出值,于是回归树模型可以表示为:

当输入空间的划分确定时,可以用平方误差来表示回归树对于训练数据的预测误差,用平方误差最小的准则求解每个单元上的最优输出值。易知,单元上的的最优值就是上所有输入实例对应的输出的均值,即:

问题是怎样对特征空间进行划分。这里采用启发式的方法,选择第个变量和它的取值,作为切分变量和切分点,并定义两个区域:

然后寻找最优切分变量和最优切分点。具体地,求解:

对固定变量可以找到最佳切分点。

遍历所有输入变量,找到最优的切分变量,构成一个对(j,s)。依次将输入空间划分为两个区域。接着递归进行上述过程,直到满足条件为止。这样的回归树通常称之为最小二乘回归树。
看起来还是很晦涩抽象,同样还是以一个简单的例子来演示计算过程。假如我们有以下数据,为了简单起见,只包含一个特征‘prices’:

import pandas as pd
from io import StringIO

data = "price($),Review Scores Rating\n\
        10.0,10\n\
        11.0,10\n\
        12.0,10\n\
        13.0,10\n\
        14.0,13\n\
        15.0,20\n\
        17.5,35\n\
        18.0,44\n\
        18.5,52\n\
        19.0,55\n\
        21.0,80\n\
        22.0,83\n\
        23.0,80\n\
        24.0,83\n\
        25.0,85\n\
        28.0,100\n\
        29.0,100\n\
        30.0,100\n\
        31.0,100\n\
        31.0,100"

df = pd.read_csv(StringIO(data))
df
简单回归数据

将其绘制出来:

plt.plot(df['price($)'], df['Review Scores Rating'], 'o', c='r', ms='15')
plt.xlabel('price($)')
plt.ylabel('Review Scores Rating')
plt.show()
绘制回归数据图像

接下来我们计算每个特征的最佳划分点,由于这个例子很简单,只有一个特征'price',我们从最小的开始,以price=11来划分,如下图:

这里不用最小的Price=10的原因是,如果以price=10划分,左子集大小为0,没有意义。

一次划分
以price=11划分后,左子集值包含一个样本,并且对应的 ,故取平均之后,得到节点的值为10。右子集包含剩下的19个样本,对 求和取平均之后得到58.9,然后计算均方根误差,如下:
计算均方差
我们将这个均方根误差先记录下来,绘制成表格。
均方根误差
接下来以price=12为划分点进行划分,如下图:
计算均方根误差:
均方根误差计算
将均方根误差也记录下来:
同理,一直对所有的取值都进行划分,最后得到了使用所有特征取值划分的均方根误差分布,如下图:
可以看到当以price=19划分时,此时的决策树具有最小的均方根误差,因此我们选取price=19作为最佳划分点,将原始数据一分为二,分为R1和R2两个子集,如下图:
第一次划分
我们对R1和R2分别执行上述相同的操作,找到R1的最佳划分点是price=15, R2的最佳划分点为price=25,这里由于篇幅原因,就不再详细描述计算过程,读者可以根据上面的例子举一反三。经过3次划分之后,我们构建出如下的决策树:
回归树
当然实际上还可以继续划分,增加树的深度,这里就不演示了。我们也可以使用sklearn来对这个例子进行回归树建模,代码如下:


from sklearn.tree import DecisionTreeRegressor

X, Y = df['price($)'], df['Review Scores Rating']

dtr = DecisionTreeRegressor(criterion='mse', splitter='best', min_samples_split=5, max_depth=2, random_state=0)
dtr.fit(X.values.reshape(-1, 1), Y.values.reshape(-1, 1))
dtr

fig, ax = plt.subplots(figsize=(10, 10))
tree.plot_tree(dtr, fontsize=10)
plt.show()
回归树

在代码中我们将max_depth设置为2,得到的决策树可视化如上图所示,可以看到这个跟我们前面手推得到的决策树是一致的。
这里再多提一句,有的同学可能会问了,我们的这个例子是只有一个特征的,如果有多个特征,那么在进行特征选择的时候,又应该怎么选择呢?其实也很简单,我们对每一个特征的每一个取值都进行划分,然后分别计算MSE,最后选取MSE最小的特征,取对应的最佳划分点,进行决策树的构造就可以了。

参考

  • 《统计学习方法》
  • 《机器学习--软件工程实践与方法》
  • Regression in Decision Tree — A Step by Step CART

你可能感兴趣的:(ID3、C4.5、CART决策树生成算法总结)