Linear Transformations
A matrix is not a grid of numbers, it is an instruction for reshaping space — and reading its columns tells you exactly where the axes end up.
Why Does This Exist?
A dense layer is a linear transformation. So is an embedding lookup, every projection inside attention, and every convolution. If you understand what a matrix does to space rather than just how to multiply one, a large amount of deep learning stops being arbitrary.
Three things this page buys you concretely.
Why depth needs nonlinearity. Stacking linear transformations gives you another linear transformation — the composition of two matrices is just their product. So a hundred-layer network without activation functions has exactly the capacity of a single layer. That is not a heuristic; it is a one-line consequence of composition.
What the determinant means. It is the factor by which volume changes, and a determinant of zero means the transformation flattened space and destroyed information. This is the quantity normalising flows track as , and knowing what it is makes those models legible.
Why "linear layer" is a misnomer. A layer with a bias is affine, not linear, and the distinction has real consequences for what you can fold, invert, and compose.
Think of It Like This
A photocopier with skew and zoom dials
Picture a photocopier that can do more than shrink and enlarge. It has dials for horizontal stretch, vertical stretch, rotation, and skew. Feed in a square and it hands back a parallelogram.
Two properties define what this machine is allowed to do. Straight lines stay straight — nothing gets curved. And the origin stays put — the copy is never shifted bodily across the page. Any machine obeying those two rules is a linear transformation, and every such machine is describable by a matrix.
Now the useful part. To work out what the machine does, you do not need to test every possible input. Feed in just the two unit arrows, one along each axis, and see where they land. Those two answers are the columns of the matrix, and everything else follows because any shape is built from combinations of them.
And the area of the output parallelogram, divided by the area of the square you put in, is the determinant. Set a dial such that the parallelogram collapses to a line and the determinant is zero — you have lost a dimension, and no setting of the dials can undo it.
How It Actually Works
A map is linear when it respects addition and scaling:
In plain words: transforming a sum gives the sum of the transforms, and scaling before is the same as scaling after. Setting in the second rule forces — a linear map must fix the origin, which is the rule bias terms break.
Every linear map from to is a matrix, and the columns of that matrix are where the basis vectors go. For , the first column is where lands and the second column is where lands. Reading a matrix column-wise tells you what it does, which is the single most useful habit on this page.
Composition is multiplication
Apply then and the result is the single transformation :
In plain words: chaining transformations is multiplying their matrices, right to left. This is where non-commutativity gets its meaning — rotating then stretching is genuinely a different operation from stretching then rotating, and is that fact in algebra.
It is also the proof that depth without nonlinearity is pointless. A stack of dense layers computes , which is one matrix. Every activation function exists to break this collapse.
The determinant is a volume factor
For a square matrix, is the signed factor by which volume changes. In 2D it is the area of the parallelogram the unit square becomes; in 3D the volume of the parallelepiped the unit cube becomes.
The sign carries orientation: negative means space was flipped, like a reflection. And three consequences matter:
- means volume collapsed to nothing — the transformation squashed space into a lower dimension. Information is destroyed, and no inverse exists. This is rank deficiency seen geometrically.
- , because volume factors multiply along a chain.
- expands, contracts. Repeated application therefore blows up or vanishes, which is one way to see why the product of many layers is unstable.
For a matrix, .
Affine is not linear, and your layers are affine
A dense layer computes . The part is linear; adding shifts the origin, which breaks . The combination is an affine transformation.
The distinction is not pedantry:
- Composition still works — affine composed with affine is affine, which is why the depth-collapse argument survives bias terms.
- Folding requires care. Merging BatchNorm into a preceding convolution at inference time works precisely because both are affine, but you have to carry the bias through correctly; dropping it is a real and silent bug.
- The standard trick is homogeneous coordinates: append a 1 to the input and put in an extra column of . Now the affine map is a single linear map in one higher dimension, which is how graphics libraries handle translation.
Worked example
Take and follow the unit square's corners.
Read the columns first: goes to and goes to . So the transformation stretches horizontally and skews the vertical axis to the right.
The four corners:
- — the origin is fixed, as linearity requires.
That is the parallelogram in the diagram. Its determinant:
In plain words: every region doubles in area. Check it independently — the parallelogram has base 2 (from to ) and vertical height 1, so its area is , against the unit square's 1. Ratio 2, matching the determinant.
Now compose with itself. , and , exactly as the multiplicative rule predicts.
Contrast with : . This one flattens the plane onto a line, and its output has no area at all.
Show Me the Code
import numpy as np
A = np.array([[2.0, 1.0], [0.0, 1.0]])square = np.array([[0, 1, 1, 0], [0, 0, 1, 1]], dtype=float) # (2, 4) corners
print((A @ square).T.tolist()) # -> [[0,0], [2,0], [3,1], [1,1]]print(A[:, 0], A[:, 1]) # -> [2. 0.] [1. 1.] where each axis landsprint(round(np.linalg.det(A), 10)) # -> 2.0 area factorprint(round(np.linalg.det(A @ A), 10)) # -> 4.0 = 2 * 2
# Depth without nonlinearity collapses to ONE matrix.W1, W2, W3 = (np.random.default_rng(k).standard_normal((3, 3)) for k in (1, 2, 3))x = np.array([1.0, 2.0, 3.0])print(np.allclose(W3 @ (W2 @ (W1 @ x)), (W3 @ W2 @ W1) @ x)) # -> True
print(round(np.linalg.det(np.array([[1.0, 2.0], [2.0, 4.0]])), 10)) # -> 0.0The corner coordinates and the determinant of 2 match the hand calculation. The allclose line is the depth-collapse argument as an executable check.
Watch Out For
Treating a dense layer as a linear map when reasoning about composition
nn.Linear computes , which is affine. Most of the time this does not bite, and then occasionally it does — hard.
The clearest case is merging layers for inference. Folding BatchNorm into a convolution is standard and safe, but the fold has two parts: the scale multiplies into the weights, and the shift combines into the bias. Implement only the weight half and the model still runs, still produces sensible-looking outputs, and is quietly wrong — typically a few percent of accuracy, which is easy to attribute to something else.
The second case is inversion. People reason "the layer is a matrix, so invert the matrix", and forget to subtract the bias first. The inverse of is , and dropping the subtraction gives an offset error that grows with the bias magnitude.
The habit that avoids both: write out the affine form explicitly whenever you compose, fold, or invert. If a derivation on a whiteboard says , check whether the biases were dropped on purpose or by accident.
Computing a determinant directly on a large matrix
The determinant multiplies numbers of similar magnitude, so it overflows and underflows catastrophically at very modest sizes.
Take a matrix whose singular values average 2. Its determinant is around , right at the edge of float64, and average 10 puts it at — plain inf. In the other direction, average 0.5 gives , which underflows to exactly 0 and then reads as "singular" when the matrix is perfectly well conditioned.
So np.linalg.det returning 0.0 or inf on a large matrix tells you almost nothing about the matrix. Two correct tools instead. np.linalg.slogdet returns the sign and the log of the absolute determinant, which stays in range — and it is what any log-likelihood involving a determinant should use, including the term in a normalising flow. And for the question "is this invertible", use np.linalg.matrix_rank or the condition number, both of which work from singular values and do not multiply anything.
The Quick Version
- Linear means additivity and scaling are preserved, which forces .
- Every linear map is a matrix, and its columns are where the basis vectors land. Read matrices column-wise.
- Composing transformations is multiplying matrices, right to left. Non-commutativity is why order matters physically.
- A stack of dense layers without activations collapses to one matrix. That is why nonlinearity exists.
- The determinant is the signed volume factor. .
- means space was flattened, information destroyed, no inverse. Same fact as rank deficiency.
- A dense layer with bias is affine, not linear. This matters for folding, inverting, and composing.
- Homogeneous coordinates turn an affine map into a linear one in one more dimension.
- Never compute a determinant directly on a large matrix. Use
slogdet, or rank and condition number.
What to Read Next
- Eigenvalues and Eigenvectors finds the directions a transformation leaves pointing the same way.
- Vector Spaces and Rank is what a zero determinant means algebraically.
- Matrix Multiplication is composition, and where the cost lives.
- Vectors, Matrices and Tensors covers the shapes these maps act on.
- Change of Variables is what happens to a probability density when a map like this is applied to it.
- Definitions worth a look: Matrix, Identity Matrix, Transpose, and Linear Combination.
- Related interview question: Implement matrix multiplication and analyse its complexity