Gradients of Matrices
This is the page where the shapes get away from people.
The rule has not changed — rows are outputs, columns are inputs — but when the input is a matrix, “columns” is no longer one index. Differentiate a matrix with respect to a matrix and you get four indices: two for the output entry, two for the input entry. That is a fourth-order tensor, and the book says so plainly rather than hiding it.
The good news is that there is nothing new to learn, only something to organise. The two ways to organise it are the content of this page, and both appear in the book’s Figure 5.7.
What you’ll learn
Section titled “What you’ll learn”- Why for matrix arguments is a tensor, and how to read its shape.
- The two ways to make it computable: flatten the matrix into a vector, or partition the tensor into blocks — Figure 5.7’s two panels.
- Example 5.12: , and the fact that it is 75% zeros.
- Example 5.13: entry by entry, including the factor of 2 on the diagonal.
- A finding the book does not state: the symmetry of shows up as a rank deficiency in the flattened Jacobian.
- Why the differential form is the practical way to work, and how it relates to the tensor.
Intuition: four indices, two ways to hold them
Section titled “Intuition: four indices, two ways to hold them”Suppose where is and is . The question “how does change when changes?” needs an answer for every pair of entries: how does respond to ? That is four indices — , , , — and numbers.
You can hold those numbers in two shapes, and the choice is purely one of convenience:
Flatten. Stretch into a vector of length and into one of length . Now it is an ordinary Jacobian matrix, , and every tool from §5.3 applies unchanged. This is what every autodiff library does internally, and it is why a library gradient always comes back with the same shape as the parameter.
Partition. Keep the tensor, but read it as an grid of blocks — one block per output entry. Each block is usually a familiar small object, which makes this the version to use by hand.
flowchart TD Q["how does K change when R changes?
K = f(R), R ∈ ℝᴹˣᴺ, K ∈ ℝᴺˣᴺ"] Q --> IDX["one number per pair of entries:
∂K_pq / ∂R_ij — FOUR indices"] IDX --> T["a fourth-order tensor
ℝ⁽ᴺˣᴺ⁾ˣ⁽ᴹˣᴺ⁾ — Eq 5.94"] T --> FLAT["FLATTEN
reshape to an (N²) × (MN) matrix
→ §5.3 applies unchanged
→ what autodiff does internally"] T --> PART["PARTITION
an N×N grid of M×N blocks
→ each block is a small familiar object
→ the version to use by hand"] T --> DIFF["or avoid it: work with DIFFERENTIALS
dK = RᵀdR + dRᵀR
→ no tensor ever built"] FLAT --> SP["and notice the sparsity:
Eq 5.92 is 75% zeros
Eq 5.98 is 44% zeros"] DIFF --> ID["§5.5: ten identities that
let you skip the tensor entirely"]
The math
Section titled “The math”The shape rule, extended
Section titled “The shape rule, extended”Consider . Then
a four-dimensional tensor , whose entries are
The book notes that since matrices represent linear mappings, and since there is a vector-space isomorphism between and , we can re-shape our matrices into vectors of lengths and respectively. The gradient using these flattened vectors is then a Jacobian of size . Figure 5.7 visualises both approaches.
That is the honest summary. Both organisations are correct; one composes with a
@ and the other needs an einsum with the right indices summed.
Example 5.12 — a vector with respect to a matrix
Section titled “Example 5.12 — a vector with respect to a matrix”with , , , and we want — the derivative with respect to the matrix this time, not the vector.
Shape first, as always:
By definition the gradient is the collection of partial derivatives:
Write out the matrix–vector product explicitly:
and the partial derivatives are
Crucially, depends on row of only. So
and stacking them,
with in slot and zeros everywhere else.
Verified for , : the numeric tensor has shape and matches Equation 5.92 to . And here is the thing the book does not point out — only 12 of its 48 entries are nonzero, exactly 25%. In general the fraction is : each output touches one row.
Example 5.13 — a matrix with respect to a matrix
Section titled “Example 5.13 — a matrix with respect to a matrix”and with
The book’s approach to “this hard problem” is to write down what is already known. The shape:
Then, denoting the -th column of by , every entry of is an inner product of two columns (§3.2):
Differentiating that sum,
The four cases are the product rule doing its job. is a product of two factors; differentiating with respect to hits the first factor when and the second when . When both happen, which is where the comes from — and that factor of 2 on the diagonal is the single most commonly dropped term in this whole chapter.
Verified for , : the numeric tensor has shape , matches Equation 5.98 to , and the diagonal ratio divided by comes out as exactly for every .
Worked example by hand
Section titled “Worked example by hand”Equation 5.98’s four cases, on a 2 by 2
Section titled “Equation 5.98’s four cases, on a 2 by 2”Take , so and
Now differentiate each entry with respect to each entry of , and check against Equation 5.98:
| which case of 5.98 | |||||
|---|---|---|---|---|---|
| : when , else | |||||
| : when ; when | |||||
| same, by symmetry | |||||
| : when , else |
Read the first row: differentiating with respect to gives , and Equation 5.98’s third case with , , gives . ✓
Read the second row: , and the first case with , , , gives . ✓
The rows for and are identical, which they must be because
is symmetric. Measured on a : the worst asymmetry
is exactly 0.0e+00.
A finding: the symmetry becomes a rank deficiency
Section titled “A finding: the symmetry becomes a rank deficiency”Flatten that tensor for , and you get a matrix. Its rank is not :
Because is symmetric, only of its entries are independent — and the Jacobian knows. Three of its rows are exact duplicates of three others, so the flattened Jacobian has a three-dimensional left null space.
This matters in practice. If you feed such a Jacobian to a solver expecting full rank, it will be singular, and the reason is not numerical: it is that you asked for the derivative of nine quantities of which only six are free. Working with ‘s upper triangle instead gives a Jacobian of full rank .
The differential form: how to avoid the tensor entirely
Section titled “The differential form: how to avoid the tensor entirely”There is a third option, and it is what people actually do. Instead of building the tensor, perturb and read off:
so
No four-index object appears. Verified: for a random direction the
central difference
agrees with to
, and contracting the full tensor against with
einsum("pqij,ij->pq", T, H) gives the same to .
The differential form and the tensor are the same information. The differential is a directional statement, and contracting the tensor with a direction is how you recover it. §5.5’s ten identities are all differentials, which is why they are usable by hand.
See it move
Section titled “See it move”From scratch
Section titled “From scratch”import numpy as np
def tensor_jac(f, X, h=1e-6):
"""Full Jacobian tensor: shape f(X).shape + X.shape, by central differences."""
X = np.asarray(X, dtype=float)
F0 = np.asarray(f(X))
J = np.zeros(F0.shape + X.shape)
it = np.nditer(X, flags=["multi_index"])
while not it.finished:
idx = it.multi_index
Xp = X.copy(); Xp[idx] += h
Xm = X.copy(); Xm[idx] -= h
J[(Ellipsis,) + idx] = (np.asarray(f(Xp)) - np.asarray(f(Xm))) / (2 * h)
it.iternext()
return J
rng = np.random.default_rng(5)
# ---- Figure 5.7: dA/dx where A in R^{4x2} depends on x in R^3
B = rng.normal(size=(4, 2, 3))
x0 = rng.normal(size=3)
T = tensor_jac(lambda x: B @ x, x0)
print("Figure 5.7")
print(f" dA/dx tensor shape {T.shape} (the book's 4 x 2 x 3)")
print(f" flattened {T.reshape(4*2, 3).shape} = (mn) x p, the other panel")
print(f" same numbers? {np.allclose(T.reshape(4*2, 3).reshape(T.shape), T)}")
# ---- Example 5.12: f = Ax, df/dA
M, N = 4, 3
A = rng.normal(size=(M, N))
x = rng.normal(size=N)
T = tensor_jac(lambda Am: Am @ x, A)
pred = np.zeros((M, M, N))
for i in range(M):
pred[i, i, :] = x # Eq 5.92
print()
print("Example 5.12")
print(f" df/dA shape {T.shape} -- Eq 5.86 predicted {M} x ({M} x {N})")
print(f" matches Eq 5.92 to {np.abs(T - pred).max():.1e}")
print(f" nonzero entries {int(np.sum(np.abs(T) > 1e-9))} of {T.size}"
f" ({100*np.sum(np.abs(T) > 1e-9)/T.size:.1f}% = 1/M)")
# ---- Example 5.13: f(R) = R^T R
Mr, Nr = 5, 3
R = rng.normal(size=(Mr, Nr))
T = tensor_jac(lambda Rm: Rm.T @ Rm, R)
def eq_5_98(R, p, q, i, j):
if j == p and p != q:
return R[i, q]
if j == q and p != q:
return R[i, p]
if j == p and p == q:
return 2 * R[i, q]
return 0.0
pred = np.zeros_like(T)
for p in range(Nr):
for q in range(Nr):
for i in range(Mr):
for j in range(Nr):
pred[p, q, i, j] = eq_5_98(R, p, q, i, j)
print()
print("Example 5.13")
print(f" dK/dR shape {T.shape} -- Eq 5.94 predicted ({Nr}x{Nr}) x ({Mr}x{Nr})")
print(f" matches Eq 5.98 to {np.abs(T - pred).max():.1e}")
print(f" nonzero entries {int(np.sum(np.abs(T) > 1e-9))} of {T.size}"
f" ({100*np.sum(np.abs(T) > 1e-9)/T.size:.1f}%)")
sym = max(float(np.abs(T[p, q] - T[q, p]).max()) for p in range(Nr) for q in range(Nr))
print(f" worst asymmetry {sym:.1e} (K is symmetric, so this must vanish)")
for p in range(Nr):
ratio = T[p, p, :, p] / R[:, p]
print(f" dK{p}{p}/dR[:,{p}] / R[:,{p}] = {np.round(ratio, 6)} <- Eq 5.98's factor of 2")
# ---- the rank deficiency the symmetry causes
flatJ = T.reshape(Nr * Nr, Mr * Nr)
print()
print("Flattened, and the consequence of symmetry")
print(f" as a matrix {flatJ.shape} = (N*N) x (M*N)")
print(f" rank {np.linalg.matrix_rank(flatJ)} of {min(flatJ.shape)}")
print(f" N(N+1)/2 {Nr*(Nr+1)//2} <- the number of FREE entries of K")
# ---- and the differential form, which never builds a tensor
H = rng.normal(size=(Mr, Nr))
h = 1e-6
num = ((R + h*H).T @ (R + h*H) - (R - h*H).T @ (R - h*H)) / (2*h)
ana = R.T @ H + H.T @ R
print()
print("The differential form dK = R^T dR + dR^T R")
print(f" vs a central difference {np.abs(num - ana).max():.1e}")
print(f" vs contracting the tensor "
f"{np.abs(np.einsum('pqij,ij->pq', T, H) - ana).max():.1e}")Figure 5.7
dA/dx tensor shape (4, 2, 3) (the book's 4 x 2 x 3)
flattened (8, 3) = (mn) x p, the other panel
same numbers? True
Example 5.12
df/dA shape (4, 4, 3) -- Eq 5.86 predicted 4 x (4 x 3)
matches Eq 5.92 to 1.5e-10
nonzero entries 12 of 48 (25.0% = 1/M)
Example 5.13
dK/dR shape (3, 3, 5, 3) -- Eq 5.94 predicted (3x3) x (5x3)
matches Eq 5.98 to 4.8e-10
nonzero entries 75 of 135 (55.6%)
worst asymmetry 0.0e+00 (K is symmetric, so this must vanish)
dK00/dR[:,0] / R[:,0] = [2. 2. 2. 2. 2.] <- Eq 5.98's factor of 2
dK11/dR[:,1] / R[:,1] = [2. 2. 2. 2. 2.] <- Eq 5.98's factor of 2
dK22/dR[:,2] / R[:,2] = [2. 2. 2. 2. 2.] <- Eq 5.98's factor of 2
Flattened, and the consequence of symmetry
as a matrix (9, 15) = (N*N) x (M*N)
rank 6 of 9
N(N+1)/2 6 <- the number of FREE entries of K
The differential form dK = R^T dR + dR^T R
vs a central difference 8.5e-10
vs contracting the tensor 3.6e-10Four readings.
The two panels of Figure 5.7 are the same numbers. reshape is free and
reversible, which is why “flatten it” is sound advice rather than a lossy shortcut.
Example 5.12 is 75% zeros, and the fraction is exactly regardless of . The book derives that structure at Equations 5.90–5.91 without naming its consequence: a naive dense tensor wastes a factor of in memory. For a linear layer with outputs that is a factor of a thousand, which is why no framework ever materialises this object.
The asymmetry is exactly 0.0e+00. and are literally the
same sum, so their derivatives are computed from the same floating-point operations
and agree bit-for-bit. That is a check on the construction; the mathematical content
is that the tensor inherits every symmetry the function has.
And the rank is 6, not 9. The symmetry that showed up as duplicate slices shows up in the flattened matrix as a rank deficiency of exactly .
On real data
Section titled “On real data”Reading the plot
Section titled “Reading the plot”From the shape ladder. Read the shape column downwards and the escalation is clear: a scalar of a vector is a row, a vector of a vector is a matrix, a matrix of a matrix is a tensor. Nothing about the rule changed — it is always — only the number of indices.
The two strategies at the bottom are not equivalent in practice. Flattening makes
the chain rule a @; partitioning keeps the structure visible. If you are deriving
on paper, partition. If you are writing code, flatten — and if you are using a
framework, it has already flattened for you, which is why param.grad always has
param.shape.
From the identities figure. Eight rules, eight bars, all between and . The important thing about that range is that it is the test’s floor, not the identities’ error: a central difference at cannot do better than about relative, as §5.1’s figure showed. So these bars say “consistent with exact”, and a rule with a transposed factor would sit at , not .
The worst bar is at , which is unsurprising: the determinant of a amplifies input perturbations by roughly its own condition number, so the difference quotient has further to fall.
From the trace figure. Two facts in one picture. The gradient has the shape of — , not — which is what makes usable directly as an update. And its rank is capped by the inner dimension: is and is , so factors through and cannot have rank above . Measured: exactly .
That rank ceiling is why low-rank adapters work. If a loss depends on only through with thin and , then every gradient it will ever produce lies in a rank-2 subspace — so there is no point storing a full-rank update.
Pitfalls
Section titled “Pitfalls”Compare
Section titled “Compare”| you want | the object | shape | how to get it |
|---|---|---|---|
| gradient | §5.2, Equation 5.40 | ||
| Jacobian | §5.3, Definition 5.6 | ||
| gradient | , or reshaped to | §5.5’s identities | |
| 3-tensor | Example 5.12 | ||
| 4-tensor | Example 5.13 | ||
| the derivative in one direction | a matrix | the shape of the output | a differential — usually what you want |
| the derivative for code | a flattened Jacobian | reshape, then §5.3 |
-
Why does differentiating a matrix with respect to a matrix give a fourth-order tensor?
Nothing new is being defined. Definition 5.6's rule still holds; it is just that 'the output index' and 'the input index' each split into two when the objects are matrices.
pch.quizShowAnswer
B — Because the rule is unchanged — one number per (output index, input index) pair — and each index is now itself a pair, so there are four indices in total — Nothing new is being defined. Definition 5.6's rule still holds; it is just that 'the output index' and 'the input index' each split into two when the objects are matrices.
-
The book recommends re-shaping matrices into vectors. What does that buy, and what does it cost?
The book's remark says exactly this: with a tensor you must pay attention to which dimensions to sum out. And einsum with the wrong output index order runs fine while computing a transpose — there is no shape error to catch it.
pch.quizShowAnswer
B — It makes the chain rule a plain matrix multiplication whose shapes check themselves; the cost is that the block structure stops being visible, so it is worse for deriving by hand — The book's remark says exactly this: with a tensor you must pay attention to which dimensions to sum out. And einsum with the wrong output index order runs fine while computing a transpose — there is no shape error to catch it.
-
Example 5.12's tensor is 25 percent nonzero for M = 4. Where does that fraction come from?
At M = 1024 that is a factor of a thousand wasted if you materialise the tensor. Frameworks store the contraction you need instead — this object is never built.
pch.quizShowAnswer
B — From Equations 5.90 and 5.91: output f-i depends on row i of A alone, so each of the M slices has one nonzero row out of M. The fraction is exactly 1/M, whatever N is — At M = 1024 that is a factor of a thousand wasted if you materialise the tensor. Frameworks store the contraction you need instead — this object is never built.
-
Flattening dK/dR for K = R-transpose R gives a 9 by 15 matrix of rank 6, not 9. Why?
The same fact appears in the tensor as slices that are exactly equal: the measured asymmetry between dKpq/dR and dKqp/dR is 0.0e+00. Differentiate the upper triangle and you get a full-rank 6 by 15 Jacobian.
pch.quizShowAnswer
B — Because K is symmetric, so only N(N+1)/2 = 6 of its 9 entries are independent — three rows of the Jacobian are exact duplicates of three others. The deficiency is structural, not numerical — The same fact appears in the tensor as slices that are exactly equal: the measured asymmetry between dKpq/dR and dKqp/dR is 0.0e+00. Differentiate the upper triangle and you get a full-rank 6 by 15 Jacobian.
-
What is the practical alternative to building the tensor at all?
Verified to 3.6e-10 against the contracted tensor and 8.5e-10 against a central difference. All ten identities in §5.5 are differentials, which is precisely why they are usable by hand.
pch.quizShowAnswer
B — Work with differentials: perturbing K = R-transpose R gives dK = R-transpose dR + dR-transpose R in two lines, with no four-index object. Contracting the full tensor with a direction recovers exactly this — Verified to 3.6e-10 against the contracted tensor and 8.5e-10 against a central difference. All ten identities in §5.5 are differentials, which is precisely why they are usable by hand.
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – Figure 5.7, both panels
Section titled “Exercise 1 – Figure 5.7, both panels”Exercise 2 – Example 5.12, and its sparsity
Section titled “Exercise 2 – Example 5.12, and its sparsity”Exercise 3 – Equation 5.98, entry by entry
Section titled “Exercise 3 – Equation 5.98, entry by entry”Exercise 4 – The symmetry becomes a rank deficiency
Section titled “Exercise 4 – The symmetry becomes a rank deficiency”Exercise 5 – Differentials, and the einsum trap
Section titled “Exercise 5 – Differentials, and the einsum trap”Recall card
Section titled “Recall card”- The shape rule never changes — one number per (output index, input index) pair — but a matrix argument makes each index a pair, so a matrix of a matrix is a fourth-order tensor of shape (p×q)×(m×n).
- Two ways to organise it. Flatten to a (pq)×(mn) Jacobian so the chain rule is a matrix product; or partition into a grid of blocks, which keeps the structure visible for hand work. Figure 5.7 shows both, and they hold identical numbers.
- The book’s own advice is to flatten in practice, because with a tensor you must attend to which dimensions to sum out.
- Example 5.12: df/dA for f = Ax has x-transpose in slot i and zeros elsewhere, because output i touches row i alone. Measured nonzero fraction exactly 1/M — 25% at M = 4, 20% at M = 5.
- Example 5.13: every entry of dK/dR for K = R-transpose R is Equation 5.98’s four-case formula, verified to 4.8e-10 across all 135 entries.
- The factor of 2 appears when p = q, because both product-rule terms fire on the same entry. The measured diagonal ratio is exactly 2 at every entry, and dropping it leaves the off-diagonals right — so the mistake looks plausible.
- K is symmetric, so dKpq/dR and dKqp/dR are bit-for-bit identical (asymmetry 0.0e+00), and the flattened Jacobian is rank-deficient by exactly N² − N(N+1)/2. Differentiate the upper triangle to get full rank.
- Differentials avoid the tensor entirely: dK = R-transpose dR + dR-transpose R, two lines, no four-index object. Contracting the tensor with a direction recovers it to 3.6e-10.
- einsum has no shape check for a transposed output index.
pqij,ij->qpruns fine and computes the transpose; on a symmetric output it even gives the right answer, so the bug hides. - Never materialise the dense tensor. Example 5.12’s is M²N numbers to hold the content of one vector.
Next: Useful Identities for Computing Gradients — ten differentials that let you skip all of this.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading