Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of double exponential filtering for synaptic traces #3

Closed
yilun-wu opened this issue Oct 12, 2023 · 2 comments · Fixed by #7
Closed

Implementation of double exponential filtering for synaptic traces #3

yilun-wu opened this issue Oct 12, 2023 · 2 comments · Fixed by #7

Comments

@yilun-wu
Copy link

Dear authors,
In the implementation for $\alpha * \big(\epsilon * S_j(t)f^{\prime}U_i(t)\big)$ (eq. 18):

const AurynWeight syn_trace_input = plasticity_sign*psp*sigma_prime;
add_to_syntrace( didx, syn_trace_input );
}
}
partial_delay->advance(); // now 'get' points to the delayed version
pre_psp_delay->advance(); // now 'get' points to the delayed version
// # SECOND compute outer convolution of synaptic traces
const AurynFloat mul_follow = auryn_timestep/tau_el_decay;
el_val_flt->follow(el_val, mul_follow);
const AurynFloat scale_const = std::exp(-auryn_timestep/tau_el_rise);
el_val->scale(scale_const);

It seems the two exponential filterings of $\alpha$ are done differently:

My questions are:

  • Why the two synaptic traces are computed differently? Why not use AurynVectorFloat::follow for both of them?
  • Related to the first question, add_to_syntrace only updates the trace by adding the $\epsilon * S_j(t)f^{\prime}U_i(t)$ before scaling it. This results in the following discrete update I think:
    $\bar{c}[t+1]=\exp(-dt/\tau)(\bar{c}[t]+c[t])\approx (1-dt/\tau)(\bar{c}[t]+c[t])=(1-dt/\tau)\bar{c}[t]+(1-dt/\tau)c[t]$
    According to eq. 21, the discrete update should be:
    $\bar{c}[t+1]=(1-dt/\tau)\bar{c}[t]+(dt/\tau)c[t]$, which is implemented correctly in AurynVectorFloat::follow, which renders the inner and outer update scheme different. I am wondering if I am missing something here.
  • Why is the outer filtering updated first (L313) before the inner filtering (L316)? Is this intentional?
@fzenke
Copy link
Collaborator

fzenke commented Oct 14, 2023

Thanks again for your question. Conventionally, I do the double filtering for synaptic traces differently when the inputs are spikes so that the jump for spike arrival in the first filter is always one regardless of the time step. However, the alpha trace does not receive spikes as inputs, so this is unnecessary here. I can only assume that I did it out of habit, but frankly, I do not remember off the top of my head.
In any case, the only thing it changes is the amplitude of the filter kernel, which can be absorbed in the learning rate. Concerning your second question, why is one filtering done first? This choice was probably unintentional, and the two are approximately the same for small time steps (or long enough time constants). Strictly speaking, all updates should be done simultaneously anyway. However, in Auryn, we deliberately do in-place operations for performance reasons.
I will keep your two tickets open until I have time to look into the code again in more detail. But as I already mentioned, it will take me some time. If we confirm discrepancies between the code and our methods, we will issue an erratum.

Thanks heaps again for your careful reading of the code, and let me know if you have any further questions.

@yilun-wu
Copy link
Author

Understood. Thank you!

This was referenced Jan 15, 2024
@fzenke fzenke closed this as completed in #7 Jan 16, 2024
@fzenke fzenke closed this as completed in 3d3c03a Jan 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants