如何使用scipy/numpy或sympy执行非线性优化?

8

我正在尝试在Python中找到以下方程组的最优解:

(x-x1)^2 + (y-y1)^2 - r1^2 = 0
(x-x2)^2 + (y-y2)^2 - r2^2 = 0
(x-x3)^2 + (y-y3)^2 - r3^2 = 0

给定一个点(x,y)和半径(r):

x1, y1, r1 = (0, 0, 0.88)
x2, y2, r2 = (2, 0, 1)
x3, y3, r3 = (0, 2, 0.75)

什么是找到点(x,y)的最优解的最佳方法? 使用上面的例子,它将是:
~(1,1)

3
你的函数eqs中有未定义的变量(x和y)。能否提供你正在使用的实际代码? - Warren Weckesser
我正在尝试优化方程组中x和y的值。 - drbunsen
我正在使用这个例子:https://dev59.com/LWoy5IYBdhLWcg3wKq0S - drbunsen
2
fsolve用于数值根查找,而不是优化,即它将寻求找到使函数输出为零的输入值。你指向的例子在这里不适用。此外,在三个方程的背景下,我不明白最优值x和y意味着什么。(从你的代码所说,计算机也不会明白。)请明确你想要实现什么。 - ilmiacs
感谢您的评论,很抱歉表达不清楚。我已经重新措辞了问题,现在应该更加清晰明了了。 - drbunsen
4个回答

17

如果我正确理解了你的问题,我想这就是你所需要的:

from scipy.optimize import minimize
import numpy as np

def f(coord, x, y, r):
    return np.sum(((coord[0] - x) ** 2) + ((coord[1] - y) ** 2) - (r ** 2))

x = np.array([0, 2, 0])
y = np.array([0, 0, 2])
r = np.array([.88, 1, .75])

# initial (bad) guess at (x,y) values
initial_guess = np.array([100, 100])

res = minimize(f, initial_guess, args=(x, y, r))

这会产生:

>>> print res.x
[0.66666665 0.66666665]

你还可以尝试最小二乘法,它期望一个返回向量的目标函数。它希望最小化该向量的平方和。使用最小二乘法,你的目标函数应该像这样:

def f2(coord, x, y, r):
    # notice that we're returning a vector of dimension 3
    return ((coord[0] - x) ** 2) + ((coord[1] - y) ** 2) - (r ** 2)

你可以像这样将其最小化:

from scipy.optimize import leastsq
res = leastsq(f2, initial_guess, args=(x, y, r))

这将产生:

>>> print res[0]
>>> [0.77958134 0.8580946 ]

这基本上与使用minimize并将原始目标函数重写为以下内容相同:

def f(coord, x, y, r):
    vec = ((coord[0] - x) ** 2) + ((coord[1] - y) ** 2) - (r ** 2)
    # return the sum of the squares of the vector
    return np.sum(vec ** 2)

这将产生以下结果:

>>> print res.x
>>> [0.77958326 0.85809648]
请注意,leastsq在处理args时略有不同,并且这两个函数返回的数据结构也不同。请参阅scipy.optimize.minimizescipy.optimize.leastsq的文档了解更多详细信息。

有关更多优化选项,请参阅scipy.optimize文档。


这正是我正在寻找的!感谢您的帮助和耐心,试图理解我想做什么。 - drbunsen
很高兴听到这个消息!你也可以尝试使用L2范数代替总和作为成本函数,即使用np.linalg.norm代替np.sum,看看它如何影响你的结果。 - John Vinyard
最小二乘法(leastsq)也可以在这里使用吗?minimize()和leastsq()之间有什么区别? - drbunsen
这里相关的主要区别是minimize期望一个标量值函数,而leastsq期望一个向量值函数。leastsq希望最小化目标函数返回的向量的平方和,因此它几乎就像使用minimize的l2范数一样。实际上,我使用leastsqminimize的l2范数得到的答案几乎相同:~`[.78,.86]`。 - John Vinyard
啊,太好了。再次感谢。你介意发布leastsq代码,这样我也可以尝试理解它吗? - drbunsen

6
我注意到被接受的解决方案中的代码已经不再起作用了...我认为可能是因为自问题发布以来 scipy.optimize 改变了其接口。我可能是错的。但无论如何,我赞成使用 scipy.optimize 中的算法,并且被接受的答案确实演示了如何使用(如果接口未更改的话)。
我在这里添加了一个额外的答案,纯粹是为了建议一种使用 scipy.optimize 算法作为核心,但对于有约束优化更加强大的替代包,该包是 mystic。 其中一个重大改进就是 mystic 提供了约束全局优化。
首先,这里是您的示例,非常类似于使用全局优化器的 scipy.optimize.minimize 方法。
from mystic import reduced

@reduced(lambda x,y: abs(x)+abs(y)) #choice changes answer
def objective(x, a, b, c):
  x,y = x
  eqns = (\
    (x - a[0])**2 + (y - b[0])**2 - c[0]**2,
    (x - a[1])**2 + (y - b[1])**2 - c[1]**2,
    (x - a[2])**2 + (y - b[2])**2 - c[2]**2)
  return eqns

bounds = [(None,None),(None,None)] #unnecessary

a = (0,2,0)
b = (0,0,2)
c = (.88,1,.75)
args = a,b,c

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

result = diffev2(objective, args=args, x0=bounds, bounds=bounds, npop=40, \ 
                 ftol=1e-8, disp=False, full_output=True, itermon=mon)

print result[0]
print result[1]

当结果看起来像这样时:

Generation 0 has Chi-Squared: 38868.949133
Generation 10 has Chi-Squared: 2777.470642
Generation 20 has Chi-Squared: 12.808055
Generation 30 has Chi-Squared: 3.764840
Generation 40 has Chi-Squared: 2.996441
Generation 50 has Chi-Squared: 2.996441
Generation 60 has Chi-Squared: 2.996440
Generation 70 has Chi-Squared: 2.996433
Generation 80 has Chi-Squared: 2.996433
Generation 90 has Chi-Squared: 2.996433
STOP("VTRChangeOverGeneration with {'gtol': 1e-06, 'target': 0.0, 'generations': 30, 'ftol': 1e-08}")
[ 0.66667151  0.66666422]
2.99643333334

如前所述,在reduced中选择lambda会影响优化器找到的点,因为实际上没有解决方案。

mystic还提供了将符号方程转换为函数的能力,其中生成的函数可以用作目标或惩罚函数。这里是同样的问题,但使用方程作为惩罚而不是目标。

def objective(x):
    return 0.0

equations = """
(x0 - 0)**2 + (x1 - 0)**2 - .88**2 == 0
(x0 - 2)**2 + (x1 - 0)**2 - 1**2 == 0
(x0 - 0)**2 + (x1 - 2)**2 - .75**2 == 0
"""

bounds = [(None,None),(None,None)] #unnecessary

from mystic.symbolic import generate_penalty, generate_conditions
from mystic.solvers import diffev2

pf = generate_penalty(generate_conditions(equations), k=1e12)

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

print result[0]
print result[1]

结果如下:

[ 0.77958328  0.8580965 ]
3.6473132399e+12

结果与以前不同,因为所应用的惩罚与我们之前在“reduced”中应用的惩罚不同。在“mystic”中,您可以选择要应用的惩罚。
有人指出方程没有解。从上面的结果可以看出,结果受到了严重的惩罚,这表明没有解决方案。然而,“mystic”还有另一种方法可以看出没有解。与其应用更传统的“惩罚”,该惩罚会惩罚违反约束条件的解决方案……“mystic”提供了一个“约束条件”,它本质上是一种核转换,可以消除不符合常数的所有潜在解决方案。
def objective(x):
    return 0.0

equations = """
(x0 - 0)**2 + (x1 - 0)**2 - .88**2 == 0
(x0 - 2)**2 + (x1 - 0)**2 - 1**2 == 0
(x0 - 0)**2 + (x1 - 2)**2 - .75**2 == 0
"""

bounds = [(None,None),(None,None)] #unnecessary

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

cf = generate_constraint(generate_solvers(simplify(equations)))

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

print result[0]
print result[1]

带有结果的情况:

[          nan  657.17740835]
0.0

其中nan实际上表示没有有效的解决方案。

顺便说一下,我是作者,所以有些偏见。但是,mystic已经存在了很长时间,几乎与scipy.optimize一样成熟,并且在这段时间内具有更加稳定的接口。我的观点是,如果您需要一个更加灵活和功能强大的约束非线性优化器,我建议使用mystic


这里有更多非线性约束的示例:https://dev59.com/TJTfa4cB1Zd3GeqPULCm#42299338 - Mike McKerns
我已经修复了被接受的解决方案中的代码,所以它现在可以正常工作。 - fdermishin

2
这些方程可以被视为描述二维空间中三个圆周上的所有点。解决方案将是圆相交的点。
圆的半径之和小于它们中心之间的距离,因此圆不重叠。下面按比例绘制了这些圆: geometric solution 没有满足这个方程组的点。

正确,但我想要最优解决方案,如果不存在精确解决方案。 - drbunsen
在这种情况下,当“没有”解决方案存在时,“最优解”是什么意思? - Roland Smith

0
我通过以下方式制作了一个示例脚本。请注意,最后一行将找到一个最优解(a,b):
import numpy as np
import scipy as scp
import sympy as smp
from scipy.optimize import minimize

a,b = smp.symbols('a b')
x_ar, y_ar = np.random.random(3), np.random.random(3)
x = np.array(smp.symbols('x0:%d'%np.shape(x_ar)[0]))
y = np.array(smp.symbols('y0:%d'%np.shape(x_ar)[0]))
func = np.sum(a**2+b**2-x*(a+b)+2*y)
print func
my_func = smp.lambdify((x,y), func)
print 1.0/3*my_func(x_ar,y_ar)
ab = smp.lambdify((a,b),my_func(x_ar,x_ar))
print ab(1,2)

def ab_v(x):
   return ab(*tuple(x))

print ab_v((1,2))

minimize(ab_v,(0.1,0.1))

输出结果为:

3*a**2 + 3*b**2 - x0*(a + b) - x1*(a + b) - x2*(a + b) + 2*y0 + 2*y1 + 2*y2
1.0*a**2 - 0.739792011558683*a + 1.0*b**2 - 0.739792011558683*b    +0.67394435712335


12.7806239653
12.7806239653
Out[33]:
  status: 0
 success: True
 njev: 3
 nfev: 12
 hess_inv: array([[1, 0],
   [0, 1]])
 fun: 3.6178137388030356
 x: array([ 0.36989601,  0.36989601])
 message: 'Optimization terminated successfully.'
 jac: array([  5.96046448e-08,   5.96046448e-08])

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