决策树
5.1 决策树模型与学习
分类决策树模型是一种描述对实例进行分类得树形结构。内部节点表示一个特征或属性,叶子节点表示一个类。
路径上内部节点的特征对应着规则的结论,而叶节点的类对应着规则的结论。决策树的路径具有一个重要的性质:互斥并且完备,每一个实例都被一条路径或一条规则所覆盖,且只被一条路径或一条规则所覆盖。
- 决策树与条件概率
决策树还可以表示给定特征条件下类的条件概率分布。下图中(a)的大正方形表示特征空间,每个小矩形表示一个单元。假设 Y Y Y的取值只有-1和+1,小矩形中的式子表示单元的类。(b)图表示特征空间划分确定时,特征单元给定下的条件概率分布。当某个单元 c c c的条件概率满足 P ( Y = + 1 ∣ X = c ) > 0.5 P(Y=+1|X=c)>0.5 P(Y=+1∣X=c)>0.5时,落在这个单元的实例都被视为+1。
- 决策树学习
决策树学习本质上是从训练数据集中归纳出一组分类规则。决策树可能不止一个也可能一个都没有,所以目标是找到与训练数据矛盾较小的决策树,同时具有很好的泛化能力。
学习过程:
(1)首先构造根节点,将所有训练数据都放在根节点。
(2)选取一个最优特征,按照这一特征将训练数据集分割成子集,使得各个子集有一个在当前条件下最好的分类。
(3)若这些子集能够被基本正确分类,那么构建叶子节点,并将这些子集分到所对应的叶子节点中去;如果还有子集不能被基本正确分类,那么就对这些子集选择新的最优特征,继续进行分割,构建相应的结点。
(4)递归(2)(3),知道所有训练数据子集被基本正确分类,或者没有合适的特征。
通过这样生成的决策树,虽然对训练数据有较好的拟合效果,但是对于测试数据可能会发生过拟合现象,所以需要适当的对树进行剪枝,去掉过于细分的叶子节点。
决策树的生成对应于模型的局部选择(当前分割后损失函数最小化为目的),决策树的剪枝对应于模型的全局选择(测试集上的损失函数尽可能小) - 决策树学习算法:特征选择、决策树的生成、决策树的剪枝
5.2 特征选择
特征选取的目的:选取对训练数据具有分类能力的特征,提高决策树学习的效率。常见的选择指标为信息增益与信息增益比。
5.2.1 信息增益
- 熵:表示随机变量不确定性的度量,熵越大,随机变量的不确定性越大,数据混乱程度越大,不整齐;且熵仅依赖于X的分布,与X的取值范围无关。
P ( X = x i ) = p i i = 1 , 2 , . . . . n H ( X ) = − ∑ i = 1 n p i log p i P(X=x_i)=p_i\ \ \ i=1,2,....n\\ H(X)=-\sum\limits_{i=1}^{n}p_i\log p_i P(X=xi)=pi i=1,2,....nH(X)=−i=1∑npilogpi
H ( X ) H(X) H(X)即为随机变量X的熵。
P ( X = x i , Y = y i ) = p i j i = 1 , 2 , . . . , n ; j = 1 , 2 , . . . , m H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) P(X=x_i,Y=y_i)=p_{ij} \ \ \ i=1,2,...,n;\ j=1,2,...,m\\ H(Y|X)=\sum\limits_{i=1}^np_iH(Y|X=x_i) P(X=xi,Y=yi)=pij i=1,2,...,n; j=1,2,...,mH(Y∣X)=i=1∑npiH(Y∣X=xi)
H ( Y ∣ X ) H(Y|X) H(Y∣X)表示随机变量X给定条件下随机变量Y的条件熵,换句话说,为X给定条件下Y的条件概率分布的熵对X的数学期望。
当熵和条件熵中的概率由数据统计(极大似然估计)得到,则对应称为经验熵和经验条件熵。 - 信息增益:集合D的经验熵H(D)与特征A在给定条件下D的经验条件熵H(D|A)之差,称为信息增益g(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的分类的不确定性减少的程度。信息增益越大,表示该特征对不确定性减少的程度越大,使得分类更偏向稳定。
5.2.2 信息增益比
信息增益作为划分时,为了尽可能降低不确定性,会出现偏向于选择取值较多的特征的问题,为了校正该问题,引入了信息增益比
- 信息增益比:信息增益
g
(
D
,
A
)
g(D,A)
g(D,A)与训练数据集D关于特征A的值的熵
H
A
(
D
)
H_A(D)
HA(D)d的比值为信息增益比
g
R
(
D
,
A
)
g_R(D,A)
gR(D,A)。
g R ( D , A ) = g ( D , A ) H A ( D ) H A ( D ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ l o g 2 ∣ D i ∣ ∣ D ∣ n 为 A 的 特 征 取 值 个 数 g_R(D,A)=\frac{g(D,A)}{H_A(D)}\\ H_A(D)=-\sum\limits_{i=1}^n\frac{|D_i|}{|D|}log_2\frac{|D_i|}{|D|} \ \ \ n为A的特征取值个数 gR(D,A)=HA(D)g(D,A)HA(D)=−i=1∑n∣D∣∣Di∣log2∣D∣∣Di∣ n为A的特征取值个数
python代码实现例题:信息增益与信息增益比
import numpy as np
import math
#直接更改x,y即可
x = [[1, 0, 0, 1],
[0, 1, 1, 1],
[0, 0, 1, 0]]
y = [0, 0, 1]
feature_num = np.shape(x)[1]
y_len = len(y)
feature = np.array(x)
for i in range(feature_num):
_feature = feature[:, i]
temp_condition_ent = 0
temp_ent_i = 0
HA_D = 0
print(_feature)
for j in set(_feature):
D_i = _feature.tolist().count(j)
temp_ent = 0
for k in set(y):
temp_ent += -(y.count(k) / y_len) * math.log(y.count(k) / y_len)#H(D)
D_ik = 0
for index in range(len(y)):
if y[index] == k and _feature[index] == j:
D_ik = D_ik + 1
if D_ik != 0:
temp_ent_i += -(D_ik / D_i) * math.log(D_ik / D_i)
else:
temp_ent_i = 0
temp_condition_ent += (D_i / y_len) * temp_ent_i#H(D|A)
conditon_ent = temp_ent - temp_condition_ent#g(D|A)
HA_D += -(D_i / y_len) * math.log(D_i / y_len)#H_A(D)
conditon_ent_ratio = conditon_ent / HA_D #g_R(D,A)
print(conditon_ent)
print(conditon_ent_ratio)
5.3 决策树的生成
5.3.1 ID3算法(python实现)
ID3算法的核心是决策树的各个节点上应用信息增益准则选择特征,递归的构建决策树,直到所有特征信息的信息增益均很小或没有特征选择位置。
算法5.2 ID3算法
输入:训练数据集D, 特征集A的阈值
ϵ
\epsilon
ϵ
输出:决策树T
- 若D中所有的实例属于同一类 C k C_k Ck,则T为单节点树,并将类 C k C_k Ck作为该节点的类标记,返回T;
- 若 A = ∅ A=\varnothing A=∅,则T为单节点树,并将D中实例数最大的类 C k C_k Ck作为该节点的类标记,返回T;
- 否则,计算A中各特征对D的信息增益,选择信息增益最大的特征 A g A_g Ag;
- 如果 A g A_g Ag的信息增益小于阈值 ϵ \epsilon ϵ,则置T为单节点树,并将D中实例数最大的类 C k C_k Ck作为该节点的类标记,返回T;
- 否则, A g A_g Ag的每一可能值 a i a_i ai,依照, A g = a i A_g=a_i Ag=ai将D分割为若干非空子集 D i D_i Di,将 D i D_i Di中实例数最大的类作为标记,构建子节点,由结点及其子节点构成树T,返回T;
- 对第i个子节点,以 D i D_i Di为训练集,以 A − { A g } A-\{A_g\} A−{Ag}为特征集,递归地调用步(1)到(5),得到子树 T i T_i Ti,返回 T i T_i Ti。
python实现ID3算法(参考机器学习实战)
from math import log
# 计算信息熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet) # 样本数
labelCounts = {}
for featVec in dataSet: # 遍历每个样本
currentLabel = featVec[-1] # 当前样本的类别
if currentLabel not in labelCounts.keys(): # 生成类别字典
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
shannonEnt = 0.0
for key in labelCounts: # 计算信息熵
prob = float(labelCounts[key]) / numEntries
shannonEnt = shannonEnt - prob * log(prob, 2)
return shannonEnt
# 划分数据集,axis:按第几个属性划分,value:要返回的子集对应的属性值
def splitDataSet(dataSet, axis, value):
retDataSet = []
featVec = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
return retDataSet
# 选择信息增益最大的特征划分数据
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 # 属性的个数
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures): # 对每个属性技术信息增益
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) # 该属性的取值集合
newEntropy = 0.0
for value in uniqueVals: # 对每一种取值计算信息增益
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet) / float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy
if (infoGain > bestInfoGain): # 选择信息增益最大的属性
bestInfoGain = infoGain
bestFeature = i
return bestFeature
# 通过排序返回出现次数最多的类别
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote not in classCount.keys(): classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(),
key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
# 递归构建决策树
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet] # 类别向量
if classList.count(classList[0]) == len(classList): # 如果只有一个类别,返回
return classList[0]
if len(dataSet[0]) == 1: # 如果所有特征都被遍历完了,返回出现次数最多的类别
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet) # 最优划分属性的索引
bestFeatLabel = labels[bestFeat] # 最优划分属性的标签
myTree = {bestFeatLabel: {}}
del (labels[bestFeat]) # 已经选择的特征不再参与分类
featValues = [example[bestFeat] for example in dataSet]
uniqueValue = set(featValues) # 该属性所有可能取值,也就是节点的分支
for value in uniqueValue: # 对每个分支,递归构建树
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(
splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
x = [[1, 0, 0, 1],
[0, 1, 1, 1],
[0, 0, 1, 0]]
y = [1, 2, 1]
Trees = createTree(x,y)
print(Trees)
5.3.2 C4.5生成算法(python实现)
与ID3算法不同的是,C4.5算法采用信息增益比选择特征。
算法5.3 C4.5算法
输入:训练数据集D, 特征集A的阈值
ϵ
\epsilon
ϵ
输出:决策树T
- 若D中所有的实例属于同一类 C k C_k Ck,则T为单节点树,并将类 C k C_k Ck作为该节点的类标记,返回T;
- 若 A = ∅ A=\varnothing A=∅,则T为单节点树,并将D中实例数最大的类 C k C_k Ck作为该节点的类标记,返回T;
- 否则,计算A中各特征对D的信息增益比,选择信息增益比最大的特征 A g A_g Ag;
- 如果 A g A_g Ag的信息增益小于阈值 ϵ \epsilon ϵ,则置T为单节点树,并将D中实例数最大的类 C k C_k Ck作为该节点的类标记,返回T;
- 否则, A g A_g Ag的每一可能值 a i a_i ai,依照, A g = a i A_g=a_i Ag=ai将D分割为若干非空子集 D i D_i Di,将 D i D_i Di中实例数最大的类作为标记,构建子节点,由结点及其子节点构成树T,返回T;
- 对第i个子节点,以 D i D_i Di为训练集,以 A − { A g } A-\{A_g\} A−{Ag}为特征集,递归地调用步(1)到(5),得到子树 T i T_i Ti,返回 T i T_i Ti。
python实现C4.5算法
只需将上述算法中特征选择函数更改为以信息增益比最大为原则选择的函数即可。
# 选择信息增益比最大的特征划分数据
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 # 属性的个数
baseEntropy = calcShannonEnt(dataSet)
bestInfoGainRatio = 0.0
bestFeature = -1
for i in range(numFeatures): # 对每个属性计算信息增益比
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) # 该属性的取值集合
newEntropy = 0.0
HA_D = 0
for value in uniqueVals: # 对每一种取值计算信息增益比
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet) / float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy#信息增益
infoGainRatio = infoGain/baseEntropy #信息增益比
if (infoGainRatio > bestInfoGainRatio): # 选择信息增益比最大的属性
bestInfoGainRatio = infoGainRatio
bestFeature = i
return bestFeature
5.4 决策树的剪枝
决策树生成算法递归地产生决策树,往往会发生过拟合现象,学习时过多地考虑如何提高对训练数据地正确分类,从而构建出过于复杂地决策树。因此,需要对生成的树进行剪枝。
决策树剪枝原则:极小化决策树整体的损失函数:
C
α
(
T
)
=
∑
t
=
1
∣
T
∣
N
t
H
t
(
T
)
+
α
∣
T
∣
C_\alpha(T)=\sum\limits_{t=1}^{|T|}N_tH_t(T)+\alpha|T|
Cα(T)=t=1∑∣T∣NtHt(T)+α∣T∣
这种剪枝方法在ISLR书中也称为:Cost complexity pruning (or weakest link pruning)
采用的损失函数为:
C α ( T ) = ∑ m = 1 ∣ T ∣ ∑ x i ∈ R m ( y i − y R m ^ ) 2 + α ∣ T ∣ C_\alpha(T)=\sum\limits_{m=1}^{|T|}\sum\limits_{x_i\in R_m}(y_i-\hat{y_{Rm}})^2+\alpha|T| Cα(T)=m=1∑∣T∣xi∈Rm∑(yi−yRm^)2+α∣T∣
其中 R m R_m Rm表示叶子节点中的样本集。
其中
∣
T
∣
|T|
∣T∣为树T的叶子节点个数,t为叶子节点,该叶节点含有
N
t
N_t
Nt个样本点,其中
k
k
k类的样本点有
N
t
k
N_{tk}
Ntk个,
H
t
(
T
)
H_t(T)
Ht(T)为叶节点t的经验上,
α
>
=
0
\alpha>=0
α>=0为参数。其中:
H
t
(
T
)
=
−
∑
k
K
N
t
k
N
t
log
N
t
k
N
t
H_t(T)=-\sum\limits_k^K\frac{N_{tk}}{N_t}\log\frac{N_{tk}}{N_t}
Ht(T)=−k∑KNtNtklogNtNtk
把该式子带入损失函数中,第一项记作:
C
(
T
)
=
∑
t
=
1
∣
T
∣
N
t
H
t
(
T
)
=
−
∑
t
=
1
∣
T
∣
∑
k
K
N
t
k
N
t
log
N
t
k
N
t
C_(T)=\sum\limits_{t=1}^{|T|}N_tH_t(T)=-\sum\limits_{t=1}^{|T|}\sum\limits_k^K\frac{N_{tk}}{N_t}\log\frac{N_{tk}}{N_t}
C(T)=t=1∑∣T∣NtHt(T)=−t=1∑∣T∣k∑KNtNtklogNtNtk
这时,决策树的损失函数又可表示为
C
α
(
T
)
=
C
(
T
)
+
α
∣
T
∣
C_\alpha(T) = C(T) + \alpha|T|
Cα(T)=C(T)+α∣T∣
其中
C
(
T
)
C(T)
C(T)表示模型对训练数据的预测误差,即模型与训练数据的拟合程度,|T|表示叶子节点的个数(即模型的复杂度),
α
\alpha
α作为惩罚系数,负责调节拟合程度与复杂度之间的权衡关系。
α
=
0
\alpha=0
α=0即不考虑复杂度,
α
\alpha
α越大,模型的复杂度越低。
算法5.4 树的剪枝算法
输入:整个树,参数
α
\alpha
α.
输出:修剪后的子树
T
α
T_\alpha
Tα.
(1)计算每个节点的经验熵。
(2)递归地从树的节点向上回缩。
设一组叶节点回缩到父节点之前与之后的整体树分类为
T
B
,
T
A
T_B,T_A
TB,TA,其对应的损失函数值为
C
α
(
T
B
)
,
C
α
(
T
A
)
C_\alpha(T_B),C_\alpha(T_A)
Cα(TB),Cα(TA),如果
C
α
(
T
B
)
≤
C
α
(
T
A
)
C_\alpha(T_B) \leq C_\alpha(T_A)
Cα(TB)≤Cα(TA)
则进行剪枝,将父节点变为新的叶子节点。
注意:该步骤由于只考虑两个树的损失函数的差,计算可以在局部进行(内部节点是否剪枝只与以该节点为根节点的子树有关)。是一种动态规划的算法。
(3)返回(2),直到不能继续为止,得到损失函数最小的子树
T
α
T_\alpha
Tα。
5.5 CART算法
CART(分类与回归树):假设决策树是二叉树,内部节点特征的取值为“是”“否”,左分支取值为“是”,右分支取值为“否”。
5.5.1 CART生成
回归树:平方误差最小化准则
分类树:基尼系数最小化准则,进行特征选择
- 回归树的生成
假设已经将输入空间划分为M个单元, R 1 , R 2 . . . R M R_1,R_2...R_M R1,R2...RM,并且在每个单元上有固定的输出值 c m c_m cm,则模型表示为
f ( x ) = ∑ m = 1 M c m I ( x ∈ R m ) f(x)=\sum\limits_{m=1}^Mc_mI(x \in R_m) f(x)=m=1∑McmI(x∈Rm)
当输入空间的划分确定时,可以用平方误差 ∑ x ∈ R m ( y i − f ( x i ) ) 2 \sum\limits_{x\in R_m}(y_i-f(x_i))^2 x∈Rm∑(yi−f(xi))2来表示回归树对于训练数据的预测误差,用平方误差准则求解每个单元上的最优输出值。已知,单元 R m R_m Rm上的 c m c_m cm的最优值 c m ^ \hat{c_m} cm^是 R m R_m Rm上所有输入实例 x i x_i xi对应的输出 y i y_i yi的均值:
c m ^ = a v e ( y i ∣ x i ∈ R m ) \hat{c_m} =ave({y_i}|x_i \in R_m) cm^=ave(yi∣xi∈Rm)
输入空间划分方法:
假设第j个变量 x ( j ) x^{(j)} x(j)和它取的值s,作为切分变量和切分点,则
R 1 ( j , s ) = { x ∣ x ( j ) ≤ s } a n d R 2 ( j , s ) = { x ∣ x ( j ) > s } R_1(j,s)=\{x|x^{(j)} \leq s \} \ \ and\ \ R_2(j,s)=\{x|x^{(j)} > s \} R1(j,s)={x∣x(j)≤s} and R2(j,s)={x∣x(j)>s}
其中最优切分变量与最优切分点的求解:
min j , s [ min c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + min c 1 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] \min\limits_{j,s}\left [ \min\limits_{c_1}\sum\limits_{x_i \in R_1(j,s)}(y_i-c_1)^2 + \min\limits_{c_1}\sum\limits_{x_i \in R_2(j,s)}(y_i-c_2)^2 \right ] j,smin⎣⎡c1minxi∈R1(j,s)∑(yi−c1)2+c1minxi∈R2(j,s)∑(yi−c2)2⎦⎤
这样就可以得到切分后区域内的最优输出值:
c 1 ^ = a v e ( y i ∣ x i ∈ R 1 ( j , s ) ) a n d c 2 ^ = a v e ( y i ∣ x i ∈ R 2 ( j , s ) ) \hat{c_1} =ave({y_i}|x_i \in R_1(j,s)) \ \ and\ \ \hat{c_2} =ave({y_i}|x_i \in R_2(j,s)) c1^=ave(yi∣xi∈R1(j,s)) and c2^=ave(yi∣xi∈R2(j,s))
对每个区域重复上述的划分过程,直到满足停止条件。
5.5 最小二乘回归树生成算法
输入:训练数据集D;
输出:回归树f(x)。
(1)求解最优切分变量j与最优切分点s.(类似决策树中的特征选择和划分数据集)
min j , s [ min c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + min c 1 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] \min\limits_{j,s}\left [ \min\limits_{c_1}\sum\limits_{x_i \in R_1(j,s)}(y_i-c_1)^2 + \min\limits_{c_1}\sum\limits_{x_i \in R_2(j,s)}(y_i-c_2)^2 \right ] j,smin⎣⎡c1minxi∈R1(j,s)∑(yi−c1)2+c1minxi∈R2(j,s)∑(yi−c2)2⎦⎤
(2)划分区域,决定对应的输出值
R 1 ( j , s ) = { x ∣ x ( j ) ≤ s } a n d R 2 ( j , s ) = { x ∣ x ( j ) > s } c m ^ = a v e ( y i ∣ x i ∈ R m ) m = 1 , 2 R_1(j,s)=\{x|x^{(j)} \leq s \} \ \ and\ \ R_2(j,s)=\{x|x^{(j)} > s \}\\ \hat{c_m} =ave({y_i}|x_i \in R_m) \ \ m=1,2 R1(j,s)={x∣x(j)≤s} and R2(j,s)={x∣x(j)>s}cm^=ave(yi∣xi∈Rm) m=1,2
(3)继续对两子区域调用步骤(1)(2),直至满足停止条件
(4)将输入空间划分为M个区域,生成决策树:
f ( x ) = ∑ m = 1 M c m I ( x ∈ R m ) f(x)=\sum\limits_{m=1}^Mc_mI(x \in R_m) f(x)=m=1∑McmI(x∈Rm) - 分类树的生成
与决策树不同,分类树中采用基尼指数选择最优特征,确定最优二值切分点:
G i n i ( D ) = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 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)=1-\sum\limits_{k=1}^K(\frac{|C_k|}{|D|})^2 \\ Gini(D,A)=\frac{|D_1|}{|D|}Gini(D_1)+\frac{|D_2|}{|D|}Gini(D_2) Gini(D)=1−k=1∑K(∣D∣∣Ck∣)2Gini(D,A)=∣D∣∣D1∣Gini(D1)+∣D∣∣D2∣Gini(D2)
C k C_k Ck是D中属于第k类的样本子集,K是类的个数。D1和D2为经特征值A的某一可能值a划分的训练集。
基尼指数数值越大,样本集合的不确定性越大,样本越混乱,与熵的特性相似。
算法5.6 CART生成算法
输入:训练数据集D,停止计算条件
输出:CART决策树。
从根节点开始,递归地对每个节点进行以下操作:
(1)设节点的训练数据集为D,计算现有特征对个数据集的基尼指数。
(2)在所有可能特征A以及他们所有的可能切分点中,选择基尼系数最小的特征及其切分点作为最优特征和最优切分点,切分该节点称为两个子节点,并将训练集依据特征分配到两个子节点中去。
(3)对两个子节点递归地调用(1)(2),直至满足停止条件。
(4)生成CART决策树。
停止条件:结点中的样本个数小于预定阈值;样本集的基尼系数小于一定阈值;没有更多特征。
5.5.2 CART剪枝
- 首先从生成算法产生的决策树
T
0
T_0
T0底端不断剪枝,直到
T
0
T_0
T0的根节点,形成一个子树序列
{
T
0
,
T
1
.
.
.
,
T
n
}
\{ T_0,T_1...,T_n\}
{T0,T1...,Tn};
对 T 0 T_0 T0的任意内部节点t,以t为单节点树的损失函数为:
C α ( t ) = C ( t ) + α C_\alpha(t)=C(t)+\alpha Cα(t)=C(t)+α
以t为根节点的子树 T t T_t Tt的损失函数“
C α ( T t ) = C ( T t ) + α ∣ T t ∣ C_\alpha(T_t)=C(T_t)+\alpha|T_t| Cα(Tt)=C(Tt)+α∣Tt∣
(1)当 α = 0 \alpha=0 α=0及 α \alpha α充分小时,因为CART生成树的原则,父节点的损失一定比以该父节点为根节点的子树的损失函数大,所以有不等式
C α ( T t ) < C α ( t ) C_\alpha(T_t)<C_\alpha(t) Cα(Tt)<Cα(t)
(2)当 α \alpha α增大时,在某 α \alpha α时
C α ( T t ) = C α ( t ) α = C ( t ) − C ( T t ) ∣ T t ∣ − 1 C_\alpha(T_t)=C_\alpha(t)\\ \alpha=\frac{C(t)-C(T_t)}{|T_t|-1} Cα(Tt)=Cα(t)α=∣Tt∣−1C(t)−C(Tt)
(3)当 α \alpha α继续增大时
C α ( T t ) > C α ( t ) C_\alpha(T_t)>C_\alpha(t) Cα(Tt)>Cα(t)
通过以上规律可以发现,当(2)情况发生时, T t T_t Tt与t有相同的损失函数,而t的节点少,因此t比 T t T_t Tt更可取,对 T t T_t Tt进行剪枝。
因此,对 T 0 T_0 T0中的每一内部结点t,计算对应 α = g ( t ) \alpha=g(t) α=g(t):
g ( t ) = C ( t ) − C ( T t ) ∣ T t ∣ − 1 g(t)=\frac{C(t)-C(T_t)}{|T_t|-1} g(t)=∣Tt∣−1C(t)−C(Tt)
表示剪枝后整体损失函数减少的程度。在 T 0 T_0 T0中剪去g(t)最小的 T t T_t Tt
为什么选取最小得而不是最大g(t)?
假设所有结点中 g ( t 1 ) g(t_1) g(t1)最小, g ( t 2 ) g(t_2) g(t2)最大。若对结点2进行剪枝,此时结点1中 C α 1 ( T 1 ) > C α 1 ( t 1 ) C_{\alpha_1}(T_1)>C_{\alpha_1}(t_1) Cα1(T1)>Cα1(t1),不剪枝就会造成树的损失变大,以此类推,其他结点也是如此,最终导致整体累计的损失更大。若对结点1进行剪枝,其余节点不剪枝的损失要小于剪枝后的损失,所以不应该修剪,这是整体的损失最小,因此应该剪掉g(t)最小的结点
将得到的子树作为 T 1 T_1 T1,同时将最小的g(t)设为 α 1 \alpha_1 α1,则 T 1 T_1 T1为区间 [ α 1 , α 2 ) \left [ \alpha_1,\alpha_2 \right) [α1,α2)的最优子树。
Breiman等人证明,将 α \alpha α从小增大, 0 = α 0 < α 1 < . . . < α n < + ∞ 0=\alpha_0<\alpha_1<...<\alpha_n<+\infty 0=α0<α1<...<αn<+∞,产生一系列的区间 [ α i , α i + 1 ) , i = 0 , 1 , . . . n \left [ \alpha_i,\alpha_{i+1} \right),\ \ i=0,1,...n [αi,αi+1), i=0,1,...n;剪枝得到的子树列对应着区间 α ∈ [ α i , α i + 1 ) , i = 0 , 1 , . . . n \alpha \in \left [ \alpha_i,\alpha_{i+1} \right),\ \ i=0,1,...n α∈[αi,αi+1), i=0,1,...n的最优子树序列 { T 0 , T 1 . . . , T n } \{ T_0,T_1...,T_n\} {T0,T1...,Tn},序列中的子树时嵌套的
- 通过交叉验证法(cross validation)在独立的验证数据集上对子树序列进行测试平方误差或基尼指数,平方误差或基尼指数最小的子树为最优子树。当最优子树确定时,对应的 α \alpha α值也确定了。
算法5.7 CART剪枝算法
输入:CART算法生成的决策树
T
0
T_0
T0
输出:最优决策树
T
α
T_\alpha
Tα
(1)设
k
=
0
,
T
=
T
0
k=0,T=T_0
k=0,T=T0;
(2)设
α
=
+
∞
\alpha=+\infty
α=+∞;
(3)自上而下的对内部节点t计算
C
(
T
t
)
,
∣
T
t
∣
,
C(T_t),|T_t|,
C(Tt),∣Tt∣,以及
g
(
t
)
=
C
(
t
)
−
C
(
T
t
)
∣
T
t
∣
−
1
α
=
min
(
α
,
g
(
t
)
)
g(t)=\frac{C(t)-C(T_t)}{|T_t|-1}\\ \alpha=\min(\alpha,g(t))
g(t)=∣Tt∣−1C(t)−C(Tt)α=min(α,g(t))
(4)对
g
(
t
)
=
α
g(t)=\alpha
g(t)=α的内部节点t进行剪枝,并对叶子节点t以多数表决决定其类,得到树T;
(5)设
k
=
k
+
1
,
α
k
=
α
,
T
k
=
T
k=k+1,\alpha_k=\alpha,T_k=T
k=k+1,αk=α,Tk=T;
(6)如果
T
k
T_k
Tk不是由根节点及两个叶子结点构成的树,则返回步骤二;否则令
T
k
=
T
n
T_k=T_n
Tk=Tn;
(7)采用交叉验证法在子树序列中选取最优子树
T
α
T_\alpha
Tα.
习题5.1(python实现)
该题代码与5.3.2中写的代码相同,完整代码如下:
from math import log
import operator
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
shannonEnt -= prob * log(prob, 2)
return shannonEnt
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
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1
baseEntropy = calcShannonEnt(dataSet)
bestInfoGainRatio= 0.0
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet) / float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGainRatio = (baseEntropy - newEntropy) / baseEntropy
if (infoGainRatio > bestInfoGainRatio):
bestInfoGainRatio = infoGainRatio
bestFeature = i
return bestFeature
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0]
if len(dataSet[0]) == 1:
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel: {}}
del (labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
dataSet = [[1, 0, 0, 1, 0],
[1, 0, 0, 2, 0],
[1, 1, 0, 2, 1],
[1, 1, 1, 1, 1],
[1, 0, 0, 1, 0],
[2, 0, 0, 1, 0],
[2, 0, 0, 2, 0],
[2, 1, 1, 2, 1],
[2, 0, 1, 3, 1],
[2, 0, 1, 3, 1],
[3, 0, 1, 3, 1],
[3, 0, 1, 2, 1],
[3, 1, 0, 2, 1],
[3, 1, 0, 3, 1],
[3, 0, 0, 1, 0] ]
labels = ['age', 'job', 'house', 'creadit']
mytree = createTree(dataSet,labels)
print (mytree)
习题5.2(python实现)
import numpy as np
# 计算数据集的平方误差
def calcMSE(dataSet):
means = np.mean(dataSet)
sums = sum([(i - means) * (i - means) for i in dataSet]) * 1.0
return sums
# 选择最优的划分点
def chooseBestFeatureToSplit(dataSet):
nums = len(dataSet) - 1
if nums == 0:
return 0
best = 0
bestMES = 100000
for i in range(nums):
temp = calcMSE(dataSet[:i + 1]) + calcMSE(dataSet[i + 1:])
if temp <= bestMES:
bestMES = temp
best = i
return best
# 建树过程
def createTree(dataSet, datalabel, left, right):
if right - left == 1:
# return dataSet[left]
return datalabel[left]
if left >= right:
return -1
# 最优划分函数加上left为原数据集上的最优划分下标
bestchoose = left + chooseBestFeatureToSplit(dataSet[left:right])
# print bestchoose+1
mytree = {datalabel[bestchoose]: {}}
mytree[datalabel[bestchoose]]['left'] = createTree(dataSet, datalabel, left, bestchoose)
mytree[datalabel[bestchoose]]['right'] = createTree(dataSet, datalabel, bestchoose + 1, right)
return mytree
dataSet = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
datalabel = [4.5, 4.75, 4.91, 5.34, 5.8, 7.05, 7.9, 8.23, 8.7, 9]
mytree = createTree(dataSet,datalabel,0,len(dataSet))
print (mytree)
习题5.3
证明:CART剪枝算法中,当
α
\alpha
α确定时,存在唯一的最小子树
T
α
T_\alpha
Tα使损失函数
C
α
(
T
)
C_\alpha(T)
Cα(T)最小。
这里采用反证法,假设由两个子树A,B都可以使的损失函数最小,如下图所示,假设最简单的情况:
由于CART树生成的特性,父节点的损失函数一定会比以父节点为根节点的子树的损失函数大,所以当
α
\alpha
α确定时,子树A,B一定是类似的关系。但是损失函数相同时,B更为简单,所以应该对A进行剪枝,即A不是最小子树。
参考柴前吾狼
习题5.4
证明:CART剪枝算法中求出的子树序列 { T 0 , T 1 . . . , T n } \{ T_0,T_1...,T_n\} {T0,T1...,Tn}分别是区间 [ α i , α i + 1 ) , i = 0 , 1 , . . . n \left [ \alpha_i,\alpha_{i+1} \right),\ \ i=0,1,...n [αi,αi+1), i=0,1,...n的最优子树 T α T_\alpha Tα,其中 0 = α 0 < α 1 < . . . < α n < + ∞ 0=\alpha_0<\alpha_1<...<\alpha_n<+\infty 0=α0<α1<...<αn<+∞。
暂未得以证明,有博主一定给出部分证明,可供参考。
正在阅读 Breiman L:《Classification and regression trees》寻求方法。
参考
[1] https://www.jianshu.com/p/da5d7a4d38dd
[2] 李航《统计学习方法》
[3] 《机器学习实战》
[4] https://blog.csdn.net/familyshizhouna/article/details/72551841
笔者刚刚入门学习机器学习,因为水平有限,李航老师的书对入门不是特别友好,还在生啃阶段,如果有错误还请之处。