Как понять использование доходности в моделях PyMC?

Я сам не являюсь пользователем PyMC, но недавно наткнулся на эту статью который показал фрагмент какой-то модели PyMC:

def linear_regression(x):
    scale = yield tfd.HalfCauchy(0, 1)
    coefs = yield tfd.Normal(tf.zeros(x.shape[1]), 1, )
    predictions = yield tfd.Normal(tf.linalg.matvec(x, coefs), scale)
    return predictions

Автор предложил пользователям

было бы неудобно с bar = yield foo

Неудобно мне и правда. Я пытался разобраться в этом генераторе, но не мог понять, как его можно использовать.

Это мой мыслительный процесс. Если я сделаю foo = linear_regression(bar) и выполню foo (например, next(foo)), он вернет мне значение scale. Однако это также изменит локальную переменную scale на None. Точно так же, если foo выполняется снова, я могу получить значение coefs, но локальное coefs станет None. Поскольку оба локальных scale и coefs равны None, как можно оценить predictions?

Или есть способ оценить foo, не вызывая yield для scale и coefs, и напрямую получить результат для predictions?

При чем здесь черная магия? Нужна помощь.


person Fanchen Bao    schedule 16.04.2020    source источник


Ответы (1)


Раскрытие информации: я являюсь автором исходной связанной статьи.

Я думаю, что ваше основное непонимание заключается в следующем: генераторы Python могут не только выдавать вам значения, но вы также можете отправлять значения обратно в генераторы, используя generator.send(). Таким образом, bar = yield foo даст вам foo; генератор будет ждать, пока вы не отправите ему другое значение (которое может быть None, что произойдет, если вы просто вызовете next(generator)!), присвоите это значение bar, а затем продолжите работу генератора.

Вот простой пример:

>>> def add_one_generator():
...     x = 0
...     while True:
...         x = yield x + 1
...
>>> gen = add_one_generator()
>>> y = gen.send(None)  # First sent value must be None, to start the generator
>>> print(y)
1
>>> z = gen.send(2)
>>> print(z)
3

Обратите внимание, что когда я send(2), генератор присваивает отправленное значение x, а затем возобновляет выполнение. В данном случае это просто снова означает yield x + 1, поэтому полученное z равно 3.

Для получения дополнительной информации об этом шаблоне и о том, почему он может быть полезен, ознакомьтесь с этим ответом StackOverflow.

Вот некоторый псевдокод, который приближает нас к тому, как все будет (вероятно) работать в PyMC4:

>>> def linear_regression(x):
...     scale = yield tfd.HalfCauchy(0, 1)
...     coefs = yield tfd.Normal(tf.zeros(x.shape[1]), 1, )
...     predictions = yield tfd.Normal(tf.linalg.matvec(x, coefs), scale)
...     return predictions
>>> model = linear_regression(data)
>>> next_distribution = model.send(None)
>>> scale = pymc_do_things(next_distribution)
>>> coefs = pymc_do_things(model.send(scale))
>>> predictions = pymc_do_things(model.send(coefs))
person eigenfoo    schedule 21.04.2020
comment
Очень удивлен, что автор статьи ответил на мой вопрос. Спасибо за объяснение. Я полагаю, что причина, по которой PyMC4 строит API таким образом, заключается в том, что пользователь может вводить scale и coefs по своему желанию, верно? - person Fanchen Bao; 23.04.2020
comment
Почти: он позволяет механизму вывода PyMC4 вводить scale и coefs по своему усмотрению. Генератор модели, указанный пользователем, передает дистрибутивы PyMC4, а PyMC4 будет взаимодействовать с этими дистрибутивами и повторно вводить дистрибутив в генератор модели. - person eigenfoo; 24.04.2020