机器学习算法之AdaBoost算法python实现

来源:互联网 发布:解压软件官方免费下载 编辑:程序博客网 时间:2024/05/16 11:08

    • 一 理论基础
      • 算法描述
      • 算法步骤
      • 训练误差分析
      • 算法的理论解释与推导
        • 前向分步算法
        • 前向分步算法步骤
        • 前向分步算法与AdaBoost
    • 二 python实现
      • 代码
      • 结果

前言:纸上得来终觉浅,绝知此事要躬行

一. 理论基础

结合后面代码看理论基础,将会更加清楚。

1. 算法描述

AdaBoost算法是boosting方法的代表性算法。给定一个训练集,adaboost求一系列比较粗糙的分类器(弱分类器),每学完一个弱分类器,改变训练数据的概率分布(权值分布),使正确分类的数据权值减小,错误分类的数据权值增大,最后组合这些弱分类器,构成一个强分类器。

2. 算法步骤

  1. 初始化权值分布

D1=(w11,w12,,w1N),     w1i=1N

  1. 迭代训练弱分类器,对每个分类训练步骤:
    a. 使用具有权值分布Dm的训练数据集学习,得到基本分类器

    Gm(x):χ1,1

    b. 计算Gm(x)在训练数据集上的分类误差率

    em=i=1NwmiI(yiGm(xi))

    c. 计算分类器Gm(x)权值

    αm=12ln1emem

    d. 更新训练数据的权值,为下一步迭代做准备

    wm+1,i=wmiZmeαmyiGm(xi)

    其中,

    Zm=i=1NeαmyiGm(xi)

  2. 当错误率(这里的错误率是指前m个弱分类器的线性组合后的分类器的错误率)达到阈值或者迭代次数(弱分类器个数)达到指定次数后,将所有的弱分类器线性组合起来

G(x)=sign(f(x))=sign(m=1MαmGm(x))

3. 训练误差分析

最终分类器的误差率满足:

1Ni=1NI(G(x)yi)1Ni=1Neyif(xi)=i=1mZm

这个定理说明,每一轮选取适当的Gm使得Zm最小,从而使训练误差下降最快。

上述定理证明如下:

G(x)yi时, yif(xi)<0

eyif(xi)>I(G(x)yi)=1

G(x)=yi时, yif(xi)>0

1>eyif(xi)>I(G(x)yi)=0

所以定理左半部分不等式成立.

下面的证明会用到公式(在算法步骤中出现过):

Zmwm+1,i=wmieαmyiGm(xi)

右边等式部分证明如下:

1Ni=1Neyif(xi)=1Ni=1NeyiMm=1[αmGm(xi)]=i=1Nw1im=1MeyiαmGm(xi)=Z1i=1Nw2im=2MeyiαmGm(xi)=Z1Z2i=1Nw3im=3MeyiαmGm(xi)==m=1MZm

所以定理右半部分成立.

4. 算法的理论解释与推导

另一种解释认为,AdaBoost是模型为加法模型,损失函数为指数函数,学习算法为前向分步算法时的二类分类学习方法。

1. 前向分步算法

加法模型:

f(x)=m=1Mβmb(x;γm)

其中, b(x;γm)为基函数,γm为基函数的参数,βm为基函数的系数.

损失函数:L(x,f(x))

目标:

minβm,γmi=1NL(yi,m=1Mβmb(xi;γm))

通常这是一个复杂的优化问题。前向分步算法求解这一优化问题的想法是:因为学习的是加法模型,如果能够从前向后,每一步只学习一个基函数及其系数,逐步逼近优化目标函数式,那么就可以简化优化的复杂度.具体地,每步只需优化如下损失函数:

minβ,γi=1NL(yi,βb(xi;γ))

2. 前向分步算法步骤

  1. 初始化f0(x)=0
  2. m=1,2,,M
    a. 极小化损失函数
    (βm,γm)=argminβ,γi=1NL(yi,fm1(xi)+βb(xi;γ))

    得到参数βm,γm
    b. 更新
    fm(x)=fm1(x)+βmb(x;γm)
  3. 得到加法模型
    f(x)=fM(x)=m=1Mβmb(x;γm)

这样,前向分步算法将同时求解从m=1到M所有参数βm,γm的优化问题简化为逐次求解各个βm,γm的优化问题.

3. 前向分步算法与AdaBoost

AdaBoost算法是前向分步加法算法的特例。这时,模型是由基本分类器组成的加法模型,损失函数是指数函数。

当基函数为基本分类器,基函数的系数为基本分类器的权值时,该加法模型等价于AdaBoost的最终分类器

f(x)=m=1MαmGmx

损失函数为指数函数:

L(y,f(x))=eyf(x)

假设经过m-1轮迭代前向分步算法已经得到fm1(x),在第m轮迭代得到αm,Gm(x),fm(x).

fm(x)=fm1(x)+αmGm(x)

目标是使前向分步算法得到的αm,Gm(x)使fm(x)在训练数据集T上的指数损失最小,即
(αm,Gm(x))=argminα,Gi=1Neyi(fm1(x)+αG(xi))=argminα,Gi=1Nω¯mieyiαG(xi)

其中,ω¯mi=eyifm1(xi),因为ω¯mi既不依赖α也不依赖于G,所以与最小化无关,但ω¯mi依赖于fm1(x),随着每一轮迭代而发生变化.

现证使上式达到最小的αm,Gm(x)就是AdaBoost算法所得到的αmGm(x).

首先,求Gm(x).对任意α>0,使上式最小的G(x)由下式得到:

Gm(x)=argminGi=1Nω¯miI(yiG(xi))

此分类器Gm(x)即为AdaBoost算法的基本分类器Gm(x),因为它是使第m轮加权训练数据分类错误率最小的基本分类器.

之后,求αm.

argminα,Gi=1Nω¯mieyiαG(xi)=yi=Gm(xi)ω¯mieα+yiGm(xi)ω¯mieα=(eαeα)i=1Nω¯miI(yiG(xi))+eαi=1Nω¯mi=(eαeα)em+eα

将上式对α求导并令其等于0,求得

αm=12ln1emem

em为分类错误率.
这里的αm与AdaBoost算法第二中的αm完全一致.

最后来看每一轮样本权值的更新,由

fm(x)=fm1(x)+αmGm(x)

以及ω¯mi=eyifm1(xi),可得

ω¯m+1,i=ω¯mieyiαmGm(x)

这与AdaBoost算法第2步中的样本权值更新只差规范化因子,因而等价.

二. python实现

1. 代码

这里的弱分类器是单层的决策树。

#encoding=utf-8#######################################################################Copyright: CNIC#Author: LiuYao#Date: 2017-9-11#Description: implements the adaboost algorithm######################################################################'''implements the adaboost'''import numpy as npimport matplotlib.pyplot as pltclass AdaBoost:    '''    implements the adaboost classifier    '''    def __init__(self):        pass    def load_simple_data(self):        '''        make a simple data set        '''        data = np.mat([[1.0, 2.0],                    [2.0, 1.1],                    [1.3, 1.0],                    [1.0, 1.0],                    [2.0, 1.0]])        labels = [1.0, 1.0, -1.0, -1.0, 1.0]        return data, labels    def classify_results(self, x_train, demension, thresh, op):        '''        get the predict results by the data, thresh, op and the special demension        Args:            x_train: train data            demension: the special demension            thresh: the spliting value            op: the operator, including '<=', '>'        '''        y_predict = np.ones((x_train.shape[0], 1))        if op == 'le':            y_predict[x_train[:, demension] <= thresh] = -1.0        else:            y_predict[x_train[:, demension] > thresh] = -1.0        return y_predict    def get_basic_classifier(self, x_train, y_train, D):        '''        generate basic classifier by the data and the weight of data        Args:            x_train: train data            y_train: train label            D: the weight of the data        '''        x_mat = np.mat(x_train)        y_mat = np.mat(y_train).T        D_mat = np.mat(D)        [m,n] = x_mat.shape        num_steps = 10.0        min_error = np.inf        best_basic_classifier = {}        best_predict = np.mat(np.zeros((m, 1)))        #traverse all demensions to find best demension        for demension in range(n):            step_length = (x_mat[:, demension].max() - x_mat[:, demension].min()) / num_steps            #traverse all spliting range in the special demension to find best spliting value            for step in range(-1, int(num_steps) + 1):                #determine which op has lower error                for op in ['le', 'g']:                    thresh = x_mat[:, demension].min() + step * step_length                    y_predict = self.classify_results(x_mat, demension, thresh, op)                    error = np.sum(D_mat[np.mat(y_predict) != y_mat])                    if error < min_error:                        min_error = error                        best_predict = np.mat(y_predict).copy()                        best_basic_classifier['demension'] = demension                        best_basic_classifier['thresh'] = thresh                        best_basic_classifier['op'] = op        return best_basic_classifier, min_error, best_predict    def train(self, x_train, y_train, max_itr=50):        '''        train function        '''        m = len(x_train)        n = len(x_train[0])        D = [1.0/m for i in range(m)]        D = np.mat(D).T        self.basic_classifier_list = []        acc_label = np.mat(np.zeros((m, 1)))        #generate each basic classifier        for i in range(max_itr):            #generate basic classifier            basic_classifier, error, y_predict = self.get_basic_classifier(x_train, y_train, D)            print 'y_predict:', y_predict.T            #compute the basic classifier weight            alpha = 0.5 * np.log((1 - error) / max(error, 1e-16))            #compute the data weight            D = np.multiply(D, np.exp(-1 * alpha * np.multiply(np.mat(y_train).T, np.mat(y_predict))))            D = D / D.sum()            print 'D:', D.T            basic_classifier['alpha'] = alpha            #store the basic classifier            self.basic_classifier_list.append(basic_classifier)            #accmulate the predict results            acc_label += alpha * y_predict            print 'acc_label', acc_label            #compute the total error of all basic classifier generated until now            total_error = np.sum(np.sign(acc_label) != np.mat(y_train).T) / float(m)            print 'total_error:', total_error            #if total error equals to the thresh, then stop            if total_error == 0.0:                 break        return self.basic_classifier_list    def predict(self, x_test):        '''        adaboost predict function        '''        x_mat = np.mat(x_test)        m = x_mat.shape[0]        acc_label = np.mat(np.zeros((m, 1)))        for i in range(len(self.basic_classifier_list)):            predict = self.classify_results(x_mat,                                 self.basic_classifier_list[i]['demension'],                                self.basic_classifier_list[i]['thresh'],                                self.basic_classifier_list[i]['op'])            # accmulate the predict results of each basic classifier            acc_label += self.basic_classifier_list[i]['alpha'] * predict        print acc_label        return np.sign(acc_label)def main():    adaboost = AdaBoost()    data, labels = adaboost.load_simple_data()    adaboost.train(data, labels, max_itr=9)    print adaboost.predict([[5,5], [0,0]])if __name__ == '__main__':    main()

2. 结果

结果用来验证实现的adaboost算法是否正确。

y_predict: [[-1.  1. -1. -1.  1.]]D: [[ 0.5    0.125  0.125  0.125  0.125]]acc_label [[-0.69314718] [ 0.69314718] [-0.69314718] [-0.69314718] [ 0.69314718]]total_error: 0.2y_predict: [[ 1.  1. -1. -1. -1.]]D: [[ 0.28571429  0.07142857  0.07142857  0.07142857  0.5       ]]acc_label [[ 0.27980789] [ 1.66610226] [-1.66610226] [-1.66610226] [-0.27980789]]total_error: 0.2y_predict: [[ 1.  1.  1.  1.  1.]]D: [[ 0.16666667  0.04166667  0.25        0.25        0.29166667]]acc_label [[ 1.17568763] [ 2.56198199] [-0.77022252] [-0.77022252] [ 0.61607184]]total_error: 0.0[[ 2.56198199] [-2.56198199]][[ 1.] [-1.]]