Skip to content
GitHub

Plasticity

Learn

Plasticity

Synapses that change with the timing of the spikes on either side of them, with no loss and no gradient. One neuron can find a pattern hidden in noise this way.

Spike-timing-dependent plasticity

When an input spikes shortly before its neuron does, it probably helped, and STDP strengthens the synapse. When it spikes shortly after, it didn't, and STDP weakens it. The closer the two spikes, the larger the change. sparx.dynamics.PairSTDP is NEST's stdp_synapse: each neuron keeps a trace of its recent spikes, and each spike reads the other side's trace.

PairSTDP

Each point runs the rule on one pair of spikes at that lag, starting from half the maximum weight. With additive bounds every change has the same size wherever the weight is, which pushes weights toward 0 or the maximum. With multiplicative bounds a strong synapse grows less and a weak one shrinks less, which keeps them in between.

Finding a pattern in noise

Masquelier, Guyonneau and Thorpe (2008) showed what this rule can do on its own. 400 afferents fire at random, at 30 Hz plus 10 Hz of noise. Now and then, for 50 ms, the first 200 replay the same frozen spike pattern, shaded below, at the same rate, so no afferent's rate gives it away. One neuron listens to all of them through STDP, with nothing telling it the pattern exists.

LeakyIntegrateAndFire + PairSTDP

At first the neuron fires anywhere. Inputs that happened to come before its spikes grow, and since the pattern always repeats the same spikes in the same order, its afferents grow more often than the others. Within about a minute of simulated time the neuron stops firing outside the pattern and fires inside most of them. The weights of the pattern's afferents (violet) split into strong and weak, and the others fall.

This is a smaller version of their experiment, with sparx's LeakyIntegrateAndFire and PairSTDP in place of their neuron and rule. At their depression of alpha = 0.85 it fell silent before finding the pattern, so this one uses 0.55.

The code
import jax
from sparx.dynamics import Delta, LeakyIntegrateAndFire, PairSTDP, Receptor
from sparx.graph import FixedProbability, Network, PoissonInput, Population, Projection, simulate
stdp = PairSTDP(tau_plus=16.8, tau_minus=33.7, lambda_=0.005, alpha=0.55, mu_plus=0.0, mu_minus=0.0,
w_max=1.0) # NEST's stdp_synapse, additive
neuron = LeakyIntegrateAndFire(tau_m=10.0, e_l=0.0, v_th=20.0, v_reset=0.0, t_ref=1.0)
network = Network(
populations=(Population("in", 400, LeakyIntegrateAndFire(), {"ampa": Receptor(Delta())}),
Population("out", 1, neuron, {"ampa": Receptor(Delta())})),
projections=(Projection("in", "out", FixedProbability(1.0), weight=0.475, delay=1.0,
receptor="ampa", plasticity=stdp),),
inputs=(PoissonInput("in", rate=40.0, weight=30.0, receptor="ampa"),),
dt=1.0,
)
result = simulate(network, network.init(jax.random.key(0)), duration=2000.0, key=jax.random.key(1))
pre, post, weight, delay = network.connections(result.variables)["in->out:ampa"]
print(weight.min(), weight.max()) # the weights as STDP left them

Checked

The browser's PairSTDP runs on recorded spikes beside sparx's in site/test/learning.test.ts: twelve synapses for 3,000 steps, additive and multiplicative, agree to 2e-15. Its neuron is the LeakyIntegrateAndFire that Neurons checks.

sparx's pair, triplet and dopamine-modulated STDP match NEST's synapses: every transmitted weight within 1e-10 relative (fidelity ledger).

All of Learn