Stacking集成模型的原理和实现详解
集成学习一般有两种方式:
还有一种方式就是 Stacking,它结合了 Boosting 和 Bagging 两种集成方式,Stacking 是利用多个基学习器学习原数据,然后将这几个基学习器学习到的数据交给第二层模型进行拟合。

图 1 Stacking思想原理
图 1 就是 Stacking 思想的原理示意图,为了防止模型过拟合,所以使用 K 折交叉验证。
图 1 首先将特征 x 和标签 y 分别输入到 3 个模型中,然后这 3 个模型分别学习,接着针对 x 给出预测值,并给出概率。此处使用预测值,然后将 3 个模型的输出值按照列的方式进行堆叠,这就形成了新的样本数据,接着将新的样本数据作为标签 x,新数据的标签仍然为原数据的标签 y,将新数据的标签 x、y 交给第二层的模型进行拟合,这个模型是用来融合前一轮 3 个模型的结果。
Stacking 易产生过拟合,所以需对上述方法进行改进,使用 K 折交叉验证的方式,不同之处就是图 1 的每个模型训练了所有的数据,然后输出 y 形成新的数据,使用 K 折交叉验证,每次只训练 K-1 折,然后将剩下 1 折的预测值作为新的数据,这就有效地防止了过拟合。
如果每个模型训练所有的数据,然后用这个模型去预测 y 值,那么生成新数据的 y 非常精确,和真实值差不多,为了增强模型的泛化能力,每次只训练其中一部分数据,然后用剩余部分数据进行预测,如下图所示。

图 2 增强模型的泛化能力
图 2 先利用 K 折交叉验证,将数据分成 4 折切分,就会形成 4 组数据集,其中黄色(带笑脸)的代表训练集,绿色(带三角形)的为验证集,然后将每组的训练集交给模型进行训练,接着对验证集进行预测,就会得到对应验证集(4 种)的输出,然后将每个模型对各自组的验证集预测的结果按照行的方式进行堆叠,会获得完整样本数据的预测值。这只是针对一个模型,与学习器同理,每个模型按照这个方式获得预测值,然后将其按照列合并。
【实例】利用 Stacking 分析 Boston 数据集回归问题。
下面代码定义第一层模型并训练:
如果直接用 Sklearn 中的线性回归,代码为:
- 第一种为 Boosting 架构,利用基学习器之间串行的方式进行构造强学习器;
- 第二种是 Bagging 架构,通过构造多个独立的模型,然后通过选举或者加权的方式构造强学习器。
还有一种方式就是 Stacking,它结合了 Boosting 和 Bagging 两种集成方式,Stacking 是利用多个基学习器学习原数据,然后将这几个基学习器学习到的数据交给第二层模型进行拟合。
Stacking原理
Stacking 通过模型对原数据拟合的堆叠进行建模,首先通过基学习器学习原数据,然后这几个基学习器都会对原数据进行输出,接着将这几个模型的输出按照列的方式进行堆叠,构成了(m,p)维的新数据,m 代表样本数,p 代表基学习器的个数,然后将新的样本数据交给第二层模型进行拟合,如下图所示:
图 1 Stacking思想原理
图 1 就是 Stacking 思想的原理示意图,为了防止模型过拟合,所以使用 K 折交叉验证。
图 1 首先将特征 x 和标签 y 分别输入到 3 个模型中,然后这 3 个模型分别学习,接着针对 x 给出预测值,并给出概率。此处使用预测值,然后将 3 个模型的输出值按照列的方式进行堆叠,这就形成了新的样本数据,接着将新的样本数据作为标签 x,新数据的标签仍然为原数据的标签 y,将新数据的标签 x、y 交给第二层的模型进行拟合,这个模型是用来融合前一轮 3 个模型的结果。
Stacking 易产生过拟合,所以需对上述方法进行改进,使用 K 折交叉验证的方式,不同之处就是图 1 的每个模型训练了所有的数据,然后输出 y 形成新的数据,使用 K 折交叉验证,每次只训练 K-1 折,然后将剩下 1 折的预测值作为新的数据,这就有效地防止了过拟合。
如果每个模型训练所有的数据,然后用这个模型去预测 y 值,那么生成新数据的 y 非常精确,和真实值差不多,为了增强模型的泛化能力,每次只训练其中一部分数据,然后用剩余部分数据进行预测,如下图所示。

图 2 增强模型的泛化能力
图 2 先利用 K 折交叉验证,将数据分成 4 折切分,就会形成 4 组数据集,其中黄色(带笑脸)的代表训练集,绿色(带三角形)的为验证集,然后将每组的训练集交给模型进行训练,接着对验证集进行预测,就会得到对应验证集(4 种)的输出,然后将每个模型对各自组的验证集预测的结果按照行的方式进行堆叠,会获得完整样本数据的预测值。这只是针对一个模型,与学习器同理,每个模型按照这个方式获得预测值,然后将其按照列合并。
Stacking模型实现
本节以 Boston 数据集为例,利用 Stacking 解决回归问题。【实例】利用 Stacking 分析 Boston 数据集回归问题。
from sklearn import datasets from sklearn.model_selection import KFold from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.ensemble import GradientBoostingRegressor as GBDT from sklearn.ensemble import ExtraTreesRegressor as ET from sklearn.ensemble import RandomForestRegressor as RF from sklearn.ensemble import AdaBoostRegressor as ADA from sklearn.metrics import r2_score import pandas as pd import numpy as np boston=datasets.load_boston() X=boston.data Y=boston.target df=pd.DataFrame(X, columns=boston.feature_names) df.head()运行程序,输出如下:

#数据集划分 X_train, X_test, Y_train, Y_test=train_test_split(X, Y, random_state=123) #标准化 transfer=StandardScaler() X_train=transfer.fit_transform(X_train) X_test=transfer.transform(X_test) print("训练样例数:"+str(X_train.shape[0])) print("测试样例数:"+str(X_test.shape[0])) print("X_train样例:"+str(X_train.shape)) print("Y_train样例:"+str(Y_train.shape))运行程序,输出如下:
训练样例数:379
测试样例数:127
X_train样例:(379, 13)
Y_train样例:(379,)
下面代码定义第一层模型并训练:
model_num=4 models=[GBDT(n_estimators=100), RF(n_estimators=100), ET(n_estimators=100), ADA(n_estimators=100)] #第二层模型训练和测试数据集 #第一层每个模型交叉验证将训练集的预测值作为训练数据, 将测试集预测值的平均作为测试数据 X_train_stack=np.zeros((X_train.shape[0], len(models))) X_test_stack=np.zeros((X_test.shape[0], len(models))) #第一层训练:10折Stacking n_folds=10 kf=KFold(n_splits=n_folds) #kf.split返回划分的索引 for i, model in enumerate(models): X_stack_test_n=np.zeros((X_test.shape[0], n_folds))#(test样本数, 10组索引) for j, (train_index, test_index)in enumerate(kf.split(X_train)): tr_x=X_train[train_index] tr_y=Y_train[train_index] model.fit(tr_x, tr_y) #生成Stacking训练数据集 X_train_stack[test_index, i]=model.predict(X_train[test_index]) X_stack_test_n[:, j]=model.predict(X_test) #生成Stacking测试数据集 X_test_stack[:, i]=X_stack_test_n.mean(axis=1) #查看构建的新数据集 print("X_train_stack样例:"+str(X_train_stack.shape)) print("X_test_stack样例:"+str(X_test_stack.shape)) X_train_stack样例:(379, 4) X_test_stack样例:(127, 4)至此,数据集便构建完毕了,接下来进入第二层模型。第二层定义了一个普通的线性模型。
注意,为了防止过拟合,这个模型应该简单些。
#第二层训练 from keras import models from keras.models import Sequential from keras.layers import Dense model_second=Sequential() model_second.add(Dense(units=1,input_dim=X_train_stack.shape[1])) model_second.compile(loss='mean_squared_error',optimizer='adam') model_second.fit(X_train_stack,Y_train,epochs=500) pred=model_second.predict(X_test_stack) print("R2:",r2_score(Y_test,pred)) Epoch 1/500 379/379[==============================]-0s 600us/step-loss:8292.2789 Epoch 2/500 379/379[==============================]-0s 63us/step-loss:8088.2254 ... 379/379[==============================]-0s 97us/step-loss:10.8623 Epoch 500/500 379/379[==============================]-0s 76us/step-loss:10.8617 R2:0.8617723461580289 #模型评估 from sklearn.metrics import mean_absolute_error Y_test=np.array(Y_test) print('M AE:%f',mean_absolute_error(Y_test,pred)) for i in range(len(Y_test)): print("Real:%f,Predict:%f"%(Y_test[i],pred[i]))运行程序,输出如下:
M AE:%f 2.252628333925262
Real:15.000000,Predict:26.671183
Real:26.600000,Predict:26.258823
Real:45.400000,Predict:47.117538
...
Real:13.600000,Predict:15.016025
Real:22.000000,Predict:21.429218
Real:22.200000,Predict:22.229422
如果直接用 Sklearn 中的线性回归,代码为:
from sklearn.linear_model import LinearRegression model_second=LinearRegression() model_second.fit(X_train_stack,Y_train) pred=model_second.predict(X_test_stack) print("R2:",r2_score(Y_test,pred)) #模型评估 from sklearn.metrics import mean_absolute_error Y_test=np.array(Y_test) print('平均绝对误差:%f',mean_absolute_error(Y_test,pred)) for i in range(len(Y_test)): print("真实:%f,预测:%f"%(Y_test[i],pred[i]))运行程序,输出如下:
R2:0.8378818580721008
平均绝对误差:%f 2.125589477978677
真实:15.000000,预测:37.333546
真实:26.600000,预测:26.755009
...
真实:13.600000,预测:12.868088
真实:22.000000,预测:20.509868
真实:22.200000,预测:21.524763