Here is a quick summary of the components and their uses that are available in the SPA system
You always have to start like this:
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
If I want to have a group of neurons whose value is a semantic pointer, make a Buffer
.
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.value = spa.Buffer(dimensions=32)
To probe data from the buffer, use nengo.Probe(model.<name_of_buffer>.state.output)
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.value = spa.Buffer(dimensions=32)
nengo.Probe(model.value.state.output)
The number of dimensions must be a multiple of 16. If you want to change this, you can set the parameter subdimensions
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.value = spa.Buffer(dimensions=30, subdimensions=10)
nengo.Probe(model.value.state.output)
You can also set the number of neurons per dimension (the default is 50)
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.value = spa.Buffer(dimensions=32, neurons_per_dimension=10)
nengo.Probe(model.value.state.output)
In addition to providing input using the JavaViz visualizer, you can also manually specify inputs. Do this by defining a function that returns the semantic pointer that will be used as input. Then create a spa.Input
object and indicate which Buffer (or other SPA object) should receive that input.
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.value = spa.Buffer(dimensions=32)
nengo.Probe(model.value.state.output)
def my_input(t):
if t < 0.1:
return 'CAT'
elif 0.1 < t < 0.2:
return 'DOG'
else:
return '0'
model.input = spa.Input(value=my_input)
A Memory
is the same as a Buffer
, except that it should continue to store a value even after you remove the input. This is implemented as an Integrator
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.memory = spa.Memory(dimensions=32)
You can probe it in the same way as the Buffer
.
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.memory = spa.Memory(dimensions=32)
nengo.Probe(model.memory.state.output)
The Memory
has the same parameters as the Buffer
. In addition, you can set the time constant of the neurotransmitter bit setting synapse
(default of 0.01 seconds). Larger values should give a more stable memory.
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.memory = spa.Memory(dimensions=32, synapse=0.1)
nengo.Probe(model.memory.state.output)
Finally, you can set the time constant of the memory. With tau=None
(which is the default), it will attempt to store the memory forever. For other values, the memory should decay over that time.
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.memory = spa.Memory(dimensions=32, synapse=0.1, tau=1.0)
nengo.Probe(model.memory.state.output)
To combine these components into a model, we can define actions, build a Basal Ganglia, and a Thalamus.
Actions have two parts: utility of the action (how good it is given the current state) and the effect of the action (what should happen when the action is selected).
Action utility is often of the form dot(buffer, CAT)
, which says that the utility is the dot product of the value stored in buffer
with the semantic pointer for CAT
.
Action effects are often of the form buffer=DOG
, which says to send the value DOG
to the SPA component buffer
. Another common type of action is buffer=other_buffer
, which takes the value from one system and sends it to another.
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.buffer1 = spa.Buffer(dimensions=32)
model.buffer2 = spa.Buffer(dimensions=32)
nengo.Probe(model.buffer1.state.output)
nengo.Probe(model.buffer2.state.output)
actions = spa.Actions(
'dot(buffer1, CAT) --> buffer2=DOG',
'dot(buffer1, DOG) --> buffer2=HORSE',
'dot(buffer1, HORSE) --> buffer2=buffer1',
)
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
Two useful things to probe in this system are model.bg.input
, which is the $Q$ values being fed into the basal ganglia, and model.thalamus.actions.output
, which indicates the selected action.
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.buffer1 = spa.Buffer(dimensions=32)
model.buffer2 = spa.Buffer(dimensions=32)
nengo.Probe(model.buffer1.state.output)
nengo.Probe(model.buffer2.state.output)
actions = spa.Actions(
'dot(buffer1, CAT) --> buffer2=DOG',
'dot(buffer1, DOG) --> buffer2=HORSE',
'dot(buffer1, HORSE) --> buffer2=buffer1',
)
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
nengo.Probe(model.bg.input)
nengo.Probe(model.thalamus.actions.output)
You can add together multiple parts for the utility, and you can also multiply by scalars.
Importantly, try to make sure that the largest utility value is around 1.0, since the neurons don't do a good job representing above that.
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.buffer1 = spa.Buffer(dimensions=32)
model.buffer2 = spa.Buffer(dimensions=32)
nengo.Probe(model.buffer1.state.output)
nengo.Probe(model.buffer2.state.output)
actions = spa.Actions(
'(dot(buffer1, CAT) + dot(buffer2, DOG))*0.5 --> buffer2=DOG',
)
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
nengo.Probe(model.bg.input)
nengo.Probe(model.thalamus.actions.output)
You can also use complex semantic pointers
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.buffer1 = spa.Buffer(dimensions=32)
model.buffer2 = spa.Buffer(dimensions=32)
nengo.Probe(model.buffer1.state.output)
nengo.Probe(model.buffer2.state.output)
actions = spa.Actions(
'dot(buffer1, CAT+DOG*FURRY+~BLUE) --> buffer2=DOG',
)
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
nengo.Probe(model.bg.input)
nengo.Probe(model.thalamus.actions.output)
You can even just have a fixed value for the utility
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.buffer1 = spa.Buffer(dimensions=32)
model.buffer2 = spa.Buffer(dimensions=32)
nengo.Probe(model.buffer1.state.output)
nengo.Probe(model.buffer2.state.output)
actions = spa.Actions(
'0.5 --> buffer2=DOG',
)
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
nengo.Probe(model.bg.input)
nengo.Probe(model.thalamus.actions.output)
For actions, you can have multiple effects by listing them
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.buffer1 = spa.Buffer(dimensions=32)
model.buffer2 = spa.Buffer(dimensions=32)
model.buffer3 = spa.Buffer(dimensions=32)
model.buffer4 = spa.Buffer(dimensions=32)
nengo.Probe(model.buffer1.state.output)
nengo.Probe(model.buffer2.state.output)
actions = spa.Actions(
'dot(buffer1, CAT) --> buffer2=DOG, buffer3=HOUSE, buffer4=CAR',
'dot(buffer1, DOG) --> buffer2=HORSE',
'dot(buffer1, HORSE) --> buffer2=buffer1',
)
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
You can also transform the semantic pointer while routing it
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.buffer1 = spa.Buffer(dimensions=32)
model.buffer2 = spa.Buffer(dimensions=32)
nengo.Probe(model.buffer1.state.output)
nengo.Probe(model.buffer2.state.output)
actions = spa.Actions(
'dot(buffer1, CAT) --> buffer2=buffer1*ANIMAL',
'dot(buffer1, DOG) --> buffer2=buffer1*ANIMAL',
'dot(buffer1, HORSE) --> buffer2=buffer1*buffer2',
)
model.bg = spa.BasalGanglia(actions)
model.thalamus = spa.Thalamus(model.bg)
You can also implement effects that are constant. That is, these are actions that should happen all the time, so we call these Cortical
actions. This forms a direct hard-wird connection between two SPA components. For example, we can set one buffer to always take the value from another buffer convolved with a particular semantic pointer
import nengo
import nengo.spa as spa
model = spa.SPA(label="My Model Name")
with model:
model.buffer1 = spa.Buffer(dimensions=32)
model.buffer2 = spa.Buffer(dimensions=32)
nengo.Probe(model.buffer1.state.output)
nengo.Probe(model.buffer2.state.output)
cortical_actions = spa.Actions(
'buffer2=buffer1*ANIMAL'
)
model.cortical = spa.Cortical(cortical_actions)