Skip to content

Useful Identities for Computing Gradients

§5.5 is a page of the book and a table of ten equations. It states them, cites Petersen and Pedersen’s Matrix Cookbook, and proves none of them.

That is a reasonable choice for a textbook and a bad situation for a reader, because a memorised identity you cannot check is worse than no identity at all — you will use it, get a plausible number, and not find out. So this page does two things: verifies all ten against independent finite differences, and isolates the two places where the stated form differs from the shortcut people actually write, with the size of the error when they do.

Both of those are about symmetry, and both are off by tens of percent.

  • All ten identities, Equations 5.99 to 5.108, each verified numerically.
  • Why Equation 5.107 says x(B+B)\mathbf{x}^\top(\mathbf{B}+\mathbf{B}^\top) and not 2xB2\mathbf{x}^\top\mathbf{B} — with the 29% error the shortcut costs.
  • Why Equation 5.108 carries the clause “for symmetric W\mathbf{W}” — with the 107% error when it is not.
  • Which identities are the same statement in different clothes, and which are genuinely independent.
  • The cost question the section raises without answering: if a library differentiates anything, why memorise these? Measured.
  • The book’s own caveat about traces and transposes of higher-order tensors.

Intuition: the identities are differentials

Section titled “Intuition: the identities are differentials”

Every line in §5.5 is a differential — a statement about what happens to the output when you nudge the input — rather than a four-index tensor. That is why they are usable, and it is the whole reason the section exists directly after §5.4’s tensors.

Read f(X)1/X=f(X)1f(X)f(X)1\partial f(\mathbf{X})^{-1}/\partial\mathbf{X} = -f(\mathbf{X})^{-1}\,\partial f(\mathbf{X})\,f(\mathbf{X})^{-1} as: nudge X\mathbf{X}, and the inverse moves by minus the inverse, times the nudge in ff, times the inverse again. No index ever appears. Contracting §5.4’s tensor against a direction gives exactly this, which the previous page measured.

diagram Diagram mermaid

That remark is doing real work. Equations 5.99 to 5.102 are written with tr\mathrm{tr} and \top applied to f/X\partial f/\partial\mathbf{X}, which for a matrix argument is a tensor — so those symbols mean contractions, not the matrix operations they look like. In practice you apply the identity in a direction, where everything is a matrix again.

Which of these are actually different statements

Section titled “Which of these are actually different statements”

Ten equations, fewer ideas.

identitieswhat they really are
5.104, 5.105the same identity. xa=ax\mathbf{x}^\top\mathbf{a} = \mathbf{a}^\top\mathbf{x} because an inner product is symmetric, so there is one fact here, listed twice for lookup convenience
5.1065.104 with a matrix in the middle. Setting a,b\mathbf{a},\mathbf{b} to basis vectors picks out a single entry
5.1035.106 composed with 5.102: differentiate X1\mathbf{X}^{-1}, then sandwich
5.107the only genuinely quadratic one, and the only one where symmetry appears explicitly
5.1085.107 composed with the chain rule and a linear inner function — and it is Chapter 9’s gradient
5.99–5.102structural: true for any differentiable ff, not for a specific form

So the list is four structural rules and three independent concrete ones, with the rest following by composition. Worth knowing, because it tells you which ones to memorise: 5.102 and 5.107, and 5.108 because you will use it constantly.

Both are the same fact wearing different hats: a quadratic form only ever sees the symmetric part of its matrix.

For any B\mathbf{B}, write B=Bs+Ba\mathbf{B} = \mathbf{B}_s + \mathbf{B}_a with Bs=12(B+B)\mathbf{B}_s = \tfrac12(\mathbf{B}+\mathbf{B}^\top) symmetric and Ba=12(BB)\mathbf{B}_a = \tfrac12(\mathbf{B}-\mathbf{B}^\top) antisymmetric. Then

xBax=0for every x\mathbf{x}^\top\mathbf{B}_a\mathbf{x} = 0 \quad\text{for every }\mathbf{x}

because xBax\mathbf{x}^\top\mathbf{B}_a\mathbf{x} is its own negative transpose. So xBx=xBsx\mathbf{x}^\top\mathbf{B}\mathbf{x} = \mathbf{x}^\top\mathbf{B}_s\mathbf{x}, and differentiating gives 2xBs=x(B+B)2\mathbf{x}^\top\mathbf{B}_s = \mathbf{x}^\top(\mathbf{B}+\mathbf{B}^\top) — Equation 5.107.

Trap one. Writing 2xB2\mathbf{x}^\top\mathbf{B} instead is right precisely when B\mathbf{B} is symmetric. Measured on a random 4×44\times4:

formrelative error against a finite difference
x(B+B)\mathbf{x}^\top(\mathbf{B}+\mathbf{B}^\top)8.8×10118.8\times10^{-11}
2xB2\mathbf{x}^\top\mathbf{B}1.20\mathbf{1.20}

120% wrong — the error is larger than the gradient itself. And with a symmetric B\mathbf{B} the two forms agree to 0.0e+00, so the bug is invisible on every symmetric test case you might try first.

Trap two. Equation 5.108’s clause “for symmetric W\mathbf{W}” is not decoration. The general result is

s(xAs)W(xAs)=(xAs)(W+W)A\frac{\partial}{\partial\mathbf{s}}(\mathbf{x}-\mathbf{A}\mathbf{s})^\top\mathbf{W}(\mathbf{x}-\mathbf{A}\mathbf{s}) = -(\mathbf{x}-\mathbf{A}\mathbf{s})^\top(\mathbf{W}+\mathbf{W}^\top)\mathbf{A}

and the factor of 22 appears only when W=W\mathbf{W} = \mathbf{W}^\top. Measured:

W\mathbf{W}Equation 5.108 as statedthe general form
symmetric1.3×10101.3\times10^{-10}1.3×10101.3\times10^{-10}
W=I\mathbf{W} = \mathbf{I}8.7×10118.7\times10^{-11}8.7×10118.7\times10^{-11}
not symmetric2.9×101\mathbf{2.9\times10^{-1}}6.6×10126.6\times10^{-12}

Read the first two rows across: for a symmetric W\mathbf{W} the stated form and the general form give the same number, because W+W=2W\mathbf{W}+\mathbf{W}^\top = 2\mathbf{W} exactly. No symmetric test can tell them apart. On the third row the stated form is 29%29\% wrong while the general form stays at 6.6×10126.6\times10^{-12}.

(The figure below uses a different matrix and gets 1.071.07107%107\% — for the same failure. How wrong the shortcut is depends on how far from symmetric W\mathbf{W} is; that it is wrong does not.)

In practice W\mathbf{W} is usually symmetric — it is an inverse covariance, or the identity — which is exactly why the clause gets forgotten.

By coordinates. xBx=ijxiBijxj\mathbf{x}^\top\mathbf{B}\mathbf{x} = \sum_{i}\sum_{j}x_iB_{ij}x_j. Differentiate with respect to xkx_k: the variable xkx_k appears once as the ii index and once as the jj index, so the product rule gives two terms,

xki,jxiBijxj=jBkjxj+ixiBik=(Bx)k+(Bx)k\frac{\partial}{\partial x_k}\sum_{i,j}x_iB_{ij}x_j = \sum_{j}B_{kj}x_j + \sum_{i}x_iB_{ik} = (\mathbf{B}\mathbf{x})_k + (\mathbf{B}^\top\mathbf{x})_k

and collecting into a row gives x(B+B)\mathbf{x}^\top(\mathbf{B}+\mathbf{B}^\top). The two terms are where the transpose comes from — one for each occurrence of xkx_k.

By differential. Nudge x\mathbf{x} by dx\mathrm{d}\mathbf{x}:

(x+dx)B(x+dx)=xBx+dxBx+xBdx+O(dx2)(\mathbf{x}+\mathrm{d}\mathbf{x})^\top\mathbf{B}(\mathbf{x}+\mathrm{d}\mathbf{x}) = \mathbf{x}^\top\mathbf{B}\mathbf{x} + \mathrm{d}\mathbf{x}^\top\mathbf{B}\mathbf{x} + \mathbf{x}^\top\mathbf{B}\,\mathrm{d}\mathbf{x} + O(\mathrm{d}\mathbf{x}^2)

The two first-order terms are scalars, so each equals its own transpose: dxBx=xBdx\mathrm{d}\mathbf{x}^\top\mathbf{B}\mathbf{x} = \mathbf{x}^\top\mathbf{B}^\top\mathrm{d}\mathbf{x}. Adding them,

d(xBx)=x(B+B)dx\mathrm{d}\bigl(\mathbf{x}^\top\mathbf{B}\mathbf{x}\bigr) = \mathbf{x}^\top(\mathbf{B}+\mathbf{B}^\top)\,\mathrm{d}\mathbf{x}

Two lines, no indices. This is the method to use.

Equation 5.108, from 5.107 and the chain rule

Section titled “Equation 5.108, from 5.107 and the chain rule”

Let e(s)=xAs\mathbf{e}(\mathbf{s}) = \mathbf{x} - \mathbf{A}\mathbf{s} and L(e)=eWeL(\mathbf{e}) = \mathbf{e}^\top\mathbf{W}\mathbf{e}. Then by §5.3’s chain rule,

Ls=Le1×NesN×D=e(W+W)(A)\frac{\partial L}{\partial\mathbf{s}} = \underbrace{\frac{\partial L}{\partial\mathbf{e}}}_{1\times N}\underbrace{\frac{\partial\mathbf{e}}{\partial\mathbf{s}}}_{N\times D} = \mathbf{e}^\top(\mathbf{W}+\mathbf{W}^\top)\cdot(-\mathbf{A})

using Equation 5.107 for the first factor and Example 5.9 for the second. If W=W\mathbf{W} = \mathbf{W}^\top this collapses to 2eWA-2\mathbf{e}^\top\mathbf{W}\mathbf{A}, which is Equation 5.108. And with W=I\mathbf{W} = \mathbf{I} it is Example 5.11’s 2eΦ-2\mathbf{e}^\top\boldsymbol{\Phi} — the same identity, three times, at increasing generality.

From ff1=If\,f^{-1} = \mathbf{I}, differentiate both sides. The right side is constant, so

dff1+fd(f1)=0    d(f1)=f1dff1\mathrm{d}f\cdot f^{-1} + f\cdot\mathrm{d}\bigl(f^{-1}\bigr) = \mathbf{0} \implies \mathrm{d}\bigl(f^{-1}\bigr) = -f^{-1}\,\mathrm{d}f\,f^{-1}

which is Equation 5.102. The minus sign is not a convention — it comes from moving a term across an equals sign, and the two f1f^{-1} factors come from multiplying through on both sides. In one dimension this is d(1/x)=dx/x2\mathrm{d}(1/x) = -\mathrm{d}x/x^2, which is the same statement with the order irrelevant.

ten_identities.py
import numpy as np
 
def numeric_jac(f, x, h=1e-6):
    """Central-difference Jacobian of a flattened function of a flat input."""
    x = np.asarray(x, dtype=float).ravel()
    f0 = np.asarray(f(x)).ravel()
    J = np.zeros((f0.size, x.size))
    for j in range(x.size):
        e = np.zeros_like(x)
        e[j] = h
        J[:, j] = (np.asarray(f(x + e)).ravel() - np.asarray(f(x - e)).ravel()) / (2 * h)
    return J
 
rng = np.random.default_rng(19)
n, h = 3, 1e-6
fX = lambda M: M @ M                        # any smooth matrix function
tests = []
 
# 5.99  d f(X)^T = (d f(X))^T, in a direction H
X, H = rng.normal(size=(n, n)), rng.normal(size=(n, n))
tests.append(("5.99", (fX(X + h*H).T - fX(X - h*H).T) / (2*h),
                      ((fX(X + h*H) - fX(X - h*H)) / (2*h)).T))
 
# 5.100  d tr(f) = tr(d f)
tests.append(("5.100", np.array([[(np.trace(fX(X + h*H)) - np.trace(fX(X - h*H))) / (2*h)]]),
                       np.array([[np.trace((fX(X + h*H) - fX(X - h*H)) / (2*h))]])))
 
# 5.101  d det(f) = det(f) tr(f^-1 d f)
Xp, Hp = rng.normal(size=(n, n)) + 3*np.eye(n), rng.normal(size=(n, n))
dfp = (fX(Xp + h*Hp) - fX(Xp - h*Hp)) / (2*h)
tests.append(("5.101",
    np.array([[(np.linalg.det(fX(Xp + h*Hp)) - np.linalg.det(fX(Xp - h*Hp))) / (2*h)]]),
    np.array([[np.linalg.det(fX(Xp)) * np.trace(np.linalg.inv(fX(Xp)) @ dfp)]])))
 
# 5.102  d f^-1 = -f^-1 (d f) f^-1
tests.append(("5.102", (np.linalg.inv(fX(Xp + h*Hp)) - np.linalg.inv(fX(Xp - h*Hp))) / (2*h),
                       -np.linalg.inv(fX(Xp)) @ dfp @ np.linalg.inv(fX(Xp))))
 
# 5.103  d(a^T X^-1 b)/dX = -(X^-1)^T a b^T (X^-1)^T
Xi = rng.normal(size=(n, n)) + 3*np.eye(n)
a, b = rng.normal(size=n), rng.normal(size=n)
Xinv = np.linalg.inv(Xi)
tests.append(("5.103", numeric_jac(lambda v: np.array([a @ np.linalg.inv(v.reshape(n, n)) @ b]), Xi),
                       (-Xinv.T @ np.outer(a, b) @ Xinv.T).reshape(1, -1)))
 
# 5.104 and 5.105  d(x^T a)/dx = a^T = d(a^T x)/dx
x, av = rng.normal(size=n), rng.normal(size=n)
tests.append(("5.104", numeric_jac(lambda v: np.array([v @ av]), x), av.reshape(1, -1)))
tests.append(("5.105", numeric_jac(lambda v: np.array([av @ v]), x), av.reshape(1, -1)))
 
# 5.106  d(a^T X b)/dX = a b^T
Xm = rng.normal(size=(n, n))
tests.append(("5.106", numeric_jac(lambda v: np.array([a @ v.reshape(n, n) @ b]), Xm),
                       np.outer(a, b).reshape(1, -1)))
 
# 5.107  d(x^T B x)/dx = x^T (B + B^T)
B = rng.normal(size=(n, n))
tests.append(("5.107", numeric_jac(lambda v: np.array([v @ B @ v]), x),
                       (x @ (B + B.T)).reshape(1, -1)))
 
# 5.108  d/ds (x - As)^T W (x - As) = -2 (x - As)^T W A,  W symmetric
m = 4
Aw = rng.normal(size=(m, n))
W0 = rng.normal(size=(m, m)); W = W0 + W0.T
xw, s0 = rng.normal(size=m), rng.normal(size=n)
r = xw - Aw @ s0
tests.append(("5.108", numeric_jac(lambda v: np.array([(xw - Aw @ v) @ W @ (xw - Aw @ v)]), s0),
                       (-2 * r @ W @ Aw).reshape(1, -1)))
 
errs = []
for name, num, ana in tests:
    num = np.asarray(num, float)
    ana = np.asarray(ana, float).reshape(num.shape)
    e = float(np.abs(num - ana).max()) / max(float(np.abs(ana).max()), 1e-12)
    errs.append(e)
    print(f"  {name:>7}  relative error {e:.2e}")
print(f"  worst of the ten: {max(errs):.1e}")
output
     5.99  relative error 0.00e+00
    5.100  relative error 2.51e-11
    5.101  relative error 1.29e-10
    5.102  relative error 4.71e-11
    5.103  relative error 2.71e-10
    5.104  relative error 4.00e-11
    5.105  relative error 4.00e-11
    5.106  relative error 5.25e-11
    5.107  relative error 8.70e-11
    5.108  relative error 1.46e-10
  worst of the ten: 2.7e-10

Two readings.

Equation 5.99’s error is exactly 0.00e+00. That is not machine precision, it is identically zero — the two sides are the same four numbers rearranged, because transposing before or after a difference of transposes is the same set of subtractions. A check that comes out exactly zero is checking the algebra rather than the analysis.

The other nine are between 2.5×10112.5\times10^{-11} and 2.7×10102.7\times10^{-10}, which is the test’s floor rather than the identities’. §5.1’s figure showed a central difference at h=106h = 10^{-6} cannot do better than about 101010^{-10} relative. So these say “consistent with exact”, and the two traps below sit at 10110^{-1} and 10010^{0} — nine orders away.

sketch A quadratic form only sees the symmetric part p5.js
Drag the entries of B and watch two things: the antisymmetric part's contribution to x-transpose B x, which is always exactly zero, and the gap between the correct gradient and the 2 x-transpose B shortcut. Press 'symmetrise' and the gap vanishes — which is why the shortcut survives every symmetric test case.
sketch Identity, autodiff, or differencing? p5.js
The cost of one gradient of x-transpose B x, three ways, as the dimension grows. Drag n. The closed form and reverse-mode autodiff track each other; finite differencing needs 2n function evaluations and pulls away as exactly n+1 times the cost. The identity is not faster than autodiff — it is the same order, and that is the point.
the_two_traps.py
import numpy as np
 
def numeric_jac(f, x, h=1e-6):
    x = np.asarray(x, dtype=float).ravel()
    f0 = np.asarray(f(x)).ravel()
    J = np.zeros((f0.size, x.size))
    for j in range(x.size):
        e = np.zeros_like(x)
        e[j] = h
        J[:, j] = (np.asarray(f(x + e)).ravel() - np.asarray(f(x - e)).ravel()) / (2 * h)
    return J
 
rng = np.random.default_rng(31)
 
# ---- Trap one: Eq 5.107 is (B + B^T), not 2B.
print("Trap one: d(x^T B x)/dx")
B = rng.normal(size=(4, 4))
x = rng.normal(size=4)
num = numeric_jac(lambda v: np.array([v @ B @ v]), x)
right = (x @ (B + B.T)).reshape(1, -1)
wrong = (2 * x @ B).reshape(1, -1)
scale = float(np.abs(right).max())
print(f"  x^T(B + B^T)   relative error {float(np.abs(num - right).max()) / scale:.2e}")
print(f"  2 x^T B        relative error {float(np.abs(num - wrong).max()) / scale:.2e}")
 
Bs = B + B.T                                   # now symmetric
num2 = numeric_jac(lambda v: np.array([v @ Bs @ v]), x)
r2 = (x @ (Bs + Bs.T)).reshape(1, -1)
w2 = (2 * x @ Bs).reshape(1, -1)
print(f"  with a SYMMETRIC B the two forms differ by {float(np.abs(r2 - w2).max()):.1e}")
print(f"  -- so the shortcut passes every symmetric test case")
 
# And why: the antisymmetric part contributes nothing to the form itself.
Ba = (B - B.T) / 2
vals = [float(v @ Ba @ v) for v in rng.normal(size=(5, 4))]
print(f"  x^T B_a x for five random x: {[f'{v:.1e}' for v in vals]}")
 
# ---- Trap two: Eq 5.108 needs W symmetric.
print()
print("Trap two: d/ds (x - As)^T W (x - As)")
m, d = 6, 3
A = rng.normal(size=(m, d))
xv = rng.normal(size=m)
s0 = rng.normal(size=d)
e0 = xv - A @ s0
 
for label, W in (("symmetric", (lambda M: M + M.T)(rng.normal(size=(m, m)))),
                 ("identity", np.eye(m)),
                 ("NOT symmetric", rng.normal(size=(m, m)))):
    num = numeric_jac(lambda v: np.array([(xv - A @ v) @ W @ (xv - A @ v)]), s0)
    stated = (-2 * e0 @ W @ A).reshape(1, -1)              # Eq 5.108 as written
    general = (-e0 @ (W + W.T) @ A).reshape(1, -1)         # the general form
    sc = max(float(np.abs(num).max()), 1e-12)
    print(f"  W {label:14} Eq 5.108 as stated {float(np.abs(num - stated).max())/sc:9.2e}"
          f"   general form {float(np.abs(num - general).max())/sc:9.2e}")
output
Trap one: d(x^T B x)/dx
  x^T(B + B^T)   relative error 8.78e-11
  2 x^T B        relative error 1.20e+00
  with a SYMMETRIC B the two forms differ by 0.0e+00
  -- so the shortcut passes every symmetric test case
  x^T B_a x for five random x: ['3.5e-18', '-4.2e-16', '3.5e-18', '-5.4e-17', '-1.4e-16']
 
Trap two: d/ds (x - As)^T W (x - As)
  W symmetric      Eq 5.108 as stated  1.26e-10   general form  1.26e-10
  W identity       Eq 5.108 as stated  8.70e-11   general form  8.70e-11
  W NOT symmetric  Eq 5.108 as stated  2.86e-01   general form  6.57e-12

The two rows to read are the last two.

For a symmetric W\mathbf{W} the stated identity and the general form give the same answer — because W+W=2W\mathbf{W}+\mathbf{W}^\top = 2\mathbf{W} exactly. So there is no test with a symmetric W\mathbf{W} that can distinguish them, and inverse covariances are symmetric, and the identity matrix is symmetric.

For a non-symmetric W\mathbf{W} the stated identity is off by 2.9×1012.9\times10^{-1} while the general form stays at 6.6×10126.6\times10^{-12} — four orders apart on the same input. The clause “for symmetric W\mathbf{W}” is the whole difference.

And the last line of the first block explains both traps: xBax\mathbf{x}^\top\mathbf{B}_a\mathbf{x} comes out between 3.5×10183.5\times10^{-18} and 4.2×10164.2\times10^{-16} for five random x\mathbf{x} — zero to within rounding. The antisymmetric part is invisible to the form, so it cannot appear in the derivative, so any expression that retains it is wrong by exactly the amount it retains.

figure All ten identities of §5.5, checked rather than quoted matplotlib
A horizontal bar chart of the ten identities of §5.5, each labelled with its equation number and formula, with bars showing relative disagreement against a central difference on a logarithmic axis between 1e-14 and 1e-6. A horizontal bar chart of the ten identities of §5.5, each labelled with its equation number and formula, with bars showing relative disagreement against a central difference on a logarithmic axis between 1e-14 and 1e-6.
Every rule differenced independently at h = 1e-6. Equation 5.99 comes out at exactly 0.0e+00 because both sides are the same numbers rearranged; the other nine sit between 2.5e-11 and 2.7e-10, which is the central difference's own floor rather than the identities' error.
figure Why memorise an identity when a library will differentiate anything matplotlib
A log-log plot of multiplication counts against dimension for three ways of computing a gradient: a closed form, reverse-mode autodiff, and finite differences, with the first two parallel and the third pulling away. A log-log plot of multiplication counts against dimension for three ways of computing a gradient: a closed form, reverse-mode autodiff, and finite differences, with the first two parallel and the third pulling away.
The closed form and reverse-mode AD are the same order and stay parallel. Finite differencing needs 2n evaluations of an n-squared form, so its ratio to the closed form is exactly n + 1 — 3x at n = 2, 257x at n = 256. The identity's advantage over AD is not speed, it is needing no graph.
figure Equation 5.108's symmetry clause is load-bearing matplotlib
Left, a scatter of data with a fitted line and vertical residual segments. Right, a bar chart of the relative error of Equation 5.108 for a symmetric W, the identity, and a non-symmetric W, with the last bar orders of magnitude longer. Left, a scatter of data with a fitted line and vertical residual segments. Right, a bar chart of the relative error of Equation 5.108 for a symmetric W, the identity, and a non-symmetric W, with the last bar orders of magnitude longer.
Left, the residuals the weighted least-squares objective squares. Right, the identity as stated: 1.7e-10 for a symmetric W, 5.6e-11 for the identity matrix, and 1.07 relative — larger than the gradient — for a non-symmetric one. The general form is minus e-transpose times (W plus W-transpose) times A.

From the ten-identities figure. The bars span 2.5×10112.5\times10^{-11} to 2.7×10102.7\times10^{-10}, and the useful thing about that narrow range is that it is uninformative about the identities: it is the resolution of the test. What would be informative is a bar at 10110^{-1}, and the next figure has one.

Two bars are worth naming. Equation 5.99 at exactly 0.0e+00 — the algebra, not the analysis, as discussed above. And Equation 5.103 at 2.7×10102.7\times10^{-10}, the worst of the ten, which is expected: it differentiates through a matrix inverse, and inverting a 3×33\times3 amplifies input perturbations by its condition number, so the difference quotient has further to fall before it converges.

From the cost figure. The green and blue lines being parallel is the answer to the question the section raises. A closed-form identity is 2n22n^2 multiplications and reverse-mode autodiff is about 3n23n^2 — the same order, a constant factor apart. So memorising identities does not buy asymptotic speed over a library.

What it buys is visible in the red line and in the ratio:

nnclosed formreverse ADdifferencingratio
2288121224243×3\times
88128128192192115211529×9\times
323220482048307230726758467\,58433×33\times
1281283276832\,7684915249\,15242270724\,227\,072129×129\times
256256131072131\,072196608196\,6083368550433\,685\,504257×257\times

The ratio is exactly n+1n+1, which falls straight out of the arithmetic: 2n(n2+n)2n2=n2+nn=n+1\frac{2n(n^2+n)}{2n^2} = \frac{n^2+n}{n} = n+1. So differencing is not a constant factor worse, it is a factor of the dimension worse — which is the real reason nobody gradient-checks a large model except on a tiny slice.

And the honest summary of the identities: they are for deriving on paper. When you are writing code, use the library; when you are working out what the gradient is, these ten lines are what you have.

From the symmetry figure. The right panel is the only bar chart in this module with a bar at 10010^{0}: 1.7×10101.7\times10^{-10} for a symmetric W\mathbf{W}, 5.6×10115.6\times10^{-11} for the identity, and 1.071.07107%107\% — for a non-symmetric one. The failing case is the one you would never test, because W\mathbf{W} is almost always symmetric.

The left panel is there to say what the objective is: those vertical red segments are the residuals, and W\mathbf{W} decides how much each one counts. With W=I\mathbf{W} = \mathbf{I} they count equally, which is ordinary least squares; with W=Σ1\mathbf{W} = \boldsymbol{\Sigma}^{-1} they are weighted by certainty, which is Chapter 9’s generalised least squares. Both symmetric. The clause survives because the case that violates it does not arise — until someone writes W = np.random.randn(m, m) in a test.

identitythe shortest way to see itwhere it is used
5.99 transposeboth sides are the same entries reindexedrearranging derivations
5.100 tracethe trace is a sum, and differentiation is linearCh 6 Gaussian log-densities
5.101 determinantJacobi’s formula; from logdet\log\det and 5.102Ch 6 change of variables, normalising flows
5.102 inversedifferentiate ff1=Iff^{-1} = \mathbf{I} and move a termCh 9 posterior updates
5.1035.106 composed with 5.102Gaussian conditionals
5.104 = 5.105an inner product is symmetriceverywhere
5.1065.104 with a matrix insertedpicking out matrix entries
5.107 quadratictwo terms because xkx_k appears twiceevery quadratic loss, every Hessian
5.108 least squares5.107 then the chain rule then Example 5.9Ch 9’s starting point

The three in bold are the ones to memorise. The rest you can rebuild from them in a line or two, which is a better use of memory than a lookup table you cannot check.

pch.quizTag Check your understanding
  1. Why does Equation 5.107 say x-transpose times (B plus B-transpose) rather than 2 x-transpose B?

    pch.quizShowAnswer

    B — Because differentiating the double sum hits x_k twice — once as the row index and once as the column index — giving Bx plus B-transpose x. Writing 2B keeps the antisymmetric part, which the quadratic form cannot see — Measured on a random 4x4: the shortcut is off by 1.20 relative — 120 percent, a larger error than the gradient is large. And for symmetric B the two forms agree to exactly 0.0e+00, so the shortcut passes every symmetric test case you would think to write.

  2. Both traps on this page come from the same underlying fact. What is it?

    pch.quizShowAnswer

    B — That a quadratic form only sees the symmetric part of its matrix: x-transpose times the antisymmetric part times x is exactly zero for every x, so that part cannot appear in the derivative — Measured: x-transpose B_a x came out between 3.5e-18 and 4.2e-16 for five random x — zero to within rounding. Any expression for the gradient that retains B_a — like 2 x-transpose B, or Eq 5.108 with a non-symmetric W — is wrong by exactly that retained part.

  3. The ten identity bars all sit between 2.5e-11 and 2.7e-10. What does that range tell you?

    pch.quizShowAnswer

    B — Almost nothing about the identities — it is the central difference's own floor at h = 1e-6. What would be informative is a bar at 1e-1, and the symmetry figure has one at 1.07 — A test whose resolution is 1e-10 can only ever say 'consistent with exact'. The value of the check is that a wrong form lands nine orders away: the two traps on this page measure 1.20 and 2.9e-01.

  4. The cost figure shows finite differencing costing exactly n+1 times the closed form. Where does that come from?

    pch.quizShowAnswer

    B — From the arithmetic: differencing needs 2n evaluations of a form costing n-squared plus n, over a closed form costing 2 n-squared — which simplifies to n+1 exactly — So differencing is not a constant factor worse, it is a factor of the DIMENSION worse: 3x at n = 2 and 257x at n = 256. That is why nobody gradient-checks a large model except on a tiny slice.

  5. Given that a closed form and reverse-mode autodiff are the same order, what are these ten identities actually for?

    pch.quizShowAnswer

    B — Deriving on paper. They need no computation graph, so they are what you use to work out what a gradient IS, and to sanity-check a library on a small case — not to replace the library — 2n-squared against 3n-squared is a constant factor, so hand-derived gradients buy no asymptotic speed and cost a maintenance burden whenever the model changes. The measured lines are parallel on the log-log plot, which is the whole point.

Exercise 2 – Trap one: why (B + B-transpose)

Section titled “Exercise 2 – Trap one: why (B + B-transpose)”

Exercise 3 – Trap two: Equation 5.108’s symmetry clause

Section titled “Exercise 3 – Trap two: Equation 5.108’s symmetry clause”

Exercise 5 – Rebuild 5.103 and 5.108 by composition

Section titled “Exercise 5 – Rebuild 5.103 and 5.108 by composition”
  • Every identity in §5.5 is a differential — a statement about one nudge — which is why they are usable by hand where §5.4’s tensors are not.
  • Ten equations, fewer ideas. 5.99 to 5.102 are structural rules for any f; 5.104 and 5.105 are the same fact; 5.103 is 5.106 composed with 5.102; 5.108 is 5.107 plus the chain rule. Memorise 5.102, 5.107 and 5.108.
  • All ten verified against central differences: worst relative error 2.7e-10, which is the test’s floor rather than the identities’.
  • Equation 5.99 comes out at exactly 0.0e+00, because both sides are the same numbers rearranged — a check on the algebra, not the analysis.
  • Trap one: Equation 5.107 is x-transpose (B + B-transpose), not 2 x-transpose B. The shortcut is off by 1.20 relative on a general B — more than the gradient itself — and exactly right on a symmetric one, so it passes every symmetric test case.
  • Trap two: Equation 5.108’s ‘for symmetric W’ is load-bearing. With a non-symmetric W the stated form is off by 2.9e-01 in one test and 1.07 in another, while the general form — minus e-transpose (W + W-transpose) A — stays at 1e-12.
  • Both traps are the same fact: a quadratic form only sees the symmetric part of its matrix, because x-transpose times the antisymmetric part times x is zero for every x — measured between 3.5e-18 and 4.2e-16.
  • The trace and transpose in 5.99 to 5.102 mean contractions when f takes a matrix argument — the book’s own Remark says the matrix operations are undefined for higher-order tensors.
  • Differencing costs exactly n+1 times a closed form — 3x at n = 2, 257x at n = 256 — because it needs 2n evaluations of an n-squared form.
  • A closed form and reverse-mode AD are the same order, 2n² against 3n². Identities buy understanding and paper derivations, not speed.

Next: Backpropagation and Automatic Differentiation — the chain rule applied mechanically, and why it costs about what the function costs.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading