web-dev-qa-db-ja.com

pythonで非線形方程式を解く

3つの未知数XY、およびZを含む4つの非線形方程式を解きます。方程式の形式は次のとおりです。

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

...ここで、ab、およびcは、4つの方程式のFの各値に依存する定数です。

これを解決する最善の方法は何ですか?

20
user1171835

これを行うには2つの方法があります。

  1. 非線形ソルバーを使用する
  2. 問題を線形化し、最小二乗の意味で解決する

セットアップ

したがって、私はあなたの質問を理解しているので、4つの異なるポイントでF、a、b、およびcを知っていて、モデルパラメータ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)
_

4つの異なるポイントでのFXY、およびZを知っている場所(例:_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_。 def、およびgの最小二乗線形反転を行うのは簡単です。その後、次からab、および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_(私は地球物理学者なので、「Green's Functions」にはGを使用し、「Model Parameters」にはmを使用します。通常はdの代わりにFの代わりに「データ」も使用します。)

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
_

非線形ソリューション

@Joeが提案したように、_scipy.optimize_を使用してこれを解決することもできます。 _scipy.optimize_で最もアクセスしやすい関数は_scipy.optimize.curve_fit_で、デフォルトでLevenberg-Marquardtメソッドを使用します。

Levenberg-Marquardtは「登山」アルゴリズムです(この場合は下り坂になりますが、この用語はとにかく使用されます)。ある意味では、モデルパラメーターの初期推測(すべて1、デフォルトでは_scipy.optimize_)を行い、パラメータースペースの_observed - predicted_の勾配に従って下に下ります。

警告:適切な非線形反転法の選択、初期推測、および方法のパラメーターの調整は、非常に「暗い芸術」です。あなたはそれをすることによってそれを学ぶだけであり、物事が適切に機能しない多くの状況があります。 Levenberg-Marquardtは、パラメータ空間がかなり滑らかな場合(これがそうでなければなりません)、一般的な方法として適しています。他にも、シミュレーテッドアニーリングなどのより一般的な方法に加えて、遺伝的アルゴリズム、ニューラルネットなど、他の状況で優れているものがたくさんあります。ここではその部分を掘り下げるつもりはありません。

_scipy.optimize_が処理しようとしていない最適化ツールキットを修正しようとする一般的な落とし穴が1つあります。モデルパラメーターの大きさが異なる場合(例、_a=1, b=1000, c=1e-8_)、大きさが類似するように物事を再スケーリングする必要があります。そうしないと、_scipy.optimize_の「山登り」アルゴリズム(LMなど)が局所勾配の推定値を正確に計算せず、結果が大きく不正確になります。今のところ、ab、およびcの大きさが比較的似ていると仮定しています。また、本質的にすべての非線形手法では、初期推測を行う必要があり、その推測に敏感であることに注意してください。デフォルトの_p0_は_curve_fit_のかなり正確な推測であるため、以下では省略します(_a, b, c = 1, 1, 1_ kwargとして_a, b, c = 3, 2, 1_として渡します)。

邪魔にならないように、_curve_fit_は、関数、観測が行われたポイントのセット(単一の_ndim x npoints_配列として)、および観測値を渡されることを期待しています。

そのため、次のような関数を記述した場合:

_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
_

2つの方法のスタンドアロン例:

完全な実装を提供するために、以下に例を示します

  1. ランダムに分布した点を生成して関数を評価し、
  2. (設定されたモデルパラメーターを使用して)それらのポイントで関数を評価します。
  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()
_
50
Joe Kington

おそらくscipyの非線形ソルバーを使用したいでしょう、それらは本当に簡単です: http://docs.scipy.org/doc/scipy/reference/optimize.nonlin.html

2
Joe