一、基于单层决策树构建弱分类器
单层决策树(decision stump, 也称决策树桩)是一种简单的决策树。前面我们已经介绍了决策树的工作原理,接下来将构建一个单层决策树,而它仅基于单个特征来做决策。由于这棵树只有一次分裂过程,因此它实际上就是一个树桩。
numpy中mat()和array的区别:
-
mat()函数与array()函数生成矩阵所需的数据格式有区别
mat()函数中数据可以为字符串以分号(;)分割,或者为列表形式以逗号(,)分割。而array()函数中数据只能为后者形式。
-
得到的结果都是:
-
矩阵性质的差异
matrix()和 array ()函数都可以通过对象后面加上 .T 得到其转置。但是matrix()还可以在后面加 .H 得到共轭矩阵, 加 .I 得到逆矩阵, array()就不可以。
-
mat()函数与array()函数生成的矩阵计算方式不同
-
(1) mat()函数中矩阵的乘积可以使用(星号) * 或 .dot()函数,其结果相同。而矩阵对应位置元素相乘需调用numpy.multiply()函数。
(2) array()函数中矩阵的乘积只能使用 .dot()函数。而星号乘 (*)则表示矩阵对应位置元素相乘,与numpy.multiply()函数结果相同。
具体可参考:https://blog.csdn.net/lfj742346066/article/details/77880668
https://blog.csdn.net/Dulpee/article/details/80833940
https://blog.csdn.net/IqqIqqIqqIqq/article/details/79820678
https://know.baidu.com/wenda/question/info?qid=841ad94abb57986f52d0348fe7184dd9efbeb47
https://www.cnblogs.com/litian0605/p/5269659.html
https://blog.csdn.net/u012328159/article/details/51613262
https://www.cnblogs.com/kevincong/p/7840382.html
https://blog.csdn.net/c406495762/article/details/78212124
https://www.cnblogs.com/kevincong/p/7840382.html
AdaBoost的一般流程如下所示:
(1)收集数据
(2)准备数据:依赖于所用的基分类器的类型,这里的是单层决策树,即树桩,该类型决策树可以处理任何类型的数据。
(3)分析数据
(4)训练算法:利用提供的数据集训练分类器
(5)测试算法:利用提供的测试数据集计算分类的错误率
(6)使用算法:算法的相关推广,满足实际的需要
接下来,具体阐述adaBoost分类算法
1 训练算法:基于错误提升分类器的性能
上面所述的基分类器,或者说弱分类器,意味着分类器的性能不会太好,可能要比随机猜测要好一些,一般而言,在二类分类情况下,弱分类器的分类错误率达到甚至超过50%,显然也只是比随机猜测略好。但是,强分类器的分类错误率相对而言就要小很多,adaBoost算法就是易于这些弱分类器的组合最终来完成分类预测的。
adaBoost的运行过程:训练数据的每一个样本,并赋予其一个权重,这些权值构成权重向量D,维度等于数据集样本个数。开始时,这些权重都是相等的,首先在训练数据集上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器,但是在第二次训练时,将会根据分类器的错误率,对数据集中样本的各个权重进行调整,分类正确的样本的权重降低,而分类错的样本权重则上升,但这些权重的总和保持不变为1.
并且,最终的分类器会基于这些训练的弱分类器的分类错误率,分配不同的决定系数alpha,错误率低的分类器获得更高的决定系数,从而在对数据进行预测时起关键作用。alpha的计算根据错误率得来:
alpha=0.5*ln(1-ε/max(ε,1e-16))
其中,ε=为正确分类的样本数目/样本总数,max(ε,1e-16)是为了防止错误率为而造成分母为0的情况发生
计算出alpha之后,就可以对权重向量进行更新了,使得分类错误的样本获得更高的权重,而分类正确的样本获得更低的权重。D的计算公式如下:
其中,h_t(x_i) = y_i表示对第i个样本训练正确,不等于则表示分类错误。Z_t是一个归一化因子:
这个公式我们可以继续化简,将两个公式进行合并,化简如下:
当我们更新完各个样本的权重之后,就可以进行下一次的迭代训练。adaBoost算法会不断重复训练和调整权重,直至达到迭代次数,或者训练错误率为0。
(1) 建立数据,用于后面的训练
def loadsimdata():
"""
创建单层决策树的数据集
Parameters:
Returns:
dataMat - 数据矩阵
classLabels - 数据标签
"""
datmat = np.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
(2)单层决策树生成函数
def stumpclassify(datamatrix, dimen, threshval, threshineq):
'''
:param datamatrix: 输入待分类的数据
:param dimen: 输入数据的某个特征
:param threshval: 设定的阈值
:param threshineq: 阈值比较
:return: 返回分类的结果
'''
retarray = np.ones((np.shape(datamatrix)[0], 1)) # 先默认分类都为1
if threshineq == 'lt': # 这个是为了找到最优的决策,因此两种情况都讨论了,即大于阈值和小于阈值
retarray[datamatrix[:, dimen] <= threshval] = -1.0 # 当数据小于阈值时为-1,因为默认为1了,为了准确率,需要考虑大于阈值的情况
else:
retarray[datamatrix[:, dimen] > threshval] = -1.0 # 如果考虑大于阈值的情况则也是为-1,这里大家可能会有疑问,这是两种情况,调用这个函数的
# 函数需要知道错误率最小的决策及阈值,因此他把两种情况都考虑了,即每次前进一步阈值都会更新,每次更新都计算大这个阈值或者小于这个阈值的情况
return retarray
函数stumpClassify()是通过阀值比较对数据进行分类的,所有在阀值一边的数据会分到类别-1,而在另一边的数据分到类+1。该函数可以通过数组过滤器来实现,首先将返回数组的全部元素设置为1,然后将所有不满足不等式要求的元素设置为-1,可以基于数据集的任一元素进行比较,同时也可将不等号在大于,小于之间切换。
(3) 找到数据集上最佳的单层决策树
def buildStump(dataArr,classLabels,D):
""" 找到数据集上最佳的单层决策树
Parameters:
dataArr - 数据矩阵
classLabels - 数据标签
D - 样本权重
Returns:
bestStump - 最佳单层决策树信息
minError - 最小误差
bestClasEst - 最佳的分类结果
"""
datamatrix = np.matrix(dataarr) # 把数据转换为矩阵数据
labelsmat = np.mat(classlabels).T # 把标签数据转换为矩阵
m, n = np.shape(datamatrix) # 得到数据的维度即m行n列
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): # 上面说计算是在numsteps以内找到最优的,因此这个循环是步数
for inequal in ['lt', 'gt']: # 遍历大于或者小于两种阈值过滤模式,lt= less than , gt = great than
threshval = (rangemin + float(j)*stepsize) # #阈值计算公式:最小值+j(-1<=j<=numSteps+1)*步长
predictedvals = stumpclassify(datamatrix, i, threshval, inequal) # #选定阈值后,调用阈值过滤函数分类预测
errarr = np.mat(np.ones((m, 1))) # #初始化错误向量
errarr[predictedvals == labelsmat] = 0 # 如果相同和真正类别相同则为0,则剩下为1则为分类错误的
weightederror = D.T*errarr # 保留分错的数据, 分类正确的数据直接为0
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#返回最佳单层决策树相关信息的字典,最小错误率,决策树预测输出结果
(4)基于单层决策树的完整Adaboost代码
def adaBoostTrainDS(dataArr,classLabels,numIt=40):
"""
:param dataArr: 数据矩阵
:param classLabels: 标签向量
:param numIt: 迭代次数
:return:
"""
#弱分类器相关信息列表
weakClassArr=[]
#获取数据集行数
m=shape(dataArr)[0]
#初始化权重向量的每一项值相等
D=mat(ones((m,1))/m)
#累计估计值向量
aggClassEst=mat((m,1))
#循环迭代次数
for i in range(numIt):
#根据当前数据集,标签及权重建立最佳单层决策树
bestStump,error,classEst=buildStump(dataArr,classLabels,D)
#打印权重向量
print("D:",D.T)
#求单层决策树的系数alpha
alpha=float(0.5*log((1.0-error)/(max(error,1e-16))))
#存储决策树的系数alpha到字典
bestStump['alpha']=alpha
#将该决策树存入列表
weakClassArr.append(bestStump)
#打印决策树的预测结果
print("classEst:",classEst.T)
#预测正确为exp(-alpha),预测错误为exp(alpha)
#即增大分类错误样本的权重,减少分类正确的数据点权重
expon=multiply(-1*alpha*mat(classLabels).T,classEst)
#更新权值向量
D=multiply(D,exp(expon))
D=D/D.sum()
#累加当前单层决策树的加权预测值
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,"\n")
#错误率为0.0退出循环
if errorRate==0.0:break
#返回弱分类器的组合列表
return weakClassArr
对于上面的代码,需要说明的有一下几点:
(1)上面的输入除了数据集和标签之外,还有用户自己指定的迭代次数,用户可以根据自己的成本需要和实际情况,设定合适的迭代次数,构建出需要的弱分类器数量。
(2)权重向量D包含了当前单层决策树分类器下,各个数据集样本的权重,一开始它们的值都相等。但是,经过分类器分类之后,会根据分类的权重加权错误率对这些权重进行修改,修改的方向为,提高分类错误样本的权重,减少分类正确的样本的权重。
(3)分类器系数alpha,是另外一个非常重要的参数,它在最终的分类器组合决策分类结果的过程中,起到了非常重要的作用,如果某个弱分类器的分类错误率更低,那么根据错误率计算出来的分类器系数将更高,这样,这些分类错误率更低的分类器在最终的分类决策中,会起到更加重要的作用。
(4)上述代码的训练过程是以达到迭代的用户指定的迭代次数或者训练错误率达到要求而跳出循环。而最终的分类器决策结果,会通过sign函数,将结果指定为+1或者-1
4 测试算法
(5)那么有了训练好的分类器,是不是要测试一下呢,毕竟训练错误率针对的是已知的数据,我们需要在分类器未知的数据上进行测试,看看分类效果。上面的训练代码会帮我们保存每个弱分类器的重要信息,比如分类器系数,分类器的最优特征,特征阈值等。有了这些重要的信息,我们拿到之后,就可以对测试数据进行预测分类了
https://www.cnblogs.com/zy230530/p/6909288.html
复制代码
#测试adaBoost,adaBoost分类函数
#@datToClass:测试数据点
#@classifierArr:构建好的最终分类器
def adaClassify(datToClass,classifierArr):
#构建数据向量或矩阵
dataMatrix=mat(datToClass)
#获取矩阵行数
m=shape(dataMatrix)[0]
#初始化最终分类器
aggClassEst=mat(zeros((m,1)))
#遍历分类器列表中的每一个弱分类器
for i in range(len(classifierArr)):
#每一个弱分类器对测试数据进行预测分类
classEst=stumpClassify(dataMat,classifierArr[i]['dim'],\
classifierArr[i]['thresh'],
classifierArr[i]['ineq'])
#对各个分类器的预测结果进行加权累加
aggClassEst+=classifierArr[i]['alpha']*classEst
print('aggClassEst',aggClassEst)
#通过sign函数根据结果大于或小于0预测出+1或-1
return sign(aggClassEst)
从结果看来,不拿发现,随着迭代次数的增加,分类结果是逐渐越强的。
三,实例:难数据集上应用adaBoost
第四章的logistic回归时用到了预测马疝病是否死亡的数据集。这里,我们再次利用该存在30%数据缺失的数据集来进行adaBoost算法测试,比较其与logistic回归分类器的分类错误率。
首先,从文件中加载数据集,转变成我们想要的数据格式,先看下面自适应数据加载函数代码:
#自适应加载数据
def loadDataSet(filename):
#创建数据集矩阵,标签向量
dataMat=[];labelMat=[]
#获取特征数目(包括最后一类标签)
#readline():读取文件的一行
#readlines:读取整个文件所有行
numFeat=len(open(filename).readline().split('\t'))
#打开文件
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
这一步得到的是分割之后的列表,列表里是字符串,下一步lineArr.append(float(curLine[i]))里float把字符串转换成浮点型。
与之前的加载数据代码不同的是,该函数可以自动检测出数据样本的特征数目。好了,来看最终的测试代码函数:
#训练和测试分类器
def classify():
#利用训练集训练分类器
datArr,labelArr=loadDataSet('horseColicTraining2.txt')
#得到训练好的分类器
classifierArray=adaBoostTrainDS(datArr,labelArr,10)
#利用测试集测试分类器的分类效果
testArr,testLabelArr=loadDataSet('horseClicTest2.txt')
prediction=adaClassify(testArr,classifierArray)
#输出错误率
num=shape(mat(labelArr))[1]
errArr=mat(ones((num,1)))
error=errArr[prediction!=mat(testLabelArr).T].sum()
print("the errorRate is: %.2f",errorRate=float(error)/float((num)))
基于上面的adaBoost分类器训练和测试代码,得到了下面的不同弱分类器数目情况下的AdaBoost测试和分类错误率。
观察商标的数据我们发现:
(1)随着分类器数目的增加,adaBoost分类器的训练错误率不断的减少,而测试错误率则是经历先减少到最小值,再逐渐增大的过程。显然,这就是所说的过拟合。因此,对于这种情况,我们应该采取相应的措施,比如采取交叉验证的方法,在训练分类器时,设定一个验证集合,不断测试验证集的分类错误率,当发现训练集错误率减少的同时,验证集的错误率较之上一次结果上升了,就停止训练。或者其他比较实用的模拟退火方法,基因遗传方法等。
(2)前面的第四章的logistic回归分类器对该数据集的分类错误率是35%,显然adaBoost分类器取得了更好的分类效果。
(3)有文献表明,对于表现好的数据集,AdaBoost的测试误差率会随着迭代次数的增加而逐渐稳定在某一个值附近,而不会出现上表中的先减小后上升的情况。显然,这里用到的数据集不能称为"表现好"的数据集,比较该数据集存在30%的数据缺失。在第四章的logistic回归中,我们讲这些确实的数据设置为0,显然这在logistic回归算法中是合适,这样不会对分类结果造成影响。但是,在adaBoost算法中依然这样设置,其合理性还有待证明,所以,有必要可以将这些缺失的数据值由0变成该特征相类似的数据,或者该特征数据的平均值,再来进行adaBoost算法训练,看看得到的结果会不会有所提升?
四,总结
adaBoost是boosting方法中最流行的一种算法。它是以弱分类器作为基础分类器,输入数据之后,通过加权向量进行加权,;在每一轮的迭代过程中都会基于弱分类器的加权错误率,更新权重向量,从而进行下一次迭代。并且会在每一轮迭代中计算出该弱分类器的系数,该系数的大小将决定该弱分类器在最终预测分类中的重要程度。显然,这两点的结合是adaBoost算法的优势所在。
优点:泛化错误率低,容易实现,可以应用在大部分分类器上,无参数调整
缺点:对离散数据点敏感