← Home

Training

ar006 · 14 May 2026 · pdf

This is the shared training recipe every model on the ladder uses. It runs, in order, through how gradients flow through time, the surrogate that lets them pass through spikes, the one flag that stops them exploding (its own article, Gradient Stabilisation), the loss and optimiser, the readout options, the firing-rate regulariser, weight initialisation, and the tasks the networks are trained on.

Backpropagation through time

Every model here is a recurrent system run forward in time, so gradients come from Backpropagation Through Time (BPTT): unroll the recurrence into a deep feedforward graph — one layer per timestep, all sharing the same weights — and backpropagate through it.

Take a hidden state 𝑡 that evolves as

𝑡=𝑓(𝑡1,𝑥𝑡;𝜃),𝑦𝑡=𝑔(𝑡;𝜃)

with input 𝑥𝑡, output 𝑦𝑡, and parameters 𝜃 shared across time. Running 𝑇 steps gives a chain 01𝑇, which for gradients we treat as a depth-𝑇 feedforward network with tied weights.

The gradient with respect to a parameter 𝜃𝑘 then sums over every timestep it touched:

𝜕ℒ︀𝜕𝜃𝑘=𝑡=1𝑇𝜕ℒ︀𝜕𝑡𝜕𝑡𝜕𝜃𝑘

The catch is the product of per-step Jacobians 𝜕𝑡+1/𝜕𝑡 running through the chain: if their norms sit above 1 the product explodes in 𝑇, if below 1 it vanishes.

SNNs fit BPTT naturally — one simulation step is one step of the recursion, with the hidden state holding membrane potentials, synaptic conductances, and refractory counters. A 200 ms trial at Δ𝑡=0.1 ms unrolls to 𝑇=2000 steps. Because the state variables carry physical units (mV, μS), the per-step Jacobians are wildly scaled: voltage updates carry tiny factors like Δ𝑡/𝐶𝑚 while surrogate gradients through spikes are 𝑂(1). That mismatch is exactly what the gradient-stabilisation flag exists to fix — derived in full in Gradient Stabilisation.

Surrogate gradients

The spike function 𝑆=𝟏[𝑈𝜃] has zero gradient almost everywhere, so the backward pass substitutes a smooth surrogate. Pinglab uses the fast-sigmoid surrogate everywhere. Forward is the hard step; backward is

𝜕𝑆̃𝜕𝑈=𝑘(1+𝑘|𝑈𝜃|)2

This matches snntorch’s FastSigmoid, so equal-𝑘 comparisons against the snntorch reference test the update rule, not the surrogate.

It takes its slope from SURROGATE_SLOPE = 5.0, overridable per-run with –surrogate-slope.

Gradient stabilisation

Conductance-based networks (COBA, PING) need one extra ingredient to train: the recurrent E↔I loop makes the backpropagated gradient explode during BPTT, and a single flag, --v-grad-dampen, tames it. The full derivation — why the gradient diverges once per gamma cycle, and why per-step voltage damping fixes it without touching the forward pass — has its own article: Gradient Stabilisation.

The training loop

Logits from the readout go into cross-entropy loss:

𝐿CE=1𝐵𝑏=1𝐵logexp(𝑦̂𝑏,𝑐𝑏)𝑘exp(𝑦̂𝑏,𝑘)

with batch size 𝐵, logit vector 𝑦̂𝑏, and true class 𝑐𝑏; chance-level loss on a 10-class problem is ln102.30. The optimiser is Adam, with gradients clipped to unit norm (GRAD_CLIP = 1.0) before each step. The saved weights.pth is the best-epoch state by test accuracy, not the final epoch.

Readout

The readout collapses the last hidden layer’s activity into class logits; --readout picks how. Four modes:

The choice matters because it sets where the gradient enters the network: mem-mean lets it flow through the output LIF’s membrane at every timestep, while spike-count only sees the aggregate.

Firing-rate regularisation

Many recipes penalise too much or too little hidden firing via --fr-reg-upper-theta, --fr-reg-upper-strength, and the matching lower pair:

ℒ︀fr=𝑠𝑢ReLU(𝑟̄𝜃𝑢)+𝑠𝑙ReLU(𝜃𝑙𝑟̄)

where 𝑟̄ is the per-layer mean firing rate (per-neuron or population, set by --fr-reg-mode). This is the mechanism behind the 𝜃𝑢 sweeps in exp025 and the rate-floor framing in ar009.

Weight init

Feedforward weights are sampled fan-in-normalised, either half-normal (Dale’s law) or normal (signed):

𝑊𝒩︀(𝜇,𝜎2),𝑊𝑊/𝑁pre

with optional sparsity 𝑠[0,1): a fraction 𝑠 of entries are zeroed and the survivors rescaled by 1/(1𝑠), so the expected synaptic input per post-neuron is preserved.

Dale’s law under Adam

When Dale’s law is on, the feedforward matrices 𝑊ff are clamped to 𝑊0 when they are read by the forward pass and every trainable constrained matrix is projected back into the non-negative cone by project_dales() after each optimiser step. The recurrent conductance matrices 𝑊𝑒𝑒, 𝑊𝑒𝑖, 𝑊𝑖𝑒, and 𝑊𝑖𝑖 are not forward-clamped: they are initialised non-negative and, when trainable, kept non-negative by the post-step projection. Their entries are conductance magnitudes; pathway-specific reversal potentials, rather than a negative stored 𝑊𝑖𝑒, determine whether a synapse is excitatory or inhibitory.