Readings: Serial Working Memory; Associative Memory
The neural integrator is an example of an attractor network
The neural integrator can be thought of as a line attractor as in a)
Note: The hill and ball analogy doesn't work anymore.
This is an oscillator. Technically a nonlinear one, since it should be stable (the Simple Harmonic Oscillator is linear). Let's build a stable oscillator.
In [45]:
%pylab inline
import nengo
from nengo.utils.ensemble import response_curves
from nengo.dists import Uniform
model = nengo.Network('Oscillator')
freq = .25
scale = 1.1
N=300
with model:
stim = nengo.Node(lambda t: [.5,.5] if 0.1<t<0.12 else [0,0])
inhib = nengo.Node(lambda t: [-1]*N if 2.8<t<2.82 else [0]*N)
osc = nengo.Ensemble(N, dimensions=2, intercepts=Uniform(.3,1))
def feedback(x):
return scale*x[0]+freq*x[1], -freq*x[0]+scale*x[1]
nengo.Connection(osc, osc, function=feedback)
nengo.Connection(inhib, osc.neurons)
nengo.Connection(stim, osc)
osc_p = nengo.Probe(osc, synapse=.01)
In [46]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/nonlinear_oscillator.py.cfg")
In [48]:
sim = nengo.Simulator(model)
sim.run(3.)
x, A = response_curves(osc, sim)
figure(figsize=(4,2))
plot(x, A)
xlabel('x')
ylabel('firing rate (Hz)')
figure(figsize=(12,4))
subplot(1,2,1)
plot(sim.trange(), sim.data[osc_p]);
xlabel('Time (s)')
ylabel('State value')
subplot(1,2,2)
plot(sim.data[osc_p][:,0],sim.data[osc_p][:,1])
xlabel('$x_0$')
ylabel('$x_1$');
In [57]:
%pylab inline
#A 1D integrator with a few hundred neurons works great
import nengo
from nengo.utils.functions import piecewise
model = nengo.Network(label='1D Line Attractor', seed=5)
N = 300
tau = 0.01
with model:
stim = nengo.Node(piecewise({.3:[1], .5:[0] }))
neurons = nengo.Ensemble(N, dimensions=1)
nengo.Connection(stim, neurons, transform=tau, synapse=tau)
nengo.Connection(neurons, neurons, synapse=tau)
stim_p = nengo.Probe(stim)
neurons_p = nengo.Probe(neurons, synapse=.01)
sim = nengo.Simulator(model)
sim.run(4)
t=sim.trange()
plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position")
legend(loc="best");
In [61]:
#Need lots of neurons to get reasonable performance in higher D
import nengo
from nengo.utils.functions import piecewise
model = nengo.Network(label='2D Plane Attractor', seed=4)
N = 2000 #600
tau = 0.01
with model:
stim = nengo.Node(piecewise({.3:[1, -1], .5:[0, 0] }))
neurons = nengo.Ensemble(N, dimensions=2)
nengo.Connection(stim, neurons, transform=tau, synapse=tau)
nengo.Connection(neurons, neurons, synapse=tau)
stim_p = nengo.Probe(stim)
neurons_p = nengo.Probe(neurons, synapse=.01)
sim = nengo.Simulator(model)
sim.run(4)
t=sim.trange()
plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position")
legend(loc="best");
In [62]:
#Note that the representation saturates at the radius
with model:
stim.output = piecewise({.2:[1, -1], 1.2:[0, 0] })
sim = nengo.Simulator(model)
sim.run(4)
t=sim.trange()
plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position");
In [63]:
model = nengo.Network(label='Ensemble Array', seed=123)
N = 300 #neurons per sub_ensemble
tau = 0.01
with model:
stim = nengo.Node(piecewise({.3:[1, -1], .5:[0, 0] }))
neurons = nengo.networks.EnsembleArray(N, n_ensembles=2)
nengo.Connection(stim, neurons.input, transform=tau, synapse=tau)
nengo.Connection(neurons.output, neurons.input, synapse=tau)
stim_p = nengo.Probe(stim)
neurons_p = nengo.Probe(neurons.output, synapse=.01)
sim = nengo.Simulator(model)
sim.run(4)
t=sim.trange()
plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position");
In [64]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/ensemble_array.py.cfg")
In [65]:
#Note that the representation saturates at the radius
with model:
stim.output = piecewise({.2:[1, -1], 1.2:[0, 0] })
sim = nengo.Simulator(model)
sim.run(4)
t=sim.trange()
plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position");
In [66]:
import nengo
import nengo_spa as spa
def colour_input(t):
if t < 0.15:
return 'BLUE'
elif 1.0 < t < 1.15:
return 'GREEN'
elif 1.7 < t < 1.85:
return 'RED'
else:
return '0'
model = spa.Network(label="HighD Working Memory", seed=5)
dimensions = 32
with model:
colour_in = spa.Transcode(colour_input, output_vocab=dimensions)
mem = spa.State(dimensions, subdimensions=4, feedback=1.,
feedback_synapse=0.1,
neurons_per_dimension=50)
# Connect the ensembles
colour_in >> mem
model.config[nengo.Probe].synapse = nengo.Lowpass(0.03)
colour_in_p = nengo.Probe(colour_in.output)
mem_p = nengo.Probe(mem.output)
sim = nengo.Simulator(model)
sim.run(3.)
plt.figure(figsize=(10, 10))
vocab = model.vocabs[dimensions]
plt.subplot(2, 1, 1)
plt.plot(sim.trange(), spa.similarity(sim.data[colour_in_p], vocab))
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("colour")
plt.subplot(2, 1, 2)
plt.plot(sim.trange(), spa.similarity(sim.data[mem_p], vocab))
plt.legend(fontsize='x-small')
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("memory")
plt.xlabel("time [s]");
In [67]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/simple_spa_wm.py.cfg")
In [21]:
# model.all_ensembles has all the neurons
model.all_ensembles[2].n_neurons
Out[21]:
If you recalled things from this memory, you'd probably have a 'recency effect'
This is seen in human memory, but so is primacy
In [68]:
import nengo
import nengo_spa as spa
def colour_input(t):
if t < 0.15:
return 'BLUE'
elif 1.0 < t < 1.15:
return 'GREEN'
elif 1.7 < t < 1.85:
return 'RED'
else:
return '0'
model = spa.Network(label="HighD Working Memory", seed=3)
dimensions = 32
with model:
colour_in = spa.Transcode(colour_input, output_vocab=dimensions)
mem = spa.State(dimensions, subdimensions=4, feedback=1.2,
feedback_synapse=0.1,
neurons_per_dimension=50)
# Connect the ensembles
colour_in >> mem
model.config[nengo.Probe].synapse = nengo.Lowpass(0.03)
colour_in_p = nengo.Probe(colour_in.output)
mem_p = nengo.Probe(mem.output)
In [29]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/simple_spa_wm_primacy.py.cfg")
In [69]:
sim = nengo.Simulator(model)
sim.run(3.)
plt.figure(figsize=(10, 10))
vocab = model.vocabs[dimensions]
plt.subplot(2, 1, 1)
plt.plot(sim.trange(), spa.similarity(sim.data[colour_in_p], vocab))
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("colour")
plt.subplot(2, 1, 2)
plt.plot(sim.trange(), spa.similarity(sim.data[mem_p], vocab))
plt.legend(fontsize='x-small')
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("memory")
plt.xlabel("time [s]");
To represent structures, we can use what we learned last time: vector binding
In this case, we can do something like:
$Pos_0\circledast Item_0 + Pos_1\circledast Item_1 + ...$
Arbitrary list lengths
This model can actually do lots more as well
Often worried about how to learn these
How to compute these kinds of mappings with the NEF/SPA?
Can do it in one layer with a feedforward approach
We've seen several techniques that will make this easy
In [112]:
import nengo
import nengo_spa as spa
D = 32
seed = 1
model = spa.Network("Associative Memory", seed=seed)
vocab = spa.Vocabulary(dimensions=D,
pointer_gen=np.random.RandomState(seed + 1))
words = ['RED', 'GREEN', 'BLUE']
vocab.populate(';'.join(words))
#noise_RED = vocab.parse("RED").v + .4*np.random.randn(D)
noise_RED = vocab.parse("RED").v + .2*vocab.parse("GREEN").v + .2*np.random.randn(D)
noise_RED = noise_RED/np.linalg.norm(noise_RED)
noise_GREEN = vocab.parse("GREEN").v + .2*np.random.randn(D)
noise_GREEN = noise_GREEN/np.linalg.norm(noise_GREEN)
def memory_input(t):
if t < 0.2:
return vocab.parse("BLUE").v
elif .2 < t < .5:
return noise_RED
elif .5 < t < .8:
return vocab.parse("RED").v
elif .8 < t < 1:
return noise_GREEN
else:
return vocab.parse("0").v
with model:
stim = spa.Transcode(memory_input, label='input', output_vocab=vocab)
am = spa.ThresholdingAssocMem(threshold=0.3, input_vocab=vocab,
mapping=vocab.keys(),
function=lambda x: x>.2)
stim >> am
in_p = nengo.Probe(stim.output)
out_p = nengo.Probe(am.output, synapse=0.03)
In [113]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/simple_cleanup.py.cfg")
In [114]:
# Notice that the magnitude of the cleaned element is preserved (without 'function='),
# and others are all made closer to or less than zero. The output is thus
# more 'purely' the target vector.
# 'function=' puts another threshold after to boost to one.
sim = nengo.Simulator(model)
sim.run(1.2)
t = sim.trange()
figure(figsize=(10,10))
plt.subplot(2, 1, 1)
plt.plot(t, spa.similarity(sim.data[in_p], vocab))
plt.ylabel("Input")
plt.ylim(top=1.1)
plt.legend(vocab.keys(), loc='best')
plt.subplot(2, 1, 2)
plt.plot(t, spa.similarity(sim.data[out_p], vocab))
plt.ylabel("Output")
plt.legend(vocab.keys(), loc='best');
In [1]:
%pylab inline
import nengo
import nengo_spa as spa
def colour_input(t):
if t < 0.25:
return 'RED'
elif t < 0.5:
return 'BLUE'
else:
return '0'
def shape_input(t):
if t < 0.25:
return 'CIRCLE'
elif t < 0.5:
return 'SQUARE'
else:
return '0'
def cue_input(t):
if t < 0.5:
return '0'
sequence = ['0', 'CIRCLE', 'RED', '0', 'SQUARE', 'BLUE']
idx = int(((t - 0.5) // (1. / len(sequence))) % len(sequence))
return sequence[idx]
# Number of dimensions for the Semantic Pointers
D = 32
seed = 4
vocab = spa.Vocabulary(dimensions=D,
pointer_gen=np.random.RandomState(seed))
words = ['RED', 'SQUARE', 'BLUE', 'CIRCLE']
vocab.populate(';'.join(words))
model = spa.Network(label="Question answering with memory")
with model:
colour_in = spa.Transcode(colour_input, output_vocab=vocab)
shape_in = spa.Transcode(shape_input, output_vocab=vocab)
cue = spa.Transcode(cue_input, output_vocab=vocab, label="cue")
conv = spa.State(vocab, subdimensions=4,
feedback=1.,
feedback_synapse=0.4,
label="memory")
#out = spa.State(vocab)
out = spa.ThresholdingAssocMem(threshold=0.3, input_vocab=vocab,
mapping=vocab.keys(), label="clean up",
function = lambda x: x>.2)
# Connect the buffers
colour_in * shape_in >> conv
conv * ~cue >> out
with model:
model.config[nengo.Probe].synapse = nengo.Lowpass(0.03)
p_colour_in = nengo.Probe(colour_in.output)
p_shape_in = nengo.Probe(shape_in.output)
p_cue = nengo.Probe(cue.output)
p_conv = nengo.Probe(conv.output)
p_out = nengo.Probe(out.output)
In [2]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/binding_with_memory.py.cfg")
In [3]:
with nengo.Simulator(model) as sim:
sim.run(3.)
plt.figure(figsize=(10, 10))
plt.subplot(5, 1, 1)
plt.plot(sim.trange(), spa.similarity(sim.data[p_colour_in], vocab))
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("color")
plt.subplot(5, 1, 2)
plt.plot(sim.trange(), spa.similarity(sim.data[p_shape_in], vocab))
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("shape")
plt.subplot(5, 1, 3)
plt.plot(sim.trange(), spa.similarity(sim.data[p_cue], vocab))
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("cue")
plt.subplot(5, 1, 4)
for pointer in ['RED * CIRCLE', 'BLUE * SQUARE']:
plt.plot(sim.trange(), vocab.parse(pointer).dot(sim.data[p_conv].T), label=pointer)
plt.legend(fontsize='x-small')
plt.ylabel("convolved")
plt.subplot(5, 1, 5)
plt.plot(sim.trange(), spa.similarity(sim.data[p_out], vocab))
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("Output")
plt.xlabel("time [s]");
In [ ]: