Python机器学习日记(十)

利用AdaBoost元算法提高分类性能

这里解除了一个新概念-元算法。元算法是对其他算法进行组合的一种方式,其中AdaBoost是最流行的元算法。接下来会建立一个单层决策树分类器,并且在一个复杂数据集上应用该分类器,来了解该算法是如何迅速超越其他分类器的。 

我们可以将不同的分类器进行组合,这种租和结果则被称为集成方法或元算法。

AdaBoost的优点是繁华错误率低,易编码,可以应用在大部分分类器上,无参数调整。

缺点是对离群点敏感。

适用数据类型:数值型和标称型数据。


bagging:基于数据随机重抽样的分类器构建方法

bagging:自居汇聚法。是从原始数据集选择S次后得到一个新数据集的一种技术。新数据集和原数据集的大小相等。每个数据集都是通过在原始数据集中随机选择一个样本来进行替换而得到的。这一性质就允许新数据集中可以有重复的值,而原始数据集的某些之在新集合中则不再出现。

boosting:bagging不同的分类器是通过串行训练而获得的,每个新分类器都根据以训练出的分类器的性能来进行训练。而后者是通过集中关注被御用分类器错分的那些数据来获得新的分类器。前者中分类器的权重是相等的,而后者并不相等,每个权重代表是其对性分类器在上一轮迭代中的成功度。

AdaBoost运行过程如下:训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量D。这些权重一开始都初始化成想等值。首先在训练数据上练出一个弱分类器并计算给分类器的错误率,然后在统一数据集上再次训练弱分类器。在分类器的第二次训练当中,将会重新调整每个样本的权重,其中第一次分队的样本的权重将会降低,而第一次分错的样本的权重将会提高。为了从所有弱分类器中得到最终的分类结果,AdaBoost为每个分类器都分配了一个权重值alpha,这些alpha值是基于每个弱分类器的错误率进行计算的。

错误率的定义为:

                                                                  

 

计算出alpha的值后,可以对权重向量D进行更新,一时的那些正确分类的样本的权重降低而错分样本的权重升高。D的计算方法如下:

 之后AdaBoost开始进入下一轮迭代。不断重复悬链和调整权重的过程,知道训练错误率为0或者弱分类器的数目达到用户的指定值为止。


接下来建立完整的AdaBoost算法没在这之前先通过一些代码来建立弱分类器及保存数据集的权重。

基于单层决策树构建弱分类器

单层决策树是一种简单的决策树,它仅仅与单个特征来做决策,只有一次分裂过程。

先建立一个简单数据集来确保在算法实现上一切就绪。

def loadSimpData():
    datMat = matrix([[1.,2.1],
                     [2.,1.1],
                     [1.3,1.],
                     [2.,1.]])
    classLabels = [1.0,1.0,-1.0,-1.0,1.0]
    return datMat,classLabels


dataMat,classLabels = loadSimpData()

通过这段代码来实现数据集和类标签的导入

接下来通过构建多个函数来建立单层决策树。

def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):
    retArray = ones((shape(dataMatrix)[0],1))
    if threshIneq == 'lt':
        retArray[dataMatrix[:,dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:,dimen] > threshVal] = -1.0
    return retArray
"""
首先将返回数组的全部元素设置为1,将所有不满足不等式要求的元素设置为-1
可以基于数据集中的任意元素进行比较,同时也可以将不等号在大于小于之间切换
第一个函数通过阈值比较对数据进行分类,所有在阈值一边的数据会分到类别-1,另一边的分到类别+1.
该函数可以通过数组过滤来实现。
"""

def bulidStump(dataArr,classLabels,D):
    dataMatrix = mat(dataArr)
    labelMat = mat(classLabels).T
    m,n = shape(dataMatrix)
    numSteps = 10.0  #用于在特征的所有可能值上进行遍历。
    bestStump = {}  #构建一个空字典,用于存储给定权重向量D时所得到的最佳单层决策树的相关信息。
    bestClasEst = mat(zeros((m,1)))
    minError = inf #一开始初始化为正无穷大,之后用于寻找可能的最小错误率。
    for i in range(n):
        rangeMin = dataMatrix[:,i].min()
        rangeMax = dataMatrix[:,i].max()
        stepSize = (rangeMax-rangeMin)/numSteps
        #第一层在数据集上的所有特征遍历,可以通过计算最小值和最大值来交接应该需要多大的步长
        for j in range(-1,int(numSteps)+1):
            #第二层循环在上一层的值上进行比遍历,将阈值设置为整个却只范围之外也是可以的
            #因此,在取值范围之外还有两个二外的步骤
            for inequal in ['lt','gt']:
                threshVal = (rangeMin+float(j)*stepSize)
                predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal)
                #循环调用stumpClassify()函数,基于这些循环变量,该函数返回分类预测结果
                errArr = mat(ones((m,1)))
                errArr[predictedVals == labelMat] = 0
                #构建列向量,如果predictedVals中的值不等于labelMat中的真正类别标签值,那么errArr的相应位置为1
                weightedError = D.T*errArr
                #将错误向量errArr和权重向量D的相应元素相乘并求和,得到数值weightedError。这就是AdaBoost和分类器交互的地方
                #在这里基于权重向量D而不是其他全无计算指标来评价分类器
                print("split:dim %d,thresh %.2f,thresh inequal:\
                %s,the weighted error is %.3f"% \
                      (i,threshVal,inequal,weightedError))
                #接下来输出岁哦头的值,方便理解函数的运行,也可以注释掉
                if weightedError<minError:
                    minError = weightedError
                    bestClasEst = predictedVals.copy()
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
                    #将当前错误率与已有的最小错误率进行对比,如果当前值较小,就在bestStump中保存该单层决策树
                    #字典,错误率和类别估计值都会返回给AdaBoost算法
        return bestStump,minError,bestClasEst


"""
将最小错误率minError设为+∞
对每个数据中的每一个特征(第一层循环):
   对每个步长(第二层循环):
      对每个不等号(第三层循环):
         建立一颗单层决策树并利用加权数据集对它进行测试
        如果错误率低于minError,则当前单层决策树设为最佳单层决策树
返回最佳单层决策树

遍历stumpClassify()函数所有可能的输入值,并找到数据集上最佳的单层决策树。
"""


dataMat,classLabels = loadSimpData()
D = mat(ones((5,1))/5)
bulidStump(dataMat,classLabels,D)

输出一下结果:

split:dim 0,thresh 0.90,thresh inequal:                lt,the weighted error is 0.400
split:dim 0,thresh 0.90,thresh inequal:                gt,the weighted error is 0.600
split:dim 0,thresh 1.00,thresh inequal:                lt,the weighted error is 0.400
split:dim 0,thresh 1.00,thresh inequal:                gt,the weighted error is 0.600
split:dim 0,thresh 1.10,thresh inequal:                lt,the weighted error is 0.400
split:dim 0,thresh 1.10,thresh inequal:                gt,the weighted error is 0.600
split:dim 0,thresh 1.20,thresh inequal:                lt,the weighted error is 0.400
split:dim 0,thresh 1.20,thresh inequal:                gt,the weighted error is 0.600
split:dim 0,thresh 1.30,thresh inequal:                lt,the weighted error is 0.200
split:dim 0,thresh 1.30,thresh inequal:                gt,the weighted error is 0.800
split:dim 0,thresh 1.40,thresh inequal:                lt,the weighted error is 0.200

 书中还返回了字典,但是我的代码中并没有返回,不知道原因是什么,如果有幸有哪位大神看到希望可以帮我解决疑惑。

上述大段文字都是在书上的原话经由我手敲出来,目的是在自己没带书的时候方便进行学习,并无抄袭的意思,如果这种做法不当还请指出,我立即删掉,感谢。

上面的代码是一种所谓的弱分类算法,接下来将使用多个弱分类器来构建完整的AdaBoost代码。


完整AdaBoost算法的实现

整个实现的伪代码如下:

对每次迭代:

   利用buildStump()函数找到最佳的单层决策树

   利用最佳单层决策树加入到单层决策树组

   计算alpha

  计算新的权重向量D

  更新累计类别估计值

  如果错误率为0.0,则退出循环

 

def adaBoostTrainDS(dataArr,classLabels,numIt = 40):
    weakClassArr = []
    #该函数后面会输出一个单层决策树的数组因此先建立一个新的python表来对其进行存储
    m = shape(dataArr)[0]
    #得到数据集中的数据点的数目m,并在后面建立一个列向量D
    D = mat(ones((m,1))/m)
    #该向量包含了每个数据点的权重,一开始这些权重豆腐鱼了相等的值
    #该向量是一个概率分布向量,所有元素之和为1.0,因此一开始都被初始化为1/m
    aggClassEst = mat(zeros((m,1)))
    #列向量aggClassEst用来记录每个数据点的类别估计累计值
    for i in range(numIt):
        bestStump,error,classEst = buildStump(dataArr,classLabels,D)
        #利用buildStump()函数建立一个单层决策树,输入为权重向量D,返回的则是利用D而得到的具有最小错误率的单层决策树
        #同时返回的还有最小的错误率以及估计的类别向量
        print("D:",D.T)
        alpha = float(0.5*log((1.0-error)/max(error,int(1e-16))))
        #该值告诉总分类器本次单层决策树输出结果的权重
        #max(error,1e-16)用于确保在没有错误的情况下不会发生除零溢出
        bestStump['alpha'] = alpha
        #将alpha值添加到列表中,盖子点包括了分类所需要的所有信息
        weakClassArr.append(bestStump)
        print("classEst:",classEst.T)
        expon = multiply(-1*alpha*mat(classLabels).T,classEst)
        D = multiply(D,exp(expon))
        D = D/D.sum()
        #上述三行用于计算下一次迭代中的新权重向量D,在错误训练率为0是,就要提前结束for循环
        aggClassEst += alpha*classEst
        print("aggClassEst:",aggClassEst.T)
        aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1)))
        errorRate = aggErrors.sum()/m
        # 程序通过aggClassEst变量保持一个运行时的类别估计值来实现
        # 该值只是一个浮点数,为了得到二值分类结果还需要调用sign()函数
        # 如果总错误率为0,则由break语句终止for循环
        print("total error:",errorRate,"\n")
        if errorRate == 0.0 :
            break
        #for循环是该算法的核心,该循环运行numIt次或者知道训练错误率为0为止
    return weakClassArr
"""
该算法的输入参数包括:
  -dataArr:数据集
  -classLabels:类别标签
  -numIt:迭代次数,也是整个算法中唯一需要用户指定的参数
print()在后面的代码添加过程总还是可以注视掉,现在的作用还是用于理解算法内部运行过程
函数名称尾部的DS就代表了单程决策树,是AdaBoost中最流行的弱分类器
"""

D: [[0.2 0.2 0.2 0.2 0.2]]
classEst: [[-1.  1. -1. -1.  1.]]
aggClassEst: [[-0.69314718  0.69314718 -0.69314718 -0.69314718  0.69314718]]
total error: 0.2 

D: [[0.5   0.125 0.125 0.125 0.125]]
classEst: [[1. 1. 1. 1. 1.]]
aggClassEst: [[-0.14384104  1.24245332 -0.14384104 -0.14384104  1.24245332]]
total error: 0.2 

D: [[0.33333333 0.08333333 0.25       0.25       0.08333333]]
classEst: [[-1.  1. -1. -1.  1.]]
aggClassEst: [[-0.49041463  1.58902692 -0.49041463 -0.49041463  1.58902692]]
total error: 0.2 

D: [[0.5    0.0625 0.1875 0.1875 0.0625]]
classEst: [[ 1. -1. -1.  1. -1.]]
aggClassEst: [[-0.09618595  1.19479823 -0.88464331 -0.09618595  1.19479823]]
total error: 0.2 

D: [[0.36363636 0.1        0.13636364 0.3        0.1       ]]
classEst: [[-1.  1. -1. -1.  1.]]
aggClassEst: [[-0.37599384  1.47460613 -1.1644512  -0.37599384  1.47460613]]
total error: 0.2 

根据数据的类别标签,在第一轮迭代中,D的所有值都相等,只有第一个数据点被错分了。因此在第二轮迭代中,D向量给第一个数据点0.5的权重。第二次迭代之后就会发现第一个数据点已经正确分类了,但此时最后一个数据点是错误的。D向量中的做后一个元素变成0.5,而D向量中的其他值都变得非常小。最后第三次迭代之后aggClassEst所有值得符号和真实类别标签都完全吻合,那么训练错误率为0,程序就此退出。

然后观察classifierArray的值:

[{'dim': 0, 'thresh': 1.3, 'ineq': 'lt', 'alpha': 0.6931471805599453}, {'dim': 0, 'thresh': 0.9, 'ineq': 'lt', 'alpha': 0.5493061443340548}

该数组包含三部词典牟其中包含了分类所需要的所有信息。此时一个分类器已经构建成功。


测试算法:基于AdaBoost的分类:

接下来需要做的是将弱分类器的训练过程从程序中抽取出来,然后应用到某个具体的实例上去。每个弱分类器的结果以及其对应的alpha的值作为权重,所有这些弱分类器的结果加权求和就得到了最后的结果。

def adaClassify(dataToClass,classifierArr):
    dataMatrix = mat(dataToClass)
    # 先将datToClass转换成了一个NumPy矩阵
    m = shape(dataMatrix)[0]
    # 得到datToClass中的待分类样例的个数m
    aggClassEst = mat(zeros((m,1)))
    for i in range(len(classifierArr)):
        classEst = stumpClassify(dataMatrix,classifierArr[i]['dim']\
                                 ,classifierArr[i]['thresh'],\
                                 classifierArr[i]['ineq'])
        aggClassEst += classifierArr[i]['alpha']*classEst
        print(aggClassEst)
    return sign(aggClassEst)
"""
利用训练出的多个弱分类器进行分类的函数,该函数的输入参数是有一个或者多个待分类样例
以及多个弱分类器组成的数组classifierArr
程序返回aggClassEst的符号,如果aggClassEst>0返回+1,<0返回-1
"""
datArr,labelArr = loadSimpData()
classifierArr = adaBoostTrainDS(datArr,labelArr,30)
adaClassify([0,0],classifierArr)

[[-0.69314718]]
[[-1.24245332]]
[[-1.58902692]]

可以看到随着迭代的进行,数据点[0,0]的分类结果越来越强。


示例:在一个难数据集上应用AdaBoost

def loadDataSet(fileName):
    numFeat = len(open(fileName).readline().split('\t'))
    dataMat = []
    labelMat = []
    fr = open(fileName)
    for line in fr.readlines():
        lineArr = []
        curLine = line.strip().split('\t')
        for i in range(numFeat-1):
            lineArr.append(float(curLine[i]))
        dataMat.append(lineArr)
        labelMat.append(float(curLine[-1]))
    return dataMat,labelMat

"""
在这里并没有指定每个文件中的特征数目,该函数能够自动监测数特征的数目,该函数也假定最后一个特征是类别标签
"""

datArr,labelArr = loadDataSet('F:\python\machinelearninginaction\Ch07\horseColicTraining2.txt')
classifierArray = adaBoostTrainDS(datArr,labelArr,10)
testArr,testLabelArr = loadDataSet('F:\python\machinelearninginaction\Ch07\horseColicTest2.txt')
prediction10 = adaClassify(testArr,classifierArray)
errArr = mat(ones((67,1)))

total error: 0.40468227424749165 

total error: 0.40468227424749165 

total error: 0.40468227424749165 

def plotROC(predStrengths,classLabels):
    import matplotlib.pyplot as plt
    cur = (1.0,1.0)
    ySum = 0.0
    numPosClas = sum(array(classLabels)==1.0)
    yStep = 1/float(numPosClas)
    xStep = 1/float(len(classLabels)-numPosClas)
    sortedIndicies = predStrengths.argsort()
    fig = plt.figure()
    fig.clf()
    ax = plt.subplot(111)
    for index in sortedIndicies.tolist()[0]:
        if classLabels[index] == 1.0:
            delX = 0
            delY = yStep
        else:
            delX = xStep
            delY = 0
            ySum += cur[1]
        ax.plot([cur[0],cur[0]-delX],[cur[1],cur[1]-delY],c='b')
        cur = (cur[0]-delX,cur[1]-delY)
    ax.plot([0,1],[0,1],'b--')
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title('ROC curve for AdaBoost Horse Colic Detection System')
    ax.axis([0,1,0,1])
    plt.show()
    print("the Area Under the Curve is:",yStep*xStep)

"""
有两个输入参数,第一个是一个NumPy数组或者一个行向量组成的矩阵
改参数代表的是分类器的预测强度
第二个参数是先前是用过的classLabels,变量ySum用于计算AOCd的值
接下来通过数组过滤方式计算正例的数目,并将该值赋给numPosClas
然后得到排序索引
"""

 已经开学一个月了,经过舍友的了解,奖学金的评选需要打比赛。最为一名菜狗,研究生期间真的想改变一下自己。希望自己不是三分钟热度,尽自己的最大努力多学一点知识。就算没有奖项,也要看看这些所谓的比赛到底是什么东西,究竟会让我学到什么。通过这些也能明确的看到自己的不足,知识面应该也能更款发一些吧。加油。晚安。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值