文章目录
- 集成学习
-
- [1. 集成学习概述](#1. 集成学习概述)
-
- [1.1 核心思想](#1.1 核心思想)
- [1.2 Bagging 和 Boosting 简单概述](#1.2 Bagging 和 Boosting 简单概述)
- [1.3 偏差与方差](#1.3 偏差与方差)
- [2. Bagging(Bootstrap Aggregating)](#2. Bagging(Bootstrap Aggregating))
-
- [2.1 核心思想](#2.1 核心思想)
- [2.2 为什么是"有放回随机"?](#2.2 为什么是"有放回随机"?)
- [2.3 基学习器要求](#2.3 基学习器要求)
- [3. Boosting](#3. Boosting)
-
- [3.1 核心思想](#3.1 核心思想)
- [3.2 三个核心问题](#3.2 三个核心问题)
- [3.3 基学习器要求](#3.3 基学习器要求)
- [4. Bagging vs Boosting](#4. Bagging vs Boosting)
- [5. 随机森林(Random Forest)](#5. 随机森林(Random Forest))
-
- [5.1 定义](#5.1 定义)
- [5.2 sklearn API 详解](#5.2 sklearn API 详解)
- [6. AdaBoost( Adaptive Boosting)](#6. AdaBoost( Adaptive Boosting))
-
- [6.1 定义](#6.1 定义)
- [6.2 三个核心问题](#6.2 三个核心问题)
- [6.3 AdaBoost 完整算法流程](#6.3 AdaBoost 完整算法流程)
- [6.4 sklearn API 详解](#6.4 sklearn API 详解)
- [7. GBDT(梯度提升树)](#7. GBDT(梯度提升树))
-
- [7.1 前置概念:提升树(BDT)](#7.1 前置概念:提升树(BDT))
- [7.2 GBDT 概述](#7.2 GBDT 概述)
- [7.3 GBDT 完整算法流程](#7.3 GBDT 完整算法流程)
- [7.4 sklearn API 详解](#7.4 sklearn API 详解)
集成学习
1. 集成学习概述
1.1 核心思想
集成学习(Ensemble Learning,En-使进入 + semble-源自拉丁语"simul"= together 一起 → "把多个模型集合到一起学习")本身不是一个具体的算法模型,而是一种组合策略思想 。
核心思路:单个模型的预测能力有限,通过训练多个模型,让它们合作做决策,取长补短,最终效果优于任何一个单独的模型。
1.2 Bagging 和 Boosting 简单概述
但究竟如何合作?集成学习中实际存在两种截然不同的合作哲学:
第一种:Bagging = Bootstrap + Aggregating
- Bootstrap(自助采样):让每个模型看到的数据各不相同,像每个人拿到了一份不同版本的复习资料
- Aggregating(聚合):最后把所有人的判断汇总,分类投票,回归取平均
简单来说就是**"独立并行,最后汇合"**。
就像一个团队里每个人独立调研同一个问题,互不商量,最后各自提交结论再汇总。每个人在团队里的地位平等,没有人指挥别人,关键靠"多角度独立判断"来抵消个体偏见。
第二种:Boosting = Boosting(提升)
- 这个词本身就是 "Boost(增强/推动)+ ing(持续进行)":不断推动、持续增强的过程
简单来说就是 "串行接力,逐轮改进" 。
不像 Bagging 那样各自为政,Boosting 是排着队来的:第一个人先做一遍,把搞错的地方标出来;第二个人重点攻克这些错误,标注自己也没搞定的;第三个人继续攻克剩下的难题......每一轮都在前一人的基础上提升。就像一个学生反复刷同一套错题,每一遍都在进步。它靠的不是"多人投票",而是"反复迭代、越来越强"。
1.3 偏差与方差
在深入集成学习之前,需要先建立 偏差-方差权衡(Bias-Variance Tradeoff)这个分析框架。任何一个监督学习模型的泛化误差都可以拆解为三部分:
总误差 = Bias 2 + Variance + 不可约误差 \text{总误差} = \text{Bias}^2 + \text{Variance} + \text{不可约误差} 总误差=Bias2+Variance+不可约误差
| 概念 | 含义 | 偏高时的表现 |
|---|---|---|
| 偏差(Bias) | 模型预测值与真实值之间的系统性偏离,反映模型本身的拟合能力 | 模型太简单,连训练数据里的基本规律都学不到 → 欠拟合 |
| 方差(Variance) | 模型对训练数据微小变化的敏感度,换一批训练数据结果就大变 | 模型太复杂,把噪声也当成规律学了 → 过拟合 |
集成学习的两大分支 Bagging 和 Boosting,本质上就是分别瞄准这两个问题:
- Bagging 主要降低方差 ,适合改善过拟合
单个复杂模型(如决策树)的方差很高,训练数据稍有变化,学出来的树结构可能面目全非。Bagging 通过平均多个独立模型的预测来压方差。
- Boosting 主要降低偏差 ,适合改善欠拟合
初始模型很简单(弱学习器),偏差大;每增加一轮,模型就能多纠正一些系统性错误,偏差逐步降低。它用多个弱模型串行叠加,逐步逼近真实函数,最终达到单个弱模型无法企及的低偏差水平。不过要注意:Boosting 迭代次数过多也会过拟合,因为它一直追着错误走,追到最后可能连噪声也拟合了。实践中通过学习率和早停(Early Stopping)来控制。
| 维度 | Bagging | Boosting |
|---|---|---|
| 全称 | Bootstrap Aggregating(自助聚合) | Boosting(提升) |
| 训练方式 | 多个模型并行训练,互不依赖 | 多个模型串行训练,后一个修正前一个的错误 |
| 基学习器 | 强学习器(如深度全树,各自容易过拟合) | 弱学习器(如深度1~6的浅树,各自准确率刚过半) |
| 主要降低 | 方差(解决过拟合) | 偏差(解决欠拟合) |
| 代表算法 | 随机森林 | AdaBoost、GBDT、XGBoost |
2. Bagging(Bootstrap Aggregating)
2.1 核心思想
Bagging 的名字已经透露了它的两个关键操作:
- Bootstrap(自助采样) :从原始训练集中有放回 地随机抽取,生成多个不同的训练子集
- Aggregating(聚合):将多个模型的结果汇总(分类用投票,回归用平均)
训练阶段:
- 自助采样生成多个子数据集:从原始训练集中有放回地随机抽取 m 个样本,重复 T 次,得到 T 个不同的训练子集。
- 并行训练基学习器:在每个子数据集上独立训练一个基学习器(通常是同一种模型)。这些学习器之间没有任何依赖,完全可以并行。
- 聚合输出 :对于分类任务,采用多数投票 ;对于回归任务,采用算术平均。
预测阶段 :
新样本到来时,让所有基学习器分别预测,然后投票或取平均作为最终结果。


2.2 为什么是"有放回随机"?
-
为什么要随机抽样?随机到底指什么?
Bagging 的精髓在于多样性,如果每个模型都在完全一样的数据上训练,那它们的行为会高度相似,集成毫无意义。为了让基学习器"好而不同",Bagging 在数据层面引入了随机性。
Bagging 的随机性有两个维度:
- 样本方向(横向):每个基学习器的训练数据是 Bootstrap 采样的不同子集
- 特征方向(纵向):每个基学习器训练时只使用特征的一个随机子集(Random Subspace 方法)
为什么特征也要随机?这个道理与样本随机一致。假设两个模型在略有不同的数据上训练,但每次都用全部特征,它们做出的判断仍然高度相关,A 犯的错 B 大概率也会犯,取平均也消不掉,集成毫无意义。当每个模型连看的特征都不一样时,它们犯的错就更"各不一样",平均时更可能互相抵消。
API 中为什么有"不放回"选项? sklearn 中
bootstrap=True(默认)使用有放回采样。设bootstrap=False则不用 Bootstrap,每棵树使用完整训练集(但特征随机仍保留)。也就是说随机性起码保证样本的采集是随机的。
-
为什么必须有放回?
如果无放回地抽 m 个,得到的只是原始数据的某种排列,虽然也能制造差异,但由于样本不会重复,模型之间没有差异,集成也就没意义了。有放回保证了每个子集之间是独立同分布的,最大化数据扰动,从而产生多样化模型。让同一个样本可能被抽中多次,也可能一次都不中。
-
一条样本被选中的概率?
一次抽取中被选中的概率是 1/m,不被选中是 1 - 1/m。m 次独立抽取后,始终不被选中的概率:
( 1 − 1 m ) m ≈ 1 e ≈ 0.368 \left(1 - \frac{1}{m}\right)^m \approx \frac{1}{e} \approx 0.368 (1−m1)m≈e1≈0.368
约 36.8% 的样本不会出现在某个 Bootstrap 采样集中。这些"落选"的样本叫袋外样本(Out-of-Bag, OOB) ,可以作为天然的验证集来评估模型,不需要额外划分验证数据,这也是
oob_score参数的由来。对每个样本,用它"没参与训练"的那些树的预测结果进行投票或平均,就能得到 OOB 预测。将所有样本的 OOB 预测与真实标签对比,即可算出 OOB 分数,它是一个不错的泛化误差估计,很多时候可替代交叉验证。
2.3 基学习器要求
Bagging 通常使用的是强学习器 ,尤其是完全生长、不剪枝的决策树 。这类树偏差极低,但方差极高(极易过拟合),恰好符合 Bagging 擅长降低方差的特性。当然 Bagging 不限制基学习器类型,理论上可以用任何模型。但实践中决策树是最常见的选择。
一个 Bagging 集成中通常使用同一种基学习器。因为需要每个模型在同等能力水平上从不同角度独立判断,能力参差不齐反而影响最终投票质量。如果混用不同类型模型,那更接近 Stacking(堆叠),属于另一种集成策略。当然,理论上基学习器可以不同(异质集成),但实践中为了简便,绝大多数 Bagging 变体(比如随机森林)都采用同质的决策树。
3. Boosting
3.1 核心思想
Boosting 的思路和 Bagging 完全不同:模型不是并行训练的,而是串行的。每一个新模型的目标,是修正前序所有模型合在一起仍然没做好的那部分错误。
训练阶段(核心:让后一个模型专门弥补前一个模型的不足)
- 初始化数据重要性:为每个训练样本赋予同等的初始权重(AdaBoost)或直接将原始标签作为首轮拟合目标(GBDT)。
- 顺序训练基学习器 :
- 基于当前数据状态(样本权重或残差)训练一个基学习器(通常是弱学习器)。
- 用该学习器对训练集进行预测。
- 根据预测结果评估本轮学习器的表现(计算错误率或损失负梯度)。
- 更新数据状态:如果是 AdaBoost,提高错分样本的权重、降低正确样本的权重;如果是 GBDT,计算每个样本的残差(或伪残差),作为下一轮拟合的目标。
- 存储该学习器及其"话语权"(AdaBoost 的 α 或 GBDT 的树结构)。
- 重复步骤 2 直到达到预设的基学习器数量 T 或损失收敛。
- 输出集成模型:得到一组串行生成、各有贡献的学习器及其组合方式。
预测阶段:
- 接收新样本:给定一个待预测样本的特征向量 x_new。
- 顺序使用所有基学习器进行预测 :
- 第一个学习器先给出预测值,然后第二个学习器针对前一个的状态输出修正值,第三个再修正......每个学习器的输出都基于前面所有学习器累积的结果。
- 加权/累加组合 :
- AdaBoost 类型:所有学习器的预测结果乘以各自的权重 α_t 后求和,分类问题取符号函数得到最终类别。
- GBDT 类型:将所有学习器的预测值直接相加(或乘以统一学习率后再相加),得到最终连续值预测;分类问题则将累加值映射为概率。
- 输出最终预测 。

关键说明:
- 模型之间有严格的顺序依赖,必须先训练完第 t 个才能训练第 t+1 个,顺序会影响最终效果
- 训练数据是全部原始数据 ,特征矩阵 X 不改变。变化的是样本权重 或拟合目标 (残差/梯度),不是输入数据被前一个模型的预测值替换。后一个模型重点关注前序模型的错误样本,通过加大错误样本的权重来实现。
3.2 三个核心问题
学习 Boosting 算法的具体实现可能觉得很混乱,所以为了总结归纳,理解各个模型,我们需要理解下面三个核心问题:
| 核心问题 | 含义 | AdaBoost 的答案 | GBDT 的答案 |
|---|---|---|---|
| ① 错误怎么衡量? | 用什么标准评价当前模型的表现 | 带样本权重的分类错误率 | 损失函数的损失函数的负梯度(伪残差) |
| ② 注意力怎么重新分配? | 基于当前表现,下轮重点关注什么 | 更新每个样本的权重系数。正确样本权重 ×e^(-α) (变小),错误样本 ×e^(α)(变大),再归一化 | 不调整样本权重,而是改变下一轮的拟合目标(从原始 y 变成残差) |
| ③ 多个模型怎么合成? | 所有模型的预测怎么融合成最终结果 | 根据每个模型的错误率赋予其一个"话语权"票权=α_t,最终加权投票 | 将所有模型的预测值相加(通常乘以一个统一的学习率),本质上也是一种加权,只是权值通过拟合负梯度自然得出。 |
这三个问题在后面 AdaBoost 和 GBDT 章节会逐一展开。
3.3 基学习器要求
Boosting 通常使用弱学习器作为基模型,比如只有一层分裂的决策树桩(Decision Stump)。因为 Boosting 本身就是将弱变强的过程,基学习器太强反而容易过拟合且失去提升空间。弱学习器的优势在于训练极快、几乎不会过拟合,Boosting 的威力就在于把这样一堆"刚及格"的模型组合成"接近满分"的强模型。
4. Bagging vs Boosting
| 对比维度 | Bagging | Boosting |
|---|---|---|
| 训练方式 | 并行(各模型独立,可同时训练) | 串行(后模型依赖前模型) |
| 数据使用 | Bootstrap 采样(有放回,每个模型看子集) | 全部数据,但样本权重或拟合目标每轮变化 |
| 结合方式 | 多数投票或简单平均 | 加权投票或累加 |
| 基学习器类型 | 强学习器(如深度全树,各自容易过拟合) | 弱学习器(如深度 1~6 的浅树,各自能力有限) |
| 基学习器是否一致 | 通常一致(同类模型) | 通常一致(同类模型,都是弱学习器) |
| 主要解决 | 降低方差 → 解决过拟合 | 降低偏差 → 解决欠拟合 |
| 对噪声 | 稳健(平均自然抵消噪声) | 敏感(噪声样本天然难分对,权重会不断增大) |
| 代表算法 | 随机森林 | AdaBoost、GBDT、XGBoost、LightGBM |
| 训练速度 | 快(可并行) | 较慢(必须串行) |
实战选择速记:
- 如果你的基模型(比如深度决策树)方差大、容易过拟合 → 用 Bagging(如随机森林)降低方差。
- 如果你的基模型(比如简单线性模型或浅树)偏差大、欠拟合 → 用 Boosting(如 GBDT)降低偏差。
5. 随机森林(Random Forest)
5.1 定义
随机森林 = Bagging + 决策树 + 特征随机选择
是有监督学习算法,既能分类 也能回归 。本质是把 Bagging 的基学习器固定为决策树,并选择双重随机性(样本的自助采样 和节点的随机特征选择),进一步降低树与树之间的相关性,极大降低了过拟合风险。
5.2 sklearn API 详解
随机森林在 sklearn 中由 ensemble 模块提供:
python
from sklearn.ensemble import RandomForestClassifier # 分类
from sklearn.ensemble import RandomForestRegressor # 回归
核心参数解读(两个模型参数基本一致):
n_estimators:森林中树的数量,默认为 100。一般越多越好,但会带来计算开销,通常 100~500 已经能取得不错效果。criterion:衡量分裂质量的指标。分类默认'gini'(基尼系数),也可选'entropy';回归默认'squared_error'(MSE)。max_depth:树的最大深度。不限制时可能完全生长,容易过拟合,建议根据数据规模调整。min_samples_split:内部节点再分裂所需的最小样本数。min_samples_leaf:叶节点最少样本数。max_features:每次分裂时考虑的特征数量,这是随机森林的灵魂参数。常用值:'sqrt': sqrt(n_features),分类默认;'log2': log2(n_features);None:使用全部特征(退化为普通 Bagging);- 浮点数如
0.8表示 80% 特征。
bootstrap:是否采用有放回自助采样,默认True。若设为False,则采用无放回采样(需配合max_samples使用)。oob_score:是否使用袋外样本评估,默认False。开启后可通过oob_score_获得分数。random_state:随机种子,保证结果可复现。
代码示例(分类):
python
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# 生成示例数据
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 建立随机森林
rf = RandomForestClassifier(
n_estimators=200, # 200棵树
max_depth=10, # 限制树深防止过拟合
max_features='sqrt', # 特征子集大小为 sqrt(20)≈4
bootstrap=True, # 有放回采样
oob_score=True, # 输出OOB分数
random_state=42
)
rf.fit(X_train, y_train)
print("OOB Score:", rf.oob_score_) # OOB估计准确率
print("Test Score:", rf.score(X_test, y_test))
6. AdaBoost( Adaptive Boosting)
6.1 定义
AdaBoost = Boosting + 自适应权重调整
全称 Adaptive Boosting(自适应增强),是 Boosting 思想最经典、最直观的实现。它通过调整样本权重迫使后续学习器关注难分样本,并将各个弱学习器按表现加权组合。
AdaBoost 是监督学习算法,分类和回归都能做,尤以二分类最为典型。



6.2 三个核心问题
回顾第三章提炼的三个核心问题,下面逐一展开 AdaBoost 是怎么回答的。
问题①:错误率怎么算?加权错误率
AdaBoost 不是简单地数"分错几个",而是算加权错误率。设第 t 轮样本权重为 D_t(i),则错误率 ε_t 为所有被错分样本的权重之和(除以总权重,因为权重可能未归一化到 1,公式中分母是总权重):
ϵ t = ∑ i = 1 m D t ( i ) ⋅ I ( G t ( x i ) ≠ y i ) ∑ i = 1 m D t ( i ) \epsilon_t = \frac{\sum_{i=1}^m D_t(i) \cdot \mathbb{I}(G_t(x_i) \neq y_i)}{\sum_{i=1}^m D_t(i)} ϵt=∑i=1mDt(i)∑i=1mDt(i)⋅I(Gt(xi)=yi)
为什么要加权? 因为 Boosting 中不同样本的重要程度不一样。之前一直被分错的样本权重更大,它们这一轮"错没错"对模型评价的影响应该比那些容易样本更大。加权错误率本质上就是"在当前注意力分配下,你有多少比例的注意力放在了错误的样本上"。
问题②:样本权重怎么调?
第一步:计算当前模型的发言权 α_t
α t = 1 2 ln ( 1 − ε t ε t ) \alpha_t = \frac{1}{2} \ln\left(\frac{1 - \varepsilon_t}{\varepsilon_t}\right) αt=21ln(εt1−εt)
错误率越小,α_t 越大,该分类器在最终投票中的分量越重
- ε_t = 0.1(模型很准)→ α_t ≈ 1.1,话语权大
- ε_t = 0.5(跟瞎猜一样)→ α_t = 0,不给话语权
- ε_t > 0.5(比瞎猜还差)→ α_t 为负,反向发言
第二步:更新每个样本的权重
w i ( t + 1 ) = w i ( t ) ⋅ e − α t ⋅ y i ⋅ h t ( x i ) w_i^{(t+1)} = w_i^{(t)} \cdot e^{-\alpha_t \cdot y_i \cdot h_t(x_i)} wi(t+1)=wi(t)⋅e−αt⋅yi⋅ht(xi)
其中 y_i 和 h_t(x_i) 都取值 +1 或 -1(二分类设定下):
- 预测正确 (y_i 和 h_t(x_i) 同号,乘积 = +1):权重 × e^(-α_t),而 α_t > 0 时 e^(-α_t) < 1 → 权重减小
- 预测错误 (y_i 和 h_t(x_i) 异号,乘积 = -1):权重 × e^(α_t),而 α_t > 0 时 e^(α_t) > 1 → 权重增大
最后对所有样本权重做归一化(每个 w_i 除以权重总和),保证权重之和始终为 1。归一化的目的是让权重始终是一个合法的概率分布,同时也防止数值在多次乘法后溢出。
α_t 和 w_i 到底是什么关系? 它们是两个独立的东西:
- α_t 是模型权重:决定这个基学习器的话语权
- w_i 是样本权重:决定下一轮训练时对每个样本的关注程度,每轮都在变
两者通过 ε_t 产生联系:α_t 由 ε_t 算出来,ε_t 又是用 w_i 加权算的。α_t 衡量的是"这一轮训练出来的模型有多可靠",只有训练完这一轮才知道,所以必须在当前轮确定;w_i 根据此话语权和预测正确与否来更新权重后,交给下一轮去影响新模型的训练。两者不能互相替代。
问题③:怎么加权投票?sign 函数
最终预测不是一人一票,而是按发言权 α_t 加权:
H ( x ) = sign ( ∑ t = 1 T α t ⋅ h t ( x ) ) H(x) = \text{sign}\left(\sum_{t=1}^{T} \alpha_t \cdot h_t(x)\right) H(x)=sign(t=1∑Tαt⋅ht(x))
每个基学习器 h_t 的投票乘以它的权重 α_t,全部累加后取符号(正 → 正类,负 → 负类)。sign 函数的作用就是把连续值映射回最终的二分类决策:大于 0 判正类,小于 0 判负类。
这里 α_t 作为每个分类器的投票权重,直接与预测值相乘。之所以用 α_t 加权,是因为它量化了该分类器的可信度:错误率低的模型说话更有分量。而且需要注意的是,每个 α_t 是在该分类器训练完成后的那一刻就固定下来的,它代表第 t 个分类器在整个团队中的贡献度。最终投票时,所有成员的加权票都要计入,不能只用最后一步的权重。
6.3 AdaBoost 完整算法流程
初始: 所有样本权重相等 w_i = 1/m
第 1 轮:
① 用带权重的样本训练决策树桩 h₁
② 计算加权错误率 ε₁
③ 由 ε₁ 算出 h₁ 的发言权 α₁
④ 更新样本权重(分错增、分对减),归一化
第 2 轮:
① 用新权重的样本训练 h₂(侧重上一轮分错的样本)
② 计算 ε₂ → α₂
③ 再次更新样本权重
...
第 T 轮后:
最终预测 H(x) = sign(α₁·h₁(x) + α₂·h₂(x) + ... + α_T·h_T(x))




疑惑 :决策树桩不就一个简单的 if-else 吗?为什么 AdaBoost 中还需要用加权错误率"找分裂点"?
树桩就是一棵深度为 1 的决策树,它选分裂点的规则和任何决策树节点完全一致,用基尼指数或信息熵来打分,只不过样本带上了权重,所以算基尼/熵的时候要把权重算进去(对应步骤:用带权重的样本训练决策树桩 h₁)。**加权错误率 ε_t 从来不是用来"遍历打分"的,它是树完全建好之后才计算一次的评估指标。**而在听课过程中,老师依靠这个讲解是为了简化思想,理解根本。
假设我们有加权数据集 D,现在要训练一个深度为 1 的决策树桩。步骤 1:遍历所有可能的"切法"
对每一个特征(比如"年龄"),对每一个可能的阈值(比如 30 岁),我们试着把数据切成两半:
- 左子节点:年龄 ≤ 30 的所有样本
- 右子节点:年龄 > 30 的所有样本
步骤 2:用加权不纯度评估这个切法
计算左子节点和右子节点各自的加权基尼系数(或加权熵),然后求加权平均,得到一个"不纯度"数值。
假设二分类,节点中有 4 个样本,标签为 0, 0, 1, 1,对应权重为 0.1, 0.1, 0.4, 0.4。
- 无权重 (或等权重):
p0=2/4=0.5,p1=0.5p 0=2/4=0.5,p 1=0.5
基尼 = 1−0.52−0.52=0.51−0.52−0.52=0.5- 有权重 :
p0=(0.1+0.1)/1.0=0.2p 0=(0.1+0.1)/1.0=0.2
p1=(0.4+0.4)/1.0=0.8p 1=(0.4+0.4)/1.0=0.8
基尼 = 1−0.22−0.82=1−0.04−0.64=0.321−0.22−0.82=1−0.04−0.64=0.32步骤 3:选出不纯度最低的那个切法
遍历完所有特征和阈值后,找到让加权基尼最低的那一刀。这一刀就是树桩的分裂规则。
到此为止,树的结构确定了(一个特征 + 一个阈值)。
步骤 4:给两个叶子节点贴上类别标签
这时才到"一边是正类,一边是负类"。规则很简单:
- 左子节点里,哪类样本的总权重最大,左子节点就预测哪一类。
- 右子节点同理。
所以**"一边正类一边负类"不是分裂时强制要求的,而是分裂后自然形成的结果**,每个叶子选自己内部权重最大的类作为预测输出。步骤 5:树桩建好,开始算加权错误率
现在用这棵树桩对所有训练样本做预测。每个样本被分到某个叶子节点,得到预测标签。
把预测错误的那些样本的权重加起来,除以总权重,得到 ϵ ϵ ϵ。
整个过程就是需要理解这个权重的作用以及是如何影响模型训练的。
树桩之所以还叫决策树,是因为第 1~2 步用的是决策树的原生分裂规则(基尼/熵),权重只是乘在了概率计算里,建树逻辑没有变。加权错误率是 AdaBoost 框架加在决策树外面的评估层,它不参与建树。
6.4 sklearn API 详解
AdaBoost 位于 ensemble 模块:
python
from sklearn.ensemble import AdaBoostClassifier # 分类
from sklearn.ensemble import AdaBoostRegressor # 回归
示例:
python
from sklearn.ensemble import AdaBoostClassifier, AdaBoostRegressor
# ========== 分类 ==========
ada_clf = AdaBoostClassifier(
estimator=None, # None 表示默认使用 max_depth=1 的决策树
n_estimators=50, # 提升轮数
learning_rate=1.0, # 学习率
algorithm='SAMME.R', # 'SAMME.R'(实值,推荐)或 'SAMME'(离散)
random_state=42
)
ada_clf.fit(X_train, y_train)
y_pred = ada_clf.predict(X_test)
y_proba = ada_clf.predict_proba(X_test) # SAMME.R 支持概率输出
# ========== 回归 ==========
ada_reg = AdaBoostRegressor(
estimator=None, # 默认使用 max_depth=3 的决策树
n_estimators=50,
learning_rate=1.0,
loss='linear', # 损失:'linear'、'square'、'exponential'
random_state=42
)
ada_reg.fit(X_train, y_train)
y_pred = ada_reg.predict(X_test)
核心参数:
| 参数 | 含义 | 默认值 |
|---|---|---|
estimator |
基学习器 | DecisionTreeClassifier(max_depth=1) |
n_estimators |
提升轮数(基学习器数量) | 50 |
learning_rate |
学习率,缩小每轮 α 的贡献 | 1.0 |
algorithm |
多分类扩展算法 | 分类 'SAMME.R';回归 'SAMME' |
learning_rate 会在每轮 α_t 前乘上这个系数:α'_t = learning_rate × α_t。取小于 1 的值(如 0.5、0.1)时,每个模型的影响被削弱,但可以配合更大的 n_estimators 来获得更稳健的效果,通常能提升泛化性能。learning_rate 和 n_estimators 是一对此消彼长的参数。
7. GBDT(梯度提升树)
7.1 前置概念:提升树(BDT)
在理解 GBDT 之前,先看它的前身:提升树(BDT, Boosting Decision Tree) 。BDT 的核心思想是用拟合残差来进行提升。
残差 = 真实值 - 当前模型预测值。 它告诉你"离正确答案还差多少"。
BDT 流程(回归为例):
初始预测 f₀(x) = 所有 y 的均值(或其他常数)
第 1 轮:
真实值 y = [100, 80, 60]
当前预测 f₀(x) = [80, 80, 80](假设均值是 80)
残差 r₁ = y - f₀(x) = [20, 0, -20]
→ 用 (X, r₁) 训练一棵决策树 h₁,让它学习预测残差,也就是去拟合这个残差
→ 更新 f₁(x) = f₀(x) + h₁(x) ≈ [96, 80, 64]
第 2 轮:
残差 r₂ = y - f₁(x) = [4, 0, -4]
→ 用 (X, r₂) 训练决策树 h₂
→ 更新 f₂(x) = f₁(x) + h₂(x) ≈ [99, 80, 61]
... 依此类推
第 T 轮后:
f_T(x) = f₀(x) + h₁(x) + h₂(x) + ... + h_T(x)

这个过程就像一个不断"追差额"的游戏:每次都告诉你"还差多少",你就往那个方向补一点,越补越接近真实值。
这里可以用"砍一刀"来比喻,每次都告诉你"还差多少",每次补一刀,越来越靠近目标,这里的最终预测值就是累加起来的结果。
在 BDT 中,残差扮演的角色就类似于 AdaBoost 中"错误样本权重",它告诉下一棵树"应该往哪个方向修正"。区别在于 BDT 没有 AdaBoost 那种样本权重调整,也没有加权投票,最终预测就是所有树的预测值直接累加。
7.2 GBDT 概述
BDT 直接使用残差,它的损失函数默认为平方损失。如果我们想换用其他损失(如绝对损失、Huber 损失、对数损失),残差的概念就不适用了。
GBDT 的解决方案 :不再直接拟合残差,改拟合损失函数的负梯度。
r i ( t ) = − ∂ L ( y i , f ( x i ) ) ∂ f ( x i ) f ( x ) = f t − 1 ( x ) r_i^{(t)} = -\left\\frac{\\partial L(y_i, f(x_i))}{\\partial f(x_i)}\\right{f(x)=f{t-1}(x)} ri(t)=−∂f(xi)∂L(yi,f(xi))f(x)=ft−1(x)
这个负梯度被称为伪残差(Pseudo-Residual):思想上和残差一样指示"往哪调、调多少",但数学上保证了对任意可微损失函数都是最速下降方向。
关键连接:当损失函数是 MSE 时,负梯度恰好等于残差:
L = 1 2 ( y − f ( x ) ) 2 L = \frac{1}{2}(y - f(x))^2 L=21(y−f(x))2
∂ L ∂ f ( x ) = f ( x ) − y = − 残差 \frac{\partial L}{\partial f(x)} = f(x) - y = -残差 ∂f(x)∂L=f(x)−y=−残差
− ∂ L ∂ f ( x ) = y − f ( x ) = 残差 -\frac{\partial L}{\partial f(x)} = y - f(x) = 残差 −∂f(x)∂L=y−f(x)=残差
所以 BDT 是 GBDT 在 MSE 损失下的特例。GBDT 通过"拟合负梯度"这个统一框架,把提升树推广到了任意可微损失函数,这就是"梯度提升"这个名字的由来。
| 维度 | 传统梯度下降 | GBDT |
|---|---|---|
| 优化对象 | 模型的参数 θ(如线性回归的 w 和 b) | 模型函数本身 f(x) |
| 所在空间 | 参数空间 | 函数空间 |
| 梯度 | ∂L/∂θ | ∂L/∂f(x) |
| 更新方式 | θ_new = θ_old - η·∂L/∂θ | f_t = f_{t-1} + η·h_t,其中 h_t 拟合 -∂L/∂f(x) |
7.3 GBDT 完整算法流程
- 初始化:通常取标签均值。(如回归用 y 的均值,分类用对数几率) F 0 ( x ) = arg min γ ∑ i L ( y i , γ ) F_0(x) = \arg\min_\gamma \sum_i L(y_i, \gamma) F0(x)=argγmini∑L(yi,γ)
- 对于 m = 1 到 M:
- 计算每个样本的伪残差:
r i m = − ∂ L ( y i , F ( x i ) ) ∂ F ( x i ) F = F m − 1 r_{im} = -\left \\frac{\\partial L(y_i, F(x_i))}{\\partial F(x_i)} \\right{F = F{m-1}} rim=−∂F(xi)∂L(yi,F(xi))F=Fm−1 - 用一棵回归树拟合 { ( x i , r i m ) } \{(x_i, r_{im})\} {(xi,rim)}(特征, 目标),得到终端区域 (R_{jm}, j=1,...,J)。
(注意:树的拟合目标不是 y_i,而是伪残差 r_i) - 对每个终端区域计算最佳步长(也可直接使用树的预测值):
γ j m = arg min γ ∑ x i ∈ R j m L ( y i , F m − 1 ( x i ) + γ ) \gamma_{jm} = \arg\min_\gamma \sum_{x_i \in R_{jm}} L(y_i, F_{m-1}(x_i) + \gamma) γjm=argγminxi∈Rjm∑L(yi,Fm−1(xi)+γ) - 更新模型:
F m ( x ) = F m − 1 ( x ) + η ⋅ ∑ j = 1 J γ j m ⋅ I ( x ∈ R j m ) F_m(x) = F_{m-1}(x) + η \cdot \sum_{j=1}^J \gamma_{jm} \cdot \mathbb{I}(x \in R_{jm}) Fm(x)=Fm−1(x)+η⋅j=1∑Jγjm⋅I(x∈Rjm)
- 计算每个样本的伪残差:
- 输出 (F_M(x))。
对步骤 2.2 的通俗理解:如果 L 是 MSE,伪残差就是 y - f(x),树在学"还差多少";如果 L 是对数损失(分类),伪残差是概率残差,树在学"概率还差多少"。不管哪种,树的拟合目标都不是原始 y,而是当前模型在梯度方向上需要修正的量。
对步骤 2.4 的 learning_rate :小步快跑,每次只往梯度方向走一小步。学习率小(0.01~0.1)配合多棵树(几百到几千),通常比学习率大配少树效果好。这也是为什么 GBDT 的 n_estimators 经常设到几百甚至上千。

7.4 sklearn API 详解
GBDT 位于 ensemble 模块:
python
from sklearn.ensemble import GradientBoostingClassifier # 分类
from sklearn.ensemble import GradientBoostingRegressor # 回归
示例:
python
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
# ========== 分类 ==========
gb_clf = GradientBoostingClassifier(
loss='log_loss', # 对数损失(二分类 / 多分类)
learning_rate=0.1, # 学习率,控制每棵树的贡献
n_estimators=100, # 提升次数(树的数量)
subsample=1.0, # 每棵树使用的样本比例
max_depth=3, # 每棵树最大深度
min_samples_split=2, # 内部节点最少样本数
min_samples_leaf=1, # 叶节点最少样本数
max_features=None, # 每次分裂考虑的特征数,None 表示全部
random_state=42
)
gb_clf.fit(X_train, y_train)
y_pred = gb_clf.predict(X_test)
y_proba = gb_clf.predict_proba(X_test)
# ========== 回归 ==========
gb_reg = GradientBoostingRegressor(
loss='squared_error', # 均方误差
learning_rate=0.1,
n_estimators=100,
subsample=1.0,
max_depth=3,
random_state=42
)
gb_reg.fit(X_train, y_train)
y_pred = gb_reg.predict(X_test)
# 常用属性和方法
gb_clf.feature_importances_ # 特征重要性
gb_clf.train_score_ # 每轮迭代后的训练集得分
gb_clf.staged_predict(X_test) # 生成器,逐轮返回预测值,可用来画学习曲线
关键参数:
| 参数 | 含义 | 默认值 | 调参建议 |
|---|---|---|---|
loss |
损失函数 | 分类 'log_loss';回归 'squared_error' |
一般默认就好 |
learning_rate |
学习率(shrinkage) | 0.1 | 0.01~0.1 常用,越小需配合越多 n_estimators |
n_estimators |
提升次数(树的数量) | 100 | 学习率小则这个值要调大,建议用早停来确定最佳值 |
subsample |
每棵树训练的样本比例 | 1.0 | 0.5~0.8 引入随机性防过拟合(随机梯度提升) |
max_depth |
每棵树最大深度 | 3 | 通常 3~8,控制单棵树复杂度 |
min_samples_split |
内部节点最少样本数 | 2 | 防过拟合 |
min_samples_leaf |
叶节点最少样本数 | 1 | 防过拟合 |
以上为个人学习总结,旨在梳理个人理解。如有疏漏或不当之处,欢迎指正与交流。如果文章对你有帮助,别忘了点个赞、留个言,让更多的小伙伴看到~ 我们下篇再见!