5
决策树与随机森林
Decision Tree & Random Forest
决策树就是"二十个问题游戏":每个节点问一个 yes/no 问题,叶子节点给答案。可解释性无敌,但单棵树容易过拟合。随机森林是"三个臭皮匠顶个诸葛亮"——种一堆树投票。
决策树怎么分裂
论信息熵 / 基尼系数
信息熵:度量"混乱程度",全是一类熵为 0,一半一半熵最大。信息增益:分裂前的熵 - 分裂后的加权熵,越大说明这次分裂越有用。基尼系数:随机抽两个样本类别不一样的概率,和熵效果差不多,算得更快(sklearn 默认)。CART 用基尼,ID3 用信息增益,C4.5 用增益率。
基尼:Gini = 1 - Σ pᵢ²
剪枝:防过拟合
| 方法 | 说明 |
|---|---|
| 预剪枝 | 构建树时就限制:max_depth、min_samples_split、min_samples_leaf。快,常用。 |
| 后剪枝 | 先长成完全树,再自底向上剪掉不提升验证集精度的分支。更精细但慢。 |
集成学习三大门派
| 方法 | 怎么训 | 代表 |
|---|---|---|
| Bagging | 并行训多个模型,投票/平均。降方差。 | 随机森林 |
| Boosting | 串行训,每个新模型纠正前面的错。降偏差。 | GBDT / XGBoost / AdaBoost |
| Stacking | 把多个模型的预测当新特征,训一个元模型。 | StackingClassifier |
随机森林:Bagging + 特征随机
论为什么有效
随机森林做了两层随机:样本随机(Bootstrap 有放回抽样)+ 特征随机(每次分裂只看一部分特征)。这样种出来的树"长得不一样",投票时降低相关性,方差比单棵树小很多。OOB(袋外误差):每棵树没抽到的样本刚好可以当验证集,免费评估。
完整案例:客户流失预测
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, roc_auc_score
X, y = make_classification(
n_samples=5000, n_features=20, n_informative=10,
n_redundant=5, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 单棵决策树:容易过拟合
tree = DecisionTreeClassifier(max_depth=None, random_state=42)
tree.fit(X_train, y_train)
print("决策树 训练集:", tree.score(X_train, y_train))
# 决策树 训练集:1.0(背答案了)
print("决策树 测试集:", tree.score(X_test, y_test))
# 决策树 测试集:0.91
# 随机森林:100 棵树投票
rf = RandomForestClassifier(
n_estimators=200, max_depth=None,
min_samples_leaf=2, oob_score=True, random_state=42, n_jobs=-1
)
rf.fit(X_train, y_train)
print("随机森林 训练集:", rf.score(X_train, y_train))
# 随机森林 训练集:0.998
print("随机森林 测试集:", rf.score(X_test, y_test))
# 随机森林 测试集:0.95
print("OOB 分数:", rf.oob_score_)
# OOB 分数:0.947
# 特征重要性
print("Top 5 重要特征:")
for i in np.argsort(rf.feature_importances_)[::-1][:5]:
print(f" 特征 {i:2d}: {rf.feature_importances_[i]:.3f}")
# 交叉验证
scores = cross_val_score(rf, X, y, cv=5, scoring="roc_auc")
print(f"5 折 ROC-AUC:{scores.mean():.3f} ± {scores.std():.3f}")
# 5 折 ROC-AUC:0.987 ± 0.006
决策树可视化
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
plt.figure(figsize=(20,10))
plot_tree(tree, feature_names=iris.feature_names,
class_names=iris.target_names, filled=True)
plt.savefig("tree.png", dpi=150, bbox_inches="tight")
# 导出为文本,看规则
from sklearn.tree import export_text
print(export_text(tree, feature_names=iris.feature_names))
# |--- petal length (cm) <= 2.45
# | |--- class: setosa
# |--- petal length (cm) > 2.45
# | |--- petal width (cm) <= 1.75
# | | |--- class: versicolor
决策树预剪枝参数
| 参数 | 作用 |
|---|---|
| max_depth | 最大深度,最常用。从 3~10 开始试。 |
| min_samples_split | 节点至少多少样本才继续分裂,默认 2。 |
| min_samples_leaf | 叶子至少多少样本,常用 5~20。 |
| max_leaf_nodes | 最多叶子数。 |
| max_features | 每次分裂考虑多少特征(随机森林用 sqrt)。 |
Stacking:用模型预测当新特征
from sklearn.ensemble import StackingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
estimators = [
("rf", RandomForestClassifier(n_estimators=100)),
("svm", SVC(probability=True)),
("lr", LogisticRegression()),
]
stack = StackingClassifier(
estimators=estimators,
final_estimator=LogisticRegression(),
cv=5
)
stack.fit(X_train, y_train)
# 基模型预测当新特征,元模型(LR)再学怎么组合
本章面试题
面试 · 决策树 / 随机森林
Q1. 决策树为什么容易过拟合?
查看答案
不限制深度的话,每片叶子只放一个样本,把训练数据完全背下来。对策:max_depth、min_samples_leaf、max_leaf_nodes。
Q2. 随机森林为什么比单棵树强?
查看答案
两层随机性(样本 + 特征)让树之间不相关,投票后方差大幅降低。"三个臭皮匠"。
Q3. 信息熵和基尼系数怎么选?
查看答案
效果差不多,基尼算得快(sklearn 默认);熵稍微更"纯"一点。
Q4. Bagging 和 Boosting 区别?
查看答案
Bagging 并行、降方差;Boosting 串行、降偏差。前者适合强学习器(深树),后者适合弱学习器(浅树)。