news 2026/10/20 6:26:02

随机森林详解:从 Bagging 到特征随机,理解算法原理与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
随机森林详解:从 Bagging 到特征随机,理解算法原理与实战

1. 引言:随机森林算法是什么?

在机器学习中,单棵决策树虽然直观、易于理解,却容易受到训练数据变化的影响:即使只增加或删除少量样本,也可能产生一棵结构明显不同的树。为了获得更稳定的预测,我们可以训练多棵决策树,再将它们的结果进行聚合。

在上一篇文章中,我们介绍了 Bagging(Bootstrap Aggregating)思想:通过有放回抽样构造不同的训练集,分别训练多个模型,再通过投票或平均得到最终预测。随机森林(Random Forest)正是在这一思路上发展起来的经典集成算法。

不过,⭐训练集不同,就一定能得到足够不同的决策树吗?

假如某个特征的预测能力很强,即使各棵树使用的样本有所不同,它们仍可能优先选择这个特征,形成相似的划分规则。当多棵树容易在相似的样本上犯错时,仅仅增加树的数量,带来的改善就可能受到限制。

随机森林进一步引入了特征随机性:在每个节点分裂时,随机抽取一部分特征作为候选,再从中寻找最佳划分。它希望在保留单棵树预测能力的同时,降低树之间的相关性,让多棵树的预测更具互补性。

这里我先给一个公式方便大家理解:

随机森林 = Bagging 集成思想 + 决策树作为基学习器 + 随机特征选择

本文将简要回顾 Bagging,重点分析它的功绩与瓶颈,以及随机森林如何通过特征随机性进行改进,最后结合算法流程与 Python 实战,理解随机森林如何训练、预测与评估。

2. 前提回顾:Bagging 的核心思想

Bagging 的完整原理可以参考前文,这里只回顾理解随机森林所需的三个步骤。

  • 有放回抽样:从原始训练集中有放回地随机抽取多个子集,每个子集的大小与原数据集相同,但其中可能包含重复样本。
  • 并行训练:在每个子集上独立训练一个基学习器,各学习器之间互不干扰。
  • 结果聚合:对于分类任务,采用投票法决定最终类别;对于回归任务,采用取平均值的方式得到最终预测结果。

2.1 有放回抽样:生成不同训练集

假设原始训练集包含 (N) 个样本。每次从中随机抽取一个样本,抽取后放回,重复 (N) 次,得到一份 Bootstrap 训练集。

由于采用有放回抽样:

  • 某些样本可能出现多次。
  • 某些样本可能没有被抽中。
  • 不同轮次得到的训练集通常有所不同。

重复这一过程,就可以构造多份训练集。

2.2 并行训练:让模型分别学习

在每份训练集上,分别训练一个基学习器

其中,(B) 表示基学习器的数量。

这些模型之间没有必须按顺序学习的依赖关系,因此可以并行训练。

2.3 结果聚合:共同给出预测

对于回归任务,通常取各模型预测的平均值:

对于分类任务,常见的思路是多数投票,也可以在基模型提供类别概率时先平均概率,再选择概率最大的类别。

Bagging 的基本流程可以概括为:

样本扰动 → 分别训练 → 预测聚合

回顾这三个步骤我们发现了一个问题 -> Bagging思想并没有保证:训练出来的模型一定具有足够大的差异,接下来让我们详细分析一下。

3. Bagging 的功绩与瓶颈:树多,还需要差异

3.1 功绩:降低预测方差

决策树,尤其是生长较深的决策树,这种深决策树是低偏差、高方差的模型。对训练数据的变化往往比较敏感。

例如,训练集中增加或删除少量样本,可能改变某个节点的最佳划分,而这个节点的变化又会影响后续整棵子树,训练数据的微小扰动就会让它面目全非。

这意味着:虽然单棵树可以拟合非常复杂的交互结构,但在不同训练集上训练得到的决策树,可能给出波动较大的预测。

Bagging 通过平均来“平滑”这种波动,而不损害其拟合能力将多棵树的预测进行聚合,可以减弱单棵树波动对最终结果的影响。它尤其适合决策树这类对数据扰动比较敏感的学习器。

不过,平均能够带来多大收益,还取决于这些预测之间的相关性。

3.2 用一个方差公式理解瓶颈

1. 为什么固定输入后,树的预测还有方差?

对于固定输入 (x),例如同一套房屋的特征。如果训练数据和训练过程都固定,一棵树的预测当然是确定的。但当我们改变训练样本或训练随机性时,得到的树会不同,对这个 (x) 的预测也可能不同,我们先分析一棵树的方差,即。

因此,我们把预测看作随机变量,并简写为:

森林对这个输入的平均预测为:

我们要计算的就是:

​​​​ ​

2. 先理解两棵树的情况

两个随机变量相加,方差公式是:

其中,协方差描述两个预测是否倾向于一起变化。

假设两棵树预测的方差都是,相关系数都是。根据相关系数的定义:

于是:

再利用:

得到两棵树平均预测的方差:

​​​​​​​ ​​​​​​​

看两个特殊情况就容易理解:

  • 如果=0 ,平均后的方差为/2。
  • 如果=1,平均后的方差仍为。

因为我们假设的是预测的方差都是,即两棵树如果完全同步波动,这种情况下取平均也无法抵消这种波动。

3. 推广到B棵树

多个随机变量之和的方差为:

​​​​​​​ ​​​​​​​

右边有两部分。

第一部分:各棵树自身的方差。

一共有B棵树,假设每棵树的预测方差为,所以:

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​

第二部分:不同树之间的协方差。

从B棵树中选两棵,共有组合数:

​​​​​​​ ​​​​​​​ ​​​​​​​

每一对的协方差都是,再乘公式前面的系数2,得到:

​​​​​​​ ​​​​​​​

求平均相当于乘上,所以方差要乘上:

​​​​​​​

这个公式揭示了两个影响因素:

  1. 树的数量 (B):增加树的数量,可以缩小第二项。
  2. 树之间的相关性 ():相关性越高,聚合后保留的共同波动越明显。
4. 为什么它能说明 Bagging 的瓶颈?

把结果拆成两项:

​​​​​​​ ​​​​​​​

假设单棵树的预测方差为 100,树之间的相关系数为 0.3,则:

树的数量 (B)平均预测的方差
1100
1037
10030.7
100030.07
趋于无穷大趋于 30

即用公式表示:

​​​​​​​ ​​​​​​​ ​​​​​​​

可以看到,增加树的数量,收益逐渐减小。

因此,在上述假设下在单树方差和相关系数保持不变的假设下,剩下的共同波动(相关性带来的那部分方差)无法靠继续增加树来消除。​​ ​​​

需要注意,这个公式用于解释平均预测的方差,不能直接当作分类错误率公式,也不能覆盖真实森林中的全部复杂因素。

4. 随机森林的破局:引入特征随机性

4.1 从“全部特征中选最好”到“候选特征中选最好”

随机森林作为 Bagging 思想的典型代表,在实际应用中展现出诸多优势,同时也存在一些局限性。

随机森林的关键改动,发生在决策树的节点分裂过程中。

普通决策树通常从全部特征中寻找最佳划分。随机森林则在每个节点:

  1. 从全部 p 个特征中,随机选取 m 个候选特征。
  2. 只在候选特征范围内搜索划分。
  3. 选择其中效果最好的特征与划分点。
  4. 进入子节点后,重新抽取候选特征。

例如,某个节点随机获得的候选集合是:

​ ​​​​​​​

那么,即使“房屋面积”在全部特征中最有优势,它也无法参与这个节点的分裂竞争。这让其他特征获得了参与构建决策规则的机会。

随机的是候选特征集合,候选集合内仍然要寻找优质划分。随机森林并不是随意选择一个特征,再随意切一刀。

4.2 两种随机性分别作用在哪里?

随机性发生位置主要作用
样本随机性每棵树训练前让不同树看到不同的样本组合
特征随机性每个节点分裂时让不同节点面对不同的候选特征集合

这里容易混淆的一点是:

经典随机森林的特征抽样发生在每个节点,而不是只在每棵树开始训练时抽取一次。

举例说明:

假设任务是分类,总特征数 N=100。根据公式​,每次节点分裂时抽取的特征数 m=10。

1. 根节点(第一次分裂):

  • 算法从全部100个特征中,随机、独立地抽取10个特征(假设抽到了编号 3, 15, 28, 42, 55, 61, 77, 80, 92, 99)。

  • 在这10个特征中寻找最佳分裂点。假设最终使用了特征15进行分裂。

2. 左子节点(第二次分裂):

  • 注意!此时算法重新从全部100个特征中随机抽取10个(假设这次抽到了 1, 2, 3, 4, 5, 15, 20, 30, 40, 50)。

  • 在这新的10个特征中找最佳分裂点。假设使用了特征2。

3. 右子节点(第三次分裂):

  • 再次从全部100个特征中随机抽取10个(假设抽到了 10, 20, 30, 40, 50, 60, 70, 80, 90, 100)。

  • 找最佳分裂点,假设用了特征90。

以此类推,直到树长满。

因此,一棵树的根节点和后续节点,可能使用不同的候选特征集合。

4.3 为什么特征越随机,效果不一定越好?

既然随机性能够增加差异,是否应该把候选特征数量降得越低越好?

答案是否定的。

当候选特征太少时,某些节点可能无法获得足够有用的特征,导致划分质量下降,单棵树的预测能力受到影响。

因此,随机森林需要平衡两个目标:

  • 单棵树需要有一定的预测能力。
  • 不同树之间需要有足够的差异。

对上面出现的候选特征公式深入理解一下:为什么是“根号N”?

这是一个偏差-方差权衡(Bias-Variance Tradeoff)的经验法则:

  • 如果 m 太大(比如接近 N):每次分裂都能看到大多数特征,单棵树会很强(偏差小),但树与树之间的差异会变小,变得高度相关。这会导致集成后的整体方差降不下来,失去了随机森林抵抗过拟合的优势。

  • 如果 m 太小:树与树之间差异很大(方差小),但单棵树因为能看的特征太少,可能长得很差(偏差大)。

​是一个在“保持单棵树一定准确率”和“降低树与树之间相关性”之间取得良好平衡的经验值(分类时)。

候选特征数量较大时,单棵树有更多机会找到好的划分,但不同树也可能更加相似;候选特征数量较小时,树之间的差异可能增大,但单棵树也可能变弱。

随机森林追求的是有效的差异,而不是随机性本身。

4.4 决策树、Bagging 与随机森林的区别

对比项单棵决策树Bagging 决策树经典随机森林
模型数量一棵多棵多棵
每棵树的训练样本通常使用完整训练集Bootstrap 抽样Bootstrap 抽样
节点候选特征通常为全部特征通常为全部特征随机特征子集
预测方式单树预测聚合多树预测聚合多树预测
核心思路学习划分规则通过聚合降低波动在聚合基础上进一步降低相关性

这里的 Bagging 决策树指采用常规全特征决策树作为基学习器的配置;Bagging 本身是更一般的框架,也可以搭配其他学习器或特征抽样策略。

5. 随机森林算法:训练与预测流程

5.1 训练过程

设训练集为:

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​

每个样本包含 p 个特征,森林包含 B 棵树,每个节点抽取 m 个候选特征。

训练过程如下:

输入:训练集 D,树的数量 B,候选特征数量 m

对 b = 1, 2, ..., B:

1. 从 D 中进行 Bootstrap 抽样,得到训练集 D_b

2. 使用 D_b 构建决策树:
对每个需要分裂的节点:
a. 随机抽取 m 个候选特征
b. 在候选特征中寻找最佳特征和划分点
c. 按照最佳划分生成子节点
d. 重复上述过程,直到满足停止条件

3. 保存决策树 h_b

输出:由 B 棵决策树组成的森林

停止条件可以包括最大深度、节点样本数不足、无法继续有效分裂等。经典随机森林通常使用生长较深的树,实际应用中也可以通过参数限制复杂度。

5.2 节点如何判断划分好坏?

以分类任务常用的基尼不纯度为例:

中,表示节点 t 中第 k 类样本的比例。

节点中的类别越混杂,基尼不纯度通常越高;如果全部样本属于同一类,则:

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​

对于某个候选划分,其不纯度下降量可以写为:

​​​​​​​ ​​​​​​​

算法在允许的候选范围内寻找使最大的划分。

随机森林改变了参与竞争的特征范围,节点内部仍然遵循决策树的划分原则。

5.3 预测过程

对于回归任务,对所有树的预测取平均:

​​​​​​​ ​​​​​​​ ​​​​​​​

对于分类任务,常见的概念描述是多数投票:

​​​​​​​ ​​​​​​​

简单理解:

  • 核心动作:“数人头”。每棵树只给出一个确定的类别(比如A或B),然后大家直接投票,少数服从多数。

  • 盲区:不管树有多确信,也只是一票。极度确信的树和瞎猜的树,权重是一样的。

不过,在本文使用的 scikit-learn 中,RandomForestClassifier会先平均各棵树给出的类别概率,再选择平均概率最大的类别:

简单理解:

  • 核心动作:“算平均分”。每棵树给出的是自己预测各个类别的概率。先所有树把概率加起来求平均,最后谁的平均概率最高,就选谁。

  • 优势:它考虑了树的“确信程度”。如果一棵树极度确信是A,即使别的树稍微偏向B,求平均后A依然能胜出。

概率平均与硬投票并不总是产生相同结果。阅读算法介绍与实际代码时,需要区分概念描述和具体实现。

5.4 袋外评估:利用未被抽中的样本

一次 Bootstrap 抽样中,某个样本在一次抽取时没有被选中的概率为:

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​

连续抽取 N 次都没有选中它的概率为:

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​

当 N 足够大时:

也就是说,一棵树进行等量 Bootstrap 抽样后,平均约有36.8% 的训练样本没有参与这棵树的训练,这些样本称为该树的袋外样本。

对于每个训练样本,可以只汇总那些没有使用过它的树所给出的预测,再计算整体指标,这就是袋外评估(OOB)。

这里要注意:

  • 不同树对应的袋外样本集合不同。
  • 36.8% 是上述抽样条件下的近似比例。
  • 如果反复根据 OOB 分数选择参数,OOB 也参与了模型选择,仍应保留独立测试集做最终评估。

不会有人对这块看不懂吧:

那我来给你举个例子你带入到上面再想一想:

36.8%是怎么来的假设你有一个袋子,里面有 N 个球(代表 N 个训练样本)。重复这个过程 N 次,最后求极值就好啦。

OOB到底是什么:

假设你的整个数据集有 1000 个样本。你训练了 100 棵树。
现在我们盯住其中的第 42 号样本,看看红框里的话是怎么操作的:

  • 第一步:找没用过它的树。在这 100 棵树里,每一棵树都有约 36.8% 没见过的数据。所以,必定有几十棵树在训练时没有抽中第 42 号样本。

  • 第二步:只让这些树给它打分。我们把那些在训练时见过 42 号样本的树全部踢掉,只保留那些没见过它的树。让这几十棵树对 42 号样本进行预测(投票或算概率)。

  • 第三步:对比真实答案。这几十棵“瞎眼”的树给出了一个综合预测结果。我们拿这个结果和 42 号样本的真实标签比一比,看预测对不对。

把上面的过程,对全部 1000 个样本都做一遍。

  • 每一个样本,都只由那些“没在训练时见过它”的树来预测。

  • 最后算出一个总的准确率或误差。

这就叫:袋外评估(OOB Error / OOB Score)。

5.5 关键参数

参数含义主要影响
n_estimators树的数量影响聚合稳定性、训练时间与模型大小
max_features每个节点考虑的候选特征数量调节单树能力与树间差异
max_depth单棵树最大深度限制树的复杂度
min_samples_leaf叶节点最小样本数避免叶节点只拟合极少量样本
max_samplesBootstrap 抽样规模控制每棵树使用的样本量
bootstrap是否采用有放回抽样决定是否引入该类样本随机性
oob_score是否计算袋外得分在 Bootstrap 配置下提供内部评估
random_state随机种子帮助复现实验

增加树的数量通常会让预测逐渐稳定,但收益可能趋于饱和。若主要问题是信息不足、数据泄漏或单树设置不合理,单纯增加树的数量也无法解决。

6. Python 实战:基于泰坦尼克号数据预测乘客生存情况

理解随机森林的原理后,我们通过泰坦尼克号乘客生存预测任务,观察单棵决策树与随机森林的表现,并使用网格搜索寻找更合适的参数。

本节设置三个实验场景:

  1. 单棵决策树。
  2. 使用默认主要参数的随机森林。
  3. 通过网格搜索调参的随机森林。

通过对照实验,我们重点观察:多树集成是否改善了模型的泛化表现,以及限制树的复杂度能否进一步提高预测效果。

6.1 任务说明与数据准备

本次使用titanic_train.csv数据,根据乘客信息预测其是否生还。

选取以下三个特征:

字段含义处理方式
Pclass船票等级保留数值形式
Sex性别独热编码
Age年龄使用训练数据的平均值填充缺失值

预测目标为Survived:

  • 0:未生还。
  • 1:生还。

这里只选择三个特征,便于理解从数据处理到模型评估的完整流程。实际任务中,还可以进一步探索票价、登船港口、同行亲属数量等信息。

6.2 数据预处理:先划分,再学习填充规则

年龄特征存在缺失值,可以使用平均值进行填充。但均值应仅根据训练数据计算,避免测试集的信息提前参与建模。

因此,需要遵循以下顺序:

划分训练集与测试集 → 根据训练集学习预处理规则 → 将相同规则应用到测试集。

交叉验证时也一样:每一折的填充规则,都应只从该折的训练部分学习。

本节使用Pipeline和ColumnTransformer,将以下操作连接起来:

  • 对Age使用平均值填充。
  • 对Sex使用众数填充,再进行独热编码。
  • 对Pclass保留数值形式。
  • 将处理后的特征交给分类模型。

这样,预处理会随模型一起参与交叉验证,避免提前利用验证数据的信息。

划分数据时,还使用stratify=y,使训练集与测试集中的类别比例尽量接近原始数据。

6.3 场景一:训练单棵决策树

首先训练一棵决策树,作为后续实验的对照基线:

DecisionTreeClassifier(random_state=23)

模型训练完成后,分别计算训练集与测试集准确率:

model.score(x_train, y_train) model.score(x_test, y_test)

训练集准确率反映模型对已见数据的拟合程度,测试集准确率则用于评估模型对未见数据的预测表现。

如果训练准确率很高,而测试准确率明显较低,就需要警惕过拟合。因此,不能仅凭训练集上的高分判断模型效果。

6.4 场景二:训练随机森林

接下来训练随机森林:

RandomForestClassifier( random_state=23, n_jobs=-1, )

这里保留默认的主要建模参数,只设置随机种子和并行计算。

其中:

  • random_state:控制随机过程,便于复现实验。
  • n_jobs=-1:允许使用所有可用处理器核心并行训练。

随机森林通过样本随机性和节点特征随机性构建多棵决策树,再聚合各棵树的预测结果。

我们将比较它与单棵决策树的训练、测试准确率,观察集成是否改善了当前任务中的泛化表现。

6.5 场景三:使用网格搜索调整随机森林参数

默认参数提供了建模起点,但不一定适合当前数据。

本次设置以下参数搜索范围:

{ "n_estimators": [60, 90, 100, 130, 150], "max_depth": [3, 5, 7, 9], }

两个参数分别控制:

参数含义本次实验关注点
n_estimators森林中树的数量增加树的数量能否改善预测表现
max_depth单棵树的最大深度限制树的复杂度能否改善泛化表现

共有: 5 * 4 = 20组候选参数。结合三折交叉验证,需要完成:20 * 3 = 60次候选模型拟合。

搜索结束后,GridSearchCV默认还会使用最佳参数,在完整训练集上重新拟合模型。

由于分类器放在流水线的model步骤中,搜索参数名称需要增加model__前缀:

{ "model__n_estimators": [60, 90, 100, 130, 150], "model__max_depth": [3, 5, 7, 9], }

网格搜索前不需要单独训练分类器,直接调用GridSearchCV.fit()即可。

6.6 完整代码

以下代码包含数据加载、预处理、三个场景的模型训练,以及结果对比。运行前,请根据数据文件的实际位置修改DATA_PATH。

from pathlib import Path import pandas as pd from sklearn.compose import ColumnTransformer from sklearn.ensemble import RandomForestClassifier from sklearn.impute import SimpleImputer from sklearn.metrics import classification_report, confusion_matrix from sklearn.model_selection import ( GridSearchCV, StratifiedKFold, train_test_split, ) from sklearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder from sklearn.tree import DecisionTreeClassifier # 根据实际文件位置修改路径 DATA_PATH = Path("../data/titanic_train.csv") RANDOM_STATE = 23 def build_pipeline(model): """将数据预处理与分类模型组合。""" # 年龄:使用当前训练数据的平均值填充 age_transformer = SimpleImputer(strategy="mean") # 性别:使用众数填充,再进行独热编码 sex_transformer = Pipeline([ ("imputer", SimpleImputer(strategy="most_frequent")), ( "encoder", OneHotEncoder(handle_unknown="ignore"), ), ]) # 对不同列分别进行处理 preprocessor = ColumnTransformer([ ("age", age_transformer, ["Age"]), ("sex", sex_transformer, ["Sex"]), ("pclass", "passthrough", ["Pclass"]), ]) return Pipeline([ ("preprocessor", preprocessor), ("model", model), ]) def evaluate_model(name, model, x_train, x_test, y_train, y_test): """记录训练集与测试集准确率。""" return { "模型": name, "训练集准确率": model.score(x_train, y_train), "测试集准确率": model.score(x_test, y_test), } def main(): # 1. 加载数据 data = pd.read_csv(DATA_PATH) x = data[["Pclass", "Sex", "Age"]].copy() y = data["Survived"] print("数据规模:", x.shape) print("\n特征缺失情况:") print(x.isna().sum()) # 2. 划分训练集与测试集 x_train, x_test, y_train, y_test = train_test_split( x, y, test_size=0.2, random_state=RANDOM_STATE, stratify=y, ) results = [] # 3. 场景一:单棵决策树 tree = build_pipeline( DecisionTreeClassifier( random_state=RANDOM_STATE, ) ) tree.fit(x_train, y_train) results.append( evaluate_model( "单棵决策树", tree, x_train, x_test, y_train, y_test, ) ) # 4. 场景二:保留默认主要参数的随机森林 forest = build_pipeline( RandomForestClassifier( random_state=RANDOM_STATE, n_jobs=-1, ) ) forest.fit(x_train, y_train) results.append( evaluate_model( "默认随机森林", forest, x_train, x_test, y_train, y_test, ) ) # 5. 场景三:网格搜索随机森林 # 搜索阶段由 GridSearchCV 并行, # 单个森林内部使用一个工作进程,避免嵌套并行。 search_pipeline = build_pipeline( RandomForestClassifier( random_state=RANDOM_STATE, n_jobs=1, ) ) param_grid = { "model__n_estimators": [60, 90, 100, 130, 150], "model__max_depth": [3, 5, 7, 9], } cv = StratifiedKFold( n_splits=3, shuffle=True, random_state=RANDOM_STATE, ) search = GridSearchCV( estimator=search_pipeline, param_grid=param_grid, scoring="accuracy", cv=cv, n_jobs=-1, refit=True, ) search.fit(x_train, y_train) results.append( evaluate_model( "网格搜索随机森林", search, x_train, x_test, y_train, y_test, ) ) # 6. 输出三个模型的评估结果 result_df = pd.DataFrame(results).set_index("模型") print("\n模型评估对比:") print(result_df.round(4).to_string()) # 去掉流水线参数前缀,方便阅读 best_params = { key.replace("model__", ""): value for key, value in search.best_params_.items() } print("\n最佳参数:") print(best_params) print( "\n最佳参数对应的交叉验证平均准确率:" f"{search.best_score_:.4f}" ) # 7. 查看交叉验证排名靠前的参数组合 cv_results = pd.DataFrame(search.cv_results_) columns = [ "param_model__n_estimators", "param_model__max_depth", "mean_test_score", "std_test_score", "rank_test_score", ] print("\n交叉验证排名前五的参数组合:") print( cv_results[columns] .sort_values("rank_test_score") .head(5) .to_string(index=False) ) # 8. 查看调参模型的详细测试结果 y_pred = search.predict(x_test) print("\n测试集前十条预测:") prediction_df = pd.DataFrame({ "真实值": y_test.to_numpy(), "预测值": y_pred, }) print(prediction_df.head(10).to_string(index=False)) print("\n分类报告:") print( classification_report( y_test, y_pred, labels=[0, 1], target_names=["未生还", "生还"], digits=4, zero_division=0, ) ) print("\n混淆矩阵:") print( confusion_matrix( y_test, y_pred, labels=[0, 1], ) ) if __name__ == "__main__": main()

本例保留性别独热编码的全部类别列。对决策树而言,不需要像某些线性模型那样,为避免完全共线性而专门删除其中一列。

6.7 实验结果如何分析?

代码运行后会输出模型对比表、最佳参数、交叉验证得分、分类报告和混淆矩阵。具体数值以实际运行结果为准。

分析时,应重点回答以下问题。

(1)随机森林是否改善了测试表现?

首先比较单棵决策树与默认随机森林的测试准确率。

如果随机森林更高,说明在当前数据划分和参数配置下,多树集成带来了泛化收益。

如果两者接近,甚至随机森林略低,也应如实记录。数据规模、特征信息与参数设置都会影响最终表现。

随机森林并不保证在每一次数据划分中都优于单棵决策树。

(2)限制树深度后,模型表现发生了什么变化?

比较默认随机森林与网格搜索随机森林的训练、测试准确率。

如果最佳参数选择了较小的max_depth,模型的训练准确率可能下降,但测试准确率可能提高。这说明限制树的复杂度,可能减少了对训练细节的拟合。

不过,不能只看训练与测试分数的差距。如果两个分数都很低,即使差距很小,也可能存在欠拟合。

因此,需要同时观察:

  • 训练集上的拟合程度。
  • 测试集上的预测表现。
  • 二者之间的差距。
(3)交叉验证得分与测试得分有什么区别?

search.best_score_表示最佳参数组合在训练集内部的交叉验证平均准确率。

而:

search.score(x_test, y_test)

表示模型在独立测试集上的准确率。

两者使用的数据不同,结果不必相同。

此外,最佳参数只是当前候选范围、评价指标与交叉验证划分下的最佳选择,并不代表所有参数组合中的全局最优。

(4)为什么不能只看准确率?

准确率反映整体预测正确的比例,但不能完整展示不同类别的识别情况。

分类报告中的主要指标包括:

指标含义
Precision被预测为某一类的样本中,实际属于该类的比例
Recall实际属于某一类的样本中,被正确识别的比例
F1-score精确率与召回率的调和平均
Support该类别在评估数据中的样本数量

混淆矩阵则可以进一步展示错误的方向。本例固定类别顺序为[0, 1],其结构为:

​​​​​​​

结合这些指标,才能更具体地理解模型在哪些样本上容易出错。

6.8 本案例的边界与改进方向

本案例展示了从单棵决策树到随机森林,再到参数搜索的完整流程,但还不能单独证明特征随机性的作用。

原因是,单棵树与随机森林之间同时存在样本抽样、多树聚合和特征随机性等差异。如果希望专门研究特征随机性,可以在其他设置相同的情况下,对比不同的max_features配置。

后续还可以从以下方向改进:

  • 丰富特征信息:加入票价、登船港口、家庭规模等变量。
  • 扩展参数搜索:加入max_features、min_samples_leaf等参数。
  • 完善验证方式:在训练集内部比较候选方案,保留独立测试集用于最终评估。

特别需要注意,不能在查看测试结果后,反复根据测试分数修改特征和参数,否则测试集也会逐渐参与模型选择,失去独立评估的意义。

这个案例的重点不仅是调用随机森林接口,更是建立完整的建模思路:先建立基线,再引入集成与参数搜索,最后通过独立评估判断改进是否有效。

7. 总结

从决策树到 Bagging,再到随机森林,可以看到一条清晰的改进路线:

单棵决策树容易受到训练数据变化的影响;Bagging 通过训练多棵树并聚合预测,减弱这种波动;随机森林进一步限制每个节点的候选特征,尝试降低树之间的相关性。

它的核心启示是:

集成模型的效果,既取决于个体是否具有预测能力,也取决于不同个体能否提供互补信息。

随机森林能够学习非线性关系和特征交互,通常不需要为了树的划分专门进行特征标准化,因此适合作为表格数据任务的重要基线模型。

同时,它也有实际限制:树的数量增加会带来计算和存储成本,整体规则比单棵树更难直接解释;标准回归森林通过叶节点预测再求平均,也不擅长外推到训练目标取值范围之外。

回到文章开头的问题:为什么已经有了 Bagging,还需要特征随机性?

因为让模型看到不同的样本,只是制造差异的一种方式;让模型在不同的候选特征中寻找规则,可以进一步拓宽它们探索问题的路径。而这种差异是否真正有益,最终仍需要通过合理的验证与对照实验来判断。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 6:25:54

可编程幻灯片系统:用代码自动化生成PPT

1. 项目概述:这不是一个PPT模板,而是一套可编程的幻灯片生成系统“ppt-master”这个名称乍一听像是某个网红PPT模板合集,或者某款带“Master”字样的美化插件。但实际接触过的人会发现,它根本不是传统意义上的设计资源包——它是一…

作者头像 李华
网站建设 2026/10/20 6:25:08

Windows进程间通信实战:从命名管道到共享内存的选型与避坑

做Windows开发这几年,进程间通信(IPC)几乎是每个项目都绕不开的坎。你可能会想:同一个程序里线程之间能共享变量,多开几个进程直接读写同一块内存不就行了?真去做就会发现,系统在进程之间立了一…

作者头像 李华
网站建设 2026/10/20 6:25:05

Word公式粘贴到富文本编辑器卡顿?一套性能优化实战方案

1. Word里的公式看着“人畜无害”,粘贴到百度编辑器怎么就成了性能炸弹接手一个在线教育平台的内容后台之后,我收到的用户吐槽里,出现频率最高的场景不是课程视频卡顿,也不是作业提交失败,而是:“我在Word里…

作者头像 李华
网站建设 2026/10/20 6:25:04

牛客tracker+每日一题:数据化刷题让面试准备更高效

“买橘子”这个梗,在程序员圈子里早就不单纯是朱自清《背影》里那个桥段了。一群刷题求职的人,把每天雷打不动的“每日一题”自嘲成“给爹买橘子”——既是对毅力的调侃,也是对未来的期许。而真正让这个梗落地成体系的,是“牛客tr…

作者头像 李华
网站建设 2026/10/20 6:24:47

AI创业的真实难度:从技术领先到商业落地的关键妥协

最近几个月,我在不同场合反复听到同一种声音:曾经最坚定地说“AI会颠覆一切”的人,开始公开承认“没那么容易”了。这种转变很有意思,它不是一个具体人物的失败,更像是整个行业从青春期进入成年期的标志。今天想聊一聊…

作者头像 李华
网站建设 2026/10/20 6:24:34

CANN Runtime与Driver接口拆解:用户态与内核态交互全链路分析

我读CANN Runtime与Driver接口的代码时,最深的感受是:真正决定整个软件栈稳定性与性能的,不是应用层怎么调用API,而是那一层用户态和内核态之间的接口边界。这个边界上既有Runtime的封装逻辑,又有Driver的设备管理逻辑…

作者头像 李华