决策树是完全利用现有数据信息的分类拟合,为了避免过拟合、提高模型泛化能力,需要对建立好的树进行剪枝。尤其是当训练数据量大、特征数量较多时,所构建的决策树可能很庞大且复杂,剪枝可以同时达到降低复杂度,解决过拟合的目的。这一篇就结合sklearn来看一下决策树的剪枝操作。
决策树的剪枝操作主要分为两种:预剪枝(Pre-Pruning),后剪枝(Post-Pruning)。
1.0 预剪枝
预剪枝是指在决策树生成过程中,对每个节点在划分前先进行估计,若当前节点的划分不能带来决策树泛化性能的提升,则停止划分并将当前节点标记为叶节点。
所谓的“决策树泛化性能”如何判定,可以使用性能评估中的留出法,即预留一部分数据用作“验证集”以进行性能评估。
通过一个经典的西瓜分类的例子来看:
使用ID3算法,基于信息增益对西瓜分类的过程进行决策树构建,得到的决策树如下图所示(计算过程略):
预剪枝是在树构建过程中进行剪枝的,此例从根节点,分别计算按照色泽(0.276)、根蒂(0.115)、敲声(0.174)、纹理(0.174)、脐部(0.276)、触感(0)划分时的信息增益,括号内即为信息增益量的大小。色泽和脐部的信息增益最大,从中随机挑选一个,如脐部作为划分特征,此时产生三个分支,如下图所示:
然而是否应该进行这次划分,需要对划分前后的泛化性能进行估计,若划分后泛华性能有提升,则划分;否则,不划分。
上例中,脐部划分前所有样本{1,2,3,6,7,10,14,15,16,17}都在根节点①,把该结点标记为叶结点,其类别标记为训练集中样本数量最多的类别。此例中的所有样本5例为好瓜,5例为坏瓜,此时随意选择一类即可,如好瓜。然后使用验证集{4,5,8,9,11,12,13}评估其性能,依据决策树规则将所有瓜都分为好瓜,此时只有{4,5,8}被正确分类,模型准确率为3/7=42.9%。
划分后的的决策树如下,再使用验证集评估,此时被正确分类的瓜有{4,5,8,11,12},正确率为5/7=71.4%,大于划分前的准确率。泛化性能得到了提升,因此用“脐部”进行划分。
划分后的的决策树如下,再使用验证集评估,此时被正确分类的瓜有{4,5,8,11,12},正确率为5/7=71.4%,大于划分前的准确率。泛化性能得到了提升,因此用“脐部”进行划分。
接下来,再对结点 ② 进行划分,信息增益值最大的那个特征是“色泽”,则使用“色泽”划分后决策树如下:
那么是否应该划分这个结点,通过验证集计算后,被正确分类的为{4,9,11,12}(正确率是遵循决策树的规则对验证集的预测结果),正确率为4/7=0.571<0.714,因此将禁止划分结点 ② 。
这就是预剪枝的应用思路。
2.0 后剪枝
后剪枝是先从训练集生成一颗完整的决策树,然后自底向上地对非叶节点进行考察,若将该节点对应的子树完全替换为叶节点能带来决策树泛化性的提升,则将该子树替换为叶节点。
依旧以西瓜分类的为例,使用ID3算法生成完整的决策树,如下:
后剪枝算法从树结构的下方向上,首先检查结点,剪枝前对于验证集的正确划分样本为{4,11,12},准确率为3/7=42.9%。
再将以 ⑥为根节点的子树删除,将结点 ⑥ 原来包含的样本{7,15}放入其中,使用投票法把该叶结点标记为“好瓜”(这里同样好瓜坏瓜的标签数量相等,所以随便取一个类别标记),此时验证集的准确率为57.1%>42.9%,根据后剪枝策略决定剪枝。
3.0 sklearn中的决策树和剪枝
sklearn中能够实现的是预剪枝,就是设置Classifier或者Regression里的参数max_depth, min_samples_split, min_samples_leaf。后剪枝目前在sklearn中是无法实现的。
"""数据准备,并展示分布情况"""
import numpy as np
import matplotlib.pyplot as plt
from 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)
"""决策树模型,不设置任何参数,让sklearn自动建树"""
from sklearn.tree import DecisionTreeClassifier
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_clf1的决策边界的形状非常不规则,这就是典型的过拟合现象。当模型为了迁就现有的数据集样本,才会出现这样的结果。
增加限制条件后,再构建一个决策树dt_clf2这里限制了决策树的深度(max_depth)为2,也就是划分到第二层就停止了。
"""决策树模型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_clf2的结果就明显”和谐“了很多。
还可以指定其他参数,比如设置样本划分的最小值(min_samples_split),即对于一个节点来说,至少包含多少个样本数量才会对这个节点继续拆分下去。数量越大越不容易过拟合,但太大的话容易欠拟合。
"""决策树模型3,限制分割节点所含样本数的最小量"""
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()
还可以设置最小样本叶节点(min_samples_leaf),即对于一个叶子节点来说要求包含样本的最小数量,越少越容易过拟合。
"""决策树模型4,限制叶节点所含样本数的最小量"""
dt_clf4 = DecisionTreeClassifier(min_samples_leaf=6)
dt_clf4.fit(X,y)
plot_decision_boundary(dt_clf4, 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()
还可以设置最大叶子结点(max_leaf_nodes),即对于一个叶子节点来说,最多有几个叶子结点,叶子越多,树越复杂,越容易过拟合。
"""决策树模型5,限制叶节点数量"""
dt_clf5 = DecisionTreeClassifier(max_leaf_nodes=4)
dt_clf5.fit(X,y)
plot_decision_boundary(dt_clf5, 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()
4.0 sklearn.中DecisionTreeClassifier的参数介绍
class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, splitter=’best’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, class_weight=None, presort=False)
1)criterion:特征选择标准,可选参数,默认是gini,可以设置为entropy。gini是基尼不纯度,entropy是熵。ID3算法使用的是entropy,CART算法使用的则是gini。
2)splitter:特征划分点选择标准,可选参数,默认是best,可以设置为random。best参数是根据算法选择最佳的切分特征,例如gini、entropy。random随机的在部分划分点中找局部最优的划分点。默认的”best”适合样本量不大的时候,而如果样本数据量非常大,此时决策树构建推荐”random”。
3)max_features:划分时考虑的最大特征数,可选参数,默认是None。寻找最佳切分时考虑的最大特征数(n_features为总共的特征数),有如下6种情况:
如果max_features是整型的数,则考虑max_features个特征;
如果max_features是浮点型的数,则考虑int(max_features * n_features)个特征;
如果max_features设为auto,那么max_features = sqrt(n_features);
如果max_features设为sqrt,那么max_featrues = sqrt(n_features),跟auto一样;
如果max_features设为log2,那么max_features = log2(n_features);
如果max_features设为None,那么max_features = n_features,也就是所有特征都用。一般来说,如果样本特征数不多,比如小于50,我们用默认的”None”就可以了,如果特征数非常多,就可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间。
4)max_depth:决策树最大深,可选参数,默认是None。这个参数表示树的层数,如果这个参数设置为None,那么决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以默认这个值。或者如果设置了min_samples_slipt或min_samples_leaf参数时,这个参数也可以默认为None,效果是相近的。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。
5)min_samples_split:内部节点再划分所需最小样本数,可选参数,默认是2。这个值限制了子树继续划分的条件。如果min_samples_split为整数,那么在切分内部结点的时候,min_samples_split作为最小的样本数,也就是说,如果样本已经少于min_samples_split个样本,则停止继续切分。如果min_samples_split为浮点数,那么min_samples_split就是一个百分比,ceil(min_samples_split * n_samples),数是向上取整的。如果样本量不大,可以无视这个参数。
6)min_weight_fraction_leaf:叶子节点最小的样本权重和,可选参数,默认是0。这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时需要注意这个值了。
7)max_leaf_nodes:最大叶子节点数,可选参数,默认是None。通过限制最大叶子节点数,可以防止过拟合。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。
8)class_weight:类别权重,可选参数,默认是None,也可以字典、字典列表。指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。类别的权重可以通过{class_label:weight}这样的格式给出,这里可以自己指定各个样本的权重,或者用balanced,如果使用balanced,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。当然,如果你的样本类别分布没有明显的偏倚,则可以不管这个参数,选择默认的None。
9)random_state:可选参数,默认是None。随机数种子,如果是整数,那么random_state会作为随机数生成器的随机数种子。如果没有设置,随机出来的数与当前系统时间有关,每个时刻都是不同的。如果设置了随机数种子,那么相同随机数种子,不同时刻产生的随机数也是相同的。如果是RandomState instance,那么random_state是随机数生成器。如果为None,则随机数生成器使用np.random。
10)min_impurity_split:节点划分最小不纯度,可选参数,默认是1e-7。这是个阈值,这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值,则该节点不再生成子节点。即为叶子节点 。
11)presort:数据是否预排序,可选参数,默认为False,这个值是布尔值,默认是False不排序。一般来说,如果样本量少或者限制了一个深度很小的决策树,设置为true可以让划分点选择更加快,决策树建立的更加快。如果样本量太大的话,反而没有什么好处。问题是样本量少的时候,速度本来就不慢。所以这个参数一般没有用处。
除了这些参数要注意以外,其他在调参时的注意点有:
当样本数量少但是样本特征非常多的时候,推荐先做维度规约,否则很容易过拟合,方法可以采用主成分分析(PCA),特征选择(Lasso)或者独立成分分析(ICA)。这样特征的维度会大大减小。再来拟合决策树模型效果会好。
推荐多用决策树的可视化,同时先限制决策树的深度,这样可以先观察下生成的决策树里数据的初步拟合情况,然后再决定是否要增加深度。
注意观察样本的类别情况是否均匀(主要指分类树)。如果类别分布非常不均匀,就要考虑用class_weight来限制模型过于偏向样本多的类别。
决策树的数组使用的是numpy的float32类型,如果训练数据不是这样的格式,算法会先做copy再运行。
如果输入的样本矩阵是稀疏的,推荐在拟合前调用csc_matrix稀疏化,在预测前调用csr_matrix稀疏化。