为什么元组列表作为 optimize.leasztsq 的参数失败?

why is list of tuple failing as an argument for optimize.leasztsq?

我使用 scipy.optimize 中的函数 leastsq 来拟合 3D 坐标中的球体坐标和半径。

所以我的代码如下所示:

def distance(pc,point):

    xc,yc,zc,rd = pc
    x ,y ,z     = point
    return np.sqrt((xc-x)**2+(yc-y)**2+(zc-z)**2)

def sphere_params(coords):

    from scipy import optimize

    err = lambda pc,point : distance(pc,point) - pc[3]

    pc = [0, 0, 0, 1]
    pc, success = optimize.leastsq(err, pc[:], args=(coords,))

    return pc

(感谢:how do I fit 3D data。)

我开始使用变量坐标作为元组列表(每个元组是一个 x、y、z 坐标):

>> coords
>> [(0,0,0),(0,0,1),(-1,0,0),(0.57,0.57,0.57),...,(1,0,0),(0,1,0)]

这导致我出错:

>> pc = sphere_params(coords)
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "/home/michel/anaconda/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 374, in leastsq
     raise TypeError('Improper input: N=%s must not exceed M=%s' % (n, m))
TypeError: Improper input: N=4 must not exceed M=3

其中N为pc中存储的参数个数,M为数据点个数。这让我看起来好像没有提供足够的数据点,而我的列表坐标实际上将 351 个元组与 pc 中的 4 个参数重新分组!

从我在 minipack 中读到的内容来看,真正的罪魁祸首似乎是这一行(来自 _check_func()):

res = atleast_1d(thefunc(*((x0[:numinputs],) + args)))

除非我记错了,在我的例子中它翻译成

res = atleast_1d(distance(*(pc[:len(pc)],) + args)

但我很难理解这与 _check_func() 函数的其余部分的含义。

我最终将坐标更改为数组,然后将其作为 sphere_param() 的参数:coords = np.asarray(coords).T,它开始工作得很好。我真的很想了解为什么数据格式给我带来麻烦!

提前,非常感谢您的回答!

编辑:我注意到我在 "distance" 和 "err" 函数中使用坐标 确实 不明智且具有误导性,但我的情况并非如此原始代码所以它不是问题的核心。现在更有意义了。

虽然我没怎么用过这个函数,但据我所知,coords 会按原样传递给您的 distance 函数。至少在错误检查允许的情况下会这样。事实上,错误检查很可能会尝试这样做,如果 distance 引发错误,则会引发错误。那么让我们试试吧。

In [91]: coords=[(0,0,0),(0,0,1),(-1,0,0),(0.57,0.57,0.57),(1,0,0),(0,1,0)]

In [92]: distance([0,0,0,0],coords)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-92-113da104affb> in <module>()
----> 1 distance([0,0,0,0],coords)

<ipython-input-89-64c557cd95e0> in distance(pc, coords)
      2 
      3         xc,yx,zx,rd = pc
----> 4         x ,y ,z     = coords
      5         return np.sqrt((xc-x)**2+(yc-y)**2+(zc-z)**2)
      6 

ValueError: too many values to unpack (expected 3)

这就是 3 的来源 - 你的 x, y, z = coords

distance([0,0,0,0],np.array(coords))

同样的错误。

distance([0,0,0,0],np.array(coords).T)

解决了那个问题(3 行可以拆分为 3 个变量),引发了另一个错误:NameError: name 'yc' is not defined

这看起来像是您给我们的代码中的错字,!顽皮,顽皮!。

更正:

In [97]: def distance(pc,coords):

        xc,yc,zc,rd = pc
        x ,y ,z     = coords
        return np.sqrt((xc-x)**2+(yc-y)**2+(zc-z)**2)
   ....: 

In [98]: distance([0,0,0,0],np.array(coords).T)
Out[98]: array([ 0.        ,  1.        ,  1.        ,  0.98726896,  1.        ,  1.        ])

# and wrapping the array in a tuple, as `leastsq` does
In [102]: distance([0,0,0,0],*(np.array(coords).T,))
Out[102]: array([ 0.        ,  1.        ,  1.        ,  0.98726896,  1.        ,  1.        ])

我得到一个 5 元素数组,coords 中每个 'point' 一个值。那是你想要的吗?

您从哪里想到 leastsq 一次将您的 coords 一个元组提供给您的 lambda

args : tuple Any extra arguments to func are placed in this tuple.

一般来说,使用这些 optimize 函数,如果您想对一组条件执行操作,则需要迭代这些条件,对每个条件调用优化。或者如果你想一次优化整个集合,那么你需要编写你的函数(错误等)来一次处理整个集合。

您的 err 函数必须采用完整的坐标列表和 return 完整的距离列表。 leastsq 然后将获取错误列表,对它们进行平方和求和,并将平方和最小化。

scipy.spatial.distance里面也有距离函数,所以我推荐:

from scipy.spatial.distance import cdist
from scipy.optimize import leastsq

def distance_cdist(pc, coords):
    return cdist([pc], coords).squeeze()

def distance_norm(pc, points):
    """ pc must be shape (D+1,) array
        points can be (N, D) or (D,) array """
    c = np.asarray(pc[:3])
    points = np.atleast_2d(points)
    return np.linalg.norm(points-c, axis=1)

def sphere_params(coords):
    err = lambda pc, coords: distance(pc[:3], coords) - pc[3]
    pc = [0, 0, 0, 1]
    pc, success = leastsq(err, pc, args=(coords,))
    return pc

coords = [(0,0,0),(0,0,1),(-1,0,0),(0.57,0.57,0.57),(1,0,0),(0,1,0)]
sphere_params(coords)

这是我从之前的帮助中得出的结论:

import numpy as np
from scipy.optimize import leastsq

def a_dist(a,B):
    # works with - a : reference point - B : coordinates matrix
    return np.linalg.norm(a-B, axis=1) 

def parametric(coords):

    err = lambda pc,point : a_dist(pc,point) - 18

    pc = [0, 0, 0] # Initial guess for the parameters
    pc, success = leastsq(err, pc[:], args=(coords,))

    return pc

它绝对适用于元组列表和形状数组 (N,3)

>>  cluster #it's more than 6000 point you won't have the same result
>>  [(4, 30, 19), (3, 30, 19), (5, 30, 19), ..., (4, 30, 3), (4, 30, 35)]
>>  sphere_params(cluster)
>>  array([ -5.25734467,  20.73419249,   9.73428766])

>>  np.asarray(cluster).shape
>>  (6017,3)
>>  sphere_params(np.asarray(cluster))
>>  array([ -5.25734467,  20.73419249,   9.73428766])

将此版本与 Askewchan 的版本相结合,即:

def sphere_params(coords):
    err = lambda pc, coords: distance(pc[:3], coords) - pc[3]
    pc = [0, 0, 0, 1]
    pc, success = leastsq(err, pc, args=(coords,))
    return pc

也很好用,老实说,我没有花时间尝试您的解决方案。但是,我绝对不再将半径作为拟合参数。我发现它根本不稳健(即使是 6000 个嘈杂的数据点也不足以获得正确的曲率!)。

与我的第一个代码进行比较时,我仍然不太确定哪里出了问题,我可能搞砸了 global/local 变量,尽管我真的不记得在其中使用过任何 "global" 语句我的任何功能。