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

    • 一 理论基础
      • 算法描述
      • 算法步骤
      • 训练误差分析
      • 算法的理论解释与推导
        • 前向分步算法
        • 前向分步算法步骤
        • 前向分步算法与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算法所得到的 αm Gm(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 np
import matplotlib.pyplot as plt

class 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.2
y_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.2
y_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.]]

你可能感兴趣的:(机器学习)