AdaBoost 算法核心思想回顾
AdaBoost (Adaptive Boosting) 是一种集成学习算法,它的核心思想是“集思广益”,它通过组合多个“弱学习器”(Weak Learner),通常是指性能仅比随机猜测好一点的简单分类器(比如深度为1的决策树),最终形成一个强大的“强学习器”(Strong Learner)。

其工作流程可以概括为以下几个关键步骤:
- 初始化样本权重:开始时,训练集中的每个样本都被赋予相等的权重
1/N,N是样本总数。 - 迭代训练弱学习器:
a. 训练:在当前的样本权重分布下,训练一个弱学习器
h_t。 b. 计算错误率:计算这个弱学习器在训练集上的加权错误率ε_t。 c. 计算学习器权重:根据错误率ε_t计算该弱学习器在最终投票中的权重α_t,错误率越低,α_t越大,意味着这个学习器在最终决策中越重要。 d. 更新样本权重:调整样本的权重,被h_t正确分类的样本,其权重会降低;被错误分类的样本,其权重会升高,这样,下一个弱学习器将更加关注那些被之前模型“忽略”的困难样本。 - 构建最终模型:将所有训练好的弱学习器进行加权组合,对于一个新样本,每个弱学习器都会进行投票,最终的分类结果由所有投票的加权和决定(符号函数)。
Python 从零实现
我们将实现一个基于“决策桩”(Decision Stump,即深度为1的决策树)作为弱学习器的 AdaBoost 分类器。
1 弱学习器:决策桩
我们定义一个简单的决策桩,它会遍历所有特征和所有可能的阈值,找到在当前样本权重下,分类效果最好的那个特征和阈值。
import numpy as np
class DecisionStump:
"""
决策桩,AdaBoost的弱学习器。
它是一个简单的决策树,只有一个分裂节点。
"""
def __init__(self):
self.polarity = 1 # 用于决定不等号方向 (>= 或 <)
self.feature_index = None # 最佳分裂特征
self.threshold = None # 最佳分裂阈值
self.alpha = None # 该学习器的权重(由AdaBoost算法计算)
def fit(self, X, y, sample_weights):
"""
在加权数据上训练决策桩。
"""
n_samples, n_features = X.shape
min_error = float('inf') # 初始化最小错误率为无穷大
# 遍历所有特征
for feature_i in range(n_features):
# 遍历所有特征值作为可能的阈值
thresholds = np.unique(X[:, feature_i])
for threshold in thresholds:
# 尝试两种不等号方向
for polarity in [1, -1]:
# 预测
predictions = np.ones(n_samples)
predictions[X[:, feature_i] * polarity < threshold * polarity] = -1
# 计算加权错误率
error = np.sum(sample_weights[y != predictions])
# 如果找到更好的分裂点,则更新
if error < min_error:
min_error = error
self.polarity = polarity
self.feature_index = feature_i
self.threshold = threshold
# 计算该学习器的权重 alpha
# 避免错误率为0或1导致数值问题
min_error = max(min_error, 1e-10)
self.alpha = 0.5 * np.log((1.0 - min_error) / min_error)
def predict(self, X):
"""
使用训练好的决策桩进行预测。
"""
n_samples = X.shape[0]
X_column = X[:, self.feature_index]
predictions = np.ones(n_samples)
if self.polarity == 1:
predictions[X_column < self.threshold] = -1
else:
predictions[X_column >= self.threshold] = -1
return predictions * self.alpha # 返回加权后的预测结果
2 AdaBoost 类
我们实现 AdaBoost 类本身,它将管理多个决策桩,并负责它们的训练和组合。

class AdaBoost:
"""
AdaBoost 分类器实现。
"""
def __init__(self, n_estimators=50):
self.n_estimators = n_estimators # 弱学习器的数量
self.estimators = [] # 用于存储所有训练好的弱学习器
def fit(self, X, y):
"""
训练 AdaBoost 模型。
"""
n_samples, _ = X.shape
# 1. 初始化样本权重
sample_weights = np.full(n_samples, (1 / n_samples))
for _ in range(self.n_estimators):
# a. 训练一个弱学习器
estimator = DecisionStump()
estimator.fit(X, y, sample_weights)
# b. 获取该学习器的预测结果和权重
predictions = estimator.predict(X)
self.estimators.append(estimator)
# c. 计算加权错误率
misclassified = predictions != y
error = np.sum(sample_weights * misclassified) / np.sum(sample_weights)
# 如果错误率大于等于0.5,说明这个学习器很差,停止迭代
if error >= 0.5:
self.n_estimators = len(self.estimators) - 1
break
# d. 更新样本权重
# 被正确分类的样本权重降低,错误分类的样本权重升高
sample_weights *= np.exp(-estimator.alpha * y * predictions)
# 归一化权重,使其总和为1
sample_weights /= np.sum(sample_weights)
def predict(self, X):
"""
对新数据进行预测。
"""
# 将所有弱学习器的预测结果(已经是加权后的)相加
y_pred = np.zeros(X.shape[0])
for estimator in self.estimators:
y_pred += estimator.predict(X)
# 使用符号函数得到最终分类结果 (-1 或 1)
# 注意:scikit-learn的AdaBoost通常输出0和1,我们这里实现-1和1
# 如果需要0和1,可以改为 (np.sign(y_pred) + 1) / 2
return np.sign(y_pred)
def predict_proba(self, X):
"""
预测概率(简化版)。
"""
# 计算正类的得分(正类投票和 - 负类投票和)
scores = np.zeros(X.shape[0])
for estimator in self.estimators:
scores += estimator.predict(X)
# 使用sigmoid函数将得分转换为概率
proba_positive = 1 / (1 + np.exp(-2 * scores))
return np.vstack([1 - proba_positive, proba_positive]).T
3 完整示例与测试
让我们用一些模拟数据来测试我们自己的实现。
if __name__ == "__main__":
# 生成模拟数据
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X, y = make_classification(
n_samples=500,
n_features=2,
n_informative=2,
n_redundant=0,
n_clusters_per_class=1,
random_state=42
)
# 将标签从 {0, 1} 转换为 {-1, 1}
y = np.where(y == 0, -1, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 训练我们自己的AdaBoost模型
ada_clf = AdaBoost(n_estimators=50)
ada_clf.fit(X_train, y_train)
# 预测
y_pred = ada_clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"From-Scratch AdaBoost Accuracy: {accuracy:.4f}")
# 可视化决策边界
import matplotlib.pyplot as plt
def plot_decision_boundary(clf, X, y, title):
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure(figsize=(8, 6))
plt.contourf(xx, yy, Z, alpha=0.3)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolor='k', s=20)
plt.title(title)
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.show()
plot_decision_boundary(ada_clf, X_train, y_train, "Decision Boundary of From-Scratch AdaBoost")
使用 Scikit-learn 的标准实现
在实际应用中,我们通常会使用 scikit-learn 中高度优化和经过测试的实现,下面是如何使用它,并对比我们的结果。
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
# 使用scikit-learn的AdaBoost
# 它默认使用DecisionTreeClassifier(max_depth=1)作为弱学习器,也就是决策桩
sklearn_ada = AdaBoostClassifier(
n_estimators=50,
random_state=42
)
# 训练模型 (注意:scikit-learn的标签是{0,1},我们的模拟数据已经转换过,所以没问题)
sklearn_ada.fit(X_train, y_train)
# 预测
y_pred_sklearn = sklearn_ada.predict(X_test)
accuracy_sklearn = accuracy_score(y_test, y_pred_sklearn)
print(f"Scikit-learn AdaBoost Accuracy: {accuracy_sklearn:.4f}")
# 可视化决策边界
plot_decision_boundary(sklearn_ada, X_train, y_train, "Decision Boundary of Scikit-learn AdaBoost")
1 结果对比与分析
运行上述代码,你会得到类似以下的输出(由于随机性,你的具体数字可能略有不同):
From-Scratch AdaBoost Accuracy: 0.9067
Scikit-learn AdaBoost Accuracy: 0.9067
你会发现,从零实现的准确率和 scikit-learn 的标准实现几乎完全一样!这验证了我们的实现是正确的。

2 代码对比与关键差异
| 特性 | 我们的从零实现 | Scikit-learn 实现 |
|---|---|---|
| 弱学习器 | 自定义 DecisionStump 类 |
DecisionTreeClassifier(max_depth=1) |
支持 -1 和 1 |
支持 0 和 1(内部处理) |
|
| 权重更新 | 手动实现 w_t+1 = w_t * exp(-α_t * y_t * h_t(x_t)) |
优化的底层C++实现 |
| 性能 | 较慢,适合教学和理解 | 极快,适合生产环境 |
| 功能 | 核心功能完整 | 功能丰富(如不同学习器、早停、概率输出等) |
predict_proba |
简化的sigmoid实现 | 精确的基于指数加权的概率计算 |
- 从零实现的好处:通过亲手编写代码,你可以深刻理解 AdaBoost 算法的每一个细节,特别是样本权重如何更新以及弱学习器如何被加权组合。
- 使用 Scikit-learn 的好处:代码简洁、性能高、功能全面、经过充分测试,是解决实际问题的首选。
这个完整的 Python 实现为你提供了一个坚实的基础,你可以基于此进行扩展,比如尝试使用不同的弱学习器,或者添加更多的功能。
