Lecture 2B: The Leaky Integrate-and-Fire Cell

The leaky integrate-and-fire (LIF) cell component in ngc-learn is a stepping stone towards working with more biophysical intricate cell components when crafting your neuronal circuit models. This cell is markedly different from the simplified LIF in both its implemented dynamics as well as what modeling routines that it offers, including the fact that it does not offer implicit fixed lateral inhibition like the SLIF does (one would need to explicitly model the lateral inhibition as a separate population of LIF cells, as we do in the Diehl and Cook model museum spiking network). Furthermore, using this neuronal cell is a useful transition to using the more complicated and biophysically more accurate neuronal models such as the adaptive exponential integrator cell or the Izhikevich cell.

Instantiating the LIF Neuronal Cell

To implement a single-component dynamical system made up of a single LIF cell, you would write code akin to the following:

from jax import numpy as jnp, random, jit

from ngclearn import Context, MethodProcess
## import model-specific mechanisms
from ngclearn.components.neurons.spiking.LIFCell import LIFCell
from ngclearn.utils.viz.spike_plot import plot_spiking_neuron

## create seeding keys (JAX-style)
dkey = random.PRNGKey(1234)
dkey, *subkeys = random.split(dkey, 3)

T = 100  ## number of simulation steps to run
dt = 0.1  # ms ## compute integration time constant
V_thr = -52.  ## mV
V_rest = -65  ## mV
tau_m = 100.

## create simple system with only one AdEx
with Context("Model") as model:
    cell = LIFCell(
        "z0", n_units=1, tau_m=tau_m, resist_m=tau_m/dt, thr=V_thr, v_rest=V_rest, v_reset=-60., tau_theta=300., 
        theta_plus=0.05, refract_time=2., key=subkeys[0]
    )

    ## create and compile core simulation commands
    advance_process = (MethodProcess("advance")
                       >> cell.advance_state)

    reset_process = (MethodProcess("reset")
                     >> cell.reset)


## set up non-compiled utility commands
def clamp(x):
    cell.j.set(x)

Simulating the LIF on Stepped Constant Electrical Current

Given our single-LIF dynamical system above, let us write some code to use our LIF node and visualize the resultant spiking pattern super-imposed over its membrane (voltage) potential by feeding into it a step current, where the electrical current j starts at \(0\) then switches to \(0.3\) at \(t = 10\) ms (much as we did for the SLIF component in the previous lesson). We craft the simulation portion of our code like so:

# create a synthetic electrical step current
current = jnp.concatenate((jnp.zeros((1,10)), jnp.ones((1,190)) * 0.3), axis=1)

curr_in = []
mem_rec = []
spk_rec = []

reset_process.run()
for ts in range(current.shape[1]):
    j_t = jnp.expand_dims(current[0,ts], axis=0) ## get data at time ts
    clamp(j_t)
    advance_process.run(t=ts*1., dt=dt)
    ## naively extract simple statistics at time ts and print them to I/O
    v = cell.v.get()
    s = cell.s.get()
    curr_in.append(j_t)
    mem_rec.append(v)
    spk_rec.append(s)
    print("\r {}: s {} ; v {}".format(ts, s, v), end="")
print()

Then, we can plot the input current, the neuron’s voltage v, and its output spikes as follows:

import numpy as np
curr_in = np.squeeze(np.asarray(curr_in))
mem_rec = np.squeeze(np.asarray(mem_rec))
spk_rec = np.squeeze(np.asarray(spk_rec))
plot_spiking_neuron(
    curr_in, mem_rec, spk_rec, None, dt, thr_line=V_thr, min_mem_val=V_rest-1., max_mem_val=V_thr+2., spike_loc=V_thr, 
    spike_spr=0.5, title="LIF-Node: Constant Electrical Input", fname="lif_plot.jpg"
)

which should produce the following plot (saved to disk):

../../_images/lif_plot.jpg

As we might observe, the LIF operates very differently from the SLIF, notably that its dynamics live in the different space of values (one aspect of the SLIF is that its dynamics are effectively normalized/configured to live a non-negative membrane potential number space), specifically values that are a bit better aligned with those observed in experimental neuroscience. While more biophysically more accurate, the LIF typically involves consideration of multiple additional hyper-parameters/simulation coefficients, including the resting membrane potential value v_rest and the reset membrane value v_reset (upon occurrence of a spike/emitted action potential); the SLIF, in contrast, assumed a v_reset = v_reset = 0.. Note that the LIF’s tau_theta and theta_plus coefficients govern its particular adaptive threshold, which is a particular increment variable (one per cell in the LIF component) that gets adjusted according to its own dynamics and added to the fixed constant threshold thr, i.e., the threshold that a cell’s membrane potential must exceed for a spike to be emitted.

The LIF cell component is particularly useful when more flexibility is required/desired in setting up neuronal dynamics, particularly when attempting to match various mathematical models that have been proposed in computational neuroscience. This benefit comes at the greater cost of additional tuning and experimental planning, whereas the SLIF can be a useful go-to initial spiking cell for building certain spiking models such as those proposed in machine intelligence research (we demonstrate one such use-case in the context of the feedback alignment-trained spiking network that we offer in the model museum).