sklearn机器学习入门第二章:白盒学习的典范——决策树(代码篇)

0 前言

决策树篇是本系列阐述sklearn机器学习算法模型所涉及的第一个算法模型,sklearn中的决策树构造简单、运行高效、性能尚可、清晰可解释,是入门sklearn机器学习的最佳选择。

1 认识sklearn中的决策树

sklearn中的决策树算法模型对象存在于sklearn.tree中,包含DecisionTreeClassifierDecisionTreeRegressor两个对象,分别适用于分类任务和回归任务。官方文档对决策树的描述为:

决策树(Decision Tree,DT)是一种用于分类和回归的非参数监督学习方法。目标是创建一个模型,通过学习从数据特征推断出的简单决策规则来预测目标变量的值。树可以看作是分段常数近似。

此外,官方文档还给出了这种方法的优缺点:

决策树的一些优点是:
1、易于理解和解释,可以被形象化;
2、需要很少的数据准备。其他技术通常需要数据标准化、创建虚拟变量并删除空白值。一些树和算法组合支持缺失值;
3、使用树(即预测数据)的成本是用于训练树的数据点数量的对数;
4、能够处理数值数据和分类数据。但是,scikit-learn 实现目前不支持分类变量。其他技术通常专门用于分析仅具有一种变量类型的数据集。请参阅算法以获取更多信息;
5、能够处理多输出问题;
6、使用白盒模型。如果给定的情况在模型中是可观察到的,则该条件的解释很容易通过布尔逻辑来解释。相比之下,在黑盒模型中(例如,在人工神经网络中),结果可能更难以解释;
7、可以使用统计测试来验证模型。这使得可以解释模型的可靠性;
8、即使生成数据的真实模型在某种程度上违反了其假设,也表现良好。

决策树的一些缺点是:
1、决策树可以创建过于复杂的树,而这些树不能很好地概括数据。这称为过度拟合。为了避免这个问题,需要采用剪枝、设置叶节点所需的最小样本数或设置树的最大深度等机制;
2、决策树可能不稳定,因为数据的微小变化可能会导致生成完全不同的树。通过在集成中使用决策树可以缓解这个问题;
3、决策树的预测既不是平滑的也不是连续的,而是分段常数近似,如上图(本文的决策树拟合路径图)所示。因此,他们不擅长推断;
4、有些概念很难学习,因为决策树不容易表达它们,例如异或、奇偶校验或多路复用器问题。
5、如果某些类占主导地位,决策树学习器会创建有偏差的树。因此,建议在拟合决策树之前平衡数据集。

总结一下优缺点:

优点在于:(1)白盒(2)简单易解释(3)鲁棒性还不错(4)统计上的一致性强;

缺点在于:(1)过拟合需要控制(2)单个决策树不算稳定(3)本质上是分段切割而不是连续、平滑的(4)算法拟合能力有限(5)对不平衡数据情况适应性差。

1.1 sklearn中决策树的生长与剪枝

决策树在生长的过程中要确定形成分支的条件,这个条件就是“尽可能降低分支的不纯度”,这个不纯度可以通过基尼系数或者信息熵来定义(对应决策树中的参数’criterion’取’gini’和’entropy’)。不纯度的直观描述为:一个集合中相似(最好相同)的元素越多,就越纯,相反的,其不纯度就越低;一个集合中不相似的元素越多,其纯度越低,不纯度越高。考虑到决策树本身就是遍历输入变量来细分的原理特点,当然是对于每一个树中节点,包含的样本越相似越好,说明分得越准。

剪枝主要是为了限制树的生长,避免为了一个小问题而生成一个“参天大树”,况且这个参天大树不好移植,即避免过拟合,保证模型的鲁棒性和泛化性能。

1.2 决策树分类器DecisionTreeClassifier

用于分类的决策树DecisionTreeClassifier对象表述为:

class sklearn.tree.DecisionTreeClassifier(*, 
criterion='gini', # 分裂标准:{'gini','entropy','log_loss'}, default = 'gini'
splitter='best', # 划分方式:{'best','random'}, default = 'best'
max_depth=None, # 最大树深度: int, default=None
min_samples_split=2, # 划分所需最小样本数量:int or float, default=2
min_samples_leaf=1, # 叶结点所需最小样本数量:int or float, default=1
min_weight_fraction_leaf=0.0, # 叶节点处所需的(所有输入样本的)权重总和的最小加权分数:float, default=0.0
max_features=None, # 最大特征数量:int, float or {“auto”, “sqrt”, “log2”}, default=None
random_state=None, # 随机状态:int, RandomState instance or None, default=None
max_leaf_nodes=None,# 最大叶节点个数:int, default=None
min_impurity_decrease=0.0, # 最小不纯度(基尼系数)减少量:float, default=0.0
class_weight=None, # 类别权重:dict, list of dict or “balanced”, default=None
ccp_alpha=0.0 # 剪枝参数:non-negative float, default=0.0
)

对参数的解释如下:

  • criterion:分裂标准,是决策树生长形成分支的策略(降低不纯度),要确定不纯度的定义,可以通过基尼系数信息熵定义,对应criterion = 'gini'criterion = 'entropy'
  • splitter:划分方式,可选“最佳”或“随机”,前者会按照不纯度减少最多的方式划分,后者会随机划分;
  • max_depth:最大树深度,决策树的生长深度极限,此参数过高会导致复杂度显著上升,同时有可能造成严重的过拟合,对建模精度影响显著,是参数调整时优先考虑的参数;
  • min_samples_split:形成一个分支所需要的最少的样本数量,如果该参数取整数值则表示样本数量,如果取浮点数值,则表示抽取 min_samples_split × n_samples \text{min\_samples\_split}\times\text{n\_samples} min_samples_split×n_samples,即从样本集(父节点)中抽取min_samples_split比例的样本;
  • min_samples_leaf:形成叶节点所需最少样本数量,如果该参数取整数值则表示样本数量,如果取浮点数值,则表示抽取 min_samples_split × n_samples \text{min\_samples\_split}\times\text{n\_samples} min_samples_split×n_samples,即从样本集(父节点)中抽取min_samples_split比例的样本;
  • min_weight_fraction_leaf:叶结点上样本的最小权重系数;
  • max_features:最大特征数量,表示算法在寻找最佳切分点时所考虑特征的最大数量,取None时涵盖全部特征;取整数时考虑表示数量的特征;取浮点数时考虑 max ⁡ ( 1 , m a x _ f e a t u r e s × n _ f e a t u r e s _ i n _ ) \max(1,max\_features\times n\_features\_in\_) max(1,max_features×n_features_in_);取sqrt时考虑 n _ f e a t u r e s \sqrt{n\_features} n_features 个特征;取log2时考虑 log ⁡ 2 n _ f e a t u r e s \log_2 n\_features log2n_features个特征;
  • random_state:随机状态
  • max_leaf_nodes:最大叶节点数量,限制总体叶节点的数量
  • min_impurity_decrease:最小不纯度减少量
  • class_weight:类别权重,自定义的话需要传入字典,也可以选择每个类别权重一致,就是"balanced",默认不设置
  • ccp_alpha:剪枝参数,控制剪枝力度,

1.3 决策树回归器DecisionTreeRegressor

用于回归的决策树DecisionTreeRegressor对象表述为:

class sklearn.tree.DecisionTreeRegressor(*, 
criterion='squared_error', # 分裂标准:{“squared_error”, “friedman_mse”, “absolute_error”, “poisson”}, default=”squared_error”
splitter='best', # 划分方式:{“best”, “random”}, default=”best”
max_depth=None, # 最大树深度: int, default=None
min_samples_split=2, # 划分所需最小样本数量:int or float, default=2
min_samples_leaf=1, # 叶结点所需最小样本数量:int or float, default=1
min_weight_fraction_leaf=0.0, # 叶节点处所需的(所有输入样本的)权重总和的最小加权分数:float, default=0.0
max_features=None, # 最大特征数量:int, float or {“auto”, “sqrt”, “log2”}, default=None
random_state=None, # 随机状态:int, RandomState instance or None, default=None
max_leaf_nodes=None, # 最大叶节点个数:int, default=None
min_impurity_decrease=0.0, # 最小不纯度(基尼系数)减少量:float, default=0.0
ccp_alpha=0.0# 剪枝参数:non-negative float, default=0.0
)
  • criterion:分裂标准,可选"squared_error"(均方误差)、“friedman_mse”(使用Friedman得分改进的均方误差)、“absolute_error”(平均绝对值误差)、“poisson”(泊松误差),无论是使用哪种分裂标准,算法的分裂方向都是减少这些误差;
  • splitter:划分方式,可选“最佳”或“随机”,前者会按照不纯度减少最多的方式划分,后者会随机划分;
  • max_depth:最大树深度,决策树的生长深度极限,此参数过高会导致复杂度显著上升,同时有可能造成严重的过拟合,对建模精度影响显著,是参数调整时优先考虑的参数;
  • min_samples_split:形成一个分支所需要的最少的样本数量,如果该参数取整数值则表示样本数量,如果取浮点数值,则表示抽取 min_samples_split × n_samples \text{min\_samples\_split}\times\text{n\_samples} min_samples_split×n_samples,即从样本集(父节点)中抽取min_samples_split比例的样本;
  • min_samples_leaf:形成叶节点所需最少样本数量,如果该参数取整数值则表示样本数量,如果取浮点数值,则表示抽取 min_samples_split × n_samples \text{min\_samples\_split}\times\text{n\_samples} min_samples_split×n_samples,即从样本集(父节点)中抽取min_samples_split比例的样本;
  • min_weight_fraction_leaf:叶结点上样本的最小权重系数;
  • max_features:最大特征数量,表示算法在寻找最佳切分点时所考虑特征的最大数量,取None时涵盖全部特征;取整数时考虑表示数量的特征;取浮点数时考虑 max ⁡ ( 1 , m a x _ f e a t u r e s × n _ f e a t u r e s _ i n _ ) \max(1,max\_features\times n\_features\_in\_) max(1,max_features×n_features_in_);取sqrt时考虑 n _ f e a t u r e s \sqrt{n\_features} n_features 个特征;取log2时考虑 log ⁡ 2 n _ f e a t u r e s \log_2 n\_features log2n_features个特征;
  • random_state:随机状态
  • max_leaf_nodes:最大叶节点数量,限制总体叶节点的数量
  • min_impurity_decrease:最小不纯度减少量
  • class_weight:类别权重,自定义的话需要传入字典,也可以选择每个类别权重一致,就是"balanced",默认不设置
  • ccp_alpha:剪枝参数,控制剪枝力度,

1.4 更随机的决策树ExtraTree

官方文档对ExtraTree的描述为:

ExtraTree与经典决策树的不同之处在于它们的构建方式。当寻找最佳分割以将节点的样本分为两组时,会对每个随机max_features选择的特征进行随机分割,并选择其中的最佳分割。当 max_features设置为 1 时,这相当于构建一个完全随机的决策树。【警告:额外树只能在集成方法中使用】

1.4.1 ExtraTreeClassifier

用于分类的ExtraTree的决策树ExtraTreeClassifier对象表述为(与DecisionTreeClassifier相同):

class sklearn.tree.ExtraTreeClassifier(*, 
criterion='gini', # 分裂标准:{'gini','entropy','log_loss'}, default = 'gini'
splitter='random',  # 划分方式:{'best','random'}, default = 'best'
max_depth=None, # 最大树深度: int, default=None
min_samples_split=2, # 划分所需最小样本数量:int or float, default=2
min_samples_leaf=1,  # 叶结点所需最小样本数量:int or float, default=1
min_weight_fraction_leaf=0.0, # 叶节点处所需的(所有输入样本的)权重总和的最小加权分数:float, default=0.0
max_features='sqrt', # 最大特征数量:int, float or {“auto”, “sqrt”, “log2”}, default=None
random_state=None, # 随机状态:int, RandomState instance or None, default=None
max_leaf_nodes=None, # 最大叶节点个数:int, default=None
min_impurity_decrease=0.0, # 最小不纯度(基尼系数)减少量:float, default=0.0
class_weight=None, # 类别权重:dict, list of dict or “balanced”, default=None
ccp_alpha=0.0# 剪枝参数:non-negative float, default=0.0
)

1.4.2 ExtraTreeRegressor

用于回归的ExtraTree的决策树ExtraTreeRegressor对象表述为(与DecisionTreeRegressor相同):

class sklearn.tree.ExtraTreeRegressor(*, 
criterion='squared_error', # 分裂标准:{“squared_error”, “friedman_mse”, “absolute_error”, “poisson”}, default=”squared_error”
splitter='best', # 划分方式:{“best”, “random”}, default=”best”
max_depth=None, # 最大树深度: int, default=None
min_samples_split=2, # 划分所需最小样本数量:int or float, default=2
min_samples_leaf=1, # 叶结点所需最小样本数量:int or float, default=1
min_weight_fraction_leaf=0.0, # 叶节点处所需的(所有输入样本的)权重总和的最小加权分数:float, default=0.0
max_features=None, # 最大特征数量:int, float or {“auto”, “sqrt”, “log2”}, default=None
random_state=None, # 随机状态:int, RandomState instance or None, default=None
max_leaf_nodes=None, # 最大叶节点个数:int, default=None
min_impurity_decrease=0.0, # 最小不纯度(基尼系数)减少量:float, default=0.0
ccp_alpha=0.0# 剪枝参数:non-negative float, default=0.0
)

2 使用sklearn中的决策树

本文以泰坦尼克沉船数据集(处理后)加利福尼亚房价数据集为例展示决策树分类器和回归器的基本使用方法(基线模型)。

2.1 在泰坦尼克沉船数据集上建立基线模型

在进行实际的建模之前,需要加载数据,这里利用已经处理过的泰坦尼克号沉船数据集:

def load_titanic(path = r"E:\Download\表单\titanic\titanic_processed_data.xlsx",test_size = .2):
	import pandas as pd
	from sklearn.model_selection import train_test_split
    data = pd.read_excel(path)
    x = data.iloc[:,1:]
    y = data.iloc[:,0]
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=test_size, random_state = 0)
    return x_train,x_test,y_train,y_test

先建立一个基线模型并查看整个决策树的情况,为方便表示,限制最大数深度max_depth为3:

from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
clf = dtc(max_depth = 3,random_state = 0).fit(x_clf_train,y_clf_train)
print('clf baseline acc:',acc(y_clf_test,clf.predict(x_clf_test).round()))
plt.figure(figsize = (40,20))
plot_tree(clf,feature_names = x_clf_train.columns,class_names = ['died','survived'],filled = True)

得到如下acc指标输出和图像输出:

clf baseline acc: 0.8370786516853933

决策树分类器的树图像

图1 决策树分类器的树图像


通过这张图像我们可以非常清楚看到,sklearn的决策树在训练集上训练后获得了怎样的一棵树。

2.2 在加利福尼亚房价数据集上建立基线模型

在进行实际的建模之前,需要加载数据,这里利用sklearn自带的加利福尼亚房价数据集:

def load_california_housing(test_size = .2):
    from sklearn.datasets import fetch_california_housing
    from sklearn.preprocessing import MinMaxScaler as mms
    data = fetch_california_housing(as_frame = True)
    x = data.data
    y = data.target
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=test_size, random_state = 0)
    scaler_1 = mms().fit(x_train)
    scaler_2 = mms().fit(y_train.values.reshape(-1,1))
    x_train = pd.DataFrame(scaler_1.transform(x_train),columns = x_train.columns)
    x_test = pd.DataFrame(scaler_1.transform(x_test),columns = x_test.columns)
    y_train = pd.DataFrame(scaler_2.transform(y_train.values.reshape(-1,1)),columns = ['MedHouseVal'])
    y_test = pd.DataFrame(scaler_2.transform(y_test.values.reshape(-1,1)),columns = ['MedHouseVal'])
    return x_train,x_test,y_train,y_test

先建立一个基线模型并查看整个决策树的情况,为方便表示,限制最大数深度max_depth为3:

# baseline model
import graphviz
from sklearn.tree import export_graphviz
reg = dtr(max_depth = 3).fit(x_reg_train, y_reg_train)
print('baseline accuracy:', mse(y_reg_test, reg.predict(x_reg_test)))
dot_data = export_graphviz(reg,feature_names = x_reg_train.columns,filled = True,rounded = True)
graph = graphviz.Source(dot_data)
graph

得到指标输出和图像输出如下:

baseline accuracy: 0.027117757001309948

决策树回归器的树图像

图2 决策树回归器的树图像


通过这张图像我们可以非常清楚看到,sklearn的决策树在训练集上训练后获得了怎样的一棵树。


3 调整sklearn中的决策树

当直接调用决策树算法模型时,模型会直接配置默认参数运行,对于任务来说并非是最合适的,因此在建立基线模型后需要进行调整。

3.1 手动调整决策树

直观认识决策树中的参数,需要借助图像来实现,因此手动调整并查看参数对运行指标的影响的必要的,借助matplotlib来观察。为了能够显示中文和负号,先执行以下代码:

import matplotlib as mpl
from matplotlib import pyplot as plt
# matplotlib其实是不支持显示中文的 显示中文需要一行代码设置字体
mpl.rcParams['font.family'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False   # 步骤二(解决坐标轴负数的负号显示问题)

3.1.1 手动调整决策树分类器

3.1.1.1 最大树深度max_depth

追踪最大树深度对决策树分类器准确率的影响代码如下:

max_depth_acc_list = []
for depth in max_depth:
    clf = dtc(max_depth=depth,random_state = 0).fit(x_clf_train,y_clf_train)
    max_depth_acc_list.append(acc(y_clf_test,clf.predict(x_clf_test).round()))
plt.plot(max_depth,max_depth_acc_list,color = 'blue',alpha=.7)
plt.xlabel('max_depth 最大树深度')
plt.ylabel('accuracy 准确率')
plt.title('图3 准确率随最大树深度变化图')
plt.show()

运行后可以看到:
在这里插入图片描述
由图3可知,在[2,100]这个区间内,最大树深度并非越大越好,而是存在一个较小的最佳值,这样的现象同样出现在对其它参数的调整上。同时注意到,当最大树深度增加到一定值得时候,模型的性能不再出现变化,这也就是最大树深度这个参数调整的横向瓶颈了,表明我们现在的最大树深度取值区间过大,应考虑缩小到适当区间长度。通过以下代码找出最佳树深度:

# 找出最佳树深度
max_depth = max_depth[max_depth_acc_list.index(max(max_depth_acc_list))]
print('max_depth:',max_depth)

得到最优结果:

max_depth: 12

得到最佳的最大树深度为12,但是注意这个数值仅仅是对于当前random_state参数为0的情况下的。

3.1.1.2 最大特征数量max_features

追踪最大特征数量对决策树分类器准确率的影响代码如下:

max_features_acc_list = []
for features in max_features:
    clf = dtc(max_depth = 12,max_features = features,random_state = 0).fit(x_clf_train,y_clf_train)
    max_features_acc_list.append(acc(y_clf_test,clf.predict(x_clf_test).round()))
plt.plot(max_features,max_features_acc_list,c = 'b',alpha = .7)
plt.xlabel('max_features 最大特征数')
plt.ylabel('accuracy 准确率')
plt.title('图4 准确率随最大特征数的变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
由图4可知,在最大特征数在30%+和迫近100%的时候,达到了三个极值,这表明其实在实践中我们并不需要100%的特征,只需要部分的特征也很好,这么一看,30%+的特征使用是性价比非常高的。

max_features = max_features[max_features_acc_list.index(max(max_features_acc_list))]
print('max_features:',max_features)

得到最优结果:

max_features: 0.9

但是通过以上代码我们可以知道,最佳的最大特征数为0.9

3.1.1.3 最大叶节点个数max_leaf_nodes

追踪最大叶节点个数对决策树分类器准确率的影响代码如下:

max_leaf_nodes_acc_list = []
for nodes in max_leaf_nodes:
    clf = dtc(
    max_depth = 12,
    max_features = .9,
    max_leaf_nodes = nodes,
    random_state = 0
).fit(x_clf_train,y_clf_train)
    max_leaf_nodes_acc_list.append(acc(y_clf_test,clf.predict(x_clf_test).round()))
plt.plot(max_leaf_nodes,max_leaf_nodes_acc_list,c = 'b',alpha = .7)
plt.xlabel('max_leaf_nodes 最大叶节点个数')
plt.ylabel('accuracy 准确率')
plt.title('图5 准确率随最大叶节点个数的变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
从图5可以看到,最大叶节点个数从40+后进入瓶颈,在70附近处使准确率到达最大值。
通过以下代码查看最优参数:

max_leaf_nodes = max_leaf_nodes[max_leaf_nodes_acc_list.index(max(max_leaf_nodes_acc_list))]
print('max_leaf_nodes:',max_leaf_nodes)

得到最优结果:

max_leaf_nodes: 67

通过以上代码可以知道,最优的最大叶节点个数为67。

3.1.1.4 最小分裂样本数min_samples_split

追踪最小分裂样本数对决策树分类器准确率的影响代码如下:

min_samples_split_acc_list = []
for split in min_samples_split:
    clf = dtc(
    max_depth = 12,
    max_features = .9,
    max_leaf_nodes = 67,
    min_samples_split = split,
    random_state = 0
).fit(x_clf_train,y_clf_train)
    min_samples_split_acc_list.append(acc(y_clf_test,clf.predict(x_clf_test).round()))
plt.plot(min_samples_split,min_samples_split_acc_list,c = 'b',alpha = .7)
plt.xlabel('min_samples_split 最小分裂样本数')
plt.ylabel('accuracy 准确率')
plt.title('图6 准确率随最小分裂样本数的变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
从图6可以看出最小分裂样本数限制最少时决策树生长最容易,使算法能够不断尝试不同的分裂路径,因此准确率很高。通过以下代码查看最优参数:

min_samples_split = min_samples_split[min_samples_split_acc_list.index(max(min_samples_split_acc_list))]
print('min_samples_split:',min_samples_split)

得到最优结果:

min_samples_split: 2
3.1.1.5 叶节点内最小样本数量min_samples_leaf
min_samples_leaf_acc_list = []
for leaf in min_samples_leaf:
    clf = dtc(
    max_depth = 12,
    max_features = .9,
    max_leaf_nodes = 67,
    min_samples_split = 2,
    min_samples_leaf = leaf,
    random_state = 0
).fit(x_clf_train,y_clf_train)
    min_samples_leaf_acc_list.append(acc(y_clf_test,clf.predict(x_clf_test).round()))
plt.plot(min_samples_leaf,min_samples_leaf_acc_list,c = 'b',alpha = .7)
plt.xlabel('min_samples_leaf 叶节点内最小样本数量')
plt.ylabel('accuracy 准确率')
plt.title('图7 准确率随叶节点内最小样本数量的变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
从图7可以看出叶结点内最小样本数量限制较少时决策树准确率很高。通过以下代码查看最优参数:

min_samples_leaf = min_samples_leaf[min_samples_leaf_acc_list.index(max(min_samples_leaf_acc_list))]
print('min_samples_leaf:',min_samples_leaf)

得到最优结果:

min_samples_leaf: 5
3.1.1.6 最小不纯度减少量min_impurity_decrease
min_impurity_decrease_acc_list = []
for impurity in min_impurity_decrease:
    clf = dtc(
    max_depth = 12,
    max_features = .9,
    max_leaf_nodes = 67,
    min_samples_split = 2,
    min_samples_leaf=5,
    min_impurity_decrease = impurity,
    random_state = 0
).fit(x_clf_train,y_clf_train)    min_impurity_decrease_acc_list.append(acc(y_clf_test,clf.predict(x_clf_test).round()))
plt.plot(min_impurity_decrease,min_impurity_decrease_acc_list,c = 'b',alpha = .7)
plt.xlabel('min_impurity_decrease 最小不纯度减少量')
plt.ylabel('accuracy 准确率')
plt.title('图8 准确率随最小不纯度减少量的变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
从图8可以看出最小不纯度减少量会严重限制决策树的准确率,这是因为决策树的生长(形成分支)被严重限制,虽然这样有助于滤除噪声,但也会降低决策树的性能。通过以下代码查看最优参数:

min_impurity_decrease = min_impurity_decrease[min_impurity_decrease_acc_list.index(max(min_impurity_decrease_acc_list))]
print('min_impurity_decrease:',min_impurity_decrease)

得到最优结果:

min_impurity_decrease: 0.01
3.1.1.7 剪枝力度ccp_alpha
ccp_alpha_acc_list = []
for alpha in ccp_alpha:
    clf = dtc(
    max_depth = 12,
    max_features = .9,
    max_leaf_nodes = 67,
    min_samples_split = 2,
    min_samples_leaf=5,
    min_impurity_decrease = 0.01,
    ccp_alpha = alpha,
    random_state = 0
).fit(x_clf_train,y_clf_train)
    ccp_alpha_acc_list.append(acc(y_clf_test,clf.predict(x_clf_test).round()))
plt.plot(ccp_alpha,ccp_alpha_acc_list,c = 'b',alpha = .7)
plt.xlabel('ccp_alpha 剪枝力度')
plt.ylabel('accuracy 准确率')
plt.title('图9 准确率随剪枝力度的变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
从图9可以看出,随着剪枝力度增大,决策树会变得过于简单,准确率发生明显的下降。通过以下代码查看最优参数:

ccp_alpha = ccp_alpha[ccp_alpha_acc_list.index(max(ccp_alpha_acc_list))]
print('ccp_alpha:',ccp_alpha)

得到最优结果:

ccp_alpha: 0.01

3.1.2 手动调整决策树回归器

决策树回归的手动参数调整过程与分类器类似。

3.1.2.1 分裂标准criterion
criterion_mse_list = []
for ct in criterion:
    reg = dtr(criterion = ct,random_state = 0).fit(x_reg_train,y_reg_train)
    criterion_mse_list.append(mse(y_reg_test,reg.predict(x_reg_test)))
plt.bar(range(0,4),height = criterion_mse_list,color = 'red',alpha = .7)
plt.xlabel('criterion 分裂标准')
plt.xticks(range(0,4),criterion)
plt.ylabel('mse 均方误差')
plt.title('图10 均方误差随分裂标准变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
通过以下代码查看最优参数:

best_criterion = criterion[criterion_mse_list.index(min(criterion_mse_list))]
print('criterion:',best_criterion)

得到最优结果:

criterion: poisson
3.1.2.2 最大树深度max_depth
max_depth_mse_list = []
for depth in max_depth:
    reg = dtr(
    criterion = best_criterion ,
    max_depth = depth,
    random_state = 0
).fit(x_reg_train,y_reg_train)
    max_depth_mse_list.append(mse(y_reg_test,reg.predict(x_reg_test)))
plt.plot(max_depth_mse_list,color = 'red',alpha = .7)
plt.xlabel('max_depth 最大树深度')
plt.ylabel('mse 均方误差')
plt.title('图11 均方误差随最大树深度变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
通过以下代码查看最优参数:

best_max_depth = max_depth[max_depth_mse_list.index(min(max_depth_mse_list))]
print('max_depth:',best_max_depth)

得到最优结果:

max_depth: 9
3.1.2.3 最大特征数量max_features
max_features_mse_list = []
for feature in max_features:
    reg = dtr(
    criterion = best_criterion,
    max_depth = best_max_depth,
    max_features = feature,
    random_state = 0
).fit(x_reg_train,y_reg_train)
    max_features_mse_list.append(mse(y_reg_test,reg.predict(x_reg_test)))
plt.plot(max_features_mse_list,color = 'red',alpha = .7)
plt.xlabel('max_features 最大特征数')
plt.ylabel('mse 均方误差')
plt.title('图12 均方误差随最大特征数变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
通过以下代码查看最优参数:

best_max_features = max_features[max_features_mse_list.index(min(max_features_mse_list))]
print('max_features:',best_max_features)

得到最优结果:

max_features: 0.88
3.1.2.4 最大叶节点个数max_leaf_nodes
#max_leaf_nodes
max_leaf_nodes_mse_list = []
for nodes in max_leaf_nodes:
    reg = dtr(
    criterion = best_criterion,
    max_depth = best_max_depth,
    max_features = best_max_features,
    max_leaf_nodes = nodes,
    random_state = 0
).fit(x_reg_train,y_reg_train)
    max_leaf_nodes_mse_list.append(mse(y_reg_test,reg.predict(x_reg_test)))
plt.plot(max_leaf_nodes_mse_list,color = 'red',alpha = .7)
plt.xlabel('max_leaf_nodes 最大叶节点个数')
plt.ylabel('mse 均方误差')
plt.title('图13 均方误差随最大叶节点个数变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
通过以下代码查看最优参数:

best_max_leaf_nodes = max_leaf_nodes[max_leaf_nodes_mse_list.index(min(max_leaf_nodes_mse_list))]
print('max_leaf_nodes:',best_max_leaf_nodes)

得到最优结果:

max_leaf_nodes: 98
3.1.2.5 最小分裂样本数min_samples_split
#min_samples_split
min_samples_split_mse_list = []
for split in min_samples_split:
    reg = dtr(
        criterion = best_criterion,
        max_depth = best_max_depth,
        max_features = best_max_features,
        max_leaf_nodes = best_max_leaf_nodes,
        min_samples_split=split,
        random_state = 0
        ).fit(x_reg_train,y_reg_train)
    min_samples_split_mse_list.append(mse(y_reg_test,reg.predict(x_reg_test)))
plt.plot(min_samples_split_mse_list,color = 'red',alpha = .7)
plt.xlabel('min_samples_split 最小分裂样本数')
plt.ylabel('mse 均方误差')
plt.title('图14 均方误差随最小分裂样本数变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
通过以下代码查看最优参数:

best_min_samples_split = min_samples_split[min_samples_split_mse_list.index(min(min_samples_split_mse_list))]
print('min_samples_split:',best_min_samples_split)

得到最优结果:

min_samples_split: 91
3.1.2.6 叶节点内最小样本数量min_samples_leaf
#min_samples_leaf
min_samples_leaf_mse_list = []
for min_leaf in min_samples_leaf:
    reg = dtr(
        criterion = best_criterion,
        max_depth = best_max_depth,
        max_features = best_max_features,
        max_leaf_nodes = best_max_leaf_nodes,
        min_samples_split= best_min_samples_split,
        min_samples_leaf = min_leaf,
        random_state = 0
        ).fit(x_reg_train,y_reg_train)
    min_samples_leaf_mse_list.append(mse(y_reg_test,reg.predict(x_reg_test)))
plt.plot(min_samples_leaf_mse_list,color = 'red',alpha = .7)
plt.xlabel('min_samples_leaf 叶节点内最小样本数量')
plt.ylabel('mse 均方误差')
plt.title('图15 均方误差随叶节点内最小样本数量变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
通过以下代码查看最优参数:

best_min_samples_leaf = min_samples_leaf[min_samples_leaf_mse_list.index(min(min_samples_leaf_mse_list))]
print('min_samples_leaf:',best_min_samples_leaf)

得到最优结果:

min_samples_leaf: 4
3.1.2.7 最小不纯度减少量min_impurity_decrease
#min_impurity_decrease
min_impurity_decrease_mse_list = []
for impurity in min_impurity_decrease:
    reg = dtr(
        criterion = "friedman_mse",
        max_depth = best_max_depth,
        max_features = best_max_features,
        max_leaf_nodes = best_max_leaf_nodes,
        min_samples_split=best_min_samples_split,
        min_samples_leaf = best_min_samples_leaf,
        min_impurity_decrease=impurity,
        random_state = 0
        ).fit(x_reg_train,y_reg_train)
    min_impurity_decrease_mse_list.append(mse(y_reg_test,reg.predict(x_reg_test)))
plt.plot(min_impurity_decrease_mse_list,color = 'red',alpha = .7)
plt.xlabel('min_impurity_decrease 最小不纯度减少量')
plt.ylabel('mse 均方误差')
plt.title('图16 均方误差随最小不纯度减少量变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
通过以下代码查看最优参数:

best_min_impurity_decrease = min_impurity_decrease[min_impurity_decrease_mse_list.index(min(min_impurity_decrease_mse_list))]
print('min_impurity_decrease:',best_min_impurity_decrease)

得到最优结果:

min_impurity_decrease: 0.01
3.1.2.8 剪枝力度ccp_alpha
#ccp_alpha
ccp_alpha_mse_list = []
for alpha in ccp_alpha:
    reg = dtr(
        criterion = "friedman_mse",
        max_depth = best_max_depth,
        max_features = best_max_features,
        max_leaf_nodes = best_max_leaf_nodes,
        min_samples_split=best_min_samples_split,
        min_samples_leaf = best_min_samples_leaf,
        min_impurity_decrease=best_min_impurity_decrease,
        ccp_alpha = alpha,
        random_state = 0
        ).fit(x_reg_train,y_reg_train)
    ccp_alpha_mse_list.append(mse(y_reg_test,reg.predict(x_reg_test)))
plt.plot(ccp_alpha,ccp_alpha_mse_list,color = 'red',alpha = .7)
plt.xlabel('ccp_alpha 剪枝力度')
plt.ylabel('mse 均方误差')
plt.title('图17 均方误差随剪枝力度变化图',y=-.2,fontsize=13)
plt.show()

运行后可以看到:
在这里插入图片描述
通过以下代码查看最优参数:

best_ccp_alpha = ccp_alpha[ccp_alpha_mse_list.index(min(ccp_alpha_mse_list))]
print('ccp_alpha:',best_ccp_alpha)

得到最优结果:

ccp_alpha: 0.01

3.2 利用第三方库调整决策树

调整决策树,除了通过手动调整参数外,还可以利用optuna库进行,参数的调整包括如下步骤:

  1. 确定参数空间
  2. 指定优化指标
  3. 指定优化方向
  4. 指定优化算法
  5. 创建并执行优化

3.2.1 决策树分类器的优化

对决策树分类器的优化代码如下(部分参数):

# classifier optimization
# metric: accuracy
# optimization direction: maximize
# 创建一个优化对象 dt_clf_obj
from sklearn.metrics import accuracy_score as acc
def dt_clf_obj(trial):
	# 给出参数空间
    params = {
        "max_depth": trial.suggest_int("max_depth", 2, 32, log=True),
        "splitter": trial.suggest_categorical("splitter",['best','random']),
        "criterion": trial.suggest_categorical("criterion", ["gini", "entropy"]),
        "max_features": trial.suggest_float("max_features", 0,1,log=False),
        "max_leaf_nodes": trial.suggest_int("max_leaf_nodes", 2, 32, log=True),
        "min_samples_split": trial.suggest_int("min_samples_split", 2, 32, log=True),
        "min_samples_leaf": trial.suggest_int("min_samples_leaf", 2, 32, log=True),
        #"min_impurity_decrease": trial.suggest_float("min_impurity_decrease", 0, 1, log=False),
        #"ccp_alpha": trial.suggest_float("ccp_alpha", 0, 1, log=False),
        "random_state":0
    }
    # 创建模型,将参数空间传入,并进行训练
    model = dtc(**params).fit(x_clf_train, y_clf_train)# modify model
    # 返回评估模型的指标acc(优化指标)
    return acc(y_clf_test, model.predict(x_clf_test))# modify metric
# modify direction, n_trials, name
# 优化对象:dt_clf_obj,优化方向:最大化,迭代搜索次数:1000次,优化任务名称:dt_clf
optm_params = {'obj':dt_clf_obj,'direction':'maximize','n_trials':1000,'name':'dt_clf'}
# 启动优化
best_trial = build_optimization(**optm_params)

其中build_optimization定义如下:

def build_optimization(obj,direction = "maximize",sampler = optuna.samplers.TPESampler(seed = 1),n_trials = 500,name = None):
	# 创建优化任务,指定方向、采样器(优化算法)、任务名称
    study = optuna.create_study(direction=direction,sampler = sampler,study_name = name)
    # 执行优化任务:优化对象obj,迭代搜索次数n_trials,显示进度条True
    study.optimize(obj,n_trials = n_trials, show_progress_bar=True)
    print("best value: {}\nbest params: {}".format(study.best_trial.values[0],study.best_trial.params))
    return study.best_trial

得到以下输出(前10条):

[I 2023-10-19 16:17:05,086] A new study created in memory with name: dt_clf
C:\Users\fansh\AppData\Roaming\Python\Python39\site-packages\optuna\progress_bar.py:56: ExperimentalWarning: Progress bar is experimental (supported from v1.2.0). The interface can change in the future.
  self._init_valid()
  0%|          | 0/1000 [00:00<?, ?it/s]
  
[I 2023-10-19 16:17:05,095] Trial 0 finished with value: 0.7359550561797753 and parameters: {'max_depth': 9, 'splitter': 'best', 'criterion': 'entropy', 'max_features': 0.4972500933605313, 'max_leaf_nodes': 7, 'min_samples_split': 3, 'min_samples_leaf': 9}. Best is trial 0 with value: 0.7359550561797753.
[I 2023-10-19 16:17:05,099] Trial 1 finished with value: 0.7359550561797753 and parameters: {'max_depth': 32, 'splitter': 'best', 'criterion': 'gini', 'max_features': 0.5956079336026455, 'max_leaf_nodes': 2, 'min_samples_split': 2, 'min_samples_leaf': 2}. Best is trial 0 with value: 0.7359550561797753.
[I 2023-10-19 16:17:05,104] Trial 2 finished with value: 0.7640449438202247 and parameters: {'max_depth': 6, 'splitter': 'random', 'criterion': 'entropy', 'max_features': 0.7674748688630131, 'max_leaf_nodes': 19, 'min_samples_split': 10, 'min_samples_leaf': 12}. Best is trial 2 with value: 0.7640449438202247.
[I 2023-10-19 16:17:05,108] Trial 3 finished with value: 0.7415730337078652 and parameters: {'max_depth': 7, 'splitter': 'random', 'criterion': 'entropy', 'max_features': 0.4686283427461092, 'max_leaf_nodes': 6, 'min_samples_split': 2, 'min_samples_leaf': 2}. Best is trial 2 with value: 0.7640449438202247.
[I 2023-10-19 16:17:05,112] Trial 4 finished with value: 0.7359550561797753 and parameters: {'max_depth': 15, 'splitter': 'random', 'criterion': 'entropy', 'max_features': 0.5238961356661317, 'max_leaf_nodes': 15, 'min_samples_split': 6, 'min_samples_leaf': 2}. Best is trial 2 with value: 0.7640449438202247.
[I 2023-10-19 16:17:05,116] Trial 5 finished with value: 0.7303370786516854 and parameters: {'max_depth': 4, 'splitter': 'random', 'criterion': 'gini', 'max_features': 0.022915131322381765, 'max_leaf_nodes': 7, 'min_samples_split': 18, 'min_samples_leaf': 2}. Best is trial 2 with value: 0.7640449438202247.
[I 2023-10-19 16:17:05,121] Trial 6 finished with value: 0.601123595505618 and parameters: {'max_depth': 12, 'splitter': 'best', 'criterion': 'gini', 'max_features': 0.005239837651465851, 'max_leaf_nodes': 7, 'min_samples_split': 21, 'min_samples_leaf': 19}. Best is trial 2 with value: 0.7640449438202247.
[I 2023-10-19 16:17:05,125] Trial 7 finished with value: 0.7359550561797753 and parameters: {'max_depth': 11, 'splitter': 'best', 'criterion': 'entropy', 'max_features': 0.6717297848174203, 'max_leaf_nodes': 4, 'min_samples_split': 21, 'min_samples_leaf': 3}. Best is trial 2 with value: 0.7640449438202247.
[I 2023-10-19 16:17:05,129] Trial 8 finished with value: 0.7471910112359551 and parameters: {'max_depth': 15, 'splitter': 'random', 'criterion': 'entropy', 'max_features': 0.3535348626679904, 'max_leaf_nodes': 3, 'min_samples_split': 16, 'min_samples_leaf': 4}. Best is trial 2 with value: 0.7640449438202247.
[I 2023-10-19 16:17:05,133] Trial 9 finished with value: 0.601123595505618 and parameters: {'max_depth': 14, 'splitter': 'best', 'criterion': 'gini', 'max_features': 0.14251833776584466, 'max_leaf_nodes': 5, 'min_samples_split': 2, 'min_samples_leaf': 2}. Best is trial 2 with value: 0.7640449438202247.
......(此处省略)......
best value: 0.8426966292134831
best params: {'max_depth': 25, 'splitter': 'best', 'criterion': 'gini', 'max_features': 0.9103978587718007, 'max_leaf_nodes': 26, 'min_samples_split': 19, 'min_samples_leaf': 4}

可以看到,经过1000次参数空间内的搜索优化后,最佳参数和最高分数(acc)为

best_params = {
	'max_depth': 25, 
	'splitter': 'best', 
	'criterion': 'gini', 
	'max_features': 0.9103978587718007, 
	'max_leaf_nodes': 26, 
	'min_samples_split': 19, 
	'min_samples_leaf': 4
}
best_value = 0.8426966292134831

这个最大树深度已经相当高了,足足25层,会存在较高的过拟合风险,然而这只是一次参数优化尝试,应该继续进行优化使得深度减少,防止过拟合情况。

3.3.2 决策树回归器的优化

对决策树回归器的优化代码如下(部分参数):

# regressor optimization
# metric: mean_squared_error
# optimization direction: minimize
from sklearn.metrics import mean_squared_error as mse
def dt_reg_obj(trial):
    params = {
        "max_depth": trial.suggest_int("max_depth", 2, 32, log=True),
        "criterion": "squared_error",
        "max_features": trial.suggest_float("max_features", 0,1,log=False),
        "min_samples_split": trial.suggest_int("min_samples_split", 2, 32, log=True),
        "min_samples_leaf": trial.suggest_int("min_samples_leaf", 2, 32, log=True),
        "random_state":0
    }
    model = dtr(**params).fit(x_reg_train, y_reg_train)# modify model
    return mse(y_reg_test, model.predict(x_reg_test))# modify metric
# modify direction, n_trials, name
# 优化对象:dt_reg_obj,优化方向:最小化,迭代搜索次数:400次,优化任务名称:dt_reg
optm_params = {'obj':dt_reg_obj,'direction':'minimize','n_trials':400,'name':'dt_reg'}
best_trial = build_optimization(**optm_params)

得到以下输出(前10条):

[I 2023-10-23 13:52:57,243] A new study created in memory with name: dt_reg
C:\Users\fansh\AppData\Roaming\Python\Python39\site-packages\optuna\progress_bar.py:56: ExperimentalWarning: Progress bar is experimental (supported from v1.2.0). The interface can change in the future.
  self._init_valid()
Best trial: 311. Best value: 0.0142813: 100%
400/400 [00:55<00:00, 8.10it/s]
[I 2023-10-23 13:52:57,305] Trial 0 finished with value: 0.021661731521252083 and parameters: {'max_depth': 5, 'max_features': 0.7203244934421581, 'min_samples_split': 2, 'min_samples_leaf': 4}. Best is trial 0 with value: 0.021661731521252083.
[I 2023-10-23 13:52:57,314] Trial 1 finished with value: 0.047799643725750234 and parameters: {'max_depth': 2, 'max_features': 0.0923385947687978, 'min_samples_split': 3, 'min_samples_leaf': 4}. Best is trial 0 with value: 0.021661731521252083.
[I 2023-10-23 13:52:57,351] Trial 2 finished with value: 0.023502366153954086 and parameters: {'max_depth': 5, 'max_features': 0.538816734003357, 'min_samples_split': 5, 'min_samples_leaf': 12}. Best is trial 0 with value: 0.021661731521252083.
[I 2023-10-23 13:52:57,392] Trial 3 finished with value: 0.02711775700130995 and parameters: {'max_depth': 3, 'max_features': 0.8781174363909454, 'min_samples_split': 2, 'min_samples_leaf': 12}. Best is trial 0 with value: 0.021661731521252083.
[I 2023-10-23 13:52:57,427] Trial 4 finished with value: 0.02351104695228429 and parameters: {'max_depth': 5, 'max_features': 0.5586898284457517, 'min_samples_split': 2, 'min_samples_leaf': 3}. Best is trial 0 with value: 0.021661731521252083.
[I 2023-10-23 13:52:57,582] Trial 5 finished with value: 0.015134718272122788 and parameters: {'max_depth': 18, 'max_features': 0.9682615757193975, 'min_samples_split': 4, 'min_samples_leaf': 13}. Best is trial 5 with value: 0.015134718272122788.
[I 2023-10-23 13:52:57,804] Trial 6 finished with value: 0.018673223849335518 and parameters: {'max_depth': 22, 'max_features': 0.8946066635038473, 'min_samples_split': 2, 'min_samples_leaf': 2}. Best is trial 5 with value: 0.015134718272122788.
[I 2023-10-23 13:52:57,848] Trial 7 finished with value: 0.02711775700130995 and parameters: {'max_depth': 3, 'max_features': 0.8781425034294131, 'min_samples_split': 2, 'min_samples_leaf': 5}. Best is trial 5 with value: 0.015134718272122788.
[I 2023-10-23 13:52:57,965] Trial 8 finished with value: 0.017471193008038413 and parameters: {'max_depth': 29, 'max_features': 0.5331652849730171, 'min_samples_split': 13, 'min_samples_leaf': 4}. Best is trial 5 with value: 0.015134718272122788.
[I 2023-10-23 13:52:58,088] Trial 9 finished with value: 0.015411370620798696 and parameters: {'max_depth': 12, 'max_features': 0.8346256718973729, 'min_samples_split': 2, 'min_samples_leaf': 15}. Best is trial 5 with value: 0.015134718272122788.
......(此处省略)......
best value: 0.014281300251904307
best params: {'max_depth': 20, 'max_features': 0.8869355121564816, 'min_samples_split': 20, 'min_samples_leaf': 12}

可以看到,经过400次参数空间内的搜索优化后,最佳参数和最高分数(mse)为

best_params = {
	'max_depth': 20, 
	'max_features': 0.8869355121564816, 
	'min_samples_split': 20, 
	'min_samples_leaf': 12
	}
best_value = 0.014281300251904307

可以看到,进行过参数优化后,mse指标有了一定的下降。

3.3 手动调整VS自动调整

通过对比以上手动调整的过程(更重要的是结果)和自动调整的结果,不难发现两者的最优结果出现了很大的不同,这是因为手动调整的过程本质上受优化顺序的制约,是一种贪心的优化策略,而自动调整有优化算法的加持,结果自然不同。至于自动调整手动调整哪个更好,在实践中往往是两者结合,这一点会在超参数调整篇详述。


4 关于决策树的一些疑问

4.1 为什么说决策树是一种白盒方法?

在回答这个问题之前,我们先明确“白盒方法”和“黑盒方法”。

4.1.1 白盒方法与黑盒方法

我们先来说说什么是“白盒方法”和“黑盒方法”:

"白盒方法"和"黑盒方法"是两种不同的方法或策略,用于解决问题或开发模型,特别是在机器学习和软件测试等领域经常使用这两种术语。它们描述了对问题或系统的理解和处理方式的不同程度。

  1. 白盒方法(White Box Approach)

    • 白盒方法是一种透明、清晰、可解释的方法。在这种方法中,问题或系统的内部结构和工作原理是已知的、可见的,可以被分析和理解。
    • 这种方法通常涉及基于具体原理和规则的建模或解决问题,例如基于物理定律的建模、符号逻辑推理或基于已知数据的统计分析。
    • 白盒方法的优势在于其可解释性,决策过程通常可以被清晰解释和验证,适用于需要透明性和可控性的应用领域。
  2. 黑盒方法(Black Box Approach)

    • 黑盒方法是一种不关心问题或系统内部工作原理的方法。在这种方法中,问题或系统的内部是未知的,只关注输入和输出之间的关系。
    • 这种方法通常涉及使用机器学习、神经网络、统计模型或其他复杂算法来解决问题,而不需要详细了解问题的本质或内部机制。
    • 黑盒方法的优势在于其灵活性和适用性,能够处理复杂问题,但通常可解释性较差,决策过程难以解释。

总之,白盒方法强调可解释性和可控性,通常在需要理解问题的本质或遵循特定规则的情况下使用。而黑盒方法侧重于处理复杂问题,通常在问题的内部机制未知或过于复杂以至于无法解释的情况下使用。选择哪种方法取决于具体问题的性质和需求。

4.1.2 决策树的白盒性

从图1和图2不难看出,决策树在训练之后,对于每一次分支时的依据,也可以通过export_text来查看更详细的分支,总之,无论通过哪种方法,决策树总可以给出每一个输入变量在树中何处产生分支(被划分)对目标的贡献程度(形成分类的权重,通过clf.feature_importances_和reg.feature_importances_查看),可以直观明了的查看。

那么为什么说决策树是白盒方法的典范呢?

是因为决策树的计算方法,包括树的生成(分支过程)、剪枝过程都清晰可见,永远都可以回溯,有关于计算的疑问都可以得到解答,计算过程中任一中间结果都可以取出,因此称为白盒方法的典范,即全透明

4.2 决策树适用于什么样的任务?

决策树适用于分类和回归任务,这一点在文章开头已经说过了,但这个问题也并非是在问这个,而是在问,决策树适用于什么体量、什么复杂度的数据任务。

决策树本身是一种不复杂的模型,其本质就是在分类,对于回归任务来说,本质上也是将回归任务转化为了分类任务在做,遍历每一个输入变量,之后将其划分为左右两个分支。因为这一点的限制,决策树不适合过于复杂的任务,其泛化性能比较有限。我们引用一张sklearn官方示例中的图:

在这里插入图片描述

图18 决策树拟合路径图


通过图18可以看到,决策树并非平滑地拟合数据,而是一直在走阶梯形,这种“非此即彼”的策略直接决定了决策树拟合能力的上限是比较低的。

4.3 为什么说决策树是一种非参方法?

4.3.1 参数方法和非参数方法

在机器学习中,"参数方法"和"非参数方法"是两种不同的建模和学习策略,它们描述了模型的复杂度和建模方式的不同特点。

  1. 参数方法(Parametric Methods)

    • 参数方法是一种假设模型有固定数量参数,并且这些参数可以通过学习过程来确定。模型的复杂度是有限的,不会随着训练数据的增加而增加。
    • 典型的参数方法包括线性回归、逻辑回归、朴素贝叶斯等。
    • 参数方法通常需要对数据做出某些假设,例如线性回归假设数据可以用线性关系建模。这些假设在模型中被明确表示。
  2. 非参数方法(Non-parametric Methods)

    • 非参数方法是一种不对模型参数数量进行固定假设的方法,模型的复杂度可以根据数据的复杂性自由扩展。这意味着非参数方法可以更好地适应复杂的数据分布和关系。
    • 典型的非参数方法包括决策树、k最近邻(K-Nearest Neighbors, KNN)、核方法(如支持向量机中的径向基函数核)等。
    • 非参数方法通常不对数据做出强假设,因此它们更加灵活,适用于各种数据分布和问题类型。

参数方法假设模型的参数数量是有限的且固定的,通常依赖于对数据的某些假设,而非参数方法不对模型参数数量做出假设,更加灵活,可以适应更广泛的问题和数据分布。选择参数方法或非参数方法通常取决于问题的性质和数据的复杂性。参数方法通常在数据量较小或对问题有一定先验知识的情况下表现良好,而非参数方法通常更适用于大规模数据和复杂关系建模。

我们可以简单理解为:参数模型其实有固定的结构和计算逻辑存在,分别由不同的、有限的参数控制,数据的不同只是调整这些参数取值;而非参数方法虽然具备一定的计算逻辑和结构,但是都服务于数据,它们会完全拟合到数据本身,数据非常复杂的话,非参数模型就会非常复杂,这也就直接导致一个问题,就是过拟合以及出现幻觉,严重依赖数据的质量和体量。

当我们讨论一个算法模型是参数模型还是非参数模型时,我们不是在讨论这个模型有没有可以调整的参数,而是这个算法模型有没有一个相对固定的对数据的描述模型。即便是众所周知的非参数模型如逻辑回归,也是有可以调整的参数,然而此参数是在描述算法的执行策略和计算方式,而非算法模型的构造。所以此“参数”非彼“参数”,一定要注意这一点。

4.3.2 决策树的非参数性质

决策树无论是在生长(形成分支)还是在剪枝的过程中,都是完全的数据为导向,这会导致一个问题,目前掌握的数据如果不全面,那么形成分支的时候,划分就是一个随意的、粗糙的,比如现在的数据表明在某个输入变量上只存在1和10两个值,那么决策树在形成分支的时候会确定一个边界(比如5),小于5的形成左路,大于等于5的形成右路,然而这些数据只是庞大数据的一部分或者说只能部分地表示数据的客观规律,实际的边界应该在另一个地方(比如0),那么这样的划分对现有的数据来说是有效的,可是对客观的数据规律来说是无效的,这就是非参模型的弊病,对数据的严重依赖,或者说其根本特性就是大数据驱动的

4.4 如何克服决策树的缺点?

前文提到,决策树的缺点包括:
(1)过拟合需要控制;
(2)单个决策树不算稳定;
(3)本质上是分段切割而不是连续、平滑的;
(4)算法拟合能力有限;
(5)对不平衡数据情况适应性差。

有鉴于此,改善的对策也很明显:
(1)抑制过拟合,我们可以通过决策树算法模型本身的参数来实现;
(2)建立多棵树,这涉及到集成学习的问题,从手动建立多个随机的决策树到直接使用随机森林再到随机建立特征集和使用堆叠法等等,总之是使用多棵树来克服;
(3)这一点无法改善,因为这是决策树的底层机制,在实践中尽量在分类任务中使用;
(4)这一点也无法改善,面对复杂度极高的任务,决策树这样的复杂度偏低的算法无能为力;
(5)对于不平衡的数据,有其他的处理方法,比如抽样或者手动构造多个平衡的数据集等。

4.5 决策树还有些什么作用?

前文提到决策树是一种白盒方法,可解释性很强,但这种可解释性不仅仅是能够看到决策树的决策过程(分支),还有一点很重要的应用,就是导出特征重要性。建立一个默认参数的决策树,通过clf.feature_importances_来查看:

for name,importance in zip(clf.feature_names_in_,clf.feature_importances_):
    print(name,':',round(importance,4))

运行后可以看到:

Pclass : 0.2064
Age : 0.0079
SibSp : 0.0
Parch : 0.0
Fare : 0.0559
Sex_female : 0.7298
Sex_male : 0.0
Embarked_C : 0.0
Embarked_Q : 0.0
Embarked_S : 0.0

从这个运行结果可以看到,Sex_female重要性高达0.7298,是最重要的特征,但注意这是默认参数下的结果,当执行一次基于optuna的优化后,传入一组最佳参数:

best_params = {
'max_depth': 9, 
'criterion': 'entropy', 
'max_features': 0.8259516795577686,
 'min_samples_split': 7, 
 'min_samples_leaf': 7
 }
 clf = dtc(**params,random_state = 0).fit(x_clf_train,y_clf_train)
 for name,importance in zip(clf.feature_names_in_,clf.feature_importances_):
    print(name,':',round(importance,4))

运行后可以看到:

Pclass : 0.151
Age : 0.1458
SibSp : 0.0238
Parch : 0.005
Fare : 0.2346
Sex_female : 0.0
Sex_male : 0.4222
Embarked_C : 0.008
Embarked_Q : 0.0
Embarked_S : 0.0096

这个时候可以看到,特征重要性发生了翻天覆地的变化,但是仍是在优化导向为准确率的情况下得到的最优参数组合,而指标的选择会严重影响优化导向,所以特征重要性仍需要慎重参考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fanshaoliang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值