Open ChiaraBartolozzi opened 6 years ago
Parallel Fibres (PF that are the output axons of Granular Cells) project to Purkinje Cells
The weight of the connection between PF and PC is plastic and depends on the erro signal carried by the Climbing Fibres (CF)
This is capered by the fact that the learning is triggered by the CF spike, then we need to look back in time the spikes produced by the PF input and use the kernel exp(dt/tau)sin(dt/tau)^20
In SpiNNaker, we need to trigger the learning at occurrence of PF spike (then we know which PF fibre to change).
So we need to store the PF and CF spikes
Every time that a CF spike happens, you should calculate the LTD amount corresponding to ALL the previous PF spikes, and not only the last one. There are two ways to implement this:
By storing a buffer with all the PF spikes (or at least the most recent ones) and query the LUT for the f(t)=exp(-t/tau)sin(t/tau)^20 for every combination PF spike/CF spike
store a "learning trace" for each PF synapse. In this second case, every PF spike will add f(0) to its learning trace, and every CF spike will update the learning trace by calculating f(t+delta_t) from f(t), and it will apply the corresponding LTD to each PF synapse. We implement this second method as it tends to be more efficient in terms of simulation time and memory consumption. Indeed, f(t) is conveniently chose to allow this kind of f(t+delta_t) update. But you probably don't need to implement this way in SpiNNaker.
The development of the f(t+delta_t) function is not so straightforward and requires some mathematical development. exp(t+delta_t)=exp(t)exp(delta_t)=f(t)exp(delta_t), that is easy. But sin(t+delta_t)^20 development bases on sin properties, concretely, sin(t+delta_t) = sin(t)cos(delta_t) + cos(t)sin(deltat). The exponential part (20) is then calculated based on the terms extracted in the STDPSinConnection::terms table, and the TermPointer variable points to the terms line corresponding to the 20 exponent. state_vars stores the state variables required for the development and update of the exp(t) and sin(t)^20. I am unsure if we still have the development of the sin(t)^20 part, but if you need it, let me know and we can try to reproduce it.
there are two ways for implementing the STDP kernel: a)The buffer-like version; the PF activity is stored in a buffer (obviously since we have a limited storage capacity the buffer length is finite. .A common choice with regards the “eligibly trace” for the buffer length is something about four times the sensorimotor pathway delay (100 ms,). All those PF spikes stored in this buffer are then convolved with the kernel(stored in the LUT (f(t)=exp(-t/tau)sin(t/tau)^20) for every combination of PF spike-CF spike. This version is not developed in NEST but it is in EDLUT. It is easier to understand and implement but it has a major drawback, the memory consumption. If memory resources are not limited, it is then the option to be picked. (please note that tau in f(t)=exp(-t/tau)sin(t/tau)^20 stands for a corrective factor needed to normalise the kernel height , it has nothing to do with the sensorimotor pathway delay)
b) the second option is to implement a mathematical development. Due to the trigonometric properties inherent to sine and cosine functions, we can store in only one variable the “elegibly trace” of our PFs with no temporal or storage restrictions: Please check the development of the sin^2 function
Note that we could just accumulate the past activity in a bunch of variables type FLOAT
This is a simplified development for f(x) =sin(t)^2, this must be expanded to sin(t)^20 in a similar manner which it seems to be way more tedious. If you finally decide to go for this second option, my recommendation is to take the mechanism already developed in NEST as it is.
Oliver and I decided to implement the STDP rule by storing the PF spike train in the SDRAM. It seems more feasible than storing the LUTs (1024x1024 + 1024x256) in the local memory.
For each PF spike, we retrieve the CF spike times (that happened before the current and previous PF spikes)
For each CF spike in the list, then, we take the PF spikes that happened before the CF spike, in a window of 255ms
We retrieve the dt between CF and previous PF spikes and apply the synaptic kernel from the LUT. The weight of the synapse is then depressed multiplicatively for each spike.
For each PF we also potentiate the synapse (additive)
When we have the CF spike and the list of previous PF spikes we need to sum the effects of each "depression": sum for i=1:n of dwi*w(i-1)
Which is the correct order? shall we start from the most recent PF event or from the farthest in time?
We decided to start from the oldest one (as the formula is an integral over time from -inf to +inf)
TODO:
@oliverrhodes, @ChiaraBartolozzi
@oliverrhodes
shall we create one issue for each todo item?
will it work with pre-driven plasticity, can we handle delays correctly, will it be efficient?