如何用 Python 从零开始实现堆叠泛化(Stacking)

一步一步地用 Python 从头开始编码堆叠集成。

集成方法是提高机器学习问题预测性能的绝佳方式。

堆叠泛化或堆叠是一种集成技术,它使用新模型来学习如何最好地组合来自两个或更多在您的数据集上训练的模型的预测。

在本教程中,您将学习如何从头开始用 Python 实现堆叠。

完成本教程后,您将了解:

  • 如何学习组合来自多个模型在数据集上的预测。
  • 如何将堆叠泛化应用于实际的预测建模问题。

用我的新书《从零开始的机器学习算法》来**启动您的项目**,包括*分步教程*和所有示例的*Python 源代码文件*。

让我们开始吧。

  • 更新于 2017 年 1 月:更改了 `cross_validation_split()` 中 `fold_size` 的计算,使其始终为整数。修复了 Python 3 的问题。
  • 2018 年 8 月更新:测试并更新以与 Python 3.6 配合使用。
How to Implementing Stacking From Scratch With Python

如何从头开始用 Python 实现堆叠
照片由 Kiran Foster 拍摄,保留部分权利。

描述

本节简要概述了堆叠泛化算法和本教程中使用的声纳数据集。

堆叠泛化算法

堆叠泛化或堆叠是一种集成算法,其中训练一个新模型来组合来自两个或更多已在您的数据集上训练的模型的预测。

来自现有模型或子模型的预测使用一个新模型进行组合,因此堆叠通常被称为混合,因为子模型的预测被混合在一起。

通常使用简单的线性方法来组合子模型的预测,例如简单的平均或投票,到使用线性回归或逻辑回归的加权和。

进行预测组合的模型必须对问题具有一定的技能,但不一定是最佳模型。这意味着您无需刻意调整子模型,只要模型显示出优于基线预测的优势即可。

重要的是子模型产生不同的预测,即所谓的非相关预测。当组合的预测都具有技能,但技能方式不同时,堆叠效果最佳。这可以通过使用内部表示非常不同的算法(树与实例)和/或在训练数据的不同表示或投影上训练的模型来实现。

在本教程中,我们将研究采用两个非常不同且未调整的子模型,并使用简单的逻辑回归算法组合它们的预测。

声纳数据集

本教程中将使用的数据集是声纳数据集。

这是一个描述声纳脉冲回波从不同表面反弹的数据集。60 个输入变量是不同角度的回波强度。这是一个二元分类问题,需要一个模型来区分岩石和金属圆柱体。共有 208 个观测值。

这是一个众所周知的数据集。所有变量都是连续的,通常在 0 到 1 的范围内。输出变量是一个字符串,“M”代表水雷,“R”代表岩石,需要转换为整数 1 和 0。

通过预测数据集中观测值最多的类别(M 或水雷),零规则算法可以达到约 53% 的准确率。

您可以在 UCI 机器学习存储库了解有关此数据集的更多信息。

免费下载数据集,并将其放置在您的工作目录中,文件名为 **sonar.all-data.csv**。

教程

本教程分为 3 个步骤

  1. 子模型和聚合器。
  2. 组合预测。
  3. 声纳数据集案例研究。

这些步骤为您理解和在自己的预测建模问题中实现堆叠提供了基础。

1. 子模型和聚合器

我们将使用两个模型作为堆叠的子模型,并使用一个线性模型作为聚合器模型。

这部分分为 3 个部分

  1. 子模型 #1:k-近邻。
  2. 子模型 #2:感知器。
  3. 聚合器模型:逻辑回归。

每个模型将根据用于训练模型的函数和用于进行预测的函数进行描述。

1.1 子模型 #1:k-近邻

k-近邻算法或 kNN 使用整个训练数据集作为模型。

因此,训练模型涉及保留训练数据集。下面是一个名为 **knn_model()** 的函数,它正是这样做的。

进行预测涉及在训练数据集中找到 k 个最相似的记录,并选择最常见的类别值。欧几里得距离函数用于计算新数据行与训练数据集中行之间的相似性。

下面是涉及 kNN 模型预测的这些辅助函数。函数 **euclidean_distance()** 计算两行数据之间的距离,**get_neighbors()** 定位训练数据集中新数据行的所有邻居,**knn_predict()** 根据邻居对新数据行进行预测。

您可以看到,邻居的数量 (k) 在 **knn_predict()** 函数中被设置为默认参数 2。这个数字是通过一些试错选择的,并没有进行调整。

现在我们有了 kNN 模型的构建块,让我们看看感知器算法。

1.2 子模型 #2:感知器

感知器算法的模型是一组从训练数据中学习到的权重。

为了训练权重,需要在训练数据上进行许多预测以计算误差值。因此,模型训练和预测都需要一个用于预测的函数。

下面是实现感知器算法的辅助函数。**perceptron_model()** 函数在训练数据集上训练感知器模型,**perceptron_predict()** 用于对数据行进行预测。

**perceptron_model()** 模型将学习率和训练周期数都指定为默认参数。同样,这些参数是通过一些试错选择的,但没有在数据集上进行调整。

现在我们有了两个子模型的实现,让我们看看如何实现聚合器模型。

1.3 聚合器模型:逻辑回归

与感知器算法一样,逻辑回归使用一组权重(称为系数)作为模型的表示。

与感知器算法一样,系数是通过在训练数据上迭代进行预测并更新它们来学习的。

下面是实现逻辑回归算法的辅助函数。**logistic_regression_model()** 函数用于在训练数据集上训练系数,**logistic_regression_predict()** 用于对数据行进行预测。

**logistic_regression_model()** 定义了学习率和训练周期数作为默认参数,与其它算法一样,这些参数是通过一些试错找到的,但并未进行优化。

现在我们有了子模型和聚合器模型的实现,让我们看看如何组合多个模型的预测。

2. 组合预测

对于机器学习算法,学习如何组合预测与从训练数据集中学习非常相似。

可以根据子模型的预测构建一个新的训练数据集,如下所示:

  • 每行代表训练数据集中的一行。
  • 第一列包含第一个子模型(例如 k-近邻)对训练数据集中每一行的预测。
  • 第二列包含第二个子模型(例如感知器算法)对训练数据集中每一行的预测。
  • 第三列包含训练数据集中该行的预期输出值。

下面是一个虚构的堆叠数据集可能的样子

然后可以在这个新数据集上训练机器学习算法,例如逻辑回归。本质上,这个新的元算法学习如何最好地组合来自多个子模型的预测。

下面是一个名为 **to_stacked_row()** 的函数,它实现了为这个堆叠数据集创建新行的过程。

该函数将模型列表作为输入,这些模型用于进行预测。该函数还以函数列表作为输入,每个模型使用一个函数进行预测。最后,包含训练数据集中的一行。

新行一次构建一列。使用每个模型和训练数据行计算预测。然后将训练数据集行中的预期输出值作为最后一列添加到该行。

在某些预测建模问题中,通过在训练行和子模型预测上训练聚合模型,可以获得更大的提升。

这种改进为聚合器模型提供了训练行中所有数据的上下文,以帮助确定如何以及何时最好地组合子模型的预测。

我们可以更新 **to_stacked_row()** 函数,通过聚合训练行(减去最后一列)和上面创建的堆叠行来实现此改进。

下面是 **to_stacked_row()** 函数的更新版本,它实现了此改进。

在你的问题上尝试这两种方法,看看哪种效果最好是个好主意。

现在我们已经拥有了堆叠泛化的所有部分,我们可以将其应用于实际问题。

3. 声纳数据集案例研究

在本节中,我们将堆叠算法应用于声纳数据集。

该示例假设数据集的 CSV 副本位于当前工作目录中,文件名为 **sonar.all-data.csv**。

数据集首先被加载,字符串值转换为数字,输出列从字符串转换为整数值 0 到 1。这是通过辅助函数 **load_csv()**、**str_column_to_float()** 和 **str_column_to_int()** 来加载和准备数据集实现的。

我们将使用 k 折交叉验证来估计学习模型在未见数据上的性能。这意味着我们将构建和评估 k 个模型,并将性能估计为平均模型误差。分类准确率将用于评估模型。这些行为由 **cross_validation_split()**、**accuracy_metric()** 和 **evaluate_algorithm()** 辅助函数提供。

我们将使用上面实现的 k-近邻、感知器和逻辑回归算法。我们还将使用我们前一步骤中定义的用于创建新堆叠数据集的技术。

开发了一个名为 **stacking()** 的新函数。该函数执行 4 项操作

  1. 它首先训练模型列表(kNN 和感知器)。
  2. 然后它使用这些模型进行预测并创建一个新的堆叠数据集。
  3. 然后它在堆叠数据集上训练聚合器模型(逻辑回归)。
  4. 然后它使用子模型和聚合器模型对测试数据集进行预测。

完整的示例如下所示。

交叉验证的 k 值为 3,每个折叠有 208/3 = 69.3 或略低于 70 条记录,每次迭代都会对其进行评估。

运行示例会打印最终配置的分数和分数的平均值。

扩展

本节列出了您可能感兴趣的本教程的扩展内容。

  • 调整算法。本教程中用于子模型和聚合模型的算法没有经过调整。探索替代配置,看看您是否可以进一步提高性能。
  • 预测相关性。如果子模型的预测弱相关,堆叠效果更好。实现计算以估计子模型预测之间的相关性。
  • 不同的子模型。实现更多不同的子模型,并使用堆叠过程进行组合。
  • 不同的聚合模型。尝试使用更简单的模型(如平均和投票)和更复杂的聚合模型,看看是否能提高性能。
  • 更多数据集。将堆叠应用于 UCI 机器学习存储库中的更多数据集。

您是否探索过这些扩展?
在下面的评论中分享您的经验。

回顾

在本教程中,您学习了如何从头开始用 Python 实现堆叠算法。

具体来说,你学到了:

  • 如何组合多个模型的预测。
  • 如何将堆叠应用于实际的预测建模问题。

你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。

了解如何从零开始编写算法!

Machine Learning Algorithms From Scratch

没有库,只有 Python 代码。

...附带真实世界数据集的逐步教程

在我的新电子书中探索如何实现
从零开始实现机器学习算法

它涵盖了 18 个教程,包含 12 种顶级算法的所有代码,例如
线性回归、k-近邻、随机梯度下降等等……

最后,揭开
机器学习算法的神秘面纱

跳过学术理论。只看结果。

查看内容

对《如何从头开始用 Python 实现堆叠泛化 (Stacking)》的 33 条回复

  1. George Liu 2016 年 11 月 18 日 下午 1:59 #

    内容总是很棒!谢谢 Jason!

  2. Kevin Tian 2016 年 12 月 20 日 上午 3:30 #

    感谢这个非常有用的教程!

    我只是有一个关于如何创建 stacked_row 的问题;在这个教程中,我们似乎在训练集上训练了子模型,并使用它们在同一训练集上的预测来构建 stacked_rows,聚合器在该 stacked_rows 上进行训练,然后直接用于测试集。

    但我看到有些人用不同的方式做这件事。他们将训练集分成几部分,比如 A 和 B;分别在 A/B 上训练子模型,然后将它们在 B/A 上的预测用作第二层聚合器的输入。我只是想知道哪种方法是正确的方法,为什么?

    谢谢!

    • Jason Brownlee 2016 年 12 月 20 日 上午 7:29 #

      嗨,凯文,

      集成有许多构建方法。你所描述的听起来像是随机子空间法的一种形式。

      尽管如此,我建议您尝试一套方法来解决一个问题,看看哪种效果最好。没有“一种最佳方法”,我们的工作是作为实践者快速缩小选择范围并集中精力于有效的方法。

      • Michael 2018 年 7 月 14 日 上午 9:12 #

        首先,感谢这篇有用的博客文章……内容非常丰富。

        然而,关于 Kevin 的评论,我认为这里代码的编写方式使得整个分类器可能非常容易过拟合。例如,假设其中一个子模型是 1NN 分类器……在这种情况下,如果在给定的测试集上训练它,然后用它在同一测试集上进行预测,它的准确率将是 1.0。如果混合器随后在同一训练集上进行训练,它将把所有权重都放在 1NN 分类器上。但这会很糟糕,因为它的泛化能力会很差。

        我对此有所研究,似乎一个好的方法是进行 k 折交叉验证,但在构建混合器时使用子模型在保留折叠上的输出,而不是它们训练时的输出。

        或者我是否遗漏了什么?

  3. Reza 2017 年 4 月 5 日 下午 6:09 #

    嗨,Jason,
    感谢您的好文章,一如既往地棒。
    您能为不平衡数据集堆叠提出一个解决方案吗?

    • Jason Brownlee 2017 年 4 月 9 日 下午 2:33 #

      抱歉,我不太明白,您能重新措辞您的问题吗?

  4. Tim 2017 年 6 月 29 日 下午 4:37 #

    嘿,Jason,干得好。但我看到你没有使用样本外数据来训练第一层模型以创建第二层聚合器模型的元特征。这不会导致信息泄露问题吗?你能澄清一下吗,谢谢

    • Jason Brownlee 2017 年 6 月 30 日 上午 8:09 #

      只要测试数据被排除在外。我预计您的建议会带来更稳健的结果。

  5. nehasharma 2017 年 8 月 31 日 上午 5:43 #

    嗨,Jason,

    我对第 168 行有一个疑问:stacked_dataset.append(stacked_row)
    在测试集中。你用它做了什么?

    • Jason Brownlee 2017 年 8 月 31 日 上午 6:29 #

      堆叠()函数是进行预测的“算法”。

      stacked_dataset 列表包含来自子模型的预测,这些预测被馈送到逻辑回归中以进行最终结果预测。

      这有帮助吗?

  6. Alexander 2017 年 9 月 20 日 下午 4:11 #

    谢谢你,杰森。漂亮的工作。

  7. 亚历山大 2017 年 10 月 21 日 下午 6:16 #

    杰森,请帮帮我。
    这是关于管道的问题。
    想象一下,我们首先决定使用无监督学习算法来搜索集群。之后我们开发神经网络。也许你知道我们如何配置网络以强调观测来自不同集群?(我们不想构建许多网络,而是想配置一个)

    • Jason Brownlee 2017 年 10 月 22 日 上午 5:18 #

      这种方法的目的是什么?

      如果你想将数据分成簇来开发专门的模型,那么你需要开发单独的模型。

      你可以构建一个模型,并使用簇 ID 作为另一个输入特征。

  8. Alexander 2017 年 10 月 22 日 下午 10:56 #

    谢谢你,Jason。

    我想找到一种方法,如何将我对样本的知识(在我们的例子中是对集群的知识)传递给网络。
    是否有可能拓宽学习任务的理解?
    想象一下,一个有许多输入的网络,我们右手边有特征空间,左手边有描述这些特征的额外信息。我们不想像使用额外的标准特征一样使用我们对特征的知识,而是寻找新的机会。

    如果问题不清楚,请不要担心。我脑子里有很多想法,这些想法对我来说非常有趣。但是,有时我无法清楚地向周围的人解释我想要什么。

  9. Alexander 2017 年 10 月 23 日 下午 5:23 #

    谢谢你。

  10. Alexander 2018 年 1 月 16 日 上午 2:47 #

    嘿,杰森,我有一个问题。在区分例如 AdaBoostClassifier 和 Stacking 时,是不是 AdaBoostClassifier 只使用一个模型,而 Stacking 使用几种机器学习算法将它们组合起来进行预测。此外,AdaBoostClassifier 使用硬投票,这基本上意味着它取每个模型的众数(预测)。问题是,我以前认为 AdaBoostClassifier 能够容纳不同的机器学习算法,但现在我意识到 AdaBoostClassifier 只能有一种类型的机器学习算法,这并不意味着你可以在 AdaBoostClassifier 算法中拥有 5 个 DecisionTreeClassifier,它由超参数“n_estimators”控制。我只想知道这两个集成之间的主要区别,这有点令人困惑。感谢您的帮助。

    • Jason Brownlee 2018 年 1 月 16 日 上午 7:39 #

      在内部,Adaboost 使用许多树,其中后续树纠正先前树的预测。

      堆叠只是让多个不同的模型对结果进行投票。

  11. Nitin 2018 年 1 月 17 日 下午 2:16 #

    嗨,Jason,

    一如既往的精彩文章!!

    我有一个问题,如果我有一个数据集,其中
    train.shape, test.shape
    ((1458, 301), (1459, 301))

    我用训练数据集训练 7 个模型,并用这 7 个模型的预测创建一个新数据集 (new_dataset.shape = (1458, 7))

    我用这个新数据集和训练集中的 Y 拟合一个新模型。

    现在,如果我使用这个新模型对测试集进行预测,我会收到错误
    ValueError: feature_names mismatch,

    这是因为 new_dataset 有 7 列,而测试集有 301 列。

    不确定,我做错了什么。请指教。

    • Jason Brownlee 2018 年 1 月 18 日 上午 10:04 #

      抱歉,我没跟上。

    • Danna 2018 年 2 月 14 日 上午 6:58 #

      尼丁,

      您还需要为测试数据集创建一个新的特征集。您可以使用整个原始训练集,使用原始测试数据对每个模型进行预测,并将这些预测组合成一个维度为 (1459, 7) 的矩阵。

  12. Anjali Bhavan 2018 年 3 月 26 日 上午 6:29 #

    一如既往的精彩文章!我有一个问题——您有没有关于我们应该为集成选择哪些组件分类器(聚合器和子模型)以获得最佳结果的建议?

  13. Nirupama 2021 年 1 月 3 日 上午 3:22 #

    我的输出分数准确率非常低,如下所示。可能的原因是什么?
    分数:[1.4492753623188406, 0.0, 0.0]
    平均准确率:0.483%

  14. Swati Matwankar Shah 2021 年 7 月 8 日 下午 12:57 #

    你好 Jason,

    感谢您发布从头开始实现集成模型的文章。我对我下面的实现场景有一个问题。

    我有一个完整的包含 1-25 列(特征)的数据集。我已经有了模型 1,它用特征 3 到 10 进行训练。我还有模型 2,它用特征 15 到 21 进行训练。我是否可以创建模型 1 和模型 2 的集成模型,以检查它是否能提高预测准确率?在这种情况下我应该使用哪种集成方法?

    我查阅了“python”Bagging/Boosting/Stacking 分类器的描述和示例。但是,它们都没有允许我指定列(特征)范围作为输入。“max_features”属性无法满足我将“选定”列输入集成模型的需求。

    有没有其他 Python 方法可以实现这一点(如果不是集成模型)?我不想在为我的场景选择特征时有任何随机性。

    此致,
    斯瓦蒂。

    • Swati Matwankar Shah 2021 年 7 月 8 日 下午 3:53 #

      你好 Json,

      我刚刚意识到我可以使用“堆叠集成”。在这里,我可以独立地训练两个模型,使用不同的特征。我可以将它们的“结果”/“预测”输入到“堆叠集成”中以获得最终预测。

      之前,我错误地认为我必须将原始训练数据输入集成模型。然后集成模型将迭代地训练嵌入模型并给我们最终预测。(有一些集成模型是这样做的。但这并非我的要求。)

    • Jason Brownlee 2021 年 7 月 9 日 上午 5:02 #

      也许将每个模型的单独性能与两个模型的平均值、加权平均值和堆叠集成进行比较。

      有很多例子可以帮助你,从这里开始
      https://machinelearning.org.cn/start-here/#ensemble

发表评论

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。