187 lines
6.1 KiB
Haskell
187 lines
6.1 KiB
Haskell
module MachineLearning where
|
|
|
|
import Control.Monad (join, replicateM)
|
|
import Debug.Trace (trace)
|
|
import System.Random (Random, StdGen, mkStdGen, random)
|
|
|
|
sigmoid :: (Floating a) => a -> a
|
|
sigmoid x = 1 / (1 + exp (-x))
|
|
|
|
randomList :: (Random a) => Int -> StdGen -> ([a], StdGen)
|
|
randomList n gen = go n gen []
|
|
where
|
|
go 0 g acc = (reverse acc, g)
|
|
go m g acc =
|
|
let (value, newGen) = random g :: (Random a) => (a, StdGen)
|
|
in go (m - 1) newGen (value : acc)
|
|
|
|
-- definition of a neuron
|
|
|
|
data NeuralNetwork = NeuralNetwork
|
|
{ layers :: [[Neuron]],
|
|
finalLayer :: Neuron
|
|
}
|
|
|
|
runNetwork :: NeuralNetwork -> Input -> Output
|
|
runNetwork NeuralNetwork {layers, finalLayer} input =
|
|
run finalLayer $ runLayers layers input
|
|
where
|
|
runLayers [] input = input
|
|
runLayers (l : ls) input = runLayers ls $ map (`run` input) l
|
|
|
|
data Neuron = Neuron
|
|
{ bias :: Float,
|
|
activate :: Float -> Output,
|
|
weights :: [Weight]
|
|
}
|
|
|
|
instance Show Neuron where
|
|
show neuron = "w = " ++ show (weights neuron) ++ ", b = " ++ show (bias neuron)
|
|
|
|
type Weight = Float
|
|
|
|
type Output = Float
|
|
|
|
type Input = [Float]
|
|
|
|
type TrainingData = [(Input, Output)]
|
|
|
|
run :: Neuron -> Input -> Output
|
|
run (Neuron {activate, weights, bias}) input = activate $ bias + sum (zipWith (*) weights input)
|
|
|
|
modifyWeights :: [Weight] -> Neuron -> Neuron
|
|
modifyWeights dw neuron = neuron {weights = zipWith (+) dw (weights neuron)}
|
|
|
|
modifyBias :: Weight -> Neuron -> Neuron
|
|
modifyBias db neuron = neuron {bias = db + bias neuron}
|
|
|
|
initializeNeuron :: StdGen -> Int -> (Neuron, StdGen)
|
|
initializeNeuron gen nWeights =
|
|
let (weights, gen') = randomList nWeights gen
|
|
(bias, gen'') = random gen'
|
|
in ( Neuron
|
|
{ bias = bias,
|
|
activate = id,
|
|
weights = weights
|
|
},
|
|
gen''
|
|
)
|
|
|
|
-- prerequisites for gradient descent
|
|
|
|
epsilon :: (Fractional a) => a
|
|
epsilon = 1e-3
|
|
|
|
meanSquaredError :: (Floating a) => [a] -> [a] -> a
|
|
meanSquaredError xs ys = mean $ zipWith (\x y -> (x - y) ** 2) xs ys
|
|
where
|
|
mean xs = sum xs / fromIntegral (length xs)
|
|
|
|
cost :: Neuron -> TrainingData -> Float
|
|
cost neuron trainingData =
|
|
let actual = map (run neuron . fst) trainingData
|
|
expected = map snd trainingData
|
|
in meanSquaredError actual expected
|
|
|
|
costNetwork :: NeuralNetwork -> TrainingData -> Float
|
|
costNetwork network trainingData =
|
|
let actual = map (runNetwork network . fst) trainingData
|
|
expected = map snd trainingData
|
|
in meanSquaredError actual expected
|
|
|
|
oneHotVectors :: Int -> [[Weight]]
|
|
oneHotVectors n = [oneHot i | i <- [0 .. n - 1]]
|
|
where
|
|
oneHot index = [if j == index then epsilon else 0 | j <- [0 .. n - 1]]
|
|
|
|
differential :: (Neuron -> TrainingData -> Float) -> Neuron -> TrainingData -> Neuron
|
|
differential costFunc neuron trainingData =
|
|
let c = costFunc neuron trainingData
|
|
weightUpdates = oneHotVectors $ length $ weights neuron
|
|
dws =
|
|
map
|
|
( \weightUpdate ->
|
|
(costFunc (modifyWeights weightUpdate neuron) trainingData - c) / epsilon
|
|
)
|
|
weightUpdates
|
|
db = (costFunc (modifyBias epsilon neuron) trainingData - c) / epsilon
|
|
in neuron {weights = dws, bias = db}
|
|
|
|
differentialNetwork :: NeuralNetwork -> TrainingData -> NeuralNetwork
|
|
differentialNetwork network trainingData =
|
|
let costFunc = costNetwork
|
|
updatedLayers = map (map (\neuron -> differential costFunc neuron trainingData)) (layers network)
|
|
updatedFinalLayer = differential costFunc (finalLayer network) trainingData
|
|
in NeuralNetwork {layers = updatedLayers, finalLayer = updatedFinalLayer}
|
|
|
|
learn :: Float -> Neuron -> (Neuron -> Neuron)
|
|
learn learningRate differential =
|
|
modifyBias (-learningRate * bias differential)
|
|
. modifyWeights (map (\dw -> -learningRate * dw) (weights differential))
|
|
|
|
learnNetwork :: Float -> NeuralNetwork -> (NeuralNetwork -> NeuralNetwork)
|
|
learnNetwork learningRate differential network =
|
|
NeuralNetwork
|
|
{ finalLayer = learn learningRate (finalLayer differential) (finalLayer network),
|
|
layers = zipWith (zipWith (learn learningRate)) (layers differential) (layers network)
|
|
}
|
|
|
|
epoch :: TrainingData -> Neuron -> Neuron
|
|
epoch trainingData neuron =
|
|
let neuron' = learn learningRate (differential cost neuron trainingData) neuron
|
|
in neuron'
|
|
where
|
|
learningRate = 1e-3
|
|
|
|
-- concrete example
|
|
|
|
trainingDataAdd, trainingDataDouble, trainingDataOr, trainingDataAnd, trainingDataNand, trainingDataNot, trainingDataXor :: TrainingData
|
|
trainingDataDouble = [([x], x * 2) | x <- [0 .. 4]]
|
|
trainingDataAdd = [([x, y], x + y) | x <- [1 .. 10], y <- [1 .. 10]]
|
|
trainingDataOr = [([0, 0], 0), ([1, 0], 1), ([0, 1], 1), ([1, 1], 1)]
|
|
trainingDataAnd = [([0, 0], 0), ([1, 0], 0), ([0, 1], 0), ([1, 1], 1)]
|
|
trainingDataNand = [([0, 0], 1), ([1, 0], 1), ([0, 1], 1), ([1, 1], 0)]
|
|
trainingDataXor = [([0, 0], 0), ([1, 0], 1), ([0, 1], 1), ([1, 1], 0)]
|
|
trainingDataNot = [([0], 1), ([1], 0)]
|
|
|
|
gen = mkStdGen 69
|
|
|
|
trainDouble n =
|
|
let (neuron, _) = initializeNeuron gen 1
|
|
in iterate (epoch trainingDataDouble) neuron !! n
|
|
|
|
trainAdd n =
|
|
let (neuron, _) = initializeNeuron gen 2
|
|
in iterate (epoch trainingDataAdd) neuron !! n
|
|
|
|
trainOr n =
|
|
let (neuron, _) = initializeNeuron gen 2
|
|
neuron' = neuron {activate = sigmoid}
|
|
in iterate (epoch trainingDataOr) neuron' !! n
|
|
|
|
trainAnd n =
|
|
let (neuron, _) = initializeNeuron gen 2
|
|
neuron' = neuron {activate = sigmoid}
|
|
in iterate (epoch trainingDataAnd) neuron' !! n
|
|
|
|
trainXor n =
|
|
let (neuron, _) = initializeNeuron gen 2
|
|
neuron' = neuron {activate = sigmoid}
|
|
in iterate (epoch trainingDataXor) neuron' !! n
|
|
|
|
trainNand n =
|
|
let (neuron, _) = initializeNeuron gen 2
|
|
neuron' = neuron {activate = sigmoid}
|
|
in iterate (epoch trainingDataNand) neuron' !! n
|
|
|
|
trainNot n =
|
|
let (neuron, _) = initializeNeuron gen 2
|
|
neuron' = neuron {activate = sigmoid}
|
|
in iterate (epoch trainingDataNot) neuron' !! n
|
|
|
|
evaluateNeuron :: Neuron -> TrainingData -> [(Input, Output)]
|
|
evaluateNeuron neuron = map (\(x, _) -> (x, run neuron x))
|
|
|
|
evaluateNetwork :: NeuralNetwork -> TrainingData -> [(Input, Output)]
|
|
evaluateNetwork network = map (\(x, _) -> (x, runNetwork network x))
|