You have used linear algebra since the first page of this book. A dot product scored a pattern in Chapter 1. Matrix products routed queries to keys in Chapter 13. Low-rank factors limited an update in Chapter 17, and PCA supplied the flat reconstruction baseline in the autoencoder interlude. Why return to the subject now?
Because the notation is compact enough to hide mistakes—a product can be legal while representing the wrong convention; an inverse can exist while being the wrong computation; and an SVD can be exact while its singular vectors are not uniquely identifiable. This appendix gathers the geometry and the PyTorch habits that let you audit those cases. It is a working reference—not a compressed linear-algebra course.
A.1 Read the map from its dimensions
Let us start with one concrete map. A matrix \(\matr{A}\in\mathbb{R}^{m\times n}\) accepts a column vector \(\vect{x}\in\mathbb{R}^{n}\) and returns \(\vect{y}=\matr{A}\vect{x}\in\mathbb{R}^{m}\). The input has \(n\) coordinates, the output has \(m\), and column \(j\) of \(\matr{A}\) records where the \(j\)th standard basis vector goes. Once those columns are known, every input follows—by linear combination:
That is the geometric reading of matrix multiplication. The computational reading is the same operation viewed by rows: output coordinate \(i\) is the dot product \(\matr{A}_{i:}\vect{x}\). Switching between the two views is useful—the columns show what the map can produce, while the rows show how each output is measured.
A map \(T\) is linear when, for every pair of vectors and scalars,
This definition includes \(T(\vect{0})=\vect{0}\)—a fact worth checking before calling an operation linear. Adding a nonzero bias breaks that condition, so \(\vect{x}\mapsto\matr{A}\vect{x}+\vect{b}\) is affine. PyTorch’s nn.Linear uses that affine form by default despite its name—a useful vocabulary distinction whenever a theorem assumes a genuinely linear map.
One vector on paper, a row batch in PyTorch
The mathematical convention above stores one sample as a column. The book’s PyTorch code stores \(B\) samples as rows of \(\matr{X}\in\mathbb{R}^{B\times n}\). The same map is therefore
PyTorch stores an nn.Linear(n, m) weight with shape \((m,n)\), and its matrix part uses exactly this row-batch convention. With the default bias, the full affine operation is \(\matr{Y}=\matr{X}\matr{A}^{\top}+\vect{1}\vect{b}^{\top}\). What do we expect before running the code? One contract is already fixed—the batch shaped \((3,2)\) multiplied by \(\matr{A}^{\top}\in\mathbb{R}^{2\times2}\) must remain \((3,2)\). Let us check the values as well as the shapes.
Code: reconcile column-vector algebra with PyTorch row batches
The two batch calculations agree exactly. The composition check exposes a second habit: matrix order reads from right to left. If \(\matr{R}\) rotates and \(\matr{S}\) scales, then \(\matr{S}\matr{R}\vect{x}\) rotates first. Reversing the factors changes the answer from \((-4,3)\) to \((-6,2)\)—both products are dimensionally legal, so a shape check alone cannot catch the semantic mistake.
TipA three-line shape audit
Before every product, write the operand shapes; identify which two dimensions contract; then write the surviving shape. For high-rank tensors, @ treats the last two axes as matrix axes and the leading axes as batch axes. Broadcasting and physical layout are gathered in Appendix B.
A.2 Span, rank, and orthogonality
The span of a set of vectors is every linear combination they can produce. A basis spans a space without redundant directions. These words make a matrix’s geometry precise:
The column space\(\operatorname{col}(\matr{A})\subseteq\mathbb{R}^{m}\) is every reachable output \(\matr{A}\vect{x}\).
The null space\(\operatorname{null}(\matr{A})\subseteq\mathbb{R}^{n}\) is every input direction that the map sends to zero.
The rank is the dimension of the column space—the number of independent output directions. It is at most \(\min(m,n)\), and it counts independent input directions that survive the map.
The dot product adds geometry. For \(\vect{x},\vect{z}\in\mathbb{R}^{n}\),
It is zero when nonzero vectors are perpendicular. If \(\matr{Q}\in\mathbb{R}^{m\times k}\) has orthonormal columns, so that \(\matr{Q}^{\top}\matr{Q}=\matr{I}_k\), then
projects \(\vect{y}\) onto \(\operatorname{col}(\matr{Q})\). The projector is symmetric and idempotent: \(\matr{P}^{\top}=\matr{P}\) and \(\matr{P}^2=\matr{P}\). The residual \(\vect{r}=\vect{y}-\widehat{\vect{y}}\) is orthogonal to every column of \(\matr{Q}\).
This is the picture behind the normal equations in Chapter 1—the fit uses every reachable direction and leaves an orthogonal residual. It also explains why the autoencoder interlude compares PCA and a tied linear autoencoder through their projectors, not through individual basis vectors. Rotating or reflecting an orthonormal basis changes its coordinates while leaving \(\matr{Q}\matr{Q}^{\top}\) unchanged—the subspace is the stable object.
Figure A.1: Projection separates what the columns can express from what they cannot. The fitted vector \(\widehat{\vect{y}}\) lies in the column space; the residual is orthogonal to every column direction. The diagram labels that residual \(\vect{e}=\vect{y}-\widehat{\vect{y}}\); the appendix uses \(\vect{r}\) for the same quantity.
A.3 Solve the problem you actually have
Suppose \(\matr{A}\in\mathbb{R}^{n\times n}\) is nonsingular and \(\vect{b}\in\mathbb{R}^{n}\). The equation \(\matr{A}\vect{x}=\vect{b}\) has one solution. The notation \(\vect{x}=\matr{A}^{-1}\vect{b}\) describes it, but it does not prescribe a good algorithm. In PyTorch, use torch.linalg.solve(A, b)—it solves the system without first materializing an inverse.
Most data problems are rectangular—the target may not lie in the column space. For \(\matr{X}\in\mathbb{R}^{N\times d}\) and \(\vect{y}\in\mathbb{R}^{N}\), least squares asks for
At an optimum, the residual—a record of what the columns cannot explain— \(\vect{r}=\vect{y}-\matr{X}\widehat{\vect{w}}\) satisfies \(\matr{X}^{\top}\vect{r}=\vect{0}\). If \(\matr{X}\) has full column rank, expanding that condition gives the familiar normal-equation expression
Again, this is an identity—not the preferred recipe. torch.linalg.lstsq(X, y) works on the rectangular system directly. It also reports rank-related information for supported algorithms, which matters when columns are redundant or nearly so.
Why avoid forming \(\matr{X}^{\top}\matr{X}\)? Let the nonzero singular values of a full-column-rank matrix be \(\sigma_{\max}\) and \(\sigma_{\min}\). Its 2-norm condition number is
Directions that were difficult to distinguish become much more difficult after the square—the next cell uses a matrix whose columns differ by only \(0.001\).
Code: solve square and rectangular systems, then expose conditioning
The least-squares solution is \((0.9,0.9)\), and the norm of \(\matr{X}^{\top}\vect{r}\) is about \(2.2\times10^{-15}\) in float64. For the nearly collinear matrix, the condition number grows from about \(4.002\times10^3\) to \(1.602\times10^7\) after forming \(\matr{A}^{\top}\matr{A}\)—the predicted square to displayed precision.
WarningMore digits do not repair missing information
Float64 makes this tiny audit easier to inspect; it does not make an ill-conditioned problem well-conditioned. Check inputs with torch.isfinite, inspect torch.linalg.cond or the singular values when stability matters, and verify the residual. Near a numerical-rank threshold, reported rank and solutions can change with dtype, tolerance, algorithm, or platform. Appendix C separates those numerical questions from the underlying algebra.
A.4 Eigenvectors are a square-matrix special case
An eigenvector asks whether one nonzero direction stays on its own line under a square map:
This question requires the input and output spaces to be the same—even then, a real matrix may have complex eigenvalues or too few independent eigenvectors. Symmetric real matrices are the friendly case: their eigenvalues are real and they admit an orthonormal eigenbasis. Use torch.linalg.eigh, rather than the more general eig, when that symmetry is known.
Deep-learning weight matrices are often rectangular, so we need a decomposition that uses one set of directions in the input space and another in the output space. That is the role of the singular value decomposition—the two spaces no longer need to be the same.
A.5 SVD: two spaces and one ordered scale
Let \(\matr{A}\in\mathbb{R}^{m\times n}\) and set \(k=\min(m,n)\). Its reduced SVD is
The singular values are real, nonnegative, and ordered—a scale ledger from the most amplified direction to the least: \(\sigma_1\geq\cdots\geq\sigma_k\geq0\). The columns satisfy
Geometrically, \(\matr{V}^{\top}\) selects orthogonal input coordinates, \(\matr{\Sigma}\) scales or removes them, and \(\matr{U}\) places the result in the output space. Strang’s rotate–stretch–rotate picture is useful in the square, two-dimensional case—but orthogonal factors may include reflections. In a reduced rectangular SVD, \(\matr{V}^{\top}\) maps \(n\) coordinates to \(k\) and \(\matr{U}\) maps those \(k\) coordinates into an \(m\)-dimensional output; the dimension change belongs to the whole factorization, not to the displayed \(k\times k\) diagonal matrix alone.
The eigenvalue bridge follows—without using \(\matr{A}^{\top}\matr{A}\) as a numerical SVD algorithm:
torch.linalg.svd(A, full_matrices=False) returns U, S, Vh; S is a one-dimensional tensor, not a diagonal matrix. Scaling the columns of U reconstructs the map without building that diagonal matrix: (U * S.unsqueeze(-2)) @ Vh.
For \(0\leq r<k\), the Eckart–Young–Mirsky result makes “best” precise—among matrices of rank at most \(r\), \(\matr{A}_r\) minimizes both the operator-norm and Frobenius-norm errors, with
Let us construct a map whose singular values are exactly 3 and 1. The full map sends the unit circle to an ellipse. The rank-one approximation keeps the longer axis and collapses the other—a visible instance of the theorem rather than a claim inferred from a complicated dataset.
Figure code: decompose a known map and expose the rank-one error
Figure A.2: A two-dimensional SVD with singular values 3 and 1. The right singular vectors choose orthogonal input directions; the left singular vectors orient the ellipse’s scaled axes. Keeping one rank-one term collapses the ellipse to its longer axis. Because there is only one omitted singular value, both the operator error and Frobenius error are exactly 1.
The singular values are unique, but their vectors need not be—each singular pair can flip sign without changing \(\matr{A}\). When singular values repeat, any orthonormal basis of the repeated subspace is valid. Accordingly, tests should compare the reconstruction or a subspace projector—not demand one particular U or Vh.
WarningMagnitude is not meaning
A truncated SVD is optimal for the matrix norms in Equation A.10. That does not prove that a large singular direction is semantic signal or that a small one is noise. Feature scaling, the downstream task, and the data-generating process decide whether discarding a direction is useful. A low-rank weight approximation can be close as a matrix yet still change a model’s predictions; measure the downstream behavior.
A.6 Batched linear algebra
The same decomposition can be applied to many matrices at once—the last two dimensions remain the matrix. For an input shaped \((B,m,n)\), torch.linalg.svd(..., full_matrices=False) returns shapes \((B,m,k)\), \((B,k)\), and \((B,k,n)\). The singular values need one inserted axis so they scale columns of U rather than a batch axis.
Code: reconstruct a batch of rectangular matrices from reduced SVDs
A batch: (2, 3, 2)
U, S, Vh: (2, 3, 2) (2, 2) (2, 2, 2)
batched reconstruction max error: 0.000e+00
singular values by matrix: [[3.0, 1.0], [2.0, 0.5]]
The leading dimension stays a batch throughout. A Python loop and a batched call are mathematically equivalent here, but floating-point implementations need not be bitwise identical. Use tolerances such as torch.allclose, and choose them from the dtype and the scale of the quantity being checked—not from the number of decimals you hope to see.
A.7 PCA is SVD after centering
Let \(\matr{X}\in\mathbb{R}^{N\times d}\) contain observations in rows, and let \(\vect{\mu}\in\mathbb{R}^{d}\) be the training mean. PCA begins with
If \(\matr{X}_c=\matr{U}\matr{\Sigma}\matr{V}^{\top}\), then the first \(r\) principal directions are the columns of \(\matr{V}_r\). The scores and reconstruction are
For the usual sample-covariance convention, component \(i\) explains variance \(\sigma_i^2/(N-1)\). Centering is not cosmetic—it changes the question. Without it, the leading singular direction of the raw data matrix can mostly describe the offset from the origin.
The next five points all have first coordinate 10 and vary only in the second coordinate. What should PCA find? The answer is vertical variation. Raw SVD instead spends its first direction on the large mean; centering restores the intended question.
Figure code: make the PCA centering failure visible
Figure A.3: PCA asks about variation around the training mean. These five points vary only vertically, but raw SVD first follows their large horizontal offset from the origin: its singular values are 22.360680 and 3.162278. After centering at (10, 0), the vertical singular value is 3.162278, the other is zero, and one component reconstructs every point exactly.
In a train/validation/test workflow, estimate \(\vect{\mu}\) and the principal directions on the training split only. Reuse that training mean for every later split; re-centering validation or test data separately changes the map and leaks information. The autoencoder interlude adds the next link: a tied linear autoencoder can recover the same principal reconstruction subspace under its stated centered, squared-loss, undercomplete, global-optimum conditions. It does not guarantee the same basis or that gradient descent reaches the optimum.
A.8 Practical torch.linalg checklist
When the algebra moves into code, ask these questions in order:
What are the last two dimensions? For operands with rank at least two, they define the matrix and leading dimensions are batches. Rank-one operands use matmul’s vector-specific insertion and removal rules; Appendix B works through those cases. Write the expected output shape before executing.
Is the task a product, a solve, or a least-squares problem? Use @, solve, or lstsq directly. Do not compute an inverse or pseudoinverse merely to multiply it by a right-hand side.
Does structure buy a better routine? Use eigh for real symmetric matrices and svdvals when only singular values are needed.
Are the values finite and the problem identifiable? Check finiteness, rank, conditioning, and residuals. A returned tensor is not evidence that the answer is trustworthy.
What is actually invariant? Compare reconstructions, losses, residuals, or projectors. Singular-vector signs and bases within repeated subspaces are not fixed.
Does autograd need the vectors? Gradients involving U or Vh can become unstable when singular values coincide or nearly coincide. If the objective needs only singular values, compute only those; otherwise test the gradient regime rather than assuming the forward decomposition settles it.
Okay, so—the whole appendix can be reduced to one habit: read the geometry and the shapes before trusting the operation. Matrices map one space to another; least squares projects onto what is reachable; SVD names the independent input and output directions; truncation has a precise norm guarantee; PCA adds the indispensable centering step. PyTorch will perform each calculation quickly, but it cannot decide whether you asked the right question.
Sources and further reading
Grant Sanderson, Essence of Linear Algebra: the visual interpretation of vectors, bases, and matrices as transformations. The figures here are independently constructed rather than adapted from the series.
(Pencil.) Let \(\matr{W}\in\mathbb{R}^{5\times3}\) be the weight of an nn.Linear(3, 5). Write the shapes of the output for inputs shaped \((3,)\), \((7,3)\), and \((4,7,3)\). Then explain why PyTorch evaluates a row batch with \(\matr{X}\matr{W}^{\top}\) even though the single-vector formula is \(\matr{W}\vect{x}\).
(Pencil and code.) From Equation A.3, derive \(\matr{X}^{\top}\vect{r}=\vect{0}\). Construct increasingly collinear design matrices, compare torch.linalg.lstsq(X, y) with a solve on the normal equations, and report residual and parameter error in float32 and float64. Do not assume which method first shows visible error.
(Pencil.) Using Equation A.6, prove Equation A.7. For a repeated singular value, show why rotating the corresponding columns of \(\matr{U}\) and \(\matr{V}\) together leaves \(\matr{A}\) unchanged.
(Code.) Generate a deterministic batch with shape \((6,8,3)\) and reconstruct all six matrices from reduced SVDs. Build rank-one and rank-two approximations, verify Equation A.10 for every batch member, and state a tolerance justified by the dtype and matrix scale.
(Code and interpretation.) Add a large offset to two-dimensional data and compare raw SVD with centered PCA. Fit the mean and basis on a training split; apply both unchanged to validation data. Compare projectors rather than signed basis vectors, and explain the leakage from recentering validation data.