Auryn simulator

Simulator for spiking neural networks with synaptic plasticity

User Tools

Site Tools


tutorials:writing_your_own_plasticity_model

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorials:writing_your_own_plasticity_model [2015/06/19 17:02] – typos zenketutorials:writing_your_own_plasticity_model [2018/02/07 23:11] (current) – Adds final paragraph on unit tests zenke
Line 6: Line 6:
   * Zenke, F., and Gerstner, W. (2014). Limits to high-speed simulations of spiking neural networks using general-purpose computers. Front Neuroinform 8, 76. [[http://journal.frontiersin.org/Journal/10.3389/fninf.2014.00076/abstract|full text]]   * Zenke, F., and Gerstner, W. (2014). Limits to high-speed simulations of spiking neural networks using general-purpose computers. Front Neuroinform 8, 76. [[http://journal.frontiersin.org/Journal/10.3389/fninf.2014.00076/abstract|full text]]
  
-If you can write down a learning rule as a differential equation involving spike trains, synaptic traces and specific postsynaptic quantities, such as the membrane potential, Auryn will bring everything need to so so intuitively. Here is an example from Gerstner and Kistler (2002):+If you can write down a learning rule as a differential equation involving spike trains, synaptic traces and specific postsynaptic quantities, such as the membrane potential, Auryn will bring everything you need to implement this learning rule intuitively. Here is an example from Gerstner and Kistler (2002):
  
 {{ :tutorials:stdpgeneral.png |}} {{ :tutorials:stdpgeneral.png |}}
  
-In Auryn you can implement this type of learning rule very intuitively if the ''u'' can be written as a function of synaptic traces and postsynaptic quantities (for many standard cases the ''u'' are synaptic traces themselves). To that end, Auryn has native support for such pre- or postsynaptic traces. +In Auryn you can implement this type of learning rule if the ''u'' can be written as a function of synaptic traces and postsynaptic quantities (for many standard cases the ''u'' are synaptic traces themselves). To that end, Auryn has native support for such pre- or postsynaptic traces.  For historical reasons, I typically use the letter ''z'' for synaptic traces, which is what you will find below.
  
  
Line 17: Line 17:
 ===== Understanding plasticity in Auryn ===== ===== Understanding plasticity in Auryn =====
  
-Before writing your own code, it might be a good idea to have a look at some existing codeA good example to start off with is the event-based implementation for [[examples:sim_background|Triplet STDP]].  +In most cases you will want to use Auryn to implement your own synapse modelThe easiest to do this is by understanding and modifying an existing model. Most of the plasticity models in Auryn are implemented in source files which contain the acronym STDP, e.g. [[manual:STDPConnection]], [[manual:STDPwdConnection]], [[manual:SymmetricSTDPConnection]] or "Triplet" in the case of [[manual:TripletConnection]] which is used in this [[examples:sim_background|example]]. You will find them in the ''src/auryn'' directoryor take a look at the [[http://fzenke.net/auryn/doxygen/current/annotated.html|class index]]. All the classes implement [[manual:SparseConnection]] objects with plasticity on top. They already implement all the purely virtual functions of the base class [[manual:Connection]] and specific functions to implement plasticity. In the following, I will explain how to understand these classes and how to easily modify them according to your needs. In particular, I will cover how to define your own synaptic traces and how to use them to define weight updates. Finally, I will illustrate how synaptic weights can be integrate in a time continuous manner, for instance to implement a weight decay, homeostatic scaling or other continuous processes.
- +
-In most cases it will be best to writing your own synapse modelby modifying an existing model (like [[examples:sim_background|Triplet STDP]]) which already implements all the purely virtual functions of the base class [[manual:Connection]]. In the following, I will explain how to do this. In particular, I will cover how to define your own synaptic traces and how to use them to define weight updates. Finally, I will illustrate how synaptic weights can be integrate in a time continuous manner, for instance to implement a weight decay, homeostatic scaling or other continuous processes.+
  
  
Line 39: Line 37:
 The macros ''DEFAULT_TRACE_MODEL'' and ''PRE_TRACE_MODEL'' are defined in ''auryn_definitions.h'' to facilitate the change of the underlying integration scheme. Per default they refer to [[manual:EulerTrace]], because Euler integration is most efficient in most situations (see [[http://journal.frontiersin.org/article/10.3389/fninf.2014.00076/abstract|Zenke and Gerstner (2014)]] for details). The macros ''DEFAULT_TRACE_MODEL'' and ''PRE_TRACE_MODEL'' are defined in ''auryn_definitions.h'' to facilitate the change of the underlying integration scheme. Per default they refer to [[manual:EulerTrace]], because Euler integration is most efficient in most situations (see [[http://journal.frontiersin.org/article/10.3389/fninf.2014.00076/abstract|Zenke and Gerstner (2014)]] for details).
  
-This declaration should be matched in the ''.cpp'' file by+This declaration should be matched in the ''.cpp'' file. Typically there should be an init function in your plastic connection class in which you can write:
 <code c++> <code c++>
 /* Initialization of presynaptic traces */ /* Initialization of presynaptic traces */
Line 49: Line 47:
 tr_post_hom = dst->get_post_trace(tau_hom); tr_post_hom = dst->get_post_trace(tau_hom);
 </code> </code>
-which initializes the traces using their respective timeconstants tau_* and registers them to either the presynaptic (''src'') or the postsynaptic (''dst'') [[manual:NeuronGroup]] respectively. By doing so, the traces will be automatically incremented by one upon each spike of the corresponding [[manual:NeuronGroup]]. Moreover, if multiple [[manual:Connection]] objects were to define a trace with the same timeconstant for the same [[manual:NeuronGroup]], Auryn knows about this and internally only computes the trace once. +which initializes the traces using their respective time constants tau_* and registers them to either the presynaptic (''src'') or the postsynaptic (''dst'') [[manual:NeuronGroup]] respectively. By doing so, the traces will be automatically evolved (decay over time) and incremented by one upon each spike of the corresponding [[manual:NeuronGroup]]. Moreover, if multiple [[manual:Connection]] objects were to define a trace with the same time constant for the same [[manual:NeuronGroup]], Auryn 'knowsabout this and internally only computes the trace once to speed up computation. The current value of a trace can then be accessed in the code via ''tr_post->get(NeuronID id)'' which we will use in the next section to compute weight updates.
- +
-The current value of a trace can then be accessed in the code via ''tr_post->get(NeuronID id)'' which we will use in the next section to compute weight updates.+
 ==== Weight updates at spiking events (propagate) ==== ==== Weight updates at spiking events (propagate) ====
  
Line 115: Line 111:
     return dw;     return dw;
 </code> </code>
-The code describes how each weight should be updated upon a presynaptic spike (hence the suffix). Since spikes in Auryn are labelled with the [[global id]] of a neuron within a [[manual:SpikingGroup]], we need to translate this to the local ID, i.e. the index of the neuron on that rank. This is done by the function [[manual:global2rank]]. Next, we compute the weight update. In the minimal triplet STDP model by Pfister and Gerstner (2006) which is implemented in [[manual:TripletConnection]] a presynaptic spike causes LTD. The amount of LTD is proportional to one of the postsynaptic traces ''tr_post'' Moreover, the amount of LTD is homeostatically modulated by a factor derived from a slower trace (''get_hom(translated_spike)''). Finally hom_fudge is a factor which contains the learning rate and a normalization constant for the value returned by ''get_hom''. By precomputing this product, we save a few multiplications each time this function is called.+The code describes how each weight should be updated upon a presynaptic spike (hence the suffix). Since spikes in Auryn are labeled with the [[global id]] of a neuron within a [[manual:SpikingGroup]], we need to translate this to the local ID, i.e. the index of the neuron on that rank. This is done by the function [[manual:global2rank]]. Next, we compute the weight update. In the minimal triplet STDP model by Pfister and Gerstner (2006) which is implemented in [[manual:TripletConnection]] a presynaptic spike causes LTD. The amount of LTD is proportional to one of the postsynaptic traces ''tr_post'' Moreover, the amount of LTD is homeostatically modulated by a factor derived from a slower trace (''get_hom(translated_spike)''). Finally hom_fudge is a factor which contains the learning rate and a normalization constant for the value returned by ''get_hom''. By precomputing this product, we save a few multiplications each time this function is called.
 The complete weight update ''dw'' is ultimately returned to our ''propagate_forward()'' function where it is added to the weight value.  The complete weight update ''dw'' is ultimately returned to our ''propagate_forward()'' function where it is added to the weight value. 
  
-This describes the plastic update upon presynaptic spikes, but what happens upon a postsynaptic spike? +One thing you should pay close attention to, because it could might easily introduce errors: **You need to translate postsynaptic IDs (as shown above)**, but not the presynaptic ones! Copies of presynaptic trances are available on each rank, whereas postsynaptic traces are distributed with the neuronal dynamics. This only makes a difference when you are running simulations in parallel with MPI, but then it's an important difference. 
 + 
 +We now covered weight updates triggered by presynaptic spikes, but what happens upon a postsynaptic spike?  
  
 === Backward spike propagation === === Backward spike propagation ===
Line 172: Line 171:
 ===== Changing the plasticity model ===== ===== Changing the plasticity model =====
  
-Suppose, you would like to change the plasticity model, all you need to do is to copy TripletConnection.h and TripletConnection.cpp to YourNameConnection.h and .cpp and then the ''dw_pre'' and ''dw_post'' would be the first places start to change things. Of course you can declare new parameters in the header of the Connection object and either directly set them from the main program, hard code them m( or access them via a bunch of getters or setters. Moreover, in many cases it might be important for you to redefine ''dw_pre'' and ''dw_post'' altogether. For instance if you would like to implement a weight dependence, you will need to include our above ''*weight'' variable into the parameter list of these functions. Similarly, you might want to access the postsynaptic membrane voltage through ''dst->get_mem(NeuronID id)'' and pass it to you ''dw'' functions ... I will let you experiment and hope that I will have the time to include some more examples here soon.+Most of the plasticity models in Auryn follow the design principles introduced above (e.g. http://fzenke.net/auryn/doxygen/current/classauryn_1_1STDPConnection.html). Suppose, you would like to change the plasticity model, all you need to do is to copy TripletConnection.h and TripletConnection.cpp to YourNameConnection.h and .cpp and then the ''dw_pre'' and ''dw_post'' would be the first places start to change things. Of course you can declare new parameters in the header of the Connection object and either directly set them from the main program, hard code them m( or access them via a bunch of getters or setters. Moreover, in many cases it might be important for you to redefine ''dw_pre'' and ''dw_post'' altogether. For instance if you would like to implement a weight dependence, you will need to include our above ''*weight'' variable into the parameter list of these functions. Similarly, you might want to access the postsynaptic membrane voltage through ''dst->get_mem(NeuronID id)'' and pass it to you ''dw'' functions ... I will let you experiment and hope that I will have the time to include some more examples here soon. 
  
-One thing you should pay attention to, because it could might easily introduce errors: You need to translate postsynaptic IDs (as shown above), but not the presynaptic ones! Copies of presynaptic trances are integrated on each rank; that sounds inefficient, but turned out to be the most efficient way to do it in most cases. 
  
 ==== Weight updates in continuous time (evolve) ==== ==== Weight updates in continuous time (evolve) ====
Line 202: Line 201:
 where you will have to define and initialize the variable time_scaling somewhere in your connection object. It will give you the new step width in multiples of Auryn's time step dt. where you will have to define and initialize the variable time_scaling somewhere in your connection object. It will give you the new step width in multiples of Auryn's time step dt.
  
 +This should give you the necessary tools to start out with writing your own plasticity models. As with all code, write simple unit tests to ensure you are simulating what you want to simulate. Happy coding. 
tutorials/writing_your_own_plasticity_model.1434733359.txt.gz · Last modified: 2015/06/19 17:02 by zenke