1

私は scipy の optimize.leastsq に基づいて 2D データの自動カーブ フィッティング ルーチンを作成していますが、動作します。ただし、開始値がわずかにずれている多くの曲線を追加すると、非物理的な結果が得られます (負の振幅など)。

私はこの投稿Scipy: bounds for fit parameter(s) when using optimize.leastsqを見つけ、 Cern の Minuit に従ってパラメーター変換を使用しようとしていました。上記の質問で、誰かが Python コードへのリンクを提供しました。

code.google.com/p/nmrglue/source/browse/trunk/nmrglue/analysis/leastsqbound.py

私はこの最小限の作業例を書きました(コードを拡張します)

"""
http://code.google.com/p/nmrglue/source/browse/trunk/nmrglue/analysis/leastsqbound.py
Constrained multivariate Levenberg-Marquardt optimization
"""

from scipy.optimize import leastsq
import numpy as np
import matplotlib.pyplot as plt #new

def internal2external_grad(xi, bounds):
    """ 
    Calculate the internal to external gradiant

    Calculates the partial of external over internal

    """

    ge = np.empty_like(xi)

    for i, (v, bound) in enumerate(zip(xi, bounds)):

        a = bound[0]    # minimum
        b = bound[1]    # maximum

        if a == None and b == None:    # No constraints
            ge[i] = 1.0

        elif b == None:      # only min
            ge[i] = v / np.sqrt(v ** 2 + 1)

        elif a == None:      # only max
            ge[i] = -v / np.sqrt(v ** 2 + 1)

        else:       # both min and max
            ge[i] = (b - a) * np.cos(v) / 2.

    return ge

def i2e_cov_x(xi, bounds, cov_x):

    grad = internal2external_grad(xi, bounds)
    grad = grad = np.atleast_2d(grad)
    return np.dot(grad.T, grad) * cov_x


def internal2external(xi, bounds):
    """ Convert a series of internal variables to external variables"""

    xe = np.empty_like(xi)

    for i, (v, bound) in enumerate(zip(xi, bounds)):

        a = bound[0]    # minimum
        b = bound[1]    # maximum

        if a == None and b == None:    # No constraints
            xe[i] = v

        elif b == None:      # only min
            xe[i] = a - 1. + np.sqrt(v ** 2. + 1.)

        elif a == None:      # only max
            xe[i] = b + 1. - np.sqrt(v ** 2. + 1.)

        else:       # both min and max
            xe[i] = a + ((b - a) / 2.) * (np.sin(v) + 1.)

    return xe

def external2internal(xe, bounds):
    """ Convert a series of external variables to internal variables"""

    xi = np.empty_like(xe)

    for i, (v, bound) in enumerate(zip(xe, bounds)):

        a = bound[0]    # minimum
        b = bound[1]    # maximum

        if a == None and b == None: # No constraints
            xi[i] = v

        elif b == None:     # only min
            xi[i] = np.sqrt((v - a + 1.) ** 2. - 1)

        elif a == None:     # only max
            xi[i] = np.sqrt((b - v + 1.) ** 2. - 1)

        else:   # both min and max
            xi[i] = np.arcsin((2.*(v - a) / (b - a)) - 1.)

    return xi

def err(p, bounds, efunc, args):

    pe = internal2external(p, bounds)    # convert to external variables
    return efunc(pe, *args)

def calc_cov_x(infodic, p):
    """
    Calculate cov_x from fjac, ipvt and p as is done in leastsq
    """

    fjac = infodic['fjac']
    ipvt = infodic['ipvt']
    n = len(p)

    # adapted from leastsq function in scipy/optimize/minpack.py
    perm = np.take(np.eye(n), ipvt - 1, 0)
    r = np.triu(np.transpose(fjac)[:n, :])
    R = np.dot(r, perm)
    try:
        cov_x = np.linalg.inv(np.dot(np.transpose(R), R))
    except LinAlgError:
        cov_x = None
    return cov_x


def leastsqbound(func, x0, bounds, args = (), **kw):
    """
    Constrained multivariant Levenberg-Marquard optimization

    Minimize the sum of squares of a given function using the 
    Levenberg-Marquard algorithm. Contraints on parameters are inforced using 
    variable transformations as described in the MINUIT User's Guide by
    Fred James and Matthias Winkler.

    Parameters:

    * func      functions to call for optimization.
    * x0        Starting estimate for the minimization.
    * bounds    (min,max) pair for each element of x, defining the bounds on
                that parameter.  Use None for one of min or max when there is
                no bound in that direction.
    * args      Any extra arguments to func are places in this tuple.

    Returns: (x,{cov_x,infodict,mesg},ier)

    Return is described in the scipy.optimize.leastsq function.  x and con_v  
    are corrected to take into account the parameter transformation, infodic 
    is not corrected.

    Additional keyword arguments are passed directly to the 
    scipy.optimize.leastsq algorithm. 

    """
    # check for full output
    if "full_output" in kw and kw["full_output"]:
        full = True
    else:
        full = False

    # convert x0 to internal variables
    i0 = external2internal(x0, bounds)

    # perfrom unconstrained optimization using internal variables
    r = leastsq(err, i0, args = (bounds, func, args), **kw)

    # unpack return convert to external variables and return
    if full:
        xi, cov_xi, infodic, mesg, ier = r
        xe = internal2external(xi, bounds)
        cov_xe = i2e_cov_x(xi, bounds, cov_xi)
        # XXX correct infodic 'fjac','ipvt', and 'qtf' 
        return xe, cov_xe, infodic, mesg, ier

    else:
        xi, ier = r
        xe = internal2external(xi, bounds)
        return xe, ier


# new below

def _evaluate(x, p):
    '''
    Linear plus Lorentzian curve
    p = list with three parameters ([a, b, I, Pos, FWHM])
    '''
    return p[0] + p[1] * x + p[2] / (1 + np.power((x - p[3]) / (p[4] / 2), 2))


def residuals(p, y, x):

    err = _evaluate(x, p) - y
    return err


if __name__ == '__main__':
    data = np.loadtxt('constraint.dat') # read data

    p0 = [5000., 0., 500., 2450., 3] #Start values for a, b, I, Pos, FWHM
    constraints = [(4000., None), (-50., 20.), (0., 2000.), (2400., 2451.), (None, None)]

    p, res = leastsqbound(residuals, p0, constraints, args = (data[:, 1], data[:, 0]), maxfev = 20000)
    print p, res

    plt.plot(data[:, 0], data[:, 1]) # plot data
    plt.plot(data[:, 0], _evaluate(data[:, 0], p0)) # plot start values
    plt.plot(data[:, 0], _evaluate(data[:, 0], p)) # plot fit values
    plt.show()

これがプロット出力です。緑が開始条件で、赤が当てはめの結果です。 プロット結果

これは正しい使い方ですか?範囲外の場合、External2internal 変換は単に nan をスローします。leastsq はこれを処理できるようですか?

こちらにフィッティングデータをアップしました。constraint.dat という名前のテキスト ファイルに貼り付けるだけです。

4

3 に答える 3

0

すでに人気のある制約付きLev-Marコードがあります

http://adsabs.harvard.edu/abs/2009ASPC..411..251M

Pythonでの実装で

http://code.google.com/p/astrolibpy/source/browse/mpfit/mpfit.py

車輪の再発明をしないことをお勧めします。

于 2012-07-03T13:35:39.007 に答える
0

sega_sai の回答に続いて、mpfit.py を使用してこの最小限の作業例を思いつきました

import matplotlib.pyplot as plt
from mpfit import mpfit
import numpy as np

def _evaluate(p, x):
    '''
    Linear plus Lorentzian curve
    p = list with three parameters ([a, b, I, Pos, FWHM])
    '''
    return p[0] + p[1] * x + p[2] / (1 + np.power((x - p[3]) / (p[4] / 2), 2))

def residuals(p, fjac = None, x = None, y = None, err = None):
    status = 0
    error = _evaluate(p, x) - y
    return [status, error / err]

if __name__ == '__main__':
    data = np.loadtxt('constraint.dat') # read data
    x = data[:, 0]
    y = data[:, 1]
    err = 0 * np.ones(y.shape, dtype = 'float64')
    parinfo = [{'value':5000., 'fixed':0, 'limited':[0, 0], 'limits':[0., 0.], 'parname':'a'},
               {'value':0., 'fixed':0, 'limited':[0, 0], 'limits':[0., 0.], 'parname':'b'},
               {'value':500., 'fixed':0, 'limited':[0, 0], 'limits':[0., 0.], 'parname':'I'},
               {'value':2450., 'fixed':0, 'limited':[0, 0], 'limits':[0., 0.], 'parname':'Pos'},
               {'value':3., 'fixed':0, 'limited':[0, 0], 'limits':[0., 0.], 'parname':'FWHM'}]
    fa = {'x':x, 'y':y, 'err':err}
    m = mpfit(residuals, parinfo = parinfo, functkw = fa)
    print m

適合結果は次のとおりです。

mpfit.py: 3714.97545, 0.484193283, 2644.47271, 2440.13385, 22.1898496
leastsq:  3714.97187, 0.484194545, 2644.46890, 2440.13391, 22.1899295

結論:両方の方法が機能し、両方とも制約を許可します。しかし、mpfit は非常に確立されたソースから提供されているため、私はそれをより信頼しています。また、エラー値があればそれも受け入れます。

于 2012-07-03T16:12:18.203 に答える
0

lmfit-py を試す - https://github.com/newville/lmfit-py

  1. また、scipy.optimize.leastsq を介して Levenberg-Marquardt (LM) アルゴリズムも使用します。不確かなことはOKです。

  2. これにより、フィッティング関数を変更せずに、フィッティング パラメーターを境界で制約するだけでなく、それらの間の数式で制約することもできます。

  3. それらのひどい p[0]、p[1] ... をフィッティング関数で使用することを忘れてください。Parameters クラスを介してフィッティング パラメータの名前を使用するだけです。

于 2012-09-02T10:59:03.783 に答える