Skip to content

Commit 6448e4c

Browse files
Create a Forward map wrapper to more easily test pipelines without using an emulator (#402)
* moved MLTs into subdirectory * added ForwardMapWrapper, made compatible with MCMC and encoder utilities * featured in sinusoid example * remove anonymous function and have G in space so sample.jl doesnt crash * add FM into sinusoid * format * Update src/Emulator.jl Co-authored-by: ArneBouillon <45404227+ArneBouillon@users.noreply.github.com> * Update docs/src/examples/sinusoid_example.md Co-authored-by: ArneBouillon <45404227+ArneBouillon@users.noreply.github.com> * add a warning message that the input encoder is not used * some starting tests * remove extra end * scope "sample" * improve const * update test_throws * qualify sample and predict * coverage * format --------- Co-authored-by: ArneBouillon <45404227+ArneBouillon@users.noreply.github.com>
1 parent 79dd511 commit 6448e4c

11 files changed

Lines changed: 580 additions & 53 deletions

File tree

33 KB
Loading

docs/src/examples/sinusoid_example.md

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ y_{obs} = G(\theta) + \gamma, \qquad \gamma \sim \mathcal{N}(0, \Gamma)
3131
where $\Gamma$ is the observational covariance matrix. We will assume the noise to be independent for each observable, giving us a diagonal covariance matrix.
3232

3333

34-
3534
# Walkthrough of code
3635

3736
You can find the full scripts to reproduce this tutorial in `examples/Sinusoid/`. The code is split into four sections:
@@ -253,7 +252,11 @@ encoder_schedule = [(decorrelate_sample_cov(), "in"), (decorrelate_structure_mat
253252
encoder_kwargs = (; obs_noise_cov = Γ,)
254253

255254
# Build emulator with data
256-
emulator_gp = Emulator(gauss_proc, input_output_pairs; encoder_schedule = encoder_schedule, encoder_kwargs = encoder_kwargs)
255+
emulator_gp = Emulator(gauss_proc,
256+
input_output_pairs;
257+
encoder_schedule = encoder_schedule,
258+
encoder_kwargs = encoder_kwargs,
259+
)
257260
optimize_hyperparameters!(emulator_gp)
258261
```
259262
For this simple example, we already know the observational noise `Γ=0.2*I`, so we set `noise_learn = false`.
@@ -291,8 +294,12 @@ random_features = VectorRandomFeatureInterface(
291294
optimizer_options = optimizer_options,
292295
)
293296

294-
emulator_random_features =
295-
Emulator(random_features, input_output_pairs; encoder_schedule = deepcopy(encoder_schedule), encoder_kwargs=deepcopy(encoder_kwargs))
297+
emulator_random_features = Emulator(
298+
random_features,
299+
input_output_pairs;
300+
encoder_schedule = deepcopy(encoder_schedule),
301+
encoder_kwargs=deepcopy(encoder_kwargs),
302+
)
296303
optimize_hyperparameters!(emulator_random_features)
297304
```
298305
### Emulator Validation
@@ -342,7 +349,22 @@ and the random features absolute errors are here:
342349
Both these error maps look similar. Importantly, we want the emulator to show the low errors in the region around the true parameter values near $\theta=(3, 6)$ (i.e, near where the EKI points converge, shown by the scatter points in
343350
the previous plot). This the region that we will be sampling in the next step.
344351
We see low errors near here for both outputs and for both emulators. Now we have validated these emulators,
345-
we will proceed the last step of CES: Sampling of the posterior distribution.
352+
we will proceed the last step of CES: Sampling of the posterior distribution.
353+
354+
## An aside, Forward map wrapper
355+
356+
What if we run a simple case, and wish to see the emulator performance against the actual forward map sampling in the pipeline? We allow for an exact substitution by using our `ForwardMapWrapper`
357+
358+
```julia
359+
emulator_fm = forward_map_wrapper(
360+
G,
361+
prior,
362+
input_output_pairs;
363+
encoder_schedule = deepcopy(encoder_schedule),
364+
encoder_kwargs = deepcopy(encoder_kwargs),
365+
)
366+
```
367+
In this way we can test against the posterior found with the true model, which in this setting is cheap enough to run.
346368

347369
## Sample
348370

@@ -441,14 +463,26 @@ process and we find similar results:
441463
| parameters | mean | std |
442464
| :---------------- | :----: | :--: |
443465
| amplitude | 2.99 | 0.21 |
444-
| vert_shift | 7.06 | 4.89 |
466+
| vert_shift | 7.06 | 0.48 |
445467

446468
![RF_2d_posterior](../assets/sinusoid_MCMC_hist_RF.png)
447469

470+
471+
### Sample with the true forward map
472+
473+
| parameters | mean | std |
474+
| :---------------- | :----: | :--: |
475+
| amplitude | 3.00 | 0.22 |
476+
| vert_shift | 7.08 | 0.44 |
477+
478+
![FM_2d_posterior](../assets/sinusoid_MCMC_hist_FM.png)
479+
480+
448481
It is reassuring to see that our uncertainty quantification methods are robust to the different emulator
449482
choices here. This is because our particular GP and RF emulators showed similar accuracy during validation.
450483
However, this result is highly sensitive to the choices of GP kernel and RF kernel structure. If you find very
451484
different posterior distributions for different emulators, it is likely that the kernel choices need be refined.
452485
The kernel choices must be flexible enough to accurately capture the relationships between the inputs and outputs.
453486
We recommend trying a variety of different emulator configurations and carefully considering emulator validation
454487
on samples that the emulator has not been trained on.
488+

examples/Sinusoid/emulate.jl

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ gppackage = Emulators.GPJL()
7777
gauss_proc = Emulators.GaussianProcess(gppackage, noise_learn = false)
7878

7979

80+
8081
# Build emulator with data
8182
emulator_gp = Emulator(
8283
gauss_proc,
@@ -120,13 +121,24 @@ random_features = VectorRandomFeatureInterface(
120121
optimizer_options = optimizer_options,
121122
)
122123

123-
emulator_random_features = Emulator(
124+
emulator_rf = Emulator(
124125
random_features,
125126
input_output_pairs;
126127
encoder_schedule = deepcopy(encoder_schedule),
127128
encoder_kwargs = deepcopy(encoder_kwargs),
128129
)
129-
optimize_hyperparameters!(emulator_random_features)
130+
optimize_hyperparameters!(emulator_rf)
131+
132+
133+
# We also create a forward map wrapper instead of using an emulator.
134+
# In this way we can test our encoded sampling pipeline with the exact model.
135+
emulator_fm = forward_map_wrapper(
136+
G,
137+
prior,
138+
input_output_pairs;
139+
encoder_schedule = deepcopy(encoder_schedule),
140+
encoder_kwargs = deepcopy(encoder_kwargs),
141+
)
130142

131143

132144
## Emulator Validation
@@ -155,20 +167,28 @@ g_true_grid = reshape(g_true, (output_dim, N_grid, N_grid))
155167

156168
# Next, predict with emulators. We first need to transform to the unconstrained space.
157169
input_grid_unconstrained = Emulators.transform_constrained_to_unconstrained(prior, input_grid)
170+
158171
gp_mean, gp_cov = Emulators.predict(emulator_gp, input_grid_unconstrained, transform_to_real = true)
159-
rf_mean, rf_cov = Emulators.predict(emulator_random_features, input_grid_unconstrained, transform_to_real = true)
172+
rf_mean, rf_cov = Emulators.predict(emulator_rf, input_grid_unconstrained, transform_to_real = true)
173+
fm_mean, fm_cov = Emulators.predict(emulator_fm, input_grid_unconstrained; transform_to_real = true)
174+
160175

161176
# Reshape into (output_dim x 50 x 50) grid
162177
output_dim = 2
163178
gp_grid = reshape(gp_mean, (output_dim, N_grid, N_grid))
164179
rf_grid = reshape(rf_mean, (output_dim, N_grid, N_grid))
180+
fm_grid = reshape(fm_mean, (output_dim, N_grid, N_grid))
165181

166182
# Convert cov matrix into std and reshape
167183
gp_std = [sqrt.(diag(gp_cov[j])) for j in 1:length(gp_cov)]
168184
gp_std_grid = reshape(permutedims(reduce(vcat, [x' for x in gp_std]), (2, 1)), (output_dim, N_grid, N_grid))
169185
rf_std = [sqrt.(diag(rf_cov[j])) for j in 1:length(rf_cov)]
170186
rf_std_grid = reshape(permutedims(reduce(vcat, [x' for x in rf_std]), (2, 1)), (output_dim, N_grid, N_grid))
171187

188+
fm_std = [sqrt.(diag(fm_cov[j])) for j in 1:length(fm_cov)]
189+
fm_std_grid = reshape(permutedims(reduce(vcat, [x' for x in fm_std]), (2, 1)), (output_dim, N_grid, N_grid))
190+
191+
172192
## Plot
173193
# First, we will plot the ground truth. We have 2 parameters and 2 outputs, so we will create a contour plot
174194
# for each output to show how they vary against the two inputs.
@@ -277,6 +297,43 @@ p = plot(
277297
)
278298
savefig(p, joinpath(data_save_directory, "sinusoid_RF_emulator_contours.png"))
279299

300+
# Plot FM emulator contours
301+
p1 = contour(
302+
amp_range,
303+
vshift_range,
304+
fm_grid[1, :, :];
305+
fill = true,
306+
clims = range_clims,
307+
xlabel = "Amplitude",
308+
ylabel = "Vertical Shift",
309+
title = "FM Sinusoid Range",
310+
)
311+
plot!(inputs[1, :], inputs[2, :]; seriestype = :scatter, zcolor = outputs[1, :], label = :false)
312+
p2 = contour(
313+
amp_range,
314+
vshift_range,
315+
fm_grid[2, :, :];
316+
fill = true,
317+
clims = mean_clims,
318+
xlabel = "Amplitude",
319+
ylabel = "Vertical Shift",
320+
title = "FM Sinusoid Mean",
321+
)
322+
plot!(inputs[1, :], inputs[2, :]; seriestype = :scatter, zcolor = outputs[2, :], label = :false)
323+
p = plot(
324+
p1,
325+
p2,
326+
right_margin = 3mm,
327+
bottom_margin = 3mm,
328+
size = (600, 300),
329+
layout = (1, 2),
330+
guidefontsize = 12,
331+
tickfontsize = 10,
332+
legendfontsize = 10,
333+
)
334+
savefig(p, joinpath(data_save_directory, "sinusoid_FM_emulator_contours.png"))
335+
336+
280337
# Both the GP and RF emulator give similar results to the ground truth G(θ), indicating they are correctly
281338
# learning the relationships between the parameters and the outputs. We also see the contours agree with the
282339
# colors of the training data points.
@@ -347,6 +404,34 @@ p2 = contour(
347404
)
348405
p = plot(p1, p2, size = (600, 300), layout = (1, 2), guidefontsize = 12, tickfontsize = 10, legendfontsize = 10)
349406
savefig(p, joinpath(data_save_directory, "sinusoid_RF_emulator_std_contours.png"))
407+
408+
# Plot FM std estimates
409+
p1 = contour(
410+
amp_range,
411+
vshift_range,
412+
fm_std_grid[1, :, :];
413+
c = :cividis,
414+
fill = true,
415+
clims = range_std_clims,
416+
xlabel = "Amplitude",
417+
ylabel = "Vertical Shift",
418+
title = "FM 1σ in Sinusoid Range",
419+
)
420+
p2 = contour(
421+
amp_range,
422+
vshift_range,
423+
fm_std_grid[2, :, :];
424+
c = :cividis,
425+
fill = true,
426+
clims = mean_std_clims,
427+
xlabel = "Amplitude",
428+
ylabel = "Vertical Shift",
429+
title = "FM 1σ in Sinusoid Mean",
430+
)
431+
p = plot(p1, p2, size = (600, 300), layout = (1, 2), guidefontsize = 12, tickfontsize = 10, legendfontsize = 10)
432+
savefig(p, joinpath(data_save_directory, "sinusoid_FM_emulator_std_contours.png"))
433+
434+
350435
# The GP and RF uncertainty predictions are similar and show lower uncertainties around the region of interest
351436
# where we have more training points.
352437

@@ -380,6 +465,7 @@ p2 = contour(
380465
p = plot(p1, p2, size = (600, 300), layout = (1, 2), guidefontsize = 12, tickfontsize = 10, legendfontsize = 10)
381466
savefig(p, joinpath(data_save_directory, "sinusoid_GP_errors_contours.png"))
382467

468+
# Now RF
383469
rf_diff_grid = abs.(rf_grid - g_true_grid)
384470
p1 = contour(
385471
amp_range,
@@ -406,6 +492,33 @@ p2 = contour(
406492
p = plot(p1, p2, size = (600, 300), layout = (1, 2), guidefontsize = 12, tickfontsize = 10, legendfontsize = 10)
407493
savefig(p, joinpath(data_save_directory, "sinusoid_RF_errors_contours.png"))
408494

495+
# Now FM
496+
fm_diff_grid = abs.(fm_grid - g_true_grid)
497+
p1 = contour(
498+
amp_range,
499+
vshift_range,
500+
fm_diff_grid[1, :, :];
501+
c = :cividis,
502+
fill = true,
503+
clims = range_diff_clims,
504+
xlabel = "Amplitude",
505+
ylabel = "Vertical Shift",
506+
title = "FM error in Sinusoid Range",
507+
)
508+
p2 = contour(
509+
amp_range,
510+
vshift_range,
511+
fm_diff_grid[2, :, :];
512+
c = :cividis,
513+
fill = true,
514+
clims = mean_diff_clims,
515+
xlabel = "Amplitude",
516+
ylabel = "Vertical Shift",
517+
title = "FM error in Sinusoid Mean",
518+
)
519+
p = plot(p1, p2, size = (600, 300), layout = (1, 2), guidefontsize = 12, tickfontsize = 10, legendfontsize = 10)
520+
savefig(p, joinpath(data_save_directory, "sinusoid_FM_errors_contours.png"))
521+
409522
# Here, we want the emulator to show the low errors in the region around the true parameter values near θ = (3, 6),
410523
# as this is the region that we will be sampling in the next step. This appears to the be case for both
411524
# outputs and both emulators.
@@ -415,12 +528,18 @@ println(mean(gp_diff_grid, dims = (2, 3)))
415528
println(mean(rf_diff_grid, dims = (2, 3)))
416529

417530

531+
532+
533+
534+
418535
save(
419536
joinpath(data_save_directory, "emulators.jld2"),
420537
"emulator_gp",
421538
emulator_gp,
422-
"emulator_random_features",
423-
emulator_random_features,
539+
"emulator_rf",
540+
emulator_rf,
541+
"emulator_fm",
542+
emulator_fm,
424543
"prior",
425544
prior,
426545
"rng",

0 commit comments

Comments
 (0)