mcmc-0.8.1.0: src/Mcmc/Proposal/Hamiltonian/Hamiltonian.hs
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module : Mcmc.Proposal.Hamiltonian.Hamiltonian
-- Description : Hamiltonian Monte Carlo proposal
-- Copyright : 2021 Dominik Schrempf
-- License : GPL-3.0-or-later
--
-- Maintainer : dominik.schrempf@gmail.com
-- Stability : experimental
-- Portability : portable
--
-- Creation date: Mon Jul 5 12:59:42 2021.
--
-- The Hamiltonian Monte Carlo (HMC) proposal.
--
-- The HMC proposal acts on 'Positions', a vector of floating point values. The
-- manipulated values can represent the complete state, or a subset of the
-- complete state. Functions converting the state to and from this vector have
-- to be provided; see 'HStructure'.
--
-- Even though the proposal may only act on a subset of the complete state, the
-- prior, likelihood, and Jacobian functions of the complete state have to be
-- provided; see 'HTarget'. This is because parameters not manipulated by the
-- HMC proposal still influence the prior, likelihood and Jacobian functions.
--
-- The points given above have implications on how the HMC proposal is handled:
-- Do not use 'liftProposalWith', 'liftProposal', or '(@~)' with the HMC
-- proposal; instead provide proper conversion functions with 'HStructure'.
--
-- The gradient of the log target function is calculated using automatic
-- differentiation; see the excellent
-- [ad](https://hackage.haskell.org/package/ad) package.
--
-- The desired acceptance rate is 0.65, although the dimension of the proposal
-- is high.
--
-- The speed of this proposal changes drastically with the leapfrog trajectory
-- length and the leapfrog scaling factor. Hence, the speed will change during
-- burn in.
--
-- References:
--
-- - [1] Chapter 5 of Handbook of Monte Carlo: Neal, R. M., MCMC Using
-- Hamiltonian Dynamics, In S. Brooks, A. Gelman, G. Jones, & X. Meng (Eds.),
-- Handbook of Markov Chain Monte Carlo (2011), CRC press.
--
-- - [2] Gelman, A., Carlin, J. B., Stern, H. S., & Rubin, D. B., Bayesian data
-- analysis (2014), CRC Press.
--
-- - [3] Review by Betancourt and notes: Betancourt, M., A conceptual
-- introduction to Hamiltonian Monte Carlo, arXiv, 1701–02434 (2017).
--
-- - [4] Matthew D. Hoffman, Andrew Gelman (2014) The No-U-Turn Sampler:
-- Adaptively Setting Path Lengths in Hamiltonian Monte Carlo, Journal of
-- Machine Learning Research.
module Mcmc.Proposal.Hamiltonian.Hamiltonian
( -- * Hamiltonian Monte Carlo proposal
HParams (..),
defaultHParams,
hamiltonian,
)
where
import Data.Bifunctor
import Mcmc.Acceptance
import Mcmc.Algorithm.MHG
import Mcmc.Proposal
import Mcmc.Proposal.Hamiltonian.Common
import Mcmc.Proposal.Hamiltonian.Internal
import Mcmc.Proposal.Hamiltonian.Masses
import Numeric.AD.Double
import qualified Numeric.LinearAlgebra as L
import Numeric.Log
import System.Random.Stateful
-- | Parameters of the Hamilton Monte Carlo proposal.
--
-- If a parameter is 'Nothing', a default value is used (see 'defaultHParams').
data HParams = HParams
{ hLeapfrogScalingFactor :: Maybe LeapfrogScalingFactor,
hLeapfrogSimulationLength :: Maybe LeapfrogSimulationLength,
hMasses :: Maybe Masses
}
deriving (Show)
-- | Default parameters.
--
-- - Estimate a reasonable leapfrog scaling factor using Algorithm 4 [4]. If all
-- fails, use 0.1.
--
-- - Leapfrog simulation length is set to 0.5.
--
-- - The mass matrix is set to the identity matrix.
defaultHParams :: HParams
defaultHParams = HParams Nothing Nothing Nothing
hamiltonianPFunctionWithTuningParameters ::
Traversable s =>
Dimension ->
HStructure s ->
(s Double -> Target) ->
TuningParameter ->
AuxiliaryTuningParameters ->
Either String (PFunction (s Double))
hamiltonianPFunctionWithTuningParameters d hstruct targetWith _ ts = do
hParamsI <- fromAuxiliaryTuningParameters d ts
pure $ hamiltonianPFunction hParamsI hstruct targetWith
-- The inverted covariance matrix and the log determinant of the covariance
-- matrix are calculated by 'hamiltonianPFunction'.
hamiltonianPFunction ::
HParamsI ->
HStructure s ->
(s Double -> Target) ->
PFunction (s Double)
hamiltonianPFunction hparamsi hstruct targetWith x g = do
p <- generateMomenta mus ms g
eRan <- uniformRM (eL, eR) g
-- NOTE: The NUTS paper does not sample l since l varies naturally because
-- of epsilon. I still think it should vary because otherwise, there may be
-- dragons due to periodicity.
let lM = la / eRan
lL = max (1 :: Int) (floor $ 0.9 * lM)
lR = max lL (ceiling $ 1.1 * lM)
lRan <- uniformRM (lL, lR) g
case leapfrog (targetWith x) msI lRan eRan q p of
-- NOTE: I am not sure if it is correct to set the expected acceptance rate
-- to 0 when the leapfrog integrator fails.
Nothing -> pure (ForceReject, Just $ AcceptanceRates 0 1)
-- Check if next state is accepted here, because the Jacobian is included in
-- the target function. If not: pure (x, 0.0, 1.0).
Just (q', p', prQ, prQ') -> do
let -- Prior of momenta.
prP = exponentialKineticEnergy msI p
prP' = exponentialKineticEnergy msI p'
r = prQ' * prP' / (prQ * prP)
accept <- mhgAccept r g
-- NOTE: For example, Neal page 12: In order for the Hamiltonian proposal
-- to be in detailed balance, the momenta have to be negated before
-- proposing the new value. That is, the negated momenta would guide the
-- chain back to the previous state. However, we are only interested in
-- the positions, and are not even storing the momenta.
let pr = if accept then ForceAccept (fromVec x q') else ForceReject
-- Limit expected acceptance rate between 0 and 1.
ar = max 0 $ min 1 (exp $ ln r)
pure (pr, Just $ AcceptanceRates ar 1)
where
(HParamsI e la ms _ _ msI mus) = hparamsi
(HStructure _ toVec fromVec) = hstruct
q = toVec x
eL = 0.9 * e
eR = 1.1 * e
-- | Hamiltonian Monte Carlo proposal.
--
-- The structure of the state is denoted as @s@.
--
-- May call 'error' during initialization.
hamiltonian ::
Traversable s =>
HParams ->
HTuningConf ->
HStructure s ->
HTarget s ->
PName ->
PWeight ->
Proposal (s Double)
hamiltonian hparams htconf hstruct htarget n w =
let -- Misc.
desc = PDescription "Hamiltonian Monte Carlo (HMC)"
(HStructure sample toVec fromVec) = hstruct
dim = L.size $ toVec sample
-- See bottom of page 1615 in [4].
pDim = PSpecial dim 0.65
-- Vectorize and derive the target function.
(HTarget mPrF lhF mJcF) = htarget
tF y = case (mPrF, mJcF) of
(Nothing, Nothing) -> lhF y
(Just prF, Nothing) -> prF y * lhF y
(Nothing, Just jcF) -> lhF y * jcF y
(Just prF, Just jcF) -> prF y * lhF y * jcF y
tFnG = grad' (ln . tF)
targetWith x = bimap Exp toVec . tFnG . fromVec x
(HParams mEps mLa mMs) = hparams
hParamsI =
either error id $
hParamsIWith (targetWith sample) (toVec sample) mEps mLa mMs
ps = hamiltonianPFunction hParamsI hstruct targetWith
hamiltonianWith = Proposal n desc PSlow pDim w ps
-- Tuning.
ts = toAuxiliaryTuningParameters hParamsI
tuner = do
tfun <- hTuningFunctionWith dim toVec htconf
let pfun = hamiltonianPFunctionWithTuningParameters dim hstruct targetWith
pure $ Tuner 1.0 ts True True tfun pfun
in case checkHStructureWith (hpsMasses hParamsI) hstruct of
Just err -> error err
Nothing -> hamiltonianWith tuner