dynamax

repository·main·Indexed 21 days ago

https://github.com/probml/dynamax

A JAX-based library for probabilistic state space models (SSMs) providing tools for inference and learning. It supports various models including Hidden Markov Models (HMMs) with Categorical, Gaussian, and Poisson observations; Linear Gaussian State Space Models (LGSSMs); Nonlinear Gaussian SSMs using EKF, UKF, and GGF; and Generalized Gaussian State Space Models (GG-SSMs) utilizing conditional moments Gaussian filtering.

Tokens
11.9K
Snippets
36
Records
47
Agent score
76%

What's inside dynamax

  1. Use Nonlinear Gaussian State Space Models

    main

    Dynamax provides model definitions for State Space Models (SSMs) that feature nonlinear dynamics and/or nonlinear observations, assuming additive Gaussian noise.

    Supported Inference Algorithms

    You can perform deterministic inference using several supported algorithms:

    • EKF (Extended Kalman Filter)
    • UKF (Unscented Kalman Filter)
    • GGF (Gaussian Gauss-Newton Filter)

    Limitations and Parameter Estimation

    Parameter estimation is currently not supported directly within the library because the nonlinear functions are treated as black-box functions. If you need to learn model parameters, you must implement your own learning/optimization code (e.g., using external gradient-based optimization or custom EM loops).

  2. Supported features of Hidden Markov Models in Dynamax

    main

    Dynamax provides implementations for Hidden Markov Models (HMMs) with the following capabilities:

    Observation Models

    Supports various observation distributions, including:

    • Categorical
    • Gaussian
    • Poisson
    • (and others)

    Inference and Estimation

    • Posterior Inference: Exact inference using the forwards-backwards algorithm.
    • Parameter Estimation: Optimization of model parameters using Expectation-Maximization (EM) and Stochastic Gradient Descent (SGD).
  3. Use Linear Gaussian State Space Models in Dynamax

    main

    Dynamax provides a model class definition for Linear Gaussian State Space Models (LGSSMs). This implementation includes built-in support for:

    • State Estimation: Using Kalman filtering and smoothing algorithms.
    • Parameter Estimation: Methods to fit model parameters to observed data.
  4. Understand Dynamax type annotations and terminology

    main

    Dynamax uses jaxtyping for type declarations of JAX arrays. While these declarations are available in the documentation, run-time type checking is currently disabled.

    To interpret the API signatures, use the following shorthand terminology:

    Basic Types

    • Scalar: An int, float, or a JAX array with shape ().
    • PyTree: Any valid JAX PyTree.
    • Array: A JAX array.

    Array Shapes and Dtypes

    Array annotations specify dimensions and data types using the following syntax:

    • Named Dimensions: Array["dim1", "dim2"] represents a JAX array with shape (dim1, dim2). When dimensions share the same name across different arrays in an API call, they must have matching sizes.
    • Zero-dimensional Arrays: Array[()] represents an array with shape ().
    • Ellipsis: ... represents an arbitrary number of dimensions (e.g., Array["times", ...]).
    • Dtypes: Array[bool] refers to a JAX array with a Boolean dtype. Other dtypes follow the same pattern.
    • Combined Annotations: You can combine shape and dtype, such as Array["dim1", "dim2", bool].
    • Unions: Union types are used when an argument can take different shapes (e.g., a transition matrix that can be either a constant matrix or a time-varying sequence).
  5. What are Generalized Gaussian State Space Models (GG-SSMs)?

    main

    A Generalized Gaussian State Space Model (GG-SSM) is a type of State Space Model (SSM) characterized by:

    1. Nonlinear Gaussian Dynamics: The state transitions follow a nonlinear function with Gaussian noise.
    2. Nonlinear Observations: The observation process is nonlinear.
    3. Non-Gaussian Emissions: Unlike standard SSMs, the emission distribution can be non-Gaussian (for example, Poisson or Categorical distributions).

    To perform approximate inference in these models, Dynamax utilizes conditional moments Gaussian filtering. This is a specific form of the generalized Gaussian filter where the observation model is represented using its conditional first and second-order moments: $E[Y|z]$ (the conditional mean) and $Cov[Y|z]$ (the conditional covariance).

  6. Install Dynamax

    main

    You can install Dynamax from PyPI. Use the [notebooks] extra if you need dependencies for running demo notebooks.

    To install the latest release:

    pip install dynamax                 # Install core dependencies
    pip install dynamax[notebooks]      # Install with demo notebook dependencies

    To install the latest development branch directly from GitHub:

    pip install git+https://github.com/probml/dynamax.git
    pip install dynamax[notebooks]
  7. Install Dynamax for development

    main

    If you are contributing to the project, you can install Dynamax in editable mode along with development, test, and documentation dependencies:

    git clone git@github.com:probml/dynamax.git
    cd dynamax
    pip install -e '.[dev]'
  8. How to create a custom HMM emission model

    main

    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)
  9. Concept: Parallel Message Passing for HMMs

    main

    The standard forward-backward algorithm for HMMs is sequential, taking $O(T)$ time. However, by treating the HMM as an undirected graphical model and using an associative scan, inference can be performed in $O(\log T)$ time using $O(T)$ parallel processors.

    This is achieved by decomposing the potential functions into a row-normalized matrix $A$ and a vector $b$. By applying a binary associative operator to these potentials, multiple latent variables can be eliminated in parallel. This approach is highly efficient on hardware like GPUs when dealing with long sequences ($T$).

  10. Handle label switching in HMM inference

    main

    When comparing learned parameters or states to ground truth, you may encounter 'label switching' (e.g., state 0 in the model corresponds to state 1 in the truth). Because the likelihood is identical, the optimizer may choose any valid permutation of labels.

    To resolve this, use the dynamax.utils.find_permutation function to find the best correspondence between discrete latent labels.

  11. Implement a custom State Space Model (SSM) by inheriting from SSM

    main

    To create a new state space model in Dynamax, you must subclass the SSM abstract base class and implement the following required methods and properties:

    Required Abstract Methods

    • initial_distribution(params, inputs): Returns a tfd.Distribution representing the initial state $p(z_1 \mid \theta)$.
    • transition_distribution(params, state, inputs): Returns a tfd.Distribution representing the conditional next state $p(z_{t+1} \mid z_t, u_t, \theta)$.
    • emission_distribution(params, state, inputs): Returns a tfd.Distribution representing the conditional emission $p(y_t \mid z_t, u_t, \theta)$.

    Required Properties

    • emission_shape: A property returning a tuple (or pytree of tuples) specifying the shape of a single time step's emissions.
    • inputs_shape (Optional): A property returning the shape of a single time step's inputs. Defaults to None if there are no inputs.

    Optional Methods

    • log_prior(params): Returns the log prior probability of the parameters. Defaults to 0.0 if not implemented.
    from dynamax.ssm import SSM
    
    class MyCustomSSM(SSM):
        @property
        def emission_shape(self):
            return (D,)
    
        def initial_distribution(self, params, inputs):
            # return a tensorflow_probability distribution
            pass
    
        def transition_distribution(self, params, state, inputs):
            # return a tensorflow_probability distribution
            pass
    
        def emission_distribution(self, params, state, inputs=None):
            # return a tensorflow_probability distribution
            pass
  12. How to assemble a custom HMM class

    main

    Once you have defined your custom HMMEmissions class, you can assemble a full HMM by inheriting from the base HMM class. This allows you to combine your custom emissions with standard components like StandardHMMInitialState and StandardHMMTransitions.

    In your custom HMM subclass:

    1. __init__: Initialize the component objects (initial state, transitions, and your custom emissions) and call super().__init__(num_states, initial_component, transition_component, emission_component).
    2. inputs_shape property: Define the shape of the inputs expected by your emission model.
    3. initialize method: Delegate parameter initialization to the underlying components and return a combined HMMParameterSet and HMMPropertySet.

    By following this pattern, your custom model automatically inherits powerful methods like .sample(), .fit_em(), and .smoother() from the base HMM class.

    class PoissonGLMHMM(HMM):
        def __init__(self, num_states, emission_dim, input_dim, ...):
            self.inputs_dim = input_dim
            initial_component = StandardHMMInitialState(num_states, ...)
            transition_component = StandardHMMTransitions(num_states, ...)
            emission_component = PoissonGLMHMMEmissions(num_states, emission_dim, input_dim, ...)
            super().__init__(num_states, initial_component, transition_component, emission_component)
    
        @property
        def inputs_shape(self) -> Tuple[int, ...]:
            return (self.inputs_dim,)
    
        def initialize(self, key, method="prior", ...):
            # ... split key and call component.initialize() ...
            return ParamsPoissonGLMHMM(**params), ParamsPoissonGLMHMM(**props)