tensor-safe-0.1.0.1: src/TensorSafe/Compile/Expr.hs
{-# LANGUAGE OverloadedStrings #-}
{-| This module describes the expression structure of a INetwork instance.
-- The INetwork can be structured into a Data structure called CNetwork, with which later
-- to compilation external languages can be done.
-}
module TensorSafe.Compile.Expr (
DLayer (..),
CNetwork (..),
JavaScript (..),
Python (..),
Generator,
generate,
generateFile
) where
import Data.Map
import Data.Text.Lazy as T
import Formatting
import Text.Casing (camel, quietSnake)
-- | Auxiliary data representation of Layers
-- IMPORTANT: If you add new Layers definitions to `TensorSafe.Layers`, you should add
-- the corresponding data structure here for the same layer.
data DLayer = DActivation
| DAdd
| DBatchNormalization
| DConv2D
| DDense
| DDropout
| DFlatten
| DGlobalAvgPooling2D
| DInput
| DLSTM
| DMaxPooling
| DRelu
| DZeroPadding2D
deriving Show
-- | Defines the
data CNetwork = CNSequence CNetwork
| CNAdd CNetwork CNetwork
| CNCons CNetwork CNetwork
| CNLayer DLayer (Map String String)
| CNReturn -- End of initial sequence network
| CNNil -- End of possible nested sequence networks
deriving Show
-- | Support for JavaScript compilation
data JavaScript = JavaScript deriving Show
-- | Support for Python compilation
data Python = Python deriving Show
-- | Defines how are the layers going to be translated to the domain language
-- This translates DLayer to String for each supported language
class LayerGenerator l where
generateName :: l -> DLayer -> String
instance LayerGenerator JavaScript where
generateName _ DActivation = "activation"
generateName _ DAdd = "addStrict"
generateName _ DBatchNormalization = "batchNormalization"
generateName _ DConv2D = "conv2d"
generateName _ DDense = "dense"
generateName _ DDropout = "dropout"
generateName _ DFlatten = "flatten"
generateName _ DGlobalAvgPooling2D = "globalAvgeragePooling2D"
generateName _ DInput = "input"
generateName _ DLSTM = "lstm"
generateName _ DMaxPooling = "maxPooling2d"
generateName _ DRelu = "reLU"
generateName _ DZeroPadding2D = "zeroPadding2D"
instance LayerGenerator Python where
generateName _ DActivation = "Activation"
generateName _ DAdd = "add"
generateName _ DBatchNormalization = "BatchNormalization"
generateName _ DConv2D = "Conv2D"
generateName _ DDense = "Dense"
generateName _ DDropout = "Dropout"
generateName _ DFlatten = "Flatten"
generateName _ DGlobalAvgPooling2D = "GlobalAvgeragePooling2D"
generateName _ DInput = "Input"
generateName _ DLSTM = "LSTM"
generateName _ DMaxPooling = "MaxPool2D"
generateName _ DRelu = "ReLu"
generateName _ DZeroPadding2D = "ZeroPadding2D"
-- | Class that defines which languages are supported for CNetworks generation to text
class Generator l where
-- | Adds supports for a language. Generates a CNetwork to Text
generate :: l -> CNetwork -> Text
-- | Similar to 'generate', but also adds necessary header and module lines of text so as to
-- have the CNetwork compiled at a separate file.
generateFile :: l -> CNetwork -> Text
instance Generator JavaScript where
generate l =
T.intercalate "\n" . generateJS
where
generateJS :: CNetwork -> [Text]
generateJS (CNSequence cn) = ["var model = tf.sequential();"] ++ generateJS cn
generateJS (CNCons cn1 cn2) = (generateJS cn1) ++ (generateJS cn2)
generateJS CNNil = []
generateJS CNReturn = []
generateJS (CNLayer layer params) =
[format
("model.add(tf.layers." % string % "(" % string % "));")
(generateName l layer)
(paramsToJS params)
]
generateFile l cn =
startCode `append` (generate l cn) `append` endCode
where
startCode :: Text
startCode = T.intercalate "\n"
[ "// Autogenerated code"
, "var tf = require(\"@tensorflow/tfjs\");"
, "function model() {"
, "\n"
]
endCode :: Text
endCode = T.intercalate "\n"
[ "\n"
, "return model;"
, "}"
, "\n"
, "module.exports = model();"
]
-- | Converts a map to a parameter object in JavaScript
paramsToJS :: Map String String -> String
paramsToJS m =
(foldrWithKey showParam "{ " m) ++ "}"
where
showParam :: String -> String -> String -> String
showParam key value accum = accum ++ (camel key) ++ ": " ++ value ++ ", "
instance Generator Python where
generate l =
T.intercalate "\n" . generatePy
where
generatePy :: CNetwork -> [Text]
generatePy (CNSequence cn) = ["model = tf.keras.models.Sequential()"] ++ generatePy cn
generatePy (CNCons cn1 cn2) = (generatePy cn1) ++ (generatePy cn2)
generatePy CNNil = []
generatePy CNReturn = []
generatePy (CNLayer layer params) =
[format
("model.add(tf.layers." % string % "(" % string % "))")
(generateName l layer)
(paramsToPython params)]
generateFile l cn =
startCode `append` (generate l cn)
where
startCode :: Text
startCode = T.intercalate "\n"
[ "// Autogenerated code"
, "import tensorflow as tf"
, "\n"
]
-- | Converts a map to keyword arguments in Python
paramsToPython :: Map String String -> String
paramsToPython =
foldrWithKey showParam ""
where
showParam :: String -> String -> String -> String
showParam key value accum = accum ++ (transform key) ++ "=" ++ value ++ ", "
-- | Translates keys to python keys of layers
--
-- There are some minor changes in names of keys for layers with respect to JS.
-- Those changes should be delcared here. For most of the keys, transforming them to
-- snake case does the trick.
transform :: String -> String
transform key
| key == "inputDim" = "input_shape"
| otherwise = quietSnake key