Как произвольно расширить число параметров Symfit в Python - PullRequest
4 голосов
/ 08 апреля 2019

У меня есть следующий код оптимизации, который параметризован переменной n.

from symfit import parameters, Eq, Ge, Fit
import numpy as np
n = 3
xdata = np.sort(np.random.choice(range(1, 4*n), n)) # Make fake data
print(xdata)
p1, p2, p3 = parameters('p1, p2, p3')
model = p1*p2*p3
constraints = [
    Eq(xdata[0]*p1+(xdata[1]-xdata[0])*p2+(xdata[2]-xdata[1])*p3, 1),
    Ge(p1, p2),
    Ge(p2, p3),
    Ge(p3, 0)
    ]

fit = Fit(- model, constraints=constraints)
fit_result = fit.execute()
print(fit_result)

Я хотел бы использовать его для гораздо больших значений n, но я не знаю, какчтобы изменить строки

 p1, p2, p3 = parameters('p1, p2, p3')
 model = p1*p2*p3

и constraints, чтобы справиться с произвольно большим n.

Код использует библиотеку symfit .Эта ссылка показывает пример использования parameters и ссылку на документацию.

Как это сделать?

Ответы [ 3 ]

4 голосов
/ 08 апреля 2019

Numpy взаимодействует действительно хорошо с библиотекой symfit.Все операции, которые вы пытаетесь обобщить, довольно просты при его использовании.


Настройка

n = 3
_data = np.sort(np.random.choice(np.arange(1, 4 * n), n))

  1. Форматирование строки

    Вы можете динамически создавать tuple параметров, используя простой итератор и str.join, которые затем можно передать в конструктор parameters, чтобы получить tupleваших параметров.

params = parameters(', '.join(f'p{i}' for i in range(1, n+1)))
                ^^
# p1, p2, p3 = parameters('p1, p2, p3')

np.prod

Эта операция довольно проста.np.prod вычисляет:

произведение элементов массива по заданной оси

, которое относится к tuple из symfitпараметры, производит желаемое p1*p2*...pn

model = np.prod(params)
        ^^
# model = p1*p2*p3

np.concatenate + np.diff

Вероятно, самая сложная строка для обобщения, но все же не слишком сложная для понимания.Вы хотите умножить различия последовательных элементов в массиве данных на ваши параметры и суммировать результаты.Поскольку первый элемент не будет иметь различий с предыдущим элементом, вы можете использовать np.concatenate, чтобы добавить его обратно.

u = np.concatenate((_data[:1], np.diff(_data)))
c1 = Eq(np.sum(params * u), 1)
              ^^
# Eq(xdata[0]*p1+(xdata[1]-xdata[0])*p2+(xdata[2]-xdata[1])*p3, 1)

np.column_stack

Требуется скользящее представление ваших параметров в качестве ограничений: p1-p2, p2-p3, ... pn, 0.Это просто складывает одноразовый кортеж с добавлением нуля к исходным tuple параметрам, а затем использует понимание списка для распаковки в ваши Ge конструкторы.

ges = [Ge(*group) for group in np.column_stack((params, params[1:] + (0,)))]

Fit!

Я здесь ничего не менял!

constraints = [c1, *ges]
fit = Fit(- model, constraints=constraints)
fit_result = fit.execute()
3 голосов
/ 08 апреля 2019

Строка параметров должна вычисляться динамически из n

paramstr = ', '.join(['p{}'.format(i) for i in range(1, n)])
# when n=1, paramstr = 'p1, p2, p3'

Использовать paramstr в качестве аргумента функции parameters.

paramvals = parameters(paramstr)

model можно реорганизовать, уменьшив paramvals над своим продуктом.


from functools import reduce

model = reduce(lambda x, y: x * y, paramvals, 1)

Теперь до интересного!constraints можно изменить следующим образом:

eqs = xdata[0] * paramvals[0] + sum(
    (xdata[i] - xdata[i-1]) * paramvals[i]
    for i in range(1, n)
)

ges = [
    Ge(paramvals[i-1], paramvals[i])
    for i in range(1, n)
]

ges.append(
    Ge(paramvals[-1], 0)
)

constraints = [
    Eq(eqs, 1),
    *ges
]

2 голосов
/ 08 апреля 2019

Я ничего не знаю о Symfit, но если вы просто пытаетесь обобщить приведенный выше код для произвольного N, то:

  • Вы можете произвольно сгенерировать строку, которая выглядит как "p1, p2, p3" для произвольного N и разобрать его в список параметров:
params_string = ", ".join("p{}".format(i + 1) for i in range(n))
params = parameters(params_string)

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

РЕДАКТИРОВАТЬ: Глядя на документацию Symfit, кажется, что parameters(s) это просто ярлык, и вы можете просто сделать:

params = [Parameter("p{}".format(i + 1)) for i in range(n)]

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

  • Вы можете обобщить ограничение Eq:
coeffs = [xdata[0]] + [(xdata[i+1] - xdata[i]) for i in range(n-1)]
eq_constraint = Eq(sum(param * coeff for param, coeff in zip(params, coeffs), 1)

Или, как это делает другой ответ, с использованием простых операций:

coeffs = np.concat(xdata[:1], np.diff(xdata))
eq_constraint = Eq(np.sum(params * coeffs), 1)
  • Вы можете обобщить ограничения Ge:
ge_constraints = [Ge(params[i + 1], params[i]) for i in range(n - 1)] + [Ge(params[-1], 0]

constraints = [eq_constraint] + ge_constraints

Опять же, это можно сделать с помощью простых операций, но я оставлю это на ответ @ user3483203.

  • Вы можете умножить все параметры, используя reduce:
model = reduce(lambda x, y: x * y, params, 1)

Или используя numpy.prod:

model = np.prod(params)

Это должно бытьдостаточно обобщить сказанное выше до произвольной N.

...