Currently 4 predefined weight update models are available:
In Python, these models can be selected by their unqualified name e.g. "StaticPulse".
Defining a new weight update model
Like the neuron models discussed in Defining your own neuron type, new weight update models are created by defining a class derived from WeightUpdateModels::Base. For convenience, the methods a new weight update model should implement can be implemented using the following basic macrosnew weight update models should be implemented using the pygenn.genn_model.create_custom_weight_update_class
function with the following basic keyword arguments:
- SET_DERIVED_PARAMS(), SET_PARAM_NAMES(), SET_VARS() and SET_EXTRA_GLOBAL_PARAMS()
derived_params
, param_names
, var_name_types
and extra_global_params
perform the same roles as they do in the neuron models discussed in Defining your own neuron type.
- DECLARE_WEIGHT_UPDATE_MODEL(TYPE, NUM_PARAMS, NUM_VARS, NUM_PRE_VARS, NUM_POST_VARS) is an extended version of
DECLARE_MODEL()
which declares the boilerplate code required for a weight update model with pre and postsynaptic as well as per-synapse state variablesclass_name
: the name of the new model .
- SET_SIM_CODE(SIM_CODE)
sim_code=SIM_CODE
: defines the simulation code that is used when a true spike is detected. The update is performed only in timesteps after a neuron in the presynaptic population has fulfilled its threshold detection condition. Typically, spikes lead to update of synaptic variables that then lead to the activation of input into the post-synaptic neuron. Most of the time these inputs add linearly at the post-synaptic neuron. This is assumed in GeNN and the term to be added to the activation of the post-synaptic neuron should be applied using the the $(addToInSyn, weight) function. For example sim_code="$(addToInSyn, $(inc));"
where "inc" is the increment of the synaptic input to a post-synaptic neuron for each pre-synaptic spike. The simulation code also typically contains updates to the internal synapse variables that may have contributed to $(inc). For an example, see WeightUpdateModels::StaticPulse for a simple synapse update model and WeightUpdateModels::PiecewiseSTDP for a more complicated model that uses STDP. To apply input to the post-synaptic neuron with a dendritic (i.e. between the synapse and the postsynaptic neuron) delay you can instead use the $(addToInSynDelay, weight, delay) function. For example "$(addToInSynDelay, $(inc), $(delay));");
sim_code="$(addToInSynDelay, $(inc), $(delay));"
where, once again, $(inc) is the magnitude of the input step to apply and delay
is the length of the dendritic delay in timesteps. By implementing delay
as a weight update model variable, heterogeneous synaptic delays can be implemented. For an example, see WeightUpdateModels::StaticPulseDendriticDelay for a simple synapse update model with heterogeneous dendritic delays. - Note
- When using dendritic delays, the maximum dendritic delay for a synapse populations must be specified using the
SynapseGroup::setMaxDendriticDelayTimesteps()
function.
One can also define synaptic effects that occur in the reverse direction, i.e. terms that are added to a target variable in the persynaptic neuron using the $(addToPre, expression) function. For instance, "$(addToPre, $(inc)*$(V_post));");
sim_code="$(addToPre, $(inc)*$(V_post));"
would add terms $(inc)*$(V_post) to the predefined presynaptic variable $(Isyn) for each outgoing synapse of a presynaptic neuron. One can also set alternative input variables in the presynaptic neuron as the target variable of this reverse input using SynapseGroup::setPreTargetVar()pygenn.SynapseGroup.pre_target_var
, see section Additional input variables on how to define additional input variables for a neuron population. - Note
- $(addToPre, expression) can be used in SET_SIM_CODE()
sim_code
, SET_EVENT_CODE()event_code
, SET_SYNAPSE_DYNAMICS_CODE()synapse_dynamics_code
, SET_LEARN_POST_CODE()learn_post_code
.
-
Unlike for normal forward synaptic actions, reverse synaptic actions with $(addToPre,$(inc)) are not modulated through a post-synaptic model but added directly into the indicated presynaptic target input variable, such as $(Isyn).
- SET_LEARN_POST_CODE(LEARN_POST_CODE)
learn_post_code=LEARN_POST_CODE
defines the code which is used in the learnSynapsesPost kernel/function, which performs updates to synapses that are triggered by post-synaptic spikes. This is typically used in STDP-like models e.g. WeightUpdateModels::PiecewiseSTDP.
- SET_NEEDS_PRE_SPIKE_TIME(PRE_SPIKE_TIME_REQUIRED) and SET_NEEDS_POST_SPIKE_TIME(POST_SPIKE_TIME_REQUIRED)
is_pre_spike_time_required=PRE_SPIKE_TIME_REQUIRED
and is_post_spike_time_required=POST_SPIKE_TIME_REQUIRED
define whether the weight update needs to know the times of the spikes emitted from the pre and postsynaptic populations. These can then be accessed through $(sT_pre) and $(sT_post). For example an STDP rule would be likely to require: is_pre_spike_time_required=True, is_post_spike_time_required=True
- SET_NEEDS_PREV_PRE_SPIKE_TIME(PREV_PRE_SPIKE_TIME_REQUIRED) and SET_NEEDS_PREV_POST_SPIKE_TIME(PREV_POST_SPIKE_TIME_REQUIRED)
is_prev_pre_spike_time_required=PREV_PRE_SPIKE_TIME_REQUIRED
and is_prev_post_spike_time_required=PREV_POST_SPIKE_TIME_REQUIRED
define whether the weight update needs to know the times of the previous (i.e. not the ones being processed this timeste) spikes emitted from the pre and postsynaptic populations. These can then be accessed through $(prev_sT_pre) and $(prev_sT_post).
For example, we can define a simple additive STDP rule with nearest-neighbour spike pairing and the following time-dependence:
in a fully event-driven manner as follows:
{
public:
"Wmin", "Wmax"});
"$(addToInSyn, $(g));\n"
"const scalar dt = $(t) - $(sT_post); \n"
"if (dt > 0) {\n"
" const scalar timing = exp(-dt / $(tauMinus));\n"
" const scalar newWeight = $(g) - ($(Aminus) * timing);\n"
" $(g) = fmax($(Wmin), fmin($(Wmax), newWeight));\n"
"}\n");
"const scalar dt = $(t) - $(sT_pre);\n"
"if (dt > 0) {\n"
" const scalar timing = exp(-dt / $(tauPlus));\n"
" const scalar newWeight = $(g) + ($(Aplus) * timing);\n"
" $(g) = fmax($(Wmin), fmin($(Wmax), newWeight));\n"
"}\n");
};
stdp_additive_model = genn_model.create_custom_weight_update_class(
"stdp_additive",
param_names=["tauPlus", "tauMinus", "aPlus", "aMinus", "wMin", "wMax"],
var_name_types=[("g", "scalar")],
sim_code="""
$(addToInSyn, $(g));
const scalar dt = $(t) - $(sT_post);
if (dt > 0) {
const scalar timing = exp(-dt / $(tauMinus));
const scalar newWeight = $(g) - ($(Aminus) * timing);
$(g) = fmax($(Wmin), fmin($(Wmax), newWeight));
}
""",
learn_post_code="""
const scalar dt = $(t) - $(sT_pre);
if (dt > 0) {
const scalar timing = exp(-dt / $(tauPlus));
const scalar newWeight = $(g) + ($(Aplus) * timing);
$(g) = fmax($(Wmin), fmin($(Wmax), newWeight));
}
""",
is_pre_spike_time_required=True,
is_post_spike_time_required=True)
Pre and postsynaptic dynamics
The memory required for synapse variables and the computational cost of updating them tends to grow with with the number of neurons. Therefore, if it is possible, implementing synapse variables on a per-neuron rather than per-synapse basis is a good idea. The SET_PRE_VARS() and SET_POST_VARS() macros pre_var_name_types
and post_var_name_types
keyword arguments are used to define any pre or postsynaptic state variables. Then the SET_PRE_DYNAMICS_CODE() and SET_POST_DYNAMICS_CODE() macros pre_dynamics_code
and post_dynamics_code
keyword arguments can be used to define any time-driven updates to these variables and the SET_PRE_SPIKE_CODE() and SET_POST_SPIKE_CODE() macros pre_spike_code
and post_spike_code
keyword arguments any spike-driven updates. For example, using pre and postsynaptic variables, our event-driven STDP rule can be extended to use all-to-all spike pairing using pre and postsynaptic trace variables [4] :
- Note
- These pre and postsynaptic code snippets can only access the corresponding pre and postsynaptic variables as well as those associated with the pre or postsynaptic neuron population. Like other state variables, variables defined here as
NAME
can be accessed in weight update model code strings using the $(NAME) syntax.
{
public:
"Wmin", "Wmax"});
{"tauPlusDecay", [](const std::vector<double> &pars, double dt){ return std::exp(-dt / pars[0]); }},
{"tauMinusDecay", [](const std::vector<double> &pars, double dt){ return std::exp(-dt / pars[1]); }}});
"$(addToInSyn, $(g));\n"
"const scalar dt = $(t) - $(sT_post); \n"
"if (dt > 0) {\n"
" const scalar newWeight = $(g) - ($(Aminus) * $(postTrace));\n"
" $(g) = fmax($(Wmin), fmin($(Wmax), newWeight));\n"
"}\n");
"const scalar dt = $(t) - $(sT_pre);\n"
"if (dt > 0) {\n"
" const scalar newWeight = $(g) + ($(Aplus) * $(preTrace));\n"
" $(g) = fmax($(Wmin), fmin($(Wmax), newWeight));\n"
"}\n");
};
stdp_additive_2_model = genn_model.create_custom_weight_update_class(
"stdp_additive_2",
param_names=["tauPlus", "tauMinus", "aPlus", "aMinus", "wMin", "wMax"],
var_name_types=[("g", "scalar")],
pre_var_name_types=[("preTrace", "scalar")],
post_var_name_types=[("postTrace", "scalar")],
sim_code="""
$(addToInSyn, $(g));
const scalar dt = $(t) - $(sT_post);
if(dt > 0) {
const scalar newWeight = $(g) - ($(aMinus) * $(postTrace));
$(g) = fmin($(wMax), fmax($(wMin), newWeight));
}
""",
learn_post_code="""
const scalar dt = $(t) - $(sT_pre);
if(dt > 0) {
const scalar newWeight = $(g) + ($(aPlus) * $(preTrace));
$(g) = fmin($(wMax), fmax($(wMin), newWeight));
}
""",
pre_spike_code="""
$(preTrace) += 1.0;
""",
pre_dynamics_code="""
$(preTrace) *= $(tauPlusDecay);
""",
post_spike_code="""
$(postTrace) += 1.0;
""",
post_dynamics_code="""
$(postTrace) *= $(tauMinusDecay);
""",
is_pre_spike_time_required=True,
is_post_spike_time_required=True)
Synapse dynamics
Unlike the event-driven updates previously described, synapse dynamics code is run for each synapse, each timestep i.e. unlike the others it is time-driven. This can be used where synapses have internal variables and dynamics that are described in continuous time, e.g. by ODEs. However using this mechanism is typically computationally very costly because of the large number of synapses in a typical network. By using the $(addToInSyn) and $(addToInSynDelay) functions discussed in the context of SET_SIM_CODE(), the synapse dynamics can also be used to implement continuous synapses for rate-based models. Synapse dynamics code is added to a model using the SET_SYNAPSE_DYNAMICS_CODE()
macro synapse_dynamics_code
keyword argument , for example a continous synapse could be implemented as follows:
synapse_dynamics_code="$(addToInSyn, $(g) * $(V_pre));",
Spike-like events
As well as time-driven synapse dynamics and spike event-driven updates, GeNN weight update models also support "spike-like events". These are triggerd by a threshold condition – implemented with the code string specified using the SET_EVENT_THRESHOLD_CONDITION_CODE() macroevent_threshold_condition_code
keyword argument . This typically involves the pre-synaptic variables, e.g. the membrane potential:
event_threshold_condition_code="$(V_pre) > -0.02"
Whenever this expression evaluates to true, the event code set using the
SET_EVENT_CODE() macroevent_code
keyword argument is executed. For an example, see
WeightUpdateModels::StaticGraded. Weight update models can indicate whether they require the times of these spike-like-events using the
SET_NEEDS_PRE_SPIKE_EVENT_TIME() and SET_NEEDS_PREV_PRE_SPIKE_EVENT_TIME() macros is_pre_spike_event_time_required
and is_prev_pre_spike_event_time_required
keyword arguments . These times can then be accessed through the $(seT_pre) and $(prev_seT_pre) variables.
Previous | Top | Next