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.
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
with input , output , and parameters shared across time. Running steps gives a chain , 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:
The catch is the product of per-step Jacobians 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 ms unrolls to 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 . That mismatch is exactly what the gradient-stabilisation flag exists to fix — derived in full in Gradient Stabilisation.
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
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.
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.
Logits from the readout go into cross-entropy loss:
with batch size , logit vector , and true class ; chance-level loss on a 10-class problem is . 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.
The readout collapses the last hidden layer’s activity into class logits; --readout picks how. Four modes:
spike-count — sum last-hidden spikes over the trial and project linearly, . Equivalent to spike-rate up to a constant.mem-mean — pass spikes through a final non-resetting LIF and average its membrane potential over the trial. Default for the COBA/PING recipes; used by exp025 and the streaming entries.li — leaky integrator: a non-spiking LIF whose final-step membrane potential is the logit.rate — softmax over per-trial spike rates.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.
Many recipes penalise too much or too little hidden firing via --fr-reg-upper-theta, --fr-reg-upper-strength, and the matching lower pair:
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.
Feedforward weights are sampled fan-in-normalised, either half-normal (Dale’s law) or normal (signed):
with optional sparsity : a fraction of entries are zeroed and the survivors rescaled by , so the expected synaptic input per post-neuron is preserved.
When Dale’s law is on, the feedforward matrices are clamped to 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.