本内容将介绍用于分类的决策树(decision tree),以及 ID3、C4.5 和 CART 算法。
决策树(decision tree)是一种基本的分类与回归方法。决策树模型呈树形结构,分为分类树、回归树和模型树,前者用于分类,后者用于预测实数值。其主要优点是模型具有可读性,分类速度快。决策树学习通常包括 3 个步骤:特征选择、决策树的生成和决策树的剪枝。这些决策树学习的思想主要来源于由 Quinlan 在 1986 年提出的 ID3 算法和 1993 年提出的 C4.5 算法,以及由 Breiman 等人在 1984 年提出的 CART 算法。
本内容主要介绍分类决策树,不涉及回归树和模型树。可以参阅 机器学习系列:决策树 02 - CART 算法 了解 CART 算法和回归树、模型树。
一、决策树模型和学习
1.1 决策树模型
一棵决策树包含一个根节点、若干个内部节点和若干个叶节点。图-1 为一棵决策树,椭圆形为判断模块,进行特征判断;矩形为终止模块,进行结果决策。从根节点到每个叶节点的路径对应了一个判断测试序列。
用决策树进行预测,从根节点开始,对实例的某一特征进行判断,根据判断结果,将实例分配到其子节点。如此递归地对实例进行判断并分配,直至到达叶节点。最后根据叶节点对实例进行决策,分类树输出类别,回归树输出预测值。
图-1 决策树模型
1.2 决策树学习
决策树的学习,也称为决策树的生成或决策树的构造。决策树学习过程:得到原始数据集,选择最优划分特征,并根据此特征划分数据集,数据将被向下传递到子节点。如果子节点上的数据满足特定条件,将构建叶节点;否则,再次选择最优特征划分数据集。如此递归地划分数据集,最终将形成一棵决策树。决策树的生成就是递归地构建树的过程。
以分类树进行说明,上面提到的特定条件为:
- 当前节点包含的样本全属于同一类别,无需划分。
- 当前可划分属性集为空,或是所有样本在所有属性上取值相同,无法划分。
- 当前节点包含的样本集合为空,不能划分。
对上面提到的情况,构造叶节点的类别设定方法:
- 设定为该节点所含样本的类别(所有样本为同一类别)。
- 设定为该节点所含样本最多的类别(即采用多数表决法决定叶节点的类别)。
- 设定为其父节点所含样本最多的类别。
决策树学习的目的是为了产生一棵泛化能力强,即处理未见实例能力强的决策树。其基本流程如下:
函数 create_tree() 的伪代码大致如下:
create_tree():
if 数据集满足特定条件:
将该节点存为叶节点
return
end if
根据最优特征划分数据集,产生多个子集
for 每个划分的子集:
if 子集为空:
构造叶结点,其类别为父节点所含样本最多的类别
else:
调用函数 create_tree() 递归划分子集
end if
end for
决策树是一种贪心算法,它要在给定时间内做出最佳选择,但并不关心能否达到全局最优。
接下来我们需要了解如何选择最优划分特征,以及如何划分数据集。
二、特征选择
特征选择在于选取对训练数据具有分类能力的特征。这样可以提高决策树学习的效率。通常特征选择的准则有信息增益、信息增益比和基尼指数等。在具体介绍它们之前,我们先来了解一下熵和条件熵。
2.1 熵与条件熵
2.1.1 熵
在信息论与概率统计中,熵(entropy,又被称为信息熵)是表示随机变量不确定性的度量。假设 X X X 是一个取有限个值的离散随机变量,其概率分布为
P ( X = x i ) = p i , i = 1 , 2 , ⋯   , n P(X=x_i) = p_i,\quad i=1,2,\cdots,n P(X=xi)=pi,i=1,2,⋯,n
则随机变量 X X X 的熵定义为
(1) H ( X ) = − ∑ i = 1 n p i l o g p i H(X) = -\sum_{i=1}^{n} p_i logp_i \tag{1} H(X)=−i=1∑npilogpi(1)
约定若 p i = 0 p_i = 0 pi=0 时, p i l o g p i = 0 p_ilogp_i = 0 pilogpi=0。 H ( X ) H(X) H(X) 的最小值为 0,最大值为 l o g n logn logn。通常,式(1)中的对数以 2 、自然常数或 10 为底,熵的单位分别称为比特(bit)、纳特(nat)或 Hart。熵越大,随机变量的不确定性就越大。
2.1.2 条件熵
假设随机变量 ( X , Y ) (X, Y) (X,Y) 的联合概率分布为
P ( X = x i , Y = y j ) = p i j , i = 1 , 2 , ⋯   , n ; j = 1 , 2 , ⋯   , m P(X=x_i, Y=y_j)=p_{ij}, \quad i=1,2,\cdots,n;j=1,2,\cdots,m P(X=xi,Y=yj)=pij,i=1,2,⋯,n;j=1,2,⋯,m
条件熵 H ( Y ∣ X ) H(Y|X) H(Y∣X) 表示在已知随机变量 X X X 的条件下随机变量 Y Y Y 的不确定性。随机变量 X X X 给定条件下 Y Y Y 的条件熵(conditional entropy) H ( Y ∣ X ) H(Y|X) H(Y∣X),定义为 X X X 给定条件下 Y Y Y 的条件概率分布的熵对 X X X 的数学期望
(2) H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) , p i = P ( X = x i ) ; i = 1 , 2 , ⋯   , n H(Y|X) = \sum_{i=1}^n p_i H(Y|X=x_i), \quad p_i = P(X=x_i); i=1,2,\cdots,n \tag{2} H(Y∣X)=i=1∑npiH(Y∣X=xi),pi=P(X=xi);i=1,2,⋯,n(2)
当熵和条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的熵和条件熵分别称为经验熵(empirical entropy)和经验条件熵(empirical conditional entropy)。
2.2 信息增益
信息增益(information gain)表示得知特征 X X X 的信息而使得类 Y Y Y 的信息的不确定性减少的程度。
特征 A A A 对训练数据集 D D D 的信息增益 G a i n ( D , A ) Gain(D,A) Gain(D,A),定义为集合 D D D 的经验熵 H ( D ) H(D) H(D) 与特征 A A A 给定条件下 D D D 的经验条件熵 H ( D ∣ A ) H(D|A) H(D∣A) 之差,即
(3) G a i n ( D , A ) = H ( D ) − H ( D ∣ A ) Gain(D,A) = H(D) - H(D|A) \tag{3} Gain(D,A)=H(D)−H(D∣A)(3)
假设训练数据集为 D D D,存在以下设定:
- ∣ D ∣ |D| ∣D∣ 为其样本容量,即样本个数。
- 训练数据集 D D D 有 K K K 个类 C k C_k Ck, k = 1 , 2 , ⋯   , K k=1,2,\cdots,K k=1,2,⋯,K, ∣ C k ∣ |C_k| ∣Ck∣ 为属于类 C k C_k Ck 的样本个数。
- 特征 A A A 有 n n n 个不同的取值 { a 1 , a 2 , ⋯   , a n } \{a_1,a_2,\cdots,a_n\} {a1,a2,⋯,an},根据特征 A A A 的取值将 D D D 划分为 n n n 个子集 D 1 , D 2 , ⋯   , D n D_1,D_2,\cdots,D_n D1,D2,⋯,Dn, ∣ D i ∣ |D_i| ∣Di∣ 为 D i D_i Di 的样本个数。
- 子集 D i D_i Di 中属于类 C k C_k Ck 的样本的集合为 D i k D_{ik} Dik, ∣ D i k ∣ |D_{ik}| ∣Dik∣ 为 D i k D_{ik} Dik 的样本个数。
则使用特征 A A A 划分训练数据集 D D D 获得的信息增益为
(4) G a i n ( D , A ) = ( − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ l o g ∣ C k ∣ ∣ D ∣ ) − ( − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) ) Gain(D,A) = \Big(-\sum_{k=1}^{K} \frac{|C_k|}{|D|}log\frac{|C_k|}{|D|}\Big) - \Big(-\sum_{i=1}^{n} \frac{|D_i|}{|D|}H(D_i) \Big) \tag{4} Gain(D,A)=(−k=1∑K∣D∣∣Ck∣log∣D∣∣Ck∣)−(−i=1∑n∣D∣∣Di∣H(Di))(4)
其中, H ( D i ) H(D_i) H(Di) 为
(5) H ( D i ) = − ∑ k = 1 K ∣ D i k ∣ ∣ D i ∣ l o g ∣ D i k ∣ ∣ D i ∣ H(D_i) = -\sum_{k=1}^{K} \frac{|D_{ik}|}{|D_i|} log\frac{|D_{ik}|}{|D_i|} \tag{5} H(Di)=−k=1∑K∣Di∣∣Dik∣log∣Di∣∣Dik∣(5)
ID3 算法就是使用信息增益来选择划分特征。
2.3 信息增益率
信息增益准则对可取值数目较多的特征有所偏好,为减少这种偏好可能带来的不利影响,可使用信息增益率(information gain ratio)来选择最优划分特征。
(6) G a i n _ r a t i o ( D , A ) = G a i n ( D , A ) I V ( A ) Gain\_ratio(D,A) = \frac{Gain(D,A)}{IV(A)} \tag{6} Gain_ratio(D,A)=IV(A)Gain(D,A)(6)
其中, I V ( A ) IV(A) IV(A) 为
(7) I V ( A ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ l o g ∣ D i ∣ ∣ D ∣ IV(A) = -\sum_{i=1}^{n} \frac{|D_i|}{|D|} log\frac{|D_i|}{|D|} \tag{7} IV(A)=−i=1∑n∣D∣∣Di∣log∣D∣∣Di∣(7)
称为特征 A A A 的“固有值”(intrinsic value)。特征 A A A 的可能取值数目越多(即 n n n 越大),则 I V ( A ) IV(A) IV(A) 的值通常会越大。
C4.5算法就是使用信息增益率来选择划分特征。 需注意的是,增益率准则对可取值数目较少的特征有所偏好。因此,C4.5 算法并不是直接选择增益率最大的候选划分特征,而是使用了一个启发式:先从候选划分特征中找出信息增益高于平均水平的特征,再从中选择增益率最高的。
2.4 基尼指数
基尼指数(Gini index)定义为
(8) G i n i ( D ) = ∑ k = 1 K ∑ k ′ ≠ k p k p k ′ Gini(D) = \sum_{k=1}^{K} \sum_{k^{'} \neq k}p_kp_{k^{'}} \tag{8} Gini(D)=k=1∑Kk′̸=k∑pkpk′(8)
(9) = ∑ k = 1 K p k ( 1 − p k ) = \sum_{k=1}^{K} p_k(1-p_k) \tag{9} =k=1∑Kpk(1−pk)(9)
(10) = 1 − ∑ k = 1 K p k 2 = 1 - \sum_{k=1}^{K}p_k^2 \tag{10} =1−k=1∑Kpk2(10)
从上面的公式可知,基尼指数有两种不同理解形式:
- 从式(8)来看, G i n i ( D ) Gini(D) Gini(D) 表示从数据集 D D D 中随机抽取两个样本,其类别标记不一致的概率。
- 从式(9)来看, G i n i ( D ) Gini(D) Gini(D) 表示从数据集 D D D 中随机选中的一个样本被分错的概率。
因此, G i n i ( D ) Gini(D) Gini(D) 越小,则数据集 D D D 的纯度越高。
则使用特征 A A A 划分训练数据集后的基尼指数为
(11) G i n i ( D , A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ G i n i ( D i ) Gini(D,A) = \sum_{i=1}^{n} \frac{|D_i|}{|D|} Gini(D_i) \tag{11} Gini(D,A)=i=1∑n∣D∣∣Di∣Gini(Di)(11)
其中 G i n i ( D i ) Gini(D_i) Gini(Di) 为
(12) G i n i ( D i ) = 1 − ∑ k = 1 K ( ∣ D i k ∣ ∣ D i ∣ ) 2 Gini(D_i) = 1 - \sum_{k=1}^{K} \Big(\frac{|D_{ik}|}{|D_i|}\Big)^2 \tag{12} Gini(Di)=1−k=1∑K(∣Di∣∣Dik∣)2(12)
CART 算法进行分类时,就是使用基尼指数来选择划分特征。
三、划分数据集方法
3.1 离散属性
针对离散属性,可以采用多分法或者二分法划分数据集。假设有数据集 D D D,其中属性 A A A 有 n n n 个不同的取值,根据属性 A A A 的取值将数据集划分为 n n n 个子集,就是多分法;选择其中的某个取值 a i a_i ai,根据该值将数据集划分为 2 个子集 D 1 D_1 D1 和 D 2 D_2 D2, D 1 D_1 D1 包含属性 A A A 不大于 a i a_i ai 的样本, D 2 D_2 D2 包含属性 A A A 大于 a i a_i ai 的样本,就是二分法。
注意:如果采用多分法后,该属性不能作为其后代节点的划分属性;如果采用二分法,该属性可以继续作为其后代节点的划分属性。
3.2 连续属性
由于连续性属性的可取值不再有限,因此,不能直接根据连续型属性的可取值来对节点进行划分。此时,连续型属性离散化技术可派上用场。最简单的策略是采用二分法(bi-partition)对连续属性进行处理,C4.5 和 CART 决策树算法都使用该方法。
注意:因为连续属性只能采用二分法,所以该属性还可作为其后代节点的划分属性。
ID3 算法不能处理连续属性,C4.5 和 CART 算法可以处理连续型属性。
四、决策树的剪枝
在决策树学习中,为了尽可能对数据集进行正确分类,从而构建出过于复杂的决策树。这样产生的树往往对训练数据集的分类很正确,但对未知的测试数据的分类却没有那么准确,即出现过拟合现象。可以通过降低对决策树的复杂度(即去掉一些分支)来解决这个问题,这个过程称之为剪枝(pruning)。
决策树剪枝有预剪枝(prepruning)和后剪枝(postpruning)两种。预剪枝是指在决策树生成过程中,对每个节点在划分前后进行估计,若当前节点的划分不能带来决策树泛化性能提升,则停止划分并将当前节点标记为叶节点。后剪枝则是先从训练集生成一棵完整的决策树;然后从上到下找到叶节点,用测试集来判断将这些叶节点合并是否能降低测试误差,如果能就进行合并。
决策树的生成对应于模型的局部选择,决策树的剪枝对应于模型的全局选择。决策树的生成只考虑局部最优,相对地,决策树的剪枝则考虑全局最优。
在 机器学习系列:决策树 02 - CART 算法 里面会讲到剪枝的具体实现。
五、代码实现
在生成分类决策树时,ID3、C4.5 和 CART 三种算法的流程基本相同,基本上只需要在特征选择和特征划分时进行不同处理即可。下面给出 ID3 算法的 Python 代码实现(Python 3.x):
from math import log
class DecisionTreeID3:
def __init__(self):
pass
def creat_tree(self, data_set, feature_labels):
"""
创建决策树
data_set:创建决策树的数据集
feature_labels:特征名列表
"""
labels_list = [sample[-1] for sample in data_set]
# 数据集中的样本属于同一个类别,返回这个类别
if labels_list.count(labels_list[0]) == len(labels_list):
return labels_list[0]
# 当数据集中已经没有可划分的属性,采用多数表决法返回类别
if len(data_set[0]) == 1:
return self._majority_cnt(labels_list)
# 选择最优划分属性
best_feature = self._choose_best_feature_to_split(data_set)
best_feature_label = feature_labels[best_feature]
# 初始化树
tree = {best_feature_label: {}}
# 移除已经选择的划分属性
del feature_labels[best_feature]
# 获取当前划分属性的所有取值
feature_values = [sample[best_feature] for sample in data_set]
# set() 可以去除 feature_values 中存在的相同值
unique_values = set(feature_values)
for value in unique_values:
sub_feature_labels = feature_labels[:]
tree[best_feature_label][value] = self.creat_tree(
self._split_data_set(data_set, best_feature, value),
sub_feature_labels)
return tree
def _calc_shannon_ent(self, data_set):
"""
计算信息熵
"""
# 计算数据集中样本数量
samples_num = len(data_set)
labels_count = {}
# 遍历数据集中所有样本,计算每种类别的样本数量
for sample in data_set:
# 获取样本的标签值,即类别
cur_label = sample[-1]
if cur_label not in labels_count.keys():
labels_count[cur_label] = 0
labels_count[cur_label] += 1
shannon_ent = 0.0
# 求信息熵
for count in labels_count.values():
prob = float(count)/samples_num
shannon_ent -= prob*log(prob, 2)
return shannon_ent
def _split_data_set(self, data_set, axis, value):
"""
按照给定特征划分数据集,返回数据集中满足特征值为 value 的子集
data_set:待划分的数据集
axis:划分数据集的特征
value:划分数据集的特征值
"""
sub_data_set = []
for feat_vec in data_set:
if feat_vec[axis] == value:
reduced_feat_vec = feat_vec[:axis]
reduced_feat_vec.extend(feat_vec[axis+1:])
sub_data_set.append(reduced_feat_vec)
return sub_data_set
def _choose_best_feature_to_split(self, data_set):
"""
选择最好的划分属性
data_set:待划分的数据集
"""
# 获取数据集的样本数量
samples_num = len(data_set)
# 获取样本特征值数量
feature_num = len(data_set[0]) - 1
# 计算数据集未划分前的信息熵
base_entropy = self._calc_shannon_ent(data_set)
best_info_gain = 0.0
best_feature = -1
# 遍历每一个特征
for i in range(feature_num):
feature_values = [sample[i] for sample in data_set]
unique_vals = set(feature_values)
new_entropy = 0.0
# 遍历当前特征的每一个值,并计算出划分数据集后信息熵
for value in unique_vals:
sub_data_set = self._split_data_set(data_set, i, value)
prob = float(len(sub_data_set))/samples_num
new_entropy -= prob*self._calc_shannon_ent(sub_data_set)
info_gain = base_entropy - new_entropy
# 获取信息增益最高的划分特征和特征值
if info_gain > best_info_gain:
best_info_gain = info_gain
best_feature = i
return best_feature
def _majority_cnt(self, labels_list):
"""
返回数量最多的类型
labels_list:类型列表
"""
labels_count = {}
for label in labels_list:
if label not in labels_count.keys():
labels_count[label] = 0
labels_count[label] += 1
sorted_labels_count = \
sorted(labels_count.items(), key=lambda x: x[1], reverse=True)
return sorted_labels_count[0][0]
def get_training_datas():
data_set = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
feature_labels = ['no surfacing', 'flippers']
return data_set, feature_labels
if __name__ == "__main__":
data_set, feature_labels = get_training_datas()
decision_tree_mode = DecisionTreeID3()
decision_tree = decision_tree_mode.creat_tree(data_set, feature_labels)
print(decision_tree)
疑问:
- C4.5 对离散型属性是采用二分还是多分?
参考:
[1] 周志华《机器学习》
[2] 李航《统计学习方法》
[3] 《机器学习实战》