Есть ли более чистый способ добиться соответствия кривой с несколькими моделями?

В моем проекте у меня есть несколько семейств функций, предопределенных для соответствия кривой. Рассмотрим самые простые:

def polyfit3(x, b0, b1, b2, b3):
    return b0+b1*x+b2*x**2+b3*x**3

def polyfit2(x, b0, b1, b2):
    return b0+b1*x+b2*x**2

def polyfit1(x, b0, b1):
    return b0+b1*x

Примечание. Я знаю, что в данном конкретном случае np.polyfit< /a> будет лучшим выбором

Функция (намного более простая), которая делает фитинг, выглядит следующим образом:

from scipy.optimize import curve_fit
try:
    from lmfit import Model
    _has_lmfit = True
except ImportError:
    _has_lmfit = False

def f(x, y, order=3):
    if _has_lmfit:
        if order == 3:
            fitModel = Model(polyfit3)
            params = fitModel.make_params(b0=0, b1=1, b2=1, b3=1)
            result = fitModel.fit(y, x=x, params=params)
        elif order == 2:
            fitModel = Model(polyfit2)
            params = fitModel.make_params(b0=0, b1=1, b2=1)
            result = fitModel.fit(y, x=x, params=params)
        elif order == 1:
            fitModel = Model(polyfit1)
            params = fitModel.make_params(b0=0, b1=1)
            result = fitModel.fit(y, x=x, params=params)
        else:
            raise ValueError('Order is out of range, please select from [1, 3].')
    else:
        if order == 3:
            popt, pcov = curve_fit(polyfit3, x, y)
            _function = polyfit3
        elif order == 2:
            popt, pcov = curve_fit(polyfit2, x, y)
            _function = polyfit2
        elif order == 1:
            popt, pcov = curve_fit(polyfit1, x, y)
            _function = polyfit1
        else:
            raise ValueError('Order is out of range, please select from [1, 3].')
    # more code there.. mostly working with the optimized parameters, plotting, etc.

Моя проблема в том, что это становится действительно уродливым быстро, и я повторяю это снова и снова. Есть ли способ построить это лучше?

ИЗМЕНИТЬ:

Я пробовал это:

def poly_fit(x, *args):
    return sum(b*x**i for i, b in enumerate(args))

...

fitModel = Model(poly_fit)
fitModel.make_params(**{f'b{i}': 1 for i in range(order+1)})

но, к сожалению, lmfit выдает ошибку:

ValueError: varargs '*args' is not supported

person Péter Leéh    schedule 08.12.2019    source источник


Ответы (2)


Я думаю, что lmfit.models.PolynomialModel() делает именно то, что вы ищете. Эта модель принимает степень полинома n в качестве аргумента и использует коэффициенты с именами c0, c1, ..., cn (обработка до n=7):

from lmfit.models import PolynomialModel

def f(x, y, degree=3):
    fitModel = PolynomialModel(degree=degree)
    params = fitModel.make_params(c0=0, c1=1, c2=1, c3=0, 
                                  c4=0, c5=0, c6=0, c7=0)
    # or if you prefer to do it the hard way:
    params = fitModel.make_params(**{'c%d'%i:0 for i in range(degree+1)})

    return fitModel.fit(y, x=x, params=params)

Обратите внимание, что здесь допустимо завышать коэффициенты. То есть, если degree=3, этот вызов fitModel.make_params(c0=0, ..., c7=0) на самом деле не создаст параметры для c4, c5, c6 или c7.

PolynomialModel поднимет TypeError, если degree > 7, поэтому я оставил ваш явный тест для этого.

Я надеюсь, что это поможет вам начать работу, но, похоже, вы могли захотеть включить и другие функции модели. В таком случае я сделал словарь имен классов:

from lmfit.models import LinearModel, PolynomialModel, GaussianModel, ....

KnownModels = {'linear': LinearModel, 'polynomial': PolynomialModel, 
              'gaussian': GaussianModel, ...}

а затем используйте это для построения модели:

modelchoice = 'linear' # probably really came from user selection in a GUI

if modelchoice in KnownModels:
    model = KnownModels[modelchoice]()
else:
    raise ValueError("unknown model '%s'" % modelchoice)

params = model.make_params(....) # <- might know and store what the parameter names are
.....
person M Newville    schedule 08.12.2019
comment
Это кажется хорошим. Какие у меня есть варианты, если я хочу использовать модель, которая (скорее всего) не предопределена, как PolynomialModel? Например это семейство, где меняется только аргумент косинуса: c0 + c1 * np.cos(c2 + c3*x), c0 + c1 * np.cos(c2 + c3*x + c4*x**2 ),... - person Péter Leéh; 09.12.2019
comment
Я ожидаю, что вы захотите иметь модельную функцию с нулевыми значениями по умолчанию для всех аргументов (возможно, cN=0, но, возможно, cN=-np.inf) и обернуть ее в класс lmfit.Model. Код для PolynomialModel может быть полезным примером для подражания. Кроме того: по возможности старайтесь использовать более осмысленные имена параметров (c0 => offset, c1 => amplitude, c2 => phase_offset и т. д.). - person M Newville; 09.12.2019

Я переписываю ваш код, создавая глобальную конфигурацию для ваших polyfit функций. Это более питоническая версия if.

polyfits = {
    1: {
        'f': polyfit1,
        'params': ['b0', 'b1'],
        'vals'  : [  0,    1], 
    },
    2: {
        'f': polyfit2,
        'params': ['b0', 'b1', 'b2'],
        'vals'  : [   0,    1,   1,], 
    },
    3: {
        'f': polyfit3,
        'params': ['b0', 'b1', 'b2', 'b3'],
        'vals'  : [   0,    1,    1,    1], 
    },

}

def f(x, y, order=3):
    if order not in polyfits.keys():
        raise ValueError('Order is out of range, please select from {}.'.format(','.join(map(str, polyfits.keys()))))
    _function = polyfits[order]['f']
    if _has_lmfit:
        fitModel = Model(_function)
        params = dict(zip(polyfits[order]['params'], polyfits[order]['vals']))
        params = fitModel.make_params(**params)
        result = fitModel.fit(y, x=x, params=params)
    else:
        popt, pcov = curve_fit(_function, x, y)

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

person Grigory Feldman    schedule 08.12.2019
comment
Да, действительно, это очень упрощенный код, и, как я уже говорил, это может быть np.polyfit. Но также у меня есть гораздо более продвинутые, где я не могу использовать np.polyfit. Моя цель - найти более чистый способ сделать это. Я проверю тот код, который вы разместили, и посмотрю, как он масштабируется. - person Péter Leéh; 08.12.2019
comment
Для масштабирования необходимо изменить только polyfits секцию (и, возможно, сгенерировать ее программно). - person Grigory Feldman; 08.12.2019