Skip to content

Commit 0e9c409

Browse files
committed
add convenience to build AGP from GPJL and implement 1D->1D unit test
1 parent 7864db8 commit 0e9c409

File tree

3 files changed

+120
-54
lines changed

3 files changed

+120
-54
lines changed

src/Emulator.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ include("RandomFeature.jl")
3535
function throw_define_mlt()
3636
throw(ErrorException("Unknown MachineLearningTool defined, please use a known implementation"))
3737
end
38-
function build_models!(mlt, iopairs)
38+
function build_models!(mlt, iopairs, mlt_kwargs...)
3939
throw_define_mlt()
4040
end
4141
function optimize_hyperparameters!(mlt)
@@ -104,7 +104,7 @@ function Emulator(
104104
standardize_outputs_factors::Union{AbstractVector{FT}, Nothing} = nothing,
105105
decorrelate::Bool = true,
106106
retained_svd_frac::FT = 1.0,
107-
mlt_kwargs...,
107+
mlt_kwargs...
108108
) where {FT <: AbstractFloat}
109109

110110
# For Consistency checks
@@ -151,15 +151,15 @@ function Emulator(
151151
training_pairs = PairedDataContainer(training_inputs, decorrelated_training_outputs)
152152
# [4.] build an emulator
153153

154-
build_models!(machine_learning_tool, training_pairs, mlt_kwargs...)
154+
build_models!(machine_learning_tool, training_pairs; mlt_kwargs...)
155155
else
156156
if decorrelate || !isa(machine_learning_tool, VectorRandomFeatureInterface)
157157
throw(ArgumentError("$machine_learning_tool is incompatible with option Emulator(...,decorrelate = false)"))
158158
end
159159
decomposition = nothing
160160
training_pairs = PairedDataContainer(training_inputs, training_outputs)
161161
# [4.] build an emulator - providing the noise covariance as a Tikhonov regularizer
162-
build_models!(machine_learning_tool, training_pairs, regularization_matrix = obs_noise_cov, mlt_kwargs...)
162+
build_models!(machine_learning_tool, training_pairs; regularization_matrix = obs_noise_cov, mlt_kwargs...)
163163
end
164164

165165
return Emulator{FT}(

src/GaussianProcess.jl

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ using EnsembleKalmanProcesses.DataContainers
33
using DocStringExtensions
44

55
# [1] For GaussianProcesses
6-
import GaussianProcesses: predict
6+
import GaussianProcesses: predict, get_params, get_param_names
77
using GaussianProcesses
8-
8+
export get_params
9+
export get_param_names
910
# [2] For SciKitLearn
1011
using PyCall
1112
using ScikitLearn
@@ -23,7 +24,7 @@ using KernelFunctions
2324
#exports (from Emulator)
2425
export GaussianProcess
2526

26-
export GPJL, SKLJL
27+
export GPJL, SKLJL, AGPJL
2728
export YType, FType
2829

2930
"""
@@ -63,7 +64,7 @@ $(DocStringExtensions.TYPEDFIELDS)
6364
"""
6465
struct GaussianProcess{GPPackage, FT} <: MachineLearningTool
6566
"The Gaussian Process (GP) Regression model(s) that are fitted to the given input-data pairs."
66-
models::Vector{Union{<:GaussianProcesses.GPE, <:PyObject, Nothing}}
67+
models::Vector{Union{<:GaussianProcesses.GPE, <:PyObject, <:AbstractGPs.PosteriorGP, Nothing}}
6768
"Kernel object."
6869
kernel::Union{<:GaussianProcesses.Kernel, <:PyObject, <:AbstractGPs.Kernel, Nothing}
6970
"Learn the noise with the White Noise kernel explicitly?"
@@ -100,7 +101,7 @@ function GaussianProcess(
100101
}
101102

102103
# Initialize vector for GP models
103-
models = Vector{Union{<:GaussianProcesses.GPE, <:PyObject, Nothing}}(undef, 0)
104+
models = Vector{Union{<:GaussianProcesses.GPE, <:PyObject, <:AbstractGPs.PosteriorGP, <: Nothing}}(undef, 0)
104105

105106
# the algorithm regularization noise is set to some small value if we are learning noise, else
106107
# it is fixed to the correct value (1.0)
@@ -112,6 +113,15 @@ function GaussianProcess(
112113
end
113114

114115
# First we create the GPJL implementation
116+
function get_params(gp::GaussianProcess{GPJL})
117+
return [get_params(model.kernel) for model in gp.models]
118+
end
119+
120+
function get_param_names(gp::GaussianProcess{GPJL})
121+
return [get_param_names(model.kernel) for model in gp.models]
122+
end
123+
124+
115125
"""
116126
$(DocStringExtensions.TYPEDSIGNATURES)
117127
@@ -318,8 +328,7 @@ end
318328
function build_models!(
319329
gp::GaussianProcess{AGPJL},
320330
input_output_pairs::PairedDataContainer{FT};
321-
log_constant_value = nothing,
322-
rbf_len = nothing,
331+
kernel_params = nothing,
323332
kwargs...,
324333
) where {FT <: AbstractFloat}
325334
# get inputs and outputs
@@ -332,8 +341,42 @@ function build_models!(
332341
@warn "GaussianProcess already built. skipping..."
333342
return
334343
end
344+
345+
##############################################################################
346+
# Notes on borrowing hyperparameters optimised within GPJL:
347+
# optimisation of the GPJL with default kernels produces kernel parameters
348+
# in the way of [a b c], where:
349+
# c is the log_const_value
350+
# [a b] is the rbf_len: lengthscale parameters for SEArd kernel
351+
# const_value = exp.(2 .* log_const_value)
352+
##############################################################################
353+
## For example A 2D->2D sinusoid input example:
354+
#=
355+
log_const_value = [2.9031145778344696; 3.8325906110973795]
356+
rbf_len = [1.9952706691900783 3.066374123568536; 5.783676639895112 2.195849064147456]
357+
=#
358+
if isnothing(kernel_params)
359+
throw(ArgumentError("""
360+
AbstractGP currently does not (yet) learn hyperparameters internally. The following can be performed instead:
361+
1. Create and optimize a GPJL emulator and default kernel.
362+
2. Obtain kernel parameters from this, given as [a b], where:
363+
- a is the `rbf_len`: lengthscale parameters for SEArd kernel [output_dim x input_dim] matrix
364+
- b is the `log_std_sqexp` of the SQexp kernel Vector [output_dim]
365+
- c is the `log_std_noise` of the noise kernel Float
366+
3. Create a Dict with
367+
kernel_params = Dict(
368+
"log_rbf_len" => (output_dim x input_dim)-Matrix,
369+
"log_std_sqexp" => (output_dim)-Vector,
370+
"log_std_noise" => Float,
371+
)
372+
4. Build a new Emulator with kwargs `kernel_params=kernel_params`
373+
"""))
374+
end
375+
376+
335377
N_models = size(output_values, 1) #size(transformed_data)[1]
336-
if gp.kernel === nothing
378+
regularization_noise = gp.alg_reg_noise
379+
#= if gp.kernel === nothing
337380
println("Using default squared exponential kernel, learning length scale and variance parameters")
338381
# Create default squared exponential kernel
339382
const_value = 1.0
@@ -349,12 +392,10 @@ function build_models!(
349392
if gp.noise_learn
350393
# Add white noise to kernel
351394
white_noise_level = 1.0
352-
white = KernelFunctions.WhiteKernel(white_noise_level)
395+
white = white_noise_level * KernelFunctions.WhiteKernel()
353396
kern += white
354397
println("Learning additive white noise")
355398
end
356-
357-
regularization_noise = gp.alg_reg_noise
358399
for i in 1:N_models
359400
kernel_i = deepcopy(kern)
360401
# In contrast to the GPJL and SKLJL case "data_i = output_values[i, :]"
@@ -372,50 +413,31 @@ function build_models!(
372413
end
373414
println("created GP: ", i)
374415
end
375-
376-
##############################################################################
377-
# Notes on borrowing hyperparameters optimised within GPJL:
378-
# optimisation of the GPJL with default kernels produces kernel parameters
379-
# in the way of [a b c], where:
380-
# c is the log_const_value
381-
# [a b] is the rbf_len: lengthscale parameters for SEArd kernel
382-
# const_value = exp.(2 .* log_const_value)
383-
##############################################################################
384-
## For example A 2D->2D sinusoid input example:
385-
#=
386-
log_const_value = [2.9031145778344696; 3.8325906110973795]
387-
rbf_len = [1.9952706691900783 3.066374123568536; 5.783676639895112 2.195849064147456]
388-
=#
389-
if isnothing(log_const_value) || isnothing(rbf_len)
390-
throw(ArgumentError("""
391-
AbstractGP currently does not (yet) learn hyperparameters internally. The following can be performed instead:
392-
1. Create and optimize a GPJL emulator and default kernel.
393-
2. Obtain kernel parameters from this, given as [a b], where:
394-
- a is the `rbf_len`: lengthscale parameters for SEArd kernel [output_dim x input_dim] matrix
395-
- b is the `log_const_value` Vector [output_dim]
396-
3. Create a new emulator with AGPJL kwargs `log_const_value`(Vector) and `rbf_len` (Matrix), `input_output_pairs`
397-
"""))
398-
else
399-
const_value = exp.(2 .* log_const_value)
400-
for i in 1:N_models
401-
opt_kern = const_value[i] * (KernelFunctions.SqExponentialKernel() ARDTransform(1 ./ exp.(rbf_len[i, :])))
402-
opt_f = AbstractGPs.GP(opt_kern)
403-
opt_fx = opt_f(input_values', regularization_noise)
404-
405-
data_i = output_values[i, :]
406-
opt_post_fx = posterior(opt_fx, data_i)
407-
println("optimised GP: ", i)
408-
push!(models, opt_post_fx)
409-
println(opt_post_fx.prior.kernel)
410-
end
416+
=#
417+
# now obtain the values
418+
var_sqexp = exp.(2 .* kernel_params["log_std_sqexp"]) # Vec [out]
419+
var_noise = exp.(2 .* kernel_params["log_std_noise"]) # Float
420+
rbf_invlen = 1 ./ exp.(kernel_params["log_rbf_len"])# rbf_len # mat [out x in]
421+
422+
for i in 1:N_models
423+
opt_kern = var_sqexp[i] * (KernelFunctions.SqExponentialKernel() ARDTransform(rbf_invlen[i, :])) + var_noise * KernelFunctions.WhiteKernel()
424+
opt_f = AbstractGPs.GP(opt_kern)
425+
opt_fx = opt_f(input_values', regularization_noise)
426+
427+
data_i = output_values[i, :]
428+
opt_post_fx = posterior(opt_fx, data_i)
429+
println("optimised GP: ", i)
430+
push!(models, opt_post_fx)
431+
println(opt_post_fx.prior.kernel)
411432
end
433+
412434
end
413435
414436
function optimize_hyperparameters!(gp::GaussianProcess{AGPJL}, args...; kwargs...)
415437
@info "AbstractGP already built. Continuing..."
416438
end
417439
418-
function predict(gp::GaussianProcess{AGPJL}, new_inputs::AbstractMatrix{Dual}) where {FT <: AbstractFloat, Dual}
440+
function predict(gp::GaussianProcess{AGPJL}, new_inputs::AbstractMatrix{Dual}) where {Dual}
419441
420442
N_models = length(gp.models)
421443
N_samples = size(new_inputs, 2)

test/GaussianProcess/runtests.jl

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Import modules
2-
using Random
32
using Test
3+
using Random
44
using GaussianProcesses
55
using Statistics
66
using Distributions
@@ -98,9 +98,51 @@ using CalibrateEmulateSample.DataContainers
9898
@test size(μ1) == (1, 5)
9999
@test vec(σ1²) [0.017, 0.003, 0.004, 0.004, 0.009] atol = 1e-2
100100

101+
# GaussianProcess 1b: use GPJL to create an abstractGP dist.
102+
agp = GaussianProcess(
103+
AGPJL();
104+
noise_learn = true,
105+
alg_reg_noise = 1e-4,
106+
prediction_type = pred_type,
107+
)
108+
@test_throws ArgumentError Emulator(
109+
agp,
110+
iopairs,
111+
obs_noise_cov = nothing,
112+
normalize_inputs = false,
113+
standardize_outputs = false,
114+
retained_svd_frac = 1.0,
115+
)
116+
gp1_opt_params = get_params(gp1)[1] # one model only
117+
gp1_opt_param_names = get_param_names(gp1)[1] # one model only
118+
119+
kernel_params = Dict(
120+
"log_rbf_len" => [gp1_opt_params[1];;], # [1x1] matrix
121+
"log_std_sqexp" => gp1_opt_params[2:end-1], # [1] vec
122+
"log_std_noise" => gp1_opt_params[end],
123+
)
124+
125+
em_agp_from_gp1 = Emulator(
126+
agp,
127+
iopairs,
128+
obs_noise_cov = nothing,
129+
normalize_inputs = false,
130+
standardize_outputs = false,
131+
retained_svd_frac = 1.0,
132+
kernel_params=kernel_params
133+
)
134+
135+
μ1b, σ1b² = Emulators.predict(em_agp_from_gp1, new_inputs)
136+
137+
# gp1 and agp_from_gp2 should give similar predictions
138+
@test all(isapprox(μ1,μ1b, atol=1e-12)
139+
@test size(μ1) == (1, 5)
140+
@test all(isapprox(σ1²,σ1b², atol=1e-12)
141+
142+
101143
# GaussianProcess 2: GPJL, predict_f
102144
pred_type = FType()
103-
145+
104146
gp2 = GaussianProcess(gppackage; kernel = GPkernel, noise_learn = true, prediction_type = pred_type)
105147

106148
em2 = Emulator(
@@ -250,5 +292,7 @@ using CalibrateEmulateSample.DataContainers
250292
# check match between the variances (should be similar at least)
251293
@test all(isapprox.(σ4²_noise_from_Σ, σ4²_noise_learnt, rtol = 2 * tol_mu))
252294

295+
253296

297+
254298
end

0 commit comments

Comments
 (0)