def test_pes_learning_rate(Simulator, plt, seed): n = 50 dt = 0.0005 T = 1.0 initial = 0.7 desired = -0.9 epsilon = 1e-3 # get to factor epsilon with T seconds # Get activity vector and initial decoders with Network(seed=seed) as model: x = nengo.Ensemble( n, 1, seed=seed, neuron_type=nengo.neurons.LIFRate()) y = nengo.Node(size_in=1) conn = nengo.Connection(x, y, synapse=None) sim = Simulator(model, dt=dt) a = get_activities(sim.model, x, [initial]) d = sim.data[conn].weights assert np.any(a > 0) # Use util function to calculate learning_rate init_error = float(desired - np.dot(d, a)) learning_rate, gamma = pes_learning_rate( epsilon / abs(init_error), a, T, dt) # Build model with no filtering on any connections with model: stim = nengo.Node(output=initial) ystar = nengo.Node(output=desired) conn.learning_rule_type = nengo.PES( pre_tau=1e-15, learning_rate=learning_rate) nengo.Connection(stim, x, synapse=None) nengo.Connection(ystar, conn.learning_rule, synapse=0, transform=-1) nengo.Connection(y, conn.learning_rule, synapse=0) p = nengo.Probe(y, synapse=None) decoders = nengo.Probe(conn, 'weights', synapse=None) sim = Simulator(model, dt=dt) sim.run(T) # Check that the final error is exactly epsilon assert np.allclose(abs(desired - sim.data[p][-1]), epsilon) # Check that all of the errors are given exactly by gamma**k k = np.arange(len(sim.trange())) error = init_error * gamma ** k assert np.allclose(sim.data[p].flatten(), desired - error) # Check that all of the decoders are equal to their analytical solution dk = d.T + init_error * a.T[:, None] * (1 - gamma ** k) / np.dot(a, a.T) assert np.allclose(dk, np.squeeze(sim.data[decoders].T)) plt.figure() plt.plot(sim.trange(), sim.data[p], lw=5, alpha=0.5) plt.plot(sim.trange(), desired - error, linestyle='--', lw=5, alpha=0.5)
def build_linear_system(model, conn, rng): eval_points = get_eval_points(model, conn, rng) activities = get_activities(model, conn.pre_obj, eval_points) if np.count_nonzero(activities) == 0: raise RuntimeError( "Building %s: 'activites' matrix is all zero for %s. " "This is because no evaluation points fall in the firing " "ranges of any neurons." % (conn, conn.pre_obj)) targets = get_targets(model, conn, eval_points) return eval_points, activities, targets
def tuning_curves(ens, sim, inputs=None): """Calculates the tuning curves of an ensemble. That is the neuron responses in dependence of the vector represented by the ensemble. For 1-dimensional ensembles, the unpacked return value of this function can be passed directly to ``matplotlib.pyplot.plot``. Parameters ---------- ens : nengo.Ensemble Ensemble to calculate the tuning curves of. sim : nengo.Simulator Simulator providing information about the built ensemble. (An unbuilt ensemble does not have tuning curves assigned to it.) inputs : sequence of ndarray, optional The inputs at which the tuning curves will be evaluated. For each of the ``D`` ensemble dimensions one array of dimensionality ``D`` is needed. The output of :func:`numpy.meshgrid` with ``indexing='ij'`` is in the right format. Returns ------- inputs : sequence of ndarray The passed or auto-generated ``inputs``. activities : ndarray The activities of the individual neurons given the ``inputs``. For ensembles with 1 dimension, the rows correspond to the ``inputs`` and the columns to individual neurons. For ensembles with > 1 dimension, the last dimension enumerates the neurons, the remaining dimensions map to ``inputs``. See Also -------- response_curves """ # note: imported here to avoid circular imports from nengo.builder.ensemble import ( # pylint: disable=import-outside-toplevel get_activities, ) if inputs is None: inputs = np.linspace(-ens.radius, ens.radius) if ens.dimensions > 1: inputs = npext.meshgrid_nd(*(ens.dimensions * [inputs])) else: inputs = [inputs] inputs = np.asarray(inputs).T else: inputs = np.asarray(inputs) eval_points = inputs.reshape((-1, ens.dimensions)) activities = get_activities(sim.data[ens], ens, eval_points) return inputs, activities.reshape(inputs.shape[:-1] + (-1, ))
def build_linear_system(model, conn, rng): eval_points = get_eval_points(model, conn, rng) ens = conn.pre_obj activities = get_activities(model.params[ens], ens, eval_points) if np.count_nonzero(activities) == 0: raise BuildError( "Building %s: 'activites' matrix is all zero for %s. " "This is because no evaluation points fall in the firing " "ranges of any neurons." % (conn, conn.pre_obj)) targets = get_targets(conn, eval_points, dtype=rc.float_dtype) return eval_points, activities, targets
def tuning_curves(ens, sim, inputs=None): """Calculates the tuning curves of an ensemble. That is the neuron responses in dependence of the vector represented by the ensemble. For 1-dimensional ensembles, the unpacked return value of this function can be passed directly to :func:`matplotlib.pyplot.plot`. Parameters ---------- ens : nengo.Ensemble Ensemble to calculate the tuning curves of. sim : nengo.Simulator Simulator providing information about the built ensemble. (An unbuilt ensemble does not have tuning curves assigned to it.) inputs : sequence of ndarray, optional The inputs at which the tuning curves will be evaluated. For each of the `D` ensemble dimensions one array of dimensionality `D` is needed. The output of :func:`numpy.meshgrid` with ``indexing='ij'`` is in the right format. Returns ------- inputs : sequence of ndarray The passed or auto-generated `inputs`. activities : ndarray The activities of the individual neurons given the `inputs`. For ensembles with 1 dimension, the rows correspond to the `inputs` and the columns to individual neurons. For ensembles with > 1 dimension, the last dimension enumerates the neurons, the remaining dimensions map to `inputs`. See Also -------- response_curves """ from nengo.builder.ensemble import get_activities if inputs is None: inputs = np.linspace(-ens.radius, ens.radius) if ens.dimensions > 1: inputs = npext.meshgrid_nd(*(ens.dimensions * [inputs])) else: inputs = [inputs] inputs = np.asarray(inputs).T else: inputs = np.asarray(inputs) eval_points = inputs.reshape((-1, ens.dimensions)) activities = get_activities(sim.data[ens], ens, eval_points) return inputs, activities.reshape(inputs.shape[:-1] + (-1,))
def build_linear_system(model, conn, rng): """Get all arrays needed to compute decoders.""" eval_points = get_eval_points(model, conn, rng) ens = conn.pre_obj activities = get_activities(model.params[ens], ens, eval_points) if np.count_nonzero(activities) == 0: raise BuildError( f"Building {conn}: 'activities' matrix is all zero for {conn.pre_obj}. " "This is because no evaluation points fall in the firing " "ranges of any neurons.") targets = get_targets(conn, eval_points, dtype=rc.float_dtype) return eval_points, activities, targets
def eval_point_decoding(conn, sim, eval_points=None): """Get the targets and actual decoded values for a set of eval points. This function evaluates the static decoding (i.e. using the neuron type's ``rates`` function) of a connection for a given set of evaluation points. Parameters ---------- conn : Connection The Connection to evaluate the decoding of. sim : Simulator A Nengo simulator storing the built connection. eval_points : array_like (N, E) (optional) An N x E array of evaluation points to evaluate the decoding for, where N is the number of points and E is the dimensionality of the input ensemble (i.e. ``conn.size_in``). If None (default), use the connection's training evaluation points. Returns ------- eval_points : ndarray (N, E) A shallow copy of the evaluation points used. E is the dimensionality of the connection input ensemble (i.e. ``conn.size_in``). targets : ndarray (N, D) The target function value at each evaluation point. decoded : ndarray (N, D) The decoded function value at each evaluation point. """ # pylint: disable=import-outside-toplevel # note: these are imported here to avoid circular imports from nengo import rc from nengo.builder.ensemble import get_activities from nengo.builder.connection import get_targets dtype = rc.float_dtype if eval_points is None: eval_points = sim.data[conn].eval_points else: eval_points = np.asarray(eval_points, dtype=dtype) ens = conn.pre_obj weights = sim.data[conn].weights activities = get_activities(sim.data[ens], ens, eval_points) decoded = np.dot(activities, weights.T) targets = get_targets(conn, eval_points, dtype=dtype) return eval_points, targets, decoded
def eval_point_decoding(conn, sim, eval_points=None): """Get the targets and actual decoded values for a set of eval points. This function evaluates the static decoding (i.e. using the neuron type's `rates` function) of a connection for a given set of evaluation points. Parameters ---------- conn : Connection The Connection to evaluate the decoding of. sim : Simulator A Nengo simulator storing the built connection. eval_points : array_like (N, E) (optional) An N x E array of evaluation points to evaluate the decoding for, where N is the number of points and E is the dimensionality of the input ensemble (i.e. `conn.size_in`). If None (default), use the connection's training evaluation points. Returns ------- eval_points : ndarray (N, E) A shallow copy of the evaluation points used. E is the dimensionality of the connection input ensemble (i.e. `conn.size_in`). targets : ndarray (N, D) The target function value at each evaluation point. decoded : ndarray (N, D) The decoded function value at each evaluation point. """ from nengo.builder.ensemble import get_activities from nengo.builder.connection import get_targets if eval_points is None: eval_points = sim.data[conn].eval_points else: eval_points = np.asarray(eval_points) decoders = sim.data[conn].decoders if decoders is None: raise ValueError("Connection must have decoders") activities = get_activities(sim.model, conn.pre_obj, eval_points) decoded = np.dot(activities, decoders.T) targets = get_targets(sim.model, conn, eval_points) return eval_points, targets, decoded
def _test_rls_network( Simulator, seed, plt, tols, dims=1, lrate=0.01, neuron_type=nengo.LIFRate(), tau=None, t_train=0.5, t_test=0.25, ): # Input is a scalar sinusoid with given frequency n_neurons = 100 freq = 5 # Learn a linear transformation within t_train seconds transform = np.random.RandomState(seed=seed).randn(dims, 1) lr = RLS(learning_rate=lrate, pre_synapse=tau) with nengo.Network(seed=seed) as model: u = nengo.Node(output=lambda t: np.sin(freq * 2 * np.pi * t)) x = nengo.Ensemble(n_neurons, 1, neuron_type=neuron_type) y = nengo.Node(size_in=dims) y_on = nengo.Node(size_in=dims) y_off = nengo.Node(size_in=dims) e = nengo.Node(size_in=dims, output=lambda t, e: e if t < t_train else np.zeros_like(e)) nengo.Connection(u, y, synapse=None, transform=transform) nengo.Connection(u, x, synapse=None) conn_on = nengo.Connection( x, y_on, synapse=None, learning_rule_type=lr, function=lambda _: np.zeros(dims), ) nengo.Connection(y, e, synapse=None, transform=-1) nengo.Connection(y_on, e, synapse=None) nengo.Connection(e, conn_on.learning_rule, synapse=tau) nengo.Connection(x, y_off, synapse=None, transform=transform) p_y = nengo.Probe(y, synapse=tau) p_y_on = nengo.Probe(y_on, synapse=tau) p_y_off = nengo.Probe(y_off, synapse=tau) p_inv_gamma = nengo.Probe(conn_on.learning_rule, "inv_gamma") with Simulator(model) as sim: sim.run(t_train + t_test) plt.plot(sim.trange(), sim.data[p_y_off], "k") plt.plot(sim.trange(), sim.data[p_y_on]) # Check _descstr ops = [op for op in sim.model.operators if isinstance(op, SimRLS)] assert len(ops) == 1 assert str(ops[0]).startswith("SimRLS") test = sim.trange() >= t_train on_versus_off = nrmse(sim.data[p_y_on][test], sim.data[p_y_off][test]) on_versus_ideal = nrmse(sim.data[p_y_on][test], sim.data[p_y][test]) off_versus_ideal = nrmse(sim.data[p_y_off][test], sim.data[p_y][test]) A = get_activities(sim.data[x], x, np.linspace(-1, 1, 1000)[:, None]) gamma_off = A.T.dot(A) + np.eye(n_neurons) / lr.learning_rate gamma_on = np.linalg.inv(sim.data[p_inv_gamma][-1]) gamma_off /= np.linalg.norm(gamma_off) gamma_on /= np.linalg.norm(gamma_on) gamma_diff = nrmse(gamma_on, gamma_off) print() print(on_versus_off, on_versus_ideal, off_versus_ideal, gamma_diff) print() assert on_versus_off < tols[0] assert on_versus_ideal < tols[1] assert off_versus_ideal < tols[2] assert gamma_diff < tols[3]
def build_bias(model, bioensemble, biases, method, bias_gain=5e-5): rng = np.random.RandomState(bioensemble.seed) neurons_lif = 100 neurons_bio = bioensemble.n_neurons tau = 0.01 lif = nengo.Ensemble( neuron_type=nengo.LIF(), dimensions=1, n_neurons=neurons_lif, seed=bioensemble.seed, add_to_container=False) model.seeds[lif] = bioensemble.seed # seeds normally set early in builder model.build(lif) # add to the model model.add_op(Copy(Signal(0), model.sig[lif]['in'], inc=True)) # connect input(t)=1 A = get_activities(model.params[lif], # grab tuning curve activities lif, model.params[lif].eval_points) # Desired output function Y -- just repeat "bias" m times Y = np.tile(biases, (A.shape[0], 1)) bias_decoders = nengo.solvers.LstsqL2()(A, Y)[0] # initialize synaptic locations syn_loc = get_synaptic_locations( rng, neurons_lif, neurons_bio, n_syn=1) syn_weights = np.zeros(( neurons_bio, neurons_lif, syn_loc.shape[2])) if method == 'weights': for b in range(syn_weights.shape[0]): syn_weights[b] = rng.uniform(np.max(biases), np.min(biases), size=syn_weights[b].shape) if method == 'weights_fixed': for b in range(syn_weights.shape[0]): syn_weights[b] = bias_gain * biases[b]**5 * np.ones(syn_weights[b].shape) # unit test that synapse and weight arrays are compatible shapes if not syn_loc.shape[:-1] == bias_decoders.T.shape: raise BuildError("Shape mismatch: syn_loc=%s, bias_decoders=%s" % (syn_loc.shape[:-1], bias_decoders)) # add synapses to the bioneurons with weights = bias_decoders neurons = model.params[bioensemble.neurons] for j, bahl in enumerate(neurons): assert isinstance(bahl, Bahl) loc = syn_loc[j] bahl.synapses[lif] = np.empty( (loc.shape[0], loc.shape[1]), dtype=object) for pre in range(loc.shape[0]): for syn in range(loc.shape[1]): # section = bahl.cell.tuft(loc[pre, syn]) section = bahl.cell.apical(loc[pre, syn]) # w_ij = np.dot(decoders[pre], gain * encoder) if method == 'decode': syn_weights[j, pre, syn] = bias_decoders[pre, j] w_ij = syn_weights[j, pre, syn] synapse = ExpSyn(section, w_ij, tau, loc[pre, syn]) bahl.synapses[lif][pre][syn] = synapse neuron.init() model.add_op(TransmitSpikes( lif, bioensemble, None, neurons, model.sig[lif]['out'], states=[model.time]))
def transform_ensemble( ens, conn_ins, sim, e_rev_E=4.33, # equiv. to 0mV for v_rest=-65mV, v_th=-50mV e_rev_I=-0.33, # equiv. to -70mV use_linear_avg_pot=False, use_conductance_synapses=True, use_factorised_weights=False, use_jbias=False): """ Creates an equivalent conductance based ensemble for the given input ensemble. Returns the node corresponding to the ensemble or None if the ensemble cannot be transformed. As a second return value returns a list containing the target dimensionalities of the newly created node for each dimension. ens: Ensemble that should be converted. conn_ins: list of input connections to that ensemble. conn_outs: list of output connections to that ensemble. sim: Simulation object that is used to fetch the decoders and encoders for the network. use_linear_avg_pot: if True, uses the linear average membrane potential estimate. use_conductance_synapses: if True, implements "normal" non-conductance based synapses. This is only useful for testing purposes. use_factorised_weights: if True, factorises the internal weight matrix into artificial encoders and decoders. use_jbias: if True, uses an external current source for the bias, otherwise decodes the bias from the pre-synaptic population. """ from nengo.builder.ensemble import get_activities # Make sure the ensemble this transformation operates on has either the # neuron type LIF or LIFRate. Fetch gains, biases, and encoders from the # ensemble. if not isinstance(ens.neuron_type, nengo.neurons.LIF): return None, None # Abort if the ensemble has no input. if len(conn_ins) == 0: return None, None # Abort if the user requested the ensemble not to be transformed if hasattr( ens, 'use_conductance_synapses') and not ens.use_conductance_synapses: return None, None n_neurons = ens.n_neurons encoder = sim.data[ens].encoders bias = sim.data[ens].bias gain = sim.data[ens].gain # Iterate over all input connections and calculate the total number of # neurons feeding into the node. Make sure the input connections are all # Ensemble objects. Fetch the decoders for the individual connections. n_dims_in = 0 decoders = [None] * len(conn_ins) activities = [None] * len(conn_ins) encoders = [None] * len(conn_ins) connectivity = [None] * len(conn_ins) direct = [False] * len(conn_ins) for i, conn_in in enumerate(conn_ins): pre_obj = conn_in.pre_obj post_obj = conn_in.post_obj # Fetch the decoders and the number of neurons/dimensions in the pre- # ensemble if isinstance(pre_obj, nengo.Ensemble): decoders[i] = sim.data[conn_in].weights n_dims = pre_obj.n_neurons elif isinstance(pre_obj, nengo.ensemble.Neurons): pre_obj = pre_obj.ensemble n_dims = pre_obj.n_neurons W = sim.data[conn_in].weights if (np.ndim(W) == 0): W = np.eye(n_dims) * W elif (np.ndim(W) == 1): W = np.diag(W) out_dim, in_dim = W.shape decoders[i] = np.zeros((out_dim, n_dims)) decoders[i][:, conn_in.pre_slice] = W elif isinstance(pre_obj, nengo.Node): n_dims = pre_obj.size_out decoders[i] = sim.data[conn_in].weights if (np.ndim(decoders[i]) == 0): decoders[i] = ( np.eye(n_dims) * decoders[i])[conn_in.pre_slice, :] direct[i] = True # Fetch the activities required for bias decoding if not use_jbias: if isinstance(pre_obj, nengo.Ensemble): activities[i] = get_activities(sim.data[pre_obj], pre_obj, sim.data[pre_obj].eval_points) elif isinstance(pre_obj, nengo.Node): activities[i] = np.zeros((1, pre_obj.size_out)) # Apply the post-slice (pre-slice is already included in the decoder), # special treatment required for ".neurons" connections if isinstance(post_obj, nengo.ensemble.Neurons): encoders[i] = ( np.eye(n_neurons) / gain.reshape(-1, 1))[:, conn_in.post_slice] else: encoders[i] = encoder[:, conn_in.post_slice] # Scale the encoders by the radius encoders[i] = encoders[i] / ens.radius connectivity[i] = slice(n_dims_in, n_dims_in + n_dims) n_dims_in += n_dims # Create the IfCondExp instance model = IfCondExp( ens.n_neurons, tau_rc=ens.neuron_type.tau_rc, tau_ref=ens.neuron_type.tau_ref, e_rev_E=e_rev_E, e_rev_I=e_rev_I, use_linear_avg_pot=use_linear_avg_pot, use_conductance_synapses=use_conductance_synapses) # Assemble the simulator node node = nengo.Node( size_out=n_neurons, size_in=n_dims_in, output=sim_if_cond_exp( decoders=decoders, activities=activities, encoders=encoders, direct=direct, bias=bias, gain=gain, model=model, dt=sim.dt, use_jbias=use_jbias, use_factorised_weights=use_factorised_weights), label=ens.label) return node, connectivity