本節代碼存放在github網址上,環境:python3
利用AdaBoost元算法提高分類性能
元算法(meta-algorithm)或者集成方法(ensemble method)是對其他算法進行組合的一種方式
AdaBoost算法
優點:範化錯誤率低,易編碼,可以應用在大部分分類器上,無參數調整
缺點:對離羣點敏感
適用數據類型: 數值型和標稱型數據
bagging和boosting所使用的多個分類器類型都是一致的!
bagging: 基於數據隨機抽樣的分類器構建方法
放回取樣得到S個數據集,將某個學習算法分別作用於每個數據集就得到了S個分類器,要對新數據進行分類時,可以應用這S個分類器進行分類,
選取分類器投票結果中最多的類別作爲最後的分類結果。分類器的權重是相等的。
random forest 也是bagging方法的一種
boosting:通過集中關注被已有分類器錯分的那些數據來獲得新的分類器
不同的分類器是通過串行訓練獲得的,每個新的分類器都根據已訓練出的分類器的性能來訓練。
其分類結果是基於所有分類器的加權求和結果的,分類器權重並不相等,每個權重代表的是其對應分類器在上一輪迭代中的成功度。
AdaBoost一般流程
- 收集數據:可以使用任何方法
- 準備數據:依賴於所使用的弱分類器類型,本章使用的是單層決策樹,這種分類器可以處理任何數據類型。當然也可以使用任意分類器作爲弱分類器,KNN, 決策樹,樸素貝葉斯,Logistic迴歸,SVM中任一分類器都可以充當弱分類器。作爲弱分類器,簡單分類器的效果最好。 確保類別標籤是+1和-1,而非1和0.
- 分析數據:可以使用任一方法分析數據:可以使用任一方法
- 訓練算法:AdaBoost的大部分時間都用在訓練上, 分類器將多次在同一數據集上訓練弱分類器。
- 測試算法:計算分類的錯誤率測試算法:計算分類的錯誤率
- 使用算法:同SVM同,AdaBoost預測兩個類別中的一個。若想把它應用到多分類的場合,需要像多類SVM中的做法一樣對AdaBoost進行修改。使用算法:同SVM同,AdaBoost預測兩個類別中的一個。若想把它應用到多分類的場合,需要像多類SVM中的做法一樣對AdaBoost進行修改。
AdaBoost運行過程
訓練數據中的每個樣本,並賦予其一個權重,這些權重構成了向量D. 一開始,這些權重都初始化成相等值。首先在訓練集上訓練出一個弱分類器,並計算該分類器的錯誤率,然後在同一數據集上再次訓練弱分類器。在分類器的第二次訓練中會調整每個樣本的權重,其中第一次分對的的樣本中的權重將會降低,而第一次分錯的樣本中的權重將會提高。爲了從所有弱分類器中得到最終的分類結果,AdaBoost爲每個弱分類器都分配了一個權重值alpha,這些alpha是基於每個弱分類器的錯誤率進行計算的。
錯誤率ε定義爲:
alpha定義爲:
計算出alpha後,可以對權重D進行更新,以使那些正確分類的樣本權重降低而錯分樣本的權重提高。D的計算方法如下:
若某個樣本被正確分類,該樣本權重更改爲:
若某個樣本被錯分,該樣本權重更改爲:
基於單層決策樹構建弱分類器
僞代碼如下:
將最小錯誤率minError設爲+inf
對數據集中每個特徵(第一層循環):
對每個步長(第二層循環):
對每個不等號(第三層循環):
建立一棵單層決策樹並利用加權數據集對它進行測試
如果錯誤率低於minError,則將當前單層決策樹設爲最佳單層決策樹
返回最佳單層決策樹
python代碼如下:
# -*- coding: utf-8 -*-
__author__ = 'WF'
# import warnings
import numpy as np
# warnings.simplefilter(action='ignore', category=FutureWarning)
# 加載簡單數據集
def loadSimpleData():
dataMat = np.matrix([[1., 2.1],
[2., 1.1],
[1.3, 1.],
[1., 1.],
[2., 1.]])
classLabel = [1.0, 1.0, -1.0, -1.0, 1.0]
return dataMat, classLabel
# 圖形化展示數據
def plot_simpledata():
import matplotlib.pyplot as plt
x, y = loadSimpleData()
y = np.array(y)
x = np.array(x)
# print(y)
# x1 = x[np.nonzero(y)[0]]
x1 = x[np.where(y == 1.0)[0]]
# print(np.nonzero(y)[0])
x0 = x[np.where(y == -1.0)[0]]
# print(x0[:, 0], x0[:, 1])
figure = plt.figure()
ax = figure.add_subplot(111)
ax.scatter(x0[:, 0], x0[:, 1], marker='<', c='r', s=50)
ax.scatter(x1[:, 0], x1[:, 1], marker='s', c='g', s=20)
plt.show()
# 單層決策樹生成函數
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
'''
通過閾值比較對數據進行分類,所有在閾值一邊的數據會被分到-1,另一邊的數據被分到1
:param dataMatrix:數據矩陣
:param dimen: 維度屬性
:param threshVal: 閾值
:param threshIneq:閾值比較符號
:return:單層決策樹字典,錯誤率,類別估計
'''
retArray = np.ones((np.shape(dataMatrix)[0], 1))
if threshIneq == 'lt':
retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:, dimen] > threshVal] = -1.0
return retArray
# 遍歷stumpClassify()函數所有的可能輸入值,並找到數據集上最佳的單層決策樹
def buildStump(dataArr, classLabels, D):
# classLabels = np.reshape(classLabels, (len(classLabels), 1))
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
# print(np.shape(labelMat)) # (5,1)
m, n = np.shape(dataMatrix)
numSteps = 10.0
bestStump = {}
bestClasEst = np.mat(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)
# print('p:', predictedVals)
# print(np.shape(predictedVals)) #(5, 1)
errArr = np.mat(np.ones((m, 1)))
# print(np.where(predictedVals == labelMat)[0])
# for num in range(len(predictedVals)):
# print(predictedVals[num] == classLabels[num])
# if float(predictedVals[num]) == float(classLabels[num]):
# errArr[num][0] = 0
# print(errArr)
errArr[predictedVals == labelMat] =0
# print(errArr)
weightedError = D.T * errArr # 計算加權錯誤率
print("split:dim %d, thresh %.2f, thresh ineqal: %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
**
完整AdaBoost算法實現
**
僞代碼:
對每次迭代:
利用buildStump()函數找到最佳的單層決策樹
將最佳的單層決策樹加入到單層決策樹組
計算alpha
計算新的權重向量D
更新累計類別估計值
如果錯誤率等於0.0, 則退出循環
python代碼如下:
def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
'''
:param dataArr:數據集(不包含label)
:param classLabels: 類別標籤
:param numIt: 迭代次數
:return:
'''
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):
bestStump, error, classEst = buildStump(dataArr, classLabels, D)
print('D:', D.T)
alpha = float(0.5*np.log((1.0-error)/max(error, 1e-16)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
print('classEst:', classEst.T)
# 爲下一次迭代計算D
expon = np.multiply(-1*alpha*np.mat(list(map(float, classLabels))).T, classEst)
D = np.multiply(D, np.exp(expon))
D = D/D.sum()
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('errorRate:', errorRate)
if errorRate == 0.0:
break
return weakClassArr, aggClassEst
基於AdaBoost的分類
每個弱分類器的結果以其對應的alpha作爲權重,所有這些弱分類器的結果加權求和就得到最後的結果。程序返回aggClassEst(記錄每個數據點的類別估計累計值)的符號,如果aggClassEst大於0則返回+1, 否則返回-1
python代碼如下:
def adaClassify(datToClass, classifierArr):
dataMatrix = np.mat(datToClass)
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]['thresh'], classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha']*classEst
print('aggClassEst:', aggClassEst)
return(np.sign(aggClassEst))
完整代碼如下
:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'WF'
# import warnings
import numpy as np
# warnings.simplefilter(action='ignore', category=FutureWarning)
def loadSimpleData():
dataMat = np.matrix([[1., 2.1],
[2., 1.1],
[1.3, 1.],
[1., 1.],
[2., 1.]])
classLabel = [1.0, 1.0, -1.0, -1.0, 1.0]
return dataMat, classLabel
def adaptive_load_data(filename):
numFeat = len(open(filename).readline().strip().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 plot_simpledata():
import matplotlib.pyplot as plt
x, y = loadSimpleData()
y = np.array(y)
x = np.array(x)
# print(y)
# x1 = x[np.nonzero(y)[0]]
x1 = x[np.where(y == 1.0)[0]]
# print(np.nonzero(y)[0])
x0 = x[np.where(y == -1.0)[0]]
# print(x0[:, 0], x0[:, 1])
figure = plt.figure()
ax = figure.add_subplot(111)
ax.scatter(x0[:, 0], x0[:, 1], marker='<', c='r', s=50)
ax.scatter(x1[:, 0], x1[:, 1], marker='s', c='g', s=20)
plt.show()
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
'''
通過閾值比較對數據進行分類,所有在閾值一邊的數據會被分到-1,另一邊的數據被分到1
:param dataMatrix:數據矩陣
:param dimen: 維度屬性
:param threshVal: 閾值
:param threshIneq:閾值比較符號
:return:單層決策樹字典,錯誤率,類別估計
'''
retArray = np.ones((np.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):
# classLabels = np.reshape(classLabels, (len(classLabels), 1))
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
# print(np.shape(labelMat)) # (5,1)
m, n = np.shape(dataMatrix)
numSteps = 10.0
bestStump = {}
bestClasEst = np.mat(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)
# print('p:', predictedVals)
# print(np.shape(predictedVals)) #(5, 1)
errArr = np.mat(np.ones((m, 1)))
# print(np.where(predictedVals == labelMat)[0])
# for num in range(len(predictedVals)):
# print(predictedVals[num] == classLabels[num])
# if float(predictedVals[num]) == float(classLabels[num]):
# errArr[num][0] = 0
# print(errArr)
errArr[predictedVals == labelMat] =0
# print(errArr)
weightedError = D.T * errArr # 計算加權錯誤率
print("split:dim %d, thresh %.2f, thresh ineqal: %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
def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
'''
:param dataArr:數據集(不包含label)
:param classLabels: 類別標籤
:param numIt: 迭代次數
:return:
'''
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):
bestStump, error, classEst = buildStump(dataArr, classLabels, D)
print('D:', D.T)
alpha = float(0.5*np.log((1.0-error)/max(error, 1e-16)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
print('classEst:', classEst.T)
# 爲下一次迭代計算D
expon = np.multiply(-1*alpha*np.mat(list(map(float, classLabels))).T, classEst)
D = np.multiply(D, np.exp(expon))
D = D/D.sum()
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('errorRate:', errorRate)
if errorRate == 0.0:
break
return weakClassArr, aggClassEst
def adaClassify(datToClass, classifierArr):
dataMatrix = np.mat(datToClass)
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]['thresh'], classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha']*classEst
print('aggClassEst:', aggClassEst)
return(np.sign(aggClassEst))
def plotROC(predStrengths, classLabels):
import matplotlib.pyplot as plt
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() # 清除當前 figure 的所有axes,但是不關閉這個 window,所以能繼續複用於其他的 plot。
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]
ax.plot([cur[0], cur[0]-delX], [cur[1], cur[1]-delY], c='b')
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()
print('the Area Under the Curve is:', ySum*xStep)
if __name__ == "__main__":
dataMat, classLabels = loadSimpleData()
# plot_simpledata()
# D = np.mat(np.ones((5, 1))/5)
# bestStump, minError, bestClasEst = buildStump(dataMat, classLabels, D)
# print(bestStump, minError, bestClasEst)
# weakClassArr = adaBoostTrainDS(dataMat, classLabels, 9)
# print(weakClassArr)
#
# print(adaClassify([[0, 0], [5, 5]], weakClassArr))
datArr, labelArr = adaptive_load_data('horseColicTraining2.txt')
# print(datArr)
print(labelArr)
classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 10)
# testArr, testLabelArr = adaptive_load_data('horseColicTest2.txt')
# prediction10 = adaClassify(testArr, classifierArr)
# print(prediction10.T)
# errArr = np.mat(np.ones((67, 1)))
# errnum = errArr[prediction10 != np.mat(testLabelArr).T].sum()
# print('errornum:', errnum, 'errorRate:', errnum/67)
plotROC(aggClassEst.T, labelArr)
分類性能度量指標:準確率、召回率、ROC曲線及AUC
TP(真陽):將正例判定爲正例
FP(假陽):將反例判定爲正例
TN(真陰):將反例判定爲反例
FN(假陰):將正例判定爲反例
錯誤率指在所有測試樣例中錯分的比例。
準確率指在預測爲正例的樣本中真正正例的比例
P = TP/(TP+FP)
召回率(查全率)指預測爲正例的真正正例佔所有真實正例的比例
R=TP/(TP+FN)
我們很容易構造一個高準確率或者高召回率的分類器,但是很難保證兩者同時成立。如果將任何樣本都判爲正例,則召回率達到100%,但是準確率很低。
另一個用來度量分類中的非均衡性的工具是ROC曲線(ROC Curve),ROC代表接收者操作特徵(receiver operating characteristic)
ROC曲線中,橫軸是假陽率=FP/(FP+TN),縱軸是真正率=TP/(TP+FN).
ROC曲線給出的是當閾值變化時假陽率和真陽率變化的情況。左下角點是將所有樣例判爲反例的情況。
右上角是將所有樣例判爲正例的情況。
理想情況下,最佳分類器應該儘可能的處於左上角,即分類器在假陽率很低的同時獲得了很高的真陽率。
對不同的ROC曲線進行比較的一個指標是曲線下的面積AUC(Area Under the Curve),其給出的是分類器的平均性能值,一個完美的分類器AUC爲1.0, 隨機猜測的AUC爲0.5.
上述代碼的ROC曲線如下圖:
基於代價函數的分類器決策控制
cost = TP*0 + FP*1 + TN*0 + FN*1
cost = TP*(-5) + FP*50 + TN*0 + FN*1
處理非均衡問題的數據抽樣方法
過抽樣或者欠抽樣
過抽樣是複製樣例,欠抽樣是刪除樣例,選擇離決策邊界較遠的樣例進行刪除。