【机器学习|DAY06】集成学习(Ensemble Learning)笔记

文章目录

  • 集成学习
    • [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(聚合):将多个模型的结果汇总(分类用投票,回归用平均)

训练阶段

  1. 自助采样生成多个子数据集:从原始训练集中有放回地随机抽取 m 个样本,重复 T 次,得到 T 个不同的训练子集。
  2. 并行训练基学习器:在每个子数据集上独立训练一个基学习器(通常是同一种模型)。这些学习器之间没有任何依赖,完全可以并行。
  3. 聚合输出 :对于分类任务,采用多数投票 ;对于回归任务,采用算术平均

预测阶段

新样本到来时,让所有基学习器分别预测,然后投票或取平均作为最终结果。


2.2 为什么是"有放回随机"?

  1. 为什么要随机抽样?随机到底指什么?

    Bagging 的精髓在于多样性,如果每个模型都在完全一样的数据上训练,那它们的行为会高度相似,集成毫无意义。为了让基学习器"好而不同",Bagging 在数据层面引入了随机性。

    Bagging 的随机性有两个维度:

    • 样本方向(横向):每个基学习器的训练数据是 Bootstrap 采样的不同子集
    • 特征方向(纵向):每个基学习器训练时只使用特征的一个随机子集(Random Subspace 方法)

为什么特征也要随机?这个道理与样本随机一致。假设两个模型在略有不同的数据上训练,但每次都用全部特征,它们做出的判断仍然高度相关,A 犯的错 B 大概率也会犯,取平均也消不掉,集成毫无意义。当每个模型连看的特征都不一样时,它们犯的错就更"各不一样",平均时更可能互相抵消。

API 中为什么有"不放回"选项? sklearn 中 bootstrap=True(默认)使用有放回采样。设 bootstrap=False 则不用 Bootstrap,每棵树使用完整训练集(但特征随机仍保留)。也就是说随机性起码保证样本的采集是随机的。

  1. 为什么必须有放回?

    如果无放回地抽 m 个,得到的只是原始数据的某种排列,虽然也能制造差异,但由于样本不会重复,模型之间没有差异,集成也就没意义了。有放回保证了每个子集之间是独立同分布的,最大化数据扰动,从而产生多样化模型。让同一个样本可能被抽中多次,也可能一次都不中。

  2. 一条样本被选中的概率?

    一次抽取中被选中的概率是 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 完全不同:模型不是并行训练的,而是串行的。每一个新模型的目标,是修正前序所有模型合在一起仍然没做好的那部分错误。

训练阶段(核心:让后一个模型专门弥补前一个模型的不足)

  1. 初始化数据重要性:为每个训练样本赋予同等的初始权重(AdaBoost)或直接将原始标签作为首轮拟合目标(GBDT)。
  2. 顺序训练基学习器
    • 基于当前数据状态(样本权重或残差)训练一个基学习器(通常是弱学习器)。
    • 用该学习器对训练集进行预测。
    • 根据预测结果评估本轮学习器的表现(计算错误率或损失负梯度)。
    • 更新数据状态:如果是 AdaBoost,提高错分样本的权重、降低正确样本的权重;如果是 GBDT,计算每个样本的残差(或伪残差),作为下一轮拟合的目标。
    • 存储该学习器及其"话语权"(AdaBoost 的 α 或 GBDT 的树结构)。
  3. 重复步骤 2 直到达到预设的基学习器数量 T 或损失收敛。
  4. 输出集成模型:得到一组串行生成、各有贡献的学习器及其组合方式。

预测阶段

  1. 接收新样本:给定一个待预测样本的特征向量 x_new。
  2. 顺序使用所有基学习器进行预测
    • 第一个学习器先给出预测值,然后第二个学习器针对前一个的状态输出修正值,第三个再修正......每个学习器的输出都基于前面所有学习器累积的结果。
  3. 加权/累加组合
    • AdaBoost 类型:所有学习器的预测结果乘以各自的权重 α_t 后求和,分类问题取符号函数得到最终类别。
    • GBDT 类型:将所有学习器的预测值直接相加(或乘以统一学习率后再相加),得到最终连续值预测;分类问题则将累加值映射为概率。
  4. 输出最终预测

关键说明:

  • 模型之间有严格的顺序依赖,必须先训练完第 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 完整算法流程

  1. 初始化:通常取标签均值。(如回归用 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,γ)
  2. 对于 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)
  3. 输出 (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 防过拟合

以上为个人学习总结,旨在梳理个人理解。如有疏漏或不当之处,欢迎指正与交流。如果文章对你有帮助,别忘了点个赞、留个言,让更多的小伙伴看到~ 我们下篇再见!

相关推荐
kdxiaojie1 小时前
Linux 驱动研究 —— V4L2 (14)
linux·运维·笔记·学习
学习中.........1 小时前
并行 BPE 训练 与 手写 `Tokenizer`
人工智能·算法·机器学习·语言模型
FellAveal14 小时前
【Go语言入门学习笔记】Part18.工作池与WaitGroup(也即协程池)
笔记·学习·golang
xqqxqxxq14 小时前
Java Socket 多人聊天室(私聊+群聊)技术笔记(V4版本)
java·网络·笔记
龙仔72514 小时前
人大金仓OS_Core数据库自动备份实施笔记(银河麒麟Linux)
linux·数据库·笔记·备份·人大金仓
databook15 小时前
用方差阈值过滤掉“惰性特征”
python·机器学习·scikit-learn
Z59981784116 小时前
c#软件开发学习笔记--Modbus-RTU通讯
笔记·学习·c#
quanjui18 小时前
【医学尝试】基于Segment Anything Model的医学图像分割研究:眼底OCT与X线胸片微调实战
人工智能·笔记·学习