EProp#
- class braintrace.EProp(model, feedback='symmetric', kappa_filter_decay=0.0, random_feedback_key=None, name=None, vjp_method='single-step', fast_solve=True, **kwargs)#
Eligibility Propagation (e-prop) for recurrent spiking networks.
E-prop approximates the gradient of a loss \(\mathcal{L}\) with respect to a recurrent weight \(W_{ji}\) by the product of a local eligibility trace and a global learning signal, dropping the temporally non-local terms of BPTT:
\[\frac{d\mathcal{L}}{dW_{ji}} = \sum_t L_j^t \, \bar{e}_{ji}^t ,\]where
\[e_{ji}^t = \frac{\partial h_j^t}{\partial W_{ji}} \approx D_j^t \, e_{ji}^{t-1} + \big[\operatorname{diag}(D_{f,j}^t)\big]\, x_i^t , \qquad \bar{e}_{ji}^t = \kappa\,\bar{e}_{ji}^{t-1} + e_{ji}^t .\]Here \(h_j^t\) is the hidden state of neuron \(j\) at time \(t\), \(x_i^t\) the presynaptic input, \(D_j^t\) the hidden-to-hidden (recurrent) Jacobian diagonal, \(D_{f,j}^t\) the state-to-output Jacobian, and \(\kappa \in [0, 1)\) the eligibility-trace low-pass factor. The learning signal is
\[\begin{split}L_j^t = \begin{cases} \ell_j^t = \dfrac{\partial \mathcal{L}}{\partial h_j^t} & \text{(symmetric feedback, standard backprop through readout)} \\[2ex] \big(B\,\hat\ell^t\big)_j, \quad \hat\ell^t = \dfrac{\ell^t}{\lVert \ell^t \rVert + \varepsilon} & \text{(random feedback: a fixed random projection } B\text{)} . \end{cases}\end{split}\]How it works. The eligibility trace \(e_{ji}^t\) is exactly the per-parameter trace maintained by
D_RTRL; it depends only on quantities local to the synapse and is updated forward in time. The learning signal \(L_j^t\) is broadcast from the readout. E-prop is therefore online (no backward pass through time) and uses memory linear in the number of parameters. Withkappa_filter_decay > 0each weight’s eligibility trace is additionally low-pass filtered (elementwise over the trailing per-hidden-state axis, so multi-state HiddenGroups are filtered per state with no cross-state mixing); withfeedback='random'the symmetric readout gradient \(\ell^t\) is L2-normalized (removing its dependence on the magnitude of the real readout weights, since reverse-AD only ever exposes \(\ell^t = W_\mathrm{out}^\top \delta^t\), never the pre-readout error \(\delta^t\) itself) and then projected through a frozen random matrix \(B\), removing the biologically implausible weight-transport requirement. Normalization removes the scale dependence on \(W_\mathrm{out}\); a residual directional dependence on the readout weights remains, since full direction- independence would require the pre-readout error \(\delta^t\) explicitly, which the current hook contract does not provide.- Parameters:
model (
Module) – The recurrent SNN whose weights are trained online.feedback (
str) –'symmetric'uses reverse-AD’s \(\partial \mathcal{L}/\partial h\) (standard backprop through the readout).'random'replaces the readout gradient with a frozen random projection of its L2-normalized direction (requiresrandom_feedback_key). The projection matrix is square (hidden-dim × hidden-dim): E-prop’s hooks only see \(\partial\mathcal L/\partial h\), which has no visibility into a separate readout layer’s width, sofeedback='random'assumes a single, direct readout whose output dimensionality equals the HiddenGroup’s own width.kappa_filter_decay (
float) – Eligibility-trace low-pass factor \(\kappa\) (see \(\bar{e}_{ji}^t\) above). If> 0, each trainable weight’s raw eligibility trace is filtered every step, per hidden-state channel.0disables filtering (the algorithm then reduces exactly toD_RTRL).random_feedback_key (
Array|None) – Seed for the random-feedback matrices. Required whenfeedback='random'; ignored otherwise.
- Raises:
ValueError – If
feedbackis not one of{'symmetric', 'random'}, or iffeedback='random'is given withoutrandom_feedback_key.
Examples
>>> import brainstate >>> import braintrace >>> >>> class RSNN(brainstate.nn.Module): ... def __init__(self): ... super().__init__() ... self.cell = braintrace.nn.ValinaRNNCell(1, 20, activation='tanh') ... self.out = braintrace.nn.Linear(20, 1) ... def update(self, x): ... return x >> self.cell >> self.out >>> >>> model = RSNN() >>> x0 = brainstate.random.randn(1) >>> # one call: initialise states, build the trace graph, return a learner >>> learner = braintrace.compile(model, braintrace.EProp, x0, kappa_filter_decay=0.9) >>> y = learner(x0) # forward pass + eligibility-trace update
References