AI-210 · Lesson 4 of 12 · 34 min

Matrix calculus and gradients

Training a machine learning model means adjusting its parameters to reduce a loss function, and the tool for doing that is the gradient: the vector of partial derivatives that points in the direction of steepest increase. A modern neural network has millions or billions of parameters, and every training step computes the gradient of the loss with respect to all of them. The method that makes this feasible, backpropagation, is nothing more than the chain rule of calculus organized efficiently, and it is what deep learning frameworks such as PyTorch and JAX automate.

This chapter builds the calculus needed to understand it: gradients, directional derivatives, Jacobians and Hessians; the handful of matrix derivative identities used constantly; the least-squares solution derived by setting a gradient to zero; the gradient of the softmax cross-entropy loss; and backpropagation through a small network, checked numerically.

Gradients and directional derivatives

For a scalar function of a vector, \(f: \mathbb{R}^n \to \mathbb{R}\), the gradient collects the partial derivatives:

\[\nabla f(\mathbf{x}) = \left(\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \dots, \frac{\partial f}{\partial x_n}\right)^\top\]

The rate of change of \(f\) in the direction of a unit vector \(\mathbf{u}\) is the directional derivative \(\nabla f^\top\mathbf{u} = \|\nabla f\|\cos\theta\). It is largest when \(\mathbf{u}\) points along the gradient, so the gradient is the direction of steepest ascent and \(-\nabla f\) the direction of steepest descent; its length is the steepest slope. Gradient descent, \(\mathbf{x} \leftarrow \mathbf{x} - \eta\nabla f(\mathbf{x})\), takes small steps downhill. The gradient is also perpendicular to the contour lines (level sets) of \(f\).

Contour plot of a bowl-shaped function with elliptical contour lines. At several points, arrows show the negative gradient pointing perpendicular to the contours toward the minimum at the center.
The negative gradient is perpendicular to the contour lines and points downhill.

Worked problem

Gradient and directional derivative

For \(f(x_1, x_2) = x_1^2 + 3x_1x_2 + 2x_2^2\), find \(\nabla f\) at (1, 2), the steepest slope there, and the directional derivative along \(\mathbf{u} = (0.8, -0.6)\).

Show solution
  1. \(\partial f/\partial x_1 = 2x_1 + 3x_2 = 2 + 6 = 8\); \(\partial f/\partial x_2 = 3x_1 + 4x_2 = 3 + 8 = 11\). So \(\nabla f = (8, 11)\).
  2. Steepest slope: \(\|\nabla f\| = \sqrt{64 + 121} = 13.6\).
  3. Along \(\mathbf{u}\): \(8 \times 0.8 + 11 \times (-0.6) = 6.4 - 6.6 = -0.2\).

Answer: \(\nabla f = (8, 11)\), steepest slope 13.6, and −0.2 along \(\mathbf{u}\): that direction is almost along a contour line, so \(f\) barely changes.

Jacobians and Hessians

For a vector-valued function \(\mathbf{f}: \mathbb{R}^n \to \mathbb{R}^m\), the derivative is the \(m \times n\) Jacobian matrix \(\mathbf{J}\), with \(J_{ij} = \partial f_i/\partial x_j\). It is the best linear approximation of \(\mathbf{f}\) near a point: \(\mathbf{f}(\mathbf{x} + \boldsymbol{\delta}) \approx \mathbf{f}(\mathbf{x}) + \mathbf{J}\boldsymbol{\delta}\). Two cases appear constantly in neural networks:

  • A linear layer \(\mathbf{f}(\mathbf{x}) = \mathbf{A}\mathbf{x}\) has Jacobian \(\mathbf{J} = \mathbf{A}\).
  • An element-wise function such as \(\tanh\) or ReLU has a diagonal Jacobian, \(\operatorname{diag}(\sigma\prime(x_1), \dots, \sigma\prime(x_n))\), because each output depends only on its own input. In code it is never formed as a matrix; it is simply an element-wise multiplication.

The second derivatives of a scalar function form the Hessian, \(H_{ij} = \partial^2 f/\partial x_i\,\partial x_j\), a symmetric \(n \times n\) matrix describing curvature. It gives the quadratic approximation \(f(\mathbf{x} + \boldsymbol{\delta}) \approx f(\mathbf{x}) + \nabla f^\top\boldsymbol{\delta} + \tfrac12\boldsymbol{\delta}^\top\mathbf{H}\boldsymbol{\delta}\). At a point where the gradient is zero, a positive definite Hessian means a local minimum, a negative definite one a local maximum, and an indefinite one (eigenvalues of both signs) a saddle point.

Worked problem

Classifying a stationary point

For \(f = x_1^2 + 3x_1x_2 + 2x_2^2\), find the Hessian and decide what kind of point the origin is.

Show solution
  1. \(\mathbf{H} = \begin{bmatrix} 2 & 3 \ 3 & 4 \end{bmatrix}\), the same everywhere because \(f\) is quadratic.
  2. At the origin \(\nabla f = 0\). \(\det\mathbf{H} = 8 - 9 = -1 < 0\), so the eigenvalues have opposite signs.

Answer: The origin is a saddle point: the function curves up in one direction and down in another. In high-dimensional neural network losses, saddle points are far more common than local minima, a point Lesson 11 returns to.

Identities for matrix derivatives

A small set of results, all derivable from the component definitions, covers most of what is needed. For vectors \(\mathbf{x}, \mathbf{a}\) and a matrix \(\mathbf{A}\):

FunctionGradient with respect to x
\(\mathbf{a}^\top\mathbf{x}\)\(\mathbf{a}\)
\(\mathbf{x}^\top\mathbf{x} = \|\mathbf{x}\|^2\)\(2\mathbf{x}\)
\(\mathbf{x}^\top\mathbf{A}\mathbf{x}\)\((\mathbf{A} + \mathbf{A}^\top)\mathbf{x}\), or \(2\mathbf{A}\mathbf{x}\) if \(\mathbf{A}\) is symmetric
\(\|\mathbf{A}\mathbf{x} - \mathbf{b}\|^2\)\(2\mathbf{A}^\top(\mathbf{A}\mathbf{x} - \mathbf{b})\)

A useful habit is to check shapes: the gradient of a scalar with respect to \(\mathbf{x} \in \mathbb{R}^n\) must itself be in \(\mathbb{R}^n\), so for example \(\mathbf{A}^\top(\mathbf{A}\mathbf{x} - \mathbf{b})\) has the right shape (\(n \times m\) times \(m\)), while \(\mathbf{A}(\mathbf{A}\mathbf{x} - \mathbf{b})\) would not even be defined for a non-square \(\mathbf{A}\).

Least squares from the gradient

Linear regression fits weights \(\mathbf{w}\) to minimize the squared error \(L(\mathbf{w}) = \|\mathbf{X}\mathbf{w} - \mathbf{y}\|^2\) over \(N\) examples. By the last identity, \(\nabla L = 2\mathbf{X}^\top(\mathbf{X}\mathbf{w} - \mathbf{y})\). Setting it to zero gives the normal equations and their solution:

\[\mathbf{X}^\top\mathbf{X}\,\mathbf{w}^* = \mathbf{X}^\top\mathbf{y} \quad\Rightarrow\quad \mathbf{w}^* = (\mathbf{X}^\top\mathbf{X})^{-1}\mathbf{X}^\top\mathbf{y}\]

Because \(L\) is a convex quadratic (its Hessian \(2\mathbf{X}^\top\mathbf{X}\) is positive semidefinite), this stationary point is the global minimum. In practice the system is solved with a factorization rather than an explicit inverse.

Worked problem

A one-parameter fit

Fit \(y = wx\) (no intercept) to the points (1, 2), (2, 3), (3, 5).

Show solution
  1. With one feature, the normal equation is \(w\sum x_i^2 = \sum x_iy_i\).
  2. \(\sum x_i^2 = 1 + 4 + 9 = 14\); \(\sum x_iy_i = 2 + 6 + 15 = 23\).
  3. \(w = 23/14 = 1.643\).

Answer: \(w \approx 1.64\). The same formula, in matrix form, fits models with thousands of features.

The program below checks the least-squares gradient numerically and then solves the normal equations. Checking an analytic gradient against finite differences is the standard way to catch derivation and coding errors.

Gradient checking and the normal equations
import numpy as np

rng = np.random.default_rng(0)
X = rng.normal(size=(50, 3))
y = X @ np.array([1.5, -2.0, 0.5]) + 0.1 * rng.normal(size=50)
w = np.zeros(3)

def loss(w):
    r = X @ w - y
    return r @ r                      # sum of squared residuals

grad = 2 * X.T @ (X @ w - y)          # analytic gradient

eps = 1e-6                            # central differences, one coordinate at a time
num = np.array([(loss(w + eps * e) - loss(w - eps * e)) / (2 * eps) for e in np.eye(3)])
print("analytic :", grad.round(4))
print("numerical:", num.round(4))
print("agree:", np.allclose(grad, num, rtol=1e-6))
w_star = np.linalg.solve(X.T @ X, X.T @ y)       # set the gradient to zero
print("least-squares weights:", w_star.round(3))
Output
analytic : [-182.2827  129.706   -60.6253]
numerical: [-182.2827  129.706   -60.6253]
agree: True
least-squares weights: [ 1.518 -2.016  0.502]

The recovered weights (1.518, −2.016, 0.502) are close to the true (1.5, −2.0, 0.5) used to generate the data; the small differences come from the noise.

The chain rule

Most functions in machine learning are compositions: a loss of a prediction of a layer of a layer. The chain rule handles them. For scalars, if \(y = f(u)\) and \(u = g(x)\), then \(dy/dx = (dy/du)(du/dx)\). For vectors, Jacobians multiply: if \(\mathbf{y} = \mathbf{f}(\mathbf{u})\) and \(\mathbf{u} = \mathbf{g}(\mathbf{x})\), then \(\mathbf{J}_{\mathbf{y}\mathbf{x}} = \mathbf{J}_{\mathbf{y}\mathbf{u}}\mathbf{J}_{\mathbf{u}\mathbf{x}}\).

Worked problem

The gradient of a single neuron

A neuron computes \(z = wx + b\), \(\hat{y} = \sigma(z)\) with the logistic sigmoid \(\sigma(z) = 1/(1 + e^{-z})\), and loss \(L = \tfrac12(\hat{y} - t)^2\). With \(w = 2\), \(x = 1\), \(b = -1\) and target \(t = 1\), find \(\partial L/\partial w\).

Show solution
  1. Forward: \(z = 2 - 1 = 1\); \(\hat{y} = \sigma(1) = 0.731\); \(L = \tfrac12(0.731 - 1)^2 = 0.036\).
  2. \(\partial L/\partial\hat{y} = \hat{y} - t = -0.269\).
  3. \(\partial\hat{y}/\partial z = \sigma(z)(1 - \sigma(z)) = 0.731 \times 0.269 = 0.197\).
  4. \(\partial z/\partial w = x = 1\).
  5. \(\partial L/\partial w = -0.269 \times 0.197 \times 1 = -0.0529\).

Answer: \(\partial L/\partial w = -0.053\). The negative sign says increasing \(w\) reduces the loss, so gradient descent will increase it, pushing the prediction toward the target.

Softmax and cross-entropy

Classifiers with \(K\) classes output \(K\) scores (logits) \(\mathbf{z}\), turned into probabilities by the softmax function, and are trained with the cross-entropy loss against a one-hot target \(\mathbf{y}\) (Lesson 9 explains why):

\[p_k = \frac{e^{z_k}}{\sum_j e^{z_j}}, \qquad L = -\sum_k y_k \log p_k\]

Working through the derivatives, the softmax and the logarithm combine into a remarkably simple gradient:

\[\frac{\partial L}{\partial \mathbf{z}} = \mathbf{p} - \mathbf{y}\]

The gradient is just the predicted probabilities minus the target: it pushes up the logit of the correct class by how far its probability falls short of 1, and pushes down every other logit by its probability. This clean result is one reason softmax and cross-entropy are always used together.

The softmax cross-entropy gradient is p − y
import numpy as np

def softmax(z):
    e = np.exp(z - z.max())           # subtract the max for numerical stability
    return e / e.sum()

z = np.array([2.0, 1.0, 0.1])         # logits for 3 classes
y = np.array([0.0, 1.0, 0.0])         # true class is class 1
p = softmax(z)
L = -np.sum(y * np.log(p))
print("p =", p.round(4), " loss =", round(L, 4))
print("gradient p - y =", (p - y).round(4))

eps = 1e-6
num = np.array([(-np.sum(y * np.log(softmax(z + eps * e))) - -np.sum(y * np.log(softmax(z - eps * e)))) / (2 * eps)
                for e in np.eye(3)])
print("numerical      =", num.round(4))
Output
p = [0.659  0.2424 0.0986]  loss = 1.417
gradient p - y = [ 0.659  -0.7576  0.0986]
numerical      = [ 0.659  -0.7576  0.0986]

Subtracting the maximum logit before exponentiating does not change the softmax (it cancels in the ratio) but prevents overflow when logits are large, a standard numerical precaution.

Backpropagation

Lesson 5 shows how frameworks automate what follows for any program, and the numerical precision it runs in.

A neural network is a chain of layers, and its loss is a composition of many functions. Backpropagation computes the gradient of the loss with respect to every parameter by applying the chain rule from the output backwards. A forward pass computes and stores each layer's output. A backward pass then starts with \(\partial L/\partial\hat{y}\) and, layer by layer, multiplies by each layer's local derivative, producing at each layer both the gradient for that layer's parameters and the gradient to pass to the layer below.

A computation graph: input x, then z1 equals W1 x, h equals tanh of z1, y-hat equals W2 h, and the loss L. Forward arrows run left to right in black. Backward arrows run right to left in red, labeled with the gradients: dL by dy-hat equals y-hat minus t, dL by dW2 equals that times h, dL by dh equals that times W2, dL by dz1 equals dL by dh times one minus h squared, and dL by dW1 equals the outer product of dL by dz1 and x.
Backpropagation: values flow forward, and gradients flow backward through the same graph by the chain rule.
Backpropagation through a two-layer network, checked numerically
import numpy as np

rng = np.random.default_rng(42)
x = rng.normal(size=4)                 # one input
t = 1.0                                # target
W1 = rng.normal(size=(3, 4)); W2 = rng.normal(size=3)

def forward(W1, W2):
    h = np.tanh(W1 @ x)                # hidden layer
    yhat = W2 @ h                      # output
    return 0.5 * (yhat - t) ** 2, h, yhat

L, h, yhat = forward(W1, W2)
# Backpropagation: apply the chain rule from the loss backwards
dy = yhat - t                          # dL/dyhat
dW2 = dy * h                           # dL/dW2
dh = dy * W2                           # dL/dh
dz = dh * (1 - h ** 2)                 # through tanh: d tanh(z)/dz = 1 - tanh^2
dW1 = np.outer(dz, x)                  # dL/dW1

eps = 1e-6
E = np.zeros_like(W1); E[1, 2] = eps   # check one entry of dW1
num = (forward(W1 + E, W2)[0] - forward(W1 - E, W2)[0]) / (2 * eps)
print(f"loss {L:.4f}")
print(f"backprop dL/dW1[1,2] = {dW1[1, 2]:.6f}")
print(f"numerical            = {num:.6f}")
Output
loss 3.2841
backprop dL/dW1[1,2] = 0.076549
numerical            = 0.076549

Notice the pattern: the gradient for a weight matrix is always an outer product of the gradient arriving from above and the input that the layer received in the forward pass. That is why training needs memory to store activations, which in large models is often a bigger constraint than storing the weights themselves.

Why reverse mode

There are two ways to apply the chain rule through a long composition. Forward mode carries derivatives along with values from input to output, which costs one pass per input variable. Reverse mode, backpropagation, carries them from output to input, which costs one backward pass per output. A training loss has millions of inputs (parameters) and one output (the loss), so reverse mode computes the full gradient in roughly the time of two or three forward passes, while forward mode would need millions. Automatic differentiation libraries implement reverse mode generally: they record the operations of the forward pass and replay them backwards, so practitioners rarely write gradients by hand. Understanding what they compute, and checking gradients numerically when writing custom operations, remains essential.

Common mistakes

  • MistakeForgetting the transpose in gradients such as \(2\mathbf{X}^\top(\mathbf{X}\mathbf{w} - \mathbf{y})\).

    InsteadCheck shapes: the gradient must have the same shape as the variable.

  • MistakeUsing \(2\mathbf{A}\mathbf{x}\) as the gradient of \(\mathbf{x}^\top\mathbf{A}\mathbf{x}\) for a non-symmetric \(\mathbf{A}\).

    InsteadThe general result is \((\mathbf{A} + \mathbf{A}^\top)\mathbf{x}\).

  • MistakeChecking gradients with one-sided differences and a tiny step.

    InsteadUse central differences, \((f(x + \epsilon) - f(x - \epsilon))/2\epsilon\), with \(\epsilon \approx 10^{-6}\) in double precision.

  • MistakeComputing softmax as exp(z)/sum(exp(z)) directly.

    InsteadSubtract the maximum logit first to avoid overflow.

  • MistakeAssuming a zero gradient means a minimum.

    InsteadCheck the Hessian: it may be a maximum or a saddle point.

Summary and key terms

  • The gradient \(\nabla f\) points uphill; its length is the steepest slope; the directional derivative is \(\nabla f^\top\mathbf{u}\).
  • Jacobians are matrices of first derivatives of vector functions; Hessians are symmetric matrices of second derivatives.
  • Key identities: \(\nabla(\mathbf{a}^\top\mathbf{x}) = \mathbf{a}\), \(\nabla(\mathbf{x}^\top\mathbf{A}\mathbf{x}) = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}\), \(\nabla\|\mathbf{A}\mathbf{x} - \mathbf{b}\|^2 = 2\mathbf{A}^\top(\mathbf{A}\mathbf{x} - \mathbf{b})\).
  • Setting the least-squares gradient to zero gives the normal equations \(\mathbf{X}^\top\mathbf{X}\mathbf{w} = \mathbf{X}^\top\mathbf{y}\).
  • At a stationary point, a positive definite Hessian means a minimum and an indefinite one a saddle.
  • The chain rule multiplies local derivatives (Jacobians) along a composition.
  • Softmax with cross-entropy has the gradient \(\mathbf{p} - \mathbf{y}\).
  • Backpropagation is reverse-mode differentiation: one backward pass gives the gradient for all parameters; verify custom gradients numerically.
Gradient
The vector of partial derivatives of a scalar function, pointing in the direction of steepest increase.
Directional derivative
The rate of change of a function along a given unit direction.
Jacobian
The matrix of first partial derivatives of a vector-valued function.
Hessian
The symmetric matrix of second partial derivatives of a scalar function.
Stationary point
A point where the gradient is zero.
Saddle point
A stationary point that is a minimum in some directions and a maximum in others.
Normal equations
The linear system \(\mathbf{X}^\top\mathbf{X}\mathbf{w} = \mathbf{X}^\top\mathbf{y}\) solved by least squares.
Chain rule
The rule for differentiating compositions by multiplying derivatives.
Logits
The unnormalized scores a classifier produces before the softmax.
Softmax
The function converting logits into probabilities that sum to one.
Cross-entropy loss
The negative log-probability the model assigns to the correct class.
Backpropagation
Computing gradients of a loss with respect to all parameters by applying the chain rule backwards.
Automatic differentiation
Software that computes exact derivatives of programs by recording and replaying operations.
Gradient checking
Comparing an analytic gradient with finite-difference estimates to detect errors.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Find the gradient of \(f(x, y) = x^2y + \sin y\).

    Show answer

    ∇f = (2xy, x² + cos y).

  2. For \(f = 3x_1^2 + x_2^2\), find the gradient at (1, 2) and the steepest slope.

    Show answer

    ∇f = (6, 4); slope √52 = 7.21.

  3. What is the Jacobian of \(\mathbf{f}(\mathbf{x}) = (x_1x_2, x_1 + x_2)\)?

    Show answer

    [[x₂, x₁], [1, 1]].

  4. Find the Hessian of \(f = x_1^2 + x_1x_2 + 3x_2^2\) and classify the origin.

    Show answer

    H = [[2, 1], [1, 6]]; det = 11 > 0 and trace > 0, so positive definite: a minimum.

  5. Using the identities, find \(\nabla_{\mathbf{w}}\) of \(\|\mathbf{X}\mathbf{w} - \mathbf{y}\|^2 + \lambda\|\mathbf{w}\|^2\).

    Show answer

    2Xᵀ(Xw − y) + 2λw.

  6. Setting the gradient from the previous question to zero, what is the solution (ridge regression)?

    Show answer

    w = (XᵀX + λI)⁻¹Xᵀy.

  7. Fit \(y = wx\) to (1, 1), (2, 3), (4, 7).

    Show answer

    w = Σxy/Σx² = (1 + 6 + 28)/(1 + 4 + 16) = 35/21 = 1.667.

  8. A softmax gives \(\mathbf{p} = (0.1, 0.7, 0.2)\) and the true class is 3. What is the gradient of the cross-entropy with respect to the logits?

    Show answer

    p − y = (0.1, 0.7, −0.8).

  9. What is the derivative of ReLU, \(\max(0, z)\), and what is its Jacobian for a vector input?

    Show answer

    1 for z > 0 and 0 for z < 0 (undefined at 0, taken as 0 in practice); the Jacobian is diagonal with those values.

  10. Why does training a network need to store the activations from the forward pass?

    Show answer

    Each weight gradient is an outer product of the backward gradient and the layer's forward input, and derivatives such as tanh′ need the forward values, so the backward pass reuses them.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz