如何在 SciPy.optimize.minimize 中定义不连续的边界

How to define discontinuous boundaries in SciPy.optimize.minimize

根据文档,我正在使用 scipy.optimize.minimize 'SLSQP' 方法:

bounds : sequence, optional

Bounds for variables (only for L-BFGS-B, TNC and SLSQP). (min, max) pairs for >each element in x, defining the bounds on that parameter. Use None for one of min >or max when there is no bound in that direction.

我想知道是否可以为变量 x 定义一个不连续的边界,例如 (0,15) & (30,50); (x介于0到15和30到50之间)

或者有没有其他更好的方法可以达到这个目的?

先谢谢大家了!

x is between 0 and 15 and between 30 and 50

这会使模型不可行。没有这样的x。你的意思可能是:

x 在 0 到 15 之间 OR 在 30 到 50

之间

这是非凸的,因此标准的局部求解器对此有问题。它通常用一个额外的二进制变量建模:

30 δ ≤ x ≤ 15(1-δ) + 50 δ  
δ ∈ {0,1}

当然,这是假设您可以处理二进制变量(SLSQP 不能)。具有二进制变量和非线性约束(或 objective 函数)的模型称为 MINLP 模型(混合整数非线性规划)。这些类型模型的求解器很容易获得。

其他一些可能有效的方法:

  • 两次解决问题。一次 0 ≤ x ≤ 15,一次 30 ≤ x ≤ 50。然后选择最佳解决方案。
  • 使用 scipy.optimize.basinhopping 全局求解器帮助您摆脱局部最优解。这不是一个严格的算法(没有保证),但它可以提供帮助。

一些通常不起作用的方法:

  • 而不是二进制变量 δ ∈ {0,1} 使用连续变量 δ ∈ [0,1] 并添加约束 δ(1-δ)=0。通常这会让你陷入困境。
  • 另一个答案中的多项式方法:这也是非凸的,并不真正适合 SLSQP。你会陷入局部最优。
  • 如果 x ∈ [15,30],则对 objective 添加惩罚。这也不适用于本地求解器。

这是实施跳盆方法的尝试

首先,构造一个根为0、15、30和50的多项式 在所需区域为正:

In [123]: x = np.linspace(-1, 51, 100)

In [124]: plt.plot(x,-(x-0)*(x-15)*(x-30)*(x-50))
Out[124]: [<matplotlib.lines.Line2D at 0x7fa01d65b748>]

In [125]: plt.axhline(color='red')
Out[145]: <matplotlib.lines.Line2D at 0x7fa01d6620b8>

In [146]: plt.show()

现在您可以使用该多项式作为约束:

import numpy as np
import scipy.optimize as optimize

cons = (
    {'type': 'ineq', 'fun': lambda x: -(x-0)*(x-15)*(x-30)*(x-50)}, )

def f(x):
    return (x-20)**2

res = optimize.basinhopping(f, [40], minimizer_kwargs={'method':'SLSQP', 'constraints': cons}, niter=10, stepsize=20)
print(res)

产量

 message: 'Optimization terminated successfully.'
    nfev: 13
     nit: 4
    njev: 4
  status: 0
 success: True
       x: array([15.])
                    message: ['requested number of basinhopping iterations completed successfully']
      minimization_failures: 0
                       nfev: 178
                        nit: 10
                       njev: 56
                          x: array([15.])

请注意,最初的猜测是在 40,但 optimize.basinhopping 设法在 x = 15 的另一个非连续区间中找到最小值。 使用与两个间隔之间的距离相近的步长对于允许 basinhopping 有机会从两个间隔进行采样很重要。

如果没有 basinhopping,optimize.minimize 使用具有非凸约束的 SLSQP 可能无法从所有允许的间隔中采样。例如,

import scipy.optimize as optimize

cons = ({'type': 'ineq', 'fun': lambda x: -(x-0)*(x-15)*(x-30)*(x-50)}, )

def f(x):
    return (x-20)**2

res = optimize.minimize(
    f, [40], method='SLSQP', constraints=cons)
print(res.x)
# [30.]

res = optimize.minimize(
    f, [5], method='SLSQP', constraints=cons)
print(res.x)
# [15.]

表明您必须 运行 optimize.minimize 两次,并在每个间隔中进行猜测 为了找到真正的最小值。