1. 程式人生 > 實用技巧 >整合學習

整合學習

整合學習(Bagging,Boosting,Stacking與Blending)

目錄

整合學習中的概念

1、個體學習器:整合學習的一般結構都是先產生一組個體學習器(individual learner),在用某種策略將他們結合起來,個體學習器通常由一個現有的學習演算法從訓練資料中產生。

2、基學習器:如果整合中只包含同種型別的個體學習器,例如決策樹整合中全都是決策樹,這樣的整合是‘同質’(homogeneous)的,同質整合中的個體學習器又稱‘基學習器’,相應的學習演算法又稱基學習演算法。

3、元件學習器:整合也可以是包含不同型別的個體學習器,例如決策樹和神經網路,這樣的整合是‘異質’(heterogenous)的,異質整合中的個體學習器稱為元件學習器或者直接稱為個體學習器。

按照個體學習器之間是否存在依賴關係可以分為兩類:

  • 個體學習器之間存在強依賴關係,一系列個體學習器基本必須序列生成,代表是boosting系列演算法。
  • 個體學習器之間不存在強依賴關係 ,一系列個體學習器可以並行生成,代表是bagging系列演算法。

整合學習的類別

  • 基礎整合技術

    • 最大投票(Max Voting)法
    • 平均(Averaging)法
    • 加權平均(Weighted Average)法
  • 高階整合技術

    • 堆疊(Stacking)
    • 混合(Blending)
    • Bagging
    • 提升(Boosting)
  • 基於Bagging和Boosting的演算法

    • Bagging meta-estimator
    • 隨機森林
    • AdaBoost
    • GBM
    • XGB
    • Light GBM
    • CatBoost

整合學習之結合策略

假設整合中包含 T 個基學習器 \(h_1,h_2,...,h_T\) ,其中 \(h_i\) 在示例 \(x\) 上的輸出為 \(h_i(x)\) 。那麼對 \(h_i\) 進行結合的常見策略有以下幾種:

平均(Averaging)法

\[H(x)=\frac{1}{T}\sum_1^Th_i(x) \]

加權平均(Weighted Average)法

\[H(x)=\sum_1^Tw_ih_i(x) \]

其中 \(w_i\) 是個體學習器 \(h_i\) 的權重, \(w_i\ge 0, \sum_1^Tw_i=1\)

一般而言,在個體學習器的效能相差較大時宜使用加權平均法,而在個體學習器效能相近時宜使用簡單平均法。

投票法

對於分類問題的預測,我們通常使用的是投票法。假設我們的預測類別是 \(\{c_1,c_2,...,c_K\}\) ,對於任意一個預測樣本 \(x\) ,我們的 \(T\) 個弱學習器的預測結果分別是 \((h_1(x),h_2(x),...,h_T(x))\)

最簡單的投票法是相對多數投票法(plurality voting),也就是我們常說的少數服從多數,也就是 \(T\) 個弱學習器的對樣本 \(x\) 的預測結果中,數量最多的類別 \(c_i\) 為最終的分類類別。如果不止一個類別獲得最高票,則隨機選擇一個做最終類別。

稍微複雜的投票法是絕對多數投票法(majority voting),也就是我們常說的要票過半數。在相對多數投票法的基礎上,不光要求獲得最高票,還要求票過半數。否則會拒絕預測。

更加複雜的是加權投票法(weighted voting),和加權平均法一樣,每個弱學習器的分類票數要乘以一個權重,最終將各個類別的加權票數求和,最大的值對應的類別為最終類別。

接下來使用sklearn中封裝的votingclassifier類實現投票

相對多數投票法(Hard Voting):

from sklearn.ensemble import VotingClassifier

voting_clf = VotingClassifier(estimators=[
    ('log_reg', LogisticRegression()),
    ('svm_clf', SVC()),
    ('dt_clf', DecisionTreeClassifier()),
], voting='hard')

voting_clf.fit(x_train, y_train)
voting_clf.score(x_test, y_test)
# 0.896

加權投票法(Soft Voting)

voting_clf2 = VotingClassifier(estimators=[
    ('log_reg', LogisticRegression()),
    ('svm_clf', SVC(probability=True)),
    ('dt_clf', DecisionTreeClassifier(random_state=666)),
], voting='soft')

voting_clf2.fit(x_train, y_train)
voting_clf2.score(x_test, y_test)
# 0.912

多數投票案例+程式碼

載入相關庫:

## 載入相關庫
from sklearn.datasets import load_iris   # 載入資料
from sklearn.model_selection import train_test_split  # 切分訓練集與測試集
from sklearn.preprocessing import StandardScaler  # 標準化資料
from sklearn.preprocessing import LabelEncoder   # 標籤化分類變數
from sklearn.metrics import roc_curve
from sklearn.metrics import auc

初步處理資料:

iris = load_iris()
X,y = iris.data[50:,[1,2]],iris.target[50:]
le = LabelEncoder()
y = le.fit_transform(y)
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.5,random_state=1,stratify=y)

我們使用訓練集訓練三種不同的分類器:邏輯迴歸 + 決策樹 + k-近鄰分類器:

from sklearn.model_selection import cross_val_score   # 10折交叉驗證評價模型
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline   # 管道簡化工作流
clf1 = LogisticRegression(penalty='l2',C=0.001,random_state=1)
clf2 = DecisionTreeClassifier(max_depth=1,criterion='entropy',random_state=0)
clf3 = KNeighborsClassifier(n_neighbors=1,p=2,metric="minkowski")
pipe1 = Pipeline([['sc',StandardScaler()],['clf',clf1]])
pipe3 = Pipeline([['sc',StandardScaler()],['clf',clf3]])
clf_labels = ['Logistic regression','Decision tree','KNN']
print('10-folds cross validation :\n')
for clf,label in zip([pipe1,clf2,pipe3],clf_labels):
    scores = cross_val_score(estimator=clf,X=X_train,y=y_train,cv=10,scoring='roc_auc')
    print("ROC AUC: %0.2f(+/- %0.2f)[%s]"%(scores.mean(),scores.std(),label))

輸出AUC:

我們使用Majority Vote Classifier整合:

from sklearn.ensemble import VotingClassifier
mv_clf = VotingClassifier(estimators=[('pipe1',pipe1),('clf2',clf2),('pipe3',pipe3)],voting='soft')
clf_labels += ['Majority Vote Classifier']
all_clf = [pipe1,clf2,pipe3,mv_clf]
print('10-folds cross validation :\n')
for clf,label in zip(all_clf,clf_labels):
    scores = cross_val_score(estimator=clf,X=X_train,y=y_train,cv=10,scoring='roc_auc')
    print("ROC AUC: %0.2f(+/- %0.2f)[%s]"%(scores.mean(),scores.std(),label))

輸出AUC:

對比結果,可以得知多數投票方式的分類演算法,抗差能力更強。

使用ROC曲線評估整合分類器:

colors = ['black','orange','blue','green']
linestyles = [':','--','-.','-']
plt.style.use('seaborn')
plt.figure(figsize=(10,6))
for clf,label,clr,ls in zip(all_clf,clf_labels,colors,linestyles):
    y_pred = clf.fit(X_train,y_train).predict_proba(X_test)[:,1]
    fpr,tpr,trhresholds = roc_curve(y_true=y_test,y_score=y_pred)
    roc_auc = auc(x=fpr,y=tpr)
    plt.plot(fpr,tpr,color=clr,linestyle=ls,label='%s (auc=%0.2f)'%(label,roc_auc))
plt.legend(loc='lower right')
plt.plot([0,1],[0,1],linestyle='--',color='gray',linewidth=2)
plt.xlim([-0.1,1.1])
plt.ylim([-0.1,1.1])
plt.xlabel('False positive rate (FPR)')
plt.xlabel('True positive rate (TPR)')
plt.show()

學習法 \(\large\color\red stacking\)

上面的方法都是對弱學習器的結果做平均或者投票,相對比較簡單,但是可能學習誤差較大,於是就有了學習法這種方法,對於學習法,代表方法是\(\large\color\red stacking\),當使用\(\large\color\red stacking\)的結合策略時, 我們不是對弱學習器的結果做簡單的邏輯處理,而stacking是再加上一層權重學習器(Meta Learner),基學習器(Base learner)的結果作為該權重學習器的輸入,得到最終結果。

在這種情況下,我們將弱學習器(Base learner)稱為初級學習器,將用於結合的學習器(Meta Learner)稱為次級學習器。對於測試集,我們首先用初級學習器預測一次,得到次級學習器的輸入樣本,再用次級學習器預測一次,得到最終的預測結果。

如果將其更加複雜化一些,學習器分為三層,第一層訓練layer1的三個學習器,第二層訓練layer2的學習器,第三層跟layer2的學習器的預測結果一起訓練得到layer3的學習器,這樣得到最終的學習結果。此時這樣的網路跟人工神經網路就有點類似了。

Bagging

Bagging的演算法原理如下:

注意:套袋方法與投票方法的不同:

投票機制在訓練每個分類器的時候都是用相同的全部樣本,而Bagging方法則是使用全部樣本的一個隨機抽樣,每個分類器都是使用不同的樣本進行訓練。其他都是跟投票方法一模一樣!

演算法基本流程:

  • (1)對訓練集利用自助取樣法進行\(T\)次隨機取樣,每次取樣得到 \(m\) 個樣本的取樣集;
  • (2)對於這 \(T\) 個取樣集,我們可以分別獨立的訓練出 \(T\) 個基學習器;
  • (3)再對這 \(T\) 個基學習器通過集合策略來得到最終的強學習器。

值得注意的是這裡的隨機取樣採用的是自助取樣法(Bootstrap sampling),自助取樣法是一種有放回的取樣。即對於 \(m\) 個樣本的原始訓練集,我們每次先隨機採集一個樣本放入取樣集,接著把該樣本放回,這樣採集 \(m\) 次,最終可以得到 \(m\)個樣本的取樣集,由於是隨機取樣,這樣每次的取樣集是和原始訓練集不同的,和其他取樣集也是不同的。

對於一個樣本,它每次被採集到的概率是 \(\frac1m\) 。不被採集到的概率為 \(1-\frac1m\)。如果\(m\)次取樣都沒有被採集中的概率是 \((1-\frac1m)^m\)。則 $ \lim \limits_{m\rightarrow +∞} (1-\frac{1}{m})^m \to \frac{1}{e} \approx 0.368$ ,即當抽樣的樣本量足夠大時,在bagging的每輪隨機取樣中,訓練集中大約有36.8%的資料沒有被採集中。對於這部分大約36.8%的沒有被取樣到的資料,我們常常稱之為袋外資料(Out Of Bag, 簡稱OOB)。這些資料未參與訓練集模型的擬合,可以用來檢測模型的泛化能力。

bagging對於弱學習器最常用的一般也是決策樹和神經網路。bagging的集合策略也比較簡單,對於分類問題,通常使用相對多數投票法。對於迴歸問題,通常使用算術平均法。

Bagging案例+程式碼

我們使用葡萄酒資料集進行建模(資料處理):

df_wine = pd.read_csv('https://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]  # drop 1 class      
y = df_wine['Class label'].values
X = df_wine[['Alcohol','OD280/OD315 of diluted wines']].values
y = le.fit_transform(y)
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=1,stratify=y)

我們使用單一決策樹分類:

tree = DecisionTreeClassifier(criterion='entropy',random_state=1,max_depth=None)   #選擇決策樹為基本分類器
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))

我們使用BaggingClassifier分類:

from sklearn.ensemble import BaggingClassifier
tree = DecisionTreeClassifier(criterion='entropy',random_state=1,max_depth=None)   #選擇決策樹為基本分類器
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)
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))

我們可以對比兩個準確率,測試準確率較之決策樹得到了顯著的提高

我們來畫圖對比下這兩個分類方法上的差異:

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))
plt.style.use('seaborn')
f, axarr = plt.subplots(nrows=1, ncols=2,sharex='col',sharey='row',figsize=(12, 6))
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,cmap="cool")
    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='green', marker='o')
    axarr[idx].set_title(tt)
axarr[0].set_ylabel('Alcohol', fontsize=12)
plt.tight_layout()
plt.text(0, -0.2,s='OD280/OD315 of diluted wines',ha='center',va='center',fontsize=12,transform=axarr[1].transAxes)
plt.show()

從結果圖看起來,三個節點深度的決策樹分段線性決策邊界在Bagging整合中看起來更加平滑。

  • Bagging meta-estimator
  • 隨機森林

Boosting

在我們進一步討論之前,這裡有另一個問題:如果第一個模型錯誤地預測了某一個數據點,然後接下來的模型(可能是所有模型),將預測組合起來會提供更好的結果嗎?Boosting就是來處理這種情況的。

Boosting是一個順序過程,每個後續模型都會嘗試糾正先前模型的錯誤。後續的模型依賴於之前的模型。接下來一起看看boosting的工作方式:

第一步:從原始資料集建立一個子集。

第二步:最初,所有資料點都具有相同的權重。

第三步:在此子集上建立基礎模型。

第四步:該模型用於對整個資料集進行預測。

第五步:使用實際值和預測值計算誤差。

第六步:預測錯誤的點獲得更高的權重。(這裡,三個錯誤分類的藍色加號點將被賦予更高的權重)

第七步:建立另一個模型並對資料集進行預測(此模型嘗試更正先前模型中的錯誤)。

第八步:類似地,建立多個模型,每個模型校正先前模型的錯誤。

第九步:最終模型(強學習器)是所有模型(弱學習器)的加權平均值。

因此,boosting演算法結合了許多弱學習器來形成一個強學習器。單個模型在整個資料集上表現不佳,但它們在資料集的某些部分上表現很好。因此,每個模型實際上提升了整合的整體效能。

boosting演算法主要有AdaBoost,GBM,XGB,Light GBM,CatBoost。

bagging是減少variance,而boosting是減少bias

Bagging對樣本重取樣,對每一重取樣得到的子樣本集訓練一個模型,最後取平均。由於子樣本集的相似性以及使用的是同種模型,因此各模型有近似相等的bias和variance(事實上,各模型的分佈也近似相同,但不獨立)。

若各子模型獨立,則有

\[V(\frac{\sum_{i=1}^{n}X_i}{n}) = \frac{1}{n^2}V(\sum_{i=1}^{n}X_i)= \frac{1}{n^2}\sum_{i=1}^{n}V(X_i) \]

若各子模型完全相同(也就是\(X_1=X_2=...=X_n\),則

\[V(\frac{\sum_{i=1}^{n}X_i}{n}) = V(\frac{n*X_1}{n}) = V(X_1)=\frac{1}{n}\sum_{i=1}^{n}V(X_i) \]

對比兩個公式,如果降低分類器之間的相關度,可以最多降低到直接訓練多個分類器的方差的 \(\frac{1}{n}\) 。bagging方法得到的各子模型是有一定相關性的,屬於上面兩個極端狀況的中間態,因此可以一定程度降低variance。為了進一步降低variance,Random forest通過隨機選取變數子集做擬合的方式de-correlated了各子模型(樹),使得variance進一步降低。

boosting從優化角度來看,是用forward-stagewise這種貪心法去最小化損失函式\(L(y, \sum_i a_i f_i(x))\)。例如,常見的AdaBoost即等價於用這種方法最小化exponential loss:\(L(y,f(x))=exp(-yf(x))\)

所謂forward-stagewise,就是在迭代的第n步,求解新的子模型f(x)及步長a(或者叫組合係數),來最小化\(L(y,f_{n-1}(x)+af(x))\),這裡\(f_{n-1}(x)\) 是前n-1步得到的子模型的和。因此boosting是在sequential地最小化損失函式,其bias自然逐步下降。

但由於是採取這種sequential、adaptive的策略,各子模型之間是強相關的,於是子模型之和並不能顯著降低variance。所以說boosting主要還是靠降低bias來提升預測精度。

Stacking與Blending

Stacking整合演算法可以理解為一個兩層的整合,第一層含有一個分類器,把預測的結果(元特徵)提供給第二層, 而第二層的分類器通常是邏輯迴歸,他把一層分類器的結果當做特徵做擬合輸出預測結果。

標準的Stacking,也叫Blending如下圖:

但是,標準的Stacking會導致資訊洩露,所以推薦以下Satcking演算法:

為了減少過擬合的風險,一般使用交叉驗證或者留一法,用之前未使用的樣本來產生次級學習器的訓練樣本。

多樣性

誤差——分歧分解

個體學習器準確率越高,多樣性越大則整合越好。

多樣性度量

常見的一些多樣性度量有不合度量(disagreement measure),相關係數(correlation coefficient),Q-統計量和Kappa統計量。

多樣性增強

  • 資料樣本擾動:基於取樣法產生不同資料子集
  • 輸入屬性擾動:基於不同屬性子集。不過如果資料屬性不多則效果不好。
  • 輸入表示擾動:改變標籤。例如隨機改變一些樣本標記,或將分類任務變為迴歸任務,或將原任務拆解為多個子任務
  • 演算法引數擾動:使用不同的引數組合進行訓練

參考資料

《python機器學習》Sebastian Raschka Vahid Mirjalili BIRMINGHAM

機器學習整合學習與模型融合

A Comprehensive Guide to Ensemble Learning

整合學習原理小結 - 劉建平Pinard - 部落格園

Xtecher | 一文讀懂整合學習(附學習資源)

整合學習(Ensemble Learning)

一文讀懂整合學習 - CSDN部落格

《機器學習》周志華

李航《統計機器學習》

為什麼說bagging是減少variance,而boosting是減少bias?