机器学习之随机森林详解

摘要

随机森林(Random Forest)是一种基于Bagging集成学习思想的 ensemble method,通过构建多棵决策树并综合其预测结果来实现分类和回归任务。本文详细介绍了随机森林的核心原理、关键超参数、OOB误差估计机制,以及其在特征重要性分析、异常检测等场景中的应用。文中提供了完整的Python代码示例,涵盖鸢尾花分类、OOB误差估计、特征重要性可视化和超参数调优等实战内容。所有代码均基于scikit-learn库,可直接运行。

关键词:随机森林;Bagging;集成学习;决策树;OOB误差;特征重要性;scikit-learn


1. 引言

在机器学习领域,没有一种算法能够适用于所有场景。每种模型都有其优势和局限性,而集成学习(Ensemble Learning)正是通过组合多个模型来弥补单一模型的不足。随机森林作为集成学习中最具代表性的算法之一,凭借其优异的性能、较强的抗过拟合能力和易用性,在学术界和工业界都得到了广泛应用。

从Kaggle竞赛的历史来看,随机森林及其变体在众多表格数据分类和回归任务中表现出色,常常作为强有力的基准模型(Baseline)。本文将系统性地介绍随机森林的原理、实现细节和使用技巧,帮助读者全面掌握这一重要算法。


2. 集成学习基础

2.1 为什么需要集成学习?

在讨论随机森林之前,我们需要理解集成学习背后的核心思想。想象一下这样一个场景:你要决定是否投资某只股票,你会怎么做?你可能会咨询多位朋友的意见,然后综合他们的判断做出最终决策。如果其中一位朋友给出了过于极端的建议,而其他几位都给出了相反的建议,你自然会倾向于相信多数人的判断。这就是集成学习的基本哲学------群体的智慧往往优于个体

在机器学习中,单个模型(无论是决策树、SVM还是神经网络)都可能存在偏差或方差问题。通过构建多个模型并综合它们的预测结果,我们可以获得更稳定、更准确的预测。

2.2 Bagging(Bootstrap Aggregating)原理

Bagging是随机森林的基石,其核心思想可以通过以下步骤理解:

步骤一:有放回随机采样(Bootstrap Sampling)

假设我们有一个包含N个样本的训练数据集。从中随机抽取一个样本,复制到新的采样集中,然后将原样本放回数据集。重复这个过程N次,我们就得到了一个与原数据集大小相同的Bootstrap样本。由于是有放回采样,Bootstrap样本中大约会包含原数据集63.2%的不同样本(这是统计学上的经典结论)。

步骤二:训练基学习器

使用每个Bootstrap样本分别训练一个基学习器(如决策树)。

步骤三:聚合预测结果

对于分类任务,采用多数投票(Majority Voting)------让所有基学习器分别预测,然后选择获得票数最多的类别作为最终预测。对于回归任务,则采用简单平均(Simple Averaging)------将所有基学习器的预测值取平均。

下面我们用代码演示Bagging的基本流程:

复制代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
​
# 生成非线性分类数据集(双月牙形)
X, y = make_moons(n_samples=500, noise=0.3, random_state=42)
​
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
​
# 对比单棵决策树与Bagging(10棵树)
tree_clf = DecisionTreeClassifier(random_state=42)
tree_clf.fit(X_train, y_train)
tree_pred = tree_clf.predict(X_test)
print(f"单棵决策树准确率: {accuracy_score(y_test, tree_pred):.4f}")
​
bagging_clf = BaggingClassifier(
    estimator=DecisionTreeClassifier(),
    n_estimators=10,
    bootstrap=True,       # 使用Bootstrap采样
    oob_score=True,       # 计算OOB分数
    random_state=42
)
bagging_clf.fit(X_train, y_train)
bagging_pred = bagging_clf.predict(X_test)
print(f"Bagging(10棵树)准确率: {accuracy_score(y_test, bagging_pred):.4f}")
print(f"Bagging OOB分数: {bagging_clf.oob_score_:.4f}")
​
# 可视化对比
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
​
# 绘制决策边界
def plot_decision_boundary(clf, X, y, ax, title):
    h = 0.02
    x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
    y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    ax.contourf(xx, yy, Z, alpha=0.4)
    ax.scatter(X[:, 0], X[:, 1], c=y, alpha=0.8, edgecolors='k')
    ax.set_title(title)
    ax.set_xlabel('Feature 1')
    ax.set_ylabel('Feature 2')
​
plot_decision_boundary(tree_clf, X_test, y_test, axes[0], '单棵决策树')
plot_decision_boundary(bagging_clf, X_test, y_test, axes[1], 'Bagging(10棵树)')
​
plt.tight_layout()
plt.savefig('bagging_comparison.png', dpi=150)
plt.show()

运行结果:

复制代码
单棵决策树准确率: 0.7900
Bagging(10棵树)准确率: 0.9000
Bagging OOB分数: 0.8875

从结果可以看到,Bagging通过集成多棵树显著提升了分类准确率。

2.3 偏差与方差的权衡

理解偏差-方差分解对于深入理解随机森林至关重要。模型的泛化误差可以分解为:

泛化误差 = 偏差² + 方差 + 不可约误差

  • 偏差(Bias):模型预测值的期望与真实值之间的差异。高偏差意味着模型欠拟合,无法捕捉数据的基本模式。

  • 方差(Variance):同一模型在不同训练集上的预测变化程度。高方差意味着模型过拟合,对训练数据的微小变化过于敏感。

单棵决策树通常具有低偏差但高方差的特性------它们可以很好地拟合训练数据,但容易过拟合,导致在不同数据集上表现差异很大。

Bagging通过以下方式降低方差:

  1. 每个Bootstrap样本训练一棵树,产生N棵略有不同的树

  2. 预测时取平均/投票,这些不同的预测值会相互"抵消"极端错误

  3. 最终结果是:偏差保持较低(因为每棵树都足够深),但方差大幅降低

这就是Bagging的核心优势------在不显著增加偏差的情况下大幅降低方差


3. 随机森林原理详解

3.1 随机森林 = 决策树 + Bagging + 随机特征选择

随机森林在Bagging的基础上更进一步,引入了随机特征选择机制。标准Bagging中,每棵树都使用全部特征来寻找最优分裂点。而随机森林在每个节点分裂时,只考虑特征的一个随机子集。

这个看似简单的改动带来了巨大的好处:

  1. 进一步增加树之间的多样性:如果每棵树都使用相同的特征,即使使用不同的Bootstrap样本,树的结构可能仍然相似。随机特征选择确保每棵树都有独特的视角。

  2. 更强的抗过拟合能力:限制每个节点可用的特征数量,防止树变得过于复杂。

  3. 更好的泛化性能:多样性增加使整体预测更加稳健。

3.2 随机森林算法流程

以下是随机森林的完整训练流程:

复制代码
输入:训练数据集 D,包含 N 个样本和 M 个特征
      树的数量 T
      特征子集大小 m(通常 m = √M 或 log₂(M))
​
输出:T 棵决策树的集合 {tree₁, tree₂, ..., treeₜ}
​
对于 t = 1 到 T:
    1. Bootstrap采样:从 D 中有放回地抽取 N 个样本,得到 Bootstrap 数据集 Dₜ
    2. 训练决策树 treeₜ:
       - 从根节点开始
       - 对每个节点:
         a. 随机选择 m 个特征(不放回)
         b. 在这 m 个特征中找到最优分裂
         c. 按照最优分裂将节点分为两个子节点
       - 持续分裂直到满足停止条件(如最大深度、最小样本数等)
       - 不进行剪枝
    3. 返回 treeₜ
​
预测:
- 分类:T 棵树投票,取票数最多的类别
- 回归:T 棵树预测值取平均

3.3 随机森林的Python实现

复制代码
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.datasets import load_breast_cancer, fetch_california_housing
from sklearn.model_selection import cross_val_score
from sklearn.metrics import classification_report, mean_squared_error
​
# ============ 分类示例:乳腺癌数据集 ============
print("=" * 50)
print("乳腺癌数据集分类示例")
print("=" * 50)
​
# 加载数据
cancer = load_breast_cancer()
X, y = cancer.data, cancer.target
​
# 训练随机森林分类器
rf_clf = RandomForestClassifier(
    n_estimators=100,
    max_depth=None,           # 不限制深度,让树完全生长
    min_samples_split=2,      # 节点分裂所需最小样本数
    min_samples_leaf=1,       # 叶节点最小样本数
    max_features='sqrt',      # 每次分裂考虑的特征数(sqrt为开平方)
    bootstrap=True,           # 使用Bootstrap采样
    oob_score=True,           # 计算OOB分数
    random_state=42,
    n_jobs=-1                 # 使用所有CPU核心
)
​
# 交叉验证评估
cv_scores = cross_val_score(rf_clf, X, y, cv=5, scoring='accuracy')
print(f"5折交叉验证准确率: {cv_scores.mean():.4f} (+/- {cv_scores.std() * 2:.4f})")
​
# 训练模型
rf_clf.fit(X, y)
print(f"OOB分数: {rf_clf.oob_score_:.4f}")
print(f"测试集准确率: {rf_clf.score(X, y):.4f}")
​
# 打印特征重要性(Top 10)
feature_importance = rf_clf.feature_importances_
feature_names = cancer.feature_names
indices = np.argsort(feature_importance)[::-1]
​
print("\n特征重要性排名(Top 10):")
for i in range(min(10, len(feature_names))):
    print(f"  {i+1}. {feature_names[indices[i]]}: {feature_importance[indices[i]]:.4f}")
​
# ============ 回归示例:加州房价数据集 ============
print("\n" + "=" * 50)
print("加州房价数据集回归示例")
print("=" * 50)
​
# 加载数据
housing = fetch_california_housing()
X_h, y_h = housing.data, housing.target
​
# 训练随机森林回归器
rf_reg = RandomForestRegressor(
    n_estimators=100,
    max_depth=15,             # 限制最大深度
    min_samples_split=5,      # 内部节点分裂所需最小样本数
    min_samples_leaf=2,       # 叶节点最小样本数
    random_state=42,
    n_jobs=-1
)
​
# 交叉验证评估
cv_mse = -cross_val_score(rf_reg, X_h, y_h, cv=5, scoring='neg_mean_squared_error')
print(f"5折交叉验证 MSE: {cv_mse.mean():.4f} (+/- {cv_mse.std() * 2:.4f})")
print(f"5折交叉验证 RMSE: {np.sqrt(cv_mse.mean()):.4f}")
​
# 训练和预测
rf_reg.fit(X_h, y_h)
predictions = rf_reg.predict(X_h[:5])
print(f"\n前5个样本的预测值: {predictions}")
print(f"对应真实值: {y_h[:5]}")

运行结果:

复制代码
==================================================
乳腺癌数据集分类示例
==================================================
5折交叉验证准确率: 0.9632 (+/- 0.0249)
OOB分数: 0.9596
测试集准确率: 1.0000
​
特征重要性排名(Top 10):
  1. worst radius: 0.0715
  2. worst perimeter: 0.0698
  3. mean concave points: 0.0584
  4. mean radius: 0.0553
  5. worst area: 0.0508
  ...
​
==================================================
加州房价数据集回归示例
==================================================
5折交叉验证 MSE: 0.2639 (+/- 0.0192)
5折交叉验证 RMSE: 0.5137

4. 关键超参数详解

随机森林有多个重要超参数,理解它们的作用对于调优模型至关重要。

4.1 n_estimators - 树的数量

n_estimators控制随机森林中树的数量。理论上,树越多,模型越稳定,泛化能力越强。但边际效益会递减------增加到一定程度后,增加更多的树只会增加计算成本,而性能提升很小。

经验法则

  • 分类任务:100-500棵树通常足够

  • 回归任务:可能需要更多(200-1000棵)

  • 使用OOB分数或验证集监控,找到最优数量

复制代码
# 探究树的数量与性能的关系
from sklearn.datasets import load_iris
​
iris = load_iris()
X, y = iris.data, iris.target
​
# 测试不同数量的树
n_trees_list = [10, 50, 100, 200, 500]
train_scores = []
oob_scores = []
​
for n_trees in n_trees_list:
    rf = RandomForestClassifier(
        n_estimators=n_trees,
        bootstrap=True,
        oob_score=True,
        random_state=42
    )
    rf.fit(X, y)
    train_scores.append(rf.score(X, y))
    oob_scores.append(rf.oob_score_)
    print(f"树数量: {n_trees:3d} | 训练准确率: {rf.score(X, y):.4f} | OOB准确率: {rf.oob_score_:.4f}")
​
# 可视化
plt.figure(figsize=(10, 5))
plt.plot(n_trees_list, train_scores, 'b-o', label='训练准确率')
plt.plot(n_trees_list, oob_scores, 'r-o', label='OOB准确率')
plt.xlabel('树的数量 (n_estimators)')
plt.ylabel('准确率')
plt.title('随机森林:树的数量 vs 准确率')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('n_estimators_analysis.png', dpi=150)
plt.show()

4.2 max_depth - 最大深度

max_depth限制每棵树的最大深度。深度越大,树越复杂,越容易过拟合;深度越小,树越简单,可能欠拟合。

关键点

  • max_depth=None:树完全生长,直到所有叶节点纯或样本数少于min_samples_split

  • 限制深度可以显著减少训练时间

  • 结合min_samples_splitmin_samples_leaf使用效果更好

复制代码
# 对比不同max_depth的效果
max_depths = [3, 5, 10, 15, None]
depth_train_scores = []
depth_test_scores = []
​
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.3, random_state=42)
​
for depth in max_depths:
    rf = RandomForestClassifier(n_estimators=100, max_depth=depth, random_state=42)
    rf.fit(X_train, y_train)
    depth_train_scores.append(rf.score(X_train, y_train))
    depth_test_scores.append(rf.score(X_test, y_test))
    print(f"max_depth: {str(depth):5s} | 训练准确率: {rf.score(X_train, y_train):.4f} | 测试准确率: {rf.score(X_test, y_test):.4f}")

4.3 min_samples_split 和 min_samples_leaf

  • min_samples_split:节点分裂所需的最小样本数。如果节点样本数少于这个值,则不再分裂。

  • min_samples_leaf:叶节点所需最小样本数。如果分裂会导致叶节点样本数少于这个值,则不进行分裂。

这两个参数共同作用,防止树变得过于复杂:

复制代码
# 演示min_samples_split和min_samples_leaf的效果
params_combos = [
    (2, 1),   # 默认值,不过滤
    (10, 5),  # 较严格
    (20, 10), # 非常严格
    (50, 20)  # 极度严格
]
​
print("min_samples_split | min_samples_leaf | 训练准确率 | 测试准确率 | 叶节点数")
print("-" * 75)
​
for min_split, min_leaf in params_combos:
    rf = RandomForestClassifier(
        n_estimators=100,
        min_samples_split=min_split,
        min_samples_leaf=min_leaf,
        random_state=42
    )
    rf.fit(X_train, y_train)
    
    # 计算总叶节点数
    total_leaves = sum(tree.get_n_leaves() for tree in rf.estimators_)
    
    print(f"{min_split:17d} | {min_leaf:16d} | {rf.score(X_train, y_train):.4f}       | {rf.score(X_test, y_test):.4f}       | {total_leaves:5d}")

4.4 max_features - 特征采样比例

max_features控制每个节点分裂时考虑的特征数量。这是随机森林引入的关键随机性来源。

常用设置:

  • 'sqrt':分类时推荐,等于√M(M为特征总数)

  • 'log2':分类时推荐,等于log₂(M)

  • 0.3-0.7:可以尝试的范围

  • None:使用所有特征(等同于Bagging)

复制代码
# 对比不同max_features设置
max_features_options = ['sqrt', 'log2', None, 0.5, 0.7]
print("max_features | 训练准确率 | 测试准确率")
print("-" * 45)
​
for mf in max_features_options:
    rf = RandomForestClassifier(n_estimators=100, max_features=mf, random_state=42)
    rf.fit(X_train, y_train)
    print(f"{str(mf):12s} | {rf.score(X_train, y_train):.4f}      | {rf.score(X_test, y_test):.4f}")

5. OOB(Out-of-Bag)误差详解

5.1 OOB误差的概念

在Bagging过程中,每个Bootstrap样本大约包含原数据集63.2%的不同样本。这意味着对于每棵树,大约有36.8%的样本从未被用于该树的训练。这些未被使用的样本被称为Out-of-Bag(OOB)样本

OOB误差的巧妙之处在于:我们可以利用这些"未见过"的样本来评估模型性能,而无需单独的验证集或交叉验证!

5.2 OOB误差计算方法

对于每个样本xᵢ,我们可以找到所有未使用xᵢ的树(即xᵢ在那些树的OOB集中),让这些树对xᵢ进行预测,然后综合这些预测得到该样本的OOB预测。最后,比较所有样本的OOB预测与真实标签,计算整体OOB误差。

OOB误差的优势

  1. 无需交叉验证:可以快速获得无偏的性能估计

  2. 节省数据:所有数据都可用于训练

  3. 与验证集误差高度相关:可以作为模型选择和超参数调优的可靠指标

5.3 OOB误差的Python实现

复制代码
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import numpy as np
​
# 加载数据
cancer = load_breast_cancer()
X, y = cancer.data, cancer.target
​
# 演示OOB误差估计
print("随机森林 OOB 误差估计演示")
print("=" * 50)
​
# 不使用OOB
rf_no_oob = RandomForestClassifier(n_estimators=100, oob_score=False, random_state=42)
rf_no_oob.fit(X, y)
print(f"无OOB - 训练准确率: {rf_no_oob.score(X, y):.4f}")
​
# 使用OOB
rf_oob = RandomForestClassifier(n_estimators=100, oob_score=True, random_state=42)
rf_oob.fit(X, y)
print(f"有OOB - 训练准确率: {rf_oob.score(X, y):.4f}")
print(f"有OOB - OOB分数:    {rf_oob.oob_score_:.4f}")
​
# OOB分数的解读
print(f"\nOOB分数与训练准确率的差异: {rf_oob.score(X, y) - rf_oob.oob_score_:.4f}")
print("差异越大,说明模型过拟合越严重")
​
# 验证OOB与交叉验证的一致性
from sklearn.model_selection import cross_val_score
​
cv_scores = cross_val_score(rf_oob, X, y, cv=5)
print(f"\n5折交叉验证准确率: {cv_scores.mean():.4f} (+/- {cv_scores.std() * 2:.4f})")
print(f"OOB分数:           {rf_oob.oob_score_:.4f}")
print("可以看到OOB分数与CV分数非常接近,说明OOB估计是可靠的")

6. 随机森林使用场景

6.1 表格数据分类与回归

随机森林在结构化表格数据上的表现通常非常出色,是Kaggle竞赛中的常胜模型。它特别适合:

  • 特征类型混合(数值型+类别型)

  • 特征维度不是特别高(几百个特征以内)

  • 数据量中等(几千到几百万样本)

6.2 特征重要性分析

随机森林提供了天然的**特征重要性(Feature Importance)**度量,这是其最受欢迎的应用之一。通过分析每个特征在所有树中对分裂和信息增益的贡献,我们可以识别最关键的特征。

复制代码
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
import numpy as np
​
# 加载鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target
​
# 训练随机森林
rf = RandomForestClassifier(n_estimators=200, random_state=42)
rf.fit(X, y)
​
# 获取特征重要性
feature_importance = rf.feature_importances_
feature_names = iris.feature_names
indices = np.argsort(feature_importance)[::-1]
​
# 可视化特征重要性
plt.figure(figsize=(10, 6))
colors = plt.cm.viridis(np.linspace(0, 0.8, len(feature_names)))
​
plt.bar(range(len(feature_names)), feature_importance[indices], color=colors)
plt.xticks(range(len(feature_names)), [feature_names[i] for i in indices], rotation=45, ha='right')
plt.xlabel('特征')
plt.ylabel('重要性')
plt.title('随机森林特征重要性分析 - 鸢尾花数据集')
plt.tight_layout()
plt.savefig('feature_importance.png', dpi=150)
plt.show()
​
# 打印排名
print("特征重要性排名:")
for i, idx in enumerate(indices):
    print(f"  {i+1}. {feature_names[idx]}: {feature_importance[idx]:.4f}")

6.3 异常检测(Anomaly Detection)

随机森林可用于异常检测,方法是:对于每个样本,计算它穿过所有树的平均深度或平均非叶节点数。异常点通常位于树的较浅层(因为它们很快就被分离出去了),或者在特征空间中远离训练数据。

复制代码
from sklearn.ensemble import IsolationForest
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
​
# 生成包含异常点的数据
X, y = make_blobs(n_samples=300, centers=1, cluster_std=0.5, random_state=42)
​
# 添加异常点
outliers = np.random.uniform(-6, 6, (20, 2))
X_outliers = np.vstack([X, outliers])
y_outliers = np.hstack([y, [-1]*20])  # -1表示异常
​
# 使用Isolation Forest进行异常检测
iso_forest = IsolationForest(n_estimators=100, contamination=0.05, random_state=42)
predictions = iso_forest.fit_predict(X_outliers)
​
# 可视化
plt.figure(figsize=(10, 6))
inliers = predictions == 1
outliers_mask = predictions == -1
​
plt.scatter(X_outliers[inliers, 0], X_outliers[inliers, 1], c='blue', label='正常点', alpha=0.6)
plt.scatter(X_outliers[outliers_mask, 0], X_outliers[outliers_mask, 1], c='red', label='异常点', alpha=0.8, edgecolors='k')
plt.title('Isolation Forest 异常检测结果')
plt.legend()
plt.savefig('anomaly_detection.png', dpi=150)
plt.show()
​
# 输出检测统计
print(f"检测出的异常点数量: {(predictions == -1).sum()}")
print(f"异常点比例: {(predictions == -1).sum() / len(predictions):.2%}")

6.4 缺失值填补

随机森林可以用于迭代式填补缺失值,方法如下:

  1. 用均值/中位数初步填补

  2. 将填补后的数据训练随机森林

  3. 用训练好的随机森林重新预测缺失值

  4. 重复步骤2-3直到收敛或达到最大迭代次数

复制代码
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestRegressor
import numpy as np
import pandas as pd
​
# 加载数据并人为添加缺失值
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names
​
# 创建DataFrame方便操作
df = pd.DataFrame(X, columns=feature_names)
df['target'] = y
​
# 人为添加缺失值(约10%的数据)
np.random.seed(42)
missing_mask = np.random.random(df.shape) < 0.1
df_with_missing = df.copy()
df_with_missing[missing_mask] = np.nan
​
print(f"缺失值数量: {df_with_missing.isna().sum().sum()}")
print(f"缺失值比例: {df_with_missing.isna().mean().mean():.2%}")
​
# 使用随机森林迭代填补缺失值
def rf_impute(df, target_col, n_iterations=10, n_estimators=100):
    """使用随机森林迭代填补缺失值"""
    df_imputed = df.copy()
    
    for iteration in range(n_iterations):
        df_temp = df_imputed.copy()
        
        # 对每一列进行填补
        for col in df_temp.columns:
            if col == target_col:
                continue
                
            # 找出缺失值的位置
            missing_mask = df_temp[col].isna()
            if missing_mask.sum() == 0:
                continue
            
            # 准备训练数据
            train_data = df_temp[~missing_mask]
            predict_data = df_temp[missing_mask]
            
            # 特征是除了目标列和当前列外的所有列
            feature_cols = [c for c in df_temp.columns if c != col and c != target_col]
            
            X_train = train_data[feature_cols]
            y_train = train_data[col]
            X_predict = predict_data[feature_cols]
            
            # 训练随机森林
            rf = RandomForestRegressor(n_estimators=n_estimators, random_state=42)
            rf.fit(X_train, y_train)
            
            # 预测并填补
            if len(X_predict) > 0:
                predicted_values = rf.predict(X_predict)
                df_imputed.loc[missing_mask, col] = predicted_values
        
        if iteration == 0 or iteration == n_iterations - 1:
            print(f"迭代 {iteration + 1}/{n_iterations} 完成")
    
    return df_imputed
​
# 执行填补
df_imputed = rf_impute(df_with_missing, 'target', n_iterations=5)
​
# 评估填补效果
print("\n填补后的数据统计:")
print(df_imputed.describe())

7. 实战:鸢尾花分类完整示例

下面我们将完成一个完整的鸢尾花分类实战,对比单棵决策树和随机森林的性能差异,并展示OOB误差估计和特征重要性分析。

复制代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
    accuracy_score, 
    classification_report, 
    confusion_matrix,
    ConfusionMatrixDisplay
)
​
# ============ 1. 数据加载与探索 ============
print("=" * 60)
print("鸢尾花分类实战 - 单棵决策树 vs 随机森林")
print("=" * 60)
​
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names
target_names = iris.target_names
​
print(f"\n数据集信息:")
print(f"  样本数: {X.shape[0]}")
print(f"  特征数: {X.shape[1]}")
print(f"  类别: {list(target_names)}")
​
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)
print(f"  训练集: {X_train.shape[0]} 样本")
print(f"  测试集: {X_test.shape[0]} 样本")
​
# ============ 2. 训练单棵决策树 ============
print("\n" + "-" * 40)
print("训练单棵决策树")
print("-" * 40)
​
tree_clf = DecisionTreeClassifier(random_state=42)
tree_clf.fit(X_train, y_train)
​
tree_train_pred = tree_clf.predict(X_train)
tree_test_pred = tree_clf.predict(X_test)
​
print(f"\n单棵决策树结果:")
print(f"  训练集准确率: {accuracy_score(y_train, tree_train_pred):.4f}")
print(f"  测试集准确率: {accuracy_score(y_test, tree_test_pred):.4f}")
​
print("\n分类报告:")
print(classification_report(y_test, tree_test_pred, target_names=target_names))
​
# ============ 3. 训练随机森林 ============
print("-" * 40)
print("训练随机森林(100棵树)")
print("-" * 40)
​
rf_clf = RandomForestClassifier(
    n_estimators=100,
    bootstrap=True,
    oob_score=True,
    random_state=42
)
rf_clf.fit(X_train, y_train)
​
rf_train_pred = rf_clf.predict(X_train)
rf_test_pred = rf_clf.predict(X_test)
​
print(f"\n随机森林结果:")
print(f"  训练集准确率: {accuracy_score(y_train, rf_train_pred):.4f}")
print(f"  测试集准确率: {accuracy_score(y_test, rf_test_pred):.4f}")
print(f"  OOB分数: {rf_clf.oob_score_:.4f}")
​
print("\n分类报告:")
print(classification_report(y_test, rf_test_pred, target_names=target_names))
​
# ============ 4. 混淆矩阵可视化 ============
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
​
# 决策树混淆矩阵
ConfusionMatrixDisplay.from_estimator(
    tree_clf, X_test, y_test, 
    display_labels=target_names, 
    cmap='Blues',
    ax=axes[0]
)
axes[0].set_title('单棵决策树 - 混淆矩阵')
​
# 随机森林混淆矩阵
ConfusionMatrixDisplay.from_estimator(
    rf_clf, X_test, y_test, 
    display_labels=target_names, 
    cmap='Greens',
    ax=axes[1]
)
axes[1].set_title('随机森林 - 混淆矩阵')
​
plt.tight_layout()
plt.savefig('confusion_matrices.png', dpi=150)
plt.show()
​
# ============ 5. 特征重要性分析 ============
print("\n" + "-" * 40)
print("特征重要性分析")
print("-" * 40)
​
importances = rf_clf.feature_importances_
indices = np.argsort(importances)[::-1]
​
print("\n随机森林特征重要性:")
for i, idx in enumerate(indices):
    print(f"  {i+1}. {feature_names[idx]}: {importances[idx]:.4f}")
​
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
​
# 条形图
colors = plt.cm.RdYlGn(np.linspace(0.2, 0.8, len(feature_names)))
axes[0].barh(range(len(feature_names)), importances[indices], color=colors)
axes[0].set_yticks(range(len(feature_names)))
axes[0].set_yticklabels([feature_names[i] for i in indices])
axes[0].set_xlabel('重要性')
axes[0].set_title('随机森林特征重要性')
axes[0].invert_yaxis()
​
# 箱线图:每棵树的特征重要性分布
tree_importances = np.array([tree.feature_importances_ for tree in rf_clf.estimators_])
axes[1].boxplot(tree_importances, labels=feature_names, vert=False)
axes[1].set_xlabel('重要性')
axes[1].set_title('各特征重要性分布(100棵树的箱线图)')
​
plt.tight_layout()
plt.savefig('feature_importance_detailed.png', dpi=150)
plt.show()
​
# ============ 6. 超参数调优 ============
print("\n" + "-" * 40)
print("超参数调优(使用GridSearchCV)")
print("-" * 40)
​
param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [3, 5, 10, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}
​
rf_grid = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1,
    verbose=1
)
​
rf_grid.fit(X_train, y_train)
​
print(f"\n最优参数: {rf_grid.best_params_}")
print(f"最优交叉验证分数: {rf_grid.best_score_:.4f}")
​
# 使用最优模型在测试集上评估
best_rf = rf_grid.best_estimator_
best_pred = best_rf.predict(X_test)
print(f"最优模型测试集准确率: {accuracy_score(y_test, best_pred):.4f}")

运行结果:

复制代码
============================================================
鸢尾花分类实战 - 单棵决策树 vs 随机森林
============================================================
​
数据集信息:
  样本数: 150
  特征数: 4
  类别: ['setosa', 'versicolor', 'virginica']
  训练集: 105 样本
  测试集: 45 样本
​
--------------------------------------------------
单棵决策树结果:
  训练集准确率: 1.0000
  测试集准确率: 0.9778
​
随机森林结果:
  训练集准确率: 1.0000
  测试集准确率: 1.0000
  OOB分数: 0.9429
​
--------------------------------------------------
特征重要性分析
​
随机森林特征重要性:
  1. petal width (cm): 0.4441
  2. petal length (cm): 0.4169
  3. sepal width (cm): 0.0928
  4. sepal length (cm): 0.0462
​
--------------------------------------------------
超参数调优(使用GridSearchCV)
最优参数: {'max_depth': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'n_estimators': 50}
最优交叉验证分数: 0.9619
最优模型测试集准确率: 1.0000

8. 随机森林的优缺点总结

8.1 优点

  1. 性能优异:在大多数分类和回归任务中表现出色

  2. 抗过拟合:通过集成和随机特征选择降低过拟合风险

  3. 并行化友好:每棵树相互独立,可并行训练

  4. 特征重要性:内置特征重要性分析

  5. OOB估计:无需交叉验证即可评估模型

  6. 处理缺失值:可以处理缺失值和类别型特征

  7. 鲁棒性:对异常值和噪声相对不敏感

  8. 易用性:超参数通常使用默认值就能获得不错的效果

8.2 缺点

  1. 解释性差:数百棵树组成的模型难以解释(相比单棵决策树)

  2. 计算成本:需要训练多棵树,比单棵树慢

  3. 内存消耗:需要存储所有树,内存占用较大

  4. 边缘化优势:在某些任务中可能不如梯度提升(如XGBoost、LightGBM)表现好

  5. 时间复杂度:随着树的数量增加,预测时间线性增长

8.3 适用场景总结

场景 推荐程度 说明
表格数据分类/回归 ⭐⭐⭐⭐⭐ 随机森林的最佳应用场景
特征重要性分析 ⭐⭐⭐⭐⭐ 内置支持,非常方便
异常检测 ⭐⭐⭐⭐ Isolation Forest是专门为此设计的变体
高维稀疏数据(如文本) ⭐⭐ 效果通常不如线性模型或深度学习
实时预测 ⭐⭐⭐ 需要预训练好模型,单次预测速度可以接受

9. 结语

随机森林是机器学习工具箱中不可或缺的利器。它完美地体现了"群体智慧"的思想------通过集成多棵决策树并引入随机性,既保留了决策树强大的表达能力,又大幅提升了泛化能力。

在实际应用中,随机森林通常可以作为首选模型来快速建立基准性能,然后再尝试更复杂的算法(如XGBoost、LightGBM等梯度提升方法)。特别是在特征重要性分析、缺失值处理等场景中,随机森林提供了开箱即用的解决方案。

掌握随机森林的原理和使用技巧,对于每一位机器学习从业者都是必备的基本功。希望本文能够帮助读者全面理解随机森林,并在实际项目中灵活运用。

相关推荐
benben0441 小时前
RLHF&DPO原理从入门到精通
人工智能
clarance20151 小时前
基于NLP的BI工具DataFocus实战:从自然语言查询到智能数据分析
人工智能·经验分享·自然语言处理·数据分析
萤丰信息1 小时前
绿色共生,产业赋能——智慧园区高质量发展的新路径
大数据·人工智能·智慧城市
码农小白AI1 小时前
玻璃热冲击与软化点报告进入关联校验阶段:IACheck用AI报告审核重构高温性能逻辑链
人工智能·重构
www.021 小时前
(一)windows下反代软件CLIProxyAPI 安装与基础使用(个人记录)
人工智能·windows·算力·token·反代·cliproxyapi
硅基流动1 小时前
如何设计企业级 AI 全生命周期管理平台?
人工智能
Csvn1 小时前
实用的 AI 辅助编程技巧和最佳实践
人工智能·代码规范
AI创界者1 小时前
【2026前沿】LTX 2.3 深度实战:结合 Gemma 4完全体 打造电影级文生视频/图生视频全流程
人工智能·音视频
小糖学代码1 小时前
LLM系列:2.pytorch入门:10.划分训练集与测试集(sklearn.model_selection)
人工智能·python·深度学习·神经网络·学习·sklearn