Spiking Neuron Network Simulator  1.0
Simulation and training of spiking neuron networks, primarily theta neurons
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Properties Pages
SpikingNeuron.cs
Go to the documentation of this file.
1 namespace SpikingNeuronNetwork.Lib.NeuronModels
2 {
3  using Interfaces;
4  using System;
5  using System.Collections.Generic;
6  using System.Linq;
7  using System.Text;
8  using Training;
9 
13  public abstract class SpikingNeuron : ISpikingNeuron
14  {
18  public static double Timestep = 0.0001;
19 
23  public static double Epsilon = 1e-10;
24 
28  public const int MaxNumOutputSpikes = 5;
29 
33  protected Dictionary<Synapse, double> _weights;
34 
39 
43  private readonly Random _random = new Random();
44 
48  public int NeuronIndex { get; set; }
49 
53  public SpikingNeuronNetwork NeuronNetwork { get; set; }
54 
58  public bool Verbose { get; set; }
59 
63  public NeuronState State { get; protected set; }
64 
68  public SimulationMethod Method { get; set; }
69 
73  public abstract double PostSpikeState { get; }
74 
78  public abstract double SpikeThreshold { get; }
79 
83  public abstract double InitialState { get; }
84 
88  public abstract double RemainingTimeToSpike { get; }
89 
93  public abstract double MaxNumericalIntegrationIterations { get; }
94 
98  public abstract OperatingRegion OperatingRegion { get; }
99 
103  public double NextSpikeTime
104  {
105  get { return RemainingTimeToSpike + State.Time; }
106  }
107 
111  public bool IsStandAloneNeuron
112  {
113  get { return NeuronNetwork == null; }
114  }
115 
119  public int NumInputs
120  {
121  get
122  {
123  return IsStandAloneNeuron
124  ? Weights.Count(x => Math.Abs(x.Value) > Epsilon)
125  : Weights.Where(x => x.Key.InputNeuronIndex < NeuronNetwork.NumInputs)
126  .Count(x => Math.Abs(x.Value) > Epsilon);
127  }
128  }
129 
133  public Dictionary<Synapse, double> Weights
134  {
135  get { return IsStandAloneNeuron ? _weights : NeuronNetwork.GetNeuronWeights(NeuronIndex); }
136  }
137 
141  protected SpikingNeuron()
142  : this(0)
143  {
144  }
145 
151  protected SpikingNeuron(SpikingNeuronNetwork network, int neuronIndex)
152  {
153  NeuronNetwork = network;
154  NeuronIndex = neuronIndex;
155  ResetState();
156  Verbose = true;
157  Method = SimulationMethod.EventDriven;
158  }
159 
164  protected SpikingNeuron(IReadOnlyList<double> weights)
165  {
166  var numInputs = weights.Count;
167  NeuronIndex = numInputs;
168  _weights = new Dictionary<Synapse, double>(weights.Count);
169  for (var k = 0; k < numInputs; k++)
170  {
171  _weights.Add(new Synapse(k, NeuronIndex), weights[k]);
172  }
173  ResetState();
174  Verbose = true;
175  Method = SimulationMethod.Numerical;
176  }
177 
183  protected SpikingNeuron(int numInputs = 3, double weightIni = 1e6)
184  {
185  _weights = new Dictionary<Synapse, double>(numInputs);
186  NeuronIndex = numInputs;
187  for (var k = 0; k < numInputs; k++)
188  {
189  var newWeight = weightIni > 100 ? 2*_random.NextDouble() - 1 : weightIni;
190  _weights.Add(new Synapse(k, NeuronIndex), newWeight);
191  }
192  ResetState();
193  Verbose = true;
194  Method = SimulationMethod.Numerical;
195  }
196 
201  public abstract ISpikingNeuron Clone();
202 
210  public abstract List<Spike> RunThetaNeuron(List<Spike> inputSpikes, int maxNumOutputSpikes = MaxNumOutputSpikes);
211 
217  public abstract IEnumerable<Spike> UpdateStateVariableOnSpike(double weight);
218 
224  public abstract IEnumerable<Spike> AdvanceNeuronState(double newTime);
225 
232  public abstract List<double> CalculatePostSpikeDerivatives(NeuronFiringHistory neuronFiringHistory);
233 
240  public abstract Dictionary<Synapse, NeuronDerivativeParameters> CalculateOutputSpikeTimeDerivatives(NeuronFiringHistory neuronFiringHistory);
241 
247  public abstract double StateDerivative(double stateVariable);
248 
255  public List<Spike> RunThetaNeuronNumerically(int maxNumOutputSpikes = MaxNumOutputSpikes)
256  {
257  var outputSpikes = new List<Spike>();
258  if (SpikeQueue.Count == 0)
259  {
260  return outputSpikes;
261  }
262  var statePast = State.StateVariable;
263  var allInputSpikeProcessed = false;
264  var currentSpike = SpikeQueue.Dequeue();
265 
266  //The maximum number of numerical integration iterations
267  var intLimit = MaxNumericalIntegrationIterations;
268 
269  var doIntegration = true;
270  while (doIntegration)
271  {
272  // Forward Euler Numeric Integration
273  State.StateVariable = statePast + Timestep * StateDerivative(statePast);
274 
275  if (!allInputSpikeProcessed && (currentSpike.Time >= State.Time) && (currentSpike.Time < (State.Time + Timestep)))
276  {
277  var synapse = new Synapse(currentSpike.NeuronIndex, NeuronIndex);
278  var spikeStats = new SpikeStats();
279  try
280  {
281  spikeStats = new SpikeStats
282  {
283  PreSpikeState = new NeuronState {StateVariable = State.StateVariable, Time = currentSpike.Time},
284  Weight = Weights[synapse],
285  Synapse = synapse
286  };
287  }
288  catch (KeyNotFoundException)
289  {
290  Console.WriteLine("Synapse: " + synapse);
291  Console.WriteLine("Weights: " + GetWeightsString(Weights));
292  throw;
293  }
294  UpdateStateVariableOnSpike(spikeStats.Weight);
295  spikeStats.PostSpikeState = new NeuronState { StateVariable = State.StateVariable, Time = currentSpike.Time };
296  if (Verbose)
297  {
298  Console.WriteLine("*** Spike From Neuron " + currentSpike.NeuronIndex + " To Neuron " + NeuronIndex + " ***");
299  Console.WriteLine(spikeStats);
300  }
301  if (SpikeQueue.Count > 0)
302  {
303  currentSpike = SpikeQueue.Dequeue();
304  }
305  else
306  {
307  allInputSpikeProcessed = true;
308  }
309  }
310 
311  // If all input spikes have been processed and phase is quiescent
312  // then no further output spikes are possible
313  if (allInputSpikeProcessed && (OperatingRegion != OperatingRegion.SpikeGeneration && OperatingRegion != OperatingRegion.TonicSpiking))
314  {
315  break;
316  }
317 
318  // Check for output spike
319  if (State.StateVariable > SpikeThreshold && statePast <= SpikeThreshold)
320  {
321  outputSpikes.Add(new Spike { NeuronIndex = NeuronIndex, Time = State.Time });
322  if (Verbose)
323  {
324  Console.WriteLine("---> " + outputSpikes.Last() + "\n");
325  }
326  State.StateVariable = PostSpikeState;
327  if (outputSpikes.Count == maxNumOutputSpikes)
328  {
329  return outputSpikes;
330  }
331  }
332 
333  // Special Case of Refractory Firing
334  if (State.StateVariable > PostSpikeState && statePast <= PostSpikeState)
335  {
336  State.StateVariable = SpikeThreshold - (State.StateVariable + PostSpikeState);
337  }
338 
339  // Prepare for Next Iteration
340  statePast = State.StateVariable;
341  State.Time += Timestep;
342  if (State.Time <= intLimit) continue;
343  if (!(OperatingRegion == OperatingRegion.SpikeGeneration ||
344  (OperatingRegion == OperatingRegion.TonicSpiking && (outputSpikes.Count == 0 || currentSpike.Time > outputSpikes.Last().Time))))
345  {
346  doIntegration = false;
347  }
348  }
349 
350  return outputSpikes;
351  }
352 
359  public List<double> CalculatePostSpikeDerivativesNumerical(NeuronFiringHistory neuronFiringHistory)
360  {
361  // Calculate all DthpDthpm1 terms (Equation 3.1.4 in NECO Paper, Although MIMO Version is Used Here)
362  var postSpikeDerivativeProducts = new List<double>();
363  SpikeStats previousSpikeStat = null;
364  var initialState = State;
365  const double offset = 0.001;
366  foreach (var spikeStat in neuronFiringHistory.SpikeStats)
367  {
368  if (previousSpikeStat == null)
369  {
370  previousSpikeStat = spikeStat;
371  continue;
372  }
373 
374  var currentSpike = new Spike
375  {
376  NeuronIndex = spikeStat.Synapse.InputNeuronIndex,
377  Time = spikeStat.PreSpikeState.Time
378  };
379 
380  State = new NeuronState { StateVariable = previousSpikeStat.PostSpikeState.StateVariable, Time = previousSpikeStat.PostSpikeState.Time };
381  State.StateVariable -= offset;
382  SpikeStats spikeStatsGradDelta1;
383  ProcessSpike(currentSpike, out spikeStatsGradDelta1);
384  State = new NeuronState { StateVariable = previousSpikeStat.PostSpikeState.StateVariable, Time = previousSpikeStat.PostSpikeState.Time };
385  State.StateVariable += offset;
386  SpikeStats spikeStatsGradDelta2;
387  ProcessSpike(currentSpike, out spikeStatsGradDelta2);
388 
389  postSpikeDerivativeProducts.Add((spikeStatsGradDelta2.PostSpikeState.StateVariable - spikeStatsGradDelta1.PostSpikeState.StateVariable) / (2 * offset));
390  previousSpikeStat = spikeStat;
391  }
392 
393  State = initialState;
394  return postSpikeDerivativeProducts;
395  }
396 
403  public Dictionary<Synapse, NeuronDerivativeParameters> CalculateOutputSpikeTimeDerivativesNumerical(NeuronFiringHistory neuronFiringHistory)
404  {
405  // Convert neuronFiringHistory to input spikes
406  var inputSpikes = neuronFiringHistory.SpikeStats.Select(spikeStat => new Spike
407  {
408  NeuronIndex = spikeStat.Synapse.InputNeuronIndex,
409  Time = spikeStat.PreSpikeState.Time
410  }).ToList();
411 
412  if (neuronFiringHistory.OutputSpikes.Count > 1)
413  {
414  throw new ArgumentException("MIMO Training Not Implemented");
415  }
416 
417  if (neuronFiringHistory.OutputSpikes.Count == 0)
418  {
419  throw new ArgumentException("Actual Output Spike Times are Missing");
420  }
421 
422  var inputSynapses = Weights.Select(x => x.Key).ToList();
423  var outputSpikeTimeDerivatives = inputSynapses.ToDictionary(synapse => synapse, synapse => new NeuronDerivativeParameters
424  {
425  OutputSpikeTimeToInputSpikeTimeDerivative = 0, OutputSpikeTimeToWeightDerivative = 0
426  });
427 
428  const double offset = 0.00001;
429  var initialState = State;
430  foreach (var spikeStat in neuronFiringHistory.SpikeStats)
431  {
432  // Modify weight
433  var weight = Weights.First(x => x.Key.InputNeuronIndex == spikeStat.Synapse.InputNeuronIndex);
434 
435  SetSynapticWeight(spikeStat.Synapse, weight.Value - offset);
436  ResetState();
437  var outputSpikeTime1 = RunThetaNeuron(inputSpikes).First().Time;
438 
439  SetSynapticWeight(spikeStat.Synapse, weight.Value + offset);
440  ResetState();
441  var outputSpikeTime2 = RunThetaNeuron(inputSpikes).First().Time;
442 
443  SetSynapticWeight(spikeStat.Synapse, weight.Value);
444  var outputToWeightDerivative = (outputSpikeTime2 - outputSpikeTime1) / (2 * offset);
445 
446  // Modify input spike time
447  var currentSpike = inputSpikes.First(x => x.NeuronIndex == spikeStat.Synapse.InputNeuronIndex);
448 
449  currentSpike.Time -= offset;
450  ResetState();
451  var outputSpikeTime3 = RunThetaNeuron(inputSpikes).First().Time;
452 
453  currentSpike.Time += 2*offset;
454  ResetState();
455  var outputSpikeTime4 = RunThetaNeuron(inputSpikes).First().Time;
456 
457  var outputToInputSpikeTimeDerivative = (outputSpikeTime4 - outputSpikeTime3) / (2 * offset);
458 
459  var derivativeParameters = new NeuronDerivativeParameters
460  {
461  OutputSpikeTimeToWeightDerivative = outputToWeightDerivative,
462  OutputSpikeTimeToInputSpikeTimeDerivative = outputToInputSpikeTimeDerivative
463  };
464  outputSpikeTimeDerivatives[spikeStat.Synapse] = derivativeParameters;
465  }
466 
467  State = initialState;
468  return outputSpikeTimeDerivatives;
469  }
470 
481  public IEnumerable<Spike> ProcessSpike(Spike spike, out SpikeStats spikeStats)
482  {
483  // Protect against non-causality
484  if (State.Time > spike.Time)
485  {
486  spikeStats = null;
487  return new List<Spike>();
488  }
489 
490  var outputSpikeTimes = AdvanceNeuronState(spike.Time).ToList();
491  spikeStats = new SpikeStats
492  {
493  PreSpikeState = new NeuronState
494  {
495  StateVariable = State.StateVariable,
496  Time = State.Time
497  },
498  Weight = Weights.First(x => x.Key.InputNeuronIndex == spike.NeuronIndex).Value,
499  Synapse = new Synapse(spike.NeuronIndex, NeuronIndex)
500  };
501 
502  var updateSpikes = UpdateStateVariableOnSpike(spikeStats.Weight).ToList();
503  var firedOnSpike = updateSpikes.Count > 0;
504  outputSpikeTimes.AddRange(updateSpikes);
505 
506  spikeStats.PostSpikeState = new NeuronState
507  {
508  StateVariable = State.StateVariable,
509  Time = State.Time
510  };
511 
512  if (Verbose)
513  {
514  Console.WriteLine("*** Spike From Neuron " + spike.NeuronIndex + " To Neuron " + NeuronIndex + " ***");
515  Console.WriteLine(spikeStats);
516  if (outputSpikeTimes.Count > 0)
517  {
518  foreach (var outputSpikeTime in outputSpikeTimes)
519  {
520  Console.WriteLine("---> " + outputSpikeTime);
521  }
522  }
523  else
524  {
525  Console.WriteLine("---> No Output Spikes Produced");
526  }
527 
528  if (firedOnSpike)
529  {
530  Console.WriteLine("---> At least one output spike fired directly from input spike");
531  }
532 
533  Console.WriteLine();
534  }
535 
536  return outputSpikeTimes;
537  }
538 
542  public void ResetState()
543  {
544  State = new NeuronState
545  {
546  StateVariable = InitialState,
547  Time = 0
548  };
549  }
550 
556  public void SetSynapticWeight(Synapse synapse, double newValue)
557  {
558  if (IsStandAloneNeuron)
559  {
560  if (_weights.ContainsKey(synapse))
561  {
562  _weights[synapse] = newValue;
563  }
564  else
565  {
566  _weights.Add(synapse, newValue);
567  }
568  }
569  else
570  {
571  NeuronNetwork.SetSynapticWeight(synapse, newValue);
572  }
573  }
574 
580  protected static string GetWeightsString(Dictionary<Synapse, double> weights)
581  {
582  var weightsStringBuilder = new StringBuilder();
583  weights.Keys.ToList().ForEach(x => weightsStringBuilder.AppendLine("\t" + weights[x] + "(" + x.InputNeuronIndex + "->" + x.OutputNeuronIndex + ")"));
584  return weightsStringBuilder.ToString();
585  }
586  }
587 }
OperatingRegion
Operating Region Enum
SpikingNeuron()
Creates a new instance of SpikingNeuron with no inputs
List< Spike > RunThetaNeuronNumerically(int maxNumOutputSpikes=MaxNumOutputSpikes)
Runs the theta neuron numerically, advancing the state and producing up to maxNumOutputSpikes output ...
List< double > CalculatePostSpikeDerivativesNumerical(NeuronFiringHistory neuronFiringHistory)
Calculates Post Spike Derivatives numerically, that is the derivate of the state after the current in...
double Time
Gets or sets the time.
Definition: Spike.cs:30
void SetSynapticWeight(Synapse synapse, double newValue)
Sets synaptic weight to newValue
SpikingNeuron(IReadOnlyList< double > weights)
Creates a new instance of SpikingNeuron with given weights
Spiking Neuron Abstract Class, Inherits From ISpikingNeuron
SpikingNeuron(SpikingNeuronNetwork network, int neuronIndex)
Creates a new instance of SpikingNeuron an associates it to a network
Spike Priority Queue used for determining what spike to process next based on spike timing ...
List< Spike > OutputSpikes
Gets or sets a list of output spikes produced during this neuron firing history
SimulationMethod
Simulation Method Enum
void ResetState()
Resets the neuron state to its initial conditions
The spike statistics class
Definition: SpikeStats.cs:9
SpikingNeuron(int numInputs=3, double weightIni=1e6)
Creates a new instance of SpikingNeuron with a given number of inputs and an initial weight ...
int NeuronIndex
Gets or sets the index of the neuron.
Definition: Spike.cs:22
static string GetWeightsString(Dictionary< Synapse, double > weights)
Get a string representation of the weights dictionary
IEnumerable< Spike > ProcessSpike(Spike spike, out SpikeStats spikeStats)
Starting from any state, process the neuron state forward in time until the input spike time and proc...
Dictionary< Synapse, NeuronDerivativeParameters > CalculateOutputSpikeTimeDerivativesNumerical(NeuronFiringHistory neuronFiringHistory)
Calculates Output Spike Derivatives numerically, that is the derivate of the output spike time relati...
Dictionary< Synapse, double > _weights
Weights backing field