Skip to content

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:

DefaultSynapse = Synapse(
    parameters = "w=0.0",
    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.

FatigueSynapse = Synapse(
    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.

Hint

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:

STDP = Synapse(
    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)
    """      
) 
  • 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.
g_target += w

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:

w = clip(w - cApost * exp((t_post - t)/tau_post) , 0.0 , wmax) 

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.

  • 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:
w = clip(w + cApre * exp((t_pre - t)/tau_pre) , 0.0 , wmax)

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.

Warning

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:

STDP_online = Synapse(
    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:

  1. Pre-synaptic events, defined by pre_spike and triggered after each pre-synaptic spike, after a delay of at least dt.
  2. Synaptic variables defined by equations.
  3. 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:

STDP_online = Synapse(
    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:

NMDA = Synapse(
    parameters = """
    tau = 10.0 : projection
    """,
    equations = """
    tau * dx/dt = -x
    tau * dg/dt = -g +  x * (1 -g)
    """, 
    pre_spike = "x += w",
    psp = "g"
)

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.