I am not a user of PyMC myself, but recently I stumbled upon this article that showed a snippet of some PyMC model:
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
The author suggested that users
would be uncomfortable with
bar = yield foo
Uncomfortable indeed I am. I tried to make sense of this generator, but couldn't see how it can be used.
This is my thought process. If I do foo = linear_regression(bar)
and execute foo
(e.g. next(foo)
), it will return the value of scale
to me. However, this will also turn the local variable scale
to None
. Similarly, if foo
is executed again, I can get the value of coefs
, but the local coefs
would become None
. With both local scale
and coefs
being None
, how can predictions
be evaluated?
Or is there a way to evaluate foo
without triggering the yield
on scale
and coefs
, and directly yield on predictions
?
What is the black magic here? Help needed.
Disclosure: I'm the author of the original linked article.
I think your main misunderstanding is this: Python generators can not only yield values to you, but you can also send back values to generators using generator.send()
. Thus, bar = yield foo
would yield foo
to you; the generator will wait until you send it another value (which can be None
, which is what happens if you just call next(generator)
!), assign that value to bar
, and then continue running the generator.
Here's a simple example:
>>> 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
Notice that when I send(2)
, the generator assigns the sent value to x
, and then resumes execution. In this case, that just means yield x + 1
again, which is why the yielded z
is 3
.
For more info on this pattern and why it might be useful, take a look at this StackOverflow answer.
Here's some pseudocode that brings us closer to how things will (probably) work in 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))