从0开始实现一个Adaboost分类器(完整代码)

从0开始实现一个Adaboost分类器(完整代码)

日前,通俗易懂的推导了三种集成学习的原理及主要公式,今天本文基于Python从0开始手动实现一个Adaboost分类器,文中提供完整代码。

01 Adaboost基本原理回顾

在这里插入图片描述
三种集成学习算法原理及核心公式推导
机器学习Python算法

02 决策树桩

在这里插入图片描述
详细代码如下,配合注解应该比较简单易懂:

class DecisionTreeClassifierWithWeight:
    def __init__(self):
        self.best_err = 1  # 最小的加权错误率
        self.best_fea_id = 0  # 最优特征id
        self.best_thres = 0  # 选定特征的最优阈值
        self.best_op = 1  # 阈值符号,其中 1: >, 0: <

    def fit(self, X, y, sample_weight=None):
        if sample_weight is None:
            sample_weight = np.ones(len(X)) / len(X)
        n = X.shape[1]
        for i in range(n):
            feature = X[:, i]  # 选定特征列
            fea_unique = np.sort(np.unique(feature))  # 将所有特征值从小到大排序
            for j in range(len(fea_unique)-1):
                thres = (fea_unique[j] + fea_unique[j+1]) / 2  # 逐一设定可能阈值
                for op in (0, 1):
                    y_ = 2*(feature >= thres)-1 if op==1 else 2*(feature < thres)-1  # 判断何种符号为最优
                    err = np.sum((y_ != y)*sample_weight)
                    if err < self.best_err:  # 当前参数组合可以获得更低错误率,更新最优参数
                        self.best_err = err
                        self.best_op = op
                        self.best_fea_id = i
                        self.best_thres = thres
        return self
    
    def predict(self, X):
        feature = X[:, self.best_fea_id]
        return 2*(feature >= self.best_thres)-1 if self.best_op==1 else 2*(feature < self.best_thres)-1
    
    def score(self, X, y, sample_weight=None):
        y_pre = self.predict(X)
        if sample_weight is not None:
            return np.sum((y_pre == y)*sample_weight)
        return np.mean(y_pre == y)

这里以sklearn库中自带的乳腺癌二分类数据集为例,以上述实现的决策树桩进行训练和评分,得到最终得分0.867,这对于一个仅有单层决策树的分类器来说效果还是比较好的。

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

X, y = load_breast_cancer(return_X_y=True)
y = 2*y-1  # 将0/1取值映射为-1/1取值
X_train, X_test, y_train, y_test = train_test_split(X, y)

DecisionTreeClassifierWithWeight().fit(X_train, y_train).score(X_test, y_test)
# 0.8671328671328671

注:按照Adaboost中的算法约定,二分类模型中标签分别用-1和1代表负类和正类。

03 Adaboost集成分类器

在实现决策树桩作为弱分类器的基础上,实现Adaboost算法就仅需按照算法流程逐层训练即可。简单起见,这里仅设置超参数n_estimators用于选择弱分类器的个数。为区分于sklearn中的Adaboost标准内置库,本文将自定义实现的Adaboost分类算法命名为AdaBoostClassifier_,并设置相同的默认弱学习器数量超参数n_estimators=50,其余不做限制。
实质上,在逐渐调整样本权重的基础上,仅需逐层训练一个最优的决策树桩作为每轮的弱学习器,并保存在一个弱学习器列表中,同步记录每个弱学习器的权重系数。最后,在实现predict接口时,用每个弱学习器逐一完成训练,而后按其权重系数加权即可得到最终结果。完整代码如下:

class AdaBoostClassifier_:
    def __init__(self, n_estimators=50):
        self.n_estimators = n_estimators
        self.estimators = []
        self.alphas = []

    def fit(self, X, y):
        sample_weight = np.ones(len(X)) / len(X)  # 初始化样本权重为 1/N
        for _ in range(self.n_estimators):
            dtc = DecisionTreeClassifierWithWeight().fit(X, y, sample_weight)  # 训练弱学习器
            alpha = 1/2 * np.log((1-dtc.best_err)/dtc.best_err)  # 权重系数
            y_pred = dtc.predict(X)
            sample_weight *= np.exp(-alpha*y_pred*y)  # 更新迭代样本权重
            sample_weight /= np.sum(sample_weight)  # 样本权重归一化
            self.estimators.append(dtc)
            self.alphas.append(alpha)
        return self

    def predict(self, X):
        y_pred = np.empty((len(X), self.n_estimators))  # 预测结果二维数组,其中每一列代表一个弱学习器的预测结果
        for i in range(self.n_estimators):
            y_pred[:, i] = self.estimators[i].predict(X)
        y_pred = y_pred * np.array(self.alphas)  # 将预测结果与训练权重乘积作为集成预测结果
        return 2*(np.sum(y_pred, axis=1)>0)-1  # 以0为阈值,判断并映射为-1和1

    def score(self, X, y):
        y_pred = self.predict(X)
        return np.mean(y_pred==y)

最后,继续以乳腺癌二分类数据集为例,对比测试自定义实现的AdaBoostClassifier_算法与sklearn标准库中的AdaBoostClassifer算法性能,得到如下结果:

from sklearn.ensemble import AdaBoostClassifier
AdaBoostClassifier_().fit(X_train, y_train).score(X_test, y_test)
# 0.986013986013986
AdaBoostClassifier().fit(X_train, y_train).score(X_test, y_test)
# 0.965034965034965

除了训练效率略低,自定义实现Adaboost算法效果简直好的不得了。

本文按部就班的实现了一个Adaboost分类算法的baseline,实现了较好的分类效果,但仍有很多需要优化的点,例如对回归算法的支持、更多集成学习参数的设置以及特殊训练情况下的处理等。To be continued……

猜你喜欢

转载自blog.csdn.net/qq_35297368/article/details/110196879
今日推荐