packages feed

synapse-0.1.0.0: src/Synapse/NN/Layers/Initializers.hs

{- | Allows to initialize values of layers parameters.

'InitializerFn' type alias represents functions that are able to initialize matrix with given size
and 'Initializer' newtype wraps 'InitializerFn's.

"Synapse" provides 4 types of initializers:
* Non-random constant initializers
* Random uniform distribution initializers
* Random normal distribution initializers
* Matrix-specific initializers
-}


module Synapse.NN.Layers.Initializers
    ( -- * 'InitializerFn' type alias and 'Initializer' newtype

      InitializerFn

    , Initializer (Initializer, unInitializer)
        
      -- * Non-random constant initializers

    , constants
    , zeroes
    , ones

      -- * Random uniform distribution initializers

    , randomUniform
    , lecunUniform
    , heUniform
    , glorotUniform

      -- * Random normal distribution initializers
    
    , randomNormal
    , lecunNormal
    , heNormal
    , glorotNormal

      -- * Matrix-like initializers
    
    , identity
    , orthogonal
    ) where


import Synapse.Tensors.Mat (Mat)
import qualified Synapse.Tensors.Mat as M

import System.Random (uniformListR, uniformRs, UniformRange, RandomGen)


-- | 'InitializerFn' type alias represents functions that are able to initialize matrix with given size.
type InitializerFn a = (Int, Int) -> Mat a


-- | 'Initializer' newtype wraps 'InitializerFn's - functions that are able to initialize matrix with given size.
newtype Initializer a = Initializer 
    { unInitializer :: InitializerFn a  -- ^ Unwraps 'Initializer' newtype.
    }


-- Non-random constant initializers

-- | Initializes list that is filled with given constant.
constants :: Num a => a -> InitializerFn a
constants c (input, output) = M.replicate (input, output) c

-- | Initializes list that is filled with zeroes.
zeroes :: Num a => InitializerFn a
zeroes = constants 0

-- | Initializes list that is filled with ones.
ones :: Num a => InitializerFn a
ones = constants 1


-- Random uniform distribution initializers

{- | Initializes list with samples from random uniform distribution in range.

This function does not preserve seed generator - split generator before calling this function.
-}
randomUniform :: (UniformRange a, RandomGen g) => (a, a) -> g -> InitializerFn a
randomUniform range gen sizes@(input, output) = M.fromList sizes $ fst $ uniformListR (input * output) range gen

{- | Initializes list with samples from random LeCun uniform distribution in range.

This function does not preserve seed generator - split generator before calling this function.
-}
lecunUniform :: (UniformRange a, Floating a, RandomGen g) => g -> InitializerFn a
lecunUniform gen sizes@(input, _) = let limit = sqrt $ 3.0 / fromIntegral input
                                    in randomUniform (-limit, limit) gen sizes

{- | Initializes list with samples from random He uniform distribution in range.

This function does not preserve seed generator - split generator before calling this function.
-}
heUniform :: (UniformRange a, Floating a, RandomGen g) => g -> InitializerFn a
heUniform gen sizes@(input, _) = let limit = sqrt $ 6.0 / fromIntegral input
                                 in randomUniform (-limit, limit) gen sizes

{- | Initializes list with samples from random Glorot uniform distribution in range.

This function does not preserve seed generator - split generator before calling this function.
-}
glorotUniform :: (UniformRange a, Floating a, RandomGen g) => g -> InitializerFn a
glorotUniform gen sizes@(input, output) = let limit = sqrt $ 6.0 / fromIntegral (input + output)
                                          in randomUniform (-limit, limit) gen sizes


-- Random normal distribution initializers

{- | Initializes list with samples from random normal distribution in range which could be truncated.

This function does not preserve seed generator - split generator before calling this function.
-}
randomNormal :: (UniformRange a, Floating a, Ord a, RandomGen g) => Maybe a -> a -> a -> g -> InitializerFn a
randomNormal truncated mean stdDev gen sizes@(input, output) = let us = pairs $ uniformRs (0.0, 1.0) gen
                                                                   ns = concatMap ((\(n1, n2) -> [n1, n2]) . transformBoxMuller) us
                                                                   ns' = map ((+ mean) . (* stdDev)) ns
                                                                   ns'' = case truncated of
                                                                               Nothing  -> ns'
                                                                               Just eps -> filter (\x -> abs (x - mean) < eps) ns'
                                                               in M.fromList sizes $ take (input * output) ns''
  where
    pairs [] = []
    pairs [x] = [(x, 1)]
    pairs (a:b:xs) = (a, b) : pairs xs

    transformBoxMuller (u1, u2) = let r = sqrt $ (-2.0) * log u1
                                      theta = 2.0 * pi * u2
                                  in (r * cos theta, r * sin theta)

{- | Initializes list with samples from random LeCun normal distribution in range
which is truncated for values more than two standard deviations from mean.

This function does not preserve seed generator - split generator before calling this function.
-}
lecunNormal :: (UniformRange a, Floating a, Ord a, RandomGen g) => g -> InitializerFn a
lecunNormal gen sizes@(input, _) = let mean = 0
                                       stdDev = sqrt $ 1.0 / fromIntegral input
                                   in randomNormal (Just $ 2.0 * stdDev) mean stdDev gen sizes

{- | Initializes list with samples from random He normal distribution in range
which is truncated for values more than two standard deviations from mean.

This function does not preserve seed generator - split generator before calling this function.
-}
heNormal :: (UniformRange a, Floating a, Ord a, RandomGen g) => g -> InitializerFn a
heNormal gen sizes@(input, _) = let mean = 0
                                    stdDev = sqrt $ 2.0 / fromIntegral input
                                in randomNormal (Just $ 2.0 * stdDev) mean stdDev gen sizes

{- | Initializes list with samples from random Glorot normal distribution in range
which is truncated for values more than two standard deviations from mean.

This function does not preserve seed generator - split generator before calling this function.
-}
glorotNormal :: (UniformRange a, Floating a, Ord a, RandomGen g) => g -> InitializerFn a
glorotNormal gen sizes@(input, output) = let mean = 0
                                             stdDev = sqrt $ 2.0 / fromIntegral (input + output)
                                         in randomNormal (Just $ 2.0 * stdDev) mean stdDev gen sizes


-- Matrix-like initializers

-- | Initializes flat identity matrix. If dimensions do not represent square matrix, an error is thrown.
identity :: Num a => InitializerFn a
identity (input, output)
    | input /= output = error "Given dimensions do not represent square matrix"
    | otherwise       = M.identity input

{- | Initializes float orthogonal matrix obtained from a random normal distribution
that is truncated for values more than two standard deviations from mean.

This function does not preserve seed generator - split generator before calling this function.
-}
orthogonal :: (UniformRange a, Floating a, Ord a, RandomGen g) => g -> InitializerFn a
orthogonal gen sizes = M.orthogonalized $ randomNormal Nothing 0.0 1.0 gen sizes