Replace connection with node

I’m trying to replace a connection between neuron populations with a Learning Node. I’m hoping to start off with just replacing a basic neuron-to-neuron connection with a nengo.Node, however I’m getting magnitude mismatches. Do I need to normalize the weights or something?

import nengo

import matplotlib.pyplot as plt

import numpy as np


def base_func(ww):

    def f(t, x):
        return np.dot(ww, x)

    return f


n_neurons = 100
seed = 0

with nengo.Network(seed=seed) as tmp_model:
    in_nd = nengo.Node(lambda t: np.sin(10 * t))

    pre = nengo.Ensemble(n_neurons, 1, seed=seed)
    post = nengo.Ensemble(n_neurons, 1, seed=seed)

    nengo.Connection(in_nd, pre)
    conn = nengo.Connection(pre, post, solver=nengo.solvers.LstsqL2(weights=True), seed=seed)

    p_in = nengo.Probe(pre, synapse=0.01)
    p_out = nengo.Probe(post, synapse=0.01)

with nengo.Simulator(tmp_model) as tmp_sim:
    tmp_sim.run(2)

plt.plot(tmp_sim.trange(), tmp_sim.data[p_in])
plt.plot(tmp_sim.trange(), tmp_sim.data[p_out])
plt.legend(["in", "out"])
plt.show()


weights = tmp_sim.data[conn].weights


with nengo.Network(seed=seed) as model:

    in_nd = nengo.Node(lambda t: np.sin(10 * t))
    conn_nd = nengo.Node(base_func(np.linalg.norm(weights)), size_in=n_neurons, size_out=n_neurons)

    pre = nengo.Ensemble(n_neurons, 1, seed=seed)
    post = nengo.Ensemble(n_neurons, 1, seed=seed)

    nengo.Connection(in_nd, pre)
    nengo.Connection(pre.neurons, conn_nd)
    nengo.Connection(conn_nd, post.neurons)

    p_in = nengo.Probe(pre, synapse=0.01)
    p_out = nengo.Probe(post, synapse=0.01)

with nengo.Simulator(model) as sim:
    sim.run(2)

plt.plot(sim.trange(), sim.data[p_in])
plt.plot(sim.trange(), sim.data[p_out])
plt.legend(["in", "out"])
plt.show()

I think in your first pure connection approach the weight get scaled by gains / radius. In the neurons to node to neuron approach, this factor is not included.

Note that it’s not just replacing a connection with a nengo.Node that I’m having trouble with. I can’t even transfer weight matrices between models. All previous examples only involved transferring decoders.

with nengo.Network(seed=seed) as model:

    in_nd = nengo.Node(lambda t: np.sin(10 * t))
    conn_nd = nengo.Node(base_func(weights), size_in=n_neurons, size_out=n_neurons)

    pre = nengo.Ensemble(n_neurons, 1, seed=seed)
    post = nengo.Ensemble(n_neurons, 1, seed=seed)

    nengo.Connection(in_nd, pre)
    nengo.Connection(pre.neurons, conn_nd)
    nengo.Connection(conn_nd, post.neurons)

    alt_pre = nengo.Ensemble(n_neurons, 1, seed=seed)
    alt_post = nengo.Ensemble(n_neurons, 1, seed=seed)

    nengo.Connection(in_nd, alt_pre)
    nengo.Connection(alt_pre.neurons, alt_post.neurons, transform=weights*0.06, seed=seed)

    p_in = nengo.Probe(pre, synapse=0.01)
    p_out = nengo.Probe(post, synapse=0.01)

    p_alt_in = nengo.Probe(alt_pre, synapse=0.01)
    p_alt_out = nengo.Probe(alt_post, synapse=0.01)

with nengo.Simulator(model) as sim:
    sim.run(1)

plt.plot(sim.trange(), sim.data[p_in])
plt.plot(sim.trange(), sim.data[p_out])
plt.plot(sim.trange(), sim.data[p_alt_in])
plt.plot(sim.trange(), sim.data[p_alt_out])
plt.legend(["in", "out", "alt_in", "alt_out"])
plt.show()

Why are you multiplying the weights with 0.06 in your alt_pre.neurons -> alt_post.neurons connection?

Just to demonstrate you have to scale significantly to get a result that somewhat matches the input signal.

As I said you have to properly account for the gains / radius scaling. Here’s the working code:

import nengo

import matplotlib.pyplot as plt

import numpy as np


def base_func(ww):

    def f(t, x):
        return np.dot(ww, x)

    return f


n_neurons = 100
seed = 0

with nengo.Network(seed=seed) as tmp_model:
    in_nd = nengo.Node(lambda t: np.sin(10 * t))

    pre = nengo.Ensemble(n_neurons, 1, seed=seed)
    post = nengo.Ensemble(n_neurons, 1, seed=seed)

    nengo.Connection(in_nd, pre)
    conn = nengo.Connection(pre, post, solver=nengo.solvers.LstsqL2(weights=True), seed=seed)

    p_in = nengo.Probe(pre, synapse=0.01)
    p_out = nengo.Probe(post, synapse=0.01)

with nengo.Simulator(tmp_model) as tmp_sim:
    tmp_sim.run(2)

plt.plot(tmp_sim.trange(), tmp_sim.data[p_in])
plt.plot(tmp_sim.trange(), tmp_sim.data[p_out])
plt.legend(["in", "out"])
plt.show()

gains = tmp_sim.data[post].gain
radius = post.radius
weights = tmp_sim.data[conn].weights
scaled_weights = weights * radius / gains[:, None]

with nengo.Network(seed=seed) as model:

    in_nd = nengo.Node(lambda t: np.sin(10 * t))
    conn_nd = nengo.Node(base_func(scaled_weights), size_in=n_neurons, size_out=n_neurons)

    pre = nengo.Ensemble(n_neurons, 1, seed=seed)
    post = nengo.Ensemble(n_neurons, 1, seed=seed)

    nengo.Connection(in_nd, pre)
    nengo.Connection(pre.neurons, conn_nd)
    nengo.Connection(conn_nd, post.neurons)

    alt_pre = nengo.Ensemble(n_neurons, 1, seed=seed)
    alt_post = nengo.Ensemble(n_neurons, 1, seed=seed)

    nengo.Connection(in_nd, alt_pre)
    nengo.Connection(alt_pre.neurons, alt_post.neurons, transform=scaled_weights, seed=seed)

    p_in = nengo.Probe(pre, synapse=0.01)
    p_out = nengo.Probe(post, synapse=0.01)

    p_alt_in = nengo.Probe(alt_pre, synapse=0.01)
    p_alt_out = nengo.Probe(alt_post, synapse=0.01)

with nengo.Simulator(model) as sim:
    sim.run(2)

plt.plot(sim.trange(), sim.data[p_in], label='in')
plt.plot(sim.trange(), sim.data[p_out], label='out')
plt.plot(sim.trange(), sim.data[p_alt_in], label='alt_in')
plt.plot(sim.trange(), sim.data[p_alt_out], label='alt_out')
plt.show()
1 Like