一、为什么要剪枝
- 决策树容易对训练集过拟合(尤其树生长完全时),表现为训练误差低但泛化性能差。
- 剪枝通过限制树的复杂度或回退不可靠的分支,改善泛化能力、降低过拟合并提高模型稳定性。

二、两类剪枝总体区别
- 预剪枝(pre-pruning / early stopping):在树生成过程“提前”阻止某些划分发生(在生成阶段就停止分裂)。
  - 优点:节省训练时间、简单高效,防止产生非常大树。
  - 缺点:可能过早停止导致欠拟合;依赖设置的阈值较敏感。
- 后剪枝(post-pruning / subtree pruning):先把树完全生长(或深度足够大),再在树上根据某种准则剪掉不必要的子树(在生成后修剪)。
  - 优点:通常效果更好(因为先观察完整树再决定哪些分支是多余的),更灵活。
  - 缺点:需要额外验证集或交叉验证,计算开销更大。

三、常见的预剪枝策略(生成时停止规则)
- 基于样本数:
  - min_samples_split:一个节点至少需要多少样本才能分裂(如 >= 2、10、50等)。
  - min_samples_leaf:叶节点最少样本数。
- 基于树结构:
  - max_depth:最大深度限制。
  - max_leaf_nodes:最大叶子节点数。
- 基于信息增益/杂质变化:
  - min_impurity_decrease:只有当分裂带来的 impurity 减少 ≥ 阈值时才分裂。
- 统计检验或启发式:
  - 卡方检验(chi-square pruning):若分裂后类分布无显著差异则不分裂。
  - 信息论准则(MDL):若编码代价没有明显下降则停止分裂。

四、常见的后剪枝方法
- 简单的验证集剪枝(Reduced Error Pruning):
  - 思路:用独立验证集,从叶子向上遍历节点,若把某子树替换为叶节点(以该子树多数类作为预测)能在验证集上不降低或提高准确率,则执行替换。
  - 优点直观且效果好,但需要保留验证集。
- 代价复杂度剪枝(Cost-Complexity Pruning, CART 的 ccp_alpha):
  - 定义目标:R_α(T) = R(T) + α |T|,R(T) 为树的误差(例如训练误差或基于节点的误差),|T| 是叶节点数,α 为惩罚系数。
  - 通过逐步增加 α 得到一系列子树(pruning path),然后用交叉验证/验证集选择最优 α。
- 误差后验估计(pessimistic error pruning):
  - 使用带置信区间的误差估计(如加上 0.5 项的惩罚)来判断是否剪枝。
- 子树替换和子树修剪:
  - 子树替换:用单个叶子替换整个子树(上面的 reduced error 就是这种形式)。
  - 子树修剪:保留子树的某些分支或把两个子树合并为一个规则。

五、算法流程(简化步骤)
- 预剪枝(示例:基于 min_samples_leaf):
  1. 从根节点开始构建树。
  2. 在每个候选分裂点评估增益;若当前节点样本数 < min_samples_split 或分裂后任何叶子样本数 < min_samples_leaf,拒绝分裂,设为叶子。
  3. 继续直到所有节点被检查。
- 后剪枝(示例:验证集的 Reduced Error Pruning):
  1. 使用训练集把树完全生长(或很深)。
  2. 使用单独验证集评估当前树性能。
  3. 从叶子向上遍历内部节点:对每个内部节点,尝试将其子树替换为叶子(叶子预测为子树内训练样本多数类),在验证集上如果性能不下降则保留替换。
  4. 重复直到没有可剪枝节点。

六、在 sklearn 中的实现建议(实践示例)
- 预剪枝:DecisionTreeClassifier/Regressor 支持很多参数:
  - max_depth、min_samples_split、min_samples_leaf、max_leaf_nodes、min_impurity_decrease 等。
  - 推荐用 GridSearchCV 或 RandomizedSearchCV 在交叉验证上搜索这些超参数。
- 后剪枝(sklearn 的 cost-complexity 剪枝,CART 风格):
  - sklearn >= 0.22 支持 cost_complexity_pruning_path 和 ccp_alpha:
    - 先用训练集 fit 一个树(通常不强制限制深度)。
    - 调用 clf.cost_complexity_pruning_path(X_train, y_train) 获取一系列 ccp_alphas。
    - 对每个 alpha 训练一个新的树(DecisionTreeClassifier(ccp_alpha=alpha)),在验证集或通过交叉验证评估,选择能带来最好泛化的 alpha。
  - sklearn 没有内置 Reduced Error Pruning;可以手动实现(需要遍历节点并测验替换,比较麻烦)。

# 预剪枝示例与后剪枝示例(sklearn)
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.tree import DecisionTreeClassifier
import numpy as np

X, y = load_iris(return_X_y=True)
X_train_full, X_test, y_train_full, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
X_train, X_val, y_train, y_val = train_test_split(X_train_full, y_train_full, test_size=0.25, random_state=0)  # 训练/验证/测试 = 60/20/20

# 1) 预剪枝:用 GridSearchCV 搜索 max_depth, min_samples_leaf
param_grid = {
    "max_depth": [2, 3, 4, None],
    "min_samples_leaf": [1, 2, 4, 8],
    "min_impurity_decrease": [0.0, 1e-3, 1e-2]
}
clf = DecisionTreeClassifier(random_state=0)
gs = GridSearchCV(clf, param_grid, cv=5)
gs.fit(X_train, y_train)
print("Best pre-prune params:", gs.best_params_)
print("Val acc (pre-prune):", gs.best_estimator_.score(X_val, y_val))

# 2) 后剪枝(cost-complexity)
clf_full = DecisionTreeClassifier(random_state=0)
clf_full.fit(X_train, y_train)
path = clf_full.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas = path.ccp_alphas
# 为每个 alpha 训练并评估
clfs = []
val_scores = []
for a in ccp_alphas:
    clf_a = DecisionTreeClassifier(random_state=0, ccp_alpha=a)
    clf_a.fit(X_train, y_train)
    clfs.append(clf_a)
    val_scores.append(clf_a.score(X_val, y_val))

best_idx = np.argmax(val_scores)
best_alpha = ccp_alphas[best_idx]
best_tree = clfs[best_idx]
print("Best ccp_alpha:", best_alpha)
print("Val acc (post-prune):", val_scores[best_idx])
print("Test acc of best post-pruned:", best_tree.score(X_test, y_test))

七、如何选择预剪枝 vs 后剪枝(实践建议)
- 数据量大、需要速度优先:优先考虑预剪枝(限制深度、min_samples_leaf 等),因为训练速度快且内存/时间占用低。
- 数据量中等或需要最佳泛化:建议先完全生长再后剪枝(如果能承担验证/交叉验证开销),通常能得到更好的模型。
- 结合方式:可先设置较宽松的预剪枝限制以避免极端大型树,再用后剪枝精调(比如设置 max_depth=30,但用 ccp_alpha 做最后剪枝)。
- 不用单纯追求低训练误差,推荐用验证集或交叉验证来对比不同剪枝参数的泛化性能。

八、选择剪枝参数(实践技巧)
- 使用交叉验证选择 max_depth、min_samples_leaf 或 ccp_alpha。
- 若类别不平衡,剪枝时使用合适的评分指标(如 F1、AUC)而不是单看准确率。
- 在 ccp_alpha 路径上通常从 0(不惩罚)到较大值(非常小的树)逐步评估;多数情况下 alpha 的最优值不是 0。
- 可绘制“复杂度-性能”曲线(如叶子数 vs 验证得分,或 ccp_alpha vs 验证得分)帮助决策。

九、优缺点汇总(简短)
- 预剪枝:速度快、实现简单、可能欠拟合、超参敏感。
- 后剪枝:泛化更好、开销大、需要额外验证数据或交叉验证、实现稍复杂(但 sklearn 的 ccp_alpha 帮助大)。

十、常见误区
- 误区:树越深越好。事实:过深很可能过拟合,剪枝能改善泛化。
- 误区:只看训练误差选择剪枝强度。应使用验证集或交叉验证。
- 误区:sklearn 不支持后剪枝。实际上 sklearn 提供了 cost_complexity_pruning_path 与 ccp_alpha。

十一、总结性建议(工程流程)
1. 先对数据做必要预处理(缺失值、类别编码、特征缩放通常对树影响较小)。
2. 初步用较宽松的预剪枝(例如 min_samples_leaf=2~5, max_depth=None),快速训练观察过拟合情况。
3. 如果需要更好泛化,用 cost-complexity 后剪枝(获取 ccp_alphas,然后用交叉验证或验证集选 alpha)。
4. 最终评估时用独立测试集验证模型性能,并记录训练/验证/测试误差、树深度、叶子数等诊断信息。
5. 对于生产场景,考虑模型的可解释性(更小的树更易解释)与预测性能做权衡。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐