决策树模型与学习
决策树分类: 从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到对应子节点;若子节点为特征的一个取值,则递归的对实例进行测试并分配,直到到达叶节点(类别)。
决策树与条件概率分布(判别式模型) 特征空间被划分为互不相交的区域,每个区域定义一个类的概率分布,从而构成了一个条件概率分布。
各区域(叶节点)上的条件概率偏向于一个类,决策树分类时,强行将节点的实例划分为条件概率较大的一类。
特征选择
特征选择在于选取具有分类能力的特征,使得每次分割之后,各分支节点所含样本尽可能属于同一类别。这样可加快决策树的生成。
信息增益
信息熵(Information Entropy)是表示随机变量纯度/不确定性的指标。设
X
X
X为有限取值的随机变量(类别),其概率分布(类别比例)
P
(
X
=
x
i
)
=
p
i
,
(
i
=
1
,
2
,
⋯
 
,
∣
Y
∣
)
P(X=x_i)=p_i, \quad (i = 1, 2, \cdots, \mathcal{|Y|})
P(X=xi)=pi,(i=1,2,⋯,∣Y∣)
则随机变量
X
X
X的信息熵记作
H
(
X
)
H(X)
H(X),即
H
(
X
)
=
−
∑
i
=
1
∣
Y
∣
p
i
log
2
p
i
H(X)=-\sum_{i=1}^{\mathcal{|Y|}}p_i\log_2p_i
H(X)=−i=1∑∣Y∣pilog2pi
熵的取值与随机变量取值无关,仅与其概率分布有关。熵值越小,不确定性越低、数据集纯度越高。 0 ≤ H ( p ) ≤ log ∣ Y ∣ 0 \leq H(p) \leq \log \mathcal{|Y|} 0≤H(p)≤log∣Y∣。
设有随机变量
(
X
,
Y
)
(X, Y)
(X,Y),其联合概率分布
P
(
X
=
x
i
,
Y
=
y
i
)
=
p
i
j
P(X = x_i, Y = y_i)=p_{ij}
P(X=xi,Y=yi)=pij
随机变量X和Y的条件熵
H
(
Y
∣
X
)
=
−
∑
i
=
1
n
∑
j
=
1
m
p
(
x
i
,
y
i
)
log
2
p
(
y
i
∣
x
i
)
=
−
∑
i
=
1
n
∑
j
=
1
m
p
(
x
i
)
p
(
y
i
∣
x
i
)
log
2
p
(
y
i
∣
x
i
)
=
∑
i
=
1
n
p
(
x
i
)
H
(
Y
∣
X
=
x
i
)
\begin{aligned} H(Y|X) &= - \sum_{i=1}^n \sum_{j=1}^m p(x_i, y_i) \log_2 p(y_i | x_i) \\ & = - \sum_{i=1}^n \sum_{j=1}^m p(x_i)p(y_i|x_i) \log_2 p(y_i | x_i) \\ & = \sum_{i=1}^n p(x_i) H(Y|X=x_i) \end{aligned}
H(Y∣X)=−i=1∑nj=1∑mp(xi,yi)log2p(yi∣xi)=−i=1∑nj=1∑mp(xi)p(yi∣xi)log2p(yi∣xi)=i=1∑np(xi)H(Y∣X=xi)
条件熵
H
(
Y
∣
X
)
H(Y|X)
H(Y∣X)表示已知
X
X
X的条件下
Y
Y
Y的不确定性,上式为已知
X
X
X的条件下
Y
Y
Y的条件概率分布的熵对
X
X
X的数学期望。当熵和条件熵由数据估计得到时,熵和条件熵分别称为经验熵
和经验条件熵
。
信息增益度量得知特征
X
X
X而使得类
Y
Y
Y不确定性减少的程度,定义为样本集
D
D
D的经验熵与已知特征
a
a
a条件下
D
D
D的经验条件熵之差,即
g
(
D
,
a
)
=
H
(
D
)
−
H
(
D
∣
a
)
g(D,a)=H(D)-H(D|a)
g(D,a)=H(D)−H(D∣a)
注:信息增益亦称为类与特征的互信息
。
最优划分属性应使划分后的样本信息增益/纯度最大(不确定性降低),即条件熵取最小
a
o
p
t
=
arg
min
A
∑
v
=
1
V
p
v
H
(
Y
∣
X
=
x
v
)
a_{opt} = \arg \min_A \sum_{v=1}^Vp_vH(Y|X=x_v)
aopt=argAminv=1∑VpvH(Y∣X=xv)
可见,信息增益准则偏好于取值较多的属性(V较大)。
编号 | 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 | 好瓜 |
---|---|---|---|---|---|---|---|
1 | 青绿 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 是 |
2 | 乌黑 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 是 |
3 | 乌黑 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 是 |
4 | 青绿 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 是 |
5 | 浅白 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 是 |
6 | 青绿 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | 是 |
7 | 乌黑 | 稍蜷 | 浊响 | 稍糊 | 稍凹 | 软粘 | 是 |
8 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 硬滑 | 是 |
9 | 乌黑 | 稍蜷 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 否 |
10 | 青绿 | 硬挺 | 清脆 | 清晰 | 平坦 | 软粘 | 否 |
11 | 浅白 | 硬挺 | 清脆 | 模糊 | 平坦 | 硬滑 | 否 |
12 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 软粘 | 否 |
13 | 青绿 | 稍蜷 | 浊响 | 稍糊 | 凹陷 | 硬滑 | 否 |
14 | 浅白 | 稍蜷 | 沉闷 | 稍糊 | 凹陷 | 硬滑 | 否 |
15 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | 否 |
16 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 硬滑 | 否 |
17 | 青绿 | 蜷缩 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 否 |
由上表可知,类别
∣
Y
∣
=
2
\mathcal{|Y|}=2
∣Y∣=2,信息熵
H
(
D
)
=
−
8
17
log
2
8
17
−
9
17
log
2
9
17
=
0.998
H(D)=-\frac{8}{17} \log_2\frac{8}{17} -\frac{9}{17} \log_2\frac{9}{17} = 0.998
H(D)=−178log2178−179log2179=0.998
若选择色泽作为划分属性,色泽包含的属性值有{青绿,乌黑,浅白},分别标记位
D
1
D_1
D1、
D
2
D_2
D2、
D
3
D_3
D3,则
g
(
D
,
色
泽
)
=
H
(
D
)
−
(
6
17
H
(
D
1
)
+
6
17
H
(
D
2
)
+
5
17
H
(
D
3
)
)
=
0.998
−
1
17
[
6
(
−
3
6
log
2
3
6
−
3
6
log
2
3
6
)
+
(
−
4
6
log
2
4
6
−
2
6
log
2
2
6
)
+
(
−
4
5
log
2
4
5
−
1
5
log
2
1
5
)
]
=
0.109
\begin{aligned} g(D, 色泽) &= H(D) - \left(\frac{6}{17}H(D_1)+ \frac{6}{17}H(D_2) + \frac{5}{17}H(D_3)\right) \\ & = 0.998 - \frac{1}{17} \left[ 6\left( -\frac{3}{6} \log_2 \frac{3}{6} -\frac{3}{6} \log_2 \frac{3}{6} \right) + \left( -\frac{4}{6} \log_2 \frac{4}{6} -\frac{2}{6} \log_2 \frac{2}{6} \right) + \left( -\frac{4}{5} \log_2 \frac{4}{5} -\frac{1}{5} \log_2 \frac{1}{5} \right) \right] \\ & = 0.109 \end{aligned}
g(D,色泽)=H(D)−(176H(D1)+176H(D2)+175H(D3))=0.998−171[6(−63log263−63log263)+(−64log264−62log262)+(−54log254−51log251)]=0.109
同理,可计算出其他特征的信息增益,其中具有最大信息增益的属性为 g ( D , 纹 理 ) = 0.381 g(D, 纹理)=0.381 g(D,纹理)=0.381。因此,使用纹理特征对样本集进行划分,所得样本的不确定性最小。
信息增益比
极端情况下,若样本集含n个样本,离散属性a含n个属性。若使用属性a分割样本集,则所得各分支节点的信息熵均为0,此时划分后的样本集纯度最大。但每个分支节点仅含1个样本,所得决策树无泛化能力。
定义增益率为特征
a
a
a的信息增益
g
(
D
,
a
)
g(D, a)
g(D,a)与训练集
D
D
D关于特征
a
a
a的值的熵
H
a
(
D
)
H_a(D)
Ha(D)之比,即
g
R
(
D
,
a
)
=
g
(
D
,
a
)
H
a
(
D
)
g_R(D, a) = \frac{g(D, a)}{H_a(D)}
gR(D,a)=Ha(D)g(D,a)
其中分母项为特征a的信息熵 H a ( D ) = ∑ i = v V ∣ D v ∣ D ∣ log 2 D v D H_a(D) = \sum_{i=v}^V\dfrac{|D_v}{|D|} \log_2\dfrac{D_v}{D} Ha(D)=∑i=vV∣D∣∣Dvlog2DDv。
属性a的取值越多,分母可能越大,增益率准则偏好于较少取值多的属性。因此,最优分割属性应同时具有较高信息增益和增益比,如C.5算法
先选择信息增益高于平均值的属性,再从中选择增益率高的属性。
ID3算法
决策树生成
输入:训练集
D
D
D,特征集
A
A
A,阈值
ε
\varepsilon
ε
输出:决策树
T
T
T
递归生成决策树算法,如下:
- 若数据集 D D D中所有实例均属于同一类 C k C_k Ck,则 T T T为单节点树(叶节点),将类别 C k C_k Ck作为节点类标记,返回 T T T;
- 若特征集 A A A为空,则 T T T为单节点树,将 D D D中含样本最多的类别 C k C_k Ck作为其类标记,返回 T T T;
- 计算 A A A中各特征的信息增益,选择信息增益最大的特征 a o p t a_{opt} aopt对样本集 D D D进行划分;
- 若 a o p t a_{opt} aopt的信息增益小于阈值 ε \varepsilon ε,则置 T T T为单节点树,将 D D D中含样本最多的类别 C k C_k Ck作为其类标记,返回 T T T;
- 否则,使用 a o p t a_{opt} aopt的 V V V个不同属性值将 D D D划分为 V V V个不同的子集 D v D_v Dv,每个子集均为节点的一个分支;
- 对所有分支节点,令 D = D v D=D_v D=Dv、 A = A − a o p t A=A-a_{opt} A=A−aopt,递归调用上述步骤,得到分支树并返回;
ID3算法的不足
- 未考虑具有连续值的属性;
- 极端情况下选择信息增益最大的属性(完全随机性变量)会使模型失去泛化能力;
- 未考虑缺失值处理与过拟合问题;
C4.5算法
C4.5对ID3算法进行了改进,使用信息增益比选择每次划分数据集的最优特征,并对缺失值和过拟合问题进行了处理。
连续值处理
- 将连续值离散化,如m个样本的特征 a a a有 m m m个不同的属性值,将属性值升序排列为 a 1 , ⋯   , a m a_1,\cdots,a_m a1,⋯,am;
- 取相邻属性值的均值作为划分点,共计 m − 1 m-1 m−1个,每个划分点 t t t可将 D D D分为子集 D t − D_t^- Dt−和 D t + D_t^+ Dt+;
- 分别计算
m
−
1
m-1
m−1个划分点作为二元分类的信息增益,取信息增益最大的点作为分类点;
g R ( D , a ) = max t ∈ T a g R ( D , a , t ) = max t ∈ T a ( H ( D ) − ∑ λ ∈ { − , + } ∣ D t λ ∣ ∣ D ∣ H ( D t λ ) ) g_R(D, a) = \max_{t \in T_a} g_R(D, a, t) = \max_{t \in T_a} (H(D)- \sum_{\lambda \in \{-, +\}} \frac{|D_t^\lambda|}{|D|}H(D_t^\lambda)) gR(D,a)=t∈TamaxgR(D,a,t)=t∈Tamax(H(D)−λ∈{−,+}∑∣D∣∣Dtλ∣H(Dtλ)) - 以连续属性划分的节点,连续属性还可作为其后代节点的划分属性;
缺失值处理
如何在属性值缺失的情况下选择最优划分属性?
给定训练集
D
D
D和属性
a
a
a,令
D
~
\tilde D
D~表示
D
D
D在属性
a
a
a上没有缺失值的样本子集,每个样本
x
x
x的权重为
w
x
w_x
wx(初始为1),则
ρ
=
∑
x
∈
D
~
w
x
∑
x
∈
D
w
x
 
p
~
k
=
∑
x
∈
D
~
k
w
x
∑
x
∈
D
~
w
x
(
1
≤
k
≤
∣
Y
∣
)
 
r
~
v
=
∑
x
∈
D
~
v
w
x
∑
x
∈
D
~
w
x
(
1
≤
v
≤
V
)
\begin{aligned} \rho &= \frac{\sum_{x \in \tilde D} w_x}{\sum_{x \in D} w_x} \\ \,\\ \tilde p_k &= \frac{\sum_{x \in \tilde D_k} w_x}{\sum_{x \in \tilde D} w_x} \quad(1 \leq k \leq |\mathcal{Y}|) \\ \,\\ \tilde r_v &= \frac{\sum_{x \in \tilde D^v} w_x}{\sum_{x \in \tilde D} w_x} \quad (1 \leq v \leq V) \end{aligned}
ρp~kr~v=∑x∈Dwx∑x∈D~wx=∑x∈D~wx∑x∈D~kwx(1≤k≤∣Y∣)=∑x∈D~wx∑x∈D~vwx(1≤v≤V)
式中, ρ \rho ρ表示未缺失值比例、 p ~ k \tilde p_k p~k表示 D ~ \tilde D D~中类别为 k k k的样本比例、 r ~ v \tilde r_v r~v表示 D ~ \tilde D D~中属性 a a a取值为 a v a_v av的样本比例。
因此缺失值属性a的信息增益推广为
g
(
D
,
a
)
=
ρ
×
g
(
D
~
,
a
)
=
ρ
×
(
H
(
D
~
)
−
∑
v
=
1
V
r
~
v
H
(
D
~
v
)
)
,
H
(
D
~
v
)
=
−
∑
k
=
1
∣
Y
∣
p
~
k
log
2
p
k
g(D, a) = \rho \times g(\tilde D, a) = \rho \times \left(H(\tilde D) - \sum_{v=1}^V \tilde r_v H(\tilde D^v) \right), \quad H(\tilde D^v) =-\sum_{k=1}^{|\mathcal{Y}|} \tilde p_k \log_2 p_k
g(D,a)=ρ×g(D~,a)=ρ×(H(D~)−v=1∑Vr~vH(D~v)),H(D~v)=−k=1∑∣Y∣p~klog2pk
给定划分属性,若样本在该属性上值缺失,如何对样本进行分配?
若样本 x x x在划分属性 a a a上取值已知,则将 x x x划入其取值对应的子节点,样本权值保持不变;若取值未知则将样本 x x x划分到所有分支节点,样本权值在 a v a^v av的分支节点中调整为 r ~ v ⋅ w x \tilde r_v \cdot w_x r~v⋅wx。
直观上,同一样本以不同概率分配到不同的分支节点。
C4.5算法的不足
- 生成的决策树为多叉树(一个父节点含多个子节点),没有二叉树模型效率高;
- 只能用于分类,不能用于回归;
- 使用熵模型选取最优划分属性,计算复杂,尤其是处理连续值属性;
决策树剪枝
一般情况下递归产生的决策树对训练数据的分类很准确,但有可能对未知数据的分类表现很差。上述现象称为过拟合,一般通过简化决策树复杂度(剪枝)解决。
预剪枝
在划分之前,评估单节点树以及节点按属性值展开后的预测精度,若单节点树的预测精度不低于展开后树的预测精度,则将该节点作为叶节点。
后剪枝
定义决策树的损失函数如下:
C
α
(
T
)
=
∑
t
=
1
∣
T
∣
N
t
H
t
(
T
)
+
α
∣
T
∣
=
C
(
T
)
+
α
∣
T
∣
,
H
t
(
T
)
=
−
∑
k
N
t
k
N
t
log
N
t
k
N
t
C_\alpha(T) = \sum_{t=1}^{|T|}N_tH_t(T)+\alpha |T| = C(T) + \alpha |T|, \quad H_t(T)=-\sum_k \frac{N_{tk}}{N_t} \log \frac{N_{tk}}{N_t}
Cα(T)=t=1∑∣T∣NtHt(T)+α∣T∣=C(T)+α∣T∣,Ht(T)=−k∑NtNtklogNtNtk
式中, ∣ T ∣ |T| ∣T∣为叶节点数目(模型的复杂度), t t t是树 T T T的叶节点, N t N_t Nt为节点 t t t所含样本数, N t k N_{tk} Ntk为 t t t节点中类别为 k k k的样本数, k = 1 , 2 , ⋯   , K k=1,2,\cdots,K k=1,2,⋯,K, H t ( T ) H_t(T) Ht(T)为 t t t节点的经验熵,可变参数 a ≥ 0 a \geq 0 a≥0, C ( T ) C(T) C(T)为模型对训练数据的预测误差。
参数 α \alpha α的值可平衡模型复杂度和训练误差。 α = 0 \alpha=0 α=0时,表示不考虑模型复杂程度,只考虑训练误差。较大的 α \alpha α偏向于较简单的模型( T T T小,训练误差大),较小的 α \alpha α偏向于较复杂的模型( T T T大,训练误差小)。
决策树的生成只考虑拟合效果(局部模型),而决策树剪枝是通过优化损失函数。
程序实现ID3
from collections import Counter
from itertools import chain
from math import log2
import numpy as np
from_iterable = chain.from_iterable
def cal_entropy(array):
"""计算信息熵"""
total = 1. * len(array)
ent = 0.0
for value in Counter(array).values():
prob = value / total
ent -= prob * log2(prob)
return ent
def cal_gain(feat_values, categories):
"""计算某特征的信息增益"""
uniques = set(feat_values)
nums = len(uniques)
feat_dict = dict(zip(uniques, range(nums)))
# 提取各特征值的子集
sub_labels = [[] for _ in range(nums)]
for value, category in zip(feat_values, categories):
sub_labels[feat_dict[value]].append(category)
# 计算条件熵
cond_ent = 0.0
total = len(categories)
for array in sub_labels:
cond_ent += cal_entropy(array) * len(array) / total
# 返回信息增益
return cal_entropy(categories) - cond_ent
def tree_depth(tree):
"""计算决策树深度,字典形式存储"""
if isinstance(tree, dict):
branches = [value for value in tree.values()][0]
return max(tree_depth(node) for node in branches.values()) + 1
else:
return 1
class DecisionTree_ID3:
def __int__(self):
self.tree = None
pass
def train(self, X, y, names=None):
self._feat_names = ['FEAT_{}'.format(i) for i in
range(len(X[0]))] if names is None else names
unique = list(set(y))
category2num = dict((category, num) for category, num in
zip(unique, range(len(unique))))
self._names = unique
categories = [category2num[i] for i in y]
data_set = np.hstack((X, np.array([categories]).T))
# 生成决策树
self.tree = self._make_tree(data_set, self._feat_names)
def _make_tree(self, data_set, feat_names):
"""生成决策树"""
categories = data_set[:, -1]
# 当前节点包含的样本为同一类别
category = categories[0]
if np.all(categories == category):
return self._names[int(category)]
# 当前属性集为空,返回样本中频数最大的类别
if len(data_set[0]) == 1:
return Counter(categories).most_common(1)[0][0]
best_num = self.best_feat(data_set)
best_name = feat_names[best_num]
# 以当前节点为根的树
my_tree = {best_name: {}}
node = my_tree[best_name]
# 最优特征
values = data_set[:, best_num]
# 遍历各属性值
data_set = np.delete(data_set, best_num, axis=1)
feat_names = np.delete(feat_names, best_num)
for value in set(values):
sub_data_set = data_set[values == value]
node[value] = self._make_tree(sub_data_set, feat_names)
return my_tree
def best_feat(self, data_set):
"""计算最优特征,ID3算法每次选取信息增益最大的特征
"""
categories = data_set[:, -1]
nums = len(data_set[0]) - 1
gains = [cal_gain(data_set[:, i], categories) for i in range(nums)]
# 找到最大信息增益特征的所在列位置
num = 0
max_gain = gains[0]
for i, gain in enumerate(gains[1:]):
if max_gain < gain:
num = i + 1
max_gain = gain
return num
if __name__ == '__main__':
X = [['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'],
['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑'],
['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'],
['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑'],
['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'],
['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘'],
['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑'],
['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑'],
['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘'],
['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘'],
['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑'],
['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑'],
['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑']]
y = ['好瓜', '好瓜', '好瓜', '好瓜', '好瓜', '好瓜', '好瓜', '好瓜', '坏瓜', '坏瓜', '坏瓜', '坏瓜',
'坏瓜', '坏瓜', '坏瓜', '坏瓜', '坏瓜']
names = ['色泽', '根蒂', '敲击', '纹理', '脐部', '触感']
tree = DecisionTree_ID3()
tree.train(X, y, names)
print(tree.tree)
# print(tree_depth(tree.tree))