39·监督学习-分类进阶

决策树:原理、构建与剪枝

classificationtree-based

第39课:决策树——原理、构建与剪枝

学习目标

完成本课学习后,你将能够:

  1. 理解 决策树用于分类的基本工作原理和流程。
  2. 掌握 使用信息增益(或基尼不纯度)选择最优划分特征的核心概念。
  3. 运用 Python 的 scikit-learn 库构建一个决策树分类模型。
  4. 解释 什么是过拟合,并理解预剪枝和后剪枝对模型泛化能力的重要性。

核心概念

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}")

代码解释

  1. 我们加载数据,并按70/30的比例划分训练集和测试集。
  2. 创建一个决策树分类器。max_depth=3 是一个预剪枝参数,限制树最多生长3层。
  3. 使用 fit 方法在训练集上构建决策树。
  4. 在测试集上进行预测,并输出准确率和详细的分类报告。
  5. plot_tree 函数将训练好的决策树可视化。filled=True 会根据节点的基尼不纯度进行颜色填充,非常直观地展示了树的划分过程。
  6. 最后,我们打印每个特征对于分类的重要性,petal length (cm)petal width (cm) 通常最重要。

实践练习

练习1:基础应用

使用 wine 数据集 (from sklearn.datasets import load_wine),完成以下任务:

  1. 加载数据并划分训练集/测试集 (80%/20%)。
  2. 创建一个决策树分类器 (不限制深度)。
  3. 训练模型并在测试集上评估准确率。
  4. 可视化生成的决策树。

预期输出

  • 一个测试集准确率数值。
  • 一棵完整的(可能很深的)决策树图形。

练习2:探索剪枝

在练习1的基础上,进行如下实验:

  1. 重新训练模型,但这次设置 max_depth=2
  2. 比较限制深度前后,测试集准确率的变化。
  3. 观察并描述两棵可视化树的复杂度差异。

思考:为什么限制深度可能反而会提高测试集准确率(或至少防止其大幅下降)?

练习3:手动计算信息增益

假设有一个非常简单的数据集,只有一个特征 Temperature(取值:Hot, Mild, Cool)和一个二元标签 PlayTennis(Yes, No)。数据如下:

TemperaturePlayTennis
HotNo
HotNo
MildYes
CoolYes
CoolYes

请手动计算:

  1. 整个数据集的初始熵 Entropy(PlayTennis)
  2. 根据特征 Temperature 划分后的条件熵。
  3. 最终的信息增益 Gain(PlayTennis, Temperature)提示:先计算每个子集的熵,再用加权平均求条件熵。

常见错误

  1. 忽略特征重要性:训练完模型后,不检查 feature_importances_,错过了解哪些特征对模型预测最关键的机会。
  2. 过度复杂的树:为了追求训练集上的100%准确率,不限制树的深度和节点数量,导致严重的过拟合,在测试集上表现糟糕。解决方法:务必使用预剪枝参数(max_depth, min_samples_leaf 等)或交叉验证来寻找最佳复杂度。
  3. 忽略剪枝:认为“树越大越准”,完全不考虑剪枝。理解预剪枝和后剪枝是控制模型复杂度、提升泛化能力的关键。
  4. 混淆分类与回归树DecisionTreeClassifier 用于离散标签(分类),DecisionTreeRegressor 用于连续标签(回归)。在标签是连续数值时误用了分类器。
  5. 可视化时中文乱码:在 plot_tree 或其他绘图时,如果遇到中文字符显示为方框,需要设置字体,例如在 plot_tree 前添加:
    import matplotlib
    matplotlib.rcParams[‘font.sans-serif‘] = [‘SimHei‘] # 用黑体显示中文
    matplotlib.rcParams[‘axes.unicode_minus‘] = False   # 正常显示负号
    

小结

  • 决策树通过一系列基于特征的问题(划分)对数据进行分类,模型直观,易于解释。
  • 构建树的核心是选择最佳划分特征,常用标准是信息增益(基于熵)和基尼不纯度
  • 决策树极易过拟合,因此剪枝至关重要。scikit-learn 中主要通过 max_depthmin_samples_leaf 等参数进行预剪枝
  • 使用 plot_tree 可以将训练好的决策树可视化,这是理解模型决策逻辑的利器。
  • 虽然单棵决策树有时不稳定且容易过拟合,但它是许多强大集成方法(如下一节课将学的随机森林)的基石。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「随机森林与 Bagging」 以巩固所学知识。