3.1.9Neural Network Fundamentals

Backpropagation algorithm derivation

1,727 words8 min readdifficulty · medium6 backlinks

WHY does backprop exist?

WHAT problem does it solve? A neural network has millions of weights. To train with gradient descent we need Lw\frac{\partial L}{\partial w} for every weight ww. Computing each derivative independently by the chain rule would repeat the same sub-expressions billions of times.

WHY it's fast: The gradient of a loss w.r.t. an early-layer weight contains as a factor the gradient w.r.t. later layers. So if we compute later gradients first and cache them, every earlier gradient is cheap. This caching-of-shared-subproblems is exactly dynamic programming over the computational graph.


Setup and notation

Consider layer ll (from 11 to LL). For layer ll:

  • W(l)W^{(l)} = weight matrix, b(l)b^{(l)} = bias vector
  • z(l)=W(l)a(l1)+b(l)z^{(l)} = W^{(l)} a^{(l-1)} + b^{(l)} — the pre-activation
  • a(l)=σ(z(l))a^{(l)} = \sigma(z^{(l)}) — the activation (σ\sigma = activation function)
  • a(0)=xa^{(0)} = x (input), a(L)=y^a^{(L)} = \hat y (output)
  • L(a(L),y)L(a^{(L)}, y) = loss

Deriving the four equations from scratch

Step 1 — Output layer error δ(L)\delta^{(L)}

HOW: By the chain rule, the loss depends on z(L)z^{(L)} only through a(L)=σ(z(L))a^{(L)} = \sigma(z^{(L)}):

δj(L)=Lzj(L)=Laj(L)aj(L)zj(L)\delta^{(L)}_j = \frac{\partial L}{\partial z^{(L)}_j} = \frac{\partial L}{\partial a^{(L)}_j}\cdot \frac{\partial a^{(L)}_j}{\partial z^{(L)}_j}

Why this step? aj(L)=σ(zj(L))a^{(L)}_j = \sigma(z^{(L)}_j) is the only path from zj(L)z^{(L)}_j to the loss, so we chain through it.

Since aj(L)=σ(zj(L))a^{(L)}_j=\sigma(z^{(L)}_j), the second factor is σ(zj(L))\sigma'(z^{(L)}_j). In vector form:

  δ(L)=aLσ(z(L))  \boxed{\;\delta^{(L)} = \nabla_a L \odot \sigma'(z^{(L)})\;}

where \odot is elementwise product (Hadamard).

Step 2 — Recurrence: δ(l)\delta^{(l)} from δ(l+1)\delta^{(l+1)}

WHY a recurrence? z(l)z^{(l)} affects the loss only through the next layer's pre-activations z(l+1)z^{(l+1)}. So:

δj(l)=Lzj(l)=kLzk(l+1)zk(l+1)zj(l)=kδk(l+1)zk(l+1)zj(l)\delta^{(l)}_j = \frac{\partial L}{\partial z^{(l)}_j} = \sum_k \frac{\partial L}{\partial z^{(l+1)}_k}\cdot \frac{\partial z^{(l+1)}_k}{\partial z^{(l)}_j} = \sum_k \delta^{(l+1)}_k \cdot \frac{\partial z^{(l+1)}_k}{\partial z^{(l)}_j}

Why the sum over kk? zj(l)z^{(l)}_j fans out to every neuron kk in layer l+1l+1; total sensitivity = sum over all those paths (multivariable chain rule).

Now compute zk(l+1)zj(l)\frac{\partial z^{(l+1)}_k}{\partial z^{(l)}_j}. Since zk(l+1)=iWki(l+1)ai(l)+bk(l+1)z^{(l+1)}_k = \sum_i W^{(l+1)}_{ki}\, a^{(l)}_i + b^{(l+1)}_k and ai(l)=σ(zi(l))a^{(l)}_i = \sigma(z^{(l)}_i):

zk(l+1)zj(l)=Wkj(l+1)σ(zj(l))\frac{\partial z^{(l+1)}_k}{\partial z^{(l)}_j} = W^{(l+1)}_{kj}\,\sigma'(z^{(l)}_j)

Substituting:

δj(l)=(kWkj(l+1)δk(l+1))σ(zj(l))\delta^{(l)}_j = \Big(\sum_k W^{(l+1)}_{kj}\,\delta^{(l+1)}_k\Big)\sigma'(z^{(l)}_j)

In matrix form:

  δ(l)=((W(l+1))δ(l+1))σ(z(l))  \boxed{\;\delta^{(l)} = \big((W^{(l+1)})^\top \delta^{(l+1)}\big)\odot \sigma'(z^{(l)})\;}

Step 3 — Gradient w.r.t. weights

zj(l)=iWji(l)ai(l1)+bj(l)z^{(l)}_j = \sum_i W^{(l)}_{ji} a^{(l-1)}_i + b^{(l)}_j, so zj(l)Wji(l)=ai(l1)\frac{\partial z^{(l)}_j}{\partial W^{(l)}_{ji}} = a^{(l-1)}_i:

LWji(l)=Lzj(l)zj(l)Wji(l)=δj(l)ai(l1)\frac{\partial L}{\partial W^{(l)}_{ji}} = \frac{\partial L}{\partial z^{(l)}_j}\cdot\frac{\partial z^{(l)}_j}{\partial W^{(l)}_{ji}} = \delta^{(l)}_j\, a^{(l-1)}_i

  LW(l)=δ(l)(a(l1))  \boxed{\;\frac{\partial L}{\partial W^{(l)}} = \delta^{(l)}\,(a^{(l-1)})^\top\;}

Why this outer product? Each weight WjiW_{ji} connects input ii to neuron jj; its gradient = (blame of jj) × (activity of ii).

Step 4 — Gradient w.r.t. biases

zj(l)bj(l)=1\frac{\partial z^{(l)}_j}{\partial b^{(l)}_j}=1, so

  Lb(l)=δ(l)  \boxed{\;\frac{\partial L}{\partial b^{(l)}} = \delta^{(l)}\;}



Worked example 1 — a 2-2-1 network by hand

Input x=[10]x=\begin{bmatrix}1\\0\end{bmatrix}, target y=1y=1. Sigmoid σ\sigma, MSE loss L=12(a(2)y)2L=\tfrac12(a^{(2)}-y)^2.

Weights: W(1)=[0.10.20.30.4]W^{(1)}=\begin{bmatrix}0.1&0.2\\0.3&0.4\end{bmatrix}, b(1)=0b^{(1)}=0, W(2)=[0.5  0.6]W^{(2)}=[0.5\ \ 0.6], b(2)=0b^{(2)}=0.

Forward:

  • z(1)=W(1)x=[0.10.3]z^{(1)}=W^{(1)}x=\begin{bmatrix}0.1\\0.3\end{bmatrix}, a(1)=σ(z(1))=[0.5250.574]a^{(1)}=\sigma(z^{(1)})=\begin{bmatrix}0.525\\0.574\end{bmatrix}. Why? σ(0.1)=0.525\sigma(0.1)=0.525, σ(0.3)=0.574\sigma(0.3)=0.574.
  • z(2)=0.5(0.525)+0.6(0.574)=0.607z^{(2)}=0.5(0.525)+0.6(0.574)=0.607, a(2)=σ(0.607)=0.647a^{(2)}=\sigma(0.607)=0.647.

Backward:

  • aL=a(2)y=0.6471=0.353\nabla_a L = a^{(2)}-y = 0.647-1=-0.353. Why? dda12(ay)2=ay\frac{d}{da}\tfrac12(a-y)^2=a-y.
  • σ(z(2))=a(2)(1a(2))=0.647(0.353)=0.228\sigma'(z^{(2)})=a^{(2)}(1-a^{(2)})=0.647(0.353)=0.228.
  • δ(2)=0.353×0.228=0.0805\delta^{(2)} = -0.353\times 0.228 = -0.0805 (BP1).
  • LW(2)=δ(2)(a(1))=[0.0423, 0.0462]\frac{\partial L}{\partial W^{(2)}}=\delta^{(2)}(a^{(1)})^\top=[-0.0423,\ -0.0462] (BP3). Why? multiply the scalar δ\delta by each activation.
  • δ(1)=(W(2))δ(2)σ(z(1))\delta^{(1)}=(W^{(2)})^\top\delta^{(2)}\odot\sigma'(z^{(1)}). (W(2))δ(2)=[0.04030.0483](W^{(2)})^\top\delta^{(2)}=\begin{bmatrix}-0.0403\\-0.0483\end{bmatrix}; σ(z(1))=[0.2490.244]\sigma'(z^{(1)})=\begin{bmatrix}0.249\\0.244\end{bmatrix}; so δ(1)=[0.01000.0118]\delta^{(1)}=\begin{bmatrix}-0.0100\\-0.0118\end{bmatrix} (BP2).

Now a gradient-descent step subtracts η\eta\cdot these gradients. Done — one full pass.

Worked example 2 — why sharing saves work

For W(1)W^{(1)} we needed δ(1)\delta^{(1)}, which reused δ(2)\delta^{(2)} (already computed). If instead we chained fully from LL to W(1)W^{(1)} we'd recompute the path through layer 2 again. In a 100-layer net that reuse is the difference between linear and exponential cost.



Flashcards

What quantity does δ(l)\delta^{(l)} represent?
L/z(l)\partial L/\partial z^{(l)}, sensitivity of loss to layer ll's pre-activation.
BP1 (output error) formula?
δ(L)=aLσ(z(L))\delta^{(L)} = \nabla_a L \odot \sigma'(z^{(L)}).
BP2 (recurrence) formula?
δ(l)=((W(l+1))δ(l+1))σ(z(l))\delta^{(l)} = ((W^{(l+1)})^\top \delta^{(l+1)}) \odot \sigma'(z^{(l)}).
Why the transpose WW^\top in the backward pass?
Error flows outputs→inputs, the adjoint direction; also dimensions require it.
Gradient w.r.t. weights (BP3)?
L/W(l)=δ(l)(a(l1))\partial L/\partial W^{(l)} = \delta^{(l)} (a^{(l-1)})^\top (outer product).
Gradient w.r.t. bias (BP4)?
L/b(l)=δ(l)\partial L/\partial b^{(l)} = \delta^{(l)} (since z/b=1\partial z/\partial b =1).
Why is backprop efficient vs naive chain rule?
It caches later-layer deltas and reuses them — dynamic programming over the computational graph.
Do we evaluate σ\sigma' at zz or aa?
At zz (the pre-activation).
For sigmoid, σ(z)=\sigma'(z)=?
a(1a)a(1-a) where a=σ(z)a=\sigma(z).
Why sum over kk in BP2 derivation?
zj(l)z^{(l)}_j fans out to all neurons kk of layer l+1l+1; multivariable chain rule sums all paths.

Recall Explain to a 12-year-old (Feynman)

A robot guesses an answer and it's wrong. To fix itself, it asks "which of my knobs caused the mistake, and how much?" The last knob is easy to check. Then it whispers the leftover blame to the knob before it, and that one to the one before, all the way to the start. Every knob only listens to the whisper from the knob just after it — nobody redoes the whole story. Each knob then turns a little in the direction that reduces blame. That whisper-passing-backwards is backpropagation.


Connections

Concept Map

requires

applied systematically

caches shared subproblems

over

propagates

defined as

computes

feeds

output layer

recurrence backward

yields

update weights

Gradient descent needs dL/dw

Chain rule

Backpropagation

Dynamic programming

Computational graph

Error signal delta

dL / dz of layer l

Forward pass

Pre-activation z and activation a

delta L = grad_a L Hadamard sigma prime

delta l from delta l+1

Weight gradients dL/dW

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, backpropagation koi jaadu nahi hai — ye sirf chain rule ka smart use hai. Network ek chain ki tarah hai: input aata hai, har layer usko transform karti hai (z=Wa+bz=Wa+b, phir a=σ(z)a=\sigma(z)), aur end me prediction y^\hat y milti hai. Ab loss batata hai ki hum kitne galat the. Humein har weight ke liye "is weight ne kitni galti karayi?" nikalna hai, yaani L/w\partial L/\partial w.

Idea simple hai: sabse pehle output pe blame nikaalo — ye δ(L)=aLσ(z)\delta^{(L)}=\nabla_a L \odot \sigma'(z) hai. Phir is blame ko peeche ki taraf bhejo. Peeche bhejte waqt weights ke transpose se multiply karte hain (kyunki blame ulti direction me jaata hai) aur σ(z)\sigma'(z) se "gate" karte hain (kyunki neuron ke andar nonlinearity thi). Yehi δ(l)=(W(l+1)δ(l+1))σ(z(l))\delta^{(l)}=(W^{(l+1)\top}\delta^{(l+1)})\odot\sigma'(z^{(l)}) hai. Har layer ka δ\delta mil gaya to weight ka gradient bas δ\delta × pichli layer ki activation (δ(a(l1))\delta (a^{(l-1)})^\top) hai.

Sabse important baat — efficiency. Agar hum har weight ke liye alag se poora chain rule karte, to same calculations baar-baar repeat hote. Backprop later layers ke δ\delta ko store karke reuse karta hai, isliye 100-layer network me bhi ek hi backward sweep me sab gradients mil jaate hain. Isko dynamic programming samjho.

Do common galtiyaan yaad rakho: (1) backward me WW nahi, WW^\top use karo. (2) σ\sigma' ko zz (pre-activation) pe evaluate karo, aa pe nahi — sigmoid me dono same dikhte hain (a(1a)a(1-a)) par ReLU me farak padta hai. Bas itna samajh liya to backprop tumhara pakka ho gaya.

Go deeper — visual, from zero

Test yourself — Neural Network Fundamentals

Connections