почему список кортежей не работает в качестве аргумента для optimise.leasztsq?

Я использую функцию наименьший квадрат из scipy.optimize, чтобы подогнать координаты сферы и радиус из 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

(Построено благодаря: как мне подгонять 3D-данные.)

Я начал работать с переменными координатами в виде списка кортежей (каждый кортеж представляет собой координату 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 — количество параметров, хранящихся в компьютере, а M — количество точек данных. Это выглядит так, как будто я не дал достаточно точек данных, в то время как мои координаты списка фактически перегруппировывают 351 кортеж против 4 параметров в pc!

Из того, что я прочитал в мини-пакете, фактическим виновником кажется эта строка (из _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, и он начал работать нормально. Я действительно хотел бы понять, почему формат данных доставлял мне проблемы!

Заранее большое спасибо за ваши ответы!

РЕДАКТИРОВАТЬ: я заметил, что мое использование координат для функций «расстояние» и «ошибка» было действительно неразумным и вводящим в заблуждение, этого не было в моем исходном коде, поэтому это не было ядром проблемы. Теперь побольше смысла.


person Michel    schedule 27.08.2015    source источник
comment
Ваша функция расстояния распаковывает координаты как x, y, z = [(x0, y0, z0), (x1, y1, z1), (x2, y2, z2), (x3, y3, z3), etc], что переводит первый элемент списка в x, второй в y и третий в z, но ваш список на самом деле намного длиннее этого. То есть он попытается установить x в (x0, y0, z0). Что вам нужно, так это x, y, z = zip(coords), который будет делать то же самое, что и транспонирование. Я не знаю, почему вы не получили ValueError: too many values to unpack   -  person askewchan    schedule 27.08.2015
comment
У вас также есть опечатка в вашей функции distance: в xc,yx,zx,rd = pc я думаю, что yx и zx должны быть yc и zc.   -  person askewchan    schedule 27.08.2015
comment
yikes, вы совершенно правы насчет этих yc и zc, спасибо. Что касается вашего предыдущего пункта, я думаю, что функция lesssq() отправляет элемент args (так что это будут кортежи (x0, y0, z0), (x1, y1, z1), (x2, y2, z2), (x3 , y3, z3 ) и т. д.) один за другим к моей лямбда-функции, которую я вызвал err. Поэтому я ожидаю, что Distance() также получит pc и только один кортеж, а не полный список.   -  person Michel    schedule 27.08.2015
comment
Хм, я думаю, что он просто распаковывает args, который представляет собой кортеж вокруг coords целиком, поэтому он передает args = (coords,) как err(pc, *args), что эквивалентно err(pc, args[0], ...), что, в свою очередь, равно err(pc, coords), поскольку len(args) равно 1.   -  person askewchan    schedule 27.08.2015


Ответы (3)


Ваша функция err должна принимать полный список координат и возвращать полный список расстояний. Затем 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)
person askewchan    schedule 27.08.2015
comment
Хотя эта функция расстояния довольно привлекательна, мне нужно иметь возможность управлять расстоянием между точкой и массивом и точкой против точки. Второй случай потерпит неудачу, поэтому я пока буду придерживаться «математического» решения. Я посмотрю, смогу ли я заставить это работать без лишнего дергания за волосы, как только все остальное будет улажено! - person Michel; 28.08.2015
comment
то, что я искал, на самом деле: np.linalg.norm (ab, axis = 1). Это должно осчастливить всех! edit: aouch нет, это не с расстоянием между точками:/ - person Michel; 28.08.2015
comment
Это верно, если вы не укажете ось, linalg.norm(a) с матрицей возвращает норму матрицы. Однако linalg.norm(a, axis = 1) дает вам норму каждого столбца, следовательно, linalg.norm (a-b, axis = 1), где b - координаты точки, от которой вы хотите расстояние. К сожалению, это не работает, если a и b являются простыми координатными векторами. - person Michel; 28.08.2015
comment
Да, для этого и нужен np.atleast_2d. В качестве альтернативы вы можете использовать axis=-1, что в любом случае может быть быстрее, но вам все равно придется в какой-то момент преобразовать в array, чтобы почувствовать разницу. - person askewchan; 28.08.2015

Хотя я мало использовал эту функцию, насколько я могу судить, 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

Похоже на опечатку в коде, который вы нам дали, !naughty, naughty!.

Исправление этого:

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. Это то, что вы хотите?

Откуда вы взяли, что leastsq передает ваш coords один кортеж за раз вашему lambda?

args : tuple В этот кортеж помещаются все дополнительные аргументы функции func.

В общем, с этими optimize функциями, если вы хотите выполнить операцию с набором условий, вам нужно выполнить итерацию по этим условиям, вызывая оптимизацию для каждого из них. Или, если вы хотите оптимизировать сразу весь набор, вам нужно написать свою функцию (ошибка и т. д.) для работы со всем набором сразу.

person hpaulj    schedule 27.08.2015
comment
Хм, как вы и Askewchan сказали, что наименьший квадрат действительно кормит мою лямбду за один раз, не знаю, почему я думал, что он будет кормить по одному! Однако мой вопрос был больше о сбое наименьшего квадрата, который происходит еще до того, как вызывается расстояние (что также было бы сбоем!). Я не понимаю, почему res = atleast_1d(distance(*(pc[:len(pc)],) + args) терпит неудачу со списком кортежей, а не с массивом, хотя, вероятно, мне следует немного потянуть за волосы, спасибо в любом случае ! - person Michel; 27.08.2015
comment
Я озадачен тем, почему _check_func вернулся без ошибки, позволив leastsq выполнить тест n>m и вызвать собственную ошибку TypeError. Мне нужно больше изучить последовательность вызовов. - person hpaulj; 27.08.2015
comment
Когда я пытаюсь запустить ваш код, я продолжаю получать ValueError: too many values to unpack (expected 3) — ошибку в вашей функции distance. Интересно, есть ли в вашей версии scipy блок try, который позволяет выйти и вернуть TypeError. - person hpaulj; 27.08.2015
comment
Расстояние для меня отлично работает с массивами, вы убедились, что coords.shape = (3,N)? Я серьезно надеюсь, что не сделал еще одну опечатку! - person Michel; 28.08.2015
comment
Мой последний комментарий был о версии списка - person hpaulj; 28.08.2015

Итак, вот что я придумал из предыдущей справки:

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])

Комбинируя эту версию с версией Аскьючана, т.е. имея:

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 зашумленных точек данных было недостаточно, чтобы получить правильную кривизну!).

При сравнении с моим первым кодом я все еще не совсем уверен, что было не так, я, вероятно, напутал с глобальными/локальными переменными, хотя я действительно не помню, чтобы использовал какой-либо «глобальный» оператор ни в одной из моих функций.

person Michel    schedule 28.08.2015