决策树介绍

决策树算法既可以作为分类算法,也可以作为回归算法,同时也特别适合集成学习比如随机森林。

一、什么是决策树

下面是一个通俗的决策树例子:

女儿:多大年纪了?
母亲:26。

女儿:长的帅不帅?
母亲:挺帅的。

女儿:收入高不?
母亲:不算很高,中等情况。

女儿:是公务员不?
母亲:是,在税务局上班呢。

女儿:那好,我去见见。

这个女孩的决策过程就是典型的分类树决策。相当于通过年龄、长相、收入和是否公务员对将男人分为两个类别:见和不见。假设这个女孩对男人的要求是:30岁以下、长相中等以上并且是高收入者或中等以上收入的公务员,那么这个可以用下图表示女孩的决策逻辑:

上图完整表达了这个女孩决定是否见一个约会对象的策略,其中绿色节点表示判断条件,橙色节点表示决策结果,箭头表示在一个判断条件在不同情况下的决策路径,图中红色箭头表示了上面例子中女孩的决策过程。

现在我们有那么多判断是否去见约会对象的条件,那么先判断哪个条件比较好呢?怎么准确的定量选择这个标准就是决策树算法的关键了。

二、决策树ID3算法

1970年代,一个叫昆兰的大牛找到了用信息论中的熵来度量决策树的决策选择过程,昆兰把这个算法叫做ID3。下面我们就看看ID3算法是怎么选择特征的。

首先,我们需要熟悉信息论中熵的概念。熵度量了事物的不确定性,越不确定的事物,它的熵就越大。具体的,随机变量X的熵的表达式如下:

$$
H(X) = -\sum\limits_{i=1}^{n}p_i logp_i
$$

  • n代表X的n种不同的离散取值
  • pi代表了X取值为i的概率
  • log为以2或者e为底的对数。

举个例子,比如X有2个可能的取值,而这两个取值各为1/2时X的熵最大,此时X具有最大的不确定性,对应熵为:

$$
H(X) = -(\frac{1}{2}log\frac{1}{2} + \frac{1}{2}log\frac{1}{2}) = log2
$$

如果一个值概率大于1/2,另一个值概率小于1/2,则不确定性减少,对应的熵也会减少。比如一个概率1/3,一个概率2/3,则对应熵为:

$$
H(X) = -(\frac{1}{3}log\frac{1}{3} + \frac{2}{3}log\frac{2}{3}) = log3 - \frac{2}{3}log2 < log2)
$$

然后我们很容易推广到多个变量的联合熵,这里给出两个变量X和Y的联合熵表达式:

$$
H(X,Y) = -\sum\limits_{i=1}^{n}p(x_i,y_i)logp(x_i,y_i)
$$

有了联合熵,又可以得到条件熵的表达式H(X|Y),条件熵类似于条件概率,它度量了在知道Y以后X剩下的不确定性。表达式如下:

$$
H(X|Y) = -\sum\limits_{i=1}^{n}p(x_i,y_i)logp(x_i|y_i) = \sum\limits_{j=1}^{n}p(y_j)H(X|y_j)
$$

我们刚才提到H(X)度量了X的不确定性,条件熵H(X|Y)度量了我们在知道Y以后X剩下的不确定性H(X)-H(X|Y)则度量了X在知道Y以后不确定性减少程度,这个度量我们在信息论中称为互信息,记为I(X,Y)。在决策树ID3算法中叫做信息增益。ID3算法就是用信息增益来判断当前节点应该用什么特征来构建决策树。信息增益大,则越适合用来分类。

1、信息增益的一个示例

我们有如下数据:

帅?性格好?身高?上进?嫁与否
不好不上进不嫁
不帅上进不嫁
上进
不帅爆好上进
不好上进不嫁
不好上进不嫁
不上进
不帅上进
爆好上进
不帅不好上进
不上进不嫁
不上进不嫁

从数据中可以看出嫁的个数为6个,占1/2,可以求得随机变量X(嫁与不嫁)的信息熵为:

$$
H(X) = -(\frac{1}{2}log\frac{1}{2} + \frac{1}{2}log\frac{1}{2}) = log2 = 0.301
$$

现在假如我知道了一个男生的身高信息。身高有三个可能的取值{矮,中,高}

  • 矮包括{1,2,3,5,6,11,12},嫁的个数为1个,不嫁的个数为6个
  • 中包括{8,9} ,嫁的个数为2个,不嫁的个数为0个
  • 高包括{4,7,10},嫁的个数为3个,不嫁的个数为0个

那么我们可以求出:

$$
H(Y|X=矮) = -(\frac{1}{7}log\frac{1}{7} + \frac{6}{7}log\frac{6}{7}) = 0.178
$$

$$
H(Y|X=中) = -(1log1 + 0) = 0
$$

$$
H(Y|X=高) = -(1log1 + 0) = 0
$$

$$
P(X = 矮) = \frac{7}{12} , P(X = 中) = \frac{2}{12} , P(X = 高) = \frac{3}{12}
$$

则可以得出条件熵为:

$$
H(Y|X) = \frac{7}{12}0.178 + \frac{2}{12}0 + \frac{3}{12}*0 = 0.103
$$

我们知道信息熵与条件熵相减就是我们的信息增益,我们在知道了身高这个信息之后,信息增益是0.301-0.103=0.198

如果我对一个男生什么都不知道的话,决定是否嫁给他的不确定性有0.301这么大。当我们知道他的身高信息后,不确定度减少了0.198。

假如其它特征我也全算了,信息增益是身高这个特征最大。那么身高对于我挑夫君是最重要的,知道了这个特征,嫁与不嫁的不确定度减少的是最多的。那么在这个决策树里面,身高就是最优特征,所以它被选作分裂特征。下面再进行递归计算信息增益,在此就不展示了

2、ID3算法的不足

  • ID3算法没有考虑连续特征,比如长度,密度都是连续值,无法在ID3运用。这大大限制了ID3的用途。
  • ID3算法采用信息增益大的特征优先建立决策树的节点。但是在相同条件下,取值比较多的特征比取值少的特征信息增益大。比如一个变量有2个值,各为1/2,另一个变量为3个值,各为1/3,其实他们都是完全不确定的变量,但是取3个值的比取2个值的信息增益大。
  • ID3算法没有考虑缺失值的情况。
  • ID3算法没有考虑过拟合的问题。

三、决策树C4.5算法

昆兰在C4.5算法中改进了ID3上述4个问题。

1、连续特征离散化

对于第一个不能处理连续特征的问题,C4.5的思路是将连续的特征离散化。

比如m个样本有m个连续特征A,从小到大排列为${a_1,a_2,…,a_m}$,则C4.5取相邻两样本值的平均数,一共取得m-1个划分点,其中第i个划分点Ti表示为:$T_i = \frac{a_i+a_{i+1}}{2}$。对于这m-1个点,分别计算以该点作为二元分类点时的信息增益。然后选择信息增益最大的点作为该连续特征的二元离散分类点

比如取到的增益最大的点为at,则小于at的值为类别1,大于at的值为类别2,这样我们就做到了连续特征的离散化。

要注意的是,与离散属性不同的是,如果当前节点为连续属性,则该属性后面还可以参与子节点的产生选择过程。

2、信息增益比

对于第二个信息增益作为标准容易偏向于取值较多的特征的问题。我们引入信息增益比,它是信息增益和特征熵的比值。

$$
I_R(D,A) = \frac{I(A,D)}{H_A(D)}
$$

其中D为样本特征输出的集合,A为样本特征,特征熵表达式如下:

$$
H_A(D) = -\sum\limits_{i=1}^{n}\frac{|D_i|}{|D|}log_2\frac{|D_i|}{|D|}
$$

其中n为特征A的类别数, Di为特征A的第i个取值对应的样本个数。|D|为样本个数。

特征数越多的特征对应的特征熵越大,它作为分母,可以校正信息增益容易偏向于取值较多的特征的问题。

3、剪枝

对于第四个过拟合的问题,C4.5引入了正则化系数进行初步的剪枝。

4、C4.5算法的不足

  • 由于决策树算法非常容易过拟合,因此对于生成的决策树必须要进行剪枝。剪枝的算法有非常多,C4.5的剪枝方法有优化的空间。思路主要是两种,一种是预剪枝,即在生成决策树的时候就决定是否剪枝。另一个是后剪枝,即先生成决策树,再通过交叉验证来剪枝。后面讲CART树的时候我们会专门讲决策树的减枝思路,主要采用的是后剪枝加上交叉验证选择最合适的决策树。
  • C4.5生成的是多叉树,即一个父节点可以有多个节点。很多时候,在计算机中二叉树模型会比多叉树运算效率高。如果采用二叉树,可以提高效率。
  • C4.5只能用于分类,如果能将决策树用于回归的话可以扩大它的使用范围。
  • C4.5由于使用了熵模型,里面有大量的耗时的对数运算,如果是连续值还有大量的排序运算。如果能够加以模型简化可以减少运算强度但又不牺牲太多准确性的话,那就更好了。

四、CART分类树算法

无论是ID3还是C4.5,都是基于信息论的熵模型的,这里面会涉及大量的对数运算。能不能简化模型同时也不至于完全丢失熵模型的优点呢?

CART分类树算法使用基尼系数来代替信息增益比,基尼系数代表了模型的不纯度,基尼系数越小,则不纯度越低,特征越好。

在分类问题中,假设有K个类别,第k个类别的概率为pk,则基尼系数的表达式为:

$$
Gini(p) = \sum\limits_{k=1}^{K}p_k(1-p_k) = 1- \sum\limits_{k=1}^{K}p_k^2
$$

如果是二类分类问题,计算就更加简单了,如果属于第一个样本输出的概率是p,则基尼系数的表达式为:

$$
Gini(p) = 2p(1-p)
$$

对于二类分类,基尼系数和熵之半的曲线如下:

从上图可以看出,基尼系数和熵之半的曲线非常接近,仅仅在45度角附近误差稍大。因此,基尼系数可以做为熵模型的一个近似替代。

1、CART分类树多分类处理

对于多分类的问题,CART分类树采用的思路是不停的二分离散特征。

回忆下ID3或者C4.5,如果某个特征A被选取建立决策树节点,如果它有A1,A2,A3三种类别,我们会在决策树上一下建立一个三叉的节点。这样导致决策树是多叉树。但是CART分类树使用的方法不同,他采用的是不停的二分,还是这个例子,CART分类树会考虑把A分成{A1}{A2,A3}, {A2}{A1,A3}, {A3}{A1,A2}三种情况,找到基尼系数最小的组合,比如{A2}{A1,A3},然后建立二叉树节点,一个节点是{A2}对应的样本,另一个节点是{A1,A3}对应的节点。同时,由于这次没有把特征A的取值完全分开,后面我们还有机会在子节点继续选择到特征A来划分A1和A3。这和ID3或者C4.5不同,在ID3或者C4.5的一棵子树中,离散特征只会参与一次节点的建立。

2、CART分类树连续值处理

对于连续值处理,CART思想和C4.5是相同的,都是将连续的特征离散化。唯一的区别在于在选择划分点时的度量方式不同,C4.5使用的是信息增益比,则CART分类树使用的是基尼系数。

具体的思路如下,比如m个样本的连续特征A有m个,从小到大排列为${a_1,a_2,…,a_m}$,则CART算法取相邻两样本值的平均数,一共取得m-1个划分点,其中第i个划分点Ti表示为:$T_i = \frac{a_i+a_{i+1}}{2}$。对于这m-1个点,分别计算以该点作为二元分类点时的基尼系数。选择基尼系数最小的点作为该连续特征的二元离散分类点

比如取到的基尼系数最小的点为at,则小于at的值为类别1,大于at的值为类别2,这样我们就做到了连续特征的离散化。

要注意的是,与ID3或者C4.5处理离散属性不同的是,如果当前节点为连续属性,则该属性后面还可以参与子节点的产生选择过程。

五、sklearn实现决策树

这里我使用scikit-learn中的鸢尾花数据集作为例子来学习决策树算法。

1、数据探索

{cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.datasets import load_iris
import numpy as np
import matplotlib.pyplot as plt

# 加载数据集
iris = load_iris()

# 数据特征:150行, 4列
features = iris['data']

# 对应的鸢尾花种类: 150个,三种鸢尾花分别用 0,1,2 表示
target = iris['target']

# 自定义4个特征的名称
feature_names = iris.feature_names
feature_names = ['花萼长度', '花萼宽度', '花瓣长度', '花瓣宽度']

# 自定义三种鸢尾花的名称
class_names = iris.target_names
class_names = ['山鸢尾花', '变色鸢尾花', '维吉尼亚鸢尾花']

为了更好的识别出鸢尾花特征和种类之间的关系,我们先通过以下代码画出鸢尾花的四个特征分别与鸢尾花种类的散点图。

{cmd
1
2
3
4
5
6
7
8
9
10
11
12
colors='rgby'

# 生成散点图
for i in range(4):
plt.subplot(2, 2, i+1)
plt.scatter(features[:,i], target, c=colors[i])
plt.xlabel(feature_names[i])
plt.ylabel('花的种类')

plt.suptitle("特征和鸢尾花种类散点图")
plt.tight_layout(pad=3, w_pad=2, h_pad=2)
plt.show()

2、sklearn实现决策树

下面我们通过 sklearn 自带的决策树函数训练样本并画出决策树的图。

{cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.model_selection import train_test_split
from sklearn import tree

# 把样本分成训练集和测试集两部分, 两者比例为: 7:3
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.3, random_state=42)

# 创建决策树分类器
clf = tree.DecisionTreeClassifier()

# 训练决策树
clf.fit(X=X_train, y=y_train)

# 查看特征比重
print("feature weight : ", clf.feature_importances_)

# 查看决策树评分
print("decision tree score : ", clf.score(X=X_test, y=y_test))
1
2
feature weight : [0. 0.01911002 0.42356658 0.5573234 ] 
decision tree score : 1.0

下面使用 graphviz 出决策树的图:

{cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import graphviz

dot_data = tree.export_graphviz(
clf,
out_file=None,
feature_names=feature_names,
class_names=class_names,
filled=True,
rounded=True,
special_characters=True)

graph = graphviz.Source(dot_data)
graph.format = 'svg'
graph.render('./DecisionTree.gv', view=True)

六、参考

赞赏一杯咖啡
0%