-
-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add the relation between a Half-Cauchy and an Inverse-Gamma scale mix… #66
base: main
Are you sure you want to change the base?
Conversation
c711b2b
to
aeeda45
Compare
Codecov ReportBase: 97.35% // Head: 97.42% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## main #66 +/- ##
==========================================
+ Coverage 97.35% 97.42% +0.06%
==========================================
Files 9 10 +1
Lines 606 621 +15
Branches 63 63
==========================================
+ Hits 590 605 +15
Misses 5 5
Partials 11 11
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
While the forward test passes this shouldn't be merged. For the backward transformation, many-to-one, I need to select a lany(
eq(rng_outer_lv, rng_lv),
succeed,
),
lany(
eq(size_outer_lv, size_lv),
succeed,
),
lany(
eq(type_idx_outer_lv, type_idx_lv),
succeed,
) |
More concerning to me is how to handle the Let's walk throught a simpler example and let's consider another kind of expansion, the convolution of two normal distributions. In particular, let's consider the relation that represents the sum of two normally-distributed random variables: Let's print the current etuplized version of the contracted expression: import aesara.tensor as at
from etuples import etuplize
srng = at.random.RandomStream(0)
mu_x = at.scalar()
sigma2_x = at.scalar()
mu_y = at.scalar()
sigma2_y = at.scalar()
X_rv = srng.normal(mu_x + mu_y, sigma2_x + sigma2_y)
pprint(etuplize(X_rv))
# e(
# e(aesara.tensor.random.basic.NormalRV, 'normal', 0, (0, 0), 'floatX', False),
# RandomGeneratorSharedVariable(<Generator(PCG64) at 0x7FD6BBEA6960>),
# TensorConstant{[]},
# TensorConstant{11},
# e(
# e(
# aesara.tensor.elemwise.Elemwise,
# <aesara.scalar.basic.Add at 0x7fd6c0e014e0>,
# <frozendict {}>),
# <TensorType(float64, ())>,
# <TensorType(float64, ())>),
# e(
# e(
# aesara.tensor.elemwise.Elemwise,
# <aesara.scalar.basic.Add at 0x7fd6c0e014e0>,
# <frozendict {}>),
# <TensorType(float64, ())>,
# <TensorType(float64, ())>)) And the expanded version of this expression: Y_rv = srng.normal(mu_x, sigma2_x) + srng.normal(mu_y, sigma2_y)
pprint(etuplize(Y_rv))
# e(
# e(
# aesara.tensor.elemwise.Elemwise,
# <aesara.scalar.basic.Add at 0x7fd6c0e014e0>,
# <frozendict {}>),
# e(
# e(
# aesara.tensor.random.basic.NormalRV,
# 'normal',
# 0,
# (0, 0),
# 'floatX',
# False),
# RandomGeneratorSharedVariable(<Generator(PCG64) at 0x7FD6BD33A880>),
# TensorConstant{[]},
# TensorConstant{11},
# <TensorType(float64, ())>,
# <TensorType(float64, ())>),
# e(
# e(
# aesara.tensor.random.basic.NormalRV,
# 'normal',
# 0,
# (0, 0),
# 'floatX',
# False),
# RandomGeneratorSharedVariable(<Generator(PCG64) at 0x7FD6BD33BE60>),
# TensorConstant{[]},
# TensorConstant{11},
# <TensorType(float64, ())>,
# <TensorType(float64, ())>)) To express the relation between these two expressions with miniKanren we start by writing the following expressions with value variables: from etuples import etuple
from kanren import var
mu_x, mu_y, sigma2_x, sigma2_y = var(), var(), var(), var()
rng, size, dtype = var(), var(), var()
X_et = etuple(
etuplize(at.random.normal),
rng,
size,
dtype,
etuple(
etuplize(at.add),
mu_x,
mu_y
),
etuple(
etuplize(at.add),
sigma2_x,
sigma2_y,
)
)
rng_x, size_x, dtype_x = var(), var(), var()
rng_y, size_y, dtype_y = var(), var(), var()
Y_et = etuple(
etuplize(at.add),
etuple(
etuplize(at.random.normal),
rng_x,
size_x,
dtype_x,
mu_x,
sigma2_x
),
etuple(
etuplize(at.random.normal),
rng_y,
size_y,
dtype_y,
mu_y,
sigma2_y
)
) The problem here stems from the fact that on the contracted version we need one from kanren import lall, eq
# Our goal is a function of (contract_expr, expand_expr)
# We simplify the problem assuming all of the distribution parameters have the same =dtype= and =size=.
lall(
eq(contract_exp, X_et),
eq(expand_exp, Y_et),
eq(rng, rng_x), # We pick the first state
eq(size, size_x),
eq(dtype, dtype_x),
) However this will not work for the contracted -> expanded transformation as We know that we need to advance the state of the random number generator, or the computations downstream will be wrong. We also know that we cannot reset to Now imagine that we have a symbolic operator rng_new = at.random.update(rng) which corresponds to the evaluated etuple: etuple(etuplize(at.random.update), rng) We can now re-write rng_x, size_x, dtype_x = var(), var(), var()
Y_et = etuple(
etuplize(at.add),
etuple(
etuplize(at.random.normal),
rng_x,
size_x,
dtype_x,
mu_x,
sigma2_x
),
etuple(
etuplize(at.random.normal),
etuple(
etuplized(at.random.update),
rng_x
),
size_y,
dtype_y,
mu_y,
sigma2_y
)
) The following goal (leaving concerns of from kanren import lany
# Our goal is a function of (contract_expr, expand_expr)
# We simplify the problem assuming all of the distribution parameters have the same =dtype= and =size=.
lall(
eq(contract_exp, X_et),
eq(expand_exp, Y_et),
eq(rng, rng_x),
) The solution may be to explicit the behind-the-scenes updating logic in Aesara with |
aeeda45
to
7431376
Compare
If the context requires a "strong" equivalence between a sub-graph and its rewritten form (e.g. sampling one must produce the exact same values as the other), then a lot of these rewrites are simply not appropriate (see aesara-devs/aesara#209); however, when it comes to generating samplers—as we're doing via these rewrites—then we're operating under considerably weaker equivalence assumptions, and these rewrites generally are appropriate. In this case, we only need consistency/reproducibility of the sampler's results between runs.
In some cases (e.g. ffbs_step), we've required that a user pass a RandomStream or seed value to the function(s) that generate a sampler. We may need to work this kind of thing into our general rewrites (e.g. some kind of global RandomStream that's accessible by rewrites), or start using the symbolic RNG constructors—i.e. use the original graphs' RNGs to seed new ones and effectively connect the user-specified state/seed to rewrite-generated ones. The latter approach sounds like the best right now, so let's see what we can do along those lines first |
Agreed.
It does, but it also helps a lot to think about these things.
Yes that was the idea. We may be able to make all this happen at the etuplization/evaluation level, in the same way we discussed adding a
👍 |
Related to #49 #64
Here are a few important guidelines and requirements to check before your PR can be merged:
pre-commit
is installed and set up.Don't worry, your PR doesn't need to be in perfect order to submit it. As development progresses and/or reviewers request changes, you can always rewrite the history of your feature/PR branches.
If your PR is an ongoing effort and you would like to involve us in the process, simply make it a draft PR.