Подгонка нескольких наборов данных с помощью lmfit без написания целевой функции

В этом разделе описывается, как разместить несколько наборов данных с помощью lmfit: Python и lmfit: как уместить несколько наборов данных с общими параметрами?

Однако он использует подгоночную / целевую функцию, написанную пользователем.

Мне было интересно, можно ли уместить несколько наборов данных с помощью lmfit без написания целевой функции и использования метода model.fit () класса модели.

В качестве примера: допустим, у нас есть несколько наборов данных с координатами (x, y), которые мы хотим подогнать, используя одну и ту же функцию модели, чтобы найти набор параметров, который в среднем лучше всего подходит для всех данных.

import numpy as np 
from lmfit import Model, Parameters
from lmfit.models import GaussianModel

def gauss(x, amp, cen, sigma):
    return amp*np.exp(-(x-cen)**2/(2.*sigma**2))

x1= np.arange(0.,100.,0.1)
x2= np.arange(0.,100.,0.09)
y1= gauss(x1, 1.,50.,5.)+ np.random.normal(size=len(x1), scale=0.1)
y2= gauss(x2, 0.8,48.4.,4.5)+ np.random.normal(size=len(x2), scale=0.1)

mod= GaussianModel()
params= mod.make_params()

mod.fit([y1,y2], params, x= [x1, x2])

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

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

Если я передаю массив, я получаю AttributeError: 'numpy.ndarray' не имеет атрибута 'exp'

Так я просто пытаюсь сделать что-то, что невозможно, или я делаю что-то не так?


person Lipo    schedule 16.07.2018    source источник


Ответы (2)


Что ж, я думаю, что ответ - «вроде как». Класс lmfit.Model предназначен для представления модели массива данных. Итак, если вы можете сопоставить свои несколько наборов данных с numpy ndarray (скажем, с np.concatenate), вы, вероятно, можете написать функцию Model, чтобы представить это, создавая подмодели для разных наборов данных и объединяя их таким же образом.

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

def model_function(x, a, b, c):
   ### do some calculation with x, a, b, c values
   result = a + x*b + x*x*c
   return result

может стать

def objective_function(params, x, data):
     vals = params.valuesdict()
     return data - model_function(x, vals['a'], vals['b'], vals['c'])

Если этот do_calc() делает что-нибудь сложное, дополнительная нагрузка на распаковку параметров и вычитание данных довольно небольшая. И, особенно если некоторые параметры будут использоваться для нескольких наборов данных, а некоторые - только для определенных наборов данных, вам придется управлять этим либо в функции модели, либо в целевой функции. В примере, на который вы ссылаетесь, мой ответ включал цикл по наборам данных, выбирая параметры по имени для каждого набора данных. Вы, вероятно, захотите сделать что-то подобное. Вы, вероятно, могли бы сделать это в модельной функции, думая об этом как о моделировании конкатенированных наборов данных, но я не уверен, что вы действительно много выиграете, сделав это.

person M Newville    schedule 17.07.2018

Я нашел проблему. На самом деле model.fit () отлично обрабатывает массивы из нескольких наборов данных и выполняет правильную подгонку. Правильный вызов model.fit () с несколькими наборами данных:

import numpy as np 
from lmfit import Model, Parameters
from lmfit.models import GaussianModel
import matplotlib.pyplot as plt

def gauss(x, amp, cen, sigma):
   "basic gaussian"
    return amp*np.exp(-(x-cen)**2/(2.*sigma**2))

x1= np.arange(0.,100.,0.1)
x2= np.arange(0.,100.,0.1)
y1= gauss(x1, 1.,50.,5.)+ np.random.normal(size=len(x1), scale=0.01)
y2= gauss(x2, 0.8,48.4,4.5)+ np.random.normal(size=len(x2), scale=0.01)

mod= GaussianModel()
params= mod.make_params()

params['amplitude'].set(1.,min=0.01,max=100.)
params['center'].set(1.,min=0.01,max=100.)
params['sigma'].set(1.,min=0.01,max=100.)

result= mod.fit(np.array([y1,y2]), params,method='basinhopping',
x=np.array([x1,x2]))

print(result.fit_report(min_correl=0.5))

fig, ax = plt.subplots()

plt.plot(x1,y1, lw=2, color='red')
plt.plot(x2,y2, lw=2, color='orange')
plt.plot(x1,result.eval(x=x1), lw=2, color='black')

plt.show()

Проблема в исходном коде на самом деле заключается в том, что мои наборы данных не имеют одинаковой длины. Однако я совсем не уверен, как с этим справиться самым элегантным способом?

person Lipo    schedule 17.07.2018
comment
хм, вы, должно быть, имеете в виду что-то другое, говоря о совпадении нескольких наборов данных, чем я. Я ожидал, что это будет означать, что вы хотите подогнать каждый из наборов данных (x1, y1) и (x2, y2) к гауссианам, возможно, разделяя параметры между двумя кривыми. Подгонка, которую вы здесь определили, будет соответствовать 1 гауссиану конкатенированному набору данных (np.concatenate((x1, x2)), np.concatenate((y1, y2))). - person M Newville; 17.07.2018
comment
Вы уверены? Я сравнил описанный выше способ с использованием целевой функции, и они дают мне точно такие же результаты? Чтобы прояснить: я хочу подогнать параметры функции так, чтобы она как можно лучше описывала все наборы данных. - person Lipo; 19.07.2018
comment
да. Поскольку ваш x1=x2 и способ сглаживания массивов, то, что вы можете получить, соответствует среднему значению y1 и y2. Это не то, что я обычно называю соответствием двух наборов данных. Другими словами: ваш результат имеет одно значение для «центра», «амплитуды» и «сигмы», верно? Если вы сделали центры ваших двух наборов данных не 48,4 и 50 (в пределах сигмы), а больше как 30 и 70, я ожидаю, что вы получите хорошее соответствие одному из пиков. Массивы, используемые для данных и возвращаемые моделью будут сглажены до одномерных массивов. - person M Newville; 19.07.2018