hasktorch-0.2.0.0: src/Torch/Optim.hs
{-# LANGUAGE RecordWildCards #-}
module Torch.Optim where
import Control.Monad.State
import Control.Monad (foldM)
import System.Mem (performGC)
import Torch.Autograd
import Torch.Functional
import Torch.Internal.GC (mallocTrim)
import Torch.NN
import Torch.Tensor
import Torch.TensorFactories
import Prelude hiding (sqrt)
type LearningRate = Tensor
type Loss = Tensor
newtype Gradients = Gradients [Tensor] deriving (Show)
newtype OptimizerState option = OptimizerState option
grad' :: Loss -> [Parameter] -> Gradients
grad' t p = Gradients (grad t p)
class Optimizer optimizer where
step :: LearningRate -> Gradients -> [Tensor] -> optimizer -> ([Tensor], optimizer)
-- | run a single iteration of an optimizer, returning new parameters and updated optimizer state
runStep :: (Parameterized model) => model -> optimizer -> Loss -> LearningRate -> IO (model, optimizer)
runStep paramState optState lossValue = runStep' paramState optState (grad' lossValue $ flattenParameters paramState)
-- | run a single iteration of an optimizer, returning new parameters and updated optimizer state
runStep' :: (Parameterized model) => model -> optimizer -> Gradients -> LearningRate -> IO (model, optimizer)
runStep' paramState optState gradients lr = do
performGC
mallocTrim 0
let (flatParameters', optState') = step lr gradients depParameters optState
newFlatParam <- mapM makeIndependent flatParameters'
pure (replaceParameters paramState newFlatParam, optState')
where
flatParameters = flattenParameters paramState
depParameters = fmap toDependent flatParameters
--
-- Gradient Descent
--
data GD = GD deriving (Show)
-- | Stateless gradient descent step
gd :: LearningRate -> Gradients -> [Tensor] -> [Tensor]
gd lr (Gradients gradients) parameters = zipWith step parameters gradients
where
step p dp = p - (lr * dp)
-- | Gradient descent step with a dummy state variable
gd' :: LearningRate -> Gradients -> [Tensor] -> GD -> ([Tensor], GD)
gd' lr gradients depParameters dummy = (gd lr gradients depParameters, dummy)
instance Optimizer GD where
step = gd'
sgd :: LearningRate -> [Parameter] -> [Tensor] -> [Tensor]
sgd lr parameters = zipWith step depParameters
where
step p dp = p - (lr * dp)
depParameters = map toDependent parameters
--
-- Gradient Descent with Momentum
--
data GDM = GDM {beta :: Float, momentum :: [Tensor]} deriving (Show)
-- gradient descent with momentum step
gdm ::
-- | learning rate
LearningRate ->
-- | model parameter gradients
Gradients ->
-- | model parameters
[Tensor] ->
-- | beta & momentum
GDM ->
-- | returns new parameters + updated momentum
([Tensor], GDM)
gdm lr (Gradients gradients) parameters (GDM beta momentum) =
(fmap fst runStep, GDM beta (fmap snd runStep))
where
step p dp z = let z' = mulScalar beta z + dp in (p - lr * z', z')
runStep = zipWith3 step parameters gradients momentum
instance Optimizer GDM where
step = gdm
--
-- Adam
--
-- | State representation for Adam Optimizer
data Adam = Adam
{ beta1 :: Float, -- 1st moment forgetting factor
beta2 :: Float, -- 2nd moment forgetting factor
m1 :: [Tensor], -- 1st moment
m2 :: [Tensor], -- 2nd moment
iter :: Int -- iteration
}
deriving (Show)
mkAdam ::
Int ->
Float ->
Float ->
[Parameter] ->
Adam
mkAdam iter beta1 beta2 parameters =
Adam
beta1
beta2
(initZeros <$> parameters)
(initZeros <$> parameters)
iter
where
initZeros = zerosLike . toDependent
-- | Adam step
adam ::
-- | learning rate
LearningRate ->
-- | model parameter gradients
Gradients ->
-- | model parameters
[Tensor] ->
-- | adam parameters - beta1, beta2, moments, iteration
Adam ->
-- | returns new parameters + updated adam parameters
([Tensor], Adam)
adam lr (Gradients gradients) parameters Adam {..} = (parameters', Adam beta1 beta2 m1' m2' (iter + 1))
where
-- decaying averages of 1st & 2nd moments
f1 m1 dp = mulScalar beta1 m1 + mulScalar (1 - beta1) dp
f2 m2 dp = mulScalar beta2 m2 + mulScalar (1 - beta2) (dp * dp)
m1' = zipWith f1 m1 gradients
m2' = zipWith f2 m2 gradients
-- bias adjustment
a beta = divScalar (1 - beta ^ (iter + 1))
a1 = fmap (a beta1) m1'
a2 = fmap (a beta2) m2'
-- parameter update
eps = 1e-37
update prevParam a1' a2' = prevParam - lr * a1' / (sqrt a2' + eps)
parameters' = zipWith3 update parameters a1 a2
instance Optimizer Adam where
step = adam
--
-- Adagrad
--
-- | State representation for Adagrad Optimizer
data Adagrad = Adagrad {gsum :: [Tensor]} -- sum of squared gradients
deriving (Show)
-- | Adagrad step
adagrad ::
-- | learning rate
LearningRate ->
-- | model parameter gradients
Gradients ->
-- | model parameters
[Tensor] ->
-- | adagrad parameters - gsum, iteration
Adagrad ->
-- | returns new parameters + updated adam parameters
([Tensor], Adagrad)
adagrad lr (Gradients gradients) parameters Adagrad {..} = (parameters', Adagrad gsum')
where
-- add gradient squared to running total
f gsum dp = gsum + dp * dp
gsum' = zipWith f gsum gradients
-- parameter update
eps = 1e-37
update prevParam a1' a2' = prevParam - lr * a1' / (sqrt (a2' + eps))
parameters' = zipWith3 update parameters gradients gsum'
instance Optimizer Adagrad where
step = adagrad
-- | syntactic sugar for looping with foldM
foldLoop :: a -> Int -> (a -> Int -> IO a) -> IO a
foldLoop x count block = foldM block x [1 .. count]