知識篇——基于AdaBoost的分類問題

當做重要決定時,大家可能都會考慮吸取多個專家而不只是一個人的意見。

這一生活問題映射到計算機世界就變成了元算法(meta-algorithm)或者集成方法(ensemble method)。這種集成可以是不同算法的集成,也可以是同一算法在不同設置下的集成,還可以是數(shù)據(jù)集不同部分分配給不同分類器之后的集成。AdaBoost就是一種最流行的元算法。

什么是AdaBoost

AdaBoost是adaptive boosting的縮寫,boosting是一種與bagging很類似的技術,將原始數(shù)據(jù)集選擇S次后得到S個新數(shù)據(jù)集,新數(shù)據(jù)集與原始數(shù)據(jù)集大小相等,每個數(shù)據(jù)集都是通過在原始數(shù)據(jù)集中隨機選擇一個樣本來替換得到的,這就意味著可以多次選擇同一個樣本。在S個數(shù)據(jù)集建好之后,將某個學習算法分別作用于每個數(shù)據(jù)集就得到了S個分類器,當我們要對新數(shù)據(jù)分類時,就可以用這S個分類器進行分類,選擇分類器投票結(jié)果最多的類別作為最后分類結(jié)果。boosting通過集中關注被已有分類器錯分的數(shù)據(jù)來獲得新的分類器,boosting給每個分類器的權(quán)重不相等,每個權(quán)重代表的是對應的分類器在上一輪迭代中的成功度,分類結(jié)果是基于所有分類器的加權(quán)求和得到的。

為什么要用AdaBoost(為什么要用弱分類器和多個實例來構(gòu)建一個強分類器)

等待答案中。。。

Adaboost算法流程是什么

1)給數(shù)據(jù)中的每一個樣本一個權(quán)重
2)訓練數(shù)據(jù)中的每一個樣本,得到第一個分類器
3)計算該分類器的錯誤率,根據(jù)錯誤率計算要給分類器分配的權(quán)重(注意這里是分類器的權(quán)重)
4)將第一個分類器分錯誤的樣本權(quán)重增加,分對的樣本權(quán)重減小(注意這里是樣本的權(quán)重)
5)然后再用新的樣本權(quán)重訓練數(shù)據(jù),得到新的分類器,到步驟3
6)直到步驟3中分類器錯誤率為0,或者到達迭代次數(shù)
7)將所有弱分類器加權(quán)求和,得到分類結(jié)果(注意是分類器權(quán)重)

解釋:

  • 步驟3中,錯誤率的定義是:


    錯誤率.png

    分類器的權(quán)重計算公式是:

分類器權(quán)重.png
  • 步驟4中,錯誤樣本權(quán)重更改公式為:


    錯誤樣本權(quán)重更改公式.png

    正確樣本權(quán)重更改公式為:


    正確樣本權(quán)重更改公式.png

    其中t指當前分類器,i指第i個樣本。

構(gòu)建基于單層決策樹的AdaBoost分類器

用一個很簡單的散點分類問題來實現(xiàn)AdaBoost分類器。

準備數(shù)據(jù)集

import numpy as np
def loadSimpData():
    datMat = np.matrix([[1. , 2.1],
                    [1.5 , 1.6],
                    [1.3, 1. ],
                    [1. , 1. ],
                    [2. , 1. ]])
    classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
    return datMat, classLabels
datMat, classLabels = loadSimpData()
showScatter(datMat, classLabels)

showScatter方法用來繪制散點圖,代碼如下:

import matplotlib.pyplot as plt
def showScatter(matrix, labels):
    plt.figure(figsize=(8,6))
    x1 = []; y1 = []; x2 = []; y2 = []
    for i in range(len(labels)):
        if labels[i] == 1.0:
            x1.append(matrix[i, 0])
            y1.append(matrix[i, 1])
        else:
            x2.append(matrix[i, 0])
            y2.append(matrix[i, 1])
    plt.scatter(x1, y1, marker='o',
        color='green', alpha=0.7, label='1.0')
    plt.scatter(x2, y2, marker='^',
                color='red', alpha=0.7, label='-1.0')
    plt.title('dataset')
    plt.ylabel('variable Y')
    plt.xlabel('Variable X')
    plt.legend(loc='upper right')
    plt.show()

所以要進行的分類如下圖所示:


數(shù)據(jù)圖

結(jié)果調(diào)用方法(期望實現(xiàn)效果)

把它放在第二步,倒著寫程序,是借用了TDD的思路,這樣會更好理解,測試就不粘出來了,從結(jié)果出發(fā),一步一步驅(qū)動出算法代碼。
對于這個demo,我們希望輸入一個或多個點,告訴我們是1.0類(綠色原點)還是-1.0類(紅色三角)。
為了讓代碼更靈活,我們把弱分類器也作為參數(shù)傳入,則最后的調(diào)用為:

# [[5, 5], [0, 0]]是要分類的點
# classifierArr是弱分器數(shù)組
adaClassify ([[5, 5], [0, 0]], classifierArr)

期望的輸出是:
[[1.], [-1.]]

寫AdaBoost分類函數(shù)

從上一步讓我們驅(qū)動出adaClassify函數(shù),我們希望每一個弱分類器是一個字典,有最好的維度、所用的閾值、是大于閾值還是小于閾值為1類這些屬性,通過這些屬性能得到一個分類結(jié)果,再用分類結(jié)果乘以分類器的權(quán)重,進行累加,返回分類結(jié)果,下面寫它的實現(xiàn)

# datToClass:要分類的數(shù)據(jù)
# classifierArr:弱分類器數(shù)組
def adaClassify(datToClass, classifierArr):
    dataMatrix = np.mat(datToClass)
    m = np.shape(dataMatrix)[0] 
    aggClassEst = np.mat(np.zeros((m, 1))) # 為了滿足輸出期望,先用0列向量初始化輸出結(jié)果
    for i in range(len(classifierArr)):
        classEst = stumpClassify(dataMatrix, classifierArr[i]['dim'], classifierArr[i]['thresh'], classifierArr[i]['ineq']) # 得到一個弱分類器分類結(jié)果
        aggClassEst += classifierArr[i]['alpha'] * classEst # 對應算法流程的步驟7,將弱分類器結(jié)果加權(quán)求和
        # print(aggClassEst)
    return np.sign(aggClassEst) #由于是二類問題,所以可以根據(jù)加權(quán)求和結(jié)果的正負情況得到期望的分類輸出

寫基于單層決策樹的AdaBoost函數(shù)

上一步可以驅(qū)動出stumpClassify函數(shù),需要傳入的參數(shù)為要分類的數(shù)據(jù)、維度、閾值、大于閾值是1類還是小于閾值是1類,希望它能給出一個分類結(jié)果,結(jié)果長這樣:[[1.],[1.],[-1.],...]。實現(xiàn)如下:

# dataMatirx:要分類的數(shù)據(jù)
# dimen:維度
# threshVal:閾值
# threshIneq:有兩種,‘lt’=lower than,‘gt’=greater than
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    retArray = np.ones((np.shape(dataMatrix)[0], 1)) 
    if threshIneq == 'gt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0 # 如果希望大于閾值的是1,則小于閾值的部分置為-1
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray

訓練分類器

接下來的工作就是訓練分類器,得到classifierArr弱分類器數(shù)組,作為參數(shù),穿給我們的最中調(diào)用函數(shù)。
回想AdaBoost分類器的步驟2-6,即每個分類器都是在上一次的基礎上更新權(quán)重,訓練數(shù)據(jù),權(quán)重更新方法是增加分類錯誤的樣本權(quán)重,減小分類正確的樣本權(quán)重。
我們希望告訴這個函數(shù)數(shù)據(jù)集,還有對應的標簽,我們設置的最多迭代次數(shù)后,能返回一個弱分類器數(shù)組。所以函數(shù)長下面這樣:

def adaBoostTrainDS(dataArr, classLabels, numIt=40):
    weakClassArr = []
    m = np.shape(dataArr)[0]
    D = np.mat(np.ones((m,1)) / m) # 初始化權(quán)重向量,給每個樣本相同的權(quán)重,[[1/m],[1/m],[1/m],...]
    aggClassEst = np.mat(np.zeros((m,1))) # 初始化每個樣本的預估值為0
    for i in range(numIt): # 遍歷迭代次數(shù)
        bestStump, error, classEst = buildStump(dataArr, classLabels, D) # 構(gòu)建一棵單層決策樹,返回最好的樹,錯誤率和分類結(jié)果
        alpha = float(0.5 * np.log((1.0 - error)/error)) #計算分類器權(quán)重
        bestStump['alpha'] = alpha #將alpha值也加入最佳樹字典
        weakClassArr.append(bestStump) # 將弱分類器加入數(shù)組
        # print("classEst:", classEst.T)
        # 更新權(quán)重向量D
        expon = np.multiply(-1*alpha*np.mat(classLabels).T, classEst) 
        D = np.multiply(D, np.exp(expon))
        D = D / D.sum()
        # 累加錯誤率,直到錯誤率為0或者到達迭代次數(shù)
        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("total error:", errorRate, "\n")
        if errorRate == 0.0:
            break;
    return weakClassArr

構(gòu)建單層決策樹分類器

通過上一步可以驅(qū)動出buildStump函數(shù)。我們希望輸入數(shù)據(jù)集和標簽以及由每個樣本的權(quán)重構(gòu)成的權(quán)重矩陣D,能得到最好的分類器的屬性,包含維度、閾值、大于閾值還是小于閾值是1.0類,還有分類器錯誤率,最好的分類器的長相。

# dataArr: 數(shù)據(jù)集
# classLabels:標簽
# D:由每個樣本的權(quán)重構(gòu)成的矩陣
def buildStump(dataArr, classLabels, D):
    dataMatrix = np.mat(dataArr)
    labelMat = np.mat(classLabels).T # 標簽轉(zhuǎn)成列向量
    m, n = np.shape(dataMatrix) #m為數(shù)據(jù)個數(shù),n為每條數(shù)據(jù)含有的樣本數(shù)(也就是特征)
    numSteps = 10.0
    bestStump = {}
    bestClasEst = np.mat(np.zeros((m, 1))) # 初始化最好的分類器為[[0],[0],[0],...]
    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']: # 每個條件,大于閾值是1還是小于閾值是1
                threshVal = (rangeMin + float(j) * stepSize) # 閾值設為最小值+第j個步長
                print('i=%d, threshVal=%f, inequal=%s'%(i,threshVal,inequal))
                predictedVals = stumpClassify(dataMatrix, i , threshVal, inequal) # 將dataMatrix的第i個特征inequal閾值的置為1,否則為-1
                print(predictedVals)
                errArr = np.mat(np.ones((m, 1)))
                errArr[predictedVals == labelMat] = 0 # 預測對的置0
                print(errArr)
                weightedError = D.T * errArr
                print("split: dim %d, threshold %.2f, threshold inequal: %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

上述過程就是,遍歷每個特征,在每個特征上找合理的分界點,每次移動一個步長,對每個步長,根據(jù)它的大于閾值是1.0還是小于閾值是1.0類得到分類情況,計算錯誤率,找錯誤率最小的一種情況返回。bestStump是一個字典,找到一個好的分類器,當然要包含所選擇的維度(映射到這個題目就是x軸還是y軸),分類的閾值是多少,大于還是小于閾值的是1.0類,所以把這些屬性放入了bestStump字典。

進行測試

到這里,就實現(xiàn)了,可以用一個或一組點進行測試,用第一步設想的調(diào)用方法:

# 分類(0,0)點
adaClassify([0,0], classifierArray)

輸出結(jié)果為:


屏幕快照 2017-04-26 下午1.59.36.png

可見,(0,0)點分類為-1類。

總結(jié)

AdaBoost簡單來講,就是多個弱分類器,可能基于單層決策樹,也可能基于其他算法,每一個弱分類器得到一個分類結(jié)果,根據(jù)它的錯誤率給這個分類器一個權(quán)重,還要更新樣本的權(quán)重,基于這個權(quán)重矩陣,再去訓練出一個弱分類器,依次循環(huán),直到錯誤率為0,就得到了一系列弱分類器,組成一個搶分類器,將這些弱分類器的結(jié)果加權(quán)求和,能得到一個較為準確的分類。


以上內(nèi)容來自822實驗室2017年4月23日17:30第一次知識分享活動:基于cascade的object detection。
我們的822,我們的青春
歡迎所有熱愛知識熱愛生活的朋友和822實驗室一起成長,吃喝玩樂,享受知識。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內(nèi)容