如何用 Python 从零开始实现 Bagging

决策树是一种简单而强大的预测建模技术,但它存在高方差的问题。

这意味着在不同的训练数据下,决策树可能会得到非常不同的结果。

一种使决策树更稳健并实现更好性能的技术称为自举聚合,简称 bagging。

在本教程中,您将学习如何使用 Python 从零开始实现带有决策树的 bagging 过程。

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

  • 如何创建数据集的自举样本。
  • 如何使用自举模型进行预测。
  • 如何将 bagging 应用于您自己的预测建模问题。

通过我的新书《从零开始的机器学习算法启动您的项目,其中包括逐步教程和所有示例的 Python 源代码文件。

让我们开始吧。

  • 更新于 2017 年 1 月:更改了 `cross_validation_split()` 中 `fold_size` 的计算,使其始终为整数。修复了 Python 3 的问题。
  • 2017 年 2 月更新:修复了 build_tree 中的一个 bug。
  • 2017 年 8 月更新:修复了 Gini 计算中的一个 bug,添加了按组大小对组 Gini 分数进行加权的功能(感谢 Michael!)。
  • 2018 年 8 月更新:测试并更新以与 Python 3.6 配合使用。
How to Implement Bagging From Scratch With Python

如何用 Python 从零开始实现 Bagging
图片来源:Michael Cory,保留部分权利。

描述

本节简要介绍了自举聚合和本教程中将使用的声纳数据集。

自举聚合算法

自举是对带替换数据集的采样。

这意味着从现有数据集的随机样本中创建一个新数据集,其中给定行可以被选择并多次添加到样本中。

当您只有有限的数据集可用时,这是一种用于估计更广泛数据集(例如平均值)的值的有用方法。通过创建数据集的样本并从这些样本中估计平均值,您可以取这些估计值的平均值,并更好地了解底层问题的真实平均值。

同样的方法也可以用于具有高方差的机器学习算法,例如决策树。每个数据自举样本都训练一个单独的模型,并使用这些模型的平均输出进行预测。这种技术称为自举聚合,简称 bagging。

方差意味着算法的性能对训练数据敏感,高方差表明训练数据变化越大,算法的性能变化越大。

通过训练许多树并取其预测的平均值,可以提高未剪枝决策树等高方差机器学习算法的性能。结果通常优于单个决策树。

除了提高性能之外,bagging 的另一个好处是袋装决策树不会过拟合问题。可以继续添加树,直到达到最大性能。

声纳数据集

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

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

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

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

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

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

教程

本教程分为 2 个部分

  1. 自举重采样。
  2. 声纳数据集案例研究。

这些步骤为您提供了将自举聚合与决策树应用于您自己的预测建模问题所需的基础。

1. 自举重采样

让我们首先深入了解自举方法的工作原理。

我们可以通过从数据集中随机选择行并将它们添加到新列表中来创建数据集的新样本。我们可以对固定数量的行重复此操作,或者直到新数据集的大小与原始数据集大小的比例匹配。

我们可以通过不删除已选择的行来允许带替换采样,以便它可用于将来的选择。

下面是一个名为 subsample() 的函数,它实现了这个过程。random 模块中的 randrange() 函数用于在循环的每次迭代中选择一个随机行索引添加到样本中。样本的默认大小是原始数据集的大小。

我们可以使用此函数来估计一个虚构数据集的平均值。

首先,我们可以创建一个包含 20 行和一列 0 到 9 之间随机数的数据集,并计算其平均值。

然后我们可以对原始数据集进行自举样本,计算平均值,并重复此过程直到我们得到一个平均值列表。对这些样本平均值取平均值应该能给我们一个对整个数据集平均值的稳健估计。

完整的示例如下所示。

每个自举样本都是作为原始 20 个观测数据集的 10% 样本(或 2 个观测值)创建的。然后我们通过创建 1、10、100 个原始数据集的自举样本进行实验,计算它们的平均值,然后对所有这些估计的平均值取平均值。

运行示例会打印我们旨在估计的原始平均值。

然后我们可以看到来自不同数量自举样本的估计平均值。我们可以看到,使用 100 个样本,我们获得了平均值的良好估计。

我们可以从每个子样本中创建一个模型,而不是计算平均值。

接下来,让我们看看如何组合来自多个自举模型的预测。

2. 声纳数据集案例研究

在本节中,我们将把随机森林算法应用于声纳数据集。

该示例假设数据集的 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() 辅助函数提供。

我们还将使用经过修改以用于装袋的分类和回归树(CART)算法实现,其中包括辅助函数 test_split() 用于将数据集分成组,gini_index() 用于评估分割点,get_split() 用于找到最佳分割点,to_terminal()split()build_tree() 用于创建单个决策树,predict() 用于使用决策树进行预测,以及前一步中描述的 subsample() 函数用于创建训练数据集的子样本。

开发了一个名为 bagging_predict() 的新函数,负责使用每棵决策树进行预测,并将预测结果组合成一个单一的返回值。这是通过从袋装树所做的预测列表中选择最常见的预测来实现的。

最后,开发了一个名为 bagging() 的新函数,负责创建训练数据集的样本,对每个样本训练一个决策树,然后使用袋装树列表对测试数据集进行预测。

完整的示例如下所示。

k 值为 5 用于交叉验证,每折有 208/5 = 41.6 或略多于 40 条记录进行每次迭代的评估。

构建了深度为 6、每个节点最小训练行数为 2 的深层树。训练数据集的样本大小为原始数据集的 50%。这是为了强制在用于训练每棵树的数据集子样本中产生一些差异。Bagging 的默认设置是样本数据集的大小与原始训练数据集的大小匹配。

评估了 4 种不同数量的树,以显示算法的行为。

打印了每次折叠的精度和每次配置的平均精度。我们可以看到,随着树的数量增加,性能有小幅提升的趋势。

这种方法的难点在于,即使构建了深层树,所创建的袋装树也高度相似。反过来,这些树做出的预测也相似,我们希望在训练数据集的不同样本上训练的树之间存在的高方差被削弱了。

这是因为树的构建中使用的贪婪算法选择相同或相似的分割点。

本教程试图通过限制用于训练每棵树的样本大小来重新注入这种方差。一种更稳健的技术是在创建每个分割点时限制可以评估的特征。这是随机森林算法中使用的方法。

扩展

  • 调整示例。探索不同数量的树甚至单个树的配置,看看是否可以进一步改善结果。
  • 袋装其他算法。其他算法也可以与 bagging 一起使用。例如,k 值较低的 k 最近邻算法将具有高方差,并且是 bagging 的良好候选者。
  • 回归问题。Bagging 可以与回归树一起使用。您不再预测预测集中最常见的类别值,而是返回袋装树预测的平均值。在回归问题上进行实验。

你尝试过这些扩展吗?
在下面的评论中分享您的经验。

回顾

在本教程中,您学习了如何使用 Python 从零开始实现自举聚合。

具体来说,你学到了:

  • 如何创建子样本并估计自举量。
  • 如何创建决策树集成并使用它们进行预测。
  • 如何将 bagging 应用于实际的预测建模问题。

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

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

Machine Learning Algorithms From Scratch

没有库,只有 Python 代码。

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

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

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

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

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

查看内容

31 条对《如何用 Python 从零开始实现 Bagging》的回复

  1. skorzec 2017 年 1 月 18 日上午 4:30 #

    感谢这个从零开始的 python 示例。我认为您的分数保持不变的原因是您使用整个数据集来选择分割属性。这导致了相似的树,从而导致集成集的方差很小。如果您以某种方式更改您的代码,使树的方差更大,您将看到集成性能的提高。一个解决方案是修改此代码片段

    # 构建决策树
    def build_tree(train, max_depth, min_size)
    root = get_split(train)
    split(root, max_depth, min_size, 1)
    return root

    • Jason Brownlee 2017 年 1 月 18 日上午 10:17 #

      谢谢您的提示!

      • Nastaran 2018 年 5 月 17 日下午 2:38 #

        嗨,Jason,

        只是想知道您是否根据他的建议修改了您的代码?(看不到您的代码和他的建议之间有什么区别)

        • Jason Brownlee 2018 年 5 月 17 日下午 3:13 #

          是的,已经修复了。它以前使用整个数据集。

  2. Jovan Sardinha 2017 年 6 月 25 日上午 9:41 #

    感谢您精彩的教程!

    我已使用 sklearn 重写此脚本,以便轻松实现
    https://gist.github.com/JovanSardinha/2c58bd1e7e3aa4c02affedfe7abe8a29

    • Jason Brownlee 2017 年 6 月 26 日上午 6:04 #

      干得好!

      • Alex Godfrey 2017 年 8 月 31 日上午 6:43 #

        嗨,Jason,

        小提示 – 在字符串到整数的转换中 – 我发现在重复运行此函数和其他使用此函数的脚本时,唯一集是以某种随机顺序创建的。为避免这种情况,我将该行更改为:

        unique = sorted(set(class_values))

        这导致每次都创建相同的查找字典。我是在使用查找字典创建中间数据的精美打印输出时偶然发现的。

    • Bhagwat 2020 年 9 月 16 日下午 9:19 #

      嘿,这个链接不起作用

  3. Leroy Veld 2018 年 9 月 3 日上午 8:02 #

    我正在尝试将您的工作移植到 R 中。
    然而,我在翻译上遇到了困难。具体来说,您“计算分割数据集的基尼指数”函数中的一行代码很难翻译成 R 语言。

    p = [row[-1] for row in group].count(class_val) / size

    您能帮我一下吗?或者考虑发布一个这个代码的 R 实现?

    提前感谢!

    • Jason Brownlee 2018 年 9 月 3 日下午 1:34 #

      抱歉,我没有能力为您翻译代码或调试新的 R 实现。

  4. Aymen 2018 年 9 月 24 日上午 5:15 #

    我遇到的问题是使用网格搜索调整参数时得到了不同的结果,我该如何解决这个问题,谢谢

  5. Shreyas SK 2018 年 11 月 2 日下午 3:59 #

    我使用随机森林回归器在 Python 中实现了 bagging,并调整了参数,但结果始终不收敛。训练精度为 95%,测试精度为 55%。

    X = np.array([Depth, Dist, Soft, Stiff, sand, Sand, Stiff_C, Invert, FP, Penetr, Pitching, GP, GF]).T

    Y = np.array(Settlement)

    Y = Y.reshape(len(Y),)

    data = [X, Y]
    data = np.random.shuffle(data)

    rf = result = BaggingRegressor(RandomForestRegressor(), n_estimators = 270,
    bootstrap = True, oob_score = True, random_state = 42, max_features = 6)

    rf.fit(X,Y)

    pred = rf.predict(X)

    r2 = r2_score(Y, pred)

    plt.scatter(x=Y, y=pred)
    plt.show()
    print(“Train Accuracy : ” + str(r2))

    print(“Test Accuracy : ” + str(rf.oob_score_))

    • Jason Brownlee 2018 年 11 月 3 日上午 6:59 #

      也许可以尝试 scikit-learn 对算法的实现?

  6. Vinu 2019 年 3 月 28 日上午 8:05 #

    嗨 Jason,教程很棒。但是,我想知道 bagging 与交叉验证有什么不同?对于大型数据集,它们做的是“相同类型”的过程。您能解释一下吗?

    • Jason Brownlee 2019 年 3 月 28 日上午 8:26 #

      相似之处在于它们都执行重采样,但目的不同。

      在 bagging 中,我们寻求高方差,因为我们正在创建一个用于预测的集成模型。

      在 CV 中,我们寻求偏差和方差之间的平衡,以获得对未见数据模型性能的可靠估计。

      • Vinu 2019 年 3 月 28 日上午 8:31 #

        谢谢你的澄清

  7. Abdoulaye Diallo 2019 年 4 月 15 日上午 8:49 #

    您好,如何使用 sklearn 分类器绘制自举的精度?

  8. safa 2019 年 5 月 15 日上午 3:17 #

    你好,当我们使用 bagging 技术创建一个集成模型时,每个模型(假设是一个 CNN)都在不同的数据集上训练,测试集对于每个模型和集成模型都是相同的,但是用于训练模型的验证集呢?我可以使用相同的数据集来验证所有模型,还是需要为每个模型使用不同的数据集,因为每个模型都在不同的数据集上训练。提前感谢!

    • Jason Brownlee 2019 年 5 月 15 日上午 8:19 #

      您可以将袋外样本用作每个袋装模型的验证数据集。

  9. dkwih 2020 年 6 月 30 日下午 5:07 #

    非常感谢!!

  10. Mark 2021 年 5 月 11 日下午 12:20 #

    感谢您的本教程。我有一个问题。

    我相信这行代码

    max(set(predictions), key=predictions.count)

    与以下代码相同

    max(predictions, key=predictions.count)

    这正确吗?

  11. DominicM 2021 年 7 月 11 日下午 8:13 #

    我对此相当陌生,所以很可能是用户错误,我使用的是 PyCharm Community 版
    === 我是不是错过了什么?? ===

    ============================= 测试会话开始 =============================
    正在收集…已收集 1 项

    ex_bagging_from_scratch.py::test_split 错误 [100%]
    测试设置失败
    文件 D:\DomiPy\venv\ex_bagging_from_scratch.py,第 81 行
    def test_split(index, value, dataset)
    E 夹具“index”未找到
    > 可用夹具:cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
    > 有关它们的帮助,请使用“pytest --fixtures [testpath]”。

    D:\DomiPy\venv\ex_bagging_from_scratch.py:81

  12. lima 2022 年 1 月 28 日上午 12:15 #

    嗨,Jason,

    非常感谢您提供的丰富信息教程!

    我是一个初学者,所以请多多包涵!🙂

    据我理解,这个实现与这篇论文 https://doi.org/10.1016/S0031-3203(02)00121-8 中的算法类似,我正在尝试将其实现用于回归问题,其中数据集很小(230 个数据点)。
    我的问题是:如何修改代码以使用带有 L2 正则化的线性回归?

    • James Carmichael 2022 年 1 月 28 日上午 10:31 #

      嗨 Lima,

      我很乐意帮忙,但我没有能力根据你的具体需求定制代码。

      我收到很多这样的请求。我相信你能理解我的理由。

      我有一些可能有所帮助的想法

      也许我有一个关于您所要求更改的教程?请在博客中搜索。
      也许你可以自己尝试进行更改?
      也许你可以在博文下方添加评论,说明你需要进行的更改,我和其他读者可以提出建议?
      也许你可以雇佣承包商或程序员进行更改?
      也许你可以在 stackoverflow.com 上发布所需代码的描述?

发表回复

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