using CairoMakie
using Distributions
using DimensionalData
using LinearAlgebra
import Random
set_theme!(
=18,
fontsize=(xgridvisible=false, ygridvisible=false,
Axis=false, rightspinevisible=false),
topspinevisible )
Differential Evolution MCMC (DE)
This is an implementation of the Differential Evolution MCMC algorithm and, it is based on:
Braak, C.J.F.T. A Markov Chain Monte Carlo version of the genetic algorithm Differential Evolution: easy Bayesian computing for real parameter spaces. Stat Comput 16, 239–249 (2006). https://doi.org/10.1007/s11222-006-8769-1
The algorithm is a MCMC algorithm that uses a population of chains to explore the parameter space. There is no need to tune the proposal distribution and no gradients of the posterior density are calculated to generate new proposals.
1 Loading packages
2 Generate some data from a multivariate normal distribution without correlations
The following four parameters needs to be estimated: μ₁, μ₂, σ₁, σ₂ n
Random.seed!(1234)
= [3.0, 10.0]
μ = [15.0, 20.0]
σ = MvNormal(μ, σ)
real_dist = rand(real_dist, 200)
empirical_data scatter(empirical_data[1, :], empirical_data[2, :]; color = (:blue, 0.6), markersize = 25)
3 A function to sample from the prior distributions
function prior_sample!(x, prior_dists)
for i in eachindex(prior_dists)
= rand(prior_dists[i])
x[i] end
return nothing
end
prior_sample! (generic function with 1 method)
4 Calculate the unnormalized posterior (“fitness”) \(\rightarrow\) prior + likelihood:
function unnormalized_posterior(x, data, prior_dists)
= x
μ₁, μ₂, σ₁, σ₂
## -------- prior
if σ₁ < 0 || σ₂ < 0
return -Inf
end
= 0.0
prior for i in eachindex(prior_dists)
+= logpdf(prior_dists[i], x[i])
prior end
## -------- likelihood
= MvNormal([μ₁, μ₂], [σ₁, σ₂])
dist = 0.0
ll for i in axes(data, 2)
= @view data[:, i]
z += logpdf(dist, z)
ll end
return prior + ll
end
unnormalized_posterior (generic function with 1 method)
5 Function to run the differential evolution MCMC algorithm
function DE_MCMC(; external_chains = 4, draws = 2000,
= 4, N = 3*d,
d = 2.38 / sqrt(2*d), b = 1e-4)
γ
= [Normal(0, 10), Normal(0, 10),
prior_dists InverseGamma(2, 3), InverseGamma(2, 3)]
= zeros(d)
xₚ = zeros(N, d)
X = zeros(external_chains, N, d, draws)
Y
for ext_n in 1:external_chains
## -------- initial population from prior
for i in axes(X, 1)
= @view X[i, :]
z prior_sample!(z, prior_dists)
end
## -------- MCMC
for draw in 1:draws
for i in 1:N
## -------- sample r1, r2 from 1:N without i
= rand(1:N)
r1 = rand(1:N)
r2
while true
if i != r1 && i != r2 && r1 != r2
break
end
= rand(1:N)
r1 = rand(1:N)
r2 end
## -------- proposal
for j in 1:d
e = rand(Normal(0, b))
= X[i, j] + γ * (X[r1, j] - X[r2, j]) + e
xₚ[j] end
= unnormalized_posterior(xₚ, empirical_data, prior_dists)
prop = unnormalized_posterior(X[i, :], empirical_data, prior_dists)
old = prop - old
r
## -------- accept or reject
if log(rand()) < min(r, 1)
:] .= xₚ
X[i, end
end
:, :, draw] .= X
Y[ext_n, end
end
= DimArray(Y, (chain = 1:size(Y, 1),
Y_dim = 1:size(Y, 2),
internal_chain = [:μ₁, :μ₂, :σ₁, :σ₂],
parameter = 1:size(Y, 4));)
draw
return Y_dim
end
DE_MCMC (generic function with 1 method)
6 Plotting function
function plot_mcmc(mcmc_obj)
@show start_covergence = size(mcmc_obj, :draw) ÷ 2
= 1
thin @show sample_x = start_covergence:thin:size(mcmc_obj, :draw)
= mcmc_obj[draw = sample_x]
samples
= ["μ₁", "μ₂", "σ₁", "σ₂"]
names = Figure(; resolution = (800, 900))
fig
for p in axes(samples, :parameter)
Axis(fig[p,1]; title = names[p])
density!(vec(samples[parameter = p]); color = (:blue, 0.5))
Axis(fig[p,2])
for ext_n in axes(samples, :chain)
for i in axes(samples, :internal_chain)
= vec(samples[chain = ext_n, internal_chain = i, parameter = p])
selected_samples
lines!(sample_x, vec(selected_samples);
= :viridis,
colormap = ext_n, colorrange = (1, size(samples, 1)))
color end
end
end
figend
plot_mcmc (generic function with 1 method)
6.1 Run the algorithm
= DE_MCMC(; external_chains = 4, draws = 2000); Y
6.2 Results
The different colours in the trace plots represent the different (external) chains. For each chain, several internal chains were simulated and plotted here in the same colour.
plot_mcmc(Y)
start_covergence = size(mcmc_obj, :draw) ÷ 2 = 1000
sample_x = start_covergence:thin:size(mcmc_obj, :draw) = 1000:1:2000
┌ Warning: Found `resolution` in the theme when creating a `Scene`. The `resolution` keyword for `Scene`s and `Figure`s has been deprecated. Use `Figure(; size = ...` or `Scene(; size = ...)` instead, which better reflects that this is a unitless size and not a pixel resolution. The key could also come from `set_theme!` calls or related theming functions.
└ @ Makie ~/.julia/packages/Makie/iRM0c/src/scenes.jl:220
6.3 Differential Evolution MCMC with fewer internal chains (DEz)
The algorithm can be run with fewer internal chains. This is useful when the dimension of the parameter space is large. Three internal chains are usually enough in comparison to to two time the dimension of the parameter space for the original algorithm.
This implementation is based on, but does not use the snooker update:
ter Braak, C.J.F., Vrugt, J.A. Differential Evolution Markov Chain with snooker updater and fewer chains. Stat Comput 18, 435–446 (2008). https://doi.org/10.1007/s11222-008-9104-9
function DEz_MCMC(; external_chains = 4,
= 2000,
draws = 4, N = 3,
d = 2.38 / sqrt(2), b = 1e-4,
γ = 10, M₀ = 10*d)
K
= [Normal(0, 10), Normal(0, 10),
prior_dists InverseGamma(2, 3), InverseGamma(2, 3)]
@assert M₀ > max(d, N)
= DimArray(zeros(external_chains, draws, N, d),
Y = 1:external_chains,
(chain = 1:draws,
draw = 1:N,
internal_chain = [:μ₁, :μ₂, :σ₁, :σ₂]);)
parameter
= DimArray(zeros(M₀+draws*N, d),
Z = 1:M₀+draws*N, parameter = [:μ₁, :μ₂, :σ₁, :σ₂]);)
(draw = zeros(N, d)
X = zeros(d)
xₚ
for ext_n in 1:external_chains
for i in 1:M₀
= @view Z[i, :]
z prior_sample!(z, prior_dists)
end
.= Z[1:N, :]
X = M₀
M
for draw in 1:draws
for _ in 1:K
for i in 1:N
## -------- sample r1, r2 from 1:M without i
= rand(1:M)
r1 = rand(1:M)
r2
while true
if i != r1 && i != r2 && r1 != r2
break
end
= rand(1:M)
r1 = rand(1:M)
r2 end
## -------- proposal
for j in 1:d
e = rand(Normal(0, b))
= X[i, j] + γ * (Z[r1, j] - Z[r2, j]) + e
xₚ[j] end
= unnormalized_posterior(xₚ, empirical_data, prior_dists)
prop = unnormalized_posterior(X[i, :], empirical_data, prior_dists)
old = prop - old
r
## -------- accept or reject
if log(rand()) < min(r, 1)
:] .= xₚ
X[i, end
end ## internal chains
end ## K
= M+1 .. M+N] = X
Z[draw += N
M = ext_n, draw = draw] .= X
Y[chain
end
end ## external_chains
return Y
end
DEz_MCMC (generic function with 1 method)
6.4 Run the algorithm
= DEz_MCMC(); Yz
6.5 Results
plot_mcmc(Yz)
start_covergence = size(mcmc_obj, :draw) ÷ 2 = 1000
sample_x = start_covergence:thin:size(mcmc_obj, :draw) = 1000:1:2000
┌ Warning: Found `resolution` in the theme when creating a `Scene`. The `resolution` keyword for `Scene`s and `Figure`s has been deprecated. Use `Figure(; size = ...` or `Scene(; size = ...)` instead, which better reflects that this is a unitless size and not a pixel resolution. The key could also come from `set_theme!` calls or related theming functions.
└ @ Makie ~/.julia/packages/Makie/iRM0c/src/scenes.jl:220