决策树是一种解决分类问题的算法
在分类问题中,表示基于特征对实例进行分类的过程,可以认为是 if-else-then
的集合,也可以认为是定义在特征空间与类空间上的条件概率分布
本文基于北京邮电大学周芮西老师开设的公选课——统计机器学习及应用实践
决策树是典型的分类方法
相关概念
决策树的特点
if-else-then
形式决策树技术发现数据模式和规则的核心是归纳算法(Inductive algorithm)
归纳(归纳推理)
归纳学习由于依赖于检验数据,因此又称为检验学习
决策树学习的算法通常是一个递归地选择最优特征,并根据该特征对训练数据进行分割,使得各个子数据集有一个最好的分类的过程。这一过程对应着对特征空间的划分,也对应着决策树的构建:
下图为决策树示意图
显然,决策树的生成是一个递归过程。在决策树基本算法中,有三种情形会导致递归返回:
一般而言,随着划分过程不断进行,我们希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的“纯度”越来越高
划分步骤:
信息熵是信息量大小的度量,即表示随机变量不确定性的度量
信息熵的两种解释:
设有随机变量 ( X , Y ) (X, Y) (X,Y),其联合概率分布为:
P ( X = x i , Y = y i ) = p i j , i = 1 , 2 , ⋯ , n ; j = 1 , 2 , ⋯ , m P(X = x_i, Y = y_i) = p_{ij}, i = 1,2,\cdots,n; \ j=1,2,\cdots,m P(X=xi,Y=yi)=pij,i=1,2,⋯,n; j=1,2,⋯,m
条件熵表示在己知随机变量 X 的条件下随机变量 Y 的不确定性
特征 A
对训练集 D
的信息增益 g(D, A)
:
D
的经验熵 H(D)
与特征 A
给定条件下 D
的经验条件熵 H ( D ∣ A ) H(D|A) H(D∣A) 之差互信息:熵 H(Y)
与条件熵 H ( D ∣ A ) H(D|A) H(D∣A)) 之差
决策树学习中的信息增益 ≈ \approx ≈ 训练数据集中类与特征的互信息
表达式总结为:
对于决策树,我们可以使用输出类 Y 和要拆分的某个属性 X 的互信息作为拆分标准
给定训练样本数据集 D,我们可以将所需的概率估计为:
P ( Y = y ) = N Y = y / N P ( X = x ) = N X = x / N P ( Y = y ∣ X = x ) = N Y = y , X = x / N X = x P(Y=y) = N_{Y=y} / N \\ P(X = x) = N_{X=x} / N \\ P(Y = y | X = x) = N_{Y=y, X=x} / N_{X=x} P(Y=y)=NY=y/NP(X=x)=NX=x/NP(Y=y∣X=x)=NY=y,X=x/NX=x
其中 N Y = y N_{Y=y} NY=y 是 Y = y Y=y Y=y 的例子的数量
信息增益的算法:
D
和特征 A
A
对训练数据集 D
的信息增益 g ( D , A ) g(D,A) g(D,A)D
的经验熵 H(D)
D
中取值为 C k C_k Ck 的样本的个数A
对数据集 D
的经验条件熵 H ( D ∣ A ) H(D|A) H(D∣A)以信息增益作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题:
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 对训练数据集 D 的信息增益比,定义为信息增益与训练数据集 D 关于特征 A 的值的熵之比:
g R ( D , A ) = g ( D , A ) H A ( D ) H A ( D ) = − ∑ i = 1 3 ∣ D i ∣ ∣ D ∣ l o g 2 ∣ D i ∣ ∣ D ∣ g_R(D, A) = \frac{g(D, A)}{H_A(D)} \\ H_A(D) = - \displaystyle\sum_{i=1}^3\frac{|D_i|}{|D|}log_2\frac{|D_i|}{|D|} gR(D,A)=HA(D)g(D,A)HA(D)=−i=1∑3∣D∣∣Di∣log2∣D∣∣Di∣
n 是特征 A 取值的个数
数据集 D
的纯度可以用基尼值来度量
假设有 K
个类,样本点属于第 k
类的概率为 p k p_k pk ,则:
G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 Gini(p) = \displaystyle\sum_{k=1}^K p_k(1-p_k) = 1 - \displaystyle\sum_{k=1}^K p_k^2 Gini(p)=k=1∑Kpk(1−pk)=1−k=1∑Kpk2
转换成二分类问题:
G i n i ( p ) = 2 p ( 1 − p ) Gini(p) = 2p(1 − p) Gini(p)=2p(1−p)
对于样本集合 D
:
G i n i ( D ) = 1 − ∑ k = 1 K ∣ C k D ∣ 2 Gini(D) = 1 - \displaystyle\sum_{k=1}^K |\frac{C_k}{D}|^2 Gini(D)=1−k=1∑K∣DCk∣2
样本集 D
根据特征 A
是否取某一值 a
被分割成 D 1 D_1 D1 和 D 2 D_2 D2,即:
D 1 = { ( x , y ) ∈ D ∣ A ( x ) = a } D 2 = D − D 1 D_1 = \{ (x, y) \in D | A(x) = a \} \\ D_2 = D - D_1 D1={(x,y)∈D∣A(x)=a}D2=D−D1
在特征 A
的条件下,集合 D
的基尼指数为:
G i n i ( D , A ) = D 1 D G i n i ( D 1 ) + D 2 D G i n i ( D 2 ) Gini(D, A) = \frac{D_1}{D} Gini(D_1) + \frac{D_2}{D} Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)
即经 A = a A = a A=a 划分后集合 D
的不确定性
基本思想:
算法步骤:
存在的问题:
核心思想:
算法步骤:
示例一:
该数据集包含 17 个训练样例,用一学习一棵能预测没剖开的是不是好瓜的决策树
在决策树学习开始时,根结点包含 D
中的所有样例,其中正例占 p 1 = 8 17 p_1 = \frac{8}{17} p1=178,反例占 p 2 = 9 17 p_2 = \frac{9}{17} p2=179
计算根结点的信息熵
H ( D ) = − ∑ i = 1 2 p i l o g 2 p i = − ( 8 17 l o g 2 8 17 + 9 17 l o g 2 9 17 ) = 0.998 H(D) = - \displaystyle\sum_{i=1}^2 p_i log_2 p_i = - (\frac{8}{17} log_2 \frac{8}{17} + \frac{9}{17} log_2 \frac{9}{17}) = 0.998 H(D)=−i=1∑2pilog2pi=−(178log2178+179log2179)=0.998
然后 ,我们要计算出当前属性集合 {色泽,根蒂,敲声,纹理,脐部,触感} 中每个属性的信息增益。以属性“色泽”为例,它有 3 个可能的取值:{青绿,乌黑,浅白}。若使用该属性对 D 进行划分,则可得到 3 个子集,分别记为 D 1 D^1 D1(色泽 = 青绿), D 2 D^2 D2(色泽 = 乌黑), D 3 D^3 D3(色泽 = 浅白)
用“色泽”划分之后所获得的 3 个分支结点的信息嫡为:
H ( D 1 ) = − ( 3 6 l o g 2 3 6 + 3 6 l o g 2 3 6 ) = 1.000 H(D^1) = - (\frac{3}{6} log_2 \frac{3}{6} + \frac{3}{6} log_2 \frac{3}{6}) = 1.000 H(D1)=−(63log263+63log263)=1.000
H ( D 2 ) = − ( 4 6 l o g 2 4 6 + 2 6 l o g 2 2 6 ) = 0.918 H(D^2) = - (\frac{4}{6} log_2 \frac{4}{6} + \frac{2}{6} log_2 \frac{2}{6}) = 0.918 H(D2)=−(64log264+62log262)=0.918
H ( D 3 ) = − ( 1 5 l o g 2 1 5 + 4 5 l o g 2 4 5 ) = 0.722 H(D^3) = - (\frac{1}{5} log_2 \frac{1}{5} + \frac{4}{5} log_2 \frac{4}{5}) = 0.722 H(D3)=−(51log251+54log254)=0.722
属性“色泽 ”的信息增益为:
g ( D , 色泽 ) = H ( D ) − ∑ i = 1 3 ∣ D i ∣ ∣ D ∣ H ( D i ) = 0.998 − ( 6 17 × 1.000 + 7 16 × 0.918 + 5 17 × 0.722 ) = 0.109 g(D, 色泽) = H(D) - \displaystyle\sum_{i=1}^3\frac{|D_i|}{|D|}H(D_i) \\ =0.998 - (\frac{6}{17} \times 1.000 + \frac{7}{16} \times 0.918 + \frac{5}{17} \times 0.722) = 0.109 g(D,色泽)=H(D)−i=1∑3∣D∣∣Di∣H(Di)=0.998−(176×1.000+167×0.918+175×0.722)=0.109
类似的,我们可计算出其他属性的信息增益:
显然,属性“纹理”的信息增益最大,于是它被选为划分属性。下图给出了基于“纹理”对根结点进行划分的结果,各分支结点所包含的样例子集显示在结点中:
然后,决策树学习算法将对每个分支结点做进一步划分(步骤相同,略)
示例二:
对数据集进行属性标注:
创建数据集,计算经验熵的代码如下:
from math import log
"""
函数说明:创建测试数据集
Parameters:无
Returns:
dataSet:数据集
labels:分类属性
Modify:
2022-09-28
"""
def creatDataSet():
# 数据集
dataSet=[[0, 0, 0, 0, 'no'],
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
# 分类属性
labels=['年龄','有工作','有自己的房子','信贷情况']
# 返回数据集和分类属性
return dataSet,labels
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet:数据集
Returns:
shannonEnt:经验熵
Modify:
2022-09-28
"""
def calcShannonEnt(dataSet):
#返回数据集行数
numEntries=len(dataSet)
#保存每个标签(label)出现次数的字典
labelCounts={}
#对每组特征向量进行统计
for featVec in dataSet:
currentLabel=featVec[-1] # 提取标签信息
if currentLabel not in labelCounts.keys(): # 如果标签没有放入统计次数的字典,添加进去
labelCounts[currentLabel]=0
labelCounts[currentLabel]+=1 # label计数
shannonEnt=0.0 # 经验熵
#计算经验熵
for key in labelCounts:
prob=float(labelCounts[key])/numEntries # 选择该标签的概率
shannonEnt-=prob*log(prob,2) # 利用公式计算
return shannonEnt # 返回经验熵
# main函数
if __name__=='__main__':
dataSet,features=creatDataSet()
print(dataSet)
print(calcShannonEnt(dataSet))
对应的输出结果:
第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
第0个特征的增益为0.252
第1个特征的增益为0.918
第2个特征的增益为0.474
{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
创建数据集,计算信息增益的代码如下:
from math import log
"""
函数说明:创建测试数据集
Parameters:无
Returns:
dataSet:数据集
labels:分类属性
Modify:
2022-09-28
"""
def creatDataSet():
# 数据集
dataSet=[[0, 0, 0, 0, 'no'],
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
#分类属性
labels=['年龄','有工作','有自己的房子','信贷情况']
#返回数据集和分类属性
return dataSet,labels
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet:数据集
Returns:
shannonEnt:经验熵
Modify:
2022-09-28
"""
def calcShannonEnt(dataSet):
#返回数据集行数
numEntries=len(dataSet)
#保存每个标签(label)出现次数的字典
labelCounts={}
#对每组特征向量进行统计
for featVec in dataSet:
currentLabel=featVec[-1] #提取标签信息
if currentLabel not in labelCounts.keys(): #如果标签没有放入统计次数的字典,添加进去
labelCounts[currentLabel]=0
labelCounts[currentLabel]+=1 #label计数
shannonEnt=0.0 #经验熵
#计算经验熵
for key in labelCounts:
prob=float(labelCounts[key])/numEntries #选择该标签的概率
shannonEnt-=prob*log(prob,2) #利用公式计算
return shannonEnt #返回经验熵
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet:数据集
Returns:
shannonEnt:信息增益最大特征的索引值
Modify:
2022-09-28
"""
def chooseBestFeatureToSplit(dataSet):
#特征数量
numFeatures = len(dataSet[0]) - 1
#计数数据集的香农熵
baseEntropy = calcShannonEnt(dataSet)
#信息增益
bestInfoGain = 0.0
#最优特征的索引值
bestFeature = -1
#遍历所有特征
for i in range(numFeatures):
# 获取dataSet的第i个所有特征
featList = [example[i] for example in dataSet]
#创建set集合{},元素不可重复
uniqueVals = set(featList)
#经验条件熵
newEntropy = 0.0
#计算信息增益
for value in uniqueVals:
#subDataSet划分后的子集
subDataSet = splitDataSet(dataSet, i, value)
#计算子集的概率
prob = len(subDataSet) / float(len(dataSet))
#根据公式计算经验条件熵
newEntropy += prob * calcShannonEnt((subDataSet))
#信息增益
infoGain = baseEntropy - newEntropy
#打印每个特征的信息增益
print("第%d个特征的增益为%.3f" % (i, infoGain))
#计算信息增益
if (infoGain > bestInfoGain):
#更新信息增益,找到最大的信息增益
bestInfoGain = infoGain
#记录信息增益最大的特征的索引值
bestFeature = i
#返回信息增益最大特征的索引值
return bestFeature
"""
函数说明:按照给定特征划分数据集
Parameters:
dataSet:待划分的数据集
axis:划分数据集的特征
value:需要返回的特征的值
Returns:
shannonEnt:经验熵
Modify:
2022-09-28
"""
def splitDataSet(dataSet,axis,value):
retDataSet=[]
for featVec in dataSet:
if featVec[axis]==value:
reducedFeatVec=featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
#main函数
if __name__=='__main__':
dataSet,features=creatDataSet()
# print(dataSet)
# print(calcShannonEnt(dataSet))
print("最优索引值:"+str(chooseBestFeatureToSplit(dataSet)))
对应的输出结果:
第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
最优索引值:2
最优特征的索引值为 2,也就是特征 A3
与 ID3
算法相似,但是做了改进,将信息增益比作为选择特征的标准:
递归构建决策树:
CART
与 ID3
,C4.5
不同之处在于 CART
生成的树必须是二叉树。也就是说,无论是回归还是分类问题,无论特征是离散的还是连续的,无论属性取值有多个还是两个,内部节点只能根据属性值进行二分。
根据变量选择多种指标:
Gini
指标回归树中,使用平方误差最小化准则来选择特征并进行划分。每一个叶子节点给出的预测值,是划分到该叶子节点的所有样本目标值的均值,这样只是在给定划分的情况下最小化了平方误差。
要确定最优化分,还需要遍历所有属性,以及其所有的取值来分别尝试划分并计算在此种划分情况下的最小平方误差,选取最小的作为此次划分的依据。由于回归树生成使用平方误差最小化准则,所以又叫做最小二乘回归树。
回归树的生成:
D
CART
回归树 f(x)
j
(切分点 s
),选定的对 ( j, s)
划分区域并决定相应的输出值m
个区域 R 1 , R 2 , . . . , R m R_1, R_2, ..., R_m R1,R2,...,Rm, 生成决策树分类树中,使用 Gini
指数最小化准则来选择特征并进行划分:
Gini
指数表示集合的不确定性,或者是不纯度
分类树的生成
D
CART
决策树A = a
时的基尼指数CART
决策树剪枝是决策树学习算法应对“过拟合”问题的主要方法。在决策树学习中,为了尽可能正确地分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多,导致把训练集自身的一些特点当作所有数据都具有的一般性质而导致的过拟合。
剪枝步骤:
t
,计算:g(t)
最小的 T t T_t Tt,将得到的子树作为 T 1 T_1 T1,同时将最小的 g(t)
设为 a 1 a_1 a1, T 1 T_1 T1 为区间 [ a 1 , a 2 ) [a_1, a_2) [a1,a2) 的最优子树a
的值,产生新的区间剪枝过程中子树的损失函数:
C α ( T ) = C ( T ) + α ∣ T ∣ C_\alpha(T) = C(T) + \alpha |T| Cα(T)=C(T)+α∣T∣
T
为任意的子树C(T)
为对训练数据的预测误|T|
为子树 T
的叶结点个数现在假设通过 CART
算法生成了一棵如下图所示的决策树:
可对树 T
进行剪枝的内部节点有 t 0 , t 1 , t 2 , t 3 t_0, t_1, t_2, t_3 t0,t1,t2,t3, 因此根据上述截图中的剪枝步骤 (3) 可以分别算出 g ( t 0 ) , g ( t 1 ) , g ( t 2 ) , g ( t 3 ) g(t_0), g(t_1), g(t_2), g(t_3) g(t0),g(t1),g(t2),g(t3),此时还不满足 (6)
此时,可对树 T
进行剪枝的内部节点有 t 0 , t 1 , t 2 t_0, t_1, t_2 t0,t1,t2, 因此根据上述截图中的剪枝步骤 (3) 可以分别算出 g ( t 0 ) , g ( t 1 ) , g ( t 2 ) g(t_0), g(t_1), g(t_2) g(t0),g(t1),g(t2),此时还不满足 (6)
图中有误,更正剪枝后的左图中的 t 1 t_1 t1 为 t 2 t_2 t2
此时,可对树 T
进行剪枝的内部节点有 t 0 , t 2 t_0, t_2 t0,t2, 因此根据上述截图中的剪枝步骤 (3) 可以分别算出 g ( t 0 ) , g ( t 2 ) g(t_0), g(t_2) g(t0),g(t2),此时还不满足 (6)
此时,得到整个子树序列:
接着采用交叉验证在子树序列中选择最优子树 T a T_a Ta 即可
考虑以下二维数据,它一共有 4 种标签:
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=300, centers=4,
random_state=0, cluster_std=1.0)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='rainbow');
在这组数据上构建的简单决策树不断将数据的一个特征或另一个特征按照某种判定条件进行分割。每分割一次,都将新区域内点的多数投票结果标签分配到该区域上。
下图展示了该数据的决策树分类器的前四级的可视化情况:
需要注意的是,在第一次分割之后,上半个分支里面的所有数据点都没有变化,因此这个分支不需要继续分割
使用 DecisionTreeClassifier
评估器
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier().fit(X, y)
编写一个辅助函数,对分类器的结果进行可视化:
def visualize_classifier(model, X, y, ax=None, cmap='rainbow'):
ax = ax or plt.gca()
# Plot the training points
ax.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=cmap,
clim=(y.min(), y.max()), zorder=3)
ax.axis('tight')
ax.axis('off')
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# fit the estimator
model.fit(X, y)
xx, yy = np.meshgrid(np.linspace(*xlim, num=200),
np.linspace(*ylim, num=200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
# Create a color plot with the results
n_classes = len(np.unique(y))
contours = ax.contourf(xx, yy, Z, alpha=0.3,
levels=np.arange(n_classes + 1) - 0.5,
cmap=cmap, clim=(y.min(), y.max()),
zorder=1)
ax.set(xlim=xlim, ylim=ylim)
visualize_classifier(DecisionTreeClassifier(), X, y)
随着决策树深度的不断加深,我们可能会得到非常奇怪的分类区域。这些区域显然不是根据数据本身的分布情况生成的正确分类结果,而更像是一个特殊的数据样本或数据噪音形成的干扰结果。也就是说,出现了过拟合
决策树的优点:
O(log2m)
决策树的缺点:
参考资料