2025/10/28日 每日总结 决策树实战:C4.5算法+剪枝策略,解决过拟合难题
决策树实战:C4.5算法+剪枝策略,解决过拟合难题
继逻辑回归和基础分类模型后,这次聚焦决策树算法——深入实践C4.5算法的实现,重点测试预剪枝、后剪枝对模型性能的影响,最终在Iris数据集上实现了96.67%的准确率。这篇笔记整理了C4.5算法原理、剪枝逻辑和完整实验流程,适合想吃透决策树的小伙伴~
一、实验核心目标
-
深入理解C4.5决策树的核心原理(信息增益比分裂、剪枝逻辑)
-
掌握预剪枝和后剪枝的实现方法与参数调优
-
用五折交叉验证完成模型训练与多指标评估
-
对比不同剪枝策略(无剪枝、轻度/重度预剪枝、轻度/重度后剪枝)的效果
-
理解决策树过拟合的原因及解决方案
二、核心概念与算法原理
1. C4.5算法核心逻辑
C4.5是ID3算法的改进版,核心优化点:
-
用信息增益比替代信息增益选择特征,解决ID3偏向取值多的特征的问题
-
支持连续值特征和缺失值处理(本次实验聚焦分类核心逻辑)
-
自带剪枝机制,提升模型泛化能力
2. 剪枝策略对比
剪枝类型 核心逻辑 实现方式 优势 缺点 预剪枝 树生长过程中停止分裂 限制树深度、最小分裂样本数、最小叶节点样本数 计算效率高,避免冗余分裂 可能欠拟合(剪枝过度) 后剪枝 先建完整树,再删除冗余分支 代价复杂度剪枝(CCP)、错误率剪枝 泛化能力更强,不易欠拟合 计算成本高,耗时长 3. 关键参数说明
C4.5算法(基于sklearn实现)核心参数:
参数 含义 剪枝类型 作用 criterion='entropy' 分裂标准(熵=信息增益比) - 模拟C4.5核心逻辑 max_depth 树的最大深度 预剪枝 限制树生长,避免过拟合 min_samples_split 内部节点分裂最小样本数 预剪枝 样本数不足则不分裂 min_samples_leaf 叶节点最小样本数 预剪枝 叶节点样本过少则合并 ccp_alpha 代价复杂度剪枝参数 后剪枝 alpha越大,剪枝越彻底 三、完整代码实现
1. C4.5决策树封装类
class C45DecisionTree: """自定义C4.5决策树类(基于sklearn封装,模拟C4.5核心逻辑)""" def __init__(self, max_depth=None, min_samples_split=2, min_samples_leaf=1, ccp_alpha=0.0, random_state=42): """ 初始化参数:兼顾预剪枝和后剪枝 - max_depth: 预剪枝-最大深度 - min_samples_split: 预剪枝-最小分裂样本数 - min_samples_leaf: 预剪枝-最小叶节点样本数 - ccp_alpha: 后剪枝-代价复杂度参数 """ self.max_depth = max_depth self.min_samples_split = min_samples_split self.min_samples_leaf = min_samples_leaf self.ccp_alpha = ccp_alpha self.random_state = random_state self.model = None def fit(self, X, y): """训练模型:用entropy模拟信息增益比,实现C4.5核心逻辑""" self.model = DecisionTreeClassifier( criterion='entropy', # C4.5用信息增益比,sklearn中用entropy近似 splitter='best', # 选择最优分裂特征 max_depth=self.max_depth, min_samples_split=self.min_samples_split, min_samples_leaf=self.min_samples_leaf, ccp_alpha=self.ccp_alpha, random_state=self.random_state ) self.model.fit(X, y) return self def predict(self, X): """预测接口""" return self.model.predict(X) def get_params(self, deep=True): """获取参数(用于交叉验证)""" return { 'max_depth': self.max_depth, 'min_samples_split': self.min_samples_split, 'min_samples_leaf': self.min_samples_leaf, 'ccp_alpha': self.ccp_alpha, 'random_state': self.random_state }2. 核心实验流程
def main(): print("=" * 80) print("C4.5决策树实战:预剪枝vs后剪枝对比实验") print("=" * 80) # 1. 加载数据 X, y, feature_names, target_names = load_iris_data() # 2. 逻辑回归基准模型(对比用) print("\n📊 逻辑回归基准模型性能:") lr_results = logistic_regression_baseline(X, y) # 3. 无剪枝C4.5模型 print("\n" + "="*50) print("无剪枝C4.5决策树性能:") print("="*50) c45_no_pruning = C45DecisionTree(max_depth=None, ccp_alpha=0.0) c45_results = five_fold_cross_validation(c45_no_pruning, X, y, "无剪枝C4.5") # 4. 可视化决策树(无剪枝) plot_decision_tree(c45_no_pruning, feature_names, target_names, "c45_no_pruning.png") # 5. 剪枝策略对比(核心实验) pruning_results = compare_prepruning_postpruning(X, y, feature_names, target_names) # 6. 生成实验结果表格 generate_results_table(lr_results, c45_results, pruning_results) # 7. 实验分析 experimental_analysis(lr_results, c45_results, pruning_results) print("\n" + "="*80) print("实验完成!") print("生成文件:c45_no_pruning.png(无剪枝决策树)、剪枝策略性能对比.png") print("="*80) # 辅助函数:五折交叉验证 def five_fold_cross_validation(model, X, y, model_name="模型"): kf = KFold(n_splits=5, shuffle=True, random_state=42) acc_scores, prec_scores, rec_scores, f1_scores = [], [], [], [] print(f"\n{model_name} 五折交叉验证结果:") print("-" * 50) for fold, (train_idx, test_idx) in enumerate(kf.split(X), 1): X_train, X_test = X[train_idx], X[test_idx] y_train, y_test = y[train_idx], y[test_idx] model.fit(X_train, y_train) y_pred = model.predict(X_test) # 计算多指标 acc = accuracy_score(y_test, y_pred) prec = precision_score(y_test, y_pred, average='weighted', zero_division=0) rec = recall_score(y_test, y_pred, average='weighted', zero_division=0) f1 = f1_score(y_test, y_pred, average='weighted', zero_division=0) acc_scores.append(acc) prec_scores.append(prec) rec_scores.append(rec) f1_scores.append(f1) print(f"第{fold}折 - 准确率: {acc:.4f}, 精确率: {prec:.4f}, 召回率: {rec:.4f}, F1值: {f1:.4f}") # 计算平均值 avg_acc = np.mean(acc_scores) avg_prec = np.mean(prec_scores) avg_rec = np.mean(rec_scores) avg_f1 = np.mean(f1_scores) print("-" * 50) print(f"平均结果 - 准确率: {avg_acc:.4f}, 精确率: {avg_prec:.4f}, 召回率: {avg_rec:.4f}, F1值: {avg_f1:.4f}") return { 'accuracy': avg_acc, 'precision': avg_prec, 'recall': avg_rec, 'f1': avg_f1, 'accuracy_scores': acc_scores } # 剪枝策略对比函数 def compare_prepruning_postpruning(X, y, feature_names, target_names): """对比6种剪枝策略:无剪枝、轻度/重度预剪枝、轻度/重度后剪枝、混合剪枝""" models = { "无剪枝": C45DecisionTree(max_depth=None, min_samples_split=2, min_samples_leaf=1, ccp_alpha=0.0), "预剪枝(轻度)": C45DecisionTree(max_depth=3, min_samples_split=5, min_samples_leaf=2, ccp_alpha=0.0), "预剪枝(重度)": C45DecisionTree(max_depth=2, min_samples_split=10, min_samples_leaf=3, ccp_alpha=0.0), "后剪枝(轻度)": C45DecisionTree(max_depth=None, min_samples_split=2, min_samples_leaf=1, ccp_alpha=0.01), "后剪枝(重度)": C45DecisionTree(max_depth=None, min_samples_split=2, min_samples_leaf=1, ccp_alpha=0.05), "预剪枝+后剪枝": C45DecisionTree(max_depth=4, min_samples_split=4, min_samples_leaf=2, ccp_alpha=0.02) } results = {} for name, model in models.items(): print(f"\n正在评估:{name}") results[name] = five_fold_cross_validation(model, X, y, name) # 可视化对比结果 plot_performance_comparison(results) return results四、实验关键结果
1. 剪枝策略性能排名
剪枝策略 准确率 精确率 召回率 F1值 核心优势 后剪枝(重度) 0.9667 0.9699 0.9667 0.9667 泛化能力最强 预剪枝(轻度) 0.9600 0.9638 0.9600 0.9600 平衡性能与效率 预剪枝+后剪枝 0.9533 0.9587 0.9533 0.9533 稳定性高 无剪枝 0.9533 0.9587 0.9533 0.9532 拟合能力强但易过拟合 后剪枝(轻度) 0.9533 0.9587 0.9533 0.9532 小幅提升泛化能力 预剪枝(重度) 0.9467 0.9548 0.9467 0.9465 易欠拟合 2. 关键发现
-
后剪枝效果优于预剪枝:重度后剪枝准确率达96.67%,比无剪枝提升1.34%
-
预剪枝需谨慎调参:轻度预剪枝(max_depth=3)表现良好,重度预剪枝(max_depth=2)因剪枝过度导致欠拟合
-
混合剪枝未达预期:预剪枝+后剪枝的性能未超过单独重度后剪枝,可能是参数搭配不当
-
决策树vs逻辑回归:逻辑回归准确率97.33%略高于最优决策树,因Iris数据集近似线性可分
3. 可视化结果亮点
-
无剪枝决策树:深度达5层,叶节点较多,存在明显过拟合迹象
-
重度后剪枝决策树:深度降至3层,删除冗余分支,保留核心分类逻辑
-
性能对比图:后剪枝在准确率、F1值上全面领先,预剪枝在效率上更有优势
五、实战心得与踩坑记录
1. 剪枝策略的选择逻辑
-
小数据集(如Iris):优先后剪枝,虽然计算成本高,但泛化能力更强
-
大数据集:优先预剪枝,兼顾性能和效率,避免构建完整树耗时过长
-
复杂数据:可尝试混合剪枝(轻度预剪枝+轻度后剪枝),平衡过拟合和欠拟合
2. 参数调优的关键技巧
-
预剪枝参数:max_depth是最敏感参数,建议从3开始尝试,逐步调整
-
后剪枝参数:ccp_alpha从小值开始(0.01),过大易导致欠拟合
-
最小样本数:min_samples_leaf建议设置为2-5,避免单个样本构成叶节点
3. 决策树的优缺点总结
-
优点:可解释性强(能可视化决策逻辑)、无需特征标准化、对缺失值不敏感
-
缺点:易过拟合(需剪枝)、对噪声数据敏感、分类边界不够平滑
-
适用场景:需要解释决策过程的场景(如医疗诊断、风控规则)、中小规模数据集
这次实验让我深刻理解了“剪枝是决策树的灵魂”——未经剪枝的决策树虽然在训练集上表现完美,但泛化能力差;而合理的剪枝策略能让模型在新数据上保持稳定性能。后续可以尝试用C4.5处理更复杂的数据集(如泰坦尼克号生存预测),进一步验证剪枝策略的通用性~

浙公网安备 33010602011771号