机器学习实战之adaboost(附python3代码)

本节代码存放在github网址上,环境:python3
利用AdaBoost元算法提高分类性能

元算法(meta-algorithm)或者集成方法(ensemble method)是对其他算法进行组合的一种方式
AdaBoost算法
优点:范化错误率低,易编码,可以应用在大部分分类器上,无参数调整
缺点:对离群点敏感
适用数据类型: 数值型和标称型数据

bagging和boosting所使用的多个分类器类型都是一致的!

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

放回取样得到S个数据集,将某个学习算法分别作用于每个数据集就得到了S个分类器,要对新数据进行分类时,可以应用这S个分类器进行分类,
选取分类器投票结果中最多的类别作为最后的分类结果。分类器的权重是相等的。

random forest 也是bagging方法的一种

boosting:通过集中关注被已有分类器错分的那些数据来获得新的分类器

不同的分类器是通过串行训练获得的,每个新的分类器都根据已训练出的分类器的性能来训练。
其分类结果是基于所有分类器的加权求和结果的,分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。

AdaBoost一般流程

  1. 收集数据:可以使用任何方法
  2. 准备数据:依赖于所使用的弱分类器类型,本章使用的是单层决策树,这种分类器可以处理任何数据类型。当然也可以使用任意分类器作为弱分类器,KNN, 决策树,朴素贝叶斯,Logistic回归,SVM中任一分类器都可以充当弱分类器。作为弱分类器,简单分类器的效果最好。 确保类别标签是+1和-1,而非1和0.
  3. 分析数据:可以使用任一方法分析数据:可以使用任一方法
  4. 训练算法:AdaBoost的大部分时间都用在训练上, 分类器将多次在同一数据集上训练弱分类器。
  5. 测试算法:计算分类的错误率测试算法:计算分类的错误率
  6. 使用算法:同SVM同,AdaBoost预测两个类别中的一个。若想把它应用到多分类的场合,需要像多类SVM中的做法一样对AdaBoost进行修改。使用算法:同SVM同,AdaBoost预测两个类别中的一个。若想把它应用到多分类的场合,需要像多类SVM中的做法一样对AdaBoost进行修改。

AdaBoost运行过程

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

在这里插入图片描述

alpha定义为:

在这里插入图片描述

计算出alpha后,可以对权重D进行更新,以使那些正确分类的样本权重降低而错分样本的权重提高。D的计算方法如下:
若某个样本被正确分类,该样本权重更改为:
在这里插入图片描述

若某个样本被错分,该样本权重更改为:

在这里插入图片描述

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

伪代码如下:

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

python代码如下:

# -*- coding: utf-8 -*-

__author__ = 'WF'
# import warnings
import numpy as np
# warnings.simplefilter(action='ignore', category=FutureWarning)

# 加载简单数据集
def loadSimpleData():
    dataMat = np.matrix([[1.,  2.1],
                         [2.,  1.1],
                         [1.3,  1.],
                         [1.,  1.],
                         [2.,  1.]])
    classLabel = [1.0, 1.0, -1.0, -1.0, 1.0]
    return dataMat, classLabel

# 图形化展示数据
def plot_simpledata():
    import matplotlib.pyplot as plt
    x, y = loadSimpleData()
    y = np.array(y)
    x = np.array(x)
    # print(y)
    # x1 = x[np.nonzero(y)[0]]
    x1 = x[np.where(y == 1.0)[0]]
    # print(np.nonzero(y)[0])
    x0 = x[np.where(y == -1.0)[0]]
    # print(x0[:, 0], x0[:, 1])
    figure = plt.figure()
    ax = figure.add_subplot(111)
    ax.scatter(x0[:, 0], x0[:, 1], marker='<', c='r', s=50)
    ax.scatter(x1[:, 0], x1[:, 1], marker='s', c='g', s=20)
    plt.show()

# 单层决策树生成函数
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    '''
    通过阈值比较对数据进行分类,所有在阈值一边的数据会被分到-1,另一边的数据被分到1
    :param dataMatrix:数据矩阵
    :param dimen: 维度属性
    :param threshVal: 阈值
    :param threshIneq:阈值比较符号
    :return:单层决策树字典,错误率,类别估计
    '''
    retArray = np.ones((np.shape(dataMatrix)[0], 1))
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray

# 遍历stumpClassify()函数所有的可能输入值,并找到数据集上最佳的单层决策树
def buildStump(dataArr, classLabels, D):
    # classLabels = np.reshape(classLabels, (len(classLabels), 1))

    dataMatrix = np.mat(dataArr)
    labelMat = np.mat(classLabels).T
    # print(np.shape(labelMat))   # (5,1)
    m, n = np.shape(dataMatrix)
    numSteps = 10.0
    bestStump = {}
    bestClasEst = np.mat(np.zeros((m, 1)))
    minError = np.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)
                # print('p:', predictedVals)
                # print(np.shape(predictedVals))  #(5, 1)
                errArr = np.mat(np.ones((m, 1)))
                # print(np.where(predictedVals == labelMat)[0])
                # for num in range(len(predictedVals)):
                #     print(predictedVals[num] == classLabels[num])
                #     if float(predictedVals[num]) == float(classLabels[num]):
                #         errArr[num][0] = 0
                # print(errArr)
                errArr[predictedVals == labelMat] =0
                # print(errArr)
                weightedError = D.T * errArr   # 计算加权错误率
                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算法实现

**
伪代码:

对每次迭代:
	利用buildStump()函数找到最佳的单层决策树
	将最佳的单层决策树加入到单层决策树组
	计算alpha
	计算新的权重向量D
	更新累计类别估计值
	如果错误率等于0.0, 则退出循环

python代码如下:

def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
 	'''
    :param dataArr:数据集(不包含label)
    :param classLabels: 类别标签
    :param numIt: 迭代次数
    :return:
    '''
    weakClassArr = []
    m = np.shape(dataArr)[0]
    D = np.mat(np.ones((m, 1))/m)
    aggClassEst = np.mat(np.zeros((m, 1)))
    for i in range(numIt):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        print('D:', D.T)
        alpha = float(0.5*np.log((1.0-error)/max(error, 1e-16)))
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)
        print('classEst:', classEst.T)
        # 为下一次迭代计算D
        expon = np.multiply(-1*alpha*np.mat(list(map(float, classLabels))).T, classEst)
        D = np.multiply(D, np.exp(expon))
        D = D/D.sum()
        aggClassEst += alpha*classEst
        print('aggClassEst:', aggClassEst.T)
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m, 1)))
        errorRate = aggErrors.sum()/m
        print('errorRate:', errorRate)
        if errorRate == 0.0:
            break
    return weakClassArr, aggClassEst

基于AdaBoost的分类

每个弱分类器的结果以其对应的alpha作为权重,所有这些弱分类器的结果加权求和就得到最后的结果。程序返回aggClassEst(记录每个数据点的类别估计累计值)的符号,如果aggClassEst大于0则返回+1, 否则返回-1
python代码如下:

def adaClassify(datToClass, classifierArr):
    dataMatrix = np.mat(datToClass)
    m = np.shape(dataMatrix)[0]
    aggClassEst = np.mat(np.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:', aggClassEst)
    return(np.sign(aggClassEst))

完整代码如下

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = 'WF'
# import warnings
import numpy as np
# warnings.simplefilter(action='ignore', category=FutureWarning)

def loadSimpleData():
    dataMat = np.matrix([[1.,  2.1],
                         [2.,  1.1],
                         [1.3,  1.],
                         [1.,  1.],
                         [2.,  1.]])
    classLabel = [1.0, 1.0, -1.0, -1.0, 1.0]
    return dataMat, classLabel


def adaptive_load_data(filename):
    numFeat = len(open(filename).readline().strip().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


def plot_simpledata():
    import matplotlib.pyplot as plt
    x, y = loadSimpleData()
    y = np.array(y)
    x = np.array(x)
    # print(y)
    # x1 = x[np.nonzero(y)[0]]
    x1 = x[np.where(y == 1.0)[0]]
    # print(np.nonzero(y)[0])
    x0 = x[np.where(y == -1.0)[0]]
    # print(x0[:, 0], x0[:, 1])
    figure = plt.figure()
    ax = figure.add_subplot(111)
    ax.scatter(x0[:, 0], x0[:, 1], marker='<', c='r', s=50)
    ax.scatter(x1[:, 0], x1[:, 1], marker='s', c='g', s=20)
    plt.show()


def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    '''
    通过阈值比较对数据进行分类,所有在阈值一边的数据会被分到-1,另一边的数据被分到1
    :param dataMatrix:数据矩阵
    :param dimen: 维度属性
    :param threshVal: 阈值
    :param threshIneq:阈值比较符号
    :return:单层决策树字典,错误率,类别估计
    '''
    retArray = np.ones((np.shape(dataMatrix)[0], 1))
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray


def buildStump(dataArr, classLabels, D):
    # classLabels = np.reshape(classLabels, (len(classLabels), 1))

    dataMatrix = np.mat(dataArr)
    labelMat = np.mat(classLabels).T
    # print(np.shape(labelMat))   # (5,1)
    m, n = np.shape(dataMatrix)
    numSteps = 10.0
    bestStump = {}
    bestClasEst = np.mat(np.zeros((m, 1)))
    minError = np.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)
                # print('p:', predictedVals)
                # print(np.shape(predictedVals))  #(5, 1)
                errArr = np.mat(np.ones((m, 1)))
                # print(np.where(predictedVals == labelMat)[0])
                # for num in range(len(predictedVals)):
                #     print(predictedVals[num] == classLabels[num])
                #     if float(predictedVals[num]) == float(classLabels[num]):
                #         errArr[num][0] = 0
                # print(errArr)
                errArr[predictedVals == labelMat] =0
                # print(errArr)
                weightedError = D.T * errArr   # 计算加权错误率
                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


def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
    '''

    :param dataArr:数据集(不包含label)
    :param classLabels: 类别标签
    :param numIt: 迭代次数
    :return:
    '''
    weakClassArr = []
    m = np.shape(dataArr)[0]
    D = np.mat(np.ones((m, 1))/m)
    aggClassEst = np.mat(np.zeros((m, 1)))
    for i in range(numIt):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        print('D:', D.T)
        alpha = float(0.5*np.log((1.0-error)/max(error, 1e-16)))
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)
        print('classEst:', classEst.T)
        # 为下一次迭代计算D
        expon = np.multiply(-1*alpha*np.mat(list(map(float, classLabels))).T, classEst)
        D = np.multiply(D, np.exp(expon))
        D = D/D.sum()
        aggClassEst += alpha*classEst
        print('aggClassEst:', aggClassEst.T)
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m, 1)))
        errorRate = aggErrors.sum()/m
        print('errorRate:', errorRate)
        if errorRate == 0.0:
            break
    return weakClassArr, aggClassEst


def adaClassify(datToClass, classifierArr):
    dataMatrix = np.mat(datToClass)
    m = np.shape(dataMatrix)[0]
    aggClassEst = np.mat(np.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:', aggClassEst)
    return(np.sign(aggClassEst))

def plotROC(predStrengths, classLabels):
    import matplotlib.pyplot as plt
    cur = (1.0, 1.0)
    ySum = 0.0   # 用于计算AUC的值
    numPosClas = sum(np.array(classLabels) == 1.0)   # 计算正例的数目
    yStep = 1/float(numPosClas)
    xStep = 1/float(len(classLabels)-numPosClas)
    sortedIndicies = predStrengths.argsort()
    fig = plt.figure()
    fig.clf()   # 清除当前 figure 的所有axes,但是不关闭这个 window,所以能继续复用于其他的 plot。
    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:', ySum*xStep)


if __name__ == "__main__":
    dataMat, classLabels = loadSimpleData()
    # plot_simpledata()

    # D = np.mat(np.ones((5, 1))/5)
    # bestStump, minError, bestClasEst = buildStump(dataMat, classLabels, D)
    # print(bestStump, minError, bestClasEst)

    # weakClassArr = adaBoostTrainDS(dataMat, classLabels, 9)
    # print(weakClassArr)
    #
    # print(adaClassify([[0, 0], [5, 5]], weakClassArr))

    datArr, labelArr = adaptive_load_data('horseColicTraining2.txt')
    # print(datArr)
    print(labelArr)
    classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 10)
    # testArr, testLabelArr = adaptive_load_data('horseColicTest2.txt')
    # prediction10 = adaClassify(testArr, classifierArr)
    # print(prediction10.T)
    # errArr = np.mat(np.ones((67, 1)))
    # errnum = errArr[prediction10 != np.mat(testLabelArr).T].sum()
    # print('errornum:', errnum, 'errorRate:', errnum/67)

    plotROC(aggClassEst.T, labelArr)

分类性能度量指标:准确率、召回率、ROC曲线及AUC

TP(真阳):将正例判定为正例
FP(假阳):将反例判定为正例
TN(真阴):将反例判定为反例
FN(假阴):将正例判定为反例
错误率指在所有测试样例中错分的比例。
准确率指在预测为正例的样本中真正正例的比例

P = TP/(TP+FP)

召回率查全率)指预测为正例的真正正例占所有真实正例的比例

R=TP/(TP+FN)

我们很容易构造一个高准确率或者高召回率的分类器,但是很难保证两者同时成立。如果将任何样本都判为正例,则召回率达到100%,但是准确率很低。

另一个用来度量分类中的非均衡性的工具是ROC曲线(ROC Curve),ROC代表接收者操作特征(receiver operating characteristic)
ROC曲线中,横轴是假阳率=FP/(FP+TN),纵轴是真正率=TP/(TP+FN).
ROC曲线给出的是当阈值变化时假阳率和真阳率变化的情况。左下角点是将所有样例判为反例的情况。
右上角是将所有样例判为正例的情况。
理想情况下,最佳分类器应该尽可能的处于左上角,即分类器在假阳率很低的同时获得了很高的真阳率。
对不同的ROC曲线进行比较的一个指标是曲线下的面积AUC(Area Under the Curve),其给出的是分类器的平均性能值,一个完美的分类器AUC为1.0, 随机猜测的AUC为0.5.

上述代码的ROC曲线如下图:
在这里插入图片描述

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

cost = TP*0 + FP*1 + TN*0 + FN*1
cost = TP*(-5) + FP*50 + TN*0 + FN*1

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

过抽样或者欠抽样
过抽样是复制样例,欠抽样是删除样例,选择离决策边界较远的样例进行删除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章