Models

TODO: what a model is (takes a Tensor (or nothing!) as input, and returns a probability distribution(s))

TODO: creating your own model (via a class which inherits pf.Model, implement __init__ and __call__, and sometimes it may be required to implement log_likelihood)

TODO: types of models and their ABCs (continuous, discrete, categorical)

Specifying the observation distribution

The return value of Model.__call__ should be a Distribution object, which corresponds to the observation distribution. The model itself predicts the shape of this observation distribution. For example, for a linear regression the weights and the bias predict the mean of the observation distribution, and the standard deviation parameter predicts the standard deviation:

class LinearRegression(pf.ContinuousModel):

    def __init__(self):
        self.w = pf.Parameter()
        self.b = pf.Parameter()
        self.s = pf.ScaleParameter()

    def __call__(self, x):
        mean = x * self.w() + self.b()
        std = self.s()
        return pf.Normal(mean, std)  # returns predicted observation distribution

Bayesian updating

Bayesian updating consists of updating a model’s parameters’ priors to match their posterior distributions, after having observed some data. This is, after all, the main point of Bayesian inference! Because ProbFlow uses variational inference, both parameters’ posteriors and priors have an analytical form (i.e. they’re both known probability distributions with known parameters - not a set of MCMC samples or something), and so we can literally just set parameters’ prior distribution variables to be equal to the current posterior distribution variables!

To perform a Bayesian update of all parameters in a model, use the Model.bayesian_update() method.

model = # your ProbFlow model
model.bayesian_updating()

This can be used for incremental model updates when we get more data, but don’t want to have to retrain the model from scratch on all the historical data. For example,

# x, y = training data
model.fit(x, y)

# Perform the Bayesian updating!
model.bayesian_updating()

# x_new, y_new = new data
model.fit(x_new, y_new)
model.bayesian_update()

# x_new2, y_new2 = even more new data
model.fit(x_new2, y_new2)
model.bayesian_update()

# etc

Manually computing the log likelihood

The default loss function uses a log likelihood which is simply the log probability of the observed data according to the observation distribution (the distribution which was returned by the __call__ method of the model, see above).

However, you can override this default by re-defining the Model.log_likelihood() method if you need more flexibility in how you’re computing the log probability.

For example, if you want to limit the log probability of each datapoint to -10 (essentially, perform gradient clipping):

import probflow as pf
import tensorflow as tf

class LinearRegressionWithClipping(pf.ContinuousModel):

    def __init__(self):
        self.w = pf.Parameter()
        self.b = pf.Parameter()
        self.s = pf.ScaleParameter()

    def __call__(self, x):
        mean = x * self.w() + self.b()
        std = self.s()
        return pf.Normal(mean, std)

    def log_likelihood(self, x, y):
        log_likelihoods = tf.math.maximum(self(x).log_prob(y), -10)
        return tf.reduce_sum(log_likelihoods)

Also see the Censored Time-to-Event Model example for an example using a custom log_likelihood method to handle censored data.