基於單層決策樹的Adaboost實踐
Adaboost 計算流程:
- 首先給定數據集,基於弱學習器計算分類誤差:
- 計算該學習器的權重:
- 更新權重分佈,分類錯誤的樣本會獲得更高的權重,從而使得其在下一次迭代時受到更多的關注
- 將所有的基學習器進行累加,合成最終的學習器:
- 流程圖:
- 基學習器爲單層決策樹
import numpy as np
def loadData():
dataMat = np.array([[0,1,3],[0,3,1],[1,2,2],[1,1,3],[1,2,3],[0,1,2],[1,1,2],[1,1,1],[1,3,1],[0,2,1]])
dataMat = np.reshape(dataMat,(-1,3))
labelMat = np.array([-1,-1,-1,-1,-1,-1,1,1,-1,-1])
return dataMat,labelMat
def stumpClassify(dataMat,dimen,threshold,threInfo):
"""根據閥值進行分類
Params:
dataMat: 數據集
dimen: 維度
threshold: 閥值
threInfo: less than or greater than 因爲要考慮兩種情況(將小於閥值的樣本歸爲1類還是歸爲-1類)
return:
retArr:根據要求的分類結果"""
m,n = dataMat.shape
retArr = np.ones((m,1)) #初始化分類
if threInfo == 'lt': #將小於閥值的樣本歸爲-1類
retArr[dataMat[:,dimen]<=threshold] = -1.0
else: #將小於閥值的樣本歸爲1類
retArr[dataMat[:,dimen]>threshold] = -1.0
return retArr
def buildStump(dataMat,labelMat,D):
"""建立單層決策樹
Params:
dataMat:數據集
labelMat:標籤集
D:權重分佈
return:
Minerror:最小誤差值
bestSump:存放閥值、維度、分類方向的字典
bestClasEst:最小誤差的分類"""
StepNum = 10.0 #有10個樣本首先將步數設置爲10
m,n = dataMat.shape
bestStump = {} #初始化一個字典
bestClasEst = np.zeros((m,1)) #初試化分類
Minerror = float('inf') #將Minerror設置爲無限大
for i in range(n): #每個維度都要遍歷
rangeMin = dataMat[:,i].min()
rangeMax = dataMat[:,i].max()
StepSize = (rangeMax-rangeMin)/(StepNum) #計算步長
for j in range(-1,int(StepNum)+1): #一個一個間隔
for inequal in ['lt','gt']: #計算lt、gt
threshold = (rangeMin+float(j)*StepSize)
retArr = stumpClassify(dataMat,i,threshold,inequal) #進行分類
retArr = np.mat(retArr)
errArr = np.mat(np.ones((m,1))) #初始化誤差
errArr[retArr == labelMat] = 0 #將正確的變爲0
weightError = D.T * errArr #計算誤差值
if weightError < Minerror: #獲取最小誤差的信息
Minerror = weightError
bestClasEst = retArr.copy()
bestStump['threshold'] = threshold
bestStump['dim']= i
bestStump['ineq'] = inequal
return Minerror,bestStump,bestClasEst
- adaboost訓練器
import numpy as np
def loadData():
dataMat = np.array([[0,1,3],[0,3,1],[1,2,2],[1,1,3],[1,2,3],[0,1,2],[1,1,2],[1,1,1],[1,3,1],[0,2,1]])
dataMat = np.reshape(dataMat,(-1,3))
labelMat = np.array([-1,-1,-1,-1,-1,-1,1,1,-1,-1])
return dataMat,labelMat
def stumpClassify(dataMat,dimen,threshold,threInfo):
"""根據閥值進行分類
Params:
dataMat: 數據集
dimen: 維度
threshold: 閥值
threInfo: less than or greater than 因爲要考慮兩種情況(將小於閥值的樣本歸爲1類還是歸爲-1類)
return:
retArr:根據要求的分類結果"""
m,n = dataMat.shape
retArr = np.ones((m,1)) #初始化分類
if threInfo == 'lt': #將小於閥值的樣本歸爲-1類
retArr[dataMat[:,dimen]<=threshold] = -1.0
else: #將小於閥值的樣本歸爲1類
retArr[dataMat[:,dimen]>threshold] = -1.0
return retArr
def buildStump(dataMat,labelMat,D):
"""建立單層決策樹
Params:
dataMat:數據集
labelMat:標籤集
D:權重分佈
return:
Minerror:最小誤差值
bestSump:存放閥值、維度、分類方向的字典
bestClasEst:最小誤差的分類"""
StepNum = 10.0 #有10個樣本首先將步數設置爲10
m,n = dataMat.shape
bestStump = {} #初始化一個字典
bestClasEst = np.zeros((m,1)) #初試化分類
Minerror = float('inf') #將Minerror設置爲無限大
for i in range(n): #每個維度都要遍歷
rangeMin = dataMat[:,i].min()
rangeMax = dataMat[:,i].max()
StepSize = (rangeMax-rangeMin)/(StepNum) #計算步長
for j in range(-1,int(StepNum)+1): #一個一個間隔
for inequal in ['lt','gt']: #計算lt、gt
threshold = (rangeMin+float(j)*StepSize)
retArr = stumpClassify(dataMat,i,threshold,inequal) #進行分類
retArr = np.mat(retArr)
errArr = np.mat(np.ones((m,1))) #初始化誤差
errArr[retArr == labelMat] = 0 #將正確的變爲0
weightError = D.T * errArr #計算誤差值
if weightError < Minerror: #獲取最小誤差的信息
Minerror = weightError
bestClasEst = retArr.copy()
bestStump['threshold'] = threshold
bestStump['dim']= i
bestStump['ineq'] = inequal
return Minerror,bestStump,bestClasEst
def Adaboost(dataMat,labelMat,Iter_num=40):
m,n = dataMat.shape
D = np.mat(np.ones((m,1)))/m #初始化權重分佈
labelMat = np.mat(labelMat).T
aggClass = np.mat(np.zeros((m,1)))
weakClass = [] #用來存放弱學習器的列表
for i in range(Iter_num): #進入迭代
error,Stump,Class = buildStump(dataMat,labelMat,D)
weakClass.append(Stump)
alpha = float((np.log((1-error)/max(error,1e-16)))/2) #根據公式求alpha
Stump['alpha'] = alpha
Class = np.mat(Class)
expon = - (np.multiply((alpha*labelMat),Class))
D = np.multiply(D , np.exp(expon))/(2*(np.sqrt(error*(1-error)))) #根據公式更新權值
aggClass+= alpha*Class #累加獲得最終學習器
aggF = np.sign(aggClass) #進行分類
Error = np.zeros(m)
Error[np.ravel(aggF) != np.ravel(labelMat)]=1
ErrorRate = float(Error.sum()/m) #計算誤差
if ErrorRate == 0.0: #如果誤差爲0即停止計算
break
return aggClass,Stump,weakClass
def AdaboostClassifier(dataMat,weakClass):
dataMat = np.reshape(dataMat,(-1,3))
m = dataMat.shape[0]
aggclass = np.zeros((m,1))
for i in range(len(weakClass)):
Class = stumpClassify(dataMat,weakClass[i]['dim'],weakClass[i]['threshold'],weakClass[i]['ineq'])
aggclass += Class*weakClass[i]['alpha']
aggF = np.sign(aggclass)
return aggF
if __name__ == '__main__':
dataMat,labelMat = loadData()
aggClass,Stump,weakClass = Adaboost(dataMat,labelMat,Iter_num=40)
Test = np.array([[0,1,1],[1,3,3]])
aggF = AdaboostClassifier(Test,weakClass)
print(aggF)
結果如下:
這兩個樣本的分類爲-1類。
SK-learn實現:
-
SK-learn的AdaBoostClassifier有五個參數
-
**base_estimator:**可選參數,默認爲DecisionTreeClassifier。理論上可以選擇任何一個分類或者回歸學習器,不過需要支持樣本權重。我們常用的一般是CART決策樹或者神經網絡MLP。默認是決策樹,即AdaBoostClassifier默認使用CART分類樹DecisionTreeClassifier,而AdaBoostRegressor默認使用CART迴歸樹DecisionTreeRegressor。另外有一個要注意的點是,如果我們選擇的AdaBoostClassifier算法是SAMME.R,則我們的弱分類學習器還需要支持概率預測,也就是在scikit-learn中弱分類學習器對應的預測方法除了predict還需要有predict_proba。
-
**algorithm:**可選參數,默認爲SAMME.R。scikit-learn實現了兩種Adaboost分類算法,SAMME和SAMME.R。兩者的主要區別是弱學習器權重的度量,SAMME使用對樣本集分類效果作爲弱學習器權重,而SAMME.R使用了對樣本集分類的預測概率大小來作爲弱學習器權重。由於SAMME.R使用了概率度量的連續值,迭代一般比SAMME快,因此AdaBoostClassifier的默認算法algorithm的值也是SAMME.R。我們一般使用默認的SAMME.R就夠了,但是要注意的是使用了SAMME.R, 則弱分類學習器參數base_estimator必須限制使用支持概率預測的分類器。SAMME算法則沒有這個限制。
-
**n_estimators:**整數型,可選參數,默認爲50。弱學習器的最大迭代次數,或者說最大的弱學習器的個數。一般來說n_estimators太小,容易欠擬合,n_estimators太大,又容易過擬合,一般選擇一個適中的數值。默認是50。在實際調參的過程中,我們常常將n_estimators和下面介紹的參數learning_rate一起考慮。
-
**learning_rate:**浮點型,可選參數,默認爲1.0。每個弱學習器的權重縮減係數,取值範圍爲0到1,對於同樣的訓練集擬合效果,較小的v意味着我們需要更多的弱學習器的迭代次數。通常我們用步長和迭代最大次數一起來決定算法的擬合效果。所以這兩個參數n_estimators和learning_rate要一起調參。一般來說,可以從一個小一點的v開始調參,默認是1。
-
**random_state:**整數型,可選參數,默認爲None。如果RandomState的實例,random_state是隨機數生成器; 如果None,則隨機數生成器是由np.random使用的RandomState實例。
-
代碼實戰:
import os
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
os.chdir('E:/Data/機器學習數據集/Machine-Learning-master/AdaBoost/')
def LoadDataSet(file_name):
dataMat = []
labelMat = []
with open(file_name) as f:
for each in f.readlines():
line = []
clean = each.strip().split('\t')
num_Feature = len(clean)
for i in range(num_Feature-1):
line.append(float(clean[i]))
dataMat.append(line)
labelMat.append(float(clean[-1]))
return dataMat,labelMat
if __name__ == '__main__':
Test_set,Test_label = LoadDataSet('horseColicTest2.txt')
Train_set,Train_label = LoadDataSet('horseColicTraining2.txt')
bdt = AdaBoostClassifier(DecisionTreeClassifier(max_depth=2),algorithm='SAMME',n_estimators=10)
bdt.fit(Train_set,Train_label)
prediction =bdt.predict(Train_set)
error = np.mat(np.ones((len(Train_set),1)))
print('訓練集錯誤率爲:{}'.format(float(error[prediction!=Train_label].sum()/len(Train_set)*100)))
prediction =bdt.predict(Test_set)
error = np.mat(np.ones((len(Test_set),1)))
print('測試集錯誤率爲:{}'.format(float(error[prediction!=Test_label].sum()/len(Test_set)*100)))
結果如下:
參考資料:
- Python3《機器學習實戰》學習筆記(十):提升分類器性能利器-AdaBoost
- 李航《統計學習方法》第二版