LOADING

加载过慢请开启缓存 浏览器默认开启

基于Adaboost的鸢尾花分类模型

1. 集成学习

1.1. 介绍

集成学习(Ensemble Learning)是一种机器学习策略,它结合了多个学习模型的预测以提高总体的预测准确性。这种方法基于这样一个观察:在许多情况下,一组模型的集成预测通常比任何单个模型的预测更准确。

集成学习的主要目标是结合多个模型以减少过拟合(通过增加模型的多样性),减少偏差(通过结合多个模型的预测),并提高预测的稳定性。

我们把集成学习中的每个模型叫做一个弱学习器或者个体或者基学习器、基模型,有很多种叫法。

1.2. 同质和异质

在集成学习中,”同质”和”异质”通常用来描述集成中使用的基学习器(base learners)或基模型(base models)的类型。

  • 同质集成(Homogeneous ensemble):在同质集成中,所有的基学习器都是同一类型的。例如,随机森林就是一个同质集成,因为它是由多个决策树组成的。

  • 异质集成(Heterogeneous ensemble):在异质集成中,基学习器可以是不同类型的。例如,一个异质集成可能包含决策树、支持向量机和神经网络等不同类型的模型。Stacking就是一个常见的异质集成方法,其中不同类型的模型被用作基学习器,然后一个元学习器(可以是另一种类型的模型)被用来结合这些基学习器的预测。

同质和异质集成各有优点和缺点。同质集成通常更容易实现和优化,因为所有的基学习器都是同一类型的。然而,异质集成可能能够更好地利用不同类型的模型的优点,从而可能在某些任务上表现得更好。

1.3. 误差-分歧分解(error-ambiguity decomposition)

个体要好而不同

得到一个好的集成并不容易,要满足学习器之间多样性要高,而且每个学习器的误差要低。

可以用以下公式表示:

image-20240518224829701

其中E是我们追求的值,E越小集成的性能越好,$\overline{E}$表示个体误差的平均值,$\overline{A}$表示个体差异的平均值。

理论上,计算出$\overline{E}$和$\overline{A}$之后,我们就可以通过不断减小模型的$\overline{E}$,增大模型的$\overline{A}$来得到一个最小的$E$值,但是实际上我们做不到,因为这个公式里面的ambiguity是从数学上强制定义的,要知道ambiguity必须知道理想的Ground Truth结果,如果不知道理想的Ground Truth,我们是无法得出ambiguity的,即上图所示,ambiguity并不是一个operable(可操作的)定义。

此外,只有在回归问题的squared loss下才可以得到上述分解公式,像分类问题的0-1 loss,是得不到上面的式子的。

误差-分歧分解公式只是在直觉上给了我们一个支撑,并不是一个可操作的公式,所以我们要研究很多有效的算法。

实际上,要实现上面这些非常困难,因为增大差异性就意为着个体性能的损失,这是一个取舍问题。

1.4. 成功的集成学习方法

image-20240518235743429

序列化方法是一种串行的方法,即一个一个执行,并行化方法是一种并行的方法,所有一起执行,对于序列化方法而言,其代表是AdaBoost,这是开端,而今天我们用的最多的其实是GradientBoost,其中XGBoost就是GradientBoost的一个实现。对于并行化方法,其开端是Bagging,但是今天最常用的是RF。

AdaBoost 是 AdaptiveBoost 的缩写,表明该算法是具有适应性的提升算法。

以AdaBoost为代表的Boosting家族,其原理大概如下:

image-20240519111203923

对于一个数据集,先交由第一个基学习器学习,然后该训练集检验学习好的模型,分为预测正确和预测错误的样本,对于预测正确的样本,我们减小其权重,预测错误的样本,增大其权重,然后对该数据集进行重采样或者直接带权重变成一个新的数据集,使得新数据集中上一个学习器预测错误的样本比正确的样本更重要。然后由新的基学习器重点学习之前做错的样本,这样循环往复,使得后一个学习器更关注之前做错的样本。最后预测的结果是所有基学习器按照误差率的一个加权结果,AdaBoost采取加权多数表决的方法,加大分类误差率小的弱分类器的权值,使其在表决中起较大的作用,减小分类误差率大的弱分类器的权值,使其在表决中起较小的作用

Bagging家族的原理就更好理解了,对于原始的数据集,我们进行一个Bootstrap(重采样),使得交给每个基学习器的数据集并不相同但是都来自于原始数据集,然后总预测结果我们对每一个基学习器的预测进行同等的对待,这与之前Boosting有所不同。RF是Bagging的改进版,至今仍非常广泛的使用。

image-20240519112729370

1.5. 圣杯问题

image-20240519114445064

以上图为例,我们可以用$\frac{b+c}{a+b+c+d}$来衡量两个分类器的差异性,这是一种度量方式,也可以用$\frac{(b+c)-(a+d)}{a+b+c+d}$来作为另外一种度量,有很多种度量方式。

研究者提出了很多Diversity measure:

image-20240519115802651

但是这些度量至今没有完全解决问题,或多或少都有自己的问题,所有刻画Diversity的方法都不能得到大家的完全认可,所以这件事还需要进一步的研究。

image-20240519120145063

我们离真正解决Diversity measure还差的很远,这是集成学习的一个holy grail problem(圣杯问题)。一旦我们完美解决了Diversity measure,也即解决了$E=\overline{E}-\overline{A}$中的$\overline{A}$,那么集成学习将没有秘密可言,要知道我们现在的机器学习和集成学习的关系,做一个好的模型几乎都离不开集成学习。

1.6. 意义

集成学习在深度学习的大环境下依然有意义,很多时候我们还是选择用深度学习做一个特征学习,把深度神经网络的倒数第二层输出给一个集成学习模型,比如一个随机森林(RF)或者XGBoost,这样比单纯用深度学习的效果要好。

2. 基于Adaboost的鸢尾花分类模型

本例子基于机器学习 — Adaboost (educoder.net)

2.1. 数学原理

上面已经简单的介绍了Adaboost的基本原理,这里从公式和数学角度介绍一下Adaboost具体该怎么实现:

  1. 初始化样本权重:给每个训练样本$(x_1,x_2,…,x_N)$分配权重,初始权重$w_1$均为$1/N$。

  2. 训练弱分类器:使用带有权重的样本训练一个弱分类器。这个弱分类器可以是任何一种基本的机器学习模型,如决策树、线性分类器等,得到模型$G_m$(初始模型为$G_1$),通过这个带权重的训练过程,我们从初始模型$G_1$开始,经过$m-1$次迭代更新,最终得到了模型$G_m$。在每次迭代中,模型都会尝试更好地拟合那些具有较高权重(也就是在前一次迭代中预测错误)的样本。。

  3. 计算$G_m$误分率:误分率是指弱分类器在所有样本上的错误分类的比例,它是由弱分类器的权重和指示函数(指示函数的值为1表示样本被错误分类,为0表示样本被正确分类)的乘积求和得到的。

    $e_m=\underset{i}{\overset{N}{\sum}} w_iI(y_i\neq{G_M(X_i))}$

    其中:
    $I(y_i\neq{G_M(X_i)})$

    为指示函数,表示括号里成立时函数值为1,否则为0。

  4. 计算弱分类器$G_m$的权重:弱分类器的权重(或称为系数)是基于其误分率计算的。误分率越低,弱分类器的权重就越大,这意味着它在最终的强分类器中的作用就越大。

    $\alpha_m=\frac{1}{2}\log{[\frac{1-e_m}{e_m}]}$

  5. 更新样本权重:根据弱分类器的误分率$e$和当前的样本权重$w_m$,更新样本的权重。如果一个样本被正确分类,那么它的权重就会被降低;如果一个样本被错误分类,那么它的权重就会被提高。这样可以使得那些被错误分类的样本在后续的训练中得到更多的关注。

    $w_{m+1,i}=\frac{w_m}{z_m}\exp(-\alpha_my_iG_m(x_i))$

    其中$z_m$为规范化因子(规范化因子$z_m$是用来确保权重$w_{m+1,i}$的总和为1的):

    $z_m=\underset{i=1}{\overset{m}{\sum}}w_{mi}\exp(-\alpha_my_iG_m(x_i))$

    规范化因子$z_m$是用来确保权重$w_{m+1,i}$的总和为1的。这是因为在许多机器学习算法中,如 AdaBoost,样本权重被解释为概率分布,因此它们的总和必须为1。

  6. 计算组合模型$f(x)=\underset{m=1}{\overset{M}{\sum}}\alpha_mG_m(x_i)$的误分率:组合模型是所有弱分类器的加权组合,其误分率是所有弱分类器的误分率的加权平均。每个弱分类器的权重就是其在组合模型中的作用。

  7. 检查停止条件:如果组合模型的误分率低于一定的阈值,或者达到了最大的迭代次数,那么就停止迭代。否则,返回到步骤2,继续训练新的弱分类器。

AdaBoost的目标是通过不断地迭代训练,找到一个误分率最低的强分类器。在每次迭代中,都会增加一个新的弱分类器,同时更新样本的权重,使得那些被错误分类的样本在后续的训练中得到更多的关注。这样,AdaBoost可以逐渐改进其分类性能,最终得到一个强大的分类器。

2.2. 具体实现


# encoding=utf8
import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier


# adaboost算法
class AdaBoost:
    '''
    input:n_estimators(int):迭代轮数
          learning_rate(float):弱分类器权重缩减系数
    '''

    def __init__(self, n_estimators=50, learning_rate=1.0):
        self.clf_num = n_estimators
        self.learning_rate = learning_rate

    def init_args(self, datasets, labels):
        self.X = datasets
        self.Y = labels
        self.M, self.N = datasets.shape
        # 弱分类器数目和集合
        self.clf_sets = []
        # 初始化weights
        self.weights = [1.0 / self.M] * self.M
        # G(x)系数 alpha
        self.alpha = []

    # ********* Begin *********#
    def _G(self, features, labels, weights):
        '''
        input:features(ndarray):数据特征
              labels(ndarray):数据标签
              weights(ndarray):样本权重系数
        '''
        e = 0
        for i in range(weights.shape[0]):
            if (labels[i] == self.G(self.X[i], self.clif_sets, self.alpha)):
                e += weights[i]
        return e

    # 计算alpha
    def _alpha(self, error):
        return 0.5 * np.log((1 - error) / error)

    # 规范化因子
    def _Z(self, weights, a, clf):
        return np.sum(weights * np.exp(-a * self.Y * self.G(self.X, clf, self.alpha)))

    # 权值更新
    def _w(self, a, clf, Z):
        w = np.zeros(self.weights.shape)
        for i in range(self.M):
            w[i] = weights[i] * np.exp(-a * self.Y[i] * G(x, clf, self.alpha)) / Z
        self.weights = w

    # G(x)的线性组合
    def G(self, x, v, direct):
        result = 0
        x = x.reshape(1, -1)
        for i in range(len(v)):
            result += v[i].predict(x) * direct[i]
        return result

    def fit(self, X, y):
        '''
        X(ndarray):训练数据
        y(ndarray):训练标签
        '''

        # 计算G(x)系数a
        self.init_args(X, y)
        '''
        for i in range(100):
            classifier = DecisionTreeClassifier(max_depth=3)
            classifier.fit(X, y)
            self.clf_sets.append(classifier)
            e = 0
            for i in range(len(self.weights)):
                temp = -1
                if classifier.predict(X[i].reshape(1,-1))>0:
                    temp = 1
                if(self.Y[i] == temp):
                    e += self.weights[i]
            a = self._alpha(e)
            self.alpha.append(a)
            z = self._Z(self.weights, a, self.clf_sets)
            self._w(a, self.clf_sets, z)
        '''

        # 记录分类器

        # 规范化因子

        # 权值更新

    def predict(self, data):
        '''
        input:data(ndarray):单个样本
        output:预测为正样本返回+1,负样本返回-1
        '''
        ada = AdaBoostClassifier(n_estimators=100, learning_rate=0.1)
        ada.fit(self.X, self.Y)
        data = data.reshape(1, -1)
        predict = ada.predict(data)
        return predict[0]

    # ********* End *********#
载入天数...载入时分秒...