我们一起学习了AdaBoost算法的原理,今天我们在python3的环境下,根据原理,自己写一段代码来实现AdaBoost算法。
01 构造单层决策树
逻辑:
遍历特征的每个步长:
计算每次迭代的weightedError
遍历步长的每个阈值对比方式(less than/greater than):
遍历数据集的每个特征:
认为weightedError最小的点(特征,阈值,方式)是最佳决策点,以此构建一棵决策树桩(stump)
根据这样的逻辑,写出如下单层决策树生成函数。
#通过阈值对数据分类+1 -1#dimen为dataMat的列索引值,即特征位置;threshIneq为阈值对比方式,大于或小于def stumpClassify(dataMatrix,dimen,threshVal,threshIneq): retArray=ones((shape(dataMatrix)[0],1))#注意,有两个() #阈值的模式,将小于某一阈值的特征归类为-1 if threshIneq=='lt':#less than retArray[dataMatrix[:,dimen]<=threshVal]=-1.0 #将大于某一阈值的特征归类为-1 else:#greater than retArray[dataMatrix[:,dimen]>threshVal]=-1.0 return retArray#单层决策树生成函数#D为各样本权重,shape=(m,1);label为样本标签,shape=(1,m)def buildStump(dataArr,classLabels,D):#将数据集和标签列表转为矩阵形式 dataMatrix=mat(dataArr);labelMat=mat(classLabels).T m,n=shape(dataMatrix) #步长或区间总数 最优决策树信息 最优单层决策树预测结果 numSteps=10.0;bestStump={};bestClasEst=mat(zeros((m,1))) #注意,有两个() #最小错误率初始化为+∞ minError=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) #初始化错误向量 errArr=mat(ones((m,1))) #将错误向量中分类正确项置0 errArr[predictedVals==labelMat]=0 #计算"加权"的错误率 weigthedError=D.T*errArr #print ("分割特征为第{0}个,分割阈值为{1},分割方式为{2},weight error为{3}"\ # .format(i+1,threshVal,threshIneq,weightedErr)) if weigthedError<minError: minError=weigthedError bestClasEst=predictedVals.copy() bestStump['dim']=i bestStump['thresh']=threshVal bestStump['ineq']=inequal #返回最佳单层决策树相关信息的字典,最小错误率,决策树预测输出结果 return bestStump,minError,bestClasEst
测试一下
data=mat([[ 1. , 2.1],[ 2. , 1.1],[ 1.3, 1. ],[ 1. , 1. ],[ 2. , 1. ]]) label=[1.0,1.0,-1.0,-1.0,1.0];D=mat(ones((5,1))/5) buildStump(array(data),array(label),D)
得到如下结果,表示最佳分类特征为第1个特征,分割阈值为1.3,分割方式为"将<阈值的样本归为-1类":
02 利用AdaBoost提升单层决策树效果
逻辑
将D带入buildStump,训练最佳决策树,得到本次迭代的最佳分类bestClass
计算汇集的分类结果aggClass(即,将每次最佳决策树分类结果bestClass*alpha相加)
根据当前的汇集分类错误率e,计算alpha,并更新样本权值D
直到达到循环次数上限或错误率e=0为止
样本初始权重D=1/m
进入循环:(设置循环次数上限or错误率e=0为止)
输出弱分类器集合(bestStump的集合)
根据这样的逻辑,给出如下函数。
#label为样本标签,shape=(1,m)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) alpha = float(0.5*log((1.0-error)/max(error,1e-16))) #max(error,1e-16)防止0误差的计算溢出 bestStump["alpha"]=alpha weakClassArr.append(bestStump) #print ("D:",D.T,"\n","predClass:",classEst.T) #为下一次迭代更新D(很关键,矩阵运算容易写错!) expon=multiply(-1*alpha*mat(classLabels).T,classEst) #shape(5,1)*shape(5,1)对应元素相乘,得到各样本的-alpha*yi*G(xi) D=multiply(D,exp(expon)) #shape(5,1)*shape(5,1)对应元素相乘, D=D/D.sum() #得到各样本更新后的wi #计算汇集的分类结果aggClassEst(即,将每次最佳决策树分类结果bestClass*alpha相加) 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) if errorRate==0.0: break return weakClassArr,errorRate,aggClassEst
测试一下
data=mat([[ 1. , 2.1],[ 2. , 1.1],[ 1.3, 1. ],[ 1. , 1. ],[ 2. , 1. ]]) label=[1.0,1.0,-1.0,-1.0,1.0];D=mat(ones((5,1))/5) adaBoostTrainDS(array(data),array(label))
输出结果如下,当叠加到第三个弱分类器时,分类错误率降为了0:
03 使用adaboost+单层决策树模型分类数据
刚才我们写好了模型,可以利用训练集结合我们的代码来训练若干个弱分类器了,当得到弱分类器集合之后,我们就可以利用这些弱分类器对测试集数据进行分类了。
分类原理是,将测试集特征带入各个弱分类器中,得到一系列预测值,然后将这些 预测值 X 对应弱分类器的alpha,再求和,就可以得到这些弱分类器对测试集样本的累加预测值了
def adaClassify(datToClass,classifierArr): dataMatrix=mat(datToClass) 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',aggClassEst) #通过sign函数根据结果大于或小于0预测出+1或-1 return sign(aggClassEst)
04 使用自定义AdaBoost预测疝病马是否存活
步骤:
读取数据、处理数据
使用adaBoostTrainDS训练弱分类器
使用adaClassify预测疝病马是否存活
def horseColic(numWeakClassifier): #读取训练集、测试集 dataArr,labelArr=loadDataSet(r'D:\DM\python\data\MLiA_SourceCode\machinelearninginaction\Ch07\horseColicTraining2.txt') dataTest,labelTest=loadDataSet(r'D:\DM\python\data\MLiA_SourceCode\machinelearninginaction\Ch07\horseColicTest2.txt') #训练弱分类器 classifierArr,trainErrorRate=adaBoostTrainDS(dataArr,labelArr,numWeakClassifier) #用训练好的弱分类器分类测试样本 predictLabels=adaClassify(dataTest,classifierArr) #计算分类准确率 errArr=mat(ones((len(labelTest),1))) errArr[predictLabels==mat(labelTest).T]=0 testErrorRate=errArr.sum()/len(labelTest) return numWeakClassifier,trainErrorRate,testErrorRate
测试一下,下面设置了训练不同个数的弱分类器
for i in [1,10,100,500,1000]: numWeakClassifier,trainErrorRate,testErrorRate=horseColic(i) print("=".center(70,"=")) print("弱分类器个数为{0}时,训练集分类错误率为{1}%,测试集分类错误率为{2}%"\ .format(i,round(trainErrorRate*100,2),round(testErrorRate*100,2)))print("=".center(70,"="))
我们来看看效果,
可以看到,并不是弱分类器越多越好,太多容易过拟合,使得测试集准确率较低,太少容易欠拟合,使得训练集准确率较低,因此adaboost的弱分类器个数是一个需要斟酌的参数
ROC曲线
最后,我们来看一下本次预测的ROC曲线,看看我们训练的这个分类器是不是一个“好医生”。
可以看到,本次训练的模型,AUC达到了0.895,图中表现为红色曲线,可以说这个模型是一个比较好的医生了,诊断结果还是比较靠谱的,只要选取合适的阈值,如A点,可以实现较低的漏诊率(1-y轴)和误诊率(x轴)。
05 总结
本文根据AdaBoost原理,给出基于python3的自定义函数实现,然后用一组疝病马的数据测试了算法。
06 参考
《机器学习实战》 Peter Harrington Chapter7
《统计学习方法》 李航 Chapter8
作者:邓莎
链接:https://www.jianshu.com/p/ef16be5b66f4
共同学习,写下你的评论
评论加载中...
作者其他优质文章