Lesson 08
决策树:用 if-else 做预测
决策树是最直观可解释的模型。本章讲解树的构建过程(信息增益/基尼系数)、剪枝控制过拟合、以及 sklearn 的 DecisionTreeClassifier/Regressor 实战。
本章细分学习地图
| 模块 | 拆细学习单元 | 完成后应产出 | 掌握标准 |
|---|---|---|---|
| 构建过程 | 递归分裂、叶节点、预测路径。 | 手画一棵简单决策树。 | 能说清分裂的停止条件。 |
| 分裂标准 | 熵、信息增益、基尼不纯度。 | 手算一个分裂的信息增益。 | 能区分熵和基尼的差异。 |
| 剪枝 | max_depth、min_samples_leaf、ccp_alpha。 | 不同深度对比实验。 | 能解释过拟合和剪枝的关系。 |
| 实战 | 训练、预测、可视化、特征重要性。 | 完整分类代码。 | 能解读 feature_importances_。 |
学习目标
决策树构建流程
本章完成度
已完成 0/4
决策树的构建过程
决策树通过贪心递归分裂构建:在每个节点选择一个特征和阈值,将数据分成两部分,使子节点的"不纯度"最小。
遍历所有特征和所有阈值,找到让子节点不纯度下降最多的分裂方式。
对每个子节点重复步骤 1,直到满足停止条件。
分类:叶中多数类;回归:叶中目标均值。
决策树每一步只考虑当前节点的最优分裂,不回溯。这意味着它不一定能找到全局最优树,但速度很快。
分裂标准
| 标准 | 特点 | sklearn 参数 |
|---|---|---|
| Gini | 计算更快,sklearn 默认。偏好较大的纯子集。 | criterion="gini" |
| Entropy | 信息论基础。在某些数据上可能产生更平衡的树。 | criterion="entropy" |
Gini 和 Entropy 在大多数数据集上产生几乎相同的树。默认 Gini 即可,除非有特殊理由。
剪枝:控制过拟合
不加限制的决策树会长到完美拟合训练集(每个叶只有一个样本),严重过拟合。常用剪枝参数:
| 参数 | 作用 | 建议 |
|---|---|---|
max_depth | 限制树的最大深度。 | 从 3-10 开始,用 CV 选择。 |
min_samples_split | 节点分裂所需的最少样本数。 | 默认 2,增大可减少过拟合。 |
min_samples_leaf | 叶节点最少样本数。 | 增大可防止叶太小(噪声主导)。 |
ccp_alpha | 代价复杂度剪枝(后剪枝)。 | 用 cost_complexity_pruning_path 找最佳值。 |
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
X, y = load_iris(return_X_y=True)
for depth in [1, 2, 3, 5, None]:
dt = DecisionTreeClassifier(max_depth=depth, random_state=42)
score = cross_val_score(dt, X, y, cv=5).mean()
print(f"max_depth={str(depth):>4s} 准确率={score:.3f}")
互动实验:树深度如何导致过拟合
下面用一份带少量噪声样本的二分类数据(●训练样本 / ▲测试样本)实时构建一棵决策树。拖动 max_depth 滑块,观察决策边界如何从简单矩形变成紧贴噪声点的锯齿形状,以及训练集准确率和测试集准确率如何分道而行。
- 训练准确率
- 0
- 测试准确率
- 0
- 叶子节点数
- 0
max_depth 较小时边界简单、训练和测试准确率都不高(欠拟合)。随着深度增加,训练准确率会一路逼近 100%——树在努力"记住"包括噪声点在内的每一个训练样本;但测试准确率往往在某个深度后不再提升甚至下降,因为树学到的是训练集里的噪声而非真实规律。这正是决策树需要剪枝(限制 max_depth)的原因。
sklearn 实战
from sklearn.datasets import load_wine
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.model_selection import train_test_split
X, y = load_wine(return_X_y=True)
feature_names = load_wine().feature_names
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(X_train, y_train)
print(f"训练准确率: {dt.score(X_train, y_train):.3f}")
print(f"测试准确率: {dt.score(X_test, y_test):.3f}")
# 文本可视化
print("\n树结构:")
print(export_text(dt, feature_names=list(feature_names), max_depth=3))
# 特征重要性
print("\n特征重要性:")
for name, imp in sorted(zip(feature_names, dt.feature_importances_),
key=lambda x: -x[1])[:5]:
print(f" {name}: {imp:.3f}")
决策树不需要特征缩放,能自动捕捉非线性关系和特征交互。但单棵树方差大,容易过拟合。下一章的集成方法(随机森林)通过组合多棵树解决这个问题。
练习题
第 1 题:一个节点有 10 个正样本和 10 个负样本,基尼不纯度是多少?
参考思路:Gini = 1 − (0.5² + 0.5²) = 1 − 0.5 = 0.5(最大不纯度)。
第 2 题:max_depth=1 的决策树叫什么?它有什么特点?
参考思路:叫决策桩(Decision Stump)。只做一次分裂,模型极简单(高偏差),常用作弱学习器组合(如 AdaBoost)。
检查点
离开本章前,请确认:
- 能解释决策树的贪心递归分裂过程。
- 能区分基尼不纯度和信息熵,知道 sklearn 默认用 Gini。
- 能用 max_depth、min_samples_leaf 等参数控制过拟合。
- 能用 export_text 或 plot_tree 可视化树结构,并解读 feature_importances_。