當做重要決定時,大家可能都會考慮吸取多個專家而不只是一個人的意見。
這一生活問題映射到計算機世界就變成了元算法(meta-algorithm)或者集成方法(ensemble method)。這種集成可以是不同算法的集成,也可以是同一算法在不同設置下的集成,還可以是數(shù)據(jù)集不同部分分配給不同分類器之后的集成。AdaBoost就是一種最流行的元算法。
什么是AdaBoost
AdaBoost是adaptive boosting的縮寫,boosting是一種與bagging很類似的技術,將原始數(shù)據(jù)集選擇S次后得到S個新數(shù)據(jù)集,新數(shù)據(jù)集與原始數(shù)據(jù)集大小相等,每個數(shù)據(jù)集都是通過在原始數(shù)據(jù)集中隨機選擇一個樣本來替換得到的,這就意味著可以多次選擇同一個樣本。在S個數(shù)據(jù)集建好之后,將某個學習算法分別作用于每個數(shù)據(jù)集就得到了S個分類器,當我們要對新數(shù)據(jù)分類時,就可以用這S個分類器進行分類,選擇分類器投票結(jié)果最多的類別作為最后分類結(jié)果。boosting通過集中關注被已有分類器錯分的數(shù)據(jù)來獲得新的分類器,boosting給每個分類器的權(quán)重不相等,每個權(quán)重代表的是對應的分類器在上一輪迭代中的成功度,分類結(jié)果是基于所有分類器的加權(quán)求和得到的。
為什么要用AdaBoost(為什么要用弱分類器和多個實例來構(gòu)建一個強分類器)
等待答案中。。。
Adaboost算法流程是什么
1)給數(shù)據(jù)中的每一個樣本一個權(quán)重
2)訓練數(shù)據(jù)中的每一個樣本,得到第一個分類器
3)計算該分類器的錯誤率,根據(jù)錯誤率計算要給分類器分配的權(quán)重(注意這里是分類器的權(quán)重)
4)將第一個分類器分錯誤的樣本權(quán)重增加,分對的樣本權(quán)重減小(注意這里是樣本的權(quán)重)
5)然后再用新的樣本權(quán)重訓練數(shù)據(jù),得到新的分類器,到步驟3
6)直到步驟3中分類器錯誤率為0,或者到達迭代次數(shù)
7)將所有弱分類器加權(quán)求和,得到分類結(jié)果(注意是分類器權(quán)重)
解釋:
-
步驟3中,錯誤率的定義是:
錯誤率.png
分類器的權(quán)重計算公式是:
-
步驟4中,錯誤樣本權(quán)重更改公式為:
錯誤樣本權(quán)重更改公式.png
正確樣本權(quán)重更改公式為:
正確樣本權(quán)重更改公式.png
其中t指當前分類器,i指第i個樣本。
構(gòu)建基于單層決策樹的AdaBoost分類器
用一個很簡單的散點分類問題來實現(xiàn)AdaBoost分類器。
準備數(shù)據(jù)集
import numpy as np
def loadSimpData():
datMat = np.matrix([[1. , 2.1],
[1.5 , 1.6],
[1.3, 1. ],
[1. , 1. ],
[2. , 1. ]])
classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
return datMat, classLabels
datMat, classLabels = loadSimpData()
showScatter(datMat, classLabels)
showScatter
方法用來繪制散點圖,代碼如下:
import matplotlib.pyplot as plt
def showScatter(matrix, labels):
plt.figure(figsize=(8,6))
x1 = []; y1 = []; x2 = []; y2 = []
for i in range(len(labels)):
if labels[i] == 1.0:
x1.append(matrix[i, 0])
y1.append(matrix[i, 1])
else:
x2.append(matrix[i, 0])
y2.append(matrix[i, 1])
plt.scatter(x1, y1, marker='o',
color='green', alpha=0.7, label='1.0')
plt.scatter(x2, y2, marker='^',
color='red', alpha=0.7, label='-1.0')
plt.title('dataset')
plt.ylabel('variable Y')
plt.xlabel('Variable X')
plt.legend(loc='upper right')
plt.show()
所以要進行的分類如下圖所示:
結(jié)果調(diào)用方法(期望實現(xiàn)效果)
把它放在第二步,倒著寫程序,是借用了TDD的思路,這樣會更好理解,測試就不粘出來了,從結(jié)果出發(fā),一步一步驅(qū)動出算法代碼。
對于這個demo,我們希望輸入一個或多個點,告訴我們是1.0類(綠色原點)還是-1.0類(紅色三角)。
為了讓代碼更靈活,我們把弱分類器也作為參數(shù)傳入,則最后的調(diào)用為:
# [[5, 5], [0, 0]]是要分類的點
# classifierArr是弱分器數(shù)組
adaClassify ([[5, 5], [0, 0]], classifierArr)
期望的輸出是:
[[1.], [-1.]]
寫AdaBoost分類函數(shù)
從上一步讓我們驅(qū)動出adaClassify函數(shù),我們希望每一個弱分類器是一個字典,有最好的維度、所用的閾值、是大于閾值還是小于閾值為1類這些屬性,通過這些屬性能得到一個分類結(jié)果,再用分類結(jié)果乘以分類器的權(quán)重,進行累加,返回分類結(jié)果,下面寫它的實現(xiàn)
# datToClass:要分類的數(shù)據(jù)
# classifierArr:弱分類器數(shù)組
def adaClassify(datToClass, classifierArr):
dataMatrix = np.mat(datToClass)
m = np.shape(dataMatrix)[0]
aggClassEst = np.mat(np.zeros((m, 1))) # 為了滿足輸出期望,先用0列向量初始化輸出結(jié)果
for i in range(len(classifierArr)):
classEst = stumpClassify(dataMatrix, classifierArr[i]['dim'], classifierArr[i]['thresh'], classifierArr[i]['ineq']) # 得到一個弱分類器分類結(jié)果
aggClassEst += classifierArr[i]['alpha'] * classEst # 對應算法流程的步驟7,將弱分類器結(jié)果加權(quán)求和
# print(aggClassEst)
return np.sign(aggClassEst) #由于是二類問題,所以可以根據(jù)加權(quán)求和結(jié)果的正負情況得到期望的分類輸出
寫基于單層決策樹的AdaBoost函數(shù)
上一步可以驅(qū)動出stumpClassify函數(shù),需要傳入的參數(shù)為要分類的數(shù)據(jù)、維度、閾值、大于閾值是1類還是小于閾值是1類,希望它能給出一個分類結(jié)果,結(jié)果長這樣:[[1.],[1.],[-1.],...]。實現(xiàn)如下:
# dataMatirx:要分類的數(shù)據(jù)
# dimen:維度
# threshVal:閾值
# threshIneq:有兩種,‘lt’=lower than,‘gt’=greater than
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
retArray = np.ones((np.shape(dataMatrix)[0], 1))
if threshIneq == 'gt':
retArray[dataMatrix[:, dimen] <= threshVal] = -1.0 # 如果希望大于閾值的是1,則小于閾值的部分置為-1
else:
retArray[dataMatrix[:, dimen] > threshVal] = -1.0
return retArray
訓練分類器
接下來的工作就是訓練分類器,得到classifierArr弱分類器數(shù)組,作為參數(shù),穿給我們的最中調(diào)用函數(shù)。
回想AdaBoost分類器的步驟2-6,即每個分類器都是在上一次的基礎上更新權(quán)重,訓練數(shù)據(jù),權(quán)重更新方法是增加分類錯誤的樣本權(quán)重,減小分類正確的樣本權(quán)重。
我們希望告訴這個函數(shù)數(shù)據(jù)集,還有對應的標簽,我們設置的最多迭代次數(shù)后,能返回一個弱分類器數(shù)組。所以函數(shù)長下面這樣:
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
weakClassArr = []
m = np.shape(dataArr)[0]
D = np.mat(np.ones((m,1)) / m) # 初始化權(quán)重向量,給每個樣本相同的權(quán)重,[[1/m],[1/m],[1/m],...]
aggClassEst = np.mat(np.zeros((m,1))) # 初始化每個樣本的預估值為0
for i in range(numIt): # 遍歷迭代次數(shù)
bestStump, error, classEst = buildStump(dataArr, classLabels, D) # 構(gòu)建一棵單層決策樹,返回最好的樹,錯誤率和分類結(jié)果
alpha = float(0.5 * np.log((1.0 - error)/error)) #計算分類器權(quán)重
bestStump['alpha'] = alpha #將alpha值也加入最佳樹字典
weakClassArr.append(bestStump) # 將弱分類器加入數(shù)組
# print("classEst:", classEst.T)
# 更新權(quán)重向量D
expon = np.multiply(-1*alpha*np.mat(classLabels).T, classEst)
D = np.multiply(D, np.exp(expon))
D = D / D.sum()
# 累加錯誤率,直到錯誤率為0或者到達迭代次數(shù)
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
構(gòu)建單層決策樹分類器
通過上一步可以驅(qū)動出buildStump函數(shù)。我們希望輸入數(shù)據(jù)集和標簽以及由每個樣本的權(quán)重構(gòu)成的權(quán)重矩陣D,能得到最好的分類器的屬性,包含維度、閾值、大于閾值還是小于閾值是1.0類,還有分類器錯誤率,最好的分類器的長相。
# dataArr: 數(shù)據(jù)集
# classLabels:標簽
# D:由每個樣本的權(quán)重構(gòu)成的矩陣
def buildStump(dataArr, classLabels, D):
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T # 標簽轉(zhuǎn)成列向量
m, n = np.shape(dataMatrix) #m為數(shù)據(jù)個數(shù),n為每條數(shù)據(jù)含有的樣本數(shù)(也就是特征)
numSteps = 10.0
bestStump = {}
bestClasEst = np.mat(np.zeros((m, 1))) # 初始化最好的分類器為[[0],[0],[0],...]
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']: # 每個條件,大于閾值是1還是小于閾值是1
threshVal = (rangeMin + float(j) * stepSize) # 閾值設為最小值+第j個步長
print('i=%d, threshVal=%f, inequal=%s'%(i,threshVal,inequal))
predictedVals = stumpClassify(dataMatrix, i , threshVal, inequal) # 將dataMatrix的第i個特征inequal閾值的置為1,否則為-1
print(predictedVals)
errArr = np.mat(np.ones((m, 1)))
errArr[predictedVals == labelMat] = 0 # 預測對的置0
print(errArr)
weightedError = D.T * errArr
print("split: dim %d, threshold %.2f, threshold inequal: %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
上述過程就是,遍歷每個特征,在每個特征上找合理的分界點,每次移動一個步長,對每個步長,根據(jù)它的大于閾值是1.0還是小于閾值是1.0類得到分類情況,計算錯誤率,找錯誤率最小的一種情況返回。bestStump是一個字典,找到一個好的分類器,當然要包含所選擇的維度(映射到這個題目就是x軸還是y軸),分類的閾值是多少,大于還是小于閾值的是1.0類,所以把這些屬性放入了bestStump字典。
進行測試
到這里,就實現(xiàn)了,可以用一個或一組點進行測試,用第一步設想的調(diào)用方法:
# 分類(0,0)點
adaClassify([0,0], classifierArray)
輸出結(jié)果為:
可見,(0,0)點分類為-1類。
總結(jié)
AdaBoost簡單來講,就是多個弱分類器,可能基于單層決策樹,也可能基于其他算法,每一個弱分類器得到一個分類結(jié)果,根據(jù)它的錯誤率給這個分類器一個權(quán)重,還要更新樣本的權(quán)重,基于這個權(quán)重矩陣,再去訓練出一個弱分類器,依次循環(huán),直到錯誤率為0,就得到了一系列弱分類器,組成一個搶分類器,將這些弱分類器的結(jié)果加權(quán)求和,能得到一個較為準確的分類。
以上內(nèi)容來自822實驗室2017年4月23日17:30第一次知識分享活動:基于cascade的object detection。
我們的822,我們的青春
歡迎所有熱愛知識熱愛生活的朋友和822實驗室一起成長,吃喝玩樂,享受知識。