fei-nn-1.0.0: src/MXNet/NN/Layer.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
module MXNet.NN.Layer where
import qualified Data.UUID as UUID
import qualified Data.UUID.V4 as UUID
import Formatting (formatToString, int, shown, stext,
(%))
import RIO
import qualified RIO.NonEmpty as NE
import qualified RIO.State as ST
import qualified RIO.Text as RT
import System.IO.Unsafe (unsafePerformIO)
import MXNet.Base
import qualified MXNet.Base.Operators.Tensor as S
import qualified MXNet.NN.Types as S
runLayerBuilder :: MonadIO m => Layer a -> m a
runLayerBuilder = liftIO . flip ST.evalStateT []
type instance TensorMonad SymbolHandle = Layer
instance PrimTensorOp SymbolHandle SymbolHandle where
prim op args = getNextNamePrefixed >>= liftIO . op args
type Layer = ST.StateT [(Maybe Text, SomeNameBuilder)] IO
class Show nb => NameBuilder nb where
nextName :: MonadIO m => nb -> m Text
data SomeNameBuilder = forall nb . (NameBuilder nb) => SomeNameBuilder nb
instance Show SomeNameBuilder where
show (SomeNameBuilder nb) = show nb
instance NameBuilder SomeNameBuilder where
nextName (SomeNameBuilder nb) = nextName nb
data UUIDNameBuilder = UUIDNameBuilder
instance Show UUIDNameBuilder where
show _ = "UUID"
instance NameBuilder UUIDNameBuilder where
nextName _ = do
uuid <- liftIO $ UUID.nextRandom
return $ UUID.toText uuid
newtype SequNameBuilder = SequNameBuilder (IORef Int)
instance Show SequNameBuilder where
show (SequNameBuilder ref) =
let idx = unsafePerformIO (readIORef ref)
in formatToString ("Seq:" % int) idx
instance NameBuilder SequNameBuilder where
nextName (SequNameBuilder ref) = do
n <- liftIO $ readIORef ref
liftIO $ writeIORef ref (n+1)
return (tshow n)
data OnceNameBuilder = OnceNameBuilder Text (IORef Bool)
instance Show OnceNameBuilder where
show (OnceNameBuilder n ref) =
let flag = unsafePerformIO (readIORef ref)
in formatToString ("Once:" % stext % "[" % shown % "]") n flag
instance NameBuilder OnceNameBuilder where
nextName (OnceNameBuilder name flag) = do
fresh <- readIORef flag
if fresh
then do
writeIORef flag False
return name
else throwString (formatToString ("name \"" % stext % "\" has been used.") name)
dumpCurrentScope :: Layer Text
dumpCurrentScope = do
scopes <- ST.get
return $ tshow scopes
sequential :: HasCallStack => Text -> Layer a -> Layer a
sequential name mk = do
nb <- liftIO $ SequNameBuilder <$> newIORef 0
subscope (Just name, SomeNameBuilder nb) mk
sequential' :: HasCallStack => Layer a -> Layer a
sequential' mk = do
nb <- liftIO $ SequNameBuilder <$> newIORef 0
subscope (Nothing, SomeNameBuilder nb) mk
unique :: HasCallStack => Text -> Layer a -> Layer a
unique name = subscope (Just name, SomeNameBuilder UUIDNameBuilder)
unique' :: HasCallStack => Layer a -> Layer a
unique' = subscope (Nothing, SomeNameBuilder UUIDNameBuilder)
named :: HasCallStack => Text -> Layer a -> Layer a
named name mk = do
scopes <- ST.get
fresh <- newIORef True
ST.put ((Nothing, SomeNameBuilder (OnceNameBuilder name fresh)) : scopes)
a <- mk
ST.put scopes
return a
getNextName :: HasCallStack => Layer Text
getNextName = do
scopes <- ST.get
case scopes of
((_, nb) : _) -> nextName nb
_ -> throwString ("No next name avaiable. The current scopes: " ++ show scopes)
getNextNamePrefixed :: HasCallStack => Layer Text
getNextNamePrefixed = do
name <- getNextName
getNamePrefixed (Just name)
getNamePrefixed :: HasCallStack => Maybe Text -> Layer Text
getNamePrefixed name = do
scopes <- ST.get
let comps = catMaybes $ reverse (map fst scopes) ++ [name]
return $ RT.intercalate "." comps
subscope :: HasCallStack => (Maybe Text, SomeNameBuilder) -> Layer a -> Layer a
subscope scope mk = do
old_scopes <- ST.get
ST.put (scope : old_scopes)
a <- mk
ST.put old_scopes
return a
subscope_named :: HasCallStack => Text -> Layer a -> Layer a
subscope_named name = subscope (Just name, SomeNameBuilder UUIDNameBuilder)
subscope_next_name :: HasCallStack => Layer a -> Layer a
subscope_next_name mk = do
name <- getNextName
subscope_named name mk
variable :: Text -> Layer SymbolHandle
variable name = getNamePrefixed (Just name) >>= liftIO . mxSymbolCreateVariable
constant :: NonEmpty Int -> [Float] -> Layer SymbolHandle
constant shape value = do
name <- getNextNamePrefixed
let build = do var <- mxSymbolCreateVariable name
mxSymbolSetAttr var "__shape__" (tshow $ NE.toList shape)
mxSymbolSetAttr var "__init__" (tshow value)
return var
named (RT.concat [name, ".sg"]) $ blockGrad =<< liftIO build
convolution :: (HasArgs "_Convolution" SymbolHandle args
'["kernel", "num_filter", "data", "stride", "dilate", "pad",
"num_group", "workspace", "layout",
"cudnn_tune", "cudnn_off", "no_bias"]
,WithoutArgs "_Convolution" SymbolHandle args
'["bias", "weight"])
=> ArgsHMap "_Convolution" _ args -> Layer SymbolHandle
convolution args = subscope_next_name $ do
b <- variable "bias"
w <- variable "weight"
name <- getNamePrefixed Nothing
if args !? #no_bias == Just True
then
liftIO $ S._Convolution (#weight := w .& args) name
else
liftIO $ S._Convolution (#bias := b .& #weight := w .& args) name
convolutionShared :: (HasArgs "_Convolution" SymbolHandle args
'["kernel", "num_filter", "stride",
"dilate", "pad", "num_group", "workspace",
"layout", "cudnn_tune", "cudnn_off", "no_bias"]
,WithoutArgs "_Convolution" SymbolHandle args
'["data", "bias", "weight"])
=> ArgsHMap "_Convolution" _ args -> Layer (SymbolHandle -> Layer SymbolHandle)
convolutionShared args = subscope_next_name $ do
b <- variable "bias"
w <- variable "weight"
return $ \data_ -> do
name <- getNextNamePrefixed
if args !? #no_bias == Just True
then
liftIO $ S._Convolution (#data := data_ .& #weight := w .& args) name
else
liftIO $ S._Convolution (#data := data_ .& #bias := b .& #weight := w .& args) name
fullyConnected :: (HasArgs "_FullyConnected" SymbolHandle args
'["flatten", "no_bias", "data", "num_hidden"]
,WithoutArgs "_FullyConnected" SymbolHandle args
'["bias", "weight"])
=> ArgsHMap "_FullyConnected" _ args -> Layer SymbolHandle
fullyConnected args = subscope_next_name $ do
b <- variable "bias"
w <- variable "weight"
name <- getNamePrefixed Nothing
if args !? #no_bias == Just True
then
liftIO $ S._FullyConnected (#weight := w .& args) name
else
liftIO $ S._FullyConnected (#bias := b .& #weight := w .& args) name
fullyConnectedShared :: (HasArgs "_FullyConnected" SymbolHandle args
'["flatten", "no_bias", "num_hidden"]
,WithoutArgs "_FullyConnected" SymbolHandle args
'["bias", "weight"])
=> ArgsHMap "_FullyConnected" _ args -> Layer (SymbolHandle -> Layer SymbolHandle)
fullyConnectedShared args = subscope_next_name $ do
b <- variable "bias"
w <- variable "weight"
return $ \data_ -> do
name <- getNextNamePrefixed
if args !? #no_bias == Just True
then
liftIO $ S._FullyConnected (#data := data_ .& #weight := w .& args) name
else
liftIO $ S._FullyConnected (#data := data_ .& #bias := b .& #weight := w .& args) name
batchnorm :: HasArgs "_BatchNorm" SymbolHandle args
'["data", "eps", "momentum", "fix_gamma",
"use_global_stats", "output_mean_var", "axis",
"cudnn_off", "min_calib_range", "max_calib_range"]
=> ArgsHMap "_BatchNorm" _ args -> Layer SymbolHandle
batchnorm args = subscope_next_name $ do
gamma <- variable "gamma"
beta <- variable "beta"
mov_mean <- variable "running_mean"
mov_var <- variable "running_var"
name <- getNamePrefixed Nothing
liftIO $ S._BatchNorm (#gamma := gamma
.& #beta := beta
.& #moving_mean := mov_mean
.& #moving_var := mov_var
.& args) name
blockGrad :: SymbolHandle -> Layer SymbolHandle
blockGrad s = prim S._BlockGrad (#data := s .& Nil)
splitBySections :: HasCallStack => Int -> Int -> Bool -> SymbolHandle -> Layer [SymbolHandle]
splitBySections num_sections axis squeeze s = do
r <- prim S.__split_v2 (#data := s
.& #axis := axis
.& #indices := []
.& #sections := num_sections
.& #squeeze_axis := squeeze .& Nil)
mapM (at r) ([0..num_sections-1] :: [Int])
-----------------------------------------------------------------------------
-- For both Symbol and NDArray
-----------------------------------------------------------------------------
pooling :: (PrimTensorOp t t, Fullfilled "_Pooling" t args)
=> ArgsHMap "_Pooling" t args -> TensorM t
pooling = prim S._Pooling
activation :: (PrimTensorOp t t, Fullfilled "_Activation" t args)
=> ArgsHMap "_Activation" t args -> TensorM t
activation = prim S._Activation
softmax :: (PrimTensorOp t t, Fullfilled "_softmax" t args)
=> ArgsHMap "_softmax" t args -> TensorM t
softmax = prim S._softmax
softmaxoutput :: (PrimTensorOp t t, Fullfilled "_SoftmaxOutput" t args)
=> ArgsHMap "_SoftmaxOutput" t args -> TensorM t
softmaxoutput = prim S._SoftmaxOutput
pick :: (PrimTensorOp t t, Fullfilled "_pick" t args)
=> ArgsHMap "_pick" t args -> TensorM t
pick = prim S._pick
stack axis ts = prim S._stack (#num_args := length ts .& #data := ts .& #axis := axis .& Nil)
flatten t = prim S._Flatten (#data := t .& Nil)
identity s = prim S.__copy (#data := s .& Nil)
dropout t p = prim S._Dropout (#data := t .& #p := p .& Nil)
reshape shape a = prim S._Reshape (#data := a .& #shape := shape .& Nil)
add_, sub_, mul_, div_, eq_, neq_, lt_, leq_, gt_, geq_ ::
PrimTensorOp t t => t -> t -> TensorM t
add_ a b = prim S._elemwise_add (#lhs := a .& #rhs := b .& Nil)
sub_ a b = prim S._elemwise_sub (#lhs := a .& #rhs := b .& Nil)
mul_ a b = prim S._elemwise_mul (#lhs := a .& #rhs := b .& Nil)
div_ a b = prim S._elemwise_div (#lhs := a .& #rhs := b .& Nil)
eq_ a b = prim S.__equal (#lhs := a .& #rhs := b .& Nil)
neq_ a b = prim S.__not_equal (#lhs := a .& #rhs := b .& Nil)
lt_ a b = prim S.__lesser (#lhs := a .& #rhs := b .& Nil)
leq_ a b = prim S.__lesser_equal (#lhs := a .& #rhs := b .& Nil)
gt_ a b = prim S.__greater (#lhs := a .& #rhs := b .& Nil)
geq_ a b = prim S.__greater_equal (#lhs := a .& #rhs := b .& Nil)
and_ a b = prim S.__logical_and (#lhs := a .& #rhs := b .& Nil)
or_ a b = prim S.__logical_or (#lhs := a .& #rhs := b .& Nil)
xor_ a b = prim S.__logical_xor (#lhs := a .& #rhs := b .& Nil)
not_ a = prim S._logical_not (#data := a .& Nil)
addScalar b a = prim S.__plus_scalar (#data := a .& #scalar := b .& Nil)
subScalar b a = prim S.__minus_scalar (#data := a .& #scalar := b .& Nil)
rsubScalar b a = prim S.__rminus_scalar (#data := a .& #scalar := b .& Nil)
mulScalar b a = prim S.__mul_scalar (#data := a .& #scalar := b .& Nil)
divScalar b a = prim S.__div_scalar (#data := a .& #scalar := b .& Nil)
rdivScalar b a = prim S.__rdiv_scalar (#data := a .& #scalar := b .& Nil)
eqScalar b a = prim S.__equal_scalar (#data := a .& #scalar := b .& Nil)
neqScalar b a = prim S.__not_equal_scalar (#data := a .& #scalar := b .& Nil)
ltScalar b a = prim S.__lesser_scalar (#data := a .& #scalar := b .& Nil)
leqScalar b a = prim S.__lesser_equal_scalar (#data := a .& #scalar := b .& Nil)
gtScalar b a = prim S.__greater_scalar (#data := a .& #scalar := b .& Nil)
geqScalar b a = prim S.__greater_equal_scalar (#data := a .& #scalar := b .& Nil)
andScalar b a = prim S.__logical_and_scalar (#data := a .& #scalar := b .& Nil)
orScalar b a = prim S.__logical_or_scalar (#data := a .& #scalar := b .& Nil)
xorScalar b a = prim S.__logical_xor_scalar (#data := a .& #scalar := b .& Nil)
addBroadcast a b = prim S._broadcast_add (#lhs := a .& #rhs := b .& Nil)
subBroadcast a b = prim S._broadcast_sub (#lhs := a .& #rhs := b .& Nil)
mulBroadcast a b = prim S._broadcast_mul (#lhs := a .& #rhs := b .& Nil)
divBroadcast a b = prim S._broadcast_div (#lhs := a .& #rhs := b .& Nil)
eqBroadcast a b = prim S._broadcast_equal (#lhs := a .& #rhs := b .& Nil)
neqBroadcast a b = prim S._broadcast_not_equal (#lhs := a .& #rhs := b .& Nil)
ltBroadcast a b = prim S._broadcast_lesser (#lhs := a .& #rhs := b .& Nil)
leqBroadcast a b = prim S._broadcast_lesser_equal (#lhs := a .& #rhs := b .& Nil)
gtBroadcast a b = prim S._broadcast_greater (#lhs := a .& #rhs := b .& Nil)
geqBroadcast a b = prim S._broadcast_greater_equal (#lhs := a .& #rhs := b .& Nil)
ceil_ a = prim S._ceil (#data := a .& Nil)
floor_ a = prim S._floor (#data := a .& Nil)
sqrt_ a = prim S._sqrt (#data := a .& Nil)
log2_ a = prim S._log2 (#data := a .& Nil)
square_ a = prim S._square (#data := a .& Nil)
concat_ :: PrimTensorOp t t => Int -> [t] -> TensorM t
concat_ d s = prim S._Concat (#data := s .& #num_args := length s .& #dim := d .& Nil)
takeI :: (HasCallStack, PrimTensorOp t t)
=> t -> t -> TensorM t
takeI i a = prim S._take (#a := a .& #indices := i .& Nil)
pickI :: (HasCallStack, PrimTensorOp t t)
=> t -> t -> TensorM t
pickI i t = prim S._pick (#data := t .& #index := i .& Nil)
where_ c a b = prim S._where (#condition := c .& #x := a .& #y := b .& Nil)
zerosLike a = prim S._zeros_like (#data := a .& Nil)
onesLike a = prim S._ones_like (#data := a .& Nil)
squeeze axis a = prim S._squeeze (#data := a .& #axis := axis .& Nil)
expandDims axis a = prim S._expand_dims (#data := a .& #axis := axis .& Nil)
broadcastAxis axis size a = prim S._broadcast_axis (#data := a .& #axis := axis .& #size := size .& Nil)
sum_ s axis keepdims = prim S._sum (#data := s .& #axis:= axis .& #keepdims := keepdims .& Nil)
transpose a axes = prim S._transpose (#data := a .& #axes := axes .& Nil)
argmax a axis keepdims = prim S._argmax (#data := a .& #axis := axis .& #keepdims := keepdims .& Nil)
slice_axis a axis beg end = prim S._slice_axis (#data := a .& #axis := axis .& #begin := beg .& #end := end .& Nil)
-- TODO constraint the `o` to conform to `dt`
cast :: PrimTensorOp t o
=> EnumType '["bool", "float16", "float32", "float64", "int32", "int64", "int8", "uint8"]
-> t
-> TensorM o
cast dt t = prim S._Cast (#dtype := dt .& #data := t .& Nil)
----------------------------------------------------------------------------
data LossAgg = AggMean | AggSum
sigmoidBCE :: (PrimTensorOp t t, Monad (TensorMonad t))
=> t -> t -> Maybe t -> LossAgg -> TensorM t
sigmoidBCE pred label sample_weight agg = do
-- pred: (B, C, 1)
-- label: (B, C, 1)
a <- prim S._relu (#data := pred .& Nil)
b <- mul_ pred label
c <- prim S._abs (#data := pred .& Nil) >>= rsubScalar 0
c <- prim S._Activation (#data := c .& #act_type := #softrelu .& Nil)
loss <- add_ c =<< sub_ a b
loss <- case sample_weight of
Just w -> mulBroadcast loss w
Nothing -> return loss
case agg of
AggMean -> prim S._mean (#data := loss .& #axis := Just [0] .& #exclude := True .& Nil)
AggSum -> prim S._sum (#data := loss .& #axis := Just [0] .& #exclude := True .& Nil)
softmaxCE :: (PrimTensorOp t t, Monad (TensorMonad t)) => Int -> t -> t -> Maybe t -> TensorM t
softmaxCE axis pred label sample_weight = do
pred <- prim S._log_softmax (#data := pred .& #axis := axis .& Nil)
labl <- prim S._reshape_like (#lhs := label .& #rhs := pred .& Nil)
loss <- mul_ pred labl
loss <- sum_ loss (Just [axis]) True >>= rsubScalar 0
loss <- case sample_weight of
Just w -> mulBroadcast loss w
Nothing -> return loss
prim S._mean (#data := loss .& #axis := Just [0] .& #exclude := True .& Nil)
-----------------------------------------------------------------------------
-- For NDArray Only
-----------------------------------------------------------------------------
copy :: (HasCallStack, PrimTensorOp t t, TensorApply t ~ (Maybe [t] -> IO [t]))
=> t -> t -> IO t
copy src dst = do
[ret] <- S.__copyto (#data := src .& Nil) (Just [dst])
return ret