using CSV, DataFrames # data loading using AbstractGPs # exact GP regression using ParameterHandling # for nested and constrained parameters using Optim # optimization using Zygote # auto-diff gradient computation using Plots # visualisation (xtrain, ytrain), (xtest, ytest) = let data = CSV.read(joinpath("/home/runner/work/AbstractGPs.jl/AbstractGPs.jl/examples/1-mauna-loa", "CO2_data.csv"), Tables.matrix; header=0) year = data[:, 1] co2 = data[:, 2] # We split the data into training and testing set: idx_train = year .< 2004 idx_test = .!idx_train (year[idx_train], co2[idx_train]), (year[idx_test], co2[idx_test]) # block's return value end # The utility variables such as `idx_train` and `idx_test` are not available outside the `let` scope function plotdata() plot(; xlabel="year", ylabel="CO₂ [ppm]", legend=:bottomright) scatter!(xtrain, ytrain; label="training data", ms=2, markerstrokewidth=0) return scatter!(xtest, ytest; label="test data", ms=2, markerstrokewidth=0) end plotdata() # initial values to match http://stor-i.github.io/GaussianProcesses.jl/latest/mauna_loa/ θ_init = (; se_long = (; σ = positive(exp(4.0)), ℓ = positive(exp(4.0)), ), seasonal = (; # product kernels only need a single overall signal variance per = (; ℓ = positive(exp(0.0)), # relative to period! p = fixed(1.0), # 1 year, do not optimize over ), se = (; σ = positive(exp(1.0)), ℓ = positive(exp(4.0)), ), ), rq = (; σ = positive(exp(0.0)), ℓ = positive(exp(0.0)), α = positive(exp(-1.0)), ), se_short = (; σ = positive(exp(-2.0)), ℓ = positive(exp(-2.0)), ), noise_scale = positive(exp(-2.0)), ) SE(θ) = θ.σ^2 * with_lengthscale(SqExponentialKernel(), θ.ℓ) Per(θ) = with_lengthscale(PeriodicKernel(; r=[θ.ℓ / 2]), θ.p) # NOTE- discrepancy with GaussianProcesses.jl RQ(θ) = θ.σ^2 * with_lengthscale(RationalQuadraticKernel(; α=θ.α), θ.ℓ) function build_gp_prior(θ) k_smooth_trend = SE(θ.se_long) k_seasonality = Per(θ.seasonal.per) * SE(θ.seasonal.se) k_medium_term_irregularities = RQ(θ.rq) k_noise_terms = SE(θ.se_short) + θ.noise_scale^2 * WhiteKernel() kernel = k_smooth_trend + k_seasonality + k_medium_term_irregularities + k_noise_terms return GP(kernel) # `ZeroMean` mean function by default end function build_finite_gp(θ) f = build_gp_prior(θ) return f(xtrain) end function build_posterior_gp(θ) fx = build_finite_gp(θ) return posterior(fx, ytrain) end fpost_init = build_posterior_gp(ParameterHandling.value(θ_init)) plot_gp!(f; label) = plot!(f(1920:0.2:2030); ribbon_scale=2, linewidth=1, label) plotdata() plot_gp!(fpost_init; label="posterior f(⋅)") function loss(θ) fx = build_finite_gp(θ) lml = logpdf(fx, ytrain) # this computes the log marginal likelihood return -lml end default_optimizer = LBFGS(; alphaguess=Optim.LineSearches.InitialStatic(; scaled=true), linesearch=Optim.LineSearches.BackTracking(), ) function optimize_loss(loss, θ_init; optimizer=default_optimizer, maxiter=1_000) options = Optim.Options(; iterations=maxiter, show_trace=true) θ_flat_init, unflatten = ParameterHandling.value_flatten(θ_init) loss_packed = loss ∘ unflatten # https://julianlsolvers.github.io/Optim.jl/stable/#user/tipsandtricks/#avoid-repeating-computations function fg!(F, G, x) if F !== nothing && G !== nothing val, grad = Zygote.withgradient(loss_packed, x) G .= only(grad) return val elseif G !== nothing grad = Zygote.gradient(loss_packed, x) G .= only(grad) return nothing elseif F !== nothing return loss_packed(x) end end result = optimize(Optim.only_fg!(fg!), θ_flat_init, optimizer, options; inplace=false) return unflatten(result.minimizer), result end θ_opt, opt_result = optimize_loss(loss, θ_init) opt_result -opt_result.minimum fpost_opt = build_posterior_gp(ParameterHandling.value(θ_opt)) fpost_opt.prior.kernel using Printf show_params(nt::Union{Dict,NamedTuple}) = String(take!(show_params(IOBuffer(), nt))) function show_params(io, nt::Union{Dict,NamedTuple}, indent::Int=0) for (s, v) in pairs(nt) if v isa Union{Dict,NamedTuple} println(io, " "^indent, s, ":") show_params(io, v, indent + 4) else println(io, " "^indent, s, " = ", @sprintf("%.3f", v)) end end return io end print(show_params(ParameterHandling.value(θ_opt))) plotdata() plot_gp!(fpost_opt; label="optimized posterior f(⋅)")