【机器学习】模型融合方法概述

【机器学习】模型融合方法概述

我理解的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分享的图片总结一下
相关文章
相关标签/搜索