前面学习决策树时,我们提到过一个问题:单棵决策树虽然直观、可解释,但很容易过拟合,也很容易受到数据扰动影响。

集成学习(Ensemble Learning)就是为了解决这类问题而出现的思想。它不再把希望全部压在一个模型身上,而是训练多个模型,再把它们的结果组合起来。

可以把它理解成:一个人判断可能会偏,很多人从不同角度判断,再投票或取平均,结果通常更稳定。

集成学习示意图

1. 什么是集成学习

集成学习的核心思想很简单:

训练多个基模型,再把这些模型的预测结果组合成最终结果。

这里的基模型也叫弱学习器(Weak Learner)或基学习器(Base Learner)。它不一定真的很弱,只是相对于最终组合模型来说,每个单独模型只负责贡献一部分判断。

例如一个分类任务中,我们训练了 5 个模型,它们分别预测:

模型 1:通过
模型 2:通过
模型 3:不通过
模型 4:通过
模型 5:通过

如果使用多数投票,最终结果就是“通过”。

如果是回归任务,例如预测房价,多个模型分别预测:

90 万、94 万、92 万、96 万、93 万

最终可以取平均值:

$$
\hat{y}=\frac{90+94+92+96+93}{5}=93
$$

这就是集成学习最朴素的形式。

2. 为什么多个模型会更好

单个模型可能会犯两类典型错误:

  • 偏差大:模型太简单,学不到数据规律,容易欠拟合。
  • 方差大:模型太复杂,对训练数据很敏感,容易过拟合。

决策树就是一个典型的高方差模型。训练数据稍微变一下,树的结构可能就明显不同。

集成学习通常希望通过组合多个模型来降低错误:

  • Bagging 主要降低方差,让模型更稳定。
  • Boosting 主要降低偏差,让模型一步步变强。
  • Stacking 尝试学习“不同模型应该怎么组合”。

它们都是集成学习,但思路并不一样,下方会对各个思路进行介绍。

3. 投票与平均

最简单的组合方式是投票和平均。

3.1 分类任务:投票

分类任务中,如果每个模型输出一个类别,可以使用多数投票:

$$
\hat{y} = mode(h_1(x), h_2(x), …, h_T(x))
$$

其中:

  • $h_t(x)$ 表示第 $t$ 个模型对样本 $x$ 的预测。
  • $T$ 表示模型数量。
  • $mode$ 表示取出现次数最多的类别。

如果模型能输出概率,也可以先平均概率,再选择概率最大的类别。

例如二分类中,三个模型给出的正类概率分别是:

0.70, 0.62, 0.81

平均概率为:

$$
\frac{0.70+0.62+0.81}{3}=0.71
$$

如果阈值是 0.5,最终预测为正类。

3.2 回归任务:平均

回归任务中,常见做法是直接平均多个模型的预测:

$$
\hat{y}=\frac{1}{T}\sum_{t=1}^{T}h_t(x)
$$

平均可以抵消一部分随机误差。只要多个模型不是以完全相同的方式犯错,组合结果就可能比单个模型更稳。

4. Bagging:并行训练多个模型

Bagging 是 Bootstrap Aggregating 的缩写。它的核心思想是:

从原始数据中有放回地抽样,训练多个模型,再把结果平均或投票。

Bagging 与 Bootstrap 抽样流程

“有放回抽样”可以这样理解:每次抽一个样本后,再把它放回去,下一次仍然可能抽到它。

假设原始训练集有 1000 个样本,Bagging 会反复抽样,得到多个新的训练集:

训练集 A:抽 1000 次,有些样本会重复,有些样本没被抽到
训练集 B:再抽 1000 次,得到另一份不同的数据
训练集 C:继续抽样

然后分别训练多个模型:

数据 A -> 模型 A
数据 B -> 模型 B
数据 C -> 模型 C

最后把它们的预测组合起来。

Bagging 的关键是让每个模型看到的数据不完全一样,从而产生差异。模型之间越有差异,组合后越有可能降低方差。

4.1 一个实际例子:用 Bagging 预测房价

假设我们要预测一套房子的价格,训练数据中有这些特征:

面积、楼层、房龄、距离地铁距离、所在区域、是否有电梯

如果只训练一棵决策树,它可能刚好学到一些很偶然的规则,比如:

如果区域 = A 且楼层 > 20,则房价很高

但这条规则可能只是训练集里的巧合。换一批数据,这棵树的结构可能就变了。

Bagging 的做法是:

  1. 从原始房价数据中有放回抽样,生成第 1 份训练集,训练树 1。
  2. 再有放回抽样,生成第 2 份训练集,训练树 2。
  3. 重复很多次,得到很多棵树。
  4. 每棵树都对同一套新房子预测价格。
  5. 最后把这些价格取平均。

例如 5 棵树的预测结果是:

树 1:92 万
树 2:88 万
树 3:95 万
树 4:90 万
树 5:93 万

Bagging 的最终预测为:

$$
\hat{y}=\frac{92+88+95+90+93}{5}=91.6
$$

这里每棵树都可能有自己的偏差,但平均以后,某一棵树的偶然错误会被其他树抵消。对新手来说,可以先把 Bagging 理解成“让很多个不完全一样的模型一起估价,然后取平均”。

4.2 Bootstrap 抽样的数学含义

假设原始训练集为:

$$
D={(x_1,y_1),(x_2,y_2),…,(x_n,y_n)}
$$

Bagging 会从 $D$ 中有放回地抽取 $n$ 次,得到一份新的训练集 $D_t$。因为是有放回抽样,同一个样本可能被抽中多次,也可能一次都没有被抽中。

对于某一个固定样本,它在一次抽样中没有被抽到的概率是:

$$
1-\frac{1}{n}
$$

连续抽 $n$ 次都没有抽到它的概率是:

$$
\left(1-\frac{1}{n}\right)^n
$$

当 $n$ 很大时:

$$
\left(1-\frac{1}{n}\right)^n \approx e^{-1} \approx 0.368 (求极限)
$$

也就是说,一份 Bootstrap 数据集中大约有 $36.8%$ 的原始样本没有出现,约 $63.2%$ 的原始样本至少出现一次。这些没被抽到的样本也叫袋外样本(Out-of-Bag Samples),可以用来估计模型表现。

4.3 Bagging 为什么能降低方差

假设有 $T$ 个基模型,它们的预测分别为:

$$
h_1(x),h_2(x),…,h_T(x)
$$

回归任务中,Bagging 的最终预测是平均值:

$$
H(x)=\frac{1}{T}\sum_{t=1}^{T}h_t(x)
$$

如果每个基模型的方差都是 $\sigma^2$,并且模型之间相互独立,那么平均后的方差为:

$$
Var(H)=\frac{\sigma^2}{T}
$$

这说明模型数量越多,平均结果越稳定。

真实情况中,不同模型不可能完全独立。假设任意两个模型之间的相关系数为 $\rho$,那么平均后的方差可以近似写成:

$$
Var(H)=\rho\sigma^2+\frac{1-\rho}{T}\sigma^2
$$

这个公式很重要(我们的目的是要方差尽量小),它说明了两件事:

  • 增加模型数量 $T$ 可以降低后一项方差。
  • 降低模型之间的相关性 $\rho$ 也很重要。

这就是为什么随机森林不仅要对样本做随机抽样,还要对特征做随机选择。它的目的就是让树和树之间不要太像。

5. 随机森林:Bagging + 决策树

随机森林(Random Forest)是 Bagging 最经典的应用。

它训练很多棵决策树,但每棵树都有两层随机性:

  1. 样本随机:每棵树使用 Bootstrap 抽样得到的数据。
  2. 特征随机:每次节点划分时,只从一部分特征中选择最优划分。

单棵决策树容易过拟合,但很多棵“看过不同数据、关注不同特征”的树组合起来,就会稳定很多。

随机森林做分类时,通常使用投票:

树 1:通过
树 2:不通过
树 3:通过
树 4:通过
树 5:不通过
最终:通过

做回归时,通常使用平均:

树 1:91
树 2:95
树 3:92
树 4:90
最终:92

随机森林的优点是工程上很实用:不太需要特征缩放,对非线性关系友好,稳定性通常比单棵树好很多。

5.1 一个实际例子:随机森林如何判断学生是否通过

假设我们要预测学生是否能通过考试,特征包括:

学习时长、睡眠时长、作业完成率、上课出勤率、历史平均分

单棵决策树可能会先问:

学习时长是否大于 4 小时?

另一棵树可能因为抽到的数据不同、可选特征不同,先问:

作业完成率是否大于 80%?

还有一棵树可能先问:

历史平均分是否大于 70?

对于一个新学生:

学习 3.5 小时
睡眠 7 小时
作业完成率 90%
出勤率 95%
历史平均分 76

不同树可能给出不同判断:

树 1:不通过
树 2:通过
树 3:通过
树 4:通过
树 5:不通过

最终随机森林投票为“通过”。

这里的关键不是每棵树都完美,而是每棵树从不同角度看问题。有的树更重视学习时长,有的树更重视作业完成率,有的树更重视历史成绩。组合起来后,模型不容易被某一个偶然规则带偏。

6. 随机森林的重要参数

使用随机森林时,可以重点关注几个参数。

参数 含义 直观理解
n_estimators 树的数量 树越多通常越稳定,但训练更慢
max_depth 每棵树最大深度 控制单棵树复杂度
max_features 每次划分可用的特征数量 增加树之间的差异
min_samples_leaf 叶子节点最少样本数 防止叶子节点过小
bootstrap 是否使用有放回抽样 随机森林通常为 True

一般来说,n_estimators 可以适当设大一些,例如 100、300、500;如果过拟合明显,可以限制 max_depth 或增大 min_samples_leaf

7. Boosting:串行训练多个模型

Bagging 中,多个模型通常是并行训练的,彼此之间没有先后依赖。

Boosting 不一样。它是一种串行思想:

后一个模型重点学习前一个模型没学好的部分。

Boosting 串行修正流程

可以把它想象成做错题本:

  1. 第一个模型先做一遍题。
  2. 找出它做错或做得不好的地方。
  3. 第二个模型重点补这些错误。
  4. 后面的模型继续修正前面整体模型的不足。

最终模型不是简单地“大家同时投票”,而是一步步累加出来的。

Boosting 的一般形式可以写成:

$$
F_T(x)=\sum_{t=1}^{T}\alpha_t h_t(x)
$$

其中:

  • $h_t(x)$ 是第 $t$ 个基模型。
  • $\alpha_t$ 是这个模型的权重。
  • $F_T(x)$ 是最终组合模型。

Boosting 往往能取得很强的预测效果,但如果参数控制不好,也可能过拟合。

7.1 Boosting 的数学形式

Boosting 不是一次性训练一个复杂模型,而是一步步构造加法模型:

$$
F_0(x), F_1(x), F_2(x), …, F_T(x)
$$

每一轮都在原有模型基础上加一个新的基模型:

$$
F_t(x)=F_{t-1}(x)+\alpha_t h_t(x)
$$

其中:

  • $F_{t-1}(x)$ 表示前 $t-1$ 轮已经得到的整体模型。
  • $h_t(x)$ 表示第 $t$ 轮新训练出来的基模型。
  • $\alpha_t$ 表示第 $t$ 个基模型的权重或步长。

训练目标是让整体损失尽可能小:

$$
\sum_{i=1}^{n}L(y_i,F_T(x_i))
$$

所以 Boosting 的本质可以理解为:每一轮都问自己一个问题:

如果现在的整体模型还不够好,下一步应该加一个什么模型,才能让损失下降?

7.2 一个实际例子:Boosting 如何一步步改错

还是用“是否通过考试”的例子。假设有 6 个学生:

学生 真实结果 第 1 个模型预测
A 通过 通过
B 通过 不通过
C 不通过 不通过
D 通过 通过
E 不通过 通过
F 不通过 不通过

第 1 个模型错了 B 和 E。

Bagging 会让其他模型独立训练,不会特别盯着 B 和 E。但 Boosting 会说:

B 和 E 是当前模型没学好的样本,下一轮要重点处理它们。

于是第 2 个模型训练时,会更关注 B 和 E。它可能学到:

B 虽然学习时长不长,但作业完成率很高,所以可能通过。
E 虽然学习时长较长,但历史平均分很低,所以可能不通过。

第 2 个模型不是从零替代第 1 个模型,而是补第 1 个模型的不足。最终预测可以理解成:

最终判断 = 第 1 个模型的判断 + 第 2 个模型的修正 + 第 3 个模型的修正 + ...

所以 Boosting 的味道和 Bagging 很不一样。Bagging 像“多个人分别判断后投票”,Boosting 更像“先做一版答案,再一轮轮检查错题并修改”。

如果把这个过程写成公式,就是:

$$
F_2(x)=F_1(x)+\alpha_2h_2(x)
$$

假设对学生 B 来说,第 1 个模型给出的分数是:

$$
F_1(x_B)=-0.4
$$

这里分数小于 0,表示模型倾向于预测“不通过”。但学生 B 的真实结果是“通过”,所以这个预测错了。

第 2 个模型专门补这个错误,给 B 一个正向修正:

$$
h_2(x_B)=1,\quad \alpha_2=0.7
$$

那么更新后的分数为:

$$
F_2(x_B)=F_1(x_B)+\alpha_2h_2(x_B)
=-0.4+0.7\times1=0.3
$$

更新后 $F_2(x_B)>0$,最终就会更倾向于预测“通过”。这就是 Boosting 中“后一个模型修正前一个模型错误”的具体计算方式。

8. AdaBoost:提高错分样本的权重

AdaBoost 是较早的 Boosting 算法。它的核心思想是:上一轮分错的样本,下一轮要更重视。

假设第一轮模型把一些样本分错了,那么这些样本的权重会变大。第二轮训练时,模型会更关注这些“难样本”。

流程可以概括为:

  1. 初始化所有样本权重相同。
  2. 训练一个弱分类器。
  3. 根据分类错误率计算该分类器的权重。
  4. 提高分错样本的权重,降低分对样本的权重。
  5. 重复训练多个分类器。
  6. 最终按分类器权重加权投票。

如果一个分类器表现好,它在最终投票中权重更高;如果表现差,它的权重更低。

8.1 AdaBoost 的权重更新

AdaBoost 会给每个样本分配一个权重。第 $t$ 轮时,第 $i$ 个样本的权重记为:

$$
w_i^{(t)}
$$

第 $t$ 个弱分类器的加权错误率为:

$$
\epsilon_t=\sum_{i=1}^{n}w_i^{(t)}I(h_t(x_i)\neq y_i)
$$

其中 $I(\cdot)$ 是指示函数,如果分类错误就取 1,否则取 0。

分类器权重为:

$$
\alpha_t=\frac{1}{2}\ln\frac{1-\epsilon_t}{\epsilon_t}
$$

可以看到:

  • 如果错误率 $\epsilon_t$ 小,说明分类器表现好,$\alpha_t$ 会比较大。
  • 如果错误率接近 $0.5$,说明分类器接近乱猜,$\alpha_t$ 会比较小。

样本权重更新公式可以写成:

$$
w_i^{(t+1)}=w_i^{(t)}\exp(-\alpha_t y_i h_t(x_i))
$$

这里通常把标签写成 $y_i\in{-1,1}$,分类器输出也写成 $h_t(x_i)\in{-1,1}$。

如果样本被分对,那么 $y_i h_t(x_i)=1$,权重会乘上 $\exp(-\alpha_t)$,变小。

如果样本被分错,那么 $y_i h_t(x_i)=-1$,权重会乘上 $\exp(\alpha_t)$,变大。

这就是“错题更重要”的数学表达。

8.2 一个实际例子:AdaBoost 如何调整权重

假设我们有 5 个训练样本,任务是判断学生是否通过考试。为了方便理解,一开始每个样本权重都一样:

学生 真实结果 初始权重
A 通过 0.2
B 通过 0.2
C 不通过 0.2
D 通过 0.2
E 不通过 0.2

第 1 个弱分类器可能只会看一个简单规则:

学习时长 >= 4 小时 -> 通过
否则 -> 不通过

假设它预测错了 B 和 E,那么 AdaBoost 会提高 B、E 的权重,降低 A、C、D 的权重。下一轮训练时,模型会更在意 B、E,因为它们现在“分量更重”。

这件事不是口头说“提高”这么简单,而是可以直接算出来。

第 1 轮中 B 和 E 被分错,其他样本分对。因为每个样本初始权重都是 0.2,所以第 1 个弱分类器的加权错误率为:

$$
\epsilon_1 = w_B + w_E = 0.2 + 0.2 = 0.4
$$

弱分类器的权重为:

$$
\alpha_1=\frac{1}{2}\ln\frac{1-\epsilon_1}{\epsilon_1}
=\frac{1}{2}\ln\frac{1-0.4}{0.4}
=\frac{1}{2}\ln 1.5
\approx 0.203
$$

接下来更新每个样本的权重。AdaBoost 的权重更新公式是:

$$
w_i^{(2)}=w_i^{(1)}\exp(-\alpha_1 y_i h_1(x_i))
$$

如果样本分对,$y_i h_1(x_i)=1$:

$$
w_i^{(2)}=0.2\times \exp(-0.203)\approx0.2\times0.816=0.163
$$

如果样本分错,$y_i h_1(x_i)=-1$:

$$
w_i^{(2)}=0.2\times \exp(0.203)\approx0.2\times1.225=0.245
$$

所以归一化前的权重大致是:

学生 第 1 轮是否分错 下一轮权重变化
A 分对 0.163
B 分错 0.245
C 分对 0.163
D 分对 0.163
E 分错 0.245

但权重需要重新归一化,让它们加起来等于 1。归一化系数为:

$$
Z=0.163+0.245+0.163+0.163+0.245=0.979
$$

归一化后:

学生 更新后权重
A $\frac{0.163}{0.979}\approx0.166$
B $\frac{0.245}{0.979}\approx0.250$
C $\frac{0.163}{0.979}\approx0.166$
D $\frac{0.163}{0.979}\approx0.166$
E $\frac{0.245}{0.979}\approx0.250$

可以看到,B 和 E 的权重从 0.2 增加到了约 0.25;A、C、D 的权重从 0.2 降低到了约 0.166。

于是第 2 个弱分类器会尝试寻找能分对 B、E 的规则。例如它可能不再只看学习时长,而是看“作业完成率”或“历史平均分”。

最终投票时,也不是每个弱分类器一票同权。表现好的分类器权重大,表现差的分类器权重小。也就是说,AdaBoost 同时做了两件事:

  • 对样本:分错的样本下一轮更重要。
  • 对模型:错误率低的模型最终投票更重要。

这就把“关注错题”和“相信好老师”结合在了一起。

9. GBDT:用新树拟合残差

GBDT(Gradient Boosting Decision Tree)可以理解为:用一棵棵决策树不断修正当前模型的错误。

GBDT 拟合残差示意图

以回归任务为例,假设真实房价是 100 万,第一棵树预测 80 万,那么残差是:

$$
100 - 80 = 20
$$

第二棵树就不再直接预测房价,而是学习这个残差。假设第二棵树预测残差为 15,那么组合模型变成:

$$
80 + 15 = 95
$$

还差 5,后面的树继续修正。

所以 GBDT 的直觉是:

当前预测不够好 -> 看还差多少 -> 用下一棵树补上

更一般地说,GBDT 每一步拟合的是损失函数的负梯度方向。对平方误差来说,负梯度刚好和残差方向一致,所以可以先把它理解为“拟合残差”。

9.1 从残差到负梯度

GBDT 的目标是最小化整体损失:

$$
\sum_{i=1}^{n}L(y_i,F(x_i))
$$

第 $t$ 轮时,当前模型是 $F_{t-1}(x)$。GBDT 会计算每个样本在当前模型下的负梯度:

$$
r_i^{(t)}=-\left[\frac{\partial L(y_i,F(x_i))}{\partial F(x_i)}\right]{F=F{t-1}}
$$

然后训练一棵新的回归树 $h_t(x)$ 去拟合这些 $r_i^{(t)}$:

输入:x_i
目标:r_i^(t)

也就是说,新树学习的不是原始标签 $y_i$,而是当前模型最应该修正的方向。

如果损失函数是平方误差:

$$
L(y,F(x))=\frac{1}{2}(y-F(x))^2
$$

那么对 $F(x)$ 求导:

$$
\frac{\partial L}{\partial F(x)}=F(x)-y
$$

负梯度为:

$$
-\frac{\partial L}{\partial F(x)}=y-F(x)
$$

这正好就是残差。

所以在平方误差下:

拟合负梯度 = 拟合残差

这也是为什么很多入门解释会说 GBDT 每一轮都在学习上一轮的残差。

9.2 一个实际例子:GBDT 如何一步步预测房价

假设我们要预测 3 套房子的价格:

房子 真实价格
A 100 万
B 160 万
C 200 万

GBDT 一开始可以先用一个很简单的初始预测,比如所有房子都预测为平均价格:

$$
F_0(x)=\frac{100+160+200}{3}=153.3
$$

于是第一轮残差为:

房子 真实价格 当前预测 $F_0$ 残差 $y-F_0$
A 100 153.3 -53.3
B 160 153.3 6.7
C 200 153.3 46.7

第 1 棵树学习的不是房价本身,而是这些残差。它要学的是:

什么样的房子应该在 153.3 万基础上减一点?
什么样的房子应该在 153.3 万基础上加一点?

假设第 1 棵树预测残差为:

房子 第 1 棵树预测残差
A -40
B 10
C 35

如果学习率 $\eta=0.5$,那么新的预测是:

$$
F_1(x)=F_0(x)+0.5h_1(x)
$$

对应结果为:

房子 $F_0$ $0.5h_1(x)$ 新预测 $F_1$
A 153.3 -20 133.3
B 153.3 5 158.3
C 153.3 17.5 170.8

可以看到,A 的预测从 153.3 往 100 靠近,B 已经比较接近,C 也往 200 靠近。

然后 GBDT 会继续计算新的残差:

A:100 - 133.3 = -33.3
B:160 - 158.3 = 1.7
C:200 - 170.8 = 29.2

也可以写成统一公式:

$$
r_i^{(2)}=y_i-F_1(x_i)
$$

例如房子 A:

$$
r_A^{(2)}=100-133.3=-33.3
$$

房子 C:

$$
r_C^{(2)}=200-170.8=29.2
$$

第 2 棵树继续学习这些新的残差。这样一轮一轮下去,模型预测会逐渐逼近真实价格。

所以 GBDT 的实际运作过程可以记成:

先给一个粗略预测
    -> 计算还差多少
    -> 训练一棵树去补这个差距
    -> 更新预测
    -> 再计算新的差距
    -> 继续补

这比“GBDT 拟合负梯度”更好入口:先把它理解成“不断补差价”,再回头看负梯度公式,就会顺很多。

10. 学习率:每棵树别迈太大步

GBDT 中有一个非常重要的参数:学习率 learning_rate

如果第 $t$ 棵树是 $h_t(x)$,GBDT 的更新可以写成:

$$
F_t(x)=F_{t-1}(x)+\eta h_t(x)
$$

其中 $\eta$ 就是学习率。

学习率越小,每棵树对最终模型的影响越小,训练通常更稳,但需要更多树。

常见经验是:

  • learning_rate 小一些,n_estimators 大一些,模型更细致但训练更慢。
  • learning_rate 太大,模型可能学得太猛,更容易过拟合。

这和梯度下降中的学习率很像:步子太大容易冲过头,步子太小又会走得慢。

11. XGBoost、LightGBM 和 CatBoost

在实际比赛和工业项目中,经常会见到 XGBoost、LightGBM、CatBoost。它们都属于梯度提升树家族,但在工程实现和细节上做了很多优化。

算法 特点
XGBoost 正则化、缺失值处理、并行优化较完善,稳定强大
LightGBM 训练速度快,内存占用低,适合大规模数据
CatBoost 对类别特征处理友好,减少目标泄漏方面设计更细

刚开始学的时候不必急着陷入每个库的实现细节。可以先抓住共同主线:

它们都是一棵树接一棵树地修正前面模型的错误。

理解了 GBDT,再学习这些工程增强版本会轻松很多。

12. Bagging 和 Boosting 的区别

Bagging 和 Boosting 是集成学习中最重要的两条路线。

对比项 Bagging Boosting
训练方式 多个模型并行训练 多个模型串行训练
模型关系 模型之间相对独立 后一个模型依赖前面的结果
主要目标 降低方差 降低偏差,也可能降低方差
典型算法 随机森林 AdaBoost、GBDT、XGBoost
对噪声敏感度 相对不敏感 可能更敏感

简单记忆:

Bagging 像开会投票:大家独立判断,然后汇总。
Boosting 像连续改错:前面哪里错了,后面重点补哪里。

13. Python 实战:随机森林分类

下面用鸢尾花数据集训练一个随机森林分类器。

from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split

# 加载鸢尾花数据集
iris = load_iris()
X = iris.data
y = iris.target

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y,
)

# 创建随机森林模型
model = RandomForestClassifier(
    n_estimators=100,
    max_depth=3,
    random_state=42,
)

# 训练模型
model.fit(X_train, y_train)

# 在测试集上预测
y_pred = model.predict(X_test)

print(classification_report(y_test, y_pred, target_names=iris.target_names))

随机森林还可以查看特征重要性:

for name, importance in zip(iris.feature_names, model.feature_importances_):
    print(name, importance)

feature_importances_ 可以帮助我们粗略观察哪些特征对模型更重要。不过它不是因果解释,只能作为模型内部参考。

14. Python 实战:梯度提升树分类

sklearn 中可以使用 GradientBoostingClassifier 体验 GBDT 的基本用法。

from sklearn.datasets import load_iris
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

iris = load_iris()
X = iris.data
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y,
)

model = GradientBoostingClassifier(
    n_estimators=100,
    learning_rate=0.05,
    max_depth=3,
    random_state=42,
)

model.fit(X_train, y_train)

y_pred = model.predict(X_test)

print("准确率:", accuracy_score(y_test, y_pred))

这里的几个参数很关键:

  • n_estimators:树的数量。
  • learning_rate:每棵树的贡献比例。
  • max_depth:每棵树的深度。

如果模型过拟合,可以尝试降低 max_depth、减小 learning_rate,或者配合交叉验证选择参数。

15. 使用交叉验证调参

集成模型的参数之间经常会相互影响。比如 GBDT 中,learning_raten_estimators 通常要一起看。

可以使用 GridSearchCV 做一个简单搜索:

from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, train_test_split

iris = load_iris()
X = iris.data
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y,
)

param_grid = {
    "n_estimators": [100, 200],
    "max_depth": [2, 3, 5],
    "min_samples_leaf": [1, 3],
}

grid_search = GridSearchCV(
    estimator=RandomForestClassifier(random_state=42),
    param_grid=param_grid,
    cv=5,
    scoring="accuracy",
)

grid_search.fit(X_train, y_train)

print("最佳参数:", grid_search.best_params_)
print("交叉验证最佳分数:", grid_search.best_score_)
print("测试集分数:", grid_search.score(X_test, y_test))

和前面讲交叉验证时一样,调参应该只在训练集上进行,测试集留到最后评估一次,避免数据泄漏。

16. 集成学习的优缺点

优点

  • 预测效果通常比单个模型更强。
  • Bagging 能明显提升模型稳定性。
  • Boosting 能逐步修正错误,表达能力很强。
  • 树模型集成通常不需要对特征做标准化。
  • 随机森林、GBDT 类模型对非线性关系处理较好。

缺点

  • 可解释性通常不如单个简单模型。
  • 训练和预测成本更高。
  • 参数更多,调参成本更高。
  • Boosting 对噪声和异常值可能更敏感。
  • 如果数据泄漏或验证方式不正确,强模型也会给出虚高结果。

17. 实践建议

使用集成学习时,可以按下面的顺序尝试:

  1. 先用逻辑回归、决策树等简单模型做基线。
  2. 如果单棵树不稳定,尝试随机森林。
  3. 如果追求更高性能,尝试 GBDT、XGBoost、LightGBM。
  4. 使用交叉验证评估模型,不要只看一次划分结果。
  5. 关注特征质量和数据泄漏,强模型不能弥补错误的数据流程。

对于表格数据,随机森林和梯度提升树通常是非常值得优先尝试的模型。它们不一定永远最好,但经常能给出很强的基线。

18. 总结

集成学习的核心不是某一个具体算法,而是一种思想:

把多个模型组合起来,让整体判断比单个模型更可靠。

Bagging 通过并行训练多个差异化模型来降低方差,随机森林就是最典型的代表。

Boosting 通过串行训练模型来不断修正错误,AdaBoost、GBDT、XGBoost、LightGBM 都属于这条路线。

理解决策树之后,再看集成学习会很自然:随机森林是很多棵树一起投票,GBDT 是一棵树接一棵树地补错误。它们都是在单棵树的基础上,让模型变得更稳定、更强大。