Learning Objectives

  • Understand the perceptron and multilayer perceptron (MLP)
  • Implement forward pass and backpropagation
  • Train networks on simple problems (XOR)
  • Debug training issues with numerical gradient checking

2.1 The Perceptron

Key Concepts

  • Single neuron with linear activation
  • Perceptron learning rule
  • Limitations and the XOR problem
Understanding where MLPs came from helps you appreciate their capability and shows why depth matters.
Haskell — single perceptron
data Neuron = Neuron
  { weights :: Vector Double
  , bias    :: Double
  }

-- Forward pass (linear, no activation yet)
forward :: Neuron -> Vector Double -> Double
forward n x = dot (weights n) x + bias n

-- Prediction (threshold at 0 for binary classification)
predict :: Neuron -> Vector Double -> Bool
predict n x = forward n x > 0

Extra reading

Perceptrons by Minsky and Papert (1969) — the classic text that shows the mathematical limitations of single-layer networks.

2.2 Multilayer Perceptron (MLP)

Key Concepts

  • Stacking layers and nonlinearity
  • Activation functions: sigmoid, tanh, ReLU
  • Solving XOR with hidden layers
  • Universal approximation theorem
MLPs are your foundation. Everything else in this course builds on this structure.
Haskell — MLP structure
data Layer = Layer
  { weights    :: Matrix Double
  , bias       :: Vector Double
  , activation :: Activation
  }

data MLP = MLP [Layer]

data Activation = Sigmoid | Tanh | ReLU
  deriving (Show, Eq)

-- Forward pass through network
forwardMLP :: MLP -> Vector Double -> Vector Double
forwardMLP (MLP layers) input = foldl forwardLayer input layers
  where
    forwardLayer x layer =
      activateLayer (activation layer)
                    (matmul (weights layer) x + bias layer)

-- Activation functions
sigmoid :: Double -> Double
sigmoid x = 1 / (1 + exp (-x))

relu :: Double -> Double
relu x = max 0 x

activateLayer :: Activation -> Vector Double -> Vector Double
activateLayer Sigmoid = fmap sigmoid
activateLayer Tanh    = fmap tanh
activateLayer ReLU    = fmap relu

Implementation Task 2.2 — Backpropagation

This is complex but critical. Key steps:

  • Compute derivatives through activation functions
  • Apply chain rule through multiple layers
  • Accumulate gradients
  • Numerical gradient checking (sanity-check your implementation)
Haskell — backprop through one layer
data ComputeContext = ComputeContext
  { layerInputs  :: [Vector Double]
  , layerOutputs :: [Vector Double]
  , parameters   :: MLP
  }

-- Store forward values needed for backward pass
computeContext :: MLP -> Vector Double -> ComputeContext

-- Backprop through one layer
backpropLayer
  :: Layer
  -> Vector Double          -- upstream gradient
  -> Vector Double          -- layer input
  -> (Gradient, Vector Double)   -- (gradient w.r.t. params, gradient for previous layer)
backpropLayer layer upstreamGrad layerInput =
  let activationGrad = computeActivationGradient (activation layer)
                         (matmul (weights layer) layerInput)
      downstreamGrad = elementwiseMultiply upstreamGrad activationGrad
      weightGrad     = outer downstreamGrad layerInput
      biasGrad       = downstreamGrad
      previousGrad   = matmul (transpose (weights layer)) downstreamGrad
  in ((weightGrad, biasGrad), previousGrad)
Haskell — numerical gradient checking
-- Verify analytical gradients against numerical finite differences
numericalGradient :: (Vector Double -> Double) -> Vector Double -> Double -> Vector Double
numericalGradient f x epsilon =
  let unitVectors = [[if i == j then 1 else 0 | j <- [1..length x]] | i <- [1..length x]]
  in vector [((f (x + epsilon * e)) - (f (x - epsilon * e))) / (2 * epsilon)
             | e <- unitVectors]

-- Relative error should be < 1e-5

2.3 Training on XOR

Haskell — XOR training loop
xorData :: [(Vector Double, Double)]
xorData = [([0, 0], 0), ([0, 1], 1), ([1, 0], 1), ([1, 1], 0)]

-- Training loop for one epoch
trainEpoch :: MLP -> Double -> [(Vector Double, Double)] -> MLP
trainEpoch network learningRate examples =
  foldl updateNetwork network examples
  where
    updateNetwork net (input, target) =
      let ctx    = computeContext net input
          output = last (layerOutputs ctx)
          loss   = meanSquaredError output [target]
          grads  = backprop net ctx [target]
      in updateParameters net learningRate grads

-- Loss function (MSE)
meanSquaredError :: Vector Double -> Vector Double -> Double
meanSquaredError pred target =
  sum (map (^2) (zipWith (-) pred target)) / fromIntegral (length pred)

What to Expect

  • Network should learn XOR within 1000–5000 epochs
  • Loss should decrease (mostly) monotonically
  • Verify by checking predictions on all four XOR inputs

Troubleshooting

SymptomLikely causeFix
Loss doesn't decreaseLearning rate wrongTry 0.1–1.0
Stuck at local minimumBad initialisationTry different random seed
Numerical instabilityVanishing/exploding gradientsCheck gradient norms

Reading & Viewing

Essential reading

Learning Representations by Back-Propagating Errors (1986) Rumelhart, Hinton, Williams — Nature 323 The paper that started it all. doi.org/10.1038/323533a0
Neural Networks and Deep Learning (free online) Michael Nielsen Chapter 2 on backpropagation is excellent. Start here. neuralnetworksanddeeplearning.com

Video

3Blue1Brown — Neural Networks (episodes 3–4) Deep visual explanation of backpropagation. YouTube playlist
Stanford CS231n — Lecture 4 (Backpropagation) Andrej Karpathy cs231n.stanford.edu

Blog

Calculus on Computational Graphs: Backpropagation Chris Olah colah.github.io/posts/2015-08-Backprop/