1- # Hierarchical Predictive Coding (Rao & ; Ballard; 1999)
1+ # Hierarchical Predictive Coding for Reconstruction (Rao & ; Ballard; 1999)
22
33In this exhibit, we create, simulate, and visualize the internally acquired receptive fields of the predictive coding
44model 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