NengoDL Simulator¶
This is the class that allows users to access the nengo_dl
backend. This can be used as a drop-in replacement for nengo.Simulator
(i.e., simply replace any instance of nengo.Simulator
with
nengo_dl.Simulator
and everything will continue to function as
normal).
In addition, the Simulator exposes features unique to the
nengo_dl
backend, such as Simulator.train()
.
Simulator arguments¶
The nengo_dl
Simulator
has a number of optional arguments, beyond
those in nengo.Simulator
, which control features specific to
the nengo_dl
backend. The full class documentation can be viewed
below; here we will explain the practical usage of these
parameters.
dtype¶
This specifies the floating point precision to be used for the simulator’s
internal computations. It can be either tf.float32
or tf.float64
,
for 32 or 64-bit precision, respectively. 32-bit precision is the default,
as it is faster, will use less memory, and in most cases will not make a
difference in the results of the simulation. However, if very precise outputs
are required then this can be changed to tf.float64
.
device¶
This specifies the computational device on which the simulation will
run. The default is None
, which means that operations will be assigned
according to TensorFlow’s internal logic (generally speaking, this means that
things will be assigned to the GPU if tensorflow-gpu
is installed,
otherwise everything will be assigned to the CPU). The device can be set
manually by passing the TensorFlow device specification to this
parameter. For example, setting device="/cpu:0"
will force everything
to run on the CPU. This may be worthwhile for small models, where the extra
overhead of communicating with the GPU outweighs the actual computations. On
systems with multiple GPUs, device="/gpu:0"
/"/gpu:1"
/etc. will select
which one to use.
unroll_simulation¶
This controls how many simulation iterations are executed each time through the outer simulation loop. That is, we could run 20 timesteps as
for i in range(20):
<run 1 step>
or
for i in range(5):
<run 1 step>
<run 1 step>
<run 1 step>
<run 1 step>
This is an optimization process known as “loop unrolling”, and
unroll_simulation
controls how many simulation steps are unrolled. The
first example above would correspond to unroll_simulation=1
, and the
second would be unroll_simulation=4
.
Unrolling the simulation will result in faster simulation speed, but increased build time and memory usage.
In general, unrolling the simulation will have no impact on the output of a
simulation. The only case in which unrolling may have an impact is if
the number of simulation steps is not evenly divisible by
unroll_simulation
. In that case extra simulation steps will be executed,
and then data will be truncated to the correct number of steps. However, those
extra steps could still change the internal state of the simulation, which
will affect any subsequent calls to sim.run
. So it is recommended that the
number of steps always be evenly divisible by unroll_simulation
.
minibatch_size¶
nengo_dl
allows a model to be simulated with multiple simultaneous inputs,
processing those values in parallel through the network. For example, instead
of executing a model three times with three different inputs, the model can
be executed once with those three inputs in parallel. minibatch_size
specifies how many inputs will be processed at a time. The default is
None
, meaning that this feature is not used and only one input will be
processed at a time (as in standard Nengo simulators).
In order to take advantage of the parallel inputs, multiple inputs need to
be passed to Simulator.run()
via the input_feeds
argument. This
is discussed in more detail below.
When using Simulator.train()
, this parameter controls how many items
from the training data will be used for each optimization iteration.
tensorboard¶
This can be used to specify an output directory if you would like to export data from the simulation in a format that can be visualized in TensorBoard.
To view the collected data, run the command
tensorboard --logdir <tensorboard_dir>
(where tensorboard_dir
is the directory name passed to tensorboard
),
then open a web browser and navigate to http://localhost:6006.
By default the TensorBoard output will only contain a visualization of the TensorFlow graph constructed for this Simulator. However, TensorBoard can also be used to track various aspects of the simulation throughout the training process; see the sim.train documentation for details.
Repeated Simulator calls with the same output directory will be organized into
subfolders according to run number (e.g., <tensorboard_dir>/run_0
).
Simulator.run arguments¶
Simulator.run()
(and its variations Simulator.step()
/
Simulator.run_steps()
) also have some optional parameters beyond those
in the standard Nengo simulator.
input_feeds¶
This parameter can be used to override the value of any
input Node
in a model (an input node is defined as
a node with no incoming connections). For example
n_steps = 5
with nengo.Network() as net:
node = nengo.Node([0])
p = nengo.Probe(node)
with nengo_dl.Simulator(net) as sim:
sim.run_steps(n_steps)
will execute the model in the standard way, and if we check the output of
node
print(sim.data[p])
>>> [[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]
we see that it is all zero, as defined.
input_feeds
is specified as a
dictionary of {my_node: override_value}
pairs, where my_node
is the
Node to be overridden and override_value
is a numpy array with shape
(minibatch_size, n_steps, my_node.size_out)
that gives the Node output
value on each simulation step. For example, if we instead run the model via
sim.run_steps(n_steps, input_feeds={node: np.ones((1, n_steps, 1))})
print(sim.data[p])
>>> [[ 1.] [ 1.] [ 1.] [ 1.] [ 1.]]
we see that the output of node
is all ones, which is the override
value we specified.
input_feeds
are usually used in concert with the minibatching feature of
nengo_dl
(see above). nengo_dl
allows multiple
inputs to be processed simultaneously, but when we construct a
Node
we can only specify one value. For example, if we
use minibatching on the above network
mini = 3
with nengo_dl.Simulator(net, minibatch_size=mini) as sim:
sim.run_steps(n_steps)
print(sim.data[p])
>>> [[[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]
[[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]
[[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]]
we see that the output is an array of zeros with size
(mini, n_steps, 1)
. That is, we simulated 3 inputs
simultaneously, but those inputs all had the same value (the one we defined
when the Node was constructed) so it wasn’t very
useful. To take full advantage of the minibatching we need to override the
node values, so that we can specify a different value for each item in the
minibatch:
with nengo_dl.Simulator(net, minibatch_size=mini) as sim:
sim.run_steps(n_steps, input_feeds={
node: np.zeros((mini, n_steps, 1)) + np.arange(mini)[:, None, None]})
print(sim.data[p])
>>> [[[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]
[[ 1.] [ 1.] [ 1.] [ 1.] [ 1.]]
[[ 2.] [ 2.] [ 2.] [ 2.] [ 2.]]]
Here we can see that 3 independent inputs have been processed during the simulation. In a simple network such as this, minibatching will not make much difference. But for larger models it will be much more efficient to process multiple inputs in parallel rather than one at a time.
profile¶
If set to True
, profiling data will be collected while the simulation
runs. This will significantly slow down the simulation, so it should be left
on False
(the default) in most cases. It is mainly used by developers,
in order to help identify performance bottlenecks.
Profiling data will be saved to <nengo-dl>/data/nengo_dl_profile.json
. It
can be viewed by opening a Chrome browser, navigating to
chrome://tracing and loading the nengo_dl_profile.json
file.
A dict of config options can be passed instead of True
, which will be
passed on to the TensorFlow profiler. See the tf.profiler documentation
for details on the available options.
Note that in order for GPU profiling to work, you need to manually add
<cuda>\extras\CUPTI\libx64
to your path (where <cuda>
is your
CUDA installation directory).
API¶
-
class
nengo_dl.simulator.
Simulator
(network, dt=0.001, seed=None, model=None, dtype=tf.float32, device=None, unroll_simulation=1, minibatch_size=None, tensorboard=None, progress_bar=True)[source]¶ Simulate network using the
nengo_dl
backend.Parameters: - network :
Network
or None A network object to be built and then simulated. If None, then a built model must be passed to
model
instead- dt : float, optional
Length of a simulator timestep, in seconds
- seed : int, optional
Seed for all stochastic operators used in this simulator
- model :
Model
, optional Pre-built model object
- dtype :
tf.DType
, optional Floating point precision to use for simulation
- device : None or
"/cpu:0"
or"/gpu:[0-n]"
, optional Device on which to execute computations (if None then uses the default device as determined by TensorFlow)
- unroll_simulation : int, optional
Unroll simulation loop by explicitly building the given number of iterations into the computation graph (improves simulation speed but increases build time)
- minibatch_size : int, optional
The number of simultaneous inputs that will be passed through the network
- tensorboard : str, optional
If not None, save network output in the TensorFlow summary format to the given directory, which can be loaded into TensorBoard
- progress_bar : bool, optional
If True (default), display progress information when building a model
-
reset
(seed=None)[source]¶ Resets the simulator to initial conditions.
Parameters: - seed : int, optional
If not None, overwrite the default simulator seed with this value (note: this becomes the new default simulator seed)
-
soft_reset
(include_trainable=False, include_probes=False)[source]¶ Resets the internal state of the simulation, but doesn’t rebuild the graph.
Parameters: - include_trainable : bool, optional
If True, also reset any training that has been performed on network parameters (e.g., connection weights)
- include_probes : bool, optional
If True, also clear probe data
-
step
(**kwargs)[source]¶ Run the simulation for one time step.
Parameters: - kwargs : dict
See
run_steps()
Notes
Progress bar is disabled by default when running via this method.
-
run
(time_in_seconds, **kwargs)[source]¶ Simulate for the given length of time.
Parameters: - time_in_seconds : float
Run the simulator for the given number of simulated seconds
- kwargs : dict
See
run_steps()
-
run_steps
(n_steps, input_feeds=None, profile=False, progress_bar=True, extra_feeds=None)[source]¶ Simulate for the given number of steps.
Parameters: - n_steps : int
The number of simulation steps to be executed
- input_feeds : dict of {
Node
:ndarray
} Override the values of input Nodes with the given data. Arrays should have shape
(sim.minibatch_size, n_steps, node.size_out)
.- profile : bool, optional
If True, collect TensorFlow profiling information while the simulation is running (this will slow down the simulation). Can also pass a dict of config options for the profiler.
- progress_bar : bool, optional
If True, print information about the simulation status to standard output.
- extra_feeds : dict of {
tf.Tensor
:ndarray
} Can be used to feed a value for arbitrary Tensors in the simulation (will be passed directly to the TensorFlow session)
Notes
If
unroll_simulation=x
is specified, andn_steps > x
, this will repeatedly executex
timesteps until the the number of steps executed is >=n_steps
.
-
train
(inputs, targets, optimizer, n_epochs=1, objective='mse', shuffle=True, truncation=None, summaries=None, profile=False, extra_feeds=None)[source]¶ Optimize the trainable parameters of the network using the given optimization method, minimizing the objective value over the given inputs and targets.
Parameters: - inputs : dict of {
Node
:ndarray
} Input values for Nodes in the network; arrays should have shape
(batch_size, n_steps, node.size_out)
- targets : dict of {
Probe
:ndarray
} Desired output value at Probes, corresponding to each value in
inputs
; arrays should have shape(batch_size, n_steps, probe.size_in)
- optimizer :
tf.train.Optimizer
TensorFlow optimizer, e.g.
tf.train.GradientDescentOptimizer(learning_rate=0.1)
- n_epochs : int, optional
Run training for the given number of epochs (complete passes through
inputs
)- objective :
"mse"
or callable, optional The objective to be minimized. Passing
"mse"
will train with mean squared error. A custom functionf(output, target) -> loss
can be passed that consumes the actual output and target output for a probe intargets
and returns atf.Tensor
representing the scalar loss value for that Probe (loss will be averaged across Probes). Note that by default the same objective will be used for all probes intargets
; a dictionary of{probe: obj, ...}
can be passed forobjective
to specify a different objective for each probe.- shuffle : bool, optional
If True, randomize the data into different minibatches each epoch
- truncation: int, optional
If not None, use truncated backpropagation when training the network, with the given truncation length.
- summaries : list of
Connection
orEnsemble
orNeurons
or"loss"
ortf.Tensor
} If not None, collect data during the training process using TensorFlow’s
tf.summary
format. The summary objects can be a Connection (in which case data on the corresponding weights will be collected), Ensemble (encoders), Neurons (biases), or"loss"
(the loss value forobjective
). The user can also create their own summaries and pass in the Tensors representing the summary ops.- profile : bool, optional
If True, collect TensorFlow profiling information while training (this will slow down the training). Can also pass a dict of config options for the profiler.
- extra_feeds : dict of {
tf.Tensor
:ndarray
} Can be used to feed a value for arbitrary Tensors in the simulation (will be passed directly to the TensorFlow session)
Notes
Most deep learning methods require the network to be differentiable, which means that trying to train a network with non-differentiable elements will result in an error. Examples of common non-differentiable elements include
LIF
,Direct
, or processes/neurons that don’t have a custom TensorFlow implementation (seeprocess_builders.SimProcessBuilder
/neuron_builders.SimNeuronsBuilder
)- inputs : dict of {
-
loss
(inputs, targets, objective, extra_feeds=None)[source]¶ Compute the loss value for the given objective and inputs/targets.
Parameters: - inputs : dict of {
Node
:ndarray
} Input values for Nodes in the network; arrays should have shape
(batch_size, n_steps, node.size_out)
- targets : dict of {
Probe
:ndarray
} Desired output value at Probes, corresponding to each value in
inputs
; arrays should have shape(batch_size, n_steps, probe.size_in)
- objective :
"mse"
or callable The objective used to compute loss. Passing
"mse"
will use mean squared error. A custom functionf(output, target) -> loss
can be passed that consumes the actual output and target output for a probe intargets
and returns atf.Tensor
representing the scalar loss value for that Probe (loss will be averaged across Probes). Note that by default the same objective will be used for all probes intargets
; a dictionary of{probe: obj, ...}
can be passed forobjective
to specify a different objective for each probe.- extra_feeds : dict of {
tf.Tensor
:ndarray
} Can be used to feed a value for arbitrary Tensors in the simulation (will be passed directly to the TensorFlow session)
- inputs : dict of {
-
save_params
(path, include_global=True, include_local=False)[source]¶ Save network parameters to the given
path
.Parameters: - path : str
Filepath of parameter output file
- include_global : bool, optional
If True (default True), save global/trainable network variables
- include_local : bool, optional
If True (default False), save local (non-trainable) network variables
Notes
This function is useful for saving/loading entire models; for saving/loading individual objects within a model, see
get_nengo_params()
.
-
load_params
(path, include_global=True, include_local=False)[source]¶ Load network parameters from the given
path
.Parameters: - path : str
Filepath of parameter input file
- include_global : bool, optional
If True (default True), load global (trainable) network variables
- include_local : bool, optional
If True (default False), load local (non-trainable) network variables
Notes
This function is useful for saving/loading entire models; for saving/loading individual objects within a model, see
get_nengo_params()
.
-
get_nengo_params
(nengo_objs, as_dict=False)[source]¶ Extract model parameters in a form that can be used to initialize Nengo objects in a different model.
For example:
with nengo.Network() as net: a = nengo.Ensemble(10, 1) b = nengo.Ensemble(10, 1) c = nengo.Connection(a, b) with nengo_dl.Simulator(net) as sim: # < do some optimization > params = sim.get_nengo_params([a, b, c]) with nengo.Network() as new_net: # < build some other network > # now we want to insert two connected ensembles with the same # parameters as our previous network: d = nengo.Ensemble(10, 1, **params[0]) e = nengo.Ensemble(10, 1, **params[1]) f = nengo.Connection(d, e, **params[2])
Parameters: - nengo_objs : (list of)
Ensemble
orConnection
A single object or list of objects for which we want to get the parameters.
- as_dict : bool, optional
If True, return the values as a dictionary keyed by object label, instead of a list (the default). Note that in this case labels must be unique.
Returns: - (list or dict) of dicts
kwarg dicts corresponding to
nengo_objs
(passing these dicts as kwargs when creating new Nengo objects will result in a new object with the same parameters as the source object). A single kwarg dict if a single object was passed in, or a list (dict ifas_dict=True
) of kwargs corresponding to multiple input objects.
- nengo_objs : (list of)
-
check_gradients
(outputs=None, atol=1e-05, rtol=0.001)[source]¶ Perform gradient checks for the network (used to verify that the analytic gradients are correct).
Raises a simulation error if the difference between analytic and numeric gradient is greater than
atol + rtol * numeric_grad
(elementwise).Parameters: - outputs :
tf.Tensor
or list oftf.Tensor
or list ofProbe
Compute gradients wrt this output (if None, computes wrt each output probe)
- atol : float, optional
Absolute error tolerance
- rtol : float, optional
Relative (to numeric grad) error tolerance
Notes
Calling this function will reset all values in the network, so it should not be intermixed with calls to
Simulator.run()
.- outputs :
-
trange
(dt=None)[source]¶ Create a vector of times matching probed data.
Note that the range does not start at 0 as one might expect, but at the first timestep (i.e.,
dt
).Parameters: - dt : float, optional
The sampling period of the probe to create a range for; if None, the simulator’s
dt
will be used.
- network :
-
class
nengo_dl.simulator.
SimulationData
(sim, minibatched)[source]¶ Data structure used to access simulation data from the model.
The main use case for this is to access Probe data; for example,
probe_data = sim.data[my_probe]
. However, it is also used to access the parameters of objects in the model; for example, after the model has been optimized viaSimulator.train()
, the updated encoder values for an ensemble can be accessed viatrained_encoders = sim.data[my_ens].encoders
.Parameters: - sim :
Simulator
The simulator from which data will be drawn
- minibatched : bool
If False, discard the minibatch dimension on probe data
Notes
SimulationData shouldn’t be created/accessed directly by the user, but rather via
sim.data
(which is an instance of SimulationData).-
__getitem__
(obj)[source]¶ Return the data associated with
obj
.Parameters: - obj :
Probe
orEnsemble
orConnection
Object whose simulation data is being accessed
Returns: - :class:`~numpy:numpy.ndarray` or :class:`~nengo:nengo.builder.ensemble.BuiltEnsemble` or :class:`~nengo:nengo.builder.connection.BuiltConnection`
Array containing probed data if
obj
is aProbe
, otherwise the corresponding parameter object
- obj :
-
get_param
(obj, attr)[source]¶ Returns the current parameter value for the given object.
Parameters: - obj :
NengoObject
The nengo object for which we want to know the parameters
- attr : str
The parameter of
obj
to be returned
Returns: - :class:`~numpy:numpy.ndarray`
Current value of the parameters associated with the given object
Notes
Parameter values should be accessed through
sim.data
(which will call this function if necessary), rather than directly through this function.- obj :
- sim :