hasty-hamiltonian

Speedy gradient-based traversal through parameter space.
Log | Files | Refs | README | LICENSE

commit 78372fbccb60397a57bc0b33dd66f03f5fc64f9f
parent ed8667057890443dae758a6e58ca00799c9b2186
Author: Jared Tobin <jared@jtobin.ca>
Date:   Thu,  8 Oct 2015 08:43:11 +1300

Pick-up.

Diffstat:
MExamples/Example.hs | 2+-
AExamples/Rosenbrock_HMC.hs | 47+++++++++++++++++++++++++++++++++++++++++++++++
AExamples/SPDE_HMC.hs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
DNumeric/MCMC/Hamiltonian.hs | 139-------------------------------------------------------------------------------
ANumeric/Sampling/Hamiltonian.hs | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhasty-hamiltonian.cabal | 27++++++++++++++-------------
6 files changed, 260 insertions(+), 153 deletions(-)

diff --git a/Examples/Example.hs b/Examples/Example.hs @@ -17,7 +17,7 @@ rosenbrock :: Target rosenbrock = Target lRosenbrock glRosenbrock tunables :: Tunables -tunables = Tunables 0.05 20 +tunables = Tunables 0.001 50 options :: Options options = Options 5000 (fromList [1, 1]) diff --git a/Examples/Rosenbrock_HMC.hs b/Examples/Rosenbrock_HMC.hs @@ -0,0 +1,47 @@ +import Numeric.MCMC.Hamiltonian +import System.Random.MWC +import System.Environment +import System.Exit +import System.IO +import Numeric.AD +import Control.Monad + +target :: RealFloat a => [a] -> a +target [x0, x1] = (-1)*(5*(x1 - x0^2)^2 + 0.05*(1 - x0)^2) + +gTarget :: [Double] -> [Double] +gTarget = grad target + +main = do + args <- getArgs + when (args == []) $ do + putStrLn "(hasty-hamiltonian) Rosenbrock density " + putStrLn "Usage: ./Rosenbrock_HMC <numSteps> <thinEvery> <nDisc> <stepSize> <inits>" + putStrLn " " + putStrLn "numSteps : Number of Markov chain iterations to run. " + putStrLn "thinEvery : Print every nth iteration. " + putStrLn "nDisc : Number of discretizing steps to take. " + putStrLn "stepSize : Perturbation scaling parameter. " + putStrLn "inits : Filepath containing points at which to " + putStrLn " initialize the chain. " + exitSuccess + + inits <- fmap (map read . words) (readFile (args !! 4)) :: IO [Double] + + let nepochs = read (head args) :: Int + thinEvery = read (args !! 1) :: Int + nDisc = read (args !! 2) :: Double + eps = read (args !! 3) :: Double + params = Options target gTarget nDisc eps + config = MarkovChain inits 0 + + g <- create + results <- runChain params nepochs thinEvery config g + + hPutStrLn stderr $ + let nAcc = accepts results + total = nepochs + in show nAcc ++ " / " ++ show total ++ " (" ++ + show ((fromIntegral nAcc / fromIntegral total) :: Float) ++ + ") proposals accepted" + diff --git a/Examples/SPDE_HMC.hs b/Examples/SPDE_HMC.hs @@ -0,0 +1,55 @@ +{-# LANGUAGE BangPatterns #-} + +import Numeric.MCMC.Hamiltonian +import System.Random.MWC +import System.Environment +import System.Exit +import System.IO +import Control.Monad +import Numeric.AD + +target :: RealFloat a => [a] -> a +target xs = go 0 0 xs + where go t0 t1 [] = (- t0 / (2*h)) - (0.5 * h * t1) + go t0 t1 (u:us:uss) = go (t0 + (us - u)^2) (t1 + v (us + u)) uss + h = 1 / fromIntegral (length xs) + v x = (1 - x^2)^2 +{-# INLINE target #-} + +gTarget :: [Double] -> [Double] +gTarget = grad target +{-# INLINE gTarget #-} + +main = do + args <- getArgs + when (args == []) $ do + putStrLn "(hasty-hamiltonian) Stochastic partial differential equation " + putStrLn "Usage: ./SPDE_HMC <numSteps> <inits> <thinEvery> " + putStrLn " " + putStrLn "numSteps : Number of Markov chain iterations to run. " + putStrLn "thinEvery : Print every nth iteration. " + putStrLn "nDisc : Number of discretizing steps to take. " + putStrLn "stepSize : Perturbation scaling parameter. " + putStrLn "inits : Filepath containing points at which to " + putStrLn " initialize the chain. " + exitSuccess + + inits <- fmap (map read . words) (readFile (args !! 4)) :: IO [Double] + + let nepochs = read (head args) :: Int + thinEvery = read (args !! 1) :: Int + nDisc = read (args !! 2) :: Double + eps = read (args !! 3) :: Double + params = Options target gTarget nDisc eps + config = MarkovChain inits 0 + + g <- create + results <- runChain params nepochs thinEvery config g + + hPutStrLn stderr $ + let nAcc = accepts results + total = nepochs + in show nAcc ++ " / " ++ show total ++ " (" ++ + show ((fromIntegral nAcc / fromIntegral total) :: Float) ++ + ") proposals accepted" + diff --git a/Numeric/MCMC/Hamiltonian.hs b/Numeric/MCMC/Hamiltonian.hs @@ -1,139 +0,0 @@ -{-# OPTIONS_GHC -Wall #-} - --- | Hamiltonian Monte Carlo. See, ex: Neal (2012) --- http://arxiv.org/pdf/1206.1901.pdf. - -module Numeric.MCMC.Hamiltonian ( - Target(..) - , Tunables(..) - , Options(..) - , hamiltonian - , hmc - ) where - -import Control.Applicative -import Control.Monad -import Control.Monad.Primitive -import Control.Monad.Trans.Class -import Control.Monad.Trans.State.Strict -import Data.Vector (Vector) -import qualified Data.Vector as V -import System.Random.MWC -import System.Random.MWC.Distributions - - --- | The target we want to sample from. Consists of a log target and its --- gradient. -data Target = Target { - lTarget :: Parameters -> Double -- ^ log target - , glTarget :: Parameters -> Parameters -- ^ gradient of log target - } - -type Parameters = Vector Double - -data Tunables = Tunables { - stepSize :: !Double -- ^ step size for a given proposal - , leapfrogs :: !Int -- ^ number of leapfrog steps to take - } deriving Show - -data Options = Options { - epochs :: !Int -- ^ number of epochs to iterate the chain - , initial :: !Parameters -- ^ start location - } deriving Show - -type Particle = (Parameters, Parameters) - -type Transition m = StateT Parameters m Parameters - --- | The Hamiltonian transition operator. -hamiltonian - :: PrimMonad m - => Target - -> Tunables - -> Gen (PrimState m) - -> Transition m -hamiltonian target tunables g = do - q0 <- get - r0 <- V.replicateM (V.length q0) (lift $ standard g) - zc <- lift $ uniform g - let (q, r) = leapfrogIntegrator target tunables (q0, r0) - next = nextState target (q0, q) (r0, r) zc - put next - return next - --- | The leapfrog or Stormer-Verlet integrator. -leapfrogIntegrator :: Target -> Tunables -> Particle -> Particle -leapfrogIntegrator target tunables (q0, r0) = go q0 r0 l where - l = leapfrogs tunables - go q r 0 = (q, r) - go q r n = - let (q1, r1) = leapfrog target tunables (q, r) - in go q1 r1 (pred n) - --- | A single iteration of the leapfrog integrator. -leapfrog :: Target -> Tunables -> Particle -> Particle -leapfrog target tunables (q, r) = (qf, rf) where - rm = adjustMomentum target tunables (q, r) - qf = adjustPosition tunables (rm, q) - rf = adjustMomentum target tunables (qf, rm) - --- | Adjust momentum according to a half-leapfrog step. -adjustMomentum :: Target -> Tunables -> Particle -> Parameters -adjustMomentum target tunables (q, r) = r .+ ((0.5 * e) .* g q) where - e = stepSize tunables - g = glTarget target - --- | Adjust position according to a half-leapfrog step. -adjustPosition :: Tunables -> Particle -> Parameters -adjustPosition tunables (r, q) = q .+ (e .* r) where - e = stepSize tunables - --- | Scalar-vector multiplication. -(.*) :: Double -> Parameters -> Parameters -z .* xs = (* z) <$> xs - --- | Scalar-vector addition. -(.+) :: Parameters -> Parameters -> Parameters -xs .+ ys = V.zipWith (+) xs ys - --- | The next state of the Markov chain. -nextState - :: Target - -> Particle - -> Particle - -> Double - -> Parameters -nextState target position momentum z - | z < pAccept = snd position - | otherwise = fst position - where - pAccept = acceptProb target position momentum - --- | A target augmented by momentum auxilliary variables. -auxilliaryTarget :: Target -> Particle -> Double -auxilliaryTarget target (t, r) = f t - 0.5 * innerProduct r r where - f = lTarget target - --- | The acceptance probability of a move. -acceptProb :: Target -> Particle -> Particle -> Double -acceptProb target (q0, q1) (r0, r1) = exp . min 0 $ - auxilliaryTarget target (q1, r1) - auxilliaryTarget target (q0, r0) - --- | Simple inner product. -innerProduct :: Parameters -> Parameters -> Double -innerProduct xs ys = V.sum $ V.zipWith (*) xs ys - --- | Run a chain of HMC. -hmc - :: PrimMonad m - => Target - -> Tunables - -> Options - -> Gen (PrimState m) - -> m [Parameters] -hmc target tunables options g = do - let h = hamiltonian target tunables g - n = epochs options - q = initial options - evalStateT (replicateM n h) q - diff --git a/Numeric/Sampling/Hamiltonian.hs b/Numeric/Sampling/Hamiltonian.hs @@ -0,0 +1,143 @@ +{-# OPTIONS_GHC -Wall #-} + +-- | Hamiltonian Monte Carlo. See, ex: Neal (2012) +-- http://arxiv.org/pdf/1206.1901.pdf. + +module Numeric.Sampling.Hamiltonian ( + Target(..) + , Tunables(..) + , Options(..) + , hamiltonian + , hmc + ) where + +import Control.Applicative +import Control.Monad +import Control.Monad.Primitive +import Control.Monad.Trans.Class +import Control.Monad.Trans.State.Strict +import qualified Data.Foldable as Foldable +import Data.Sampling.Types +import Data.Vector (Vector) +import qualified Data.Vector as V +import System.Random.MWC +import System.Random.MWC.Distributions + +data Tunables = Tunables { + stepSize :: !Double -- ^ step size for a given proposal + , leapfrogs :: !Int -- ^ number of leapfrog steps to take + } deriving Show + +data Options = Options { + epochs :: !Int -- ^ number of epochs to iterate the chain + , initial :: !Parameters -- ^ start location + } deriving Show + +type Particle = (Parameters, Parameters) + +type Transition m = StateT Parameters m Parameters + +-- | The Hamiltonian transition operator. +hamiltonian + :: PrimMonad m + => Target + -> Tunables + -> Gen (PrimState m) + -> Transition m +hamiltonian target tunables g = do + q0 <- get + r0 <- V.replicateM (V.length q0) (lift $ standard g) + zc <- lift $ uniform g + let (q, r) = leapfrogIntegrator target tunables (q0, r0) + next = nextState target (q0, q) (r0, r) zc + put next + return next +{-# INLINE hamiltonian #-} + +-- | The leapfrog or Stormer-Verlet integrator. +leapfrogIntegrator :: Target -> Tunables -> Particle -> Particle +leapfrogIntegrator target tunables (q0, r0) = go q0 r0 l where + l = leapfrogs tunables + go q r 0 = (q, r) + go q r n = + let (q1, r1) = leapfrog target tunables (q, r) + in go q1 r1 (pred n) +{-# INLINE leapfrogIntegrator #-} + +-- | A single iteration of the leapfrog integrator. +leapfrog :: Target -> Tunables -> Particle -> Particle +leapfrog target tunables (q, r) = (qf, rf) where + rm = adjustMomentum target tunables (q, r) + qf = adjustPosition tunables (rm, q) + rf = adjustMomentum target tunables (qf, rm) +{-# INLINE leapfrog #-} + +-- | Adjust momentum according to a half-leapfrog step. +adjustMomentum :: Target -> Tunables -> Particle -> Parameters +adjustMomentum target tunables (q, r) = r .+ ((0.5 * e) .* g q) where + e = stepSize tunables + g = glTarget target +{-# INLINE adjustMomentum #-} + +-- | Adjust position according to a half-leapfrog step. +adjustPosition :: Tunables -> Particle -> Parameters +adjustPosition tunables (r, q) = q .+ (e .* r) where + e = stepSize tunables +{-# INLINE adjustPosition #-} + +-- | Scalar-vector multiplication. +(.*) :: Double -> Parameters -> Parameters +z .* xs = (* z) <$> xs +{-# INLINE (.*) #-} + +-- | Scalar-vector addition. +(.+) :: Parameters -> Parameters -> Parameters +xs .+ ys = V.zipWith (+) xs ys +{-# INLINE (.+) #-} + +-- | The next state of the Markov chain. +nextState + :: Target + -> Particle + -> Particle + -> Double + -> Parameters +nextState target position momentum z + | z < pAccept = snd position + | otherwise = fst position + where + pAccept = acceptProb target position momentum +{-# INLINE nextState #-} + +-- | A target augmented by momentum auxilliary variables. +auxilliaryTarget :: Target -> Particle -> Double +auxilliaryTarget target (t, r) = f t - 0.5 * innerProduct r r where + f = lTarget target +{-# INLINE auxilliaryTarget #-} + +-- | The acceptance probability of a move. +acceptProb :: Target -> Particle -> Particle -> Double +acceptProb target (q0, q1) (r0, r1) = exp . min 0 $ + auxilliaryTarget target (q1, r1) - auxilliaryTarget target (q0, r0) +{-# INLINE acceptProb #-} + +-- | Simple inner product. +innerProduct :: Parameters -> Parameters -> Double +innerProduct xs ys = V.sum $ V.zipWith (*) xs ys +{-# INLINE innerProduct #-} + +-- | Run a chain of HMC. +hmc + :: PrimMonad m + => Target + -> Tunables + -> Options + -> Gen (PrimState m) + -> m [Parameters] +hmc target tunables options g = do + let h = hamiltonian target tunables g + n = epochs options + q = initial options + evalStateT (replicateM n h) q +{-# INLINE hmc #-} + diff --git a/hasty-hamiltonian.cabal b/hasty-hamiltonian.cabal @@ -1,5 +1,5 @@ name: hasty-hamiltonian -version: 0.2.0.0 +version: 0.3.0.0 synopsis: Speedy traversal through parameter space. homepage: http://jtobin.github.com/hasty-hamiltonian license: BSD3 @@ -8,7 +8,7 @@ author: Jared Tobin maintainer: jared@jtobin.ca category: Numeric build-type: Simple -cabal-version: >=1.10 +cabal-version: >=1.18 Description: Speedy gradient-based traversal through parameter space. @@ -20,14 +20,15 @@ Source-repository head library default-language: Haskell2010 exposed-modules: - Numeric.MCMC.Hamiltonian + Numeric.Sampling.Hamiltonian build-depends: - base >= 4.7.0.1 && < 5 - , mtl >= 2.2.1 - , mwc-random >= 0.13.2.0 - , primitive >= 0.5.3.0 - , transformers >= 0.4.1.0 - , vector >= 0.10.11.0 + base + , mtl + , mcmc-types + , mwc-random + , primitive + , transformers + , vector ghc-options: -Wall @@ -39,9 +40,9 @@ Test-Suite hasty-examples ghc-options: -threaded -rtsopts -Wall -O2 build-depends: - ad >= 4.2.1 - , base >= 4.7.0.1 && < 5 - , hasty-hamiltonian >= 0.2.0.0 + ad + , base + , hasty-hamiltonian , mwc-random >= 0.13.2.0 - , vector >= 0.10.11.0 + , vector