第39课:决策树——原理、构建与剪枝
学习目标
完成本课学习后,你将能够:
- 理解 决策树用于分类的基本工作原理和流程。
- 掌握 使用信息增益(或基尼不纯度)选择最优划分特征的核心概念。
- 运用 Python 的
scikit-learn库构建一个决策树分类模型。 - 解释 什么是过拟合,并理解预剪枝和后剪枝对模型泛化能力的重要性。
核心概念
1. 什么是决策树?
想象你在玩一个“猜动物”的游戏。你可能会问一系列问题:“是哺乳动物吗?”、“生活在水里吗?”、“有长鼻子吗?”。每个问题都根据答案(是/否)将可能性范围缩小,最终猜出动物。决策树的工作原理与此类似。
它是一种树形结构,其中:
- 内部节点:代表一个特征(属性) 上的测试(如“花瓣长度≤2.5cm吗?”)。
- 分支:代表测试的一个输出(如“是”或“否”)。
- 叶节点:代表一个类别标签(如“山鸢尾”)。
从根节点开始,根据样本的特征值沿分支向下,最终到达某个叶节点,该叶节点的类别即为预测结果。
2. 如何构建树:选择最佳划分
构建决策树的核心问题是:在当前节点,选择哪个特征进行划分最好? 我们需要一个标准来衡量划分的“好坏”。这个标准的目标是让划分后子节点中的样本尽可能“纯净”,即属于同一个类别的样本尽量多。
常用的两个标准是信息增益和基尼不纯度。
-
信息熵:衡量数据集不确定性的指标。熵越高,数据越混乱。 $$Entropy(D) = -\sum_{i=1}^{k} p_i \log_2 p_i$$ 其中,
p_i是数据集D中第i类样本的比例。 -
信息增益:通过某个特征
A划分数据集D后,不确定性减少的程度。增益越大,划分越优。 $$Gain(D, A) = Entropy(D) - \sum_{v \in Values(A)} \frac{|D_v|}{|D|} Entropy(D_v)$$ 其中,Values(A)是特征A的所有可能取值,D_v是特征A取值为v的子集。 -
基尼不纯度:另一种衡量数据集不纯度的指标。值越小,数据越纯净。 $$Gini(D) = 1 - \sum_{i=1}^{k} p_i^2$$
scikit-learn 中的 DecisionTreeClassifier 默认使用基尼不纯度 (criterion=‘gini’),你也可以选择信息熵 (criterion=‘entropy’)。
3. 决策树的优缺点
- 优点:模型直观、易于理解和解释(白盒模型);无需对数据进行大量预处理(如归一化);既可以处理数值型数据,也可以处理类别型数据。
- 缺点:非常容易过拟合,即树过于复杂,完美拟合训练数据但在新数据上表现差;决策边界是轴平行的,可能不适合某些数据分布。
4. 什么是剪枝?
为了防止过拟合,我们需要控制树的复杂度,这就是剪枝。
- 预剪枝:在构建树的过程中提前停止。例如,限制树的最大深度 (
max_depth)、叶节点最少样本数 (min_samples_leaf)、或内部节点最少样本数 (min_samples_split)。 - 后剪枝:先让树充分生长,然后自下而上地剪掉那些对验证集精度提升不大的子树。
scikit-learn主要通过ccp_alpha参数进行基于复杂度的剪枝。
代码示例
我们将使用经典的**鸢尾花(Iris)**数据集,通过决策树进行花卉分类。
# 1. 导入必要的库
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
# 2. 加载数据并划分训练集和测试集
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names
class_names = iris.target_names
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
# 3. 创建决策树分类器实例 (使用基尼不纯度)
# 这里我们设置 max_depth=3 作为预剪枝,防止过拟合
dt_clf = DecisionTreeClassifier(criterion=‘gini‘, max_depth=3, random_state=42)
# 4. 在训练集上拟合模型
dt_clf.fit(X_train, y_train)
# 5. 进行预测并评估
y_pred = dt_clf.predict(X_test)
print(f"测试集准确率: {accuracy_score(y_test, y_pred):.2f}")
print("\n分类报告:\n", classification_report(y_test, y_pred, target_names=class_names))
# 6. 可视化决策树 (这非常直观!)
plt.figure(figsize=(15, 10))
plot_tree(dt_clf,
feature_names=feature_names,
class_names=class_names,
filled=True, # 用颜色表示纯度
rounded=True)
plt.title("鸢尾花分类决策树 (max_depth=3)")
plt.show()
# 7. 查看特征重要性
print("\n特征重要性:")
for name, importance in zip(feature_names, dt_clf.feature_importances_):
print(f"{name}: {importance:.3f}")
代码解释:
- 我们加载数据,并按70/30的比例划分训练集和测试集。
- 创建一个决策树分类器。
max_depth=3是一个预剪枝参数,限制树最多生长3层。 - 使用
fit方法在训练集上构建决策树。 - 在测试集上进行预测,并输出准确率和详细的分类报告。
plot_tree函数将训练好的决策树可视化。filled=True会根据节点的基尼不纯度进行颜色填充,非常直观地展示了树的划分过程。- 最后,我们打印每个特征对于分类的重要性,
petal length (cm)和petal width (cm)通常最重要。
实践练习
练习1:基础应用
使用 wine 数据集 (from sklearn.datasets import load_wine),完成以下任务:
- 加载数据并划分训练集/测试集 (80%/20%)。
- 创建一个决策树分类器 (不限制深度)。
- 训练模型并在测试集上评估准确率。
- 可视化生成的决策树。
预期输出:
- 一个测试集准确率数值。
- 一棵完整的(可能很深的)决策树图形。
练习2:探索剪枝
在练习1的基础上,进行如下实验:
- 重新训练模型,但这次设置
max_depth=2。 - 比较限制深度前后,测试集准确率的变化。
- 观察并描述两棵可视化树的复杂度差异。
思考:为什么限制深度可能反而会提高测试集准确率(或至少防止其大幅下降)?
练习3:手动计算信息增益
假设有一个非常简单的数据集,只有一个特征 Temperature(取值:Hot, Mild, Cool)和一个二元标签 PlayTennis(Yes, No)。数据如下:
| Temperature | PlayTennis |
|---|---|
| Hot | No |
| Hot | No |
| Mild | Yes |
| Cool | Yes |
| Cool | Yes |
请手动计算:
- 整个数据集的初始熵
Entropy(PlayTennis)。 - 根据特征
Temperature划分后的条件熵。 - 最终的信息增益
Gain(PlayTennis, Temperature)。 提示:先计算每个子集的熵,再用加权平均求条件熵。
常见错误
- 忽略特征重要性:训练完模型后,不检查
feature_importances_,错过了解哪些特征对模型预测最关键的机会。 - 过度复杂的树:为了追求训练集上的100%准确率,不限制树的深度和节点数量,导致严重的过拟合,在测试集上表现糟糕。解决方法:务必使用预剪枝参数(
max_depth,min_samples_leaf等)或交叉验证来寻找最佳复杂度。 - 忽略剪枝:认为“树越大越准”,完全不考虑剪枝。理解预剪枝和后剪枝是控制模型复杂度、提升泛化能力的关键。
- 混淆分类与回归树:
DecisionTreeClassifier用于离散标签(分类),DecisionTreeRegressor用于连续标签(回归)。在标签是连续数值时误用了分类器。 - 可视化时中文乱码:在
plot_tree或其他绘图时,如果遇到中文字符显示为方框,需要设置字体,例如在plot_tree前添加:import matplotlib matplotlib.rcParams[‘font.sans-serif‘] = [‘SimHei‘] # 用黑体显示中文 matplotlib.rcParams[‘axes.unicode_minus‘] = False # 正常显示负号
小结
- 决策树通过一系列基于特征的问题(划分)对数据进行分类,模型直观,易于解释。
- 构建树的核心是选择最佳划分特征,常用标准是信息增益(基于熵)和基尼不纯度。
- 决策树极易过拟合,因此剪枝至关重要。
scikit-learn中主要通过max_depth、min_samples_leaf等参数进行预剪枝。 - 使用
plot_tree可以将训练好的决策树可视化,这是理解模型决策逻辑的利器。 - 虽然单棵决策树有时不稳定且容易过拟合,但它是许多强大集成方法(如下一节课将学的随机森林)的基石。