Scipy:最小化违反给定边界

8

我正在尝试使用scipy.optimize.minimize函数并设置简单的a <= x <= b约束条件。但是,我的目标函数经常在界限之外进行评估。据我理解,这是由于minimize尝试在边界处确定目标函数梯度引起的。


最小示例:

import math
import numpy as np
from scipy.optimize import Bounds, minimize

constraint = Bounds([-1, -1], [1, 1], True)
def fun(x):
    print(x)
    return -math.exp(-np.dot(x,x))
result = minimize(fun, [-1, -1], bounds=constraint)

输出结果显示,最小化器跳到点[1,1],然后试图在[1.00000001, 1]处进行评估。
[-1. -1.]
[-0.99999999 -1.        ]
[-1.         -0.99999999]
[-0.72932943 -0.72932943]
[-0.72932942 -0.72932943]
[-0.72932943 -0.72932942]
[-0.22590689 -0.22590689]
[-0.22590688 -0.22590689]
[-0.22590689 -0.22590688]
[1. 1.]
[1.00000001 1.        ]
[1.         1.00000001]
[-0.03437328 -0.03437328]
...

当然,在这个例子中没有问题,因为fun也可以在那里评估。但是可能并不总是这种情况...


在我的实际问题中,最小值不能在边界上,我可以通过将 epsilon 添加到范围中来轻松解决此问题。 但是人们期望有一种简单的解决方法,即使最小值可能在边界上,该方法也适用?

PS:如果我是第一个遇到这个问题的人,那就太奇怪了--如果这个问题以前已经被问过,请原谅,我没有在任何地方找到它。


1
你不是第一个遇到这个问题的人。答案似乎是“这就是数值微分所做的事情”。(https://github.com/scipy/scipy/issues/3056#issuecomment-204802021) - user6655984
1个回答

7

正如在此处所讨论的那样(感谢@“Welcome to Stack Overflow”提供指引),问题确实是梯度例程没有遵守边界限制。我编写了一个新的程序来解决这个问题:

import math
import numpy as np
from scipy.optimize import minimize

def gradient_respecting_bounds(bounds, fun, eps=1e-8):
    """bounds: list of tuples (lower, upper)"""
    def gradient(x):
        fx = fun(x)
        grad = np.zeros(len(x))
        for k in range(len(x)):
            d = np.zeros(len(x))
            d[k] = eps if x[k] + eps <= bounds[k][1] else -eps
            grad[k] = (fun(x + d) - fx) / d[k]
        return grad
    return gradient

bounds = ((-1, 1), (-1, 1))
def fun(x):
    print(x)
    return -math.exp(-np.dot(x,x))
result = minimize(fun, [-1, -1], bounds=bounds,
                  jac=gradient_respecting_bounds(bounds, fun))

注意,这种方法可能会稍微低效一些,因为在每个点上fun(x)现在被评估了两次。这似乎是不可避免的,在lbfgsb.py中的_minimize_lbfgsb中有相关片段:
if jac is None:
    def func_and_grad(x):
        f = fun(x, *args)
        g = _approx_fprime_helper(x, fun, epsilon, args=args, f0=f)
        return f, g
else:
    def func_and_grad(x):
        f = fun(x, *args)
        g = jac(x, *args)
        return f, g

正如您所看到的,f 的值只能被内部的 _approx_fprime_helper 函数重复使用。


在我看来,您似乎缺少了下限,函数应该是: d[k] = eps if (x[k] + eps <= bounds[k][1] and x[k] + eps >= bounds[k][0]) else -eps - Markus Kaukonen
@MarkusKaukonen 我假设 eps 是正数,且 "x" 不超出边界,即 x[k] >= bounds[k][0]。那么第二个检查就是不必要的。 - Noiralef
好的,我明白了。我继续使用 https://pypi.org/project/mystic/ 这个包,对于有约束和边界问题来说,它似乎比scipy更好。 - Markus Kaukonen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接