packages feed

goal-probability-0.1: Goal/Probability/Graphical/Harmonium.hs

-- | Exponential Family 'Harmonium's and gibbs sampling.
module Goal.Probability.Graphical.Harmonium
    ( -- * Harmoniums
      Harmonium (Harmonium)
    -- ** Type Synonyms
    , NaturalFunction
    -- ** Structural Manipulation
    , splitHarmonium
    , joinHarmonium
    , harmoniumTranspose
    -- ** Conditional Distribution Functions
    , conditionalLatentDistribution
    , conditionalObservableDistribution
    , conditionalLatentDistributions
    , conditionalObservableDistributions
    -- ** Gibbs Sampling
    , bulkGibbsSampling
    , bulkGibbsSampling0
    -- * Transducers
    , buildNormalTransducer
    , buildReplicatedNormalTransducer
    , modulateTransducerGain
    , modulateHarmoniumBelief
    ) where


--- Imports ---


-- Goal --

import Goal.Geometry

import Goal.Probability.Statistical
import Goal.Probability.ExponentialFamily
import Goal.Probability.Distributions
import Goal.Probability.Graphical

import System.Random.MWC.Monad
import qualified Data.Vector.Storable as C


--- Types ---

-- | A quadratic function in the product space of two exponential families.
data Harmonium m n = Harmonium m n deriving (Eq, Read, Show)

-- Datatype manipulation --

splitHarmonium :: (Manifold m, Manifold n)
    => Function c d :#: Harmonium m n -> (d :#: m, Function c d :#: Tensor m n, Dual c :#: n)
-- | Splits a 'Harmonium' into its components parts of a 'Tensor' and a pair of biases.
splitHarmonium qdc =
    let (Harmonium m n) = manifold qdc
        tns = Tensor m n
        (mcs,css') = C.splitAt (dimension m) $ coordinates qdc
        (mtxcs,ncs) = C.splitAt (dimension tns) css'
     in (fromCoordinates m mcs, fromCoordinates tns mtxcs, fromCoordinates n ncs)

joinHarmonium
    :: (Manifold m, Manifold n) => d :#: m -> Function c d :#: Tensor m n -> Dual c :#: n -> Function c d :#: Harmonium m n
-- | Assembles a 'Harmonium' out of the components of the quadratic function.
joinHarmonium dm mtx cn =
    let (Tensor m n) = manifold mtx
     in fromCoordinates (Harmonium m n) $ coordinates dm C.++ coordinates mtx C.++ coordinates cn

harmoniumTranspose :: (Manifold n, Manifold m, Primal c, Primal d)
    => Function c d :#: Harmonium m n -> Function (Dual d) (Dual c) :#: Harmonium n m
-- | Transposes the 'Tensor' in the 'Harmonium' and swaps the biases.
harmoniumTranspose qdc =
    let (dm,mtx,dn) = splitHarmonium qdc
     in joinHarmonium dn (matrixTranspose mtx) dm


--- Functions ---


conditionalLatentDistributions :: (Manifold m, ExponentialFamily n)
    => NaturalFunction :#: Harmonium m n -> [Sample n] -> [Natural :#: m]
-- | Calculates the latent distributions given some observations.
conditionalLatentDistributions p os =
    let (Harmonium _ n) = manifold p
     in p >$> (sufficientStatistic n <$> os)

conditionalObservableDistributions :: (ExponentialFamily m, Manifold n)
    => NaturalFunction :#: Harmonium m n -> [Sample m] -> [Natural :#: n]
-- | Calculates the observable distributions given some latent states.
conditionalObservableDistributions p ls =
    let (Harmonium m _) = manifold p
     in harmoniumTranspose p >$> (sufficientStatistic m <$> ls)

conditionalLatentDistribution :: (Manifold m, ExponentialFamily n)
    => NaturalFunction :#: Harmonium m n -> Sample n -> Natural :#: m
-- | Calculates the latent distributions given an observation.
conditionalLatentDistribution p o =
    let (Harmonium _ n) = manifold p
     in p >.> sufficientStatistic n o

conditionalObservableDistribution :: (ExponentialFamily m, Manifold n)
    => NaturalFunction :#: Harmonium m n -> Sample m -> Natural :#: n
-- | Calculates the observable distributions given a latent state.
conditionalObservableDistribution p l =
    let (Harmonium m _) = manifold p
     in harmoniumTranspose p >.> sufficientStatistic m l

bulkGibbsSampling
    :: (ExponentialFamily m, Generative Natural m, ExponentialFamily n, Generative Natural n)
    => Int -> NaturalFunction :#: Harmonium m n -> [Sample n] -> RandST s [[(Sample m, Sample n)]]
-- | Returns a Markov chain over the latent and observable states generated by Gibbs sampling.
bulkGibbsSampling k0 p o0s = do
    l0s <- mapM generate $ conditionalLatentDistributions p o0s
    gbs <- gibbsSampler k0 l0s []
    return $ zip l0s o0s : gbs
        where (Harmonium m n) = manifold p
              gibbsSampler 0 _ acc = return $ reverse acc
              gibbsSampler k ls acc = do
                  let mls = sufficientStatistic m <$> ls
                  os' <- mapM generate $ harmoniumTranspose p >$> mls
                  let mos' = sufficientStatistic n <$> os'
                  ls' <- mapM generate $ p >$> mos'
                  gibbsSampler (k-1) ls' (zip ls' os':acc)

bulkGibbsSampling0
    :: (ExponentialFamily m, Generative Natural m, ExponentialFamily n, Generative Natural n)
    => Int -> NaturalFunction :#: Harmonium m n -> [Mixture :#: n] -> RandST s [[(Mixture :#: m, Mixture :#: n)]]
-- | Returns a Markov chain over the latent and observable expoential families generated by Gibbs sampling.
bulkGibbsSampling0 k0 p mo0s = gibbsSampler k0 mo0s []
    where (Harmonium m n) = manifold p
          gibbsSampler 0 mos acc = return . reverse $ zip (potentialMapping <$> (p >$> mos)) mos:acc
          gibbsSampler k mos acc = do
              ls <- mapM generate $ p >$> mos
              let mls = sufficientStatistic m <$> ls
              os' <- mapM generate $ harmoniumTranspose p >$> mls
              let mos' = sufficientStatistic n <$> os'
              gibbsSampler (k-1) mos' (zip mls mos:acc)

modulateHarmoniumBelief :: (Manifold m, Manifold n)
    => Mixture :#: m
    -> NaturalFunction :#: Harmonium m n
    -> NaturalFunction :#: Harmonium m n
-- | Adds the projection of the given belief to the biases over the state.
modulateHarmoniumBelief z trns =
    let (lb,mtx,ob) = splitHarmonium trns
     in joinHarmonium lb mtx $ ob <+> matrixTranspose mtx >.> z


--- Transducers ---

normalBias :: (Standard :#: Normal) -> Double
normalBias sp =
    let [mu,vr] = listCoordinates sp
     in - mu^2/(2*vr)

buildNormalTransducer
    :: [Standard :#: Normal] -> NaturalFunction :#: Harmonium (Replicated Poisson) Normal
-- | Builds a Transducer (i.e. Population Code) which is a 'Harmonium' with
-- a 'Replicated' 'Poisson' latent 'Manifold'. Here the observable 'Normal'
-- is 'Normal'.
buildNormalTransducer sps =
    let nps = chart Natural . transition <$> sps
        rp = Replicated Poisson $ length nps
        lb = fromList rp $ normalBias <$> sps
        ob = fromList Normal $ replicate 2 0
        tns = fromCoordinates (Tensor rp Normal) . C.concat $ coordinates <$> nps
     in joinHarmonium lb tns ob

buildReplicatedNormalTransducer
    :: [Standard :#: Replicated Normal] -> NaturalFunction :#: Harmonium (Replicated Poisson) (Replicated Normal)
-- | Builds a Transducer (i.e. Population Code) which is a 'Harmonium' with
-- a 'Replicated' 'Poisson' latent 'Manifold'. Here the observable 'Normal'
-- is 'Replicated' 'Normal'.
buildReplicatedNormalTransducer sps =
    let nps = chart Natural . transition <$> sps
        m = manifold $ head sps
        rp = Replicated Poisson $ length nps
        lb = fromList rp $ sum . mapReplicated normalBias <$> sps
        ob = fromList m $ replicate (dimension m) 0
        tns = fromCoordinates (Tensor rp m) . C.concat $ coordinates <$> nps
     in joinHarmonium lb tns ob

modulateTransducerGain :: Manifold n
    => Double
    -> NaturalFunction :#: Harmonium (Replicated Poisson) n
    -> NaturalFunction :#: Harmonium (Replicated Poisson) n
-- | Multiplies the current gain of the transducer by the given value.
-- Transducers are intially constructed with a gain of 1, and so initially
-- this will simply set the gain.
modulateTransducerGain gn trns =
    let (lb,mtx,ob) = splitHarmonium trns
        lb' = alterCoordinates (+ log gn) lb
     in joinHarmonium lb' mtx ob


--- Instances ---


-- Harmoniums --

instance (Manifold m, Manifold n) => Manifold (Harmonium m n) where
    dimension (Harmonium m n) = dimension m * dimension n + dimension m + dimension n

instance (Manifold m, Manifold n) => Map (Harmonium m n) where
    type Domain (Harmonium m n) = n
    domain (Harmonium _ n) = n
    type Codomain (Harmonium m n) = m
    codomain (Harmonium m _) = m

instance (Manifold m, Manifold n) => Apply c d (Harmonium m n) where
    (>.>) p x =
        let (lb,mtxp,_) = splitHarmonium p
         in lb <+> (mtxp >.> x)
    (>$>) p xs =
        let (lb,mtxp,_) = splitHarmonium p
         in (lb <+>) <$> (mtxp >$> xs)