上兩篇說了決策樹到集成學習的大概,這節我們通過adaboost來具體了解一下集成學習的簡單做法。
集成學習有bagging和boosting兩種不同的思路,bagging的代表是隨機森林,boosting比較基礎的adaboost,高級一點有GBDT,在這里我也說下我理解的這兩個做法的核心區別:
隨機森林的bagging是采用有放回抽樣得到n個訓練集,每個訓練集都會有重復的樣本,每個訓練集數據都一樣,然后對每個訓練集生成一個決策樹,這樣生成的每個決策樹都是利用了整個樣本集的一部分,也就說每棵決策樹只是學習了大部分,然后決策的時候綜合每棵決策樹的評分,最終得出一個總的評分
boosting的adaboost每次訓練的時候用的是同一個數據集,但是前一棵決策樹分錯的樣本在后面的權重會升高,相當於說,后面的決策樹利用了前面決策樹學習的結果,不斷的優化這個結果,也就是說,后面的決策樹恰恰擅長的是前面決策樹不擅長(分錯)的樣本,相當於說,boosting的每棵決策樹擅長的“領域”不一樣,並且在擅長的領域都很厲害
而對比bagging,bagging中的每一棵樹都是同樣普通的決策樹,相當於每棵決策樹擅長的“領域”區分不大,也沒有很擅長。
下面我們看下adaboost是如何工作的吧,這次我們使用更簡單的例子。
這是二維平面的上的兩個點,紅色是正樣本,綠色是負樣本,如果使用強分類器,例如深度大於1的決策樹,很簡單就可以區分開了,或者使用邏輯回歸,計算一下,就可以輕松得到一條直線把這兩個類別的點區分開了,我們這里主要是先學習adaboost是如何把弱分類器組裝成強大的強分類器以及boosting的學習效果
類別和之前不一樣,這里的正負樣本的類似是1,-1,原因是預測結果的分界線不一樣,決策樹是沒有對類別做任何的操作,adaboost設計到多棵樹的權重相加,使用0作為正負樣本的分界線會更好
def loadSimpData(): datMat = 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
選擇弱分類器:深度為1的決策樹無疑是很弱的分類器,深度為1的決策樹我們一般稱為決策樹墩,決策樹墩在二維平面上是一條平行坐標軸的直線
對於我們的數據集,決策樹墩是沒有辦法正確划分的,除非能學習出直線之間的組合關系
決策樹墩做什么事情:決策樹墩做的事情是根據特征的特征值給出判斷錯誤的列表(個數)
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):#just classify the data retArray = ones((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 = 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))) errArr[predictedVals == labelMat] = 0 weightedError = D.T*errArr #每個樣本的權重*每個樣本划分對錯(對的為零,錯的為一,所以這里計算的是錯誤的權重) # 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
下面是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 alpha = float(0.5*log((1.0-error)/max(error,1e-16)))#計算α值 bestStump['alpha'] = alpha #print "classEst: ",classEst.T expon = multiply(-1*alpha*mat(classLabels).T,classEst) #計算新的權重 D = multiply(D,exp(expon)) #權重歸一化 D = D/D.sum() #每個樣本到當前的評分 aggClassEst += alpha*classEst #得到分類錯誤的列表(分對為零,分錯為1,計算內積,得到分錯的個數) aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1))) errorRate = aggErrors.sum()/m print "total error: ",errorRate bestStump['error_rate']=errorRate weakClassArr.append(bestStump) #記錄每一棵最優的決策樹墩 if errorRate == 0.0: break return weakClassArr,aggClassEst
下面我們看每一棵決策樹墩的分類的效果吧:樣本類別如下:
[1.0, 1.0, -1.0, -1.0, 1.0]
每個樣本的權重如下
[ 0.2 0.2 0.2 0.2 0.2]
第一棵決策樹,X1小於等於1.3為負樣本,這棵樹的權重為0.69,錯誤率0.2(分錯了一個),每個樣本被預測的結果如下:
[-0.69314718 0.69314718 -0.69314718 -0.69314718 0.69314718]
第一次權重調整:可以發現第一個樣本(被分錯的)權重升高了
[ 0.5 0.125 0.125 0.125 0.125]
兩棵決策樹:第二棵決策樹X2小於等於1.0為負樣本,權重0.97,錯誤率還是0.2,樣本預測總評分如下
[ 0.27980789 1.66610226 -1.66610226 -1.66610226 -0.27980789]
第二次調整權重,可以看到最后一次被分錯的權重最高,前一次被分錯的稍小,一直被分對的概率最小
[ 0.28571429 0.07142857 0.07142857 0.07142857 0.5 ]
看下三棵決策樹墩的效果:X1小於等於0.9的為負樣本,權重0.9,錯誤率0,所有樣本評分如下:可以發現雖然每個正樣本的評分都大於0,但是有些評分已經高達2.5了,不過我們也發現,最后的評分跟一直分對或者分錯沒什么關系
[ 1.17568763 2.56198199 -0.77022252 -0.77022252 0.61607184]
在這個簡單的數據中,錯誤率可以降到0,但是不是所有的數據集都可以達到錯誤率為0的,比如我們前面的100個點的實驗
真正的原因在於數據集在特征維度是否是超平面可分的,如果可以,adaboost理論上可以把錯誤率降到0
希望到這里大家可以理解adaboost,理解集成學習。