Решение нелинейных уравнений в Python

у меня 4 нелинейных уравнения с тремя неизвестными X, Y и Z что я хочу решить для. Уравнения имеют вид:

F(m) = X^2 + a(m)Y^2 + b(m)XYcosZ + c(m)XYsinZ

...где a, b и c - константы, которые зависят от каждого значения F в четырех уравнений.

каков наилучший способ решить эту проблему?

2 ответов


есть два способа сделать это.

  1. используйте нелинейный решатель
  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

автономный пример двух методы:

даст вам полную реализацию, вот пример

  1. генерирует случайно распределенные точки для оценки функции on,
  2. оценивает функцию на этих точках (используя параметры модели set),
  3. добавляет шума к результатам,
  4. а затем инвертирует параметры модели, используя как линейные, так и нелинейные методы, описанные выше.

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