Simple Feedback Loops
I have been steadily adding features to my SynaptiFlux toy neuron/synapse model. In my most recent work I have been considering feedback loops, since I think there is a lot of scope for interesting structures using feedback. Though in this post I will only consider a handful of simple feedback loops. If anyone has ideas for more interesting structures, please let me know, and I will try to implement them.
In this post I will be using my .map notation, which currently has two types of learn rules:
synapse(s) => neuron(s)
neuron(s) |=> synapse(s)
It is not yet clear if I need more types of learn rules, but these are sufficiently powerful for now. Further, I have a script run_map.py that processes these learn rules, and has a handful of available commands:
- print: prints the given string
- poke: pokes the given neurons, specified in SDB ket/superposition/sequence style
- poke-list: pokes the given neurons, specified in Python list style
- poke-string: pokes the string, converted to a list of characters
- update: update the system by the given integer number of time steps
- print-global-sequences: print our NeuralModule global sequences (this is out of the scope for this post)
- exit: exit the current map file
where poking means activating the given neuron for 1 time step, independent of any other input at that time step. The idea is you build some neural structure, and then you poke into that system to start it going, or to stop it.
Examples
Now, on to our simple feedback loops, though I also call them latches, since once they latch on they tend to stay latched on. My test cases are in our latching.map file.
simple latches
The first example is the simplest, feed the output of a synapse back into the input of a neuron. The .map is given by:
|simple latch> => |simple latch on neuron>
|simple latch on neuron> |=> |simple latch>
|simple latch off neuron> |=> -1 |simple latch>
where we poke the on neuron to switch on the latch, and the off neuron to switch off the latch. Basically the simplest kind of feedback loop. Something to note here is -1 |simple latch> which inhibits the simple latch neuron, and so breaks the feedback loop. Our code required a very small tweak to enable inhibition, but it seems to be working
. Given this code
update: 5
print:
print: Switch on simple latch:
poke: |simple latch on neuron>
update: 5
print:
print: Switch off simple latch:
poke: |simple latch off neuron>
update: 5
we have the following output:
update: 5
Switch on simple latch:
poke-list: [['simple latch on neuron']]
update: 5
1: 5) simple latch
1: 6) simple latch
1: 7) simple latch
1: 8) simple latch
1: 9) simple latch
Switch off simple latch:
poke-list: [['simple latch off neuron']]
update: 5
1: 10) simple latch
update: 5
where it takes one time step for the latch to switch off, and then it stays off. Also, we should note our lines printed here have this structure
{current-layer}: {current-time-step}) {neuron-name}
where if the input pattern to a neuron has a maximum layer number of n, then the neuron is given the layer number n+1, and time step is the number of time steps our system has evolved.
Alternating or periodic latches
The next latch type is alternating or periodic, it still feedback’s on to itself like the simple latch, but only after a given delay. We do this using:
|alternating latch> => |alternating latch on neuron>
|alternating latch on neuron> |=> |> . |> . |> . |alternating latch>
|alternating latch off neuron> |=> -1 |alternating latch>
where
|> . |> . |> . |alternating latch>
is SDB style sequence notation, with meaning: do nothing for three time steps, and then activate the alternating latch. We invoke and then switch off this latch using this code:
update: 5
print:
print: Switch on an alternating latch with period 4:
poke: |alternating latch on neuron>
update: 20
print:
print: Switch off alternating latch (NB: the phase must be correct for this to switch off the neuron):
update: 3
poke: |alternating latch off neuron>
update: 20
which produces the following output:
Switch on an alternating latch with period 4:
poke-list: [['alternating latch on neuron']]
update: 20
1: 23) alternating latch
1: 27) alternating latch
1: 31) alternating latch
1: 35) alternating latch
1: 39) alternating latch
Switch off alternating latch (NB: the phase must be correct for this to switch off the neuron):
update: 3
poke-list: [['alternating latch off neuron']]
update: 20
1: 43) alternating latch
Noting:
- the time steps are increasing by 4, instead of 1
- we have to line up the phase of the
switch offpoke, otherwise the latch stays on
Poke buffers
We can also repurpose alternating latches as poke buffers. This is a buffer of given length, say 10, in to which we can poke binary values. Then every 10 steps, it outputs the elements that have been poked on, and will continue to do so until you poke off each element, again, while getting the phase exactly right. The buffer is given by:
|poke buffer 10> => |poke buffer 10 on neuron>
|poke buffer 10 on neuron> |=> |> . |> . |> . |> . |> . |> . |> . |> . |> . |poke buffer 10>
|poke buffer 10 off neuron> |=> -1 |poke buffer 10>
which is simply an alternating latch with period 10. Then we invoke it using this code:
print:
print: Switch on an element in the poke buffer of length 10:
poke: |poke buffer 10 on neuron>
update: 3
print: Switch on another element in the poke buffer:
poke: |poke buffer 10 on neuron>
update: 1
print: Switch on another element:
poke: |poke buffer 10 on neuron>
update: 30
print:
print: Switching off the poke buffer currently requires we poke at just the right time-steps in the sequence:
update: 5
poke: |poke buffer 10 off neuron>
update: 3
poke: |poke buffer 10 off neuron>
update: 1
poke: |poke buffer 10 off neuron>
update: 30
where we poked in a value, then waited 3 time steps, poked in a value, waited another time step, and then poked in a third value. On reflection I probably could have poked the buffer in one line:
poke: |poke buffer 10 on neuron> . |> . |> . |> . |poke buffer 10 on neuron> . |poke buffer 10 on neuron>
Regardless, here is the output:
Switch on an element in the poke buffer of length 10:
poke-list: [['poke buffer 10 on neuron']]
update: 3
Switch on another element in the poke buffer:
poke-list: [['poke buffer 10 on neuron']]
update: 1
Switch on another element:
poke-list: [['poke buffer 10 on neuron']]
update: 30
1: 72) poke buffer 10
1: 75) poke buffer 10
1: 76) poke buffer 10
1: 82) poke buffer 10
1: 85) poke buffer 10
1: 86) poke buffer 10
1: 92) poke buffer 10
1: 95) poke buffer 10
1: 96) poke buffer 10
Switching off the poke buffer currently requires we poke at just the right time-steps in the sequence:
update: 5
poke-list: [['poke buffer 10 off neuron']]
update: 3
1: 102) poke buffer 10
poke-list: [['poke buffer 10 off neuron']]
update: 1
1: 105) poke buffer 10
poke-list: [['poke buffer 10 off neuron']]
update: 30
1: 106) poke buffer 10
The key takeaway is that the output repeats itself every 10 time steps, until we unpoke the elements in the buffer.
temporary simple latches
The next latch type is a simple latch, that switches itself off after some number of time steps. This is defined using:
|temporary simple latch> => |temporary simple latch on neuron>
|temporary simple latch on neuron> |=> |temporary simple latch> . |> . |> . |> . |> . |> . -1 |temporary simple latch>
|temporary simple latch off neuron> |=> -1 |temporary simple latch>
And invoked using:
print:
print: Switch on temporary simple latch:
poke: |temporary simple latch on neuron>
update: 20
with corresponding output:
Switch on temporary simple latch:
poke-list: [['temporary simple latch on neuron']]
update: 20
1: 136) temporary simple latch
1: 137) temporary simple latch
1: 138) temporary simple latch
1: 139) temporary simple latch
1: 140) temporary simple latch
1: 141) temporary simple latch
1: 142) temporary simple latch
So even though we update the system 20 time steps, the latch only stays on for 7 time steps. The desired number of time steps is tweakable by changing the number of empty kets |> in our sequence on the right hand side of the |temporary simple latch on neuron> learn rule.
temporary alternating latches
The same approach can be used for alternating latches:
|temporary alternating latch> => |temporary alternating latch on neuron>
|temporary alternating latch on neuron> |=> |> . |> . |> . |temporary alternating latch> . |> . |> . |> . |> . |> . |> . |> . -1 |temporary alternating latch>
|temporaryalternating latch off neuron> |=> -1 |temporary alternating latch>
Invoked using:
print:
print: Switch on temporary alternating latch:
poke: |temporary alternating latch>
update: 50
with corresponding output:
Switch on temporary alternating latch:
poke-list: [['temporary alternating latch']]
update: 50
0: 156) temporary alternating latch
1: 160) temporary alternating latch
1: 164) temporary alternating latch
1: 168) temporary alternating latch
length 2 neuron chains, with multiple activation choices
This one is slightly more interesting. We define a length 2 neuron chain, and then define a collection of neurons that can activate that chain, and a single neuron to switch off the chain. The code is given by:
|A> => |H>
|H> => |A>
|H1> |=> |H>
|H2> |=> |H>
|H3> |=> |H>
|H4> |=> |H>
|H5> |=> |H>
|H off> |=> -1 |H>
where
AactivatesHHactivatesAAactivatesH, and so on- any of
H1,H2,H3,H4,H5can activateH H offinhibits theHneuron
This is invoked using:
print:
print: Switch on A by poking H3:
poke: |H3>
update: 10
print: Switch off A by poking |H off>:
poke: |H off>
update: 10
with output:
Switch on A by poking H3:
poke-list: [['H3']]
update: 10
0: 206) H
2: 207) A
1: 208) H
2: 209) A
1: 210) H
2: 211) A
1: 212) H
2: 213) A
1: 214) H
2: 215) A
Switch off A by poking |H off>:
poke-list: [['H off']]
update: 10
1: 216) H
where it takes 1 time step after poking H off for the sequence to stop.
neuron chain
Finally, we have a neuron chain. Each neuron activates the next one, and the final one invokes the initial neuron. The code is given by:
|B> => |F1>
|F1> => |F2>
|F2> => |F3>
|F3> => |F4>
|F4> => |F5>
|F5> => |F6>
|F6> => |B>
with the property that we can activate the full chain by poking any member of the chain. I haven’t tested it, but presumably switching off the chain would require getting the phase just right. This is invoked using:
print:
print: Switch on B feedback loop by poking F2:
poke: |F2>
update: 20
with output:
Switch on B feedback loop by poking F2:
poke-list: [['F2']]
update: 20
2: 226) F2
3: 227) F3
4: 228) F4
5: 229) F5
6: 230) F6
7: 231) B
1: 232) F1
2: 233) F2
3: 234) F3
4: 235) F4
5: 236) F5
6: 237) F6
7: 238) B
1: 239) F1
2: 240) F2
3: 241) F3
4: 242) F4
5: 243) F5
6: 244) F6
7: 245) B
Also note we have different layer numbers (the number in front of the colon) for each of the chain members, which makes sense. Where a layer number is a measure of how deep a neuron is in the neural module. As I said above, if a neuron has synaptic inputs with a max layer number of n, then that neuron is given the layer number n+1.
Future?
I have some thoughts on how these will be useful later (eg, for switching modes on and off), so more later. But again, if anyone has proposals for more interesting feedback loops, please post, and I will see if I can implement them. The above are foundational examples, and many more structures are possible!