API
AbstractMCMC defines an interface for sampling Markov chains.
Model
AbstractMCMC.AbstractModel — TypeAbstractModelAn AbstractModel represents a generic model type that can be used to perform inference.
AbstractMCMC.LogDensityModel — TypeLogDensityModel <: AbstractMCMC.AbstractModelWrapper around something that implements the LogDensityProblem.jl interface.
Note that this does not implement the LogDensityProblems.jl interface itself, but it simply useful for indicating to the sample and other AbstractMCMC methods that the wrapped object implements the LogDensityProblems.jl interface.
Fields
logdensity: The object that implements the LogDensityProblems.jl interface.
Sampler
AbstractMCMC.AbstractSampler — TypeAbstractSamplerThe AbstractSampler type is intended to be inherited from when implementing a custom sampler. Any persistent state information should be saved in a subtype of AbstractSampler.
When defining a new sampler, you should also overload the function transition_type, which tells the sample function what type of parameter it should expect to receive.
Sampling a single chain
StatsBase.sample — Methodsample(
rng::Random.AbatractRNG=Random.default_rng(),
model::AbstractModel,
sampler::AbstractSampler,
N_or_isdone;
kwargs...,
)Sample from the model with the Markov chain Monte Carlo sampler and return the samples.
If N_or_isdone is an Integer, exactly N_or_isdone samples are returned.
Otherwise, sampling is performed until a convergence criterion N_or_isdone returns true. The convergence criterion has to be a function with the signature
isdone(rng, model, sampler, samples, state, iteration; kwargs...)where state and iteration are the current state and iteration of the sampler, respectively. It should return true when sampling should end, and false otherwise.
Keyword arguments
See https://turinglang.org/AbstractMCMC.jl/dev/api/#Common-keyword-arguments for common keyword arguments.
StatsBase.sample — Methodsample(
rng::Random.AbstractRNG=Random.default_rng(),
logdensity,
sampler::AbstractSampler,
N_or_isdone;
kwargs...,
)Wrap the logdensity function in a LogDensityModel, and call sample with the resulting model instead of logdensity.
The logdensity function has to support the LogDensityProblems.jl interface.
Iterator
AbstractMCMC.steps — Methodsteps(
rng::Random.AbstractRNG=Random.default_rng(),
model::AbstractModel,
sampler::AbstractSampler;
kwargs...,
)Create an iterator that returns samples from the model with the Markov chain Monte Carlo sampler.
Examples
julia> struct MyModel <: AbstractMCMC.AbstractModel end
julia> struct MySampler <: AbstractMCMC.AbstractSampler end
julia> function AbstractMCMC.step(rng, ::MyModel, ::MySampler, state=nothing; kwargs...)
# all samples are zero
return 0.0, state
end
julia> iterator = steps(MyModel(), MySampler());
julia> collect(Iterators.take(iterator, 10)) == zeros(10)
trueAbstractMCMC.steps — Methodsteps(
rng::Random.AbstractRNG=Random.default_rng(),
logdensity,
sampler::AbstractSampler;
kwargs...,
)Wrap the logdensity function in a LogDensityModel, and call steps with the resulting model instead of logdensity.
The logdensity function has to support the LogDensityProblems.jl interface.
Transducer
AbstractMCMC.Sample — MethodSample(
rng::Random.AbstractRNG=Random.default_rng(),
model::AbstractModel,
sampler::AbstractSampler;
kwargs...,
)Create a transducer that returns samples from the model with the Markov chain Monte Carlo sampler.
Examples
julia> struct MyModel <: AbstractMCMC.AbstractModel end
julia> struct MySampler <: AbstractMCMC.AbstractSampler end
julia> function AbstractMCMC.step(rng, ::MyModel, ::MySampler, state=nothing; kwargs...)
# all samples are zero
return 0.0, state
end
julia> transducer = Sample(MyModel(), MySampler());
julia> collect(transducer(1:10)) == zeros(10)
trueAbstractMCMC.Sample — MethodSample(
rng::Random.AbstractRNG=Random.default_rng(),
logdensity,
sampler::AbstractSampler;
kwargs...,
)Wrap the logdensity function in a LogDensityModel, and call Sample with the resulting model instead of logdensity.
The logdensity function has to support the LogDensityProblems.jl interface.
Sampling multiple chains in parallel
StatsBase.sample — Methodsample(
rng::Random.AbstractRNG=Random.default_rng(),
model::AbstractModel,
sampler::AbstractSampler,
parallel::AbstractMCMCEnsemble,
N::Integer,
nchains::Integer;
kwargs...,
)Sample nchains Monte Carlo Markov chains from the model with the sampler in parallel using the parallel algorithm, and combine them into a single chain.
Keyword arguments
See https://turinglang.org/AbstractMCMC.jl/dev/api/#Common-keyword-arguments for common keyword arguments.
StatsBase.sample — Methodsample(
rng::Random.AbstractRNG=Random.default_rng(),
logdensity,
sampler::AbstractSampler,
parallel::AbstractMCMCEnsemble,
N::Integer,
nchains::Integer;
kwargs...,
)Wrap the logdensity function in a LogDensityModel, and call sample with the resulting model instead of logdensity.
The logdensity function has to support the LogDensityProblems.jl interface.
Two algorithms are provided for parallel sampling with multiple threads and multiple processes, and one allows for the user to sample multiple chains in serial (no parallelization):
AbstractMCMC.MCMCThreads — TypeMCMCThreadsThe MCMCThreads algorithm allows users to sample MCMC chains in parallel using multiple threads.
Usage
sample(model, sampler, MCMCThreads(), N, nchains)See also sample.
AbstractMCMC.MCMCDistributed — TypeMCMCDistributedThe MCMCDistributed algorithm allows users to sample MCMC chains in parallel using multiple processes.
Usage
sample(model, sampler, MCMCDistributed(), N, nchains)See also sample.
AbstractMCMC.MCMCSerial — TypeMCMCSerialThe MCMCSerial algorithm allows users to sample serially, with no thread or process parallelism.
Usage
sample(model, sampler, MCMCSerial(), N, nchains)See also sample.
Common keyword arguments
The description of keyword arguments here is fairly generic, since it only pertains to the AbstractMCMC interface. If you are specifically interested in sampling with Turing.jl, there is a dedicated documentation page for that.
Common keyword arguments for regular and parallel sampling are:
progress(default:AbstractMCMC.PROGRESS[]which istrueinitially): toggles progress logging. See the section on Progress logging below for more details.chain_type(default:Any): determines the type of the returned chaincallback(default:nothing): ifcallback !== nothing, thencallback(rng, model, sampler, sample, iteration; kwargs...)is called after every sampling step, wheresampleis the most recent sample of the Markov chain anditerationis the current iteration- Keyword arguments
kwargs...are passed down from the call tosample(...). If you are performing multiple-chain sampling, thenkwargsadditionally contains achain_numberkeyword argument, which runs from 1 to the number of chains. This is not present when performing single-chain sampling.
- Keyword arguments
num_warmup(default:0): number of "warm-up" steps to take before the first "regular" step, i.e. number of times to callAbstractMCMC.step_warmupbefore the first call toAbstractMCMC.step.discard_initial(default:num_warmup): number of initial samples that are discarded. Note that ifdiscard_initial < num_warmup, warm-up samples will also be included in the resulting samples.thinning(default:1): factor by which to thin samples.initial_state(default:nothing): ifinitial_state !== nothing, the first call toAbstractMCMC.stepis passedinitial_stateas thestateargument.
The common keyword arguments progress, chain_type, and callback are not supported by the iterator AbstractMCMC.steps and the transducer AbstractMCMC.Sample.
There is no "official" way for providing initial parameter values yet. However, multiple packages such as EllipticalSliceSampling.jl and AdvancedMH.jl support an initial_params keyword argument for setting the initial values when sampling a single chain. To ensure that sampling multiple chains "just works" when sampling of a single chain is implemented, we decided to support initial_params in the default implementations of the ensemble methods:
initial_params(default:nothing): ifinitial_params isa AbstractArray, then theith element ofinitial_paramsis used as initial parameters of theith chain. If one wants to use the same initial parametersxfor every chain, one can specify e.g.initial_params = FillArrays.Fill(x, N).
Progress logging
Progress logging is controlled in one of two ways:
- by passing the
progresskeyword argument to thesample(...)function, or - by globally changing the defaults with
AbstractMCMC.setprogress!andAbstractMCMC.setmaxchainsprogress!.
progress keyword argument
For single-chain sampling (i.e., sample([rng,] model, sampler, N)), as well as multiple-chain sampling with MCMCSerial, the progress keyword argument should be a Bool.
For multiple-chain sampling using MCMCThreads, there are several, more detailed, options:
:overall: create one progress bar for the overall sampling process, which tracks the percentage of samples that have been sampled across all chains:perchain: in addition to:overall, also create one progress bar for each individual chain:none: do not create any progress bartrue(the default): same as:overall, i.e. one progress bar for the overall sampling processfalse: same as:none, i.e. no progress bar
Multiple-chain sampling using MCMCDistributed behaves the same as MCMCThreads, except that :perchain is not (yet?) implemented.
If you are implementing your own methods for sample(...), you should make sure to not override the progress keyword argument if you want progress logging in multi-chain sampling to work correctly, as the multi-chain sample() call makes sure to specifically pass custom values of progress to the single-chain calls.
Global settings
If you are sampling multiple times and would like to change the default behaviour, you can use this function to control progress logging globally:
AbstractMCMC.setprogress! — Functionsetprogress!(progress::Bool; silent::Bool=false)Enable progress logging globally if progress is true, and disable it otherwise. Optionally disable informational message if silent is true.
setprogress! is more general, and applies to all types of sampling (both single- and multiple-chain). It only takes a boolean argument, which switches progress logging on or off. For example, setprogress!(false) will disable all progress logging.
Note that setprogress! cannot be used to set the type of progress bar for multiple-chain sampling. If you want to use :perchain, it has to be set on each individual call to sample.
Chains
The chain_type keyword argument allows to set the type of the returned chain. A common choice is to return chains of type Chains from MCMCChains.jl.
AbstractMCMC defines the abstract type AbstractChains for Markov chains.
AbstractMCMC.AbstractChains — TypeAbstractChainsAbstractChains is an abstract type for an object that stores parameter samples generated through a MCMC process.
The following two functions exist for converting AbstractChains from and to matrices of samples.
AbstractMCMC.to_samples — FunctionAbstractMCMC.to_samples(::Type{T}, chains::AbstractChains) where {T}Convert an AbstractChains object to an Matrix of parameter samples.
Methods of this function should be implemented with the signature listed above, and should return a Matrix{T}.
See also: from_samples.
AbstractMCMC.from_samples — FunctionAbstractMCMC.from_samples(::Type{T}, samples::Matrix) where {T<:AbstractChains}Convert a Matrix of parameter samples to an AbstractChains object.
Methods of this function should be implemented with the signature listed above, and should return a chain of type T.
This function is the inverse of to_samples.
In general, it is expected that for chains::Tchn, from_samples(Tchn, to_samples(Tsample, chains)) contains data that are equivalent to that in chains (although differences in e.g. metadata or ordering are permissible).
Furthermore, the same should hold true for to_samples(Tsample, from_samples(Tchn, samples)) where samples::Matrix{Tsample}.
For chains of this type, AbstractMCMC defines the following two methods.
AbstractMCMC.chainscat — Functionchainscat(c::AbstractChains...)Concatenate multiple chains.
By default, the chains are concatenated along the third dimension by calling cat(c...; dims=3).
AbstractMCMC.chainsstack — Functionchainsstack(c::AbstractVector)Stack chains in c.
By default, the vector of chains is returned unmodified. If eltype(c) <: AbstractChains, then reduce(chainscat, c) is called.
Interacting with states of samplers
To make it a bit easier to interact with some arbitrary sampler state, we encourage implementations of AbstractSampler to implement the following methods:
AbstractMCMC.getparams — Functiongetparams([model::AbstractModel, ]state)::Vector{<:Real}Retrieve the values of parameters from the sampler's state as a Vector{<:Real}.
AbstractMCMC.setparams!! — Functionsetparams!!([model::AbstractModel, ]state, params)Set the values of parameters in the sampler's state from a Vector{<:Real}.
This function should follow the BangBang interface: mutate state in-place if possible and return the mutated state. Otherwise, it should return a new state containing the updated parameters.
Although not enforced, it should hold that setparams!!(state, getparams(state)) == state. In other words, the sampler should implement a consistent transformation between its internal representation and the vector representation of the parameter values.
Sometimes, to maintain the consistency of the log density and parameter values, a model should be provided. This is useful for samplers that need to evaluate the log density at the new parameter values.
getparams and setparams!! provide a generic interface for interacting with the parameters of a sampler's state, regardless of how that state is represented internally.
This allows generic code to be written that works with any sampler implementing this interface. For example, a generic ensemble sampler could use getparams to extract the parameters from each of its component samplers' states, and setparams!! to initialize each component sampler with a different set of parameters.
The optional model argument to these functions allows sampler implementations to customize their behavior based on the model being used. For example, some samplers may need to evaluate the log density at new parameter values when setting parameters, which requires access to the model. If access to model is not needed, the sampler only needs to implement the version without the model argument - the default implementations will then call those methods directly.
These methods are particularly useful for implementing samplers which wrap some inner samplers, such as a mixture of samplers. In the next section, we will see how getparams and setparams!! can be used to implement a MixtureSampler.
Example: MixtureSampler
In a MixtureSampler we need two things:
components: collection of samplers.weights: collection of weights representing the probability of choosing the corresponding sampler.
struct MixtureSampler{W,C} <: AbstractMCMC.AbstractSampler
components::C
weights::W
endTo implement the state, we need to keep track of a couple of things:
index: the index of the sampler used in thisstep.states: the current states of all the components.
We need to keep track of the states of all components rather than just the state for the sampler we used previously. The reason is that lots of samplers keep track of more than just the previous realizations of the variables, e.g. in AdvancedHMC.jl we keep track of the momentum used, the metric used, etc.
struct MixtureState{S}
index::Int
states::S
endThe step for a MixtureSampler is defined by the following generative process
\[\begin{aligned} i &\sim \mathrm{Categorical}(w_1, \dots, w_k) \\ X_t &\sim \mathcal{K}_i(\cdot \mid X_{t - 1}) \end{aligned}\]
where $\mathcal{K}_i$ denotes the i-th kernel/sampler, and $w_i$ denotes the weight/probability of choosing the i-th sampler. AbstractMCMC.getparams and AbstractMCMC.setparams!! comes into play in defining/computing $\mathcal{K}_i(\cdot \mid X_{t - 1})$ since $X_{t - 1}$ could be coming from a different sampler.
If we let state be the current MixtureState, i the current component, and i_prev is the previous component we sampled from, then this translates into the following piece of code:
# Update the corresponding state, i.e. `state.states[i]`, using
# the state and transition from the previous iteration.
state_current = AbstractMCMC.setparams!!(
state.states[i],
AbstractMCMC.getparams(state.states[i_prev]),
)
# Take a `step` for this sampler using the updated state.
transition, state_current = AbstractMCMC.step(
rng, model, sampler_current, sampler_state;
kwargs...
)The full AbstractMCMC.step implementation would then be something like:
function AbstractMCMC.step(rng, model::AbstractMCMC.AbstractModel, sampler::MixtureSampler, state; kwargs...)
# Sample the component to use in this `step`.
i = rand(Categorical(sampler.weights))
sampler_current = sampler.components[i]
# Update the corresponding state, i.e. `state.states[i]`, using
# the state and transition from the previous iteration.
i_prev = state.index
state_current = AbstractMCMC.setparams!!(
state.states[i],
AbstractMCMC.getparams(state.states[i_prev]),
)
# Take a `step` for this sampler using the updated state.
transition, state_current = AbstractMCMC.step(
rng, model, sampler_current, state_current;
kwargs...
)
# Create the new states.
# NOTE: Code below will result in `states_new` being a `Vector`.
# If we wanted to allow usage of alternative containers, e.g. `Tuple`,
# it would be better to use something like `@set states[i] = state_current`
# where `@set` is from Setfield.jl.
states_new = map(1:length(state.states)) do j
if j == i
# Replace the i-th state with the new one.
state_current
else
# Otherwise we just carry over the previous ones.
state.states[j]
end
end
# Create the new `MixtureState`.
state_new = MixtureState(i, states_new)
return transition, state_new
endAnd for the initial AbstractMCMC.step we have:
function AbstractMCMC.step(rng, model::AbstractMCMC.AbstractModel, sampler::MixtureSampler; kwargs...)
# Initialize every state.
transitions_and_states = map(sampler.components) do spl
AbstractMCMC.step(rng, model, spl; kwargs...)
end
# Sample the component to use this `step`.
i = rand(Categorical(sampler.weights))
# Extract the corresponding transition.
transition = first(transitions_and_states[i])
# Extract states.
states = map(last, transitions_and_states)
# Create new `MixtureState`.
state = MixtureState(i, states)
return transition, state
endSuppose we then wanted to use this with some of the packages which implements AbstractMCMC.jl's interface, e.g. AdvancedMH.jl, then we'd simply have to implement getparams and setparams!!.
To use MixtureSampler with two samplers sampler1 and sampler2 from AdvancedMH.jl as components, we'd simply do
sampler = MixtureSampler([sampler1, sampler2], [0.1, 0.9])
transition, state = AbstractMCMC.step(rng, model, sampler)
while ...
transition, state = AbstractMCMC.step(rng, model, sampler, state)
end