Computer Science 252
Neuromorphic Computing
Group Exercise #1: Building Spiking Networks with the Nengo Simulator
Objectives
-
- Become familiar with the Nengo simulator for the Neural Engineering Framework (NEF).
- Use Nengo SPA to build a spiking neural network for Kanerva’s “Dollar of Mexico” example.
As usual when learning a new software tool, we will take a programming-by-example (copy/paste/modify) approach. Although Nengo is a Python library than can be used in any Python environment (IDLE, Jupyter, etc.), we will use the Nengo Simulator for immediate, real-time visualization of our spiking networks in our web browser.
Part 1: Implementing the Lorenz attractor in Nengo
The Three Principles of the NEF give us a broad variety of dynamical (time-varying) systems to model. One of the most interesting dynamical systems is a chaotic system, where the state of the system, like the weather, is very hard to predict (Such systems are the focus a popular W&L spring-term course.) Chaotic systems typically follow a pattern combining regularity and apparent randomness, called a chaotic (or “strange”) attractor. A well-studied chaotic system is the Lorenz system, which represents the <x,y,z>values of a point in three-dimensional space using a simple set of differential equations (new value = old value + change).
To implement the Lorenz attractor in the Nengo simulator, I found it easiest to work from this animated image, which shows you both the graphical simulation (left side) and Python code (right side; make sure to shrink image so you see the whole thing!) To get started, open a terminal window in the folder where you want to work (desktop is fine), and do:
nengo nef_lorenz.py
Your browser should launch a new tab showing an empty simulator (like the one in the image, but with no animation or code). Type in the code from the right side of the image (you can ignore the interactive error-checking messages on the bottom of the simulator until you’re done typing). If you’ve typed everything correctly, you should see, in the left (GUI) side of the simulator, an icon representing a neural population with a recurrent (self) connection. (To save space, the icon shows a small number of neurons by default, not the full 600 that you specified in the code.) Before continuing, hit the play button in the lower right, then the pause button, to make sure your network runs without errors. Finally, right-click on the network icon in the left side, using the Value, XY-value, and Spikes options to flesh out the simulation so that it looks like the one in the image (doesn’t have to be perfect, but all six items should be there). I suggest adding one such item at a time, then re-running the network to make sure you’re seeing what you’re supposed to. For the value plots, it will also be necessary to right-click on the plot and set the ranges, and the X,Y indices for the XY plots. Thankfully, the work you’ve done in the GUI isn’t lost when close the browser tab: looking back on your desktop, you should see an auto-generated nef_lorenz.py.cfg (graphical configuration) file. Close the browser tab containing the simulator, re-run the nengo nef_lorenz.py terminal command, and you should see the entire network along with your code. (Sometimes I found that the config file didn’t keep all my items; if you run into this problem, let me know.)
Part 2: Implementing the Rössler attractor
A less famous but equally easy to implement chaotic attractor is the Rössler attractor, whose equations are here (though the similarly-named discount steakhouse sadly went out of business many years ago.) To implement this attractor, copy your nef_lorenz.py script to a new script nef_rossler.py, and rename the lorenz function in the script to rossler, and make the additional modifications necessary to build and run the Rössler attractor.
Initially, you are likely to get a disappointing result: the three signals (x, y, z) quickly converge to what my Ph.D. adviser calls a “mediocre stable state”, where nothing interesting is happening. This bothered me at first, until I recalled two crucial facts about this model:
-
- By definition, chaotic systems are sensitive to initial conditions. This means that you’ll have to try a few different values for the seed parameter to the Network constructor.
- You can’t expect a network that works well for one dynamical system (Lorenz) to work well for another (Rössler). Fortunately, for these networks, the number of hyper-parameters you need to specify is relatively small: you could modify (1) the synaptic weight to a value other than 0.1; (2) the number of neurons to a value other than 600; (3) the value of the radius (maximum vector magnitude) to a value other than 30. I left the synaptic weight alone and experimented with the number of neurons and the radius until I got a system that oscillated nicely. Hint: the first rule of thumb with Nengo is try more neurons.
Part 3: The Dollar of Mexico revisited
Though $4.99 will no longer buy a steak dinner with all-you-can-eat salad bar, Kanerva’s Dollar of Mexico VSA analogy model is still worth implementing. As before, we’ll start with an existing model, then modify it to do what we want. To implement this analogical model we will use Nengo SPA, allowing us to work directly with the state vectors rather than the neurons.
To get started, copy-paste-modify the code from this Jupyter notebook, naming your script spa_dollar.py. To keep things simple, do this in IDLE3 (or your favorite Python environment) instead of the Nengo Simulator. (If you run it from the command line, python3 spa_dollar.py, you’ll see the progress report more clearly than if you hit F5 in IDLE3.) I skipped over the Hello World program and went directly to the query program in the SPA Syntax section, by copy/pasting the code from In [1] (starting with import nengo), [2], [7], [8], [9]. Don’t forget to end your script with plt.show() ! Once you’ve replicated the results using IDLE3, See if you can figure out what the query from In [9] is doing, and simplify it so that it only uses 'CIRCLE' instead of the complicated lambda function. Once you’ve simplified the example so that the query is CIRCLE (and the answer is therefore BLUE), take a closer look at the line scene * ~query >> result. This code says to bind (convolve) the vector for scene with the inverse of the vector for query and put the resulting vector into result. As we discussed in class, the reason you need to use the inverse of the query vector is that Nengo/SPA uses the HRR encoding by default, and HRR vectors do not have the self-inverse property.
Thankfully, U. Waterloo Ph.D. student Aaron Voelker was kind enough to provide us with a solution to using self-inverse vectors with SPA. First, between the lines d = 32 and with spa.Network() as model:, insert the following block of code:
class CustomAlgebra(spa.algebras.HrrAlgebra): def make_unitary(self, v): # This is a hack for generating custom vectors # that are unitary but with the additional constraint # that every Fourier coefficient has magnitude 1 or -1 # so that v is its own inverse (v == ~v) d = len(v) fft_val = np.fft.fft(v) fft_real = fft_val.real # This is achieved by having each coefficient either # act with a 180 degree rotation or no rotation # Arbitrarily pick which coefficients do what # in order to get 2^d possibilities fcoefs = fft_real < 0 fft_real[fcoefs] = -1 fft_real[~fcoefs] = +1 return np.fft.ifft(fft_real, n=d).real vocab = spa.Vocabulary(dimensions=d, pointer_gen=np.random.RandomState(seed=0), algebra=CustomAlgebra()) vocab.populate('BLUE.unitary()') vocab.populate('CIRCLE.unitary()') vocab.populate('RED.unitary()') vocab.populate('SQUARE.unitary()')
Second, in the rest of the code, replace very instance of vocab=d with vocab=vocab. Finally, since the new vector “vocabulary” has the desired self-inverse property, replace ~query with query. You should now see similar results (winner = CIRCLE).
For the final phase of this exercise, modify the code to implement the Dollar of Mexico analogy and query. As with the Lorenz-to-Rössler conversion, you’ll probably have to modify a hyper-parameter to get the modified code to work. In this case, I found that increasing the dimensionality d to a larger power of two (like 256) did the trick.
Extra Credit Option
Copy/paste/modify your spa_dollar.py script to spa_dollar_gui.py, to run in the GUI. Ideally you should be able to see the PESO answer emerge as the winner, in a word-cloud format.
What to turn into sakai
-
- nef_rossler.py
- nef_rossler.py.cfg
- spa_dollar.py
- spa_dollar_gui.py, spa_dollar_gui.py.cfg (if you do the extra credit)