用Python从零实现差分进化算法

差分进化是一种用于非线性、不可微连续空间函数全局优化的启发式方法。

差分进化算法属于更广泛的进化计算算法家族。与其他流行的直接搜索方法(如遗传算法和进化策略)类似,差分进化算法从候选解的初始种群开始。这些候选解通过引入种群变异并保留能产生较低目标函数值的最优候选解来迭代改进。

差分进化算法相较于上述流行方法具有优势,因为它能够处理非线性、不可微的多维目标函数,同时只需要很少的控制参数来引导最小化过程。这些特性使得该算法更易于使用且更实用。

在本教程中,您将了解用于全局优化的差分进化算法。

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

  • 差分进化是一种用于非线性、不可微连续空间函数全局优化的启发式方法。
  • 如何从零开始在 Python 中实现差分进化算法。
  • 如何将差分进化算法应用于实值二维目标函数。

通过我的新书 机器学习优化 开启您的项目,书中包含分步教程和所有示例的Python源代码文件。

让我们开始吧。

  • 2021年6月:修复了代码中的变异操作,使其与描述匹配。

教程概述

本教程分为三个部分;它们是:

  1. 差分进化
  2. 差分进化算法(从零开始)
  3. 球函数上的差分进化算法

差分进化

差分进化是一种用于非线性、不可微连续空间函数全局优化的启发式方法。

为了使最小化算法被认为实用,它需要满足五个不同的要求:

(1) 能够处理不可微、非线性、多模态成本函数。
(2) 可并行化,以应对计算密集型成本函数。
(3) 易于使用,即很少的控制变量来引导最小化。这些变量应
且稳定且易于选择。
(4) 良好的收敛性,即在
连续独立试验中一致收敛到全局最小值。

A Simple and Efficient Heuristic for Global Optimization over Continuous Spaces, 1997.

差分进化算法的优势在于它被设计用于满足上述所有要求。

差分进化 (DE) 可以说是近年来最强大、最通用的连续参数空间进化优化器之一。

Recent advances in differential evolution: An updated survey, 2016.

该算法首先随机初始化实值决策向量(也称为基因组或染色体)种群。这些代表多维优化问题的候选解。

在每次迭代中,算法会引入种群变异以生成新的候选解。变异过程将两个种群向量的加权差添加到第三个向量上,生成一个变异向量。变异向量的参数在称为交叉的过程中与另一个预定向量(目标向量)的参数混合,目的是增加扰动参数向量的多样性。由此产生的向量称为试探向量。

DE 通过将两个种群向量的加权差添加到第三个向量来生成新的参数向量。称此操作为变异。
为了增加扰动参数向量的多样性,引入了交叉。

A Simple and Efficient Heuristic for Global Optimization over Continuous Spaces, 1997.

这些变异根据变异策略生成,该策略遵循一般的命名约定 DE/x/y/z,其中 DE 代表差分进化,x 表示要变异的向量,y 表示用于 x 变异的差向量数量,z 表示所使用的交叉类型。例如,流行的策略

  • DE/rand/1/bin
  • DE/best/2/bin

指定向量 x 可以从种群中随机选择 (rand),或者选择成本最低的向量 (best);考虑的差向量数量为 1 或 2;并且交叉根据独立的二项式 (bin) 实验进行。特别是 DE/best/2/bin 策略,在种群规模足够大的情况下,对于改善种群多样性似乎非常有益。

如果种群向量数量 NP 足够大,使用两个差向量似乎可以改善种群多样性。

A Simple and Efficient Heuristic for Global Optimization over Continuous Spaces, 1997.

最后的选择操作用试探向量(其后代)替换目标向量(其父代),前提是试探向量产生更低的目标函数值。因此,更优的后代现在成为新生成的种群的成员,并随后参与进一步种群成员的变异。这些迭代继续进行,直到达到终止标准。

迭代将继续,直到满足终止条件(例如,最大函数评估次数耗尽)。

Recent advances in differential evolution: An updated survey, 2016.

差分进化算法需要很少的参数即可运行,即种群大小 NP、一个 [0, 2] 范围内的实数且恒定的尺度因子 F,它在变异过程中为差分变化加权,以及一个通过实验确定的交叉率 CR ∈ [0, 1]。这使得该算法易于使用且更实用。

此外,经典 DE 只需要很少的控制参数(精确地说,3 个:尺度因子、交叉率和种群大小)——这一特性使其对实践者来说易于使用。

Recent advances in differential evolution: An updated survey, 2016.

上述经典差分进化算法已进一步发展出各种变体,
您可以在 Recent advances in differential evolution – An updated survey, 2016 中阅读。

既然我们已经熟悉了差分进化算法,那么我们就来看看如何从零开始实现它。

想要开始学习优化算法吗?

立即参加我为期7天的免费电子邮件速成课程(附示例代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

差分进化算法(从零开始)

在本节中,我们将探讨如何从零开始实现差分进化算法。
差分进化算法首先生成候选解的初始种群。为此,我们将使用 rand() 函数生成一个数组,其中包含从 [0, 1) 上的均匀分布采样的随机值。

然后,我们将对这些值进行缩放,以将它们的分布范围更改为 (下界, 上界),其中界限以二维数组的形式指定,每个维度对应一个输入变量。

目标函数也将在此边界范围内进行评估。因此,可以选择的目标函数和每个输入变量的边界可以定义如下:

我们可以通过将候选解的初始种群作为输入参数传递给目标函数来评估它。

随着种群演变并收敛到最优解,我们将用更好的值替换 obj_all 中的值。

然后,我们可以遍历算法预定的迭代次数,例如 100 或 1,000 次(由参数 iter 指定),以及所有候选解。

算法迭代的第一步是执行变异过程。为此,从种群中随机选择三个非当前候选解 a、b 和 c,并通过计算:a + F * (b – c) 来生成变异向量。请记住,F ∈ [0, 2] 表示变异尺度因子。

变异过程由 mutation 函数执行,我们将 a、b、c 和 F 作为输入参数传递给它。

由于我们在有界值的范围内操作,我们需要检查新变异的向量是否也位于指定的边界内,如果不是,则根据需要将其值裁剪到上限或下限。此检查由 check_bounds 函数执行。

下一步执行交叉,其中当前(目标)向量的特定值被替换为变异向量中的相应值,以创建试探向量。替换哪些值由为每个输入变量生成的统一随机值是否小于交叉率决定。如果是,则将变异向量中的相应值复制到目标向量。

交叉过程由 crossover() 函数实现,该函数接收变异向量和目标向量,以及交叉率 cr ∈ [0, 1] 和输入变量的数量作为输入。

最后的选择步骤用试探向量替换目标向量,前提是试探向量产生更低的目标函数值。为此,我们评估这两个向量的目标函数,然后执行选择,如果试探向量是最优的,则将新的目标函数值存储在 obj_all 中。

我们可以将所有步骤整合到一个 differential_evolution() 函数中,该函数接收种群大小、每个输入变量的边界、总迭代次数、变异尺度因子和交叉率作为输入参数,并返回找到的最佳解决方案及其评估值。

既然我们已经实现了差分进化算法,让我们来看看如何使用它来优化目标函数。

球函数上的差分进化算法

在本节中,我们将把差分进化算法应用于一个目标函数。
我们将使用一个简单的二维球函数,指定在 [-5, 5] 的边界内。球函数是连续的、凸的和单峰的,其特征是在 f(0, 0) = 0.0 处有一个全局最小值。

我们将使用基于 DE/rand/1/bin 策略的差分进化算法来最小化此目标函数。

为此,我们必须为算法参数定义值,特别是种群大小、迭代次数、变异尺度因子和交叉率。我们将这些值经验性地设置为 10、100、0.5 和 0.7。

我们还定义了每个输入变量的边界。

接下来,我们执行搜索并报告结果。

将所有这些结合起来,完整的示例如下所示。

运行示例会报告搜索进度,包括迭代次数以及每次检测到改进时的目标函数响应。

在搜索结束时,找到最佳解决方案并报告其评估结果。

注意:由于算法或评估过程的随机性,或数值精度的差异,您的结果可能会有所不同。考虑运行几次示例并比较平均结果。

在这种情况下,我们可以看到算法在约 33 次改进(100 次迭代中)后收敛到非常接近 f(0.0, 0.0) = 0.0 的值。

通过稍微修改 differential_evolution() 函数来跟踪目标函数值并将其作为列表 obj_iter 返回,我们可以绘制每次改进时返回的目标函数值。

然后,我们可以创建这些目标函数值的折线图,以查看搜索过程中每次改进的相对变化。

将这些结合起来,完整的示例列在下面。

运行此示例将创建一个线图。

折线图显示了每次改进的目标函数评估值,初期变化较大,搜索后期变化非常小,因为算法已收敛到最优值。

Line Plot of Objective Function Evaluation for Each Improvement During the Differential Evolution Search

差分进化搜索过程中每次改进的目标函数评估值折线图

想要开始学习优化算法吗?

立即参加我为期7天的免费电子邮件速成课程(附示例代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

进一步阅读

如果您想深入了解,本节提供了更多关于该主题的资源。

论文

书籍

文章

总结

在本教程中,您了解了差分进化算法。
具体来说,你学到了:

  • 差分进化是一种用于非线性、不可微连续空间函数全局优化的启发式方法。
  • 如何从零开始在 Python 中实现差分进化算法。
  • 如何将差分进化算法应用于实值二维目标函数。

掌握现代优化算法!

Optimization for Maching Learning

加深您对优化的理解

...只需几行python代码

在我的新电子书中探索如何实现
机器学习优化

它提供**自学教程**,并附有关于以下内容的**完整工作代码**:
梯度下降遗传算法爬山法曲线拟合RMSPropAdam,以及更多...

将现代优化算法应用于
您的机器学习项目


查看内容

Python 零基础实现差分进化 的 32 条回复

  1. Jim Winburn 2021年6月16日 11:58 am #

    你好,我正在收到一个关于 pyplot.plot(solution[2], ‘.-‘) 的 list index out of range 错误。
    有什么想法吗?

  2. JG 2021年6月18日 8:07 pm #

    嗨,Jason,

    感谢教程。我看到了差分进化和遗传算法在全局优化方面的巨大潜力!

    我没有太多时间玩这个差分进化代码教程,但我已经玩过 scipy API:differential_evolution()。所以我认为这个教程只是在讲如何构建自己的(从零开始的)算法。对吗?

    无论如何,我更关注你之前的:遗传算法(从零开始):https://machinelearning.org.cn/simple-genetic-algorithm-from-scratch-in-python/

    所以,我看到的第一个问题是,这两个(遗传算法和差分进化代码)在过程上非常非常相似,如果不是完全相同的话。
    我唯一看到的是,遗传算法中的变异过程是在变量的二进制版本上执行的,而在这里的 DE 中则不是。我还看到了 DE/x/y/z 的策略模式……我说对了吗?

    第二个问题是由于这个过程(遗传 & 差分进化)都专注于获得全局最优……而且速度很快、稳定且高效地收敛到全局最优……以至于我认为有人必须利用它们来“替换”反向传播或随机梯度下降,以获得更新 ML/DL 模型权重的新选择,我说对了吗?

    谢谢

    • Jason Brownlee 2021年6月19日 5:53 am #

      新解的创建方式和使用的表示法是主要区别。

      是的,但通常来说,反向传播/SGD 是神经网络的更好解决方案。

  3. WF 2021年6月18日 9:15 pm #

    你好,

    文本说变异是 a + F * (b – c),但代码写的是

    def mutation(x, F)
    return x[0] + F * x[1] – x[2]

    祝好

  4. Anthony The Koala 2021年6月18日 9:34 pm #

    尊敬的Jason博士,
    另一种方法是转到“完整代码”。在代码的顶部,有一个看起来像“”的图标,它会打开纯代码。按 CTRL+A 然后 CTRL+C 复制代码。

    然后打开一个 DOS 窗口并输入 python 在 DOS 下运行 python 解释器。

    粘贴代码,并在最后一行 pyplot.show() 的末尾按 Enter 键。

    我试过了,代码可以正常工作。

    或者,运行一个解释器,如“Spyder”,而不是 Idle IDE。

    粘贴代码 = Ctrl+V。

    它有效。
    谢谢你,
    悉尼的安东尼。

    • Anthony The Koala 2021年6月18日 10:42 pm #

      尊敬的Jason博士,
      该图标用于在纯代码和带行号的代码之间切换。
      不知道为什么它们在用引号括起来时消失了。
      所以,通过点击图标可以在纯代码和带行号的代码之间切换。
      谢谢你,
      悉尼的Anthony

      • Anthony The Koala 2021年6月19日 2:14 am #

        抱歉,我指的是那个看起来像的图标,它在纯代码和带行号的代码之间切换。它消失了。

        谢谢你,
        悉尼的Anthony

        • Anthony The Koala 2021年6月19日 2:20 am #

          图标是 gt(大于号)和 lt(小于号)的组合。图标的图像是 lt gt。

          出于无法解释的原因,实际的 gt 和 lt 符号没有显示。这些符号的使用看起来是在纯代码和带行号代码之间切换。

          谢谢你,
          悉尼的Anthony

    • Jason Brownlee 2021年6月19日 5:54 am #

      感谢分享。

  5. Anthony The Koala 2021年6月20日 5:37 pm #

    尊敬的Jason博士,
    在你的教程中,你有一个目标函数

    如果我有一组数据,但不知道目标函数怎么办?如何从原始数据中确定目标函数?

    谢谢你,
    悉尼的Anthony

    • Jason Brownlee 2021年6月21日 5:36 am #

      在机器学习中,目标函数通常是损失函数,例如,最小化模型在训练数据集上进行预测时的损失。

  6. Anthony The Koala 2021年6月24日 8:19 am #

    尊敬的Jason博士,
    如何提取损失函数以用于此差分进化算法?
    谢谢你,
    悉尼的Anthony

    • Jason Brownlee 2021年6月25日 6:08 am #

      你说的“提取损失函数”是什么意思?

      通常,你在机器学习中定义一个你想最小化的损失函数,通常是模型对训练数据集的误差函数。

      • Anthony The Koala 2021年6月26日 2:02 pm #

        尊敬的Jason博士,
        我想能够使用 xgboost、keras 的 ML 函数中的损失函数,并将其插入差分进化算法中,以防 xgboost 和 keras 中的 ML 算法的成本函数具有不可微函数。

        这就是为什么我想能够从 xgboost 和 keras 中提取成本函数并将其插入差分进化算法中。

        谢谢你,
        悉尼的Anthony

        • Jason Brownlee 2021年6月27日 4:34 am #

          是的,正如我所说,这是预测值与训练数据集中的预期值之间的损失。

          您可能需要编写自定义代码才能将任意优化算法与 GBDT 或神经网络一起使用。

          • Anthony The Koala 2021年6月27日 3:05 pm #

            尊敬的Jason博士,
            谢谢你的回复。
            三种提问方式相同——如果使用 Keras、XGBoost 和 Torch 等软件包,它们有自己的优化技术,就不需要担心使用差分进化了。

            * 所以我可以得出结论,当使用 Keras、XGBoost 和 Torch 等机器学习算法时,真的没有必要使用差分进化吗?

            * 换句话说,本教程中的差分进化算法以及 https://docs.scipy.org.cn/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html 中的算法是针对已知但不可微分的函数吗?

            * 换句话说,使用差分进化来处理已知的软件包 Keras、XGBoostr 和 Torch 等是没有意义的。

            一直很欣赏你的回答,

            谢谢你,

            悉尼的Anthony

          • Jason Brownlee 2021年6月28日 7:57 am #

            并非没有意义,但你需要一个充分的理由。每种算法使用的传统方法都是最知名的。

  7. Anthony The Koala 2021年6月28日 2:09 pm #

    尊敬的Jason博士,
    谢谢你的回复。
    悉尼的Anthony

    • Jason Brownlee 2021年6月29日 4:45 am #

      不客气。

      • Eri 2021年8月26日 10:49 pm #

        如果我想同时优化多个输出,该怎么做?

        • Stefania Cristina
          Stefania Cristina 2021年8月31日 5:38 pm #

          如果你想用 DE 解决多目标优化问题,有几种方法可以做到:(论文)。本质上,引用论文中的话,“*在评估目标函数之前,该方法根据目标的相对重要性为其乘以权重。然后通过对加权目标求和来形成一个新的目标函数。因此,它将多目标转换为单目标。*”

  8. Eyal 2021年12月6日 12:55 am #

    假设我的目标函数是相对的——我能看出 x 比 y 差,但我无法用标量给它们评分——需要进行哪些调整才能使其正常工作?或者是否有其他进化算法我应该尝试?

    • Adrian Tam
      Adrian Tam 2021年12月8日 7:38 am #

      这很难,但我会试着问自己,为什么我会说 x 比 y 差?如果你能给出逻辑,你就可以得出一个标量度量。

      • Eyal 2021年12月10日 12:13 am #

        假设你正在学习下棋,你通过让玩家互相玩几局来比较他们。你可以说“X 在 10 场比赛中赢了 Y,所以 X 更好”,但给他们一个标量值是没有意义的——尤其是因为这种排序不一定具有传递性(排序 X > Y、Y > Z、Z > X 是可能的)。每一代你可能都会让整个种群与整个种群进行比赛——在这种方案中,胜利得 1 分,失败得 -1 分。问题是,这在每一代都需要 population_size^2 * k 场比赛。

        在现实世界的比赛中,棋手会获得 ELO 评分,这代表了他们获胜可能性的估计。我对此进行了一些实验,但由于不适的基因组会被淘汰,平均 ELO 不断上升,而将其标准化几乎使整个方案变得无用。我还怀疑模拟种群的相对较小的规模也起到了作用。也许这是可能的,但它非常不简单,以至于我怀疑它是否值得。

        这个问题也很棘手,因为没有可衡量的目标,所以很难比较不同的实验,也很难评估你的解决方案(除了局部收敛,这可能是“红皇后”问题)。

        • Adrian Tam
          Adrian Tam 2021年12月10日 4:23 am #

          每个模型都有一些假设。如果这些假设被违反,你就不能期望模型能够正常工作。我认为这里就是这种情况。如果 X>Y,Y>Z,Z>X,那么为什么我不能声称 Y>X?这表明你可能遇到问题。

          • Eyal 2021年12月10日 5:43 am #

            如果我正确理解了你——你假设 X > Y,这与你声称 Y > X 相矛盾。

            想象一下石头剪刀布。
            X 总是玩石头
            Y 总是玩剪刀
            Z 总是玩布

            X > Z > Y > X

            这表明通过标量对这种关系进行排序是行不通的。

            请注意,石头剪刀布是一个无趣的游戏——在不知道对手的情况下,所有策略的适合度都相同,但在各种游戏中也可能发生同样的情况。

  9. Agrover112 2022年3月8日 4:44 pm #

    嘿 Jason,我如何将这个改编成多于 2 个 X,比如如果我的 X 有 X1,X2…Xn?例如 [1,2,3,4]

    如果你能帮我解决这个问题,那就太好了!?

    • James Carmichael 2022年3月9日 5:53 am #

      嗨 Agrover112……请指明您希望修改的代码部分以及原因。

  10. Hadi 2024年10月22日 6:40 am #

    你好,非常感谢您对算法的清晰准确的实现。
    我只是注意到您不必在内循环中为每个个体计算“obj_target”(obj_target = obj(pop[j]),因为它与“obj_all[j]”相同。虽然这是一个小问题,但当我们在评估大型模型时,例如在我研究中的大型 CNN,它的成本会非常高。

    再次感谢

    • James Carmichael 2024年10月22日 7:10 am #

      你好 Hadi……不用谢!感谢你更新你的进展!

留下回复

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