Skip to content

Commit a1d3000

Browse files
author
Alexander Ororbia
committed
began draft of dyn-syn lesson
1 parent 0a028b4 commit a1d3000

File tree

5 files changed

+216
-13
lines changed

5 files changed

+216
-13
lines changed
30.1 KB
Loading
30 KB
Loading
+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Dynamic Synapses and Conductance
2+
3+
In this lesson, we will study dynamic synapses, or synaptic cable components in
4+
ngc-learn that evolve on fast time-scales in response to their pre-synaptic inputs.
5+
These types of chemical synapse components are useful for modeling time-varying
6+
conductance which ultimately drives eletrical current input into neuronal units
7+
(such as spiking cells).
8+
Here, we will learn how to build two important types of dynamic synapses in
9+
ngc-learn -- the exponential synapse and the alpha synapse -- and visualize
10+
the time-course of their resulting conductances. In addition, we will then
11+
construct and study a small neuronal circuit involving a leaky integrator that
12+
is driven by exponential synapses relaying pulses from an excitatory and an
13+
inhibitory population of Poisson input encoding cells.
14+
15+
## Chemical Synapses
16+
17+
18+
Building a dynamic synapse can be done by importing
19+
[ExponentialSynapse](ngclearn.components.synapses.ExponentialSynapse) and
20+
[AlphaSynapse](ngclearn.components.synapses.AlphaSynapse)
21+
from ngc-learn's in-built components and setting them up within a model
22+
context for easy analysis.
23+
This can be done as follows (using the meta-parameters we provide in the
24+
code block below to ensure reasonable dynamics):
25+
26+
```python
27+
from jax import numpy as jnp, random, jit
28+
from ngcsimlib.context import Context
29+
import numpy as np
30+
np.random.seed(42)
31+
from ngclearn.components import ExponentialSynapse, AlphaSynapse
32+
from ngclearn.operations import summation
33+
34+
from ngcsimlib.compilers.process import Process
35+
from ngcsimlib.context import Context
36+
import ngclearn.utils.weight_distribution as dist
37+
38+
39+
dkey = random.PRNGKey(1234) ## creating seeding keys for synapses
40+
dkey, *subkeys = random.split(dkey, 6)
41+
dt = 0.1 # ms ## integration time constant
42+
T = 8. # ms ## total duration time
43+
44+
Tsteps = int(T/dt) + 1
45+
46+
# ---- build a two-synapse system ----
47+
with Context("dual_syn_system") as ctx:
48+
Wexp = ExponentialSynapse( ## exponential dynamic synapse
49+
name="Wexp", shape=(1, 1), tau_syn=3., g_syn_bar=1., syn_rest=0., resist_scale=1.,
50+
weight_init=dist.constant(value=1.), key=subkeys[0]
51+
)
52+
Walpha = AlphaSynapse( ## alpha dynamic synapse
53+
name="Walpha", shape=(1, 1), tau_syn=1., g_syn_bar=1., syn_rest=0., resist_scale=1.,
54+
weight_init=dist.constant(value=1.), key=subkeys[0]
55+
)
56+
57+
## set up basic simulation process calls
58+
advance_process = (Process("advance_proc")
59+
>> Wexp.advance_state
60+
>> Walpha.advance_state)
61+
ctx.wrap_and_add_command(jit(advance_process.pure), name="run")
62+
63+
reset_process = (Process("reset_proc")
64+
>> Wexp.reset
65+
>> Walpha.reset)
66+
ctx.wrap_and_add_command(jit(reset_process.pure), name="reset")
67+
```
68+
69+
where we notice in the above we have instantiated two different kinds of chemical synapse components
70+
that we will run side-by-side in order to extract their produced conductance values in response to
71+
the exact same input stream. For both the exponential and the alpha synapse, there are at least three
72+
important hyper-parameters to configure:
73+
1. `tau_syn` ($\tau_{\text{syn}}$): the synaptic conductance decay time constant;
74+
2. `g_syn_bar` ($\bar{g}_{\text{syn}}$): the maximal conductance value produced by each pulse transmitted
75+
across this synapse; and,
76+
3. `syn_rest` ($E_{rest}$): the (post-synaptic) reversal potential for this synapse -- note that this value
77+
determines the direction of current flow through the synapse, yielding a synapse with an
78+
excitatory nature for non-negative values of `syn_rest` or a synapse with an inhibitory
79+
nature for negative values of `syn_rest`.
80+
81+
82+
The flow of electrical current from a pre-synaptic neuron to a post-synaptic one is often modeled under the assumption that pre-synaptic pulses result in impermanent (transient; lasting for a short period of time) changes in the conductance of a post-synaptic neuron. As a result, the resulting conductance dynamics $g_{\text{syn}}(t)$ of each of the two synapses that you have built above can be simulated in ngc-learn according to one or more ordinary differential equations (ODEs).
83+
For the exponential synapse, the dynamics adhere to the following ODE:
84+
85+
$$
86+
\frac{\partial g_{\text{syn}}(t)}{\partial t} = -g_{\text{syn}}(t)/\tau_{\text{syn}} + \bar{g}_{\text{syn}} \sum_{k} \delta(t - t_{k})
87+
$$
88+
89+
where the conductance (for a post-synaptic unit) output of this synapse is driven by a sum over all of its incoming pre-synaptic spikes; this ODE means that pre-synaptic spikes are filtered via an expoential kernel (i.e., a low-pass filter).
90+
On the other hand, for the alpha synapse, the dynamics adhere to the following coupled set of ODEs:
91+
92+
$$
93+
\frac{\partial h_{\text{syn}}(t)}{\partial t} &= -h_{\text{syn}}(t)/\tau_{\text{syn}} + \bar{g}_{\text{syn}} \sum_{k} \delta(t - t_{k}) \\
94+
\frac{\partial g_{\text{syn}}(t)}{\partial t} &= -g_{\text{syn}}(t)/\tau_{\text{syn}} + h_{\text{syn}}(t)/\tau_{\text{syn}}
95+
$$
96+
97+
where $h_{\text{syn}}(t)$ is an intermediate variable that operates in service of driving the conductance variable $g_{\text{syn}}(t)$ itself.
98+
99+
For both the exponential and the alpha synapse, the changes in conductance are finally converted (via Ohm's law) to electrical current to produce the final derived variable $j_{\text{syn}}(t)$:
100+
101+
$$
102+
j_{\text{syn}}(t) = g_{\text{syn}}(t) (v(t) - E_{\text{rest}})
103+
$$
104+
105+
where $v_{\text{rest}$ (or $E_{\text{rest}}$) is the post-synaptic reverse potential of the synapse; this is typically set to $E_{\text{rest}} = 0$ (millivolts; mV)for the case of excitatory changes and $E_{\text{rest}} = -75$ (mV) for the case of inhibitory changes. $v(t)$ is the voltage/membrane potential of the post-synaptic the synaptic cable wires to, meaning that the conductance models above are voltage-dependent (in ngc-learn, if one wants voltage-independent conductance, then `syn_rest` must be set to `None`).
106+
107+
108+
### Examining the Conductances of Dynamic Synapses
109+
110+
We can track and visualize the conductance outputs of our two different dynamic synapses by running a stream of controlled pre-synaptic pulses. Specifically, we will observe the output behavior of each in response to a sparse stream, eight milliseconds in length, where only a single spike is emitted at one millisecond.
111+
To create the simulation of a single input pulse stream, you can write the following code:
112+
113+
```python
114+
time_ticks = []
115+
time_labs = []
116+
for t in range(Tsteps):
117+
if t % 10 == 0:
118+
time_ticks.append(t)
119+
time_labs.append(f"{t * dt:.1f}")
120+
121+
time_span = []
122+
g = []
123+
ga = []
124+
ctx.reset()
125+
for t in range(Tsteps):
126+
s_t = jnp.zeros((1, 1))
127+
if t * dt == 1.: ## pulse at 1 ms
128+
s_t = jnp.ones((1, 1))
129+
Wexp.inputs.set(s_t)
130+
Walpha.inputs.set(s_t)
131+
Wexp.v.set(Wexp.v.value * 0)
132+
Walpha.v.set(Walpha.v.value * 0)
133+
ctx.run(t=t * dt, dt=dt)
134+
135+
print(f"\r g = {Wexp.g_syn.value} ga = {Walpha.g_syn.value}", end="")
136+
g.append(Wexp.g_syn.value)
137+
ga.append(Walpha.g_syn.value)
138+
time_span.append(t) #* dt)
139+
print()
140+
g = jnp.squeeze(jnp.concatenate(g, axis=1))
141+
g = g/jnp.amax(g)
142+
ga = jnp.squeeze(jnp.concatenate(ga, axis=1))
143+
ga = ga/jnp.amax(ga)
144+
```
145+
146+
Note that we further normalize the conductance trajectories of both synapses to lie within
147+
the range of $[0, 1]$, primarily for visualization purposes.
148+
Finally, to visualize the conductance time-course of both synapses, you can write the
149+
following:
150+
151+
```python
152+
import matplotlib #.pyplot as plt
153+
matplotlib.use('Agg')
154+
import matplotlib.pyplot as plt
155+
cmap = plt.cm.jet
156+
157+
## ---- plot the exponential synapse conductance time-course ----
158+
fig, ax = plt.subplots()
159+
160+
gvals = ax.plot(time_span, g, '-', color='tab:red')
161+
#plt.xticks(time_span, time_labs)
162+
ax.set_xticks(time_ticks, time_labs)
163+
ax.set(xlabel='Time (ms)', ylabel='Conductance',
164+
title='Exponential Synapse Conductance Time-Course')
165+
ax.grid(which="major")
166+
fig.savefig("exp_syn.jpg")
167+
plt.close()
168+
169+
## ---- plot the alpha synapse conductance time-course ----
170+
fig, ax = plt.subplots()
171+
172+
gvals = ax.plot(time_span, ga, '-', color='tab:blue')
173+
#plt.xticks(time_span, time_labs)
174+
ax.set_xticks(time_ticks, time_labs)
175+
ax.set(xlabel='Time (ms)', ylabel='Conductance',
176+
title='Alpha Synapse Conductance Time-Course')
177+
ax.grid(which="major")
178+
fig.savefig("alpha_syn.jpg")
179+
plt.close()
180+
```
181+
182+
which should produce and save two plots to disk. You can then compare and contrast the plots of the
183+
expoential and alpha synapse conductance trajectories:
184+
185+
```{eval-rst}
186+
.. table::
187+
:align: center
188+
189+
+---------------------------------------------------------+-----------------------------------------------------------+
190+
| .. image:: ../docs/images/tutorials/neurocog/expsyn.png | .. image:: ../docs/images/tutorials/neurocog/alphasyn.png |
191+
| :width: 100px | :width: 100px |
192+
| :align: center | :align: center |
193+
+---------------------------------------------------------+-----------------------------------------------------------+
194+
```
195+
196+
## Excitatory-Inhibitory Driven Dynamics
197+
198+
199+

docs/tutorials/neurocog/index.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ work towards more advanced concepts.
5858

5959
.. toctree::
6060
:maxdepth: 1
61-
:caption: Forms of Plasticity
61+
:caption: Synapses and Forms of Plasticity
6262

63+
synaptic_conductance
6364
hebbian
6465
stdp
6566
mod_stdp

docs/tutorials/neurocog/short_term_plasticity.md

+15-12
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ synapse that evolves according to STP. We will first write our
1919
simulation of this dynamic synapse from the perspective of STF-dominated
2020
dynamics, plotting out the results under two different Poisson spike trains
2121
with different spiking frequencies. Then, we will modify our simulation
22-
to emulate dynamics from a STD-dominated perspective.
22+
to emulate dynamics from an STD-dominated perspective.
2323

2424
### Starting with Facilitation-Dominated Dynamics
2525

@@ -39,10 +39,10 @@ some mixture of the two.
3939

4040
Ultimately, the above means that, in the context of spiking cells, when a
4141
pre-synaptic neuron emits a pulse, this act will affect the relative magnitude
42-
of the synapse's efficacy;
43-
in some cases, this will result in an increase (facilitation) and, in others,
44-
this will result in a decrease (depression) that lasts over a short period
45-
of time (several hundreds to thousands of milliseconds in many instances).
42+
of the synapse's efficacy. In some cases, this will result in an increase
43+
(facilitation) and, in others, this will result in a decrease (depression)
44+
that lasts over a short period of time (several hundreds to thousands of
45+
milliseconds in many instances).
4646
As a result of considering synapses to have a dynamic nature to them, both over
4747
short and long time-scales, plasticity can now be thought of as a stimulus and
4848
resource-dependent quantity, reflecting an important biophysical aspect that
@@ -87,13 +87,16 @@ tau_d = 50. # ms
8787
plot_fname = "{}Hz_stp_{}.jpg".format(firing_rate_e, tag)
8888

8989
with Context("Model") as model:
90-
W = STPDenseSynapse("W", shape=(1, 1), weight_init=dist.constant(value=2.5),
91-
resources_init=dist.constant(value=Rval),
92-
tau_f=tau_f, tau_d=tau_d, key=subkeys[0])
90+
W = STPDenseSynapse(
91+
"W", shape=(1, 1), weight_init=dist.constant(value=2.5),
92+
resources_init=dist.constant(value=Rval), tau_f=tau_f, tau_d=tau_d,
93+
key=subkeys[0]
94+
)
9395
z0 = PoissonCell("z0", n_units=1, target_freq=firing_rate_e, key=subkeys[0])
94-
z1 = LIFCell("z1", n_units=1, tau_m=tau_m, resist_m=(tau_m / dt) * R_m,
95-
v_rest=-60., v_reset=-70., thr=-50.,
96-
tau_theta=0., theta_plus=0., refract_time=0.)
96+
z1 = LIFCell(
97+
"z1", n_units=1, tau_m=tau_m, resist_m=(tau_m / dt) * R_m, v_rest=-60.,
98+
v_reset=-70., thr=-50., tau_theta=0., theta_plus=0., refract_time=0.
99+
)
97100

98101
W.inputs << z0.outputs ## z0 -> W
99102
z1.j << W.outputs ## W -> z1
@@ -156,7 +159,7 @@ resources ready for the dynamic synapse's use.
156159

157160
### Simulating and Visualizing STF
158161

159-
Now that we understand the basics of how an ngc-learn STP works, we can next
162+
Now that we understand the basics of how an ngc-learn STP synapse works, we can next
160163
try it out on a simple pre-synaptic Poisson spike train. Writing out the
161164
simulated input Poisson spike train and our STP model's processing of this
162165
data can be done as follows:

0 commit comments

Comments
 (0)