<?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>SnnTorch on &lt;raj.sh&#39;log&gt;</title>
    <link>https://shalemrajkumar.github.io/tags/snntorch/</link>
    <description>Recent content in SnnTorch 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/snntorch/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>
  </channel>
</rss>
