Python 如何使用scipy.optimize同时最小化多个标量值函数
我问了一种使用python计算最接近投影点的方法 多亏了这个答案,我能够使用下面的代码来计算多个抛物面的最近点Python 如何使用scipy.optimize同时最小化多个标量值函数,python,performance,numpy,scipy,Python,Performance,Numpy,Scipy,我问了一种使用python计算最接近投影点的方法 多亏了这个答案,我能够使用下面的代码来计算多个抛物面的最近点 from scipy.optimize import minimize # This function calculate the closest projection on a hyperbolic paraboloid # As Answered by @Jaime https://stackoverflow.com/questions/18858448/speeding-up-
from scipy.optimize import minimize
# This function calculate the closest projection on a hyperbolic paraboloid
# As Answered by @Jaime https://stackoverflow.com/questions/18858448/speeding-up-a-closest-point-on-a-hyperbolic-paraboloid-algorithm
def fun_single(x, p0, p1, p2, p3, p):
u, v = x
s = u*(p1-p0) + v*(p3-p0) + u*v*(p2-p3-p1+p0) + p0
return np.linalg.norm(p-s)
# Example use case:
# Generate some random data for 3 random hyperbolic paraboloids
# A real life use case will count in the tens of thousands.
import numpy as np
COUNT = 3
p0 = np.random.random_sample((COUNT,3))
p1 = np.random.random_sample((COUNT,3))
p2 = np.random.random_sample((COUNT,3))
p3 = np.random.random_sample((COUNT,3))
p = np.random.random_sample(3)
uv = []
for i in xrange(COUNT):
uv.append(minimize(fun_single, (0.5, 0.5), (p0[i], p1[i], p2[i], p3[i], p)).x)
uv = np.array(uv)
# UV projections for my random data
#[[ 0.34109572 4.39237344]
# [-0.2720813 0.17083423]
# [ 0.48993333 -0.99415568]]
现在我有了每个项目的投影,可以找到更有用的信息,比如给定的项目中哪一个最接近查询点,找到它的数组索引并从中派生更多数据,等等
为每个项目调用minimize
的问题是,它在处理数十万个项目时变得非常慢。因此,为了解决这个问题,我尝试将函数更改为使用多个输入
from numpy.core.umath_tests import inner1d
# This function calculate the closest projection to many hyperbolic paraboloids
def fun_array(x, p0, p1, p2, p3, p):
u, v = x
s = u*(p1-p0) + v*(p3-p0) + u*v*(p2-p3-p1+p0) + p0
V = p-s
return np.min(np.sqrt(inner1d(V,V)))
# Lets pass all the data to minimize
uv = minimize(fun_array, (0.5, 0.5), (p0, p1, p2, p3, p)).x
# Result: [ 0.25090064, 1.19732181]
# This corresponds to index 2 of my random data,
# which is the closest projection.
最小化函数fun_array
比迭代方法快得多,但它只返回单个最近投影,而不是所有投影
问题:
是否可以使用minimize
像迭代方法一样返回所有投影?如果没有,是否至少可以得到“获胜”数组元素的索引?严格的答案
你必须要有技巧,但是要使最小化并不难。关键是。但是我们可以把所有的距离加起来,因为它们自然是非负的量,全局最小值是由每个距离最小的配置定义的。因此,我们不是求COUNT
二元标量函数的最小值,而是求COUNT*2
变量的单个标量函数的最小值。这恰好是两变量函数的总和。但请注意,我不相信这会更快,因为我可以想象高维最小搜索比相应的低维独立最小搜索集更不稳定
您肯定应该为uv
预先分配内存,并在其中插入值,而不是逐项增加很多次:
uv = np.empty((COUNT,2))
for i in range(COUNT):
uv[i,:] = minimize(fun_single, (0.5, 0.5), (p0[i], p1[i], p2[i], p3[i], p)).x
无论如何,为了使用单个调用来最小化
,我们只需要对函数进行矢量化,这比您想象的要容易:
def fun_vect(x, p0, p1, p2, p3, p):
x = x.reshape(-1,2) # dimensions are mangled by minimize() call
u,v = x.T[...,None] # u,v shaped (COUNT,1) for broadcasting
s = u*(p1-p0) + v*(p3-p0) + u*v*(p2-p3-p1+p0) + p0 # shape (COUNT,3)
return np.linalg.norm(p-s, axis=1).sum() # sum up distances for overall cost
x0 = 0.5*np.ones((COUNT,2))
uv_vect = minimize(fun_vect, x0, (p0, p1, p2, p3, p)).x.reshape(-1,2)
如您所见,此函数沿列扩展标量函数。每行对应一个独立的最小化问题(与点的定义一致)。矢量化很简单,唯一不重要的部分是我们需要处理维度,以确保所有内容都能很好地进行广播,并且我们应该注意在输入时重塑x0
,因为minimize
习惯于展平数组值的输入位置。当然,最终的结果必须重新塑造。相应地,必须提供一个形状数组(计数,2)
,作为x0
,这是最小化
可以推断问题维度的唯一功能
比较我的随机数据:
>>> uv
array([[-0.13386872, 0.14324999],
[ 2.42883931, 0.55099395],
[ 1.03084756, 0.35847593],
[ 1.47276203, 0.29337082]])
>>> uv_vect
array([[-0.13386898, 0.1432499 ],
[ 2.42883952, 0.55099405],
[ 1.03085143, 0.35847888],
[ 1.47276244, 0.29337179]])
请注意,我将COUNT
更改为4,因为我喜欢在测试时保持每个维度的不同。这样,如果我弄乱了我的尺寸,我可以确定我会遇到错误。还请注意,一般来说,您可能希望保留minimize
返回的完整对象,以确保一切正常并收敛
更有用的解决方案
正如我们在评论中所讨论的那样,上述解决方案虽然完美地回答了这个问题,但并不特别可行,因为它运行时间太长,比单独进行每个最小化要长得多。这个问题很有趣,让我思考起来。为什么不尽可能准确地解决这个问题呢
您要做的(现在考虑一个双曲面和一个查询点q)是用
距离d(s,q)
最小。由于距离是一个适当的度量(特别是,它是非负的),这相当于最小化d(s,q)^2
。到目前为止还不错
为了简化推导过程,我们通过引入几个常量向量重写s
的参数化方程:
s(u,v) = p0 + u*a + v*b + u*v*c
s - q = p0-q0 + u*a + v*b + u*v*c
= d + u*a + v*b + u*v*c
d(s,q)^2 = (s-q)^2
(在本节中,^
将表示幂,因为这是线性代数。)现在,距离函数的最小值是一个固定点,因此在u_min,v_min
点中,我们寻找s(u,v)
相对于u
的梯度,并且v
为零。这相当于说d(s,q)^2
对u
和v
的导数必须同时为零;这给了我们两个未知量为u
和v
的非线性方程:
2*(s-q)*ds/du = 0 (1)
2*(s-q)*ds/dv = 0 (2)
扩展这两个方程是一项有点乏味的工作。第一个方程恰好在u
中是线性的,第二个方程在v
中是线性的。我收集了第一个等式中包含u
的所有项,这就给出了关系
u(v) = (-v^2*b.c - v*(c.d + a.b) - a.d)/(a + v*c)^2
其中,
表示点积。上面的方程告诉我们,对于我们选择的任何v
,如果这样选择u
,方程(1)将完全满足。所以我们必须解方程(2)
我所做的是扩展方程(2)中的所有项,并将u(v)
替换为u
。原来的方程有多项式项1,u,v,uv,u^2,u^2v
,所以我可以告诉你这不太好。通过一些不发散的小假设(在直线拟合问题中,发散可能对应于垂直线的等效值),我们可以得出以下美丽的方程式:
(b.d + v*b^2)*f^2 - (c.d + a.b + 2*v*b.c)*e*f + (a.c + v*c^2)*e^2 = 0
新的标量定义为
e = v^2*b.c + v*(c.d + a.b) + a.d
f = (a + v*c)^2 = (a^2 + 2*v*a.c + v^2*c^2)
无论v
如何解这个方程,相应的(u(v),v)
点将对应于距离的一个固定点。我们应该首先注意,这个方程考虑了五阶多项式的根,如果e = v^2*b.c + v*(c.d + a.b) + a.d
f = (a + v*c)^2 = (a^2 + 2*v*a.c + v^2*c^2)
import numpy as np
# generate dummy inputs
COUNT = 100
p0 = np.random.random_sample((COUNT,3))
p1 = np.random.random_sample((COUNT,3))
p2 = np.random.random_sample((COUNT,3))
p3 = np.random.random_sample((COUNT,3))
p = np.random.random_sample(3)
def mydot(v1,v2):
"""generalized dot product for multidimensional arrays: (...,N,3)x(...,N,3) -> (...,N,1)"""
# (used in u_from_v for vectorized dot product)
return np.einsum('...j,...j->...',v1,v2)[...,None]
def u_from_v(v, a, b, c, d):
"""return u(v) corresponding to zero of gradient"""
# use mydot() instead of dot to enable array-valued v input
res = (- v**2*mydot(b,c) - v*(mydot(c,d)+mydot(a,b)) - mydot(a,d))/np.linalg.norm(a+v*c, axis=-1, keepdims=True)**2
return res.squeeze()
def check_distance(uv, p0, p1, p2, p3, p):
"""compute the distance from optimization results to query point"""
u,v = uv.T[...,None]
s = u*(p1-p0) + v*(p3-p0) + u*v*(p2-p3-p1+p0) + p0
return np.linalg.norm(p-s, axis=-1)
def poly_for_v(a, b, c, d):
"""return polynomial representation of derivative of d(s,p)^2 for the parametrized s(u(v),v) point"""
# only works with a scalar problem:( one polynomial at a time
# v is scalar, a-b-c-d are 3-dimensional vectors (for a given paraboloid)
# precompute scalar products appearing multiple times in the formula
ab = a.dot(b)
ac = a.dot(c)
cc = c.dot(c)
cd = c.dot(d)
bc = b.dot(c)
Poly = np.polynomial.polynomial.Polynomial
e = Poly([a.dot(d), cd+ab, bc])
f = Poly([a.dot(a), 2*ac, cc])
res = Poly([b.dot(d), b.dot(b)])*f**2 - Poly([cd+ab,2*bc])*e*f + Poly([ac,cc])*e**2
return res
def minimize_manually(p0, p1, p2, p3, p):
"""numpy polynomial version for the minimization problem"""
# auxiliary arrays, shape (COUNT,3)
a = p1 - p0
b = p3 - p0
c = p2 - p3 - p1 + p0
d = p0 - p
# preallocate for collected result
uv_min = np.empty((COUNT,2))
for k in range(COUNT):
# collect length-3 vectors needed for a given surface
aa,bb,cc,dd = (x[k,:] for x in (a,b,c,d))
# compute 5 complex roots of the derivative distance
roots = poly_for_v(aa, bb, cc, dd).roots()
# keep exactly real roots
vroots = roots[roots.imag==0].real
if vroots.size == 1:
# we're done here
vval, = vroots
uval = u_from_v(vval, aa, bb, cc, dd)
uv_min[k,:] = uval,vval
else:
# need to find the root with minimal distance
uvals = u_from_v(vroots[:,None], aa, bb, cc, dd)
uvtmp = np.stack((uvals,vroots),axis=-1)
dists = check_distance(uvtmp, p0[k,:], p1[k,:], p2[k,:], p3[k,:], p)
winner = np.argmin(dists) # index of (u,v) pair of minimum
uv_min[k,:] = uvtmp[winner,:]
return uv_min
uv_min = minimize_manually(p0, p1, p2, p3, p)
# for comparison with the minimize-based approaches:
# distances = check_distance(uv_manual,p0,p1,p2,p3,p))