上次我们介绍了Blending算法的优缺点,而Stack算法的出现就是解决了Blending的缺点并进行改进。
简单来说,Stacking就是当用初始训练数据学习出若干个基学习器后,将这几个学习器的预测结果作为新的训练集,来学习一个新的学习器。我们举个例子来具体感受Stacking处理问题的细节。
(1)将所有数据集生成测试集和训练集(假如训练集为10000条,测试集为2500条),那么第一层会进行5折交叉验证(折数可以自己定),使用训练集中的8000条作为训练集,剩余2000行作为验证集。
(2)通过5折交叉验证,我们可以在2000行的验证集得到52000个的预测结果,在2500条测试集得到5 2500条测试集的预测结果。
(3)将验证集的5* 2000条预测结果拼接成10000行长的矩阵,标记为 A 1 A_{1} A1,而对于5* 2500行的测试集的预测结果进行加权平均,得到一个2500一列的矩阵,标记为 B 1 B_{1} B1 。
(4)在(3)中,一个基模型在数据集上的预测结果为 A 1 A_{1} A1、 B 1 B_{1} B1 ,这样当我们对3个基模型进行集成的话,相于得到了 A 1 A_{1} A1、 B 1 B_{1} B1 、 A 2 A_{2} A2、 B 2 B_{2} B2、 A 3 A_{3} A3、 B 3 B_{3} B3六个矩阵。将 A 1 A_{1} A1、A_{2} 、 、 、A_{3}$ 并列(横向拼接)在一起成10000行3列的矩阵作为第二层的training data, B 1 B_{1} B1 、 B 2 B_{2} B2、 B 3 B_{3} B3合并在一起成2500行3列的矩阵作为第二层的testing data。
(5)第二层的训练便基于每个基础模型的预测结果作为特征(三个特征),从而得到最后的预测结果。
为了方便读者复现代码,还是优先考虑sklearn自带数据集,本次实战采用乳腺癌数据。
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from mlxtend.classifier import StackingCVClassifier
## 读取数据
cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
print('data shape: {0}; no. positive: {1}; no. negative: {2}'.format(
X.shape, y[y==1].shape[0], y[y==0].shape[0]))
# 1. 简单堆叠3折CV分类
RANDOM_SEED = 42
clf1 = KNeighborsClassifier(n_neighbors=1)
clf2 = RandomForestClassifier(random_state=RANDOM_SEED)
clf3 = GaussianNB()
lr = LogisticRegression()
# Starting from v0.16.0, StackingCVRegressor supports
# `random_state` to get deterministic result.
sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3], # 第一层分类器
meta_classifier=lr, # 第二层分类器
random_state=RANDOM_SEED)
print('3-fold cross validation:\n')
for clf, label in zip([clf1, clf2, clf3, sclf], ['KNN', 'Random Forest', 'Naive Bayes','StackingClassifier']):
scores = cross_val_score(clf, X, y, cv=3, scoring='accuracy')
print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))
# 2.使用概率作为元特征
clf1 = KNeighborsClassifier(n_neighbors=1)
clf2 = RandomForestClassifier(random_state=1)
clf3 = GaussianNB()
lr = LogisticRegression()
sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3],
use_probas=True, # 使用概率作为元特征
meta_classifier=lr,
random_state=42)
print('3-fold cross validation:')
for clf, label in zip([clf1, clf2, clf3, sclf],
['KNN',
'Random Forest',
'Naive Bayes',
'StackingClassifier']):
scores = cross_val_score(clf, X, y,
cv=3, scoring='accuracy')
print("Accuracy: %0.2f (+/- %0.2f) [%s]"
% (scores.mean(), scores.std(), label))
注意:网格搜索在大数据集上是非常耗时
# 3. 堆叠5折CV分类与网格搜索(结合网格搜索调参优化)
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from mlxtend.classifier import StackingCVClassifier
# Initializing models
clf1 = KNeighborsClassifier(n_neighbors=1)
clf2 = RandomForestClassifier(random_state=RANDOM_SEED)
clf3 = GaussianNB()
lr = LogisticRegression()
sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3],
meta_classifier=lr,
random_state=42)
params = {'kneighborsclassifier__n_neighbors': [1, 5],
'randomforestclassifier__n_estimators': [10, 50],
'meta_classifier__C': [0.1, 10.0]}
grid = GridSearchCV(estimator=sclf,
param_grid=params,
cv=5,
refit=True)
grid.fit(X, y)
cv_keys = ('mean_test_score', 'std_test_score', 'params')
for r, _ in enumerate(grid.cv_results_['mean_test_score']):
print("%0.3f +/- %0.2f %r"
% (grid.cv_results_[cv_keys[0]][r],
grid.cv_results_[cv_keys[1]][r] / 2.0,
grid.cv_results_[cv_keys[2]][r]))
print('Best parameters: %s' % grid.best_params_)
print('Accuracy: %.2f' % grid.best_score_)