Python受限非线性优化

38

在Python中进行受限非线性优化,有哪些推荐的包?

我要解决的具体问题是:

我有一个未知的 X(Nx1),我有 M(Nx1)个 u 向量和 M(NxN)个 s 矩阵。

max [5th percentile of (ui_T*X), i in 1 to M]
st 
0<=X<=1 and
[95th percentile of (X_T*si*X), i in 1 to M]<= constant

当我开始解决这个问题时,我只有一个的点估计值,并且我能够使用解决上面的问题。

我意识到,我不是只有一个的估计值,而是整个数值分布,因此我想改变目标函数,以便我可以利用整个分布。上面的问题描述是我试图以有意义的方式包含那些信息。

无法用于解决此问题,我尝试了scipy.optimize.anneal,但似乎无法设置未知值的范围。我也看了看pulp,但它不允许非线性约束。


1
在Stack Overflow上,询问我们推荐或找到工具、库或喜爱的外部资源的问题是不相关的,因为它们往往会吸引持有观点的答案和垃圾信息。相反,请描述问题以及迄今为止已经采取的解决方案。 - jonrsharpe
1
当我开始解决这个问题时,我只有一个关于u和s的点估计值,并且我能够使用cvxpy解决上述问题。后来我意识到,我不仅有一个u和s的估计值,而是整个值分布,因此我想改变目标函数,以便我可以使用整个分布。上面的问题描述是我试图以有意义的方式包含该信息的尝试。cvxpy无法用于解决此问题,我已经尝试了scipy.optimize.anneal,但似乎无法设置未知值的边界。我也看过pulp,但它不允许非线性约束。 - akhil
4个回答

30

虽然scipy.optimize.minimize中的SLSQP算法很好,但它有许多限制。首先是它是一个QP求解器,因此适用于适合于二次规划范例的方程。但如果您具有函数约束会发生什么?此外,scipy.optimize.minimize不是全局优化器,因此您通常需要非常靠近最终结果才能开始。

有一个受限非线性优化包(名为mystic)几乎存在于scipy.optimize本身出现的时间——我建议将其作为处理任何一般受限非线性优化的首选。

例如,如果我理解您的伪代码正确,您的问题大致如下:

import numpy as np

M = 10
N = 3
Q = 10
C = 10

# let's be lazy, and generate s and u randomly...
s = np.random.randint(-Q,Q, size=(M,N,N))
u = np.random.randint(-Q,Q, size=(M,N))

def percentile(p, x):
    x = np.sort(x)
    p = 0.01 * p * len(x)
    if int(p) != p:
        return x[int(np.floor(p))]
    p = int(p)
    return x[p:p+2].mean()

def objective(x, p=5): # inverted objective, to find the max
    return -1*percentile(p, [np.dot(np.atleast_2d(u[i]), x)[0] for i in range(0,M-1)])


def constraint(x, p=95, v=C): # 95%(xTsx) - v <= 0
    x = np.atleast_2d(x)
    return percentile(p, [np.dot(np.dot(x,s[i]),x.T)[0,0] for i in range(0,M-1)]) - v

bounds = [(0,1) for i in range(0,N)]

因此,要处理您在 mystic 中的问题,您只需指定边界和约束条件。

from mystic.penalty import quadratic_inequality
@quadratic_inequality(constraint, k=1e4)
def penalty(x):
  return 0.0

from mystic.solvers import diffev2
from mystic.monitors import VerboseMonitor
mon = VerboseMonitor(10)

result = diffev2(objective, x0=bounds, penalty=penalty, npop=10, gtol=200, \
                 disp=False, full_output=True, itermon=mon, maxiter=M*N*100)

print result[0]
print result[1]

结果看起来像这样:

Generation 0 has Chi-Squared: -0.434718
Generation 10 has Chi-Squared: -1.733787
Generation 20 has Chi-Squared: -1.859787
Generation 30 has Chi-Squared: -1.860533
Generation 40 has Chi-Squared: -1.860533
Generation 50 has Chi-Squared: -1.860533
Generation 60 has Chi-Squared: -1.860533
Generation 70 has Chi-Squared: -1.860533
Generation 80 has Chi-Squared: -1.860533
Generation 90 has Chi-Squared: -1.860533
Generation 100 has Chi-Squared: -1.860533
Generation 110 has Chi-Squared: -1.860533
Generation 120 has Chi-Squared: -1.860533
Generation 130 has Chi-Squared: -1.860533
Generation 140 has Chi-Squared: -1.860533
Generation 150 has Chi-Squared: -1.860533
Generation 160 has Chi-Squared: -1.860533
Generation 170 has Chi-Squared: -1.860533
Generation 180 has Chi-Squared: -1.860533
Generation 190 has Chi-Squared: -1.860533
Generation 200 has Chi-Squared: -1.860533
Generation 210 has Chi-Squared: -1.860533
STOP("ChangeOverGeneration with {'tolerance': 0.005, 'generations': 200}")
[-0.17207128  0.73183465 -0.28218955]
-1.86053344078

mystic非常灵活,可以处理任何类型的约束条件(例如相等性、不等式),包括符号和函数约束。

上面我将约束条件指定为“惩罚”,这是一种传统方法,当约束条件被违反时,它们会对目标函数施加惩罚。

mystic还提供了非线性内核变换,通过减少有效解空间的空间映射或内核变换来约束解空间。

以下是一个示例,展示了mystic如何解决许多二次规划求解器无法处理的问题,因为这些约束条件不是以约束矩阵的形式给出的。该问题是优化压力容器的设计。

"Pressure Vessel Design"

def objective(x):
    x0,x1,x2,x3 = x
    return 0.6224*x0*x2*x3 + 1.7781*x1*x2**2 + 3.1661*x0**2*x3 + 19.84*x0**2*x2

bounds = [(0,1e6)]*4
# with penalty='penalty' applied, solution is:
xs = [0.72759093, 0.35964857, 37.69901188, 240.0]
ys = 5804.3762083

from mystic.symbolic import generate_constraint, generate_solvers, simplify
from mystic.symbolic import generate_penalty, generate_conditions

equations = """
-x0 + 0.0193*x2 <= 0.0
-x1 + 0.00954*x2 <= 0.0
-pi*x2**2*x3 - (4/3.)*pi*x2**3 + 1296000.0 <= 0.0
x3 - 240.0 <= 0.0
"""
cf = generate_constraint(generate_solvers(simplify(equations)))
pf = generate_penalty(generate_conditions(equations), k=1e12)


if __name__ == '__main__':

    from mystic.solvers import diffev2
    from mystic.math import almostEqual
    from mystic.monitors import VerboseMonitor
    mon = VerboseMonitor(10)

    result = diffev2(objective, x0=bounds, bounds=bounds, constraints=cf, penalty=pf, \ 
                     npop=40, gtol=50, disp=False, full_output=True, itermon=mon)

    assert almostEqual(result[0], xs, rel=1e-2)
    assert almostEqual(result[1], ys, rel=1e-2)

在这里找到这个例子以及大约100个类似的例子:https://github.com/uqfoundation/mystic.

我是作者,所以我有点偏见。但是,这种偏见非常微小。mystic既成熟又得到良好支持,并且在解决困难的受限非线性优化问题方面无与伦比。


更多非线性约束的示例请参见此处:https://dev59.com/TJTfa4cB1Zd3GeqPULCm#42299338 - Mike McKerns
听起来很有前途,但从 https://pypi.org/project/mystic/(http://www.cacr.caltech.edu/~mmckerns)链接的项目主页已经失效了! - feetwet
@feetwet:该页面已经移动到http://trac.mystic.cacr.caltech.edu/project/mystic/wiki.html。链接将在即将发布的版本中得到更新。 - Mike McKerns
@Mike McKerns:我正在尝试进行优化,以使已知的12x12矩阵的特征值与一个具有10个变量的符号矩阵(相同大小)匹配。我在这里发布了一个问题https://stackoverflow.com/questions/48873110/least-square-optimization-to-minimize-the-error-between-the-eigenvalues-of-one-k?noredirect=1#comment84750676_48873110。我能否使用mystic包进行这种优化? - Paul Thomas
1
@ozataman:基本上我选择k的方法通常是从一个小值开始,如果看起来惩罚没有被“尊重”,那么我就增加k。是的,这一切都是关于将比例与目标匹配。最理想的情况是,除了在违反约束条件的地方,你希望惩罚在目标的任何地方都可以忽略不计...在那里,你希望惩罚支配目标。这就是我所能做到的最好的。 - Mike McKerns
显示剩余6条评论

17

scipy 有一个针对约束非线性优化的出色包。

您可以通过阅读 optimize 文档 来入手,但这里有一个使用 SLSQP 的示例:

minimize(func, [-1.0,1.0], args=(-1.0,), jac=func_deriv, constraints=cons, method='SLSQP', options={'disp': True})

2
谢谢Slater,但是计算上述问题的雅可比矩阵似乎对我来说并不简单。 - akhil
2
@akhil,雅可比矩阵是可选输入。仔细阅读实际文档可以更好地帮助您启动和运行,胜过我所能提供的任何帮助。 - Slater Victoroff
1
是的,我解决了上面的问题。非常感谢! - akhil
1
嗨,SLSQP算法能用于非凸优化问题吗? - chink
1
@Chinni SLSQP不应该假设一个凸函数。也就是说,非凸优化很困难,无法告诉您SLSQP的工作效果如何。 - Slater Victoroff

15

正如其他评论所指出的,SciPy minimize软件包是一个很好的起点。我们还在Python Gekko论文(见第4节)中对许多其他优化软件包进行了审查。我在下面提供了一个例子(Hock Schittkowski#71基准),其中包括一个目标函数、等式约束和不等式约束,在Scipy.optimize.minimize中实现。

import numpy as np
from scipy.optimize import minimize

def objective(x):
    return x[0]*x[3]*(x[0]+x[1]+x[2])+x[2]

def constraint1(x):
    return x[0]*x[1]*x[2]*x[3]-25.0

def constraint2(x):
    sum_eq = 40.0
    for i in range(4):
        sum_eq = sum_eq - x[i]**2
    return sum_eq

# initial guesses
n = 4
x0 = np.zeros(n)
x0[0] = 1.0
x0[1] = 5.0
x0[2] = 5.0
x0[3] = 1.0

# show initial objective
print('Initial SSE Objective: ' + str(objective(x0)))

# optimize
b = (1.0,5.0)
bnds = (b, b, b, b)
con1 = {'type': 'ineq', 'fun': constraint1} 
con2 = {'type': 'eq', 'fun': constraint2}
cons = ([con1,con2])
solution = minimize(objective,x0,method='SLSQP',\
                    bounds=bnds,constraints=cons)
x = solution.x

# show final objective
print('Final SSE Objective: ' + str(objective(x)))

# print solution
print('Solution')
print('x1 = ' + str(x[0]))
print('x2 = ' + str(x[1]))
print('x3 = ' + str(x[2]))
print('x4 = ' + str(x[3]))

以下是使用Python Gekko解决同样问题的代码:

from gekko import GEKKO
m = GEKKO()
x1,x2,x3,x4 = m.Array(m.Var,4,lb=1,ub=5)
x1.value = 1; x2.value = 5; x3.value = 5; x4.value = 1

m.Equation(x1*x2*x3*x4>=25)
m.Equation(x1**2+x2**2+x3**2+x4**2==40)
m.Minimize(x1*x4*(x1+x2+x3)+x3)

m.solve(disp=False)
print(x1.value,x2.value,x3.value,x4.value)

如果SLSQP无法解决您的问题,Python非线性规划求解器还有更全面的讨论。如果需要求解器方法的详细信息,可以获取我关于工程设计优化的课程材料。


谢谢您的回答,关于def constraint2(x),您能否提供sum_eq到约束函数中,而不是像def constraint2(x, sum_eq)那样硬编码?我打算使用点积约束,例如np.linalg.dot(x,p) == 0,其中p是我在优化过程之外计算的静态向量。谢谢。 - Zhubarb
你可以通过使用偏函数实现这样的操作,例如 new_constraint = functools.partial(constraint2, p=p) - Joonatan Samuel
你可以在Gekko中使用数组,例如 x=m.Array(m.Var,(4,3))p=m.Array(m.Var,3)。然后,你可以将点积包含在 m.Equations(np.linalg.dot(x,p) == 0) 中。这里有一些示例:https://github.com/BYU-PRISM/GEKKO/blob/master/examples/test_arrays.py 和 https://github.com/BYU-PRISM/GEKKO/blob/master/examples/test_matrix.py。 - John Hedengren
1
@JohnHedengren,嘿,伙计,GEKKO看起来非常棒,具有非常直观和简单的API,这是大多数优化包所缺乏的。您能解释一下GEKKO相对于其他包(如SciPy)的主要优势是什么吗? - GitHunter0
1
两者都有优点和缺点。GEKKO具有超越非线性规划的其他解决方案模式:https://gekko.readthedocs.io/en/latest/overview.html 它使用自动微分以稀疏形式提供精确的一阶和二阶导数,以供IPOPT和APOPT等求解器使用。对于大规模问题,它可以更快。主要缺点是它不能使用外部模型函数调用。方程必须用Python编写。 - John Hedengren

3
通常用于拟合的可以使用 scipy.optimize 函数,或者是 lmfit,后者只是简单地扩展了 scipy.optimize 包以便更轻松地传递例如边界之类的内容。我个人喜欢使用 kapteyn 库中的 kmpfit,它基于 MPFIT 的 C 实现。 scipy.optimize.minimize() 可能是最易于获得并且常用的函数。

谢谢 pseudocubic。这些包看起来很棒。我现在会尝试它们。只是想知道是否有一种简单的方法来进行矩阵乘法?还是我需要展开每个术语? - akhil
其实我的问题不是适配问题。 - akhil

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