module ML.NN
(
ActivationFunction
, ActivationFunctionDerivative
, Layer(..)
, Network(..)
, randLayer
, randNetwork
, feedForward
, runNetwork
, TrainingConfig(..)
, Sample(..)
, Gradient(..)
, CostDerivative
, sgd
, gradientDescentCore
, mse'
, computeZsAndAs
) where
import ML.NN.ActivationFunction (ActivationFunction, ActivationFunctionDerivative)
import ML.Sample (Sample(..))
import Control.Monad (forM, replicateM)
import Control.Monad.Random (Rand, evalRandIO, liftRand)
import Control.Monad.Random.Extended (vshuffle)
import Control.Monad.State (State, evalState, get, put)
import Data.Foldable (foldl')
import Data.List (unfoldr)
import Data.Random.Normal (normal)
import qualified Data.Vector as V
import Numeric.LinearAlgebra (Matrix, R, Vector, (><), (<>), (#>), cmap, konst, tr, vector)
import Numeric.LinearAlgebra.Data (asColumn, asRow, size)
import System.Random (RandomGen)
data Layer = Layer
{ layerBiases :: !(Vector R)
, layerWeights :: !(Matrix R)
} deriving (Show, Read)
data Network = Network
{ networkLayers :: [Layer]
} deriving (Show, Read)
data TrainingConfig = TrainingConfig
{ trainingEta :: R
, trainingActivation :: ActivationFunction
, trainingActivationDerivative :: ActivationFunctionDerivative
, trainingCostDerivative :: CostDerivative
}
randNetwork :: RandomGen g => [Int] -> Rand g Network
randNetwork sizes = Network <$> forM dims (uncurry randLayer)
where dims = adjacentPairs sizes
adjacentPairs :: [a] -> [(a,a)]
adjacentPairs xs = zip (init xs) (tail xs)
randLayer :: RandomGen g
=> Int
-> Int
-> Rand g Layer
randLayer numInputs numNeurons = do
bias <- vector <$> replicateM numNeurons (liftRand normal)
weights <- (numNeurons><numInputs) <$> replicateM (numNeurons*numInputs) (liftRand normal)
return $ Layer bias weights
feedForward :: ActivationFunction
-> Vector R
-> Layer
-> Vector R
feedForward act x (Layer biases weights) = cmap act z
where z = (weights #> x) + biases
runNetwork :: Network
-> ActivationFunction
-> Vector R
-> Vector R
runNetwork (Network layers) act input = foldl' (feedForward act) input layers
type CostDerivative = Vector R -> Vector R -> Vector R
data Gradient = Gradient
{ gradientNablaB :: !(V.Vector (Vector R))
, gradientNablaW :: !(V.Vector (Matrix R))
} deriving (Read, Show)
sumGradients :: V.Vector Gradient -> Gradient
sumGradients = V.foldl1' addGradient
where (Gradient !lb !lw) `addGradient` (Gradient !rb !rw) =
Gradient (V.zipWith (+) lb rb) (V.zipWith (+) lw rw)
computeZsAndAs :: ActivationFunction -> Network -> Vector R -> ([Vector R], [Vector R])
computeZsAndAs act (Network layers) input = (reverse zs, reverse (input:as))
where (zs, as) = unzip $ evalState (mapM computeZA layers) input
computeZA :: Layer -> State (Vector R) (Vector R, Vector R)
computeZA (Layer b w) = do
x <- get
let z = w #> x + b
a = cmap act z
put a
pure $ (z,a)
backpropogation :: ActivationFunction
-> ActivationFunctionDerivative
-> CostDerivative
-> Network
-> Sample
-> Gradient
backpropogation act act' cost' net@(Network layers) (Sample x y) =
Gradient (V.reverse $ V.cons nablaB_L nablaBs) (V.reverse $ V.cons nablaW_L nablaWs)
where (z:zs, a:a':as) = computeZsAndAs act net x
delta_L = cost' a y * cmap act' z
nablaB_L = delta_L
nablaW_L = asColumn delta_L <> asRow a'
(nablaBs, nablaWs) =
backpropogationBackwardsPass act' delta_L zs as (reverse $ tail layers)
backpropogationBackwardsPass :: ActivationFunctionDerivative
-> Vector R
-> [Vector R]
-> [Vector R]
-> [Layer]
-> (V.Vector (Vector R), V.Vector (Matrix R))
backpropogationBackwardsPass act' delta_L zs as layers = V.unzip output
where !output = V.fromList $ evalState (mapM processLayer (zip3 layers zs as)) delta_L
processLayer :: (Layer, Vector R, Vector R) -> State (Vector R) (Vector R, Matrix R)
processLayer ((Layer _bias weights), z, a) = do
delta <- get
let actPrime = cmap act' z
delta' = (tr weights #> delta) * actPrime
put delta'
pure (delta', asColumn delta' <> asRow a)
gradientDescentCore :: TrainingConfig -> V.Vector Sample -> Network -> Network
gradientDescentCore (TrainingConfig eta act act' cost') trainingData net@(Network layers) =
Network layers'
where sampleGradients :: V.Vector Gradient
!sampleGradients = V.map (backpropogation act act' cost' net) trainingData
Gradient nablaB nablaW = sumGradients sampleGradients
numSamples = fromIntegral $ length trainingData
layers' = V.toList $ V.zipWith3 updateWeightsAndBiases (V.fromList layers) nablaB nablaW
updateWeightsAndBiases :: Layer -> Vector R -> Matrix R -> Layer
updateWeightsAndBiases (Layer b w) nb nw =
Layer (b(konst (eta/numSamples) (size b)) * nb)
(w(konst (eta/numSamples) (size w)) * nw)
sgd :: TrainingConfig
-> Int
-> Int
-> V.Vector Sample
-> Network
-> IO Network
sgd trainingConfig epochs miniBatchSize trainingData network = go epochs network
where go 0 net = putStrLn "done!" >> pure net
go epoch net = do
putStrLn $ "Epoch: " ++ show epoch
shuffledTrainingData <- evalRandIO $ vshuffle trainingData
let miniBatches = miniBatchSize `vChunksOf` shuffledTrainingData
net' = foldl' (flip (gradientDescentCore trainingConfig)) net miniBatches
go (epoch1) net'
vChunksOf :: Int -> V.Vector a -> [V.Vector a]
vChunksOf n vec = Data.List.unfoldr makeChunk vec
where makeChunk :: V.Vector a -> Maybe (V.Vector a, V.Vector a)
makeChunk v | V.null v = Nothing
| otherwise = Just $ V.splitAt n v
mse' :: CostDerivative
mse' outputActivations y = outputActivations y