本文以西瓜数据集为例演示决策树使用信息增益剪枝的过程
为了评价决策树模型的表现,我们将数据划分训练集(橘色)和验证集(蓝色)
西瓜数据集下载:传送门
关于西瓜数据集训练集和验证集的划分、决策树模型的训练与评估见文末附录
1、为什么要剪枝
如果我们让决策树一直生长,最后得到的决策树可能会很庞大。为了尽可能正确的分类训练样本,模型会对原始数据充分学习,这就会造成分支过多,进而产生过拟合的问题
过拟合是指模型在训练集上表现的很好,但是在测试集上表现的很差,即模型泛化性差。可以通过剪枝主动去掉一些分支来降低过拟合的风险,并使用留出法进行剪枝前后决策树的优劣评估
也就是说,缓解决策树过拟合可以通过剪枝操作完成。而剪枝方式又可以分为:预剪枝和后剪枝
-
预剪枝(Pre-Pruning):在决策树生长过程中,对每个结点在划分前进行估计,若当前结点的划分不能带来决策树泛化性能的提升,则停止划分并将当前结点标记为叶结点
-
后剪枝(Post-Pruning):先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能带来决策树泛化性能的提升,则将该子树替换为叶结点
什么意思呢?下面以西瓜数据集为例说明。在西瓜数据集上生成的一颗完整(未剪枝)的决策树,如下图所示:
2、预剪枝
预剪枝过程为:将当前节点标记为叶节点,类别标记为训练样例中最多的类别
对于训练集D,首先计算其根节点的信息熵:
- 训练集分为好瓜、坏瓜,所以|y|=2
- 根结点包含10个训练样例,其中好瓜共计5个样例,所占比例为5/10
- 坏瓜共计5个样例,所占比例为5/10
将数据带入信息熵公式,即可得到根结点的信息熵:
E n t ( D ) = − ∑ k = 1 2 p k log 2 p k = − ( 5 10 log 2 5 10 + 5 10 log 2 5 10 ) = 1 Ent(D)= -\sum_{k=1}^{2} p_k\log_2 p_k \\ =-\left( \frac{5}{10}\log_2\frac{5}{10}+\frac{5}{10}\log_2\frac{5}{10} \right) =1 Ent(D)=−k=1∑2pklog2pk=−(105log2105+105log2105)=1
以信息增益为例,分别计算出所有特征的信息增益,据此选出决策树的根节点
以色泽为例,其对应3个数据子集{青绿,乌黑,浅白},则计算色泽属性的信息增益为:
G
a
i
n
(
D
,
色泽
)
=
E
n
t
(
D
)
−
∑
v
=
1
3
∣
D
v
∣
∣
D
∣
E
n
t
(
D
)
=
1
−
(
4
10
∗
1.000
+
4
10
∗
0.918
+
2
10
∗
0
)
=
0.276
Gain(D,色泽)=Ent(D)-\sum_{v=1}^{3}\frac{|D^v|}{|D|}Ent(D) \\ =1-\left( \frac{4}{10} * 1.000+\frac{4}{10}*0.918+\frac{2}{10}*0 \right) =0.276
Gain(D,色泽)=Ent(D)−v=1∑3∣D∣∣Dv∣Ent(D)=1−(104∗1.000+104∗0.918+102∗0)=0.276
同理,计算其他属性的信息增益为:
G a i n ( D , 根蒂 ) = 0.115 G a i n ( D , 敲声 ) = 0.174 G a i n ( D , 纹理 ) = 0.174 G a i n ( D , 脐部 ) = 0.276 G a i n ( D , 触感 ) = 0.000 Gain(D,根蒂)=0.115 \\ Gain(D,敲声)=0.174 \\ Gain(D,纹理)=0.174 \\ Gain(D,脐部)=0.276 \\ Gain(D,触感)=0.000 \\ Gain(D,根蒂)=0.115Gain(D,敲声)=0.174Gain(D,纹理)=0.174Gain(D,脐部)=0.276Gain(D,触感)=0.000
由结果可知,色泽和脐部的信息增益最大,所以从这两个中随机选择一个,这里选择脐部来对数据集进行划分,这会产生三个分支:
但是因为是预剪枝,所以要判断是否应该进行这个划分。判断的标准就是看划分前后的泛化性能是否有提升,也就是如果划分后模型的泛化性能有提升,则划分;否则,不划分
预剪枝对脐部属性的划分过程为:划分前,所有样本都在根节点,将该结点标记为叶节点,其类别标记为训练样本数量最多的类别(划分后):
然后使用验证集对其进行性能评估(以好瓜为例):
- 划分前:验证集中样本{4,5,8}被分类正确,得到验证集精度为3/7=42.9%
- 划分后:根据未剪枝决策树节点②③④的样例数据,验证集{9,13}这两个编号的瓜原本都是坏瓜,结果都被化分成好瓜;其他都被划分对了。所以,得到验证集的精度为5/7=71.4%
通过比较,71.4%>42.9%,划分后模型的泛化性能有提升,因此,对脐部进行划分
同理,可以在对脐部划分的基础上,再对节点②③进行划分:
划分后决策树为:
通过对比未剪枝的决策树和经过预剪枝的决策树,我们不难发现:预剪枝使得决策树的很多分支都没有展开,这不仅降低了模型过拟合的风险,还显著减少了决策树的训练和测试时间开销。但是,另一方面,预剪枝决策树也可能带来欠拟合的风险
3、后剪枝
后剪枝过程为:自底向上,将当前子树节点标记为叶节点,类别标记为训练样例中最多的类别
在一棵完整(未剪枝)的决策树中(见第1节),对于子树节点⑥,若将该子树节点标记为叶节点,其类别标记为训练样本数量最多的类别(剪枝后):
将子树标记为叶节点的过程称为后剪枝。同理,还要判断是否应该标记(剪枝)。判断的标准就是看剪枝前后的泛化性能是否有提升,也就是如果剪枝后模型的泛化性能有提升,则剪枝;否则,不剪枝
还是使用验证集对其进行性能评估(以好瓜为例):
- 剪枝前:验证集中样本{4,5,8}被分类正确,得到验证集精度为3/7=42.9%
- 剪枝后:根据未剪枝决策树节点⑥的样例数据,{青绿,乌黑}都是好瓜,验证集中样本{5,8,13}被分类错误;其他都被分类对了。所以,得到验证集的精度为4/7=57.1%
通过比较,57.1%>42.9%,划分后模型的泛化性能有提升,因此,对子树节点⑥进行剪枝
同理,对子树节点②进行后剪枝,剪枝后的决策树为:
通过对比预剪枝,我们能够发现,后剪枝过程是在构建一棵完整决策树之后进行的,且要自底向上对树中的所有非叶节点进行逐一考察,因此,训练时间开销要比未剪枝决策树和预剪枝决策树都大。但是后剪枝决策树通常比预剪枝决策树保留了更多的分支,一般情况下,后剪枝决策树的欠拟合风险更小,泛化性能往往也要优于预剪枝决策树
4、决策树剪枝总结
对于决策树的预剪枝和后剪枝,我们通常根据以下两点来进行评估:
1)泛化性能:
- 预剪枝:过拟合风险降低,欠拟合风险增加
- 后剪枝:过拟合风险降低,欠拟合风险基本不变
- 泛化性能:后剪枝通常优于预剪枝
2)时间开销:
- 预剪枝:训练时间开销降低,测试时间开销降低
- 后剪枝:训练时间开销增加,测试时间开销降低
- 时间开销:后剪枝通常小于预剪枝
5、附录
决策树分类在西瓜数据集上的应用如下
1)加载数据集
import pandas as pd
import matplotlib.pyplot as plt
# 加载数据集
path = r'C:\Users\cc\Desktop\watermelon_dataset\watermelon_3.csv'
data = pd.read_csv(path)
data.drop(columns=['编号', '密度', '含糖率'], inplace=True)
print(data.head().to_string())
'''
色泽 根蒂 敲声 纹理 脐部 触感 好瓜
0 青绿 蜷缩 浊响 清晰 凹陷 硬滑 是
1 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑 是
2 乌黑 蜷缩 浊响 清晰 凹陷 硬滑 是
3 青绿 蜷缩 沉闷 清晰 凹陷 硬滑 是
4 浅白 蜷缩 浊响 清晰 凹陷 硬滑 是
'''
2)数据预处理
from sklearn.preprocessing import OrdinalEncoder
# 序列编码
enc = OrdinalEncoder()
arr = enc.fit_transform(data)
data = pd.DataFrame(arr, columns=data.columns)
print(data.head().to_string())
'''
色泽 根蒂 敲声 纹理 脐部 触感 好瓜
0 2.0 2.0 1.0 1.0 0.0 0.0 1.0
1 0.0 2.0 0.0 1.0 0.0 0.0 1.0
2 0.0 2.0 1.0 1.0 0.0 0.0 1.0
3 2.0 2.0 0.0 1.0 0.0 0.0 1.0
4 1.0 2.0 1.0 1.0 0.0 0.0 1.0
'''
3)划分训练集和验证集
# 特征
X = data.iloc[:, 0: -1]
# 标签
y = data.iloc[:, -1]
# 划分训练集和验证集
X_train = X.drop(index=[4, 5, 8, 9, 11, 12, 13])
y_train = y.drop(index=[4, 5, 8, 9, 11, 12, 13])
X_test = X.iloc[[4, 5, 8, 9, 11, 12, 13], :]
y_test = y.iloc[[4, 5, 8, 9, 11, 12, 13]]
4)拟合模型与评估
from sklearn.tree import DecisionTreeClassifier
# 决策树分类器
clf = DecisionTreeClassifier()
# 训练模型
clf.fit(X_train, y_train)
# 在验证集上评分
print(clf.score(X_test, y_test)) # 0.42857142857142855
5)绘制决策树
from sklearn.tree import plot_tree
# 决策树可视化
plt.rcParams["font.family"] = "SimHei"
plot_tree(clf, feature_names=data.columns, filled=True, rounded=True)
plt.show()
使用信息增益或基尼系数划分生成的决策树如图所示:
参考文章:
https://blog.csdn.net/zfan520/article/details/82454814
https://blog.csdn.net/u012328159/article/details/79285214