
Bagging & Random Forest —— 随机森林
系统讲解集成学习的核心思想,从Bootstrap自助采样的数学原理出发,深入剖析Bagging的方差降低机制、随机森林的双重随机性(行采样+列采样)、OOB误差的理论基础与实用价值,并对比基尼重要性与排列重要性两种特征重要性计算方法。
阅读文章ZHY's Blog
A UNIVERSE OF IDEAS · BY ZHANG HAOYI
让好奇心 点亮知识宇宙
在代码、模型与思想之间自由漫游。这里持续记录人工智能、机器学习、软件工程与成长实践,让每次阅读都成为一次新的发现。
ARTICLE NOTE
上一篇文章中,我们讨论了 Bagging 与随机森林 —— 通过 Bootstrap 采样构建多棵并行的决策树,以投票方式降低方差。这种并行集成的策略有效驯服了单棵决策树的高方差问题,其核心在于“让多棵树独立生长,平等表决”。
AdaBoost 则走向了另一条截然不同的路径 —— 串行集成。它不再让基学习器独立生长,而是让它们按顺序依次生成,每一棵新树都聚焦于前序模型犯错的样本。这种“串行纠错”的直觉,可以用一个生动的比喻来概括:“错题本机制” —— 做错的题标记出来重点复习,再做新题,再标记新错题,反复迭代,最终将一系列“表现平平”的弱分类器组合成一个强分类器。
AdaBoost 由 Freund 和 Schapire 于 1997 年正式提出,是 Boosting 家族最具影响力的奠基之作。其数学框架可被精炼地概括为“前向分步加法模型 + 指数损失函数” —— 每一轮通过解析优化指数损失,同时推导出两个核心更新公式:弱分类器的权重 αt(错误率越低、话语权越大)和样本权重 Dt(被错分的样本下一轮获得更高关注)。
我们将从这一框架出发,完成从损失函数到两个权重更新公式的完整推导,并揭示其与后续 GBM 之间的深层联系 —— AdaBoost 本质上是 GBM 在指数损失下的特例,而 GBM 则将损失函数从指数泛化为任意可微损失,将优化方式从解析求解扩展为函数空间中的梯度下降。
Note读完本文,你将彻底理解“串行纠错”的数学本质,并厘清 AdaBoost 与 GBM 之间的继承与泛化关系。
下一篇文章,我们将进入 Gradient Boosting Machine(GBM) ——它将 AdaBoost 的指数损失泛化为任意可微损失,把“错题本”的直觉升级为“梯度下降”的通用框架,为 XGBoost 与 LightGBM 等工程级实现奠定理论基础。
在上一篇文章中,我们详细讨论了决策树的构建与剪枝。单棵完全生长的决策树具有以下特点:
这就是决策树容易过拟合的根本原因。如果我们直接用一棵完整的决策树去做分类,在训练集上可能表现完美,但在测试集上往往“原形毕露”。
如何克服单棵决策树的不稳定性?集成学习给出了两个不同的答案:
| 并行集成(Bagging) | 串行集成(Boosting) | |
|---|---|---|
| 代表算法 | 随机森林 | AdaBoost、GBM |
| 构建方式 | 多棵树独立生长 | 多棵树串行生成 |
| 核心思想 | 平等投票,降低方差 | 串行纠错,降低偏差 |
| 树的特点 | 完全生长的深树 | 浅层弱学习器 |
Bagging(随机森林) :让多棵树并行生长,每棵树都是“独立的专家”,最后平等投票。就像专家会诊 —— 多位专家独立诊断后投票决定。
Boosting(AdaBoost) :让多棵树串行生长,后一棵树专门用来修正前一棵树的错误。就像渐进式诊疗 —— 先做初步诊断,发现错误后针对性复查,不断修正。
AdaBoost属于后者——串行集成。它的核心思想是:用一系列弱分类器(每棵树的深度都很浅,分类能力仅比随机猜测稍好一点点),通过串行地调整样本权重,让每个新的弱分类器都重点关注前一轮被分错的样本,最终将这些弱分类器加权组合成一个强分类器。
AdaBoost最直观的理解方式就是 “错题本”机制。
想象你在准备一场考试:
AdaBoost的每一轮迭代都在做同样的事情 —— 增加前一个基学习器在训练过程中预测错误样本的权重,使得后续基学习器更加关注这些被错误标注的训练样本,尽可能纠正这些错误。
但需要注意的是,AdaBoost并不是只关注错题。它只是更偏向错题,而不是只看错题。就像复习时不能只看错题,也得看看做对的题 —— 只是错题的权重大一些。
在正式进入算法之前,首先需要约定符号:
步骤1:初始化样本权重
将所有样本的权重设为相等:D1(i)=N1,i=1,2,...,N
步骤2:对 t=1,2,...,T 迭代
(a) 训练弱分类器:在当前样本权重分布 Dt 下,训练一个弱分类器 ht(x)。
(b) 计算加权错误率:
ϵt=i=1∑NDt(i)⋅I(ht(xi)=yi)其中 I(⋅) 是指示函数,条件成立时为1,否则为0。
(c) 计算弱分类器的权重:
αt=21ln(ϵt1−ϵt)(d) 更新样本权重:
Dt+1(i)=ZtDt(i)⋅exp(−αtyiht(xi))其中 Zt=∑i=1NDt(i)⋅exp(−αtyiht(xi)) 是归一化因子,保证 ∑iDt+1(i)=1。
步骤3:输出最终强分类器:
H(x)=sign(t=1∑Tαtht(x))代码实现:
1import numpy as np2from sklearn.tree import DecisionTreeClassifier3
4class AdaBoost:5 """从零实现AdaBoost算法"""6
7 def __init__(self, n_estimators=50):8 self.n_estimators = n_estimators9 self.alphas = []10 self.weak_classifiers = []11
12 def fit(self, X, y):13 """14 X: shape (n_samples, n_features)15 y: shape (n_samples,), 取值 {-1, +1}16 """17 n_samples = X.shape[0]18
19 # 步骤1:初始化样本权重20 D = np.ones(n_samples) / n_samples21
22 for t in range(self.n_estimators):23 # 步骤2(a):训练弱分类器(决策树桩,深度为1)24 weak_clf = DecisionTreeClassifier(max_depth=1)25 weak_clf.fit(X, y, sample_weight=D)26 predictions = weak_clf.predict(X)27
28 # 步骤2(b):计算加权错误率29 misclassified = (predictions != y)30 epsilon = np.sum(D * misclassified)31
32 # 防止除零或数值不稳定33 if epsilon == 0:34 epsilon = 1e-1035 if epsilon == 1:36 epsilon = 1 - 1e-1037
38 # 步骤2(c):计算弱分类器的权重39 alpha = 0.5 * np.log((1 - epsilon) / epsilon)40
41 # 步骤2(d):更新样本权重42 # 正确分类: 权重乘以 exp(-alpha),错误分类: 权重乘以 exp(alpha)43 D = D * np.exp(-alpha * y * predictions)44 D = D / np.sum(D) # 归一化45
46 # 保存结果47 self.alphas.append(alpha)48 self.weak_classifiers.append(weak_clf)49
50 return self51
52 def predict(self, X):53 """预测:加权投票"""54 # 计算所有弱分类器的加权预测之和55 weighted_sum = np.zeros(X.shape[0])56 for alpha, clf in zip(self.alphas, self.weak_classifiers):57 weighted_sum += alpha * clf.predict(X)58 return np.sign(weighted_sum)AdaBoost最终得到的模型是一个加法模型(Additive Model)—— 多个基学习器的线性组合:
H(x)=t=1∑Tαtht(x)其中 ht(x) 是第 t 个弱分类器,αt 是其权重。
这个形式看起来简单,但问题在于如何确定每一轮的 αt 和 ht?
前向分步算法(Forward Stagewise Algorithm) 是解决这个问题的核心策略。此算法的核心思想是:从前向后,每一步只优化当前这一个基分类器,固定之前已经选好的所有基分类器不变。
具体来说:
由此可见:前向分步算法将一个复杂的全局优化问题(同时优化所有 αt 和 ht)转化为 T 个相对简单的局部优化问题(每一步只优化一个 αt 和 ht)。
AdaBoost使用的损失函数 —— 指数损失函数(Exponential Loss) :
L(y,H(x))=e−yH(x)其中 y∈{−1,+1},H(x) 是模型的预测值(实数,不一定是 ±1)。
使用指数损失的原因:
定理:AdaBoost算法是前向分步加法算法在以指数函数为损失函数时的特例。
假设在第 t 轮,我们已经有了前 t−1 轮的集成模型 Ht−1(x)。现在要选择新的弱分类器 ht(x) 和权重 αt,使得指数损失最小:
(αt,ht)=argα,hmini=1∑Nexp(−yi(Ht−1(xi)+αh(xi)))令 wi(t)=exp(−yiHt−1(xi))(上一轮更新后的样本权重,未归一化),则目标函数变为:
i=1∑Nwi(t)exp(−αyih(xi))对于固定的 α,最小化上式等价于最小化加权错误率:
ϵt=∑i=1Nwi(t)∑i=1Nwi(t)I(h(xi)=yi)现在推导 αt 的表达式:
将样本分为两类:被 h 正确分类的(yih(xi)=1)和错误分类的(yih(xi)=−1)。
目标函数可以写成:
i:yi=h(xi)∑wi(t)e−α+i:yi=h(xi)∑wi(t)eα=(1−ϵt)e−α+ϵteα(这里假设权重已经归一化,即 ∑wi(t)=1)
对 α 求导并令其为零:
dαd[(1−ϵt)e−α+ϵteα]=−(1−ϵt)e−α+ϵteα=0解得:
αt=21ln(ϵt1−ϵt)这就是AdaBoost中弱分类器权重的计算公式。
下列推导样本权重 wi(t)=exp(−yiHt−1(xi)) 的更新规律
在得到 ht 和 αt 后:
wi(t+1)=exp(−yiHt(xi))=exp(−yi(Ht−1(xi)+αtht(xi)))=wi(t)⋅exp(−αtyiht(xi))当 ht(xi)=yi(分类正确)时:wi(t+1)=wi(t)⋅e−αt
当 ht(xi)=yi(分类错误)时:wi(t+1)=wi(t)⋅eαt
由于 αt>0(因为 ϵt<0.5),所以分类正确的样本权重减小(乘以 e−αt<1),分类错误的样本权重增大(乘以 eαt>1)。
这正是AdaBoost “错题本”机制的数学本质 —— 被分错的样本在下一轮获得更高的权重。
统一写成:
wi(t+1)=wi(t)⋅exp(−αtyiht(xi))加上归一化因子 Zt 后:
Dt+1(i)=ZtDt(i)⋅exp(−αtyiht(xi))其中 Zt=∑i=1NDt(i)exp(−αtyiht(xi))。
代码实现:
1# 验证权重更新公式的正确性2def verify_weight_update():3 np.random.seed(42)4 N = 105 y = np.random.choice([-1, 1], N)6 h = np.random.choice([-1, 1], N)7 epsilon = 0.38 alpha = 0.5 * np.log((1 - epsilon) / epsilon)9
10 D = np.ones(N) / N11
12 # 更新权重13 D_new_raw = D * np.exp(-alpha * y * h)14 D_new = D_new_raw / np.sum(D_new_raw)15
16 # 检查:错误分类的样本权重是否增大17 misclassified = (h != y)18 correct = (h == y)19
20 print(f"错误分类样本的平均权重变化: {np.mean(D_new[misclassified] / D[misclassified]):.4f}")21 print(f"正确分类样本的平均权重变化: {np.mean(D_new[correct] / D[correct]):.4f}")22 # 错误分类的权重变化 > 1,正确分类的权重变化 < 123
24verify_weight_update()考虑期望指数损失 E[e−yH(x)],对其关于 H(x) 求偏导:
∂H∂E[e−yH(x)]=E[−ye−yH(x)]=0展开期望公式得:
P(y=1)⋅e−H−P(y=−1)⋅eH=0整理得:
P(y=−1)P(y=1)=e2H两边取自然对数H(x)=21lnP(y=−1)P(y=1)因此:
sign(H(x))={+1,−1,P(y=1)>P(y=−1)P(y=1)<P(y=−1)这意味着:最小化指数损失得到的分类器,恰好是贝叶斯最优分类器。指数损失是0-1损失的一个一致的替代损失函数(Surrogate Loss Function) 。
在后续文章中,我们会详细讨论了梯度提升机(GBM) 。GBM的核心思想是每一轮用基学习器去拟合损失函数的负梯度。
AdaBoost和GBM之间有着深刻的联系 —— AdaBoost可以被视为GBM在“指数损失函数 + 特定的坐标下降优化”下的一个特例。更准确地说,如果GBM选择了指数损失函数 L(y,f(x))=e−yf(x),那么GBM就退化成了AdaBoost算法。
这个联系可以从两个角度理解:
角度一:损失函数。GBM允许使用任意可微的损失函数,而AdaBoost固定使用指数损失函数。从这个意义上说,GBM是AdaBoost在损失函数维度上的泛化——它把AdaBoost的指数损失替换成了任意可微损失。
角度二:优化算法。AdaBoost使用前向分步加法模型(每一步解析地求解最优的 αt 和 ht),而GBM使用函数空间中的梯度下降(每一步用基学习器拟合负梯度)。从这个意义上说,GBM是AdaBoost在优化算法维度上的泛化——它把解析求解替换成了梯度下降。
优点:
缺点:
我们可以把从AdaBoost到GBM的演进看作一条清晰的脉络:
| 阶段 | 算法 | 损失函数 | 优化方法 |
|---|---|---|---|
| 第一阶段 | AdaBoost | 指数损失 | 前向分步(解析求解) |
| 第二阶段 | GBM | 任意可微损失 | 函数空间梯度下降 |
AdaBoost 证明了“串行纠错”这个思路是有效的,并用指数损失给出了一个优雅的数学框架。
GBM 则把这个框架泛化了 —— 把“指数损失”换成“任意可微损失”,把“解析求解”换成“梯度下降”,从而把AdaBoost从一个具体的算法变成了一个通用的算法框架。
1import numpy as np2import matplotlib.pyplot as plt3from sklearn.datasets import make_classification4from sklearn.model_selection import train_test_split5from sklearn.metrics import accuracy_score6from sklearn.tree import DecisionTreeClassifier7
8class AdaBoost:9 """完整的AdaBoost实现(含训练过程记录)"""10
11 def __init__(self, n_estimators=50):12 self.n_estimators = n_estimators13 self.alphas = []14 self.weak_classifiers = []15 self.training_errors = []16 self.epsilon_history = []17
18 def fit(self, X, y):19 n_samples = X.shape[0]20 D = np.ones(n_samples) / n_samples21
22 for t in range(self.n_estimators):23 # 训练弱分类器24 weak_clf = DecisionTreeClassifier(max_depth=1)25 weak_clf.fit(X, y, sample_weight=D)26 predictions = weak_clf.predict(X)27
28 # 计算加权错误率29 misclassified = (predictions != y)30 epsilon = np.sum(D * misclassified)31
32 if epsilon == 0:33 epsilon = 1e-1034 if epsilon == 1:35 epsilon = 1 - 1e-1036
37 # 计算弱分类器权重38 alpha = 0.5 * np.log((1 - epsilon) / epsilon)39
40 # 更新样本权重41 D = D * np.exp(-alpha * y * predictions)42 D = D / np.sum(D)43
44 # 记录历史45 self.alphas.append(alpha)46 self.weak_classifiers.append(weak_clf)47 self.epsilon_history.append(epsilon)48
49 # 计算当前集成的训练误差50 train_pred = self.predict(X)51 self.training_errors.append(np.mean(train_pred != y))52
53 return self54
55 def predict(self, X):56 weighted_sum = np.zeros(X.shape[0])57 for alpha, clf in zip(self.alphas, self.weak_classifiers):58 weighted_sum += alpha * clf.predict(X)59 return np.sign(weighted_sum)60
61# ============ 生成数据并训练 ============62np.random.seed(42)63X, y = make_classification(64 n_samples=500, n_features=2, n_informative=2, n_redundant=0,65 n_clusters_per_class=1, random_state=4266)67y = np.where(y == 0, -1, 1) # 转换为 {-1, +1}68
69X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)70
71# 训练AdaBoost72adaboost = AdaBoost(n_estimators=100)73adaboost.fit(X_train, y_train)74
75# 评估76train_acc = accuracy_score(y_train, adaboost.predict(X_train))77test_acc = accuracy_score(y_test, adaboost.predict(X_test))78
79print(f"训练集准确率: {train_acc:.4f}")80print(f"测试集准确率: {test_acc:.4f}")81
82# ============ 可视化:训练过程 ============83fig, axes = plt.subplots(1, 3, figsize=(15, 4))84
85# 1. 弱分类器权重 α_t 的变化86axes[0].plot(adaboost.alphas, 'b-')87axes[0].set_xlabel('迭代轮次 t')88axes[0].set_ylabel('弱分类器权重 α_t')89axes[0].set_title('弱分类器权重的变化')90axes[0].grid(True)91
92# 2. 加权错误率 ε_t 的变化93axes[1].plot(adaboost.epsilon_history, 'r-')94axes[1].set_xlabel('迭代轮次 t')95axes[1].set_ylabel('加权错误率 ε_t')96axes[1].set_title('加权错误率的变化')97axes[1].axhline(y=0.5, color='gray', linestyle='--', label='随机猜测 (0.5)')98axes[1].legend()99axes[1].grid(True)100
101# 3. 训练误差的下降102axes[2].plot(adaboost.training_errors, 'g-')103axes[2].set_xlabel('迭代轮次 t')104axes[2].set_ylabel('训练集错误率')105axes[2].set_title('训练误差的下降')106axes[2].grid(True)107
108plt.tight_layout()109plt.show()110
111# ============ 可视化:决策边界 ============112def plot_decision_boundary(X, y, model, title):113 x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5114 y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5115 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),116 np.arange(y_min, y_max, 0.02))117 Z = model.predict(np.c_[xx.ravel(), yy.ravel()])118 Z = Z.reshape(xx.shape)119
120 plt.figure(figsize=(8, 6))121 plt.contourf(xx, yy, Z, alpha=0.4, cmap='RdBu')122 plt.scatter(X[:, 0], X[:, 1], c=y, cmap='RdBu', edgecolors='k', s=50)123 plt.xlabel('特征1')124 plt.ylabel('特征2')125 plt.title(title)126 plt.show()127
128# 对比:单个弱分类器 vs AdaBoost集成129first_clf = adaboost.weak_classifiers[0]130plot_decision_boundary(X_train, y_train, first_clf,131 f'第1个弱分类器的决策边界 (准确率: {accuracy_score(y_train, first_clf.predict(X_train)):.3f})')132plot_decision_boundary(X_train, y_train, adaboost,133 f'AdaBoost集成的决策边界 (准确率: {accuracy_score(y_train, adaboost.predict(X_train)):.3f})')总结
概念 核心内容 Boosting思想 串行训练,后一个模型修正前一个模型的错误 错题本机制 被分错的样本权重增大,被分对的样本权重减小 加法模型 H(x)=∑αtht(x),基学习器的线性组合 前向分步算法 每一步只优化当前一个基分类器,固定之前的结果 指数损失 L(y,H)=e−yH,AdaBoost的优化目标 弱分类器权重 αt=21lnϵt1−ϵt 样本权重更新 Dt+1(i)∝Dt(i)⋅exp(−αtyiht(xi)) AdaBoost与GBM AdaBoost是GBM在指数损失下的特例 核心要点回顾
- AdaBoost的核心思想是“错题本”:每一轮训练后,提高被分错样本的权重,降低被分对样本的权重,让下一个弱分类器重点关注上一轮的错误。
- AdaBoost是前向分步加法模型的特例:模型是基学习器的线性组合(加法模型),学习策略是每一步只优化当前一个基学习器(前向分步),损失函数是指数损失。
- 指数损失函数 L(y,H)=e−yH 是AdaBoost的数学核心。它有两个关键性质:连续可微便于优化,且与0-1损失一致(最小化指数损失等价于最小化分类错误率)。
- 两个权重的推导:
- 弱分类器权重 αt=21lnϵt1−ϵt:错误率越低权重越大
- 样本权重更新 Dt+1(i)∝Dt(i)⋅exp(−αtyiht(xi)):被分错的样本权重增大
- AdaBoost与GBM的底层联系:AdaBoost是GBM在指数损失函数下的特例。GBM将AdaBoost的“指数损失”泛化为“任意可微损失”,将“解析求解”泛化为“函数空间梯度下降”。
按顺序完成这组文章,循序渐进地掌握主题
发现错误、内容过时或有改进想法?欢迎告诉我
根据本文分类与标签,为你推荐可能感兴趣的内容

系统讲解集成学习的核心思想,从Bootstrap自助采样的数学原理出发,深入剖析Bagging的方差降低机制、随机森林的双重随机性(行采样+列采样)、OOB误差的理论基础与实用价值,并对比基尼重要性与排列重要性两种特征重要性计算方法。
阅读文章
系统讲解隐马尔可夫模型(HMM)的完整数学原理与三大核心算法:从马尔可夫链到双重随机过程的演进出发,定义HMM的五元组参数(Q, V, π, A, B);详细推导估值问题、解码问题和学习问题;并通过词性标注、语音识别等经典应用场景展示HMM的实践价值。
阅读文章
系统讲解期望最大化(EM)算法的完整数学原理:从极大似然估计在隐变量存在时的困境出发,推导E步与M步的迭代框架;基于Jensen不等式证明ELBO证据下界与收敛性;通过二硬币模型与高斯混合模型(GMM)两个完整实例展示EM的具体计算流程;揭示K-Means是EM在硬分配下的特例这一深层联系。
阅读文章请使用微信扫描二维码分享
当前文章会保持在原页面