synapse-0.1.0.0: src/Synapse/Autograd.hs
{- | This module implements reverse-mode automatic differentiation.
Machine learning and training of models are based on calculating gradients of operations.
This can be done symbolically by dynamically creating a graph of all operations,
which is then traversed to obtain the gradient.
"Synapse" provides several operations that support automatic differentiation,
but you could easily extend list of those: you just need to define function
that returns 'Symbol' with correct local gradients.
You can check out implementations in the source to give yourself a reference
and read more about it in 'Symbol' datatype docs.
-}
-- 'FlexibleInstances' and 'TypeFamilies' are needed to instantiate 'Indexable', 'ElementwiseScalarOps', 'SingletonOps', 'VecOps', 'MatOps' typeclasses.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
module Synapse.Autograd
( -- * 'Symbolic' and 'Symbol'
Symbolic (symbolicZero, symbolicOne, symbolicN)
, SymbolIdentifier (SymbolIdentifier, unSymbolIdentifier)
, Symbol (Symbol, symbolIdentifier, unSymbol, symbolGradients)
, SymbolVec
, SymbolMat
, symbol
, constSymbol
, renameSymbol
, symbolicUnaryOp
, symbolicBinaryOp
-- * 'Gradients' calculation
, Gradients (unGradients)
, allGradients
, getGradientsOf
, wrt
, nthPartialGradient
, nthGradient
) where
import Synapse.Tensors (DType, Indexable(..), ElementwiseScalarOps(..), ElementwiseScalarOps(..), SingletonOps(..), VecOps(..), MatOps(..))
import Synapse.Tensors.Vec (Vec)
import qualified Synapse.Tensors.Vec as V
import Synapse.Tensors.Mat (Mat)
import qualified Synapse.Tensors.Mat as M
import Data.Foldable (foldl')
import Data.String (IsString(..))
import Data.Hashable (Hashable(..))
import qualified Data.HashMap.Lazy as HM
{- | 'Symbolic' typeclass describes types with few properties that are needed for autogradient.
Members of this typeclass could have default implementation due to 'Num', but such implementation is not always correct.
'Synapse.Tensors.Vec.Vec's and 'Synapse.Tensors.Mat.Mat's do not have only one zero or identity element, and so numerical literal is not enough.
'symbolicZero' and 'symbolicOne' function additionally take reference value to consider dimensions.
Absence of default implementations forces to manually ensure correctness of those functions.
"Synapse" provides implementations for primitive types ('Int', 'Float', 'Double'),
and for containers types ('Synapse.Tensors.Vec.Vec', 'Synapse.Tensors.Vec.Vec').
Detailed laws of 'Symbolic' properties are in the docs for associated functions.
-}
class (Eq a, Num a) => Symbolic a where
-- | Returns additive and multiplicative (elementwise) zero element. Argument is passed for the reference of the dimension.
symbolicZero :: a -> a
-- | Returns multiplicative (elementwise) identity element. Argument is passed for the reference of the dimension.
symbolicOne :: a -> a
-- | Returns what could be considered @N@ constant (sum of @N@ 'symbolicOne's). Argument is passed for the reference of the dimension.
symbolicN :: Int -> a -> a
symbolicN n c
| n < 0 = negate $ go (abs n) c
| n == 0 = symbolicZero c
| otherwise = go n c
where
go 0 x = x
go i x = go (i - 1) (x + symbolicOne x)
-- Instances
instance Symbolic Int where
symbolicZero = const 0
symbolicOne = const 1
symbolicN = const
instance Symbolic Float where
symbolicZero = const 0.0
symbolicOne = const 1.0
symbolicN n = const (fromIntegral n)
instance Symbolic Double where
symbolicZero = const 0.0
symbolicOne = const 1.0
symbolicN n = const (fromIntegral n)
instance Symbolic a => Symbolic (Vec a) where
symbolicZero reference = V.replicate (V.size reference) 0
symbolicOne reference = V.replicate (V.size reference) 1
symbolicN n reference = V.replicate (V.size reference) (fromIntegral n)
instance Symbolic a => Symbolic (Mat a) where
symbolicZero reference = M.replicate (M.size reference) 0
symbolicOne reference = M.replicate (M.size reference) 1
symbolicN n reference = M.replicate (M.size reference) (fromIntegral n)
-- | 'SymbolIdentifier' is a newtype that wraps string, which needs to uniquely represent symbol.
newtype SymbolIdentifier = SymbolIdentifier
{ unSymbolIdentifier :: String -- ^ Identifier of a symbol.
} deriving (Eq, Show)
instance IsString SymbolIdentifier where
fromString = SymbolIdentifier
instance Semigroup SymbolIdentifier where
(<>) (SymbolIdentifier a) (SymbolIdentifier b) = SymbolIdentifier $ a <> b
instance Monoid SymbolIdentifier where
mempty = SymbolIdentifier ""
{- | Datatype that represents symbol variable (variable which operations are recorded to symbolically obtain derivatives).
Any operation returning @Symbol a@ where @a@ is 'Symbolic' could be autogradiented - returned 'Symbol' has 'symbolGradients' list,
which allows "Synapse" to build a graph of computation and obtain needed gradients.
'symbolGradients' list contains pairs: first element in that pair is symbol wrt which you can take gradient and
the second element is closure that represents chain rule - it takes incoming local gradient of said symbol and multiplies it by local derivative.
You can check out implementations of those operations in the source to give yourself a reference.
-}
data Symbol a = Symbol
{ symbolIdentifier :: SymbolIdentifier -- ^ Name of a symbol (identifier for differentiation).
, unSymbol :: a -- ^ Value of a symbol.
, symbolGradients :: [(Symbol a, Symbol a -> Symbol a)] -- ^ List of gradients (wrt to what Symbol and closure to calculate gradient).
}
-- | Creates new symbol that refers to a variable (so it must have a name to be able to be differentiated wrt).
symbol :: SymbolIdentifier -> a -> Symbol a
symbol name value = Symbol name value []
-- | Creates new symbol that refers to constant (so it does not have name and thus its gradients are not saved).
constSymbol :: a -> Symbol a
constSymbol = symbol mempty
-- | Renames symbol which allows differentiating wrt it. Note: renaming practically creates new symbol for the gradient calculation.
renameSymbol :: SymbolIdentifier -> Symbol a -> Symbol a
renameSymbol name (Symbol _ value localGradients) = Symbol name value localGradients
-- | @SymbolVec a@ type alias stands for @Symbol (Vec a)@.
type SymbolVec a = Symbol (Vec a)
-- | @SymbolMat a@ type alias stands for @Symbol (Mat a)@.
type SymbolMat a = Symbol (Mat a)
-- Typeclasses
instance Show a => Show (Symbol a) where
show (Symbol name value _) = "Symbol " ++ show name ++ ": " ++ show value
instance Eq (Symbol a) where
(==) (Symbol name1 _ _) (Symbol name2 _ _) = name1 == name2
instance Hashable (Symbol a) where
hashWithSalt salt (Symbol (SymbolIdentifier name) _ _) = hashWithSalt salt name
-- Symbolic symbols
instance Symbolic a => Symbolic (Symbol a) where
symbolicZero x = constSymbol $ symbolicZero $ unSymbol x
symbolicOne x = constSymbol $ symbolicOne $ unSymbol x
type instance DType (SymbolVec a) = a
type instance DType (SymbolMat a) = a
-- | Converts unary operation into symbolic one.
symbolicUnaryOp :: (a -> a) -> Symbol a -> [(Symbol a, Symbol a -> Symbol a)] -> Symbol a
symbolicUnaryOp op x = Symbol mempty (op (unSymbol x))
-- | Converts binary operation into symbolic one.
symbolicBinaryOp :: (a -> a -> a) -> Symbol a -> Symbol a -> [(Symbol a, Symbol a -> Symbol a)] -> Symbol a
symbolicBinaryOp op a b = Symbol mempty (op (unSymbol a) (unSymbol b))
instance Symbolic a => Num (Symbol a) where
(+) a b = symbolicBinaryOp (+) a b [(a, id), (b, id)]
(-) a b = symbolicBinaryOp (-) a b [(a, id), (b, negate)]
negate x = symbolicUnaryOp negate x [(x, negate)]
(*) a b = symbolicBinaryOp (*) a b [(a, (* b)), (b, (a *))]
abs x = symbolicUnaryOp abs x [(x, signum)]
signum x = symbolicUnaryOp signum x [(x, id)]
fromInteger = constSymbol . fromInteger
instance (Symbolic a, Fractional a) => Fractional (Symbol a) where
(/) a b = symbolicBinaryOp (/) a b [(a, (/ b)), (b, (* (negate a / (b * b))))]
recip x = symbolicUnaryOp recip x [(x, (* (negate (symbolicOne x) / (x * x))))]
fromRational = constSymbol . fromRational
instance (Symbolic a, Floating a) => Floating (Symbol a) where
pi = constSymbol pi
(**) a b = symbolicBinaryOp (**) a b [(a, (* (b * a ** (b - symbolicOne b)))), (b, (* (a ** b * log a)))]
sqrt x = symbolicUnaryOp sqrt x [(x, (* (recip $ symbolicN 2 x * sqrt x)))]
exp x = symbolicUnaryOp exp x [(x, (* exp x))]
log x = symbolicUnaryOp log x [(x, (* recip x))]
sin x = symbolicUnaryOp sin x [(x, (* cos x))]
cos x = symbolicUnaryOp cos x [(x, (* negate (sin x)))]
asin x = symbolicUnaryOp asin x [(x, (* recip (sqrt (symbolicOne x - x * x))))]
acos x = symbolicUnaryOp acos x [(x, (* negate (recip (sqrt (symbolicOne x - x * x)))))]
atan x = symbolicUnaryOp atan x [(x, (* recip (symbolicOne x + x * x)))]
sinh x = symbolicUnaryOp sinh x [(x, (* cosh x))]
cosh x = symbolicUnaryOp cosh x [(x, (* sinh x))]
asinh x = symbolicUnaryOp asinh x [(x, (* recip (sqrt (symbolicOne x + x * x))))]
acosh x = symbolicUnaryOp acosh x [(x, (* recip (sqrt (x * x - symbolicOne x))))]
atanh x = symbolicUnaryOp atanh x [(x, (* recip (symbolicOne x - x * x)))]
instance Symbolic a => ElementwiseScalarOps (Symbol (Vec a)) where
(+.) x n = x + constSymbol (V.replicate (V.size $ unSymbol x) n)
(-.) x n = x - constSymbol (V.replicate (V.size $ unSymbol x) n)
(*.) x n = x * constSymbol (V.replicate (V.size $ unSymbol x) n)
(/.) x n = x / constSymbol (V.replicate (V.size $ unSymbol x) n)
(**.) x n = x ** constSymbol (V.replicate (V.size $ unSymbol x) n)
elementsMin x n = symbolicUnaryOp (`elementsMin` n) x
[(x, (* constSymbol (V.generate (V.size $ unSymbol x) $ \i -> if unsafeIndex (unSymbol x) i <= n then 1 else 0)))]
elementsMax x n = symbolicUnaryOp (`elementsMax` n) x
[(x, (* constSymbol (V.generate (V.size $ unSymbol x) $ \i -> if unsafeIndex (unSymbol x) i >= n then 1 else 0)))]
instance Symbolic a => ElementwiseScalarOps (SymbolMat a) where
(+.) x n = x + constSymbol (M.replicate (M.size $ unSymbol x) n)
(-.) x n = x - constSymbol (M.replicate (M.size $ unSymbol x) n)
(*.) x n = x * constSymbol (M.replicate (M.size $ unSymbol x) n)
(/.) x n = x / constSymbol (M.replicate (M.size $ unSymbol x) n)
(**.) x n = x ** constSymbol (M.replicate (M.size $ unSymbol x) n)
elementsMin x n = symbolicUnaryOp (`elementsMin` n) x
[(x, (* constSymbol (M.generate (M.size $ unSymbol x) $ \i -> if unsafeIndex (unSymbol x) i <= n then 1 else 0)))]
elementsMax x n = symbolicUnaryOp (`elementsMax` n) x
[(x, (* constSymbol (M.generate (M.size $ unSymbol x) $ \i -> if unsafeIndex (unSymbol x) i >= n then 1 else 0)))]
instance Symbolic a => SingletonOps (SymbolVec a) where
singleton = constSymbol . singleton
isSingleton = isSingleton . unSymbol
unSingleton = unSingleton . unSymbol
extendSingleton vec reference = constSymbol $ extendSingleton (unSymbol vec) (unSymbol reference)
elementsSum x = symbolicUnaryOp elementsSum x [(x, (`extendSingleton` x))]
elementsProduct x = let innerProduct = unSingleton $ elementsProduct $ unSymbol x
in symbolicUnaryOp elementsProduct x [(x, \path -> extendSingleton path x * constSymbol (V.generate (V.size $ unSymbol x) $ \i -> innerProduct / unsafeIndex (unSymbol x) i))]
mean x = symbolicUnaryOp mean x [(x, \path -> extendSingleton path x /. fromIntegral (V.size (unSymbol x)))]
norm x = symbolicUnaryOp norm x [(x, \path -> extendSingleton path x * (x /. unSingleton (norm $ unSymbol x)))]
instance Symbolic a => SingletonOps (SymbolMat a) where
singleton = constSymbol . singleton
isSingleton = isSingleton . unSymbol
unSingleton = unSingleton . unSymbol
extendSingleton mat reference = constSymbol $ extendSingleton (unSymbol mat) (unSymbol reference)
elementsSum x = symbolicUnaryOp elementsSum x [(x, (`extendSingleton` x))]
elementsProduct x = let innerProduct = unSingleton $ elementsProduct $ unSymbol x
in symbolicUnaryOp elementsProduct x [(x, \path -> extendSingleton path x * constSymbol (M.generate (M.size $ unSymbol x) $ \i -> innerProduct / unsafeIndex (unSymbol x) i))]
mean x = symbolicUnaryOp mean x [(x, \path -> extendSingleton path x /. fromIntegral (M.nElements $ unSymbol x))]
norm x = symbolicUnaryOp norm x [(x, \path -> extendSingleton path x * (x /. unSingleton (norm $ unSymbol x)))]
instance Symbolic a => VecOps (SymbolVec a) where
dot a b = elementsSum $ a * b
instance Symbolic a => MatOps (SymbolMat a) where
transpose x = symbolicUnaryOp M.transpose x [(x, (* transpose x))]
addMatRow mat row = symbolicBinaryOp addMatRow mat row [(mat, id), (row, constSymbol . M.rowVec . flip M.indexRow 0 . unSymbol)]
matMul a b = symbolicBinaryOp M.matMul a b [(a, (`matMul` transpose b)), (b, (transpose a `matMul`))]
-- Gradients calculation
-- | 'Gradients' datatype holds all gradients of one symbol with respect to other symbols.
newtype Gradients a = Gradients
{ unGradients :: HM.HashMap (Symbol a) (Symbol a) -- ^ Map of gradients.
}
-- | Returns key-value pairs of all gradients of symbol.
allGradients :: Gradients a -> [(Symbol a, Symbol a)]
allGradients = HM.toList . unGradients
-- Typeclasses
instance Show a => Show (Gradients a) where
show gradients = show $ allGradients gradients
-- | Generates 'Gradients' for given symbol.
getGradientsOf :: Symbolic a => Symbol a -> Gradients a
getGradientsOf differentiatedSymbol = Gradients $ HM.insert differentiatedSymbol wrtItself $
HM.delete (Symbol mempty undefined []) $
go HM.empty differentiatedSymbol wrtItself
where
wrtItself = symbolicOne differentiatedSymbol
go grads s pathValue =
foldr (\(child, mulPath) grad -> let pathValue' = mulPath pathValue
grad' = HM.alter (\e -> Just $ case e of
Nothing -> pathValue'
Just x -> x + pathValue'
) child grad
in go grad' child pathValue') grads (symbolGradients s)
-- | Chooses gradient with respect to given symbol.
wrt :: Symbolic a => Gradients a -> Symbol a -> Symbol a
wrt gradients x = HM.findWithDefault (symbolicZero x) x $ unGradients gradients
-- | Takes partial gradients wrt to all symbols in a list sequentially, returning last result.
nthPartialGradient :: Symbolic a => Symbol a -> [Symbol a] -> Symbol a
nthPartialGradient = foldl' $ \y x -> getGradientsOf y `wrt` x
-- | Takes nth order gradient of one symbol wrt other symbol. If n is negative number, an error is returned.
nthGradient :: Symbolic a => Int -> Symbol a -> Symbol a -> Symbol a
nthGradient n y x
| n < 0 = error "Cannot take negative order gradient"
| otherwise = nthPartialGradient y (replicate n x)