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 the loc of y 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 use borch.posterior.Automatic or borch.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 the loc of y will be updated with the value of student_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 to 1 (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 the loc of y 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 use borch.posterior.Automatic or borch.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 to scaling*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 the loc of y 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 use borch.posterior.Automatic or borch.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