Training a simple Neural Network#
⚠️ ATTENTION: Please compile the packages from source to run this tutorial, as it cannot be currently executed on Binder due to an issue with the aidge-learning PyPI package version 0.5.0. (Last update: 05/12/2025).
This tutorial introduces the basics of learning with the framework Aidge.
What you will learn:
creating an SNN model using Aidge API
create and import a dataset
train the model
Install requirements#
torch and torchvision, are properly installed in the current environment. If it is the case, the following setup steps can be skipped.[ ]:
%pip install aidge-core \
aidge-backend-cpu \
aidge-learning
%pip install torch torchvision
Next, we start by importing every module we need:
[ ]:
import aidge_core
import aidge_backend_cpu
import aidge_learning
import numpy as np
# Required to load MNIST dataset
import torchvision
import torchvision.transforms as transforms
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import torch
# Changing Log level to avoid too much logs
aidge_core.Log.set_console_level(aidge_core.Level.Fatal)
Creating Aidge model#
In this example, we will create a simple perceptron model (One FC (Fully Connected) layer followed by one LIF (Leaky Integrate-And-Fire) layer followed by one FC layer followed by one LIF layer).
For this we cannot use the sequential helper function (as there is currently no way for it to distinguish between the multiple outputs of the Leaky operator), so we create nodes and connect them by hand. We also need to add nodes that will initialize the memory potential of LIF nodes.
First we define the network constants (and in particular the number of timesteps used):
[ ]:
num_steps = 10
# Network topology
in_channels = 28 * 28
hidden_channels = 1000
out_channels = 10
# SNN parameters
beta = 0.95
threshold = 1.0
batch_size = 32
reset_type = aidge_core.leaky_reset.subtraction # Type of reset for leaky
Then the network itself, and the initialization of the weights and bias.
[ ]:
# Creation of the network
stack = aidge_core.Stack(num_steps, name="stack")
pop = aidge_core.Pop(name="pop")
fc1 = aidge_core.FC(in_channels, hidden_channels, name="fc1")
lif1 = aidge_core.Leaky(num_steps + 1, beta, threshold, reset_type, False, name="lif1")
fc2 = aidge_core.FC(hidden_channels, out_channels, name="fc2")
lif2 = aidge_core.Leaky(num_steps + 1, beta, threshold, reset_type, True, name="lif2")
model = aidge_core.GraphView()
# Connect operators
pop.add_child(fc1, 0, 0)
fc1.add_child(lif1, 0, 0)
lif1.add_child(fc2, 1, 0) # 1 for spike output
fc2.add_child(lif2, 0, 0)
lif2.add_child(stack, 0, 0)
for name, channels in [("lif1", hidden_channels), ("lif2", out_channels)]:
memory_init = aidge_core.Tensor()
memory_init.set_backend("cpu")
memory_init.to_dtype(aidge_core.dtype.float32)
memory_init.resize([batch_size, channels])
memory_init.zeros()
memory_node = aidge_core.Producer(memory_init, "memory_init_n")
if name == "lif1":
memory_node.add_child(lif1, 0, 1)
memory_node.add_child(lif1, 0, 2)
else:
memory_node.add_child(lif2, 0, 1)
memory_node.add_child(lif2, 0, 2)
model.add(memory_node)
model.add(fc1)
model.add(lif1)
model.add(fc2)
model.add(lif2)
model.add(stack)
model.add(pop)
model.compile("cpu", aidge_core.dtype.float32)
model.forward_dims()
# Initialize parameters (weights and bias)
for node in model.get_nodes():
if node.type() == "Producer":
prod_op = node.get_operator()
value = prod_op.get_output(0)
tuple_out = node.output(0)[0]
# No conv in current network
if tuple_out[0].type() == "FC" and tuple_out[1] == 1:
aidge_core.he_filler(value) # FC weight
elif tuple_out[0].type() == "FC" and tuple_out[1] == 2:
aidge_core.constant_filler(value, 0.01) # FC bias
else:
pass
Following the creation of our model, we setup the database object:
[ ]:
def create_mnist_loader():
dtype = torch.float
device = torch.device("cpu")
data_path = "/tmp/data"
transform = transforms.Compose(
[
transforms.Resize((28, 28)),
transforms.Grayscale(),
transforms.ToTensor(),
transforms.Normalize((0,), (1,)),
]
)
mnist_train = datasets.MNIST(
data_path, train=True, download=True, transform=transform
)
mnist_test = datasets.MNIST(
data_path, train=False, download=True, transform=transform
)
# Create Dataloaders
train_loader = DataLoader(mnist_train, batch_size, shuffle=True, drop_last=True)
test_loader = DataLoader(mnist_test, batch_size, shuffle=True, drop_last=True)
return train_loader, test_loader
Finally, we need to create the learning objects:
[ ]:
opt = aidge_learning.Adam()
opt.set_learning_rate_scheduler(aidge_learning.constant_lr(0.0005))
opt.set_parameters(
(
[
fc1.get_operator().get_input(1), # FC1 weight
fc2.get_operator().get_input(1), # FC2 bias
fc1.get_operator().get_input(2), # FC2 weight
fc2.get_operator().get_input(2), # FC2 bias
]
)
)
And create the scheduler object:
[ ]:
scheduler = aidge_core.SequentialScheduler(model, False)
Next we can create the training loop. For each batch of data, we call forward(). The difference between this and the normal training is that we also need to setup our scheduler so that is saves inputs and outputs of the nodes (scheduler.save_and_restore_inputs), since they are needed for training. After running forward, we simply call scheduler.backward() to perform BPTT and opt.update() to update the parameters.
[ ]:
def test_learning():
train_loader, test_loader = create_mnist_loader()
train_batch = iter(train_loader)
counter = 0
for data, targets in train_batch:
aidge_input = (
data.view(batch_size, -1)
.unsqueeze(0)
.repeat(num_steps, 1, 1)
.cpu()
.detach()
.numpy()
)
aidge_label = (
torch.nn.functional.one_hot(targets, out_channels)
.numpy()
.astype(np.float32)
)
input_tensor = aidge_core.Tensor(aidge_input)
input_tensor.to_dtype(aidge_core.dtype.float32)
for node in model.get_nodes():
if node.type() == "Pop":
node.get_operator().associate_input(0, input_tensor)
scheduler.reset_scheduling()
scheduler.generate_scheduling()
scheduler.save_and_restore_inputs(model)
scheduler.forward()
output = stack.get_operator().get_output(0)
target_tensor = aidge_core.Tensor(aidge_label)
target_tensor.to_dtype(aidge_core.dtype.float32)
opt.reset_grad()
loss_value = aidge_learning.loss.multiStepCELoss(
output, target_tensor, num_steps
)
print(f"Loss: {loss_value[0]}", flush=True)
scheduler.backward()
opt.update()
counter += 1
if counter > 5:
break
print("Done.")
Running the learning loop#
[ ]:
test_learning()