Hello, and welcome back. In our previous lesson, we derived several key matrix calculus identities, including the gradient of the log-determinant, and saw how they form the building blocks for the loss functions of advanced generative models like VAEs and Normalizing Flows.
Today, we will assemble these building blocks to understand how a neural network learns end-to-end. We'll move from differentiating isolated functions to calculating the gradient of a final loss with respect to every parameter in a network. This process is orchestrated by the backpropagation algorithm. Our objective is to trace a full backpropagation pass through a small multi-layer perceptron (MLP) by hand. Crucially, we will frame this process in the modern language of Jacobian-vector products (JvPs), which is not only computationally efficient but also directly reflects how frameworks like PyTorch and JAX implement automatic differentiation.
1. Backpropagation and Computational Graphs
At its core, a neural network is a deeply nested function. Deriving gradients for such complex functions by manually expanding the entire expression on paper is tedious, error-prone, and not modular. A more systematic approach is to represent the network as a computational graph, where each node represents a basic operation (e.g., matrix multiplication, an activation function).
Backpropagation is an algorithm that efficiently computes gradients by traversing this graph backward, from the final loss to the input parameters. The process involves two passes:
- Forward Pass: Data flows through the graph from left to right. Each node computes its output and caches any values needed for the backward pass.
- Backward Pass: Gradient information flows from right to left. Each node receives a gradient from "upstream" and uses it to compute gradients for its own inputs, which are then passed "downstream".
The video below gives an excellent overview of this concept.
This video from the Michigan Online channel provides a clear, visual introduction to computational graphs and the mechanics of backpropagation.
First, watch the segment on computational graphs to understand why they are a powerful and modular tool for managing the complexity of neural networks. Next, watch the step-by-step walkthrough of a simple scalar example. Pay close attention to the introduction of three key terms: upstream gradient, local gradient, and downstream gradient.
As the video explains, the chain rule at each node takes the form:
Downstream Gradient = Upstream Gradient × Local Gradient
Let's formalize this for the vector case.
2. The Vector Chain Rule and Jacobian-Vector Products
When we move from scalar operations to vector-valued functions, as in neural network layers, the "local gradient" becomes a Jacobian matrix. For a function , where and , the Jacobian is the matrix of all partial derivatives .
If we have a final scalar loss that is a function of , the upstream gradient is . The chain rule to find the downstream gradient becomes a matrix-vector multiplication:
This operation, , is the cornerstone of backpropagation. It's known as a Jacobian-vector product (JvP). Backpropagation is, in essence, a sequence of these JvP calculations, where the output of one JvP (the downstream gradient) becomes the input vector for the next JvP upstream.

A crucial insight, which we will see in action, is that we almost never need to explicitly construct the full Jacobian matrix. Doing so would be incredibly memory-intensive. Instead, for most common operations, we can derive a much more efficient, direct procedure to compute the result of the JvP. This is sometimes called the "VJP trick" (for Vector-Jacobian Product, referring to , which is dimensionally equivalent to the JvP for our purposes).

3. The Efficiency of Reverse Mode (Backpropagation)
The chain of JvPs can be computed from left-to-right (forward mode) or right-to-left (reverse mode). For training neural networks, reverse mode—which is what we call backpropagation—is vastly more efficient.
This is because training involves a function that maps many parameters (weights and biases) to a single scalar loss. Reverse mode starts with one gradient () and propagates this single piece of information backward, performing computationally cheap matrix-vector products at each step. Forward mode, in contrast, would require passing a full Jacobian matrix forward at each step, involving expensive matrix-matrix products.
The following article provides a clear, compelling numerical example of this efficiency gain.
Everything You Need to Know About Backpropagation | George Assaad
This article by George Assaad provides a modern, VJP-centric view of backpropagation.
Read the section Forward vs. Reverse Mode. The numerical example with a 500-neuron hidden layer clearly demonstrates why reverse mode (backpropagation) is the preferred method for training neural networks.
4. Backpropagation in a 2-Layer MLP
Let's now trace a full backward pass through a simple MLP. Our network will take a vector , pass it through one hidden layer with a ReLU activation, and then to an output layer producing logits .
Forward Pass:
Where are the parameters and is the one-hot encoded true label.
Backward Pass:
We start at the end and work our way back, computing the gradient of the loss with respect to each variable. The initial upstream gradient is .
Step 1: Gradient of Loss w.r.t. Logits ()
This is the first "real" gradient. As we derived in a previous lesson, the combination of Softmax and Cross-Entropy loss yields a remarkably simple gradient.
This vector is our first upstream gradient, which we'll denote .
Step 2: Backprop through Layer 2 ()
We need to compute , , and the new upstream gradient . The upstream gradient we have is .
-
Gradient w.r.t. :
The local Jacobian is . The JvP gives us the new upstream gradient for the next layer back: -
Gradient w.r.t. :
The local Jacobian is . The JvP is trivial:(Note: If using batching, this requires a sum over the batch dimension).
-
Gradient w.r.t. :
This is the most interesting part. Explicitly forming the Jacobian is cumbersome as it would be a 3rd-order tensor. Instead, we derive the JvP directly. The result is a simple outer product:This is a highly efficient operation that completely avoids large intermediate matrices. For a detailed derivation of this crucial step, the following resources are excellent.
Everything You Need to Know About Backpropagation | George Assaad
This section derives the gradient for a linear layer, showing how the JvP simplifies to an efficient outer product.
Focus on the part titled the derivation. It shows how to compute the gradient with respect to the weight matrix W without ever materializing the full Jacobian.
NN - 6 - Backpropagation: Compute the Derivatives - Part 1
This video from Meerkat Statistics provides another perspective on deriving the gradient with respect to the weight matrix.
Watch the segment from this timestamp, which explains why naively taking the derivative of a vector-matrix product with respect to the matrix leads to a third-order tensor and how this simplifies to a much more practical outer product.
Step 3: Backprop through ReLU ()
The upstream gradient is now . Since ReLU is an element-wise function, its Jacobian is diagonal. The JvP simplifies to an element-wise product (Hadamard product, ).
The derivative is a vector containing 1 for elements where and 0 otherwise. This operation effectively acts as a "gradient router," only allowing gradients to pass through neurons that were active during the forward pass.
Step 4: Backprop through Layer 1 ()
This step is identical in form to Step 2, but with different variables. The upstream gradient is .
At this point, we have computed the gradients for all parameters (), and the backpropagation pass is complete. An optimization algorithm like SGD or Adam can now use these gradients to update the parameters.
5. From Theory to Code: Modular Implementation
This node-by-node, JvP-based view of backpropagation maps directly to how modern deep learning frameworks are designed. In PyTorch, you can define custom operations by subclassing torch.autograd.Function and implementing forward and backward static methods.
- The
forwardmethod performs the operation and usesctx.save_for_backwardto cache any tensors (like inputs or outputs) needed for the gradient calculation. - The
backwardmethod receives the upstream gradient (grad_output) and must return the gradients with respect to each of the function's inputs. Thisbackwardmethod is precisely an implementation of the JvP for that operation.
The Michigan Online video provides a fantastic explanation of this concept.
This section demonstrates how the modular, local computation of backpropagation is realized in PyTorch's API.
Watch the segment on the modular API approach. It shows the structure of a torch.autograd.Function, which is the direct implementation of the principles we've discussed. This provides the bridge from the "by-hand" derivation to practical, extensible code for your implementation projects.
Conclusion
In this lesson, we demystified backpropagation by framing it as an efficient application of the chain rule on a computational graph. We saw that the core mathematical operation is the Jacobian-vector product, and that the algorithm's efficiency comes from avoiding the explicit creation of large Jacobian matrices.
Key Takeaways:
- Backpropagation is reverse-mode automatic differentiation on a computational graph.
- The chain rule for vector functions is applied sequentially via Jacobian-vector products (JvPs).
- For a typical neural network (many parameters to a single loss), reverse-mode is far more efficient than forward-mode differentiation.
- The JvP for a linear layer's weights simplifies to an outer product ().
- The JvP for an element-wise activation simplifies to an element-wise product with the local derivative ().
- This modular, JvP-based structure is directly mirrored in the
torch.autograd.FunctionAPI, allowing for custom, differentiable layers.
We have focused on first-order derivatives (gradients and Jacobians). In our next lesson, we will extend these ideas to second-order derivatives, exploring the Hessian matrix and its approximations, which are critical for understanding the curvature of the loss landscape and for advanced optimization techniques.