Skip to content

Commit ffe8ddb

Browse files
committed
add how-to-guide for hyper param optim with optuna.
1 parent 649f5d3 commit ffe8ddb

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# How to tune hyperparameters with Optuna"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"This guide shows a minimal [`optuna`](https://optuna.org/) loop for hyperparameter tuning in `sbi`. It uses a toy simulator, `NPE`, an embedding network, and the `posterior_nn` helper. We tune just two hyperparameters: the embedding dimension and the number of flow transforms in an `nsf` density estimator."
15+
]
16+
},
17+
{
18+
"cell_type": "markdown",
19+
"metadata": {},
20+
"source": [
21+
"Optuna is not a dependency of `sbi`, you need to install it yourself in your\n",
22+
"environment. \n",
23+
"\n",
24+
"Optuna is a lightweight hyperparameter optimization library. You define an objective\n",
25+
"function that trains a model (e.g., NPE) and returns a validation metric, and Optuna runs multiple\n",
26+
"trials to explore the search space and track the best configuration. As validation\n",
27+
"metric, we recommend using the negative log probability of a held-out validation set\n",
28+
"`(theta, x)` under the current posterior estimate (see Lueckmann et al. 2021 for\n",
29+
"details). "
30+
]
31+
},
32+
{
33+
"cell_type": "markdown",
34+
"metadata": {},
35+
"source": [
36+
"## Setup a tiny simulation task\n",
37+
"\n",
38+
"```python\n",
39+
"import optuna\n",
40+
"import torch\n",
41+
"from sbi.inference import NPE\n",
42+
"from sbi.neural_nets import posterior_nn\n",
43+
"from sbi.neural_nets.embedding_nets import FCEmbedding\n",
44+
"from sbi.utils import BoxUniform\n",
45+
"\n",
46+
"torch.manual_seed(0)\n",
47+
"\n",
48+
"def simulator(theta):\n",
49+
" return theta + 0.1 * torch.randn_like(theta)\n",
50+
"\n",
51+
"prior = BoxUniform(low=-2 * torch.ones(2), high=2 * torch.ones(2))\n",
52+
"\n",
53+
"theta = prior.sample((6000,))\n",
54+
"x = simulator(theta)\n",
55+
"# Use a separate validation data set for optuna\n",
56+
"theta_train, x_train = theta[:5000], x[:5000]\n",
57+
"theta_val, x_val = theta[5000:], x[5000:]\n",
58+
"```"
59+
]
60+
},
61+
{
62+
"cell_type": "markdown",
63+
"metadata": {},
64+
"source": [
65+
"## Define the Optuna objective\n",
66+
"\n",
67+
"```python\n",
68+
"def objective(trial):\n",
69+
" embedding_dim = trial.suggest_categorical(\"embedding_dim\", [16, 32, 64])\n",
70+
" num_transforms = trial.suggest_int(\"num_transforms\", 2, 6)\n",
71+
"\n",
72+
" embedding_net = FCEmbedding(input_dim=x_train.shape[1], output_dim=embedding_dim)\n",
73+
" density_estimator = posterior_nn(\n",
74+
" model=\"nsf\",\n",
75+
" embedding_net=embedding_net,\n",
76+
" num_transforms=num_transforms,\n",
77+
" )\n",
78+
"\n",
79+
" inference = NPE(prior=prior, density_estimator=density_estimator)\n",
80+
" inference.append_simulations(theta_train, x_train)\n",
81+
" estimator = inference.train(\n",
82+
" max_num_epochs=50,\n",
83+
" training_batch_size=128,\n",
84+
" show_train_summary=False,\n",
85+
" )\n",
86+
" posterior = inference.build_posterior(estimator)\n",
87+
"\n",
88+
" with torch.no_grad():\n",
89+
" nll = -posterior.log_prob_batched(\n",
90+
" theta_val.unsqueeze(0), x=x_val\n",
91+
" ).mean().item()\n",
92+
" return nll\n",
93+
"```"
94+
]
95+
},
96+
{
97+
"cell_type": "markdown",
98+
"metadata": {},
99+
"source": [
100+
"## Run the study and retrain\n",
101+
"\n",
102+
"```python\n",
103+
"study = optuna.create_study(direction=\"minimize\")\n",
104+
"# This will run the above NPE training up to 25 times\n",
105+
"study.optimize(objective, n_trials=25)\n",
106+
"\n",
107+
"best_params = study.best_params\n",
108+
"embedding_net = FCEmbedding(\n",
109+
" input_dim=x_train.shape[1],\n",
110+
" output_dim=best_params[\"embedding_dim\"],\n",
111+
")\n",
112+
"density_estimator = posterior_nn(\n",
113+
" model=\"nsf\",\n",
114+
" embedding_net=embedding_net,\n",
115+
" num_transforms=best_params[\"num_transforms\"],\n",
116+
")\n",
117+
"\n",
118+
"inference = NPE(prior=prior, density_estimator=density_estimator)\n",
119+
"inference.append_simulations(theta, x)\n",
120+
"final_estimator = inference.train(training_batch_size=128)\n",
121+
"posterior = inference.build_posterior(final_estimator)\n",
122+
"```"
123+
]
124+
},
125+
{
126+
"cell_type": "markdown",
127+
"metadata": {},
128+
"source": [
129+
"## Notes\n",
130+
"\n",
131+
"- The toy simulator keeps the example short. Replace it with your simulator and prior.\n",
132+
"- You can expand the search space with additional `posterior_nn` arguments (e.g., `hidden_features`)."
133+
]
134+
}
135+
],
136+
"metadata": {
137+
"kernelspec": {
138+
"display_name": "Python 3 (ipykernel)",
139+
"language": "python",
140+
"name": "python3"
141+
},
142+
"language_info": {
143+
"codemirror_mode": {
144+
"name": "ipython",
145+
"version": 3
146+
},
147+
"file_extension": ".py",
148+
"mimetype": "text/x-python",
149+
"name": "python",
150+
"nbconvert_exporter": "python",
151+
"pygments_lexer": "ipython3",
152+
"version": "3.12.4"
153+
}
154+
},
155+
"nbformat": 4,
156+
"nbformat_minor": 5
157+
}

0 commit comments

Comments
 (0)