機器學習實戰(六)AdaBoost元演算法
目錄
學習完機器學習實戰的AdaBoost元演算法,簡單的做個筆記。文中部分描述屬於個人消化後的理解,僅供參考。
所有程式碼和資料可以訪問 我的 github
如果這篇文章對你有一點小小的幫助,請給個關注喔~我會非常開心的~
0. 前言
在分類任務中,我們通常可以不僅僅採用一個演算法,而是多個演算法模型結合使用,綜合每個弱分類器的結果,稱為整合方法(ensemble method)或元演算法(meta-algorithm)。
通常有以下兩種方法:
- bagging:自舉匯聚法(bootstrap aggregating),從原資料集中選擇若干次與原資料集大小相等的新資料集,每個資料的選擇是隨機的,即新資料集中,資料可能重複,原資料集中有些資料可能也不會出現在新資料集中。
- boosting:分類器是序列訓練的,新分類器根據已有的分類器效能進行訓練,集中關注被已有分類器分錯的那些資料。
bagging 中,分類器的權重是相等的,boosting 中,分類器的權重不相等。
本篇中,主要介紹 boosting 中的 AdaBoost(adaptive boosting)。
- 優點:泛化錯誤率低,易編碼
- 缺點:對離群點敏感
- 適用資料型別:數值型和標稱型資料
1. AdaBoost
AdaBoost 演算法流程可描述如下:
- 對每個訓練樣本設定相等的權重,即資料集構成權重向量
- 訓練一個加權錯誤率最低的最佳弱分類器
- 根據加權錯誤率,計算弱分類器的分類器權重
- 根據權重向量 和弱分類器的分類器權重 ,更新權重向量
- 繼續訓練下一個弱分類器......
- 直到弱分類器達到指定數量,或者弱分類器的訓練錯誤率為
- 預測時,將資料通過每個弱分類器,將其結果加權求和
初始時,權重向量 被設定為每個樣本相同,即 。
弱分類器的加權錯誤率 定義為錯誤樣本的權重相加。
弱分類器的分類器權重 定義如下:
更新權重向量 時,若樣本分類正確,則權重下降,若樣本分類錯誤,則權重上升:
注:AdaBoost 每次訓練的弱分類器,會集中關注那些被分類錯誤的樣本。
2. 單層決策樹
單層決策樹(decision stump,也稱決策樹樁),是僅僅基於單個特徵進行分類的弱分類器。
在本篇中,使用單層決策樹作為弱分類器,使用三重迴圈構建最佳的單層決策樹:
- 第一層迴圈:遍歷每一個特徵
- 第二層迴圈:遍歷此特徵的每一個閾值(即小於或者大於閾值,屬於一類)
- 第三層迴圈:遍歷小於閾值屬於正類大於閾值屬於反類,和小於閾值屬於反類大於閾值屬於正類
每一次都計算加權錯誤率,最後選擇加權錯誤率最小的單層決策樹作為這次的弱分類器。
3. 非均衡資料
對於資料是非均勻的情況,可根據實際情況選擇:
- Precision、Recall、F-score(F1-measure)
- TPR、FPR、TNR、FNR、AUC
- Accuracy
例如,寧可將反類判成正類,也不願將正類判成反類,就可以使用 Recall。
這部分可以詳見 吳恩達機器學習(九)Precision、Recall、F-score、TPR、FPR、TNR、FNR、AUC、Accuracy
或者可對資料進行欠抽樣(undersampling)和過抽樣(oversampling):
- 欠抽樣:刪除部分樣例,可將離決策邊界較遠的樣例刪除
- 過抽樣:複製部分樣例,或者可加入與已有樣例相似的點
4. 實戰案例
以下將展示書中案例的程式碼段,所有程式碼和資料可以在github中下載:
4.1. 馬病死亡案例
# coding:utf-8
from numpy import *
import matplotlib.pyplot as plt
"""
馬病死亡案例
"""
# 載入資料集
def loadDataSet(fileName):
numFeat = len(open(fileName).readline().split('\t'))
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
lineArr = []
curLine = line.strip().split('\t')
for i in range(numFeat - 1):
lineArr.append(float(curLine[i]))
dataMat.append(lineArr)
labelMat.append(float(curLine[-1]))
return dataMat, labelMat
# 根據特徵和閾值劃分資料類別
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
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
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
weakClassArr.append(bestStump)
print("classEst: ", classEst.T)
# 更新樣本權重向量
expon = multiply(-1 * alpha * mat(classLabels).T, classEst)
D = multiply(D, exp(expon)) / D.sum()
# 更新每個樣本的加權分類結果
aggClassEst += alpha * classEst
print("aggClassEst: ", aggClassEst.T)
# 計算當前加權的錯誤率
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1)))
errorRate = aggErrors.sum() / m
print("total error: ", errorRate)
if errorRate == 0.0: break
return weakClassArr, aggClassEst
# 分類函式
def adaClassify(datToClass, classifierArr):
dataMatrix = mat(datToClass)
m = shape(dataMatrix)[0]
# 加權的預測結果
aggClassEst = mat(zeros((m, 1)))
# 遍歷每一個弱分類器
for i in range(len(classifierArr)):
classEst = stumpClassify(dataMatrix, classifierArr[i]['dim'], \
classifierArr[i]['thresh'], \
classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha'] * classEst
print(aggClassEst)
return sign(aggClassEst)
# 畫ROC曲線
def plotROC(predStrengths, classLabels):
cur = (1.0, 1.0)
ySum = 0.0
# 正類數量
numPosClas = sum(array(classLabels) == 1.0)
# 1/正類數量
yStep = 1 / float(numPosClas)
# 1/反類數量
xStep = 1 / float(len(classLabels) - numPosClas)
# 按照從小到大,索引排序
sortedIndicies = predStrengths.argsort()
fig = plt.figure()
fig.clf()
ax = plt.subplot(111)
# 遍歷排序後的索引
# 表示屬於正類的概率
# 因排序,屬於正類的概率越來越大,條件越來越苛刻
# 由初始 TPF->1 FPR->1
# 最終 TPR->0 FPR->0
for index in sortedIndicies.tolist()[0]:
if classLabels[index] == 1.0:
delX = 0
delY = yStep
else:
delX = xStep
delY = 0
ySum += cur[1]
ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c='b')
# 更新cur
cur = (cur[0] - delX, cur[1] - delY)
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()
# 微積分算AUC面積
# 對多個小長方塊求面積之和
# 小長方塊的寬為 xStep
# 每一次的長為 cur[1]_i
# 即 cur[1]_1 * xStep + ... + cur[1]_n * xStep
# 即 ySum * xStep
print("the Area Under the Curve is: ", ySum * xStep)
if __name__ == '__main__':
datArr, labelArr = loadDataSet('horseColicTraining2.txt')
classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 50)
testArr, testLabelArr = loadDataSet('horseColicTest2.txt')
pred = adaClassify(testArr, classifierArr)
errArr = mat(ones((67, 1)))
errNum = errArr[pred != mat(testLabelArr).T].sum()
print(float(errNum) / len(testLabelArr))
plotROC(aggClassEst.T, labelArr)
如果這篇文章對你有一點小小的幫助,請給個關注喔~我會非常開心的~