决策树(Decision Tree)
4.1 基本流程
决策树是基于树模型做决策。一般的,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点;叶结点对应于决策结果,其他每个结点则对应于一个属性测试;每个结点包含的样本集合根据属性测试的结果被划分到子结点中;根结点包含样本全集。
从根结点到每个叶结点的路径对应了一个判定测试序列。决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树。
根据表4-1可生成如图4-1所示的决策树。
如上所述,图4-1决策树每一个通往叶子结点的路径就是一条判断序列。例如对于表4-1中的第1个样本,从决策树根结点出发,判断“纹理”特征,1号样本纹理为“清晰”,进入纹理结点最左侧的子结点中,接着判断“根蒂”,1号样本根蒂为“蜷缩”,进入根蒂结点最左侧的子结点中,该子结点为叶子结点,因此得到1号样本的预测结果为“好瓜”。
根据训练集样本生成一棵决策树的算法,如图4-2所示。
决策树的生成是一个递归过程。简单得说,生成决策树主要分为两个部分。
① 特征选择: 特征选择是指从当前结点所拥有的特征中选择一个特征
a
a
a作为当前结点的划分标准,一般特征选择的策略将直接影响决策树算法的性能。此步骤对应图4-2中TreeGenerate过程的第8步。
② 生成子结点: 按照当前结点所包含的训练样本数据在属性
a
a
a上的取值将训练样本分成若干类,每一类作为当前结点的子结点所具有的数据样本。然后对未划分的结点进行考察,递归地进行属性选择和数据样本划分。
例如,对于表4-1中的17个样本,我们选择“纹理”属性对数据集进行划分,得到三个数据子集,分别是:清晰(1,2,3,4,5,6,8,10,15),稍糊(7,9,13,14,17),模糊(11,12,16)。生成三个子结点,将这三个子集分别分到对应的子结点中,就形成了图4-3所示的二层决策树。然后递归处理,对新生成的三个子结点执行同样的操作(注意:新生成的三个子结点不包含属性“纹理”)。
在图4-2所示的算法流程中,有三种情形会导致递归返回。
(1)当前结点所包含的样本全属于同一类别,无需划分;
(2)当前属性集为空,或是所有样本在所有属性上取值相同,无法划分;
(3)当前结点包含的样本集合为空,不能划分。
对于情况(1),很好理解,当前结点所包含的样本全部属于同一类别,即使进行划分生成子树,该结点的子树所有叶结点也只能属于同一类别,这样做是没有意义的。
对于情况(2),属性集为空,则没有特征可供选择,划分没有依据;所有样本在所有属性上取值相同,则划分出的集合只有一个(它本身,相当于没有划分)。对于此种情况,直接把当前结点标记为叶结点,并将其类别设定为该结点所含样本最多的类别。
对于情况(3),直接把当前结点标记为叶结点,其类别为父结点所含样本最多的类别。
4.2 划分选择
此小节主要介绍特征选择部分选择特征的原则(“特征”和“属性”是一个意思)。一般而言,随着划分过程不断进行,我们希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的“纯度”越来越高。
常用三种指标来作为选择特征进行划分的依据。
4.2.1 信息增益
信息熵: 度量样本集合纯度最常用的一种指标。假定当前样本集合
D
D
D中第
k
k
k类样本所占的比例为
p
k
(
k
=
1
,
2
,
.
.
.
,
∣
y
∣
)
p_k(k=1,2,...,|y|)
pk(k=1,2,...,∣y∣),则
D
D
D的信息熵定义为
E
n
t
(
D
)
=
−
∑
k
=
1
∣
y
∣
p
k
∗
l
o
g
2
p
k
\begin{aligned} Ent(D)=-\sum_{k=1}^{|y|}p_k*log_2p_k \end{aligned}
Ent(D)=−k=1∑∣y∣pk∗log2pk
E
n
t
(
D
)
Ent(D)
Ent(D)值越小,则
D
D
D的纯度越高。
信息增益: 假定离散属性
a
a
a有
V
V
V个可能的取值
{
a
1
,
a
2
,
.
.
.
,
a
V
}
\{a^1,a^2,...,a^V\}
{a1,a2,...,aV}(例如表4-1中“纹理”属性有:清晰、稍糊、模糊,三个取值) ,若使用
a
a
a来对样本集
D
D
D进行划分,则会产生
V
V
V个分支结点,其中第
v
v
v个分支结点包含了
D
D
D中所有在属性
a
a
a上取值为
a
v
a^v
av的样本,记为
D
v
D^v
Dv(例如图4-3)。
∣
D
v
∣
|D^v|
∣Dv∣表示集合
D
v
D^v
Dv包含样本的数量,则数据集
D
D
D在属性
a
a
a上的信息增益定义为
G
a
i
n
(
D
,
a
)
=
E
n
t
(
D
)
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
E
n
t
(
D
v
)
\begin{aligned} Gain(D,a)=Ent(D)-\sum_{v=1}^{V}\frac{|D^v|}{|D|}Ent(D^v) \end{aligned}
Gain(D,a)=Ent(D)−v=1∑V∣D∣∣Dv∣Ent(Dv)
以表4-1的西瓜数据集为例,数据集记为D。D中样本分为两类,则
∣
y
∣
=
2
|y|=2
∣y∣=2,好瓜占
8
17
\frac{8}{17}
178,坏瓜占
9
17
\frac{9}{17}
179。因此
D
D
D的信息熵为
E
n
t
(
D
)
=
−
∑
k
=
1
2
p
k
∗
l
o
g
2
p
k
=
−
(
8
17
∗
l
o
g
2
8
17
+
9
17
∗
l
o
g
2
9
17
)
=
0.998
\begin{aligned} Ent(D)=-\sum_{k=1}^{2}p_k*log_2p_k=-(\frac{8}{17}*log_2\frac{8}{17}+\frac{9}{17}*log_2\frac{9}{17})=0.998 \end{aligned}
Ent(D)=−k=1∑2pk∗log2pk=−(178∗log2178+179∗log2179)=0.998
以属性“色泽”为例,使用色泽属性对
D
D
D进行划分,则得到3个子集:
D
1
(
色
泽
=
青
绿
)
D^1(色泽=青绿)
D1(色泽=青绿),
D
2
(
色
泽
=
乌
黑
)
D^2(色泽=乌黑)
D2(色泽=乌黑),
D
3
(
色
泽
=
浅
白
)
D^3(色泽=浅白)
D3(色泽=浅白)。
分别计算
D
1
,
D
2
,
D
3
D^1,D^2,D^3
D1,D2,D3的信息熵
E
n
t
(
D
1
)
=
−
(
3
6
∗
l
o
g
2
3
6
+
3
6
∗
l
o
g
2
3
6
)
=
1.000
\begin{aligned} Ent(D^1)=-(\frac{3}{6}*log_2\frac{3}{6}+\frac{3}{6}*log_2\frac{3}{6})=1.000 \end{aligned}
Ent(D1)=−(63∗log263+63∗log263)=1.000
E
n
t
(
D
2
)
=
−
(
4
6
∗
l
o
g
2
4
6
+
2
6
∗
l
o
g
2
2
6
)
=
0.918
\begin{aligned} Ent(D^2)=-(\frac{4}{6}*log_2\frac{4}{6}+\frac{2}{6}*log_2\frac{2}{6})=0.918 \end{aligned}
Ent(D2)=−(64∗log264+62∗log262)=0.918
E
n
t
(
D
3
)
=
−
(
1
5
∗
l
o
g
2
1
5
+
4
5
∗
l
o
g
2
4
5
)
=
0.722
\begin{aligned} Ent(D^3)=-(\frac{1}{5}*log_2\frac{1}{5}+\frac{4}{5}*log_2\frac{4}{5})=0.722 \end{aligned}
Ent(D3)=−(51∗log251+54∗log254)=0.722
计算属性“色泽”在数据集
D
D
D上的信息增益为:
G
a
i
n
(
D
,
色
泽
)
=
E
n
t
(
D
)
−
∑
v
=
1
3
∣
D
v
∣
∣
D
∣
E
n
t
(
D
v
)
=
0.998
−
(
6
17
∗
1.000
+
6
17
∗
0.918
+
5
17
∗
0.722
)
=
0.109
\begin{aligned} Gain(D,色泽)=Ent(D)-\sum_{v=1}^{3}\frac{|D^v|}{|D|}Ent(D^v)=0.998-(\frac{6}{17}*1.000+\frac{6}{17}*0.918+\frac{5}{17}*0.722)=0.109 \end{aligned}
Gain(D,色泽)=Ent(D)−v=1∑3∣D∣∣Dv∣Ent(Dv)=0.998−(176∗1.000+176∗0.918+175∗0.722)=0.109
同理,计算出其他属性(根蒂、敲声、纹理、脐部、触感)的信息增益,从中选出信息增益最大的属性
a
a
a划分数据集
D
D
D。信息增益越大,意味着使用属性
a
a
a来进行划分所获得的“纯度提升”越大。
信息增益准则对可取值数目较多的属性有所偏好。
4.2.2 增益率
定义属性
a
a
a在数据集
D
D
D上的“增益率”为
G
a
i
n
_
r
a
t
i
o
(
D
,
a
)
=
G
a
i
n
(
D
,
a
)
I
V
(
a
)
\begin{aligned} Gain\_ratio(D,a)=\frac{Gain(D,a)}{IV(a)} \end{aligned}
Gain_ratio(D,a)=IV(a)Gain(D,a)
其中
I
V
(
a
)
=
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
∗
l
o
g
2
∣
D
v
∣
∣
D
∣
\begin{aligned} IV(a)=-\sum_{v=1}^{V}\frac{|D^v|}{|D|}*log_2\frac{|D^v|}{|D|} \end{aligned}
IV(a)=−v=1∑V∣D∣∣Dv∣∗log2∣D∣∣Dv∣
属性
a
a
a的可能取值数目越多(即
V
V
V越大),
I
V
(
a
)
IV(a)
IV(a)的值通常越大。
增益率准则对可取值数目较少的属性有所偏好。因此,增益率常与信息增益结合使用:先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。
4.2.3 基尼指数
定义数据集
D
D
D的基尼值为
G
i
n
i
(
D
)
=
∑
k
=
1
∣
y
∣
∑
k
′
≠
k
p
k
p
k
′
=
1
−
∑
k
=
1
∣
y
∣
p
k
2
\begin{aligned} Gini(D)=\sum_{k=1}^{|y|}\sum_{k'\neq k}p_kp_{k'}=1-\sum_{k=1}^{|y|}p_k^2 \end{aligned}
Gini(D)=k=1∑∣y∣k′=k∑pkpk′=1−k=1∑∣y∣pk2
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在数据集
D
D
D上的基尼指数为
G
i
n
i
_
i
n
d
e
x
(
D
,
a
)
=
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
G
i
n
i
(
D
v
)
\begin{aligned} Gini\_index(D,a)=\sum_{v=1}^{V}\frac{|D^v|}{|D|}Gini(D^v) \end{aligned}
Gini_index(D,a)=v=1∑V∣D∣∣Dv∣Gini(Dv)
选择基尼指数最小的属性作为最优划分属性。
4.2.4 小结
指标 | 确定最优划分属性 | 偏好 | 经典决策树算法 |
---|---|---|---|
信息增益 | 取最大值对应属性 | 偏好可取值数目较多的属性 | ID3 |
增益率 | 取最大值对应属性 | 偏好可取值数目较少的属性 | C4.5 |
基尼指数 | 取最小值对应属性 | —— | CART |
4.2.5 决策树代码实现
使用Python语言从底层实现决策树的学习算法、预测新样本等功能。未使用机器学习库,以便能帮助自己更好的理解算法细节。使用信息增益作为选择最优划分属性的依据。
# -*- coding: utf-8 -*-
"""
Created on Sat Nov 21 19:22:06 2020
@author: qiqi
"""
import math
import random
from collections import Counter
class TreeNode(object):
def __init__(self):
'''
IsLeaf:当前结点是否为叶子结点
C:当前结点标签类别,仅当IsLeaf为True时才有意义
a:当前结点判断的属性
Sons:当前结点子结点字典,通过a的取值作为key访问
Returns
-------
None.
'''
self.IsLeaf = False
self.C = None
self.a = None
self.Sons = dict()
class DecisionTree(object):
def __init__(self, AValue=dict(), Attribute=[]):
'''
Parameters
----------
AValue : TYPE, optional
记录每个属性的可以取哪些值. The default is dict().
Attribute : TYPE, optional
数据集属性/特征列表. The default is [].
Returns
-------
None.
'''
self.AValue = AValue
self.Attribute = Attribute
def __CalClassNum(self, D):
#计算数据集D中的样本类别数,其中D为二维列表,最后一列是类别标签
s = set()
D = list(D)
for d in D:
s.add(d[-1])
return len(s)
def __Same(self, D, A):
#判断D中样本在A属性上取值是否相同。A为属性列表,A中某个属性在A的下标i,与D中第i列数据对应
for a in A:
s = set()
for d in D:
s.add(d[self.Attribute.index(a)])
if len(s) > 1:
return False
return True
def __CalMax(self, D):
#计算数据集D中样本数最多的类别
C = [d[-1] for d in D] #从D中的每一行取最后一个元素,得到每个样本的类别
dic = Counter(C) #统计C中每个元素出现的次数,返回一个字典
_, result = max(zip(dic.values(), dic.keys()))
return result
def __Ent(self, D):
#计算信息熵
C = [d[-1] for d in D] #从D中的每一行取最后一个元素,得到每个样本的类别
dic = Counter(C) #统计C中每个元素出现的次数,返回一个字典
rate = list(dic.values())
Sum = 0
for r in rate:
Sum += (r/len(D)) * math.log2(r/len(D))
return -Sum
def __Gain(self, D, A, a):
#计算属性a在数据集D上的信息增益
EntD = self.__Ent(D)
Gain = 0
for a_v in self.AValue[a]:
#将数据集D按照属性a的取值分为若干部分
D_v = self.__GetSubset(D, A, a, a_v)
Gain += (len(D_v)/len(D)) * self.__Ent(D_v)
Gain = EntD - Gain
return Gain
def __SplitA(self, D, A):
#计算数据集D对A中每个属性的信息增益或增益率或基尼指数作为评价值,选择最大评价值对应的属性
Max = 0
result = A[0]
for a in A:
e = self.__Gain(D, A, a)
if e > Max:
Max = e
result = a
return result
def __GetSubset(self, D, A, a, a_v):
#计算数据集D中在某一属性a上取值为a_v的样本子集,记为D_v
D_v = []
for d in D:
if d[self.Attribute.index(a)] == a_v:
D_v.append(d)
return D_v
def CalAValue(self, D, A):
#计算属性集A中每个属性在数据集D中的取值,返回一个字典,字典key为属性,value为取值集合
AValue = dict()
for a in A:
s = set()
for d in D:
s.add(d[A.index(a)])
AValue[a] = s
return AValue
def TreeGenerate(self, D=None, A=None):
node = TreeNode() #生成结点node
if self.__CalClassNum(D) == 1:
#D中样本全属于同一类别C,将node标记为C类叶结点
C = D[0][-1]
node.IsLeaf = True
node.C = C
return node
elif A is None or len(A)==0 or self.__Same(D, A):
#当前属性集A为空,或者D中样本在A上取值相同,将node标记为叶结点,
#其类别标记为D中样本数最多的类别
node.IsLeaf = True
node.C = self.__CalMax(D)
return node
else:
#从A中选择最优划分属性a
#对a的每一个值a_v为node生成一个分支,令D_v表示D中在a上取值为a_v的样本子集
#如果D_v为空,将分支结点标记为叶结点,其类别标记为D中样本最多的类别,return
#如果D_v不为空,以TreeGenerate(D_v, A\{a})为分支结点
a = self.__SplitA(D, A)
for a_v in self.AValue[a]:
D_v = self.__GetSubset(D, A, a, a_v)
if len(D_v) == 0:
son = TreeNode()
son.IsLeaf = True
son.C = self.__CalMax(D)
node.Sons[a_v] = son
else:
A_a = A.copy()
A_a.remove(a)
son = self.TreeGenerate(D_v, A_a)
node.IsLeaf = False
node.a = a
node.Sons[a_v] = son
return node
def Predict(self, root, A, d):
#预测单个样本d
if root is not None:
if root.IsLeaf:
return root.C
else:
return self.Predict(root.Sons[d[A.index(root.a)]], A, d)
else:
return None
def Evaluate(self, root, A, D):
#计算决策树在数据集D上的精度,保留两位小数
right = 0
for d in D:
if self.Predict(root, A, d) == d[-1]:
right += 1
return round(right/len(D), 2)
def Dfs(self, root, p, Path):
#以深度优先遍历方式得到通往每个叶结点的路径
if root.IsLeaf:
p += '--' + root.C
Path.append(p)
else:
for a_v in self.AValue[root.a]:
tp = p
p += root.a + '?' + '--' + '(' + a_v + ')' + '--'
self.Dfs(root.Sons[a_v], p, Path)
p = tp
#定义数据集
D = [['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '好瓜'],
['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', '好瓜'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', '好瓜'],
['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜'],
['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘', '坏瓜'],
['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑', '坏瓜'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘', '坏瓜'],
['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', '坏瓜'],
['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', '坏瓜'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '坏瓜'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', '坏瓜'],
['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜']]
#定义属性集,属性集每个属性的顺序需要与D中取值对应
A = ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感']
random.shuffle(D) #打乱数据集
train = D[0:10] #训练集取前10个样本
test = D[10:] #验证集取剩余样本
Dec = DecisionTree(AValue=dict(), Attribute=A)
Dec.AValue = Dec.CalAValue(D, A) #计算数据集D中每个属性可以取到什么值
root = Dec.TreeGenerate(train, A) #使用训练数据训练决策树
print('测试集精度:', Dec.Evaluate(root, A, test))
Path = []
Dec.Dfs(root, '', Path)
print('决策树通向每个叶结点的判断路径如下')
edge = ''
for _ in range(max(len(p) for p in Path)*2): edge += '*'
print(edge)
for i in range(len(Path)): print('路径%d:' % (i), Path[i])
print(edge)
代码运行结果如图4-4所示。由于训练集和测试集是随机划分的,并且样本数量很少,因此代码运行多次,可能得到的结果相差很大。若使用 D D D中全部17个样本训练,则可得到如上面图4-1所示的决策树。图4-4中每条路径对应了决策树由根结点通往每个叶结点的判定序列。
4.3 剪枝处理
剪枝是决策树学习算法对付“过拟合”的主要手段。决策树剪枝的基本策略有“预剪枝”和“后剪枝”。
4.3.1 预剪枝
预剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点。
简单来说,就是在每次对当前结点划分前,计算划分前验证集的精度
a
1
a_1
a1(也可以是其他评价指标),再试探性地对当前结点进行划分,计算试探性划分后验证集精度
a
2
a_2
a2。若
a
1
<
a
2
a_1<a_2
a1<a2,则进行这次划分;若
a
1
>
a
2
a_1>a_2
a1>a2,则不进行此次划分,此试探性划分作废,将当前节点标记为叶结点;若
a
1
=
a
2
a_1=a_2
a1=a2,则划分与不划分两者均可,一般不进行划分,因为这样做可降低生成的决策树的规模,减少预测新样本时的时间开销。划分步骤属性的选择和叶结点类别标记设置的策略与上面讲到的图4-2所示的决策树学习算法一致。
图4-5所示决策树基于表4-3数据集生成的未剪枝决策树,图4-6所示决策树基于表4-3生成的预剪枝决策树。
4.3.2 后剪枝
后剪枝先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能够带来决策树泛化性能提升,则将该子树替换为叶结点,叶结点类别标记取当前考察非叶结点包含样本数最多的类别,若有多个类别满足条件,则可从其中任取一个。
简单来说,就是自底向上依次考察每个非叶结点,计算当前决策树下验证集的精度
a
1
a_1
a1(也可以是其他评价指标),再试探性地把当前结点修改为叶结点,计算试探性修改后整棵决策树验证集精度
a
2
a_2
a2。若
a
1
<
a
2
a_1<a_2
a1<a2,则保存这次试探性修改;若
a
1
>
a
2
a_1>a_2
a1>a2,则撤销此次试探性修改;若
a
1
=
a
2
a_1=a_2
a1=a2,则两者均可,一般保存修改,因为这样做可降低生成的决策树的规模,减少预测新样本时的时间开销,此种情形下验证集精度虽无提高,但根据奥卡姆剃刀准则,剪枝后的模型更好。
4.3.3 小结
预剪枝可以在生成决策树时裁剪掉众多分支,这不仅降低了过拟合风险,还能显著减少决策树训练时间和测试时间开销。但有些分支的当前划分虽不能提升泛化性能、甚至导致泛化性能下降,但在其基础上进行的后续划分却有可能导致性能显著提高。预剪枝基于“贪心”本质禁止某些分支展开,容易造成欠拟合。
后剪枝 通常比预剪枝保留了更多的分支,后剪枝决策树欠拟合风险很小,泛化性能往往优于预剪枝。但后剪枝在训练决策树时开销远大于预剪枝。一般情况下,如果条件允许的话,还是建议使用后剪枝。
4.4 连续与缺失值
4.4.1 连续值处理
由于连续属性的可取值数目不再有限,因此,不能直接根据连续属性的可取值来对结点进行划分。此时,需要使用连续属性离散化技术,最简单的策略是采用二分法。
给定样本集
D
D
D和连续属性
a
a
a,假定
a
a
a在
D
D
D上出现了
n
n
n个不同的取值,将这些值从小到大进行排序,记为
{
a
1
,
a
2
,
…
,
a
n
}
\{a^1,a^2,…,a^n \}
{a1,a2,…,an}。基于划分点
t
t
t可将
D
D
D分为子集
D
t
−
D_t^−
Dt−和
D
t
+
D_t^+
Dt+,其中
D
t
−
D_t^−
Dt−包含那些在属性
a
a
a上取值不大于
t
t
t的样本,而
D
t
+
D_t^+
Dt+则包含那些在属性
a
a
a上取值大于
t
t
t的样本。
对连续属性
a
a
a,可考察包含
n
−
1
n-1
n−1个元素的候选划分点集合
T
a
=
{
a
i
+
a
i
+
1
2
∣
1
≤
i
≤
n
−
1
}
\begin{aligned} T_a=\{\frac{a^i+a^{i+1}}{2} |1 \le i \le n-1\} \end{aligned}
Ta={2ai+ai+1∣1≤i≤n−1}
即把区间
[
a
i
,
a
i
+
1
)
[a^i,a^{i+1})
[ai,ai+1)的中位点
a
i
+
a
i
+
1
2
\frac{a^i+a^{i+1}}{2}
2ai+ai+1作为候选划分点,然后使用每个中位点
t
t
t将
a
a
a的属性值划分为两个集合,并计算此种划分后的信息增益
G
a
i
n
(
D
,
a
,
t
)
Gain(D,a,t)
Gain(D,a,t),从中选择最大信息增益所对应的划分点对属性
a
a
a进行划分,生成当前结点的子结点。
G
a
i
n
(
D
,
a
)
=
max
t
∈
T
a
G
a
i
n
(
D
,
a
,
t
)
=
max
t
∈
T
a
E
n
t
(
D
)
−
∑
λ
∈
{
−
,
+
}
∣
D
t
λ
∣
∣
D
∣
E
n
t
(
D
t
λ
)
\begin{aligned} Gain(D,a)=\max_{t\in T_a}Gain(D,a,t)=\max_{t\in T_a}Ent(D)-\sum_{\lambda \in \{-,+\}}\frac{|D_t^\lambda|}{|D|}Ent(D_t^\lambda) \end{aligned}
Gain(D,a)=t∈TamaxGain(D,a,t)=t∈TamaxEnt(D)−λ∈{−,+}∑∣D∣∣Dtλ∣Ent(Dtλ)
其中
G
a
i
n
(
D
,
a
,
t
)
Gain(D,a,t)
Gain(D,a,t)是数据集
D
D
D基于划分点
t
t
t二分后的信息增益,选择是
G
a
i
n
(
D
,
a
,
t
)
Gain(D,a,t)
Gain(D,a,t)最大化的划分点。
例如,对于一组数据共
6
6
6个样本,某一个属性
a
a
a取值有
6
6
6种,分别为
{
1
,
2
,
3
,
4
,
5
,
6
}
\{1,2,3,4,5,6\}
{1,2,3,4,5,6},其候选划分点集合为
T
a
=
{
1.5
,
2.5
,
3.5
,
4.5
,
5.5
}
T_a=\{1.5, 2.5, 3.5, 4.5, 5.5\}
Ta={1.5,2.5,3.5,4.5,5.5},依次使用每个候选划分点将
6
6
6个样本分为两部分,划分后其中一部分的所有样本的属性
a
a
a的值均小于等于使用的划分点,另一部分大于划分点。
假设取划分点为
2.5
2.5
2.5,即
t
=
2.5
t=2.5
t=2.5,则将得到两个划分集合
D
t
−
=
{
1
,
2
}
D_t^−=\{1,2\}
Dt−={1,2}和
D
t
+
=
{
3
,
4
,
5
,
6
}
D_t^+=\{3,4,5,6\}
Dt+={3,4,5,6},进而计算出
G
a
i
n
(
D
,
a
,
t
=
2.5
)
Gain(D,a,t=2.5)
Gain(D,a,t=2.5)。依次考虑
T
a
T_a
Ta中每个元素,假设使用
3.5
3.5
3.5划分后
G
a
i
n
(
D
,
a
,
t
)
Gain(D,a,t)
Gain(D,a,t)最大,则当前节点的判定条件为“
a
≤
3.5
a \le 3.5
a≤3.5”,当前节点生成两个子结点,并将
D
t
−
D_t^-
Dt−和
D
t
+
D_t^+
Dt+集合所对应的样本分别放入这两个子结点中。
注意,与离散属性不同,若当前结点划分属性为连续属性,该属性还可作为其后代结点的划分属性。
4.4.2 缺失值处理
现实任务中常会遇到不完整样本,即样本的某些属性值缺失。对于此类情况,一般在数据预处理步骤使用特征工程相关方法予以解决。但本小结主要介绍在决策树学习算法中如何应对这样的问题。
面对缺失数据我们需要解决两个问题:
(1)如何在属性值缺失的情况下进行划分属性选择,也就是如何计算每个属性
a
a
a的信息增益
G
a
i
n
(
D
,
a
)
Gain(D,a)
Gain(D,a);
(2)给定划分属性,若样本在该属性上的值缺失,如何对样本进行划分。例如:西瓜的色泽属性有“乌黑”、“青绿”、“浅白”三种,根据每个样本的色泽属性取值将样本分成三类,但若存在某个样本
x
x
x,
x
x
x的色泽属性值缺失,那么样本
x
x
x该分到哪一类?
给定数据集
D
D
D和属性
a
a
a,令
D
~
\tilde{D}
D~表示
D
D
D中在属性
a
a
a上没有缺失值的样本子集。对问题(1),可根据
D
~
\tilde{D}
D~来判断属性
a
a
a的优劣。假定属性
a
a
a有
V
V
V个可取值
{
a
1
,
a
2
,
.
.
.
,
a
V
}
\{a^1,a^2,...,a^V\}
{a1,a2,...,aV},令
D
v
~
\tilde{D^v}
Dv~表示
D
~
\tilde{D}
D~中在属性
a
a
a上取值为
a
v
a^v
av的样本子集,
D
k
~
\tilde{D_k}
Dk~表示
D
~
\tilde{D}
D~中属于第
k
k
k类
(
k
=
1
,
2
,
.
.
.
,
∣
y
∣
)
(k=1,2,...,|y|)
(k=1,2,...,∣y∣)的样本子集,则显然有
D
~
=
∪
k
=
1
∣
y
∣
D
k
~
\tilde{D}=\cup _{k=1}^{|y|}\tilde{D_k}
D~=∪k=1∣y∣Dk~,
D
~
=
∪
v
=
1
V
D
v
~
\tilde{D}=\cup _{v=1}^{V}\tilde{D^v}
D~=∪v=1VDv~。假定为每个样本
x
x
x赋予一个权重
w
x
w_x
wx,并定义
ρ
=
∑
x
∈
D
~
w
x
∑
x
∈
D
w
x
\begin{aligned} \rho=\frac{\sum_{x\in \tilde{D}}w_x}{\sum_{x \in D}w_x} \end{aligned}
ρ=∑x∈Dwx∑x∈D~wx
p
k
~
=
∑
x
∈
D
k
~
w
x
∑
x
∈
D
~
w
x
(
1
≤
k
≤
∣
y
∣
)
\begin{aligned} \tilde{p_k}=\frac{\sum_{x\in \tilde{D_k}}w_x}{\sum_{x \in \tilde{D}}w_x} \ \ \ \ \ (1 \le k \le |y|) \end{aligned}
pk~=∑x∈D~wx∑x∈Dk~wx (1≤k≤∣y∣)
r
v
~
=
∑
x
∈
D
v
~
w
x
∑
x
∈
D
~
w
x
(
1
≤
v
≤
V
)
\begin{aligned} \tilde{r_v}=\frac{\sum_{x\in \tilde{D^v}}w_x}{\sum_{x \in \tilde{D}}w_x} \ \ \ \ \ (1 \le v \le V) \end{aligned}
rv~=∑x∈D~wx∑x∈Dv~wx (1≤v≤V)
对属性
a
a
a,
ρ
\rho
ρ表示无缺失值样本所占的比例,
p
k
~
\tilde{p_k}
pk~表示无缺失值样本中第
k
k
k类所占的比例,
r
v
~
\tilde{r_v}
rv~表示无缺失值样本中在属性
a
a
a上取值
a
v
a^v
av的样本所占的比例。推广信息增益公式为
G
a
i
n
(
D
,
a
)
=
ρ
∗
G
a
i
n
(
D
~
,
a
)
=
ρ
∗
(
E
n
t
(
D
~
)
−
∑
v
=
1
V
r
v
~
E
n
t
(
D
v
~
)
)
\begin{aligned} Gain(D,a)=\rho*Gain(\tilde{D},a)=\rho*(Ent(\tilde{D})-\sum _{v=1} ^{V}\tilde{r_v}Ent(\tilde{D^v})) \end{aligned}
Gain(D,a)=ρ∗Gain(D~,a)=ρ∗(Ent(D~)−v=1∑Vrv~Ent(Dv~))
其中,
E
n
t
(
D
~
)
=
−
∑
k
=
1
∣
y
∣
p
k
~
l
o
g
2
p
k
~
\begin{aligned} Ent(\tilde{D})=-\sum _{k=1} ^{|y|}\tilde{p_k}log_2 {\tilde{p_k}} \end{aligned}
Ent(D~)=−k=1∑∣y∣pk~log2pk~
对于问题(2),若样本
x
x
x在划分属性
a
a
a上的取值已知,则将
x
x
x划入与其取值对应的子结点,且样本权值在子结点中保持为
w
x
w_x
wx。若样本
x
x
x在划分属性
a
a
a上的取值未知,则将
x
x
x同时划入所有子结点,且样本权值在与属性
a
v
a^v
av对应的子结点中调整为
r
v
~
∗
w
x
\tilde{r_v}*w_x
rv~∗wx;根结点中的样本权值全部初始化为
1
1
1。直观感受,这是让同一个样本以不同概率划入到不同的子结点中。
例如,在如表4-4所示的“西瓜数据集——缺失部分数据”中,假设对“纹理”进行根结点划分。划分结果是使编号
{
1
,
2
,
3
,
4
,
5
,
6
,
15
}
\{1,2,3,4,5,6,15\}
{1,2,3,4,5,6,15}的样本进行“纹理=清晰”分支,编号
{
7
,
9
,
13
,
14
,
17
}
\{7,9,13,14,17\}
{7,9,13,14,17}的样本进入“纹理=稍糊”分支,编号
{
11
,
12
,
16
}
\{11,12,16\}
{11,12,16}的样本进行“纹理=模糊”分支,且样本在各子结点中权值保持为
1
1
1。对于样本
8
8
8,将该样本放入全部三个子结点中,并分别将权值调整为
1
∗
7
15
1*\frac{7}{15}
1∗157、
1
∗
5
15
1*\frac{5}{15}
1∗155、
1
∗
3
15
1*\frac{3}{15}
1∗153。注意,在对子结点进行划分时,子结点中样本
8
8
8的权值不再是
1
1
1。
4.5 多变量决策树
以上所叙述的决策树都是单变量决策树,即每个非叶结点都是对单个属性的判断。而除了单变量决策树之外,还有多变量决策树,即每个非叶结点是对多个属性组合的判断。这里多个属性组合的方式可以是简单的线性组合,也可以是复杂组合,如每个非叶结点设置一个神经网络对多个属性的复杂组合进行判断。
图4-8展示了一棵采用线性组合方式的多变量决策树。
我基于sklearn的线性回归模型实现了多变量决策树,详细内容可以关注我的另一篇博客。
4.6 sklearn机器学习库——决策树
下面代码基于sklearn机器学习库实现了乳腺癌数据分类,sklearn中还包含了可用于回归任务的决策树。sklearn包含了众多经典的机器学习算法,并做了很多相关的优化工作。建议在实际工程中使用,但由于方法库屏蔽掉了很多算法细节,因此在学习阶段不建议过于依赖方法库。sklearn中文文档
# -*- coding: utf-8 -*-
"""
Created on Tue Nov 24 14:59:04 2020
@author: qiqi
"""
import pydot
from sklearn.tree import export_graphviz
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer() #加载乳腺癌数据集
#参数random_state是指随机生成器
X_train, X_test, y_train, y_test = train_test_split(cancer['data'],cancer['target'], random_state=42)
#设置深度为4,即产生4个问题就停止生长.这是一种预剪枝手段,防止过拟合。
tree = DecisionTreeClassifier(max_depth=4, random_state=0)
#训练决策树
tree.fit(X_train,y_train)
print('Train score:{:.3f}'.format(tree.score(X_train,y_train)))
print('Test score:{:.3f}'.format(tree.score(X_test,y_test)))
#生成可视化图,Windows下class_names使用中文会乱码
export_graphviz(tree, out_file='tree.dot',class_names=['serious','slight'],feature_names=cancer.feature_names,impurity=False,filled=True)
#展示可视化图
(graph,) = pydot.graph_from_dot_file('tree.dot')
graph.write_png('tree.png')
上面代码使用了预剪枝策略(代码确实简短),生成的决策树训练集精度为 0.99 0.99 0.99,测试集精度为 0.92 0.92 0.92。图4-9是使用sklearn库方法绘制的决策树结构图,sklearn确实好用。
博客参考周志华教授《机器学习》(西瓜书)