Giter Club home page Giter Club logo

advancedmh.jl's Introduction

AdvancedMH.jl

Stable Dev AdvancedMH-CI

AdvancedMH.jl currently provides a robust implementation of random walk Metropolis-Hastings samplers.

Further development aims to provide a suite of adaptive Metropolis-Hastings implementations.

AdvancedMH works by allowing users to define composable Proposal structs in different formats.

Usage

First, construct a DensityModel, which is a wrapper around the log density function for your inference problem. The DensityModel is then used in a sample call.

# Import the package.
using AdvancedMH
using Distributions
using MCMCChains

using LinearAlgebra

# Generate a set of data from the posterior we want to estimate.
data = rand(Normal(0, 1), 30)

# Define the components of a basic model.
insupport(θ) = θ[2] >= 0
dist(θ) = Normal(θ[1], θ[2])
density(θ) = insupport(θ) ? sum(logpdf.(dist(θ), data)) : -Inf

# Construct a DensityModel.
model = DensityModel(density)

# Set up our sampler with a joint multivariate Normal proposal.
spl = RWMH(MvNormal(zeros(2), I))

# Sample from the posterior.
chain = sample(model, spl, 100000; param_names=["μ", "σ"], chain_type=Chains)

Output:

Object of type Chains, with data of type 100000×3×1 Array{Float64,3}

Iterations        = 1:100000
Thinning interval = 1
Chains            = 1
Samples per chain = 100000
internals         = lp
parameters        = μ, σ

2-element Array{ChainDataFrame,1}

Summary Statistics

│ Row │ parameters │ mean     │ std      │ naive_se    │ mcse       │ ess     │ r_hat   │
│     │ Symbol     │ Float64  │ Float64  │ Float64     │ Float64    │ Any     │ Any     │
├─────┼────────────┼──────────┼──────────┼─────────────┼────────────┼─────────┼─────────┤
│ 1   │ μ          │ 0.1561520.199630.0006312850.003230333911.731.00009 │
│ 2   │ σ          │ 1.074930.1501110.0004746930.002403173707.731.00027 │

Quantiles

│ Row │ parameters │ 2.5%25.0%50.0%75.0%97.5%    │
│     │ Symbol     │ Float64  │ Float64   │ Float64  │ Float64  │ Float64  │
├─────┼────────────┼──────────┼───────────┼──────────┼──────────┼──────────┤
│ 1   │ μ          │ -0.233610.02970060.1591390.2834930.558694 │
│ 2   │ σ          │ 0.8282880.9726821.058041.161551.41349

Alternatively, you can define your model with the LogDensityProblems.jl interface:

using LogDensityProblems

# Use a struct instead of `typeof(density)` for sake of readability.
struct LogTargetDensity end

LogDensityProblems.logdensity(p::LogTargetDensity, θ) = density(θ)  # standard multivariate normal
LogDensityProblems.dimension(p::LogTargetDensity) = 2
LogDensityProblems.capabilities(::LogTargetDensity) = LogDensityProblems.LogDensityOrder{0}()

sample(LogTargetDensity(), spl, 100000; param_names=["μ", "σ"], chain_type=Chains)

Proposals

AdvancedMH offers various methods of defining your inference problem. Behind the scenes, a MetropolisHastings sampler simply holds some set of Proposal structs. AdvancedMH will return posterior samples in the "shape" of the proposal provided -- currently supported methods are Array{Proposal}, Proposal, and NamedTuple{Proposal}. For example, proposals can be created as:

# Provide a univariate proposal.
m1 = DensityModel(x -> logpdf(Normal(x,1), 1.0))
p1 = StaticProposal(Normal(0,1))
c1 = sample(m1, MetropolisHastings(p1), 100; chain_type=Vector{NamedTuple})

# Draw from a vector of distributions.
m2 = DensityModel(x -> logpdf(Normal(x[1], x[2]), 1.0))
p2 = StaticProposal([Normal(0,1), InverseGamma(2,3)])
c2 = sample(m2, MetropolisHastings(p2), 100; chain_type=Vector{NamedTuple})

# Draw from a `NamedTuple` of distributions.
m3 = DensityModel(x -> logpdf(Normal(x.a, x.b), 1.0))
p3 = (a=StaticProposal(Normal(0,1)), b=StaticProposal(InverseGamma(2,3)))
c3 = sample(m3, MetropolisHastings(p3), 100; chain_type=Vector{NamedTuple})

# Draw from a functional proposal.
m4 = DensityModel(x -> logpdf(Normal(x,1), 1.0))
p4 = StaticProposal((x=1.0) -> Normal(x, 1))
c4 = sample(m4, MetropolisHastings(p4), 100; chain_type=Vector{NamedTuple})

Static vs. Random Walk

Currently there are only two methods of inference available. Static MH simply draws from the prior, with no conditioning on the previous sample. Random walk will add the proposal to the previously observed value. If you are constructing a Proposal by hand, you can determine whether the proposal is a StaticProposal or a RandomWalkProposal using

static_prop = StaticProposal(Normal(0,1))
rw_prop = RandomWalkProposal(Normal(0,1))

Different methods are easily composeable. One parameter can be static and another can be a random walk, each of which may be drawn from separate distributions.

Multiple chains

AdvancedMH.jl implements the interface of AbstractMCMC which means sampling of multiple chains is supported for free:

# Sample 4 chains from the posterior serially, without thread or process parallelism.
chain = sample(model, spl, MCMCSerial(), 100000, 4; param_names=["μ","σ"], chain_type=Chains)

# Sample 4 chains from the posterior using multiple threads.
chain = sample(model, spl, MCMCThreads(), 100000, 4; param_names=["μ","σ"], chain_type=Chains)

# Sample 4 chains from the posterior using multiple processes.
chain = sample(model, spl, MCMCDistributed(), 100000, 4; param_names=["μ","σ"], chain_type=Chains)

Metropolis-adjusted Langevin algorithm (MALA)

AdvancedMH.jl also offers an implementation of MALA if the ForwardDiff and DiffResults packages are available.

A MALA sampler can be constructed by MALA(proposal) where proposal is a function that takes the gradient computed at the current sample. It is required to specify an initial sample initial_params when calling sample.

# Import the package.
using AdvancedMH
using Distributions
using MCMCChains
using ForwardDiff
using StructArrays

using LinearAlgebra

# Generate a set of data from the posterior we want to estimate.
data = rand(Normal(0, 1), 30)

# Define the components of a basic model.
insupport(θ) = θ[2] >= 0
dist(θ) = Normal(θ[1], θ[2])
density(θ) = insupport(θ) ? sum(logpdf.(dist(θ), data)) : -Inf

# Construct a DensityModel.
model = DensityModel(density)

# Set up the sampler with a multivariate Gaussian proposal.
σ² = 0.01
spl = MALA(x -> MvNormal((σ² / 2) .* x, σ² * I))

# Sample from the posterior.
chain = sample(model, spl, 100000; initial_params=ones(2), chain_type=StructArray, param_names=["μ", "σ"])

As above, we can define the model with the LogDensityProblems.jl interface. We can implement the gradient of the log density function manually, or use LogDensityProblemsAD.jl to provide us with the gradient computation used in MALA. Using our implementation of the LogDensityProblems.jl interface above:

using LogDensityProblemsAD
model_with_ad = LogDensityProblemsAD.ADgradient(Val(:ForwardDiff), LogTargetDensity())
sample(model_with_ad, spl, 100000; initial_params=ones(2), chain_type=StructArray, param_names=["μ", "σ"])

advancedmh.jl's People

Contributors

briandepasquale avatar cnrrobertson avatar cpfiffer avatar devmotion avatar github-actions[bot] avatar jaimerzp avatar juliatagbot avatar luiarthur avatar mhauru avatar mschauer avatar pearlzli avatar torfjelde avatar yebai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

advancedmh.jl's Issues

Quasi-MH

I think that taking advantage of this should let you get a significant improvement in sampling without being too much work.

Multithreaded sampling example in README

I believe the example given in README to do multithreaded sampling is out-of-date, as psample is no longer defined; digging through the issues I saw this mentioned in #37:

chain = sample(model, spl, MCMCThreads(), 100000, 4; param_names=["μ", "σ"], chain_type=Chains)

Compatibility issues when trying to install

Hello,

I tried to install AdvancedMH.jl but ran into the following error:

(v1.3) pkg> add AdvancedMH
 Resolving package versions...
ERROR: Unsatisfiable requirements detected for package Requires [ae029012]:
 Requires [ae029012] log:
 ├─possible versions are: [0.5.0-0.5.2, 1.0.0-1.0.1] or uninstalled
 ├─restricted by compatibility requirements with Plots [91a5bcdd] to versions: [0.5.0-0.5.2, 1.0.0-1.0.1]
 │ └─Plots [91a5bcdd] log:
 │   ├─possible versions are: [0.12.1-0.12.4, 0.13.0-0.13.1, 0.14.0-0.14.2, 0.15.0-0.15.1, 0.16.0, 0.17.0-0.17.4, 0.18.0, 0.19.0-0.19.3, 0.20.0-0.20.6, 0.21.0, 0.22.0-0.22.5, 0.23.0-0.23.2, 0.24.0, 0.25.0-0.25.3, 0.26.0-0.26.3, 0.27.0-0.27.1, 0.28.0-0.28.4, 0.29.0-0.29.7] or uninstalled
 │   └─restricted to versions 0.29.7 by an explicit requirement, leaving only versions 0.29.7
 ├─restricted by compatibility requirements with AdvancedMH [5b7e9947] to versions: 1.0.0-1.0.1
 │ └─AdvancedMH [5b7e9947] log:
 │   ├─possible versions are: [0.1.0, 0.2.0, 0.3.0-0.3.1, 0.4.0-0.4.1] or uninstalled
 │   ├─restricted to versions * by an explicit requirement, leaving only versions [0.1.0, 0.2.0, 0.3.0-0.3.1, 0.4.0-0.4.1]
 │   └─restricted by compatibility requirements with Distributions [31c24e10] to versions: [0.3.0-0.3.1, 0.4.0-0.4.1] or uninstalled, leaving only versions: [0.3.0-0.3.1, 0.4.0-0.4.1]
 │     └─Distributions [31c24e10] log:
 │       ├─possible versions are: [0.16.0-0.16.4, 0.17.0, 0.18.0, 0.19.1-0.19.2, 0.20.0, 0.21.0-0.21.3, 0.21.5-0.21.12, 0.22.0-0.22.5] or uninstalled
 │       └─restricted to versions 0.22.5 by an explicit requirement, leaving only versions 0.22.5
 └─restricted by compatibility requirements with Flatten [4c728ea3] to versions: 0.5.0-0.5.2 — no versions left
   └─Flatten [4c728ea3] log:
     ├─possible versions are: [0.0.1-0.0.4, 0.1.0, 0.2.0-0.2.1, 0.3.0-0.3.2] or uninstalled
     └─restricted to versions 0.1.0 by an explicit requirement, leaving only versions 0.1.0

Is this something you can fix on your side?

My system:

Julia Version 1.3.1
Commit 2d5741174c (2019-12-30 21:36 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin18.6.0)
  CPU: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.1 (ORCJIT, skylake)
Environment:
  JULIA_EDITOR = atom  -a
  JULIA_NUM_THREADS = 4

RW prior on vectors

How can I apply random walk priors to a vector (beta[1:10]) using AdvancedMH in Turing? Not working sampling code is as follows:

chain = sample(
    model,
    MH(
        :sigmae => AdvancedMH.RandomWalkProposal(Normal(0, 0.1)),
        :alpha => AdvancedMH.RandomWalkProposal(Normal(0, 0.1)),
        :beta => AdvancedMH.RandomWalkProposal(Normal(0, 0.1)),
    ),
    1_000
)

Support for Ensemble Sampler

I come from the python astronomy community which includes an MCMC inference tool called the Ensemble Sampler (code, paper). Dan has said he's currently working with PyMC3 team to incorporate his sampler there and I think it would be nice to incorporate into Turing.

In general, I don't have great experience implementing inference methods (I did a Metropolis-Hastings in a stats class once!), so my attempt at implementing this sampler would definitely need oversight from more adept inferencers. Input from @dfm would be great, too!

Is this something that should be looked at soon or something that should wait for some of the roadmap goals of simplifying the sampler API? (#689)

Document basic usage with Turing model

I am quite new to Turing, but I really like how it composes with basically any Julia library which is fantastic.

For hard problems, I think being able to customize sampling like in e.g. Gen.jl is the way to go. As far as I understand, this is the goal of AdvancedHMC and AdvancedMH, which are getting refactored out of Turing.

I am facing a difficult discrete problem where, currently, my only efficient solution is an implementation using Infer.NET. This library basically compiles programs to factor graphs. I would like to have a more high level implementation in Julia using Turing. I guess the way to go is to use AdvancedMH plus Turing. Would it be possible to get a little Turing example model with custom AdvancedMH sampling documented? I couldn't find any appropriate examples that included a Turing model.

Is Emcee implemented?

From the code I see there is an emcee.jl file which seems to implement (part of?) the affine-invariant MCMC algorithm, aka Emcee in Python. Is this work on progress or is it already usable? I do not see any mention in the Readme file.

If that helps, the same algorithm (with just the parallel stretch step) is implemented in

https://github.com/madsjulia/AffineInvariantMCMC.jl

The code is very simple and of course does not try to comply to the MCMCInterface.

Return accept/reject stat

Acceptance status should be included in the transition struct for easy calculations of rejection rates.

Negative density in MALA

Hi all. I recall that minus sign... Let me take a look. Yes perhaps within an issue is more appropriate. I will be a little slow though - I am deep in an academic search right now and about to start interviewing, very time intensive and stressful. But hopefully won't be months, like my original PR :)

Originally posted by @briandepasquale in #58 (comment)

Seems there's a negative sign in the MALA code -- not sure why.

Custom proposals

Readme says:

# Set up our sampler with initial parameters.
spl = MetropolisHastings([0.0, 0.0], x -> MvNormal(x, 0.5)) 

but this doesn't appear to work. When I modify the provided example to this proposal I get the following error:

julia> spl = MetropolisHastings(ones(3), x-> MvNormal(x,0.01));

julia> chain = sample(Random.GLOBAL_RNG, model, spl, 10000; param_names=["μ", "σ1", "σ2"])
ERROR: ArgumentError: Sampler for this object is not defined
Stacktrace:
 [1] Random.Sampler(::Type{MersenneTwister}, ::Random.SamplerTrivial{getfield(Main, Symbol("##9#10")),Any}, ::Val{1}) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.2/Random/src/Random.jl:143
 [2] Random.Sampler(::MersenneTwister, ::Random.SamplerTrivial{getfield(Main, Symbol("##9#10")),Any}, ::Val{1}) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.2/Random/src/Random.jl:140
 [3] rand(::MersenneTwister, ::Random.SamplerTrivial{getfield(Main, Symbol("##9#10")),Any}) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.2/Random/src/Random.jl:230 (repeats 2 times)
 [4] rand(::Function) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.2/Random/src/Random.jl:235
 [5] propose(::MetropolisHastings{Array{Float64,1},getfield(Main, Symbol("##9#10"))}, ::DensityModel{typeof(density)}, ::Array{Float64,1}) at /Users/briandepasquale/.julia/packages/AdvancedMH/VDKYB/src/AdvancedMH.jl:74
 [6] propose(::MetropolisHastings{Array{Float64,1},getfield(Main, Symbol("##9#10"))}, ::DensityModel{typeof(density)}, ::AdvancedMH.Transition{Array{Float64,1},Float64}) at /Users/briandepasquale/.julia/packages/AdvancedMH/VDKYB/src/AdvancedMH.jl:76
 [7] #step!#2(::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol},NamedTuple{(:iteration, :param_names),Tuple{Int64,Array{String,1}}}}, ::typeof(AbstractMCMC.step!), ::MersenneTwister, ::DensityModel{typeof(density)}, ::MetropolisHastings{Array{Float64,1},getfield(Main, Symbol("##9#10"))}, ::Int64, ::AdvancedMH.Transition{Array{Float64,1},Float64}) at /Users/briandepasquale/.julia/packages/AdvancedMH/VDKYB/src/AdvancedMH.jl:118
 [8] (::getfield(AbstractMCMC, Symbol("#kw##step!")))(::NamedTuple{(:iteration, :param_names),Tuple{Int64,Array{String,1}}}, ::typeof(AbstractMCMC.step!), ::MersenneTwister, ::DensityModel{typeof(density)}, ::MetropolisHastings{Array{Float64,1},getfield(Main, Symbol("##9#10"))}, ::Int64, ::AdvancedMH.Transition{Array{Float64,1},Float64}) at ./none:0
 [9] #sample#6(::Bool, ::Base.Iterators.Pairs{Symbol,Array{String,1},Tuple{Symbol},NamedTuple{(:param_names,),Tuple{Array{String,1}}}}, ::typeof(sample), ::MersenneTwister, ::DensityModel{typeof(density)}, ::MetropolisHastings{Array{Float64,1},getfield(Main, Symbol("##9#10"))}, ::Int64) at /Users/briandepasquale/.julia/packages/AbstractMCMC/pi3j6/src/AbstractMCMC.jl:219
 [10] (::getfield(StatsBase, Symbol("#kw##sample")))(::NamedTuple{(:param_names,),Tuple{Array{String,1}}}, ::typeof(sample), ::MersenneTwister, ::DensityModel{typeof(density)}, ::MetropolisHastings{Array{Float64,1},getfield(Main, Symbol("##9#10"))}, ::Int64) at ./none:0
 [11] top-level scope at REPL[20]:1

Do I need to write my own rand() function to do this? Looking to use AdvancedMH.jl to do MALA by passing the gradient of the logpdf to the proposal distribution.

Tracking acceptances

We are not currently tracking acceptances, which is a statistic that could be useful for manual tuning. We need to change the Transition struct to track accept/reject.

In addition to this, the AdpativeProposal code in from #39 has some legacy stuff that mutates the sampler in place, as opposed to tracking the total number of draws. A fix for this would allow the AdaptiveProposal code to track the total number of draws within each Transition, so any mutation can be removed from the system.

Check out this discussion for more context.

Package scope

The current plan for this package is to provide a robust random-walk MH sampler, and its adaptive variants. But this could be relaxed to include slice samplers (e.g. Radford Neal's slice sampler, and Iain Murray's elliptical slice sampler) etc. In the latter case, a more proper name can be AdvancedMCMC.

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Array of proposals versus proposals of arrays

It is confusing that Proposals such as StaticProposal accept arrays of proposals and one can also specify arrays of Proposals in the MetropolisHastings sampler. Would it be sufficient to only support arrays of proposals (and similar for tuples etc.)?

Discrete sampling

The test suites don't include discrete sampling, which is kind of important. The code also makes it kind of difficult to allow different proposal distributions (discrete or continuous) for each variable.

Related work

ESS

More

Example code in README doesn't appear to work

The following code (as well as presuming I've ran using Distributions):

# Import the package.
using AdvancedMH

# Define the components of a basic model.
insupport(θ) = θ[2] >= 0
dist(θ) = Normal(θ[1], θ[2])
density(data, θ) = insupport(θ) ? sum(logpdf.(dist(θ), data)) : -Inf

# Generate a set of data from the posterior we want to estimate.
data = rand(Normal(0, 1), 30)

# Construct a DensityModel.
model = DensityModel(density)

# Set up our sampler with initial parameters.
spl = MetropolisHastings([0.0, 0.0])

# Sample from the posterior.
chain = sample(model, spl, 100000; param_names=["μ", "σ"])

Produces the following error, and I am not sure why:

ERROR: MethodError: no method matching density(::Array{Float64,1})
Closest candidates are:
  density(::Any, ::Any) at REPL[13]:1
Stacktrace:
 [1] ℓπ(::DensityModel{typeof(density)}, ::Array{Float64,1}) at /Users/harrisonwilde/.julia/packages/AdvancedMH/n87l5/src/AdvancedMH.jl:90
 [2] AdvancedMH.Transition(::DensityModel{typeof(density)}, ::Array{Float64,1}) at /Users/harrisonwilde/.julia/packages/AdvancedMH/n87l5/src/AdvancedMH.jl:65
 [3] #step!#1(::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol},NamedTuple{(:iteration, :param_names),Tuple{Int64,Array{String,1}}}}, ::typeof(AbstractMCMC.step!), ::Random._GLOBAL_RNG, ::DensityModel{typeof(density)}, ::MetropolisHastings{Array{Float64,1},Array{Distribution{Univariate,Continuous},1}}, ::Int64) at /Users/harrisonwilde/.julia/packages/AdvancedMH/n87l5/src/AdvancedMH.jl:103
 [4] (::AbstractMCMC.var"#kw##step!")(::NamedTuple{(:iteration, :param_names),Tuple{Int64,Array{String,1}}}, ::typeof(AbstractMCMC.step!), ::Random._GLOBAL_RNG, ::DensityModel{typeof(density)}, ::MetropolisHastings{Array{Float64,1},Array{Distribution{Univariate,Continuous},1}}, ::Int64) at ./none:0
 [5] #sample#6(::Bool, ::Base.Iterators.Pairs{Symbol,Array{String,1},Tuple{Symbol},NamedTuple{(:param_names,),Tuple{Array{String,1}}}}, ::typeof(sample), ::Random._GLOBAL_RNG, ::DensityModel{typeof(density)}, ::MetropolisHastings{Array{Float64,1},Array{Distribution{Univariate,Continuous},1}}, ::Int64) at /Users/harrisonwilde/.julia/packages/AbstractMCMC/pi3j6/src/AbstractMCMC.jl:217
 [6] #sample at ./none:0 [inlined]
 [7] #sample#5 at /Users/harrisonwilde/.julia/packages/AbstractMCMC/pi3j6/src/AbstractMCMC.jl:194 [inlined]
 [8] (::StatsBase.var"#kw##sample")(::NamedTuple{(:param_names,),Tuple{Array{String,1}}}, ::typeof(sample), ::DensityModel{typeof(density)}, ::MetropolisHastings{Array{Float64,1},Array{Distribution{Univariate,Continuous},1}}, ::Int64) at ./none:0
 [9] top-level scope at REPL[19]:1

Fisher adaptive MALA

Not sure how much people use MALA when we have HMC, but Michalis Titisas has a new gradient-based covariance adaptation scheme at NeurIPS'23. Probably worth considering for the future addition.

AdvancedMH + Bijectors?

It is sometimes useful to perform MH in a reparametrised space, similar to HMC with its bijectors. This would effectively allow for non-stationary Gaussian proposals, among others. It does not seem like there is any support in AdvancedMH for this. I have some code for a ReparamProposal that wraps another proposal, but this is very inelegant. Does anyone have any pointers or ideas for structuring this properly?

MALA implementation results in the wrong covariance

import AdvancedMH, LogDensityProblems, Distributions

struct TheNormalLogDensity{M}
    A::M
end

# can do gradient
LogDensityProblems.capabilities(::Type{<:TheNormalLogDensity}) = LogDensityProblems.LogDensityOrder{1}()

LogDensityProblems.dimension(d::TheNormalLogDensity) = size(d.A, 1)
LogDensityProblems.logdensity(d::TheNormalLogDensity, x) = -x' * d.A * x / 2

function LogDensityProblems.logdensity_and_gradient(d::TheNormalLogDensity, x)
    return -x' * d.A * x / 2, -d.A * x
end

Σ = [1.5 0.35; 0.35 1.0]
σ² = 0.5
spl = AdvancedMH.MALA(g -> Distributions.MvNormal((σ² / 2) .* g, σ² * I))
#spl = RWMH(MvNormal(zeros(2), I))


chain = AdvancedMH.sample(TheNormalLogDensity(inv(Σ)), spl, 500000; initial_params=ones(2))
data = stack([c.params for c = chain])
cov(data, dims=2)

Someone pointed out that MALA results in samples that have a wrong covariance.
It is not a mixing/variance issue since I'm hitting an acceptance rate of 0.57.
For the above example, I get:

julia> cov(data, dims=2)
2×2 Matrix{Float64}:
 0.500508  0.115434
 0.115434  0.332796

I didn't have the time to look into too much detail, but it seems that RWMH returns the correct covariance, while only MALA does not. For instance, RWMH yields:

julia> cov(data, dims=2)
2×2 Matrix{Float64}:
 1.47741   0.344062
 0.344062  0.995041

Lack of tuning of scale factor for variance of normal proposal

A pretty common feature of Metropolis-Hastings samplers seems to be missing for parameter proposals/jumps, which is the tuning of the variance of a normal distribution according to the acceptance rate. For one-at-a-time parameter proposals, the size of the jump would be scaled according to the acceptance rate to obtain an "optimal" jump size.

In a nutshell, acceptance rates around approx 0.23 wouldn't require tuning, but rates of acceptance that are too low or too high would trigger scaling the size down or up. Given that acceptance rates are tracked, it should be straightforward to implement it by checking them for each parameter at a regular interval (possibly during warm-up only, or throughout) and using a function like what they had in PyMC2. That would provide a "standard" proposal method that didn't require hand tuning proposal distributions, or relied on the scaling of priors, whose variance need not match the size of jumps.

function tune(currentScale, acceptanceRate):
    """Tunes the scaling parameter for the proposal
    distribution according to the acceptance rate over the
    last tune_interval:

    Rate    Variance adaptation
    ----    -------------------
    <0.001        x 0.1
    <0.05         x 0.5
    <0.2          x 0.9
    >0.5          x 1.1
    >0.75         x 2
    >0.95         x 10
    """
    if (acceptanceRate < 0.001)
        # reduce by 90 percent
        newScale = currentScale * 0.1
    elseif (acceptanceRate < 0.05)
        # reduce by 50 percent
        newScale = currentScale * 0.5
    elseif (acceptanceRate < 0.2)
        # reduce by 10 percent
        newScale = currentScale*0.90
    elseif (acceptanceRate > 0.5)
        # increase by ten percent
        newScale = currentScale * 1.1
    elseif (acceptanceRate > 0.75)
        # increase by one hundred percent
        newScale = currentScale * 2
    elseif (acceptanceRate > 0.95):
        # increase by one thousand percent
        newScale = currentScale * 10
    else
        newScale = currentScale
    end

    return newScale
end

Skipping the Hastings computation for symmetric proposals

In here and here, to compute the log acceptance probability (loga), the proposal density evaluated at the proposed and previous sample is always computed. However, this is not necessary if the proposal is symmetric, since q(spl, params_prev, params) - q(spl, params, params_prev) == 0.

Is there a reasonable way to check if the proposal is symmetric and, if it is, skip the Hastings part of the log acceptance probability computation?

I noticed this when looking at #39. The proposal is always Normal, so q doesn't need to be computed. I understand that there really isn't a computation bottleneck. But for multivariate proposals, it could be nice to avoid this unnecessary computation.

MethodError in MALA sampler with DensityModel

Description

When trying to use the MALA sampler with a DensityModel, I encountered a MethodError indicating ambiguity in the propose function. This error occurs when running the example code provided in the README.md of the AdvancedMH.jl package.

Steps to Reproduce

  1. Install the required packages:

    using Pkg
    Pkg.add(["AdvancedMH", "Distributions", "MCMCChains", "ForwardDiff", "StructArrays", "LinearAlgebra"])
  2. Run the following code:

    using AdvancedMH
    using Distributions
    using MCMCChains
    using ForwardDiff
    using StructArrays
    using LinearAlgebra
    
    # Generate a set of data from the posterior we want to estimate.
    data = rand(Normal(0, 1), 30)
    
    # Define the components of a basic model.
    insupport(θ) = θ[2] >= 0
    dist(θ) = Normal(θ[1], θ[2])
    density(θ) = insupport(θ) ? sum(logpdf.(dist(θ), data)) : -Inf
    
    # Construct a DensityModel.
    model = DensityModel(density)
    
    # Set up the sampler with a multivariate Gaussian proposal.
    σ² = 0.01
    spl = MALA(x -> MvNormal((σ² / 2) .* x, σ² * I))
    
    # Sample from the posterior.
    chain = sample(model, spl, 100000; initial_params=ones(2), chain_type=StructArray, param_names=["μ", "σ"])

Error Message

ERROR: MethodError: propose(::Random.TaskLocalRNG, ::MALA{RandomWalkProposal{false, var"#5#6"}}, ::DensityModel{typeof(density)}) is ambiguous.
Candidates:
  propose(rng::Random.AbstractRNG, ::MALA, model)
    @ AdvancedMH ~/.julia/packages/AdvancedMH/7JckQ/src/MALA.jl:22
  propose(rng::Random.AbstractRNG, sampler::AdvancedMH.MHSampler, model::Union{AbstractMCMC.LogDensityModel, DensityModel})
    @ AdvancedMH ~/.julia/packages/AdvancedMH/7JckQ/src/mh-core.jl:51
Possible fix, define
  propose(::Random.AbstractRNG, ::MALA, ::Union{AbstractMCMC.LogDensityModel, DensityModel})

Environment Information

  • Julia version: v"1.10.4"
  • AdvancedMH.jl version: v0.7.4
  • Distributions.jl version: v0.25.109
  • MCMCChains.jl version: v6.0.6
  • ForwardDiff.jl version: v0.10.36
  • StructArrays.jl version: v0.6.18

Additional Context

This code is taken directly from the README.md of the AdvancedMH.jl package. It seems that there might be an incompatibility between the current implementations of propose for MALA and DensityModel.

I also tried initializing the sampler with spl = MALA(MvNormal(2, sqrt(σ²))) or spl = MALA(σ²) instead of spl = MALA(x -> MvNormal((σ² / 2) .* x, σ² * I)), but this resulted in the same error.

Possible Solution

As suggested in the error message, defining a method for propose(::Random.AbstractRNG, ::MALA, ::Union{AbstractMCMC.LogDensityModel, DensityModel}) might resolve this ambiguity.

I would greatly appreciate any assistance in resolving this issue or guidance on how to properly use the MALA sampler with a DensityModel. Thank you for your time and effort in maintaining this package.

Adaptive proposals in Turing composite Gibbs sampler

Hi there, a question related to this PR

I'm currently facing an application where I would really like to use adaptive proposals like those defined in this PR in a Metropolis-within-Gibbs setting (i.e. we have a parameter vector x, for each parameter have an adaptive univariate proposal, and in each iteration of the MCMC sampler we update each component of the parameter vector conditional on the others using a Metropolis-Hastings step). The Turing-way to go would seem to use the stuff implemented in AdvancedMH in a Turing composite Gibbs sampler (something roughly like Gibbs(AdaptiveMH(:p1), AdaptiveMH(:p2), ...) where the p1, p2, ... are the parameter vector components)? I think in general this is worthwhile for low-dimensional applications where the gradient of the loglikelihood is really costly or unavailable. I wonder what would be the best way to proceed to allow this? Thanks for any hints!

Vectorised MH

I was wondering whether there is a vectorised implementation of MH (i.e. running several chains in parallel) available in this repo? I've seen that there is a vectorised implementation in AdvancedHMC but I couldn't find any information whether it's available for MH as well.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.