本文仅供学习使用
决策树Ch07
1. 决策树基础知识
基于树的学习算法是一类非参数化的有监督学习算法。在决策树(decision tree)
中,每个决策规则产生一个决策节点,并创建通向新节点的分支。终点处设有决策规则的分支被称为叶子节点(leaf),一棵决策树包含一个根节点、若干个内部节点和若干个叶节点:叶节点对应于决策结果,其他每个几点则对应于一个属性测试。
决策树的主要思想是从树的根(root,或base)
开始,把分类任务按顺序分解为一个个选择,一步步进行到叶子节点,最终得到分类的结果。树结构很容易理解,可以表示称 if-then 规则的集合,适合应用于规则归纳(rule induction)
系统;在优化和搜索过程中,决策树使用贪婪的启发式方法运行搜索,每一步都选择最富含信息量的特征,评估当期那学习阶段的可能选项,并使那个阶段达到最佳,这非常适合应用于长时间领域。
决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树,其基本流程遵循简单且直观的分而治之(divide-and-conquer)。
1.1 决策树直观理解
当构建好一个判断模型后,新来一个用户后,可以根据构建好的模型直接进行判断, 比如新用户特性为:无房产、单身、年收入55K,那么根据判断得出该用户无法进行债务偿还。这种决策对于借贷业务有比较好的指导意义。
可以将一系列的条件转化成逻辑析取式的集合(if…then规则)
def predict(x1, x2, x3):
if x3 >= 97.5:
return 1
else:
if x1 == 1:
return 1
else:
if x2 == 0:
return 0
elif x2 == 1:
return 1
else:
return 0
# 无房产、单身、年收入55K
predict(0,0,55)
1.2 比特化(Bits)
1.2.1 等概率
假设存在一组随机变量X,各个值出现的概率关系:
P(X=A)=1/4 P(X=B)=1/4 P(X=C)=1/4 P(X=D)=1/4
现在有一组由X变量组成的序列: BACADDCBAC…;如果现在希望将这个序列转换为二进制来进行网络传输,那么我们得到一个得到一个这样的序列: 01001000111110010010…
A | B | C | D |
---|---|---|---|
00 | 01 | 10 | 11 |
结论: 在这种情况下,我们可以使用两个比特位来表示一个随机变量。
1.2.2 不等概率
当X变量出现的概率值不一样的时候:
P(X=A)=1/2 P(X=B)=1/4 P(X=C)=1/8 P(X=D)=1/8
对于一组序列信息来讲, 每个变量平均需要多少个比特位来描述呢?
- A与非A——0与1
- 非A中:B与非B——10与11
- 非A、B中:C与非C——110与111
A | B | C | D |
---|---|---|---|
0 | 10 | 110 | 111 |
E = 1 ⋅ 1 2 + 2 ⋅ 1 4 + 3 ⋅ 1 8 + 3 ⋅ 1 8 = 1.75 E\text{=}1\cdot \frac{1}{2}\text{+}2\cdot \frac{1}{4}\text{+}3\cdot \frac{1}{8}\text{+}3\cdot \frac{1}{8}\text{=}1.75 E=1⋅21+2⋅41+3⋅81+3⋅81=1.75
E = − log 2 ( 1 2 ) ⋅ 1 2 − log 2 ( 1 4 ) ⋅ 1 4 − log 2 ( 1 8 ) ⋅ 1 8 − log 2 ( 1 8 ) ⋅ 1 8 = 1.75 E\text{=}-{{\log }_{2}}(\frac{1}{2})\cdot \frac{1}{2}-{{\log }_{2}}(\frac{1}{4})\cdot \frac{1}{4}-{{\log }_{2}}(\frac{1}{8})\cdot \frac{1}{8}-{{\log }_{2}}(\frac{1}{8})\cdot \frac{1}{8}\text{=}1.75 E=−log2(21)⋅21−log2(41)⋅41−log2(81)⋅81−log2(81)⋅81=1.75
1.2.3 一般化的比特化(Bits)
假设现在随机变量X具有m个值,分别为: V1,V2,…,Vm;并且各个值出现的概率为:
P(X=V1)=p1 P(X=V2)=p2 P(X=V3)=p3 … P(X=Vm)=pm
那么对于一组序列信息来讲,每个变量平均需要多少个比特位来描述呢?
可以使用这些变量的期望来表示每个变量需要多少个比特位来描述信息:
E
(
X
)
=
−
p
1
log
2
(
p
1
)
−
p
2
log
2
(
p
2
)
−
…
−
p
m
log
2
(
p
m
)
=
−
∑
i
=
1
m
p
i
log
2
(
p
i
)
E(X)\text{=}-{{p}_{1}}{{\log }_{2}}({{p}_{1}})-{{p}_{2}}{{\log }_{2}}({{p}_{2}})-\ldots -{{p}_{m}}{{\log }_{2}}({{p}_{m}})\text{=}-\sum\limits_{i=1}^{m}{{{p}_{i}}{{\log }_{2}}({{p}_{i}})}
E(X)=−p1log2(p1)−p2log2(p2)−…−pmlog2(pm)=−i=1∑mpilog2(pi)——信息熵
1.3 信息熵(Entropy)
树是怎么工作的,以及什么是富含
信息量(informative)
,选择下一步要用什么特征。数学上完成这个任务的是信息论(information theory)
——最好的特征及时选择目前对分类能提供最大信息量的那个
信息熵是1984年克劳德·香农在他的论文《通信的数学理论》(A Mathematical Theory of Communication)中提出的,他给出了信息熵(information entropy)
的度量,来描述特征集合的冗余量、
H(X)就叫做随机变量X的信息熵: H ( X ) = − ∑ i = 1 m p i log 2 ( p i ) H(X)\text{=}-\sum\limits_{i=1}^{m}{{{p}_{i}}{{\log }_{2}}({{p}_{i}})} H(X)=−i=1∑mpilog2(pi)
信息量: 指的是一个样本/事件所蕴含的信息,如果一个事件的概率越大,那么就可以认为该事件所蕴含的信息越少。极端情况下,比如:“太阳从东方升起”,因为是确定事件,所以不携带任何信息量。
信息熵: 1948年,香农引入信息熵;一个系统越是有序(确定度高),信息熵就越低,一个系统越是混乱(不确定度高),信息熵就越高,所以信息熵被认为是一个系统有序程度(不确定度)的度量。
信息熵就是用来描述系统信息量的不确定度
有序:某一个大概率,确定度高,不确定度低,信息熵低
无序:概率分布均匀,确定度低,不确定度高,信息熵高
High Entropy(高信息熵): 表示随机变量X是均匀分布的,各种取值情况是等概率出现的。
Low Entropy(低信息熵): 表示随机变量X各种取值不是等概率出现。可能出现有的事件概率很大,有的事件概率很小。
信息熵计算过程
import numpy as np
def entropy(p):
return np.sum([-t * np.log2(t) for t in p]) # if t!= 0
print(entropy([0.25, 0.25, 0.25, 0.25]))
print(entropy([0.5, 0.5]))
print(entropy([0.65, 0.25, 0.05, 0.05]))
print(entropy([0.97, 0.01, 0.01, 0.01]))
print(entropy([0.5, 0.25, 0.25]))
print(entropy([0.7, 0.3]))
print(entropy([5.0 / 8, 3.0 / 8]))
print(entropy([0.4, 0.6]))
信息熵(Entropy)案例:
赌马比赛中,有两组赛马共八匹,获胜的概率如下:
在比赛前,对于第一组而言,我们只知道A/B/C/D获胜的概率是 一样的,我们是判断不出来任何偏向的;但是对于第二组而言, 我们很清楚的就能够判断A会获胜。
1.4 条件熵 H(Y|X)
给定条件X的情况下,随机变量Y的信息熵就叫做条件熵。
不难看出:
P
(
X
=
数学
)
=
1
2
、
P
(
X
=
I
T
)
=
1
4
、
P
(
X
=
英语
)
=
1
4
P(X=数学)=\frac{1}{2}、P(X=IT)=\frac{1}{4}、P(X=英语)=\frac{1}{4}
P(X=数学)=21、P(X=IT)=41、P(X=英语)=41
P ( Y = M ) = 1 2 、 P ( Y = F ) = 1 2 P(Y=M)=\frac{1}{2}、P(Y=F)=\frac{1}{2} P(Y=M)=21、P(Y=F)=21
H ( X ) = − ∑ i = 1 m p i log 2 ( p i ) = − 1 2 log 2 ( 1 2 ) − 1 4 log 2 ( 1 4 ) − 1 4 log 2 ( 1 4 ) = 1.5 H(X)=-\sum\limits_{i=1}^{m}{{{p}_{i}}{{\log }_{2}}({{p}_{i}})}=-\frac{1}{2}{{\log }_{2}}(\frac{1}{2})-\frac{1}{4}{{\log }_{2}}(\frac{1}{4})-\frac{1}{4}{{\log }_{2}}(\frac{1}{4})=1.5 H(X)=−i=1∑mpilog2(pi)=−21log2(21)−41log2(41)−41log2(41)=1.5
H ( Y ) = − ∑ i = 1 m p i log 2 ( p i ) = − 1 2 log 2 ( 1 2 ) − 1 2 log 2 ( 1 2 ) = 1 H(Y)=-\sum\limits_{i=1}^{m}{{{p}_{i}}{{\log }_{2}}({{p}_{i}})}=-\frac{1}{2}{{\log }_{2}}(\frac{1}{2})-\frac{1}{2}{{\log }_{2}}(\frac{1}{2})=1 H(Y)=−i=1∑mpilog2(pi)=−21log2(21)−21log2(21)=1
即: P ( y 1 ∣ x 1 ) = P ( y 1 ⋅ x 1 ) P ( x 1 ) = 2 4 = 1 2 P({{y}_{1}}|{{x}_{1}})=\frac{P({{y}_{1}}\cdot {{x}_{1}})}{P({{x}_{1}})}=\frac{2}{4}=\frac{1}{2} P(y1∣x1)=P(x1)P(y1⋅x1)=42=21
当专业(X)为数学的时候,Y的信息熵的值为:H(Y|X=数学):
H
(
Y
∣
x
1
)
=
−
1
2
log
2
(
1
2
)
−
1
2
log
2
(
1
2
)
=
1
H(Y|{{x}_{1}})=-\frac{1}{2}{{\log }_{2}}(\frac{1}{2})-\frac{1}{2}{{\log }_{2}}(\frac{1}{2})=1
H(Y∣x1)=−21log2(21)−21log2(21)=1
H
(
Y
∣
x
2
)
=
0
H(Y|{{x}_{2}})=0
H(Y∣x2)=0
H
(
Y
∣
x
3
)
=
0
H(Y|{{x}_{3}})=0
H(Y∣x3)=0
H ( Y ∣ X ) = ∑ j = 1 P ( x j ) H ( Y ∣ x j ) = 1 2 ⋅ 1 + 1 4 ⋅ 0 + 1 4 ⋅ 0 = 1 2 H(Y|X)=\sum\limits_{j=1}{P({{x}_{j}})H(Y|{{x}_{j}})}=\frac{1}{2}\cdot 1+\frac{1}{4}\cdot 0+\frac{1}{4}\cdot 0=\frac{1}{2} H(Y∣X)=j=1∑P(xj)H(Y∣xj)=21⋅1+41⋅0+41⋅0=21
给定条件X的情况下,所有不同x值情况下Y的信息熵的平均值叫做条件熵。
H ( Y ∣ X ) = ∑ j = 1 P ( x j ) H ( Y ∣ x j ) H(Y|X)=\sum\limits_{j=1}{P({{x}_{j}})H(Y|{{x}_{j}})} H(Y∣X)=j=1∑P(xj)H(Y∣xj)
另外一个公式如下所示:
H
(
Y
∣
X
)
=
H
(
X
,
Y
)
−
H
(
X
)
H(Y|X)=H(X,Y)-H(X)
H(Y∣X)=H(X,Y)−H(X)
事件(X,Y)发生所包含的熵,减去事件X单独发生的熵,即为在事件X发生的前提下,Y发生“新”带来的熵,这个也就是条件熵本身的概念。
H ( Y ∣ X ) = ∑ j = 1 P ( x j ) H ( Y ∣ x j ) = ∑ x P ( x ) H ( Y ∣ x ) = ∑ x P ( x ) ( − ∑ y p ( y ∣ x ) log 2 p ( y ∣ x ) ) H(Y|X)=\sum\limits_{j=1}{P({{x}_{j}})H(Y|{{x}_{j}})}=\sum\limits_{x}{P(x)H(Y|x)}=\sum\limits_{x}{P(x)(-\sum\limits_{y}^{{}}{p(y|x){{\log }_{2}}p(y|x))}} H(Y∣X)=j=1∑P(xj)H(Y∣xj)=x∑P(x)H(Y∣x)=x∑P(x)(−y∑p(y∣x)log2p(y∣x))
= − ∑ x ∑ y p ( x ) p ( y ∣ x ) log 2 p ( y ∣ x ) = − ∑ x ∑ y p ( x , y ) log 2 ( p ( x , y ) p ( x ) ) =-\sum\limits_{x}{\sum\limits_{y}^{{}}{p(x)p(y|x){{\log }_{2}}p(y|x)}}=-\sum\limits_{x}{\sum\limits_{y}^{{}}{p(x,y){{\log }_{2}}(\frac{p(x,y)}{p(x)})}} =−x∑y∑p(x)p(y∣x)log2p(y∣x)=−x∑y∑p(x,y)log2(p(x)p(x,y))
= − ∑ x ∑ y p ( x , y ) log 2 p ( x , y ) + ∑ x ∑ y p ( x , y ) log 2 p ( x ) = H ( X , Y ) − ( − ∑ x p ( x ) log 2 p ( x ) ) =-\sum\limits_{x}{\sum\limits_{y}^{{}}{p(x,y){{\log }_{2}}p(x,y)+}}\sum\limits_{x}{\sum\limits_{y}^{{}}{p(x,y){{\log }_{2}}p(x)}}=H(X,Y)-(-\sum\limits_{x}{p(x){{\log }_{2}}p(x)}) =−x∑y∑p(x,y)log2p(x,y)+x∑y∑p(x,y)log2p(x)=H(X,Y)−(−x∑p(x)log2p(x))
= H ( X , Y ) − H ( X ) =H(X,Y)-H(X) =H(X,Y)−H(X)
1.5 互信息、条件熵、联合熵
I(X;Y)为互信息 : I(X;Y)=H(Y)+H(X)-H(X,Y)
2. 决策树
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
创建决策树分类器对象:
decisiontree = DecisionTreeClassifier(random_state=0)
训练模型:model = decisiontree.fit(features,target)
创建新样本:observation = []
预测样本的分类:model.predict(observation)
使用predict_proba方法查看该样本属于每个分类(预测的分类)的概率:model.predict_proba(observation)
使用entropy作为不纯度检测方法创建决策树分类器对象:decisiontree_entropy = DecisionTreeClassifier(criterion='entropy',random_state=0)
训练模型:model_entropy = decisiontree_entropy.fit(features,target)
决策树(Decision Tree)
是在已知各种情况发生概率的基础上,通过构建决策树来进行分析的一种方式,是一种直观应用概率分析的一种图解法;决策树是一种预测模型,代表的是对象属性与对象值之间的映射关系;决策树是一种树形结构,其中每个内部节点表示一个属性的测试,每个分支表示一个测试输出,每个叶节点代表一 种预测类别;决策树是一种非常常用的有监督的分类算法。
决策树的决策过程就是从根节点(Root)开始,测试待分类项中对应的特征属性,并按照其值选择输出分支,直到叶子节点(leaf),将叶子节点的存放的类别作为决策结果。 (内部节点)
决策树分为两大类:分类树(main)
和回归树
,前者用于分类标签值,后者用于预测连续值, 常用算法有ID3
、C4.5
、CART
等
2.1 决策树构建过程
决策树算法的重点就是决策树的构造;决策树的构造就是进行属性选择度量,确定各个特征属性之间的拓扑结构(树结构);构建决策树的关键步骤就是分裂属性,分裂属性是指在某个节点按照某一类特征属性的不同划分构建不同的分支,其目标就是让各个分裂子集尽可能的纯(让一个分裂子数据集中待分类的项尽可能的属于同一个类别)。
构建步骤如下:
- 将所有的特征看成一个一个的节点;
- 遍历当前特征的每一种分割方式,找到最好的分割点;将数据划分为不同的子节点,eg: N1、 N2…Nm;计算划分之后所有子节点的’纯度’信息;
- 使用第二步遍历所有特征,选择出最优的特征以及该特征的最优的划分方式;得出最终的子节点: N1、N2…Nm
- 对子节点N1、N2…Nm分别继续执行2-3步,直到每个最终的子节点都足够’纯’。
若为一棵仅有一层划分的决策树,亦称为
决策树桩(decision stump)
2.1.1 决策树特征属性类型
根据特征属性的类型不同,在构建决策树的时候,采用不同的方式,具体如下:
- 属性是离散值,而且不要求生成的是二叉决策树,此时一个属性就是一个分支
- 属性是离散值,而且要求生成的是二叉决策树,此时使用属性划分的子集进行测试, 按照“属于此子集”和“不属于此子集”分成两个分支
- 属性是连续值,可以确定一个值作为
分裂点split_point
,按照 >split_point 和 <=split_point 生成两个分支
决策树算法是一种“贪心”算法策略,只考虑在当前数据特征情况下的最好分割方式,不能进行回溯操作。
对于整体的数据集而言,按照所有的特征属性进行划分操作,对所有划分操作的结果集的“纯度”进行比较,选择“纯度”越高的特征属性作为当前需要分割的数据集进行分割操作,持续迭代,直到得到最终结果。决策树是通过 “纯度” 来选择分割特征属性点的。
2.1.2 决策树量化纯度
决策树的构建是基于样本概率和纯度进行构建操作的,那么进行判断数据集是否“纯”——纯度(purity)
可以通过三个公式进行判断,分别是Gini系数
、熵(Entropy)
、 错误率
,这三个公式值越大,表示数据越“不纯”;越小表示越“纯”;实践证明这三种公式效果差不多,一般情况使用熵公式。若是改变不纯度度量方式,可以修改参数criterion
G i n i = 1 − ∑ i = 1 n p ( i ) 2 Gini=1-\sum\limits_{i=1}^{n}{p{{(i)}^{2}}} Gini=1−i=1∑np(i)2(默认)
H ( E n t r o p y ) = − ∑ i = 1 m p i log 2 ( p i ) H(Entropy)=-\sum\limits_{i=1}^{m}{{{p}_{i}}{{\log }_{2}}({{p}_{i}})} H(Entropy)=−i=1∑mpilog2(pi)
E r r o r = 1 − max n i = 1 { p ( i ) } Error=1-\underset{i=1}{\overset{n}{\mathop{\max }}}\,\{p(i)\} Error=1−i=1maxn{p(i)}
决策树的训练器会尝试找到节点上能够最大限度降低数据不纯度(impurity)
的决策规则
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
## 设置字符集,防止中文乱码
mpl.rcParams['font.sans-serif'] = [u'simHei']
mpl.rcParams['axes.unicode_minus'] = False
##信息熵
def entropy(p):
return np.sum([-i * np.log2(i) for i in p if i != 0])
##信息熵
def H(p):
return entropy(p)
##基尼系数
def gini(p):
return 1 - np.sum([i * i for i in p])
##错误率
def error(p):
return 1 - np.max(p)
# # 不纯度
P0 = np.linspace(0.0, 1.0, 1000) # 这个表示在0到1之间生成1000个p值(另外的就是1-p)
P1 = 1 - P0
y1 = [H([p0, 1 - p0]) * 0.5 for p0 in P0] ##熵之半
y2 = [gini([p0, 1 - p0]) for p0 in P0]
y3 = [error([p0, 1 - p0]) for p0 in P0]
plt.plot(P0, y1, label=u'熵之半')
plt.plot(P0, y2, label=u'gini')
plt.plot(P0, y3, label=u'error')
plt.legend(loc='upper left')
plt.xlabel(u'p', fontsize=18)
plt.ylabel(u'不纯度', fontsize=18)
plt.title(u'不纯度指标', fontsize=20)
plt.show() # 绘制图像
# # 熵和熵之半
y = [H([p0, 1 - p0]) for p0 in P0]
y1 = [H([p0, 1 - p0]) * 0.5 for p0 in P0]
plt.plot(P0, y, label=u'H(X)')
plt.plot(P0, y1, label=u'熵之半')
plt.legend(loc='upper left')
plt.xlabel(u'p', fontsize=18)
plt.ylabel(u'熵', fontsize=18)
plt.title(u'熵和熵之半', fontsize=20)
plt.show() # 绘制图像
当计算出各个特征属性的量化纯度值后使用信息增益度来选择出当前数据集的分割特征属性;如果信息增益度的值越大,表示在该特征属性上会损失的纯度越大 ,那么该属性就越应该在决策树的上层,计算公式为:
G a i n = Δ = H ( D ) − H ( D ∣ A ) Gain=\Delta =H(D)-H(D|A) Gain=Δ=H(D)−H(D∣A)
D目标属性,A为某一个待划分的特征属性;Gain为A为特征对训练数据集D的信息增益,它为集合D的 经验熵H(D) 与特征A给定条件下D的 经验条件熵H(D|A) 之差
import sys
import numpy as np
import pandas as pd
def entropy(p):
"""
信息熵的计算公式
:param p:
:return:
"""
return np.sum([-t * np.log2(t) for t in p])
def gini(p):
"""
Gini系数的计算公式
:param p:
:return:
"""
return 1 - np.sum([t * t for t in p])
def error(p):
"""
错误率的公式
:param p:
:return:
"""
return 1 - np.max(p)
def h(p):
"""
决策树中对于纯度的衡量
:param p:
:return:
"""
return gini(p)
# return entropy(p)
"""
是,单身,125,否
否,已婚,100,否
否,单身,100,否
是,已婚,110,否
是,离婚,60,否
否,离婚,95,是
否,单身,85,是
否,已婚,75,否
否,单身,90,是
是,离婚,220,否
"""
# 一、第一个判断节点的选择
# 1.0 计算整个数据集的纯度(样本数10个,3个是,7个否——Y)
h0 = h([0.7, 0.3])
# 1.1 计算以房产作为分割点的时候条件熵(遍历数据计算x1为是和否的概率)
# 第一个分支: 是;样本数目:4,全部为否
p11 = 0.4
h11 = h([1.0])
# 第二个分支:否;样本数目:6,3个是,3个否
p12 = 0.6
h12 = h([0.5, 0.5])
h1 = p11 * h11 + p12 * h12
g1 = h0 - h1
# todo:信息增益率
print("以房产作为划分特征的时候,信息增益为:{}".format(g1))
# 1.2 计算以婚姻情况作为分割点的时候条件熵(遍历数据计算x2为单身、离婚、已婚的概率)
# 第一个分支: 单身;样本数目:4,2个是,2个否
p21 = 0.4
h21 = h([0.5, 0.5])
# 第二个分支:离婚;样本数目:3,1个是,2个否
p22 = 0.3
h22 = h([1.0 / 3, 2.0 / 3])
# 第三个分支: 已婚,样本数目:3,3个否
p23 = 0.3
h23 = h([1.0])
h2 = p21 * h21 + p22 * h22 + p23 * h23
g2 = h0 - h2
print("以婚姻作为划分特征的时候,信息增益为:{}".format(g2))
# 1.3. 以年收入80作为划分值
# 第一个分支: 小于等于80;样本数目:2,2个否
p31 = 0.2
h31 = h([1.0])
# 第二个分支:大于80;样本数目:8,3个是,5个否
p32 = 0.8
h32 = h([3.0 / 8, 5.0 / 8])
h3 = p31 * h31 + p32 * h32
g3 = h0 - h3
print("以年收入80作为划分特征的时候,信息增益为:{}".format(g3))
# 1.4. 以年收入97.5作为划分值
# 第一个分支: 小于等于97.5;样本数目:5,3个是,2个否
p41 = 0.5
h41 = h([2.0 / 5, 3.0 / 5])
# 第二个分支:大于97.5;样本数目:5,5个否
p42 = 0.5
h42 = h([1.0])
h4 = p41 * h41 + p42 * h42
g4 = h0 - h4
print("以年收入97.5作为划分特征的时候,信息增益为:{}".format(g4))
# sys.exit()
print("=" * 100)
2.2 决策树算法的停止条件
决策树构建的过程是一个递归的过程,所以必须给定停止条件,否则过程将不会进行停止,一般情况有两种停止条件:
- 说法1
- 当每个子节点只有一种类型的时候停止构建
- 当前节点中样本数小于某个阈值,同时迭代次数达到给定值时,停止构建过程, 此时使用 max(p(i)) 作为节点的对应类型
NOTE:方式一可能会使树的节点过多,导致过拟合(Overfiting) 等问题;比较常用的方式是使用方式二作为停止条件。
- 说法2
- 当前节点包含的样本全属与同一类别,无需划分;
- 当前属性集为空,或是所有样本在所有属性上取值相同,无法划分;
- 当前节点包含的样本集合为空,不能划分。
在第二种情形下,我们把当前结点标记为叶结点,并将其类别设定为该结点所含样本最多的类别;在第三种情形下,同样把当前节点标记为叶结点,但将其类别设定为其父结点所含样本最多的类别——前者是在利用当前节点的后验分布,后者则是把父结点的样本分布作为当前节点的先验分布。
2.3 决策树算法效果评估
决策树的效果评估和一般的分类算法一样,采用混淆矩阵来进行计算准确率、 召回率、精确率等指标
也可以采用叶子节点的不纯度值总和来评估算法的效果,值越小,效果越好
决策树的损失函数(该值越小,算法效果越好)
C ( T ) = ∑ i = 1 l e a f ∣ D i ∣ ∣ D ∣ H ( t ) C(T)=\sum\limits_{i=1}^{leaf}{\frac{\left| {{D}_{i}} \right|}{\left| D \right|}}H(t) C(T)=i=1∑leaf∣D∣∣Di∣H(t)(D为总数量,Dt为叶子中的样本数量)
决策树的损失函数(该值越小,算法效果越好)
l
o
s
s
=
∑
i
=
1
l
e
a
f
∣
D
i
∣
∣
D
∣
H
(
t
)
loss=\sum\limits_{i=1}^{leaf}{\frac{\left| {{D}_{i}} \right|}{\left| D \right|}}H(t)
loss=i=1∑leaf∣D∣∣Di∣H(t)
2.4 决策树直观理解结果计算
import sys
import numpy as np
import pandas as pd
def entropy(p):
"""
信息熵的计算公式
:param p:
:return:
"""
return np.sum([-t * np.log2(t) for t in p])
def gini(p):
"""
Gini系数的计算公式
:param p:
:return:
"""
return 1 - np.sum([t * t for t in p])
def error(p):
"""
错误率的公式
:param p:
:return:
"""
return 1 - np.max(p)
def h(p):
"""
决策树中对于纯度的衡量
:param p:
:return:
"""
return gini(p)
# return entropy(p)
"""
是,单身,125,否
否,已婚,100,否
否,单身,100,否
是,已婚,110,否
是,离婚,60,否
否,离婚,95,是
否,单身,85,是
否,已婚,75,否
否,单身,90,是
是,离婚,220,否
"""
# 一、第一个判断节点的选择
# 1.0 计算整个数据集的纯度(样本数10个,3个是,7个否——Y)
h0 = h([0.7, 0.3])
# 1.1 计算以房产作为分割点的时候条件熵(遍历数据计算x1为是和否的概率)
# 第一个分支: 是;样本数目:4,全部为否
p11 = 0.4
h11 = h([1.0])
# 第二个分支:否;样本数目:6,3个是,3个否
p12 = 0.6
h12 = h([0.5, 0.5])
h1 = p11 * h11 + p12 * h12
g1 = h0 - h1
# todo:信息增益率
print("以房产作为划分特征的时候,信息增益为:{}".format(g1))
# 1.2 计算以婚姻情况作为分割点的时候条件熵(遍历数据计算x2为单身、离婚、已婚的概率)
# 第一个分支: 单身;样本数目:4,2个是,2个否
p21 = 0.4
h21 = h([0.5, 0.5])
# 第二个分支:离婚;样本数目:3,1个是,2个否
p22 = 0.3
h22 = h([1.0 / 3, 2.0 / 3])
# 第三个分支: 已婚,样本数目:3,3个否
p23 = 0.3
h23 = h([1.0])
h2 = p21 * h21 + p22 * h22 + p23 * h23
g2 = h0 - h2
print("以婚姻作为划分特征的时候,信息增益为:{}".format(g2))
# 1.3. 以年收入80作为划分值
# 第一个分支: 小于等于80;样本数目:2,2个否
p31 = 0.2
h31 = h([1.0])
# 第二个分支:大于80;样本数目:8,3个是,5个否
p32 = 0.8
h32 = h([3.0 / 8, 5.0 / 8])
h3 = p31 * h31 + p32 * h32
g3 = h0 - h3
print("以年收入80作为划分特征的时候,信息增益为:{}".format(g3))
# 1.4. 以年收入97.5作为划分值
# 第一个分支: 小于等于97.5;样本数目:5,3个是,2个否
p41 = 0.5
h41 = h([2.0 / 5, 3.0 / 5])
# 第二个分支:大于97.5;样本数目:5,5个否
p42 = 0.5
h42 = h([1.0])
h4 = p41 * h41 + p42 * h42
g4 = h0 - h4
print("以年收入97.5作为划分特征的时候,信息增益为:{}".format(g4))
# sys.exit()
print("=" * 100)
# 二、针对于左子树继续划分(年收入小于等于97.5的数据;样本数目:5,3个是,2个否)
h0 = h41
# 2.1 计算以房产作为分割点的时候条件熵
# 第一个分支: 是;样本数目:1,全部为否
p11 = 0.2
h11 = h([1.0])
# 第二个分支:否;样本数目:4,3个是,1个否
p12 = 0.8
h12 = h([0.75, 0.25])
h1 = p11 * h11 + p12 * h12
g1 = h0 - h1
print("以房产作为划分特征的时候,信息增益为:{}".format(g1))
# 2.2 计算以婚姻情况作为分割点的时候条件熵(遍历数据计算x2为单身、离婚、已婚的概率)
# 第一个分支: 单身;样本数目:2,2个是
p21 = 0.4
h21 = h([1.0])
# 第二个分支:离婚;样本数目:2,1个是,1个否
p22 = 0.4
h22 = h([0.5, 0.5])
# 第三个分支: 已婚,样本数目:1,1个否
p23 = 0.2
h23 = h([1.0])
h2 = p21 * h21 + p22 * h22 + p23 * h23
g2 = h0 - h2
print("以婚姻作为划分特征的时候,信息增益为:{}".format(g2))
print("=" * 100)
# 三、针对于右子树继续划分(年收入大于97.5的数据;样本数目:5,5个否 --> 不需要划分)
# 四、针对于第一个分支的子树继续划分(单身的数据;样本数目:2,2个是 --> 不需要划分)
# 五、针对于第二个分支的子树继续划分(离婚的数据;样本数目:2,1个是 1个否)
"""
是,离婚,60,否
否,离婚,95,是
"""
h0 = h22
# 计算以房产作为分割点的时候条件熵
# 第一个分支: 是;样本数目:1,全部为否
p11 = 0.5
h11 = h([1.0])
# 第二个分支:否;样本数目:1,全部为是
p12 = 0.5
h12 = h([1.0])
h1 = p11 * h11 + p12 * h12
g3 = h0 - h1
print("以房产作为划分特征的时候,信息增益为:{}".format(g3))
# 六、针对于第三个分支的子树继续划分(已婚的数据;样本数目:1,1个否 --> 不需要划分)
print("=" * 100)
重新构建决策树——(如果一个特征不允许重复选择)
当构建好一个判断模型后,新来一个用户后,可以根据构建好的模型直接进行判断,比如新用户特性为:无房产、单身、年收入55K,那么根据判断得出该用户无法进行债务偿还。这种决策对于借贷业务有比较好的指导意义。
2.5 决策树主要算法
建立决策树的主要是以下三种算法:
- ID3 (Iterative Dichotomiser)
- C4.5 (Quinlan,1993)
- CART(Classification And Regression Tree)
2.5.1 ID3算法
ID3算法
是决策树的一个经典的构造算法,内部使用信息熵以及信息增益来进行构建;每次迭代选择信息增益最大的特征属性作为分割属性 (选择具有最大信息增益的特征)
ID3的核心思想是:在决策树下一次分类时,对于每一个特征,通过计算整个训练集的熵的减少来选择特征,这也称为信息增益(information gain)
,描述为整个几何的熵减去每一个特定特征被选择后的熵。
- ID3算法只支持离散的特征属性,不支持连续的特征属性
- ID3算法构建的是多叉树
G a i n = Δ = H ( D ) − H ( D ∣ A ) Gain=\Delta =H(D)-H(D|A) Gain=Δ=H(D)−H(D∣A)
H ( D ) = − ∑ i = 1 m p i log 2 ( p i ) H(D)\text{=}-\sum\limits_{i=1}^{m}{{{p}_{i}}{{\log }_{2}}({{p}_{i}})} H(D)=−i=1∑mpilog2(pi)
每一步都通过选择具有最大信息增益的叶子,用贪婪算法搜索可能的决策树的空间,算法的输出就是整个决策树,即一个包括节点、边和叶子的集合。
优点:
• 决策树构建速度快;实现简单;
缺点:
• 计算依赖于特征取值数目较多的特征,而属性值最多的属性并不一定最优
• ID3算法不是递增算法
• ID3算法是单变量决策树,对于特征属性之间的关系不会考虑
• 抗噪性差
• 只适合小规模数据集,需要将数据放到内存中
如何从训练样例推广到所有可能输入的集合是ID3算法中一个值得考虑的内容——归纳偏差(inductive bias)
:在添加树的下一个特征时,选择具有最高信息增益的特征,这使算法偏向于生成较小的树,因为它试图最小化剩余的信息量——即短解决方案通常比较长的解决方案更好,奥卡姆剃刀KISS(Keep It Simple, Stupid)——最小描述长度(MDL,Rissanen,1989)
,表示对某些东西的最短描述,即压缩最充分的就是最好的描述。
该算法可以处理数据集中的噪声,因为标记被分配给目标特征的最为常见的值。决策树的另一个好处是可以处理丢失的数据(可以跳过树的那个节点,并在没有这个特征的情况下继续进行,对该特征所有的可能值进行求和)——在神经网络中,通常是丢弃任何缺少数据的数据点,要么进行估测(通过检测类似的数据点,并使用这些点相应特征的值、均值或中值。这种方法假定丢失的数据随机分布在数据集中,而不是由于某些位置过程而丢失)
认为ID3偏向短树只能算是部分正确的看法,在更先进的算法中使用的方法为剪枝法
2.5.2 C4.5算法
C4.5使用规则后修剪的方法。该方法首先获取ID3生成的数,将其转换为一组 if-then 规则,然后通过删除之前的特征从而修剪每个特征,如果规则的准确性增加,则去除该特征。然后根据训练接的准确性对规则进行排序并按顺序应用。利用规则处理的好处是更容易理解,它们在树种的顺序无关紧要,只需要在分类中的准确性
在ID3算法的基础上,进行算法优化提出的一种算法(C4.5)
;现在C4.5已经是特别经典的一种决策树构造算法;使用信息增益率来取代ID3算法中的信息增益,在树的构造过程中会进行剪枝(pruning)操作进行优化——防止过拟合;能够自动完成对连续属性的离散化处理;C4.5构建的是多分支的决策树;C4.5算法在选中分割属性的时候选择信息增益率最大的属性,涉及到的公式为:
G a i n = Δ = H ( D ) − H ( D ∣ A ) Gain=\Delta =H(D)-H(D|A) Gain=Δ=H(D)−H(D∣A)
H ( D ) = − ∑ i = 1 m p i log 2 ( p i ) H(D)\text{=}-\sum\limits_{i=1}^{m}{{{p}_{i}}{{\log }_{2}}({{p}_{i}})} H(D)=−i=1∑mpilog2(pi)
G a i n _ r a t i o ( A ) = G a i n ( A ) H ( A ) Gain\_ratio(A)=\frac{Gain(A)}{H(A)} Gain_ratio(A)=H(A)Gain(A) (此处不可误认为H(D))
实际上,信息增益准则对可取值数目较多的属性有所偏好,为减少这种偏好可能带来的不利影响,不直接使用信息增益,而是是使用
增益率(gain ratio)
来选择最优划分属性;
此处H(A)称为属性A的固有值(intrinsic value)
,属性A的可能取值数目越多,则该值通常会越大;
增益率准则对可取值数目较少的属性有所偏好,因此C4.5 算法并不是直接选择增益率最大的候选划分属性,而是使用了一个启发式:先从候选划分属性中找出信息增益高于平均水平的属性,再从选择增益率最高的。
优点:
• 产生的规则易于理解
• 准确率较高
• 实现简单
缺点:
• 对数据集需要进行多次顺序扫描和排序,所以效率较低
• 只适合小规模数据集,需要将数据放到内存中
决策树剪枝的基本策略——预剪枝(prepruning)
和后剪枝(postpruning
):预剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点;后剪枝则是先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能带来决策树泛化性能提升,则将该子树替换为叶结点。
如何判断决策树泛化性能是否提升?——留出法:预留一部分数据用作“验证集”以进行性能评估。
- 预剪枝要对划分前后的泛化性能进行估计;预剪枝使得决策树的很多分支都没有“展开”,这不仅降低了过拟合的风险,还显著减少了决策树的训练时间开销和测试时间开销;但另一方面,有些分支的当前划分虽不能提升泛化性能、甚至可能导致泛化性能暂时下降,但在其基础上进行的后续划分却有可能导致性能显著提高;预剪枝基于“贪心”本质禁止这些分支展开,给预剪枝决策树带来了欠拟合的风险。
- 后剪枝先从训练集生成一棵完整决策树;后剪枝决策树通常比预剪枝决策树保留了更多的分支。一般情况下,后剪枝决策树的欠拟合风险很小,泛化性能往往由于预剪枝决策树。但后剪枝过程是在生成完全决策树之后进行的,因此其训练时间开销比未剪枝决策树和预剪枝决策树都大得多。
连续值处理——连续属性离散化:最简单的策略是采用二分法(bi-partition)
对连续属性进行处理——将离散点区间的中位点作为候选划分点,然后可以向离散属性值一样来考察这些划分点(可将划分点设为该属性在训练集中出现的不大于中位点的最大值,从而使得最终决策树使用的划分点都在训练集中出现过)
给定样本集D和连续属性a,假定a在D上出现了n和不同的取值,将这些值从小到大进行排序,记为 { a 1 , a 2 , . . . , a n } \{{{a}^{1}},{{a}^{2}},...,{{a}^{n}}\} {a1,a2,...,an},基于划分点t可将D分为子集 D t − D_{t}^{-} Dt−和 D t + D_{t}^{+} Dt+,其中 D t − D_{t}^{-} Dt−包含那些在属性a上取值不大于t的样本,而 D t + D_{t}^{+} Dt+则包含那些在属性值a上取值大于t的样本。显然,对相邻的属性取值 a i {{a}^{i}} ai与 a i + 1 {{a}^{i+1}} ai+1,t在区间 [ a i , a i + 1 ) [{{a}^{i}},{{a}^{i+1}}) [ai,ai+1)中取任意值所产生的划分结果相同。因此,对连续属性a,我们可考察包含 n-1 个元素的候选划分点集合:
T a = { a i + a i + 1 2 ∣ 1 ≤ i ≤ n − 1 } {{T}_{a}}=\left\{ \frac{{{a}^{i}}+{{a}^{i+1}}}{2}|1\le i\le n-1 \right\} Ta={2ai+ai+1∣1≤i≤n−1}
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
λ
)
Gain(D,a)=\underset{t\in {{T}_{a}}}{\mathop{\max }}\,Gain(D,a,t)=\underset{t\in {{T}_{a}}}{\mathop{\max }}\,Ent(D)-\sum\limits_{\lambda \in \{-,+\}}{\frac{\left| D_{t}^{\lambda } \right|}{\left| D \right|}}Ent(D_{t}^{\lambda })
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基于划分点t二分后的信息增益,于是,我们就可选择使
G
a
i
n
(
D
,
a
,
t
)
Gain(D,a,t)
Gain(D,a,t)最大化的划分点。
与离散属性不同,若当前结点划分属性为连续属性,该属性还可作为其后代结点的划分属性
缺失值处理——需解决两个问题:①如何在属性值缺失的情况下进行划分属性选择?②给定划分属性,若样本在该属性上的值缺失,如何对样本进行划分?
给定训练集D和属性a,令
D
~
{\tilde{D}}
D~表示D中在属性a上没有缺失值的样本子集,对于问题①,显然我们仅可根据
D
~
{\tilde{D}}
D~来判断属性a的优劣。假定属性a有V个可取值
{
a
1
,
a
2
,
.
.
.
,
a
V
}
\{{{a}^{1}},{{a}^{2}},...,{{a}^{V}}\}
{a1,a2,...,aV},另
D
~
υ
{{{\tilde{D}}}^{\upsilon }}
D~υ表示
D
~
{\tilde{D}}
D~中在属性上a取值为
a
υ
{{a}^{\upsilon }}
aυ的样本子集,
D
~
k
{{{\tilde{D}}}_{k}}
D~k表示
D
~
{\tilde{D}}
D~中属于第k类
(
k
=
1
,
2
,
.
.
.
,
∣
γ
∣
)
(k=1,2,...,\left| \gamma \right|)
(k=1,2,...,∣γ∣)的样本子集,则显然有
D
~
=
⋃
k
=
1
∣
γ
∣
D
~
k
\tilde{D}=\bigcup _{k=1}^{\left| \gamma \right|}{{{\tilde{D}}}_{k}}
D~=⋃k=1∣γ∣D~k,
D
~
=
⋃
υ
=
1
∣
V
∣
D
~
υ
\tilde{D}=\bigcup _{\upsilon =1}^{\left| V \right|}{{{\tilde{D}}}^{\upsilon }}
D~=⋃υ=1∣V∣D~υ,假定我们为每个样本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
≤
∣
γ
∣
)
r
~
v
=
∑
x
∈
D
~
υ
w
x
∑
x
∈
D
~
w
x
(
1
≤
υ
≤
∣
V
∣
)
\left\{ \begin{matrix} \rho =\frac{\sum\limits_{x\in \tilde{D}}{{{w}_{x}}}}{\sum\limits_{x\in D}{{{w}_{x}}}} \\ {{{\tilde{p}}}_{k}}=\frac{\sum\limits_{x\in {{{\tilde{D}}}_{k}}}{{{w}_{x}}}}{\sum\limits_{x\in \tilde{D}}{{{w}_{x}}}}(1\le k\le \left| \gamma \right|) \\ {{{\tilde{r}}}_{v}}=\frac{\sum\limits_{x\in {{{\tilde{D}}}^{\upsilon }}}{{{w}_{x}}}}{\sum\limits_{x\in \tilde{D}}{{{w}_{x}}}}(1\le \upsilon \le \left| V \right|) \\ \end{matrix} \right.
⎩
⎨
⎧ρ=x∈D∑wxx∈D~∑wxp~k=x∈D~∑wxx∈D~k∑wx(1≤k≤∣γ∣)r~v=x∈D~∑wxx∈D~υ∑wx(1≤υ≤∣V∣)
直观地看,对属性a,
ρ
\rho
ρ表示无缺失值样本所占的比例,
p
~
k
{{{\tilde{p}}}_{k}}
p~k表示无缺失值样本中第k类所占的比例,
r
~
v
{{{\tilde{r}}}_{v}}
r~v则表示无缺失值样本中在属性a上取值
a
υ
{{a}^{\upsilon }}
aυ的样本所占的比例,显然:
∑
k
=
1
∣
γ
∣
p
~
k
=
1
\sum\nolimits_{k=1}^{\left| \gamma \right|}{{{{\tilde{p}}}_{k}}}=1
∑k=1∣γ∣p~k=1,
∑
υ
=
1
∣
V
∣
r
~
v
=
1
\sum\nolimits_{\upsilon =1}^{\left| V \right|}{{{{\tilde{r}}}_{v}}}=1
∑υ=1∣V∣r~v=1
基于上述定义,我们可以将信息增益计算式推广为:
G a i n ( D , a ) = ρ × G a i n ( D ~ , a ) = ρ × ( E n t ( D ~ ) − ∑ υ = 1 V r ~ v E n t ( D ~ υ ) ) Gain(D,a)=\rho \times Gain(\tilde{D},a)=\rho \times (Ent(\tilde{D})-\sum\limits_{\upsilon =1}^{V}{{{{\tilde{r}}}_{v}}Ent({{{\tilde{D}}}^{\upsilon }})}) Gain(D,a)=ρ×Gain(D~,a)=ρ×(Ent(D~)−υ=1∑Vr~vEnt(D~υ))
E n t ( D ~ υ ) = − ∑ k = 1 ∣ γ ∣ p ~ k log 2 p ~ k Ent({{{\tilde{D}}}^{\upsilon }})=-\sum\limits_{k=1}^{\left| \gamma \right|}{{{{\tilde{p}}}_{k}}{{\log }_{2}}{{{\tilde{p}}}_{k}}} Ent(D~υ)=−k=1∑∣γ∣p~klog2p~k
对于问题②,若样本x在划分属性a上的取值已知,则将x划入与其取值对应的子结点,且样本权值在子结点中保持为 w x {{w}_{x}} wx。若样本x在划分属性a上的取值未知,则将x同时划入所有子结点,且样本权值与属性值 a υ {{a}^{\upsilon }} aυ对应的子结点中调整为 r ~ v ⋅ w x {{{\tilde{r}}}_{v}}\cdot {{w}_{x}} r~v⋅wx;直观地看,这就是让同一个样本以不同的概率划入到不同的子结点中去。
2.5.3 CART算法
对于回归问题,最简单的解决方案是对连续变量进行离散化。这些算法生成的树都是单变量树,因为它们一次选择一个特征(维度)并根据该特征进行分割。还有一些算法通过选择特征组合来生成多变量树。如果可以找到能够很好地奋力数据但不与任何轴平行的直线,那么就可以生成相当小的树。然而单变量树更简单且往往会获得良好的结果,因此我们不会在考虑多变量树。
使用基尼系数(分类树)作为数据纯度的量化指标
来构建的决策树算法就叫做CART(Classification And Regression Tree,分类回归树)算法
。CART算法使用GINI增益率作为分割属性选择的标准,选择GINI增益率最大的作为当前数据集的分割属性;可用于分类和回归两类问题。强调备注:CART构建是二叉树。
G i n i = 1 − ∑ i = 1 n p ( i ) 2 Gini=1-\sum\limits_{i=1}^{n}{p{{(i)}^{2}}} Gini=1−i=1∑np(i)2
G a i n = Δ = G i n i ( D ) − G i n i ( D ∣ A ) Gain=\Delta =Gini(D)-Gini(D|A) Gain=Δ=Gini(D)−Gini(D∣A)
G a i n _ r a t i o ( A ) = G a i n ( A ) G i n i ( A ) Gain\_ratio(A)=\frac{Gain(A)}{Gini(A)} Gain_ratio(A)=Gini(A)Gain(A)
2.5.4 ID3、C4.5、CART分类树算法总结
ID3、C4.5和CART算法均只适合在小规模数据集上使用
ID3、C4.5和CART算法都是单变量决策树
当属性值取值比较多的时候,最好考虑C4.5算法,ID3得出的效果会比较差
决策树分类一般情况只适合小数据量的情况(数据可以放内存)
CART算法是三种算法中最常用的一种决策树构建算法 (sklearn中仅支持CART) 。
三种算法的区别仅仅只是对于当前树的评价标准不同而已,ID3使用信息增益、C4.5使用信息增益率、CART使用基尼系数。(不是主要区别)
CART算法构建的一定是二叉树,ID3和C4.5构建的不一定是二叉树。(主要区别)
- 计算复杂度
一般情况,构造二叉树的计算成本——时间复杂度为: O ( N log N ) O(N\log N) O(NlogN),其中N是节点的数量,这些结果只适用于平衡二叉树,而决策树通常不平衡,信息测量试图通过找到将数据分成两个均等部分的分裂方法来保持树的平衡(具有最大的熵),但并不能保证这一点。
如果假设树是近似平衡的,那么每个节点的成本包括搜索d个可能的特征(尽管在每个层级减少1,但是并不会影响 O ( ⋅ ) O(\centerdot ) O(⋅)符号的复杂性),然后计算每个分裂的数据集的信息增益。这要花费 O ( d n log n ) O(dn\log n) O(dnlogn),其中n是该节点上数据集的大小。对于根节点n=N,并且如果树是平衡的,则在树的每个阶段将n除以2。在树种的大约 log N \log N logN层级上对此求和,得到计算成本 O ( d N 2 log N ) O(d{{N}^{2}}\log N) O(dN2logN)
2.6 分类树和回归树的区别
分类树
采用信息增益、信息增益率、基尼系数来评价树的效果,都是基于概率值进行判断
的;而分类树的叶子节点的预测值一般为叶子节点中概率最大的类别作为当前叶子的预测值。
在回归树
中,叶子节点的预测值一般为叶子节点中所有值的均值来作为当前叶子节点的预测值
。所以在回归树中一般采用 均方误差MSE(默认) 或者 平均绝对误差MAE 作为树的评价指标(参数criterion可以选择分裂质量split quality的度量方式),即均方差。
M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 、 M A E = 1 n ∑ i = 1 n ∣ y i − y ^ i ∣ MSE=\frac{1}{n}\sum\limits_{i=1}^{n}{{{({{y}_{i}}-{{{\hat{y}}}_{i}})}^{2}}}、MAE=\frac{1}{n}\sum\limits_{i=1}^{n}{|{{y}_{i}}-{{{\hat{y}}}_{i}}|} MSE=n1i=1∑n(yi−y^i)2、MAE=n1i=1∑n∣yi−y^i∣
from sklearn.tree import DecisionTreeRegressor
创建决策树回归模型对象:
decisiontree = DecisionTreeRegressor(random_state=0)
训练模型:model = decisiontree.fit(features,target)
创建新样本:observation = []
预测样本:model.predict(observation)
改用MAE的减少量作为分裂标准来构造决策树模型:decisiontree_mae = DecisionTreeRegressor(criterion = 'mae', random_state=0)
训练模型:model_mae = decisiontree_mae.fit(features,target)
2.7 代码实现
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
algo = DecisionTreeClassifier()
def _ _ init _ _ (self,
criterion=“gini”,
splitter=“best”,
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0.,
max_features=None,
random_state=None,
max_leaf_nodes=None,
min_impurity_decrease=0.,
min_impurity_split=None,
class_weight=None,
presort=False)
criterion: 给定决策树构建过程中的纯度的衡量指标,可选值: gini、entropy, 默认gini
splitter: 给定选择特征属性的方式,best指最优选择,random指随机选择(局部最优)
max_features: 当splitter参数设置为random的有效,是给定随机选择的局部区域有多大。
max_depth: 剪枝参数,用于限制最终的决策树的深度,默认为None,表示不限制
min_samples_split=2: 剪枝参数,给定当数据集中的样本数目大于等于该值的时候,允许对当前数据集进行分裂;如果低于该值,那么不允许继续分裂。
min_samples_leaf=1 , 剪枝参数,要求叶子节点中的样本数目至少为该值。
class_weight: 给定目标属性中各个类别的权重系数。
# -- encoding:utf-8 --
"""
只要是机器学习中,代码的编写流程一般和下面这个一样!!!!
Create on 19/3/2
"""
import warnings
import sys
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
from sklearn.metrics import auc, roc_curve, classification_report
warnings.filterwarnings('ignore')
mpl.rcParams['font.sans-serif'] = [u'simHei']
# 一、数据加载
path = "F:/datas/iris.data"
names = ['A', 'B', 'C', 'D', 'cla']
df = pd.read_csv(filepath_or_buffer=path, sep=",", header=None, names=names)
# df.info()
# 二、数据的清洗
# class_name_2_label = {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2}
# df['cla'] = list(map(lambda cla: class_name_2_label[cla], df['cla'].values))
# print(df['cla'].values)
# df.info()
# 三、根据需求和原始模型从最原始的特征属性中获取具体的特征属性矩阵X和目标属性矩阵Y
X = df.drop('cla', axis=1)
X = np.asarray(X).astype(np.float64)
Y = df['cla']
# 对目标属性做一个类别的转换,将字符串的数据转换为从0开始的int值
label_encoder = LabelEncoder()
Y = label_encoder.fit_transform(Y)
# print(label_encoder.classes_)
# print(label_encoder.transform(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']))
# print(label_encoder.inverse_transform([0, 1, 2, 0, 2, 1]))
# X.info()
# 四、数据分割(将数据分割为训练数据和测试数据)
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.1, random_state=28)
print("训练数据X的格式:{}, 以及数据类型:{}".format(x_train.shape, type(x_train)))
print("测试数据X的格式:{}".format(x_test.shape))
print("训练数据Y的数据类型:{}".format(type(y_train)))
print("Y的取值范围:{}".format(np.unique(Y)))
# 五、特征工程的操作
# # a. 创建对象(标准化操作)
# scaler = StandardScaler()
# # b. 模型训练+训练数据转换
# x_train = scaler.fit_transform(x_train, y_train)
# # c. 基于训练好的对象对数据做一个转换
# x_test = scaler.transform(x_test)
# 六、模型对象的构建
"""
def __init__(self,
criterion="gini",
splitter="best",
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0.,
max_features=None,
random_state=None,
max_leaf_nodes=None,
min_impurity_decrease=0.,
min_impurity_split=None,
class_weight=None,
presort=False)
criterion: 给定决策树构建过程中的纯度的衡量指标,可选值: gini、entropy, 默认gini
splitter:给定选择特征属性的方式,best指最优选择,random指随机选择(局部最优)
max_features:当splitter参数设置为random的有效,是给定随机选择的局部区域有多大。
max_depth:剪枝参数,用于限制最终的决策树的深度,默认为None,表示不限制
min_samples_split=2:剪枝参数,给定当数据集中的样本数目大于等于该值的时候,允许对当前数据集进行分裂;如果低于该值,那么不允许继续分裂。
min_samples_leaf=1, 剪枝参数,要求叶子节点中的样本数目至少为该值。
class_weight:给定目标属性中各个类别的权重系数。
"""
algo = DecisionTreeClassifier(max_depth=2)
# 七. 模型的训练
algo.fit(x_train, y_train)
# 八、模型效果的评估
print("各个特征属性的重要性权重系数(值越大,对应的特征属性就越重要):{}".format(algo.feature_importances_))
print("训练数据上的分类报告:")
print(classification_report(y_train, algo.predict(x_train)))
print("测试数据上的分类报告:")
print(classification_report(y_test, algo.predict(x_test)))
print("训练数据上的准确率:{}".format(algo.score(x_train, y_train)))
print("测试数据上的准确率:{}".format(algo.score(x_test, y_test)))
# sys.exit()
# 查看相关属性
test1 = [x_test[6]]
print("预测函数:")
print(algo.predict(test1))
print("预测概率函数:")
print(algo.predict_proba(test1))
# sys.exit()
# ROC和AUC的计算
# 对于三个类别分开计算auc和roc的值
y_predict_proba = algo.predict_proba(x_train)
# print(y_predict_proba)
# 针对于类别1
y1_true = (y_train == 0).astype(np.int)
y1_score = y_predict_proba[:, 0]
fpr1, tpr1, _ = roc_curve(y1_true, y1_score)
auc1 = auc(fpr1, tpr1)
# 针对于类别2
y2_true = (y_train == 1).astype(np.int)
y2_score = y_predict_proba[:, 1]
fpr2, tpr2, _ = roc_curve(y2_true, y2_score)
auc2 = auc(fpr2, tpr2)
# 针对于类别3
y3_true = (y_train == 2).astype(np.int)
y3_score = y_predict_proba[:, 2]
fpr3, tpr3, _ = roc_curve(y3_true, y3_score)
auc3 = auc(fpr3, tpr3)
print((auc1, auc2, auc3))
plt.plot(fpr1, tpr1, 'r-o')
plt.plot(fpr2, tpr2, 'g-o')
plt.plot(fpr3, tpr3, 'b-o')
plt.show()
手动控制决策树的结构和规模:
from sklearn.tree import DecisionTreeClassifier
创建决策树分类器对象:decisiontree = DecisionTreeClassifier(random_state=0,max_depth=None,min_samples_split=2,min_samples_leaf=1,min_weight_fraction_leaf=0,max_leaf_nodes=None,min_impority_decrease=0)
训练模型:model = decisiontress.fit(features,target)
max_depth
: 树的最大深度。如果该参数为None,则树将一直生长,直到所有叶子都为纯节点。如果提供整数作为该参数的值,这棵树就会被有效“修剪”到这个整数值所表示的深度;- min_samples_split: 在该节点分裂之前,节点上最小的样本数。如果提供整数作为该参数的值,则这个整数代表最小的样本数;如果提供浮点数作为参数值,则最小样本数为总样本数乘以该浮点数。
- min_samples_leaf : 叶子节点需要的最小样本数。与min_samples_split使用相同的参数格式。
- max_leaf_nodes: 最大叶子节点数;
min_impurity_split
: 执行分裂所需的最小不纯度减少量
2.8 多变量决策树(OC1)
若我们把每个属性视为坐标空间中的一个坐标轴,则d个属性描述的样本就对应了d维空间中的一个数据点,对样本分类则意味着在这个坐标空间中寻找不同类样本之间的分类分界。决策树所形成的分类边界有一个明显的特点:轴平行(axis-parallel)
,即它的分类边界由若干个与坐标轴平行的分段组成。
显然,分类边界的每一段都是与坐标轴平行的。这样的分类边界使得学习结果有较好的可解释性,因为每一段划分都直接对应了某个属性取值,但在学习任务的真是分类边界比较复杂时,必须使用很多短划分才能获得较好的近似——此时的决策树会相当复杂,由于要进行大量的属性测试,预测时间开销会很大。
若能使用斜的划分边界,则决策树模型将大为简化。多变量决策树(multivariate decision tree)
就是能实现这样的“斜划分”甚至更加复杂划分的决策树(这样的多变量决策树亦称斜决策树 oblique decision tree
)。以实现斜划分的多变量决策树为例,在此类决策树中,非叶结点不再是仅对某个属性,而是对属性的线性组合进行测试;换言之,每个非叶结点是一个形如
∑
i
=
1
d
w
i
a
i
=
t
\sum\nolimits_{i=1}^{d}{{{w}_{i}}}{{a}_{i}}=t
∑i=1dwiai=t的线性分类器,其中
w
i
{{w}_{i}}
wi是属性
a
i
{{a}_{i}}
ai的权重,
w
i
{{w}_{i}}
wi和t可在该结点所含的样本集和属性集上学得。于是,与传统的单变量决策树(univariate decision tree)
不同,在多变量决策树的学习过程中,不是为每个非叶结点寻找一个最优划分属性,而是试图建立一个合适的线性分类器
3. 决策树可视化
graphviz服务安装(windows的安装):
- 下载安装包(msi安装包): http://www.graphviz.org/;
- 执行下载好的安装包(双击msi安装包);
- 将graphviz的根目录下的bin文件夹路径添加到PATH环境变量中;
pip install pydotplus
pip install graphviz
将决策树模型导出为DOT格式并可视化:
import pydotplus
from IPython.display import Image
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
创建决策树分类器对象:
decisiontree= DecisionTreeClassifier(random_state = 0)
训练模型:model = decisiontree.fit(features,target)
创建DOT数据:dot_data = tree.export_graphviz(decisiontree, out_file=None, feature_names=iris.feature_names, class_names=iris.target_names)
绘制图形:graph = pydotplus.graph_from_dot_data(dot_data)
显示图形:Image(graph.creat_png())
创建PDF:graph.write_pdf('iris.pdf')
创建PNG:graph.write_png('iris.png')
将正科模型可视化,是决策树分类器的有点之一,这也使决策树称为机器学习中解释性最好的模型之一。
# -- encoding:utf-8 --
"""
只要是机器学习中,代码的编写流程一般和下面这个一样!!!!
https://scikit-learn.org/0.18/modules/classes.html#module-sklearn.linear_model
"""
import warnings
import sys
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import auc, roc_curve, classification_report
warnings.filterwarnings('ignore')
mpl.rcParams['font.sans-serif'] = [u'simHei']
# 一、数据加载
path = "F:/datas/iris.data"
names = ['A', 'B', 'C', 'D', 'cla']
df = pd.read_csv(filepath_or_buffer=path, sep=",", header=None, names=names)
# df.info()
# 二、数据的清洗
# class_name_2_label = {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2}
# df['cla'] = list(map(lambda cla: class_name_2_label[cla], df['cla'].values))
# print(df['cla'].values)
# df.info()
# 三、根据需求和原始模型从最原始的特征属性中获取具体的特征属性矩阵X和目标属性矩阵Y
X = df.drop('cla', axis=1)
X = np.asarray(X).astype(np.float64)
Y = df['cla']
# 对目标属性做一个类别的转换,将字符串的数据转换为从0开始的int值
label_encoder = LabelEncoder()
Y = label_encoder.fit_transform(Y)
# print(label_encoder.classes_)
# print(label_encoder.transform(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']))
# print(label_encoder.inverse_transform([0, 1, 2, 0, 2, 1]))
# X.info()
# 四、数据分割(将数据分割为训练数据和测试数据)
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.1, random_state=28)
print("训练数据X的格式:{}, 以及数据类型:{}".format(x_train.shape, type(x_train)))
print("测试数据X的格式:{}".format(x_test.shape))
print("训练数据Y的数据类型:{}".format(type(y_train)))
print("Y的取值范围:{}".format(np.unique(Y)))
# 五、特征工程的操作
# # a. 创建对象(标准化操作)
# scaler = StandardScaler()
# # b. 模型训练+训练数据转换
# x_train = scaler.fit_transform(x_train, y_train)
# # c. 基于训练好的对象对数据做一个转换
# x_test = scaler.transform(x_test)
# 六、模型对象的构建
"""
def __init__(self,
criterion="gini",
splitter="best",
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0.,
max_features=None,
random_state=None,
max_leaf_nodes=None,
min_impurity_decrease=0.,
min_impurity_split=None,
class_weight=None,
presort=False)
criterion: 给定决策树构建过程中的纯度的衡量指标,可选值: gini、entropy, 默认gini
splitter:给定选择特征属性的方式,best指最优选择,random指随机选择(局部最优)
max_features:当splitter参数设置为random的有效,是给定随机选择的局部区域有多大。
max_depth:剪枝参数,用于限制最终的决策树的深度,默认为None,表示不限制
min_samples_split=2:剪枝参数,给定当数据集中的样本数目大于等于该值的时候,允许对当前数据集进行分裂;如果低于该值,那么不允许继续分裂。
min_samples_leaf=1, 剪枝参数,要求叶子节点中的样本数目至少为该值。
class_weight:给定目标属性中各个类别的权重系数。
"""
algo = DecisionTreeClassifier(criterion='entropy', max_depth=None, min_samples_leaf=1, min_samples_split=2)
# 七. 模型的训练
algo.fit(x_train, y_train)
# 八、模型效果的评估
print("各个特征属性的重要性权重系数(值越大,对应的特征属性就越重要):{}".format(algo.feature_importances_))
print("训练数据上的分类报告:")
print(classification_report(y_train, algo.predict(x_train)))
print("测试数据上的分类报告:")
print(classification_report(y_test, algo.predict(x_test)))
print("训练数据上的准确率:{}".format(algo.score(x_train, y_train)))
print("测试数据上的准确率:{}".format(algo.score(x_test, y_test)))
# 查看相关属性
test1 = [x_test[10]]
print("预测函数:")
print(algo.predict(test1))
print("预测概率函数:")
print(algo.predict_proba(test1))
print(algo.apply(test1))
# 决策树可视化
# 方式一:输出dot文件,然后使用dot的命令进行转换
# 命令:dot -Tpdf iris.dot -o iris.pdf 或者dot -Tpng iris.dot -o iris.png
from sklearn import tree
with open('iris.dot', 'w') as writer:
tree.export_graphviz(decision_tree=algo, out_file=writer)
# sys.exit()
# 方式二:直接使用pydotpuls库将数据输出为图像
import pydotplus
from sklearn import tree
# import os
# os.environ["PATH"] += os.pathsep + 'G:/program_files/graphviz/bin'
dot_data = tree.export_graphviz(decision_tree=algo, out_file=None,
feature_names=['A', 'B', 'C', 'D'],
class_names=['1', '2', '3'],
filled=True, rounded=True,
special_characters=True
)
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_png("iris0.png")
graph.write_pdf("iris0.pdf")
4. 补充
- 在信息增益、增益率、基尼系数之外,人们还设计了许多其他的准则用于决策树划分选择,这些准则虽然对决策树的尺寸有较大影响,但对泛化性能的影响很有限;
- 剪枝方法和程度对决策树泛化性能的影响相当显著,在数据带有噪声时通过剪枝甚至可将决策树的泛化性能提高25%;
- 多变量决策树算法主要为
OC1
,OC1先贪心地寻找每个属性的最优权值,在局部优化的基础上再对分类边界进行随机扰动以试图找到更好的边界;其他算法则直接引入线性分类器学习的最小二乘法。还有一些算法试图在决策树的叶结点上嵌入神经网络,以结合这两种学习机制的优势,例如感知机树(Perceptron tree)
在决策树的每个叶结点上训练一个感知机,或者直接在叶结点上嵌入多层神经网络。 - 有一些决策树学习算法可进行
增量学习(incremental learning)
,即在接受到新样本后可对已学得的模型进行调整,而不用完全重新学习,主要机制是通过调整分支路径上的划分属性次序来对树进行部分重构。增量学习可有效地降低每次接收到新样本后的训练时间开销,但多步增量学习后的模型会与基于全部数据训练而得的模型有较大差别。