Skip to content

Commit c47564d

Browse files
Example Notebook explaining changes from 1.0 to 2.0 (#369)
* added examples to standardize transformation documentation * ran linter * improve standardize doc further * rerun linter * minor cleaning * added 1.0 to 2.0 example notebook on clean history * minor cleaning of bayesflow 1.0 to 2.0 notebook --------- Co-authored-by: Paul-Christian Bürkner <[email protected]>
1 parent 4251bbd commit c47564d

File tree

1 file changed

+372
-0
lines changed

1 file changed

+372
-0
lines changed
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Moving from Bayesflow 1.0 to 2.0\n",
8+
"\n",
9+
"_Author: Leona Odole_"
10+
]
11+
},
12+
{
13+
"cell_type": "markdown",
14+
"metadata": {},
15+
"source": [
16+
"Older users of bayesflow will notice that with the update to version 2.0 many things have changed. This short guide aims to clarify those changes. Users familiar with the previous Quickstart guide will notice that it follows a similar structure, but assumes that users are already familiar with bayesflow. So we omit many of the the mathematical explaination in favor of demonstrating the differences in workflow. For a more detailed explaination of any of the bayesflow framework, users should read, for example, the linear regresion example notebook. \n",
17+
"\n",
18+
"Additionally to avoid confusion, similarly named objects from _bayesflow1.0_ will have 1.0 after their name, whereas those from _bayesflow2.0_ will not. Finally, a short table with a summary of the function call changes is provided at the end of the guide. "
19+
]
20+
},
21+
{
22+
"cell_type": "markdown",
23+
"metadata": {},
24+
"source": [
25+
"## Keras Framework\n",
26+
"\n",
27+
"Bayesflow 2.0 looks quite different from 1.0 because the backend has been entirely reformatted in line with `keras` standards. Previously bayesflow was only compatible with `TensorFlow`, but now users can choose their prefered machine learning framework among `TensorFlow`, `JAX` or `Pytorch`."
28+
]
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": null,
33+
"metadata": {},
34+
"outputs": [],
35+
"source": [
36+
"import numpy as np\n",
37+
"\n",
38+
"# ensure the backend is set\n",
39+
"import os\n",
40+
"if \"KERAS_BACKEND\" not in os.environ:\n",
41+
" # set this to \"torch\", \"tensorflow\", or \"jax\"\n",
42+
" os.environ[\"KERAS_BACKEND\"] = \"tensorflow\"\n",
43+
"\n",
44+
"import keras\n",
45+
"import bayesflow as bf\n",
46+
"import pandas as pd"
47+
]
48+
},
49+
{
50+
"cell_type": "markdown",
51+
"metadata": {},
52+
"source": [
53+
"This version of bayeflow also relies much more heavily on dictionaries since parameters are now named by convention. Many objects now expect a dictionary, so parameters and data are returned as a dictionaries. "
54+
]
55+
},
56+
{
57+
"cell_type": "markdown",
58+
"metadata": {},
59+
"source": [
60+
"## Example Workflow \n",
61+
"\n",
62+
"### 1. Priors and Likelihood Model\n",
63+
"\n",
64+
"Any Bayesflow workflow begins with simulated data which is specified with a prior and a corresponding likelihood function. While these two core components are still present, their use and naming conventions within the workflow have changed. \n",
65+
"\n",
66+
"Previously users would define a prior function, which would then be used by a `Prior1.0` object to sample prior values. The likelihood would then also be specified via function and used by a `Simulator1.0` wrapper to produce observations for a given prior. These were then combined in the `GenerativeModel1.0`. \n",
67+
"\n",
68+
"In 2.0 we no longer make use of the `Prior1.0`, `Simulator1.0` or `GenerativeModel1.0` objects. Instead the roll of the `GenerativeModel1.0` has been renamed to `simulator` which can be invoked as a single function that glues the prior and likelihood functions together to generate samples of both the prior and observations. "
69+
]
70+
},
71+
{
72+
"cell_type": "code",
73+
"execution_count": null,
74+
"metadata": {},
75+
"outputs": [],
76+
"source": [
77+
"def theta_prior():\n",
78+
" theta = np.random.normal(size=4)\n",
79+
" # previously: \n",
80+
" # return theta \n",
81+
" return dict(theta=theta) # notice we return a dictionary\n",
82+
" \n",
83+
"\n",
84+
"def likelihood_model(theta, n_obs):\n",
85+
" x = np.random.normal(loc=theta, size=(n_obs, theta.shape[0]))\n",
86+
" return dict(x=x)"
87+
]
88+
},
89+
{
90+
"cell_type": "markdown",
91+
"metadata": {},
92+
"source": [
93+
"Previously the prior and likelihood were defined as"
94+
]
95+
},
96+
{
97+
"cell_type": "code",
98+
"execution_count": null,
99+
"metadata": {},
100+
"outputs": [],
101+
"source": [
102+
"# Do Not Run\n",
103+
"prior_1 = bf.simulation.Prior(prior_fun=theta_prior)\n",
104+
"simulator_1 = bf.simulation.Simulator(simulator_fun=likelihood_model)\n",
105+
"model_1 = bf.simulation.GenerativeModel(prior=prior_1, simulator=simulator_1)"
106+
]
107+
},
108+
{
109+
"cell_type": "markdown",
110+
"metadata": {},
111+
"source": [
112+
"Whereas the new framework directly uses the likelihood and prior functions directly in the simulator. We also a define a meta function which allows us, for example, to dynamically set the number of observations per simulated dataset. "
113+
]
114+
},
115+
{
116+
"cell_type": "code",
117+
"execution_count": null,
118+
"metadata": {},
119+
"outputs": [],
120+
"source": [
121+
"def meta():\n",
122+
" return dict(n_obs=1)\n",
123+
"\n",
124+
"simulator = bf.make_simulator([theta_prior, likelihood_model], meta_fn=meta)"
125+
]
126+
},
127+
{
128+
"cell_type": "markdown",
129+
"metadata": {},
130+
"source": [
131+
"We can then generate batches of training samples as follows."
132+
]
133+
},
134+
{
135+
"cell_type": "code",
136+
"execution_count": null,
137+
"metadata": {},
138+
"outputs": [],
139+
"source": [
140+
"sim_draws = simulator.sample(500)\n",
141+
"print(sim_draws[\"x\"].shape)\n",
142+
"print(sim_draws[\"theta\"].shape)"
143+
]
144+
},
145+
{
146+
"cell_type": "markdown",
147+
"metadata": {},
148+
"source": [
149+
"### 2. Adapter and Data Configuration\n",
150+
"\n",
151+
"In _bayesflow2.0_ we now need to specify the data configuration. For example we should specify which variables are `summary_variables` meaning observations that will be summarized in the summary network, the `inference_variables` meaning the prior draws on which we're interested in training the posterior network and the `inference_conditions` which specify our number of observations. Previously these things were inferred from the type of network used, but now they should be defined explictly with the `adapter`. The new approach is much more explicit and extensible. It also makes it easier to change individual settings, while keeping other settings at their defaults."
152+
]
153+
},
154+
{
155+
"cell_type": "code",
156+
"execution_count": null,
157+
"metadata": {},
158+
"outputs": [],
159+
"source": [
160+
"adapter = (\n",
161+
" bf.adapters.Adapter()\n",
162+
" .to_array()\n",
163+
" .broadcast(\"n_obs\")\n",
164+
" .convert_dtype(from_dtype=\"float64\", to_dtype=\"float32\")\n",
165+
" .standardize(exclude=[\"n_obs\"])\n",
166+
" .rename(\"x\", \"summary_variables\")\n",
167+
" .rename(\"theta\", \"inference_variables\")\n",
168+
" .rename(\"n_obs\", \"inference_conditions\")\n",
169+
")"
170+
]
171+
},
172+
{
173+
"cell_type": "markdown",
174+
"metadata": {},
175+
"source": [
176+
"In addition the adapter now has built in functions to transform data such as standardization or one-hot encoding. For a full list of the adapter transforms, please see the documentation. "
177+
]
178+
},
179+
{
180+
"cell_type": "markdown",
181+
"metadata": {},
182+
"source": [
183+
"### 3. Summary Network and Inference Network"
184+
]
185+
},
186+
{
187+
"cell_type": "markdown",
188+
"metadata": {},
189+
"source": [
190+
"As in _bayesflow1.0_ we still use a summary network, which is still a Deepset model. Nothing has changed in this step of the workflow. "
191+
]
192+
},
193+
{
194+
"cell_type": "code",
195+
"execution_count": null,
196+
"metadata": {},
197+
"outputs": [],
198+
"source": [
199+
"summary_net = bf.networks.DeepSet(depth=2, summary_dim=10)"
200+
]
201+
},
202+
{
203+
"cell_type": "markdown",
204+
"metadata": {},
205+
"source": [
206+
"For the inference network there are now several implemented architectures for users to choose from. They are `FlowMatching`, `ConsistencyModel`, `ContinuousConsistencyModel` and `CouplingFlow`. For this demonstration we use `FlowMatching`, but for further explaination of the different models please see the other examples and documentation. "
207+
]
208+
},
209+
{
210+
"cell_type": "code",
211+
"execution_count": null,
212+
"metadata": {},
213+
"outputs": [],
214+
"source": [
215+
"inference_net = bf.networks.FlowMatching()"
216+
]
217+
},
218+
{
219+
"cell_type": "markdown",
220+
"metadata": {},
221+
"source": [
222+
"### 4. Approximator (Amortizer Posterior)"
223+
]
224+
},
225+
{
226+
"cell_type": "markdown",
227+
"metadata": {},
228+
"source": [
229+
"Previously the actual training and amortization was done in two steps with two different objects the `Amortizer1.0` and `Trainer1.0`. First, users would create an amortizer containing the summary and inference networks."
230+
]
231+
},
232+
{
233+
"cell_type": "code",
234+
"execution_count": null,
235+
"metadata": {},
236+
"outputs": [],
237+
"source": [
238+
"### Do Not Run \n",
239+
"\n",
240+
"# Renamed to Approximator\n",
241+
"amortizer = bf.amortizers.AmortizedPosterior(inference_net, summary_net)\n",
242+
"\n",
243+
"# Defunct\n",
244+
"trainer = bf.trainers.Trainer(amortizer=amortizer, generative_model=gen_model)"
245+
]
246+
},
247+
{
248+
"cell_type": "markdown",
249+
"metadata": {},
250+
"source": [
251+
" This has been renamed to an `Approximator` and takes the summary network, inference network and the data adapter as arguments. "
252+
]
253+
},
254+
{
255+
"cell_type": "code",
256+
"execution_count": null,
257+
"metadata": {},
258+
"outputs": [],
259+
"source": [
260+
"approximator = bf.approximators.ContinuousApproximator(\n",
261+
" summary_network=summary_net,\n",
262+
" inference_network=inference_net,\n",
263+
" adapter=adapter\n",
264+
")"
265+
]
266+
},
267+
{
268+
"cell_type": "markdown",
269+
"metadata": {},
270+
"source": [
271+
"Whereas previously a `Trainer1.0` object for training, now users call fit on the `approximator` directly. For additional flexibility in training the `approximator` also has two additional arguments the `learning_rate` and `optimizer`. The optimizer can be any keras optimizer."
272+
]
273+
},
274+
{
275+
"cell_type": "code",
276+
"execution_count": null,
277+
"metadata": {},
278+
"outputs": [],
279+
"source": [
280+
"learning_rate = 1e-4\n",
281+
"optimizer = keras.optimizers.AdamW(learning_rate=learning_rate, clipnorm=1.0)"
282+
]
283+
},
284+
{
285+
"cell_type": "markdown",
286+
"metadata": {},
287+
"source": [
288+
"Users must then compile the `approximator` with the `optimizer` to make everything ready for training."
289+
]
290+
},
291+
{
292+
"cell_type": "code",
293+
"execution_count": null,
294+
"metadata": {},
295+
"outputs": [],
296+
"source": [
297+
"approximator.compile(optimizer=optimizer)"
298+
]
299+
},
300+
{
301+
"cell_type": "markdown",
302+
"metadata": {},
303+
"source": [
304+
"\n",
305+
"To train the network, and save output users now need only to call fit on the `approximator`. "
306+
]
307+
},
308+
{
309+
"cell_type": "code",
310+
"execution_count": null,
311+
"metadata": {},
312+
"outputs": [],
313+
"source": [
314+
"history = approximator.fit(\n",
315+
" epochs=50,\n",
316+
" num_batches=200,\n",
317+
" batch_size=64,\n",
318+
" simulator=simulator\n",
319+
")"
320+
]
321+
},
322+
{
323+
"cell_type": "markdown",
324+
"metadata": {},
325+
"source": [
326+
"## 5.Diagnostics \n",
327+
"Another change was made in the model diagnostics, much of the functionality remains the same, but the naming convention has changes. For example previously users would plot losses by using \n",
328+
"`bf.diagnostics.plot_losses()`. In *bayesflow2.0*, we instead have all the plotting function grouped together in `bf.diagnostics.plots`. This means, for example, that the loss function is now in `bf.diagnostics.plots.loss()`."
329+
]
330+
},
331+
{
332+
"cell_type": "code",
333+
"execution_count": null,
334+
"metadata": {},
335+
"outputs": [],
336+
"source": [
337+
"f = bf.diagnostics.plots.loss(\n",
338+
" train_losses=history.history['loss']\n",
339+
")"
340+
]
341+
},
342+
{
343+
"cell_type": "markdown",
344+
"metadata": {},
345+
"source": [
346+
"This was done as we have also added diagnostic metrics such as calibration error, posterior contraction, and root mean squared error. These functions can accordingly be found in `bf.diagnostics.metrics`. For more information please see the documentation."
347+
]
348+
},
349+
{
350+
"cell_type": "markdown",
351+
"metadata": {},
352+
"source": [
353+
"# Summary Change Table \n",
354+
"\n",
355+
"| Bayesflow 1.0 | Bayesflow 2.0 useage |\n",
356+
"| :--------| :---------| \n",
357+
"| `Prior`, `Simulator` | Defunct and no longer standalone objects but incorporated into `simulator` | \n",
358+
"|`GenerativeModel` | Defunct with it's functionality having been taken over by `simulations.make_simulator` | \n",
359+
"| `training.configurator` | Functionality taken over by `Adapter` | \n",
360+
"|`Trainer` | Functionality taken over by `fit` method of `Approximator` | \n",
361+
"| `AmortizedPosterior`| Renamed to `Approximator` | "
362+
]
363+
}
364+
],
365+
"metadata": {
366+
"language_info": {
367+
"name": "python"
368+
}
369+
},
370+
"nbformat": 4,
371+
"nbformat_minor": 2
372+
}

0 commit comments

Comments
 (0)