2025/10/28日 每日总结 决策树实战:C4.5算法+剪枝策略,解决过拟合难题

决策树实战:C4.5算法+剪枝策略,解决过拟合难题

继逻辑回归和基础分类模型后,这次聚焦决策树算法——深入实践C4.5算法的实现,重点测试预剪枝、后剪枝对模型性能的影响,最终在Iris数据集上实现了96.67%的准确率。这篇笔记整理了C4.5算法原理、剪枝逻辑和完整实验流程,适合想吃透决策树的小伙伴~

一、实验核心目标

  1. 深入理解C4.5决策树的核心原理(信息增益比分裂、剪枝逻辑)

  2. 掌握预剪枝和后剪枝的实现方法与参数调优

  3. 用五折交叉验证完成模型训练与多指标评估

  4. 对比不同剪枝策略(无剪枝、轻度/重度预剪枝、轻度/重度后剪枝)的效果

  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处理更复杂的数据集(如泰坦尼克号生存预测),进一步验证剪枝策略的通用性~

posted @ 2025-12-29 14:45  Moonbeamsc  阅读(2)  评论(0)    收藏  举报
返回顶端