<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>SNN on &lt;raj.sh&#39;log&gt;</title>
    <link>https://shalemrajkumar.github.io/tags/snn/</link>
    <description>Recent content in SNN on &lt;raj.sh&#39;log&gt;</description>
    <generator>Hugo -- 0.148.2</generator>
    <language>en</language>
    <lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://shalemrajkumar.github.io/tags/snn/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Brief summary of snnTorch tutorials</title>
      <link>https://shalemrajkumar.github.io/mydocs/snntorch_tutorials/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://shalemrajkumar.github.io/mydocs/snntorch_tutorials/</guid>
      <description>quick review of snnTorch tutorials</description>
      <content:encoded><![CDATA[<p><strong><em>This is a brief documentation for my reference from <a href="">snnTorch documentation</a></em></strong></p>
<h3 id="reading">Reading</h3>
<ul>
<li><a href="https://ieeexplore.ieee.org/abstract/document/10242251">The SNNTorch tutorial series is based on the following IEEE paper by JASON et al</a></li>
</ul>
<h3 id="tutorial-1-spike-generation-to-encode-inputs"><a href="https://snntorch.readthedocs.io/en/latest/tutorials/tutorial_1.html"><code>Tutorial-1: Spike Generation to encode inputs</code></a></h3>
<h4 id="how-to-convert-datasets-into-spiking-datasets">How to convert datasets into spiking datasets?</h4>
<p>Building SNNs we need Input data</p>
<p>So our inputs can be encoded in terms of spikes or could be used directly (in tutorial 3)</p>
<p><u>basic questions</u></p>
<ul>
<li>Why to encoding data?</li>
<li>How do brain encodes information? (latency vs firing rate)</li>
<li>How long to encode? (number of time steps)</li>
<li>how many spikes (frequency) to encode?</li>
<li>how to encode each kind of data? (image, audio, text, etc.)</li>
</ul>
<h4 id="why-to-encoding-data">Why to encoding data?</h4>
<p>Appeal of encoding data come from the three S&rsquo;s: spikes, sparsity, and static suppression.</p>
<ul>
<li><strong>spikes</strong>
<ul>
<li>Biological neurons process and communicate via spikes (100s of mV in amplitude, 1-2 ms in duration)</li>
<li>Many computational models of neurons simplify this voltage burst to a discrete, single-bit event: a &lsquo;1&rsquo; or a &lsquo;0&rsquo;.</li>
<li>This is far simpler to represent in hardware than a high precision value.</li>
</ul>
</li>
</ul>
<span style="display: block; margin-bottom: 1.5rem;">
<ul>
<li><strong>sparsity</strong>
<ul>
<li>Neurons spend most of their time at rest, silencing most activations (in a network) to zero at any given time.</li>
<li>Not only are sparse vectors/tensors (with loads of zeros) cheap to store, but say we need to multiply sparse activations with synaptic weights. If most values are multiplied by &lsquo;0&rsquo;, then we don&rsquo;t need to read many of the network parameters from memory. This means neuromorphic hardware can be extremely efficient.</li>
<li>least overlaping encoding</li>
</ul>
</li>
</ul>
<span style="display: block; margin-bottom: 1.5rem;">
<ul>
<li><strong>Static-Suppression</strong> (a.k.a, event-driven processing)
<ul>
<li>response to unchanging input is suppressed, so that the network only processes changes in the input. (movement, change in frequency, intensity, etc.)</li>
<li>Event-driven processing now only contributes to sparsity and power-efficiency by blocking unchanging input, but it often allows for much faster processing speeds.</li>
</ul>
</li>
</ul>
<span style="display: block; margin-bottom: 1.5rem;">
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial1/3s.png?raw=true' width="600">
</center>
<h4 id="spike-encoding">Spike Encoding</h4>
<p>MNIST is 28x28 (0-255) grayscale images of handwritten digits.</p>
<p><em>How to encode them ?</em></p>
<p>Spiking Neural Networks (SNNs) are made to exploit time-varying data, yet MNIST is static.</p>
<p>There are two options for using MNIST with an SNN:</p>
<ol>
<li>Repeatedly pass the same training sample $\mathbf{X}\in\mathbb{R}^{m\times n}$ to the network at each time step. This is like converting MNIST into a static, unchanging video. Each element of $\mathbf{X}$ can take a high precision value normalized between 0 and 1: $X_{ij}\in [0, 1]$.</li>
</ol>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial1/1_2_1_static.png?raw=true' width="700">
</center>
<ol start="2">
<li>Convert the input into a spike train of sequence length <code>num_steps</code>, where each feature/pixel takes on a discrete value $X_{i,j} \in {0, 1}$.
In this case, MNIST is converted into a time-varying sequence of spikes that features a relation to the original image.</li>
</ol>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial1/1_2_2_spikeinput.png?raw=true' width="700">
</center>
<p>The module <code>snntorch.spikegen</code> (i.e., spike generation) contains a series of functions that simplify the conversion of data into spikes. There are currently three options available for spike encoding in <code>snntorch</code>:</p>
<ol>
<li>Rate coding: <a href="https://snntorch.readthedocs.io/en/latest/snntorch.spikegen.html#snntorch.spikegen.rate"><code>spikegen.rate</code></a></li>
<li>Latency coding: <a href="https://snntorch.readthedocs.io/en/latest/snntorch.spikegen.html#snntorch.spikegen.latency"><code>spikegen.latency</code></a></li>
<li>Delta modulation: <a href="https://snntorch.readthedocs.io/en/latest/snntorch.spikegen.html#snntorch.spikegen.delta"><code>spikegen.delta</code></a></li>
</ol>
<p>How do these differ?</p>
<ol>
<li><em>Rate coding</em> uses <em><strong>input</strong></em> features to determine spiking <strong>frequency</strong></li>
<li><em>Latency coding</em> uses input <em><strong>features</strong></em> to determine spike <strong>timing</strong></li>
<li><em>Delta modulation</em> uses the <em><strong>temporal change</strong></em> of input features to generate spikes</li>
</ol>
<h4 id="rate-coding">Rate coding</h4>
<p>One example of converting input data (MNIST) into a rate code is as follows.</p>
<ul>
<li>
<p>Each normalised input feature $X_{ij}$ is used as the probability an event (spike) occurs at any given time step, returning a rate-coded value $R_{ij}$.</p>
</li>
<li>
<p>This can be treated as a Bernoulli trial: $R_{ij}\sim B(n,p)$, where the number of trials is $n=1$, and the probability of success (spiking) is $p=X_{ij}$. Explicitly, the probability a spike occurs is:</p>
<ul>
<li>$${\rm P}(R_{ij}=1) = X_{ij} = 1 - {\rm P}(R_{ij} = 0)$$</li>
<li>example: one input pixel of MNIST with value 0.5 (normalized) will have a 50% chance of spiking at any given time step (here we are using 5 time steps).</li>
<li>input_vector = [0.5, 0.5, 0.5, 0.5, 0.5]</li>
<li>torch.bernoulli(input_vector) = [0, 1, 0, 1, 1] (randomly generated)</li>
</ul>
</li>
</ul>
<h4 id="how-do-brain-encodes-information">How do brain encodes information?</h4>
<p>There has been a debate in neuroscience about whether the brain uses rate coding or latency coding.</p>
<p>Work by Bruno A Olshausen title: &ldquo;What is the other 85 percent of V1 doing&rdquo; (2004) using the arguments of power consuption and metabolic cost, he argued that the brain mostly uses latency coding by demonstrating that rate-coding can only explain, at most, the activity of 15% of neurons in the primary visual cortex (V1). It is unlikely to be the only mechanism within the brain, which is both resource-constrained and highly efficient.</p>
<p>We know that the reaction time of a human is roughly around 250ms. If the average firing rate of a neuron in the human brain is on the order of 10Hz, then we can only process about 2 spikes within our reaction timescale.</p>
<p>So my belif is that brain uses both rate and latency coding depending on the task and the type of neurons.</p>
<ul>
<li>latency coding: deep cortical neurons (V1, V2, V4) and sensory neurons (auditory, visual, olfactory).</li>
<li>rate coding: sensory periphery, motor neurons and some cortical neurons.</li>
</ul>
<p>But power and latency disadvantages are partually offset by showing huge robustness to noise</p>
<h4 id="latency-coding">latency coding</h4>
<p>Temporal codes capture information about the precise firing time of neurons.</p>
<p>a single spike carries much more meaning than in rate codes which rely on firing frequency.</p>
<ul>
<li>
<p>susceptibility to noise</p>
</li>
<li>
<p>less power consumed by the hardware running SNN algorithms by orders of magnitude</p>
</li>
<li>
<p>For our MNIST example,<span style="display: block; margin-bottom: 1.5rem;"></p>
<ul>
<li>
<p>We can use <code>spikegen.latency</code> function which allows each input to fire at most once during the full time sweep.</p>
</li>
<li>
<p>Features closer to 1 will fire earlier and features closer to 0 will fire later.</p>
</li>
<li>
<p>Spike timing is calculated by treating the input feature as the current injection $I_{in}$ into an RC circuit.<span style="display: block; margin-bottom: 1.5rem;"></p>
<ul>
<li>This current moves charge onto the capacitor, which increases $V(t)$. We assume that there is a trigger voltage, $V_{thr}$, which once reached, generates a spike.</li>
<li><strong>The question then becomes</strong>: <em>for a given input current (and equivalently, input feature), how long does it take for a spike to be generated?</em>
<break></li>
<li>Starting with Kirchhoff&rsquo;s current law, $I_{in} = I_R + I_C$, the rest of the derivation leads us to a logarithmic relationship between time and the input.</li>
</ul>
</li>
</ul>
</li>
</ul>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial1/1_2_4_latencyrc.png?raw=true' width="600">
</center>
<h5 id="rate-coding-vs-latency-coding-visualization">rate coding vs latency coding visualization</h5>
<p><code>Rate coding</code></p>
<p><img alt="Rate-coded-mnist-5" loading="lazy" src="https://github.com/shalemrajkumar/shalemrajkumar.github.io/blob/main/images/Mydocs/spike_mnist_test.gif?raw=true"></p>
<p><code>Latency coding</code></p>
<p><img alt="latency-coded-mnist-5" loading="lazy" src="https://github.com/shalemrajkumar/shalemrajkumar.github.io/blob/main/images/Mydocs/mnist_latency.gif?raw=true"></p>
<h4 id="delta-modulation">Delta Modulation</h4>
<p>There are theories that the retina is adaptive: it will only process information when there is something new to process. If there is no change in your field of view, then your photoreceptor cells are  less prone to firing.</p>
<p>Delta modulation is based on event-driven spiking. The <code>snntorch.delta</code> function accepts a time-series tensor as input. It takes the difference between each subsequent feature across all time steps. By default, if the difference is both <em>positive</em> and <em>greater than the threshold $V_{thr}$</em>, a spike is generated:</p>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial1/1_2_7_delta.png?raw=true' width="600">
</center>
<h4 id="code">code</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#%% Imports and Environment Setup %%</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> snntorch <span style="color:#66d9ef">as</span> snn
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> torch
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> snntorch <span style="color:#f92672">import</span> utils
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> snntorch <span style="color:#f92672">import</span> spikegen
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> torch.utils.data <span style="color:#f92672">import</span> DataLoader 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> matplotlib.pyplot <span style="color:#66d9ef">as</span> plt
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> snntorch.spikeplot <span style="color:#66d9ef">as</span> splt
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> IPython.display <span style="color:#f92672">import</span> HTML 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Training Parameters</span>
</span></span><span style="display:flex;"><span>batch_size<span style="color:#f92672">=</span><span style="color:#ae81ff">128</span>
</span></span><span style="display:flex;"><span>data_path<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;./data&#39;</span>
</span></span><span style="display:flex;"><span>num_classes <span style="color:#f92672">=</span> <span style="color:#ae81ff">10</span>  <span style="color:#75715e"># MNIST has 10 output classes</span>
</span></span><span style="display:flex;"><span>num_steps <span style="color:#f92672">=</span> <span style="color:#ae81ff">100</span> 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Torch Variables</span>
</span></span><span style="display:flex;"><span>dtype <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>float
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#%% Download MNIST Dataset %%</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> torchvision <span style="color:#f92672">import</span> datasets, transforms
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Define a transform (not actually doing much, just converting PIL images to tensors)</span>
</span></span><span style="display:flex;"><span>transform <span style="color:#f92672">=</span> transforms<span style="color:#f92672">.</span>Compose([
</span></span><span style="display:flex;"><span>            transforms<span style="color:#f92672">.</span>Resize((<span style="color:#ae81ff">28</span>,<span style="color:#ae81ff">28</span>)),  <span style="color:#75715e">## already in same shape</span>
</span></span><span style="display:flex;"><span>            transforms<span style="color:#f92672">.</span>Grayscale(),      <span style="color:#75715e">## already in grey scale</span>
</span></span><span style="display:flex;"><span>            transforms<span style="color:#f92672">.</span>ToTensor(),       <span style="color:#75715e">## converts PIL object to tensor</span>
</span></span><span style="display:flex;"><span>            transforms<span style="color:#f92672">.</span>Normalize((<span style="color:#ae81ff">0</span>,), (<span style="color:#ae81ff">1</span>,))]) <span style="color:#75715e">## subtracting 0 and dividing by 1</span>
</span></span><span style="display:flex;"><span>mnist_train <span style="color:#f92672">=</span> datasets<span style="color:#f92672">.</span>MNIST(data_path, train<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, download<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, transform<span style="color:#f92672">=</span>transform)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34; 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Note: This is just an example so we won&#39;t be training on whole dataset
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- snntorch.utils contains a few useful functions for modifying datasets
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- we will use snntorch.utils.data_subset to create a smaller subset of the MNIST dataset.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    - E.g., for subset=10, a training set of 60,000 will be reduced to 6,000.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>subset <span style="color:#f92672">=</span> <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>mnist_train <span style="color:#f92672">=</span> utils<span style="color:#f92672">.</span>data_subset(mnist_train, subset)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#%% Creating Dataloaders %%</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- The Dataset objects created above load data into memory, and the DataLoader will serve it up in batches. 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- DataLoaders in PyTorch are a handy interface for passing data into a network. 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    - They return an iterator divided up into mini-batches of size batch_size.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">why use dataloder instead of for loop ?
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">* very efficient than &#34;for loop&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">* Better through put for the gpu
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">* shuffle and batching feature for epochs
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>train_loader <span style="color:#f92672">=</span> DataLoader(mnist_train, batch_size<span style="color:#f92672">=</span>batch_size, shuffle<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#&gt; read about encoding in the above tutorial </span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#%% rate encoding of MNIST dataset %%#</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Iterate through one of minibatches</span>
</span></span><span style="display:flex;"><span>data <span style="color:#f92672">=</span> iter(train_loader)
</span></span><span style="display:flex;"><span>data_it, targets_it <span style="color:#f92672">=</span> next(data)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Spiking Data | structure: [num_steps x batch_size x input dimensions]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>spike_data_rate <span style="color:#f92672">=</span> spikegen<span style="color:#f92672">.</span>rate(data_it, num_steps<span style="color:#f92672">=</span>num_steps, gain<span style="color:#f92672">=</span><span style="color:#ae81ff">0.25</span>) <span style="color:#75715e"># gain reduces the # of spikes so p=1 is not torch.ones(num_steps) i.e always spiking.</span>
</span></span><span style="display:flex;"><span>spike_data_latency <span style="color:#f92672">=</span> spikegen<span style="color:#f92672">.</span>latency(data_it, num_steps<span style="color:#f92672">=</span>num_steps)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># spike_data_delta = spikegen.delta(data_it, num_steps=num_steps) ## this doesn&#39;t work for mnist because it is static representation</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># visualize the spike data </span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sample_idx <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>spike_data_rate_sample <span style="color:#f92672">=</span> spike_data_rate[:, sample_idx, <span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>spike_data_latency_sample <span style="color:#f92672">=</span> spike_data_latency[:, sample_idx, <span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;target:&#34;</span>, targets_it[sample_idx]<span style="color:#f92672">.</span>item())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>fig, ax <span style="color:#f92672">=</span> plt<span style="color:#f92672">.</span>subplots(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">2</span>, figsize<span style="color:#f92672">=</span>(<span style="color:#ae81ff">8</span>, <span style="color:#ae81ff">4</span>))
</span></span><span style="display:flex;"><span>ax[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>set_title(<span style="color:#e6db74">&#34;Rate Coding&#34;</span>)
</span></span><span style="display:flex;"><span>ax[<span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>set_title(<span style="color:#e6db74">&#34;Latency Coding&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>anim_rate <span style="color:#f92672">=</span> splt<span style="color:#f92672">.</span>animator(spike_data_rate_sample, fig, ax[<span style="color:#ae81ff">0</span>])
</span></span><span style="display:flex;"><span>anim_latency <span style="color:#f92672">=</span> splt<span style="color:#f92672">.</span>animator(spike_data_latency_sample, fig, ax[<span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>show()
</span></span></code></pre></div><h4 id="additional-docs">Additional docs</h4>
<h5 id="spikegenrate-docs"><strong>spikegen.rate docs</strong></h5>
<ul>
<li><em>&hellip;</em></li>
</ul>
<h5 id="spikegenlatency-docs"><strong>spikegen.latency docs</strong></h5>
<ul>
<li><em>&hellip;</em></li>
</ul>
<h5 id="spikegendelta-docs"><strong>spikegen.delta docs</strong></h5>
<ul>
<li><em>&hellip;</em></li>
</ul>
<h3 id="tutorial-2-lif-neuron-over-perceptron"><a href="https://snntorch.readthedocs.io/en/latest/tutorials/tutorial_2.html"><code>Tutorial-2 LIF Neuron over perceptron</code></a></h3>
<p>So if we are using spiking or event driven data, we need a special types of neuron different from traditional perceptron or relu neurons.</p>
<p>so we can go with different levels of abstraction over relu neurons, may be from LIF neuron to Hodgkin-Huxley neuron. But the fact is biology has its limitations and so does our hardware. So we need to find a balance between biological plausibility and hardware efficiency with the primary <strong>goal</strong> in mind.</p>
<blockquote>
<p>We are looking for a event based computation, hoping its is what biology trying to achieve.</p></blockquote>
<p><u>Note:</u> We are missing the spacial computation aspect of it. I am not sure if the delay, refractory period and inhibtion could make up for the missing spacial computation.</p>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial2/2_1_neuronmodels.png?raw=true' width="1000">
</center>
<h4 id="leaky-integrate-and-fire-neuron"><strong>Leaky Integrate-and-Fire Neuron</strong></h4>
<p>The leaky integrate-and-fire (LIF) neuron, Just like the relu neuron takes a sum of weighted inputs But rather than passing it directly to an activation function, it will integrate the input over time with a leakage, much like an RC circuit. If the integrated value exceeds a threshold, then the LIF neuron will emit a voltage spike.</p>
<p>The LIF neuron abstracts away the shape and profile of the output spike; it is simply treated as a discrete event. (why? biological importance of spike is to pass the signal along a long axon - but wait there are dendro-axonic, axo-axonic connections in fly) As a result, information is not stored within the spike, but rather the timing (or frequency) of spikes.</p>
<p>Simple spiking neuron models have produced much insight into the neural code, memory, network dynamics, and more recently, deep learning. The LIF neuron sits in the sweet spot between biological plausibility and practicality.</p>
<h5 id="what-are-we-missing-in-lif-neuron">what are we missing in LIF neuron?</h5>
<ul>
<li>backpropagation of spikes</li>
<li>shunting inhibition</li>
<li>dendritic computation</li>
<li>&hellip;</li>
</ul>
<h4 id="derivation-of-lif-neuron">Derivation of LIF neuron</h4>
<p>Now say some arbitrary time-varying current $I_{\rm in}(t)$ is injected into the neuron, be it via electrical stimulation or from other neurons. The total current in the circuit is conserved, so:</p>
<p>$$I_{\rm in}(t) = I_{R} + I_{C}$$</p>
<p>From Ohm&rsquo;s Law, the membrane potential measured between the inside and outside of the neuron $U_{\rm mem}$ is proportional to the current through the resistor:</p>
<p>$$I_{R}(t) = \frac{V_{\rm mem}(t)}{R}$$</p>
<p>The capacitance is a proportionality constant between the charge stored on the capacitor $Q$ and $U_{\rm mem}(t)$:</p>
<p>$$Q = CV_{\rm mem}(t)$$</p>
<p>The rate of change of charge gives the capacitive current:</p>
<p>$$\frac{dQ}{dt}=I_C(t) = C\frac{dV_{\rm mem}(t)}{dt}$$</p>
<p>Therefore:</p>
<p>$$I_{\rm in}(t) = \frac{V_{\rm mem}(t)}{R} + C\frac{dV_{\rm mem}(t)}{dt}$$</p>
<p>$$\implies RC \frac{dV_{\rm mem}(t)}{dt} = -V_{\rm mem}(t) + RI_{\rm in}(t)$$</p>
<p>The right hand side of the equation is of units <strong>[Voltage]</strong>. On the left hand side of the equation, the term $\frac{dV_{\rm mem}(t)}{dt}$ is of units <strong>[Voltage/Time]</strong>. To equate it to the left hand side (i.e., voltage), $RC$ must be of unit <strong>[Time]</strong>. We refer to $\tau = RC$ as the time constant of the circuit:</p>
<p>$$ \tau \frac{dV_{\rm mem}(t)}{dt} = -V_{\rm mem}(t) + RI_{\rm in}(t)$$</p>
<p>The passive membrane is therefore described by a linear differential equation.</p>
<p>For a derivative of a function to be of the same form as the original function, i.e., $\frac{dV_{\rm mem}(t)}{dt} \propto V_{\rm mem}(t)$, this implies the solution is exponential with a time constant $\tau$.</p>
<p>Say the neuron starts at some value $U_{0}$ with no further input, i.e., $I_{\rm in}(t)=0$. The solution of the linear differential equation is:</p>
<p>$$V_{\rm mem}(t) = V_0e^{-\frac{t}{\tau}}$$</p>
<blockquote>
<p>In simple terms the injected ions $\rightarrow$ accumulate charge on the membrane + leakage of ions through the leaky channels</p></blockquote>
<p>Using forward Euler method, we can discretize the differential equation to solve for $V_mem$ at each time step $t$:</p>
<p>$$V(t+\Delta t) = V(t) + \frac{\Delta t}{\tau}\big(-V(t) + RI_{\rm in}(t)\big)$$</p>
<p>simply this can be achieved by <a href="https://snntorch.readthedocs.io/en/latest/snn.neurons_lapicque.html"><code>snntorh.Lapicque</code></a></p>
<blockquote>
<p>Add the if condition based threshold and reset mechanism to get the LIF neuron from the Lapicque model (RC circuit).</p></blockquote>
<p><u>Note:</u> Most of the tutorial 2 is about coding simple LIF neuron from scratch and comparing with SNN torch Lapicque linking the use of spikegen module as input.</p>
<h4 id="code-1">code</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># LIF w/Reset mechanism</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">leaky_integrate_and_fire</span>(mem, cur<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>, threshold<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, time_step<span style="color:#f92672">=</span><span style="color:#ae81ff">1e-3</span>, R<span style="color:#f92672">=</span><span style="color:#ae81ff">5.1</span>, C<span style="color:#f92672">=</span><span style="color:#ae81ff">5e-3</span>):
</span></span><span style="display:flex;"><span>  tau_mem <span style="color:#f92672">=</span> R<span style="color:#f92672">*</span>C
</span></span><span style="display:flex;"><span>  spk <span style="color:#f92672">=</span> (mem <span style="color:#f92672">&gt;</span> threshold)
</span></span><span style="display:flex;"><span>  mem <span style="color:#f92672">=</span> mem <span style="color:#f92672">+</span> (time_step<span style="color:#f92672">/</span>tau_mem)<span style="color:#f92672">*</span>(<span style="color:#f92672">-</span>mem <span style="color:#f92672">+</span> cur<span style="color:#f92672">*</span>R) <span style="color:#f92672">-</span> spk<span style="color:#f92672">*</span>threshold  <span style="color:#75715e"># every time spk=1, subtract the threhsold</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> mem, spk
</span></span></code></pre></div><h3 id="tutorial-3-simplified-lif-neuron-and-feedforward-snn"><a href="https://snntorch.readthedocs.io/en/latest/tutorials/tutorial_3.html"><code>Tutorial-3 Simplified LIF neuron and feedforward SNN</code></a></h3>
<p>We currently had two main concepts covered</p>
<ol>
<li>How to encode data into spikes</li>
<li>How to build a simple LIF neuron model</li>
</ol>
<p>What needs to be covered ?</p>
<ol start="3">
<li>Make a model with encoded inputs, spiking neurons and required architecture to solve a problem (classification, regression, etc.)</li>
<li>Training and testing the model</li>
</ol>
<p>we will cover these aspects step by step, lets go with building a simple feedforward fully connected SNN model with random inputs generated from <code>snntorch.spikegen.rate_cov</code>.</p>
<h4 id="simplified-lif-neuron">Simplified LIF neuron</h4>
<p>we will first <strong>simplify</strong> the current LIF model discussed previously to</p>
<div>
$$U[t+1] = \underbrace{\beta V[t]}_{\mathrm{decay}} + \underbrace{WX[t+1]}_{\mathrm{input}} - \underbrace{S[t]V_{\mathrm{thr}}}_{\mathrm{reset}} \tag{0}$$
</div>
<h5 id="decay-rate-beta"><u><strong>Decay Rate</strong></u> ($\beta$)</h5>
<p>In the previous tutorial, the Euler method was used to derive the following solution to the passive membrane model:</p>
<p>$$V(t+\Delta t) = (1-\frac{\Delta t}{\tau})V(t) + \frac{\Delta t}{\tau} I_{\rm in}(t)R \tag{1}$$</p>
<p>Now assume $I_{\rm in}(t)=0 A$:</p>
<p>$$V(t+\Delta t) = (1-\frac{\Delta t}{\tau})V(t) \tag{2}$$</p>
<p>Let the ratio of subsequent values of $V$, i.e., $V(t+\Delta t)/V(t)$ be the decay rate of the membrane potential, also known as the <code>inverse time constant</code>:</p>
<p>$$V(t+\Delta t) = \beta V(t) \tag{3}$$</p>
<p>From $(1)$, this implies that:</p>
<p>$$\beta = (1-\frac{\Delta t}{\tau}) \tag{4}$$</p>
<p>For reasonable accuracy, $\Delta t &laquo; \tau$.</p>
<p>If we assume $t$ represents time-steps rather than continuous time (discretize time)</p>
<p>Then we can set $\Delta t = 1$. To further reduce the number of hyperparameters, assume $R=1$. From $(4)$, these assumptions lead to:</p>
<p>$$\beta = (1-\frac{1}{\tau}) \implies (1-\beta)I_{\rm in} = \frac{1}{\tau}I_{\rm in} \tag{5}$$</p>
<p>The input current is weighted by $(1-\beta)$ and also note $\tau$ = C.
By additionally assuming input current instantaneously contributes to the membrane potential:</p>
<p>$$V[t+1] = \beta V[t] + (1-\beta)I_{\rm in}[t+1] \tag{6}$$</p>
<p><u>Note:</u> The discretization of time means we are assuming that each time bin $t$ is brief enough to fit maximum of one spike in this interval.</p>
<h5 id="weight-w"><u><strong>Weight</strong></u> ($W$)</h5>
<p>In deep learning, the weighting factor of an input is often a learnable parameter. Taking a step away from the physically viable assumptions made thus far, we subsume the effect of $(1-\beta)$ from $(6)$ into a learnable weight $W$, and replace $I_{\rm in}[t]$ accordingly with an input $X[t]$:</p>
<p>$$WX[t] = I_{\rm in}[t] \tag{7}$$</p>
<p>This can be interpreted in the following way. $X[t]$ is an input voltage, or spike, and is scaled by the synaptic conductance of $W$ to generate a current injection to the neuron. This gives us the following result:</p>
<p>$$U[t+1] = \beta U[t] + WX[t+1] \tag{8}$$</p>
<p>In future simulations, the effects of $W$ and $\beta$ are decoupled.
$W$ is a learnable parameter that is updated independently of $\beta$.</p>
<h5 id="spiking-and-reset"><u><strong>Spiking and Reset</strong></u></h5>
<p>Recall that if the membrane exceeds the threshold, then the neuron emits an output spike:</p>
<p>$$S[t] = \begin{cases} 1, &amp;\text{if}~V[t] &gt; V_{\rm thr} \\
0, &amp;\text{otherwise}\end{cases} \tag{9}$$</p>
<p>If a spike is triggered, the membrane potential should be reset. The <em>reset-by-subtraction</em> mechanism is modeled by:</p>
<blockquote>
<p>$$V[t+1] = \beta V[t] + WX[t+1] - S[t]V_{\rm thr} \tag{10}$$</p></blockquote>
<p>As $W$ is a learnable parameter, and $V_{\rm thr}$ is often just set to $1$ (though can be tuned), this leaves the decay rate $\beta$ as the only hyperparameter left to be specified.</p>
<p><u>Note:</u> some implementations might make slightly different assumptions. E.g., $S[t] \rightarrow S[t+1]$ in $(9)$, or $X[t] \rightarrow X[t+1]$ in $(10)$. This above derivation is what is used in snnTorch as it maps intuitively to a recurrent neural network representation, without any change in performance.</p>
<br>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">leaky_integrate_and_fire</span>(mem, x, w, beta, threshold<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>):
</span></span><span style="display:flex;"><span>  spk <span style="color:#f92672">=</span> (mem <span style="color:#f92672">&gt;</span> threshold) <span style="color:#75715e"># if membrane exceeds threshold, spk=1, else, 0</span>
</span></span><span style="display:flex;"><span>  mem <span style="color:#f92672">=</span> beta <span style="color:#f92672">*</span> mem <span style="color:#f92672">+</span> w<span style="color:#f92672">*</span>x <span style="color:#f92672">-</span> spk<span style="color:#f92672">*</span>threshold
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> spk, mem
</span></span></code></pre></div><br>
<p>To set $\beta$, we have the option of either using Eq $(3)$ to define it, or hard-coding it directly. Here, we will use $(3)$ for the sake of a demonstration, but in future, it will just be <strong>hard-coded</strong> as <strong>we are more focused on something that works rather than biological precision</strong>.</p>
<p>Equation $(3)$ tells us that $\beta$ is the ratio of membrane potential across two subsequent time steps.</p>
<p>Solve this using the continuous time-dependent form of the equation (assuming no current injection), which was derived in <a href="#tutorial-2-lif-neuron-over-perceptron">Tutorial 2</a>:</p>
<p>$$V(t) = V_0e^{-\frac{t}{\tau}}$$</p>
<p>Assume the time-dependent equation is computed at discrete steps of $t, (t+\Delta t), (t+2\Delta t)&hellip;$, then we can find the ratio of membrane potential between subsequent steps using:</p>
<p>$$\beta = \frac{V_0e^{-\frac{t+\Delta t}{\tau}}}{V_0e^{-\frac{t}{\tau}}} = \frac{V_0e^{-\frac{t + 2\Delta t}{\tau}}}{V_0e^{-\frac{t+\Delta t}{\tau}}} =&hellip;$$
$$\implies \beta = e^{-\frac{\Delta t}{\tau}} $$</p>
<h4 id="feedforward-spiking-neural-network-using-snntorch">Feedforward Spiking Neural Network using snnTorch</h4>
<p>we are going to use <a href="https://snntorch.readthedocs.io/en/latest/snn.neurons_leaky.html"><code>snntorch.Leaky</code></a> which is a simplified version of LIF neuron we discussed above. compared to <a href="https://snntorch.readthedocs.io/en/latest/snn.neurons_lapicque.html"><code>snntorch.Lapicque</code></a> we have to deal with less parameters.</p>
<p>Also <code>snntorch.Leaky</code> uses soft reset mechanism which enables better performance in deep learning benchmarks. Not really sure why that is the case.</p>
<div>
$$V[t+1] = \underbrace{\beta V[t]}_\text{decay} + \underbrace{WX[t+1]}_\text{input} - \underbrace{\beta S[t]V_{\rm thr}}_\text{soft reset} \tag{11}$$
</div>
<p>Now we will create a 3-layer fully-connected neural network of dimensions 784-1000-10 using snnTorch</p>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial2/2_8_fcn.png?raw=true' width="600">
</center>
<blockquote>
<p>PyTorch routes the neurons together, and snnTorch loads the results into spiking neuron models. In terms of coding up a network, these spiking neurons can be treated like time-varying activation functions.</p></blockquote>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># imports</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> snntorch <span style="color:#66d9ef">as</span> snn
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> snntorch <span style="color:#f92672">import</span> spikeplot <span style="color:#66d9ef">as</span> splt
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> snntorch <span style="color:#f92672">import</span> spikegen
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> torch
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> torch.nn <span style="color:#66d9ef">as</span> nn
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> matplotlib.pyplot <span style="color:#66d9ef">as</span> plt 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>num_steps <span style="color:#f92672">=</span> <span style="color:#ae81ff">200</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># layer parameters</span>
</span></span><span style="display:flex;"><span>num_inputs <span style="color:#f92672">=</span> <span style="color:#ae81ff">784</span>
</span></span><span style="display:flex;"><span>num_hidden <span style="color:#f92672">=</span> <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>num_outputs <span style="color:#f92672">=</span> <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>beta <span style="color:#f92672">=</span> <span style="color:#ae81ff">0.99</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># initialize layers</span>
</span></span><span style="display:flex;"><span>fc1 <span style="color:#f92672">=</span> nn<span style="color:#f92672">.</span>Linear(num_inputs, num_hidden)
</span></span><span style="display:flex;"><span>lif1 <span style="color:#f92672">=</span> snn<span style="color:#f92672">.</span>Leaky(beta<span style="color:#f92672">=</span>beta)
</span></span><span style="display:flex;"><span>fc2 <span style="color:#f92672">=</span> nn<span style="color:#f92672">.</span>Linear(num_hidden, num_outputs)
</span></span><span style="display:flex;"><span>lif2 <span style="color:#f92672">=</span> snn<span style="color:#f92672">.</span>Leaky(beta<span style="color:#f92672">=</span>beta)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Initialize hidden variables and outputs of each neuron</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">As networks increase in depth, this becomes more tedious to initial state variables like mem. The static method init_leaky() can be used to take care of this by creating the correctly-shaped, zeroed-out initial membrane potential tensor for that layer, also each neuron type have their own init methods
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mem1 <span style="color:#f92672">=</span> lif1<span style="color:#f92672">.</span>init_leaky()
</span></span><span style="display:flex;"><span>mem2 <span style="color:#f92672">=</span> lif2<span style="color:#f92672">.</span>init_leaky()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># record outputs</span>
</span></span><span style="display:flex;"><span>mem2_rec <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>spk1_rec <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>spk2_rec <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- We create a random input spike train to pass to the network with 200 timesteps and 784 neurons
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- Usually neural nets process data in batches and snnTorch uses dim &#34;1&#34; as the batch dimension
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>spk_in <span style="color:#f92672">=</span> spikegen<span style="color:#f92672">.</span>rate_conv(torch<span style="color:#f92672">.</span>rand((<span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">784</span>)))<span style="color:#f92672">.</span>unsqueeze(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- In terms of coding up a network, these spiking neurons can be treated like time-varying activation functions.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- Here is a sequential account of what&#39;s going on:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">* The $i^</span><span style="color:#e6db74">{th}</span><span style="color:#e6db74">$ input from `spk_in` to the $j^</span><span style="color:#e6db74">{th}</span><span style="color:#e6db74">$ neuron is weighted by the parameters initialized in `nn.Linear`: $X_</span><span style="color:#e6db74">{i}</span><span style="color:#e6db74"> </span><span style="color:#ae81ff">\t</span><span style="color:#e6db74">imes W_</span><span style="color:#e6db74">{ij}</span><span style="color:#e6db74">$ (similar to W.T@X)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">* This generates the input current term from Equation $(10)$, contributing to $V[t+1]$ of the spiking neuron (voltage rises from rest)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">* If $V[t+1] &gt; V_{</span><span style="color:#ae81ff">\r</span><span style="color:#e6db74">m thr}$, then a spike is triggered from this neuron (threshold check -&gt; spike generation)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">* This spike is weighted by the second layer weight, and the above process is repeated for all inputs, weights, and neurons.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Note: Now we are now scaling the input current with a weight generated by `nn.Linear`, rather than manually setting W ourselves.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># network simulation</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> step <span style="color:#f92672">in</span> range(num_steps):
</span></span><span style="display:flex;"><span>    cur1 <span style="color:#f92672">=</span> fc1(spk_in[step]) <span style="color:#75715e"># post-synaptic current &lt;-- spk_in x weight</span>
</span></span><span style="display:flex;"><span>    spk1, mem1 <span style="color:#f92672">=</span> lif1(cur1, mem1) <span style="color:#75715e"># mem[t+1] &lt;--post-syn current + decayed membrane</span>
</span></span><span style="display:flex;"><span>    cur2 <span style="color:#f92672">=</span> fc2(spk1)
</span></span><span style="display:flex;"><span>    spk2, mem2 <span style="color:#f92672">=</span> lif2(cur2, mem2)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    mem2_rec<span style="color:#f92672">.</span>append(mem2)
</span></span><span style="display:flex;"><span>    spk1_rec<span style="color:#f92672">.</span>append(spk1)
</span></span><span style="display:flex;"><span>    spk2_rec<span style="color:#f92672">.</span>append(spk2)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># convert lists to tensors</span>
</span></span><span style="display:flex;"><span>mem2_rec <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>stack(mem2_rec)
</span></span><span style="display:flex;"><span>spk1_rec <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>stack(spk1_rec)
</span></span><span style="display:flex;"><span>spk2_rec <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>stack(spk2_rec)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>fig, ax <span style="color:#f92672">=</span> plt<span style="color:#f92672">.</span>subplots(<span style="color:#ae81ff">3</span>, figsize<span style="color:#f92672">=</span>(<span style="color:#ae81ff">8</span>,<span style="color:#ae81ff">7</span>), sharex<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, 
</span></span><span style="display:flex;"><span>                        gridspec_kw <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#39;height_ratios&#39;</span>: [<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">0.4</span>]})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Plot input spikes</span>
</span></span><span style="display:flex;"><span>splt<span style="color:#f92672">.</span>raster(spk_in[:,<span style="color:#ae81ff">0</span>], ax[<span style="color:#ae81ff">0</span>], s<span style="color:#f92672">=</span><span style="color:#ae81ff">0.03</span>, c<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;black&#34;</span>)
</span></span><span style="display:flex;"><span>ax[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>set_ylabel(<span style="color:#e6db74">&#34;Input Spikes&#34;</span>)
</span></span><span style="display:flex;"><span>ax[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>set_title(<span style="color:#e6db74">&#34;Fully Connected Spiking Neural Network&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Plot hidden layer spikes</span>
</span></span><span style="display:flex;"><span>splt<span style="color:#f92672">.</span>raster(spk1_rec<span style="color:#f92672">.</span>reshape(num_steps, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>), ax[<span style="color:#ae81ff">1</span>], s <span style="color:#f92672">=</span> <span style="color:#ae81ff">0.05</span>, c<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;black&#34;</span>)
</span></span><span style="display:flex;"><span>ax[<span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>set_ylabel(<span style="color:#e6db74">&#34;Hidden Layer&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Plot output spikes</span>
</span></span><span style="display:flex;"><span>splt<span style="color:#f92672">.</span>raster(spk2_rec<span style="color:#f92672">.</span>reshape(num_steps, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>), ax[<span style="color:#ae81ff">2</span>], c<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;black&#34;</span>, marker<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;|&#34;</span>)
</span></span><span style="display:flex;"><span>ax[<span style="color:#ae81ff">2</span>]<span style="color:#f92672">.</span>set_ylabel(<span style="color:#e6db74">&#34;Output Spikes&#34;</span>)
</span></span><span style="display:flex;"><span>ax[<span style="color:#ae81ff">2</span>]<span style="color:#f92672">.</span>set_ylim([<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">10</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>show()
</span></span></code></pre></div><p><img alt="random input-output snn" loading="lazy" src="https://github.com/shalemrajkumar/shalemrajkumar.github.io/blob/main/images/Mydocs/random_io_snn.png?raw=true"></p>
<p>At this stage this is just a random input spike trains, random weights and random outputs. We need to train the network to get meaningful inputs, outputs.</p>
<h3 id="tutorial-4"><a href="https://snntorch.readthedocs.io/en/latest/tutorials/tutorial_4.html"><code>Tutorial-4 </code></a></h3>
<p>Till now we have seen whenever there is input current there is instantaneous response in the $V_m$ which is fixed by soft reset but still we have instantaneous synaptic current when presynaptic neuron spikes but in reality post neuronal input current (prev_neuron spike $\rightarrow$ travel via axon $\rightarrow$ synaptic neurotransmitter release $\rightarrow$ post_neuron) gradually grows and decays with some delay.</p>
<p>Currently I am not really sure on functional aspects of <strong>delayed post synaptic current</strong>, <strong>non linearity</strong> associated with this post synaptic current (bi-exponential growth and decay).</p>
<p>Now only transmitter release dynamics but also neurotransmitters activate the post-synaptic receptors, which directly influence the effective current that flows into the post-synaptic neuron. Shown below are two types of excitatory receptors, AMPA and NMDA.</p>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial2/2_6_synaptic.png?raw=true' width="600">
</center>
<p>The simplest model of synaptic current assumes an increasing current on a very fast time-scale, followed by a relatively slow exponential decay, as seen in the AMPA receptor response above. This is very similar to the membrane potential dynamics of Lapicque&rsquo;s model.</p>
<p>The synaptic model has two exponentially decaying terms: $I_{\rm syn}(t)$ and $U_{\rm mem}(t)$. The ratio between subsequent terms (i.e., decay rate) of $I_{\rm syn}(t)$ is set to $\alpha$, and that of $U(t)$ is set to $\beta$:</p>
<p>$$ \alpha = e^{-\Delta t/\tau_{\rm syn}}$$</p>
<p>$$ \beta = e^{-\Delta t/\tau_{\rm mem}}$$</p>
<p>where the duration of a single time step is normalized to $\Delta t = 1$ in future. $\tau_{\rm syn}$ models the time constant of the synaptic current in an analogous way to how $\tau_{\rm mem}$ models the time constant of the membrane potential. $\beta$ is derived in the exact same way as the previous tutorial, with a similar approach to $\alpha$:</p>
<p>$$I_{\rm syn}[t+1]=\underbrace{\alpha I_{\rm syn}[t]}<em>\text{decay} + \underbrace{WX[t+1]}</em>\text{input}$$</p>
<p>$$U[t+1] = \underbrace{\beta V[t]}<em>\text{decay} + \underbrace{I</em>{\rm syn}[t+1]}<em>\text{input} - \underbrace{R[t]}</em>\text{reset}$$</p>
<p>The same conditions for spiking as the previous LIF neurons still hold:</p>
<p>$$S_{\rm out}[t] = \begin{cases} 1, &amp;\text{if}~V[t] &gt; V_{\rm thr} \\
0, &amp;\text{otherwise}\end{cases}$$</p>
<h4 id="synaptic-neuron-model">Synaptic Neuron Model</h4>
<p>we can use <a href="https://snntorch.readthedocs.io/en/latest/snn.neurons_synaptic.html"><code>snnTorch.Synaptic</code></a> to achive this 2nd-Order Integrate-and-Fire Neuron (including synaptic conductance)</p>
<ul>
<li>$\alpha$: the decay rate of the synaptic current</li>
<li>$\beta$: the decay rate of the membrane potential (as with Lapicque)</li>
</ul>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial2/2_7_stein.png?raw=true' width="600">
</center>
<p>Each spike contributes a shifted exponential decay to the synaptic current $I_{\rm syn}$, which are all summed together. This current is then integrated by the passive membrane equation derived earlier in tutorial 2</p>
<p><u><strong>When to use 1st or 2nd order neurons ?</strong></u></p>
<p><u style="text-decoration: underline dashed; text-underline-offset: 4px;"><strong>When 2nd-order neurons are better</strong></u></p>
<ul>
<li>If the temporal relations of your input data occur across long time-scales,</li>
<li>or if the input spiking pattern is sparse</li>
</ul>
<p>By having two recurrent equations with two decay terms ($\alpha$ and $\beta$), this neuron model is able to &lsquo;sustain&rsquo; input spikes over a longer duration. This can be beneficial to retaining long-term relationships.</p>
<p>An alternative use case might also be:</p>
<ul>
<li>When temporal codes matter</li>
</ul>
<blockquote>
<p>If you care for the precise timing of a spike, it seems easier to control that for a 2nd-order neuron. In the <code>Leaky</code> model, a spike would be triggered in direct synchrony with the input. For 2nd-order models, the membrane potential is &lsquo;smoothed out&rsquo; (i.e., the synaptic current model low-pass filters the membrane potential), which means $V[t]$ experiences a finite rise time. This is clear from the above image, where the output spikes experience a delay with respect to the input spikes.</p></blockquote>
<p><u style="text-decoration: underline dashed; text-underline-offset: 4px;"><strong>When 1st-order neurons are better</strong></u></p>
<ul>
<li>Any case that doesn&rsquo;t fall into the above, and sometimes, the above cases.</li>
</ul>
<p>By having one less equation in 1st-order neuron models (such as <code>Leaky</code>), the backpropagation process is made a little simpler. Though having said that, the <code>Synaptic</code> model is functionally equivalent to the <code>Leaky</code> model for $\alpha=0$.</p>
<p>In Jason&rsquo;s own hyperparameter sweeps on simple datasets, the optimal results seem to push $\alpha$ as close to 0 as possible. As data increases in complexity, $\alpha$ may grow larger.</p>
<h4 id="alpha-neuron-model">Alpha Neuron model</h4>
<p>Alpha neuron model is a class of Spike Response Model (SRM), we need to understand SRM class of neuron models.</p>
<p>SRM is a generalization of LIF that describes a neuron&rsquo;s membrane potential <em>not</em> through a differential equation, but through kernels (response functions) convolved with input spikes.</p>
<p>SRM directly writes the membrane potential as a sum of postsynaptic potentials (PSPs) triggered by each incoming spike, plus a reset/refractory kernel triggered by the neuron&rsquo;s own past spikes:</p>
<div>
$$ V(t)=\underbrace{\sum _{f}\eta (t-t^{f})}_\text{effect of own past spikes: reset}+\underbrace{\int _{0}^{\infty }\kappa (s)I(t-s)\,ds}_\text{effect of incoming spikes}+V_{rest} $$
</div>
<p>$\kappa$ : the kernel describing how much a single input spike raises the membrane potential over time (the shape of one PSP).</p>
<p>$\eta$ : the refractory kernel describing how the neuron&rsquo;s own spike suppresses further firing right after.</p>
<p>So SRM is essentially: &ldquo;skip solving the ODE — just define the shape of the response to a spike directly, and stack them up.&rdquo; It&rsquo;s more general than LIF because you can pick any kernel shape you like.</p>
<blockquote>
<p>SRM models are appealing as they can arbitrarily add refractoriness, threshold adaptation, and any number of other features simply by embedding them into the filter.</p></blockquote>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial2/exp.gif?raw=true' width="400">
</center> 
<figure style="text-align: center;">
  <img src="https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial2/alpha.gif?raw=true" width="400" alt="Spike response to different kernels">
  <figcaption>spike to response from different kernels</figcaption>
</figure>
<br>
<p>The <strong>Alpha neuron model</strong> is SRM with a particular choice of kernel: the <strong>alpha function</strong> (rises and decays)</p>
<p>$$V_{\rm mem}(t) = \sum_i W(\kappa * S_{\rm in})(t)$$</p>
<p>where the incoming spikes $S_{\rm in}$ are convolved with a spike response kernel $\kappa( \cdot )$. The spike response is scaled by a synaptic weight, $W$. In the figures above, the top kernel is an exponentially decaying function and would be the equivalent of Lapicque&rsquo;s 1st-order neuron model. On the bottow, the kernel is an alpha function:</p>
<p>$$\kappa(t) = \frac{t}{\tau}e^{1-t/\tau}\Theta(t)$$</p>
<p>where $\tau$ is the time constant of the alpha kernel and $\Theta$ is the Heaviside step function. Most kernel-based methods adopt the alpha function as it provides a time-delay that is useful for temporal codes that are concerned with specifying the exact spike time of a neuron.</p>
<blockquote>
<p>In snnTorch, the spike response model is not directly implemented as a filter. Instead, it is recast into a recursive form such that only the previous time step of values are required to calculate the next set of values. This significantly reduces the memory overhead during learning.</p></blockquote>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial2/2_9_alpha.png?raw=true' width="600">
</center> 
<p>As the membrane potential is now determined by the sum of two exponentials, each of these exponents has their own independent decay rate. $\alpha$ defines the decay rate of the positive exponential, and $\beta$ defines the decay rate of the negative exponential.</p>
<p>Usage of <a href="https://snntorch.readthedocs.io/en/latest/snn.neurons_alpha.html"><code>snnTorch.Alpha</code></a> is similar to previous neurons except we need divide synaptics currents into positive and negative.</p>
<p>Alpha neuron models are included with the intent of providing an option for porting across SRM-based models over into snnTorch, although natively training them seems to not be too effective, because we need to separate positive and negative currents.</p>
<blockquote>
<p>In general, <strong>Leaky</strong> and <strong>Synaptic</strong> seem to be the most useful for training a network.</p></blockquote>
<h3 id="tutorial-5-training-snns"><a href="https://snntorch.readthedocs.io/en/latest/tutorials/tutorial_5.html"><code>Tutorial-5 Training SNNs</code></a></h3>
<blockquote>
<p><u>Note</u>: This tutorial along with other additional details covered <a href="https://shalemrajkumar.github.io/mydocs/training_snns/">here</a></p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Training SNNs</title>
      <link>https://shalemrajkumar.github.io/mydocs/training_snns/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://shalemrajkumar.github.io/mydocs/training_snns/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
this is a work in progress.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;snns-and-surrogate-gradient-descent&#34;&gt;SNNs and Surrogate gradient descent&lt;/h3&gt;
&lt;p&gt;Spiking neural networks (SNNs) are biologically inspired models that compute via discrete, sparse spikes, rather than continuous activations (non differentiable non linearity). This event driven framework not only captures &lt;strong&gt;rich temporal patterns&lt;/strong&gt; (such as inter spike intervals and cross neuron synchrony) but also powers energy efficient neuromorphic hardware.&lt;/p&gt;
&lt;p&gt;Surrogate gradient descent (SuGD) answer the most important challenging question: &amp;ldquo;how to convert precise spike timing into effective learning signals ?&amp;rdquo;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p>[!WARNING]
this is a work in progress.</p></blockquote>
<h3 id="snns-and-surrogate-gradient-descent">SNNs and Surrogate gradient descent</h3>
<p>Spiking neural networks (SNNs) are biologically inspired models that compute via discrete, sparse spikes, rather than continuous activations (non differentiable non linearity). This event driven framework not only captures <strong>rich temporal patterns</strong> (such as inter spike intervals and cross neuron synchrony) but also powers energy efficient neuromorphic hardware.</p>
<p>Surrogate gradient descent (SuGD) answer the most important challenging question: &ldquo;how to convert precise spike timing into effective learning signals ?&rdquo;</p>
<p>This problem is solved by Zenke <em>et al</em> 2018 through replacing the non differentiable spike with a smooth surrogate, thereby allowing backpropagation through time (BPTT).</p>
<p>SuGD can learn to make use of information that is not only encoded in the rate of spikes but also timing. SGD training can extract interspike intervals, spatio-temporal spike patterns or polychrony, and coincidence codes (<a href="10.1088/2634-4386/ae46d5">Ziqiao Yu <em>et al</em> 2026</a>)[1].</p>
<h4 id="example">Example</h4>
<p><u><em>Heavy side function</em></u></p>
<p>$$\Theta(x) = \begin{cases} 0 &amp; x &lt; 0 \\ 1 &amp; x \geq 0 \end{cases} \tag{1}$$</p>
<p><u><em>D(Heavy side function)</em></u></p>
<p>$$\frac{d\Theta(x)}{dx} = \begin{cases} 0 &amp; x &lt; 0 \\ \text{undefined} &amp; x \geq 0 \end{cases} \tag{2}$$</p>
<h4 id="surrogate-approximation">Surrogate approximation</h4>
<p>using a sigmoid or fast sigmoid function (kind of cheat:)</p>
<p><u><em>sigmoid or logistic function</em></u></p>
<p>$$\sigma(x) = \frac{1}{1 + e^{-\beta x}} \tag{3}$$</p>
<p><u><em>D(sigmoid or logistic function)</em></u></p>
<p>$$\frac{d\sigma(x)}{dx} = \beta\sigma(x)(1 - \sigma(x))\tag{4}$$</p>
<p><u><em>fast sigmoid function</em></u></p>
<p>$$\sigma(x) = \frac{x}{1 + |x|} \tag{5}$$</p>
<p><u><em>D(fast sigmoid function)</em></u></p>
<p>$$\frac{d\sigma(x)}{dx} = \frac{1}{(1 + |x|)^2} \tag{6}$$</p>
<p><u><strong>Code</strong></u></p>
<p>$$\text{heaviside}(x) \approx \sigma(x)$$</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">SurrogateHeaviside</span>(torch<span style="color:#f92672">.</span>autograd<span style="color:#f92672">.</span>Function):
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@staticmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">forward</span>(ctx, input):
</span></span><span style="display:flex;"><span>        ctx<span style="color:#f92672">.</span>save_for_backward(input)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> torch<span style="color:#f92672">.</span>heaviside(input, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@staticmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">backward</span>(ctx, grad_output):
</span></span><span style="display:flex;"><span>        input, <span style="color:#f92672">=</span> ctx<span style="color:#f92672">.</span>saved_tensors
</span></span><span style="display:flex;"><span>        beta <span style="color:#f92672">=</span> <span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span>        s <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>sigmoid(beta<span style="color:#f92672">*</span>input)
</span></span><span style="display:flex;"><span>        grad <span style="color:#f92672">=</span> grad_output<span style="color:#f92672">*</span>beta<span style="color:#f92672">*</span>s<span style="color:#f92672">*</span>(<span style="color:#ae81ff">1</span><span style="color:#f92672">-</span>s)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> grad
</span></span><span style="display:flex;"><span>surrogate_heaviside <span style="color:#f92672">=</span> SurrogateHeaviside<span style="color:#f92672">.</span>apply
</span></span></code></pre></div><p>$$\text{heaviside}(x) \approx fast\_sigmoid(x)$$</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">SurrogateHeaviside</span>(torch<span style="color:#f92672">.</span>autograd<span style="color:#f92672">.</span>Function):
</span></span><span style="display:flex;"><span>    scale <span style="color:#f92672">=</span> <span style="color:#ae81ff">100.0</span> <span style="color:#75715e"># controls steepness of surrogate gradient</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@staticmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">forward</span>(ctx, input):
</span></span><span style="display:flex;"><span>        ctx<span style="color:#f92672">.</span>save_for_backward(input) <span style="color:#75715e">## storing context or input to use during backprop</span>
</span></span><span style="display:flex;"><span>        out <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>zeros_like(input) <span style="color:#75715e">## first step make everything zero (i.e. all negative inputs are zero)</span>
</span></span><span style="display:flex;"><span>        out[input <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>] <span style="color:#f92672">=</span> <span style="color:#ae81ff">1.0</span> <span style="color:#75715e">## mask the inputs &gt; 0 and make them 1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> out
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@staticmethod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">backward</span>(ctx, grad_output):
</span></span><span style="display:flex;"><span>        input, <span style="color:#f92672">=</span> ctx<span style="color:#f92672">.</span>saved_tensors <span style="color:#75715e">## fetching previously saved input</span>
</span></span><span style="display:flex;"><span>        grad_input <span style="color:#f92672">=</span> grad_output<span style="color:#f92672">.</span>clone() <span style="color:#75715e">## safely copying the gradient output so we don&#39;t accidentally break PyTorch graph</span>
</span></span><span style="display:flex;"><span>        grad <span style="color:#f92672">=</span> grad_input<span style="color:#f92672">/</span>(SurrogateHeaviside<span style="color:#f92672">.</span>scale<span style="color:#f92672">*</span>torch<span style="color:#f92672">.</span>abs(input)<span style="color:#f92672">+</span><span style="color:#ae81ff">1.0</span>)<span style="color:#f92672">**</span><span style="color:#ae81ff">2</span> <span style="color:#75715e">## computing fast sigmoid instead of using the e^x which takes longer to run</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> grad
</span></span><span style="display:flex;"><span>surrogate_heaviside  <span style="color:#f92672">=</span> SurrogateHeaviside<span style="color:#f92672">.</span>apply
</span></span></code></pre></div><p><em>credits: Tutoral by Dan F M Goodman, 2026 FENS Chen Summer School on Learning with spikes [2]</em></p>
<h3 id="tutorial-sugd-for-training-snns"><u>Tutorial: SuGD for training SNNs</u></h3>
<br>
<blockquote>
<p><em>This section is inspired snnTorch <a href="https://snntorch.readthedocs.io/en/latest/tutorials/tutorial-5.html">tutorial-5</a> which is in turn inspired by  Friedemann Zenke&rsquo;s extensive work on SNNs. Check out his repo on surrogate gradients <a href="https://github.com/fzenke/spytorch">spytorch</a></em></p></blockquote>
<br>
<p>Now we will implement a basic supervised learing algorithm with SuGD for training spiking neurons to perform image classfication on Static MNIST.</p>
<h4 id="the-recurrent-representation-of-snns">The Recurrent representation of SNNs</h4>
<p>If you think of SNNs like a Machine learning researcher you can tell it is type of RNNs with implict recurrence with nonlinear activation function.</p>
<p>Further Infromation on RNNs are found <a href="">here</a></p>
<p><u>LIF Neuron discrete recursive form</u></p>
<div>
$$U[t+1] = \underbrace{\beta U[t]}_\text{decay} + \underbrace{WX[t+1]}_\text{input} - \underbrace{R[t]}_\text{reset} \tag{7}$$
</div>
<p>where if the membrane potential exceeds the threshold, a spike is emitted:</p>
<p>$$S[t] = \begin{cases} 1, &amp;\text{if}~U[t] &gt; U_{\rm thr} \\
0, &amp;\text{otherwise}\end{cases}{\tag{8}}$$</p>
<figure style="text-align: center;">
  <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b5/Recurrent_neural_network_unfold.svg/500px-Recurrent_neural_network_unfold.svg.png?utm_source=en.wikipedia.org&utm_campaign=parser&utm_content=thumbnail"width="400" alt="Vanilla-RNN">
  <figcaption>image credit: wikipedia</figcaption>
</figure>
<p>Like in RNNs, we have dependence from previous state (here state variable $U[t]$), Also its not even a property of network just the neuron itself.This is illustrated using an <em>implicit</em> recurrent connection for the decay of the membrane potential.</p>
<p><u> Vanilla-RNN </u></p>
<p>hidden state: $$h_t = f\left(W_{ih} * x_t + W_{hh} * h_{t-1} + b_h)\right)$$</p>
<p>output: $$ y_t = W_{ho} * h_t + b_o $$</p>
<p>This is almost perfectly poised to take advantage of the developments in training recurrent neural networks (RNNs) and sequence-based models.</p>
<p>there is the special aspect of recurrence called <strong><em>explict recurrence</em></strong> in SNNs  where the output spike $S_{\rm out}$ is fed back to the input, i.e. neurons in same layer are connected to each other or even to itself simlar to <a href=""><code>RLeaky</code></a> in snnTorch and these connection ($W_{recurr}$ where diagonals represent self connections and off diagonal elements represent recurrent connection between different neurons) fully trainable.</p>
<p>In the figure below, the connection weighted by $-U_{\rm thr}$ represents the reset mechanism $R[t]$.</p>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial5/unrolled_2.png?raw=true' width="800">
</center>
<p>The benefit of an unrolled graph is that it provides an explicit description of how computations are performed. The process of unfolding illustrates the flow of information forward in time (from left to right) to compute outputs and losses, and backward in time to compute gradients. The more time steps that are simulated, the deeper the graph becomes.</p>
<p>Conventional RNNs treat $\beta$ as a learnable parameter. This is also possible for SNNs, though by default, they are treated as hyperparameters. This replaces the vanishing and exploding gradient problems with a hyperparameter search.</p>
<h4 id="the-non-differentiablility-of-spikes">The non differentiablility of spikes</h4>
<p>In previous section we have discussed an example of heavy side function and how its derivative is always zero or undefined. This exactly how LIF neurons behave after thresholding.</p>
<p>An alternative way to represent the relationship between $S$ and $U$ in $(1)$ or $(8)$ is:</p>
<p>$$S[t] = \Theta(U[t] - U_{\rm thr}) \tag{9}$$</p>
<p>where $\Theta(\cdot)$ is the Heaviside step function:</p>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial3/3_2_spike_descrip.png?raw=true' width="600">
</center>
<p>Training a network in this form poses some serious challenges. Consider a single, isolated time step of the computational graph from the previous figure titled <em>&ldquo;Recurrent representation of spiking neurons&rdquo;</em>, as shown in the <em>forward pass</em> below:</p>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial5/non-diff.png?raw=true' width="400">
</center>
<p>The goal is to train the network using the gradient of the loss with respect to the weights, such that the weights are updated to minimize the loss. The backpropagation algorithm achieves this using the chain rule:</p>
<div>
$$\frac{\partial \mathcal{L}}{\partial W} = 
\frac{\partial \mathcal{L}}{\partial S}
\underbrace{\frac{\partial S}{\partial U}}_{\{0, \infty\}}
\frac{\partial U}{\partial I}\
\frac{\partial I}{\partial W}\ \tag{10}$$
</div>
<p>From $(7)$, $\partial I/\partial W=X$, and $\partial U/\partial I=1$. While we have not yet defined a loss function, we can assume $\partial \mathcal{L}/\partial S$ has an analytical solution, in a similar form to the cross-entropy or mean-square error loss (more on that shortly).</p>
<p>However, the term that we are going to grapple with is $\partial S/\partial U$. The derivative of the Heaviside step function from $(9)$ is the Dirac Delta function from $(2)$, which evaluates to 0 everywhere, except at the threshold $U_{\rm thr} = \theta$, where it tends to infinity. This means the gradient will almost always be nulled to zero (or saturated if $U$ sits precisely at the threshold), and no learning can take place. This is known as the <strong>dead neuron problem</strong>.</p>
<h4 id="overcoming-the-dead-neuron-problem">Overcoming the Dead Neuron Problem</h4>
<p>As we have seen in first section we keep the Heaviside function as it is during the forward pass, but swap the derivative term $\partial S/\partial U$ for something that does not kill the learning process during the backward pass, which will be denoted $\partial \tilde{S}/\partial U$.</p>
<p>This might sound weird, but it turns out that neural networks are quite robust to such approximations. This is commonly known as the <em>surrogate gradient</em> approach.</p>
<p>A variety of options exist to using surrogate gradients, The default method in snnTorch (as of v0.6.0) is to smooth the Heaviside function with the <strong>arctangent function</strong>.</p>
<p>The backward-pass derivative used is:</p>
<p>$$ \frac{\partial \tilde{S}}{\partial U} \leftarrow \frac{1}{\pi}\frac{1}{(1+[U\pi]^2)} \tag{11}$$</p>
<h4 id="backpropagation-through-time-bptt">Backpropagation Through Time (BPTT)</h4>
<p>Equation $(10)$ only calculates the gradient for one single time step (referred to as the <em>immediate influence</em> in the figure below), but the backpropagation through time (BPTT) algorithm calculates the gradient from the loss to <em>all</em> descendants and sums them together.</p>
<p>Look at the path and we can see that the current output has depedence of previous $U$ which in turn has dependence on previous $W$ and so on.</p>
<p>The weight $W$ is applied at every time step, and so imagine a loss is also calculated at every time step. The influence of the weight on present and historical losses must be summed together to define the global gradient:</p>
<div>
$$\frac{\partial \mathcal{L}}{\partial W}=\sum_t \frac{\partial\mathcal{L}[t]}{\partial W} = 
\sum_t \sum_{s\leq t} \frac{\partial\mathcal{L}[t]}{\partial W[s]}\frac{\partial W[s]}{\partial W} \tag{12} $$
</div>
<p style="font-size: 12px; color: #888; margin-top: -0.5em;">
    <u style="text-decoration: underline;">credit:</u> Claude 5 Sonnet
</p>
<blockquote>
<p>So why we have multiple $W[s]$ if all we have is one weight just across the time ?</p>
<p>The total gradient is a sum of per-timestep gradient contributions ($\frac{\partial \mathcal{L[t]}}{\partial W}$). Each of those is itself a sum over every earlier time step
$s\leq t$, because $W$ was reused at each of those steps ($W[s]$), and each reuse contributed its own path of influence on the loss at t.</p>
<p>If $W$ appeared <em>only once</em> in the whole computation, we could just chain straight through: $\mathcal{L}[t] \leftarrow U[t] \leftarrow U[t-1] \leftarrow \cdots \leftarrow W$, and $\frac{\partial \mathcal{L}[t]}{\partial W}$ would be one single product of derivatives, no sum needed. like we saw in previous figure and inital explaination in this segment.</p>
<p>But $W$ has <strong>multiple separate paths of influence</strong> on $\mathcal{L}[t]$: one path through $h[1]$, another through $U[2]$, another through $U[3]$, etc. Changing $W$ nudges <em>all</em> these usages simultaneously, and each nudge sends its own ripple forward to $\mathcal{L}[t]$.</p>
<p><strong>This is just the multivariable chain rule</strong></p>
<p>We&rsquo;ve probably seen this rule before: if $z = f(x, y)$ and both $x = x(t)$ and $y = y(t)$ depend on the same $t$, then</p>
<p>$$\frac{dz}{dt} = \frac{\partial z}{\partial x}\frac{dx}{dt} + \frac{\partial z}{\partial y}\frac{dy}{dt}$$</p>
<p>We don&rsquo;t get to pick just one path — we sum over <em>every</em> path by which $t$ reaches $z$.</p></blockquote>
<p>The point of $(12)$ is to ensure causality: by constraining $s\leq t$, we only account for the contribution of immediate and prior influences of $W$ on the loss. A recurrent system constrains the weight to be shared across all steps: $W[0]=W[1] =~&hellip; ~ = W$. Therefore, a change in $W[s]$ will have the same effect on all $W$, which implies that $\partial W[s]/\partial W=1$:</p>
<p>$$\frac{\partial \mathcal{L}}{\partial W}=
\sum_t \sum_{s\leq t} \frac{\partial\mathcal{L}[t]}{\partial W[s]} \tag{13} $$</p>
<p>As an example, isolate the prior influence due to $s = t-1$ <em>only</em>; this means the backward pass must track back in time by one step. The influence of $W[t-1]$ on the loss can be written as:</p>
<div>
$$\frac{\partial \mathcal{L}[t]}{\partial W[t-1]} = 
\frac{\partial \mathcal{L}[t]}{\partial S[t]}
\underbrace{\frac{\partial \tilde{S}[t]}{\partial U[t]}}_{Eq.~(12)}
\underbrace{\frac{\partial U[t]}{\partial U[t-1]}}_\beta
\underbrace{\frac{\partial U[t-1]}{\partial I[t-1]}}_1
\underbrace{\frac{\partial I[t-1]}{\partial W[t-1]}}_{X[t-1]} \tag{14}$$
<div>
<p>We have already dealt with all of these terms from $(11)$, except for $\partial U[t]/\partial U[t-1]$. From $(7)$, this temporal derivative term simply evaluates to $\beta$. So if we really wanted to, we now know enough to painstakingly calculate the derivative of every weight at every time step by hand, and it&rsquo;d look something like this for a single neuron:</p>
<center>
<img src='https://github.com/jeshraghian/snntorch/blob/master/docs/_static/img/examples/tutorial5/bptt.png?raw=true' width="600">
</center>
<p>But thankfully, PyTorch&rsquo;s autodiff takes care of that in the background for us.</p>
<p><em><u>Note</u>: The reset mechanism has been omitted from the above figure. In snnTorch, reset is included in the forward-pass, but detached from the backward pass.</em></p>
<h4 id="setting-up-the-loss-and-output-decoding">Setting up the loss and output decoding</h4>
<p>In a spiking neural net, there are several options to interpreting the output spikes. The most common approaches are:</p>
<ul>
<li>Rate coding: Take the neuron with the highest firing rate (or spike count) as the predicted class</li>
<li>Latency coding: Take the neuron that fires first as the predicted class</li>
</ul>
<p>This part is similar to encoding. here, we are interpreting (decoding) the output spikes, rather than encoding/converting raw input data into spikes.</p>
<p><u>Rate code</u>:. When input data is passed to the network, we want the correct neuron class to emit the most spikes over the course of the simulation run. This then corresponds to the highest average firing frequency.</p>
<p>One way to achieve this is to increase the membrane potential of the correct class to $U&gt;U_{\rm thr}$, and that of incorrect classes to $U&lt;U_{\rm thr}$. Applying the target to $U$ serves as a proxy for modulating spiking behavior from $S$.</p>
<p>This can be implemented by taking the softmax of the membrane potential for output neurons, where $C$ is the number of output classes:</p>
<div>
$$p_i[t] = \frac{e^{U_i[t]}}{\sum_{j=0}^{C}e^{U_j[t]}} \tag{15}$$
</div>
<p>The cross-entropy between $p_i$ and the target $y_i \in {0,1}^C$, which is a one-hot target vector, is obtained using:</p>
<div>
$$\mathcal{L}_{CE}[t] = -\sum_{i=0}^Cy_i{\rm log}(p_i[t]) \tag{16}$$
</div>
<p>The practical effect is that the membrane potential of the correct class is encouraged to increase while those of incorrect classes are reduced.</p>
<blockquote>
<p>In effect, this means the correct class is encouraged to fire at all time steps, while incorrect classes are suppressed at all steps. This may not be the most efficient implementation of an SNN, but it is among the simplest.</p></blockquote>
<p>This target is applied at every time step of the simulation, thus also generating a loss at every step. These losses are then summed together at the end of the simulation:</p>
<div>
$$\mathcal{L}_{CE} = \sum_t\mathcal{L}_{CE}[t] \tag{17}$$
</div>
<p>This is just one of many possible ways to apply a loss function to a spiking neural network. A few variety of approaches are available to use in  <a href="https://snntorch.readthedocs.io/en/latest/snntorch.functional.html"><code>snnTorch.functional</code></a>.</p>
<h4 id="code">code</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#========================== Training SNN with static MNIST dataset ==========================</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># imports</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> snntorch <span style="color:#66d9ef">as</span> snn
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> snntorch <span style="color:#f92672">import</span> spikeplot <span style="color:#66d9ef">as</span> splt
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> snntorch <span style="color:#f92672">import</span> spikegen
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> torch
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> torch.nn <span style="color:#66d9ef">as</span> nn
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> torch.utils.data <span style="color:#f92672">import</span> DataLoader
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> torchvision <span style="color:#f92672">import</span> datasets, transforms
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> matplotlib.pyplot <span style="color:#66d9ef">as</span> plt
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> numpy <span style="color:#66d9ef">as</span> np
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> itertools
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#-------------------------- Surrogate LIF neuron -------------------------</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Leaky neuron model, overriding the backward pass with a custom function</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LeakySurrogate</span>(nn<span style="color:#f92672">.</span>Module):
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">__init__</span>(self, beta, threshold<span style="color:#f92672">=</span><span style="color:#ae81ff">1.0</span>):
</span></span><span style="display:flex;"><span>      super(LeakySurrogate, self)<span style="color:#f92672">.</span><span style="color:#a6e22e">__init__</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># initialize decay rate beta and threshold</span>
</span></span><span style="display:flex;"><span>      self<span style="color:#f92672">.</span>beta <span style="color:#f92672">=</span> beta
</span></span><span style="display:flex;"><span>      self<span style="color:#f92672">.</span>threshold <span style="color:#f92672">=</span> threshold
</span></span><span style="display:flex;"><span>      self<span style="color:#f92672">.</span>spike_gradient <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>ATan<span style="color:#f92672">.</span>apply
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># the forward function is called each time we call Leaky</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">forward</span>(self, input_, mem):
</span></span><span style="display:flex;"><span>    spk <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>spike_gradient((mem<span style="color:#f92672">-</span>self<span style="color:#f92672">.</span>threshold))  <span style="color:#75715e"># call the Heaviside function</span>
</span></span><span style="display:flex;"><span>    reset <span style="color:#f92672">=</span> (self<span style="color:#f92672">.</span>beta <span style="color:#f92672">*</span> spk <span style="color:#f92672">*</span> self<span style="color:#f92672">.</span>threshold)<span style="color:#f92672">.</span>detach() <span style="color:#75715e"># remove reset from computational graph as the surrogate gradient should only be applied to  $\partial S/\partial U$, and not $\partial R/\partial U$ </span>
</span></span><span style="display:flex;"><span>    mem <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>beta <span style="color:#f92672">*</span> mem <span style="color:#f92672">+</span> input_ <span style="color:#f92672">-</span> reset <span style="color:#75715e"># Eq (1)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> spk, mem
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Forward pass: Heaviside function</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Backward pass: Override Dirac Delta with the ArcTan function</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">@staticmethod</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">ATan</span>(torch<span style="color:#f92672">.</span>autograd<span style="color:#f92672">.</span>Function):
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">@staticmethod</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">forward</span>(ctx, mem):
</span></span><span style="display:flex;"><span>          spk <span style="color:#f92672">=</span> (mem <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>)<span style="color:#f92672">.</span>float() <span style="color:#75715e"># Heaviside on the forward pass: Eq(2)</span>
</span></span><span style="display:flex;"><span>          ctx<span style="color:#f92672">.</span>save_for_backward(mem)  <span style="color:#75715e"># store the membrane for use in the backward pass</span>
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span> spk
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">@staticmethod</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">backward</span>(ctx, grad_output):
</span></span><span style="display:flex;"><span>          (mem,) <span style="color:#f92672">=</span> ctx<span style="color:#f92672">.</span>saved_tensors  <span style="color:#75715e"># retrieve the membrane potential </span>
</span></span><span style="display:flex;"><span>          grad <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">/</span> (<span style="color:#ae81ff">1</span> <span style="color:#f92672">+</span> (np<span style="color:#f92672">.</span>pi <span style="color:#f92672">*</span> mem)<span style="color:#f92672">.</span>pow_(<span style="color:#ae81ff">2</span>)) <span style="color:#f92672">*</span> grad_output <span style="color:#75715e"># Eqn 5</span>
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span> grad
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>lif1 <span style="color:#f92672">=</span> LeakySurrogate(beta<span style="color:#f92672">=</span><span style="color:#ae81ff">0.9</span>) <span style="color:#75715e"># similar to snn.Leaky(beta=0.9) as all snnTorch neurons apply surrogate gradients (Atan) by default</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#-------------------------- Load MNIST dataset -------------------------</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># dataloader arguments</span>
</span></span><span style="display:flex;"><span>batch_size <span style="color:#f92672">=</span> <span style="color:#ae81ff">128</span>
</span></span><span style="display:flex;"><span>data_path<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;/tmp/data/mnist&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>dtype <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>float
</span></span><span style="display:flex;"><span>device <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>device(<span style="color:#e6db74">&#34;cuda&#34;</span>) <span style="color:#66d9ef">if</span> torch<span style="color:#f92672">.</span>cuda<span style="color:#f92672">.</span>is_available() <span style="color:#66d9ef">else</span> torch<span style="color:#f92672">.</span>device(<span style="color:#e6db74">&#34;cpu&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Define a transform</span>
</span></span><span style="display:flex;"><span>transform <span style="color:#f92672">=</span> transforms<span style="color:#f92672">.</span>Compose([
</span></span><span style="display:flex;"><span>            transforms<span style="color:#f92672">.</span>Resize((<span style="color:#ae81ff">28</span>, <span style="color:#ae81ff">28</span>)),
</span></span><span style="display:flex;"><span>            transforms<span style="color:#f92672">.</span>Grayscale(),
</span></span><span style="display:flex;"><span>            transforms<span style="color:#f92672">.</span>ToTensor(),
</span></span><span style="display:flex;"><span>            transforms<span style="color:#f92672">.</span>Normalize((<span style="color:#ae81ff">0</span>,), (<span style="color:#ae81ff">1</span>,))])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mnist_train <span style="color:#f92672">=</span> datasets<span style="color:#f92672">.</span>MNIST(data_path, train<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, download<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, transform<span style="color:#f92672">=</span>transform)
</span></span><span style="display:flex;"><span>mnist_test <span style="color:#f92672">=</span> datasets<span style="color:#f92672">.</span>MNIST(data_path, train<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>, download<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, transform<span style="color:#f92672">=</span>transform)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Create DataLoaders</span>
</span></span><span style="display:flex;"><span>train_loader <span style="color:#f92672">=</span> DataLoader(mnist_train, batch_size<span style="color:#f92672">=</span>batch_size, shuffle<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, drop_last<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>test_loader <span style="color:#f92672">=</span> DataLoader(mnist_test, batch_size<span style="color:#f92672">=</span>batch_size, shuffle<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, drop_last<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#-------------------------- Define Network model -------------------------</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#............. Network Parameters .............</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Network Architecture</span>
</span></span><span style="display:flex;"><span>num_inputs <span style="color:#f92672">=</span> <span style="color:#ae81ff">28</span><span style="color:#f92672">*</span><span style="color:#ae81ff">28</span>
</span></span><span style="display:flex;"><span>num_hidden <span style="color:#f92672">=</span> <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>num_outputs <span style="color:#f92672">=</span> <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Temporal Dynamics</span>
</span></span><span style="display:flex;"><span>num_steps <span style="color:#f92672">=</span> <span style="color:#ae81ff">25</span>
</span></span><span style="display:flex;"><span>beta <span style="color:#f92672">=</span> <span style="color:#ae81ff">0.95</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#...............Network Class...................</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Define Network</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Net</span>(nn<span style="color:#f92672">.</span>Module):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">__init__</span>(self):
</span></span><span style="display:flex;"><span>        super()<span style="color:#f92672">.</span><span style="color:#a6e22e">__init__</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Initialize layers</span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>fc1 <span style="color:#f92672">=</span> nn<span style="color:#f92672">.</span>Linear(num_inputs, num_hidden)
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>lif1 <span style="color:#f92672">=</span> snn<span style="color:#f92672">.</span>Leaky(beta<span style="color:#f92672">=</span>beta)
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>fc2 <span style="color:#f92672">=</span> nn<span style="color:#f92672">.</span>Linear(num_hidden, num_outputs)
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>lif2 <span style="color:#f92672">=</span> snn<span style="color:#f92672">.</span>Leaky(beta<span style="color:#f92672">=</span>beta)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">forward</span>(self, x):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Initialize hidden states at t=0</span>
</span></span><span style="display:flex;"><span>        mem1 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>lif1<span style="color:#f92672">.</span>init_leaky()
</span></span><span style="display:flex;"><span>        mem2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>lif2<span style="color:#f92672">.</span>init_leaky()
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Record the final layer</span>
</span></span><span style="display:flex;"><span>        spk2_rec <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>        mem2_rec <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> step <span style="color:#f92672">in</span> range(num_steps):
</span></span><span style="display:flex;"><span>            cur1 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>fc1(x)
</span></span><span style="display:flex;"><span>            spk1, mem1 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>lif1(cur1, mem1)
</span></span><span style="display:flex;"><span>            cur2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>fc2(spk1)
</span></span><span style="display:flex;"><span>            spk2, mem2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>lif2(cur2, mem2)
</span></span><span style="display:flex;"><span>            spk2_rec<span style="color:#f92672">.</span>append(spk2)
</span></span><span style="display:flex;"><span>            mem2_rec<span style="color:#f92672">.</span>append(mem2)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> torch<span style="color:#f92672">.</span>stack(spk2_rec, dim<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>), torch<span style="color:#f92672">.</span>stack(mem2_rec, dim<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Load the network onto CUDA if available</span>
</span></span><span style="display:flex;"><span>net <span style="color:#f92672">=</span> Net()<span style="color:#f92672">.</span>to(device)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#-------------------------- Training and Testing SNN -------------------------</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#............. Accuracy metric.............</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># pass data into the network, sum the spikes over time</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># and compare the neuron with the highest number of spikes</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># with the target</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">print_batch_accuracy</span>(data, targets, train<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>):
</span></span><span style="display:flex;"><span>    output, _ <span style="color:#f92672">=</span> net(data<span style="color:#f92672">.</span>view(batch_size, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span>    _, idx <span style="color:#f92672">=</span> output<span style="color:#f92672">.</span>sum(dim<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>)<span style="color:#f92672">.</span>max(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    acc <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>mean((targets <span style="color:#f92672">==</span> idx)<span style="color:#f92672">.</span>detach()<span style="color:#f92672">.</span>cpu()<span style="color:#f92672">.</span>numpy())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> train:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Train set accuracy for a single minibatch: </span><span style="color:#e6db74">{</span>acc<span style="color:#f92672">*</span><span style="color:#ae81ff">100</span><span style="color:#e6db74">:</span><span style="color:#e6db74">.2f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">%&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Test set accuracy for a single minibatch: </span><span style="color:#e6db74">{</span>acc<span style="color:#f92672">*</span><span style="color:#ae81ff">100</span><span style="color:#e6db74">:</span><span style="color:#e6db74">.2f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">%&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">train_printer</span>(
</span></span><span style="display:flex;"><span>    data, targets, epoch,
</span></span><span style="display:flex;"><span>    counter, iter_counter,
</span></span><span style="display:flex;"><span>        loss_hist, test_loss_hist, test_data, test_targets):
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Epoch </span><span style="color:#e6db74">{</span>epoch<span style="color:#e6db74">}</span><span style="color:#e6db74">, Iteration </span><span style="color:#e6db74">{</span>iter_counter<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Train Set Loss: </span><span style="color:#e6db74">{</span>loss_hist[counter]<span style="color:#e6db74">:</span><span style="color:#e6db74">.2f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Test Set Loss: </span><span style="color:#e6db74">{</span>test_loss_hist[counter]<span style="color:#e6db74">:</span><span style="color:#e6db74">.2f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    print_batch_accuracy(data, targets, train<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    print_batch_accuracy(test_data, test_targets, train<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#.............Loss and optimiser .............</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>loss <span style="color:#f92672">=</span> nn<span style="color:#f92672">.</span>CrossEntropyLoss()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Adaptive Moment Estimation (Adam) optimizer best for training RNNs</span>
</span></span><span style="display:flex;"><span>optimizer <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>optim<span style="color:#f92672">.</span>Adam(net<span style="color:#f92672">.</span>parameters(), lr<span style="color:#f92672">=</span><span style="color:#ae81ff">5e-4</span>, betas<span style="color:#f92672">=</span>(<span style="color:#ae81ff">0.9</span>, <span style="color:#ae81ff">0.999</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#............. Training Loop ............</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>num_epochs <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>loss_hist <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>test_loss_hist <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>counter <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Outer training loop</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> epoch <span style="color:#f92672">in</span> range(num_epochs):
</span></span><span style="display:flex;"><span>    iter_counter <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    train_batch <span style="color:#f92672">=</span> iter(train_loader)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Minibatch training loop</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> data, targets <span style="color:#f92672">in</span> train_batch:
</span></span><span style="display:flex;"><span>        data <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>to(device)
</span></span><span style="display:flex;"><span>        targets <span style="color:#f92672">=</span> targets<span style="color:#f92672">.</span>to(device)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># forward pass</span>
</span></span><span style="display:flex;"><span>        net<span style="color:#f92672">.</span>train()
</span></span><span style="display:flex;"><span>        spk_rec, mem_rec <span style="color:#f92672">=</span> net(data<span style="color:#f92672">.</span>view(batch_size, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># initialize the loss &amp; sum over time</span>
</span></span><span style="display:flex;"><span>        loss_val <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>zeros((<span style="color:#ae81ff">1</span>), dtype<span style="color:#f92672">=</span>dtype, device<span style="color:#f92672">=</span>device)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> step <span style="color:#f92672">in</span> range(num_steps):
</span></span><span style="display:flex;"><span>            loss_val <span style="color:#f92672">+=</span> loss(mem_rec[step], targets)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Gradient calculation + weight update</span>
</span></span><span style="display:flex;"><span>        optimizer<span style="color:#f92672">.</span>zero_grad()
</span></span><span style="display:flex;"><span>        loss_val<span style="color:#f92672">.</span>backward()
</span></span><span style="display:flex;"><span>        optimizer<span style="color:#f92672">.</span>step()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Store loss history for future plotting</span>
</span></span><span style="display:flex;"><span>        loss_hist<span style="color:#f92672">.</span>append(loss_val<span style="color:#f92672">.</span>item())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Test set</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">with</span> torch<span style="color:#f92672">.</span>no_grad():
</span></span><span style="display:flex;"><span>            net<span style="color:#f92672">.</span>eval()
</span></span><span style="display:flex;"><span>            test_data, test_targets <span style="color:#f92672">=</span> next(iter(test_loader))
</span></span><span style="display:flex;"><span>            test_data <span style="color:#f92672">=</span> test_data<span style="color:#f92672">.</span>to(device)
</span></span><span style="display:flex;"><span>            test_targets <span style="color:#f92672">=</span> test_targets<span style="color:#f92672">.</span>to(device)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Test set forward pass</span>
</span></span><span style="display:flex;"><span>            test_spk, test_mem <span style="color:#f92672">=</span> net(test_data<span style="color:#f92672">.</span>view(batch_size, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Test set loss</span>
</span></span><span style="display:flex;"><span>            test_loss <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>zeros((<span style="color:#ae81ff">1</span>), dtype<span style="color:#f92672">=</span>dtype, device<span style="color:#f92672">=</span>device)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> step <span style="color:#f92672">in</span> range(num_steps):
</span></span><span style="display:flex;"><span>                test_loss <span style="color:#f92672">+=</span> loss(test_mem[step], test_targets)
</span></span><span style="display:flex;"><span>            test_loss_hist<span style="color:#f92672">.</span>append(test_loss<span style="color:#f92672">.</span>item())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Print train/test loss/accuracy</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> counter <span style="color:#f92672">%</span> <span style="color:#ae81ff">50</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>                train_printer(
</span></span><span style="display:flex;"><span>                    data, targets, epoch,
</span></span><span style="display:flex;"><span>                    counter, iter_counter,
</span></span><span style="display:flex;"><span>                    loss_hist, test_loss_hist,
</span></span><span style="display:flex;"><span>                    test_data, test_targets)
</span></span><span style="display:flex;"><span>            counter <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>            iter_counter <span style="color:#f92672">+=</span><span style="color:#ae81ff">1.</span> 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#............... Plotting Loss History ............</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Plot Loss</span>
</span></span><span style="display:flex;"><span>fig <span style="color:#f92672">=</span> plt<span style="color:#f92672">.</span>figure(facecolor<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;w&#34;</span>, figsize<span style="color:#f92672">=</span>(<span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">5</span>))
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>plot(loss_hist)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>plot(test_loss_hist)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>title(<span style="color:#e6db74">&#34;Loss Curves&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>legend([<span style="color:#e6db74">&#34;Train Loss&#34;</span>, <span style="color:#e6db74">&#34;Test Loss&#34;</span>])
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>xlabel(<span style="color:#e6db74">&#34;Iteration&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>ylabel(<span style="color:#e6db74">&#34;Loss&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>show()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#................ Plotting test accuracy............</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>total <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>correct <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># drop_last switched to False to keep all samples</span>
</span></span><span style="display:flex;"><span>test_loader <span style="color:#f92672">=</span> DataLoader(mnist_test, batch_size<span style="color:#f92672">=</span>batch_size, shuffle<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, drop_last<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> torch<span style="color:#f92672">.</span>no_grad():
</span></span><span style="display:flex;"><span>  net<span style="color:#f92672">.</span>eval()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> data, targets <span style="color:#f92672">in</span> test_loader:
</span></span><span style="display:flex;"><span>    data <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>to(device)
</span></span><span style="display:flex;"><span>    targets <span style="color:#f92672">=</span> targets<span style="color:#f92672">.</span>to(device)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># forward pass</span>
</span></span><span style="display:flex;"><span>    test_spk, _ <span style="color:#f92672">=</span> net(data<span style="color:#f92672">.</span>view(data<span style="color:#f92672">.</span>size(<span style="color:#ae81ff">0</span>), <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># calculate total accuracy</span>
</span></span><span style="display:flex;"><span>    _, predicted <span style="color:#f92672">=</span> test_spk<span style="color:#f92672">.</span>sum(dim<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>)<span style="color:#f92672">.</span>max(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    total <span style="color:#f92672">+=</span> targets<span style="color:#f92672">.</span>size(<span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>    correct <span style="color:#f92672">+=</span> (predicted <span style="color:#f92672">==</span> targets)<span style="color:#f92672">.</span>sum()<span style="color:#f92672">.</span>item()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Total correctly classified test set images: </span><span style="color:#e6db74">{</span>correct<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>total<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Test Set Accuracy: </span><span style="color:#e6db74">{</span><span style="color:#ae81ff">100</span> <span style="color:#f92672">*</span> correct <span style="color:#f92672">/</span> total<span style="color:#e6db74">:</span><span style="color:#e6db74">.2f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">%&#34;</span>)
</span></span></code></pre></div><h3 id="additional-reading-on-sugd">Additional reading on SuGD</h3>
<h5 id="foundational-details">Foundational details</h5>
<ul>
<li>Neftci, Mostafa &amp; Zenke (2019)</li>
<li>Eshraghian, Ward, Neftci et al. (2021/2023)</li>
</ul>
<h5 id="reset-mechanisms">reset mechanisms</h5>
<ul>
<li>Zenke &amp; Vogels (2021)</li>
</ul>
<h5 id="synapticmembrane-dynamics">synaptic/membrane dynamics</h5>
<ul>
<li>Wu et al. (2018),</li>
</ul>
<h5 id="initializations">Initializations</h5>
<ul>
<li>Rossbroich, Gygax &amp; Zenke (2022), &ldquo;Fluctuation-Driven Initialization for Spiking Neural Network Training,&rdquo; Neuromorphic Computing and Engineering</li>
<li>Micheli et al. (2024/2025), &ldquo;Deep Activity Propagation via Weight Initialization in Spiking Neural Networks&rdquo;</li>
</ul>
<h5 id="miscellaneous">miscellaneous</h5>
<ul>
<li>Direct Training High-Performance Deep Spiking Neural Networks: A Review of Theories and Methods</li>
<li>Fractional-order Spiking Neural Network</li>
</ul>
<h3 id="pytorch-tricks">Pytorch tricks</h3>
<h3 id="pytorch-drawbacks">Pytorch drawbacks</h3>
<h3 id="what-are-we-missing-in-the-snns-">what are we missing in the SNNs ?</h3>
<h3 id="references">References</h3>
<ol>
<li><a href="10.1088/2634-4386/ae46d5">Ziqiao Yu et al 2026 Neuromorph. Comput. Eng. 6 014016</a></li>
<li><a href="https://github.com/neural-reckoning/cambridge-fens-chen-summer-school-2026">Dan F M Goodman, 2026 FENS Chen Summer School on Learning with spikes</a></li>
</ol>
]]></content:encoded>
    </item>
  </channel>
</rss>
