【问题标题】:Constrained optimisation in scipy enters restricted areascipy中的约束优化进入禁区
【发布时间】:2022-09-17 23:55:33
【问题描述】:

我正在尝试使用解决多元优化问题python 与 scipy. 让我定义我工作的环境:

搜索参数:

和问题本身:

(在我的情况下日志L功能很复杂,所以我将用微不足道的功能代替它,从而产生类似的问题。因此,在这个例子中,我没有完全使用函数参数,但为了问题的一致性,我将它们包括在内)。

我使用以下约定将参数存储在单个平面数组中:

这是脚本,应该可以解决我的问题。

import numpy as np
from scipy import optimize as opt
from pprint import pprint
from typing import List

_d = 2
_tmax = 500.0
_T = [[1,2,3,4,5], [6,7,8,9]]

def logL(args: List[float], T : List[List[float]], tmax : float):
    # simplified - normaly using T in computation, here only to determine dimension
    d = len(T)
    # trivially forcing args to go \'out-of constrains\'
    return -sum([(args[2 * i] + args[2 * i + 1] * tmax)**2 for i in range(d)]) 

def gradientForIthDimension(i, d, t_max):
    g = np.zeros(2 * d + 2 * d**2)
    g[2 * i] = 1.0
    g[2 * i + 1] = t_max + 1.0

    return g

def zerosWithOneOnJth(j, l):
    r = [0.0 for _ in range(l)]
    r[j] = 1.0
    
    return r

new_lin_const = {
    \'type\': \'ineq\',
    \'fun\' : lambda x: np.array(
        [x[2 * i] + x[2 * i + 1] * (_tmax + 1.0) for i in range(_d)] 
        + [x[j] for j in range(2*_d + 2*_d**2) if j not in [2 * i + 1 for i in range(_d)]]
        ),
    \'jac\' : lambda x: np.array(
        [gradientForIthDimension(i, _d, _tmax) for i in range(_d)]
         + [zerosWithOneOnJth(j, 2*_d + 2*_d**2) for j in range(2*_d + 2*_d**2) if j not in [2 * i + 1 for i in range(_d)]]
        )
}

最后优化

logArgs = [2 for _ in range(2 * (_d ** 2) + 2 * _d)]

# addditional bounds, not mentioned in a problem, but suppose a\'priori knowledge
bds = [(0.0, 10.0) for _ in range(2 * (_d ** 2) + 2 * _d)]
for i in range(_d):
    bds[2*i + 1] = (-10.0, 10.0)

res = opt.minimize(lambda x, args: -logL(x, args[0], args[1]),
constraints=new_lin_const, x0 = logArgs, args=([_T, _tmax]), method=\'SLSQP\', options={\'disp\': True}, bounds=bds)

但是在检查结果时,我得到:

pprint(res)
#     fun: 2.2124712864600578e-05
#     jac: array([0.00665204, 3.32973738, 0.00665204, 3.32973738, 0.        ,
#       0.        , 0.        , 0.        , 0.        , 0.        ,
#       0.        , 0.        ])
# message: \'Optimization terminated successfully\'
#    nfev: 40
#     nit: 3
#    njev: 3
#  status: 0
# success: True
#       x: array([ 1.66633206, -0.00332601,  1.66633206, -0.00332601,  2.        ,
#        2.        ,  2.        ,  2.        ,  2.        ,  2.        ,
#        2.        ,  2.        ])

特别是:

print(res.x[0] + res.x[1]*(501.0))
# -3.2529534621517087e-13

所以结果超出了限制区域...... 我试图遵循文档,但对我来说它不起作用。我很乐意听到任何关于哪里出了问题的建议。

    标签: python optimization scipy scipy-optimize


    【解决方案1】:

    首先,请停止多次发布相同的问题。这个问题和你的另一个here基本一样。下次,只需编辑您的问题,而不是发布新问题。

    话虽这么说,你的代码是不必要的复杂鉴于您的优化问题非常简单。你的目标应该是阅读你的代码就像阅读数学优化问题一样简单。一个非常受欢迎的副作用是,如果它没有按预期工作,那么调试代码要容易得多。

    为此,强烈建议您熟悉 numpy 及其矢量化操作(如您之前问题的 cmets 中所述)。例如,您不需要循环来实现您的目标、约束函数或雅可比。将所有优化变量打包成一个大向量x 是正确的方法。但是,您可以简单地将x 再次解压缩到其组件 lambda、gamma、alpha 和 beta 中。这使您更容易编写函数并且更易于阅读。

    好吧,您可以在下面找到一个简化且有效的实现,而不是削减我的代码。通过评估函数并将输出与代码 sn-p 中的评估函数进行比较,您应该了解您这边出了什么问题。

    编辑:似乎scipy.minimize 的大部分算法都无法收敛到局部最小化器,同时保持约束的严格可行性。如果您愿意使用其他软件包,我建议您使用最先进的 NLP 求解器 Ipopt。您可以通过cyipopt 包来使用它,并且由于它的minimize_ipopt 方法,您可以像scipy.optimize.minimize 一样使用它:

    import numpy as np
    #from scipy.optimize import minimize
    from cyipopt import minimize_ipopt as minimize
    
    d = 2
    tmax = 500.0
    N = 2*d + 2*d**2
    
    def logL(x, d, tmax):
        lambda_, gamma, alpha, beta = np.split(x, np.cumsum([d, d, d**2]))
        return np.sum((lambda_ + tmax*gamma)**2)
    
    def con_fun(x, d, tmax):
        # split the packed variable x = (lambda_, gamma, alpha, beta)
        lambda_, gamma, alpha, beta = np.split(x, np.cumsum([d, d, d**2]))
        return lambda_ + (tmax + 1.0) * gamma 
    
    def con_jac(x, d, tmax):
        jac = np.block([np.eye(d), (tmax + 1.0)*np.eye(d), np.zeros((d, 2*d**2))])
        return jac
    
    constr = {
        'type': 'ineq', 
        'fun': lambda x: con_fun(x, d, tmax), 
        'jac': lambda x: con_jac(x, d, tmax)
    }
    
    bounds = [(0, 10.0)]*N + [(-10.0, 10.0)]*N + [(0.0, 10.0)]*2*d**2
    x0 = np.full(N, 2.0)
    
    res = minimize(lambda x: logL(x, d, tmax), x0=x0, constraints=constr, 
        method='SLSQP', options={'disp': True}, bounds=bounds)
    
    print(res)
    

    产量

    
    ******************************************************************************
    This program contains Ipopt, a library for large-scale nonlinear optimization.
     Ipopt is released as open source code under the Eclipse Public License (EPL).
             For more information visit https://github.com/coin-or/Ipopt
    ******************************************************************************
    
         fun: 0.00014085582293562834
        info: {'x': array([ 2.0037865 ,  2.0037865 , -0.00399079, -0.00399079,  2.00700641,
            2.00700641,  2.00700641,  2.00700641,  2.00700641,  2.00700641,
            2.00700641,  2.00700641]), 'g': array([0.00440135, 0.00440135]), 'obj_val': 0.00014085582293562834, 'mult_g': array([-0.01675576, -0.01675576]), 'mult_x_L': array([5.00053270e-08, 5.00053270e-08, 1.00240003e-08, 1.00240003e-08,
           4.99251018e-08, 4.99251018e-08, 4.99251018e-08, 4.99251018e-08,
           4.99251018e-08, 4.99251018e-08, 4.99251018e-08, 4.99251018e-08]), 'mult_x_U': array([1.25309309e-08, 1.25309309e-08, 1.00160027e-08, 1.00160027e-08,
           1.25359789e-08, 1.25359789e-08, 1.25359789e-08, 1.25359789e-08,
           1.25359789e-08, 1.25359789e-08, 1.25359789e-08, 1.25359789e-08]), 'status': 0, 'status_msg': b'Algorithm terminated successfully at a locally optimal point, satisfying the convergence tolerances (can be specified by options).'}
     message: b'Algorithm terminated successfully at a locally optimal point, satisfying the convergence tolerances (can be specified by options).'
        nfev: 15
         nit: 14
        njev: 16
      status: 0
     success: True
           x: array([ 2.0037865 ,  2.0037865 , -0.00399079, -0.00399079,  2.00700641,
            2.00700641,  2.00700641,  2.00700641,  2.00700641,  2.00700641,
            2.00700641,  2.00700641])
    

    并在找到的解决方案处评估约束函数产生

    In [17]: print(constr['fun'](res.x))
    [0.00440135 0.00440135]
    

    因此,约束得到满足。

    【讨论】:

    • 我感谢所有关于可读性的评论以及 sn-p 中的示例。但它不起作用。拆分变量迷惑建议您更改了变量的布局,与我的方法相比,但是在日志L您正在使用与我相同的索引。在确保一致性之后,我得到了相同的结果。
    • @PiotrCukier你是对的,我错过了正确重写目标。不过,它应该在确保一致性后起作用。我编辑了我的答案,请随时再次检查。
    • 仍然失败。还调整了边界(现在应该是循环中的bounds[d+ i] = (-10.0, 10.0)),然后在设置tmax=1.0 之后,结果是x: array([ 0.00000000e+00, 0.00000000e+00, -1.09245946e-13, -1.11910481e-13, 2.00000000e+00, 2.00000000e+00, 2.00000000e+00, 2.00000000e+00, 2.00000000e+00, 2.00000000e+00, 2.00000000e+00, 2.00000000e+00]),看起来,约束被忽略了。
    • 约束不会被“忽略”。只是底层算法使用数值公差。我再次编辑了答案。如果您可以随意使用其他软件包,您可以尝试使用 Ipopt。
    • 仅供参考,我需要删除边界参数并将其重写为线性约束。然后我的问题收敛到有效的解决方案。
    猜你喜欢
    • 2014-07-13
    • 2019-02-10
    • 2022-01-22
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-18
    • 1970-01-01
    相关资源
    最近更新 更多