neural-0.3.0.1: src/Numeric/Neural/Pipes.hs
{-# OPTIONS_HADDOCK show-extensions #-}
{-# LANGUAGE BangPatterns #-}
{-|
Module : Numeric.Neural.Pipes
Description : a pipes API for models
Copyright : (c) Lars Brünjes, 2016
License : MIT
Maintainer : brunjlar@gmail.com
Stability : experimental
Portability : portable
This module provides a "pipes"-based API for working with models.
-}
module Numeric.Neural.Pipes
( TS(..)
, descentP
, simpleBatchP
, cachingBatchP
, reportTSP
, consumeTSP
, qualityP
, qualityP'
, classifierAccuracyP
, classifierAccuracyP'
, module Pipes
) where
import Data.MyPrelude
import Numeric.Neural.Model
import Numeric.Neural.Normalization
import Data.Utils.Cache
import Data.Utils.Random (takeR)
import Pipes
import qualified Pipes.Prelude as P
-- | The training state of a model.
--
data TS f g a b c = TS
{ tsModel :: Model f g a b c -- ^ updated model
, tsGeneration :: Int -- ^ generation
, tsEta :: Double -- ^ learning rate
, tsBatchError :: Double -- ^ last training error
}
-- | A 'Pipe' for training a model: It consumes mini-batches of samples from upstream and pushes
-- the updated training state downstream.
--
descentP :: (Foldable h, Monad m) =>
Model f g a b c -- ^ initial model
-> Int -- ^ first generation
-> (Int -> Double) -- ^ computes the learning rate from the generation
-> Pipe (h a) (TS f g a b c) m r
descentP m i f = loop m i where
loop m' i' = do
xs <- await
let !eta = f i'
let (e, m'') = descent m' eta xs
m'' `deepseq` yield TS
{ tsModel = m''
, tsGeneration = i'
, tsEta = eta
, tsBatchError = e
}
loop m'' (succ i')
-- | A simple 'Producer' of mini-batches.
simpleBatchP :: MonadRandom m
=> [a] -- ^ all available samples
-> Int -- ^ mini-batch size
-> Producer [a] m r
simpleBatchP xs n = forever $ lift (takeR n xs) >>= yield
-- | Function 'simpleBatchP' only works when all available samples fit into memory.
-- If this is not the case, 'cachingBatchP' can be used instead.
-- It takes an effectful way to get specific samples and then caches some of those samples
-- in memory for a couple of rounds, drawing mini-batches from the cached values.
--
cachingBatchP :: MonadRandom m
=> ([Int] -> m [a]) -- ^ get samples with specified indices
-> Int -- ^ number of all available samples
-> Int -- ^ mini-batch size
-> Int -- ^ cache size
-> Int -- ^ number of cache reuses
-> Producer [a] m s
cachingBatchP f ns bs cs nr = do
let c = newCache f cs
loop c
where
loop c = do
xs <- lift $ takeR cs [0 .. pred ns]
(ys, c') <- lift $ retrieveC c xs
replicateM_ nr $ lift (takeR bs ys) >>= yield
loop c'
-- | A 'Pipe' for progress reporting of model training.
--
reportTSP :: Monad m
=> Int -- ^ report interval
-> (TS f g a b c -> m ()) -- ^ report action
-> Pipe (TS f g a b c) (TS f g a b c) m r
reportTSP n act = P.mapM $ \ts -> do
when (tsGeneration ts `mod` n == 0) (act ts)
return ts
-- | A 'Consumer' of training states that decides when training is finished and then returns a value.
--
consumeTSP :: Monad m
=> (TS f g a b c -> m (Maybe x)) -- ^ check whether training is finished and what to return in that case
-> Consumer (TS f g a b c) m x
consumeTSP check = loop where
loop = do
ts <- await
mx <- lift (check ts)
case mx of
Just x -> return x
Nothing -> loop
-- | Computes the average "quality" of a given 'Model' over a stream of pairs of input and expected output.
--
qualityP :: (Monad m, Fractional x)
=> Model f g a b c -- ^ the 'Model'
-> (b -> c -> c -> x) -- ^ gives the quality for given input, expected output and actual output
-> Producer (b, c) m r -- ^ a 'Producer' of pairs of input and expected output
-> m (Maybe x) -- ^ the average model quality or 'Nothing' if the 'Producer' was empty.
qualityP m f p = P.fold g (0, 0 :: Int) h $ void p
where
g (!xs, !n) (b, c) = let c' = model m b
x = f b c c'
in (x + xs, succ n)
h (_, 0) = Nothing
h (x, n) = Just $ x / fromIntegral n
-- | Pure version of 'qualityP'.
--
qualityP' :: Fractional x
=> Model f g a b c -- ^ the 'Model'
-> (b -> c -> c -> x) -- ^ gives the quality for given input, expected output and actual output
-> [(b, c)] -- ^ list of pairs of input and expected output
-> Maybe x -- ^ the average model quality or 'Nothing' if the list was empty.
qualityP' m f xs = runIdentity $ qualityP m f $ each xs
-- | Specialization of 'qualityP' to the case of 'Classifier's.
--
classifierAccuracyP :: (Monad m, Eq c, Fractional x)
=> Classifier f n b c
-> Producer (b, c) m r
-> m (Maybe x)
classifierAccuracyP m = qualityP m $ \_ c c' -> if c == c' then 1 else 0
-- | Pure version of 'classifierAccuracyP'.
--
classifierAccuracyP' :: (Eq c, Fractional x)
=> Classifier f n b c
-> [(b, c)]
-> Maybe x
classifierAccuracyP' m xs = runIdentity $ classifierAccuracyP m $ each xs