一、算法原理
1.算法的基本思想
Adaboost是adaptive boosting的簡寫,是自適應的boosting算法,基本思想爲:在前一個弱分類器的基礎上,增加誤分類樣本的權重,這些誤分類的樣本在下一個弱分類器那裏被重點關注,依次迭代進行,直到到達預定的足夠小的錯誤率或最大的迭代次數爲止。大概流程描述如下:
- 初始化訓練數據的權值分佈,假設樣本個數爲 ,則每個樣本的權值爲 ;
- 在初始訓練集上訓練出一個弱分類器,根據分類結果,被誤分類的樣本權重增加,正確分類的樣本的權重將減少,然後將權值更新過的訓練數據集用於訓練下一個弱分類器,不斷進行迭代。
- 將各個弱分類器進行組合形成強分類器。各個弱分類器也有自己的權重,加大分類誤差率小的弱分類器的權重,使其在最終的分類函數中有更大的決定權,同理,減少分類誤差率高的弱分類器的權重,使其在最終的分類函數中起着較小的決定權。
2.算法的流程
假設一個二分類的訓練數據集
其中,,標記。
(1)初始化訓練數據集的權值分佈
(2)訓練每個弱分類器,假設有 個弱分類器,對於
(a)使用具有權值分佈的訓練數據集學習,得到基本分類器
(b)計算基分類器在訓練數據集上的分類誤差率
可以看出來,每個基分類器的誤差率其實就是誤分類樣本的權值之和。
©計算基分類器 的係數
這裏的對數是自然對數。
我們知道,基分類器要滿足 “好而不同”,而 “好” 體現在每個基分類器的性能要比隨機猜測要好一些,
當 時,,則;
當 時,,則;
給分類性能好的分類器較大的權重,使其在最終的分類函數中起到更大的決定作用。
(d)更新訓練集的權值分佈,
其中,
是一個規範化因子
觀察訓練樣本的權值更新公式,我們可以發現,
當 ,即樣本點 被正確分類時,有
當 ,即樣本點被誤分類,有
一句話總結,增加被誤分類樣本的權重,減少已被正確分類的樣本的權重。
(3)構建基本分類器的線性組合
最終的分類器函數爲,
二、實戰分析
1.基於單層決策樹構建弱分類器
加載數據以及可視化數據:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
def loadSimData():
dataMat = np.matrix([[1.0,2.1],
[2.0,1.1],
[1.3,1.0],
[1.0,1.0],
[2.0,1.0]])
classlabels = [1.0,1.0,-1.0,-1.0,1.0]
return dataMat,classlabels
dataMat,classlabels = loadSimData()
def plotDecisionStu(dataMat,classlabes):
n = np.shape(dataMat)[0]
xcord1=[];ycord1=[]
xcord2=[];ycord2=[]
for i in range(n):
if classlabels[i]==1:
xcord1.append(dataMat[i,0]);ycord1.append(dataMat[i,1])
else:
xcord2.append(dataMat[i,0]);ycord2.append(dataMat[i,1])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1,ycord1,s=30,c='red',marker = 's')
ax.scatter(xcord2,ycord2,s=30,c='blue',marker = '8')
mpl.rcParams['font.sans-serif'] = ['simhei']
plt.title('單層決策樹測試數據')
plt.show()
print(plotDecisionStu(dataMat,classlabels))
結果:
單層決策樹生成函數:
僞代碼:
將最小錯誤率minError設爲
對數據集中的每一個特徵(第一層循環):
對每個步長(第二層 循環):
對每個不等號(第三層循環):
建立一棵單層決策樹並用加權數據集進行訓練
如果錯誤率低於minError,則將當前的單層決策樹設爲最佳的單層決策樹
返回最佳的單層決策樹
# 單層決策樹生成函數
#單層決策樹的閾值過濾函數
# 參數說明:dataMatrix-訓練數據集
# dimen-某一個特徵的索引
# threshVal-閾值
# threshIneq-不等式符號
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):
# 初始化每一個樣本點的類標籤爲1
reArray = np.ones((np.shape(dataMatrix)[0],1))
# 判斷不等式的符號:lt-表示小於或等於閾值
# gt-表示大於閾值
if threshIneq == 'lt':
# 如果是lt,表示特徵值小於或等於閾值,類標籤爲-1
reArray[dataMatrix[:,dimen] <= threshVal] = -1.0
else:
# 否則,表示特徵值大於閾值,類標籤爲-1
reArray[dataMatrix[:,dimen] > threshVal] = -1.0
return reArray
# 返回數據集上的最佳決策樹
def buildStump(dataMatrix,labelMat,D):
# 返回訓練集的大小
m,n = np.shape(dataMatrix)
# 步數,最佳決策樹信息,最優單層決策樹的預測結果
numSteps = 10.0;bestStemp = {};bestClassEst = 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):
# 遍歷每一個不等式
for inequal in ['lt','gt']:
# 計算閾值
threshVal = (rangeMin + float(j)*StepSize)
# 返回預測值
predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal)
# 初始化誤分類的樣本矩陣
errArr = np.mat(np.ones((m,1)))
# 將誤分類矩陣中預測值與真實值相等的位置賦值爲0
errArr[labelMat.T == predictedVals] = 0
# 每個基分類器的誤差率其實就是誤分類樣本的權值之和
weightedError = D.T*errArr
#print("split: dim % d,thresh %.2f,thresh inequal %s,the weighted error is # %.3f"%(i,threshVal,inequal,weightedError))
# 更新最小誤差率
if weightedError < minError:
minError = weightedError
# 將閾值添加到字典的threshVal鍵
bestStemp['threshVal'] = threshVal
# 將不等式添加到字典的threshIneq
bestStemp['threshIneq']= inequal
# 將預測值賦值給bestClassEst
bestClassEst = predictedVals.copy()
# 最佳的分類特徵
bestStemp['dim'] = i
return bestStemp,bestClassEst,minError
D = np.mat(np.ones((5,1))/5)
# bestStemp, bestClassEst, minError = buildStump(dataMat,classlabels,D)
print(buildStump(dataMat,classlabels,D))
結果:
2.完整的Adaboost算法
**上面的程序只是生成了單個基分類器,這裏,我們要生成多個弱分類器來構建完整的Adaboost算法:**
整個代碼實現的僞代碼如下:
對每次迭代
利用buildStump()函數找到最佳的單層決策樹
將單層決策樹加入到單層決策樹組
計算alpha
計算新的權值向量
更新累計的類別估計值
如果錯誤率等於0,則退出循環
# 完整的Adaboost算法
def adaBoostTrainDs(dataArr,classLabels,numIt=40):
# 弱分類器的相關信息表
weakClassArr = []
# 返回樣本點的個數
m = np.shape(dataArr)[0]
# 初始化樣本點的權重向量
D = np.mat(np.ones((m,1))/m)
# 集成的弱分類器的分類矩陣
aggClassEst = np.mat(np.zeros((m,1)))
# 開始迭代
for i in range(numIt):
# 得到最佳的單層決策樹
bestStemp,ClassEst,Error = buildStump(dataArr,classlabels,D)
print('D:',D.T)
# 計算每一個單層決策樹的權重係數
alpha = float(0.5*np.log((1-Error)/max(Error,1e-16)))
# 將每一個弱分類器的係數alpha添加到字典 bestStemp
bestStemp['alpha'] = alpha
# 將該決策樹的信息存儲起來
weakClassArr.append(bestStemp)
print('ClassEst:',ClassEst.T)
# 更新每一個樣本的權值向量
expon = np.multiply(-alpha*np.mat(classlabels).T,ClassEst)
D = np.multiply(D,np.exp(expon))
D = D/D.sum()
# 累加當前決策樹的加權預測值
aggClassEst += alpha*ClassEst
print('aggClassEst:',aggClassEst)
# 返回一個 m×1 的矩陣,預測正確的位置爲0,誤分類的位置爲1
aggErrors = np.multiply(np.sign(aggClassEst)!=np.mat(classlabels).T,np.ones((m,1)))
# 計算誤分類率
errorRate = aggErrors.sum()/m
print('total error',errorRate)
# 如果誤分類率爲0,則退出循環
if errorRate == 0.0:
break
return weakClassArr
print(adaBoostTrainDs(dataMat,classlabels,numIt=40))
結果:
D: [[0.2 0.2 0.2 0.2 0.2]]
ClassEst: [[-1. 1. -1. -1. 1.]]
aggClassEst: [[-0.69314718]
[ 0.69314718]
[-0.69314718]
[-0.69314718]
[ 0.69314718]]
total error 0.2
D: [[0.5 0.125 0.125 0.125 0.125]]
ClassEst: [[ 1. 1. -1. -1. -1.]]
aggClassEst: [[ 0.27980789]
[ 1.66610226]
[-1.66610226]
[-1.66610226]
[-0.27980789]]
total error 0.2
D: [[0.28571429 0.07142857 0.07142857 0.07142857 0.5 ]]
ClassEst: [[1. 1. 1. 1. 1.]]
aggClassEst: [[ 1.17568763]
[ 2.56198199]
[-0.77022252]
[-0.77022252]
[ 0.61607184]]
total error 0.0
[{'threshVal': 1.3, 'threshIneq': 'lt', 'dim': 0, 'alpha': 0.6931471805599453}, {'threshVal': 1.0, 'threshIneq': 'lt', 'dim': 1, 'alpha': 0.9729550745276565}, {'threshVal': 0.9, 'threshIneq': 'lt', 'dim': 0, 'alpha': 0.8958797346140273}]
其中,最後一行詳細記錄了每一個基學習器採用的閾值、不等式、特徵、權重。
3.基於Adaboost的分類
上一個代碼是在訓練集上訓練弱分類器,這裏,我們把訓練好的弱分類器抽離出來,在小數據集上進行測試,
# 利用訓練好的基學習器對數據進行分類
def adaClassifiy(dataToClass,classifierArr):
dataMatrix = np.mat(dataToClass)
m = np.shape(dataMatrix)[0]
aggClassEst = np.mat(np.zeros((m,1)))
for i in range(len(classifierArr)):
classEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],classifierArr[i]['threshVal'],
classifierArr[i]['threshIneq'])
aggClassEst += classifierArr[i]['alpha']*classEst
AggClassEst = np.sign(aggClassEst)
return AggClassEst
classifierArr = adaBoostTrainDs(dataMat,classlabels,numIt=40)
AggClassEst = adaClassifiy([[5,5],[0,0]],classifierArr)
print(AggClassEst)
結果:
D: [[0.2 0.2 0.2 0.2 0.2]]
ClassEst: [[-1. 1. -1. -1. 1.]]
aggClassEst: [[-0.69314718]
[ 0.69314718]
[-0.69314718]
[-0.69314718]
[ 0.69314718]]
total error 0.2
D: [[0.5 0.125 0.125 0.125 0.125]]
ClassEst: [[ 1. 1. -1. -1. -1.]]
aggClassEst: [[ 0.27980789]
[ 1.66610226]
[-1.66610226]
[-1.66610226]
[-0.27980789]]
total error 0.2
D: [[0.28571429 0.07142857 0.07142857 0.07142857 0.5 ]]
ClassEst: [[1. 1. 1. 1. 1.]]
aggClassEst: [[ 1.17568763]
[ 2.56198199]
[-0.77022252]
[-0.77022252]
[ 0.61607184]]
total error 0.0
[[ 1.]
[-1.]]
參考資料:
1.《統計學習方法》李航
2.《機器學習實戰》Peter Harrington
3.matplotlib.markers:https://matplotlib.org/api/markers_api.html
4.matplotlib繪圖之中文標題、座標軸標籤亂碼問題:https://blog.csdn.net/anmo1221/article/details/77746528
5.常用數學符號的 LaTeX 表示方法:http://mohu.org/info/symbols/symbols.htm