def get_gain_bias(ens, rng=np.random): if ens.gain is not None and ens.bias is not None: gain = get_samples(ens.gain, ens.n_neurons, rng=rng) bias = get_samples(ens.bias, ens.n_neurons, rng=rng) max_rates, intercepts = ens.neuron_type.max_rates_intercepts( gain, bias) if (ens.max_rates is not Ensemble.max_rates.default or ens.intercepts is not Ensemble.intercepts.default): warnings.warn(NengoWarning( "Specifying the gains and biases for %s imposes a set of " "maximum firing rates and intercepts. Further specifying " "either max_rates or intercepts has no effect." % ens)) elif ens.gain is not None or ens.bias is not None: # TODO: handle this instead of error raise NotImplementedError("gain or bias set for %s, but not both. " "Solving for one given the other is not " "implemented yet." % ens) else: max_rates = get_samples(ens.max_rates, ens.n_neurons, rng=rng) intercepts = get_samples(ens.intercepts, ens.n_neurons, rng=rng) gain, bias = ens.neuron_type.gain_bias(max_rates, intercepts) if gain is not None and ( not np.all(np.isfinite(gain)) or np.any(gain <= 0.)): raise BuildError( "The specified intercepts for %s lead to neurons with " "negative or non-finite gain. Please adjust the intercepts so " "that all gains are positive. For most neuron types (e.g., " "LIF neurons) this is achieved by reducing the maximum " "intercept value to below 1." % ens) return gain, bias, max_rates, intercepts
def get_gain_bias(ens, rng=np.random, intercept_limit=1.0, dtype=None): # Modified from the Nengo version to handle `intercept_limit` dtype = nengo.rc.float_dtype if dtype is None else dtype if ens.gain is not None and ens.bias is not None: gain = get_samples(ens.gain, ens.n_neurons, rng=rng) bias = get_samples(ens.bias, ens.n_neurons, rng=rng) max_rates, intercepts = ens.neuron_type.max_rates_intercepts(gain, bias) elif ens.gain is not None or ens.bias is not None: # TODO: handle this instead of error raise NotImplementedError( "gain or bias set for %s, but not both. " "Solving for one given the other is not " "implemented yet." % ens ) else: int_distorarray = ens.intercepts if isinstance(int_distorarray, nengo.dists.Uniform): if int_distorarray.high > intercept_limit: warnings.warn( "Intercepts are larger than intercept limit (%g). " "High intercept values cause issues when discretizing " "the model for running on Loihi." % intercept_limit ) int_distorarray = nengo.dists.Uniform( min(int_distorarray.low, intercept_limit), min(int_distorarray.high, intercept_limit), ) max_rates = get_samples(ens.max_rates, ens.n_neurons, rng=rng) intercepts = get_samples(int_distorarray, ens.n_neurons, rng=rng) if np.any(intercepts > intercept_limit): intercepts[intercepts > intercept_limit] = intercept_limit warnings.warn( "Intercepts are larger than intercept limit (%g). " "High intercept values cause issues when discretizing " "the model for running on Loihi." % intercept_limit ) gain, bias = ens.neuron_type.gain_bias(max_rates, intercepts) if gain is not None and (not np.all(np.isfinite(gain)) or np.any(gain <= 0.0)): raise BuildError( f"{ens}: The specified intercepts lead to neurons with negative or " "non-finite gain. Please adjust the intercepts so that all gains are " "positive. For most neuron types (e.g., LIF neurons) this is achieved " "by reducing the maximum intercept value to below 1." ) dtype = nengo.rc.float_dtype gain = gain.astype(dtype) if gain is not None else gain bias = bias.astype(dtype) if bias is not None else bias max_rates = max_rates.astype(dtype) if max_rates is not None else max_rates intercepts = intercepts.astype(dtype) if intercepts is not None else intercepts return gain, bias, max_rates, intercepts
def get_gain_bias(ens, rng=np.random): if ens.gain is not None and ens.bias is not None: gain = get_samples(ens.gain, ens.n_neurons, rng=rng) bias = get_samples(ens.bias, ens.n_neurons, rng=rng) max_rates, intercepts = None, None # TODO: determine from gain & bias elif ens.gain is not None or ens.bias is not None: # TODO: handle this instead of error raise NotImplementedError("gain or bias set for %s, but not both. " "Solving for one given the other is not " "implemented yet." % ens) else: max_rates = get_samples(ens.max_rates, ens.n_neurons, rng=rng) intercepts = get_samples(ens.intercepts, ens.n_neurons, rng=rng) gain, bias = ens.neuron_type.gain_bias(max_rates, intercepts) return gain, bias, max_rates, intercepts
def get_gain_bias(ens, rng=np.random, dtype=None): """Compute concrete gain and bias for ensemble.""" dtype = rc.float_dtype if dtype is None else dtype if ens.gain is not None and ens.bias is not None: gain = get_samples(ens.gain, ens.n_neurons, rng=rng) bias = get_samples(ens.bias, ens.n_neurons, rng=rng) max_rates, intercepts = ens.neuron_type.max_rates_intercepts(gain, bias) if ( ens.max_rates != Ensemble.max_rates.default or ens.intercepts != Ensemble.intercepts.default ): warnings.warn( NengoWarning( f"Specifying the gains and biases for {ens} imposes a set of " "maximum firing rates and intercepts. Further specifying " "either max_rates or intercepts has no effect." ) ) elif ens.gain is not None or ens.bias is not None: # TODO: handle this instead of error raise NotImplementedError( f"gain or bias set for {ens}, but not both. " "Solving for one given the other is not implemented yet." ) else: max_rates = get_samples(ens.max_rates, ens.n_neurons, rng=rng) intercepts = get_samples(ens.intercepts, ens.n_neurons, rng=rng) gain, bias = ens.neuron_type.gain_bias(max_rates, intercepts) if gain is not None and (not np.all(np.isfinite(gain)) or np.any(gain <= 0.0)): raise BuildError( f"The specified intercepts for {ens} lead to neurons with " "negative or non-finite gain. Please adjust the intercepts so " "that all gains are positive. For most neuron types (e.g., " "LIF neurons) this is achieved by reducing the maximum " "intercept value to below 1." ) gain = gain.astype(dtype) if gain is not None else gain bias = bias.astype(dtype) if bias is not None else bias max_rates = max_rates.astype(dtype) if max_rates is not None else max_rates intercepts = intercepts.astype(dtype) if intercepts is not None else intercepts return gain, bias, max_rates, intercepts
def make_state(self, n_neurons, rng=np.random, dtype=None): dtype = rc.float_dtype if dtype is None else dtype state = {} initial_state = {} if self.initial_state is None else self.initial_state for name in self.state: dist = initial_state.get(name, self.state[name]) state[name] = get_samples(dist, n=n_neurons, d=None, rng=rng).astype(dtype, copy=False) return state
def get_gain_bias(ens, rng=np.random, intercept_limit=1.0): if ens.gain is not None and ens.bias is not None: gain = get_samples(ens.gain, ens.n_neurons, rng=rng) bias = get_samples(ens.bias, ens.n_neurons, rng=rng) max_rates, intercepts = ens.neuron_type.max_rates_intercepts( gain, bias) elif ens.gain is not None or ens.bias is not None: # TODO: handle this instead of error raise NotImplementedError("gain or bias set for %s, but not both. " "Solving for one given the other is not " "implemented yet." % ens) else: int_distorarray = ens.intercepts if isinstance(int_distorarray, nengo.dists.Uniform): if int_distorarray.high > intercept_limit: warnings.warn( "Intercepts are larger than intercept limit (%g). " "High intercept values cause issues when discretizing " "the model for running on Loihi." % intercept_limit) int_distorarray = nengo.dists.Uniform( min(int_distorarray.low, intercept_limit), min(int_distorarray.high, intercept_limit)) max_rates = get_samples(ens.max_rates, ens.n_neurons, rng=rng) intercepts = get_samples(int_distorarray, ens.n_neurons, rng=rng) if np.any(intercepts > intercept_limit): intercepts[intercepts > intercept_limit] = intercept_limit warnings.warn( "Intercepts are larger than intercept limit (%g). " "High intercept values cause issues when discretizing " "the model for running on Loihi." % intercept_limit) gain, bias = ens.neuron_type.gain_bias(max_rates, intercepts) if gain is not None and ( not np.all(np.isfinite(gain)) or np.any(gain <= 0.)): raise BuildError( "The specified intercepts for %s lead to neurons with " "negative or non-finite gain. Please adjust the intercepts so " "that all gains are positive. For most neuron types (e.g., " "LIF neurons) this is achieved by reducing the maximum " "intercept value to below 1." % ens) return gain, bias, max_rates, intercepts
def build_ensemble(model, ens): # Create random number generator rng = np.random.RandomState(model.seeds[ens]) eval_points = gen_eval_points(ens, ens.eval_points, rng=rng) # Set up encoders if isinstance(ens.neuron_type, nengo.Direct): encoders = np.identity(ens.dimensions) elif isinstance(ens.encoders, Distribution): encoders = get_samples(ens.encoders, ens.n_neurons, ens.dimensions, rng=rng) else: encoders = npext.array(ens.encoders, min_dims=2, dtype=np.float64) if ens.normalize_encoders: encoders /= npext.norm(encoders, axis=1, keepdims=True) # Build the neurons gain, bias, max_rates, intercepts = get_gain_bias(ens, rng, model.intercept_limit) group = CxGroup(ens.n_neurons, label='%s' % ens) group.bias[:] = bias model.build(ens.neuron_type, ens.neurons, group) # set default filter just in case no other filter gets set group.configure_default_filter(model.inter_tau, dt=model.dt) if ens.noise is not None: raise NotImplementedError("Ensemble noise not implemented") # Scale the encoders if isinstance(ens.neuron_type, nengo.Direct): raise NotImplementedError("Direct neurons not implemented") # scaled_encoders = encoders else: # to keep scaling reasonable, we don't include the radius # scaled_encoders = encoders * (gain / ens.radius)[:, np.newaxis] scaled_encoders = encoders * gain[:, np.newaxis] model.add_group(group) model.objs[ens]['in'] = group model.objs[ens]['out'] = group model.objs[ens.neurons]['in'] = group model.objs[ens.neurons]['out'] = group model.params[ens] = BuiltEnsemble(eval_points=eval_points, encoders=encoders, intercepts=intercepts, max_rates=max_rates, scaled_encoders=scaled_encoders, gain=gain, bias=bias)
def get_gain_bias(ens, rng=np.random): if ens.gain is not None and ens.bias is not None: gain = get_samples(ens.gain, ens.n_neurons, rng=rng) bias = get_samples(ens.bias, ens.n_neurons, rng=rng) max_rates, intercepts = None, None # TODO: determine from gain & bias elif ens.gain is not None or ens.bias is not None: # TODO: handle this instead of error raise NotImplementedError("gain or bias set for %s, but not both. " "Solving for one given the other is not " "implemented yet." % ens) else: max_rates = get_samples(ens.max_rates, ens.n_neurons, rng=rng) intercepts = get_samples(ens.intercepts, ens.n_neurons, rng=rng) gain, bias = ens.neuron_type.gain_bias(max_rates, intercepts) if gain is not None and (not np.all(np.isfinite(gain)) or np.any(gain <= 0.)): raise BuildError( "The specified intercepts for %s lead to neurons with " "negative or non-finite gain. Please adjust the intercepts so " "that all gains are positive. For most neuron types (e.g., " "LIF neurons) this is achieved by reducing the maximum " "intercept value to below 1." % ens) return gain, bias, max_rates, intercepts
def build_connection(model, conn): """Builds a `.Connection` object into a model. A brief summary of what happens in the connection build process, in order: 1. Solve for decoders. 2. Combine transform matrix with decoders to get weights. 3. Add operators for computing the function or multiplying neural activity by weights. 4. Call build function for the synapse. 5. Call build function for the learning rule. 6. Add operator for applying learning rule delta to weights. Some of these steps may be altered or omitted depending on the parameters of the connection, in particular the pre and post types. Parameters ---------- model : Model The model to build into. conn : Connection The connection to build. Notes ----- Sets ``model.params[conn]`` to a `.BuiltConnection` instance. """ # Create random number generator rng = np.random.RandomState(model.seeds[conn]) # Get input and output connections from pre and post def get_prepost_signal(is_pre): target = conn.pre_obj if is_pre else conn.post_obj key = "out" if is_pre else "in" if target not in model.sig: raise BuildError( "Building %s: the %r object %s is not in the " "model, or has a size of zero." % (conn, "pre" if is_pre else "post", target) ) if key not in model.sig[target]: raise BuildError( "Building %s: the %r object %s has a %r size of zero." % (conn, "pre" if is_pre else "post", target, key) ) return model.sig[target][key] model.sig[conn]["in"] = get_prepost_signal(is_pre=True) model.sig[conn]["out"] = get_prepost_signal(is_pre=False) weights = None eval_points = None solver_info = None signal_size = conn.size_out post_slice = conn.post_slice # Sample transform if given a distribution transform = get_samples(conn.transform, conn.size_out, d=conn.size_mid, rng=rng) # Figure out the signal going across this connection in_signal = model.sig[conn]["in"] if isinstance(conn.pre_obj, Node) or ( isinstance(conn.pre_obj, Ensemble) and isinstance(conn.pre_obj.neuron_type, Direct) ): # Node or Decoded connection in directmode weights = transform sliced_in = slice_signal(model, in_signal, conn.pre_slice) if conn.function is None: in_signal = sliced_in elif isinstance(conn.function, np.ndarray): raise BuildError("Cannot use function points in direct connection") else: in_signal = Signal(np.zeros(conn.size_mid), name="%s.func" % conn) model.add_op(SimPyFunc(in_signal, conn.function, None, sliced_in)) elif isinstance(conn.pre_obj, Ensemble): # Normal decoded connection eval_points, weights, solver_info = build_decoders(model, conn, rng, transform) if conn.solver.weights: model.sig[conn]["out"] = model.sig[conn.post_obj.neurons]["in"] signal_size = conn.post_obj.neurons.size_in post_slice = Ellipsis # don't apply slice later else: weights = transform in_signal = slice_signal(model, in_signal, conn.pre_slice) if isinstance(conn.post_obj, Neurons): weights = multiply(model.params[conn.post_obj.ensemble].gain[post_slice], weights) # Add operator for applying weights model.sig[conn]["weights"] = Signal(weights, name="%s.weights" % conn, readonly=True) signal = Signal(np.zeros(signal_size), name="%s.weighted" % conn) model.add_op(Reset(signal)) op = ElementwiseInc if weights.ndim < 2 else DotInc model.add_op(op(model.sig[conn]["weights"], in_signal, signal, tag="%s.weights_elementwiseinc" % conn)) # Add operator for filtering if conn.synapse is not None: signal = model.build(conn.synapse, signal) # Store the weighted-filtered output in case we want to probe it model.sig[conn]["weighted"] = signal # Copy to the proper slice model.add_op(SlicedCopy(signal, model.sig[conn]["out"], dst_slice=post_slice, inc=True, tag="%s.gain" % conn)) # Build learning rules if conn.learning_rule is not None: rule = conn.learning_rule rule = [rule] if not is_iterable(rule) else rule targets = [] for r in itervalues(rule) if isinstance(rule, dict) else rule: model.build(r) targets.append(r.modifies) if "encoders" in targets: encoder_sig = model.sig[conn.post_obj]["encoders"] if not any(isinstance(op, PreserveValue) and op.dst is encoder_sig for op in model.operators): encoder_sig.readonly = False model.add_op(PreserveValue(encoder_sig)) if "decoders" in targets or "weights" in targets: if weights.ndim < 2: raise BuildError("'transform' must be a 2-dimensional array for learning") model.sig[conn]["weights"].readonly = False model.add_op(PreserveValue(model.sig[conn]["weights"])) model.params[conn] = BuiltConnection( eval_points=eval_points, solver_info=solver_info, transform=transform, weights=weights )
def build_ensemble(model, ens): """Builds an `.Ensemble` object into a model. A brief summary of what happens in the ensemble build process, in order: 1. Generate evaluation points and encoders. 2. Normalize encoders to unit length. 3. Determine bias and gain. 4. Create neuron input signal 5. Add operator for injecting bias. 6. Call build function for neuron type. 7. Scale encoders by gain and radius. 8. Add operators for multiplying decoded input signal by encoders and incrementing the result in the neuron input signal. 9. Call build function for injected noise. Some of these steps may be altered or omitted depending on the parameters of the ensemble, in particular the neuron type. For example, most steps are omitted for the `.Direct` neuron type. Parameters ---------- model : Model The model to build into. ens : Ensemble The ensemble to build. Notes ----- Sets ``model.params[ens]`` to a `.BuiltEnsemble` instance. """ # Create random number generator rng = np.random.RandomState(model.seeds[ens]) eval_points = gen_eval_points(ens, ens.eval_points, rng=rng) # Set up signal model.sig[ens]['in'] = Signal(np.zeros(ens.dimensions), name="%s.signal" % ens) model.add_op(Reset(model.sig[ens]['in'])) # Set up encoders if isinstance(ens.neuron_type, Direct): encoders = np.identity(ens.dimensions) elif isinstance(ens.encoders, Distribution): encoders = get_samples( ens.encoders, ens.n_neurons, ens.dimensions, rng=rng) else: encoders = npext.array(ens.encoders, min_dims=2, dtype=np.float64) if ens.normalize_encoders: encoders /= npext.norm(encoders, axis=1, keepdims=True) # Build the neurons gain, bias, max_rates, intercepts = get_gain_bias(ens, rng) if isinstance(ens.neuron_type, Direct): model.sig[ens.neurons]['in'] = Signal( np.zeros(ens.dimensions), name='%s.neuron_in' % ens) model.sig[ens.neurons]['out'] = model.sig[ens.neurons]['in'] model.add_op(Reset(model.sig[ens.neurons]['in'])) else: model.sig[ens.neurons]['in'] = Signal( np.zeros(ens.n_neurons), name="%s.neuron_in" % ens) model.sig[ens.neurons]['out'] = Signal( np.zeros(ens.n_neurons), name="%s.neuron_out" % ens) model.sig[ens.neurons]['bias'] = Signal( bias, name="%s.bias" % ens, readonly=True) model.add_op(Copy(model.sig[ens.neurons]['bias'], model.sig[ens.neurons]['in'])) # This adds the neuron's operator and sets other signals model.build(ens.neuron_type, ens.neurons) # Scale the encoders if isinstance(ens.neuron_type, Direct): scaled_encoders = encoders else: scaled_encoders = encoders * (gain / ens.radius)[:, np.newaxis] model.sig[ens]['encoders'] = Signal( scaled_encoders, name="%s.scaled_encoders" % ens, readonly=True) # Inject noise if specified if ens.noise is not None: model.build(ens.noise, sig_out=model.sig[ens.neurons]['in'], inc=True) # Create output signal, using built Neurons model.add_op(DotInc( model.sig[ens]['encoders'], model.sig[ens]['in'], model.sig[ens.neurons]['in'], tag="%s encoding" % ens)) # Output is neural output model.sig[ens]['out'] = model.sig[ens.neurons]['out'] model.params[ens] = BuiltEnsemble(eval_points=eval_points, encoders=encoders, intercepts=intercepts, max_rates=max_rates, scaled_encoders=scaled_encoders, gain=gain, bias=bias)
def build_connection(model, conn): """Builds a `.Connection` object into a model. A brief summary of what happens in the connection build process, in order: 1. Solve for decoders. 2. Combine transform matrix with decoders to get weights. 3. Add operators for computing the function or multiplying neural activity by weights. 4. Call build function for the synapse. 5. Call build function for the learning rule. 6. Add operator for applying learning rule delta to weights. Some of these steps may be altered or omitted depending on the parameters of the connection, in particular the pre and post types. Parameters ---------- model : Model The model to build into. conn : Connection The connection to build. Notes ----- Sets ``model.params[conn]`` to a `.BuiltConnection` instance. """ # Create random number generator rng = np.random.RandomState(model.seeds[conn]) # Get input and output connections from pre and post def get_prepost_signal(is_pre): target = conn.pre_obj if is_pre else conn.post_obj key = 'out' if is_pre else 'in' if target not in model.sig: raise BuildError("Building %s: the %r object %s is not in the " "model, or has a size of zero." % (conn, 'pre' if is_pre else 'post', target)) if key not in model.sig[target]: raise BuildError( "Building %s: the %r object %s has a %r size of zero." % (conn, 'pre' if is_pre else 'post', target, key)) return model.sig[target][key] model.sig[conn]['in'] = get_prepost_signal(is_pre=True) model.sig[conn]['out'] = get_prepost_signal(is_pre=False) weights = None eval_points = None solver_info = None signal_size = conn.size_out post_slice = conn.post_slice # Sample transform if given a distribution transform = get_samples(conn.transform, conn.size_out, d=conn.size_mid, rng=rng) # Figure out the signal going across this connection in_signal = model.sig[conn]['in'] if (isinstance(conn.pre_obj, Node) or (isinstance(conn.pre_obj, Ensemble) and isinstance(conn.pre_obj.neuron_type, Direct))): # Node or Decoded connection in directmode weights = transform sliced_in = slice_signal(model, in_signal, conn.pre_slice) if conn.function is None: in_signal = sliced_in elif isinstance(conn.function, np.ndarray): raise BuildError("Cannot use function points in direct connection") else: in_signal = Signal(np.zeros(conn.size_mid), name='%s.func' % conn) model.add_op(SimPyFunc(in_signal, conn.function, None, sliced_in)) elif isinstance(conn.pre_obj, Ensemble): # Normal decoded connection eval_points, weights, solver_info = model.build( conn.solver, conn, rng, transform) if conn.solver.weights: model.sig[conn]['out'] = model.sig[conn.post_obj.neurons]['in'] signal_size = conn.post_obj.neurons.size_in post_slice = None # don't apply slice later else: weights = transform in_signal = slice_signal(model, in_signal, conn.pre_slice) # Add operator for applying weights model.sig[conn]['weights'] = Signal(weights, name="%s.weights" % conn, readonly=True) signal = Signal(np.zeros(signal_size), name="%s.weighted" % conn) model.add_op(Reset(signal)) op = ElementwiseInc if weights.ndim < 2 else DotInc model.add_op( op(model.sig[conn]['weights'], in_signal, signal, tag="%s.weights_elementwiseinc" % conn)) # Add operator for filtering if conn.synapse is not None: signal = model.build(conn.synapse, signal) # Store the weighted-filtered output in case we want to probe it model.sig[conn]['weighted'] = signal if isinstance(conn.post_obj, Neurons): # Apply neuron gains (we don't need to do this if we're connecting to # an Ensemble, because the gains are rolled into the encoders) gains = Signal(model.params[conn.post_obj.ensemble].gain[post_slice], name="%s.gains" % conn) model.add_op( ElementwiseInc(gains, signal, model.sig[conn]['out'][post_slice], tag="%s.gains_elementwiseinc" % conn)) else: # Copy to the proper slice model.add_op( Copy(signal, model.sig[conn]['out'], dst_slice=post_slice, inc=True, tag="%s" % conn)) # Build learning rules if conn.learning_rule is not None: rule = conn.learning_rule rule = [rule] if not is_iterable(rule) else rule targets = [] for r in itervalues(rule) if isinstance(rule, dict) else rule: model.build(r) targets.append(r.modifies) if 'encoders' in targets: encoder_sig = model.sig[conn.post_obj]['encoders'] encoder_sig.readonly = False if 'decoders' in targets or 'weights' in targets: if weights.ndim < 2: raise BuildError( "'transform' must be a 2-dimensional array for learning") model.sig[conn]['weights'].readonly = False model.params[conn] = BuiltConnection(eval_points=eval_points, solver_info=solver_info, transform=transform, weights=weights)
def build_ensemble(model, ens): """Builds an `.Ensemble` object into a model. A brief summary of what happens in the ensemble build process, in order: 1. Generate evaluation points and encoders. 2. Normalize encoders to unit length. 3. Determine bias and gain. 4. Create neuron input signal 5. Add operator for injecting bias. 6. Call build function for neuron type. 7. Scale encoders by gain and radius. 8. Add operators for multiplying decoded input signal by encoders and incrementing the result in the neuron input signal. 9. Call build function for injected noise. Some of these steps may be altered or omitted depending on the parameters of the ensemble, in particular the neuron type. For example, most steps are omitted for the `.Direct` neuron type. Parameters ---------- model : Model The model to build into. ens : Ensemble The ensemble to build. Notes ----- Sets ``model.params[ens]`` to a `.BuiltEnsemble` instance. """ # Create random number generator rng = np.random.RandomState(model.seeds[ens]) eval_points = gen_eval_points(ens, ens.eval_points, rng=rng, dtype=rc.float_dtype) # Set up signal model.sig[ens]["in"] = Signal(shape=ens.dimensions, name="%s.signal" % ens) model.add_op(Reset(model.sig[ens]["in"])) # Set up encoders if isinstance(ens.neuron_type, Direct): encoders = np.identity(ens.dimensions, dtype=rc.float_dtype) elif isinstance(ens.encoders, Distribution): encoders = get_samples(ens.encoders, ens.n_neurons, ens.dimensions, rng=rng) encoders = np.asarray(encoders, dtype=rc.float_dtype) else: encoders = npext.array(ens.encoders, min_dims=2, dtype=rc.float_dtype) if ens.normalize_encoders: encoders /= npext.norm(encoders, axis=1, keepdims=True) if np.any(np.isnan(encoders)): raise BuildError( "NaNs detected in %r encoders. This usually means that you had zero-length " "encoders that were normalized, resulting in NaNs. Ensure all encoders " "have non-zero length, or set `normalize_encoders=False`." % ens) # Build the neurons gain, bias, max_rates, intercepts = get_gain_bias(ens, rng, dtype=rc.float_dtype) if isinstance(ens.neuron_type, Direct): model.sig[ens.neurons]["in"] = Signal(shape=ens.dimensions, name="%s.neuron_in" % ens) model.sig[ens.neurons]["out"] = model.sig[ens.neurons]["in"] model.add_op(Reset(model.sig[ens.neurons]["in"])) else: model.sig[ens.neurons]["in"] = Signal(shape=ens.n_neurons, name="%s.neuron_in" % ens) model.sig[ens.neurons]["out"] = Signal(shape=ens.n_neurons, name="%s.neuron_out" % ens) model.sig[ens.neurons]["bias"] = Signal(bias, name="%s.bias" % ens, readonly=True) model.add_op( Copy(model.sig[ens.neurons]["bias"], model.sig[ens.neurons]["in"])) # This adds the neuron's operator and sets other signals model.build(ens.neuron_type, ens.neurons) # Scale the encoders if isinstance(ens.neuron_type, Direct): scaled_encoders = encoders else: scaled_encoders = encoders * (gain / ens.radius)[:, np.newaxis] model.sig[ens]["encoders"] = Signal(scaled_encoders, name="%s.scaled_encoders" % ens, readonly=True) # Inject noise if specified if ens.noise is not None: model.build(ens.noise, sig_out=model.sig[ens.neurons]["in"], mode="inc") # Create output signal, using built Neurons model.add_op( DotInc( model.sig[ens]["encoders"], model.sig[ens]["in"], model.sig[ens.neurons]["in"], tag="%s encoding" % ens, )) # Output is neural output model.sig[ens]["out"] = model.sig[ens.neurons]["out"] model.params[ens] = BuiltEnsemble( eval_points=eval_points, encoders=encoders, intercepts=intercepts, max_rates=max_rates, scaled_encoders=scaled_encoders, gain=gain, bias=bias, )
def build_ensemble(model, ens): if isinstance(ens.neuron_type, nengo.Direct): raise NotImplementedError("Direct neurons not implemented") # Create random number generator rng = np.random.RandomState(model.seeds[ens]) eval_points = gen_eval_points( ens, ens.eval_points, rng=rng, dtype=nengo.rc.float_dtype ) # Set up encoders if isinstance(ens.encoders, Distribution): encoders = get_samples(ens.encoders, ens.n_neurons, ens.dimensions, rng=rng) encoders = np.asarray(encoders, dtype=nengo.rc.float_dtype) else: encoders = npext.array(ens.encoders, min_dims=2, dtype=nengo.rc.float_dtype) if ens.normalize_encoders: encoders /= npext.norm(encoders, axis=1, keepdims=True) if np.any(np.isnan(encoders)): raise BuildError( f"{ens}: NaNs detected in encoders. This usually means that you have " "zero-length encoders; when normalized, these result in NaNs. Ensure all " "encoders have non-zero length, or set `normalize_encoders=False`." ) # Build the neurons gain, bias, max_rates, intercepts = get_gain_bias( ens, rng, intercept_limit=model.intercept_limit, dtype=nengo.rc.float_dtype ) block = LoihiBlock(ens.n_neurons, label="%s" % ens) block.compartment.bias[:] = bias # build the neuron_type (see builders below) model.build(ens.neuron_type, ens.neurons, block) # set default filter just in case no other filter gets set block.compartment.configure_default_filter(model.decode_tau, dt=model.dt) if ens.noise is not None: raise NotImplementedError("Ensemble noise not implemented") # Scale the encoders # we exclude the radius to keep scaling reasonable for decode neurons scaled_encoders = encoders * gain[:, np.newaxis] # add instructions for splitting model.block_shapes[block] = model.config[ens].block_shape model.add_block(block) model.objs[ens]["in"] = block model.objs[ens]["out"] = block model.objs[ens.neurons]["in"] = block model.objs[ens.neurons]["out"] = block model.params[ens] = BuiltEnsemble( eval_points=eval_points, encoders=encoders, intercepts=intercepts, max_rates=max_rates, scaled_encoders=scaled_encoders, gain=gain, bias=bias, )
def build_connection(model, conn): if isinstance(conn.transform, conv.Conv2D): # TODO: integrate these into the same function conv.build_conv2d_connection(model, conn) return # Create random number generator rng = np.random.RandomState(model.seeds[conn]) pre_cx = model.objs[conn.pre_obj]['out'] post_cx = model.objs[conn.post_obj]['in'] assert isinstance(pre_cx, (CxGroup, CxSpikeInput)) assert isinstance(post_cx, (CxGroup, CxProbe)) weights = None eval_points = None solver_info = None neuron_type = None # Sample transform if given a distribution transform = get_samples(conn.transform, conn.size_out, d=conn.size_mid, rng=rng) tau_s = 0.0 # `synapse is None` gets mapped to `tau_s = 0.0` if isinstance(conn.synapse, nengo.synapses.Lowpass): tau_s = conn.synapse.tau elif conn.synapse is not None: raise NotImplementedError("Cannot handle non-Lowpass synapses") needs_interneurons = False if isinstance(conn.pre_obj, Node): assert conn.pre_slice == slice(None) if np.array_equal(transform, np.array(1.)): # TODO: this identity transform may be avoidable transform = np.eye(conn.pre.size_out) else: assert transform.ndim == 2, "transform shape not handled yet" assert transform.shape[1] == conn.pre.size_out assert transform.shape[1] == conn.pre.size_out if isinstance(conn.pre_obj, ChipReceiveNeurons): weights = transform / model.dt neuron_type = conn.pre_obj.neuron_type else: # input is on-off neuron encoded, so double/flip transform weights = np.column_stack([transform, -transform]) # (max_rate = INTER_RATE * INTER_N) is the spike rate we # use to represent a value of +/- 1 weights = weights * model.inter_scale elif (isinstance(conn.pre_obj, Ensemble) and isinstance(conn.pre_obj.neuron_type, nengo.Direct)): raise NotImplementedError() elif isinstance(conn.pre_obj, Ensemble): # Normal decoded connection eval_points, weights, solver_info = model.build( conn.solver, conn, rng, transform) # the decoder solver assumes a spike height of 1/dt; that isn't the # case on loihi, so we need to undo that scaling weights = weights / model.dt neuron_type = conn.pre_obj.neuron_type if not conn.solver.weights: needs_interneurons = True elif isinstance(conn.pre_obj, Neurons): assert conn.pre_slice == slice(None) assert transform.ndim == 2, "transform shape not handled yet" weights = transform / model.dt neuron_type = conn.pre_obj.ensemble.neuron_type else: raise NotImplementedError("Connection from type %r" % (type(conn.pre_obj), )) if neuron_type is not None and hasattr(neuron_type, 'amplitude'): weights = weights * neuron_type.amplitude mid_cx = pre_cx mid_axon_inds = None post_tau = tau_s if needs_interneurons and not isinstance(conn.post_obj, Neurons): # --- add interneurons assert weights.ndim == 2 d, n = weights.shape if isinstance(post_cx, CxProbe): # use non-spiking interneurons for voltage probing assert post_cx.target is None assert conn.post_slice == slice(None) # use the same scaling as the ensemble does, to get good # decodes. Note that this assumes that the decoded value # is in the range -radius to radius, which is usually true. weights = weights / conn.pre_obj.radius gain = 1 # model.dt * INTER_RATE(=1000) dec_cx = CxGroup(2 * d, label='%s' % conn, location='core') dec_cx.configure_nonspiking(dt=model.dt, vth=model.vth_nonspiking) dec_cx.bias[:] = 0 model.add_group(dec_cx) model.objs[conn]['decoded'] = dec_cx dec_syn = CxSynapses(n, label="probe_decoders") weights2 = gain * np.vstack([weights, -weights]).T else: # use spiking interneurons for on-chip connection post_d = conn.post_obj.size_in post_inds = np.arange(post_d, dtype=np.int32)[conn.post_slice] assert len(post_inds) == conn.size_out == d mid_axon_inds = np.hstack([post_inds, post_inds + post_d] * model.inter_n) gain = model.dt * model.inter_rate dec_cx = CxGroup(2 * d * model.inter_n, label='%s' % conn, location='core') dec_cx.configure_relu(dt=model.dt) dec_cx.bias[:] = 0.5 * gain * np.array( ([1.] * d + [1.] * d) * model.inter_n) if model.inter_noise_exp > -30: dec_cx.enableNoise[:] = 1 dec_cx.noiseExp0 = model.inter_noise_exp dec_cx.noiseAtDendOrVm = 1 model.add_group(dec_cx) model.objs[conn]['decoded'] = dec_cx if isinstance(conn.post_obj, Ensemble): # loihi encoders don't include radius, so handle scaling here weights = weights / conn.post_obj.radius dec_syn = CxSynapses(n, label="decoders") weights2 = 0.5 * gain * np.vstack( [weights, -weights] * model.inter_n).T # use tau_s for filter into interneurons, and INTER_TAU for filter out dec_cx.configure_filter(tau_s, dt=model.dt) post_tau = model.inter_tau dec_syn.set_full_weights(weights2) dec_cx.add_synapses(dec_syn) model.objs[conn]['decoders'] = dec_syn dec_ax0 = CxAxons(n, label="decoders") dec_ax0.target = dec_syn pre_cx.add_axons(dec_ax0) model.objs[conn]['decode_axons'] = dec_ax0 if conn.learning_rule_type is not None: if isinstance(conn.learning_rule_type, nengo.PES): pes_learn_rate = conn.learning_rule_type.learning_rate # scale learning rates to roughly match Nengo # 1e-4 is the Nengo core default learning rate pes_learn_rate *= 4 / 1e-4 assert isinstance(conn.learning_rule_type.pre_synapse, nengo.synapses.Lowpass) pes_pre_syn = conn.learning_rule_type.pre_synapse.tau # scale pre_syn.tau from s to ms pes_pre_syn *= 1e3 dec_syn.set_learning(tracing_tau=pes_pre_syn, tracing_mag=pes_learn_rate) else: raise NotImplementedError() mid_cx = dec_cx if isinstance(post_cx, CxProbe): assert post_cx.target is None assert conn.post_slice == slice(None) post_cx.target = mid_cx mid_cx.add_probe(post_cx) elif isinstance(conn.post_obj, Neurons): assert isinstance(post_cx, CxGroup) assert conn.post_slice == slice(None) if weights is None: raise NotImplementedError("Need weights for connection to neurons") else: assert weights.ndim == 2 n2, n1 = weights.shape assert post_cx.n == n2 syn = CxSynapses(n1, label="neuron_weights") gain = model.params[conn.post_obj.ensemble].gain syn.set_full_weights(weights.T * gain) post_cx.add_synapses(syn) model.objs[conn]['weights'] = syn ax = CxAxons(mid_cx.n, label="neuron_weights") ax.target = syn mid_cx.add_axons(ax) post_cx.configure_filter(post_tau, dt=model.dt) if conn.learning_rule_type is not None: raise NotImplementedError() elif isinstance(conn.post_obj, Ensemble) and conn.solver.weights: assert isinstance(post_cx, CxGroup) assert weights.ndim == 2 n2, n1 = weights.shape assert post_cx.n == n2 # loihi encoders don't include radius, so handle scaling here weights = weights / conn.post_obj.radius syn = CxSynapses(n1, label="%s::decoder_weights" % conn) syn.set_full_weights(weights.T) post_cx.add_synapses(syn) model.objs[conn]['weights'] = syn ax = CxAxons(n1, label="decoder_weights") ax.target = syn mid_cx.add_axons(ax) post_cx.configure_filter(post_tau, dt=model.dt) if conn.learning_rule_type is not None: raise NotImplementedError() elif isinstance(conn.post_obj, Ensemble): if 'inter_encoders' not in post_cx.named_synapses: build_interencoders(model, conn.post_obj) mid_ax = CxAxons(mid_cx.n, label="encoders") mid_ax.target = post_cx.named_synapses['inter_encoders'] mid_ax.set_axon_map(mid_axon_inds) mid_cx.add_axons(mid_ax) model.objs[conn]['mid_axons'] = mid_ax post_cx.configure_filter(post_tau, dt=model.dt) elif isinstance(conn.post_obj, Node): raise NotImplementedError() else: raise NotImplementedError() model.params[conn] = BuiltConnection(eval_points=eval_points, solver_info=solver_info, transform=transform, weights=weights)
def build_connection(model, conn): if isinstance(conn.transform, conv.Conv2D): # TODO: integrate these into the same function conv.build_conv2d_connection(model, conn) return # Create random number generator rng = np.random.RandomState(model.seeds[conn]) pre_cx = model.objs[conn.pre_obj]['out'] post_cx = model.objs[conn.post_obj]['in'] assert isinstance(pre_cx, (CxGroup, CxSpikeInput)) assert isinstance(post_cx, (CxGroup, CxProbe)) weights = None eval_points = None solver_info = None neuron_type = None # Sample transform if given a distribution transform = get_samples( conn.transform, conn.size_out, d=conn.size_mid, rng=rng) tau_s = 0.0 # `synapse is None` gets mapped to `tau_s = 0.0` if isinstance(conn.synapse, nengo.synapses.Lowpass): tau_s = conn.synapse.tau elif conn.synapse is not None: raise NotImplementedError("Cannot handle non-Lowpass synapses") needs_decode_neurons = False target_encoders = None if isinstance(conn.pre_obj, Node): assert conn.pre_slice == slice(None) if np.array_equal(transform, np.array(1.)): # TODO: this identity transform may be avoidable transform = np.eye(conn.pre.size_out) else: assert transform.ndim == 2, "transform shape not handled yet" assert transform.shape[1] == conn.pre.size_out assert transform.shape[1] == conn.pre.size_out if isinstance(conn.pre_obj, ChipReceiveNeurons): weights = transform / model.dt neuron_type = conn.pre_obj.neuron_type else: # input is on-off neuron encoded, so double/flip transform weights = np.column_stack([transform, -transform]) target_encoders = 'node_encoders' elif (isinstance(conn.pre_obj, Ensemble) and isinstance(conn.pre_obj.neuron_type, nengo.Direct)): raise NotImplementedError() elif isinstance(conn.pre_obj, Ensemble): # Normal decoded connection eval_points, weights, solver_info = model.build( conn.solver, conn, rng, transform) # the decoder solver assumes a spike height of 1/dt; that isn't the # case on loihi, so we need to undo that scaling weights = weights / model.dt neuron_type = conn.pre_obj.neuron_type if not conn.solver.weights: needs_decode_neurons = True elif isinstance(conn.pre_obj, Neurons): assert conn.pre_slice == slice(None) assert transform.ndim == 2, "transform shape not handled yet" weights = transform / model.dt neuron_type = conn.pre_obj.ensemble.neuron_type else: raise NotImplementedError("Connection from type %r" % ( type(conn.pre_obj),)) if neuron_type is not None and hasattr(neuron_type, 'amplitude'): weights = weights * neuron_type.amplitude mid_cx = pre_cx mid_axon_inds = None post_tau = tau_s if needs_decode_neurons and not isinstance(conn.post_obj, Neurons): # --- add decode neurons assert weights.ndim == 2 d, n = weights.shape if isinstance(post_cx, CxProbe): # use non-spiking decode neurons for voltage probing assert post_cx.target is None assert conn.post_slice == slice(None) # use the same scaling as the ensemble does, to get good # decodes. Note that this assumes that the decoded value # is in the range -radius to radius, which is usually true. weights = weights / conn.pre_obj.radius gain = 1 dec_cx = CxGroup(2 * d, label='%s' % conn, location='core') dec_cx.configure_nonspiking(dt=model.dt, vth=model.vth_nonspiking) dec_cx.bias[:] = 0 model.add_group(dec_cx) model.objs[conn]['decoded'] = dec_cx dec_syn = CxSynapses(n, label="probe_decoders") weights2 = gain * np.vstack([weights, -weights]).T dec_syn.set_full_weights(weights2) dec_cx.add_synapses(dec_syn) model.objs[conn]['decoders'] = dec_syn else: # use spiking decode neurons for on-chip connection if isinstance(conn.post_obj, Ensemble): # loihi encoders don't include radius, so handle scaling here weights = weights / conn.post_obj.radius post_d = conn.post_obj.size_in post_inds = np.arange(post_d, dtype=np.int32)[conn.post_slice] assert weights.shape[0] == len(post_inds) == conn.size_out == d mid_axon_inds = model.decode_neurons.get_post_inds( post_inds, post_d) target_encoders = 'decode_neuron_encoders' dec_cx, dec_syn = model.decode_neurons.get_cx( weights, cx_label="%s" % conn, syn_label="decoders") model.add_group(dec_cx) model.objs[conn]['decoded'] = dec_cx model.objs[conn]['decoders'] = dec_syn # use tau_s for filter into decode neurons, decode_tau for filter out dec_cx.configure_filter(tau_s, dt=model.dt) post_tau = model.decode_tau dec_ax0 = CxAxons(n, label="decoders") dec_ax0.target = dec_syn pre_cx.add_axons(dec_ax0) model.objs[conn]['decode_axons'] = dec_ax0 if conn.learning_rule_type is not None: rule_type = conn.learning_rule_type if isinstance(rule_type, nengo.PES): if not isinstance(rule_type.pre_synapse, nengo.synapses.Lowpass): raise ValidationError( "Loihi only supports `Lowpass` pre-synapses for " "learning rules", attr='pre_synapse', obj=rule_type) tracing_tau = rule_type.pre_synapse.tau / model.dt # Nengo builder scales PES learning rate by `dt / n_neurons` n_neurons = (conn.pre_obj.n_neurons if isinstance(conn.pre_obj, Ensemble) else conn.pre_obj.size_in) learning_rate = rule_type.learning_rate * model.dt / n_neurons # Account for scaling to put integer error in range [-127, 127] learning_rate /= model.pes_error_scale # Tracing mag set so that the magnitude of the pre trace # is independent of the pre tau. `dt` factor accounts for # Nengo's `dt` spike scaling. Where is the second `dt` from? # Maybe the fact that post decode neurons have `vth = 1/dt`? tracing_mag = -np.expm1(-1. / tracing_tau) / model.dt**2 # learning weight exponent controls the maximum weight # magnitude/weight resolution wgt_exp = model.pes_wgt_exp dec_syn.set_learning( learning_rate=learning_rate, tracing_mag=tracing_mag, tracing_tau=tracing_tau, wgt_exp=wgt_exp, ) else: raise NotImplementedError() mid_cx = dec_cx if isinstance(post_cx, CxProbe): assert post_cx.target is None assert conn.post_slice == slice(None) post_cx.target = mid_cx mid_cx.add_probe(post_cx) elif isinstance(conn.post_obj, Neurons): assert isinstance(post_cx, CxGroup) assert conn.post_slice == slice(None) if weights is None: raise NotImplementedError("Need weights for connection to neurons") else: assert weights.ndim == 2 n2, n1 = weights.shape assert post_cx.n == n2 syn = CxSynapses(n1, label="neuron_weights") gain = model.params[conn.post_obj.ensemble].gain syn.set_full_weights(weights.T * gain) post_cx.add_synapses(syn) model.objs[conn]['weights'] = syn ax = CxAxons(mid_cx.n, label="neuron_weights") ax.target = syn mid_cx.add_axons(ax) post_cx.configure_filter(post_tau, dt=model.dt) if conn.learning_rule_type is not None: raise NotImplementedError() elif isinstance(conn.post_obj, Ensemble) and conn.solver.weights: assert isinstance(post_cx, CxGroup) assert weights.ndim == 2 n2, n1 = weights.shape assert post_cx.n == n2 # loihi encoders don't include radius, so handle scaling here weights = weights / conn.post_obj.radius syn = CxSynapses(n1, label="%s::decoder_weights" % conn) syn.set_full_weights(weights.T) post_cx.add_synapses(syn) model.objs[conn]['weights'] = syn ax = CxAxons(n1, label="decoder_weights") ax.target = syn mid_cx.add_axons(ax) post_cx.configure_filter(post_tau, dt=model.dt) if conn.learning_rule_type is not None: raise NotImplementedError() elif isinstance(conn.post_obj, Ensemble): assert target_encoders is not None if target_encoders not in post_cx.named_synapses: build_decode_neuron_encoders( model, conn.post_obj, kind=target_encoders) mid_ax = CxAxons(mid_cx.n, label="encoders") mid_ax.target = post_cx.named_synapses[target_encoders] mid_ax.set_axon_map(mid_axon_inds) mid_cx.add_axons(mid_ax) model.objs[conn]['mid_axons'] = mid_ax post_cx.configure_filter(post_tau, dt=model.dt) elif isinstance(conn.post_obj, Node): raise NotImplementedError() else: raise NotImplementedError() model.params[conn] = BuiltConnection( eval_points=eval_points, solver_info=solver_info, transform=transform, weights=weights)