BRAGA

02 Writing02-02 Notes02-02-308

Jacobian Matrix (f: ℝⁿ → ℝᵐ)

Published 16 Apr 202610 min read
Contents
  1. 01Deep Breakdown
  2. 02Flowchart
  3. 03Real-world use
  4. 04The formula, piece by piece
  5. 05The formula in words
  6. 06Critical Concepts
  7. 07Step-by-step calculation (tiny example)
  8. 08Code examples
Jf=[∂f1∂x1…∂f1∂xn⋮⋱⋮∂fm∂x1…∂fm∂xn]∈Rm×nJ_f = \begin{bmatrix} \frac{\partial f_1}{\partial x_1} & \dots & \frac{\partial f_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial f_m}{\partial x_1} & \dots & \frac{\partial f_m}{\partial x_n} \end{bmatrix} \in \mathbb{R}^{m \times n}
(1)

Deep Breakdown

What it tells me (Situation Awareness): The Jacobian matrix generalizes the gradient to vector-valued functions. While the gradient covers scalar functions f:Rn→Rf: \mathbb{R}^n \to \mathbb{R}, the Jacobian covers the full vector case f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^m. The (i,j)(i,j) entry is ∂fi∂xj\frac{\partial f_i}{\partial x_j}: how the ii-th output changes when the jj-th input is nudged. The Jacobian is an m×nm \times n matrix that captures all first-order sensitivities between inputs and outputs simultaneously. In DA/DS and ML, the Jacobian appears in: backpropagation through vector-to-vector layers (each layer maps a vector of activations to a new vector; the Jacobian is the derivative of that map), the multivariate chain rule in matrix form (Jg∘f=Jg⋅JfJ_{g \circ f} = J_g \cdot J_f), change-of-variables in probability (the Jacobian determinant appears in the PDF transformation formula), and sensitivity/influence analysis in multi-output regression models.

Flowchart

Vector function f:R^n - R^mm outputcomponents: f1,f2, ..., fmn inputvariables: x1,x2, ..., xnFor each outputfi: computegradient of fiw.r.t. all inputsRow i ofJacobian:[dfi/dx1, ...,dfi/dxn] in R^{1 xn}Stack m rows:Jacobian J inR^{m x n}Special casesm=1: J is a 1 x nrow vector =gradient(transposed)m=n: J issquare; det(J) =JacobiandeterminantChain rule: J_{go f}(x) = J_g(f(x))* J_f(x)

Real-world use

Where it is used

Backpropagation: each layer f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^m has a Jacobian; the chain rule in vector form is Jacobian matrix multiplication. Change-of-variables in probability: pY(y)=pX(x)/∣det⁡Jf(x)∣p_Y(\mathbf{y}) = p_X(\mathbf{x}) / |\det J_f(\mathbf{x})| where JfJ_f is the Jacobian of the transformation. Normalizing flows (generative models): the Jacobian determinant must be computable. Sensitivity analysis: which inputs most affect which outputs? The Jacobian matrix answers this. Root finding (Newton's method for systems of equations): x(t+1)=x(t)−Jf−1f(x(t))\mathbf{x}^{(t+1)} = \mathbf{x}^{(t)} - J^{-1}_f \mathbf{f}(\mathbf{x}^{(t)}).

Problem it solves

When a function maps multiple inputs to multiple outputs, the standard derivative (a scalar) is insufficient. The Jacobian provides the complete first-order linear approximation: f(x+h)≈f(x)+Jf(x) hf(\mathbf{x} + \mathbf{h}) \approx f(\mathbf{x}) + J_f(\mathbf{x})\,\mathbf{h}. This generalizes both the gradient (scalar output, vector input) and the ordinary derivative (scalar to scalar).

How it solves the problem

By computing all m×nm \times n partial derivatives and organizing them into a matrix. Each row ii is the gradient of the ii-th output function fif_i with respect to all inputs. The matrix structure enables the multivariate chain rule to be expressed as matrix multiplication: if g:Rm→Rkg: \mathbb{R}^m \to \mathbb{R}^k and f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^m, then Jg∘f(x)=Jg(f(x))⋅Jf(x)∈Rk×nJ_{g \circ f}(\mathbf{x}) = J_g(f(\mathbf{x})) \cdot J_f(\mathbf{x}) \in \mathbb{R}^{k \times n}.

Example in a business

A multi-label email classifier outputs a probability vector p=softmax(z)∈RK\mathbf{p} = \text{softmax}(\mathbf{z}) \in \mathbb{R}^K for KK categories. The Jacobian of softmax is Jsoftmax(z)=diag(p)−ppT∈RK×KJ_{\text{softmax}}(\mathbf{z}) = \text{diag}(\mathbf{p}) - \mathbf{p}\mathbf{p}^T \in \mathbb{R}^{K \times K}. This Jacobian is multiplied by the upstream gradient during backpropagation to propagate errors through the softmax layer.

The formula, piece by piece

Jf=[∂f1∂x1⋯∂f1∂xn⋮⋱⋮∂fm∂x1⋯∂fm∂xn]∈Rm×nJ_f = \begin{bmatrix} \frac{\partial f_1}{\partial x_1} & \cdots & \frac{\partial f_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial f_m}{\partial x_1} & \cdots & \frac{\partial f_m}{\partial x_n} \end{bmatrix} \in \mathbb{R}^{m \times n}
(2)

Each symbol:

  • JfJ_f or DfDf: the Jacobian of ff — also called the "total derivative"
  • (i,j)(i,j) entry: [Jf]ij=∂fi∂xj[J_f]_{ij} = \frac{\partial f_i}{\partial x_j} — how output ii responds to input jj
  • Rm×n\mathbb{R}^{m \times n}: mm rows (one per output), nn columns (one per input)
  • Row ii: the gradient of the scalar function fif_i w.r.t. x\mathbf{x} Chain rule in Jacobian form (g:Rm→Rkg: \mathbb{R}^m \to \mathbb{R}^k, f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^m):
Jg∘f(x)=Jg(f(x))⋅Jf(x)∈Rk×nJ_{g \circ f}(\mathbf{x}) = J_g(f(\mathbf{x})) \cdot J_f(\mathbf{x}) \in \mathbb{R}^{k \times n}
(3)

Linear approximation: f(x+h)≈f(x)+Jf(x) hf(\mathbf{x} + \mathbf{h}) \approx f(\mathbf{x}) + J_f(\mathbf{x})\,\mathbf{h}

The formula in words

"For each of the mm outputs, compute its gradient (a row vector of nn partial derivatives). Stack these mm row-gradients vertically into a matrix. Row ii tells you how output ii responds to infinitesimal changes in all inputs. Column jj tells you how all outputs respond to a nudge in input xjx_j. The entire matrix is the best linear approximation of ff at x\mathbf{x}."

Critical Concepts

  • Linear approximation: f(x+h)≈f(x)+Jf(x) hf(\mathbf{x} + \mathbf{h}) \approx f(\mathbf{x}) + J_f(\mathbf{x})\,\mathbf{h} for small h\mathbf{h}.
  • Jacobian in backprop (VJP): for a layer f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^m, the gradient of a scalar loss w.r.t. the input is ∇xL=Jf(x)T∇yL\nabla_\mathbf{x} L = J_f(\mathbf{x})^T \nabla_{\mathbf{y}} L — the vector-Jacobian product (VJP).
  • Jacobian determinant: for square JJ (m=nm = n), ∣det⁡Jf(x)∣|\det J_f(\mathbf{x})| measures volume distortion. Used in change-of-variables: pY(y)=pX(f−1(y))/∣det⁡Jf(f−1(y))∣p_Y(\mathbf{y}) = p_X(f^{-1}(\mathbf{y})) / |\det J_f(f^{-1}(\mathbf{y}))|.
  • Softmax Jacobian: Jsoftmax=diag(p)−ppTJ_{\text{softmax}} = \text{diag}(\mathbf{p}) - \mathbf{p}\mathbf{p}^T — symmetric, rank-(K−1)(K-1).
  • Numerical check: [Jf]ij≈fi(x+hej)−fi(x−hej)2h[J_f]_{ij} \approx \frac{f_i(\mathbf{x}+h\mathbf{e}_j)-f_i(\mathbf{x}-h\mathbf{e}_j)}{2h} (centered difference).
  • Forward vs. reverse mode AD: forward mode computes one column of JJ per pass (efficient for few inputs).

Reverse mode (backprop) computes one row per pass (efficient for scalar output = VJP).

Step-by-step calculation (tiny example)

Let f:R2→R3f: \mathbb{R}^2 \to \mathbb{R}^3 with f(x1,x2)=(x1x2,  x12,  x1+3x2)f(x_1, x_2) = (x_1 x_2,\; x_1^2,\; x_1 + 3x_2). Compute JfJ_f at (2,1)(2, 1).

Step 1: List the three output functions f1=x1x2f_1 = x_1 x_2, f2=x12f_2 = x_1^2, f3=x1+3x2f_3 = x_1 + 3x_2

Step 2: Compute all 6 partial derivatives ∂f1∂x1=x2\frac{\partial f_1}{\partial x_1} = x_2, ∂f1∂x2=x1\frac{\partial f_1}{\partial x_2} = x_1; ∂f2∂x1=2x1\frac{\partial f_2}{\partial x_1} = 2x_1, ∂f2∂x2=0\frac{\partial f_2}{\partial x_2} = 0; ∂f3∂x1=1\frac{\partial f_3}{\partial x_1} = 1, ∂f3∂x2=3\frac{\partial f_3}{\partial x_2} = 3

Step 3: Assemble the 3×23 \times 2 Jacobian at (2,1)(2, 1)

Jf(2,1)=[124013]J_f(2,1) = \begin{bmatrix} 1 & 2 \\ 4 & 0 \\ 1 & 3 \end{bmatrix}
(4)

Step 4: Verify linear approximation for h=(0.01,0.01)T\mathbf{h} = (0.01, 0.01)^T Jfh=[0.03,0.04,0.04]TJ_f \mathbf{h} = [0.03, 0.04, 0.04]^T vs. exact f(2.01,1.01)−f(2,1)=[0.0301,0.0401,0.04]Tf(2.01,1.01)-f(2,1) = [0.0301, 0.0401, 0.04]^T

Code examples

Beginner example (Python)

python
import numpy as np

def f(x: np.ndarray) -> np.ndarray:
    """f: R^2 -> R^3. f(x1, x2) = [x1*x2, x1^2, x1 + 3*x2]."""
    x1, x2 = x[0], x[1]
    return np.array([x1 * x2, x1**2, x1 + 3.0 * x2])

def analytic_jacobian(x: np.ndarray) -> np.ndarray:
    """Jacobian of f at x: 3 x 2 matrix."""
    x1, x2 = x[0], x[1]
    return np.array([
        [x2, x1],      # row 1: grad of f1
        [2*x1, 0.0],   # row 2: grad of f2
        [1.0, 3.0],    # row 3: grad of f3
    ])

def numerical_jacobian(fn, x: np.ndarray, h: float = 1e-5) -> np.ndarray:
    """Numerical Jacobian via centered finite differences."""
    n = len(x)
    f0 = fn(x)
    m = len(f0)
    jac = np.zeros((m, n))
    for j in range(n):
        e_j = np.zeros(n)
        e_j[j] = 1.0
        jac[:, j] = (fn(x + h * e_j) - fn(x - h * e_j)) / (2 * h)
    return jac


x0 = np.array([2.0, 1.0])
j_analytic = analytic_jacobian(x0)
j_numerical = numerical_jacobian(f, x0)
print(f"Jacobian at {x0}:")
print("Analytic:")
print(j_analytic)
print("\nNumerical:")
print(j_numerical.round(6))
print(f"\nMax error: {np.abs(j_analytic - j_numerical).max():.2e}")

# Linear approximation test
h_vec = np.array([0.01, 0.01])
delta_f_approx = j_analytic @ h_vec
delta_f_exact = f(x0 + h_vec) - f(x0)
print(f"\nLinear approximation: {delta_f_approx}")
print(f"Exact change: {delta_f_exact}")

Beginner example (R)

r
f_fn <- function(x) c(x[1]*x[2], x[1]^2, x[1] + 3*x[2])  # R^2 -> R^3

# Analytic Jacobian at x
analytic_jac <- function(x) {
  matrix(c(x[2], 2*x[1], 1,  # column 1 (partial w.r.t. x1)
           x[1], 0, 3),      # column 2 (partial w.r.t. x2)
         nrow = 3, ncol = 2)
}

# Numerical Jacobian
num_jac <- function(fn, x, h = 1e-5) {
  n <- length(x); f0 <- fn(x); m <- length(f0)
  jac <- matrix(0, nrow = m, ncol = n)
  for (j in seq_len(n)) {
    e <- rep(0, n); e[j] <- 1
    jac[, j] <- (fn(x + h * e) - fn(x - h * e)) / (2 * h)
  }
  jac
}
x0 <- c(2, 1)
j_a <- analytic_jac(x0); j_n <- num_jac(f_fn, x0)
cat("Analytic Jacobian:\n"); print(j_a)
cat("\nNumerical Jacobian:\n"); print(round(j_n, 6))
cat("Max error:", max(abs(j_a - j_n)), "\n")

# Linear approximation
h_vec <- c(0.01, 0.01)
cat("Linear approx:", j_a %*% h_vec, "\n")
cat("Exact change: ", f_fn(x0 + h_vec) - f_fn(x0), "\n")

Professional example (Python)

python
"""
Jacobian of softmax: core derivative for multi-class classification backprop.

For z in R^K:
    softmax(z)_i = exp(z_i) / sum_j exp(z_j)

Jacobian: J_softmax[i,j] = p_i * (delta_{ij} - p_j) = diag(p) - p @ p^T
VJP (backprop): delta_z = J_softmax^T @ delta_p

We verify the analytic Jacobian against numerical differences and
demonstrate the VJP used in backward pass of multi-class softmax.
"""
import numpy as np
RANDOM_SEED = 42
rng = np.random.default_rng(RANDOM_SEED)

def softmax(z: np.ndarray) -> np.ndarray:
    """Numerically stable softmax."""
    z_stable = z - z.max()
    exp_z = np.exp(z_stable)
    return exp_z / exp_z.sum()

def softmax_jacobian(p: np.ndarray) -> np.ndarray:
    """Analytic Jacobian: diag(p) - p @ p.T."""
    return np.diag(p) - np.outer(p, p)

def numerical_jacobian_softmax(z: np.ndarray, h: float = 1e-5) -> np.ndarray:
    """Centered finite difference Jacobian."""
    k = len(z)
    jac = np.zeros((k, k))
    for j in range(k):
        e_j = np.zeros(k)
        e_j[j] = h
        jac[:, j] = (softmax(z + e_j) - softmax(z - e_j)) / (2 * h)
    return jac


K = 5
z_test = rng.standard_normal(K)
p_test = softmax(z_test)
j_analytic = softmax_jacobian(p_test)
j_numerical = numerical_jacobian_softmax(z_test)
print("=== Softmax Jacobian ===")
print(f"p = {p_test.round(4)}")
print(f"\nAnalytic J (5x5):\n{j_analytic.round(4)}")
print(f"\nNumerical J (5x5):\n{j_numerical.round(4)}")
print(f"\nMax error: {np.abs(j_analytic - j_numerical).max():.2e}")
print(f"Symmetric: {np.allclose(j_analytic, j_analytic.T)}")
print(f"Row sums (should all be ~0): {j_analytic.sum(axis=1).round(6)}")
print(f"Rank: {np.linalg.matrix_rank(j_analytic)} (= K-1 = {K-1}, one zero eigenvalue)")

# VJP: backprop through softmax layer
# Upstream: dL/dp from cross-entropy with true class = 2
y_onehot = np.zeros(K)
y_onehot[2] = 1.0
delta_p = -(y_onehot / np.clip(p_test, 1e-12, 1.0))  # dL/dp
delta_z = j_analytic.T @ delta_p  # VJP: J^T @ upstream

# Efficient combined CE + softmax gradient: delta_z = p - y
delta_z_combined = p_test - y_onehot
print(f"\nVJP delta_z (J^T @ delta_p): {delta_z.round(4)}")
print(f"Combined CE+softmax gradient: {delta_z_combined.round(4)}")
print(f"Match: {np.allclose(delta_z, delta_z_combined, atol=1e-4)}")

Source: MML2 p. 155

#calculus#multivariable-calculus-and-matrix-calculus#differential-operators-gradients-jacobians-hessians

Keyboard shortcuts

Single keys, ignored while typing in a field or with Cmd, Ctrl or Alt held.

0
Home
1 2 3 4
Work, Writing, Tools, About
j / k
Next / previous section on this page
n / p
Next / previous project or essay
u
Up one level
l
Deutsch
t
Light or dark theme
?
This list
Esc
Close