机器学习实战学习笔记(六)分类—利用AdaBoost元算法提高分类性能(python3实现)

来源:互联网 发布:mac用什么炒股软件 编辑:程序博客网 时间:2024/05/17 23:49

基于数据集多重抽样的分类器

上面几篇学习了五种不同的分类算法,将这些分类器组合起来,这种组合结果被称为集成方法或者元算法。使用集成方法时会有多种形式,可以是不同算法的集成,也可以是同一算法不同设置下的集成,还可以是数据集不同部分分配给不同分类器之后的集成。下面学习基于同一种分类器多个不同实例的两种计算方法,这些方法中,数据集也会不断变化,而后应用于不用的实例分类器。

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

自举汇聚法,是在从原始数据集选择S次后得到S个新数据集的一种技术。新数据集和原数据集大小相等,每个数据集都是通过在原始数据集中随机选择一个样本来进行替换得到(有放回的抽样)。这就意味着新数据集中可以有重复的值,二原始数据集的某些值在心机和中则不再出现。

在S个数据集建好之后,将某个学习算法分别作用于每个数据集就得了S个分类器。当我们要对新数据进行分类,就可以应用这S个分类器进行分类,并选择分类器投票结果中最多的类别作为最后的分类结果

bossting

boosting是一种yubaging很类似的技术。二者所使用的多个分类器 的类型都是一致的,但是在bagging中,不同分类器是通过串行训练二获得的,每个新分类都根据已训练处 的分类器的性能来进行驯良。boosting是通过集中关注被已有分类器错分的那些数据来获得新的分类器。

由于boosting分类 的记过是基于所有分类器的加权求和结果的,因此boosting与bagging不太一样。bagging中的分类器权重是相等的,而boosting中的分类器权重并不相等每个权重代表的是其对应分类器在上一轮迭代中的成功度。

boosting方法中最流行的版本也就是AdaBoost

AdaBoost优点:泛华错误率低,易编码,可以应用到大部分分类器上,无参数调整;缺点:对离群点敏感;适用数据类型:数值型和标称型数据

AdaBoost的一般流程

a. 收集数据

b. 准备数据:依赖于所选用的弱分类器类型(以单层决策树为例),作为弱分类器,简单分类器效果更好

c. 分析数据

d. 训练算法:基于错误提升分类器性能

AdaBoost的大部分时间都用在训练上,分类器将多次在同一数据集上训练弱分类器。“弱”意味着分类器性能比随机猜测要略好,但是也不会好太多,这就是说,在二分类的情况下,弱分类器的错误率会高于50%。

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

错误率=未正确分类的样本数目/所有样本数目

alpha=ln((1-错误率)/错误率)/2

过程如下图:


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

在计算出D之后,AdaBoost进入下一轮的迭代。AdaBoost会不断地重复训练和调整权重的过程,知道错误率为0或者弱分类器的数目到达用户指定的值

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

构建一个单层决策树,它仅基于单个特征来做决策。在构造AdaBoost时,先通过一个简单数据集来确保在算法实现上一切就绪,python3代码如下:

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

有了数据,接下来通过够姜多个函数来建立单层决策树,第一个函数用于测试是否某个值小于或者大于我们正在测试的阈值,第二个函数则更加复杂一下,他会在一个加权数据集中循环,并找到具有最低错误率的单层决策树,(基于加权输入值进行决策分类)伪代码如下

将最小错误率minError设为正无穷

对数据集中的每一个特征(第一层循环):

     对每个步长(第二层循环)::

           对每个不等号(第三层循环):

               建立一颗单层决策树并利用加权数据及对它进行测试

               如果错误率低于minError,则将当前单层决策树设为最佳单层决策树

返回最佳单层决策树

python3代码如下:

def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):#just classify the data    retArray = ones((shape(dataMatrix)[0],1))    if threshIneq == 'lt':        retArray[dataMatrix[:,dimen] <= threshVal] = -1.0    else:        retArray[dataMatrix[:,dimen] > threshVal] = -1.0    return retArraydef buildStump(dataArr,classLabels,D):    dataMatrix = mat(dataArr); labelMat = mat(classLabels).T    m,n = shape(dataMatrix)    numSteps = 10.0; bestStump = {}; bestClasEst = mat(zeros((m,1)))    minError = inf #init error sum, to +infinity    for i in range(n):#loop over all dimensions        rangeMin = dataMatrix[:,i].min(); rangeMax = dataMatrix[:,i].max();        stepSize = (rangeMax-rangeMin)/numSteps        for j in range(-1,int(numSteps)+1):#loop over all range in current dimension            for inequal in ['lt', 'gt']: #go over less than and greater than                threshVal = (rangeMin + float(j) * stepSize)                predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal)#call stump classify with i, j, lessThan                errArr = mat(ones((m,1)))                errArr[predictedVals == labelMat] = 0                weightedError = D.T*errArr  #calc total error multiplied by D                #print "split: dim %d, thresh %.2f, thresh ineqal: %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    return bestStump,minError,bestClasEst

-.完整AdaBoost算法的实现

构建一个完整的AdaBoost算法,这个实现的伪代码:

对每次迭代:

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

     计算alpha

     计算新的权重向量D

     更新累计类别估计值

     如果错误率等于0.0,退出循环

Python3代码如下:

def adaBoostTrainDS(dataArr,classLabels,numIt=40):    weakClassArr = []    m = shape(dataArr)[0]    D = mat(ones((m,1))/m)   #init D to all equal    aggClassEst = mat(zeros((m,1)))    for i in range(numIt):        bestStump,error,classEst = buildStump(dataArr,classLabels,D)#build Stump        #print "D:",D.T        alpha = float(0.5*log((1.0-error)/max(error,1e-16)))#calc alpha, throw in max(error,eps) to account for error=0        bestStump['alpha'] = alpha        weakClassArr.append(bestStump)                  #store Stump Params in Array        #print "classEst: ",classEst.T        expon = multiply(-1*alpha*mat(classLabels).T,classEst) #exponent for D calc, getting messy        D = multiply(D,exp(expon))                              #Calc New D for next iteration        D = D/D.sum()        #calc training error of all classifiers, if this is 0 quit for loop early (use break)        aggClassEst += alpha*classEst        #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    return weakClassArr,aggClassEst

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

Python3代码如下:

def adaClassify(datToClass,classifierArr):    dataMatrix = mat(datToClass)#do stuff similar to last aggClassEst in adaBoostTrainDS    m = shape(dataMatrix)[0]    aggClassEst = mat(zeros((m,1)))    for i in range(len(classifierArr)):        classEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],\                                 classifierArr[i]['thresh'],\                                 classifierArr[i]['ineq'])#call stump classify        aggClassEst += classifierArr[i]['alpha']*classEst        print(aggClassEst)    return sign(aggClassEst)

f. 在一个难数据集上应用AdaBoost-马疝病数据集上应用

几个步骤:

-收集数据

-准备数据:确保类别标签是+1和-1而非1和0

-分析数据:手工检查数据

-训练算法:在数据上,利用adaBoostTrainDS()函数训练处一系列的分类器

-测试算法:再不采取随机抽样的方法下利用这两个数据集进行AdaBoost和Logistic回归的结果进行对等比较

-使用算法:观察该例子上的错误率

首先,一个向文件中加载数据的方法,Python3代码如下:

def loadDataSet(fileName):      #general function to parse tab -delimited floats    numFeat = len(open(fileName).readline().split('\t')) #get number of fields     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

g.非均衡分类问题

不同类别的分类问题的分类代价是不同的,我们需要考察一种新的分类器性能度量方法

-正确率、召回率、ROC曲线

之前分析分类器的性能的时候,我们多采用错误率,实际上,这样的度量错误掩盖了样例如何被分错的事实。我们可以用一个称为混淆矩阵的工具,若该矩阵非对角线上的元素都是非0,就会得到一个完美的分类器。我们考虑这样一个混淆矩阵针对一个简单的二类分类问题:


当某个类别的重要性高于其他类别时,我们就可以利用上述定义来定义出多个比错误率更好的新指标。第一个是正确率=TP/(TP+FP),指出了预测为正例的样本中真正正例的比例。第二个是召回率=TP/(TP+FN),指出了预测为正例的真实正例占所有真实正例的比例。在召回率很高的分类器中,真正判错的正例的数目并不多。但是同时构建一个正确率和召回率都很高的分类器显然矛盾。

我们提出另外一个工具,ROC曲线,ROC代表接收者操作特征,下面是利用10个单层决策树的AdaBoost马疝病检测系统的ROC曲线:


ROC曲线中给出了两条线,其中横轴是伪正例的比例(假阳率=FP/(FP+TN)),纵轴是真正例的比例(真阳率=TP/(TP+FN))。ROC曲线给出的是当阈值变化时,假阳率和真阳率的变化情况。左下角代表的是将所有样例判为反例的情况,右上角的点对应的则是将所有样例判为正例的情况。虚线给出随机猜测的结果曲线。

ROC还可以用于成本效益分析以做出决策。由于不同的阈值下,不同分类器的表现情况可能不相同,因此以某种方式将它们组合起来或许会更有意义。

在理想的情况下,最佳的分类器应该尽可能地处于左上角,也就意味着分类器在假阳率很低的同时获得了很高的真阳率。

对不同的ROC曲线进行比较的一个指标是曲线下的面积(AUC:Area Under the Curve)。AUC给出的是分类器的平均性能值。这只是我们观察整条曲线的一个指标而不是全部,完美分类器AUC是1.0,而随机猜测的AUC是0.5.

为了画出ROC曲线,分类器必须提供每个样例被判为阳性或者阴性的可信程度值,但是通常情况下这些值最后输出离散分类标签之前被清除。之前提到过的分类器的这一值可以用于衡量给定分类器的预测强度,为了创建ROC曲线,首先要将分类样例按照其预测强度排序。先从排名低的样例开始,所有排名更低的样例被判为反例,而所有排名更高的样例都被判为正例。然后,将其移到排名次低的样例中,如果该样例属于正例,那么对真阳率进行修改;否则对假阴率进行修改。Python3代码如下:

def plotROC(predStrengths, classLabels):    import matplotlib.pyplot as plt    cur = (1.0,1.0) #cursor    ySum = 0.0 #variable to calculate AUC    numPosClas = sum(array(classLabels)==1.0)    yStep = 1/float(numPosClas); xStep = 1/float(len(classLabels)-numPosClas)    sortedIndicies = predStrengths.argsort()#get sorted index, it's reverse    fig = plt.figure()    fig.clf()    ax = plt.subplot(111)    #loop through all the values, drawing a line segment at each point    for index in sortedIndicies.tolist()[0]:        if classLabels[index] == 1.0:            delX = 0; delY = yStep;        else:            delX = xStep; delY = 0;            ySum += cur[1]        #draw line from cur to (cur[0]-delX,cur[1]-delY)        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: ",ySum*xStep)

-基于代价函数的分类器决策控制

还有一种方法叫做代价敏感的学习,下面有两个代价矩阵,代价计算分别是TP*0+FN*1+FP*1+TN*0和TP*(-5)+FN*1+FP*50+TN*0,我们可以选择代价最小的分类器,


在分类算法中,我们有很多方法引入代驾信息,在AdaBoost中,可以基于代价函数来调整错误权重向量D,在朴素贝叶斯中,可以选择具有最小期望代价而不是最大概率的类别作为最后的结果。在SVM中,可以在代价函数中对于不同的类别选择不同的参数C。上述做法会给较小类更多权重,即训练时,只允许更少的错误

-处理非均衡问题的数据抽样方法

通过对分类器训练数据进行改造来调节分类器的方法,有欠抽样和过抽样两种方式。其中,过抽样意味着复制样例,而欠抽样意味着删除样例,抽样过程既可以是随机的,也可以通过某个预定方式来实现,最终将数据改造为新形式。

通常也会存在某个罕见的类别需要我们来识别,一些训练集中的类别为1的可能较少(信用卡欺诈例子),我们需要保留正例中的所有样例同时对反例进行欠抽样。但是如何确定删除那些样例以尽量减少损失独特性的有价值信息。解决这种问题的方法就是选择那些离决策边界较远的样例进行删除。欠抽样和过抽样想象结合。再对正例进行过抽样时,我们可以复制已有样例或者加入与已有样例相似的点,一种方法是加入已有数据点的插值点(可能会导致过拟合)

小结

集成方法通过组合多个分类器的分类结果获得了比简单分类器更好的效果,但是如果多个分类器组合可能进一步凸显单分类器的不足,我们可以通过放大分类器之间的差别(算法本身或者是应用于算法上的数据的不同)来缓解这一问题。


阅读全文
0 0
原创粉丝点击