本文目录导读:

在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)优势
- α值越小,树越大,越容易过拟合
- α值越大,树越小,可能欠拟合
- 通过交叉验证选择最佳α值
剪枝建议
- 优先使用预剪枝:计算效率高,适合大多数场景
- 必要时使用后剪枝:当预剪枝效果不佳时
- 组合使用:先做预剪枝控制大概范围,再用CCP精细调整
- 交叉验证:总是使用交叉验证选择参数
选择哪种剪枝方法取决于你的具体需求:如果追求模型解释性,可以稍大一些的树;如果追求泛化性能,需要更积极的剪枝。