To create a custom emission model in Dynamax, you must implement a class that inherits from HMMEmissions. Your class must implement the following interface:
- Properties:
emission_shape: Returns the shape of the emission distribution.inputs_shape: Returns the shape of the inputs to the emission distribution.
- Methods:
initialize(key, method, ...): Returns a tuple of (params, props). params contains the actual parameter values, while props contains ParameterProperties (e.g., using TFP bijectors to enforce constraints like non-negativity during optimization).log_prior(params): Computes the log prior probability of the parameters. If not implemented, the base class assumes a zero log prior.distribution(params, state, inputs): Returns a tensorflow_probability.substrates.jax.distributions object representing the likelihood $p(y_t \mid x_t, z_t)$ for a given state and input.
Example of a Poisson GLM emission implementation:
class PoissonGLMHMMEmissions(HMMEmissions):
def __init__(self, num_states, emission_dim, input_dim, ...):
super().__init__(...)
self.num_states = num_states
self.emission_dim = emission_dim
self.input_dim = input_dim
@property
def emission_shape(self) -> Tuple:
return (self.emission_dim,)
@property
def inputs_shape(self) -> Tuple[int]:
return (self.input_dim,)
def initialize(self, key, method="prior", ...):
# ... implementation returning (params, props) ...
# Use tfb.Softplus() in props to enforce non-negativity
props = ParamsPoissonGLMHMMEmissions(weights=ParameterProperties(constrainer=tfb.Softplus()))
return params, props
def distribution(self, params, state, inputs):
activations = params.weights[state] @ inputs
return tfd.Independent(tfd.Poisson(rate=activations), 1)
class PoissonGLMHMMEmissions(HMMEmissions):
def __init__(self, num_states, emission_dim, input_dim, ...):
super().__init__(...)
self.num_states = num_states
self.emission_dim = emission_dim
self.input_dim = input_dim
@property
def emission_shape(self) -> Tuple:
return (self.emission_dim,)
@property
def inputs_shape(self) -> Tuple[int]:
return (self.input_dim,)
def initialize(self, key, method="prior", ...):
# ... implementation returning (params, props) ...
props = ParamsPoissonGLMHMMEmissions(weights=ParameterProperties(constrainer=tfb.Softplus()))
return params, props
def distribution(self, params, state, inputs):
activations = params.weights[state] @ inputs
return tfd.Independent(tfd.Poisson(rate=activations), 1)