Spiking synapses
Synapses in spiking networks differ from rate-coded synapses in that they are event-driven, i.e. the most important changes occur whenever a pre- or post-synaptic spike is emitted. For this reason, additional arguments have to be passed to the Synapse
object.
Increase of conductance after a pre-synaptic spike
In the simplest case, a pre-synaptic spike increases a target
conductance value in the post-synaptic neuron. The rule defining how this conductance is modified has to be placed in the pre_spike
argument of a Synapse
object.
The default spiking synapse in ANNarchy is equivalent to:
= ann.Synapse(
DefaultSynapse = "w=0.0",
parameters = "",
equations = """
pre_spike g_target += w
"""
)
The only thing it does is to increase the conductance g_target
of the post-synaptic neuron (for example g_exc
if the target is exc
) every time a pre-synaptic spike arrives at the synapse, proportionally to the synaptic efficiency w
of the synapse. Note that w
is implicitely defined in all synapses, you will never need to define it explicitely.
You can override this default behavior by providing a new Synapse
object when building a Projection
. For example, you may want to implement a "fatigue" mechanism for the synapse, transciently reducing the synaptic efficiency when the pre-synaptic neuron fires too strongly.
One solution would be to decrease a synaptic variable everytime a pre-synaptic spike is received and increase the post-synaptic conductance proportionally to this value. When no spike is received, this trace
variable should slowly return to its maximal value.
= ann.Synapse(
FatigueSynapse = """
parameters tau = 1000 : postsynaptic # Time constant of the trace is 1 second
dec = 0.05 : postsynaptic # Decrement of the trace
""",
= """
equations tau * dtrace/dt + trace = 1.0 : min = 0.0
""",
= """
pre_spike g_target += w * trace
trace -= dec
"""
)
Each time a pre-synaptic spike occurs, the post-synaptic conductance is increased from w*trace
. As the baseline of trace
is 1.0 (as defined in equations
), this means that a "fresh" synapse will use the full synaptic efficiency. However, after each pre-synaptic spike, trace is decreased from dec = 0.05
, meaning that the "real" synaptic efficiency can go down to 0.0 (the minimal value of trace) if the pre-synaptic neuron fires too often.
It is important here to restrict trace
to positive values with the flags min=0.0
, as it could otherwise transform an excitatory synapse into an inhibitory one.
It is obligatory to use the keyword g_target
for the post-synaptic conductance. This value relates to the corresponding value in post-synaptic neuron: The target
will be replaced with the projection’s target (for example exc
or inh
). So if you use this synapse in a projection with target = 'exc', the value of g_exc in post-synaptic neuron will be automatically replaced.
Synaptic plasticity
In spiking networks, there are usually two methods to implement event-driven synaptic plasticity (see the entry on STDP at Scholarpedia):
- by using the difference in spike times between the pre- and post-synaptic neurons;
- by using online implementations.
Using spike-time differences
A Synapse
has access to two specific variables:
t_pre
corresponding to the time of the last pre-synaptic spike in milliseconds.t_post
corresponding to the time of the last post-synaptic spike in milliseconds.
These times are relative to the creation of the network, so they only make sense when compared to each other or to t
.
Spike-timing dependent plasticity can for example be implemented the following way:
= ann.Synapse(
STDP = """
parameters tau_pre = 10.0 : projection
tau_post = 10.0 : projection
cApre = 0.01 : projection
cApost = 0.0105 : projection
wmax = 0.01 : projection
""",
= """
pre_spike g_target += w
w = clip(w - cApost * exp((t_post - t)/tau_post) , 0.0 , wmax)
""",
= """
post_spike w = clip(w + cApre * exp((t_pre - t)/tau_pre) , 0.0 , wmax)
"""
)
pre_spike
Every time a pre-synaptic spike arrives at the synapse (pre_spike
), the post-synaptic conductance is increased from the current value of the synaptic efficiency.
+= w g_target
When a synapse object is defined, this behavior should be explicitely declared.
The value w
is then decreased using a decreasing exponential function of the time elapsed since the last post-synaptic spike:
= clip(w - cApost * exp((t_post - t)/tau_post) , 0.0 , wmax) w
The clip()
global function is there to ensure that w
is bounded between 0.0 and wmax
. As t >= t_post
, the exponential part is smaller than 1.0. The pre_spike
argument therefore ensures that the synapse is depressed is a pre-synaptic spike occurs shortly after a post-synaptic one. "Shortly" is quantified by the time constant tau_post
, usually in the range of 10 ms.
post_spike
Every time a post-synaptic spike is emitted (post_spike
), the value w
is increased proportionally to the time elapsed since the last pre-synaptic spike:
= clip(w + cApre * exp((t_pre - t)/tau_pre) , 0.0 , wmax) w
This term defines the potentiation of a synapse when a pre-synaptic spike is followed immediately by a post-synaptic one: the inferred causality between the two events should be reinforced.
Only the last pre- and post-synaptic spikes are accessible, not the whole history. Only nearest-neighbor spike-interactions are possible using ANNarchy, not temporal all-to-all interactions where the whole spike history is used for learning (see the entry on STDP at Scholarpedia).
Some networks may not work properly when using this simulation mode. For example, whenever the pre-synaptic neurons fires twice in a very short interval and causes a post-synaptic spike, the corresponding weight should be reinforced twice. With the proposed STDP rule, it would be reinforced only once.
It is therefore generally advised to use online versions of STDP.
Online version
The online version of STDP requires two synaptic traces, which are increased whenever a pre- resp. post-synaptic spike is perceived, and decay with their own dynamics in between. Using the same vocabulary as Brian, such an implementation would be:
= ann.Synapse(
STDP_online = """
parameters tau_pre = 10.0 : projection
tau_post = 10.0 : projection
cApre = 0.01 : projection
cApost = 0.0105 : projection
wmax = 0.01 : projection
""",
= """
equations tau_pre * dApre/dt = - Apre : event-driven
tau_post * dApost/dt = - Apost : event-driven
""",
= """
pre_spike g_target += w
Apre += cApre * wmax
w = clip(w - Apost, 0.0 , wmax)
""",
= """
post_spike Apost += cApost * wmax
w = clip(w + Apre, 0.0 , wmax)
"""
)
The variables Apre
and Apost
are exponentially decreasing traces of pre- and post-synaptic spikes, as shown by the leaky integration in equations
. When a pre-synaptic spike is emitted, Apre
is incremented, the conductance level of the post-synaptic neuron g_target
too, and the synaptic efficiency is decreased proportionally to Apost
(this means that if a post-synaptic spike was emitted shortly before, LTD will strongly be applied, while if it was longer ago, no major change will be observed). When a post-synaptic spike is observed, Apost
increases and the synaptic efficiency is increased proportionally to Apre
.
The effect of this online version is globally the same as the spike timing dependent version, except that the history of pre- and post-synaptic spikes is fully contained in the variables Apre
and Apost
.
The event-driven
keyword allows event-driven integration of the variables Apre
and Apost
. This means the equations are not updated at each time step, but only when a pre- or post-synaptic spike occurs at the synapse. This is only possible because the two variables follow linear first-order ODEs. The event-driven integration method allows to spare a lot of computations if the number of spikes is not too high in the network.
Order of evaluation
Three types of updates are potentially executed at every time step:
- Pre-synaptic events, defined by
pre_spike
and triggered after each pre-synaptic spike, after a delay of at leastdt
. - Synaptic variables defined by
equations
. - Post-synaptic events, defined by
post_spike
and triggered after each post-synaptic spike, without delay.
These updates are conducted in that order at each time step. First, all spikes emitted in the previous step (or earlier if there are delays) are propagated to the corresponding synapses and influence variables there (especially conductance increases), then all synaptic variables are updated according to their ODE (after the neurons' equations are updated), then all neurons which have emitted a spike in the current step modify their synapses.
A potential problem arises when a pre-synaptic and a post-synaptic spike are emitted at the same time. STDP-like plasticity rules are usually not defined when the spike time difference is 0, as the two spikes can not be correlated in that case (the pre-spike can not possibly be the cause of the post-spike).
By default, both event-driven updates (pre_spike
leading to LTD, post_spike
leading to LTP) will be conducted when the spikes are emitted at the same time. This can be problematic for some plastic models, for example the simple_stdp
example provided in the source code.
To avoid this problem, the flag unless_post
can be specified in pre_spike
to indicate that the corresponding variable should be updated after each pre-synaptic spike, unless the post-synaptic neuron also fired at the previous time step. Without even-driven integration, the online STDP learning rule would become:
= ann.Synapse(
STDP_online = """
parameters tau_pre = 10.0 : projection
tau_post = 10.0 : projection
cApre = 0.01 : projection
cApost = 0.0105 : projection
wmax = 0.01 : projection
""",
= """
equations tau_pre * dApre/dt = - Apre
tau_post * dApost/dt = - Apost
""",
= """
pre_spike g_target += w
Apre += cApre : unless_post
w = clip(w - Apost, 0.0 , wmax) : unless_post
""",
= """
post_spike Apost += cApost
w = clip(w + Apre, 0.0 , wmax)
"""
)
Continuous synaptic transmission
In some cases, synaptic transmission cannot be described in an event-driven framework. Synapses using the NMDA neurotransmitter are for example often modeled as non-linear synapses. Non-linear synapses can require the post-synaptic conductance to be a sum of synapse-specific variables, as for rate-coded neurons, and not simply incremented when a pre-synaptic spike occurs. NMDA synapses can be represented by two variables x(t) and g(t) following first-order ODEs:
\begin{aligned} \begin{aligned} \tau \cdot \frac{dx(t)}{dt} &= - x(t) \\ \tau \cdot \frac{dg(t)}{dt} &= - g(t) + x(t) \cdot (1 - g(t)) \end{aligned} \end{aligned}
When a pre-synaptic spike occurs, x(t) is incremented by the weight w(t). However, it does not influence directly the post-synaptic neuron, as the output of a synapse is the signal g(t). The post-synaptic conductance is defined at each time t as the sum over all synapses of the same type of their variable g(t):
g_\text{exc}(t) = \sum_{i=1}^{N_\text{exc}} g_i (t)
Such a synapse could be implemented the following way:
= ann.Synapse(
NMDA = """
parameters tau = 10.0 : projection
""",
= """
equations tau * dx/dt = -x
tau * dg/dt = -g + x * (1 -g)
""",
= "x += w",
pre_spike = "g"
psp )
The synapse defines a psp
argument which means that the output of this synapse is non-linear and the post-synaptic conductance should be summed over this value (g
in this case). It is not possible to use the event-driven integration scheme for such non-linear synapses.