随机森林的 “Bootstrap 采样” 与 “特征随机选择”:如何避免过拟合?(附分类 / 回归任务实战)
随机森林的 “Bootstrap 采样” 与 “特征随机选择”:如何避免过拟合?(附分类 / 回归任务实战)
第一部分:揭开随机森林的神秘面纱
1.1 告别“过拟合”,拥抱更强大的模型
在机器学习的旅程中,我们常常会遇到一个“敌人”——过拟合(Overfitting)。想象一个学生,他只会死记硬背老师划定的考试范围和标准答案。在模拟考试(训练数据)中,他总能考满分,因为题目一模一样。可一旦到了正式考场(测试数据),题目稍微变换一下形式,他就束手无策,成绩一落千丈。
这种“在训练数据上表现完美,但在新数据上表现糟糕”的现象,就是“过拟合”。我们精心构建的模型,就像那个只会死记硬背的学生,失去了泛化到新环境的能力。
决策树(Decision Tree)模型,作为一种简单直观的算法,就很容易陷入过拟合的陷阱。它会不断地划分数据,直到把训练集中的每个样本都“安排”得明明白白,但这往往导致它学习到了过多的噪音和细节。
那么,如何让我们的模型变得更“聪明”,而不是一个只会“死记硬背”的书呆子呢?答案是:集成学习(Ensemble Learning)。古话说得好:“三个臭皮匠,顶个诸葛亮”。如果我们能汇集多个模型的智慧,是不是就能得到一个更全面、更稳健的决策?
今天,我们将要学习的主角——随机森林(Random Forest),正是集成学习中的佼佼者。它通过构建一片由决策树组成的“森林”,让每棵树独立学习、独立思考,最后通过集体投票的方式,做出远比单棵树更准确、更可靠的判断。
在本篇博客中,我将牵着你的手,一步步带你:
- 理解随机森林为何能有效避免过拟合。
- 掌握其背后的两大核心技术:Bootstrap 采样与特征随机选择。
- 从零开始,搭建环境,并亲手完成一个分类任务和一个回归任务的实战。
1.2 什么是随机森林?—— 一片由决策树构成的“智慧森林”
随机森林,顾名思义,它不是单一的模型,而是一个包含了多棵决策树的“森林”。当有新的数据需要预测时,森林中的每一棵决策树都会独立地给出一个自己的判断。最后,模型会综合所有树的“意见”,通过“投票”(分类任务)或“取平均值”(回归任务)的方式,得出最终的结论。
这就像一个专家委员会在进行会诊。每一位专家(决策树)都有自己的知识背景和判断依据。面对同一个病例(输入数据),他们分别给出诊断。最后,委员会采纳最多专家支持的诊断结果(分类),或者综合所有专家的数值评估得出一个平均指标(回归)。这种集体决策的方式,远比依赖单一专家的判断要可靠得多。
我们可以用下面的图来直观地理解这个结构:
你可能会问,如果森林里的每一棵树都长得一模一样,那和只有一棵树又有什么区别呢?问得好!这正是随机森林“随机”二字的精髓所在。为了让每一棵树都具备自己独特的“视角”,随机森林在两个关键环节引入了随机性:
- 训练样本的随机性:每棵树的训练数据,都是从原始数据集中随机抽样得来的。
- 特征选择的随机性:每棵树在构建时,其内部的每个节点都只能从一部分随机选择的特征中进行分裂。
正是这两个“随机”的引入,才保证了森林中树木的多样性,从而铸就了模型的强大。接下来,让我们深入探索这两大“法宝”。
第二部分:随机森林避免过拟合的两大“法宝”
2.1 法宝一:Bootstrap 采样 —— “我的训练数据,我做主”
是什么?
Bootstrap 采样,又称“自助法采样”或“有放回抽样”。它的过程非常简单:
假设我们有一个包含 N
个样本的原始数据集。现在,我们想为森林中的第一棵树准备训练数据。我们从原始数据集中随机抽取一个样本,记录下来,然后把它放回去。接着,我们再重复这个过程,总共重复 N
次。
这样,我们就得到了一个同样包含 N
个样本的新数据集。由于是“有放回”抽样,这个新的数据集中,有些原始样本可能一次都没被抽到,而有些则可能被抽到了一次、两次甚至更多次。
随机森林会为每一棵将要构建的决策树,都重复进行一次这样的 Bootstrap 采样,为它们各自生成一套“专属”的训练数据。
为什么?
这样做最大的好处,就是保证了每棵树学习的“起点”不同。
如果所有的树都使用完全相同的训练数据,它们很可能会学习到相同的模式,犯相同的错误,最终长成一模一样的“克隆树”,这就失去了集成的意义。
通过 Bootstrap 采样,每棵树接触到的数据都有细微的差别。有的树可能对某类样本“见多识广”,有的则可能从未见过某些样本。这种差异性,使得每棵树都成了某一方面的“偏科生”,但当我们将这些“偏科生”的意见汇集起来时,就能得到一个非常全面的“全才”模型。
可视化
下面的流程图清晰地展示了 Bootstrap 采样的过程:
graph TDsubgraph 原始数据集 (N个样本)D1[样本1]D2[样本2]D3[样本3]D4[...]D5[样本N]endsubgraph 为决策树1采样 (得到N个样本)direction LRS1_1(样本3)S1_2(样本1)S1_3(样本3)S1_4(...)S1_5(样本N)endsubgraph 为决策树2采样 (得到N个样本)direction LRS2_1(样本N)S2_2(样本2)S2_3(样本1)S2_4(...)S2_5(样本2)endsubgraph 为决策树N采样 (得到N个样本)direction LRSN_1(...)SN_2(...)endD1 & D2 & D3 & D4 & D5 -- "有放回抽样" --> S1_1 & S1_2 & S1_3 & S1_4 & S1_5;D1 & D2 & D3 & D4 & D5 -- "有放回抽样" --> S2_1 & S2_2 & S2_3 & S2_4 & S2_5;D1 & D2 & D3 & D4 & D5 -- "有放回抽样" --> SN_1 & SN_2;
2.2 法宝二:特征随机选择 —— “别让‘学霸’特征带偏节奏”
是什么?
如果说 Bootstrap 采样保证了“输入”的多样性,那么特征随机选择则保证了“学习过程”的多样性。
决策树的构建过程,是在每个节点上,选择一个“最优”的特征来进行数据划分。但在随机森林中,这个规则被修改了。
当某棵决策树需要在某个节点进行分裂时,它不会考察当前所有的特征。而是,先从所有 M
个特征中,随机挑选出 m
个特征(通常 m
远小于 M
),然后再从这 m
个特征中,挑选出最优的一个用于分裂。
这个过程,在树的每一个分裂节点上,都会重复进行。
为什么?
想象一下,在你的数据集中,有一个“学霸”特征。这个特征的预测能力非常强,如果让决策树自由选择,它很可能在每一个节点都优先使用这个特征,导致森林中大部分树的结构都变得非常相似,因为它们都严重依赖这同一个“学霸”。
这就削弱了我们想要的多样性。
通过随机选择特征,我们“强迫”决策树在分裂时,给那些不那么强势的“普通”特征一些机会。也许在某个特定的数据子集中,一个“普通”特征反而能发挥出意想不到的好效果。
这极大地增强了森林中树木之间的差异性,降低了它们之间的相关性。当所有树都从不同角度、依据不同特征来进行判断时,整个森林的鲁棒性(Robustness)和泛化能力就得到了质的飞跃。
可视化
下图展示了在一个决策节点上,特征是如何被随机选择的:
graph TDA[所有特征: M个<br/>(Feature 1, Feature 2, ... Feature M)] --> B{在节点分裂时};B -- "随机选择m个特征" --> C[候选特征子集<br/>(Feature 3, Feature 8, ...)];C -- "从中选择最优特征" --> D[使用Feature 8进行分裂];
2.3 总结:两大“法宝”如何协同工作
现在,我们将这两个强大的工具结合起来,看看一个完整的随机森林是如何被构建并避免过拟合的:
- 样本随机:通过 Bootstrap 采样,为每棵树生成一份独特的数据“教材”,保证了树与树之间的“宏观”差异性。
- 特征随机:在每棵树的生长过程中,通过限制节点分裂时的候选特征,保证了树内部结构的“微观”差异性。
这两个机制共同作用,确保了森林中的每一棵树都是独一无二的。它们从不同的数据、不同的角度学习,最终汇集成的集体智慧,自然就拥有了强大的泛化能力,能够很好地抵抗过拟合。
第三部分:万事俱备,只欠“实战”
理论讲完了,是时候动手实践了!一个好的程序员,不仅要懂理论,更要能动手。接下来,我们一起搭建一个简单的机器学习环境。
3.1 环境准备:搭建你的机器学习实验室
第一步:拥有 Python
首先,你需要一个 Python 环境。现代的操作系统通常都内置了 Python。你可以在终端(Terminal 或命令提示符)中输入以下命令来检查:
python --version
# 或者
python3 --version
如果能看到版本号(如 Python 3.9.7
),说明你已经安装好了。如果没有,请前往 Python 官方网站 下载并安装最新版本。
第二步:创建虚拟环境(强烈推荐)
为什么需要虚拟环境? 想象一下,你同时在做两个项目,项目 A 需要 1.0 版本的工具库,而项目 B 需要 2.0 版本。如果你把它们都装在系统里,就会产生冲突。虚拟环境就像是为每个项目创建了一个独立的、干净的“实验室”,项目 A 的工具放在 A 实验室,项目 B 的工具放在 B 实验室,它们互不干扰。
让我们为本次实战创建一个名为 rf_env
的虚拟环境:
# 1. 创建虚拟环境
python3 -m venv rf_env# 2. 激活虚拟环境
# 在 macOS / Linux 上:
source rf_env/bin/activate
# 在 Windows 上:
.\\rf_env\\Scripts\\activate
激活成功后,你会看到终端提示符前面多了 (rf_env)
的字样,这表示你已经进入了这个独立的“实验室”。
第三步:安装必备工具库
在这个虚拟环境中,我们需要安装几个核心的 Python 库:
scikit-learn
:这是我们今天的主角,一个强大的机器学习库,随机森林模型就在其中。pandas
:用于数据处理和分析,可以方便地读取和操作数据。numpy
:Python 科学计算的基础库,提供了高效的数组操作。
使用 pip
(Python 的包管理器)来安装它们:
pip install scikit-learn pandas numpy
等待安装完成,我们的实验环境就准备就绪了!
第四部分:实战演练:从零开始构建你的随机森林模型
现在,让我们用刚刚学到的知识,来解决两个真实的机器学习问题。
4.1 任务一:随机森林分类实战(以“鸢尾花”分类为例)
鸢尾花数据集是一个非常经典的分类任务数据集,它包含了三种不同鸢尾花(Setosa, Versicolour, Virginica)的四个特征(花萼长度、花萼宽度、花瓣长度、花瓣宽度)。我们的任务是构建一个模型,根据这些特征来判断鸢尾花属于哪个种类。
第一步:创建代码文件
在你喜欢的位置,创建一个名为 classification_test.py
的 Python 文件,然后我们开始编写代码。
第二步:编写代码
# classification_test.py# 1. 导入我们需要的工具库
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score# 2. 加载数据并了解它
# scikit-learn 内置了鸢尾花数据集,我们可以直接加载
iris = load_iris()
# 为了方便观察,我们把它转换成 pandas 的 DataFrame 格式
# 特征数据
X = pd.DataFrame(iris.data, columns=iris.feature_names)
# 目标类别 (我们要预测的值)
y = pd.Series(iris.target)# 打印前5行数据看看
print("--- 特征数据 (前5行) ---")
print(X.head())
print("\\n--- 目标数据 (前5行) ---")
print(y.head())# 3. 划分数据集
# 将数据划分为 训练集 和 测试集,这是评估模型性能的标准流程
# test_size=0.3 表示我们将30%的数据用作测试,剩下的70%用作训练
# random_state 是一个随机种子,保证每次划分的结果都一样,便于复现
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(f"\\n训练集大小: {X_train.shape[0]} a, 测试集大小: {X_test.shape[0]}")# 4. “反面教材”:训练一个单独的决策树
print("\\n--- 训练单个决策树 ---")
# 创建一个决策树分类器实例
single_tree = DecisionTreeClassifier(random_state=42)
# 使用训练数据来训练模型
single_tree.fit(X_train, y_train)# 使用训练好的模型在 测试集 和 训练集 上进行预测
pred_tree_test = single_tree.predict(X_test)
pred_tree_train = single_tree.predict(X_train)# 评估模型性能
acc_tree_test = accuracy_score(y_test, pred_tree_test)
acc_tree_train = accuracy_score(y_train, pred_tree_train)
print(f"单个决策树 - 训练集准确率: {acc_tree_train:.4f}")
print(f"单个决策树 - 测试集准确率: {acc_tree_test:.4f}")
# 你可能会发现,训练集准确率是1.0,这正是过拟合的迹象!# 5. “正面典型”:训练一个随机森林
print("\\n--- 训练随机森林 ---")
# 创建一个随机森林分类器实例
# n_estimators=100 表示这个森林里有100棵决策树
# random_state 同样是为了结果可复现
random_forest = RandomForestClassifier(n_estimators=100, random_state=42)
# 使用训练数据来训练模型
random_forest.fit(X_train, y_train)# 使用训练好的模型在 测试集 和 训练集 上进行预测
pred_rf_test = random_forest.predict(X_test)
pred_rf_train = random_forest.predict(X_train)# 评估模型性能
acc_rf_test = accuracy_score(y_test, pred_rf_test)
acc_rf_train = accuracy_score(y_train, pred_rf_train)
print(f"随机森林 - 训练集准确率: {acc_rf_train:.4f}")
print(f"随机森林 - 测试集准确率: {acc_rf_test:.4f}")
第三步:运行与结果分析
在你的终端中(确保你仍处于 rf_env
虚拟环境中),运行这个文件:
python classification_test.py
你会看到类似下面的输出:
--- 特征数据 (前5行) ---sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2--- 目标数据 (前5行) ---
0 0
1 0
2 0
3 0
4 0
dtype: int64训练集大小: 105, 测试集大小: 45--- 训练单个决策树 ---
单个决策树 - 训练集准确率: 1.0000
单个决策树 - 测试集准确率: 1.0000--- 训练随机森林 ---
随机森林 - 训练集准确率: 1.0000
随机森林 - 测试集准确率: 1.0000
结果分析
在这个特定的例子中,因为鸢尾花数据集非常“干净”且易于区分,你会发现单个决策树和随机森林在测试集上的表现都达到了 100% 的准确率。但请注意关键的一点:单个决策树在训练集上的准确率是 1.0000
,这说明它完美地“记住”了所有训练数据,这是一个典型的过拟合信号。虽然在这个简单任务上它的测试表现也很好,但在更复杂、噪音更多的数据集上,这种过拟合会严重影响其泛化能力。
而随机森林,同样在训练集上表现完美,但它的完美是建立在“集体智慧”上的,其模型本质上比单个决策树要稳健得多。让我们在下一个回归任务中,更清晰地看到这种差异。
4.2 任务二:随机森林回归实战(以“加州房价”预测为例)
现在,我们来挑战一个更复杂的任务:根据加州各个街区的人口、收入等特征,来预测房价的中位数。这是一个回归问题。
第一步:创建代码文件 regression_test.py
第二步:编写代码
# regression_test.py# 1. 导入工具库
import pandas as pd
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
import numpy as np# 2. 加载数据
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = pd.Series(housing.target)# 3. 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 4. 训练单个决策树回归模型
print("--- 训练单个决策树回归模型 ---")
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(X_train, y_train)# 在测试集上进行预测
pred_tree = tree_reg.predict(X_test)
# 使用“均方根误差 (RMSE)”来评估模型
# RMSE 越小,说明模型的预测值与真实值的差异越小,模型越好
tree_rmse = np.sqrt(mean_squared_error(y_test, pred_tree))
print(f"单个决策树 - 测试集 RMSE: {tree_rmse:.4f}")# 5. 训练随机森林回归模型
print("\\n--- 训练随机森林回归模型 ---")
forest_reg = RandomForestRegressor(n_estimators=100, random_state=42)
forest_reg.fit(X_train, y_train)# 在测试集上进行预测
pred_forest = forest_reg.predict(X_test)
# 同样使用 RMSE 进行评估
forest_rmse = np.sqrt(mean_squared_error(y_test, pred_forest))
print(f"随机森林 - 测试集 RMSE: {forest_rmse:.4f}")# 6. 对比结果
print(f"\\n随机森林相比单个决策树,误差降低了: {((tree_rmse - forest_rmse) / tree_rmse) * 100:.2f}%")
第三步:运行与结果分析
在终端中运行:
python regression_test.py
你将看到如下输出:
--- 训练单个决策树回归模型 ---
单个决策树 - 测试集 RMSE: 0.7107--- 训练随机森林回归模型 ---
随机森林 - 测试集 RMSE: 0.5034随机森林相比单个决策树,误差降低了: 29.17%
结果分析
这次的结果对比就非常明显了!
- 单个决策树模型的均方根误差(RMSE)是
0.7107
。 - 随机森林模型的均方根误差(RMSE)是
0.5034
。
随机森林的预测误差降低了近 30%!这清晰地证明,在处理更复杂的回归问题时,由多棵树组成的森林通过集成学习,显著地减少了由单个决策树过拟合带来的误差,得到了一个预测能力更强、更可靠的模型。