{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"*Copyright (C) 2021 Intel Corporation*
\n",
"*SPDX-License-Identifier: BSD-3-Clause*
\n",
"*See: https://spdx.org/licenses/*\n",
"\n",
"---\n",
"\n",
"# Custom Learning Rules\n",
"\n",
"_**Motivation**: In this tutorial, we will demonstrate usage of a software model of Loihi's learning engine, exposed in Lava. This involves the LearningRule object for learning rule and other learning-related information encapsulation and the LearningDense Lava Process modelling learning-enabled connections._\n",
"\n",
"#### This tutorial assumes that you:\n",
"- have the [Lava framework installed](../../in_depth/tutorial01_installing_lava.ipynb \"Tutorial on Installing Lava\")\n",
"- are familiar with the [Process concept in Lava](../../in_depth/tutorial02_processes.ipynb \"Tutorial on Processes\")\n",
"- are familiar with the [ProcessModel concept in Lava](../../in_depth/tutorial02_process_models.ipynb \"Tutorial on ProcessModels\")\n",
"- are familiar with how to [connect Lava Processes](../../in_depth/tutorial05_connect_processes.ipynb \"Tutorial on connecting Processes\")\n",
"\n",
"This tutorial gives a bird's-eye view of how to develop custom learning rules for Loihi. For this purpose, we will create a network of LIF and Dense processes with one plastic connection and generate frozen patterns of activity. We can easily choose between a floating point simulation of the learning engine and a fixed point simulation, which approximates the behavior on the Loihi neuromorphic hardware. We also will create monitors to observe the behavior of the weights and activity traces of the neurons and learning rules."
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"\n",
"## 2. Loihi's learning engine\n",
"Loihi provides a programmable learning engine that can evolve synaptic state variables over time as a function of several locally available parameters and the equations relating input terms to output synaptic target variables are called _learning rules_. These learning rule equations are highly configurable but are constrained to a sum of products form.\n",
"\n",
"#### Epoch-based updates\n",
"For efficiency reasons, trace and synaptic variable updates proceed in learning epochs with a length of $t_{epoch}$ time steps. Within an epoch, spike events are recorded but trace and synaptic variable updates are only computed and applied with a slight delay at the end of the epoch. This delayed application will theoretically not have any impact as long as there is only one spike per synapse and per epoch.\n",
"\n",
"#### Synaptic variables\n",
"For each synapse, Loihi's computational model defines a set of three synaptic state variables that can be modified by the learning engine. These are :\n",
"- Weight $W$, representing _synaptic efficacy_.\n",
"- Delay $D$, representing _synaptic delay_.\n",
"- Tag $T$, which is an additional synaptic variable that allows for constructing more complex learning dynamics.\n",
"\n",
"#### Learning rules\n",
"The amount of change by which a target synaptic variable is updated at the end of a learning epoch is given by the learning rule associated with said variable. The rules are specified in sum-of-products form:\n",
"\n",
"$$Z \\in \\{W, D, T\\}$$\n",
"\n",
"$$dZ = \\sum_{i = 1}^{N_P} D_i \\cdot \\left[ S_i \\cdot \\prod_{j = 1}^{N_F^i} F_{i, j} \\right]$$\n",
"\n",
"The learning rule consists in a _sum_ of $N_P$ _products_. Each $i$'th product is composed of a dependency operator $D_i$, a scaling factor $S_i$ and a sub-product of $N_F^i$ factors with $F_{i, j}$ denoting the $j$'th factor of the current product.\n",
"\n",
"#### Dependencies\n",
"Each product is associated with a _dependency_ operator $D_i$ that conditions the evaluation of a product on the presence of a pre- or post-synaptic spike during the past epoch or evaluates a product unconditionally every other epoch. $D_i$ also determines at what time step during an epoch, all trace variables in the associated product are evaluated. The table below lists the various dependency operators and their behavior:\n",
"\n",
"| Dependency | $t_{eval}$ | Description |\n",
"| :- | :- | :- |\n",
"| $x_0$ | $t_x$ | Conditioned on at least one pre-synaptic spike during epoch. |\n",
"| $y_0$ | $t_y$ | Conditioned on at least one post-synaptic spike during epoch. |\n",
"| $u_{\\kappa}$ | $t_{epoch}$ | Unconditionally executed every $\\kappa \\cdot t_{epoch}$ time steps. |\n",
"\n",
"#### Scaling factors\n",
"Each product is also associated with a _scaling factor_ (constant literal) that is given in mantissa/exponent form :\n",
"\n",
"$$S_i = S_i^{mant} \\cdot 2^{S_i^{exp}}$$\n",
"\n",
"#### Factors\n",
"Furthermore, Loihi provides a set of locally available quantities which can be used in learning rule to derive synaptic variable updates. The table below lists the various types of variables whose value $F_{i, j}$ can assume:\n",
"\n",
"| Factor | Description |\n",
"| :- | :- |\n",
"| $$x_0 + C$$ | Pre-synaptic spike. |\n",
"| $$x_1(t_{eval}) + C$$ | Pre-synaptic trace $x_1$. |\n",
"| $$x_2(t_{eval}) + C$$ | Pre-synaptic trace $x_2$. |\n",
"| $$y_0 + C$$ | Post-synaptic spike. |\n",
"| $$y_1(t_{eval}) + C$$ | Post-synaptic trace $y_1$. |\n",
"| $$y_2(t_{eval}) + C$$ | Post-synaptic trace $y_2$. |\n",
"| $$y_3(t_{eval}) + C$$ | Post-synaptic trace $y_3$. |\n",
"| $$W + C$$ | Weight synaptic variable $W$. |\n",
"| $$D + C$$ | Delay synaptic variable $D$. |\n",
"| $$T + C$$ | Tag synaptic variable $T$. |\n",
"| $$sgn(W + C)$$ | Sign of $W$. |\n",
"| $$sgn(D + C)$$ | Sign of $D$. |\n",
"| $$sgn(T + C)$$ | Sign of $T$. |\n",
"| $$C$$ | Constant term _(variant 1)_. |\n",
"| $$C^{mant} \\cdot 2^{C^{exp}}$$ | Constant term _(variant 2)_. |\n",
"\n",
"#### Traces\n",
"Traces are low-pass filtered versions of spike train that are typically used in online implementations of [Spike-Timing Dependent Plasticity (STDP)](http://www.scholarpedia.org/article/Spike-timing_dependent_plasticity \"Spike-Timing Dependent Plasticity\"). For each synapse, Loihi provides a set of **2 pre-synaptic traces** $\\{x_1, x_2\\}$ and **3 post-synaptic traces** $\\{y_1, y_2, y_3\\}$. The dynamics of an ideal spike trace is given by :\n",
"\n",
"$$z \\in \\{x_1, x_2, y_1, y_2, y_3\\}$$\n",
"\n",
"$$z(t) = z(t_{k-1}) \\cdot exp(- \\frac{t-t_{k-1}}{\\tau^z}) + \\xi^z \\cdot \\delta^{z}(t - t_k)$$\n",
"\n",
"Here, the set $\\{t_k\\}$ are successive spike times at which the trace accumulates the spike impulse value $\\xi^{z}$ while $\\tau^z$ governs the speed of exponential decay between spike events. Finally, $\\delta^z$ denotes the raw spike train associated with the trace $z$.\n",
"\n",
"#### Example: Basic pair-based STDP\n",
"\n",
"$$dW = S_1 \\cdot x_0 \\cdot y_1 + S_2 \\cdot y_0 \\cdot x_1$$\n",
"\n",
"where $S_1 < 0$ and $S_2 > 0$."
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Create custom learning rule\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
},
"tags": []
},
"source": [
"#### Instantiating LearningRule\n",
"\n",
"Next, we define a learning rule ($dw$) for the _weight_ synaptic variable. The learning rule is first written in string format and passed to the LearningRule object as instantiation argument. This string learning rule will get internally parsed, transformed into and stored as a _ProductSeries_, which is a custom data structure that is particularly well-suited for sum-of-products representation.\n",
"\n",
"Here, we use the basic pair-based STDP learning rule defined by :\n",
"\n",
"$$dw = -2 \\cdot x_0 \\cdot y_1 + 2 \\cdot y_0 \\cdot x_1$$\n",
"\n",
"### Parameters"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"As a reminder, the main function of the LearningRule object is not only to encapsulate learning rules, but also other learning-related such as trace impulse values and decay constants for all of the traces as well as the length of the learning epoch. The following table lists the different fields of the LearningRule class:\n",
"\n",
"| Field | Python type | Description |\n",
"| :- | :- | :- |\n",
"| `dw` | ProductSeries | Learning rule targetting the synaptic variable $W$. |\n",
"| `dd` | ProductSeries | Learning rule targetting the synaptic variable $D$. |\n",
"| `dt` | ProductSeries | Learning rule targetting the synaptic variable $T$. |\n",
"| `x1_impulse` | float | Trace impulse value associated with $x_1$ trace. |\n",
"| `x1_tau` | int | Trace decay constant associated with $x_1$ trace. |\n",
"| `x2_impulse` | float | Trace impulse value associated with $x_2$ trace. |\n",
"| `x2_tau` | int | Trace decay constant associated with $x_2$ trace. |\n",
"| `y1_impulse` | float | Trace impulse value associated with $y_1$ trace. |\n",
"| `y1_tau` | int | Trace decay constant associated with $y_1$ trace. |\n",
"| `y2_impulse` | float | Trace impulse value associated with $y_2$ trace. |\n",
"| `y2_tau` | int | Trace decay constant associated with $y_2$ trace. |\n",
"| `y3_impulse` | float | Trace impulse value associated with $y_3$ trace. |\n",
"| `y3_tau` | int | Trace decay constant associated with $y_3$ trace. |\n",
"| `t_epoch` | int | Learning epoch length. |"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"from lava.magma.core.learning.learning_rule import Loihi2FLearningRule\n",
"\n",
"# Learning rule coefficient\n",
"on_pre_stdp = -2\n",
"on_post_stdp = 2\n",
"\n",
"learning_rate = 1\n",
"\n",
"# Trace decay constants\n",
"x1_tau = 10\n",
"y1_tau = 10\n",
"\n",
"# Impulses\n",
"x1_impulse = 16\n",
"y1_impulse = 16\n",
"\n",
"# Epoch length\n",
"t_epoch = 2\n",
"\n",
"# Define dw as string\n",
"dw = f\"{learning_rate} * ({on_pre_stdp}) * x0 * y1 +\" \\\n",
" f\"{learning_rate} * {on_post_stdp} * y0 * x1\""
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"# Create custom LearningRule\n",
"stdp = Loihi2FLearningRule(dw=dw,\n",
" x1_impulse=x1_impulse,\n",
" x1_tau=x1_tau,\n",
" y1_impulse=y1_impulse,\n",
" y1_tau=y1_tau,\n",
" t_epoch=t_epoch)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Network parameters"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"# Set this tag to \"fixed_pt\" or \"floating_pt\" to choose the corresponding models.\n",
"SELECT_TAG = \"fixed_pt\"\n",
"\n",
"# LIF parameters\n",
"if SELECT_TAG == \"fixed_pt\":\n",
" du = 4095\n",
" dv = 4095\n",
"elif SELECT_TAG == \"floating_pt\":\n",
" du = 1\n",
" dv = 1\n",
"vth = 240\n",
"\n",
"# Number of neurons per layer\n",
"num_neurons = 1\n",
"shape_lif = (num_neurons, )\n",
"shape_conn = (num_neurons, num_neurons)\n",
"\n",
"# Connection parameters\n",
"\n",
"# SpikePattern -> LIF connection weight\n",
"wgt_inp = np.eye(num_neurons) * 250\n",
"\n",
"# LIF -> LIF connection initial weight (learning-enabled)\n",
"wgt_plast_conn = np.full(shape_conn, 50)\n",
" \n",
"# Number of simulation time steps\n",
"num_steps = 200\n",
"time = list(range(1, num_steps + 1))\n",
"\n",
"# Spike times\n",
"spike_prob = 0.03\n",
"\n",
"# Create spike rasters\n",
"np.random.seed(123)\n",
"spike_raster_pre = np.zeros((num_neurons, num_steps))\n",
"np.place(spike_raster_pre, np.random.rand(num_neurons, num_steps) < spike_prob, 1)\n",
"\n",
"spike_raster_post = np.zeros((num_neurons, num_steps))\n",
"np.place(spike_raster_post, np.random.rand(num_neurons, num_steps) < spike_prob, 1)"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Create Network\n",
"The following diagram depics the Lava Process architecture used in this tutorial. It consists of:\n",
"- 2 Constant pattern generators for injection spike trains to LIF neurons.\n",
"- 2 _LIF_ Processes representing pre- and post-synaptic Leaky Integrate-and-Fire neurons.\n",
"- 1 _Dense_ Process representing learning-enable connection between LIF neurons.\n",
"\n",
">**Note:** \n",
"All neuronal population (spike generator, LIF) are composed of only 1 neuron in this tutorial."
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"#### The plastic connection Process\n",
"We now instantiate our plastic Dense process. The Dense Process provides the following Vars and Ports relevant for plasticity:\n",
"\n",
"| Component | Name | Description |\n",
"| :- | :- | :- |\n",
"| **InPort** | `s_in_bap` | Receives spikes from post-synaptic neurons.\n",
"| **Var** | `tag_2` | Delay synaptic variable.\n",
"| | `tag_1` | Tag synaptic variable.\n",
"| | `x0` | State of $x_0$ dependency.\n",
"| | `tx` | Within-epoch spike times of pre-synaptic neurons.\n",
"| | `x1` | State of $x_1$ trace.\n",
"| | `x2` | State of $x_2$ trace.\n",
"| | `y0` | State of $y_0$ dependency.\n",
"| | `ty` | Within-epoch spike times of post-synaptic neurons.\n",
"| | `y1` | State of $y_1$ trace.\n",
"| | `y2` | State of $y_2$ trace.\n",
"| | `y3` | State of $y_3$ trace.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"pycharm": {
"name": "#%%\n"
},
"tags": []
},
"outputs": [],
"source": [
"from lava.proc.lif.process import LIF\n",
"from lava.proc.io.source import RingBuffer\n",
"from lava.proc.dense.process import LearningDense, Dense"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"# Create input devices\n",
"pattern_pre = RingBuffer(data=spike_raster_pre.astype(int))\n",
"pattern_post = RingBuffer(data=spike_raster_post.astype(int))\n",
"\n",
"# Create input connectivity\n",
"conn_inp_pre = Dense(weights=wgt_inp)\n",
"conn_inp_post = Dense(weights=wgt_inp)\n",
"\n",
"# Create pre-synaptic neurons\n",
"lif_pre = LIF(u=0,\n",
" v=0,\n",
" du=du,\n",
" dv=du,\n",
" bias_mant=0,\n",
" bias_exp=0,\n",
" vth=vth,\n",
" shape=shape_lif,\n",
" name='lif_pre')\n",
"\n",
"# Create plastic connection\n",
"plast_conn = LearningDense(weights=wgt_plast_conn,\n",
" learning_rule=stdp,\n",
" name='plastic_dense')\n",
"\n",
"# Create post-synaptic neuron\n",
"lif_post = LIF(u=0,\n",
" v=0,\n",
" du=du,\n",
" dv=du,\n",
" bias_mant=0,\n",
" bias_exp=0,\n",
" vth=vth,\n",
" shape=shape_lif,\n",
" name='lif_post')\n",
"\n",
"# Connect network\n",
"pattern_pre.s_out.connect(conn_inp_pre.s_in)\n",
"conn_inp_pre.a_out.connect(lif_pre.a_in)\n",
"\n",
"pattern_post.s_out.connect(conn_inp_post.s_in)\n",
"conn_inp_post.a_out.connect(lif_post.a_in)\n",
"\n",
"lif_pre.s_out.connect(plast_conn.s_in)\n",
"plast_conn.a_out.connect(lif_post.a_in)\n",
"\n",
"# Connect back-propagating actionpotential (BAP)\n",
"lif_post.s_out.connect(plast_conn.s_in_bap)"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Create monitors to observe traces"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"pycharm": {
"name": "#%%\n"
},
"tags": []
},
"outputs": [],
"source": [
"from lava.proc.monitor.process import Monitor"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"pycharm": {
"name": "#%%\n"
},
"tags": []
},
"outputs": [],
"source": [
"# Create monitors\n",
"mon_pre_trace = Monitor()\n",
"mon_post_trace = Monitor()\n",
"mon_pre_spikes = Monitor()\n",
"mon_post_spikes = Monitor()\n",
"mon_weight = Monitor()\n",
"\n",
"# Connect monitors\n",
"mon_pre_trace.probe(plast_conn.x1, num_steps)\n",
"mon_post_trace.probe(plast_conn.y1, num_steps)\n",
"mon_pre_spikes.probe(lif_pre.s_out, num_steps)\n",
"mon_post_spikes.probe(lif_post.s_out, num_steps)\n",
"mon_weight.probe(plast_conn.weights, num_steps)"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
},
"tags": []
},
"source": [
"### Running"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"from lava.magma.core.run_conditions import RunSteps\n",
"from lava.magma.core.run_configs import Loihi2SimCfg"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"# Running\n",
"pattern_pre.run(condition=RunSteps(num_steps=num_steps), run_cfg=Loihi2SimCfg(select_tag=SELECT_TAG))"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"# Get data from monitors\n",
"pre_trace = mon_pre_trace.get_data()['plastic_dense']['x1']\n",
"post_trace = mon_post_trace.get_data()['plastic_dense']['y1']\n",
"pre_spikes = mon_pre_spikes.get_data()['lif_pre']['s_out']\n",
"post_spikes = mon_post_spikes.get_data()['lif_post']['s_out']\n",
"weights = mon_weight.get_data()['plastic_dense']['weights'][:, :, 0]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"# Stopping\n",
"pattern_pre.stop()"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Results\n",
"\n",
"Now, we can take a look at the results of the simulation. "
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"pycharm": {
"name": "#%%\n"
},
"tags": []
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"#### Plot spike trains"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"