本博客记录《机器学习实战》(MachineLearningInAction)的学习过程,包括算法介绍和python实现。
AdaBoost算法
Adaboost,即Adaptive Boosting(自适应增强),是一种迭代算法,其核心思想是针对同一个训练集训练不同的分类器(弱分类器),然后把这些弱分类器组合起来,构成一个更强的分类器(强分类器)。
算法原理
算法训练出的每个分类器都有一个权重,最终分类时根据权重大小来决定每个分类器的话语权,即对最终分类结果的影响力。这个权重的大小则根据每个分类器的错误率来决定,错误率小的分类器拥有更大的权重。需要注意的是,这里提到的错误率并不是错分样本占总样本的比例,而是依据每个样本的权值算出的(没错,每个样本也有权值,而且每个分类器中的样本权值还不一样)。
算法步骤
1.给所有m个训练样本赋予相同的权重:
wi=1m
2.使用这组数据及权重训练弱分类器,使得错误率尽量小,其中错误率是每个错分样本权重的总和。
3.计算训练出的弱分类器的权值,其中e代表错误率:
4.更新样本权值,其中 yi 为样本类别, f(xi) 为分类器的输出结果,两者都是1或-1,Z是归一化参数,使得最终的权值总和为1,从式中也可以看出错分样本的权值会增加,正确分类样本的权值会降低:
5.使用新的样本权值继续训练新的分类器,以此往复,直到达到要求。
分类方式
最终训练出了n个弱分类器,并且得到了每个分类器对应的权值
α
,分类时计算每个分类器分类结果的加权和即可:
弱分类器
书中的弱分类器采用的是单层决策树(Stump)分类器,分类器选择一个阈值以及某一个特征,该特征大于阈值的样本分为一类,小于阈值的样本分为另一类。在训练时需要遍历所有特征,并且取很多的阈值,来计算出使错误率最小的一组参数。
python实现
# 使用stump分类器分类数据,dimen为特征下标,threshVal为阈值
# threshIneq的两个值表示小于阈值的样本是分类为正集或负集
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
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):
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
m, n = np.shape(dataMatrix)
numSteps = 10.0
# 单层决策树用字典存放
bestStump = {}
bestClasEst = np.mat(np.zeros((m, 1)))
minError = np.inf # init error sum to +infinity
# 遍历每个特征
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)
# 计算错误率
errArr = np.mat(np.ones((m, 1)))
errArr[predictedVals == labelMat] = 0
weightedError = D.T * errArr
# 保存使错误率最小的决策树参数
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):
# 存放所有的弱分类器
weakClassArr = []
m = np.shape(dataArr)[0]
# 初始化权值向量D
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)
# 计算alpha(需要防止错误率为0时除以0的问题)
alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16)))
# 保存弱分类器
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
# 计算新的数据权值
expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst)
D = np.multiply(D, np.exp(expon))
D = D / D.sum()
# 用当前训练出的所有弱分类器进行分类得出结果
aggClassEst += alpha * classEst
# 计算错误率,达到0之后停止迭代
aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m, 1)))
errorRate = aggErrors.sum() / m
print("total error: ", 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
return np.sign(aggClassEst)
# 测试函数,数据集从文件读取
def AdaboostTest():
dataMatTrain, classLabelTrain = loadDataSet("horseColicTraining2.txt")
dataMatTest, classLabelTest = loadDataSet("horseColicTest2.txt")
weakClassArr, aggClassEst = adaBoostTrainDS(dataMatTrain, classLabelTrain, numIt=50)
classEst = adaClassify(dataMatTest, weakClassArr)
mTest = np.shape(classLabelTest)[0]
error = np.ones((mTest, 1))
error[classEst != np.mat(classLabelTest).T] = 0
errorRate = sum(error) / mTest
print("total error: ", 1 - errorRate)
总结
Adaboost是一种提升(boosting)方法,把若干个弱分类器组合成一个强分类器,迭代的过程中,不断提高错分样本的权值,在总体上又使错误率低的样本具有更大的话语权,使得整体分类效果很好。