例子了解scipy盆地跳跃优化函数

时间:2014-03-05 19:30:56

标签: python scipy mathematical-optimization

我在scipy遇到basin hopping algorithm并创建了一个简单的问题来理解如何使用它,但它似乎并没有正确地解决这个问题。可能是我做错了。

以下是代码:

import scipy.optimize as spo
import numpy as np
minimizer_kwargs = {"method":"BFGS"}    
f1=lambda x: (x-4)
def mybounds(**kwargs):
    x = kwargs["x_new"]
    tmax = bool(np.all(x <= 1.0))
    tmin = bool(np.all(x >= 0.0))
    print x
    print tmin and tmax
    return tmax and tmin


def print_fun(x, f, accepted):
      print("at minima %.4f accepted %d" % (f, int(accepted)))
x0=[1.]     
spo.basinhopping(f1,x0,accept_test=mybounds,callback=print_fun,niter=200,minimizer_kwargs=minimizer_kwargs)

它提供的解决方案是x: array([ -1.80746874e+08])

3 个答案:

答案 0 :(得分:33)

您正在测试的函数使用一种名为Metropolis-Hastings的方法,可以将其修改为一个称为模拟退火的过程,该过程可以随机方式优化函数。

其工作方式如下。首先,你选择一个点,就像点x0一样。从那时起,您将生成随机扰动(这称为“提议”)。一旦出现了拟议的扰动,您可以通过对当前的扰动应用扰动来获得新点的候选者。所以,你可以把它想象成x1 = x0 + perturbation

在常规的旧梯度下降中,perturbation项只是一个确定性计算的数量,就像梯度方向上的一个步骤一样。但是在Metropolis-Hastings中,perturbation是随机生成的(有时候通过使用渐变作为关于随机去哪里的线索......但有时只是随机地没有线索)。

此时你已经x1,你必须问自己:“我是通过随机扰乱x0做了一件好事还是我把所有东西搞砸了?”其中一部分与粘贴在某些边界内部有关,例如你的mybounds函数。它的另一部分与目标函数的价值在新的点上变得更好/更差有关。

因此有两种方式拒绝x1的提案:首先,它可能会违反您设定的界限,并且在问题的定义中成为不可行的点;第二,从Metropolis-Hastings的接受/拒绝评估步骤来看,它可能只是一个非常糟糕的观点,应该被拒绝。在任何一种情况下,您都会拒绝x1,而是设置x1 = x0并假装您只是在同一地点再次尝试。

使用渐变式方法进行对比,无论如何,无论如何,总是至少做出一些类型的移动(渐变方向上的一步)。

哇,好的。抛开这一切,让我们考虑一下如何使用basinhopping函数。从文档中我们可以看到take_step参数访问典型的接受条件,文档说明:“默认步骤采用例程是坐标的随机位移,但其他步骤采用算法可能更好对于某些系统。“因此,即使除了mybounds边界检查器之外,该函数还将对坐标进行随机位移以生成其尝试的新点。由于此函数的梯度只是常数1,因此它总是在负梯度方向上进行相同的大步骤(用于最小化)。

在实际水平上,这意味着x1的建议点总是在[0,1]区间之外,并且你的边界检查员将总是否决它们。

当我运行你的代码时,我发现这种情况一直在发生:

In [5]: spo.basinhopping(f1,x0,accept_test=mybounds,callback=print_fun,niter=200,minimizer_kwargs=minimizer_kwargs)
at minima -180750994.1924 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.5530 accepted 0
[ -1.80746873e+08]
False
at minima -180746877.3896 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.7281 accepted 0
[ -1.80746874e+08]
False
at minima -180746878.2433 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.5774 accepted 0
[ -1.80746874e+08]
False
at minima -180746878.3173 accepted 0
[ -1.80750990e+08]
False
at minima -180750994.3509 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.6605 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.6966 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.6900 accepted 0
[ -1.80750990e+08]
False
at minima -180750993.9707 accepted 0
[ -1.80750990e+08]
False
at minima -180750994.0494 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.5824 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.5459 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.6679 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.5823 accepted 0
[ -1.80750990e+08]
False
at minima -180750993.9308 accepted 0
[ -1.80746874e+08]
False
at minima -180746878.0395 accepted 0
[ -1.80750991e+08]
False

# ... etc.

所以它从不接受posposal积分。输出并没有告诉你它找到了解决方案。它告诉你比探索可能的解决方案的随机扰动一直导致优化器看起来更好和更好的点,但是它们仍然不能满足你的标准。它无法找到回到[0,1]的方式来获取满足mybounds的点。

答案 1 :(得分:6)

你编码的盆地跳跃的行为是将扰动与局部最小化结合起来。

由于本地优化部分,您的例行程序一直无法生成。基本上你正在使用的BFGS程序是完全不受约束的,所以它遵循渐变到负无穷大。然后这个结果被反馈到你的检查器。

因此,无论您的盆地跳跃点x1在哪里,BFGS部分总是会产生巨大的负值。

您使用的基准函数x - 4不是此处的理想目标。查看例如Rastrigin function。如果您确实需要优化线性函数,那么可以使用一整套算法(请参阅维基百科上的Linear Programming)。

答案 2 :(得分:1)

Yike Lu已经指出了问题:你的界限只在顶级强制执行,但本地优化器BFGS知道对它们一无所知。

一般来说,使用&#34; hard&#34;优化的界限因为大多数算法都不允许直接在允许空间的边界上使算法达到最佳状态的路径,或者它将被终止。您可以看到在上面的情况下(x = 0)很难找到最佳值,而不是尝试x = -0.0000001,发现你走得太远了,走了一小会儿? 现在,有些算法可以通过转换输入数据来实现这一点(在scipy.optimize中,那些是接受边界作为参数的那些),但一般的解决方案是:

如果输入超出允许的范围,您可以更新成本函数以快速增加:

def f1(x):
    cost_raw = (x-4)
    if   x >= 1.0: cost_overrun = (1000*(x-1))**8
    elif x <= 0.0: cost_overrun = (1000*(-x))**8
    else: cost_overrun = 0.0

    return(cost_raw + cost_overrun)

这样,任何优化器都会看到成本函数增加,一旦超出界限,就会回到允许的空间。这不是严格的执行,但优化器无论如何都是迭代近似的,因此根据您需要它的严格程度,您可以调整惩罚函数以使增加或多或少模糊。一些优化器更喜欢具有连续导数(因此是幂函数),有些人会乐意处理固定的步骤 - 在这种情况下,只要你超出界限就可以简单地添加10000。