机器学习基础—集成学习Task13(Stacking)

导语:
本次任务的主题是“Stacking集成学习算法”。
竞赛圈中,它(Stacking)可以帮你打败当前学术界性能最好的算法!
学习链接:
集成学习: EnsembleLearning项目-github.

1.Stacking原理

Stacking严格来说并不是一种算法,而是精美而又复杂的,对模型集成的一种策略!
在上一任务中,我们知道Blending在集成的过程中只会用到验证集的数据,即数据划分方式为hold-out作为测试集,并非cv,这样自然可能带来模型的过拟合,为获得更加稳健的模型,自然联想到交叉验证的方式。
Stacking建模过程:(参考:https://www.cnblogs.com/Christina-Notebook/p/10063146.html)

  • 首先将所有数据集生成测试集和训练集(假如训练集为10000,测试集为2500行),那么上层会进行5折交叉检验,使用训练集中的8000条作为训练集,剩余2000行作为验证集(橙色)。
  • 每次验证相当于使用了蓝色的8000条数据训练出一个模型,使用模型对验证集进行验证得到2000条数据,并对测试集进行预测,得到2500条数据,这样经过5次交叉检验,可以得到中间的橙色的5* 2000条验证集的结果(相当于每条数据的预测结果),5* 2500条测试集的预测结果。
  • 接下来会将验证集的5* 2000条预测结果拼接成10000行长的矩阵,标记为 A 1 A_1 A1,而对于5* 2500行的测试集的预测结果进行加权平均,得到一个2500一列的矩阵,标记为 B 1 B_1 B1
  • 上面得到一个基模型在数据集上的预测结果 A 1 A_1 A1 B 1 B_1 B1,这样当我们对3个基模型进行集成的话,相于得到了 A 1 A_1 A1 A 2 A_2 A2 A 3 A_3 A3 B 1 B_1 B1 B 2 B_2 B2 B 3 B_3 B3六个矩阵。
  • 之后我们会将 A 1 A_1 A1 A 2 A_2 A2 A 3 A_3 A3并列在一起成10000行3列的矩阵作为training data, B 1 B_1 B1 B 2 B_2 B2 B 3 B_3 B3合并在一起成2500行3列的矩阵作为testing data,让下层学习器基于这样的数据进行再训练。
  • 再训练是基于每个基础模型的预测结果作为特征(三个特征),次学习器会学习训练如果往这样的基学习的预测结果上赋予权重w,来使得最后的预测最为准确。
  • 机器学习基础—集成学习Task13(Stacking)_第1张图片

实际上,Stacking一般多是两层就够了,多层也是可以的,例如下图:
机器学习基础—集成学习Task13(Stacking)_第2张图片

2.Stacking实践

准备: Stacking库函数安装及参数说明

sklearn并没有直接对Stacking的方法,因此我们需要下载mlxtend工具包(pip install mlxtend),然后调用:
StackingClassifier(classifiers, meta_classifier, use_probas=False, average_probas=False, verbose=0, use_features_in_secondary=False)
参数:
classifiers : 基分类器,数组形式,[cl1, cl2, cl3]. 每个基分类器的属性被存储在类属性 self.clfs_.
meta_classifier : 目标分类器,即将前面分类器合起来的分类器
use_probas : bool (default: False) ,如果设置为True, 那么目标分类器的输入就是前面分类输出的类别概率值而不是类别标签
average_probas : bool (default: False),当上一个参数use_probas = True时需设置,average_probas=True表示所有基分类器输出的概率值需被平均,否则拼接。
verbose : int, optional (default=0)。用来控制使用过程中的日志输出,当 verbose = 0时,什么也不输出, verbose = 1,输出回归器的序号和名字。verbose = 2,输出详细的参数信息。verbose > 2, 自动将verbose设置为小于2的,verbose -2.
use_features_in_secondary : bool (default: False). 如果设置为True,那么最终的目标分类器就被基分类器产生的数据和最初的数据集同时训练。如果设置为False,最终的分类器只会使用基分类器产生的数据训练。

2.1 5折交叉验证Stacking

还是以iris数据集作实例分析,以KNN、Random Forest、Naive Bayes、SVC作为第一层的基分类器,将其预测结果(标签值)作为第二层的特征,第二层分类器为LogisticRegression,采用5折交叉验证(cv=5),代码如下:

from sklearn import datasets
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.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from mlxtend.classifier import StackingCVClassifier
import numpy as np
import pandas as pd 
import matplotlib.pyplot as plt

#加载数据
iris = datasets.load_iris()
X, y = iris.data[:, 1:3], iris.target  #为了后续画出各个模型的决策边界,这里只用了X的两个特征

RANDOM_SEED = 42

clf1 = KNeighborsClassifier(n_neighbors=1)
clf2 = RandomForestClassifier(random_state=RANDOM_SEED)
clf3 = GaussianNB()
clf4 = SVC(probability = True)
lr = LogisticRegression()

# Starting from v0.16.0, StackingCVRegressor supports
# `random_state` to get deterministic result.
sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3, clf4],  # 第一层分类器
                            meta_classifier=lr,   # 第二层分类器
                            random_state=RANDOM_SEED)

print('5-fold cross validation:\n')

for clf, label in zip([clf1, clf2, clf3, clf4, sclf], ['KNN', 'Random Forest', 'Naive Bayes',"SVC",'StackingClassifier']):
    scores = cross_val_score(clf, X, y, cv=5, scoring='accuracy')
    print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

运行结果:
5-fold cross validation:

Accuracy: 0.91 (+/- 0.07) [KNN]
Accuracy: 0.94 (+/- 0.04) [Random Forest]
Accuracy: 0.91 (+/- 0.04) [Naive Bayes]
Accuracy: 0.95 (+/- 0.04) [SVC]
Accuracy: 0.95 (+/- 0.04) [StackingClassifier]

可看出,StackingClassifier的结果是由于每一个基分类器的,当然单SVC也达到了同样的效果,但随着数据集的变化和数据集的增加,StackingClassifier一般会得到更加鲁棒的预测结果!
我们可以将决策边界可视化出来:

# 画出决策边界
from mlxtend.plotting import plot_decision_regions
import matplotlib.gridspec as gridspec
import itertools

gs = gridspec.GridSpec(2, 3)
fig = plt.figure(figsize=(10,8))
for clf, lab, grd in zip([clf1, clf2, clf3,clf4, sclf], 
                         ['KNN', 
                          'Random Forest', 
                          'Naive Bayes',
                          "SVC",
                          'StackingCVClassifier'],
                          itertools.product([0, 1, 2], repeat=2)):
    clf.fit(X, y)
    ax = plt.subplot(gs[grd[0], grd[1]])
    fig = plot_decision_regions(X=X, y=y, clf=clf)
    plt.title(lab)
plt.show()

机器学习基础—集成学习Task13(Stacking)_第3张图片
从决策边界看以看出,关于1类和2类边界线,StackingClassifier和Random Forest是很像的,但0类和1类边界线,StackingClassifier却要比Random Forest更加平滑一些,这意味着可在一定程度上减小过拟合。

2.2 使用概率作为元特征的Stacking

使用第一层所有基分类器所产生的类别概率值作为meta-classfier的输入
在2.2中,第二层的特征是第一层基分类器预测的标签值,而实际上,这个分类标签值是具有可信度的,即可以用概率的形式表现,如果将概率作为第二层的元特征,由于特征粒度更小,应该能提升Stacking集成的效果!
上述操作需要在StackingClassifier 中增加一个参数设置:use_probas = True。
另外,还有一个参数设置average_probas = True,那么这些基分类器所产出的概率值将按照列被平均,否则会拼接。

# 2.使用概率作为元特征
clf1 = KNeighborsClassifier(n_neighbors=1)
clf2 = RandomForestClassifier(random_state=1)
clf3 = GaussianNB()
clf4 = SVC(probability = True)
lr = LogisticRegression()

sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3, clf4],
                            use_probas=True,  # 
                            meta_classifier=lr,
                            random_state=42)

print('5-fold cross validation:\n')

for clf, label in zip([clf1, clf2, clf3, clf4, sclf], 
                      ['KNN', 
                       'Random Forest', 
                       'Naive Bayes',
                       "SVC",
                       'StackingClassifier']):

    scores = cross_val_score(clf, X, y, 
                                              cv=5, scoring='accuracy')
    print("Accuracy: %0.2f (+/- %0.2f) [%s]" 
          % (scores.mean(), scores.std(), label))

运行结果:
5-fold cross validation:

Accuracy: 0.91 (+/- 0.07) [KNN]
Accuracy: 0.94 (+/- 0.04) [Random Forest]
Accuracy: 0.91 (+/- 0.04) [Naive Bayes]
Accuracy: 0.95 (+/- 0.04) [SVC]
Accuracy: 0.95 (+/- 0.04) [StackingClassifier]

因为数据集或者模型选择等因素,这里基于分类概率元特征的StackingClassifier没有明显的提升效果。

2.3 在不同特征子集上运行的分类器的堆叠

在很多时候,抽取不同特征列来训练基模型,可增加集成学习模型的稳健性,Stacking也有类似的方法:

# 3.在不同特征子集上运行的分类器的堆叠
##不同的1级分类器可以适合训练数据集中的不同特征子集。以下示例说明了如何使用scikit-learn管道和ColumnSelector:
from sklearn.datasets import load_iris
from mlxtend.classifier import StackingCVClassifier
from mlxtend.feature_selection import ColumnSelector
from sklearn.pipeline import make_pipeline
from sklearn.svm import SVC

iris = load_iris()
X = iris.data
y = iris.target

pipe1 = make_pipeline(ColumnSelector(cols=(0, 2)),  # 选择第0,2列
                      SVC())
pipe2 = make_pipeline(ColumnSelector(cols=(1, 3)),  # 选择第1,3列
                      SVC())
pipe3 = make_pipeline(ColumnSelector(cols=(0, 1, 2, 3)),  # 选择第0,1,2,3列
                      SVC())

sclf = StackingCVClassifier(classifiers=[pipe1, pipe2, pipe3], 
                            meta_classifier=LogisticRegression(),
                            random_state=42)

print('5-fold cross validation:\n')

for clf, label in zip([pipe1,pipe2,pipe3,sclf], ['LR02', 'LR13', 'LR0123', 'SCLF']):
    scores = cross_val_score(clf, X, y, cv=5, scoring='accuracy')
    print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

运行结果:
5-fold cross validation:

Accuracy: 0.95 (+/- 0.03) [LR02]
Accuracy: 0.96 (+/- 0.04) [LR13]
Accuracy: 0.97 (+/- 0.02) [LR0123]
Accuracy: 0.96 (+/- 0.02) [SCLF]

可以看出,通过不同的列采样,StackingClassifier不仅准确率提升了,5折交叉验证的标准差也降低了,这极大提升了模型的鲁棒性能!

至此,我们讨论了如何使用Blending和Stacking的方式去集成多个模型,相比于Bagging与Boosting的集成方式,Blending和Stacking的方式更加简单和直观,且效果还很好,因此在比赛中有这么一句话:它(Stacking)可以帮你打败当前学术界性能最好的算法 。

你可能感兴趣的:(机器学习)