Skip to content

Commit b102f1f

Browse files
authored
Bugfix for MCMC with encoding (#378)
* fix additional averaging of likelihood * weighting bug for many-data * clearer error message * format * new tests and all pass * rm accidental feature push * added mv input testing at different input size * remove unnec. prints
1 parent 101d3f6 commit b102f1f

6 files changed

Lines changed: 422 additions & 169 deletions

File tree

src/Emulator.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ function Emulator(
125125

126126
if !isnothing(obs_noise_cov)
127127
if haskey(encoder_kwargs, :obs_noise_cov)
128-
@warn "Keyword argument `obs_noise_cov=` is deprecated and will be ignored in favor of `encoder_kwargs[:obs_noise_cov]`."
128+
@warn "Keyword argument `obs_noise_cov=` is deprecated and will be ignored in favor of `encoder_kwargs=(obs_noise_cov=...)`."
129129
else
130-
@warn "Keyword argument `obs_noise_cov=` is deprecated. Please use `encoder_kwargs[:obs_noise_cov]` instead."
130+
@warn "Keyword argument `obs_noise_cov=` is deprecated. Please use `encoder_kwargs=(obs_noise_cov=...)` instead."
131131
end
132132
end
133133

src/GaussianProcess.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ function build_models!(
167167
end
168168
N_models = size(output_values, 1) #size(transformed_data)[1]
169169

170-
171170
# Use a default kernel unless a kernel was supplied to GaussianProcess
172171
if gp.kernel === nothing
173172
println("Using default squared exponential kernel, learning length scale and variance parameters")
@@ -184,7 +183,6 @@ function build_models!(
184183
println("Using user-defined kernel", kern)
185184
end
186185

187-
188186
if gp.noise_learn
189187
# Add white noise to kernel
190188
white_logstd = log(1.0)
@@ -202,9 +200,10 @@ function build_models!(
202200
else
203201
diag(output_structure_mat)
204202
end
205-
end
206203

207-
logstd_regularization_noise = log.(sqrt.(regularization .* gp.alg_reg_noise))
204+
end
205+
regularization_noise = regularization .* gp.alg_reg_noise
206+
logstd_regularization_noise = log.(sqrt.(regularization_noise))
208207

209208
for i in 1:N_models
210209
logstd_regularization_i = logstd_regularization_noise[i]
@@ -228,7 +227,7 @@ function build_models!(
228227
push!(models, m)
229228

230229
end
231-
append!(gp.regularization, logstd_regularization_noise)
230+
append!(gp.regularization, regularization_noise)
232231

233232
end
234233

@@ -262,6 +261,7 @@ function _predict(
262261
# Predicts columns of inputs: input_dim × N_samples
263262
μ = zeros(M, N_samples)
264263
σ2 = zeros(M, N_samples)
264+
# predict method ::YType will add gp.regularization back in here, ::FType will not
265265
for i in 1:M
266266
μ[i, :], σ2[i, :] = predict_method(gp.models[i], new_inputs)
267267
end
@@ -430,7 +430,6 @@ AbstractGP currently does not (yet) learn hyperparameters internally. The follow
430430
"""))
431431
end
432432

433-
434433
N_models = size(output_values, 1) #size(transformed_data)[1]
435434
# use the output_structure_matrix to scale regularization scale
436435
regularization = if isempty(output_structure_mats)

src/MarkovChainMonteCarlo.jl

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,14 @@ autodiff_hessian(model::AdvancedMH.DensityModel, params, sampler::MH) where {MH
233233
"""
234234
$(DocStringExtensions.TYPEDSIGNATURES)
235235
236-
Defines the internal log-density function over a vector of observation samples using an assumed conditionally indepedent likelihood, that is with a log-likelihood of `ℓ(y,θ) = sum^n_i log( p(y_i|θ) )`.
236+
Defines the internal log-density function over a vector of observation samples using an assumed conditionally indepedent likelihood, that is with a log-likelihood of `ℓ(y,θ) = sum^n_i log( p(y_i|θ) )`.
237+
238+
Inputs:
239+
=======
240+
- θ: Parameters in physical (constrained) coordinates.
241+
- prior: Prior distribution as a ParameterDistribution
242+
- em: Emulator object with predict(.) method, evaluations returned in encoded space
243+
- obs_vec: encoded data vector sample(s)
237244
"""
238245
function emulator_log_density_model(
239246
θ,
@@ -242,21 +249,17 @@ function emulator_log_density_model(
242249
obs_vec::AV,
243250
) where {FT <: AbstractFloat, AV <: AbstractVector}
244251

245-
# θ: model params we evaluate at; in original coords.
246-
# transform_to_real = false means g, g_cov, obs_sample are in decorrelated coords.
252+
#
253+
# transform_to_real = false means g, g_cov, obs_sample are in encoded coords.
247254

248-
# Recall predict() written to return multiple N_samples: expects input to be a
249-
# Matrix with N_samples columns. Returned g is likewise a Matrix, and g_cov is a
250-
# Vector of N_samples covariance matrices. For MH, N_samples is always 1, so we
251-
# have to reshape()/re-cast input/output; simpler to do here than add a
252-
# predict() method.
255+
# predict is written to apply to columns.
256+
# Returned g is a length-1, Vector{Real} or Vector{Vector}, and g_cov is length-1 Vector{Vector} or Vector{Matrix} respectively
253257
g, g_cov = Emulators.predict(em, reshape(θ, :, 1), transform_to_real = false)
254258

255259
if isa(g_cov[1], Real)
256-
257-
return 1.0 / length(obs_vec) * sum([logpdf(MvNormal(obs, g_cov[1] * I), vec(g)) for obs in obs_vec]) + logpdf(prior, θ)
260+
return sum([logpdf(MvNormal(obs, g_cov[1] * I), vec(g)) for obs in obs_vec]) + logpdf(prior, θ)
258261
else
259-
return 1.0 / length(obs_vec) * sum([logpdf(MvNormal(obs, g_cov[1]), vec(g)) for obs in obs_vec]) + logpdf(prior, θ)
262+
return sum([logpdf(MvNormal(obs, g_cov[1]), vec(g)) for obs in obs_vec]) + logpdf(prior, θ)
260263
end
261264

262265
end
@@ -556,7 +559,6 @@ function MCMCWrapper(
556559
# encoding works on columns but mcmc wants vec-of-vec
557560
encoded_obs = [vec(encode_data(em, reshape(obs, :, 1), "out")) for obs in obs_slice]
558561

559-
560562
log_posterior_map = EmulatorPosteriorModel(prior, em, encoded_obs)
561563
mh_proposal_sampler = MetropolisHastingsSampler(mcmc_alg, prior)
562564

src/ScalarRandomFeature.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ function ScalarRandomFeatureInterface(
178178
"localization" => EKP.Localizers.NoLocalization(), # localization / sample error correction for small ensembles
179179
"cov_correction" => "nice", # type of conditioning to improve estimated covariance
180180
"n_cross_val_sets" => 2, # if >1 do cross validation, else if 0 do no data splitting and no training fraction
181-
"overfit" => 1.0, # if >1 this forcibly overfits to the data
181+
"overfit" => 1.0, # if >1 this forcibly overfits to the data
182182
)
183183

184184
if !isnothing(optimizer_options)
@@ -436,12 +436,10 @@ function build_models!(
436436
),
437437
)
438438
end
439-
440439
# scale up the prior so that default priors are always "reasonable"
441440
prior_in_scale = 1.0 ./ std(input_values, dims = 2)
442441
prior_out_scale = std(output_values[i, :])
443442

444-
445443
prior = build_default_prior(input_dim, kernel_structure)
446444

447445
# where prior space has changed we need to rebuild the priors
@@ -453,16 +451,17 @@ function build_models!(
453451
prior = build_default_prior(input_dim, kernel_structure)
454452

455453
end
456-
# [2.] Estimate covariance at mean value
454+
# [2a.] Estimate the covariance at prior mean
455+
n_ensemble = optimizer_options["n_ensemble"]
457456
μ_hp = transform_unconstrained_to_constrained(prior, mean(prior))
458457

459458
cov_sample_multiplier = optimizer_options["cov_sample_multiplier"]
460459
cov_correction = optimizer_options["cov_correction"]
461460
overfit = max(optimizer_options["overfit"], 1e-4)
462461
n_cov_samples_min = n_test + 2
463462
n_cov_samples = Int(floor(n_cov_samples_min * max(cov_sample_multiplier, 0.0)))
464-
println("estimating covariances with " * string(n_cov_samples) * " iterations...")
465463

464+
println("estimating covariances with " * string(n_cov_samples) * " iterations...")
466465
observation_vec = []
467466
for cv_idx in 1:n_cross_val_sets
468467

@@ -504,7 +503,6 @@ function build_models!(
504503
end
505504
observation = combine_observations(observation_vec)
506505
# [3.] set up EKP optimization
507-
n_ensemble = optimizer_options["n_ensemble"]
508506
n_iteration = optimizer_options["n_iteration"]
509507
scheduler = optimizer_options["scheduler"]
510508
accelerator = optimizer_options["accelerator"]
@@ -594,6 +592,7 @@ function build_models!(
594592
end
595593

596594
io_pairs_i = PairedDataContainer(input_values, reshape(output_values[i, :], 1, size(output_values, 2)))
595+
597596
# Now, fit new RF model with the optimized hyperparameters
598597

599598
rfm_i = RFM_from_hyperparameters(

src/VectorRandomFeature.jl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ Constructs a `VectorRandomFeatureInterface <: MachineLearningTool` interface for
160160
- "cov_correction" => "nice": type of conditioning to improve estimated covariance. "shrinkage", "shrinkage_corr" (Ledoit Wolfe 03), "nice" for (Vishny, Morzfeld et al. 2024)
161161
- "overfit" => 1.0: if > 1.0 forcibly overfit/under-regularize the optimizer cost, (vice versa for < 1.0).
162162
- "n_cross_val_sets" => 2, train fraction creates (default 5) train-test data subsets, then use 'n_cross_val_sets' of these stacked in the loss function. If set to 0, train=test on the full data provided ignoring "train_fraction".
163-
164163
"""
165164
function VectorRandomFeatureInterface(
166165
n_features::Int,
@@ -203,7 +202,7 @@ function VectorRandomFeatureInterface(
203202
"accelerator" => EKP.NesterovAccelerator(), # acceleration with momentum
204203
"cov_correction" => "nice", # type of conditioning to improve estimated covariance
205204
"n_cross_val_sets" => 2, # if set to 0, removes data split. i.e takes train & test to be the same data set
206-
"overfit" => 1.0, # if >1 this forcibly overfits to the data
205+
"overfit" => 1.0, # if >1 this forcibly overfits to the data
207206
)
208207

209208
if !isnothing(optimizer_options)
@@ -471,11 +470,9 @@ function build_models!(
471470
end
472471
end
473472

474-
# [2.] Estimate covariance at mean value
473+
# [2a.] Estimate the cov at prior mean
474+
n_ensemble = optimizer_options["n_ensemble"] # minimal ensemble size n_hp,
475475
μ_hp = transform_unconstrained_to_constrained(prior, mean(prior))
476-
cov_sample_multiplier = optimizer_options["cov_sample_multiplier"]
477-
cov_correction = optimizer_options["cov_correction"]
478-
overfit = max(optimizer_options["overfit"], 1e-4)
479476

480477
if nameof(typeof(kernel_structure)) == :SeparableKernel
481478
if nameof(typeof(get_output_cov_structure(kernel_structure))) == :DiagonalFactor
@@ -486,7 +483,13 @@ function build_models!(
486483
else
487484
n_cov_samples_min = (n_test * output_dim + 2)
488485
end
486+
487+
cov_sample_multiplier = optimizer_options["cov_sample_multiplier"]
488+
cov_correction = optimizer_options["cov_correction"]
489+
overfit = max(optimizer_options["overfit"], 1e-4)
489490
n_cov_samples = Int(floor(n_cov_samples_min * max(cov_sample_multiplier, 0.0)))
491+
492+
println("estimating covariances with " * string(n_cov_samples) * " iterations...")
490493
observation_vec = []
491494
for cv_idx in 1:n_cross_val_sets
492495
internal_Γ, approx_σ2 = estimate_mean_and_coeffnorm_covariance(
@@ -525,7 +528,6 @@ function build_models!(
525528
observation = combine_observations(observation_vec)
526529

527530
# [3.] set up EKP optimization
528-
n_ensemble = optimizer_options["n_ensemble"] # minimal ensemble size n_hp,
529531
n_iteration = optimizer_options["n_iteration"]
530532
opt_verbose_flag = optimizer_options["verbose"]
531533
scheduler = optimizer_options["scheduler"]

0 commit comments

Comments
 (0)