Модульный (питонический) способ подбора комбинированных параметров составной функции

Мой вопрос касается подбора параметров сложной модели, состоящей из различных параметрических функций.

Точнее, хочу описать сложный эксперимент. В ходе эксперимента создается одномерный массив измеренных данных data, где каждая его запись соответствует (набору) экспериментальных контрольных переменных x.

Теперь у меня теоретическая модель (на самом деле несколько моделей, см. Ниже) model(x,pars), которая принимает x и множество параметров pars, чтобы дать прогноз для data. Однако не все параметры известны, и мне нужно их подогнать.

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

К сожалению, переключение одного компонента на другой может привести к появлению новых (неизвестных) параметров, то есть теперь у нас есть modelA(x,parsA) и modelB(x,parsB), которые имеют разные параметры.

По сути, модель состоит из функций f(x, pars, vals_of_subfuncs), где x - независимая переменная, pars - некоторые явные параметры f, а vals_of_subfuncs - результаты оценки некоторых функций нижнего уровня, которые сами зависят от своих собственных параметров (и, возможно, результатов их собственные функции нижнего уровня и т. д.) Очевидно, что рекурсии невозможны, и есть функции нижнего уровня, которые не зависят от значений других функций.

Ситуацию лучше всего иллюстрирует этот рисунок:

Архитектура модульной модели

Независимая переменная - x (синий), параметры - a,b,c,d (красный), а значения подфункций отображаются в виде зеленых стрелок в узлах, представляющих функции.

В (1) у нас есть функция нижнего уровня G(x; (a,b); {}) без подфункций и функция верхнего уровня F(x; c; G(x; (a,b)), оценка которой дает результат модели, который зависит от x и pars=(a,b,c).

В (2) и (3) мы меняем компонент модели (F->F') и (G->G') соответственно. Это изменяет зависимость параметров окончательной модели.

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

На данный момент я пытаюсь найти решение этой проблемы с помощью lmfit. Я также подумал, может быть, попытаться использовать sympy для работы с символическими «параметрами», но я не думаю, что все появляющиеся функции можно легко записать в виде выражений, которые могут быть оценены asteval.

Кто-нибудь знает естественный способ подойти к такой ситуации?


person beralt    schedule 18.12.2018    source источник


Ответы (3)


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

def model_func(x, a, b, c, d):
     gresult = G(x, a, b, d)
     return F(x, b, c, gresult)

но вы также хотите контролировать, действительно ли d и b являются переменными и передается ли c в F. Это верно?

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

Например, вы можете переставить что-то вроде этого:

def G(x, a, b=None, d=None):
    if b is not None and d is None:
       return calc_g_without_d(x, a, b)
    return calc_g_with_d(x, a, d) 

def F(x, gresult, b, c=None):
    if c is None:
       return calc_f_without_c(x, gresult, b)
    return calc_f_with_c(x, gresult, b, c) 

def model_func(x, a, b, c, d, g_with_d=True, f_with_c=True):
     if g_with_d:
        gresult = G(x, a, d)
     else:
        gresult = G(x, a, b)
     if f_with_c:
         return F(x, gresult, b, c=c)
     else:
         return F(x, gresult, b)

Теперь, когда вы создаете свою модель, вы можете переопределить значения по умолчанию f_with_c и / или g_with_d:

import lmfit
mymodel = lmfit.Model(model_func, f_with_c=False)
params = mymodel.make_params(a=100, b=0.2201, c=2.110, d=0)

а затем оценить модель с помощью mymodel.eval() или выполнить подгонку с помощью mymodel.fit() и передать явные значения для аргументов ключевого слова f_with_c и / или g_with_d, например

test = mymodel.eval(params, x=np.linspace(-1, 1, 41), 
                    f_with_c=False, g_with_d=False)

or

result = mymodel.fit(ydata, params, x=xdata, g_with_d=False)

Я думаю, что так, как вы это указали, вы бы хотели убедиться, что d не был подходящей переменной, когда g_with_d=False, и есть случаи, когда вы бы хотели, чтобы b не изменялся в соответствии. Вы можете сделать это с помощью

params['b'].vary = False
params['d'].vary = False

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

person M Newville    schedule 20.12.2018

Спасибо за ответы.

Я думаю, lmfit мог бы делать то, что я хочу, но мне придется реализовать «модульность» самостоятельно. Приведенный мной пример был всего лишь концептуальной и мимической моделью. В общем, "сети" функций и их зависимости намного сложнее, чем то, что я делаю в примере.

Мой текущий план таков: я напишу класс Network для "сети", который содержит определенные Node. Примечания указывают их возможную «символьную» зависимость от subNodes, явных параметров и независимых переменных.

Класс Network будет иметь процедуры для проверки целостности такой построенной сети. Более того, он будет иметь объект (lmfit) Parameters (т.е. объединение всех параметров, от которых явно зависят узлы) и предоставит некоторый метод для генерации lmfit Model из этого.

Тогда я буду использовать lmfit для примерки.

По крайней мере, таков план. Если мне это удастся, я опубликую обновленный пост с моим кодом.

person beralt    schedule 09.01.2019

Поскольку вы подняли sympy, я думаю, вам следует взглянуть на symfit, что делает именно то, о чем вы просите в последнем абзаце. С symfit вы можете писать символьные выражения, которые затем дополняются scipy. Это упростит вам объединение ваших различных подмоделей волей-неволей.

Позвольте мне реализовать ваш второй пример, используя symfit:

from symfit import variables, parameters, Fit, Model

a, b, c = parameters('a, b, c')
x, G, F = variables('x, G, F')

model_dict = {
    G: a * x + b,
    F: b * G + c * x
}
model = Model(model_dict)
print(model.connectivity_mapping)

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

{F: {b, G, x, c}, G: {b, a, x}}

Итак, вы видите, что это действительно отображение того, что вы нарисовали. (Аргументы не расположены в определенном порядке в каждом наборе, но они будут оцениваться в правильном порядке, например, G перед F.) Чтобы затем соответствовать вашим данным, просто выполните

fit = Fit(model, x=xdata, F=Fdata)
fit_results = fit.execute()

Вот и все! Надеюсь, это проясняет, почему я думаю, что symfit подходит для вашего варианта использования. Извините, я не мог уточнить это раньше, я все еще дорабатывал эту функцию в API, поэтому до сих пор она существовала только в ветке разработки. Но я только что выпустил релиз с этой и многими другими функциями :).

Отказ от ответственности: я автор symfit.

person tBuLi    schedule 19.12.2018
comment
Спасибо за ответ. Я лишь немного знаком с sympy, но не пробовал symfit. Насколько я понимаю из документации, это не совсем подходит для моего варианта использования, который является довольно общим. Я посмотрю, как поступить. Если я найду решение с помощью symfit, я опубликую это здесь. - person beralt; 09.01.2019
comment
@beralt, в связи с выходом новой версии symfit я обновил свой ответ, потому что думаю, вам могут быть интересны некоторые новые функции. - person tBuLi; 18.05.2019