For event-based data, you’re definitely right that it’s inefficient to put them into frames for PresentInput, and then convert them back to spikes to send to the board.
However, much of your IO will be governed by how you convert them back to spikes. The default in NengoLoihi is to use the OnOffDecodeNeurons
class, which has pairs of on and off neurons with negative intercepts. These negative intercepts mean that if a particular dimension has an input of 0, both the neurons for that dimension are firing; this helps us represent values near zero more accurately (since the total firing rate is always non-zero), but in your case it means you’re sending spikes to communicate the many zeros in your data.
To work around this, you could do the encoding yourself. The easiest way to do this is to make an ensemble off-chip to do the encoding, set up the neurons how you want, and then connect the .neurons
of this ensemble to something on-chip. Using the .neurons
will have NengoLoihi send the spikes from this ensemble, rather than trying to do some sort of encoding itself. Here’s a sketch of how to do that. (You’ll notice I use two ensembles, one for “on” neurons representing positive values in the input, and one for “off” neurons representing negative values. However, since I just have “on” neurons on the chip, I’ve set things up so that negative values inhibit the on-chip neurons. This is just one of many ways to do things. You could put the “on” and “off” neurons together in one ensemble, but then you may want to use Sparse
transforms to limit the number of connection weights required.)
import matplotlib.pyplot as plt
import numpy as np
import nengo
import nengo_loihi
x = np.array([[0, 1, 0], [0, 0, 0.5], [0.2, 0, 0]])
input_dim = x.size
with nengo.Network() as net:
nengo_loihi.set_defaults()
nengo_loihi.add_params(net)
node = nengo.Node(x.ravel())
encode_pos = nengo.Ensemble(
input_dim,
dimensions=1,
gain=nengo.dists.Choice([1000]),
bias=nengo.dists.Choice([0]),
neuron_type=nengo.SpikingRectifiedLinear()
)
net.config[encode_pos].on_chip = False
nengo.Connection(node, encode_pos.neurons)
p_encode_pos = nengo.Probe(encode_pos.neurons)
encode_neg = nengo.Ensemble(
input_dim,
dimensions=1,
gain=nengo.dists.Choice([1000]),
bias=nengo.dists.Choice([0]),
neuron_type=nengo.SpikingRectifiedLinear()
)
net.config[encode_neg].on_chip = False
nengo.Connection(node, encode_neg.neurons, transform=-1)
p_encode_neg = nengo.Probe(encode_neg.neurons)
# this is our on-chip target ensemble
target_ens = nengo.Ensemble(
input_dim,
dimensions=1,
# We need a gain slightly higher than 1 here, because if the gain is 1 then an
# input spike pushes a neuron to its firing threshold, but not over the firing
# threshold, and it takes a second spike to push it over, resulting in half the
# firing rate that we would expect.
gain=nengo.dists.Choice([1.05]),
bias=nengo.dists.Choice([0]),
neuron_type=nengo.SpikingRectifiedLinear()
)
nengo.Connection(encode_pos.neurons, target_ens.neurons)
nengo.Connection(encode_neg.neurons, target_ens.neurons, transform=-1)
p_target_ens = nengo.Probe(target_ens.neurons)
with nengo_loihi.Simulator(net) as sim:
sim.run(1.0)
pos_rates = sim.data[p_encode_pos].mean(axis=0).reshape(x.shape)
neg_rates = sim.data[p_encode_neg].mean(axis=0).reshape(x.shape)
target_rates = sim.data[p_target_ens].mean(axis=0).reshape(x.shape)
print("encode_pos rates:")
print(pos_rates)
print("target_ens rates:")
print(target_rates)
plt.subplot(2, 2, 1)
plt.imshow(x)
plt.title("input")
plt.subplot(2, 2, 2)
plt.imshow(target_rates)
plt.title("target ens")
plt.subplot(2, 2, 3)
plt.imshow(pos_rates)
plt.title("encode pos")
plt.subplot(2, 2, 4)
plt.imshow(neg_rates)
plt.title("encode neg")
plt.show()