SciPy 的 trust-constr 忽略了我的约束

SciPy's trust-constr is ignoring my constraints

我正在做一些与统计相关的优化。我正在尝试使用 scipy 的最小化。但是,它给我的解决方案是不可行的。我的密码是

import numpy as np
from scipy.optimize import minimize, NonlinearConstraint
from math import e

def get_expectation(f,mu):
    return f.dot(mu)

def get_neg_subgaus_coeff(f,mu):
    return -(e**((f/10)**2)).dot(mu)

def grad_neg_subgaus_coeff(f,mu):
    return -(2*e**((f/10)**2)*f)*mu

 def my_mu(n):
    return 1/(np.abs((np.arange(n)-n//2))+1)**2

def get_constraints(n):
    constr_list = []
    for i in range(n-1):

        constr = lambda f: f[i]- f[i+1]
        constr_list.append(NonlinearConstraint(constr,.1, .1))

    return constr_list

def callback(x,_): # not important, only debug info
    print(get_neg_subgaus_coeff(x, mu))
    return False

def get_solution(mu,n):
    constraints = get_constraints(n) + [NonlinearConstraint(lambda f: get_expectation(f,mu), 0, 0)]
    return minimize(get_neg_subgaus_coeff, np.zeros(n)-1, args=(mu),jac=grad_neg_subgaus_coeff, method="trust-constr", bounds=[(-100, 100) for _ in range(n)], constraints=constraints, tol=1e-8, options={"maxiter":1000, "disp":True}, callback=callback)

if __name__ == "__main__":
    n=10
    mu = my_mu(n)
    sol = get_solution(mu,n).x
    print(sol)

现在,问题是当优化完成时(“满足xtol终止条件。”和"constraint violation: 8.33e-17"),它给出了以下解决方案

[-0.93668183 -0.63135599 -0.35261352 -0.54613932 -0.52013279  0.21760397
  1.25553898 -0.48837217 -2.04095876 -2.14095876]

这显然不满足 get_constraints 产生的条件,即数组的两个相邻元素最多相差 0.1。

当我删除雅可比行列式时,它的行为相同(只是运行速度较慢)。关于堆栈溢出有几个听起来很相似的问题,但那些是关于 SLSQP 的,似乎不是很相关。

知道为什么会这样吗?或者怎么解决?

我认为这与著名的 "feature" lambda 表达式有关。如果您查看:

 flist = [lambda x: x+i for i in range(3)]

然后执行时:

  [flist[i](1) for i in range(3)]

你可能会期待 [1+0,1+1,1+2] = [1,2,3]。但是,您会看到:

  [3, 3, 3]

基本上,此设置中只有一个 lambda(或者更确切地说:循环结束时的延迟绑定)。

一个可能的解决方法是:

 from functools import partial
 flist = [partial(lambda x,j: x+j, j=i) for i in range(3)]
 [flist[i](1) for i in range(3)]

这将显示:[1,2,3]

将此技术应用于您的问题时,我们可以这样写:

def get_constraints(n):
    flist = [partial(lambda f,j: f[j]-f[j+1], j=i) for i in range(n-1)]
    return [NonlinearConstraint(flist[i],.1,.1) for i in range(n-1)]

现在的解决方案如下:

x: array([ 0.49289571,  0.39289571,  0.29289571,  0.19289571,  0.09289571,
   -0.00710429, -0.10710429, -0.20710429, -0.30710429, -0.40710429])

结论:与求解器无关,而是对 lambda 的工作方式存在误解。

PS。准确地说,这是由于 "late binding closures"(不使用 lambda 函数也会发生同样的事情,但我通常会在其中看到这种行为)。参见 https://docs.python-guide.org/writing/gotchas/