Skip to main content

Introduction

To customize a model's architecture, SNNAX provides a simple way to define the structure of the model.

SNNAX offers two primary methods for defining the architecture:

StatefulModel​

The StatefulModel class enables the creation of custom SNNs with nearly arbitrary connectivity, defined through a graph structure known as the connectivity graph. It inherits from eqx.Module to be a callable pytree.

Arguments​

  • graph_structure (GraphStructure): A GraphStructure object that specifies the network topology.
  • layers (Sequence[eqx.Module]): The computational building blocks of the model.
  • forward_fn (Callable): The evaluation procedure/loop for the model. Defaults to default_forward_fn.

Methods​

  • init_state(in_shape, shapes=None, key=PRNGKey): Initializes the state of the model by recursively calling the init functions of the stateful layers. Non-stateful layers are initialized as None and their output shape is computed using a mock input.

    • Arguments:

      • in_shape (Union[Sequence[Tuple[int]], Tuple[int]]): Shape of the input.
      • shapes (Union[Sequence[Tuple[int]], None]): Optional shapes for the layers.
      • key (PRNGKey): Random key for initialization.
    • Returns: Sequence[Array] containing the initial state of the model.

  • __call__(input_states, input_batch, key, burnin=0): The forward function that performs the actual computation when the model is called. Uses lax.scan to iterate over the layers.

    • Arguments:

      • input_states (Sequence[jnp.ndarray]): Initial states of the model.
      • input_batch: Batch of input data.
      • key (jrand.PRNGKey): Random key for the computation.
      • burnin (int): Number of initial steps to ignore in backpropagation.
    • Returns: Tuple containing the new states and outputs.

Example​

1. Custom topology

First, define the layers, which can be from the equinox library or SNNAX's snnax.snn.layers.

from snnax import snn
import equinox as eqx

layers = [
eqx.Linear(10, 20),
eqx.LayerNorm(20),
snn.LIF()
]

Next, define the GraphStructure object, which contains meta-information about the computational graph.

graph_structure = snn.GraphStructure(
num_layers=3,
input_layer_ids=[[0], [], []],
final_layer_ids=[2],
input_connectivity=[[2], [0], [1]]
)

Finally, create the StatefulModel object by passing the graph_structure and layers as arguments.

model = snn.StatefulModel(
graph_structure=graph_structure,
layers=layers
)

The model architecture will be as follows:

2. Custom Class

You can also create a custom class by inheriting from StatefulModel and defining the __init__, init_state or __call__ methods.

class CustomModel(snn.StatefulModel):
def __init__(self):

# Custom __init__ method
GraphStructure = snn.GraphStructure(
num_layers=2,
input_layer_ids=[[0], []],
final_layer_ids=[1],
input_connectivity=[[], [0]]
)
layers = [
eqx.Linear(10, 20),
snn.LIF()
]
super().__init__(graph_structure, layers)

def init_state(self, in_shape, shapes=None, key=PRNGKey):

# Custom initialization
return super().init_state(in_shape, shapes, key)

def __call__(self, input_states, input_batch, key, burnin=0):

# Custom forward pass
return super().__call__(input_states, input_batch, key, burnin)

model = CustomModel()

GraphStructure​

The GraphStructure class contains meta-information about the computational graph and is used with the StatefulModel class to construct a computational model.

Arguments​

  • num_layers (int): The number of layers in the model.
  • input_layer_ids (Sequence[Sequence[int]]): Indices of the layers that receive external input.
  • final_layer_ids (Sequence[int]): Indices of the layers whose outputs are desired.
  • input_connectivity (Sequence[Sequence[int]]): Specifies how the layers are connected to each other.

Example​

graph_structure = snn.GraphStructure(
num_layers=5,
input_layer_ids=[[0], [], [], [0, 1], []],
final_layer_ids=[4],
input_connectivity=[[], [0, 2], [1], [2, 3], [3]]
)

The resulting model architecture is: