FlowJAX

repository·main·Indexed 42 days ago

https://github.com/danielward27/flowjax

A library for distributions, bijections, and normalizing flows built on top of Equinox and JAX. Version 19.1.1 provides state-of-the-art architectures including block neural autoregressive flows, coupling flows, and masked autoregressive flows. It features PyTree compatibility, conditional density estimation, and experimental NumPyro integration. Includes training utilities like fit_to_data for maximum likelihood and variational inference.

Tokens
17.5K
Snippets
59
Records
71
Agent score
51%

What's inside FlowJAX

  1. Overview of FlowJAX features and architectures

    main

    FlowJAX provides a suite of tools for working with distributions, bijections, and normalizing flows using Equinox and JAX.

    Key Capabilities

    • PyTree Compatibility: Distributions and bijections are Equinox modules, allowing them to work seamlessly with JAX transformations.
    • Conditional Support: First-class support for conditional distributions and density estimation.
    • Inversion: Includes a bisection search algorithm to invert bijections that lack a known analytical inverse.

    Supported Architectures

    • Normalizing Flows:
      • coupling_flow (Dinh et al., 2017)
      • masked_autoregressive_flow (Kingma et al., 2016; Papamakarios et al., 2017)
      • block_neural_autoregressive_flow (De Cao et al., 2019)
      • planar_flow (Rezende and Mohamed, 2015)
      • triangular_spline_flow (introduced in this repo)
    • Transformers: Flows can use arbitrary bijections as transformers, such as Affine or RationalQuadraticSpline (for neural spline flows).

    Training Methods

    Training scripts are available for:

    • Maximum likelihood
    • Variational inference
    • Contrastive learning for sequential neural posterior estimation
  2. Overview of FlowJAX capabilities

    main

    FlowJAX is a package for continuous distributions, bijections, and normalizing flows. It is built on top of equinox and jax.

    Key features include:

    • Wide range of distributions and bijections: Provides many standard and specialized mathematical objects.
    • JAX Compatibility: Distributions and bijections are implemented as PyTrees via equinox modules, allowing them to work seamlessly with JAX transformations like jit, grad, and vmap.
    • Normalizing Flows: Includes many state-of-the-art normalizing flow models.
    • Conditional Support: First-class support for conditional distributions, which is essential for applications like amortized variational inference and simulation-based inference.
  3. How normalizing flows are implemented in FlowJAX

    main

    In FlowJAX, normalizing flows are implemented as convenient constructors for flowjax.distributions.Transformed distributions. A flow defines a flexible distribution by transforming a simple base distribution through a flexible bijection (diffeomorphism).

    FlowJAX uses two primary methods to compose bijections:

    1. flowjax.bijections.Chain: Allows chaining arbitrary and heterogeneous bijections. However, it compiles each layer separately, which can increase compilation time.
    2. flowjax.bijections.Scan: Requires layers to share the same structure, but avoids compiling each layer separately.

    Note: All FlowJAX flows use flowjax.bijections.Scan by default to optimize and reduce compilation times.

  4. Use conditional distributions

    main

    Conditional distributions in FlowJAX include a cond_shape attribute (which is None for unconditional distributions). When using conditional distributions, you can pass a condition argument to sample and log_prob. If you pass batches of samples and conditions, FlowJAX follows NumPy's broadcasting rules.

    from flowjax.distributions import Normal
    from flowjax.flows import coupling_flow
    import jax.numpy as jnp
    import jax.random as jr
    
    key = jr.key(0)
    dist = coupling_flow(key, base_dist=Normal(jnp.zeros(3)), cond_dim=2)
    
    # Sampling 10 times for a single conditioning variable instance
    condition = jnp.ones(2)
    samples = dist.sample(key, (10,), condition=condition)
    # samples.shape == (10, 3)
    
    # Sampling once for each of 5 conditioning variable instances
    condition = jnp.ones((5, 2))
    samples = dist.sample(key, condition=condition)
    # samples.shape == (5, 3)
  5. Treat distributions and bijections as PyTrees

    main

    Distributions and bijections are registered as PyTrees via equinox. This allows you to use equinox.filter_vmap to vectorize the initialization and operations of distributions. Instead of managing a batch_shape attribute manually, you can use JAX vectorization to handle batches of non-identically distributed objects.

    import equinox as eqx
    import jax.numpy as jnp
    from flowjax.distributions import Normal
    
    # Vectorize initialization to create a batch of independent distributions
    normals = eqx.filter_vmap(Normal)(jnp.arange(3))
    
    # Vectorize log_prob computation over the batch of distributions
    log_probs = eqx.filter_vmap(lambda dist, x: dist.log_prob(x))(normals, jnp.arange(3))
    # log_probs.shape == (3,)
  6. Understand the Distribution hierarchy in FlowJAX

    main
    In FlowJAX, all probability distributions are built upon the flowjax.distributions.AbstractDistribution base class. This provides a consistent interface for working with different types of distributions, ensuring they all support core probabilistic operations. For a high-level introduction to using these distributions, refer to the getting started guide.
  7. Optimize flow performance for sampling vs density evaluation

    main

    Bijections in normalizing flows often have asymmetric computational efficiency. In FlowJAX, the default behavior is optimized for density evaluation (log_prob) rather than sampling.

    • Default Behavior: FlowJAX inverts the bijection with flowjax.bijections.Invert before transforming the base distribution. This makes log_prob faster but makes sample and sample_and_log_prob slower.
    • Optimization for Sampling: If you require faster sample and sample_and_log_prob methods (for example, when using certain variational objectives), you should set invert=False when constructing the flow.
  8. Build the HTML documentation locally

    main

    To build the HTML version of the FlowJAX documentation on your local machine, navigate to the docs directory and run the make command. Once the build is complete, you can view the documentation by opening the generated index.html file in your browser.

    make -C docs html
    # View the output at ./docs/_build/html/index.html
  9. Define transformed distributions

    main

    There are two ways to create a distribution that is the result of a transformation (a bijection) applied to a base distribution:

    1. Using Transformed: Pass a base distribution and a bijection directly.
    2. Inheriting from AbstractTransformed: Create a custom class for more flexibility (e.g., adding extra methods).

    In both cases, the bijection.shape and distribution.shape must match. You can combine unconditional and conditional components as long as all conditional parts share the same cond_shape.

    # Option 1: Using Transformed
    from flowjax.distributions import Normal, Transformed
    from flowjax.bijections import Exp
    log_normal = Transformed(Normal(), Exp())
    
    # Option 2: Inheriting from AbstractTransformed
    from flowjax.distributions import AbstractTransformed
    import jax.numpy as jnp
    
    class LogNormal(AbstractTransformed):
        base_dist: Normal
        bijection: Exp
    
        def __init__(self, loc=0, scale=1):
            self.base_dist = Normal(loc, scale)
            self.bijection = Exp(self.base_dist.shape)
    
    log_normal = LogNormal()
  10. Standardize variables using bijections and Transformed distributions

    main

    When working with target samples that require specific scales or forms, you can use a bijection for preprocessing. To ensure the flow operates on the original scale, wrap the flow in a Transformed distribution using the inverse of your preprocessing bijection.

    import jax
    import jax.numpy as jnp
    import jax.random as jr
    from flowjax.distributions import Normal, Transformed
    from flowjax.bijections import Affine, Invert
    from flowjax.train import fit_to_data
    
    key = jr.key(0)
    x = jr.normal(key, (1000, 3))
    flow = Normal(jnp.ones(3))
    
    # 1. Define preprocessing
    preprocess = Affine(-x.mean(axis=0)/x.std(axis=0), 1/x.std(axis=0))
    x_processed = jax.vmap(preprocess.transform)(x)
    
    # 2. Fit the flow to processed data
    # flow, losses = fit_to_data(key, dist=flow, x=x_processed)
    
    # 3. Wrap the flow to 'undo' preprocessing automatically
    flow = Transformed(flow, Invert(preprocess))
  11. Enable runtime type checking with jaxtyping and beartype

    main

    To enable helpful runtime error messages regarding array shapes and types, you can use jaxtyping's import hook combined with beartype. This is useful for catching shape mismatches during development.

    from jaxtyping import install_import_hook
    
    # Use the hook to wrap the flowjax import with beartype
    with install_import_hook("flowjax", "beartype.beartype"):
        from flowjax import bijections as bij
    
    # This will now raise a helpful error if shapes are incorrect
    exp = bij.Exp(shape=2)  # Raises error because shape should be a tuple, e.g., (2,)