Righto! We took a look at your code, and we have identified what is going on with it. Part of the NengoDL model compilation process (i.e., when you make the NengoDL simulator object) is an optimization step that tries to minimize the amount of TF operations needed to be done. With your two models, it looks like that by specifying a synapse in the NengoDL converter, the synapses add enough operations to the model to make the optimized result different, resulting in the mismatch error.
Fixing your model is straightforward, you’ll want to disable the optimizer where ever the NengoDL simulator object is created. This is done like so:
In your ConstructNetworkAllInOne
function:
...
net = converter.net
nengo_input = converter.inputs[input]
nengo_output = converter.outputs[output]
nengo_prob1 = converter.layers[l1]
with net:
# Disable the NengoDL graph optimizer
nengo_dl.configure_settings(planner=nengo_dl.graph_optimizer.noop_planner)
# run training
with nengo_dl.Simulator(net, minibatch_size=100, seed=0) as sim:
sim.compile(
optimizer=tf.optimizers.SGD(),
loss={nengo_output: tf.losses.mse},
)
sim.fit(
inputTrainNgo,
{nengo_output: outputTrainNgo},
validation_data=(inputValidationNgo, outputValidationNgo),
epochs=1,
)
sim.save_params("./nengo-model-relu-1")
...
And also in your RunNetwork
function:
...
# set some options to speed up simulation
with nengo_converter.net:
nengo_dl.configure_settings(planner=nengo_dl.graph_optimizer.noop_planner)
nengo_dl.configure_settings(stateful=False)
# build network, load in trained weights, run inference on test
with nengo_dl.Simulator(
nengo_converter.net, minibatch_size=100, progress_bar=False
) as nengo_sim:
nengo_sim.load_params(params_file)
data = nengo_sim.predict({nengo_input: testInputData})
...
By disabling the graph optimizer, the models generated with and without the synapse should be structurally similar to allow the load_params
to work.
Some Notes
Regarding the save_params
and load_params
functionality, it is only really guaranteed to work in the case where the two networks are structurally exactly identical. Such things like adding synapses modifies the structure of the network enough that this guarantee no longer holds true. For more complex models, if you want to save and load network parameters, you’ll need to manually extract and the subsequently load the network parameters. You can access the network parameters using the sim.data
functionality (see here).
I should also note that for your specific example code, because you are using the converter on a Keras model, and then doing the fit
right after (without any making any NengoDL-specific additions to the model), it will probably be less complex to do the training directly in TensorFlow itself. The NengoDL simulator fit
function is, afterall, pretty much just a wrapper around the TensorFlow fit
function.
If you train directly in TensorFlow, the work flow would go something like this:
- Create TensorFlow model.
- Train TensorFlow model using TensorFlow
fit
function.
- Use NengoDL converter on trained TensorFlow model.
- Run NengoDL simulator on converted NengoDL network.
You can repeat steps 3 & 4 with spiking neurons, or whatever activation functions you desire. 