Как сделать полиномиальную подгонку с фиксированными точками в 3D

У меня есть наборы точек x, y, z в трехмерном пространстве и еще одна переменная с именем charge, которая представляет количество заряда, отложенного в определенной координате x, y, z. Я хотел бы сделать взвешенный (взвешенный по количеству заряда, накопленного в детекторе, который просто соответствует более высокому весу для большего заряда) для этих данных, чтобы они проходили через заданную точку, вершину.

Теперь, когда я делал это для 2D, я пробовал всевозможные методы (приводя вершину к началу координат и выполняя такое же преобразование для всех других точек и заставляя подгонку проходить через начало координат, придавая вершине действительно большой вес), но ни один из них не был так хорош, как ответ, данный Хайме здесь: Как сделать полиномиальную подгонку с фиксированными точками

Он использует метод множителей Лагранжа, с которым я смутно знаком по студенческому курсу Advanced Multi variable, но не более того, и не похоже, что преобразование этого кода будет таким же простым, как простое добавление координаты z. (Обратите внимание, что хотя код не учитывает сумму депонированного платежа, он все же дал мне наилучшие результаты). Мне было интересно, есть ли версия того же алгоритма, но в 3D. Я также связался с автором ответа в Gmail, но не получил от него ответа.

Вот еще немного информации о моих данных и о том, что я пытаюсь сделать в 2D: Как взвесить точки на точечной диаграмме для подбора?

Вот мой код для этого таким образом, что я заставляю вершину находиться в начале координат, а затем подбираю настройку данных fit_intercept=False. В настоящее время я использую этот метод для 2D-данных, так как не уверен, есть ли 3D-версия для множителей Лагранжа, но есть способы линейной регрессии сделать это в 3D, например, здесь: Подгонка линии в 3D:

import numpy as np
import sklearn.linear_model

def plot_best_fit(image_array, vertexX, vertexY):
    weights = np.array(image_array)
    x = np.where(weights>0)[1]
    y = np.where(weights>0)[0]
    size = len(image_array) * len(image_array[0])
    y = np.zeros((len(image_array), len(image_array[0])))
    for i in range(len(np.where(weights>0)[0])):
        y[np.where(weights>0)[0][i]][np.where(weights>0)[1][i]] = np.where(weights>0)[0][i]
    y = y.reshape(size)
    x = np.array(range(len(image_array)) * len(image_array[0]))
    weights = weights.reshape((size))
    for i in range(len(x)):
        x[i] -= vertexX
        y[i] -= vertexY
    model = sklearn.linear_model.LinearRegression(fit_intercept=False)
    model.fit(x.reshape((-1, 1)),y,sample_weight=weights)
    line_x = np.linspace(0, 512, 100).reshape((-1,1))
    pred = model.predict(line_x)
    m, b = np.polyfit(np.linspace(0, 512, 100), np.array(pred), 1)
    angle = math.atan(m) * 180/math.pi
    return line_x, pred, angle, b, m

image_array — это массив numpy, а vertexX и vertexY — это координаты x и y вершины соответственно. Вот мои данные: https://uploadfiles.io/bbhxo. Я не могу создать игрушечные данные, так как нет простого способа воспроизвести эти данные, они были получены с помощью Geant4, моделирующего нейтрино, взаимодействующее с ядром аргона. Я не хочу избавляться от сложности данных. И это конкретное событие оказывается тем, для которого мой код не работает, я не уверен, смогу ли я сгенерировать данные специально, чтобы мой код не работал с ним.


person Always Learning Forever    schedule 13.08.2018    source источник
comment
Вы пытаетесь придать больший вес очкам с большей платой? Или вы пытаетесь провести подогнанную линию через несколько ключевых точек? Это две разные проблемы. На первую проблему уже есть ответ в вашем предыдущем вопросе. Множители Лагранжа помогут решить вторую проблему (т. е. подобрать кривую с учетом ограничений).   -  person Andrew Guy    schedule 13.08.2018
comment
В идеале оба (в моем случае есть только один момент, который я должен выполнить). Но, как я сказал в вопросе, если бы я мог получить множитель Лагранжа для 3D (поскольку он лучше всего работает для 2D) без весов, этого было бы достаточно.   -  person Always Learning Forever    schedule 13.08.2018
comment
Итак, у вас есть i) единственная точка, которая должна находиться на линии наилучшего соответствия, и ii) хотите применить веса ко всем остальным точкам? Я бы перецентрировал ваши данные вокруг вашей точки ограничения, а затем просто подогнал бы полиномиальную регрессию, используя Scikit-learn с соответствующими весами, установив fit_intercept=False.   -  person Andrew Guy    schedule 13.08.2018
comment
Когда вы говорите центрировать мои данные вокруг точки ограничения, вы имеете в виду переместить точку ограничения в начало координат и изменить все остальные точки на ту же величину?   -  person Always Learning Forever    schedule 13.08.2018
comment
Кроме того, нет, это не работает для моих данных   -  person Always Learning Forever    schedule 13.08.2018
comment
Вот один из моих данных, если вам интересно: ufile.io/6ljp1. Первый столбец - это точки оси x, 2-й - ось y, 3-й - веса. Вверху и внизу есть несколько текстов, поэтому вам, возможно, придется их отредактировать.   -  person Always Learning Forever    schedule 13.08.2018
comment
Да, я имею в виду вычесть координаты точки ограничения из каждой точки данных в наборе данных. Почему это не работает для ваших данных?   -  person Andrew Guy    schedule 13.08.2018
comment
Это очень хороший вопрос, я не уверен. Если хотите, можете посмотреть данные сами. Координата вершины: 129 255. Кроме того, пока это только в 2D, как только я найду достойный способ сделать это, я попробую этот подход в 3D.   -  person Always Learning Forever    schedule 13.08.2018
comment
Метод множителей Лагранжа действительно работает здесь, но у меня пока нет его аналога в 3D, и я хочу убедиться, что метод, который я использую, одинаков в 2D и 3D. .   -  person Always Learning Forever    schedule 13.08.2018
comment
Если у вас есть рабочий код для случая 2D, опубликуйте здесь минимальный, полный, воспроизводимый пример. Может быть простое изменение, которое можно распространить на 3D-кейс. В противном случае вы просите кого-то сделать всю работу за вас.   -  person Andrew Guy    schedule 13.08.2018
comment
Конечно, вот мой пустой массив (вершина включена в начало): ufile.io/bbhxo. Вот мой код: textuploader.com/d7ssa. Вы передаете массив numpy в качестве первого аргумента image_array и vertexX, vertexY — это x, y. координаты вершины. Этот пример страдает от проблемы, заключающейся в том, что он не соответствует данным должным образом. Лучше всего, опять же, работает множитель Лагранжа, код которого указан в вопросе. Мне потребуется некоторое время, чтобы сделать мой текущий код более независимым, чтобы его можно было использовать, но я могу это сделать, если вам это нужно.   -  person Always Learning Forever    schedule 13.08.2018
comment
Пожалуйста, не ссылайтесь на внешние сайты для файлов кода/данных. У меня нет возможности узнать, заслуживают ли они доверия. Вы должны отредактировать свой вопрос, включив в него соответствующий код и некоторые игровые данные. Кроме того, убедитесь, что это MCVE - stackoverflow.com/help/mcve. Если вы сделаете это, вы резко увеличите шансы кого-то решение вашей проблемы.   -  person Andrew Guy    schedule 13.08.2018
comment
Я могу включить сюда свой код, но не думаю, что смогу имитировать свои данные. Мой код отлично работает для большинства моих данных, но не для некоторых образцов. Это один из тех образцов, которые не работают   -  person Always Learning Forever    schedule 13.08.2018
comment
Просто позвольте мне спросить еще раз, единственное, что вам нужно, это линия, которая проходит через фиксированную точку и наилучшим образом соответствует набору данных, где точки данных имеют разные веса?   -  person mikuszefski    schedule 16.08.2018
comment
@mikuszefski Это правильно   -  person Always Learning Forever    schedule 16.08.2018


Ответы (1)


Это скорее сделанное вручную решение с использованием базовой оптимизации. Это прямо вперед. Нужно просто измерить расстояние от точки до линии, которую нужно подобрать, и минимизировать взвешенные расстояния, используя базовый optimize.leastsq. Код выглядит следующим образом:

# -*- coding: utf-8 -*
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.cm as cm
from scipy import optimize
import numpy as np

def rnd( a ):
    return  a * ( 1 - 2 * np.random.random() ) 

def affine_line( s, theta, phi, x0, y0, z0 ):
    a = np.sin( theta) * np.cos( phi )
    b = np.sin( theta) * np.sin( phi )
    c = np.cos( theta )
    return np.array( [ s * a + x0, s * b + y0, s * c + z0 ] )

def point_to_line_distance( x , y, z , theta, phi, x0, y0, z0 ):
    xx = x - x0
    yy = y - y0
    zz = z - z0
    a = np.sin( theta) * np.cos( phi )
    b = np.sin( theta) * np.sin( phi )
    c = np.cos( theta )
    r = np.array( [ xx, yy, zz ] )
    t = np.array( [ a, b, c ] )
    return np.linalg.norm( r - np.dot( r, t) * t )

def residuals( parameters, fixpoint, data, weights=None ):
    theta, phi = parameters
    x0, y0, z0 = fixpoint
    if weights is None:
        w = np.ones( len( data ) )
    else:
        w = np.array( weights )
    diff = np.array( [ point_to_line_distance( x , y, z , theta, phi , *fixpoint ) for x, y, z in data ] )
    diff = diff * w
    return diff

### some test data
fixpoint = [ 1, 2 , -.3 ]
trueline = np.array( [ affine_line( s, .7, 1.7, *fixpoint ) for s in np.linspace( -1, 2, 50 ) ] )
rndData = np.array( [ np.array( [ a + rnd( .6), b + rnd( .35 ), c + rnd( .45 ) ] ) for a, b, c in trueline ] )
zData = [ 20 * point_to_line_distance( x , y, z , .7, 1.7, *fixpoint ) for x, y, z in rndData ]

### unweighted
bestFitValuesUW, ier= optimize.leastsq(residuals, [ 0, 0],args=( fixpoint, rndData ) )
print bestFitValuesUW
uwLine = np.array( [ affine_line( s, bestFitValuesUW[0], bestFitValuesUW[1], *fixpoint ) for s in np.linspace( -2, 2, 50 ) ] )

### weighted ( chose inverse distance as weight....would be charge in OP's case )
bestFitValuesW, ier= optimize.leastsq(residuals, [ 0, 0],args=( fixpoint, rndData, [ 1./s for s in zData ] ) )
print bestFitValuesW
wLine = np.array( [ affine_line( s, bestFitValuesW[0], bestFitValuesW[1], *fixpoint ) for s in np.linspace( -2, 2, 50 ) ] )

### plotting
fig = plt.figure()
ax = fig.add_subplot( 1, 1, 1, projection='3d' )
ax.plot( *np.transpose(trueline ) ) 
ax.scatter( *fixpoint, color='k' )
ax.scatter( rndData[::,0], rndData[::,1], rndData[::,2] , c=zData, cmap=cm.jet )

ax.plot( *np.transpose( uwLine ) ) 
ax.plot( *np.transpose( wLine ) ) 

ax.set_xlim( [ 0, 2.5 ] )
ax.set_ylim( [ 1, 3.5 ] )
ax.set_zlim( [ -1.25, 1.25 ] )

plt.show()

который возвращает

>> [-0.68236386 -1.3057938 ]
>> [-0.70928735 -1.4617517 ]

результаты

Фиксированная точка показана черным цветом. Оригинальная линия синего цвета. Невзвешенная и взвешенная подгонка выделены оранжевым и зеленым цветом соответственно. Данные окрашены в соответствии с расстоянием до линии.

person mikuszefski    schedule 17.08.2018
comment
Быстрый вопрос: какие именно два значения возвращаются? [-0,68236386 -1,3057938 ], это тэта и фи? - person Always Learning Forever; 12.09.2018
comment
Кроме того, что вы подразумеваете под оригинальной линией? - person Always Learning Forever; 12.09.2018
comment
@AlwaysLearningForever Да, возвращаемые параметры являются наиболее подходящими значениями для theta и phi. Поскольку это общие данные, я сгенерировал их, предоставив заданную строку, добавив случайные числа. Это «исходная линия», и, следовательно, подгонка должна быть близка к этой линии. - person mikuszefski; 12.09.2018
comment
@AlwaysLearningForever В этом случае у меня / у нас есть преимущество знать, как должна выглядеть подгонка. И это может помочь немного поиграть с ним. Можно видеть, например, что в зависимости от того, как распределяются случайные точки, взвешивание оказывает большее или меньшее влияние. - person mikuszefski; 12.09.2018