Решение нелинейных уравнений в Python
у меня 4 нелинейных уравнения с тремя неизвестными X
, Y
и Z
что я хочу решить для. Уравнения имеют вид:
F(m) = X^2 + a(m)Y^2 + b(m)XYcosZ + c(m)XYsinZ
...где a
, b
и c
- константы, которые зависят от каждого значения F
в четырех уравнений.
каков наилучший способ решить эту проблему?
2 ответов
есть два способа сделать это.
- используйте нелинейный решатель
- Линеаризовать задачу и решать ее в наименьших квадратов смысла
настройка
Итак, как я понимаю ваш вопрос, вы знаете F, a, b и c в 4 разных точках, и вы хотите инвертировать для параметров модели X, Y и Z. У нас есть 3 неизвестных и 4 наблюдаемых точки данных, поэтому проблема переопределена. Поэтому мы будем решать в метода наименьших квадратов.
в этом случае чаще используется противоположная терминология,поэтому давайте перевернем ваше уравнение. Вместо:
F_i = X^2 + a_i Y^2 + b_i X Y cosZ + c_i X Y sinZ
пишем:
F_i = a^2 + X_i b^2 + Y_i a b cos(c) + Z_i a b sin(c)
откуда мы знаем!--10-->, X
, Y
и Z
в 4 разных точках (например,F_0, F_1, ... F_i
).
мы просто меняем имена переменных, а не само уравнение. (Это больше для моего удобства, чем ничего еще.)
Линейный Решением
это на самом деле можно линеаризовать это уравнение. Вы можете легко решить для a^2
, b^2
, a b cos(c)
и a b sin(c)
. Чтобы сделать это немного проще, давайте еще раз перечислим вещи:
d = a^2
e = b^2
f = a b cos(c)
g = a b sin(c)
теперь уравнение намного проще: F_i = d + e X_i + f Y_i + g Z_i
. Легко сделать линейную инверсию наименьших квадратов для d
, e
, f
и g
. Тогда мы сможем получить a
, b
и c
от:
a = sqrt(d)
b = sqrt(e)
c = arctan(g/f)
Хорошо, давайте запишем это в матричной форме. Мы собираемся перевести 4 наблюдения (код, который мы напишем, будет принимать любое количество наблюдений, но давайте сохраним его конкретным на данный момент):
F_i = d + e X_i + f Y_i + g Z_i
в:
|F_0| |1, X_0, Y_0, Z_0| |d|
|F_1| = |1, X_1, Y_1, Z_1| * |e|
|F_2| |1, X_2, Y_2, Z_2| |f|
|F_3| |1, X_3, Y_3, Z_3| |g|
или: F = G * m
(я geophysist, поэтому мы используем G
для "функций Грина" и m
для "параметры модели". Обычно мы использовали d
для "данных" вместо F
, так же.)
In python, это переводится на:
def invert(f, x, y, z):
G = np.vstack([np.ones_like(x), x, y, z]).T
m, _, _, _ = np.linalg.lstsq(G, f)
d, e, f, g = m
a = np.sqrt(d)
b = np.sqrt(e)
c = np.arctan2(g, f) # Note that `c` will be in radians, not degrees
return a, b, c
нелинейное решение
вы также можете решить эту проблему с помощью scipy.optimize
, как предложил @Joe. Самая доступная функция в scipy.optimize
is scipy.optimize.curve_fit
который использует метод Levenberg-Marquardt по умолчанию.
Levenberg-Marquardt-это алгоритм "восхождения на холм" (ну, в этом случае он идет вниз, но термин все равно используется). В некотором смысле вы делаете первоначальное предположение о параметрах модели (все они, по умолчанию в scipy.optimize
) и следуйте по склону observed - predicted
в пространстве параметров вниз до дна.
предостережение: выбор правильного метода нелинейной инверсии, начальная догадка и настройка параметров метода-это очень "темное искусство". Вы только узнаете это, делая это, и есть много ситуаций, когда вещи не будут работать должным образом. Levenberg-Marquardt-хороший общий метод, если ваше пространство параметров довольно гладкое (это должно быть). Есть много другие (включая генетические алгоритмы, нейронные сети и т. д. В дополнение к более распространенным методам, таким как имитационный отжиг), которые лучше в других ситуациях. Я не собираюсь углубляться в эту часть.
есть один общий gotcha, что некоторые инструменты оптимизации пытаются исправить для этого scipy.optimize
не пытается справиться. Если параметры модели имеют разные значения (например,a=1, b=1000, c=1e-8
), вам нужно будет масштабировать вещи так, чтобы они были похожи по величине. В противном случае scipy.optimize
'ы алгоритмы "восхождения на холм" (например, LM) не будут точно вычислять оценку локального градиента и дадут дико неточные результаты. На данный момент, я предполагаю, что a
, b
и c
относительно аналогичных величин. Кроме того, имейте в виду, что по существу все нелинейные методы требуют от вас сделать первоначальное предположение и чувствительны к этому предположению. Я оставляю его ниже (просто передайте его как p0
kwarg к curve_fit
) потому что по умолчанию a, b, c = 1, 1, 1
- это довольно точный думаю для a, b, c = 3, 2, 1
.
с оговорками в сторону, curve_fit
ожидает, что будет передана функция, набор точек, где были сделаны наблюдения (как один ndim x npoints
array) и наблюдаемые значения.
Итак, если мы напишем функцию следующим образом:
def func(x, y, z, a, b, c):
f = (a**2
+ x * b**2
+ y * a * b * np.cos(c)
+ z * a * b * np.sin(c))
return f
нам нужно обернуть его, чтобы принять немного разные аргументы, прежде чем передавать его curve_fit
.
в двух словах:
def nonlinear_invert(f, x, y, z):
def wrapped_func(observation_points, a, b, c):
x, y, z = observation_points
return func(x, y, z, a, b, c)
xdata = np.vstack([x, y, z])
model, cov = opt.curve_fit(wrapped_func, xdata, f)
return model
автономный пример двух методы:
даст вам полную реализацию, вот пример
- генерирует случайно распределенные точки для оценки функции on,
- оценивает функцию на этих точках (используя параметры модели set),
- добавляет шума к результатам,
- а затем инвертирует параметры модели, используя как линейные, так и нелинейные методы, описанные выше.
import numpy as np
import scipy.optimize as opt
def main():
nobservations = 4
a, b, c = 3.0, 2.0, 1.0
f, x, y, z = generate_data(nobservations, a, b, c)
print 'Linear results (should be {}, {}, {}):'.format(a, b, c)
print linear_invert(f, x, y, z)
print 'Non-linear results (should be {}, {}, {}):'.format(a, b, c)
print nonlinear_invert(f, x, y, z)
def generate_data(nobservations, a, b, c, noise_level=0.01):
x, y, z = np.random.random((3, nobservations))
noise = noise_level * np.random.normal(0, noise_level, nobservations)
f = func(x, y, z, a, b, c) + noise
return f, x, y, z
def func(x, y, z, a, b, c):
f = (a**2
+ x * b**2
+ y * a * b * np.cos(c)
+ z * a * b * np.sin(c))
return f
def linear_invert(f, x, y, z):
G = np.vstack([np.ones_like(x), x, y, z]).T
m, _, _, _ = np.linalg.lstsq(G, f)
d, e, f, g = m
a = np.sqrt(d)
b = np.sqrt(e)
c = np.arctan2(g, f) # Note that `c` will be in radians, not degrees
return a, b, c
def nonlinear_invert(f, x, y, z):
# "curve_fit" expects the function to take a slightly different form...
def wrapped_func(observation_points, a, b, c):
x, y, z = observation_points
return func(x, y, z, a, b, c)
xdata = np.vstack([x, y, z])
model, cov = opt.curve_fit(wrapped_func, xdata, f)
return model
main()
вы, вероятно, хотите использовать нелинейные решатели scipy, они очень просты:http://docs.scipy.org/doc/scipy/reference/optimize.nonlin.html