Python 如何使用scipy.optimize同时最小化多个标量值函数

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-

我问了一种使用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-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))