机器学习实战(七)——利用AdaBoost元算法提高分类性能
概述
元算法:处理问题是不是单靠一个算法的结果而是对多个算法综合考虑
一、基于数据集多重抽样的分类器
元算法/集成方法:将不同的分类器进行组合
1.1、bagging:基于数据随机重抽样的分类器构建方法
自举汇聚法(bagging)方法
- 构建数据集:假设原始数据集有T个数据,我们要利用该数据集构建S个新数据集,具体的构建方法为:每一次构建都是从原始数据集中有放回的抽样T次(因此可能出现相同的样本),重复S次构建。
- 学习分类器:基于这S个数据集的每一个去训练一个基学习器
- 将S个基学习器进行组合
- 在对预测输出进行结合时,Bagging通常对分类任务使用简单投票法,对回归任务使用简单平均法。
特点是串行训练,每一个新分类器都是根据已训练处的分类器的性能来进行训练
1.2、boosting
boosting方式每次使用的是全部的样本,每轮训练改变样本的权重。
bagging和boosting的主要区别为:
- 样本选择:bagging是在原始样本集上有放回的选取样本,从原始集中选出的各轮训练集之间是独立的;而boosting是每一轮的训练样本不变,都是同一个,但是样本的权重发生改变,该轮分类错误的样本权重增大,正确的样本权重减少
- 权重:bagging各个样本的权重一样,boosting各个样本的权重不断变化
- 预测函数:bagging各个弱分类器的权重是相等的,boosting各个分类器的权重是不等的
- 并行与串行:bagging各个弱分类器可以并行生成,而boosting不行,必须串行
二、训练算法:基于错误提升分类器的性能
Adaboost的基本流程
训练集中的每一个样本都有一个权重来象征其重要程度,初始时权重都相等,而在第一个弱分类器训练之后计算错误率,并且重新训练该弱分类器,而此时权重将会调整,那些错误分类的样本权重会增加,正确分类的样本权重会降低
具体的计算与更新流程可以查看《统计学习方法》中的Adaboost章节
三、基于单层决策树构建弱分类器
单层决策树:就是根据一个特征然后直接进行分类,相当于只有一个根节点
简单数据绘图
def loadSimpData():
dataMat = matrix([[1.0,2.1],
[2.0,1.1],
[1.3,1.0],
[1.0,1.0],
[2.0,1.0]])
classLabels = [1.0,1.0,-1.0,-1.0,1.0]
return dataMat,classLabels
fig = plt.figure()
ax = fig.add_subplot(111)
dataMat,classLabels = Adaboost.loadSimpData()
ax.scatter(dataMat[[0,1,4],0].tolist(),dataMat[[0,1,4],1].tolist(),marker='o')
ax.scatter(dataMat[2:4,0].tolist(),dataMat[2:4,1].tolist(),marker='s')
plt.show()
书中绘图好像有错误,并没有坐标为[1.5,1.6]左右的1类坐标点
那么从上述图像中我们可以得知如果要用一个单层决策树,从某一个特征(即某一个x)选择一个值(用一个与对应坐标轴垂直的曲线)来划分两种类别是无法实现的
构建单层决策树代码
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
retArray = ones((shape(dataMatrix)[0], 1))
if threshIneq == 'lt':
retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
# 所有样本的第dimen维度的数据取出来,然后如果元素是小于就为1否则为0
# 通过一个01序列去索引retArray这个数组,1的取出来命为-1
# 如果是It,那么大于临界值为正类,小于为负类
else:
retArray[dataMatrix[:, dimen] > threshVal] = -1.0
return retArray
def buildStump(dataArr, classLabels, D):
dataMartix = mat(dataArr)
labelMat = mat(classLabels).T
m, n = shape(dataMartix)
numSteps = 10.0 # 将区间除以10,修改可决定步长
bestStump = {} # 用于存放错误率最低时的各种信息
bestClassEst = mat(zeros((m, 1))) # 用于存放错误率最低时的分类结果
minError = inf # 存放最低错误率
for i in range(n):
rangeMin = dataMartix[:, i].min() # 当前特征对应的最小值
rangeMax = dataMartix[:, i].max() # 当前特征对应的最大值
stepSize = (rangeMax - rangeMin) / numSteps
for j in range(-1, int(numSteps) + 1): # 对每一个可能的间距都遍历
# 这里取-1还有到范围外(+1)是为了有两种情况,即全部归属于正类或者全部归属于负类
# 尽管对于样本来说,好像这样会重复,但是对于未知的新样本来说并不一样
for inequal in ['lt', 'gt']: # 对当前间距的左边和右边分别试一次+1-1和与一次-1+1
threshVal = (rangeMin + float(j) * stepSize)
predictedVals = stumpClassify(dataMartix, i, threshVal, inequal)
# 这里返回的是分类的结果
errArr = mat(ones((m, 1)))
errArr[predictedVals == labelMat] = 0
weightedError = D.T * errArr # 权重乘以错误的分类再求和
if weightedError < minError:
minError = weightedError
bestClassEst = predictedVals.copy() # 必须要copy()
# 否则进入下一次循环对predictedVals赋值的时候bestClassEst也就变了
bestStump['dim'] = i # 错误率最低时的分类维度
bestStump['thresh'] = threshVal # 错误率最低时的间隔值
bestStump['ineq'] = inequal # 错误率最小时+-1在哪一边
return bestStump, minError, bestClassEst
四、完成AdaBoost算法的实现
伪代码如下
对每次迭代:
- 利用前述构建单层决策树的代码找到当前最佳的单层决策树
- 将此单层决策树加入到单层决策树数组中
- 计算对应alpha
- 更新权重向量D
- 更新累计类别估计值
- 如果错误率为0就结束迭代
基于单层决策树的AdaBoost训练过程
def AdaBoostTrainDs(dataArr, classLabels, numIt=40):
weakClassArr = []
m = shape(dataArr)[0]
D = mat(ones((m, 1)) / m)
aggClassEst = mat(zeros((m, 1)))
for i in range(numIt):
bestStump, error, classEst = buildStump(dataArr, classLabels, D)
print("D:", D.T)
print("bestStump:", bestStump, "\terror:", error, "\tclassEst:", classEst)
if (error == 0):
alpha = float(0.5 * log((1.0 - error) / 1e-16))
else:
alpha = float(0.5 * log((1.0 - error) / error))
# 这里如果直接max(error,1e-16)会报错,因此np.max不支持浮点数的比较,python2就没问题
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
print("alpha:", alpha)
print("classEst:", classEst.T)
expon = multiply(-1 * alpha * mat(classLabels).T, classEst)
# 如果分类结果相同,两个矩阵对应元素相乘为1,再前面一个负一,那就和更新公式对应了
D = multiply(D, exp(expon))
D = D / D.sum()
aggClassEst += alpha * classEst
# AdaBoost就是每一个分类器的分类结果(正负1)乘上权重然后加起来再去sign
print("aggClassEst: ", aggClassEst)
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1)))
errorRate = aggErrors.sum() / m
print("total error:", errorRate, "\n")
if errorRate == 0.0: break
return weakClassArr
五、测试算法:基于AdaBoost的分类
训练好模型后,输入样本即可输出类别的函数:
#用来预测新样本的函数
def AdaClassify(dataToClass, classifierArr):
# 第一个参数是我们要用来进行分类的样本
# 第二个参数是我们已经训练好的模型
dataMatrix = mat(dataToClass)
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'])
# 将每一个模型的各个参数代入然后计算
aggClassEst += classifierArr[i]['alpha'] * classEst
# 将每一个模型的预测结果乘以对应权重后求和
print(aggClassEst)
return sign(aggClassEst)
七、非均衡分类问题
核心问题就是:样本正类但被误判为负类,以及样本负类但被误判为正类,在之前我们都认为是相同的代价,就是错误次数+1,但是在现实生活中这种问题而产生的误判代价是不一样的。
2、基于代价函数的分类器决策控制
可以考虑设置其两者产生误判的代价不同,例如:
预测结果 | 预测结果 | ||
---|---|---|---|
+1 | -1 | ||
真实结果 | +1 | -5 | 1 |
真实结果 | -1 | 50 | 0 |
那么在学习的时候就会更倾向于将误分类代价大的样本正确分类
八、处理非均衡问题的数据抽样方法
非均衡的数据即正负类样本数目的差距很大,主要方法是对训练数据进行改造,可以通过欠抽样和过抽样实现。
- 欠抽样:用于数据量更大的那一类,从其他随机抽取部分样本然后剔除掉
- 过抽样:用于数据量更小的那一类,从其他随机抽取部分样本然后进行复制
那么在学习的时候就会更倾向于将误分类代价大的样本正确分类
八、处理非均衡问题的数据抽样方法
非均衡的数据即正负类样本数目的差距很大,主要方法是对训练数据进行改造,可以通过欠抽样和过抽样实现。
- 欠抽样:用于数据量更大的那一类,从其他随机抽取部分样本然后剔除掉
- 过抽样:用于数据量更小的那一类,从其他随机抽取部分样本然后进行复制