盆地跳跃是一种全局优化算法。
它最初是为了解决化学物理学中的问题而开发的,但它也是一种适用于具有多个最优值的非线性目标函数的有效算法。
在本教程中,您将了解盆地跳跃全局优化算法。
完成本教程后,您将了解:
- 盆地跳跃优化是一种全局优化算法,它利用随机扰动跳跃盆地,并利用局部搜索算法优化每个盆地。
- 如何在 Python 中使用盆地跳跃优化算法 API。
- 使用盆地跳跃解决具有多个最优值的全局优化问题的示例。
用我的新书《机器学习优化》启动您的项目,包括分步教程和所有示例的Python 源代码文件。
让我们开始吧。
Python 中的 Basin Hopping 优化
图片由 Pedro Szekely 提供,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 盆地跳跃优化
- 盆地跳跃 API
- 盆地跳跃示例
- 具有局部最优的多模态优化
- 具有多个全局最优的多模态优化
盆地跳跃优化
盆地跳跃是一种为化学物理学领域开发设计的全局优化算法。
盆地跳跃 (BH) 或蒙特卡洛最小化 (MCM) 是迄今为止化学物理学中最可靠的算法,用于搜索原子簇和高分子系统的最低能量结构。
—— 偶尔跳跃的盆地跳跃,2004 年。
局部优化是指旨在为单变量目标函数定位最优值或在被认为存在最优值的区域中操作的优化算法。而全局优化算法旨在在潜在的多个局部(非全局)最优值中定位单个全局最优值。
盆地跳跃由 David Wales 和 Jonathan Doye 在他们 1997 年的论文“通过盆地跳跃和包含多达 110 个原子的 Lennard-Jones 簇的最低能量结构进行全局优化”中描述。
该算法涉及循环两个步骤,即扰动良好的候选解和将局部搜索应用于扰动解。
[盆地跳跃]将复杂的能量景观转换为盆地集合,并通过跳跃探索它们,这通过随机蒙特卡洛移动和使用 Metropolis 准则的接受/拒绝来实现。
—— 偶尔跳跃的盆地跳跃,2004 年。
扰动允许搜索算法跳跃到搜索空间的新区域,并可能找到导致不同最优值的新盆地,例如技术名称中的“盆地跳跃”。
局部搜索允许算法遍历新盆地以达到最优值。
新的最优值可以保留作为新随机扰动的基础,否则将被丢弃。保留新解的决定由具有“温度”变量的随机决策函数控制,非常类似于模拟退火。
温度根据算法的迭代次数进行调整。这允许在运行初期温度高时接受任意解,并在搜索后期温度低时采用更严格的策略只接受质量更好的解。
通过这种方式,该算法很像具有不同(扰动)起始点的迭代局部搜索。
该算法运行指定次数的迭代或函数评估,并且可以多次运行以增加对全局最优值已被定位或已定位相对良好解的信心。
现在我们已经从高层次上熟悉了基本跳跃算法,接下来让我们看看 Python 中盆地跳跃的 API。
想要开始学习优化算法吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
盆地跳跃 API
Python 中通过 basinhopping() SciPy 函数提供盆地跳跃功能。
该函数接受要最小化的目标函数的名称和初始起始点。
1 2 3 |
... # 执行盆地跳跃搜索 结果 = basinhopping(目标, pt) |
另一个重要的超参数是通过“niter”参数设置的运行搜索的迭代次数,默认为 100 次。
这可以设置为数千次迭代或更多。
1 2 3 |
... # 执行盆地跳跃搜索 结果 = basinhopping(目标, pt, niter=10000) |
应用于候选解的扰动量可以通过“步长”控制,该步长定义了在问题域边界范围内应用的最大变化量。默认情况下,它设置为 0.5,但应设置为域中可能允许搜索找到新盆地的合理值。
例如,如果搜索空间的合理边界是 -100 到 100,那么 5.0 或 10.0 单位的步长可能是合适的(例如,域的 2.5% 或 5%)。
1 2 3 |
... # 执行盆地跳跃搜索 结果 = basinhopping(目标, pt, stepsize=10.0) |
默认情况下,使用的局部搜索算法是“L-BFGS-B”算法。
这可以通过将“minimizer_kwargs”参数设置为具有“method”键和作为要使用的局部搜索算法名称的值(例如“nelder-mead”)的目录来更改。可以使用 SciPy 库提供的任何局部搜索算法。
1 2 3 |
... # 执行盆地跳跃搜索 结果 = basinhopping(目标, pt, minimizer_kwargs={'method':'nelder-mead'}) |
搜索结果是一个 OptimizeResult 对象,其属性可以像字典一样访问。搜索的成功(或不成功)可以通过“success”或“message”键访问。
函数评估的总次数可以通过 ‘nfev’ 访问,搜索找到的最优输入可以通过 ‘x’ 键访问。
现在我们熟悉了 Python 中的盆地跳跃 API,接下来我们看一些实际示例。
盆地跳跃示例
在本节中,我们将通过一些示例来了解如何在多模态目标函数上使用盆地跳跃算法。
多模态目标函数是那些具有多个最优值的函数,例如一个全局最优值和许多局部最优值,或者多个具有相同目标函数输出的全局最优值。
我们将通过盆地跳跃在这两种函数上的示例进行说明。
具有局部最优的多模态优化
Ackley 函数是一个目标函数的例子,它具有一个单一的全局最优值和多个局部最优值,局部搜索可能会陷入其中。
因此,需要一种全局优化技术。它是一个二维目标函数,在 [0,0] 处有全局最优解,其值为 0.0。
下面的例子实现了 Ackley 函数并创建了一个三维曲面图,显示了全局最优解和多个局部最优解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# ackley 多峰函数 from numpy import arange from numpy import exp from numpy import sqrt from numpy import cos from numpy import e from numpy import pi from numpy import meshgrid from matplotlib import pyplot from mpl_toolkits.mplot3d import Axes3D # 目标函数 def 目标(x, y): 返回 -20.0 * exp(-0.2 * sqrt(0.5 * (x**2 + y**2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20 # 定义输入范围 r_min, r_max = -5.0, 5.0 # 以 0.1 为增量均匀采样输入范围 xaxis = arange(r_min, r_max, 0.1) yaxis = arange(r_min, r_max, 0.1) # 从坐标轴创建网格 x, y = meshgrid(xaxis, yaxis) # 计算目标值 结果 = 目标(x, y) # 使用 jet 配色方案创建曲面图 图形 = pyplot.图形() 轴 = 图形.gca(投影='3d') 轴.plot_surface(x, y, 结果, cmap='jet') # 显示绘图 pyplot.show() |
运行该示例会创建 Ackley 函数的曲面图,显示大量的局部最优解。

Ackley 多峰函数的三维曲面图
我们可以将盆地跳跃算法应用于 Ackley 目标函数。
在这种情况下,我们将使用从输入域 -5 到 5 之间随机抽取的一个点作为搜索的起点。
1 2 3 |
... # 将起始点定义为从域中随机采样 pt = r_min + rand(2) * (r_max - r_min) |
我们将使用 0.5 的步长、200 次迭代和默认的局部搜索算法。此配置是经过几次试错后选择的。
1 2 3 |
... # 执行盆地跳跃搜索 结果 = basinhopping(目标, pt, 步长=0.5, niter=200) |
搜索完成后,它将报告搜索状态和执行的迭代次数,以及找到的最佳结果及其评估。
1 2 3 4 5 6 7 8 |
... # 总结结果 print('状态 : %s' % 结果['消息']) print('总评估次数:%d' % 结果['nfev']) # 评估解 解 = 结果['x'] 评估 = 目标(解) print('解:f(%s) = %.5f' % (解, 评估)) |
综上所述,将盆地跳跃应用于 Ackley 目标函数的完整示例列举如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# Ackley 多模态目标函数的盆地跳跃全局优化 从 scipy.optimize 导入 basinhopping from numpy.random import rand from numpy import exp from numpy import sqrt from numpy import cos from numpy import e from numpy import pi # 目标函数 def objective(v): x, y = v 返回 -20.0 * exp(-0.2 * sqrt(0.5 * (x**2 + y**2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20 # 定义输入范围 r_min, r_max = -5.0, 5.0 # 将起始点定义为从域中随机采样 pt = r_min + rand(2) * (r_max - r_min) # 执行盆地跳跃搜索 结果 = basinhopping(目标, pt, 步长=0.5, niter=200) # 总结结果 print('状态 : %s' % 结果['消息']) print('总评估次数:%d' % 结果['nfev']) # 评估解 解 = 结果['x'] 评估 = 目标(解) print('解:f(%s) = %.5f' % (解, 评估)) |
运行该示例会执行优化,然后报告结果。
注意:鉴于算法或评估过程的随机性,或数值精度差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
在这种情况下,我们可以看到算法找到了输入接近零且目标函数评估值几乎为零的最优值。
我们可以看到,200 次算法迭代产生了 86,020 次函数评估。
1 2 3 |
状态:['已成功完成请求的盆地跳跃迭代次数'] 总评估次数:86020 解:f([ 5.29778873e-10 -2.29022817e-10]) = 0.00000 |
具有多个全局最优的多模态优化
Himmelblau 函数是具有多个全局最优值的目标函数的示例。
具体来说,它有四个最优值,每个最优值具有相同的目标函数评估值。它是一个二维目标函数,在 [3.0, 2.0]、[-2.805118, 3.131312]、[-3.779310, -3.283186]、[3.584428, -1.848126] 处具有全局最优值。
这意味着全局优化算法的每次运行都可能找到不同的全局最优值。
下面的示例实现了 Himmelblau 函数,并创建了一个三维曲面图,以直观地了解目标函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# himmelblau 多模态测试函数 from numpy import arange from numpy import meshgrid from matplotlib import pyplot from mpl_toolkits.mplot3d import Axes3D # 目标函数 def 目标(x, y): 返回 (x**2 + y - 11)**2 + (x + y**2 -7)**2 # 定义输入范围 r_min, r_max = -5.0, 5.0 # 以 0.1 为增量均匀采样输入范围 xaxis = arange(r_min, r_max, 0.1) yaxis = arange(r_min, r_max, 0.1) # 从坐标轴创建网格 x, y = meshgrid(xaxis, yaxis) # 计算目标值 结果 = 目标(x, y) # 使用 jet 配色方案创建曲面图 图形 = pyplot.图形() 轴 = 图形.gca(投影='3d') 轴.plot_surface(x, y, 结果, cmap='jet') # 显示绘图 pyplot.show() |
运行该示例会创建 Himmelblau 函数的曲面图,显示四个全局最优值(深蓝色盆地)。

Himmelblau 多模态函数的三维曲面图
我们可以将盆地跳跃算法应用于 Himmelblau 目标函数。
与前一个示例一样,我们将使用从输入域 -5 到 5 之间随机抽取的一个点作为搜索的起点。
我们将使用 0.5 的步长、200 次迭代和默认的局部搜索算法。搜索结束后,我们将报告找到的最佳最优值的输入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# Himmelblau 多模态目标函数的盆地跳跃全局优化 从 scipy.optimize 导入 basinhopping from numpy.random import rand # 目标函数 def objective(v): x, y = v 返回 (x**2 + y - 11)**2 + (x + y**2 -7)**2 # 定义输入范围 r_min, r_max = -5.0, 5.0 # 将起始点定义为从域中随机采样 pt = r_min + rand(2) * (r_max - r_min) # 执行盆地跳跃搜索 结果 = basinhopping(目标, pt, 步长=0.5, niter=200) # 总结结果 print('状态 : %s' % 结果['消息']) print('总评估次数:%d' % 结果['nfev']) # 评估解 解 = 结果['x'] 评估 = 目标(解) print('解:f(%s) = %.5f' % (解, 评估)) |
运行该示例会执行优化,然后报告结果。
想开始学习集成学习吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
在这种情况下,我们可以看到算法在大约 [3.0, 2.0] 处找到了一个最优值。
我们可以看到,200 次算法迭代产生了 7,660 次函数评估。
1 2 3 |
状态:['已成功完成请求的盆地跳跃迭代次数'] 总评估次数:7660 解:f([3. 1.99999999]) = 0.00000 |
如果再次运行搜索,我们可能会找到不同的全局最优值。
例如,在下面,我们可以看到在大约 [-2.805118, 3.131312] 处找到的最优值,与之前的运行不同。
1 2 3 |
状态:['已成功完成请求的盆地跳跃迭代次数'] 总评估次数:7636 解:f([-2.80511809 3.13131252]) = 0.00000 |
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
论文
- 通过盆地跳跃和包含多达 110 个原子的 Lennard-Jones 簇的最低能量结构进行全局优化, 1997.
- 偶尔跳跃的盆地跳跃, 2004.
书籍
- 能量景观:在簇、生物分子和玻璃中的应用, 2004.
API
文章
总结
在本教程中,您学习了盆地跳跃全局优化算法。
具体来说,你学到了:
- 盆地跳跃优化是一种全局优化算法,它利用随机扰动跳跃盆地,并利用局部搜索算法优化每个盆地。
- 如何在 Python 中使用盆地跳跃优化算法 API。
- 使用盆地跳跃解决具有多个最优值的全局优化问题的示例。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
尊敬的Jason博士,
在倒数第二段代码中,您有以下导入行
我注意到 Axes3D 没有被调用。
所以我注释掉了这行
结论:程序中没有产生错误
我推断 Axes3D 没有必要,因为 '3d' 参数已在以下代码中设置
谢谢你,
悉尼的Anthony
谢谢。
尊敬的Jason博士,
在最后一个示例中,您提到了 basinhopping 函数用于查找全局最小值。
是否有函数可以找到全局最大值?我问这个的原因是我做了 dir(scipy.optimize) 并且找不到一个直观的名称来表示 basinhopping 的反向以找到全局最大值。
谢谢你,
悉尼的Anthony
是的,您可以通过在成本函数结果中添加负号来反转最大化问题,从而将其转换为最小化问题。
尊敬的Jason博士,
我尝试了“在结果中添加负号”
以下结果
我只想找到全局最大值。
谢谢你,
悉尼的Anthony
您应该在 objective() 函数返回的结果中添加负号,例如,函数的最后一行。
尊敬的Jason博士,
谢谢你。
改变目标函数极性的结果
我学到了一件事
目标函数返回函数的负值,以便“找到”全局最大值
但是有一点不清楚的是,当代码在调用 basinhopping 函数时没有指定 v 是什么时,v 是如何传递给目标函数的?
谢谢你,
悉尼的Anthony
该函数正在搜索 v 的域——这就是优化问题的定义。
尊敬的Jason博士,
谢谢你。
那么,当您没有声明或分配 v 时,v 是如何从目标函数传递到以下代码中的?
谢谢你,
悉尼的Anthony
优化算法生成“v”的候选值,由我们的目标函数进行评估。
尊敬的Jason博士,
感谢您的回答和耐心。
我对 objective 的参数 v 进行了一个“实验”,并将其替换为其他东西。
结论
* 看起来该参数是由 basinhopping 函数在内部使用的,无需您显式传递。
* objective 的参数,无论您称之为 v 还是 boobooboo,类型都是 ndarray
– 变量 result 的类型是 scipy.optimize.optimize.OptimizeResult
– result['x'] 返回优化后的 x 和 y 值的 ndarray。
– result['x'] 被传递给 objective(v) 或 objective(boobooboo) 以返回 z 的结果。
再次感谢您的时间和耐心
悉尼的Anthony
是的。
尊敬的Jason博士,
我编写了一个程序,结合了盆地跳跃和显示函数的三维 (x,y,z)。
它有效。
它需要两次使用目标函数。(i) 首先,basinhopping 函数不需要 x 和 y,(ii) 打印图形需要单独设置 x、y 和 z
结论
* 比较计算出的 x、y 和 z 与图形,我们发现最小值出现在
解:f([4.41865299e-10 1.35549800e-10]) = 0.00000
* 当使用图表时,最小值出现在 (0,0) = 0。
回顾
* 为了在盆地跳跃中评估目标函数,该函数生成内部 x 和 y。
* 打印 3d 目标函数需要您生成 x 和 y,通过以下方式将 x、y 输入到目标函数中
– 创建一个网格 v = np.array([x,y])
– 将网格插入目标函数 objective(v)
– 绘制数据。
谢谢你,
悉尼的安东尼。
感谢分享!
尊敬的Jason博士,
在我上面的评论中,我希望使用上面找到全局最小值的可行代码来找到全局最大值。
为了找到全局最大值,我将原始目标函数的返回值反转了。
正如预期的那样,x、y、z 的 3D 图被反转了。
但是
* 多次重复程序,大部分时间产生相同的结果
大部分时间,z = f(x,y) = -22.35040,偶尔,z = f(x,y) 是 -16。
* 即便如此,通过对实际图形的目视检查,预期值为 z = f(x,y) = -6 ..
代码
* 前一个评论中展示的代码和这个评论中展示的代码之间没有变化,除了目标函数中符号的变化。
总结
* 改变目标函数结果的符号应该有助于我们确定全局最大值。
* 尽管代码反复运行,z = f(x,y) = -22.35040,但目视检查 3D 图时,看起来并不像预期值 z = f(x,y) = f(0,0) = -6。
谢谢你,
悉尼的Anthony
也许引入了错误?
也许函数被充分改变以定义新的最优值?
…
尊敬的Jason博士,
感谢您的回复。
从您的回复中,我不明白为什么在程序只是“复制”并且只改变了目标函数 objective = z = f(x,y) 的返回符号时,会引入错误。
然而,我的直觉是“...函数已被充分改变以定义新的最优值...”
如果存在计算全局最小值的函数,则应该有一个等效的函数来找到全局最大值。
在微积分中,我们不仅计算函数的最小值,而且可能“有必要”也计算全局最大值。
问题:肯定有一种方法可以计算函数的最大值吧?也许它不是来自 scipy.optimize 模块。
再次感谢您的时间和耐心,
悉尼的Anthony
我只是在猜测故障原因,也许我错过了一些好的猜测。
在优化领域中,通常使用的方法是反转(改变符号)函数而不是改变算法。
尊敬的Jason博士,
我以后可能会再来处理这个问题。
您说“…域上使用的典型方法是改变函数的符号…”
这以前已经演示过,通过检查图表,反向的目标函数并没有返回图形中描绘的最大值。
我休息一下,稍后再继续,
感谢您的时间和耐心,
悉尼的Anthony
嘿,杰森博士,谢谢分享。
我想知道是否可以使用机器学习预测模型(即随机森林回归模型)作为目标函数?目标是优化机器学习模型的可控输入特征。
谢谢!
嗨 CC…这是可能的,也是进行超参数优化时的常见做法。
感谢您的快速教程。我尚未弄清楚的一件事是如何控制成功的容差。例如,如果我希望当目标函数达到 0.1 时算作成功迭代,是否有为此设计的输入变量(即 tol = 0.1)。另一种方法是我想通过“args”对目标函数输入一些权重……有什么想法?
嗨,斯科特……不客气!以下资源可能会增加清晰度
https://datascience.stackexchange.com/questions/49255/how-to-make-scipy-optimize-basinhopping-find-the-global-optimal-point