Tutorial 1
In this tutorial, we are going to build an SNN capable of classifying MNIST by copying the weights obtained by training the following simple ANN using TensorFlow:
Clearly, this is far from a state of the art architecture, but it still achieves 97.6% accuracy on MNIST.
Install
Download wheel file
[3]:
if "google.colab" in str(get_ipython()):
!gdown 1V_GzXUDzcFz9QDIpxAD8QNEglcSipssW
!pip install pygenn-5.0.0-cp310-cp310-linux_x86_64.whl
%env CUDA_PATH=/usr/local/cuda
!rm -rf /content/ml_genn
!git clone https://github.com/genn-team/ml_genn.git --branch genn_5 -c advice.detachedHead=false
!pip install ./ml_genn/ml_genn
Downloading... From: https://drive.google.com/uc?id=1V_GzXUDzcFz9QDIpxAD8QNEglcSipssW To: /content/pygenn-5.0.0-cp310-cp310-linux_x86_64.whl
0% 0.00/8.29M [00:00<?, ?B/s]
- 100% 8.29M/8.29M [00:00<00:00, 179MB/s]
Processing ./pygenn-5.0.0-cp310-cp310-linux_x86_64.whl Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (1.25.2) Requirement already satisfied: deprecated in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (1.2.14) Requirement already satisfied: psutil in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (5.9.5) Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.10/dist-packages (from deprecated->pygenn==5.0.0) (1.14.1) pygenn is already installed with the same version as the provided wheel. Use –force-reinstall to force an installation of the wheel. env: CUDA_PATH=/usr/local/cuda Cloning into ‘ml_genn’… remote: Enumerating objects: 8238, done. remote: Counting objects: 100% (545/545), done. remote: Compressing objects: 100% (320/320), done. remote: Total 8238 (delta 270), reused 302 (delta 222), pack-reused 7693 Receiving objects: 100% (8238/8238), 37.42 MiB | 17.44 MiB/s, done. Resolving deltas: 100% (5502/5502), done. Processing ./ml_genn/ml_genn
Preparing metadata (setup.py) … done
Requirement already satisfied: pygenn<6.0.0,>=5.0.0 in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (5.0.0) Collecting enum-compat (from ml-genn==2.1.0)
Using cached enum_compat-0.0.3-py3-none-any.whl (1.3 kB)
Requirement already satisfied: tqdm>=4.27.0 in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (4.66.2) Requirement already satisfied: deprecated in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (1.2.14) Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from pygenn<6.0.0,>=5.0.0->ml-genn==2.1.0) (1.25.2) Requirement already satisfied: psutil in /usr/local/lib/python3.10/dist-packages (from pygenn<6.0.0,>=5.0.0->ml-genn==2.1.0) (5.9.5) Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.10/dist-packages (from deprecated->ml-genn==2.1.0) (1.14.1) Building wheels for collected packages: ml-genn
Building wheel for ml-genn (setup.py) … done Created wheel for ml-genn: filename=ml_genn-2.1.0-py3-none-any.whl size=109412 sha256=0ece5fc0211f2162cc9869025d27cb23b9a99eca2430555ea9891a76922fbfa0 Stored in directory: /tmp/pip-ephem-wheel-cache-ev0ig411/wheels/3f/cf/27/0e9dec4bb1be2afac4b38c2dfb4ce0bc164ce1ecb32b6f91b8
Successfully built ml-genn Installing collected packages: enum-compat, ml-genn Successfully installed enum-compat-0.0.3 ml-genn-2.1.0 </pre>
0% 0.00/8.29M [00:00<?, ?B/s]
- 100% 8.29M/8.29M [00:00<00:00, 179MB/s]
Processing ./pygenn-5.0.0-cp310-cp310-linux_x86_64.whl Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (1.25.2) Requirement already satisfied: deprecated in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (1.2.14) Requirement already satisfied: psutil in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (5.9.5) Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.10/dist-packages (from deprecated->pygenn==5.0.0) (1.14.1) pygenn is already installed with the same version as the provided wheel. Use –force-reinstall to force an installation of the wheel. env: CUDA_PATH=/usr/local/cuda Cloning into ‘ml_genn’{ldots} remote: Enumerating objects: 8238, done. remote: Counting objects: 100% (545/545), done. remote: Compressing objects: 100% (320/320), done. remote: Total 8238 (delta 270), reused 302 (delta 222), pack-reused 7693 Receiving objects: 100% (8238/8238), 37.42 MiB | 17.44 MiB/s, done. Resolving deltas: 100% (5502/5502), done. Processing ./ml_genn/ml_genn
Preparing metadata (setup.py) {ldots} done
Requirement already satisfied: pygenn<6.0.0,>=5.0.0 in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (5.0.0) Collecting enum-compat (from ml-genn==2.1.0)
Using cached enum_compat-0.0.3-py3-none-any.whl (1.3 kB)
Requirement already satisfied: tqdm>=4.27.0 in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (4.66.2) Requirement already satisfied: deprecated in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (1.2.14) Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from pygenn<6.0.0,>=5.0.0->ml-genn==2.1.0) (1.25.2) Requirement already satisfied: psutil in /usr/local/lib/python3.10/dist-packages (from pygenn<6.0.0,>=5.0.0->ml-genn==2.1.0) (5.9.5) Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.10/dist-packages (from deprecated->ml-genn==2.1.0) (1.14.1) Building wheels for collected packages: ml-genn
Building wheel for ml-genn (setup.py) {ldots} done Created wheel for ml-genn: filename=ml_genn-2.1.0-py3-none-any.whl size=109412 sha256=0ece5fc0211f2162cc9869025d27cb23b9a99eca2430555ea9891a76922fbfa0 Stored in directory: /tmp/pip-ephem-wheel-cache-ev0ig411/wheels/3f/cf/27/0e9dec4bb1be2afac4b38c2dfb4ce0bc164ce1ecb32b6f91b8
Successfully built ml-genn Installing collected packages: enum-compat, ml-genn Successfully installed enum-compat-0.0.3 ml-genn-2.1.0 end{sphinxVerbatim}
0% 0.00/8.29M [00:00<?, ?B/s]
- 100% 8.29M/8.29M [00:00<00:00, 179MB/s]
Processing ./pygenn-5.0.0-cp310-cp310-linux_x86_64.whl Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (1.25.2) Requirement already satisfied: deprecated in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (1.2.14) Requirement already satisfied: psutil in /usr/local/lib/python3.10/dist-packages (from pygenn==5.0.0) (5.9.5) Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.10/dist-packages (from deprecated->pygenn==5.0.0) (1.14.1) pygenn is already installed with the same version as the provided wheel. Use –force-reinstall to force an installation of the wheel. env: CUDA_PATH=/usr/local/cuda Cloning into ‘ml_genn’… remote: Enumerating objects: 8238, done.[K remote: Counting objects: 100% (545/545), done.[K remote: Compressing objects: 100% (320/320), done.[K remote: Total 8238 (delta 270), reused 302 (delta 222), pack-reused 7693[K Receiving objects: 100% (8238/8238), 37.42 MiB | 17.44 MiB/s, done. Resolving deltas: 100% (5502/5502), done. Processing ./ml_genn/ml_genn
Preparing metadata (setup.py) … [?25l[?25hdone
Requirement already satisfied: pygenn<6.0.0,>=5.0.0 in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (5.0.0) Collecting enum-compat (from ml-genn==2.1.0)
Using cached enum_compat-0.0.3-py3-none-any.whl (1.3 kB)
Requirement already satisfied: tqdm>=4.27.0 in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (4.66.2) Requirement already satisfied: deprecated in /usr/local/lib/python3.10/dist-packages (from ml-genn==2.1.0) (1.2.14) Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from pygenn<6.0.0,>=5.0.0->ml-genn==2.1.0) (1.25.2) Requirement already satisfied: psutil in /usr/local/lib/python3.10/dist-packages (from pygenn<6.0.0,>=5.0.0->ml-genn==2.1.0) (5.9.5) Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.10/dist-packages (from deprecated->ml-genn==2.1.0) (1.14.1) Building wheels for collected packages: ml-genn
Building wheel for ml-genn (setup.py) … [?25l[?25hdone Created wheel for ml-genn: filename=ml_genn-2.1.0-py3-none-any.whl size=109412 sha256=0ece5fc0211f2162cc9869025d27cb23b9a99eca2430555ea9891a76922fbfa0 Stored in directory: /tmp/pip-ephem-wheel-cache-ev0ig411/wheels/3f/cf/27/0e9dec4bb1be2afac4b38c2dfb4ce0bc164ce1ecb32b6f91b8
Successfully built ml-genn Installing collected packages: enum-compat, ml-genn Successfully installed enum-compat-0.0.3 ml-genn-2.1.0
Download pre-trained weights
[4]:
!gdown 1cmNL8W0QZZtn3dPHiOQnVjGAYTk6Rhpc
!gdown 131lCXLEH6aTXnBZ9Nh4eJLSy5DQ6LKSF
Downloading...
From: https://drive.google.com/uc?id=1cmNL8W0QZZtn3dPHiOQnVjGAYTk6Rhpc
To: /content/weights_0_1.npy
100% 402k/402k [00:00<00:00, 140MB/s]
Downloading...
From: https://drive.google.com/uc?id=131lCXLEH6aTXnBZ9Nh4eJLSy5DQ6LKSF
To: /content/weights_1_2.npy
100% 5.25k/5.25k [00:00<00:00, 21.5MB/s]
Install MNIST package
[5]:
!pip install mnist
Collecting mnist
Downloading mnist-0.2.2-py2.py3-none-any.whl (3.5 kB)
Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from mnist) (1.25.2)
Installing collected packages: mnist
Successfully installed mnist-0.2.2
Build model
Import standard modules and required mlGeNN classes
[6]:
import mnist
import numpy as np
from ml_genn import InputLayer, Layer, SequentialNetwork
from ml_genn.compilers import InferenceCompiler
from ml_genn.neurons import IntegrateFire, IntegrateFireInput
from ml_genn.connectivity import Dense
from ml_genn.callbacks import SpikeRecorder
Because our network is entirely feedforward, we can define it as a SequentialNetwork where each layer is automatically connected to the previous layer. We are going to convert MNIST digits to spikes by simply treating the intensity of each pixel (multiplied by a scaling factor) as the input to an integrate-and-fire neuron. For our hidden and output layers we are going use very simple Integrate-and-Fire neurons which best match the transfer function of the ReLU neurons our ANN was trained
with. Finally, we are going to read classifications from our output layer by simply counting spikes.
[7]:
# Create sequential model
network = SequentialNetwork()
with network:
input = InputLayer(IntegrateFireInput(v_thresh=5.0), 784)
Layer(Dense(weight=np.load("weights_0_1.npy")),
IntegrateFire(v_thresh=5.0))
output = Layer(Dense(weight=np.load("weights_1_2.npy")),
IntegrateFire(v_thresh=5.0, readout="spike_count"))
In mlGeNN, in order to turn an abstract network description into something that can actually be used for training or inference you use a compiler class. Here, as we are performing inference, we use the InferenceCompiler and specify batch size and how many timesteps to evaluate each example for.
[8]:
compiler = InferenceCompiler(dt=1.0, batch_size=128,
evaluate_timesteps=100)
compiled_net = compiler.compile(network)
We can then load the MNIST testing data and evaluate the compiled pre-trained network on it. The normal grayscale values of the MNIST dataset are passed into the input layer, scaled by a factor (here 0.01) which is translated into spikes by the IntegrateFireInput layer. The testing labels are passed as a simple array of of 0 to 9, corresponding to the default SparseCategorical loss type.
[9]:
# Load testing data
testing_images = np.reshape(mnist.test_images(), (-1, 784))
testing_labels = mnist.test_labels()
with compiled_net:
# Evaluate model on numpy dataset
metrics, _ = compiled_net.evaluate({input: testing_images * 0.01},
{output: testing_labels})