<?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>Training on &lt;raj.sh&#39;log&gt;</title>
    <link>https://shalemrajkumar.github.io/tags/training/</link>
    <description>Recent content in Training 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/training/index.xml" rel="self" type="application/rss+xml" />
    <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>
