packages feed

backprop-0.2.7.0: bench/bench.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

import Control.DeepSeq
import Criterion.Main
import Criterion.Types
import Data.Char
import Data.Functor.Identity
import Data.Time
import qualified Data.Vector as V
import GHC.Generics (Generic)
import GHC.TypeLits
import Lens.Micro
import Lens.Micro.TH
import Numeric.Backprop
import Numeric.Backprop.Class
import qualified Numeric.LinearAlgebra as HM
import Numeric.LinearAlgebra.Static
import System.Directory
import qualified System.Random.MWC as MWC

type family HKD f a where
  HKD Identity a = a
  HKD f a = f a

data Layer' i o f
  = Layer
  { _lWeights :: !(HKD f (L o i))
  , _lBiases :: !(HKD f (R o))
  }
  deriving (Generic)

type Layer i o = Layer' i o Identity

deriving instance (KnownNat i, KnownNat o) => Show (Layer i o)
instance NFData (Layer i o)

makeLenses ''Layer'

data Network' i h1 h2 o f
  = Net
  { _nLayer1 :: !(HKD f (Layer i h1))
  , _nLayer2 :: !(HKD f (Layer h1 h2))
  , _nLayer3 :: !(HKD f (Layer h2 o))
  }
  deriving (Generic)

type Network i h1 h2 o = Network' i h1 h2 o Identity

deriving instance (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) => Show (Network i h1 h2 o)
instance NFData (Network i h1 h2 o)

makeLenses ''Network'

main :: IO ()
main = do
  g <-
    MWC.initialize
      . V.fromList
      . map (fromIntegral . ord)
      $ "hello world"
  test0 <- MWC.uniformR @(R 784, R 10) ((0, 0), (1, 1)) g
  net0 <- MWC.uniformR @(Network 784 300 100 10) (-0.5, 0.5) g
  t <- getZonedTime
  let tstr = formatTime defaultTimeLocale "%Y%m%d-%H%M%S" t
  createDirectoryIfMissing True "bench-results"
  defaultMainWith
    defaultConfig
      { reportFile = Just $ "bench-results/mnist-bench_" ++ tstr ++ ".html"
      , timeLimit = 10
      }
    [ bgroup
        "gradient"
        [ let runTest x y = gradNetManual x y net0
           in bench "manual" $ nf (uncurry runTest) test0
        , let runTest x y = gradBP (netErr x y) net0
           in bench "bp-lens" $ nf (uncurry runTest) test0
        , let runTest x y = gradBP (netErrHKD x y) net0
           in bench "bp-hkd" $ nf (uncurry runTest) test0
        , let runTest x y = gradBP (\n' -> netErrHybrid n' y x) net0
           in bench "hybrid" $ nf (uncurry runTest) test0
        ]
    , bgroup
        "descent"
        [ let runTest x y = trainStepManual 0.02 x y net0
           in bench "manual" $ nf (uncurry runTest) test0
        , let runTest x y = trainStep 0.02 x y net0
           in bench "bp-lens" $ nf (uncurry runTest) test0
        , let runTest x y = trainStepHKD 0.02 x y net0
           in bench "bp-hkd" $ nf (uncurry runTest) test0
        , let runTest x y = trainStepHybrid 0.02 x y net0
           in bench "hybrid" $ nf (uncurry runTest) test0
        ]
    , bgroup
        "run"
        [ let runTest = runNetManual net0
           in bench "manual" $ nf runTest (fst test0)
        , let runTest x = evalBP (`runNetwork` x) net0
           in bench "bp-lens" $ nf runTest (fst test0)
        , let runTest x = evalBP (`runNetworkHKD` x) net0
           in bench "bp-hkd" $ nf runTest (fst test0)
        , let runTest x = evalBP (`runNetHybrid` x) net0
           in bench "hybrid" $ nf runTest (fst test0)
        ]
    ]

-- ------------------------------
-- - "Backprop" Lens Mode       -
-- ------------------------------

runLayer ::
  (KnownNat i, KnownNat o, Reifies s W) =>
  BVar s (Layer i o) ->
  BVar s (R i) ->
  BVar s (R o)
runLayer l x = (l ^^. lWeights) #>! x + (l ^^. lBiases)
{-# INLINE runLayer #-}

softMax :: (KnownNat n, Reifies s W) => BVar s (R n) -> BVar s (R n)
softMax x = konst' (1 / sumElements' expx) * expx
  where
    expx = exp x
{-# INLINE softMax #-}

runNetwork ::
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o, Reifies s W) =>
  BVar s (Network i h1 h2 o) ->
  R i ->
  BVar s (R o)
runNetwork n =
  softMax
    . runLayer (n ^^. nLayer3)
    . logistic
    . runLayer (n ^^. nLayer2)
    . logistic
    . runLayer (n ^^. nLayer1)
    . auto
{-# INLINE runNetwork #-}

crossEntropy ::
  (KnownNat n, Reifies s W) =>
  R n ->
  BVar s (R n) ->
  BVar s Double
crossEntropy t r = negate $ log r <.>! auto t
{-# INLINE crossEntropy #-}

netErr ::
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o, Reifies s W) =>
  R i ->
  R o ->
  BVar s (Network i h1 h2 o) ->
  BVar s Double
netErr x t n = crossEntropy t (runNetwork n x)
{-# INLINE netErr #-}

trainStep ::
  forall i h1 h2 o.
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) =>
  Double ->
  R i ->
  R o ->
  Network i h1 h2 o ->
  Network i h1 h2 o
trainStep r !x !t !n = n - realToFrac r * gradBP (netErr x t) n
{-# INLINE trainStep #-}

-- ------------------------------
-- - "Backprop" HKD Mode        -
-- ------------------------------

runLayerHKD ::
  (KnownNat i, KnownNat o, Reifies s W) =>
  BVar s (Layer i o) ->
  BVar s (R i) ->
  BVar s (R o)
runLayerHKD (splitBV -> Layer w b) x = w #>! x + b
{-# INLINE runLayerHKD #-}

runNetworkHKD ::
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o, Reifies s W) =>
  BVar s (Network i h1 h2 o) ->
  R i ->
  BVar s (R o)
runNetworkHKD (splitBV -> Net l1 l2 l3) =
  softMax
    . runLayerHKD l3
    . logistic
    . runLayerHKD l2
    . logistic
    . runLayerHKD l1
    . auto
{-# INLINE runNetworkHKD #-}

netErrHKD ::
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o, Reifies s W) =>
  R i ->
  R o ->
  BVar s (Network i h1 h2 o) ->
  BVar s Double
netErrHKD x t n = crossEntropy t (runNetworkHKD n x)
{-# INLINE netErrHKD #-}

trainStepHKD ::
  forall i h1 h2 o.
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) =>
  Double ->
  R i ->
  R o ->
  Network i h1 h2 o ->
  Network i h1 h2 o
trainStepHKD r !x !t !n = n - realToFrac r * gradBP (netErrHKD x t) n
{-# INLINE trainStepHKD #-}

-- ------------------------------
-- - "Manual" Mode              -
-- ------------------------------

runLayerManual ::
  (KnownNat i, KnownNat o) =>
  Layer i o ->
  R i ->
  R o
runLayerManual l x = (l ^. lWeights) #> x + (l ^. lBiases)
{-# INLINE runLayerManual #-}

softMaxManual :: KnownNat n => R n -> R n
softMaxManual x = konst (1 / sumElements expx) * expx
  where
    expx = exp x
{-# INLINE softMaxManual #-}

runNetManual ::
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) =>
  Network i h1 h2 o ->
  R i ->
  R o
runNetManual n =
  softMaxManual
    . runLayerManual (n ^. nLayer3)
    . logistic
    . runLayerManual (n ^. nLayer2)
    . logistic
    . runLayerManual (n ^. nLayer1)
{-# INLINE runNetManual #-}

gradNetManual ::
  forall i h1 h2 o.
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) =>
  R i ->
  R o ->
  Network i h1 h2 o ->
  Network i h1 h2 o
gradNetManual x t (Net (Layer w1 b1) (Layer w2 b2) (Layer w3 b3)) =
  let y1 = w1 #> x
      z1 = y1 + b1
      x2 = logistic z1
      y2 = w2 #> x2
      z2 = y2 + b2
      x3 = logistic z2
      y3 = w3 #> x3
      z3 = y3 + b3
      o0 = exp z3
      o1 = HM.sumElements (extract o0)
      o2 = o0 / konst o1
      -- o3 = - (log o2 <.> t)
      dEdO3 = 1
      dEdO2 = - (dEdO3 * t / o2)
      dEdO1 = -((dEdO2 <.> o0) / (o1 ** 2))
      dEdO0 = konst dEdO1 + dEdO2 / konst o1
      dEdZ3 = dEdO0 * o0
      dEdY3 = dEdZ3
      dEdX3 = tr w3 #> dEdY3
      dEdZ2 = dEdX3 * (x3 * (1 - x3))
      dEdY2 = dEdZ2
      dEdX2 = tr w2 #> dEdY2
      dEdZ1 = dEdX2 * (x2 * (1 - x2))
      dEdY1 = dEdZ1
      dEdB3 = dEdZ3
      dEdW3 = dEdY3 `outer` x3
      dEdB2 = dEdZ2
      dEdW2 = dEdY2 `outer` x2
      dEdB1 = dEdZ1
      dEdW1 = dEdY1 `outer` x
   in Net (Layer dEdW1 dEdB1) (Layer dEdW2 dEdB2) (Layer dEdW3 dEdB3)
{-# INLINE gradNetManual #-}

trainStepManual ::
  forall i h1 h2 o.
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) =>
  Double ->
  R i ->
  R o ->
  Network i h1 h2 o ->
  Network i h1 h2 o
trainStepManual r !x !t !n =
  let gN = gradNetManual x t n
   in n - (realToFrac r * gN)

-- ------------------------------
-- - "Hybrid" Mode              -
-- ------------------------------

layerOp :: (KnownNat i, KnownNat o) => Op '[Layer i o, R i] (R o)
layerOp = op2 $ \(Layer w b) x ->
  ( w #> x + b
  , \g -> (Layer (g `outer` x) g, tr w #> g)
  )
{-# INLINE layerOp #-}

logisticOp ::
  Floating a =>
  Op '[a] a
logisticOp = op1 $ \x ->
  let lx = logistic x
   in (lx, \g -> lx * (1 - lx) * g)
{-# INLINE logisticOp #-}

softMaxOp ::
  KnownNat n =>
  Op '[R n] (R n)
softMaxOp = op1 $ \x ->
  let expx = exp x
      tot = sumElements expx
      invtot = 1 / tot
      res = konst invtot * expx
   in ( res
      , \g -> res - konst (invtot ** 2) * exp (2 * x) * g
      )
{-# INLINE softMaxOp #-}

softMaxCrossEntropyOp ::
  KnownNat n =>
  R n ->
  Op '[R n] Double
softMaxCrossEntropyOp targ = op1 $ \x ->
  let expx = exp x
      sm = konst (1 / sumElements expx) * expx
      ce = negate $ log sm <.> targ
   in ( ce
      , \g -> (sm - targ) * konst g
      )
{-# INLINE softMaxCrossEntropyOp #-}

runNetHybrid ::
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o, Reifies s W) =>
  BVar s (Network i h1 h2 o) ->
  R i ->
  BVar s (R o)
runNetHybrid n =
  liftOp1 softMaxOp
    . liftOp2 layerOp (n ^^. nLayer3)
    . liftOp1 logisticOp
    . liftOp2 layerOp (n ^^. nLayer2)
    . liftOp1 logisticOp
    . liftOp2 layerOp (n ^^. nLayer1)
    . auto
{-# INLINE runNetHybrid #-}

netErrHybrid ::
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o, Reifies s W) =>
  BVar s (Network i h1 h2 o) ->
  R o ->
  R i ->
  BVar s Double
netErrHybrid n t =
  liftOp1 (softMaxCrossEntropyOp t)
    . liftOp2 layerOp (n ^^. nLayer3)
    . liftOp1 logisticOp
    . liftOp2 layerOp (n ^^. nLayer2)
    . liftOp1 logisticOp
    . liftOp2 layerOp (n ^^. nLayer1)
    . auto
{-# INLINE netErrHybrid #-}

trainStepHybrid ::
  forall i h1 h2 o.
  (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) =>
  Double ->
  R i ->
  R o ->
  Network i h1 h2 o ->
  Network i h1 h2 o
trainStepHybrid r !x !t !n =
  let gN = gradBP (\n' -> netErrHybrid n' t x) n
   in n - (realToFrac r * gN)
{-# INLINE trainStepHybrid #-}

-- ------------------------------
-- - Operations                 -
-- ------------------------------

infixr 8 #>!
(#>!) ::
  (KnownNat m, KnownNat n, Reifies s W) =>
  BVar s (L m n) ->
  BVar s (R n) ->
  BVar s (R m)
(#>!) = liftOp2 . op2 $ \m v ->
  (m #> v, \g -> (g `outer` v, tr m #> g))
{-# INLINE (#>!) #-}

infixr 8 <.>!
(<.>!) ::
  (KnownNat n, Reifies s W) =>
  BVar s (R n) ->
  BVar s (R n) ->
  BVar s Double
(<.>!) = liftOp2 . op2 $ \x y ->
  ( x <.> y
  , \g -> (konst g * y, x * konst g)
  )
{-# INLINE (<.>!) #-}

konst' ::
  (KnownNat n, Reifies s W) =>
  BVar s Double ->
  BVar s (R n)
konst' = liftOp1 . op1 $ \c -> (konst c, HM.sumElements . extract)
{-# INLINE konst' #-}

sumElements :: KnownNat n => R n -> Double
sumElements = HM.sumElements . extract
{-# INLINE sumElements #-}

sumElements' ::
  (KnownNat n, Reifies s W) =>
  BVar s (R n) ->
  BVar s Double
sumElements' = liftOp1 . op1 $ \x -> (sumElements x, konst)
{-# INLINE sumElements' #-}

logistic :: Floating a => a -> a
logistic x = 1 / (1 + exp (-x))
{-# INLINE logistic #-}

-- ------------------------------
-- - Instances                  -
-- ------------------------------

instance (KnownNat i, KnownNat o) => Num (Layer i o) where
  Layer w1 b1 + Layer w2 b2 = Layer (w1 + w2) (b1 + b2)
  Layer w1 b1 - Layer w2 b2 = Layer (w1 - w2) (b1 - b2)
  Layer w1 b1 * Layer w2 b2 = Layer (w1 * w2) (b1 * b2)
  abs (Layer w b) = Layer (abs w) (abs b)
  signum (Layer w b) = Layer (signum w) (signum b)
  negate (Layer w b) = Layer (negate w) (negate b)
  fromInteger x = Layer (fromInteger x) (fromInteger x)

instance (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) => Num (Network i h1 h2 o) where
  Net a b c + Net d e f = Net (a + d) (b + e) (c + f)
  Net a b c - Net d e f = Net (a - d) (b - e) (c - f)
  Net a b c * Net d e f = Net (a * d) (b * e) (c * f)
  abs (Net a b c) = Net (abs a) (abs b) (abs c)
  signum (Net a b c) = Net (signum a) (signum b) (signum c)
  negate (Net a b c) = Net (negate a) (negate b) (negate c)
  fromInteger x = Net (fromInteger x) (fromInteger x) (fromInteger x)

instance (KnownNat i, KnownNat o) => Fractional (Layer i o) where
  Layer w1 b1 / Layer w2 b2 = Layer (w1 / w2) (b1 / b2)
  recip (Layer w b) = Layer (recip w) (recip b)
  fromRational x = Layer (fromRational x) (fromRational x)

instance (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) => Fractional (Network i h1 h2 o) where
  Net a b c / Net d e f = Net (a / d) (b / e) (c / f)
  recip (Net a b c) = Net (recip a) (recip b) (recip c)
  fromRational x = Net (fromRational x) (fromRational x) (fromRational x)

instance KnownNat n => MWC.Variate (R n) where
  uniform g = randomVector <$> MWC.uniform g <*> pure Uniform
  uniformR (l, h) g = (\x -> x * (h - l) + l) <$> MWC.uniform g

instance (KnownNat m, KnownNat n) => MWC.Variate (L m n) where
  uniform g = uniformSample <$> MWC.uniform g <*> pure 0 <*> pure 1
  uniformR (l, h) g = (\x -> x * (h - l) + l) <$> MWC.uniform g

instance (KnownNat i, KnownNat o) => MWC.Variate (Layer i o) where
  uniform g = Layer <$> MWC.uniform g <*> MWC.uniform g
  uniformR (l, h) g = (\x -> x * (h - l) + l) <$> MWC.uniform g

instance (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) => MWC.Variate (Network i h1 h2 o) where
  uniform g = Net <$> MWC.uniform g <*> MWC.uniform g <*> MWC.uniform g
  uniformR (l, h) g = (\x -> x * (h - l) + l) <$> MWC.uniform g

instance Backprop (R n) where
  zero = zeroNum
  add = addNum
  one = oneNum

instance (KnownNat n, KnownNat m) => Backprop (L m n) where
  zero = zeroNum
  add = addNum
  one = oneNum

instance (KnownNat i, KnownNat o) => Backprop (Layer i o)
instance (KnownNat i, KnownNat h1, KnownNat h2, KnownNat o) => Backprop (Network i h1 h2 o)