【機器學習】模型融合方法概述

【機器學習】模型融合方法概述

我理解的Kaggle比賽中提升成績主要有3個地方html

  1. 特徵工程
  2. 調參
  3. 模型融合

以前每次打比賽都只作了前兩部分,最後的模型融合就是簡單的加權平均,對於進階的Stacking方法一直沒嘗試,這幾天摸索了一下仍是把Stacking方法給弄懂了。(本文重點講解Stacking,Bagging和Boosting有不少權威的好教程,因此不詳細介紹)最先的Stacking思想早些年就有論文發表,可是應用Stacking方法到比賽中的相關文章仍是少之甚少,這有兩篇HUMAN ENSEMBLE LEARNING講的很棒,可是以前由於理解不到位,有幾處卡住了。在@Wille 的文章如何在 Kaggle 首戰中進入前 10%中Stacking只是做爲一部分提到。所以決定本身寫一篇關於模型融合的文章。本文不涉及到各個算法原理層次的深度,目的在於從宏觀上幫助理解這幾個模型融合方法。python

1、Voting

模型融合其實也沒有想象的那麼高大上,從最簡單的Voting提及,這也能夠說是一種模型融合。假設對於一個二分類問題,有3個基礎模型,那麼就採起投票制的方法,投票多者肯定爲最終的分類。

2、Averaging

對於迴歸問題,一個簡單直接的思路是取平均。稍稍改進的方法是進行加權平均。權值能夠用排序的方法肯定,舉個例子,好比A、B、C三種基本模型,模型效果進行排名,假設排名分別是1,2,3,那麼給這三個模型賦予的權值分別是3/六、2/六、1/6
這兩種方法看似簡單,其實後面的高級算法也能夠說是基於此而產生的,Bagging或者Boosting都是一種把許多弱分類器這樣融合成強分類器的思想。

3、Bagging

Bagging就是採用有放回的方式進行抽樣,用抽樣的樣本創建子模型,對子模型進行訓練,這個過程重複屢次,最後進行融合。大概分爲這樣兩步:
  1. 重複K次

  • 有放回地重複抽樣建模
  • 訓練子模型

2.模型融合
    • 分類問題:voting

  • 迴歸問題:average

Bagging算法不用咱們本身實現,隨機森林就是基於Bagging算法的一個典型例子,採用的基分類器是決策樹。R和python都集成好了,直接調用。git

4、Boosting

Bagging算法能夠並行處理,而Boosting的思想是一種迭代的方法,每一次訓練的時候都更加關心分類錯誤的樣例,給這些分類錯誤的樣例增長更大的權重,下一次迭代的目標就是可以更容易辨別出上一輪分類錯誤的樣例。最終將這些弱分類器進行加權相加。引用加州大學歐文分校Alex Ihler教授的兩頁 PPT


一樣地,基於Boosting思想的有AdaBoost、GBDT等,在R和python也都是集成好了直接調用。
PS:理解了這兩點,面試的時候關於Bagging、Boosting的區別就能夠說上來一些,問Randomfroest和AdaBoost的區別也能夠從這方面入手回答。也算是留一個小問題,隨機森林、Adaboost、GBDT、XGBoost的區別是什麼?

5、Stacking

Stacking方法其實弄懂以後應該是比Boosting要簡單的,畢竟小几十行代碼能夠寫出一個Stacking算法。我先從一種「錯誤」可是容易懂的Stacking方法講起。
Stacking模型本質上是一種分層的結構,這裏簡單起見,只分析二級Stacking.假設咱們有3個基模型M一、M二、M3。

1. 基模型M1,對訓練集train訓練,而後用於預測train和test的標籤列,分別是P1,T1
\begin{pmatrix}
\vdots  \\
P_1   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
\begin{pmatrix}
\vdots  \\
T_1   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
對於M2和M3,重複相同的工做,這樣也獲得P2,T2,P3,T3。github

2. 分別把P1,P2,P3以及T1,T2,T3合併,獲得一個新的訓練集和測試集train2,test2.\begin{pmatrix}
\vdots  \\
P_1   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
\begin{pmatrix}
\vdots  \\
P_2   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
\begin{pmatrix}
\vdots  \\
P_3   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
\implies
\overbrace{\begin{pmatrix}
\vdots &\vdots  &\vdots \\
P_1 & P_2 &P_3   \\
\vdots  &\vdots &\vdots \\
\vdots &\vdots &\vdots  \\
\end{pmatrix}}^{train2}web

\begin{pmatrix}
\vdots  \\
T_1   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
\begin{pmatrix}
\vdots  \\
T_2   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
\begin{pmatrix}
\vdots  \\
T_3   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
\implies
\overbrace{\begin{pmatrix}
\vdots &\vdots  &\vdots \\
T_1 & T_2 &T_3   \\
\vdots  &\vdots &\vdots \\
\vdots &\vdots &\vdots  \\
\end{pmatrix}}^{test2}


3. 再用第二層的模型M4訓練train2,預測test2,獲得最終的標籤列。 \overbrace{\begin{pmatrix}
\vdots &\vdots  &\vdots \\
P_1 & P_2 &P_3   \\
\vdots  &\vdots &\vdots \\
\vdots &\vdots &\vdots  \\
\end{pmatrix}}^{train2}
\overbrace{\implies}^{train}
\overbrace{\begin{pmatrix}
\vdots &\vdots  &\vdots \\
T_1 & T_2 &T_3   \\
\vdots  &\vdots &\vdots \\
\vdots &\vdots &\vdots  \\
\end{pmatrix}}^{test2}
\overbrace{\implies}^{predict}
\begin{pmatrix}
\vdots  \\
pred   \\
\vdots  \\
\vdots  \\
\end{pmatrix}

Stacking本質上就是這麼直接的思路,可是這樣確定是不行的,問題在於P1的獲得是有問題的,用整個訓練集訓練的模型反過來去預測訓練集的標籤,毫無疑問過擬合是很是很是嚴重的,所以如今的問題變成了如何在解決過擬合的前提下獲得P一、P二、P3,這就變成了熟悉的節奏——K折交叉驗證。咱們以2折交叉驗證獲得P1爲例,假設訓練集爲4行3列面試

\begin{pmatrix}
a_{11} & a_{12} &a_{13}  \\
a_{21} & a_{22} &a_{23}  \\
a_{31} & a_{32} &a_{33}  \\
a_{41} & a_{42} &a_{43}  \\
\end{pmatrix}

將其劃分爲2部分算法

\overbrace{
\begin{pmatrix}
a_{11} & a_{12} &a_{13}  \\
a_{21} & a_{22} &a_{23}  \\
\end{pmatrix}
}^{traina}

\overbrace{
\begin{pmatrix}
a_{31} & a_{32} &a_{33}  \\
a_{41} & a_{42} &a_{43}  \\
\end{pmatrix}
}^{trainb}

用traina訓練模型M1,而後在trainb上進行預測獲得preb3和pred4
\overbrace{
\begin{pmatrix}
a_{11} & a_{12} &a_{13}  \\
a_{21} & a_{22} &a_{23}  \\
\end{pmatrix}
}^{traina}
\overbrace{\implies}^{train}
\overbrace{
\begin{pmatrix}
a_{31} & a_{32} &a_{33}  \\
a_{41} & a_{42} &a_{43}  \\
\end{pmatrix}
}^{trainb}
\overbrace{\implies}^{predict}
\begin{pmatrix}
pred3  \\
pred4   \\
\end{pmatrix}
在trainb上訓練模型M1,而後在traina上進行預測獲得pred1和pred2
\overbrace{
\begin{pmatrix}
a_{31} & a_{32} &a_{33}  \\
a_{41} & a_{42} &a_{43}  \\
\end{pmatrix}
}^{trainb}
\overbrace{\implies}^{train}
\overbrace{
\begin{pmatrix}
a_{11} & a_{12} &a_{13}  \\
a_{21} & a_{22} &a_{23}  \\
\end{pmatrix}
}^{traina}
\overbrace{\implies}^{predict}
\begin{pmatrix}
pred1  \\
pred2   \\
\end{pmatrix}
而後把兩個預測集進行拼接
\begin{pmatrix}
pred1  \\
pred2   \\
\end{pmatrix}
+
\begin{pmatrix}
pred3  \\
pred4   \\
\end{pmatrix}
=
\begin{pmatrix}
pred1  \\
pred2   \\
pred3  \\
pred4   \\
\end{pmatrix}
=
\begin{pmatrix}
\vdots  \\
P_1   \\
\vdots  \\
\vdots  \\
\end{pmatrix}
對於測試集T1的獲得,有兩種方法。注意到剛剛是2折交叉驗證,M1至關於訓練了2次,因此一種方法是每一次訓練M1,能夠直接對整個test進行預測,這樣2折交叉驗證後測試集至關於預測了2次,而後對這兩列求平均獲得T1。
或者直接對測試集只用M1預測一次直接獲得T1。
P一、T1獲得以後,P二、T二、P三、T3也就是一樣的方法。理解了2折交叉驗證,對於K折的狀況也就理解也就很是順利了。因此最終的代碼是兩層循環,第一層循環控制基模型的數目,每個基模型要這樣去獲得P1,T1,第二層循環控制的是交叉驗證的次數K,對每個基模型,會訓練K次最後拼接獲得P1,取平均獲得T1。這下再把@Wille博文中的那張圖片放出來就很容易看懂了。app


該圖是一個基模型獲得P1和T1的過程,採用的是5折交叉驗證,因此循環了5次,拼接獲得P1,測試集預測了5次,取平均獲得T1。而這僅僅只是第二層輸入的一列/一個特徵,並非整個訓練集。再分析做者的代碼也就很清楚了。也就是剛剛提到的兩層循環。

 

python實現

用了一個泰坦尼克號的嘗試了一下代碼,從頭至尾都是能夠運行的。代碼放在 Github,針對其中一段關鍵的稍做分析
def get_oof(clf, x_train, y_train, x_test):
 oof_train = np.zeros((ntrain,))  
 oof_test = np.zeros((ntest,))
 oof_test_skf = np.empty((NFOLDS, ntest))  #NFOLDS行,ntest列的二維array
 for i, (train_index, test_index) in enumerate(kf): #循環NFOLDS次
     x_tr = x_train[train_index]
     y_tr = y_train[train_index]
     x_te = x_train[test_index]
     clf.fit(x_tr, y_tr)
     oof_train[test_index] = clf.predict(x_te)
     oof_test_skf[i, :] = clf.predict(x_test)  #固定行填充,循環一次,填充一行
 oof_test[:] = oof_test_skf.mean(axis=0)  #axis=0,按列求平均,最後保留一行
 return oof_train.reshape(-1, 1), oof_test.reshape(-1, 1)  #轉置,從一行變爲一列
這裏只實現了針對一個基模型作K折交叉驗證,由於P1和T1都是多行一列的結構,這裏是先存儲爲一行多列,最後進行轉置。
可是Stacking方法其實在R中也有集成好的能夠調用。

caretEnsemble包下的caretStack()方法

關鍵代碼以下:
algorithmList <- c('lda', 'rpart', 'glm', 'knn', 'svmRadial')
stackControl <- trainControl(method="repeatedcv", number=10, repeats=3, savePredictions=TRUE, classProbs=TRUE)
stack.glm <- caretStack(models, method="glm", metric="Accuracy", trControl=stackControl)
有一篇 博文講的比較詳細

h2o包的h2o.stack()方法

關鍵代碼以下:
nfolds <- 5  
glm1 <- h2o.glm(x = x, y = y, family = family,
             training_frame = train,
             nfolds = nfolds,
             fold_assignment = "Modulo",
             keep_cross_validation_predictions = TRUE)
gbm1 <- h2o.gbm(x = x, y = y, distribution = "bernoulli",
             training_frame = train,
             seed = 1,
             nfolds = nfolds,
             fold_assignment = "Modulo",
             keep_cross_validation_predictions = TRUE)
rf1 <- h2o.randomForest(x = x, y = y, # distribution not used for RF
                     training_frame = train,
                     seed = 1,
                     nfolds = nfolds,
                     fold_assignment = "Modulo",
                     keep_cross_validation_predictions = TRUE)
dl1 <- h2o.deeplearning(x = x, y = y, distribution = "bernoulli",
                     training_frame = train,
                     nfolds = nfolds,
                     fold_assignment = "Modulo",
                     keep_cross_validation_predictions = TRUE)
models <- list(glm1, gbm1, rf1, dl1)
metalearner <- "h2o.glm.wrapper"
stack <- h2o.stack(models = models,
                response_frame = train[,y],
                metalearner = metalearner,
                seed = 1,
                keep_levelone_data = TRUE)
# Compute test set performance:
perf <- h2o.ensemble_performance(stack, newdata = test)
詳情見 h2o的Github網站
最後放一張H2O分享的圖片總結一下
相關文章
相關標籤/搜索