杰瑞科技汇

如何用Python实现AdaBoost算法?

AdaBoost 算法核心思想回顾

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

如何用Python实现AdaBoost算法?-图1
(图片来源网络,侵删)

其工作流程可以概括为以下几个关键步骤:

  1. 初始化样本权重:开始时,训练集中的每个样本都被赋予相等的权重 1/NN 是样本总数。
  2. 迭代训练弱学习器: a. 训练:在当前的样本权重分布下,训练一个弱学习器 h_t。 b. 计算错误率:计算这个弱学习器在训练集上的加权错误率 ε_t。 c. 计算学习器权重:根据错误率 ε_t 计算该弱学习器在最终投票中的权重 α_t,错误率越低,α_t 越大,意味着这个学习器在最终决策中越重要。 d. 更新样本权重:调整样本的权重,被 h_t 正确分类的样本,其权重会降低;被错误分类的样本,其权重会升高,这样,下一个弱学习器将更加关注那些被之前模型“忽略”的困难样本。
  3. 构建最终模型:将所有训练好的弱学习器进行加权组合,对于一个新样本,每个弱学习器都会进行投票,最终的分类结果由所有投票的加权和决定(符号函数)。

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 类本身,它将管理多个决策桩,并负责它们的训练和组合。

如何用Python实现AdaBoost算法?-图2
(图片来源网络,侵删)
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 的标准实现几乎完全一样!这验证了我们的实现是正确的。

如何用Python实现AdaBoost算法?-图3
(图片来源网络,侵删)

2 代码对比与关键差异

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

这个完整的 Python 实现为你提供了一个坚实的基础,你可以基于此进行扩展,比如尝试使用不同的弱学习器,或者添加更多的功能。

分享:
扫描分享到社交APP
上一篇
下一篇