Metrics Extraction with PES Learning

Hi @vzanon, and welcome back to the Nengo forums. :smiley:

I’m not entirely clear where you want to perform the analysis of these signals, so I’ll describe a few options. If you are looking to analyze the signals after the simulation has completed, the best way to do this is to use probes to obtain the signals, then use regular Python code to perform the analysis. As an example:

with nengo.Network() a model:
    ens = nengo.Ensemble(...)
    ...
    probe_ens = nengo.Probe(ens)

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

probe_data = sim.data[probe_ens]
std_data = np.std(probe_data)

Note that in the approach above, you’ll be performing the analysis on data recorded for each timestep of the simulation. If you don’t want to do this, and only use data recorded at a specific interval, you can use the sample_every parameter of the nengo.Probe to do this:

probe_ens = nengo.Probe(ens, sample_every=0.1)  # sample ens output every 0.1s

If you are looking to analyze the signal as the simulation is running, this is a little more complex. For this, the easiest is probably to put it in a nengo.Node. However, you’ll need to take into account that the function you pass to the node is evaluated at every timestep of the simulation. So, if you want to calculate something like the STD, you’ll need it to maintain a history of some sort. This forum thread has an example of how one user accomplishes this.

The last way I can think of analyzing the data is using a spiking network to perform the analysis. This is an even more complex option than using a nengo.Node since you’ll need to design a spiking network to do this. You’ll need a memory circuit to store all of the information, one set of ensemble to compute the mean, then more ensembles to perform the square function, and another ensemble to perform the summation and square root. The ensembles performing the mathematical formula to compute the STD are not too difficult to implement (it’ll be a triple layer network of ensembles), but the memory circuit to store the data points would be difficult to implement since it’ll require timing circuits to operate correctly. I would recommend not going this approach.

For nengo.Connections, this is not the case. In fact, both function and transform are used to do the same thing: solve for the connection’s weights. As an example, the following two pieces of code will result in the same connection weights:

# Connection with transform
with nengo.Network() as model:
    ens = nengo.Ensemble(30, 1, seed=1)
    out = nengo.Node(size_in=1)
    conn = nengo.Connection(ens, out, transform=2)
    p_conn = nengo.Probe(conn, "weights")

with nengo.Simulator(model) as sim:
    sim.run(0.001)
    print(sim.data[p_conn])
# Connection with function
with nengo.Network() as model:
    ens = nengo.Ensemble(30, 1, seed=1)
    out = nengo.Node(size_in=1)
    conn = nengo.Connection(ens, out, function=lambda x: 2 * x)
    p_conn = nengo.Probe(conn, "weights")

with nengo.Simulator(model) as sim:
    sim.run(0.001)
    print(sim.data[p_conn])

Functions are only evaluated at each timestep for nengo.Nodes. Functions passed to nengo.Connections are used by Nengo during the weight solving step to solve for a set of weights that approximate the desired function over a specific input range. For single dimensional values, the default range is from -1 to 1. For multi-dimensional values, the default range are points within the unit hypersphere. All this is to say that after Nengo has done the weights solving, any input value that is provided to the ensemble (that is inside the range of values) will cause the output signal of the ensemble to approximate the desired function applied to the input value. One important note about this process is that once the weights have been solved for, the weights become static, and do not change over time (unless a learning rule is applied to it).

From your code, you’ve passed the np.std function as a parameter to the nengo.Connection’s function parameter. This doesn’t (probably) have the effect you want it to have. Here’s what it actually does:

  1. By doing it this way, the initial weights that Nengo puts between pre and post will approximate the result of the init_func. Ostensively, this means that when the simulation first starts, the network will “compute” the STD function on the input signal, but because there’s a learning rule applied to that connection, the weights will slowly be modified to compute the identity (y=x) function instead. What this really means is that nothing in the network ends up computing the STD function.
  2. Another issue is that the STD function is meant to be computed over a series of data points. In your code, however, the output of pre and the input to post are single dimensional. This means that Nengo will try to solve for weights that compute the STD of a single dimensional vector (because x that is passed to np.std(x) is 1D).

If I understand what you are attempting to do, I think the approach you want to take is to use the nengo.Probes to collect the data, and then perform the STD analysis on it after the simulation has completed.

1 Like