Python脚本数据分类决策树如何剪枝

wen 实用脚本 2

本文目录导读:

Python脚本数据分类决策树如何剪枝

  1. 预剪枝(Pre-pruning)
  2. 后剪枝(Post-pruning)
  3. 交叉验证选择最佳剪枝参数
  4. 实战:完整的剪枝流程
  5. 关键总结

在Python中实现决策树剪枝主要通过两种方式:预剪枝后剪枝,我详细介绍一下这两种方法在scikit-learn中的实现。

预剪枝(Pre-pruning)

在决策树生长过程中提前停止分裂,最常用的方法:

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
# 加载数据
iris = load_iris()
X, y = iris.data, iris.target
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 对比不同剪枝参数的效果
param_comparison = {
    'max_depth': [3, 5, 10, None],  # 最大深度
    'min_samples_split': [2, 5, 10, 20],  # 内部节点最小样本数
    'min_samples_leaf': [1, 2, 5, 10],  # 叶节点最小样本数
    'max_leaf_nodes': [3, 5, 10, None]  # 最大叶节点数
}
# 示例:使用max_depth进行预剪枝
for max_depth in [3, 5, 10, None]:
    dt = DecisionTreeClassifier(
        max_depth=max_depth,
        random_state=42
    )
    dt.fit(X_train, y_train)
    train_score = dt.score(X_train, y_train)
    test_score = dt.score(X_test, y_test)
    print(f"max_depth={max_depth}: 训练集准确率={train_score:.3f}, 测试集准确率={test_score:.3f}")

后剪枝(Post-pruning)

使用代价复杂度剪枝(Cost Complexity Pruning, CCP),这是最常用的后剪枝方法:

from sklearn.tree import DecisionTreeClassifier
import numpy as np
def cost_complexity_pruning_example(X_train, X_test, y_train, y_test):
    """代价复杂度剪枝示例"""
    # 1. 训练一个完整(未剪枝)的决策树
    clf = DecisionTreeClassifier(random_state=42)
    # 2. 计算剪枝路径
    path = clf.cost_complexity_pruning_path(X_train, y_train)
    ccp_alphas = path.ccp_alphas  # 获取不同的alpha值
    impurities = path.impurities  # 对应的不纯度
    print(f"共有 {len(ccp_alphas)} 个不同的alpha值")
    print(f"alpha范围: {min(ccp_alphas):.4f} 到 {max(ccp_alphas):.4f}")
    # 3. 训练不同alpha值下的决策树
    clfs = []
    for ccp_alpha in ccp_alphas:
        clf = DecisionTreeClassifier(random_state=42, ccp_alpha=ccp_alpha)
        clf.fit(X_train, y_train)
        clfs.append(clf)
    # 4. 计算训练集和测试集准确率
    train_scores = [clf.score(X_train, y_train) for clf in clfs]
    test_scores = [clf.score(X_test, y_test) for clf in clfs]
    # 5. 找出最佳alpha值(测试集准确率最高时)
    best_index = np.argmax(test_scores)
    best_alpha = ccp_alphas[best_index]
    best_clf = clfs[best_index]
    print(f"\n最佳alpha值: {best_alpha:.4f}")
    print(f"最终树的大小: {best_clf.tree_.node_count} 个节点")
    print(f"测试集准确率: {test_scores[best_index]:.3f}")
    return best_clf, ccp_alphas, test_scores, train_scores
# 使用示例
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
best_tree, alphas, test_scores, train_scores = cost_complexity_pruning_example(
    X_train, X_test, y_train, y_test
)
# 可视化剪枝效果
def plot_pruning_effect(alphas, train_scores, test_scores):
    """可视化不同alpha值下的模型性能"""
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.plot(alphas, train_scores, marker='o', label='训练集', drawstyle="steps-post")
    ax.plot(alphas, test_scores, marker='*', label='测试集', drawstyle="steps-post")
    ax.set_xlabel('alpha')
    ax.set_ylabel('准确率')
    ax.set_title('代价复杂度剪枝效果')
    ax.legend()
    ax.grid(True)
    # 标记最佳alpha值
    best_idx = np.argmax(test_scores)
    ax.axvline(x=alphas[best_idx], color='r', linestyle='--', alpha=0.7)
    plt.show()
# plot_pruning_effect(alphas, train_scores, test_scores)

交叉验证选择最佳剪枝参数

使用GridSearchCV自动寻找最佳参数:

from sklearn.model_selection import GridSearchCV
def find_best_pruning_params(X_train, y_train, cv=5):
    """使用网格搜索寻找最佳剪枝参数"""
    # 定义参数网格
    param_grid = {
        'max_depth': [3, 5, 7, 9, None],
        'min_samples_split': [2, 5, 10, 20],
        'min_samples_leaf': [1, 2, 5, 10],
        'max_leaf_nodes': [None, 5, 10, 20],
        'ccp_alpha': [0.0, 0.001, 0.005, 0.01, 0.02, 0.05, 0.1]
    }
    # 创建决策树基础模型
    base_clf = DecisionTreeClassifier(random_state=42)
    # 网格搜索
    grid_search = GridSearchCV(
        base_clf, 
        param_grid, 
        cv=cv,
        scoring='accuracy',
        n_jobs=-1,
        verbose=1
    )
    grid_search.fit(X_train, y_train)
    print("最佳参数组合:")
    print(grid_search.best_params_)
    print(f"最佳交叉验证得分: {grid_search.best_score_:.3f}")
    return grid_search.best_estimator_
# 使用示例
best_model = find_best_pruning_params(X_train, y_train)

实战:完整的剪枝流程

from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.metrics import classification_report, confusion_matrix
import pandas as pd
class PrunedDecisionTreeDemo:
    """决策树剪枝完整演示"""
    def __init__(self, X_train, X_test, y_train, y_test):
        self.X_train = X_train
        self.X_test = X_test
        self.y_train = y_train
        self.y_test = y_test
    def no_pruning(self):
        """无剪枝的决策树"""
        clf = DecisionTreeClassifier(random_state=42)
        clf.fit(self.X_train, self.y_train)
        return clf
    def pre_pruning(self, max_depth=5, min_samples_split=10, min_samples_leaf=5):
        """预剪枝"""
        clf = DecisionTreeClassifier(
            max_depth=max_depth,
            min_samples_split=min_samples_split,
            min_samples_leaf=min_samples_leaf,
            random_state=42
        )
        clf.fit(self.X_train, self.y_train)
        return clf
    def post_pruning(self):
        """代价复杂度后剪枝"""
        # 先训练完整树
        clf = DecisionTreeClassifier(random_state=42)
        path = clf.cost_complexity_pruning_path(self.X_train, self.y_train)
        # 使用交叉验证选择最佳alpha
        from sklearn.model_selection import cross_val_score
        best_score = 0
        best_alpha = 0
        best_clf = None
        # 只测试部分alpha值以节省时间
        alphas_to_test = path.ccp_alphas[::len(path.ccp_alphas)//20 + 1]
        for alpha in alphas_to_test:
            clf = DecisionTreeClassifier(random_state=42, ccp_alpha=alpha)
            scores = cross_val_score(clf, self.X_train, self.y_train, cv=5)
            mean_score = scores.mean()
            if mean_score > best_score:
                best_score = mean_score
                best_alpha = alpha
        # 使用最佳alpha训练最终模型
        final_clf = DecisionTreeClassifier(random_state=42, ccp_alpha=best_alpha)
        final_clf.fit(self.X_train, self.y_train)
        print(f"最佳alpha值: {best_alpha:.4f}")
        print(f"交叉验证得分: {best_score:.3f}")
        return final_clf
    def compare_models(self):
        """比较不同剪枝策略"""
        models = {
            '无剪枝': self.no_pruning(),
            '预剪枝': self.pre_pruning(),
            '后剪枝': self.post_pruning()
        }
        results = []
        for name, model in models.items():
            train_score = model.score(self.X_train, self.y_train)
            test_score = model.score(self.X_test, self.y_test)
            tree_size = model.tree_.node_count
            results.append({
                '剪枝策略': name,
                '训练集准确率': f"{train_score:.3f}",
                '测试集准确率': f"{test_score:.3f}",
                '树节点数': tree_size
            })
        return pd.DataFrame(results)
# 使用示例
demo = PrunedDecisionTreeDemo(X_train, X_test, y_train, y_test)
results_df = demo.compare_models()
print("\n剪枝策略对比:")
print(results_df)

关键总结

预剪枝参数对比

参数 作用 常用范围 效果
max_depth 限制树的最大深度 3-10 最常用的剪枝参数
min_samples_split 内部节点的最小样本数 10-100 防止过细划分
min_samples_leaf 叶节点的最小样本数 5-50 确保叶节点统计意义
max_leaf_nodes 最大叶节点数 10-100 控制模型复杂度

后剪枝(CCP)优势

  • α值越小,树越大,越容易过拟合
  • α值越大,树越小,可能欠拟合
  • 通过交叉验证选择最佳α值

剪枝建议

  1. 优先使用预剪枝:计算效率高,适合大多数场景
  2. 必要时使用后剪枝:当预剪枝效果不佳时
  3. 组合使用:先做预剪枝控制大概范围,再用CCP精细调整
  4. 交叉验证:总是使用交叉验证选择参数

选择哪种剪枝方法取决于你的具体需求:如果追求模型解释性,可以稍大一些的树;如果追求泛化性能,需要更积极的剪枝。

抱歉,评论功能暂时关闭!