torchdyn

repository·master·Indexed 23 days ago

https://github.com/diffeqml/torchdyn

A PyTorch library dedicated to neural differential equations, implicit models, and related numerical methods. It provides a unified API for implementing architectures such as Neural ODEs, Galerkin Neural ODEs, Neural SDEs, Graph Neural ODEs, and Hamiltonian Neural Networks, along with tools for sensitivity algorithms, hypersolvers, and augmentation strategies like ANODE.

Tokens
11.8K
Snippets
33
Records
41
Agent score
82%

What's inside torchdyn

  1. Overview of torchdyn model implementations

    master

    The torchdyn library provides a unified and flexible API for implementing continuous and implicit learning models. The torchdyn.models module and associated tutorials include implementations of several key architectures and strategies:

    Core Architectures

    • Neural Ordinary Differential Equations (Neural ODE)
    • Galerkin Neural ODE
    • Neural Stochastic Differential Equations (Neural SDE)
    • Graph Neural ODEs
    • Hamiltonian Neural Networks

    Sequence and Hybrid Models

    • ODE-RNN: A recurrent or 'hybrid' version designed for sequences.

    Numerical Methods and Augmentation

    • Hypersolvers: Neural numerical methods.
    • Augmentation Strategies: Designed to increase expressivity and reduce computational burden on numerical solvers, including:
      • ANODE (0-augmentation)
      • Input-layer augmentation
      • Higher-order augmentation

    Sensitivity Algorithms

    • Integral loss adjoint variants.
  2. Overview of torchdyn.numerics

    master
    The torchdyn.numerics package provides the numerical methods necessary for the inference and training of torchdyn models. It exposes a functional API used for solving differential equations (DE solvers) and root-finding methods. Beyond model training, this API can be used to simulate various dynamical systems and generate synthetic data.
  3. Explore the torchdyn package structure

    master

    The torchdyn package is organized into several specialized subpackages for neural differential equations and related tasks:

    • torchdyn.core: Core abstractions and logic for the library.
    • torchdyn.datasets: Utilities and implementations for handling datasets.
    • torchdyn.models: Predefined model architectures.
    • torchdyn.nn: Neural network components and layers.
    • torchdyn.numerics: Numerical solvers and integration methods.
  4. Explore torchdyn benchmarks and pretrained models

    master

    The torchdyn repository provides quickstart examples and tutorials covering continuous/implicit learning and numerical methods. The project is developing standardized benchmarks across several domains to help practitioners identify state-of-the-art (SOTA) machine learning and numerical techniques for neural differential equations and implicit models.

    Planned benchmark domains include:

    • Time series tasks
    • Generative modeling
    • Optimal control
    • Image classification

    The project also intends to release loadable pretrained weights for top-performing models in these benchmarks.

  5. Core components of torchdyn

    master

    The torchdyn.core module contains the fundamental building blocks for neural differential equations in torchdyn. The primary abstractions you will use are:

    • NeuralODE: The main class for defining neural ordinary differential equations.
    • ODEProblem: A class used to define the specific problem instance (the ODE function and initial conditions) to be solved.
    • MultipleShootingLayers: A layer implementation used for multiple shooting methods to improve stability in solving long-horizon ODEs.

    These components work together to allow you to define, formulate, and solve neural differential equations using PyTorch.

  6. Use torchdyn.numerics for ODE integration and numerical methods

    master

    The torchdyn.numerics package is organized into several specialized modules for different numerical tasks:

    • odeint: Functions for ODE integration.
    • sensitivity: Methods for computing sensitivities (gradients/adjonts).
    • solvers: Core ODE solvers.
    • hypersolvers: Advanced or high-order solvers.
    • interpolators: Tools for interpolating between solved points.
    • root: Root-finding methods.
    • systems: Representations of dynamical systems.
    • utils: Numerical utility functions.
  7. Compare torchdyn gradients with torchdiffeq

    master

    The notebook demonstrates that torchdyn's gradient computation (using the adjoint method) is consistent with torchdiffeq. When calculating the gradient of a loss with respect to the time span T or the start time t0, the difference between torchdyn and torchdiffeq.odeint_adjoint should be negligible (near zero).

    # torchdyn gradient
    t_span = torch.cat([t0, T])
    t_eval, traj = prob(x, t_span)
    l = ((t_span[-1:] - torch.tensor([5]))**2).mean()
    dldt_torchdyn = grad(l, T)[0]
    
    # torchdiffeq gradient (requires wrapping vector field)
    class VectorField(nn.Module):
        def __init__(self, f):
            super().__init__()
            self.f = f
        def forward(self, t, x):
            return self.f(x)
    
    sys = VectorField(f)
    t_span = torch.cat([t0, T])
    t_eval_diff, traj_diff = torchdiffeq.odeint_adjoint(sys, x, t_span, method='dopri5', atol=1e-4, rtol=1e-4), None # simplified
    l_diff = ((t_span[-1:] - torch.tensor([5]))**2).mean()
    dldt_torchdiffeq = grad(l_diff, T)[0]
    
    # Check consistency
    print(dldt_torchdyn - dldt_torchdiffeq)
  8. How to trigger integral adjoint computation in a training loop

    master

    When using torchdyn with an integral loss, the backward pass needs a 'trigger' to initiate the integral adjoint computation.

    1. Triggering the gradient: In your training_step, you must compute a 'dummy loss' (e.g., loss = 0. * y_hat.sum()) to construct the computational graph that allows backward() to reach the integral adjoint logic.
    2. Explicit evaluation (Optional): If you need to log the actual integral loss value (not just the gradient), you can compute it explicitly by augmenting the state with an auxiliary variable and temporarily switching the sensitivity to 'autograd' or 'interpolated_adjoint' to solve the augmented system.
  9. Use the Hypersolver API with odeint

    master

    Hypersolvers are hybrid ODE solvers that use a neural network to approximate residuals. They are integrated into the standard odeint API. A key feature is that the solver's state preserves persistent information, such as the hypernetwork parameters, allowing for seamless integration with standard ODE solving workflows.

    To use a hypersolver, you pass the hypersolver instance (e.g., HyperEuler) to the solver argument of odeint.

    from torchdyn.numerics import odeint, Euler, HyperEuler
    
    # Assuming 'sys' is your system and 'hypersolver' is a trained HyperEuler instance
    _, trajectory = odeint(sys, x0, t_span, solver=hypersolver, atol=1e-7, rtol=1e-7)
  10. Implement Stacked Neural ODEs with Discrete State Transitions

    master

    This variant combines stacked Neural ODEs with learned discrete transition maps (jumps) at the end of each interval. The state is updated by $z^+ = g(z(t), \omega_i)$ at the boundary $t = t_{i+1}$.

    To implement this:

    1. Define a sequence of NeuralODE flows.
    2. Define a sequence of transition layers (e.g., nn.Linear).
    3. In the forward pass, iterate through the flows, and after each flow, apply the corresponding jump layer to the last state of the trajectory before passing it to the next flow.
    from torchdyn.core import NeuralODE
    from torchdyn.nn import DataControl
    import torch.nn as nn
    
    num_pieces = 5
    
    # stacked depth-invariant Neural ODEs
    nde = []
    for i in range(num_pieces):
        nde.append(NeuralODE(nn.Sequential(DataControl(),
                                          nn.Linear(4, 4), 
                                          nn.Tanh(), 
                                          nn.Linear(4, 2)), solver='rk4'))
    
    # State "jump" parametrized by a linear layer
    jumps = nn.Sequential(*[nn.Linear(2, 2) for _ in range(num_pieces)]).to(device)
    flows = nn.Sequential(*nde).to(device)
    
    t_span = torch.linspace(0, 1, 5)
    
    class PCDST_NeuralODE(nn.Module):
        def __init__(self):
            super().__init__()
            self.flows = flows
            self.jumps = jumps
        def forward(self, x, t_span):
            for k, node in enumerate(self.flows):
                t_eval, traj = node(x, t_span)
                x = self.jumps[k](traj[-1])
            return t_eval, traj
    
    model = PCDST_NeuralODE()
  11. Implement Stacked Neural ODE (Piece-wise Constant Weights)

    master

    Stacked Neural ODEs approximate depth-varying weights by using piece-wise constant weights over $N$ intervals. This is equivalent to stacking $N$ depth-invariant Neural ODEs sequentially, where each ODE is solved over its own sub-interval $\Delta t_i$.

    To implement this:

    1. Create a list of NeuralODE instances (one for each piece).
    2. Wrap them in an nn.Sequential or a custom nn.Module.
    3. In the forward pass, iterate through the nodes, passing the output of one node as the input to the next.

    Note: Since individual nodes are depth-invariant, you can solve them in the interval $[0, \Delta t_i]$.

    from torchdyn.core import NeuralODE
    from torchdyn.nn import DataControl
    import torch.nn as nn
    
    num_pieces = 5
    
    # Stacked depth-invariant Neural ODEs
    nde = []
    for i in range(num_pieces):
        nde.append(NeuralODE(nn.Sequential(DataControl(),
                                          nn.Linear(4, 4), 
                                          nn.Tanh(), 
                                          nn.Linear(4, 2)), solver='rk4')) 
    pc_node = nn.Sequential(*nde).to(device)
    
    class PC_NeuralODE(nn.Module):
        def __init__(self):
            super().__init__()
            self.pc_node = pc_node
        def forward(self, x, t_span):
            for node in self.pc_node:
                t_eval, traj = node(x, t_span)
                x = traj[-1]
            return t_eval, traj
    
    model = PC_NeuralODE()