Posterior¶
The concept of posteriors is as important as the concept of modules in the borch
framework.
The posterior’s job is to create RandomVariable
s for approximating distributions.
Whenever we add a RandomVariable
to a Module
, the posterior will pick it up
and create an approximating distribution for it.
Thus one need to select the correct posterior to use with a specific inference method. However most posteriors are designed for variational inference.
-
class
borch.posterior.
AsPrior
¶ A posterior for variational inference that will add approximating RandomVariable`s based on the `RandomVariable that gets register.
It will use the same type of distribution as the prior and initialize the variables in the same point as the prior.
Examples
>>> import borch >>> import borch.distributions as dist >>> class Model(borch.Module): ... def __init__(self): ... super().__init__(posterior=AsPrior()) ... self.student_t = dist.StudentT(3, 3, 4) ... self.gamma = dist.Gamma(.5, .5) ... def forward(self): ... self.y = dist.Normal(self.student_t, self.gamma) ... return self.y >>> model = Model() >>> type(model()) <class 'torch.Tensor'>
The posterior will be initialized with the same type of distribution and the same values for the parameters.
>>> type(model.posterior.student_t) <class 'borch.distributions.rv_distributions.StudentT'> >>> model.posterior.student_t.loc == model.prior.student_t.loc tensor(True) >>> model.posterior.student_t.scale == model.prior.student_t.scale tensor(True)
However, the posterior distribution is only created the first time a
RandomVariable
gets added to the model it will not preform well for hierarchies. In this case it would mean that theloc
ofy
is only set for the first forward and then optimized like a normal parameter after that. If one want to keep the hierarchy in the posterior as well one should useborch.posterior.Automatic
orborch.posterior.Manual
.>>> model.posterior.y.loc == model.student_t tensor(True) >>> borch.sample(model) >>> model.posterior.y.loc == model.student_t tensor(False)
-
register_random_variable
(name, rv)¶ A passed
RandomVariable
will be cloned and an appropriate approximating distribution will be returned in its place, based on whether the given rv had an approximating distribution or not.
-
-
class
borch.posterior.
Automatic
¶ An automatic posterior will automatically add approximating distributions based on the
RandomVariable
objects that are used in any call.It will use the same type of distribution as the prior and initialize the variables in the same point as the prior, but if some of the arguments of the prior requires gradients, it will create an parameter for this argument.
Examples
>>> import borch >>> import borch.distributions as dist >>> class Model(borch.Module): ... def __init__(self): ... super().__init__(posterior=Automatic()) ... self.student_t = dist.StudentT(3, 3, 4) ... self.gamma = dist.Gamma(.5, .5) ... def forward(self): ... self.y = dist.Normal(self.student_t, self.gamma) ... return self.y >>> model = Model() >>> type(model()) <class 'torch.Tensor'>
The posterior will be initialized with the same type of distribution and the same values for the parameters.
>>> type(model.posterior.student_t) <class 'borch.distributions.rv_distributions.StudentT'> >>> model.posterior.student_t.loc == model.prior.student_t.loc tensor(True) >>> model.posterior.student_t.scale == model.prior.student_t.scale tensor(True)
The main purpose of the
Automatic
posterior is that keeps the hierarchy of the model. In this case it means that theloc
ofy
will be updated with the value ofstudent_t
in every forward call in such a way that the gradients will propagate.>>> model.posterior.y.loc == model.student_t tensor(True) >>> borch.sample(model) >>> _ = model() >>> model.posterior.y.loc == model.student_t tensor(True) >>> id(model.posterior.y.loc) == id(model.student_t) True
-
register_random_variable
(name, rv)¶ A passed RandomVariable will be cloned and an appropriate approximating distribution will be returned in its place, based on whether the given
rv
had an approximating distribution or not.
-
update_random_variable
(name, rv)¶ Code that gets run every time a random variable is added to a model
-
-
class
borch.posterior.
Manual
¶ The manual posterior must have all RandomVariable objects explicitly added before they are available for use in a Model.
Notes
Adding a RVPair will add the approximating RandomVariable to the posterior will raise an error.
Examples
>>> import torch >>> import borch >>> import borch.distributions as dist >>> class ManualPosterior(Manual): ... def __init__(self): ... super().__init__() ... self.loc = torch.nn.Parameter(torch.randn(1)) ... self.scale = torch.nn.Parameter(torch.randn(1)) ... def forward(self): ... self.student_t = dist.StudentT(3, self.loc, torch.exp(self.scale)) ... self.y = dist.Normal(self.student_t, .3) ... return self.y >>> manual_posterior = ManualPosterior() >>> class Model(borch.Module): ... def __init__(self, posterior): ... super().__init__(posterior=posterior) ... self.student_t = dist.StudentT(3, 3, 4) ... def forward(self): ... self.y = dist.Normal(self.student_t, .3) ... return self.y
In order to use the posterior properly, it is important to remember to first run the forward of manual posterior before running the forward of the model.
>>> _ = manual_posterior() >>> model = Model(manual_posterior) >>> type(model()) <class 'torch.Tensor'>
-
register_random_variable
(name, rv)¶ Just add the rv to the posterior
-
-
class
borch.posterior.
Normal
(log_scale=-3, loc_at_prior_mean=True)¶ An automatic posterior which, when given a prior which is a continuous distribution, creates an approximating distribution :math:
\mathbb{N}(\mu, \sigma^2)
.- Parameters
log_scale (float) – The scale of the posterior will be initialized at exp(log_scale), where a
torch.nn.Parameter
is initialized at log_scale with the appropriate size.loc_at_prior_mean (bool) – If one should initlize at the mean value of the prior or the current value of the prior if False.
Examples
>>> import borch >>> import borch.distributions as dist >>> class Model(borch.Module): ... def __init__(self): ... super().__init__(posterior=Normal(log_scale=0)) ... self.student_t = dist.StudentT(3, 3, 4) ... self.gamma = dist.Gamma(.5, .5) ... def forward(self): ... self.y = dist.Normal(self.student_t, self.gamma) ... return self.y >>> model = Model() >>> type(model()) <class 'torch.Tensor'>
The posterior will be initialized as a
Normal
distribution with the scale set to1 (exp(0))
.>>> type(model.posterior.student_t) <class 'borch.distributions.rv_distributions.Normal'> >>> model.posterior.student_t.loc == model.prior.student_t.loc tensor(True) >>> float(model.posterior.student_t.scale) 1.0
However, the posterior distribution is only created the first time a
RandomVariable
gets added to the model it will not preform well for hierarchies. In this case it would mean that theloc
ofy
is only set for the first forward and then optimized like a normal parameter after that. If one want to keep the hierarchy in the posterior as well one should useborch.posterior.Automatic
orborch.posterior.Manual
.>>> model.posterior.y.loc == model.student_t tensor(True) >>> borch.sample(model) >>> model.posterior.y.loc == model.student_t tensor(False)
-
register_random_variable
(name, rv)¶ Construct the q_distibution and add it to the
Posterior
.
-
class
borch.posterior.
PointMass
¶ A posterior that is used to operate on unconstrained parameter values, intended to be used with MCMC.
The PointMass posterior creates a borch.distributions.distributions.PointMass as the approximating distribution. That means it will be a parameter that is constrained to be on the same support as the prior. So unless one update the values of the parameters ex. using an optimizer. The `RandomVariable`s will have the same value after getting sampled.
Examples
>>> import borch >>> import borch.distributions as dist >>> class Model(borch.Module): ... def __init__(self): ... super().__init__(posterior=PointMass()) ... self.student_t = dist.StudentT(3, 3, 4) ... self.gamma = dist.Gamma(.5, .5) ... def forward(self): ... self.y = dist.Normal(self.student_t, self.gamma) ... return self.y >>> model = Model() >>> type(model()) <class 'torch.Tensor'> >>> gamma = float(model.gamma) >>> borch.sample(model) >>> float(model.gamma) == gamma True
-
register_q_random_variable
(name, p_rv, q_rv)¶ Allow the user to directly comunicate with the posterior of what q_dist to use
-
register_random_variable
(name, rv)¶ Register a random variable with the posterior
-
-
class
borch.posterior.
Posterior
¶ Base class to create a posterior
-
forward
()¶ Don’t call this, not supported
-
register_random_variable
(name, rv)¶ Called the first time a RandomVariable gets added
-
set_random_variable
(name, rv)¶ If __setattr__ gets called on a model it will call here
-
update_random_variable
(name, rv)¶ Code that gets run every time a random variable is added to a model
-
-
class
borch.posterior.
ScaledNormal
(scaling=0.01, loc_at_prior_mean=True)¶ An automatic posterior which, when given a prior which is a continuous distribution, creates an approximating distribution :math:
\mathbb{N}(\mu, \sigma^2)
where :math:sigma^2 = scale*std(prior)``.The approximating distribution is initialised at current value of the RV if it is finite, otherwise a sample drawn from the prior is used.
- Parameters
scaling – value to multiply the stddev of the prior with to initialize the scale parameter of the posterior distribution.
loc_at_prior_mean (bool) – If one should initlize at the mean value of the prior or the current value of the prior if False.
Examples
>>> import borch >>> import borch.distributions as dist >>> class Model(borch.Module): ... def __init__(self): ... super().__init__(posterior=ScaledNormal(scaling=.1)) ... self.student_t = dist.StudentT(3, 3, 4) ... self.gamma = dist.Gamma(.5, .5) ... def forward(self): ... self.y = dist.Normal(self.student_t, self.gamma) ... return self.y >>> model = Model() >>> type(model()) <class 'torch.Tensor'>
The posterior will be initialized as a
Normal
distribution with the scale set toscaling*prior.distribution.stddev
.>>> type(model.posterior.student_t) <class 'borch.distributions.rv_distributions.Normal'> >>> round(float(model.posterior.student_t.scale), 2) 0.4
However, the posterior distribution is only created the first time a
RandomVariable
gets added to the model it will not preform well for hierarchies. In this case it would mean that theloc
ofy
is only set for the first forward and then optimized like a normal parameter after that. If one want to keep the hierarchy in the posterior as well one should useborch.posterior.Automatic
orborch.posterior.Manual
.>>> model.posterior.y.loc == model.student_t tensor(True) >>> borch.sample(model) >>> model.posterior.y.loc == model.student_t tensor(False)
-
register_random_variable
(name, rv)¶ Called the first time a RandomVariable gets added