决策树的剪枝是什么?预剪枝和后剪枝有什么区别?
简化版
剪枝(Pruning) 是决策树防止过拟合的核心手段——通过去掉一些分支、让树变简单,提升泛化能力。分两类:预剪枝(Pre-pruning) 在建树过程中提前停止分裂(如限制最大深度、叶子最小样本数、要求分裂带来的提升超过阈值),快但可能欠拟合(过早停止会错过后面有用的分裂)。后剪枝(Post-pruning) 先让树完全长成,再自底向上尝试把子树替换成叶子、用验证集看泛化是否提升,搜索更充分但计算与验证成本更高。原则:预剪枝提前缩小搜索、后剪枝从较大树中选择子树,实践中常两者结合。
详细版
为什么要剪枝: 决策树不加限制会一直分到每个叶子极纯,把训练集(含噪声)完全记住 → 过拟合、高方差,测试表现差。
预剪枝 vs 后剪枝:
| 预剪枝 | 后剪枝 | |
|---|---|---|
| 时机 | 建树时提前停止 | 建完整棵树后再剪 |
| 方向 | 自顶向下(边建边停) | 自底向上(从叶子往上剪) |
| 速度 | 快(树小,省算力) | 慢(先建满再剪) |
| 欠/过拟合 | 可能欠拟合(过早停止) | 一般泛化更好 |
| 代表方法 | max_depth、min_samples_leaf、min_impurity_decrease | 悲观剪枝(C4.5)、代价复杂度剪枝(CART/CCP) |
预剪枝常用停止条件:
- 树深度达上限、节点样本数低于阈值、叶子样本数低于阈值、纯度提升小于阈值、划分后验证集精度不升。
后剪枝方法:
- REP(错误率降低剪枝):用验证集,剪掉后错误率不上升就剪。
- 悲观剪枝(PEP,C4.5):用训练集加惩罚估计,无需单独验证集。
- 代价复杂度剪枝(CCP,CART):最小化
误差 + α×叶子数,用交叉验证选 α。
完整版教学
一、为什么决策树非剪不可
决策树如果不加任何限制,会一直分裂到每个叶子都极纯(甚至每个叶子只有一个样本)。这样的树把训练集的每个细节、包括噪声都记了下来——训练误差几乎为 0,但这是「背答案」,换到测试集就崩。这就是决策树天生容易过拟合、高方差的根源。
剪枝就是主动砍掉一些对泛化没帮助(甚至有害)的分支,让树变浅变简单,用一点训练精度换取更好的泛化。它是决策树对抗过拟合的头号武器(另一个是集成)。按剪枝发生的时机,分预剪枝和后剪枝。
二、预剪枝:边建树边喊停
预剪枝在建树过程中就设置条件,一旦满足就停止继续分裂,把当前节点直接变成叶子。常用停止条件(也就是 sklearn 里那些超参数):
max_depth:树的最大深度,到了就不再分。min_samples_split:节点样本数少于它就不再分裂。min_samples_leaf:分裂后若某叶子样本数太少,就不允许这次分裂。min_impurity_decrease:分裂带来的纯度提升小于阈值就不分(提升太小不值得)。- 验证集准则:若这次分裂不能提升验证集精度,就不分。
优点:树在生长时就被控制得较小,训练快、省内存、模型简单。
致命缺点——可能欠拟合:预剪枝是贪心地看「眼前」。有时某次分裂当前看提升不大(甚至没提升),但它之后能带来非常有用的深层分裂——预剪枝会因为「眼前提升不够」而提前停止,错过后面的好分裂。这种「目光短浅」导致预剪枝有欠拟合风险。
三、后剪枝:先长满,再往回砍
后剪枝反过来:先让决策树完全生长(尽情过拟合),然后自底向上逐个考察内部节点——尝试把某个子树整体替换成一个叶子节点(叶子类别取该子树样本中最多的类),如果替换后泛化能力不下降(甚至提升),就执行这次剪枝。
优点:后剪枝基于较大的候选树比较整棵子树的误差与复杂度,能降低预剪枝因局部早停而错过后续结构的风险。它通常需要更多训练与存储成本,泛化效果是否优于预剪枝仍要通过同一验证方案比较。
缺点:要先建完整棵大树再剪,计算和内存开销更大、更慢。
经典 CART 用代价复杂度目标 R_α(T)=R(T)+α|T| 生成一串嵌套子树:α 越大,对叶节点数量的惩罚越强,得到的树越小。训练误差必然偏爱更大的树,所以最终 α 应通过交叉验证选择;测试集只能用于最终一次评估,不能参与剪枝强度选择。
四、后剪枝的三种经典方法
1. REP(错误率降低剪枝,Reduced Error Pruning)
用独立的验证集:自底向上,对每个内部节点,比较「保留子树」和「剪成叶子」在验证集上的错误率,剪掉后错误率不升就剪。简单直接,但需要额外划出验证集(数据少时是负担)。
2. 悲观剪枝(PEP,C4.5 采用)
不需要单独验证集,只用训练集估计。它给训练误差加一个惩罚项(因为训练误差过于乐观),用这个「悲观」的误差估计来判断是否剪枝。好处是不浪费数据划验证集,速度快;是 C4.5 的默认方式。
3. 代价复杂度剪枝(CCP,CART 采用)
CART 的后剪枝,核心是在「误差」和「树的复杂度」之间权衡,最小化:
代价复杂度 = 误差(T) + α × |叶子数(T)|
- α 是复杂度惩罚系数:α 越大越倾向简单的树(叶子少)。
- 做法:对不同 α 生成一系列剪枝后的子树,再用交叉验证选出泛化最好的那棵(对应最优 α)。
- 这和正则化思想一致——
误差 + α×复杂度,用 α 控制模型简单程度。sklearn 的ccp_alpha参数就是它。
五、预剪枝 vs 后剪枝怎么选
| 场景 | 倾向 |
|---|---|
| 数据量大、算力紧张 | 预剪枝(快、省资源) |
| 希望从嵌套子树路径选复杂度 | 后剪枝 + 独立验证 |
| 数据少 | 后剪枝(预剪枝易欠拟合);PEP 不占验证集 |
实践建议:
- 单棵树想要好效果,后剪枝(尤其 CCP)通常更优。
- 工程上也常用预剪枝超参数(max_depth、min_samples_leaf 等)+ 交叉验证调参来控制复杂度,简单有效。
- 两者可结合:先预剪枝控制规模,再后剪枝精修。
六、常见追问
- 剪枝和集成什么关系? 都是对付过拟合。单棵树靠剪枝;随机森林靠 Bagging 多树平均降方差,单棵树反而可以不剪、长满(靠集成消化方差);GBDT 用浅树 + 正则。
- 预剪枝为什么会欠拟合? 贪心地提前停止,可能砍掉「当前提升小但后续有用」的分支。
- CCP 里的 α 怎么定? 生成 α 序列对应的子树,用交叉验证选泛化最好的。
- 不剪枝行不行? 单棵树几乎必过拟合;除非放进 Bagging 集成里靠平均消化方差。
七、用成本复杂度目标判断该不该砍
设子树有 5 个叶子,训练误差 0.10;把它替换成单叶后误差升到 0.14。成本复杂度目标 R(T)+α|T| 下,保留子树的目标是 0.10+5α,剪成单叶是 0.14+α;两者在 α=0.01 时相等。α 更大时复杂度代价占优,算法倾向剪枝。
R_alpha(T) = R(T) + alpha * |leaves(T)|
keep: 0.10 + 5*alpha
prune: 0.14 + 1*alpha
| 对象/方案 | 核心机制 | 选择或风险 |
|---|---|---|
| 预剪枝 | 生长过程中触发深度/样本/增益门槛 | 快,但可能错过后续有价值结构 |
| 后剪枝 | 先得到大树,再比较子树替换 | 搜索更充分,计算和验证成本更高 |
| 成本复杂度 | 用 α 权衡经验误差与叶数 | 产生一条嵌套子树路径供验证选择 |
生长大树 → 计算最弱环节 α → 依次剪出子树序列
→ 交叉验证选 α → 重训/定稿
记忆钩子:剪枝选择的是泛化复杂度,不是追求节点越少越好;α 应由未参与拟合的数据决定。
八、常见误区与追问
- 误区:预剪枝和后剪枝只是执行时间不同。 预剪枝可能提前阻断后续有用切分,搜索空间也不同。
- 误区:后剪枝一定优于预剪枝。 数据量、算力和验证噪声都会影响结果,没有无条件保证。
- 追问:ccp_alpha 越大树会怎样? 复杂度惩罚更强,通常得到叶子更少的子树路径。
- 追问:能用测试集选择剪枝强度吗? 不能,应使用训练内交叉验证或验证集,测试集只做最终评估。
- 追问:随机森林里的单树还要强剪枝吗? 通常允许较深树保留低偏差,再靠去相关平均降方差,但仍受叶样本等约束。
九、加强记忆
剪枝是决策树防过拟合的核心:不剪的树会把训练集(含噪声)背下来、高方差。预剪枝在建树时提前停止分裂(max_depth、min_samples_leaf、min_impurity_decrease 等),快、省资源,但贪心地提前停止可能错过后续好分裂而欠拟合;后剪枝先让树长满再自底向上把子树换成叶子(看泛化是否不降),搜索更充分但不保证泛化一定更好,方法有 REP(用验证集)、悲观剪枝 PEP(C4.5,只用训练集)、代价复杂度剪枝 CCP(CART,最小化 误差+α×叶子数,交叉验证选 α)。选择:大数据/紧算力用预剪枝,追求泛化用后剪枝;随机森林里单树可不剪、靠集成降方差。