Bagging(Bootstrap aggregating)、隨機森林(random forests)、AdaBoost

夜空中最亮的不咚發表於2018-04-02
轉載自 https://blog.csdn.net/xlinsist/article/details/51475345

引言

在這篇文章中,我會詳細地介紹Bagging、隨機森林和AdaBoost演算法的實現,並比較它們之間的優缺點,並用scikit-learn分別實現了這3種演算法來擬合Wine資料集。全篇文章伴隨著例項,由淺入深,看過這篇文章以後,相信大家一定對ensemble的這些方法有了很清晰地瞭解。

Bagging

bagging能提升機器學習演算法的穩定性和準確性,它可以減少模型的方差從而避免overfitting。它通常應用在決策樹方法中,其實它可以應用到任何其它機器學習演算法中。如果大家對決策樹的演算法不太理解,請大家參考這篇文章:決策樹ID3、C4.5、C5.0以及CART演算法之間的比較,在下面的例子中,都會涉及到決策樹,希望大家能理解一下這個演算法。

下面,我介紹一下bagging技術的過程:

假設我有一個大小為n的訓練集D,bagging會從D中有放回的均勻地抽樣,假設我用bagging生成了m個新的訓練集Di,每個Di的大小為j。由於我有放回的進行抽樣,那麼在Di中的樣本有可能是重複的。如果j=n,這種取樣稱為bootstrap取樣。現在,我們可以用上面的m個訓練集來擬合m個模型,然後結合這些模型進行預測。對於迴歸問題來說,我們平均這些模型的輸出;對於分類問題來說,我們進行投票(voting)。bagging可以改良不穩定演算法的效能,比如:人工神經網路、CART等等。下面,我舉一個具體的例子說明一下bagging。

假設有一個訓練集D的大小為7,我想用bagging生成3個新的訓練集Di,每個Di的大小為7,結果如下表:

樣本索引bagging(D1)bagging(D2)bagging(D3)
1273
2234
3123
4313
5516
6251
7641

那麼現在我就可以用上面生成的3個新訓練集來擬合模型了。

決策樹是一個很流行的機器學習演算法。這個演算法的效能在特徵值的縮放和各種轉換的情況下依然保持不變,即使在包含不相關特徵的前提下,它依然很健壯。然而,決策樹很容易過擬合訓練集。它有低的偏差,但是有很高的方差,因此它的準確性不怎麼好。

bagging是早期的整合方法(ensemble method),它可以重複地構建多個決策樹基於有放回地重新取樣,然後整合這些決策樹模型進行投票,從而得到更好地準確性。稍後,我會介紹決策森林演算法,它可以比bagging更好地解決決策樹overfitting的問題。這些方法雖然會增加一些模型的偏差和丟失一些可解釋性,但是它們通常會使模型具有更好地效能。

下面,我用scikit-learn實現bagging來擬合Wine資料集來實戰一下bagging方法。這是資料集的介紹:Wine資料集

import pandas as pd
df_wine = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)
df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash', 'Alcalinity of ash', 'Magnesium', 'Total phenols', 'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins', 'Color intensity', 'Hue', 'OD280/OD315 of diluted wines', 'Proline']
df_wine = df_wine[df_wine['Class label'] != 1] # 資料集中有3個類別,這裡我們只用其中的2個類別
y = df_wine['Class label'].values
X = df_wine[['Alcohol', 'Hue']].values # 為了視覺化的目的,我們只選擇2個特徵

from sklearn.preprocessing import LabelEncoder
from sklearn.cross_validation import train_test_split
le = LabelEncoder()
y = le.fit_transform(y) # 把label轉換為0和1
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.40,  random_state=1) # 拆分訓練集的40%作為測試集

from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
tree = DecisionTreeClassifier(criterion='entropy', max_depth=None)
# 生成500個決策樹,詳細的引數建議參考官方文件
bag = BaggingClassifier(base_estimator=tree, n_estimators=500, max_samples=1.0, max_features=1.0, bootstrap=True, bootstrap_features=False, n_jobs=1, random_state=1)

# 度量單個決策樹的準確性
from sklearn.metrics import accuracy_score
tree = tree.fit(X_train, y_train)
y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)
tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print('Decision tree train/test accuracies %.3f/%.3f' % (tree_train, tree_test))
# Output:Decision tree train/test accuracies 1.000/0.854

# 度量bagging分類器的準確性
bag = bag.fit(X_train, y_train)
y_train_pred = bag.predict(X_train)
y_test_pred = bag.predict(X_test)
bag_train = accuracy_score(y_train, y_train_pred)
bag_test = accuracy_score(y_test, y_test_pred)
print('Bagging train/test accuracies %.3f/%.3f' % (bag_train, bag_test))
# Output:Bagging train/test accuracies 1.000/0.89612345678910111213141516171819202122232425262728293031323334353637複製程式碼

從上面的輸出我們可以看出,Bagging分類器的效果的確要比單個決策樹的效果好。下面,讓我們列印出兩個分類器的決策邊界,看看它們之間有什麼不同,程式碼如下:

x_min = X_train[:, 0].min() - 1
x_max = X_train[:, 0].max() + 1
y_min = X_train[:, 1].min() - 1
y_max = X_train[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1), np.arange(y_min, y_max, 0.1))
f, axarr = plt.subplots(nrows=1, ncols=2, sharex='col', sharey='row', figsize=(8, 3))

for idx, clf, tt in zip([0, 1], [tree, bag], ['Decision Tree', 'Bagging']):
    clf.fit(X_train, y_train)
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    axarr[idx].contourf(xx, yy, Z, alpha=0.3)
    axarr[idx].scatter(X_train[y_train==0, 0], X_train[y_train==0, 1], c='blue', marker='^')
    axarr[idx].scatter(X_train[y_train==1, 0], X_train[y_train==1, 1], c='red', marker='o')
    axarr[idx].set_title(tt)

plt.show()1234567891011121314151617複製程式碼

決策樹決策邊界

從上圖我們可以看出,Bagging分類器的決策邊界更加平滑。注意:bagging是不能減小模型的偏差的,因此我們要選擇具有低偏差的分類器來整合,例如:沒有修剪的決策樹。

隨機森林(random forests)

隨機森林與上面Bagging方法的唯一的區別是,隨機森林在生成決策樹的時候用隨機選擇的特徵。之所以這麼做的原因是,如果訓練集中的幾個特徵對輸出的結果有很強的預測性,那麼這些特徵會被每個決策樹所應用,這樣會導致樹之間具有相關性,這樣並不會減小模型的方差。

隨機森林通常可以總結為以下4個簡單的步驟:

  1. 從原始訓練集中進行bootstrap抽樣
  2. 用步驟1中的bootstrap樣本生成決策樹
    • 隨機選擇特徵子集
    • 用上面的特徵子集來拆分樹的節點
  3. 重複1和2兩個步驟
  4. 整合所有生成的決策樹進行預測

在上面的步驟2中,我們訓練單個決策樹的時候並沒有用全部的特徵,我們只用了特徵的子集。假設我們全部的特徵大小為m,那麼m‾‾√個特徵子集是一個很好地選擇。

隨機森林並不像決策樹一樣有很好地解釋性,這是它的一個缺點。但是,隨機森林有更好地準確性,同時我們也並不需要修剪隨機森林。對於隨機森林來說,我們只需要選擇一個引數,生成決策樹的個數。通常情況下,你決策樹的個數越多,效能越好,但是,你的計算開銷同時也增大了。

下面,我用隨機森林訓練上面的Wine資料集。這次我不在只選擇資料集的2個特徵了,我要用全部的13個特徵。而且這次的輸出我用了3個類別。程式碼如下:

import pandas as pd
df_wine = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)
df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash', 'Alcalinity of ash', 'Magnesium', 'Total phenols', 'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins', 'Color intensity', 'Hue', 'OD280/OD315 of diluted wines', 'Proline']
y = df_wine['Class label'].values
X = df_wine.values[:, 1:]

from sklearn.preprocessing import LabelEncoder
from sklearn.cross_validation import train_test_split
le = LabelEncoder()
y = le.fit_transform(y) # 把label轉換為0和1
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.40,  random_state=1) # 拆分訓練集的40%作為測試集

from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=1000, criterion='gini', max_features='sqrt', max_depth=None, min_samples_split=2, bootstrap=True, n_jobs=1, random_state=1)
# 度量隨機森林的準確性
rf = rf.fit(X_train, y_train)
y_train_pred = rf.predict(X_train)
y_test_pred = rf.predict(X_test)
tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print('Random Forest train/test accuracies %.3f/%.3f' % (tree_train, tree_test)) 

# output: Random Forest train/test accuracies 1.000/0.9861234567891011121314151617181920212223複製程式碼

對於上面RandomForestClassifier類的引數資訊,我強烈建議大家去官網檢視:RandomForestClassifier類

Boosting

宣告:下面的部分內容是我從Bishop模式識別與機器學習(Pattern Recognition and Machine Learning)書中翻譯下來的,如果你想對Boosting有一個更深刻的理解,我建議你讀書中的14.3節。

Boosting集合多個’base’分類器從而使它的效能比任何單個base分類器都好地多。在這個小節中,我描述一個更廣泛使用的boosting演算法adaptive boosting(AdaBoost)。即使base分類器的效能比隨機猜測稍微好一點(因此base分類器也叫做weak learners),Boosting依舊會得到一個很好地預測結果。Boosting最初的目的是解決分類問題,現在它也可以解決迴歸問題。

Boosting與Bagging主要的不同是:Boosting的base分類器是按順序訓練的(in sequence),訓練每個base分類器時所使用的訓練集是加權重的,而訓練集中的每個樣本的權重係數取決於前一個base分類器的效能。如果前一個base分類器錯誤分類地樣本點,那麼這個樣本點在下一個base分類器訓練時會有一個更大的權重。一旦訓練完所有的base分類器,我們組合所有的分類器給出最終的預測結果。過程如下圖:

Boosting過程

上圖中,每個base分類器ym(x)用加權重的訓練集來訓練(藍色箭頭), 訓練集的權重係數w(m)n取決於上個base分類器ym−1(x)(綠色箭頭)的效能。一旦所有的base分類器訓練完成,結合它們給出最終的分類器YM(x)(紅色箭頭)。

下面,給出一個兩類別的分類問題。輸入向量x1,x2,…,xN構成了訓練集樣本,其對應的目標變數(標籤)為t1,t2,…,tN,其中tn∈{−1,1}。每個訓練樣本給一個初始化權重1N.假設我們有一個現成的程式(y(x)∈{−1,1})可以用加權的訓練集來訓練base分類器。 在演算法的每次迭代中,AdaBoost用資料集來訓練一個新的base分類器,其中所使用訓練集的加權係數依據上個base分類器的效能不斷作出調整,既錯誤分類的資料點在下一次分類中被賦予更大地權重係數。最終,所有訓練完以後的分類器被分配不同的權重係數。AdaBoost具體的演算法如下:

1、初始化資料集的權重係數{wn},設定w(1)n=1對於n=1,2,…,N
2、For m=1,…,M:
(a) 訓練base分類器ym(x)通過最小化加權誤差函式:

Jm=∑n=1Nw(m)nI(ym(xn)≠tn)(1)上面的I為指示器函式,當ym(xn)≠tn時為1,否則為0

(b) 計算ϵm和αm:

ϵm=∑Nn=1w(m)nI(ym(xn)≠tn)∑Nn=1w(m)n(2)αm=ln(1−ϵmϵm)(3)

(c) 更新樣本的權重係數:

w(m+1)n=w(m)nexp(αmI(ym(xn)≠tn))(4)

3、用最終模型進行預測:
YM(x)=sign(∑m=1Mαmym(x))(5)

從上面的演算法中我們看到了第一個base分類器y1(x)用權重係數(w(1)n)相等的訓練樣本訓練。

假設我們的base分類器為決策樁(decision stumps, 單個節點的決策樹),因此,每個base分類器分類一個樣本通過這個樣本的某個特徵是否超過某個闕值,所以,我們的每個分類器所產生的決策邊界是一個平行某個座標軸的線性決策表面,它簡單地把樣本空間拆分成下圖所示的兩個區域。

AdaBoost實現過程

上面每幅圖中的m表示了當前要訓練的第m個分類器,虛線表示第m個分類器的決策邊界,綠線表示所有m個分類器結合以後的決策邊界。每個資料點用圓描述,圓半徑的大小表示當訓練第m個分類器時資料點權重的大小。例如,上面在訓練第1個base分類器錯誤劃分的資料點,在訓練第2個base分類器時得到了更大的權重係數。

下面,讓我們用scikit-learn來訓練一下AdaBoost。

from sklearn.ensemble import AdaBoostClassifier

# 決策樁分類器效能
tree = DecisionTreeClassifier(criterion='entropy', max_depth=1)
tree = tree.fit(X_train, y_train)
y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)
tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print('Decision tree train/test accuracies %.3f/%.3f' % (tree_train, tree_test)) # Decision tree train/test accuracies 0.915/0.896

# Boosting分類器效能
ada = AdaBoostClassifier(base_estimator=tree, n_estimators=1000, learning_rate=0.1, random_state=0)
ada = ada.fit(X_train, y_train)
y_train_pred = ada.predict(X_train)
y_test_pred = ada.predict(X_test)
ada_train = accuracy_score(y_train, y_train_pred)
ada_test = accuracy_score(y_test, y_test_pred)
print('AdaBoost train/test accuracies %.3f/%.3f' % (ada_train, ada_test)) # AdaBoost train/test accuracies 1.000/0.97912345678910111213141516171819複製程式碼

AdaBoostClassifier類引數的詳細資訊建議大家去官網檢視:AdaBoostClassifier類

總結

上面我提到的這些ensemble方法雖然提高了模型的效能,但是它與單個分類器相比確實增加了很多的計算開銷。在實際應用中,應該仔細考慮我們是否想要用計算的代價來換取模型效能的提高。雖然已經證明bagging和boosting比單個分類器有更好的準確性,然而,我們必須考慮在什麼情況下和怎麼使用這些技術。

當訓練集有很小地變化時都會導致預測結果有明顯地不同,在這種情況下,Bagging會有很好地效果。Bagging更適合應用到具有很小偏差(small bias)地分類方法中。Bagging減小方差通過平均方法(averaging),如果你的模型具有很高的偏差,Bagging並不會對模型有很大的影響,就好比是一堆臭皮匠平均下來還是臭皮匠。但是,如果你的模型個個都是諸葛亮(都很好地擬合訓練集,高方差),那麼如果我把這些諸葛亮的決策結果平均下來,會產生很好地效果。

Boosting的原理就好比每個人(weak learners)都是一個比平民(隨機猜測)厲害點的人物,我這裡有個大問題需要這些人解決一下,Boosting派出第1個人解決了一些問題,但是剩下了一些難題,接著Boosting派出第2個人主要解決第1個人剩下地難題,接著Boosting派出第3個人解決第2個人剩下地難題,依此類推……到最後,Boosting一定可以很好地解決問題。從上面地例子中,我們也看出了每個人有很高地偏差(更適合解決某一部分問題,也就是不能很好地擬合訓練集),但是,Boosting通過上面的手段不僅平均了大家的智慧而且還減小了某個人偏差的問題。

對於穩定的模型來說,Bagging並不會工作地很好,而Boosting可得會有幫助。如果在訓練集上有noisy資料,Boosting會很快地過擬合,降低模型的效能,而Bagging不存在這樣地問題。

一句話總結:在實際應用中,Bagging通常都會有幫助,而Boosting是一把利劍,用好的情況下肯定會比Bagging出色,但是用不好很可能會傷到自己。


相關文章