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

Weights in stdp_triplet_connection cannot be negative - these cannot be used for inhibitory synapses? #240

Closed
sanjayankur31 opened this issue Feb 29, 2016 · 5 comments

Comments

@sanjayankur31
Copy link
Contributor

I may be missing something here, but from the examples that I've come across, if one wants to use synapse models as inhibitory, one just provides a negative weight. Now, in the implementation of the stdp_triplet_connection, the weights are bounded to (0, Wmax) - they cannot be negative. Does this mean they cannot be used for inhibitory synapses at all?

https://github.com/nest/nest-simulator/blob/master/models/stdp_triplet_connection.h#L195

  inline double_t
  depress_( double_t w, double_t kminus, double_t Kplus_triplet_ )
  {
    double_t new_w = w - kminus * ( Aminus_ + Aminus_triplet_ * Kplus_triplet_ );
    return new_w > 0.0 ? new_w : 0.0;
  }

If a synapse is to be inhibitory, the Wmax value will also be negative. So, changing the check to something like this is enough.

  inline double_t
  depress_( double_t w, double_t kminus, double_t Kplus_triplet_ )
  {
    double_t new_w = w - kminus * ( Aminus_ + Aminus_triplet_ * Kplus_triplet_ );
    return (new_w / Wmax_ )> 0.0 ? new_w : 0.0;
  }

This ensures that w and Wmax are of the same sign.

@sanjayankur31
Copy link
Contributor Author

@flinz - I ran into this while working on the symmetric stdp pull request - what do you think?

@flinz
Copy link
Contributor

flinz commented Feb 29, 2016

@sanjayankur31
The triplet learning rule is only intended for positive weights (reflecting the amount of neurotransmitter released). In general, this also seems to be the case for example for https://github.com/nest/nest-simulator/blob/master/models/stdp_connection.h, in which no negative weights are allowed

So far I was of the opinion that negative weights are a workaround in NEST when multiple spike receptors are not being used, however it seems that most models rely indeed in positive and negative weights. So, regardless of a debate whether the STDP models in question are at all viable for models of inhibitory plasticity, it would probably be worth doing some minor changes to enable also negative weights for STDP connections.

There are three options as far as I see it:

  1. Allow negative weights for also the triplet model (amongst others): then all constants for depression and facilitation (A_minus or A_plus) would have to switch sign (since, facilitation on a negative weight should also decrease it). In addition one would have to check to prevent sign switching of weights. I do not favor this method.
  2. Introduce a general flag is_inhibitory for STDP models, that effectively controls the sign only for the outgoing weighted spikes. E.g. multiply by -1 in https://github.com/nest/nest-simulator/blob/master/models/stdp_connection.h#L252 or https://github.com/nest/nest-simulator/blob/master/models/stdp_triplet_connection.h#L267.
  3. Modify the parrot_neuron to have an option to also repeat weighted spikes (changing https://github.com/nest/nest-simulator/blob/master/models/parrot_neuron.cpp#L102) and add a connection_switching_sign synapse, that multiplies the incoming weighted spike by -1. Then inhibitory connections could be routed as ``NEURON_PRE->STDP_SYN->PARROT->SWITCHER->NEURON_POST. Maybe there is an easier way of doing this..

@heplesser or @abigailm might have more insight or opinions..

@heplesser
Copy link
Contributor

@sanjayankur31 The problem you describe relates to a design decision taken very early in the history of NEST, long before we started to introduce plasticity: Weights between neurons with current-based synapses are signed. Thus, they lump together two aspects: the strength of the connection (which is a quite complex effective strength reflecting the total number of synapses between to neurons, the strength of the individual synapses, their placement within the neuron, etc) and the type of the connection (inhibitory/excitatory). The typical way to handle incoming spikes then is to classify them based on their sign, e.g.,

void
iaf_psc_alpha::handle( SpikeEvent& e )
{
  assert( e.get_delay() > 0 );

  const double_t s = e.get_weight() * e.get_multiplicity();

  if ( e.get_weight() > 0.0 )
    B_.ex_spikes_.add_value(
      e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ), s );
  else
    B_.in_spikes_.add_value(
      e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ), s );
}

This allows different time constants for excitatory and inhibitory synapses. Even conductance-based models such as iaf_cond_alpha use the sign to route spikes so they can be handled with different time constants; in this case, the absolute value of the weight is used to determine the amplitude of the conductance change.

The most suitable way to address this issue, I believe, is to split routing (excitatory vs inhibitory "channel") from synapse strength, i.e., to make all weights positive and the use receptor_type to determine whether an input is handled as excitatory or inhibitory. Currently, no model with current-based synapses supports this fully, but iaf_psc_alpha_multisynapse (or ..._exp_...) would be the best starting point. It allows for an arbitrary number of synapses with different time constants. Connections are formed to the different synapses by specifying the receptor_type. Currently, incoming spikes are accumulated with their weights

void
iaf_psc_alpha_multisynapse::handle( SpikeEvent& e )
{
  assert( e.get_delay() > 0 );

  for ( size_t i = 0; i < P_.num_of_receptors_; ++i )
  {
    if ( P_.receptor_types_[ i ] == e.get_rport() )
    {
      B_.spikes_[ i ].add_value(
        e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
        e.get_weight() * e.get_multiplicity() );
    }
  }
}

and then during the update step just added to the derivative of the synaptic current:

    for ( size_t i = 0; i < P_.num_of_receptors_; i++ )
    {
      // alpha shape PSCs
      S_.y2_syn_[ i ] = V_.P21_syn_[ i ] * S_.y1_syn_[ i ] + V_.P22_syn_[ i ] * S_.y2_syn_[ i ];
      S_.y1_syn_[ i ] *= V_.P11_syn_[ i ];

      // collect spikes
      S_.y1_syn_[ i ] += V_.PSCInitialValues_[ i ] * B_.spikes_[ i ].get_value( lag );
    }

The weighting factor V_.PSCInitialValues_[i] is currently just e / tau_syn[i]. All that is missing is a an additional parameter array with scaling factors for the different input channels, so that PSCInitialValues_[i] became scale[i] * e / tau_syn[i]. To create a neuron with an excitatory and inhibitory synapse, one would set scale = [1, -1]. One could in principle also use this to assign different weights to proximal and distal input. All connections would then be made with positive weights, the excitatory once using receptor_type 1 and the inhibitory ones with receptor_type 2.

@heplesser
Copy link
Contributor

@sanjayankur31 I just merged #284. I hope that solves the issue you raised. Would you close it if you are satisfied?

@sanjayankur31
Copy link
Contributor Author

Yes. Thank you! Closing.

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

No branches or pull requests

3 participants