在上一章,我们学习了决策树,这个算法以其直观易懂的特点赢得了众多开发者的青睐。但决策树有一个致命的缺点:它太容易过拟合了。一颗未经限制的决策树会不断生长,直到完美记住训练数据的每一个细节,却对未见过的数据毫无泛化能力。
这个问题其实不难理解。想象一下,如果我们让一个人根据有限的经验做决策,他很容易受到个别异常案例的影响,形成过于极端的判断。但如果我们将十个人的判断综合起来,异常案例的影响就会被稀释,整体的判断会更加稳健。
随机森林正是基于这个思想构建的。它通过训练多棵决策树,然后综合它们的预测结果,显著提升了模型的性能和稳定性。这种方法被称为"集成学习"(Ensemble Learning),是现代机器学习中最强大的技术之一。
集成学习的思想
集成学习的核心理念是"三个臭皮匠,顶个诸葛亮"。多个弱学习器的组合往往能够超越任何单个强学习器。为什么这个思想有效?主要有两个原因:
-
多样性:不同的模型可能会犯不同的错误。当模型A错误地预测样本1时,模型B可能预测正确,综合它们的判断就能避免错误。
-
统计原理:多个独立随机变量的平均值,其方差会随着数量的增加而减小。这在数学上保证了集成学习的稳定性。
集成学习主要分为两种方法:
- Bagging(Bootstrap Aggregating):通过有放回采样训练多个模型,然后投票或平均。随机森林属于这种方法。
- Boosting:顺序训练模型,每个新模型专注于修正前一个模型的错误。XGBoost、LightGBM属于这种方法。
随机森林采用的就是Bagging策略。
随机森林的原理
随机森林由多棵决策树组成,每棵树都在不同的数据子集上训练,并且在每个分裂点只考虑随机选择的一部分特征。这种"随机"体现在两个层面:
- 数据随机:每棵树使用不同的训练数据子集(通过bootstrap采样)
- 特征随机:每个分裂点只考虑随机选择的部分特征
这种双重随机性确保了树与树之间的差异性,使得它们能够捕捉到数据中不同的模式,从而实现更好的集成效果。
Bagging的流程
Bagging(Bootstrap Aggregating)的步骤如下:
- 从原始数据集中有放回地随机抽取m个样本,构建新的训练集
- 在这个新训练集上训练一个模型
- 重复步骤1-2,共训练n个模型
- 对于分类问题,使用多数投票;对于回归问题,使用平均值
python
import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 生成数据
X, y = make_classification(n_samples=1000, n_features=10, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 手动实现Bagging
class SimpleBagging:
def __init__(self, n_estimators=10):
self.n_estimators = n_estimators
self.models = []
def fit(self, X, y):
n_samples = X.shape[0]
for _ in range(self.n_estimators):
# Bootstrap采样
indices = np.random.choice(n_samples, n_samples, replace=True)
X_bootstrap, y_bootstrap = X[indices], y[indices]
# 训练决策树
model = DecisionTreeClassifier(max_depth=5, random_state=None)
model.fit(X_bootstrap, y_bootstrap)
self.models.append(model)
def predict(self, X):
predictions = np.array([model.predict(X) for model in self.models])
# 多数投票
return np.round(predictions.mean(axis=0)).astype(int)
# 训练和测试
bagging = SimpleBagging(n_estimators=10)
bagging.fit(X_train, y_train)
y_pred = bagging.predict(X_test)
print(f"Bagging准确率: {accuracy_score(y_test, y_pred):.4f}")
随机森林的实现
1. 使用Scikit-learn实现随机森林
让我们使用Scikit-learn来实现一个随机森林分类器:
python
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# 生成数据
X, y = make_classification(
n_samples=1000, n_features=20, n_informative=15,
n_redundant=5, n_classes=2, random_state=42
)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 训练随机森林
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
# 预测
y_pred = rf.predict(X_test)
# 评估
accuracy = accuracy_score(y_test, y_pred)
print(f"随机森林准确率: {accuracy:.4f}")
print("\n分类报告:")
print(classification_report(y_test, y_pred))
2. 对比单棵决策树
让我们对比一下随机森林和单棵决策树的性能:
python
# 训练单棵决策树
dt = DecisionTreeClassifier(random_state=42)
dt.fit(X_train, y_train)
# 预测和评估
y_pred_dt = dt.predict(X_test)
accuracy_dt = accuracy_score(y_test, y_pred_dt)
print("=== 决策树 vs 随机森林 ===")
print(f"决策树准确率: {accuracy_dt:.4f}")
print(f"随机森林准确率: {accuracy:.4f}")
print(f"性能提升: {(accuracy - accuracy_dt) * 100:.2f}%")
3. 查看单棵树的结构
随机森林由多棵决策树组成,我们可以查看其中的任意一棵:
python
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
# 查看第一棵树
plt.figure(figsize=(20, 10))
plot_tree(rf.estimators_[0],
max_depth=3,
filled=True,
rounded=True,
fontsize=10)
plt.title('随机森林中的第一棵树', fontsize=16)
plt.show()
随机森林的关键参数
随机森林有多个重要参数需要调整,了解这些参数的作用对于调优模型至关重要。
1. n_estimators(树的数量)
树的数量越多,模型的稳定性越好,但计算成本也会增加。通常选择50-500之间的值。
python
# 比较不同树数量的随机森林
n_estimators_list = [10, 50, 100, 200, 500]
print("=== 不同树数量对比 ===")
for n in n_estimators_list:
rf = RandomForestClassifier(n_estimators=n, random_state=42)
rf.fit(X_train, y_train)
train_acc = accuracy_score(y_train, rf.predict(X_train))
test_acc = accuracy_score(y_test, rf.predict(X_test))
print(f"树数量={n:>3} | 训练集准确率: {train_acc:.4f} | 测试集准确率: {test_acc:.4f}")
2. max_depth(最大深度)
限制每棵树的最大深度可以防止单棵树过拟合。随机森林通过集成多棵树,已经大大降低了过拟合的风险,但限制深度仍然可以提高模型的泛化能力。
3. max_features(最大特征数)
每个分裂点考虑的最大特征数。这个参数控制了特征随机性,是随机森林的核心。常用值有:
- 'sqrt':√n_features(分类问题默认值)
- 'log2':log₂(n_features)
- None:使用所有特征
python
# 比较不同max_features
max_features_options = ['sqrt', 'log2', None]
print("\n=== 不同max_features对比 ===")
for mf in max_features_options:
rf = RandomForestClassifier(n_estimators=100, max_features=mf, random_state=42)
rf.fit(X_train, y_train)
train_acc = accuracy_score(y_train, rf.predict(X_train))
test_acc = accuracy_score(y_test, rf.predict(X_test))
print(f"max_features={str(mf):>5} | 训练集准确率: {train_acc:.4f} | 测试集准确率: {test_acc:.4f}")
4. min_samples_split和min_samples_leaf
这两个参数与决策树中的含义相同,用于控制单棵树的生长。
5. bootstrap
是否使用bootstrap采样。默认为True。如果设为False,所有树使用完整数据集训练,但仍然使用特征随机性。
特征重要性
随机森林的一个强大功能是能够计算特征重要性。这是通过计算每个特征在所有决策树中分裂时带来的平均不纯度减少量来实现的。
python
# 获取特征重要性
feature_importance = pd.DataFrame({
'特征': [f'特征{i+1}' for i in range(X.shape[1])],
'重要性': rf.feature_importances_
}).sort_values('重要性', ascending=False)
print("=== 特征重要性 ===")
print(feature_importance.head(10))
# 可视化
plt.figure(figsize=(12, 6))
plt.bar(feature_importance['特征'][:20], feature_importance['重要性'][:20])
plt.xticks(rotation=45, ha='right')
plt.xlabel('特征', fontsize=12)
plt.ylabel('重要性', fontsize=12)
plt.title('随机森林 - 特征重要性', fontsize=14)
plt.tight_layout()
plt.show()
特征重要性分析可以帮助我们:
- 理解哪些特征对预测结果影响最大
- 进行特征选择,剔除不重要的特征
- 获得业务洞察,为决策提供依据
Out-of-Bag (OOB) 误差评估
随机森林有一个独特的优势:可以使用Out-of-Bag(袋外)样本来评估模型性能,而不需要额外的验证集。
原理:在bootstrap采样时,每个样本有大约37%的概率未被选中。这些未被选中的样本称为OOB样本,可以作为"免费"的验证集。
python
# 使用OOB误差评估
rf_oob = RandomForestClassifier(
n_estimators=100,
oob_score=True, # 启用OOB评分
random_state=42
)
rf_oob.fit(X_train, y_train)
print("=== OOB误差评估 ===")
print(f"OOB准确率: {rf_oob.oob_score_:.4f}")
print(f"测试集准确率: {accuracy_score(y_test, rf_oob.predict(X_test)):.4f}")
print(f"\n解读:")
print(f" - OOB准确率与测试集准确率接近,说明模型泛化能力良好")
print(f" - OOB误差可以作为模型选择的依据,无需额外的验证集")
随机森林 vs 决策树
让我们全面对比一下随机森林和决策树:
| 特性 | 决策树 | 随机森林 |
|---|---|---|
| 准确率 | 较低 | 较高 |
| 过拟合风险 | 高 | 低 |
| 训练时间 | 快 | 慢 |
| 预测时间 | 快 | 慢(但可并行) |
| 可解释性 | 强 | 中等 |
| 特征重要性 | 支持 | 更稳定 |
| 内存占用 | 低 | 高 |
随机森林通过牺牲一些可解释性和训练速度,换取了更高的准确率和更好的泛化能力。
随机森林的实际应用
1. 信用评分
金融机构使用随机森林评估贷款申请人的信用风险。通过分析申请人的收入、负债、信用历史等多维度特征,可以准确预测违约概率。
python
# 信用评分示例(伪数据)
import pandas as pd
# 模拟信用数据
np.random.seed(42)
n_samples = 1000
credit_data = {
'年收入(万)': np.random.normal(15, 5, n_samples),
'负债率': np.random.uniform(0.1, 0.8, n_samples),
'信用卡数量': np.random.randint(1, 10, n_samples),
'逾期次数': np.random.poisson(0.5, n_samples),
'信用年限': np.random.randint(1, 20, n_samples),
}
df_credit = pd.DataFrame(credit_data)
# 生成标签(违约=1,不违约=0)
df_credit['是否违约'] = (
(df_credit['年收入(万)'] < 8) * 0.4 +
(df_credit['负债率'] > 0.6) * 0.3 +
(df_credit['逾期次数'] > 1) * 0.2 +
np.random.randn(n_samples) * 0.1
) > 0.3
df_credit['是否违约'] = df_credit['是否违约'].astype(int)
# 训练随机森林
X_credit = df_credit.drop('是否违约', axis=1)
y_credit = df_credit['是否违约']
X_train_c, X_test_c, y_train_c, y_test_c = train_test_split(
X_credit, y_credit, test_size=0.2, random_state=42, stratify=y_credit
)
rf_credit = RandomForestClassifier(n_estimators=100, random_state=42)
rf_credit.fit(X_train_c, y_train_c)
# 评估
accuracy_credit = accuracy_score(y_test_c, rf_credit.predict(X_test_c))
print(f"信用评分模型准确率: {accuracy_credit:.4f}")
# 特征重要性
feature_imp = pd.DataFrame({
'特征': X_credit.columns,
'重要性': rf_credit.feature_importances_
}).sort_values('重要性', ascending=False)
print("\n特征重要性:")
print(feature_imp)
2. 医疗诊断
医生可以使用随机森林根据患者的症状和检查结果诊断疾病。随机森林能够处理高维数据,自动识别关键指标。
3. 推荐系统
电商平台使用随机森林预测用户是否会购买某个商品,从而优化推荐策略。
4. 异常检测
网络安全领域使用随机森林识别异常流量和潜在攻击。随机森林能够识别复杂的攻击模式,减少误报率。
随机森林回归
随机森林不仅适用于分类问题,也适用于回归问题。对于回归问题,随机森林通过平均所有树的预测值来得到最终结果。
python
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score
# 生成回归数据
X_reg, y_reg = make_classification(
n_samples=1000, n_features=10, n_informative=8,
n_redundant=2, random_state=42
)
y_reg = X_reg[:, 0] * 2 + X_reg[:, 1] * 3 + np.random.randn(1000) * 0.5
# 划分数据集
X_train_reg, X_test_reg, y_train_reg, y_test_reg = train_test_split(
X_reg, y_reg, test_size=0.2, random_state=42
)
# 训练随机森林回归
rf_reg = RandomForestRegressor(n_estimators=100, random_state=42)
rf_reg.fit(X_train_reg, y_train_reg)
# 预测和评估
y_pred_reg = rf_reg.predict(X_test_reg)
mse = mean_squared_error(y_test_reg, y_pred_reg)
r2 = r2_score(y_test_reg, y_pred_reg)
print("=== 随机森林回归 ===")
print(f"均方误差: {mse:.4f}")
print(f"R²分数: {r2:.4f}")
实战技巧与最佳实践
1. 网格搜索调优
使用网格搜索寻找最佳超参数组合:
python
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [None, 10, 20],
'max_features': ['sqrt', 'log2'],
'min_samples_split': [2, 5, 10]
}
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1 # 使用所有CPU核心
)
grid_search.fit(X_train, y_train)
print("=== 最佳参数 ===")
print(grid_search.best_params_)
print(f"最佳准确率: {grid_search.best_score_:.4f}")
best_rf = grid_search.best_estimator_
2. 并行化训练
随机森林天然支持并行化,因为每棵树是独立训练的。设置n_jobs=-1可以使用所有CPU核心加速训练:
python
# 使用所有CPU核心
rf = RandomForestClassifier(n_estimators=100, n_jobs=-1, random_state=42)
3. 处理类别不平衡
当数据类别不平衡时,可以通过设置class_weight参数来调整:
python
# 自动平衡类别权重
rf_balanced = RandomForestClassifier(
n_estimators=100,
class_weight='balanced',
random_state=42
)
4. 特征选择
利用特征重要性进行特征选择:
python
# 选择重要性前50%的特征
threshold = np.percentile(rf.feature_importances_, 50)
selected_features = X.columns[rf.feature_importances_ > threshold]
print(f"原始特征数: {X.shape[1]}")
print(f"选择特征数: {len(selected_features)}")
print(f"选择特征: {selected_features.tolist()}")
X_selected = X[selected_features]
5. 早停法
虽然随机森林不像XGBoost那样支持真正的早停,但我们可以通过交叉验证选择合适的树数量:
python
from sklearn.model_selection import cross_val_score
n_estimators_list = range(10, 201, 10)
cv_scores = []
for n in n_estimators_list:
rf = RandomForestClassifier(n_estimators=n, random_state=42)
scores = cross_val_score(rf, X_train, y_train, cv=5, scoring='accuracy')
cv_scores.append(scores.mean())
# 找到最佳树数量
best_n = n_estimators_list[np.argmax(cv_scores)]
print(f"最佳树数量: {best_n}")
print(f"最高交叉验证准确率: {max(cv_scores):.4f}")
本章小结
随机森林是基于Bagging策略的强大集成学习方法,通过训练多棵决策树并综合它们的预测结果,显著提升了模型性能。本章我们学习了:
- 集成学习的思想:多个弱学习器的组合能够超越单个强学习器
- 随机森林的原理:数据随机和特征随机确保了树的多样性
- 随机森林的实现:使用Scikit-learn训练分类器和回归器
- 关键参数:n_estimators、max_depth、max_features等
- 特征重要性:随机森林提供稳定且有意义的特征重要性分析
- OOB误差:使用袋外样本评估模型性能
- 实际应用:信用评分、医疗诊断、推荐系统等
随机森林在工业界被广泛应用,是解决分类和回归问题的首选算法之一。它不需要复杂的调参就能获得很好的性能,而且对异常值不敏感,对特征缩放没有要求。