python实现AdaBoost算法

★  AdaBoost的理解:

      对于二分类而言,我们通过一个阈值threshVal将数据集划分为两种分类(正类和负类),而这个阈值就是我们所说的分割线,只不过它总是平行于坐标轴(因为我们取的基分类器是单层决策树):

 

       对于上述数据集,一个基分类器即可将其分类,但是对于大多数训练集而言,一个基分类器是不足够的,比如:

 

         在图3中,我们找不到一条平行于坐标轴的直线将数据集正确的划分,因此我们使用了3个基分类器将其分开,由图4看出,3个分类器把数据集分成6个部分,每一部分我们公式sign(\pm \alpha _{1}\pm \alpha _{2}\pm ...\pm \alpha _{m})计算出来它是正类还是负类,比如对第①部分:sign(-0.69+0.8-0.97)=-1,所以第①部分是负类(蓝色点就是负类),再比如第⑥部分:sign(0.69-0.8+0.97)=1,所以第⑥部分是正类(红色点就是正类)。依次计算这6个部分,画出图来就是:

         由图我们可以看出,橘红色部分(②,③,⑥)是正类,绿色部分(①,④,⑤)是负类。

★ AdaBoost 分类器简介:

       adaboost 就是把几个弱分类器,通过线性组合,组合成强分类器,它在训练集上有很高的准确率,但是在测试集上效果却没有那么好,因为它是逐步优化分错的样本,所以最终分类误差率会降到很低,这个组合分类器的数学描述是:

                                 f(x)=\alpha _{1}G_{1}(x)+...+\alpha _{m}G_{m}(x)                                              ①

       其中m是分类器个数,也就是当组合m个弱分类器后,误差率能满足我们的要求。

        \alpha _{i}\; \; \; i=1,...,m 代表着该分类器的重要程度。

★ AdaBoost 算法流程:

      (1) 初始化权值,每一个训练样本最开始时都被赋予相同的权值:1/N (N是样本个数),它代表着开始时每个样本的重要性都是相等的。

               D_{1}=(w_{1,1},w_{1,2},...,w_{1,N})\; \; \; \; w_{1,i}=\frac{1}{N},\; \; i=1,2,...,N                           ②

       我们经过每一次的迭代,上述权值会不断变化,但初始时各权值是相等的。

     (2) 使用具有权值分布D_{m}的训练数据集学习,得到基本分类器(分类器可以选择SVM,决策树等,后面我们选择最简单的单层决策树作为基分类器):

                      G_{m}(x):\; \; \; \; \; \chi \rightarrow\begin{Bmatrix} -1,+1 \end{Bmatrix}

          这里G_{m}就是第m个基分类器,它的取值只有-1或者+1。

     (3) 计算G_{m} 在训练集上的分类误差率:

                e_{m}=P(G_{m}(x_{i})\neq y_i)=\frac{\sum _{G_{m}(x_{i})\neq y_{i}}w_{m,i}}{\sum _{i=1}^{N}w_{m,i}}=\sum_{i=1}^{N}w_{m,i}\cdot I(G_{m}(x_{i})\neq y_{i})                       ③

        其中:e_{m}是第m个分类器G_{m}的分类误差率,就是分类器G_{m}分错的概率。

                    w_{m,i} 是第m个分类器G_{m}在数据集x上使用的每个样本的权值,跟公式②的 w_{1,i} 一个意思

                    G_{m}(x_{i})是第m个分类器对第i个样本的预测分类。它的取值是-1或+1

                    y_{i} 是第i个样本真实的分类。对于二分类来说,它的取值也是-1或+1

                    I(G_{m}(x_{i})=y_{i})当括号里面条件为true的时候取值为1,反之,为false的时候取值为0,这里I 可不是单位矩阵哈

     (4) 计算G_{m}(x)的系数\alpha _{m}\alpha _{m}代表着该分类器的重要程度,当这个分类器的分类误差率越低的时候,\alpha _{m}就越大。

                               \alpha _{m}=\frac{1}{2}\cdot ln\frac{1-e_{m}}{e_{m}}                                ④

            从上式④可以看出,分类误差率e_{m}越小,\alpha _{m}就越大,对应的分类器就越重要。

     (5) 更新每个样本的权值:

                     D_{m+1}=(w_{m+1,1},w_{m+1,2},...,w_{m+1,N})

                     w_{m+1,i}=\frac{w_{m,i}\cdot e^{-\alpha _{m}y_{i}G_{m}(x_{i})}}{Z_m}\; \;\;\; \; \; \; \; \; \; i=1,...,N                 ⑤

         其中: D_{m+1} 是第m+1个分类器的各样本点权值分布

                      w_{m+1,i} 是第m+1个分类器的样本点权值的更新规则。

                      Z_{m}是归一化因子,他其实就是分子的和。

                            Z_{m}=\sum_{i=1}^{N}(w_{m,i}\cdot e^{-\alpha _{m}y_{i}G_{m}(x_{i})})=(\sum _{G_{m}(x_{i})=y_{i}}w_{m.i}\cdot e^{-\alpha _{m}})+(\sum _{G_{m}(x_{i})\neq y_{i}}w_{m.i}\cdot e^{\alpha _{m}})

                                   =(1-e_{m})\cdot e^{-\alpha _{m}}+e_{m}\cdot e^{\alpha _{m}}=(1-e_{m}\cdot e^{-\frac{1}{2}ln\frac{1-e_{m}}{e_{m}}})+e_{m}\cdot e^{\frac{1}{2}ln\frac{1-e_{m}}{e_{m}}}

                                   =2\sqrt{(1-e_{m})\cdot e_{m}}                                 ⑥

           上式的公式⑥是有公式③、④代入得到的。

   (6) 线性组合各弱分类器:

                     f(x)=\alpha _{1}G_{1}(x)+...+\alpha _{m}G_{m}(x)=\sum _{m=1}^{M}\alpha _{m}\cdot G_{m}(x)            ⑦

        得到最终的分类器:

                    G(x)=sign(f(x))=sign(\sum _{m=1}^{M}\alpha _{m}\cdot G_{m}(x))                            ⑧

★ AdaBoost 算法的解释:

      证明 AdaBoost 的损失函数是指数函数:(可通过前向分布算法来推adaboost,只要证明前向分布算法与adaboost都是加法模型,并当前向分布算法取指数损失函数时就是adaboost,那么就证明了adaboost使用的是指数损失函数)

      ✿ 首先我们先来看一下前向分布算法:

        前向分布算法的加法模型是:

                                                    f(x)=\sum _{m=1}^{M}\beta _{m}\cdot b(x;\gamma _{m})                          ⑨

        其中,b(x;\gamma _{m})为基函数,\gamma _{m} 为基函数的参数,\beta _{m}为基函数的系数,M是基分类器个数由公式⑦可以看出,adaboost也是一个加法模型。

        假设前向分布算法的损失函数是L(y,f(x)),那么算法的目标函数就是极小化损失函数:

                     \begin{matrix}min\; \sum_{i=1}^{N}L(y_{i},f(x))= min\; \sum_{i=1}^{N}L(y_{i},\sum _{m=1}^{M}\beta _{m}\cdot b(x_{i};\gamma _{m}))\\ \beta _{m} ,\gamma _{m}\; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \beta _{m} ,\gamma _{m}\; \; \; \; \; \; \; \; \; \; \; \;\; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \end{matrix}                   ⑩

        上式中的N是样本个数。

        前向分布算法求解这一优化问题的思想是:因为该学习是加法模型,如果能够从前向后,每一步只学习一个基函数及其系数,逐步逼近优化目标函数式⑩,那么就可以简化该优化问题的复杂度,因此每一步只需优化如下损失函数(即优化每一个基分类器的损失函数):

                           \begin{matrix} min\; \sum_{i=1}^{N}L(y_{i},\beta \cdot b(x_{i};\gamma))\\ \beta ,\gamma \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \end{matrix}                                      ⑪

        当前向分布算法的损失函数L(y,f(x))是指数损失函数时,即:

                               L(y,f(x))=e^{-y\cdot f(x)}                                                ⑫

        只需证明该学习的具体操作等价于AdaBoost的具体操作,就可证明AdaBoost也使用的是指数损失函数。假设我们经过m-1次迭代前向分布算法得到f_{m-1}(x)

                   f_{m-1}(x)=f_{m-2}(x)+\alpha _{m-1}G_{m-1}(x)=\alpha _{1}G_{1}(x)+...+\alpha _{m-1}G_{m-1}(x)               ⑬

        在第m轮迭代得到\alpha _{m}G_{m}(x)f_{m}(x):

                         f_{m}(x)=f_{m-1}(x)+\alpha _{m}G_{m}(x)                           ⑭

        目标是使前向分布算法得到的\alpha _{m}G_{m}(x) 使得f_{m}(x)在训练集上的指数损失函数最小,即:

                 \begin{matrix} (\alpha _{m},G_{m}(x))=arg\; min\sum _{i=1}^{N}e^{-y_{i}(f_{m-1}(x_{i})+\alpha G(x_{i}))}\\\alpha,G\; \; \; \; \; \; \; \end{matrix}                    ⑮

        公式⑮可以表示为:

                          \begin{matrix} (\alpha _{m},G_{m}(x))=arg\; min\sum _{i=1}^{N}\varpi _{mi}\cdot e^{-y_{i}\alpha G(x_{i})}\\\alpha,G \; \end{matrix}                    ⑯

        其中:\varpi _{mi}=e^{-y_{i}\cdot f_{m-1}(x_{i})} 。 因为\varpi _{mi} 既不依赖\alpha也不依赖于G,所以与最小化无关。但\varpi _{mi}依赖于f_{m-1}(x),随着每一轮迭代而发生改变。

        现证使公式⑯达到最小时,\alpha _{m}^{*}e_{m}^{*}w_{m+1,i}^{*}的取值就是AdaBoost算法得到的\alpha _{m}e_{m}w_{m+1,i}

        对于任意\alpha >0,使公式最小的G(x)由下式得到:

       \begin{matrix} min\sum _{i=1}^{N}\varpi _{mi}\cdot e^{-y_{i}\alpha G(x_{i})}=min\; (\sum_{y_{i}=G_{m}(x_i)}\varpi_{mi} \cdot e^{-\alpha }+\sum_{y_{i}\neq G_{m}(x_i)}\varpi_{mi} \cdot e^{\alpha })\\\alpha,G \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \;\; \; \; \; \; \; \; \; \; \; \; \;\alpha,G \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \;\; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \end{matrix}

                                                         \begin{matrix} =min\; (e^{-\alpha }\cdot (\sum_{i=1}^{N}\varpi _{mi}-\sum _{y_{i}\neq G_{m}(x_{i})}\varpi _{mi})+e^{\alpha }\cdot \sum _{y_{i}\neq G_{m}(x_{i})}\varpi _{mi})\\ \alpha ,G\; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \;\; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \end{matrix}

                                                         \begin{matrix} =min\; ((e^{\alpha }-e^{-\alpha })\cdot \sum _{y_{i}\neq G_{m}(x_{i})}\varpi _{mi}+e^{-\alpha }\cdot \sum_{i=1}^{N}\varpi _{mi})\\ \alpha ,G\; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \end{matrix}         ⑰

         我们要求极小值,所以将上式对\alpha求偏导并令其为0:

                       (e^{\alpha }+e^{-\alpha })\cdot \sum _{y_{i}\neq G_{m}(x_{i})}\varpi _{mi}-e^{-\alpha }\cdot \sum _{i=1}^{N}\varpi _{mi}=0                        ⑱

          上式的两边同时除以 \sum _{i=1}^{N}\varpi _{mi},且令:

                                              e_{m}^{*}=\frac{\sum _{y_{i}\neq G_{m}(x_{i})}\varpi _{mi}}{\sum _{i=1}^{N}\varpi _{mi}}                         ⑲

           而AdaBoost的e_{m}为:

          e_{m}=P(G_{m}(x_{i})\neq y_i)=\frac{\sum _{G_{m}(x_{i})\neq y_{i}}w_{m,i}}{\sum _{i=1}^{N}w_{m,i}}=\sum_{i=1}^{N}w_{m,i}\cdot I(G_{m}(x_{i})\neq y_{i})         ③

          对比公式③、⑲可以看出,AdaBoost的e_{m}和前向分布算法的e_{m}^{*}是一致的。

           故求导的原式为:

                                           (e^{\alpha }+e^{-\alpha })\cdot e_{m}^{*}-e^{-\alpha }=0

           解上式得:

                                           a_{m}^{*}=\frac{1}{2}\cdot ln\frac{1-e_{m}^{*}}{e_{m}^{*}}                                  ⑳

          而AdaBoost的 \alpha _{m}

                                          \alpha _{m}=\frac{1}{2}\cdot ln\frac{1-e_{m}}{e_{m}}                                  ④

          对比公式④、⑳可以看出,AdaBoost的\alpha _{m}和前向分布算法的\alpha _{m}^{*}是一致的。

          我们再来推算前向分布算法的每个样本的权值更新,由公式:

                                        \left\{\begin{matrix} f_{m}(x)=f_{m-1}(x)+\alpha _{m}G_{m}(x)\\ \varpi _{mi}=e^{-y_{i}\cdot f_{m-1}(x_{i})}\; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \; \end{matrix}\right.             ㉑

           将公式㉑的第一个式子,等式两边同时乘以 -y 再取以e为底数:

                                  e^{-y_{i}\cdot f_{m}(x_{i})}=e^{-y_{i}\cdot f_{m-1}(x_{i})-y_{i}\cdot \alpha _{m}G_{m}(x_{i})}

                                       \varpi _{m+1,i}=\varpi _{mi}\cdot e^{-y_{i}\alpha _{m}G_{m}(x_{i})}                                     ㉒

           对比公式⑤、㉒,可以看出AdaBoost的w_{m+1,i}和前向分布算法的\varpi _{m+1,i}只差一个归一化因子,因而等价。

           因此,AdaBoost算法使用的是指数损失函数。

★  代码实践:

from numpy import *
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.font_manager import *

def loadDataSet():                # 加载测试数据
    dataMat = mat([[1.,2.1],
                   [2.,1.1],
                   [1.3,1.],
                   [1.,1.],
                   [1.1,1.2],
                   [2.,1.],
                   ])

    labelList = [1.0,1.0,-1.0,-1.0,1.0,1.0]
    return dataMat, labelList      # 数据集返回的是矩阵类型,标签返回的是列表类型

def stumpClassify(dataMat,dimen,threshVal,threshIneq): # dimen:第dimen列,也就是第几个特征, threshVal:是阈值  threshIneq:标志
    retArray = ones((shape(dataMat)[0],1))   # 创造一个 样本数×1 维的array数组
    if threshIneq == 'lt':        # lt表示less than,表示分类方式,对于小于等于阈值的样本点赋值为-1
        retArray[dataMat[:,dimen] <= threshVal] = -1.0
    else:  # 我们确定一个阈值后,有两种分法,一种是小于这个阈值的是正类,大于这个值的是负类,
           #第二种分法是小于这个值的是负类,大于这个值的是正类,所以才会有这里的if 和else
        retArray[dataMat[:,dimen] > threshVal] = -1.0
    return  retArray            # 返回的是一个基分类器的分类好的array数组

def buildStump(dataArr,classLabels,D):
    dataMat = mat(dataArr)
    labelMat = mat(classLabels).T
    m,n = shape(dataMat)
    numStemp = 10
    bestStump = {}
    bestClassEst = mat(zeros((m,1)))
    minError = inf                      # 无穷
    for i in range(n):                  # 遍历特征
        rangeMin = dataMat[:,i].min()    # 检查到该特征的最小值
        rangeMax = dataMat[:,i].max()
        stepSize = (rangeMax - rangeMin)/numStemp  # 寻找阈值的步长是最大减最小除以10,你也可以按自己的意愿设置步长公式
        for j in range(-1, int(numStemp)+1):
            for inequal in ['lt', 'gt']:   # 因为确定一个阈值后,可以有两种分类方式
                threshVal = (rangeMin+float(j) * stepSize)
                predictedVals = stumpClassify(dataMat,i,threshVal,inequal)  # 确定一个阈值后,计算它的分类结果,predictedVals就是基分类器的预测结果,是一个m×1的array数组
                errArr = mat(ones((m,1)))
                errArr[predictedVals==labelMat] =0   # 预测值与实际值相同,误差置为0
                weightedEroor = D.T*errArr     # D就是每个样本点的权值,随着迭代,它会变化,这段代码是误差率的公式
                if weightedEroor<minError:     # 选出分类误差最小的基分类器
                    minError=weightedEroor     # 保存分类器的分类误差
                    bestClassEst = predictedVals.copy()   # 保存分类器分类的结果
                    bestStump['dim']=i          # 保存那个分类器的选择的特征
                    bestStump['thresh']=threshVal    # 保存分类器选择的阈值
                    bestStump['ineq']=inequal        # 保存分类器选择的分类方式
    return bestStump,minError,bestClassEst

def adaBoostTrainDS(dataMat, classLabels,numIt=40):   # 迭代40次,直至误差满足要求,或达到40次迭代
    weakClassArr = []   # 保存每个基分类器的信息,存入列表
    m =shape(dataMat)[0]
    D = mat(ones((m,1))/m)
    aggClassEst = mat(zeros((m,1)))
    for i in range(numIt):
        bestStump,error,classEst = buildStump(dataMat,classLabels,D)
        print('D: ',D)
        alpha = float(0.5 * log((1.0-error)/max(error,1e-16)))   # 对应公式 a = 0.5* (1-e)/e
        bestStump['alpha']=alpha
        weakClassArr.append(bestStump)  # 把每个基分类器存入列表
        print('classEst: ',classEst.T)
        expon = multiply(-1 * alpha * mat(classLabels).T, classEst)   # multiply是对应元素相乘
        D = multiply(D,exp(expon))           # 根据公式 w^m+1 = w^m (e^-a*y^i*G)/Z^m
        D = D/D.sum()                 # 归一化
        aggClassEst += alpha * classEst      # 分类函数 f(x) = a1 * G1
        print("aggClassEst: ",aggClassEst.T)
        aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m,1)))   # 分错的矩阵
        errorRate = aggErrors.sum() /m   # 分错的个数除以总数,就是分类误差率
        print('total error: ',errorRate)
        if errorRate == 0.0:         # 误差率满足要求,则break退出
            break
    return weakClassArr,aggClassEst

def adaClassify(datToClass, classifierArr):    # 预测分类
    dataMat = mat(datToClass)    # 测试数据集转为矩阵格式
    m = shape(dataMat)[0]
    aggClassEst = mat(zeros((m,1)))
    for i in range(len(classifierArr)):
        classEst = stumpClassify(dataMat, classifierArr[i]['dim'], classifierArr[i]['thresh'],classifierArr[i]['ineq'])     # 可以对比文章开头的图,其实就是那个公式
        aggClassEst += classifierArr[i]['alpha']*classEst
        print(aggClassEst)
    return sign(aggClassEst)

def draw_figure(dataMat,labelList,weakClassArr):         # 画图
    # myfont = FontProperties(fname='/usr/share/fonts/simhei.ttf')    # 显示中文
    matplotlib.rcParams['axes.unicode_minus'] = False     # 防止坐标轴的‘-’变为方块
    matplotlib.rcParams["font.sans-serif"]=["simhei"]     # 第二种显示中文的方法
    fig = plt.figure()                    # 创建画布
    ax = fig.add_subplot(111)             # 添加子图

    red_points_x = []                     # 红点的x坐标
    red_points_y = []                     # 红点的y坐标
    blue_points_x = []                    # 蓝点的x坐标
    blue_points_y = []                    # 蓝点的y坐标
    m,n = shape(dataMat)  # 训练集的维度是 m×n ,m就是样本个数,n就是每个样本的特征数
    dataSet_list = array(dataMat)         # 训练集转化为array数组

    for i in range(m):            # 遍历训练集,把红点,蓝点分开存入
        if labelList[i] == 1:
            red_points_x.append(dataSet_list[i][0])      # 红点x坐标
            red_points_y.append(dataSet_list[i][1])
        else:
            blue_points_x.append(dataSet_list[i][0])
            blue_points_y.append(dataSet_list[i][1])

    line_thresh = 0.025      # 画线阈值,就是不要把线画在点上,而是把线稍微偏移一下,目的就是为了让图更加美观直接
    annotagte_thresh = 0.03   # 箭头间隔,也是为了美观
    x_min = y_min = 0.50   # 自设的坐标显示的最大最小值,这里固定死了,应该是根据训练集的具体情况设定
    x_max = y_max = 2.50

    v_line_list = []       # 把竖线阈值的信息存起来,包括阈值大小,分类方式,alpha大小都存起来
    h_line_list = []       # 横线阈值也是如此,因为填充每个区域时,竖阈值和横阈值是填充边界,是不一样的,需各自分开存贮
    for baseClassifier in weakClassArr:           # 画阈值
        if baseClassifier['dim'] == 0:              # 画竖线阈值
            if baseClassifier['ineq'] == 'lt':      # 根据分类方式,lt时
                ax1=ax.vlines(baseClassifier['thresh']+line_thresh, y_min, y_max, colors='green',label='阈值')   # 画直线
                ax.arrow(baseClassifier['thresh']+line_thresh,1.5,0.08,0,head_width=0.05,head_length=0.02)  # 显示箭头
                ax.text(baseClassifier['thresh']+annotagte_thresh,1.5+line_thresh, str(round(baseClassifier['alpha'],2)))   # 画alpha值
                v_line_list.append([baseClassifier['thresh'],1,baseClassifier['alpha']])   # 把竖线信息存入,注意分类方式,lt就存1,gt就存-1

            else:           # gt时,分类方式不同,箭头指向也不同
                ax.vlines(baseClassifier['thresh']+line_thresh, y_min, y_max, colors='green',label="阈值")
                ax.arrow(baseClassifier['thresh']+line_thresh,1.,-0.08,0,head_width=0.05,head_length=0.02)
                ax.text(baseClassifier['thresh']+annotagte_thresh, 1.+line_thresh, str(round(baseClassifier['alpha'],2)))
                v_line_list.append([baseClassifier['thresh'],-1,baseClassifier['alpha']])
        else:                                     # 画横线阈值
            if baseClassifier['ineq'] == 'lt':    # 根据分类方式,lt时
                ax.hlines(baseClassifier['thresh']+line_thresh,x_min,x_max,colors='black',label="阈值")
                ax.arrow(1.5+line_thresh, baseClassifier['thresh']+line_thresh,0.,0.08,head_width=0.05,head_length=0.05)
                ax.text(1.5+annotagte_thresh, baseClassifier['thresh']+0.04,str(round(baseClassifier['alpha'],2)))
                h_line_list.append([baseClassifier['thresh'],1,baseClassifier['alpha']])
            else:            # gt时
                ax.hlines(baseClassifier['thresh']+line_thresh,x_min,x_max,colors='black',label="阈值")
                ax.arrow(1.0+line_thresh,baseClassifier['thresh'],0.,0.08,head_width=-0.05,head_length=0.05)
                ax.text(1.0+annotagte_thresh,baseClassifier['thresh']+0.04,str(round(baseClassifier['alpha'],2)))
                h_line_list.append([baseClassifier['thresh'],-1,baseClassifier['alpha']])
    v_line_list.sort(key=lambda x: x[0])   # 我们把存好的竖线信息按照阈值大小从小到大排序,因为我们填充颜色是从左上角开始,所以竖线从小到大排
    h_line_list.sort(key=lambda x: x[0],reverse=True)      # 横线从大到小排序
    v_line_list_size = len(v_line_list)     # 排好之后,得到竖线有多少条
    h_line_list_size = len(h_line_list)     # 得到横线有多少条
    alpha_value = [x[2] for x in v_line_list]+[y[2] for y in h_line_list]  # 把属性横线的所有alpha值取出来,这里也证实了上面的排序不是无用功
    print('alpha_value',alpha_value)
    
    for i in range(h_line_list_size+1):    # 开始填充颜色,(横线的条数+1) × (竖线的条数+1) = 分割的区域数,然后开始往这几个区域填颜色
        for j in range(v_line_list_size+1):  # 我们是左上角开始填充直到右下角,所以采用这种遍历方式
            list_test = list(multiply([1]*j+[-1]*(v_line_list_size-j),[x[1] for x in v_line_list]))+list(multiply([-1]*i+[1]*(h_line_list_size-i),[x[1] for x in h_line_list]))
            # 上面是一个规律公式,后面会用文字解释它
            # print('list_test',list_test)
            temp_value = multiply(alpha_value,list_test)  # list_test其实就是加减号,我们知道了所有alpha值,可是每个alpha是相加还是相加,这就是list_test作用了
            reslut_test = sign(sum(temp_value))     # 计算完后,sign一下,然后根据结果进行分类
            if reslut_test==1:          # 如果是1,就是正类红点
                color_select = 'orange'   # 填充的颜色是橘红色
                hatch_select = '.'        # 填充图案是。
                # print("是正类,红点")
            else:                       # 如果是-1,那么是负类蓝点
                color_select = 'green'   # 填充的颜色是绿色
                hatch_select = '*'       # 填充图案是*
                # print("是负类,蓝点")
            if i == 0:        # 上边界     现在开始填充了,用fill_between函数,我们需要得到填充的x坐标范围,和y的坐标范围,x范围就是多条竖线阈值夹着的区域,y范围是横线阈值夹着的范围
                if j == 0:   # 左上角
                    ax.fill_between(x=[x for x in arange(x_min,v_line_list[j][0]+line_thresh,0.001)],y1=y_max,y2=h_line_list[i][0]+line_thresh,color=color_select,alpha=0.3,hatch=hatch_select)
                elif j== v_line_list_size:          # 右上角
                    ax.fill_between(x=[x for x in arange(v_line_list[-1][0]+line_thresh,x_max,0.001)],y1=y_max,y2=h_line_list[i][0]+line_thresh,color=color_select,alpha=0.3,hatch=hatch_select)
                else:   # 中间部分
                    ax.fill_between(x=[x for x in arange(v_line_list[j-1][0]+line_thresh,v_line_list[j][0]+line_thresh,0.001)],y1=y_max,y2=h_line_list[i][0]+line_thresh,color=color_select,alpha=0.3,hatch=hatch_select)
            elif i == h_line_list_size:  # 下边界
                if j == 0:   # 左下角
                    ax.fill_between(x=[x for x in arange(x_min,v_line_list[j][0]+line_thresh,0.001)],y1=h_line_list[-1][0]+line_thresh,y2=y_min,color=color_select,alpha=0.3,hatch=hatch_select)
                elif j== v_line_list_size:          # 右下角
                    ax.fill_between(x=[x for x in arange(v_line_list[-1][0]+line_thresh,x_max,0.001)],y1=h_line_list[-1][0]+line_thresh,y2=y_min,color=color_select,alpha=0.3,hatch=hatch_select)
                else:   # 中间部分
                    ax.fill_between(x=[x for x in arange(v_line_list[j-1][0]+line_thresh,v_line_list[j][0]+line_thresh,0.001)],y1=h_line_list[-1][0]+line_thresh,y2=y_min,color=color_select,alpha=0.3,hatch=hatch_select)
            else:
                if j == 0:   # 中左角
                    ax.fill_between(x=[x for x in arange(x_min,v_line_list[j][0]+line_thresh,0.001)],y1=h_line_list[i-1][0]+line_thresh,y2=h_line_list[i][0]+line_thresh,color=color_select,alpha=0.3,hatch=hatch_select)
                elif j== v_line_list_size:          # 中右角
                    ax.fill_between(x=[x for x in arange(v_line_list[-1][0]+line_thresh,x_max,0.001)],y1=h_line_list[i-1][0]+line_thresh,y2=h_line_list[i][0]+line_thresh,color=color_select,alpha=0.3,hatch=hatch_select)
                else:   # 中间部分
                    ax.fill_between(x=[x for x in arange(v_line_list[j-1][0]+line_thresh,v_line_list[j][0]+line_thresh,0.001)],y1=h_line_list[i-1][0]+line_thresh,y2=h_line_list[i][0]+line_thresh,color=color_select,alpha=0.3,hatch=hatch_select)

    ax.scatter(red_points_x,red_points_y,s=30,c='red', marker='s',label="red points")  # 画红点
    ax.scatter(blue_points_x,blue_points_y,s=40,label="blue points")         # 画蓝点
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.legend()      # 显示图例    如果你想用legend设置中文字体,参数设置为 prop=myfont
    ax.set_title("图 5 AdaBoost分类",position=(0.5,-0.175))       # 设置标题,改变位置,可以放在图下面,这个position是相对于图片的位置
    plt.show()

if __name__ =='__main__':                             # 运行函数
    dataMat, labelList = loadDataSet()                     # 加载数据集
    weakClassArr, aggClassEst = adaBoostTrainDS(dataMat,labelList)
    draw_figure(dataMat,labelList,weakClassArr)      # 画图
    print('weakClassArr',weakClassArr)
    print('aggClassEst',aggClassEst)
    classify_result =adaClassify([0.7,1.7],weakClassArr)   # 预测的分类结果,测试集我们用的是[0.7,1.7]测试集随便选
    print("结果是:",classify_result)

★ 运行结果:

★ 推算上述填充颜色的规律(以至于写出填充颜色的代码):

        不失一般性,我们随便举一个例子如上图7,共有3条纵向阈值,2条横向阈值,将信息绘制成表格:

纵向阈值0.40.60.8
分类方式gtltlt
横向阈值1.21.0
分类方式gtlt

         注意:纵向阈值(竖线)按从小达到排序,横向阈值(横线)按从大到小排序,至于原因,可以看图7就知道了。

         gt和lt对应上文的两种分类方式,gt箭头就朝左,lt箭头就朝右。可以看出,这3条竖线和2条横线把该区域分成了12个部分,这里分成的区域数就是:  (横线条数+1)×(竖线条数+1)。

         现在我们判别每一部分是正类还是负类,可以写出每一部分的计算公式,比如我先写出①②③④这个四个部分的计算公式:

                                     \left\{\begin{matrix} sign(+0.4-0.6-0.8-1.2+1.0)=-1\; \; \; \; \;\; \; \; (1.)\\ sign(-0.4-0.6-0.8-1.2+1.0)=-1 \; \; \; \; \; \; \; \; (2.)\\ sign(-0.4+0.6-0.8-1.2+1.0)=-1 \; \; \; \; \;\; \; \; (3.)\\ sign(-0.4+0.6+0.8-1.2+1.0)=+1 \; \; \; \; \;\; \; \; (4.)\\...\end{matrix}\right.         ①

          当然了,上述式子是通过观察图7,根据箭头指向来判断出加减号,然后写出来的计算式子。那么如何通过代码来代替人的观察以此来写出加减号呢?

          其实加号或减号在代码中的体现就是+1或-1。我们把(1.),(2.),(3.),(4.)的符号提出来观察一下:

                       \begin{matrix} + & - & -& -&+ \\ - & -& -& - &+ \\ - & + & - &- & +\\ - &+ &+ & - & + \end{matrix}           ②     == ==>           \begin{matrix} \; \: \: 1 & -1 & -1&\; \; \; |\; \; -1&1 \\ - 1& -1& -1&\; \; \; |\; \; - 1& 1\\ - 1& \; \; \: 1& - 1&\; \; \;| \; \; - 1& 1\\ - 1&\; \; \: 1&\; \; \: 1&\; \; \;| \; \; -1 & 1 \end{matrix}        ③

        我把式子③分成左右两部分了(因为左边是竖线阈值提出来的符号,右部分是横线阈值提出来的符号)。gt和lt是字母表示的是分类方式,字母我们不能用于计算,所以我们规定:lt为+1,而gt为-1。(当然这是我自己规定的,你也可以反过来)。因此3条竖线阈值可以写为[-1,1,1],而2条横线阈值可以写为[-1,1]。

        我们再来看图7,每一部分都跟这3条竖线和2条横线有关系,比如第①部分:它在竖线阈值0.4的左边,在竖线阈值0.6的左边,在竖线阈值0.8的左边,在横线阈值1.2的上边,在横线阈值1.0的上边。现在我们再次规定:在直线右边或上边的部分为+1,在直线左边或下边的部分为-1。因此由刚才叙述的第①部分与各直线的关系,我们可以写出来第①部分和3条竖线阈值的关系是[-1,-1,-1],与2条横线阈值的关系是:[1,1]。我们把阈值与区域和阈值的关系进行点乘(竖线和横线要分开,然后再合起来),比如还是第①部分:

        [-1,-1,-1]*[-1,1,1]+[1,1]*[-1,1]=[1,-1,-1]+[-1,1]=[1,-1,-1,-1,1]       ④

       上式④的+号是合并列表的意思,不是加减乘除的+号。对比式子③,可以看出与式子③的第一个式子一致,因此这样考虑是可行的,不信的话,可以根据上述规则写出第②部分的计算式子:

      [1,-1,-1]*[-1,1,1]+[1,1]*[-1,1]=[-1,-1,-1]+[-1,1]=[-1,-1,-1,-1,1]      ⑤

        再把⑤和式子③的第二个式子对比,发现还是一致,现在小伙伴们可以相信这个做法是正确的。

       可是还有一个问题:上述区域与竖线阈值(或横线阈值)的关系而写出的表达式使我们用肉眼看出来的,比如你看到第①部分在竖线阈值0.4的左边,你才根据规则写出的-1,可是程序不会肉眼看啊,现在我们再来发现一个规律:先把这些位置关系的表达式写出来,看看有没有规律可循,比如我先把①②③④这个四个区域与竖线阈值的关系写出来,你可以看出端倪:

                                                \begin{matrix} [-1,-1,-1]\\ [\; \; \: 1,-1,-1] \\ [\; \; \: 1,\; \; \: 1,-1] \\ [\; \; \: 1,\; \; \: 1,\; \; \: 1] \end{matrix}

       我们可以看出第一行0个1,3个-1,而第二行1个1,2个-1,第三行2个1,1个-1,第四行3个1,0个-1。而在写代码时,我们遍历行的时候,就是遍历①②③④这四个部分来填充颜色,不正是第①部分j=0;第②部分j=1;第③部分j=2;第④部分j=3。那么上面矩阵的每一行就可以这么来制造,伪代码就是:

       for j in range(竖线阈值的条数+1):                # 因为看图7,第一行不正好4部分,对应区域①②③④

            row_list = [1]*j + [-1]*(竖线阈值的条数)

        同样的方法,我们也可以写出①⑤⑨这个3个区域与横线阈值的关系(为啥不继续写①②③④与横线的关系,因为①②③④与横线的关系是一样的,①⑤⑨在图7中是不同列的,他们的关系才是不同的):

                                               \begin{matrix} [\; \; \: 1,\; \; \: 1]\\ [-1,\; \; \: 1] \\ [-1,-1] \end{matrix}

        我们同样看出了端倪:第一行0个-1,2个1;第二行1个-1,1个1;第三行2个-1,0个1。这正是我们写代码时遍历列,第一列 i = 0,第二列 i=1,第三列 i=2,因此伪代码是:

        for i in range(横线阈值的条数+1):

             column_list = [-1]*i + [1]*(横线阈值的条数)

         因此我们把遍历行和遍历列的伪代码拼在一起,成为遍历3×4个区域,依次填充颜色。

✿ 我们写成伪代码(代码里的+号都是合并列表的意思,不是加减乘除的+号):

              for  i in range(横线阈值的条数+1):

                    for j in range(竖线阈值的条数+1):

                          symbol_list = row_list = [1]*j + [-1]*(竖线阈值的条数) + column_list = [-1]*i + [1]*(横线阈值的条数)

                          result = sign([alpha值的列表]*symbol_list):

                                    if  reslut == 1:

                                            正类(红点)

                                     else:

                                            负类(蓝点)

★ 参考链接:

         1.   http://www.cnblogs.com/Tang-tangt/p/9557089.html

         2.   https://zhuanlan.zhihu.com/p/30035094

         3.   统计学习与方法, 李航 P138-P146

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值