0x01 剪枝
当训练数据量大、特征数量较多时构建的决策树可能很庞大,这样的决策树用来分类是否好?答案是否定的。
决策树是依据训练集进行构建的,为了尽可能正确地分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多。这就可能会把训练样本学的“太好”了,以至于把训练集自身的一些特点当作所有数据都具有的一般性质而导致过拟合。因此可主动去掉一些分支来降低过拟合风险。
决策树非常容易产生过拟合,实际所有非参数学习算法,都非常容易产生过拟合。
因此,对于决策树的构建还需要最后一步,即决策树的修剪。两个目的:降低复杂度,解决过拟合。
决策树的修剪,也就是剪枝操作,主要分为两种:
-
预剪枝(Pre-Pruning)
-
后剪枝(Post-Pruning)
接下来我们将详细地介绍这两种剪枝方法。
0x02 预剪枝
2.1 概念
预剪枝是指在决策树生成过程中,
对每个节点在划分前先进行估计,若当前节点的划分不能带来决策树泛化性能的提升,则停止划分并将当前节点标记为叶节点。
那么所谓的“决策树泛化性能”如何来判定呢?这就可以使用性能评估中的留出法,即预留一部分数据用作“验证集”以进行性能评估。
如在下面的数据集中,将其划分成两部分:一部分作为训练集用来构建决策树,一部分作为验证集用来进行决策树的剪枝。具体划分如下。
2.2 具体实例
我们使用ID3算法,即使用信息增益进行决策树构建。得到的决策树如下图所示:
下面是手工计算的过程:
因为色泽和脐部的信息增益值最大,所以从这两个中随机挑选一个,这里选择脐部来对数据集进行划分,这会产生三个分支,如下图所示:
然而我们是否应该进行这次划分呢?
评判依据就是对划分前后的泛化性能进行估计:划分前后的泛华性能是否有提升,也就是如果划分后泛华性能有提升,则划分;否则,不划分。
下面来看看是否要用脐部进行划分,划分前:所有样本都在根节点,把该结点标记为叶结点,其类别标记为训练集中样本数量最多的类别,因此标记为好瓜,然后用验证集对其性能评估,可以看出样本{4,5,8}被正确分类,其他被错误分类,因此精度为43.9%。划分后:划分后的的决策树为:
则验证集在这颗决策树上的精度为:5/7 = 71.4% > 42.9%。泛化性能得到了提升,因此,用“脐部”进行划分。
接下来,决策树算法对结点 (2) 进行划分,再次使用信息增益挑选出值最大的那个特征,这里我就不算了,计算方法和上面类似,信息增益值最大的那个特征是“色泽”,则使用“色泽”划分后决策树为:
但到底该不该划分这个结点,还是要用验证集进行计算,可以看到划分后,精度为:4/7=0.571<0.714,因此,预剪枝策略将禁止划分结点 (2) 。对于结点 (3) 最优的属性为“根蒂”,划分后验证集精度仍为71.4%,因此这个划分不能提升验证集精度,所以预剪枝将禁止结点 (3) 划分。对于结点 (4) ,其所含训练样本已属于同一类,所以不再进行划分。
所以基于预剪枝策略生成的最终的决策树为:
对比未剪枝的决策树和经过预剪枝的决策树可以看出:预剪枝使得决策树的很多分支都没有“展开”,这不仅降低了过拟合的风险,还显著减少了决策树的训练时间开销和测试时间开销。但是,另一方面,因为预剪枝是基于“贪心”的,所以,虽然当前划分不能提升泛化性能,但是基于该划分的后续划分却有可能导致性能提升,因此预剪枝决策树有可能带来欠拟合的风险。
2.3 伪代码
0x03 后剪枝
3.1 概念
后剪枝是先从训练集生成一颗完整的决策树,然后自底向上地对非叶节点进行考察,若将该节点对应的子树完全替换为叶节点能带来决策树繁花性的提升,则将该子树替换为叶节点。
3.2 具体实例
首先生成一棵完整决策树:
后剪枝算法首先考察上图中的结点 (6),若将以其为根节点的子树删除,即相当于把结点 (6) 替换为叶结点,替换后的叶结点包括编号为{7,15}的训练样本,因此把该叶结点标记为“好瓜”(因为这里正负样本数量相等,所以随便标记一个类别),因此此时的决策树在验证集上的精度为57.1%(为剪枝的决策树为42.9%),所以后剪枝策略决定剪枝,剪枝后的决策树如下图所示:
接着考察结点 5,同样的操作,把以其为根节点的子树替换为叶结点,替换后的叶结点包含编号为{6,7,15}的训练样本,根据“多数原则”把该叶结点标记为“好瓜”,测试的决策树精度认仍为57.1%,所以不进行剪枝。
考察结点 2 ,和上述操作一样,不多说了,叶结点包含编号为{1,2,3,14}的训练样本,标记为“好瓜”,此时决策树在验证集上的精度为71.4%,因此,后剪枝策略决定剪枝。剪枝后的决策树为:
接着考察结点 3 ,同样的操作,剪枝后的决策树在验证集上的精度为71.4%,没有提升,因此不剪枝;对于结点 1 ,剪枝后的决策树的精度为42.9%,精度下降,因此也不剪枝。
因此,基于后剪枝策略生成的最终的决策树如上图所示,其在验证集上的精度为71.4%。
3.3 伪代码
3.4 总结
对比预剪枝和后剪枝,能够发现,后剪枝决策树通常比预剪枝决策树保留了更多的分支,一般情形下,后剪枝决策树的欠拟合风险小,泛华性能往往也要优于预剪枝决策树。但后剪枝过程是在构建完全决策树之后进行的,并且要自底向上的对树中的所有非叶结点进行逐一考察,因此其训练时间开销要比未剪枝决策树和预剪枝决策树都大得多。
0x04 sklearn中的剪枝处理
4.1 展示
sklearn中现在能做的是预剪枝,就是设置Classifier或者Regression里的参数max_depth, min_samples_split, min_samples_leaf。
后剪枝的确是在sklearn中做不到的。
我们看一下具体的例子。首先构造数据:
import numpy as npimport matplotlib.pyplot as pltfrom sklearn import datasets
X,y = datasets.make_moons(noise=0.25,random_state=666)
plt.scatter(X[y==0,0],X[y==0,1])plt.scatter(X[y==1,0],X[y==1,1])plt.show()
然后加载描绘分类边界的函数:
def plot_decision_boundary(model, axis): # model是模型,axis是范围 x0, x1 = np.meshgrid( np.linspace(axis[0], axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1), np.linspace(axis[2], axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1), ) X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new) zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9']) plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
下面开始进行决策树的构建和比较。首先创建决策树dt_clf1,在这里不限定决策树的最大深度,则决策树会一直向下划分,直到每一个节点的基尼系数为0为止。
from sklearn.tree import DecisionTreeClassifier
# 如果在构建时不传参数,则默认是使用基尼系数进行特征划分# 不限定max_depth,则决策树会一直向下划分,直到每一个节点的基尼系数为0为止dt_clf1 = DecisionTreeClassifier()dt_clf1.fit(X,y) plot_decision_boundary(dt_clf1, axis=[-1.5,2.5,-1.0,1.5])plt.scatter(X[y==0,0],X[y==0,1])plt.scatter(X[y==1,0],X[y==1,1])plt.show()
我们可以看到,决策边界的形状非常不规则,这就是典型的过拟合现象。在图中用红色箭头标出的部分,就是为了迁就现有的数据集样本,才会学习成这个样子的。
下面我们重新生成一个决策树dt_clf2,这里限制了决策树的深度为2,也就是划分到第二层就停止了。
dt_clf2 = DecisionTreeClassifier(max_depth=2)dt_clf2.fit(X,y)
plot_decision_boundary(dt_clf2, axis=[-1.5,2.5,-1.0,1.5])plt.scatter(X[y==0,0],X[y==0,1])plt.scatter(X[y==1,0],X[y==1,1])plt.show()
我们还可以设置最小样本划分,即对于一个节点来说,至少有多少个样本数据,才会对这个节点拆分下去。数值越高 越不容易过拟合,太高的话容易欠拟合。
dt_clf3 = DecisionTreeClassifier(min_samples_split=10)dt_clf3.fit(X,y)
plot_decision_boundary(dt_clf3, axis=[-1.5,2.5,-1.0,1.5])plt.scatter(X[y==0,0],X[y==0,1])plt.scatter(X[y==1,0],X[y==1,1])plt.show()
还可以设置最小样本叶节点,对于一个叶子节点来说,至少有几个样本。越少越容易过拟合。
dt_clf4 = DecisionTreeClassifier(min_samples_leaf=6)dt_clf4.fit(X,y)