packages feed

synapse-0.1.0.0: src/Synapse/NN/Batching.hs

{- | Implements batching - technology that allows packing and processing multiple samples at once.
-}


-- 'TypeFamilies' are needed to instantiate 'DType'.
{-# LANGUAGE TypeFamilies #-}


module Synapse.NN.Batching
    ( -- * 'Sample' datatype
    
      Sample (Sample, sampleInput, sampleOutput)

      -- * 'Dataset' datatype

    , Dataset (Dataset, unDataset)

    , datasetSize

    , shuffleDataset
    , splitDataset

    , VecDataset
    , BatchedDataset
    , batchVectors
    ) where


import Synapse.Tensors (DType, Indexable(unsafeIndex))

import Synapse.Tensors.Vec (Vec(Vec))
import qualified Synapse.Tensors.Vec as V

import Synapse.Tensors.Mat (Mat)
import qualified Synapse.Tensors.Mat as M

import Control.Monad.ST (runST)

import System.Random (RandomGen, uniformR)

import Data.Vector (thaw, unsafeFreeze)
import Data.Vector.Mutable (swap)


-- | 'Sample' datatype represents known pair of inputs and outputs of function that is unknown.
data Sample a = Sample
    { sampleInput  :: a  -- ^ Sample input.
    , sampleOutput :: a  -- ^ Sample output.
    } deriving (Eq, Show)

type instance DType (Sample a) = DType a


-- | 'Dataset' newtype wraps vector of 'Sample's - it represents known information about unknown function.
newtype Dataset a = Dataset 
    { unDataset :: Vec (Sample a)  -- ^ Unwraps 'Dataset' newtype.
    } deriving (Eq, Show)

type instance DType (Dataset a) = DType a

-- | Returns size of dataset.
datasetSize :: Dataset a -> Int
datasetSize = V.size . unDataset


-- | Shuffles any 'Dataset' using Fisher-Yates algorithm.
shuffleDataset :: RandomGen g => Dataset a -> g -> (Dataset a, g)
shuffleDataset (Dataset dataset) gen
    | V.size dataset <= 1 = (Dataset dataset, gen)
    | otherwise           = runST $ do
        mutableVector <- thaw $ V.unVec dataset
        gen' <- go mutableVector (V.size dataset - 1) gen
        shuffledVector <- unsafeFreeze mutableVector
        return (Dataset $ Vec shuffledVector, gen')
  where
    go _ 0 seed = return seed
    go v lastIndex seed = let (swapIndex, seed') = uniformR (0, lastIndex) seed
                          in swap v swapIndex lastIndex >> go v (lastIndex - 1) seed'

-- | Splits dataset such that size of left dataset divided on size of right dataset will be equal to given ratio.
splitDataset :: Dataset a -> Float -> (Dataset a, Dataset a)
splitDataset (Dataset dataset) ratio = let (left, right) = V.splitAt (round $ fromIntegral (V.size dataset) * ratio) dataset
                                       in (Dataset left, Dataset right)


-- | 'VecDataset' type alias represents 'Dataset's with samples of vector functions.
type VecDataset a = Dataset (Vec a)
-- | 'BatchedDataset' type alias represents 'Dataset's with samples of vector functions where multiple samples were batched together.
type BatchedDataset a = Dataset (Mat a)

-- | Batches 'VecDataset' by grouping a given amount of samples into batches.
batchVectors :: Int -> VecDataset a -> BatchedDataset a
batchVectors batchSize (Dataset dataset) = Dataset $ V.fromList $ map groupBatch $ split dataset
  where
    split vector
        | V.size vector <= batchSize = [vector]
        | otherwise                  = let (current, remainder) = V.splitAt batchSize vector
                                       in current : split remainder
    
    groupBatch vector = let (rows, inputCols) = (V.size vector, V.size $ sampleInput $ unsafeIndex vector 0)
                            group (r, c) = unsafeIndex ((if c < inputCols then sampleInput else sampleOutput) (unsafeIndex vector r)) (c `mod` inputCols)
                            fullBatch = M.generate (rows, inputCols + V.size (sampleOutput $ unsafeIndex vector 0)) group
                            (batchInput, batchOutput, _, _) = M.split fullBatch (rows, inputCols)
                        in Sample batchInput batchOutput