机器学习之AdaBoost

之前看的机器学习的分类算法所构建的分类器都是单分类器,就是一整个算法当中,只构建了一个分类器用于分类。单分类器算法构建简单,但是缺点也很大——就是独裁,这个分类器所分类的结果就是最终的结果,缺少多方的意见,因此,就希望分类算法能够实现多方意见机制,也就是多个分类器共同分类来决定其最终的分类结果。

这个多方意见机制其实就像专家咨询机制,每一个专家的权威不同,所给出的意见也就不同,其最中的结果,需要综合各方的意见以及可采信程度综合得出最终的意见结果;对于分类器而言,可信度就是该分类器的采信权重alpha,其计算公式为:

1
2
µ = 分错的样本个数 / 总样本个数
alpha = 1 / 2 * ln((1 - µ) / µ)

专家个数呢?其实就是同一种分类器,根据采信程度来区分成不同的分类器;但是,一个分类器用一个样本训练,无论训练多少次应该只能得到一个分类器模型才对,那是怎么得到这么多不同的采信程度不同的分类器?这个adaboost算法已经解决了。

AdaBoost

adaboost算法是解决上面问题的一种实现方案,adaboost算法就是一种生成多个置信程度不同的分类器,然后将这些分类起器分类结果进行加权求和得到最终的分类结果,而这个权重值,就是刚刚说的aplha值:1 / 2 * ln((1 - µ) / µ),µ = 分错的样本个数 / 总样本个数;那么分类器可以选择什么呢?可以是前面的朴素贝叶斯分类器,可以是决策树,可以是Logistic回归分类器等等等;在这里,用了最容易实现的单层决策树来作为adaboost的基分类器。

单层决策树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
"""
: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

利用上面的代码我们构建成了一个单层决策树,单层决策树的算法是在某个特征上取一个阈值并通过大小关系来实现二分类。

得到当前最好的单决策树

分类器的好坏判别标准就是分类的准确率,遍历所有特征,同时,在某特征下,遍历所有该特征的可能取值;就这样通过双重循环,不断得到不同的单层决策树,通过计算准确率以及和最大正确率(最小错误率)比较,取得单层决策树的最优树;同时,在python的字典对象中保存最优单层决策树的特征信息:{第几个特征; 该特征的取值;正确率;错误率}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def buildStump(dataArr, classLabels, D):
"""
:param dataArr:
:param classLabels:
:param D:
:return:
"""
dataMatrix = np.mat(dataArr);
labelMat = np.mat(classLabels).T
m, n = np.shape(dataMatrix)
numSteps = 10.0;
bestStump = {};
bestClassEst = 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 + np.float(j) * stepSize)
predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)
errArr = np.mat(np.ones((m, 1)))
errArr[predictedVals == labelMat] = 0
weightedError = D.T * errArr
print "split: dim %d, thresh %.2f, thresh ineqal: %s, the weights error is %.3f" % (i, threshVal, inequal, weightedError)
if weightedError < minError:
minError = weightedError
bestClassEst = predictedVals.copy()
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump, minError, bestClassEst

adaboost 模型训练

从上面已知最优决策树的获得方式,因此就可以通过训练模型,获得一个由最优单层决策树组成的list对象,最优单层决策树数组长度取决于最终模型训练的情况以及预初始化的数组长度;如果说模型训练的错误率小于等于给定的目标值,那么就结束模型训练,此时循环训练的轮数就是最优单层决策树数组的长度;如果训练轮数达到预初始值,那么直接结束循环,返回最优单层决策树数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
"""
模型训练,返回numIt个单决策树模型数组
:param dataArr:
: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 = np.float(0.5 * np.log((1.0 - error) / max(error, 1e-16)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
print "classEst:", classEst.T
expon = np.multiply(-1 * alpha * np.mat(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 "total error:", errorRate, '\n'
if errorRate == 0.0:
break
return weakClassArr

分类函数

根据上一步获得最优单层决策树数组,计算每个最优单层决策树的分类情况,乘上对应的权重值,累加之后得到aggClassEst,之后利用np.sign函数进行计算,将向量映射到[0, 1]空间,实现最后的二分类效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def adaClassify(datToClass, classifierArr):
"""[summary]

Arguments:
datToClass {[type]} -- [description]
classifierArr {[type]} -- [description]

Returns:
[type] -- [description]
"""

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
return np.sign(aggClassEst)