【机器学习】集成学习 (Ensemble Learning) (四) —— Stacking 与 Blending


相关文章

【机器学习】集成学习 (Ensemble Learning) (一) —— 导引

【机器学习】集成学习 (Ensemble Learning) (二) —— Bagging 与 Random Forest

【机器学习】集成学习 (Ensemble Learning) (三) —— Boosting 与 Adaboost + GBDT


目录

2.3 堆叠法 (Stacking) 与 融合法 (Blending)

2.3.1 Stacking

2.3.2 Stacking 与 神经网络 NN

2.3.3 Blending


2.3 堆叠法 (Stacking) 与 融合法 (Blending)

2.3.1 Stacking

Stacking 学习几个不同的弱学习器,并通过训练一个元模型来组合这些弱模型的输出,作为最终的预测结果。

Stacking 与 Bagging、Boosting 相比,主要 差异 如下:

  1. Stacking 主要使用 异质弱学习器(不同模型),而 Bagging 和 Boosting 主要使用 同质弱学习器
  2. Stacking 训练一个 元模型 来组合基础模型,而 Bagging 和 Boosting 则 根据 确定性算法 组合弱学习器

因此,为构建 Stacking 模型,需定义至少两部分(通常是 2 层)模型

  1. 多个需要拟合的 弱学习器模型
  2. 一个用于组合各弱学习器模型的 元模型

例如,对于分类问题,可选 KNN、Logistic Regression、SVM 等作为弱分类器,并采用学习神经网络 NN 作为元模型。然后,神经网络将会把三个弱学习器的输出作为输入,并返回基于该输入的最终预测。所以,假设要拟合由 L 个弱学习器组成的 Stacking 集成模型,须遵循以下步骤:

  • 将训练数据分为两组;
  • 选择 L 个弱学习器,用它们拟合第一组数据;
  • 使 L 个学习器中的每个学习器对第二组数据中的观测数据进行预测;
  • 在第二组数据上拟合元模型,使用弱学习器做出的预测作为输入。

在前面的步骤中,将数据集一分为二,因为对用于训练弱学习器的数据的预测与元模型的训练 不相关。因此,将数据集分成两部分的一个 明显缺点 是:只有一半的数据用于训练基础模型,另一半数据用于训练元模型。

为克服这种限制,可使用某种 K 折交叉训练 方法(类似于 K 折交叉验证的做法)。这样所有的观测数据都可用来训练元模型:对于任意观测数据,弱学习器的预测都是通过在 K-1折数据(不包含已考虑的观测数据)上训练这些弱学习器的实例来完成的。换言之,它在 K-1 折数据上训练,在剩下 1 折数据上预测。迭代地重复改过程,就可得到对任 1 折观测数据的预测结果。这样一来,就可为数据集中的每个观测数据生成相关的预测,然后使用所有这些预测结果训练元模型。

Stacking 从数据集中训练出初级学习器,然后 ”生成“ 一个新的数据集用于训练次级学习器。由于深度学习模型一般需要较长的训练周期,如果硬件设备不允许建议选取留出法,如果需要追求精度可以使用交叉验证的方法。加之为防止过拟合,可采用 K 折交叉验证 求解。假设采用 5 折交叉验证,每个模型都要做满 5 次训练和预测,对于每一次:

  • 从 80% 的数据训练得到一个模型 ht,然后预测训练集剩下的 20%,同时也要预测测试集。
  • 每次有 20% 的训练数据被预测,5 次后正好每个训练样本都被预测过了。
  • 每次都要预测测试集,因此最后测试集被预测 5 次,最终结果取 5 次的平均。

一个示例

Stacking 算法

Stacking 的实现

(1) 最基本的使用方法,即使用前面分类器产生的特征输出 或 概率输出 作为 meta-classifier 的输入数据

from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn import model_selection
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 StackingClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
import warnings; warnings.filterwarnings(action='ignore')

# 载入iris数据集
iris = load_iris()
X = iris.data[:, :5]
y = iris.target
print('5 features = ', X[:5, :])
print('5 targets = ', y[:5])
print('\n')

# 实现Stacking集成
def StackingMethod(X, y):
    '''
    Stacking方法实现分类
    INPUT -> 特征, 分类标签
    '''
    scaler = StandardScaler() # 标准化转换
    scaler.fit(X)  # 训练标准化对象
    traffic_feature= scaler.transform(X)   # 转换数据集
    feature_train, feature_test, target_train, target_test = model_selection.train_test_split(X, y, test_size=0.3, random_state=0)
    
    # 弱分类器
    clf1 = LogisticRegression(random_state=1)  # 弱分类器 - Logistic 回归
    clf2 = RandomForestClassifier(random_state=1)  # 若分类器 - 随机森林
    clf3 = GaussianNB()  # 弱分类器 - 先验为高斯分布的朴素贝叶斯
    
    # Stacking 集成
    sclf = StackingClassifier(classifiers=[clf1, clf2, clf3],
                              # use_probas=True, 类别概率值作为meta-classfier的输入
                              # average_probas=False,  是否对每一个类别产生的概率值做平均
                              meta_classifier=LogisticRegression())

    sclf.fit(feature_train, target_train)

    # 模型测试
    predict_results = sclf.predict(feature_test)
    conf_mat = confusion_matrix(target_test, predict_results)
    print(f"test accuracy score: {accuracy_score(predict_results, target_test)}")
    print(f"confusion_matrix: {conf_mat}")
    print(f"classification_report: {classification_report(target_test, predict_results)}")

    # 5折交叉验证
    for clf, label in zip([clf1, clf2, clf3, sclf], ['Logistic Regression', 'Random Forest', 'naive Bayes', 'StackingModel']):
        scores = model_selection.cross_val_score(clf, X, y, cv=5, scoring='accuracy')
        print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

    return sclf

if __name__ == '__main__':
    model = StackingMethod(X, y)
5 features =  [[5.1 3.5 1.4 0.2]
               [4.9 3.  1.4 0.2]
               [4.7 3.2 1.3 0.2]
               [4.6 3.1 1.5 0.2]
               [5.  3.6 1.4 0.2]]
5 targets =  [0 0 0 0 0]

test accuracy score: 0.9777777777777777
confusion_matrix: [[16  0  0]
                   [ 0 17  1]
                   [ 0  0 11]]

classification_report:  precision   recall   f1-score    support

                   0       1.00      1.00      1.00        16
                   1       1.00      0.94      0.97        18
                   2       0.92      1.00      0.96        11

            accuracy                           0.98        45
           macro avg       0.97      0.98      0.98        45
        weighted avg       0.98      0.98      0.98        45

Accuracy: 0.97 (+/- 0.02) [Logistic Regression]
Accuracy: 0.97 (+/- 0.02) [Random Forest]
Accuracy: 0.95 (+/- 0.03) [naive Bayes]
Accuracy: 0.97 (+/- 0.02) [StackingModel]

 (2) 另一种方法,对训练集中的特征维度进行操作,此时不是给每一个基分类器全部的特征,而是给不同的基分类器分配不同的特征。如基分类器 1 训练前半部分特征,基分类器2 训练后半部分特征(可通过 sklearn 的 pipelines 实现),最终通过 StackingClassifier 组合起来。

from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn import model_selection
from sklearn.linear_model import LogisticRegression
from mlxtend.feature_selection import ColumnSelector
from mlxtend.classifier import StackingClassifier
from sklearn.pipeline import make_pipeline
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
import warnings; warnings.filterwarnings(action='ignore')


# 载入iris数据集
iris = load_iris()
X = iris.data
y = iris.target
print('5 features = ', X[:5, :])
print('5 targets = ', y[:5])
print('\n')

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


# 实现Stacking集成
def StackingMethod(X, y):
    '''
    Stacking方法实现分类
    INPUT -> 特征, 分类标签
    '''
    scaler = StandardScaler() # 标准化转换
    scaler.fit(X)  # 训练标准化对象
    traffic_feature = scaler.transform(X)   # 转换数据集
    feature_train, feature_test, target_train, target_test = model_selection.train_test_split(X, y, test_size=0.3, random_state=0)

    pipe1 = make_pipeline(ColumnSelector(cols=(0, 2)),
                          LogisticRegression())
    pipe2 = make_pipeline(ColumnSelector(cols=(1, 2, 3)),
                          LogisticRegression())

    sclf = StackingClassifier(classifiers=[pipe1, pipe2], 
                              meta_classifier=LogisticRegression())

    sclf.fit(feature_train, target_train)

    # 模型测试
    predict_results = sclf.predict(feature_test)
    conf_mat = confusion_matrix(target_test, predict_results)
    print(f"test accuracy score: {accuracy_score(predict_results, target_test)}")
    print(f"confusion_matrix: {conf_mat}")
    print(f"classification_report: {classification_report(target_test, predict_results)}")
    
    return sclf

if __name__ == '__main__':
    model = StackingMethod(X, y)
5 features =  [[5.1 3.5 1.4 0.2]
               [4.9 3.  1.4 0.2]
               [4.7 3.2 1.3 0.2]
               [4.6 3.1 1.5 0.2]
               [5.  3.6 1.4 0.2]]
5 targets =  [0 0 0 0 0]

test accuracy score: 0.9777777777777777
confusion_matrix: [[16  0  0]
                   [ 0 17  1]
                   [ 0  0 11]]

classification_report:  precision    recall  f1-score   support

                  0       1.00      1.00      1.00        16
                  1       1.00      0.94      0.97        18
                  2       0.92      1.00      0.96        11
    
           accuracy                           0.98        45
          macro avg       0.97      0.98      0.98        45
       weighted avg       0.98      0.98      0.98        45

 Stacking 的特点

  • 它可以帮你打败当前学术界性能最好的算法        
  • 有可能将集成的知识迁移到到简单的分类器上          
  • 自动化的大型集成策略可通过添加正则项有效的对抗过拟合,且无需太多的调参和特征选择,所以原则上 Stacking 非常适合 “懒人”        
  • 这是目前提升机器学习效果最好的方法,或者说是最效率的方法 Human Ensemble Learning

2.3.2 Stacking 与 神经网络 NN

Stacking 不是万能药,但往往很有效。通过与神经网络的对比,可以从另一个角度加深对 Stacking 的理解。

(1) Stacking是一种表示学习 (representation learning)

表示学习 指的是 模型从原始数据中自动抽取有效特征的过程,如深度学习。关于表示学习的理解可以参考:阿萨姆:人工智能(AI)是如何处理数据的?原始数据可能是杂乱无规律的。在 Stacking 中,通过第一层的多个学习器后,有效的特征被学习出来了。从该角度看,Stacking 的第一层即为特征抽取的过程。

preview

在相关研究中,一些图示可说明情况:上排是未经 Stacking 的数据,下排是经过 Stacking (多个无监督学习算法) 处理后的数据。可见,红色和蓝色的数据在下排中分界更为明显。(* 数据经过了压缩处理)。此例表明,有效的 Stacking 可有效地抽取原始数据中的有用特征

(2) Stacking 和神经网络从某种角度看异曲同工,神经网络也可被视为是一种集成学习

一方面,Stacking 的学习能力主要来自对特征的表示学习,思路同神经网络。这也是为什么上述说成 “第一层”、“最后一层”。

另一方面,神经网络也可被视为是一种集成学习,主要取决于不同神经元、网络层对不同特征的不同理解。从浅层到深层可看作是一种从具体到抽象的过程。

Stacking 的第一层可等价于神经网络的前 n-1 层隐藏层,而 Stacking 的最终分类层可类比于神经网络最后的输出层不同之处在于,Stacking 中不同的分类器通过异质来体现对于不同特征的表示,神经网络则是从同质到异质的过程 有分布式表示的特点 (distributed representation)。Stacking 中应也有分布式的特点,主要表现在多个分类器的结果并非完全不同,而有很大程度的相同之处。

但同时这也提出了一个挑战,多个分类器应尽量在保证效果好的同时尽量不同,Stacking 对基分类器的两个要求:

  • 差异化 (diversity) 要大
  • 准确性 (accuracy) 要高

(3) Stacking 的输出层为什么用逻辑回归?

Stacking 的有效性主要来自于特征抽取。而 表示学习 中,如影随形的问题就是 过拟合,试回想深度学习中的过拟合问题

周志华教授也曾重申了 Stacking 的过拟合问题。因为第二层的特征来自于对于第一层数据的学习,那么 第二层数据中的特征中不应包括原始特征以降低过拟合的风险。例如:

  • 第二层数据特征来源:仅包含第一层学习到的特征
  • 第二层数据特征来源:既包含第一层学习到的特征,又包含输入第一层的原始特征

另一个例子是,Stacking 中一般都用交叉验证来避免过拟合,足可见这个问题的严重性。

为降低过拟合风险,第二层分类器应较简单,广义线性如逻辑回归就是一个不错的选择,当然 MLR 也是在特征提取的过程中,已经使用了复杂的非线性变换,因此在输出层就不需要复杂的分类器了这一点可类比神经网络的激活函数或输出层,都是相对简单的函数,一点原因就是无需复杂函数且能控制复杂度

因此,Stacking 的输出层无需过分复杂的函数。此外,用逻辑回归作为第二层分类器还有额外的好处:

  • 逻辑回归配合 L1 正则化可进一步防止过拟合
  • 逻辑回归配合 L1 正则化可选择有效特征,从第一层的学习器中删除不必要的分类器,节省运算开销。
  • 逻辑回归的输出结果还可被理解为概率

(4) Stacking 是否需要多层?第一层的分类器是否越多越好?

综上可知,Stacking 的表示学习的效果不是来自于多层堆叠,而是源于不同学习器对不同特征的学习能力 及其有效的结合。通常,2 层 Stacking 足够了,更多层则会面临更加复杂的过拟合问题,且收益有限。

第一层分类器的数量对于特征学习应该有所帮助,从经验角度看,异质基分类器越多越好。即使有所重复和高依赖性,也依然可以通过特征选择来处理,问题不大。

这提出了 另一个 Stacking 与深度学习的差别:

  • Stacking 需要宽度,深度学习不需要
  • 深度学习需要深度,而 Stacking不需要

但 Stacking 和深度学习都均需面临:

  • 黑箱与解释性问题
  • 严重的过拟合问题

若 Stacking 和深度学习都是一种表示学习,如何选择?这可能和样本量有关:

  • 小样本上深度学习不具备可操作性,Stacking 或许可以
  • 大样本上 Stacking 的效率理当不如深度学习,这也是一种取舍

2.3.3 Blending

Blending 与 Stacking 大致相同,只是 Blending 的主要区别在于训练集不是通过 K-Fold 的 CV 策略来获得预测值从而生成第二阶段模型的特征,而是建立一个 Holdout 集,例如10% 的训练数据,第二阶段的 stacker 模型就基于第一阶段模型对这 10% 训练数据的预测值进行拟合。说白了,就是把 Stacking 流程中的 K-Fold CV 改成 HoldOut CV。

流程上,Blending 相较 Stacking 更简单些,大致为:

  1. 将数据划分为训练集和测试集 (test_set),其中训练集需要再次划分为训练集 (train_set) 和验证集 (val_set);
  2. 创建第一层的多个弱学习器模型,其既可为同质,也可为异质;
  3. 使用 train_set 训练步骤 2 中的多个模型,然后用训练好的模型预测 val_set 和 test_set 得到 val_predict, test_predict1;
  4. 创建第二层的模型,使用 val_predict 作为训练集训练第二层的模型;
  5. 使用第二层训练好的模型对第二层测试集 test_predict1 进行预测,该结果为整个测试集的结果。

Blending 优点

  1. 比 Stacking 简单(因为不用进行 K 次的交叉验证来获得 stacker features)
  2. 避开了一个信息泄露问题:generlizers 和 stacker 使用了不一样的数据集
  3. 在团队建模过程中,无需给队友分享自己的随机种子

Blending 缺点

  1. 使用了很少的数据(是划分 hold-out 作为测试集,并非 cv)
  2. 可能会过拟合(其实大概率是第一点导致的)
  3. Stacking 使用多次的 CV 会比较稳健

参考文献

通俗讲解集成学习算法!

三种集成学习算法原理及核心公式推导

集成学习(一)模型融合与 Bagging

Stacking 方法详解

「Stacking」与「神经网络」

为什么做stacking之后,准确率反而降低了?

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值