Heterogeneous Synapses¶
In this example, we demonstrate how to build an Ensemble that uses different synapses per dimension in the vector space, or a different synapse per neuron.
For the most general case, ``HeteroSynapse`` is a function for use within a Node. It accepts some vector as input, and outputs a filtered version of this vector. The dimensionality of the output vector depends on the number of elements in the list synapses and the boolean value elementwise: - If elementwise == False, then each synapse is applied to every input dimension, resulting in an output vector that is len(synapses) times larger. - If elementwise == True, then
each synapse is applied separately to a single input dimension, resulting in an output vector that is size len(synapses), which must also be the same as the input dimension.
Neuron Example¶
We first sample 100 neurons and 100 synapses randomly.
[1]:
import numpy as np
import nengo
import nengolib
from nengolib.stats import sphere
from nengolib.synapses import HeteroSynapse
n_neurons = 100
dt = 0.001
T = 0.1
dims_in = 2
taus = nengo.dists.Uniform(0.001, 0.1).sample(n_neurons)
synapses = [nengo.Lowpass(tau) for tau in taus]
encoders = sphere.sample(n_neurons, dims_in)
Now we create two identical ensembles, one to hold the expected result, and one to compare this with the actual result from using HeteroSynapse. The former is computed via brute-force, by creating a separate connection for each synapse. The latter requires a single connection to the special node.
When elementwise = False, each input dimension is effectively broadcast to all of the neurons with a different synapse per neuron. We also note that since we are connecting directly to the neurons, we must embed the encoders in the transformation.
[2]:
hs = HeteroSynapse(synapses, dt)
def embed_encoders(x):
# Reshapes the vectors to be the same dimensionality as the
# encoders, and then takes the dot product row by row.
# See http://stackoverflow.com/questions/26168363/ for a more
# efficient solution.
return np.sum(encoders * hs.from_vector(x), axis=1)
with nengolib.Network() as model:
# Input stimulus
stim = nengo.Node(size_in=dims_in)
for i in range(dims_in):
nengo.Connection(
nengo.Node(output=nengo.processes.WhiteSignal(T, high=10)),
stim[i], synapse=None)
# HeteroSynapse node
syn = nengo.Node(size_in=dims_in, output=hs)
# For comparing results
x = [nengo.Ensemble(n_neurons, dims_in, seed=0, encoders=encoders)
for _ in range(2)] # expected, actual
# Expected
for i, synapse in enumerate(synapses):
t = np.zeros_like(encoders)
t[i, :] = encoders[i, :]
nengo.Connection(stim, x[0].neurons, transform=t, synapse=synapse)
# Actual
nengo.Connection(stim, syn, synapse=None)
nengo.Connection(syn, x[1].neurons, function=embed_encoders, synapse=None)
# Probes
p_exp = nengo.Probe(x[0].neurons, synapse=None)
p_act = nengo.Probe(x[1].neurons, synapse=None)
# Check correctness
sim = nengo.Simulator(model, dt=dt)
sim.run(T)
assert np.allclose(sim.data[p_act], sim.data[p_exp])
Vector Example¶
This example applies 2 synapses to their respective dimensions in a 2D-vector. We first initialize our parameters to use 20 neurons and 2 randomly chosen synapses.
[3]:
n_neurons = 20
dt = 0.0005
T = 0.1
dims_in = 2
synapses = [nengo.Alpha(0.1), nengo.Lowpass(0.005)]
assert dims_in == len(synapses)
encoders = sphere.sample(n_neurons, dims_in)
Similar to the last example, we create two ensembles, one to obtain the expected result for verification, and another to be computed using the HeteroSynapse node.
[4]:
with nengolib.Network() as model:
# Input stimulus
stim = nengo.Node(size_in=dims_in)
for i in range(dims_in):
nengo.Connection(
nengo.Node(output=nengo.processes.WhiteSignal(T, high=10)),
stim[i], synapse=None)
# HeteroSynapse Nodes
syn_elemwise = nengo.Node(
size_in=dims_in,
output=HeteroSynapse(synapses, dt, elementwise=True))
# For comparing results
x = [nengo.Ensemble(n_neurons, dims_in, seed=0, encoders=encoders)
for _ in range(2)] # expected, actual
# Expected
for j, synapse in enumerate(synapses):
nengo.Connection(stim[j], x[0][j], synapse=synapse)
# Actual
nengo.Connection(stim, syn_elemwise, synapse=None)
nengo.Connection(syn_elemwise, x[1], synapse=None)
# Probes
p_exp = nengo.Probe(x[0], synapse=None)
p_act_elemwise = nengo.Probe(x[1], synapse=None)
# Check correctness
sim = nengo.Simulator(model, dt=dt)
sim.run(T)
assert np.allclose(sim.data[p_act_elemwise], sim.data[p_exp])
Multiple Vector Example¶
As a final example, to demonstrate the generality of this approach, we consider the situation where we wish to apply a number of different synapses to every dimension. For instance, with a 2D input vector, we pick 3 synapses to apply to every dimension, such that our ensemble will represent a 6D-vector (one for each dimension/synapse pair).
[5]:
n_neurons = 20
dt = 0.0005
T = 0.1
dims_in = 2
synapses = [nengo.Alpha(0.1), nengo.Lowpass(0.005), nengo.Alpha(0.02)]
dims_out = len(synapses)*dims_in
encoders = sphere.sample(n_neurons, dims_out)
We also demonstrate that this can be achieved in two different ways. The first is with elementwise=False, by a broadcasting similar to the first example. The second is with elementwise=True, by replicating each synapse to align with each dimension, and then proceeding similar to the second example.
[6]:
with nengolib.Network() as model:
# Input stimulus
stim = nengo.Node(size_in=dims_in)
for i in range(dims_in):
nengo.Connection(
nengo.Node(output=nengo.processes.WhiteSignal(T, high=10)),
stim[i], synapse=None)
# HeteroSynapse Nodes
syn_dot = nengo.Node(
size_in=dims_in, output=HeteroSynapse(synapses, dt))
syn_elemwise = nengo.Node(
size_in=dims_out,
output=HeteroSynapse(np.repeat(synapses, dims_in), dt, elementwise=True))
# For comparing results
x = [nengo.Ensemble(n_neurons, dims_out, seed=0, encoders=encoders)
for _ in range(3)] # expected, actual 1, actual 2
# Expected
for j, synapse in enumerate(synapses):
nengo.Connection(stim, x[0][j*dims_in:(j+1)*dims_in], synapse=synapse)
# Actual (method #1 = matrix multiplies)
nengo.Connection(stim, syn_dot, synapse=None)
nengo.Connection(syn_dot, x[1], synapse=None)
# Actual (method #2 = elementwise)
for j in range(len(synapses)):
nengo.Connection(stim, syn_elemwise[j*dims_in:(j+1)*dims_in], synapse=None)
nengo.Connection(syn_elemwise, x[2], synapse=None)
# Probes
p_exp = nengo.Probe(x[0], synapse=None)
p_act_dot = nengo.Probe(x[1], synapse=None)
p_act_elemwise = nengo.Probe(x[2], synapse=None)
# Check correctness
sim = nengo.Simulator(model, dt=dt)
sim.run(T)
assert np.allclose(sim.data[p_act_dot], sim.data[p_exp])
assert np.allclose(sim.data[p_act_elemwise], sim.data[p_exp])
[7]: