前言
决策树是一种非线性图形模型,它在机器学习中有着重要的应用,主要是因为它的可解释性,以及它在其他强大模型中的作用,接下来将描述训练一种流行的决策树(CART,分类和回归树的缩写)的原理。将讨论 CART 在分类、回归方面的发展
分类
分类树采用基尼指数选择最优特征,同时决定该特征的最优二值切分点
基尼指数:
分类问题中,假设有 个类,样本点属于第k类的概率为 ,则概率分布的基尼指数定义为:
假设有一个二分类问题,目标是建立一个决策树来对样本进行分类。要最小化每个节点上的基尼不纯度,考虑一个节点 上的基尼不纯度 定义为:
其中, 是节点 上属于第 个类的样本比例
在一个二叉树中,考虑一个划分点 将数据集划分成两个子集 和 ,分别对应于左子树和右子树,则划分后的加权基尼不纯度为:
其中, 是原始节点 上的数据集, 和 分别是划分后两个子集的基尼不纯度, 和 分别是两个子集的样本数量
CART 算法的目标就是选择一个划分点 ,使得 达到最小值,CART的推导就是寻找到最优划分点的过程
在实际应用中,为了避免过拟合,通常会引入正则化项,例如限制树的深度或节点的最小样本数,以提高模型的泛化能力
回归
CART并不依赖于观测值的类别标签,因此它并不特定于分类,与分类的CART相比回归问题实现CART时,唯一的区别在于:1) 不纯度度量的选择; 2) 如何在叶节点估算目标值
不纯度度量的选择
为了更好地理解回归中的不纯度及其度量概念,可以从CRAT分类角度考虑,通过每次分割获得更纯净的子节点集合的效用在于,如果将一个节点视为叶节点(在这种情况下,使用最简单的多数投票分类器,将该节点中的所有训练数据分类为多数类),则对于落入该节点的训练数据,能更加确定其类别标签。在回归中,一旦将一个节点指定为叶节点,可以使用训练数据落入该节点的目标值的平均值作为该叶节点中任何测试观测的目标值的估算器。因此,一个更“纯净”的节点(为了更确定目标)将是一个节点,其中该节点中训练数据的目标值更接近它们的均值。选择均值作为叶节点上目标的最简单估算器后,可以采用各种度量方式,如均方误差(MSE)或平均绝对误差(MAE),来衡量叶节点中目标值在平均值附近的程度。因此,可以将那些具有更大这些度量值的节点视为更不纯的节点。然而,当均值被用作目标估算器时,使用MSE和MAE作为不纯度度量的一个微妙之处在于,均值最小化MSE函数,而不是MAE。特别地,如果存在 则在所有常数估算器 中, 的均值最小化MSE;也就是说:
其中:
因此,为了使用一对相同的不纯度和目标估算器,通常使用均值和MSE不纯度一起。然而,当使用MAE时,通常使用中位数(落入节点的训练数据的目标值的中位数)作为的目标估算器。这是因为如果在 中的 被绝对值函数 替换,那么最优的常数估算器就是中位数
因此,可以使用以下不纯度函数找到最佳分割:
这里:
通常也将MSE和MAE分别称为“均方误差”和“绝对误差”
叶节点的回归
当使用 和 作为不纯度度量时,分别将叶节点中训练数据的目标值的均值和中位数用作该节点中任何观测值的目标估算
Scikit-learn 分类和回归的实现
Scikit-learn 中对分类和回归的实现:决策树分类器和回归器分别在 DecisionTreeClassifier 类和 DecisionTreeRegressor 类中实现,位于 sklearn.tree 模块。DecisionTreeClassifier 目前支持两种不同的不纯度准则,可以通过将 'criterion' 参数的值设置为 gini(默认)和 entropy 来更改。在 scikit-learn 版本 >= 1.1.0 中新增的 log_loss 选项与使用 entropy 等效。DecisionTreeRegressor 目前支持 squared_error(默认)、absolute_error 和其他两种准则
对于 DecisionTreeClassifier 和 DecisionTreeRegressor,在 scikit-learn 中可以通过设置 max_depth、max_leaf_nodes、min_samples_leaf 和 min_impurity_decrease 来设定在 Section 分割停止准则,即预定的最大深度、最大叶节点数、每个叶节点的最小样本数和最小不纯度减小值。这些参数的默认值导致完全生长的树,这取决于数据,可能非常庞大,这可能导致过拟合。在实践中,应将这些参数视为超参数,并在缺乏先验知识来设置它们的情况下,在模型选择阶段进行调整。除了限制 CART 复杂性的上述超参数之外,在 scikit-learn 实现这些类时还有另一个超参数,即 max_features,它确定要检查以在每个节点找到最佳分割的特征数。max_features 的默认值检查所有 p 个特征以找到最佳分割,但可以将其设置为某个数字,例如 d < p,以便在每个节点只随机选择(无替换)和检查 d 个特征;也就是说,找到最佳分割的最大化是在 d 个随机选择的特征上进行的。尽管在高维设置中,从计算上来说,这个选项可能是有帮助的
分类python实现
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.tree import DecisionTreeClassifier as CART
arrays = np.load('data/iris_train_scaled.npz')
X_train = arrays['X']
y_train = arrays['y']
arrays = np.load('data/iris_test_scaled.npz')
X_test = arrays['X']
y_test = arrays['y']
X_train = X_train[:,[0,1]]
X_test = X_test[:,[0,1]]
print('X shape = {}'.format(X_train.shape) + '\ny shape = {}'.format(y_train.shape))
print('X shape = {}'.format(X_test.shape) + '\ny shape = {}'.format(y_test.shape))
color = ('aquamarine', 'bisque', 'lightgrey')
cmap = ListedColormap(color)
mins = X_train.min(axis=0) - 0.1
maxs = X_train.max(axis=0) + 0.1
x = np.arange(mins[0], maxs[0], 0.01)
y = np.arange(mins[1], maxs[1], 0.01)
X, Y = np.meshgrid(x, y)
coordinates = np.array([X.ravel(), Y.ravel()]).T
fig, axs = plt.subplots(2, 2, figsize=(6, 4), dpi = 200)
fig.tight_layout()
min_samples_leaf_val = [1, 2, 5, 10]
for ax, msl in zip(axs.ravel(), min_samples_leaf_val):
cart = CART(min_samples_leaf=msl)
cart.fit(X_train, y_train)
Z = cart.predict(coordinates)
Z = Z.reshape(X.shape)
ax.tick_params(axis='both', labelsize=6)
ax.set_title('CART Decision Regions: min_samples_leaf=' + str(msl), fontsize=7)
ax.pcolormesh(X, Y, Z, cmap = cmap, shading='nearest')
ax.contour(X ,Y, Z, colors='black', linewidths=0.5)
ax.plot(X_train[y_train==0, 0], X_train[y_train==0, 1],'g.', markersize=4)
ax.plot(X_train[y_train==1, 0], X_train[y_train==1, 1],'r.', markersize=4)
ax.plot(X_train[y_train==2, 0], X_train[y_train==2, 1],'k.', markersize=4)
ax.set_xlabel('sepal length (normalized)', fontsize=7)
ax.set_ylabel('sepal width (normalized)', fontsize=7)
print('The accuracy for min_samples_leaf={} on the training data is {:.3f}'.format(msl, cart.score(X_train, y_train)))
print('The accuracy for min_samples_leaf={} on the test data is {:.3f}'.format(msl, cart.score(X_test, y_test)))
for ax in axs.ravel():
ax.label_outer()
如图所看到的,允许树生长到最大深度(即 min_samples_leaf=1)在训练数据上产生了高准确度(93.3%),但在测试集上的准确度较低(63.3%)。这表明模型出现了过拟合。同时,选择 min_samples_leaf 取最大值在训练集上导致了最低的准确度,并且在测试集上也表现不佳。在这个例子中,min_samples_leaf 取值为2和5的情况表现相似,都在测试集上达到了66.7%的准确度
这个观察说明了调整 min_samples_leaf 参数可以在一定程度上控制决策树模型的复杂度,避免过拟合。通过选择适当的 min_samples_leaf,可以在训练集和测试集上取得更好的平衡,提高模型的泛化能力。在这个例子中,min_samples_leaf 取值为2和5都表现良好,避免了过拟合问题,而 min_samples_leaf=1 和取更大值的情况则导致了性能下降
决策树的可解释性
决策树的一个关键优势在于其"玻璃箱"结构,与一些其他预测模型(如神经网络)的“黑箱”结构形成对比。换句话说,一旦开发了决策树,通过标准化的问题层次结构,其分类机制是可解释和透明的。为了更好地了解训练后决策树的工作机制,可以将训练后的树的分裂序列可视化。使用sklearn.tree模块中的plot_tree()是一种直观的方法。在下面的代码中,可视化了上文训练的最后一棵树,即min_samples_leaf=10的CART。为此,首先再次加载鸢尾花数据,以便访问其属性,如feature_names和target_names。使用class_names=iris.target_names[0:3]来显示叶节点中的主要类别。在plot_tree()中使用filled=True的选项会导致节点被着色为该节点内的主要类别,其中较深的颜色对应于更纯净的节点
from sklearn import datasets
from sklearn.tree import plot_tree
plt.figure(figsize=(15,15), dpi=200)
iris = datasets.load_iris()
plot_tree(cart, feature_names = iris.feature_names[0:2], class_names=iris.target_names[0:3], filled=True)
针对鸢尾花分类任务训练得到的决策树。图示展示了决策树的“玻璃箱”结构,强调了决策树的可解释性是其关键优势之一。按照惯例,在一个节点中满足条件的子节点位于左侧分支
简单解读:
树的根节点是通过对"sepal length (cm)"特征进行条件判定,如果其值小于等于-0.514,则进入左子树,否则进入右子树。
左子树的节点表示在满足条件"sepal length (cm) <= -0.514"的情况下,继续判断"sepal width (cm) <= -0.068"。如果满足条件,则进入左子树,否则进入右子树。
叶节点表示决策树的最终分类结果。每个叶节点包含样本的基尼不纯度(gini)和样本数量(samples),以及每个类别的样本数量和类别(class)
如果你对类似于这样的文章感兴趣。
欢迎关注、点赞、转发~