Skip to content

Commit d21e1a5

Browse files
author
Alexander Ororbia
committed
made revisions to pc-rao doc
1 parent 16f4293 commit d21e1a5

File tree

1 file changed

+131
-146
lines changed

1 file changed

+131
-146
lines changed

docs/museum/pc_rao_ballard1999.md

Lines changed: 131 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Hierarchical Predictive Coding (Rao & Ballard; 1999)
1+
# Hierarchical Predictive Coding for Reconstruction (Rao & Ballard; 1999)
22

33
In this exhibit, we create, simulate, and visualize the internally acquired receptive fields of the predictive coding
44
model originally proposed in (Rao & Ballard, 1999) [1].
@@ -7,80 +7,68 @@ The model code for this exhibit can be found
77
[here](https://github.com/NACLab/ngc-museum/tree/main/exhibits/pc_recon).
88

99

10+
## Setting Up Hierarchical Predictive Coding (HPC) with NGC-Learn
1011

1112

12-
## Predictive Coding with NGC-Learn
13-
<!--
14-
-----------------------------------------------------------------------------------------------------
15-
-->
16-
---
13+
### The HPC Model for Reconstruction Tasks
1714

18-
### PC model for Reconstruction Task
19-
20-
For building PC model you first need to define all the components inside the model.
21-
Then you should wire those components together with specific configuration depending
22-
on the task.
23-
24-
1. **Create neural component**
25-
2. **Create synaptic component**
26-
3. **Wire components** – define how the components connect and interact with each others.
27-
28-
<!--
29-
-----------------------------------------------------------------------------------------------------
30-
-->
31-
---
15+
To build an HPC model, you will first need to define all of the components inside of the model.
16+
After doing this, you will next wire those components together under a specific configuration, depending
17+
on the task.
18+
This setup process involves doing the following:
19+
1. **Create neural component**: instantiating neuronal unit (with dynamics) components.
20+
2. **Create synaptic component**: instantiating synaptic connection components.
21+
3. **Wire components**: defining how the components connect and interact with each other.
3222

3323
<!-- ################################################################################ -->
3424

35-
### 1: Make Neural Component(s):
25+
### 1: Create the Neural Component(s):
3626

3727
<!-- ################################################################################ -->
3828

3929

40-
**Responding Neurons**
30+
**Representation (Response) Neuronal Layers**
4131
<br>
4232

43-
We want to build a hierarchical neural network we need neural layers. In predictive coding network with real-valued dynamics
44-
we use `RateCell` components ([RateCell tutorial](https://ngc-learn.readthedocs.io/en/latest/tutorials/neurocog/rate_cell.html)).
45-
Here, we want 3-layer network (3-hidden layers) so we define 3 components, each with `n_units` size for hidden representatins.
33+
If we want to build an HPC model, which is a hierarchical neural network, we will need to set up a few neural layers. For predictive coding with real-valued (graded) dynamics, we will want to use the library's in-built `RateCell` components ([RateCell tutorial](https://ngc-learn.readthedocs.io/en/latest/tutorials/neurocog/rate_cell.html)).
34+
Since we want a 3-layer network (i.e., an HPC model with three hidden, or "representation", layers), we need to define three components, each with an `n_units` size for their respective hidden representations. This is done as follows:
4635

4736
```python
48-
z3 = RateCell("z3", n_units=h3_dim, tau_m=tau_m, act_fx=act_fx, prior=(prior_type, lmbda))
49-
z2 = RateCell("z2", n_units=h2_dim, tau_m=tau_m, act_fx=act_fx, prior=(prior_type, lmbda))
50-
z1 = RateCell("z1", n_units=h1_dim, tau_m=tau_m, act_fx=act_fx, prior=(prior_type, lmbda))
37+
with Context("Circuit") as circuit: ## set up a (simulation) context for HPC model w/ 3 hidden layers
38+
z3 = RateCell("z3", n_units=h3_dim, tau_m=tau_m, act_fx=act_fx, prior=(prior_type, lmbda))
39+
z2 = RateCell("z2", n_units=h2_dim, tau_m=tau_m, act_fx=act_fx, prior=(prior_type, lmbda))
40+
z1 = RateCell("z1", n_units=h1_dim, tau_m=tau_m, act_fx=act_fx, prior=(prior_type, lmbda))
5141
```
5242

53-
54-
55-
5643
<!-- ################################################################################ -->
5744

5845
<br>
5946
<br>
6047

6148
<img src="../images/museum/hgpc/GEC.png" width="120" align="right" />
6249

63-
**Error Neurons**
50+
**Error Neuronal Layers**
6451
<br>
6552

6653

67-
For each activation layer we have a set of additional neurons with the same size to measure the prediction error for individual
68-
`RateCell` components. The error value will later be used to calculate the **energy** for layers (including hiddens) and the whole model.
69-
54+
For each (`RateCell`) layer's activation, we will also want to setup an additional set of neuronal
55+
layers -- with the same size as the representation layers -- to measure the prediction error(s)
56+
for the sets of individual `RateCell` components. The error values that this layers will emit will
57+
be later used to calculate the (free) **energy** for each layer as well as the whole model. This is
58+
specified like so:
7059

7160
```python
72-
e2 = GaussianErrorCell("e2", n_units=h2_dim) ## e2_size == z2_size
73-
e1 = GaussianErrorCell("e1", n_units=h1_dim) ## e1_size == z1_size
74-
e0 = GaussianErrorCell("e0", n_units=in_dim) ## e0_size == z0_size (x size)
61+
e2 = GaussianErrorCell("e2", n_units=h2_dim) ## e2_size == z2_size
62+
e1 = GaussianErrorCell("e1", n_units=h1_dim) ## e1_size == z1_size
63+
e0 = GaussianErrorCell("e0", n_units=in_dim) ## e0_size == z0_size (x size) (stimulus layer)
7564
```
7665

77-
7866
<br>
7967
<br>
8068

8169
<!-- ################################################################################ -->
8270

83-
### 2: Make Synaptic Component(s):
71+
### 2: Create the Synaptic Component(s):
8472

8573
<!-- ################################################################################ -->
8674

@@ -89,18 +77,22 @@ e0 = GaussianErrorCell("e0", n_units=in_dim) ## e0_size == z0_size (x s
8977

9078
<!-- <img src="images/GEC.png" width="120" align="right"/> -->
9179

92-
**Forward Synapses**
80+
**Forward Synaptic Connections**
9381
<br>
9482

95-
To connect layers to each others we create synapstic components. To send infromation in forward pass (from input into deeper layers with a bottom-up stream)
96-
we use `ForwardSynapse` components. Check out [Brain's Information Flow](https://github.com/Faezehabibi/pc_tutorial/blob/main/information_flow.md#---information-flow-in-the-brain--)
97-
for detailed explanation of information flow in brain modeling.
98-
83+
To connect the layers of our model to each other, we will need to create synaptic components
84+
(which will project/propagate information across the layers); ultimately, this means we need
85+
to construct the message-passing scheme of our HPC model. In order to send information in a
86+
"forward pass" (from the stimulus/input layer into deeper hidden layers, in a bottom-up stream),
87+
we make use of `ForwardSynapse` components. Please check out
88+
[Brain's Information Flow](https://github.com/Faezehabibi/pc_tutorial/blob/main/information_flow.md#---information-flow-in-the-brain--) for a more detailed explanation of the flow of information that we use in the context
89+
of brain modeling.
90+
Setting up the forward projections/pathway is done like so:
9991

10092
```python
101-
E3 = ForwardSynapse("E3", shape=(h2_dim, h3_dim)) ## pre-layer size (x) => (h1) post-layer size
102-
E2 = ForwardSynapse("E2", shape=(h1_dim, h2_dim)) ## pre-layer size (h1) => (h2) post-layer size
103-
E1 = ForwardSynapse("E1", shape=(in_dim, h1_dim)) ## pre-layer size (h2) => (h3) post-layer size
93+
E3 = ForwardSynapse("E3", shape=(h2_dim, h3_dim)) ## pre-layer size (h2) => (h3) post-layer size
94+
E2 = ForwardSynapse("E2", shape=(h1_dim, h2_dim)) ## pre-layer size (h1) => (h2) post-layer size
95+
E1 = ForwardSynapse("E1", shape=(in_dim, h1_dim)) ## pre-layer size (x) => (h1) post-layer size
10496
```
10597

10698
<!-- ################################################################################ -->
@@ -110,139 +102,136 @@ E1 = ForwardSynapse("E1", shape=(in_dim, h1_dim)) ## pre-layer size (h2
110102

111103
<!-- <img src="images/GEC.png" width="120" align="right"/> -->
112104

113-
**Backward Synapses**
105+
**Backward(s) Synaptic Connections**
114106
<br>
115107

116-
For each `ForwardSynapse` components sending infromation upward (bottom-up stream) exist a `BackwardSynapse` component to reverse the information flow and
117-
send it back downward (top-down stream -- from top layer to bottom/input). If you are not convinced, check out [Information Flow](https://github.com/Faezehabibi/pc_tutorial/blob/19b0692fa307f2b06676ca93b9b93ba3ba854766/information_flow.md).
108+
For each `ForwardSynapse` component that sends information upward (i.e., the "bottom-up" stream),
109+
there exists a `BackwardSynapse` component that reverses the flow of information flow by sending
110+
signals back downwards (i.e., the "top-down" stream -- from the top layer to the bottom/input ones).
111+
Again, we refer you to this resource [Information Flow](https://github.com/Faezehabibi/pc_tutorial/blob/19b0692fa307f2b06676ca93b9b93ba3ba854766/information_flow.md) for more information.
112+
To set up the backwards/message-passing connections, you will write to the following:
118113

119114
```python
120-
W3 = BackwardSynapse("W3",
121-
shape=(h3_dim, h2_dim), ## pre-layer size (h3) => (h2) post-layer size
122-
optim_type=opt_type, ## optimization method (sgd, adam, ...)
123-
weight_init=w3_init, ## W3[t0]: initial values before training at time[t0]
124-
w_bound=w_bound, ## -1 for deactivating the bouding synaptic value
125-
sign_value=-1., ## -1 means M-step solve minimization problem
126-
eta=eta, ## learning-rate (lr)
127-
)
128-
W2 = BackwardSynapse("W2",
129-
shape=(h2_dim, h1_dim), ## pre-layer size (h2) => (h1) post-layer size
130-
optim_type=opt_type, ## Optimizer
131-
weight_init=w2_init, ## W2[t0]
132-
w_bound=w_bound, ## -1: deactivate the bouding
133-
sign_value=-1., ## Minimization
134-
eta=eta, ## lr
135-
)
136-
W1 = BackwardSynapse("W1",
137-
shape=(h1_dim, in_dim), ## pre-layer size (h1) => (x) post-layer size
138-
optim_type=opt_type, ## Optimizer
139-
weight_init=w1_init, ## W1[t0]
140-
w_bound=w_bound, ## -1: deactivate the bouding
141-
sign_value=-1., ## Minimization
142-
eta=eta, ## lr
143-
)
115+
W3 = BackwardSynapse("W3",
116+
shape=(h3_dim, h2_dim), ## pre-layer size (h3) => (h2) post-layer size
117+
optim_type=opt_type, ## optimization method (sgd, adam, ...)
118+
weight_init=w3_init, ## W3[t0]: initial values before training at time[t0]
119+
w_bound=w_bound, ## -1 for deactivating the bouding synaptic value
120+
sign_value=-1., ## -1 means M-step solve minimization problem
121+
eta=eta, ## learning-rate (lr)
122+
)
123+
W2 = BackwardSynapse("W2",
124+
shape=(h2_dim, h1_dim), ## pre-layer size (h2) => (h1) post-layer size
125+
optim_type=opt_type, ## Optimizer
126+
weight_init=w2_init, ## W2[t0]
127+
w_bound=w_bound, ## -1: deactivate the bouding
128+
sign_value=-1., ## Minimization
129+
eta=eta, ## lr
130+
)
131+
W1 = BackwardSynapse("W1",
132+
shape=(h1_dim, in_dim), ## pre-layer size (h1) => (x) post-layer size
133+
optim_type=opt_type, ## Optimizer
134+
weight_init=w1_init, ## W1[t0]
135+
w_bound=w_bound, ## -1: deactivate the bouding
136+
sign_value=-1., ## Minimization
137+
eta=eta, ## lr
138+
)
144139
```
145140

146-
147-
148-
149-
150-
151141
<br>
152142
<br>
153143
<!-- ----------------------------------------------------------------------------------------------------- -->
154144

155-
### Wire the Component(s) Together:
145+
### Wiring the Component(s) Together:
156146

157147

158-
The signal pathway is according to Rao & Ballard 1999.
159-
Error is information goes from buttom to up in the forward pass.
160-
Corrected prediction comes back from top to the down in the backward pass.
148+
The signaling pathway that we will create is in accordance with <b>[1]</b> (Rao and Ballard's classical model).
149+
Error (mismatch signals) is information that goes from the bottom (layer) of the model to its top (layer) in
150+
the forward pass(es).
151+
Corrected prediction information will come back from the top (layer) to the bottom (layer) in the backward
152+
pass(es).
161153

154+
The following code block will set up the top-down projection message-passing pathway:
162155

163156
```python
164-
######### Feedback pathways (Top-down) #########
165-
### actual neural activation
166-
e2.target << z2.z
167-
e1.target << z1.z
168-
169-
### Top-down prediction
170-
e2.mu << W3.outputs
171-
e1.mu << W2.outputs
172-
e0.mu << W1.outputs
173-
174-
### Top-down prediction errors
175-
z1.j_td << e1.dtarget
176-
z2.j_td << e2.dtarget
177-
178-
W3.inputs << z3.zF
179-
W2.inputs << z2.zF
180-
W1.inputs << z1.zF
157+
######### Feedback pathways (Top-down) #########
158+
### Actual neural activations
159+
z2.z >> e2.target ## Layer 2's target is z2's rate-value `z`
160+
z1.z >> e1.target ## Layer 1's target is z1's rate-value `z`
161+
## Note: e0.target will be clamped to input data `x`
162+
163+
### Top-down predictions
164+
z3.zF >> W3.inputs ## pass phi(z3) down W3
165+
W3.outputs >> e2.mu ## prediction `mu` for (layer 2) z2's `z`
166+
z2.zF >> W2.inputs ## pass phi(z2) down W2
167+
W2.outputs >> e1.mu ## prediction `mu` for (layer 1) z1's `z`
168+
z1.zF >> W1.inputs ## pass phi(z1) down W1
169+
W1.outputs >> e0.mu ## prediction `mu` for (input layer) z0=x
170+
171+
### Top-down prediction errors
172+
e1.dtarget >> z1.j_td
173+
e2.dtarget >> z2.j_td
181174
```
182175

176+
The following code-block will set up the error-feedback, bottom-up message-passing pathway:
183177

184178
```python
185-
######### Forward propagation (Bottom-up) #########
186-
## feedforward the errors via synapses
187-
E3.inputs << e2.dmu
188-
E2.inputs << e1.dmu
189-
E1.inputs << e0.dmu
190-
191-
## Bottom-up modulated errors
192-
z3.j << E3.outputs
193-
z2.j << E2.outputs
194-
z1.j << E1.outputs
179+
######### Forward propagation (Bottom-up) #########
180+
## feedforward the errors via synapses
181+
e2.dmu >> E3.inputs
182+
e1.dmu >> E2.inputs
183+
e0.dmu >> E1.inputs
184+
185+
## Bottom-up modulated errors
186+
E3.outputs >> z3.j
187+
E2.outputs >> z2.j
188+
E1.outputs >> z1.j
195189
```
196190

191+
Finally, to enable learning, we will need to set up simple 2-term/factor Hebbian rules like so:
197192

198193
```python
199-
######## Hebbian learning #########
200-
## Pre Synaptic Activation
201-
W3.pre << z3.zF
202-
W2.pre << z2.zF
203-
W1.pre << z1.zF
204-
205-
## Post-synaptic residual error
206-
W3.post << e2.dmu
207-
W2.post << e1.dmu
208-
W1.post << e0.dmu
194+
########### Hebbian learning ############
195+
### Set up terms for 2-term Hebbian rules
196+
## Pre-synaptic activation (terms)
197+
z3.zF >> W3.pre
198+
z2.zF >> W2.pre
199+
z1.zf >> W1.pre
200+
201+
## Post-synaptic residual error (terms)
202+
e2.dmu >> W3.post
203+
e1.dmu >> W2.post
204+
e0.dmu >> W1.post
209205
```
210206

211-
212-
213-
214207
<br>
215208
<br>
216209
<!-- ----------------------------------------------------------------------------------------------------- -->
217210

218-
#### Specifying the Process Dynamics:
211+
#### Specifying the HPC Model's Process Dynamics:
219212

220213

221214
```python
222-
######### Process #########
215+
######### Process #########
223216

224-
########### reset/set all components to their resting values / initial conditions
225-
circuit.reset()
226-
227-
circuit.clamp_input(obs) ## clamp the signal to the lowest layer activation
228-
z0.z.set(obs) ## or directly put obs in e0.target.set(obs)
217+
########### reset/set all components to their resting values / initial conditions
218+
circuit.reset()
229219

230-
########### pin/tie feedback synapses to transpose of forward ones
231-
E1.weights.set(jnp.transpose(W1.weights.value))
232-
E2.weights.set(jnp.transpose(W2.weights.value))
233-
E3.weights.set(jnp.transpose(W3.weights.value))
220+
circuit.clamp_input(obs) ## clamp the signal to the lowest layer activation
221+
z0.z.set(obs) ## or directly put obs in e0.target.set(obs)
234222

235-
circuit.process(jnp.array([[dt * i, dt] for i in range(T)])) ## Perform several E-steps
223+
########### pin/tie feedback synapses to transpose of forward ones
224+
E1.weights.set(jnp.transpose(W1.weights.value))
225+
E2.weights.set(jnp.transpose(W2.weights.value))
226+
E3.weights.set(jnp.transpose(W3.weights.value))
236227

237-
circuit.evolve(t=T, dt=1.) ## Perform M-step (scheduled synaptic updates)
228+
circuit.process(jnp.array([[dt * i, dt] for i in range(T)])) ## Perform several E-steps
229+
circuit.evolve(t=T, dt=1.) ## Perform M-step (scheduled synaptic updates)
238230

239-
obs_mu = e0.mu.value ## get reconstructed signal
240-
L0 = e0.L.value ## calculate reconstruction loss
231+
obs_mu = e0.mu.value ## get reconstructed signal
232+
L0 = e0.L.value ## calculate reconstruction loss
241233
```
242234

243-
244-
245-
246235
<br>
247236
<br>
248237
<br>
@@ -274,10 +263,6 @@ similar filters or receptive fields as in convolutional neural networks (CNNs).
274263
```
275264

276265

277-
278-
279-
280-
281266
<!-- -------------------------------------------------------------------------------------
282267
### Train PC model for reconstructing the full image
283268

0 commit comments

Comments
 (0)