Example #1
0
def test_signal_init_values(RefSimulator):
    """Tests that initial values are not overwritten."""
    zero = Signal([0])
    one = Signal([1])
    five = Signal([5.0])
    zeroarray = Signal([[0], [0], [0]])
    array = Signal([1, 2, 3])

    m = Model(dt=0)
    m.operators += [
        PreserveValue(five),
        PreserveValue(array),
        DotInc(zero, zero, five),
        DotInc(zeroarray, one, array)
    ]

    sim = RefSimulator(None, model=m)
    assert sim.signals[zero][0] == 0
    assert sim.signals[one][0] == 1
    assert sim.signals[five][0] == 5.0
    assert np.all(np.array([1, 2, 3]) == sim.signals[array])
    sim.step()
    assert sim.signals[zero][0] == 0
    assert sim.signals[one][0] == 1
    assert sim.signals[five][0] == 5.0
    assert np.all(np.array([1, 2, 3]) == sim.signals[array])
Example #2
0
def test_signal_indexing_1(RefSimulator):
    one = Signal(np.zeros(1), name="a")
    two = Signal(np.zeros(2), name="b")
    three = Signal(np.zeros(3), name="c")
    tmp = Signal(np.zeros(3), name="tmp")

    m = Model(dt=0.001)
    m.operators += [
        Reset(one),
        Reset(two),
        Reset(tmp),
        DotInc(Signal(1, name="A1"), three[:1], one),
        DotInc(Signal(2.0, name="A2"), three[1:], two),
        DotInc(Signal([[0, 0, 1], [0, 1, 0], [1, 0, 0]], name="A3"), three,
               tmp),
        Copy(src=tmp, dst=three, as_update=True),
    ]

    sim = RefSimulator(None, model=m)
    sim.signals[three] = np.asarray([1, 2, 3])
    sim.step()
    assert np.all(sim.signals[one] == 1)
    assert np.all(sim.signals[two] == [4, 6])
    assert np.all(sim.signals[three] == [3, 2, 1])
    sim.step()
    assert np.all(sim.signals[one] == 3)
    assert np.all(sim.signals[two] == [4, 2])
    assert np.all(sim.signals[three] == [1, 2, 3])
Example #3
0
def test_multidotinc_compress(monkeypatch):
    if nengo.version.version_info < (2, 3, 1):  # LEGACY
        # Nengo versions <= 2.3.0 have more stringent op validation which
        # required PreserveValue. That's been removed, so the strict
        # validation causes this test to fail despite it working.
        monkeypatch.setattr(nengo.utils.simulator, "validate_ops", lambda *args: None)

    a = Signal([0, 0])
    b = Signal([0, 0])
    A = Signal([[1, 2], [0, 1]])
    B = Signal([[2, 1], [-1, 1]])
    x = Signal([1, 1])
    y = Signal([1, -1])

    m = Model(dt=0)
    m.operators += [Reset(a), DotInc(A, x, a), DotInc(B, y, a)]
    m.operators += [DotInc(A, y, b), DotInc(B, x, b)]

    with nengo_ocl.Simulator(None, model=m) as sim:
        sim.step()
        assert np.allclose(sim.signals[a], [4, -1])
        assert np.allclose(sim.signals[b], [2, -1])
        sim.step()
        assert np.allclose(sim.signals[a], [4, -1])
        assert np.allclose(sim.signals[b], [4, -2])
Example #4
0
def build_pes(model, pes, rule):
    # TODO: Filter activities
    conn = rule.connection
    activities = model.sig[conn.pre_obj]['out']
    error = model.sig[pes.error_connection]['out']

    scaled_error = Signal(np.zeros(error.shape),
                          name="PES:error * learning_rate")
    scaled_error_view = scaled_error.reshape((error.size, 1))
    activities_view = activities.reshape((1, activities.size))
    lr_sig = Signal(pes.learning_rate * model.dt, name="PES:learning_rate")

    model.add_op(Reset(scaled_error))
    model.add_op(DotInc(lr_sig, error, scaled_error, tag="PES:scale error"))

    if conn.solver.weights or (isinstance(conn.pre_obj, Neurons)
                               and isinstance(conn.post_obj, Neurons)):
        post = (conn.post_obj.ensemble
                if isinstance(conn.post_obj, Neurons) else conn.post_obj)
        transform = model.sig[conn]['transform']
        encoders = model.sig[post]['encoders']
        encoded_error = Signal(np.zeros(transform.shape[0]),
                               name="PES: encoded error")

        model.add_op(Reset(encoded_error))
        model.add_op(
            DotInc(encoders,
                   scaled_error,
                   encoded_error,
                   tag="PES:Encode error"))

        encoded_error_view = encoded_error.reshape((encoded_error.size, 1))
        model.add_op(
            ElementwiseInc(encoded_error_view,
                           activities_view,
                           transform,
                           tag="PES:Inc Transform"))
    elif isinstance(conn.pre_obj, Neurons):
        transform = model.sig[conn]['transform']
        model.add_op(
            ElementwiseInc(scaled_error_view,
                           activities_view,
                           transform,
                           tag="PES:Inc Transform"))
    else:
        assert isinstance(conn.pre_obj, Ensemble)
        decoders = model.sig[conn]['decoders']
        model.add_op(
            ElementwiseInc(scaled_error_view,
                           activities_view,
                           decoders,
                           tag="PES:Inc Decoder"))

    # expose these for probes
    model.sig[rule]['scaled_error'] = scaled_error
    model.sig[rule]['activities'] = activities

    model.params[rule] = None  # no build-time info to return
Example #5
0
def test_operators():
    sig = Signal(np.array([0.0]), name="sig")
    assert fnmatch(repr(TimeUpdate(sig, sig)), "<TimeUpdate at 0x*>")
    assert fnmatch(repr(TimeUpdate(sig, sig, tag="tag")),
                   "<TimeUpdate 'tag' at 0x*>")
    assert fnmatch(repr(Reset(sig)), "<Reset at 0x*>")
    assert fnmatch(repr(Reset(sig, tag="tag")), "<Reset 'tag' at 0x*>")
    assert fnmatch(repr(Copy(sig, sig)), "<Copy at 0x*>")
    assert fnmatch(repr(Copy(sig, sig, tag="tag")), "<Copy 'tag' at 0x*>")
    assert fnmatch(repr(ElementwiseInc(sig, sig, sig)),
                   "<ElementwiseInc at 0x*>")
    assert fnmatch(repr(ElementwiseInc(sig, sig, sig, tag="tag")),
                   "<ElementwiseInc 'tag' at 0x*>")
    assert fnmatch(repr(DotInc(sig, sig, sig)), "<DotInc at 0x*>")
    assert fnmatch(repr(DotInc(sig, sig, sig, tag="tag")),
                   "<DotInc 'tag' at 0x*>")
    assert fnmatch(repr(SimPyFunc(sig, lambda x: 0.0, True, sig)),
                   "<SimPyFunc at 0x*>")
    assert fnmatch(
        repr(SimPyFunc(sig, lambda x: 0.0, True, sig, tag="tag")),
        "<SimPyFunc 'tag' at 0x*>",
    )
    assert fnmatch(repr(SimPES(sig, sig, sig, 0.1)), "<SimPES at 0x*>")
    assert fnmatch(repr(SimPES(sig, sig, sig, 0.1, tag="tag")),
                   "<SimPES 'tag' at 0x*>")
    assert fnmatch(repr(SimBCM(sig, sig, sig, sig, 0.1)), "<SimBCM at 0x*>")
    assert fnmatch(repr(SimBCM(sig, sig, sig, sig, 0.1, tag="tag")),
                   "<SimBCM 'tag' at 0x*>")
    assert fnmatch(repr(SimOja(sig, sig, sig, sig, 0.1, 1.0)),
                   "<SimOja at 0x*>")
    assert fnmatch(repr(SimOja(sig, sig, sig, sig, 0.1, 1.0, tag="tag")),
                   "<SimOja 'tag' at 0x*>")
    assert fnmatch(repr(SimVoja(sig, sig, sig, sig, 1.0, sig, 1.0)),
                   "<SimVoja at 0x*>")
    assert fnmatch(
        repr(SimVoja(sig, sig, sig, sig, 0.1, sig, 1.0, tag="tag")),
        "<SimVoja 'tag' at 0x*>",
    )
    assert fnmatch(repr(SimRLS(sig, sig, sig, sig)), "<SimRLS at 0x*>")
    assert fnmatch(
        repr(SimRLS(sig, sig, sig, sig, tag="tag")),
        "<SimRLS 'tag' at 0x*>",
    )
    assert fnmatch(repr(SimNeurons(LIF(), sig, {"sig": sig})),
                   "<SimNeurons at 0x*>")
    assert fnmatch(
        repr(SimNeurons(LIF(), sig, {"sig": sig}, tag="tag")),
        "<SimNeurons 'tag' at 0x*>",
    )
    assert fnmatch(repr(SimProcess(WhiteNoise(), sig, sig, sig)),
                   "<SimProcess at 0x*>")
    assert fnmatch(
        repr(SimProcess(WhiteNoise(), sig, sig, sig, tag="tag")),
        "<SimProcess 'tag' at 0x*>",
    )
Example #6
0
def build_pes(model, pes, rule):
    conn = rule.connection

    # Create input error signal
    error = Signal(np.zeros(rule.size_in), name="PES:error")
    model.add_op(Reset(error))
    model.sig[rule]['in'] = error  # error connection will attach here

    acts = filtered_signal(model, pes, model.sig[conn.pre_obj]['out'],
                           pes.pre_tau)
    acts_view = acts.reshape((1, acts.size))

    # Compute the correction, i.e. the scaled negative error
    correction = Signal(np.zeros(error.shape), name="PES:correction")
    local_error = correction.reshape((error.size, 1))
    model.add_op(Reset(correction))

    # correction = -learning_rate * (dt / n_neurons) * error
    n_neurons = (conn.pre_obj.n_neurons if isinstance(conn.pre_obj, Ensemble)
                 else conn.pre_obj.size_in)
    lr_sig = Signal(-pes.learning_rate * model.dt / n_neurons,
                    name="PES:learning_rate")
    model.add_op(DotInc(lr_sig, error, correction, tag="PES:correct"))

    if conn.solver.weights or (isinstance(conn.pre_obj, Neurons)
                               and isinstance(conn.post_obj, Neurons)):
        post = get_post_ens(conn)
        transform = model.sig[conn]['transform']
        encoders = model.sig[post]['encoders']

        # encoded = dot(encoders, correction)
        encoded = Signal(np.zeros(transform.shape[0]), name="PES:encoded")
        model.add_op(Reset(encoded))
        model.add_op(DotInc(encoders, correction, encoded, tag="PES:encode"))
        local_error = encoded.reshape((encoded.size, 1))
    elif not isinstance(conn.pre_obj, (Ensemble, Neurons)):
        raise ValueError("'pre' object '%s' not suitable for PES learning" %
                         (conn.pre_obj))

    # delta = local_error * activities
    model.add_op(Reset(model.sig[rule]['delta']))
    model.add_op(
        ElementwiseInc(local_error,
                       acts_view,
                       model.sig[rule]['delta'],
                       tag="PES:Inc Delta"))

    # expose these for probes
    model.sig[rule]['error'] = error
    model.sig[rule]['correction'] = correction
    model.sig[rule]['activities'] = acts

    model.params[rule] = None  # no build-time info to return
Example #7
0
def build_node(model, node):
    # Get input
    if node.output is None or callable(node.output):
        if node.size_in > 0:
            model.sig[node]['in'] = model.Signal(npext.castDecimal(
                np.zeros(node.size_in)),
                                                 name="%s.signal" % node)
            # Reset input signal to 0 each timestep
            model.add_op(Reset(model.sig[node]['in']))

    # Provide output
    if node.output is None:
        model.sig[node]['out'] = model.sig[node]['in']
    elif not callable(node.output):
        model.sig[node]['out'] = model.Signal(node.output, name=str(node))
    else:
        sig_in, sig_out = build_pyfunc(model=model,
                                       fn=node.output,
                                       t_in=True,
                                       n_in=node.size_in,
                                       n_out=node.size_out,
                                       label="%s.pyfn" % node)
        if sig_in is not None:
            model.add_op(
                DotInc(model.sig[node]['in'],
                       model.sig['common'][1],
                       sig_in,
                       tag="%s input" % node))
        if sig_out is not None:
            model.sig[node]['out'] = sig_out

    model.params[node] = None
Example #8
0
def test_signal_init_values(Simulator):
    """Tests that initial values are not overwritten."""

    zero = Signal([0.0])
    one = Signal([1.0])
    five = Signal([5.0])
    zeroarray = Signal([[0.0], [0.0], [0.0]])
    array = Signal([1.0, 2.0, 3.0])

    m = nengo.builder.Model(dt=0)
    m.operators += [
        ElementwiseInc(zero, zero, five),
        DotInc(zeroarray, one, array)
    ]

    probes = [
        dummies.Probe(zero, add_to_container=False),
        dummies.Probe(one, add_to_container=False),
        dummies.Probe(five, add_to_container=False),
        dummies.Probe(array, add_to_container=False)
    ]
    m.probes += probes
    for p in probes:
        m.sig[p]['in'] = p.target

    with Simulator(None, model=m) as sim:
        sim.run_steps(3)
        assert np.allclose(sim.data[probes[0]], 0)
        assert np.allclose(sim.data[probes[1]], 1)
        assert np.allclose(sim.data[probes[2]], 5)
        assert np.allclose(sim.data[probes[3]], [1, 2, 3])
Example #9
0
def test_signal_init_values(Simulator):
    """Tests that initial values are not overwritten."""

    zero = Signal([0.0])
    one = Signal([1.0])
    five = Signal([5.0])
    zeroarray = Signal([[0.0], [0.0], [0.0]])
    array = Signal([1.0, 2.0, 3.0])

    class DummyProbe(nengo.Probe):
        # pylint: disable=super-init-not-called
        def __init__(self, target):
            # bypass target validation
            nengo.Probe.target.data[self] = target

    m = nengo.builder.Model(dt=0)
    m.operators += [ElementwiseInc(zero, zero, five),
                    DotInc(zeroarray, one, array)]

    probes = [DummyProbe(zero, add_to_container=False),
              DummyProbe(one, add_to_container=False),
              DummyProbe(five, add_to_container=False),
              DummyProbe(array, add_to_container=False)]
    m.probes += probes
    for p in probes:
        m.sig[p]['in'] = p.target

    with Simulator(None, model=m) as sim:
        sim.run_steps(3)
        assert np.allclose(sim.data[probes[0]], 0)
        assert np.allclose(sim.data[probes[1]], 1)
        assert np.allclose(sim.data[probes[2]], 5)
        assert np.allclose(sim.data[probes[3]], [1, 2, 3])
Example #10
0
def build_delta_rule(model, delta_rule, rule):
    conn = rule.connection

    # Create input error signal
    error = Signal(np.zeros(rule.size_in), name="DeltaRule:error")
    model.add_op(Reset(error))
    model.sig[rule]["in"] = error  # error connection will attach here

    # Multiply by post_fn output if necessary
    post_fn = delta_rule.post_fn.function
    post_tau = delta_rule.post_tau
    post_target = delta_rule.post_target
    if post_fn is not None:
        post_sig = model.sig[conn.post_obj][post_target]
        post_synapse = Lowpass(post_tau) if post_tau is not None else None
        post_input = (post_sig if post_synapse is None else model.build(
            post_synapse, post_sig))

        post = Signal(np.zeros(post_input.shape), name="DeltaRule:post")
        model.add_op(
            SimPyFunc(post,
                      post_fn,
                      t=None,
                      x=post_input,
                      tag="DeltaRule:post_fn"))
        model.sig[rule]["post"] = post

        error0 = error
        error = Signal(np.zeros(rule.size_in), name="DeltaRule:post_error")
        model.add_op(Reset(error))
        model.add_op(ElementwiseInc(error0, post, error))

    # Compute: correction = -learning_rate * dt * error
    correction = Signal(np.zeros(error.shape), name="DeltaRule:correction")
    model.add_op(Reset(correction))
    lr_sig = Signal(-delta_rule.learning_rate * model.dt,
                    name="DeltaRule:learning_rate")
    model.add_op(DotInc(lr_sig, error, correction, tag="DeltaRule:correct"))

    # delta_ij = correction_i * pre_j
    pre_synapse = Lowpass(delta_rule.pre_tau)
    pre = model.build(pre_synapse, model.sig[conn.pre_obj]["out"])

    model.add_op(Reset(model.sig[rule]["delta"]))
    model.add_op(
        ElementwiseInc(
            correction.reshape((-1, 1)),
            pre.reshape((1, -1)),
            model.sig[rule]["delta"],
            tag="DeltaRule:Inc Delta",
        ))

    # expose these for probes
    model.sig[rule]["error"] = error
    model.sig[rule]["correction"] = correction
    model.sig[rule]["pre"] = pre
Example #11
0
def test_simple_pyfunc(RefSimulator):
    dt = 0.001
    time = Signal(np.zeros(1), name="time")
    sig = Signal(np.zeros(1), name="sig")
    m = Model(dt=dt)
    sig_in, sig_out = build_pyfunc(m, lambda t, x: np.sin(x), True, 1, 1, None)
    m.operators += [
        Reset(sig),
        DotInc(Signal([[1.0]]), time, sig_in),
        DotInc(Signal([[1.0]]), sig_out, sig),
        DotInc(Signal(dt), Signal(1), time, as_update=True),
    ]

    sim = RefSimulator(None, model=m)
    for i in range(5):
        sim.step()
        t = i * dt
        assert np.allclose(sim.signals[sig], np.sin(t))
        assert np.allclose(sim.signals[time], t + dt)
Example #12
0
    def merge(ops):
        # Simple merge if all X are the same.
        if all(o.X is ops[0].X for o in ops):
            A, A_sigr = SigMerger.merge([o.A for o in ops])
            Y, Y_sigr = SigMerger.merge([o.Y for o in ops])
            return DotInc(A, ops[0].X, Y), Merger.merge_dicts(A_sigr, Y_sigr)

        assert all(o1.X is not o2.X for i, o1 in enumerate(ops)
                   for o2 in ops[i + 1:])

        # BSR merge if X differ
        X, X_sigr = SigMerger.merge([o.X for o in ops])
        Y, Y_sigr = SigMerger.merge([o.Y for o in ops])

        # Construct sparse A representation
        data = np.array([o.A.initial_value for o in ops], dtype=rc.float_dtype)
        if data.ndim == 1:
            raise NotImplementedError("A.ndim should be > 2")
        elif data.ndim == 2:
            raise NotImplementedError("A.ndim should be > 2")
        indptr = np.arange(len(ops) + 1, dtype=rc.int_dtype)
        indices = np.arange(len(ops), dtype=rc.int_dtype)
        name = f"bsr_merged<{ops[0].A.name}, ..., {ops[-1].A.name}>"
        readonly = all(o.A.readonly for o in ops)
        A = Signal(data, name=name, readonly=readonly)
        A_sigr = {}
        for i, s in enumerate(o.A for o in ops):
            A_sigr[s] = Signal(
                data[i],
                name=f"{s.name}[{i}]",
                base=A,
                offset=i * A.itemsize * np.prod(A.shape[1:]),
            )
            assert np.allclose(s.initial_value,
                               A_sigr[s].initial_value,
                               atol=0,
                               rtol=0,
                               equal_nan=True)
            assert s.shape == A_sigr[s].shape or (s.shape == () and
                                                  A_sigr[s].shape == (1, 1))

        reshape = reshape_dot(
            ops[0].A.initial_value,
            ops[0].X.initial_value,
            ops[0].Y.initial_value,
            tag=ops[0].tag,
        )
        return (
            BsrDotInc(A, X, Y, indices=indices, indptr=indptr,
                      reshape=reshape),
            Merger.merge_dicts(X_sigr, Y_sigr, A_sigr),
        )
def test_planner_size():
    # check that operators are selected according to number of available ops
    input0 = dummies.Signal()
    operators = [Copy(input0, dummies.Signal(), inc=True)
                 for _ in range(2)]
    operators += [Copy(input0, dummies.Signal())]
    operators += [DotInc(input0, dummies.Signal(), dummies.Signal())
                  for _ in range(3)]
    plan = greedy_planner(operators)
    assert len(plan) == 3
    assert len(plan[0]) == 3
    assert len(plan[1]) == 2
    assert len(plan[2]) == 1
Example #14
0
def test_dotinc_op(rng):
    argnames = ["A", "X", "Y"]
    args = {
        "A": Signal(np.ones((3, 3)), name="Av"),
        "X": Signal(np.ones((3, )), name="Xv"),
        "Y": Signal(np.ones((3, )), name="Yv"),
    }
    _, sim = _test_operator_arg_attributes(DotInc, argnames, args=args)
    assert (
        str(sim) ==
        "DotInc{Signal(name=Av, shape=(3, 3)), Signal(name=Xv, shape=(3,)) -> "
        "Signal(name=Yv, shape=(3,))}")

    A = Signal(np.ones((2, 2)))
    X = Signal(np.ones((2, 2)))
    Y = Signal(np.ones(2))
    with pytest.raises(BuildError, match="X must be a column vector"):
        DotInc(A, X, Y)

    X = Signal(np.ones(2))
    Y = Signal(np.ones((2, 2)))
    with pytest.raises(BuildError, match="Y must be a column vector"):
        DotInc(A, X, Y)
Example #15
0
def build_mpes(model, mpes, rule):
    conn = rule.connection

    # Create input error signal
    error = Signal(shape=(rule.size_in, ), name="PES:error")
    model.add_op(Reset(error))
    model.sig[rule]["in"] = error  # error connection will attach here

    acts = build_or_passthrough(model, mpes.pre_synapse,
                                model.sig[conn.pre_obj]["out"])

    post = get_post_ens(conn)
    encoders = model.sig[post]["encoders"]

    pos_memristors, neg_memristors, r_min_noisy, r_max_noisy, exponent_noisy = initialise_memristors(
        mpes, acts.shape[0], encoders.shape[0])

    model.sig[conn]["pos_memristors"] = pos_memristors
    model.sig[conn]["neg_memristors"] = neg_memristors

    if conn.post_obj is not conn.post:
        # in order to avoid slicing encoders along an axis > 0, we pad
        # `error` out to the full base dimensionality and then do the
        # dotinc with the full encoder matrix
        # comes into effect when slicing post connection
        padded_error = Signal(shape=(encoders.shape[1], ))
        model.add_op(Copy(error, padded_error, dst_slice=conn.post_slice))
    else:
        padded_error = error

    # error = dot(encoders, error)
    local_error = Signal(shape=(post.n_neurons, ))
    model.add_op(Reset(local_error))
    model.add_op(DotInc(encoders, padded_error, local_error, tag="PES:encode"))

    model.operators.append(
        SimmPES(acts, local_error, model.sig[conn]["pos_memristors"],
                model.sig[conn]["neg_memristors"], model.sig[conn]["weights"],
                mpes.noise_percentage, mpes.gain, r_min_noisy, r_max_noisy,
                exponent_noisy, mpes.initial_state))

    # expose these for probes
    model.sig[rule]["error"] = error
    model.sig[rule]["activities"] = acts
    model.sig[rule]["pos_memristors"] = pos_memristors
    model.sig[rule]["neg_memristors"] = neg_memristors
Example #16
0
def test_signal_init_values(Simulator):
    """Tests that initial values are not overwritten."""

    zero = Signal([0.0])
    one = Signal([1.0])
    five = Signal([5.0])
    zeroarray = Signal([[0.0], [0.0], [0.0]])
    array = Signal([1.0, 2.0, 3.0])

    class DummyProbe():
        def __init__(self, target):
            self.target = target
            self.sample_every = None
            self.size_in = target.size

    m = nengo.builder.Model(dt=0)
    m.operators += [
        ElementwiseInc(zero, zero, five),
        DotInc(zeroarray, one, array)
    ]

    probes = [
        DummyProbe(zero),
        DummyProbe(one),
        DummyProbe(five),
        DummyProbe(array)
    ]
    m.probes += probes
    for p in probes:
        m.sig[p]['in'] = p.target

    with Simulator(None, model=m) as sim:
        sim.run_steps(3)
        assert np.allclose(sim.data[probes[0]], 0)
        assert np.allclose(sim.data[probes[1]], 1)
        assert np.allclose(sim.data[probes[2]], 5)
        assert np.allclose(sim.data[probes[3]], [1, 2, 3])
Example #17
0
def test_remove_reset_incs():
    # elementwiseinc converted to elementwiseset
    x = dummies.Signal()
    operators = [
        Reset(x),
        ElementwiseInc(dummies.Signal(), dummies.Signal(), x)
    ]
    new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 1
    assert isinstance(new_operators[0], op_builders.ElementwiseSet)
    assert new_operators[0].Y is x
    assert new_operators[0].incs == []
    assert new_operators[0].sets == [x]

    # dotinc converted to dotset
    x = dummies.Signal()
    operators = [Reset(x), DotInc(dummies.Signal(), dummies.Signal(), x)]
    new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 1
    assert isinstance(new_operators[0], op_builders.DotSet)
    assert new_operators[0].Y is x

    # copy inc converted to copy set
    x = dummies.Signal()
    operators = [Reset(x), Copy(dummies.Signal(), x, inc=True)]
    new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 1
    assert not new_operators[0].inc
    assert new_operators[0].dst is x

    # simprocess inc converted to simprocess set
    x = dummies.Signal()
    operators = [
        Reset(x),
        SimProcess(None, dummies.Signal(), x, dummies.Signal(), mode="inc"),
    ]
    new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 1
    assert new_operators[0].mode == "set"
    assert new_operators[0].output is x

    # convinc converted to convset
    x = dummies.Signal()
    operators = [
        Reset(x),
        ConvInc(dummies.Signal(), dummies.Signal(), x, None)
    ]
    new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 1
    assert isinstance(new_operators[0], transform_builders.ConvSet)
    assert new_operators[0].Y is x

    # sparsedotinc converted to sparsedotset
    x = dummies.Signal()
    operators = [
        Reset(x),
        SparseDotInc(dummies.Signal(sparse=True), dummies.Signal(), x, None),
    ]
    new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 1
    assert isinstance(new_operators[0], op_builders.SparseDotSet)
    assert new_operators[0].Y is x

    # resetinc converted to reset
    x = dummies.Signal()
    operators = [Reset(x), op_builders.ResetInc(x)]
    operators[1].value = np.ones((2, 3))
    new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 1
    assert type(new_operators[0]) == Reset
    assert np.allclose(new_operators[0].value, 1)
    assert new_operators[0].dst is x

    # multiple incs
    x = dummies.Signal()
    operators = [
        Reset(x),
        ElementwiseInc(dummies.Signal(), dummies.Signal(), x),
        ElementwiseInc(dummies.Signal(), dummies.Signal(), x),
    ]
    new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 2
    assert isinstance(new_operators[0], op_builders.ElementwiseSet)
    assert isinstance(new_operators[1], ElementwiseInc)

    # nonzero reset doesn't get converted
    x = dummies.Signal()
    operators = [
        Reset(x, value=1),
        ElementwiseInc(dummies.Signal(), dummies.Signal(), x),
    ]
    new_operators = remove_reset_incs(operators)
    assert operators == new_operators

    # reset without inc
    x = dummies.Signal()
    operators = [
        Reset(x),
        Copy(dummies.Signal(), x, inc=False),
    ]
    new_operators = remove_reset_incs(operators)
    assert operators == new_operators

    # reset with partial inc
    x = Signal(shape=(10, ))
    operators = [
        Reset(x),
        Copy(dummies.Signal(), x[:5], inc=True),
    ]
    new_operators = remove_reset_incs(operators)
    assert operators == new_operators

    # unknown inc type
    class NewCopy(Copy):
        pass

    x = dummies.Signal()
    operators = [
        Reset(x),
        NewCopy(dummies.Signal(), x, inc=True),
        ElementwiseInc(dummies.Signal(), dummies.Signal(), x),
    ]
    with pytest.warns(UserWarning, match="Unknown incer type"):
        new_operators = remove_reset_incs(operators)
    assert len(new_operators) == 2
    # uses the known op (ElementwiseInc) instead of unknown one
    assert isinstance(new_operators[0], op_builders.ElementwiseSet)
    assert new_operators[1] is operators[1]

    operators = [
        Reset(x),
        NewCopy(dummies.Signal(), x, inc=True),
    ]
    # no optimization if only unknown incers
    with pytest.warns(UserWarning, match="Unknown incer type"):
        new_operators = remove_reset_incs(operators)
    assert new_operators == operators
Example #18
0
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 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 = ens.encoders.sample(ens.n_neurons, ens.dimensions, rng=rng)
        encoders = np.asarray(encoders, dtype=np.float64)
    else:
        encoders = npext.array(ens.encoders, min_dims=2, dtype=np.float64)
    encoders /= npext.norm(encoders, axis=1, keepdims=True)

    # Determine max_rates and intercepts
    max_rates = sample(ens.max_rates, ens.n_neurons, rng=rng)
    intercepts = sample(ens.intercepts, ens.n_neurons, rng=rng)

    # Build the neurons
    if ens.gain is not None and ens.bias is not None:
        gain = sample(ens.gain, ens.n_neurons, rng=rng)
        bias = sample(ens.bias, ens.n_neurons, rng=rng)
    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:
        gain, bias = ens.neuron_type.gain_bias(max_rates, intercepts)

    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.add_op(
            Copy(src=Signal(bias, name="%s.bias" % ens),
                 dst=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)

    # 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_pes(model, pes, rule):
    """
    Builds a `nengo.PES` object into a model.

    Parameters
    ----------
    model : Model
        The model to build into.
    pes : PES
        Learning rule type to build.
    rule : LearningRule
        The learning rule object corresponding to the neuron type.

    Notes
    -----
    Does not modify ``model.params[]`` and can therefore be called
    more than once with the same `nengo.PES` instance.
    """

    conn = rule.connection

    # Create input error signal
    error = Signal(np.zeros(rule.size_in), name="PES:error")
    model.add_op(Reset(error))
    model.sig[rule]['in'] = error  # error connection will attach here

    if LooseVersion(nengo_version) < "2.7.1":
        acts = model.build(
            Lowpass(pes.pre_tau), model.sig[conn.pre_obj]["out"])
    else:
        acts = model.build(pes.pre_synapse, model.sig[conn.pre_obj]["out"])

    if not conn.is_decoded:
        # multiply error by post encoders to get a per-neuron error

        post = get_post_ens(conn)
        encoders = model.sig[post]["encoders"]

        if conn.post_obj is not conn.post:
            # in order to avoid slicing encoders along an axis > 0, we pad
            # `error` out to the full base dimensionality and then do the
            # dotinc with the full encoder matrix
            padded_error = Signal(np.zeros(encoders.shape[1]))
            model.add_op(Copy(error, padded_error,
                              dst_slice=conn.post_slice))
        else:
            padded_error = error

        # error = dot(encoders, error)
        local_error = Signal(np.zeros(post.n_neurons), name="PES:encoded")
        model.add_op(Reset(local_error))
        model.add_op(DotInc(encoders, padded_error, local_error,
                            tag="PES:encode"))
    else:
        local_error = error

    model.operators.append(SimPES(acts, local_error, model.sig[rule]["delta"],
                                  pes.learning_rate))

    # expose these for probes
    model.sig[rule]["error"] = error
    model.sig[rule]["activities"] = acts
Example #20
0
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 test_remove_zero_incs():
    # check that zero inputs get removed (for A or X)
    operators = [DotInc(dummies.Signal(), dummies.Signal(initial_value=1),
                        dummies.Signal())]
    new_operators = remove_zero_incs(operators)
    assert new_operators == []

    operators = [DotInc(dummies.Signal(initial_value=1), dummies.Signal(),
                        dummies.Signal())]
    new_operators = remove_zero_incs(operators)
    assert new_operators == []

    # check that zero inputs (copy) get removed
    operators = [Copy(dummies.Signal(), dummies.Signal(), dummies.Signal(), inc=True)]
    new_operators = remove_zero_incs(operators)
    assert new_operators == []

    # check that node inputs don't get removed
    x = dummies.Signal(label="<Node lorem ipsum")
    operators = [DotInc(dummies.Signal(initial_value=1), x, dummies.Signal())]
    new_operators = remove_zero_incs(operators)
    assert new_operators == operators

    # check that zero inputs + trainable don't get removed
    x = dummies.Signal()
    x.trainable = True
    operators = [DotInc(dummies.Signal(initial_value=1), x, dummies.Signal())]
    new_operators = remove_zero_incs(operators)
    assert new_operators == operators

    # check that updated input doesn't get removed
    x = dummies.Signal()
    operators = [DotInc(dummies.Signal(initial_value=1), x, dummies.Signal()),
                 dummies.Op(updates=[x])]
    new_operators = remove_zero_incs(operators)
    assert new_operators == operators

    # check that inc'd input doesn't get removed
    x = dummies.Signal()
    operators = [DotInc(dummies.Signal(initial_value=1), x, dummies.Signal()),
                 dummies.Op(incs=[x])]
    new_operators = remove_zero_incs(operators)
    assert new_operators == operators

    # check that set'd input doesn't get removed
    x = dummies.Signal()
    operators = [DotInc(dummies.Signal(initial_value=1), x, dummies.Signal()),
                 dummies.Op(sets=[x])]
    new_operators = remove_zero_incs(operators)
    assert new_operators == operators

    # check that Reset(0) input does get removed
    x = dummies.Signal()
    operators = [DotInc(dummies.Signal(initial_value=1), x, dummies.Signal()),
                 Reset(x)]
    new_operators = remove_zero_incs(operators)
    assert new_operators == operators[1:]

    # check that Reset(1) input does not get removed
    x = dummies.Signal()
    operators = [DotInc(dummies.Signal(initial_value=1), x, dummies.Signal()),
                 Reset(x, 1)]
    new_operators = remove_zero_incs(operators)
    assert new_operators == operators

    # check that set's get turned into a reset
    x = dummies.Signal()
    operators = [Copy(dummies.Signal(), x)]
    new_operators = remove_zero_incs(operators)
    assert len(new_operators) == 1
    assert isinstance(new_operators[0], Reset)
    assert new_operators[0].dst is x
    assert new_operators[0].value == 0
Example #22
0
def build_pes(model, pes, rule):
    """Builds a `.PES` object into a model.

    Calls synapse build functions to filter the pre activities,
    and adds a `.SimPES` operator to the model to calculate the delta.

    Parameters
    ----------
    model : Model
        The model to build into.
    pes : PES
        Learning rule type to build.
    rule : LearningRule
        The learning rule object corresponding to the neuron type.

    Notes
    -----
    Does not modify ``model.params[]`` and can therefore be called
    more than once with the same `.PES` instance.
    """

    conn = rule.connection

    # Create input error signal
    error = Signal(shape=rule.size_in, name="PES:error")
    model.add_op(Reset(error))
    model.sig[rule]["in"] = error  # error connection will attach here

    # Filter pre-synaptic activities with pre_synapse
    acts = build_or_passthrough(
        model,
        pes.pre_synapse,
        slice_signal(
            model,
            model.sig[conn.pre_obj]["out"],
            conn.pre_slice,
        ) if isinstance(conn.pre_obj, Neurons) else
        model.sig[conn.pre_obj]["out"],
    )

    if conn._to_neurons:
        # multiply error by post encoders to get a per-neuron error
        #   i.e. local_error = dot(encoders, error)
        post = get_post_ens(conn)
        if not isinstance(conn.post_slice, slice):
            raise BuildError(
                "PES learning rule does not support advanced indexing on non-decoded "
                "connections")

        encoders = model.sig[post]["encoders"]
        # slice along neuron dimension if connecting to a neuron object, otherwise
        # slice along state dimension
        encoders = (encoders[:, conn.post_slice] if isinstance(
            conn.post_obj, Ensemble) else encoders[conn.post_slice, :])

        local_error = Signal(shape=(encoders.shape[0], ))
        model.add_op(Reset(local_error))
        model.add_op(DotInc(encoders, error, local_error, tag="PES:encode"))
    else:
        local_error = error

    model.add_op(
        SimPES(acts, local_error, model.sig[rule]["delta"], pes.learning_rate))

    # expose these for probes
    model.sig[rule]["error"] = error
    model.sig[rule]["activities"] = acts
Example #23
0
def build_connection(model, conn):
    # 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
        print('pre', conn.pre_obj, 'post', conn.post_obj)
        key = 'out' if is_pre else 'in'

        if target not in model.sig:
            raise ValueError("Building %s: the '%s' 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 ValueError("Error building %s: the '%s' object %s "
                             "has a '%s' 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)

    decoders = None
    eval_points = None
    solver_info = None
    transform = full_transform(conn, slice_pre=False)

    # Figure out the signal going across this connection
    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
        if (conn.function is None and isinstance(conn.pre_slice, slice)
                and (conn.pre_slice.step is None or conn.pre_slice.step == 1)):
            signal = model.sig[conn]['in'][conn.pre_slice]
        else:
            sig_in, signal = build_pyfunc(
                fn=(lambda x: x[conn.pre_slice]) if conn.function is None else
                (lambda x: conn.function(x[conn.pre_slice])),
                t_in=False,
                n_in=model.sig[conn]['in'].size,
                n_out=conn.size_mid,
                label=str(conn),
                model=model)
            model.add_op(
                DotInc(model.sig[conn]['in'],
                       model.sig['common'][1],
                       sig_in,
                       tag="%s input" % conn))
    elif isinstance(conn.pre_obj, Ensemble):
        # Normal decoded connection
        eval_points, activities, targets = build_linear_system(
            model, conn, rng)

        # Use cached solver, if configured
        solver = model.decoder_cache.wrap_solver(conn.solver)
        if conn.solver.weights:
            # account for transform
            targets = np.dot(targets, transform.T)
            transform = np.array(1, dtype=rc.get('precision', 'dtype'))

            decoders, solver_info = solver(
                activities,
                targets,
                rng=rng,
                E=model.params[conn.post_obj].scaled_encoders.T)
            model.sig[conn]['out'] = model.sig[conn.post_obj.neurons]['in']
            signal_size = model.sig[conn]['out'].size
        else:
            decoders, solver_info = solver(activities, targets, rng=rng)
            signal_size = conn.size_mid

        # Add operator for decoders
        decoders = decoders.T

        model.sig[conn]['decoders'] = model.Signal(decoders,
                                                   name="%s.decoders" % conn)
        signal = model.Signal(npext.castDecimal(np.zeros(signal_size)),
                              name=str(conn))
        model.add_op(Reset(signal))
        model.add_op(
            DotInc(model.sig[conn]['decoders'],
                   model.sig[conn]['in'],
                   signal,
                   tag="%s decoding" % conn))
    else:
        # Direct connection
        signal = model.sig[conn]['in']

    # Add operator for filtering
    if conn.synapse is not None:
        signal = filtered_signal(model, conn, signal, conn.synapse)

    if conn.modulatory:
        # Make a new signal, effectively detaching from post
        model.sig[conn]['out'] = model.Signal(npext.castDecimal(
            np.zeros(model.sig[conn]['out'].size)),
                                              name="%s.mod_output" % conn)
        model.add_op(Reset(model.sig[conn]['out']))

    # Add operator for transform
    if isinstance(conn.post_obj, Neurons):
        if not model.has_built(conn.post_obj.ensemble):
            # Since it hasn't been built, it wasn't added to the Network,
            # which is most likely because the Neurons weren't associated
            # with an Ensemble.
            raise RuntimeError("Connection '%s' refers to Neurons '%s' "
                               "that are not a part of any Ensemble." %
                               (conn, conn.post_obj))

        if conn.post_slice != slice(None):
            raise NotImplementedError(
                "Post-slices on connections to neurons are not implemented")

        gain = model.params[conn.post_obj.ensemble].gain[conn.post_slice]
        if transform.ndim < 2:
            transform = transform * gain
        else:
            transform *= gain[:, np.newaxis]

    model.sig[conn]['transform'] = model.Signal(transform,
                                                name="%s.transform" % conn)
    print('abcd', model.sig[conn]['out'].value, signal.value)
    if transform.ndim < 2:
        print('line 174', model.sig[conn]['transform'].value)
        model.add_op(
            ElementwiseInc(model.sig[conn]['transform'],
                           signal,
                           model.sig[conn]['out'],
                           tag=str(conn)))
    else:
        model.add_op(
            DotInc(model.sig[conn]['transform'],
                   signal,
                   model.sig[conn]['out'],
                   tag=str(conn)))

    if conn.learning_rule_type:
        # Forcing update of signal that is modified by learning rules.
        # Learning rules themselves apply DotIncs.

        if isinstance(conn.pre_obj, Neurons):
            modified_signal = model.sig[conn]['transform']
        elif isinstance(conn.pre_obj, Ensemble):
            if conn.solver.weights:
                # TODO: make less hacky.
                # Have to do this because when a weight_solver
                # is provided, then learning rules should operators on
                # "decoders" which is really the weight matrix.
                model.sig[conn]['transform'] = model.sig[conn]['decoders']
                modified_signal = model.sig[conn]['transform']
            else:
                modified_signal = model.sig[conn]['decoders']
        else:
            raise TypeError(
                "Can't apply learning rules to connections of "
                "this type. pre type: %s, post type: %s" %
                (type(conn.pre_obj).__name__, type(conn.post_obj).__name__))

        model.add_op(PreserveValue(modified_signal))

    model.params[conn] = BuiltConnection(decoders=decoders,
                                         eval_points=eval_points,
                                         transform=transform,
                                         solver_info=solver_info)
Example #24
0
def build_connection(model, conn):
    # 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 ValueError("Building %s: the '%s' 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 ValueError("Error building %s: the '%s' object %s "
                             "has a '%s' 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)

    decoders = None
    eval_points = None
    solver_info = None
    transform = full_transform(conn, slice_pre=False)

    # Figure out the signal going across this connection
    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
        if (conn.function is None and isinstance(conn.pre_slice, slice)
                and (conn.pre_slice.step is None or conn.pre_slice.step == 1)):
            signal = model.sig[conn]['in'][conn.pre_slice]
        else:
            signal = Signal(np.zeros(conn.size_mid), name='%s.func' % conn)
            fn = ((lambda x: x[conn.pre_slice]) if conn.function is None else
                  (lambda x: conn.function(x[conn.pre_slice])))
            model.add_op(
                SimPyFunc(output=signal,
                          fn=fn,
                          t_in=False,
                          x=model.sig[conn]['in']))
    elif isinstance(conn.pre_obj, Ensemble):
        # Normal decoded connection
        eval_points, activities, targets = build_linear_system(
            model, conn, rng)

        # Use cached solver, if configured
        solver = model.decoder_cache.wrap_solver(conn.solver)

        if conn.solver.weights:
            # include transform in solved weights
            targets = np.dot(targets, transform.T)
            transform = np.array(1., dtype=np.float64)

            decoders, solver_info = solver(
                activities,
                targets,
                rng=rng,
                E=model.params[conn.post_obj].scaled_encoders.T)
            model.sig[conn]['out'] = model.sig[conn.post_obj.neurons]['in']
            signal_size = model.sig[conn]['out'].size
        else:
            decoders, solver_info = solver(activities, targets, rng=rng)
            signal_size = conn.size_mid

        # Add operator for decoders
        decoders = decoders.T

        model.sig[conn]['decoders'] = Signal(decoders,
                                             name="%s.decoders" % conn)
        signal = Signal(np.zeros(signal_size), name=str(conn))
        model.add_op(Reset(signal))
        model.add_op(
            DotInc(model.sig[conn]['decoders'],
                   model.sig[conn]['in'],
                   signal,
                   tag="%s decoding" % conn))
    else:
        # Direct connection
        signal = model.sig[conn]['in']

    # Add operator for filtering
    if conn.synapse is not None:
        signal = filtered_signal(model, conn, signal, conn.synapse)

    # Add operator for transform
    if isinstance(conn.post_obj, Neurons):
        if not model.has_built(conn.post_obj.ensemble):
            # Since it hasn't been built, it wasn't added to the Network,
            # which is most likely because the Neurons weren't associated
            # with an Ensemble.
            raise RuntimeError("Connection '%s' refers to Neurons '%s' "
                               "that are not a part of any Ensemble." %
                               (conn, conn.post_obj))

        if conn.post_slice != slice(None):
            raise NotImplementedError(
                "Post-slices on connections to neurons are not implemented")

        gain = model.params[conn.post_obj.ensemble].gain[conn.post_slice]
        if transform.ndim < 2:
            transform = transform * gain
        else:
            transform *= gain[:, np.newaxis]

    model.sig[conn]['transform'] = Signal(transform,
                                          name="%s.transform" % conn)
    if transform.ndim < 2:
        model.add_op(
            ElementwiseInc(model.sig[conn]['transform'],
                           signal,
                           model.sig[conn]['out'],
                           tag=str(conn)))
    else:
        model.add_op(
            DotInc(model.sig[conn]['transform'],
                   signal,
                   model.sig[conn]['out'],
                   tag=str(conn)))

    # Build learning rules
    if conn.learning_rule:
        if isinstance(conn.pre_obj, Ensemble):
            model.add_op(PreserveValue(model.sig[conn]['decoders']))
        else:
            model.add_op(PreserveValue(model.sig[conn]['transform']))

        if isinstance(conn.pre_obj, Ensemble) and conn.solver.weights:
            # TODO: make less hacky.
            # Have to do this because when a weight_solver
            # is provided, then learning rules should operate on
            # "decoders" which is really the weight matrix.
            model.sig[conn]['transform'] = model.sig[conn]['decoders']

        rule = conn.learning_rule
        if is_iterable(rule):
            for r in itervalues(rule) if isinstance(rule, dict) else rule:
                model.build(r)
        elif rule is not None:
            model.build(rule)

    model.params[conn] = BuiltConnection(decoders=decoders,
                                         eval_points=eval_points,
                                         transform=transform,
                                         solver_info=solver_info)
Example #25
0
def build_pes(model, pes, rule):
    """Builds a `.PES` object into a model.

    Calls synapse build functions to filter the pre activities,
    and adds several operators to implement the PES learning rule.
    Unlike other learning rules, there is no corresponding `.Operator`
    subclass for the PES rule. Instead, the rule is implemented with
    generic operators like `.ElementwiseInc` and `.DotInc`.
    Generic operators are used because they are more likely to be
    implemented on other backends like Nengo OCL.

    Parameters
    ----------
    model : Model
        The model to build into.
    pes : PES
        Learning rule type to build.
    rule : LearningRule
        The learning rule object corresponding to the neuron type.

    Notes
    -----
    Does not modify ``model.params[]`` and can therefore be called
    more than once with the same `.PES` instance.
    """

    conn = rule.connection

    # Create input error signal
    error = Signal(np.zeros(rule.size_in), name="PES:error")
    model.add_op(Reset(error))
    model.sig[rule]['in'] = error  # error connection will attach here

    # Filter pre-synaptic activities with pre_synapse
    acts = build_or_passthrough(model, pes.pre_synapse,
                                model.sig[conn.pre_obj]['out'])

    # Compute the correction, i.e. the scaled negative error
    correction = Signal(np.zeros(error.shape), name="PES:correction")
    model.add_op(Reset(correction))

    # correction = -learning_rate * (dt / n_neurons) * error
    n_neurons = (conn.pre_obj.n_neurons if isinstance(conn.pre_obj, Ensemble)
                 else conn.pre_obj.size_in)
    lr_sig = Signal(-pes.learning_rate * model.dt / n_neurons,
                    name="PES:learning_rate")
    model.add_op(ElementwiseInc(lr_sig, error, correction, tag="PES:correct"))

    if not conn.is_decoded:
        post = get_post_ens(conn)
        weights = model.sig[conn]['weights']
        encoders = model.sig[post]['encoders'][:, conn.post_slice]

        # encoded = dot(encoders, correction)
        encoded = Signal(np.zeros(weights.shape[0]), name="PES:encoded")
        model.add_op(Reset(encoded))
        model.add_op(DotInc(encoders, correction, encoded, tag="PES:encode"))
        local_error = encoded
    elif isinstance(conn.pre_obj, (Ensemble, Neurons)):
        local_error = correction
    else:
        raise BuildError("'pre' object '%s' not suitable for PES learning" %
                         (conn.pre_obj))

    # delta = local_error * activities
    model.add_op(Reset(model.sig[rule]['delta']))
    model.add_op(
        ElementwiseInc(local_error.column(),
                       acts.row(),
                       model.sig[rule]['delta'],
                       tag="PES:Inc Delta"))

    # expose these for probes
    model.sig[rule]['error'] = error
    model.sig[rule]['correction'] = correction
    model.sig[rule]['activities'] = acts
def build_mpes(model, mpes, rule):
    conn = rule.connection

    # Create input error signal
    error = Signal(shape=(rule.size_in, ), name="PES:error")
    model.add_op(Reset(error))
    model.sig[rule]["in"] = error  # error connection will attach here

    acts = build_or_passthrough(model, mpes.pre_synapse,
                                model.sig[conn.pre_obj]["out"])

    post = get_post_ens(conn)
    encoders = model.sig[post]["encoders"]

    out_size = encoders.shape[0]
    in_size = acts.shape[0]

    from scipy.stats import truncnorm

    def get_truncated_normal(mean, sd, low, upp):
        try:
            return truncnorm( (low - mean) / sd, (upp - mean) / sd, loc=mean, scale=sd ) \
                .rvs( out_size * in_size ) \
                .reshape( (out_size, in_size) )
        except ZeroDivisionError:
            return np.full((out_size, in_size), mean)

    np.random.seed(mpes.seed)
    r_min_noisy = get_truncated_normal(mpes.r_min,
                                       mpes.r_min * mpes.noise_percentage[0],
                                       0, np.inf)
    np.random.seed(mpes.seed)
    r_max_noisy = get_truncated_normal(mpes.r_max,
                                       mpes.r_max * mpes.noise_percentage[1],
                                       np.max(r_min_noisy), np.inf)
    np.random.seed(mpes.seed)
    exponent_noisy = np.random.normal(
        mpes.exponent,
        np.abs(mpes.exponent) * mpes.noise_percentage[2], (out_size, in_size))
    np.random.seed(mpes.seed)
    pos_mem_initial = np.random.normal(1e8, 1e8 * mpes.noise_percentage[3],
                                       (out_size, in_size))
    np.random.seed(mpes.seed + 1)
    neg_mem_initial = np.random.normal(1e8, 1e8 * mpes.noise_percentage[3],
                                       (out_size, in_size))

    pos_memristors = Signal(shape=(out_size, in_size),
                            name="mPES:pos_memristors",
                            initial_value=pos_mem_initial)
    neg_memristors = Signal(shape=(out_size, in_size),
                            name="mPES:neg_memristors",
                            initial_value=neg_mem_initial)

    model.sig[conn]["pos_memristors"] = pos_memristors
    model.sig[conn]["neg_memristors"] = neg_memristors

    if conn.post_obj is not conn.post:
        # in order to avoid slicing encoders along an axis > 0, we pad
        # `error` out to the full base dimensionality and then do the
        # dotinc with the full encoder matrix
        # comes into effect when slicing post connection
        padded_error = Signal(shape=(encoders.shape[1], ))
        model.add_op(Copy(error, padded_error, dst_slice=conn.post_slice))
    else:
        padded_error = error

    # error = dot(encoders, error)
    local_error = Signal(shape=(post.n_neurons, ))
    model.add_op(Reset(local_error))
    model.add_op(DotInc(encoders, padded_error, local_error, tag="PES:encode"))

    model.operators.append(
        SimmPES(acts, local_error, mpes.learning_rate,
                model.sig[conn]["pos_memristors"],
                model.sig[conn]["neg_memristors"], model.sig[conn]["weights"],
                mpes.noise_percentage, mpes.gain, r_min_noisy, r_max_noisy,
                exponent_noisy))

    # expose these for probes
    model.sig[rule]["error"] = error
    model.sig[rule]["activities"] = acts
    model.sig[rule]["pos_memristors"] = pos_memristors
    model.sig[rule]["neg_memristors"] = neg_memristors