Adaboost是一種集成學習的方法,當採用基於簡單模型的單個分類器對樣本進行分類的效果不理想時,人們希望能夠通過構建並整合多個分類器來提高最終的分類性能。
Boosting方法並不是簡單地對多個分類器的輸出進行投票決策,而是通過一種迭代過程對分類器的輸入和輸出進行加權處理。在不同應用中可以採用不同類型的弱分類器,在每次迭代過程中,根據分類的情況對各個樣本進行加權,而不僅僅是簡單的重採樣。
弱分類器:人們常稱不理想的單個分類器稱爲弱分類器
1 Adaboost算法流程
Boosting算法的訓練過程:
目前,最爲廣泛使用的Boosting方法是提出的AdaBoost算法。這裏對這個算法做一個簡單的介紹。
設給定個訓練樣本,用表示個弱分類器在樣本上的輸出,通過AdaBoost算法構造這個分類器並進行決策的具體過程如下:
1. 初始化訓練樣本的權重
2. 對,重複使用以下過程:
(1)利用加權後的訓練樣本構造分類器。(注意構造弱分類器的具體算法可以不同,例如使用線性分類器和決策樹等。)
(2)計算樣本用加權後的分類錯誤率,並令
(3)更新訓練數據集的權重分佈:
,是規範化因子。
新的權值分佈。
3. 構造基本分類器的線性組合
對於待分類樣本,分類器的輸出爲
這裏補充說明幾點:
(1)表示設置迭代的次數,也是AdaBoost算法構造弱分類器的最大個數。
(2)錯誤率小於一個最小值時停止循環,或者是==0停止循環也行。在中作爲分母,設計程序時需要避開的情況。
2 最佳的單層決策樹
接下來,只訓練一個弱分類器。
(1)數據集:
{1,2.1,1
1.5,1.6,1
1.3,1,-1
1,1,-1
2,1,1}
(2)圖
從圖中發現,訓練一個單層決策樹的弱分類器,無論怎麼劃分,都存在一個點誤分類。
(3)模型測試
# 找出數據集上最佳的單層決策樹
def get_Stump(xMat, yMat, D):
"""
參數說明:
xMat:特徵矩陣
yMat:標籤矩陣
D:樣本權重
返回:
bestStump:最佳單層決策樹信息
minE:最小誤差
bestClass:最佳的分類結果
"""
m, n = xMat.shape #m爲樣本的個數,n爲特徵數
Step = 10 # 初始化一個步數
bestStump = {} # 用字典形式來存儲樹樁信息
bestClass = np.mat(np.zeros((m, 1))) # 初始化分類結果爲1
minE = np.inf # 最小誤差初始化爲正無窮大
for i in range(n): # 遍歷所有特徵值
min = xMat[:, i].min() # 找到特徵的最小值
max = xMat[:, i].max() # 找到特徵的最大值
stepSize = (max - min)/ Step # 計算步長
for j in range(-1, int(Step)+1):
for S in ['lt', 'gt']: # 大於和小於的情況,均遍歷
Q = (min + j * stepSize) # 計算閾值
re = Classify0(xMat, i, Q, S) # 計算分類結果
err = np.mat(np.ones((m,1))) # 初始化誤差矩陣
err[re == yMat] = 0 # 分類正確的,賦值爲0
eca = D.T * err # 計算誤差
if eca < minE: # 找到誤差最小的分類方式
minE = eca
bestClass = re.copy()
bestStump['特徵值'] = i
bestStump['閾值'] = Q
bestStump['標誌'] = S
return bestStump, minE, bestClass
(4)訓練結果
bestStump:{'特徵值': 0, '閾值': 1.3, '標誌': 'lt'}
minE:[[0.2]]
bestClass:[[-1.]
[ 1.]
[-1.]
[-1.]
[ 1.]]
3 基於單層決策樹的Adaboost集成訓練
單層決策樹程序設計的傳入參數有
xMat:特徵矩陣
yMat:標籤矩陣
D:樣本權重
在上述講解的算法步驟中,第二步的第三小步,更新訓練數據集的權重分佈,將更新的權值作爲參數D重新傳入到單層決策樹中,返回誤差minE通過公式得到的作爲新增分類器的權重。
Adaboost集成訓練的程序:
# 基於單層決策樹的Adaboost訓練過程
def Ada_train(xMat, yMat, maxC=40):
"""
函數功能:基於單層決策樹的Adaboost訓練過程
參數說明:
xMat:特徵矩陣
yMat:標籤矩陣
maxC:最大迭代次數
返回:
weakClass:弱分類器信息
aggClass:類別估值(更改標籤估值)
"""
weakClass = []
m = xMat.shape[0]
D = np.mat(np.ones((m, 1))/m) # 初始化權重
aggClass = np.mat(np.zeros((m,1)))
for i in range(maxC):
Stump, error, bestClass = get_Stump(xMat, yMat, D) # 構造單層決策樹
alpha = float(0.5*np.log((1-error)/max(error, 1e-6))) # 計算弱分類器權重alpha
Stump['alpha'] = np.round(alpha, 2)
weakClass.append(Stump) # 存儲單層決策樹
expon = np.multiply(-1*alpha*yMat, bestClass) # 計算e的指數項
D = np.multiply(D, np.exp(expon))
D = D / D.sum() # 更新權重
aggClass += alpha * bestClass # 計算類別估值(更改標籤估值)
aggErr = np.multiply(np.sign(aggClass) != yMat, np.ones((m, 1))) # 統計誤分類樣本數
errRate = aggErr.sum()/m
print("total error: ", errRate)
if errRate == 0:
break
return weakClass, aggClass
4 病馬訓練集的測試
完整程序:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2020/6/30 15:46
"""
1.實現單層決策樹,找出數據集上最佳的單層決策樹
2.實現多層的分類(adaboost)
"""
import numpy as np
import matplotlib.pyplot as plt
# 加載文件
def loadDataSet(path = 'horseColicTraining2.txt'):
data = list()
labels = list()
with open(path) as f:
lines = f.readlines()
for line in lines:
line = line.rstrip().split('\t')
lineArr = []
for i in range(len(line)-1):
lineArr.append(float(line[i]))
data.append(lineArr)
labels.append(float(line[-1]))
xMat = np.array(data)
yMat = np.array(labels).reshape(-1, 1)
return xMat, yMat
# 按照閾值分類結果
def Classify0(xMat, i, Q, S):
"""
xMat:數據矩陣
Q:閾值
S:標誌
"""
re = np.ones((xMat.shape[0], 1))
if S == 'lt':
re[xMat[:, i] <= Q] = -1 # 如果小於閾值,則賦值爲-1
else:
re[xMat[:, i] > Q] = 1 # 如果大於閾值,則賦值爲1
return re
# 找出數據集上最佳的單層決策樹
def get_Stump(xMat, yMat, D):
"""
參數說明:
xMat:特徵矩陣
yMat:標籤矩陣
D:樣本權重
返回:
bestStump:最佳單層決策樹信息
minE:最小誤差
bestClass:最佳的分類結果
"""
m, n = xMat.shape #m爲樣本的個數,n爲特徵數
Step = 10 # 初始化一個步數
bestStump = {} # 用字典形式來存儲樹樁信息
bestClass = np.mat(np.zeros((m, 1))) # 初始化分類結果爲1
minE = np.inf # 最小誤差初始化爲正無窮大
for i in range(n): # 遍歷所有特徵值
min = xMat[:, i].min() # 找到特徵的最小值
max = xMat[:, i].max() # 找到特徵的最大值
stepSize = (max - min)/ Step # 計算步長
for j in range(-1, int(Step)+1):
for S in ['lt', 'gt']: # 大於和小於的情況,均遍歷
Q = (min + j * stepSize) # 計算閾值
re = Classify0(xMat, i, Q, S) # 計算分類結果
err = np.mat(np.ones((m,1))) # 初始化誤差矩陣
err[re == yMat] = 0 # 分類正確的,賦值爲0
eca = D.T * err # 計算誤差
if eca < minE: # 找到誤差最小的分類方式
minE = eca
bestClass = re.copy()
bestStump['特徵值'] = i
bestStump['閾值'] = Q
bestStump['標誌'] = S
return bestStump, minE, bestClass
# 基於單層決策樹的Adaboost訓練過程
def Ada_train(xMat, yMat, maxC=40):
"""
函數功能:基於單層決策樹的Adaboost訓練過程
參數說明:
xMat:特徵矩陣
yMat:標籤矩陣
maxC:最大迭代次數
返回:
weakClass:弱分類器信息
aggClass:類別估值(更改標籤估值)
"""
weakClass = []
m = xMat.shape[0]
D = np.mat(np.ones((m, 1))/m) # 初始化權重
aggClass = np.mat(np.zeros((m,1)))
for i in range(maxC):
Stump, error, bestClass = get_Stump(xMat, yMat, D) # 構造單層決策樹
alpha = float(0.5*np.log((1-error)/max(error, 1e-6))) # 計算弱分類器權重alpha
Stump['alpha'] = np.round(alpha, 2)
weakClass.append(Stump) # 存儲單層決策樹
expon = np.multiply(-1*alpha*yMat, bestClass) # 計算e的指數項
D = np.multiply(D, np.exp(expon))
D = D / D.sum() # 更新權重
aggClass += alpha * bestClass # 計算類別估值(更改標籤估值)
aggErr = np.multiply(np.sign(aggClass) != yMat, np.ones((m, 1))) # 統計誤分類樣本數
errRate = aggErr.sum()/m
print("total error: ", errRate)
if errRate == 0:
break
return weakClass, aggClass
# 開始對待預測的數據進行分類
def AdaClassify(xMat, weakClass):
m = xMat.shape[0] # 待分類數據集的長度
aggClass = np.mat(np.zeros((m, 1)))
for i in range(len(weakClass)): # 遍歷所有分類器進行分類
classEst = Classify0(xMat,
weakClass[i]['特徵值'],
weakClass[i]['閾值'],
weakClass[i]['標誌'],
)
aggClass += classEst * weakClass[i]['alpha']
return np.sign(aggClass)
# ROC圖像
def plotROC(predStrengths, classLabels):
"""
輸入:
predStrengths : Adaboost預測的結果(行)
classLabels : 原本訓練數據的標籤(列)
"""
cur = (1.0, 1.0) # 光標
ySum = 0.0 # 變量來計算AUC
numPosClas = sum(np.array(classLabels) == 1.0)
yStep = 1 / float(numPosClas) # 向上的步長
xStep = 1 / float(len(classLabels) - numPosClas) # 向右的步長
sortedIndicies = predStrengths.argsort() # 得到排序索引,它是反向的
fig = plt.figure()
fig.clf()
ax = plt.subplot(111)
# 循環所有的值,在每個點上畫一條線段
for index in sortedIndicies.tolist()[0]:
if classLabels[index] == 1.0:
delX = 0
delY = yStep
else:
delX = xStep
delY = 0
ySum += cur[1]
# draw line from cur to (cur[0]-delX,cur[1]-delY)
ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c='b') # 逐漸加入曲線變化的一條直線
cur = (cur[0] - delX, cur[1] - delY) # 重新更新cur的起始點
ax.plot([0, 1], [0, 1], 'b--')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC curve for AdaBoost horse colic detection system')
ax.axis([0, 1, 0, 1])
plt.show()
print("the Area Under the Curve is: ", ySum * xStep)
if __name__ == '__main__':
xMat, yMat = loadDataSet(path='horseColicTraining2.txt') # 訓練數據
# 測試單層決策樹
m = xMat.shape[0]
D = np.mat(np.ones((m, 1))/m)
bestStump, minE, bestClass = get_Stump(xMat, yMat, D)
# 測試單層決策樹的Adaboost訓練過程
weakClass, aggClass = Ada_train(xMat, yMat, maxC=10) # 返回弱分類器的集合,以及弱分類的標籤值
print('分類器的個數:', len(weakClass))
testArr, testLabelArr = loadDataSet(path='horseColicTest2.txt') # 測試數據
pre = AdaClassify(testArr, weakClass) # 返回預測值
# 計算準確度
errArr = np.mat(np.ones((len(pre), 1))) # 一共有m個預測樣本
cnt = errArr[pre != testLabelArr].sum()
print('誤分類點在總體預測樣本中的比例爲:', cnt / len(pre))
# 繪畫出ROC圖
plotROC(aggClass.T, yMat)
結果(1):
分類器爲:
[{'特徵值': 2, '閾值': 36.72, '標誌': 'lt', 'alpha': 0.27},
{'特徵值': 0, '閾值': 1.0, '標誌': 'lt', 'alpha': 0.15},
{'特徵值': 18, '閾值': 0.0, '標誌': 'lt', 'alpha': 0.14},
{'特徵值': 18, '閾值': 53.400000000000006, '標誌': 'lt', 'alpha': 0.16},
{'特徵值': 0, '閾值': 0.9, '標誌': 'lt', 'alpha': 0.06},
{'特徵值': 12, '閾值': 1.2, '標誌': 'lt', 'alpha': 0.05},
{'特徵值': 0, '閾值': 1.0, '標誌': 'lt', 'alpha': 0.04},
{'特徵值': 18, '閾值': 53.400000000000006, '標誌': 'lt', 'alpha': 0.03},
{'特徵值': 0, '閾值': 0.9, '標誌': 'lt', 'alpha': 0.04},
{'特徵值': 4, '閾值': 57.599999999999994, '標誌': 'lt', 'alpha': 0.04}]
結果(2):
ROC曲線圖爲:
結果(3):
誤分類點在總體預測樣本中的比例爲: 0.3283582089552239
the Area Under the Curve is: 0.6757359086266125
參考資料:
1 https://www.bilibili.com/video/BV1it411q7wy?from=search&seid=15656450157548541974
2 《統計學習方法》- 李航