A matrix is not a grid of numbers that you multiply by a strange rule. It is a linear function, written down. Once you see that, matrix multiplication stops being arbitrary — it is composition, and the shape rules are just Lecture 1's requirement that the codomain of one map match the domain of the next.
By the end you can
Define a linear map and prove every such map \(\RR^n\to\RR^m\) is a matrix
Read \(\mA\vx\) both as a stack of dot products and as a combination of columns
Derive the matrix multiplication formula from composition
Define rank, column space and null space, and connect rank deficiency to unidentifiable models
Debug shape errors and broadcasting bugs in NumPy on purpose
Equivalently: \(T\) preserves addition and preserves scaling. Note the immediate consequence
\(T(\vzero)=\vzero\) — take \(\alpha=\beta=0\).
Intuition
Linear means you can do the arithmetic before or after applying \(T\) and get the
same answer. Mix two inputs then transform, or transform each then mix — identical. That commuting
property is what makes linear algebra computable at scale, and it is exactly what a neural network's
nonlinearity deliberately destroys, because a composition of linear maps is just another linear map and would
buy no expressive power at all.
The standard basis. \(\ve_j\) has a 1 in slot \(j\) and zeros elsewhere. These \(n\) vectors are independent and span \(\RR^n\) (Lecture 8), so every vector decomposes along them uniquely.
Coordinates ARE the coefficients. This looks trivial and is the crux: the numbers in a column vector are precisely the coefficients of its expansion in the standard basis.
Apply linearity. Pull the sum and the scalars out. This is the whole argument: \(T\) is completely determined by what it does to the \(n\) basis vectors. Everything else follows by linear combination. \(n\) vectors' worth of information determines a function on an infinite set.
Build the matrix. Store those \(n\) output vectors as the columns of a matrix. Each is in \(\RR^m\), so the matrix is \(m\times n\): as many rows as the output dimension, as many columns as the input dimension. This is why the shape convention is what it is.
Define the matrix-vector product. Matrix-vector multiplication is defined to be exactly this: a linear combination of the columns, weighted by the entries of \(\vx\). It was not invented arbitrarily — it is the only formula that makes \(\mA\) represent \(T\). ∎
2Two ways to read Ax
The derivation gave the column picture. Writing out coordinates gives the row
picture. Both are correct and you need both.
Column picture: \(\mA\vx\) is a linear combination of \(\mA\)'s columns. So the set of
all achievable outputs — the image of \(T\) — is exactly \(\operatorname{span}\) of the columns. This
is called the column space. Solving \(\mA\vx=\vb\) asks: is \(\vb\) in the span of the
columns, and with what coefficients?
Row picture: each output coordinate is a dot product of one row with the input. This is
the computational view, and the one that makes a neural network layer legible: row \(i\) of \(\mW\) is the
weight vector of neuron \(i\), and \((\mW\vx)_i\) is that neuron's pre-activation.
A 2×2 matrix as a transformation of the planeInteractive
image of e₁ = first columnimage of e₂ = second columntransformed grida test vector and its image
Faint grey is the original grid; green is where it lands. The two coloured arrows are the images of the standard basis vectors — read straight off as the COLUMNS of A. Every other point moves as the corresponding combination of them, which is the derivation above, made visible. Push the determinant towards zero (try a=1, b=2, c=0.5, d=1) and the whole plane collapses onto a line: the map is no longer invertible, and information has been destroyed.
3Matrix multiplication is composition
Now the formula that looks arbitrary until you see where it comes from. Apply \(\mB\) then
\(\mA\). By Lecture 1 that is the composition \(\mA\circ\mB\), and it is again linear (check: it preserves
combinations because each factor does). So it must be a matrix. Which one?
Shapes first. \(\mB\) eats an \(n\)-vector and produces a \(p\)-vector; \(\mA\) eats a \(p\)-vector and produces an \(m\)-vector. For the pipeline to type-check, \(\mB\)'s output dimension must equal \(\mA\)'s input dimension — both \(p\). This is why the inner dimensions must match. It is Lecture 1's codomain-meets-domain condition, nothing more.
Use the column characterisation. From the previous derivation, the \(j\)-th column of any matrix is what it does to \(\ve_j\). And applying the composition to \(\ve_j\) means applying \(\mB\) first.
Same fact applied to B. Multiplying by \(\ve_j\) selects column \(j\) — read it off the column picture: the combination has coefficient 1 on column \(j\) and 0 everywhere else.
\[\text{column } j \text{ of } \mA\mB = \mA\vb_j\]
So the rule is: apply A to each column of B. That single sentence is matrix multiplication, and it is worth remembering in preference to the index formula.
Write it in coordinates. Entry \(i\) of \(\mA\vb_j\) is the dot product of row \(i\) of \(\mA\) with \(\vb_j\) (row picture), and \((\vb_j)_k = B_{kj}\). ∎ The famous index formula falls out; it was never a definition, it was a consequence.
Check yourself
Why is \(\mA\mB \neq \mB\mA\) in general — even when both products are defined and the same size?
Correct. Exactly. This is Lecture 1's quiz reappearing: \((g\circ f)\neq(f\circ g)\) for \(f(x)=ax+b\),
\(g(u)=u^2\). Matrices inherit that non-commutativity because they are functions.
A concrete geometric example: let \(\mR\) rotate the plane by \(90^\circ\) and \(\mS\) scale only the
\(x\)-axis by 2. Rotate-then-stretch stretches what used to be the \(y\)-axis; stretch-then-rotate stretches
what is still the \(x\)-axis. Different outcomes, visibly.
This matters practically: in a network, \(\mW_2\mW_1\vx\) and \(\mW_1\mW_2\vx\) are different models, and
the order of layers is a real architectural choice, not a formatting one.
Not quite. Shape mismatch is a separate issue — and the question explicitly assumes both products exist and
have the same size, as happens whenever \(\mA\) and \(\mB\) are both \(n\times n\). Even then they generally
differ. Try \(\mA=\begin{bmatrix}1&1\\0&1\end{bmatrix}\), \(\mB=\begin{bmatrix}1&0\\1&1\end{bmatrix}\):
\(\mA\mB=\begin{bmatrix}2&1\\1&1\end{bmatrix}\) but \(\mB\mA=\begin{bmatrix}1&1\\1&2\end{bmatrix}\).
Not quite. This is exact mathematics, not numerics. The two products differ symbolically, with integer
entries and no rounding anywhere. (Floating-point non-associativity is a real and separate phenomenon —
\((a+b)+c \neq a+(b+c)\) in float — but it is not what is happening here.)
Order matters: rotate-then-shear vs shear-then-rotateInteractive
original unit squareA then BB then A
Both purple and orange shapes are built from the SAME two matrices — a rotation and a shear — applied in opposite orders. They are congruent only by coincidence (at shear = 0 or angle = 0). Everywhere else they differ, which is what AB ≠ BA looks like.
4Rank, column space, null space
Definition — the four words you need
For \(\mA\in\RR^{m\times n}\):
Column space \(\mathcal{C}(\mA)=\{\mA\vx : \vx\in\RR^n\} = \operatorname{span}\) of the
columns. A subspace of \(\RR^m\). This is the image of the map.
Null space \(\mathcal{N}(\mA)=\{\vx : \mA\vx=\vzero\}\). A subspace of \(\RR^n\). These are
the inputs the map annihilates.
Rank \(\rank(\mA) = \dim\mathcal{C}(\mA)\) = the number of independent columns. It also
equals the number of independent rows, which is a small miracle worth pausing on.
Rank–nullity theorem: \(\;\rank(\mA) + \dim\mathcal{N}(\mA) = n\). Dimensions you keep plus
dimensions you destroy equals dimensions you started with.
Now connect this to Lecture 1. A map is injective exactly when nothing but \(\vzero\) is sent to
\(\vzero\) — i.e. when \(\mathcal{N}(\mA)=\{\vzero\}\). It is surjective onto \(\RR^m\) exactly when
\(\mathcal{C}(\mA)=\RR^m\). An \(n\times n\) matrix is invertible exactly when both hold, which by rank–nullity
happens exactly when \(\rank=n\), which happens exactly when \(\det\mA\neq0\). All those conditions are the same
condition.
Where this shows up in ML
Why rank deficiency ruins a regression. Suppose your design matrix \(\mX\)
has two perfectly correlated features — say height_cm and height_m. Then one column is
100 times the other, the columns are dependent, and \(\rank(\mX)<d\).
Consequences, all the same fact in different clothes:
\(\mathcal{N}(\mX)\) contains a nonzero \(\vv\) — here \(\vv\) with \(+1\) on one feature and \(-100\) on the
other. So \(\mX(\vw+\vv)=\mX\vw\): two different weight vectors give identical predictions.
The model is unidentifiable.
\(\mX^\top\mX\) is singular, so the normal equations of Lecture 10 have no unique solution and
np.linalg.inv either fails or returns garbage.
Even near-dependence is bad: the matrix becomes ill-conditioned, the fitted coefficients become
enormous and unstable, and tiny changes in the data swing them wildly. This is multicollinearity.
Ridge regression fixes it by solving \((\mX^\top\mX+\lambda\mI)^{-1}\), which is invertible for any
\(\lambda>0\). We prove that in Lecture 11 using eigenvalues, and it is the linear-algebra reason regularisation
works.
5Matrices in NumPy: shapes, and the mistakes to make on purpose
Everything about shapes, including the bugsPython · NumPy
\(\mW\vx\) is a linear map \(\RR^d\to\RR^h\). Row \(i\) is neuron \(i\)'s weights; \(\rank(\mW)\le\min(d,h)\) bounds how much information can pass through.
Adding \(\vb\) makes it affine, not linear — \(T(\vzero)=\vb\neq\vzero\). Affine maps are linear maps plus a translation, and the distinction matters when you read theorems.
\(\sigma\) is applied elementwise and is nonlinear. Without it, stacking layers gives \(\mW_2\mW_1\vx\), which is just another matrix — a deep linear network has exactly the expressive power of a single layer. The nonlinearity is not a detail; it is the entire reason depth helps.
In practice you process a batch: \(\mX\in\RR^{B\times d}\) with samples as rows, and the layer is
X @ W.T + b, shape \((B,h)\). Now you can see why frameworks store weights as \((h,d)\) and
transpose — it keeps the mathematical convention \(\mW\vx\) while letting the data stay row-major.
7Exercises
★
Let \(\mA=\begin{bmatrix}2&1\\0&3\end{bmatrix}\), \(\mB=\begin{bmatrix}1&-1\\2&0\end{bmatrix}\).
Compute \(\mA\mB\) and \(\mB\mA\) and confirm they differ.
Compute \(\mA\vx\) for \(\vx=(1,2)\), both by rows and by columns, and check they agree.
Compute \(\det\mA\), \(\det\mB\), \(\det(\mA\mB)\). What relationship do you observe?
3. \(\det\mA = 6-0 = 6\). \(\det\mB = 0-(-2) = 2\).
\(\det(\mA\mB) = 4(0)-(-2)(6) = 12 = 6\times2\) ✓.
The relationship is \(\det(\mA\mB)=\det\mA\cdot\det\mB\), and it has a clean geometric reading: determinant
is the factor by which area is scaled, and applying two maps in sequence multiplies the scalings. It also
immediately explains why a product is singular if either factor is: destroy a dimension once and no
later map can restore it.
★★
Prove that a linear map \(T:\RR^n\to\RR^m\) is injective if and only if
\(\mathcal{N}(\mA)=\{\vzero\}\), where \(\mA\) is its matrix.
Then use rank–nullity to show that a linear map \(\RR^n\to\RR^m\) with \(n>m\) can never be
injective, and interpret that for dimensionality reduction.
(⟸) Suppose \(\mathcal{N}(\mA)=\{\vzero\}\) and \(\mA\vu=\mA\vv\). Then by
linearity \(\mA(\vu-\vv)=\vzero\), so \(\vu-\vv\in\mathcal{N}(\mA)=\{\vzero\}\), so \(\vu=\vv\). That is
injectivity, in exactly the form from Lecture 1.
(⟹) Suppose \(T\) is injective and take \(\vx\in\mathcal{N}(\mA)\), so \(\mA\vx=\vzero\).
But also \(\mA\vzero=\vzero\) (every linear map sends zero to zero). Two inputs with the same output, so
injectivity forces \(\vx=\vzero\). ∎
Note how much simpler this is than for a general function: for a linear map you only have to check one
point, the origin. That is a genuine gift of linearity.
Rank–nullity consequence. \(\rank(\mA)+\dim\mathcal{N}(\mA)=n\). Since
\(\mathcal{C}(\mA)\subseteq\RR^m\) we have \(\rank(\mA)\le m\). Therefore
A positive-dimensional null space contains nonzero vectors, so \(T\) is not injective. ∎
Interpretation. Any linear dimensionality reduction from \(\RR^n\) to \(\RR^m\) with
\(m<n\) must lose information: there is a subspace of dimension at least \(n-m\) that gets crushed to
zero and can never be recovered. PCA does not evade this; it chooses which \(n-m\) dimensions to
destroy, picking the ones carrying least variance. Autoencoders do not evade it either — they escape only by
being nonlinear, so that the "subspace" being preserved can be a curved manifold rather than a flat
subspace.
★★
Show that the composition of two linear maps is linear, directly from the definition. Then explain
in one paragraph why this means a neural network without activation functions has exactly the expressive power
of a single linear layer, no matter how deep.
Let \(S,T\) be linear. For any \(\vu,\vv\) and scalars \(\alpha,\beta\):
The second equality used linearity of \(T\); the third used linearity of \(S\). ∎ (Same peeling pattern as
the injectivity-of-composition proof in Lecture 1.)
Consequence for depth. An \(L\)-layer network with no activation computes
\(\vy=\mW_L\mW_{L-1}\cdots\mW_1\vx\). By the above, the composition is linear, so it equals \(\mW\vx\) for the
single matrix \(\mW=\mW_L\cdots\mW_1\). Whatever function the deep stack computes, one layer of the right
shape computes it too. Worse, \(\rank(\mW)\le\min_\ell \rank(\mW_\ell)\), so depth can only reduce
the achievable rank — a hundred layers buys strictly nothing and risks a bottleneck.
Adding biases makes it affine rather than linear, but the same collapse happens: a composition of affine
maps is affine. The nonlinearity is the only thing standing between "deep network" and "one matrix".
Caveat worth knowing: deep linear networks are still studied, because although their
expressivity is that of one layer, their optimisation dynamics are different and
surprisingly informative. Expressive power and trainability are separate questions.
★★★
Coding. Implement matrix multiplication three ways and compare correctness and
speed on \(512\times512\) matrices:
Triple nested Python loop, using the index formula.
Column-by-column, using A @ b_j for each column of \(\mB\) — the derivation's own
formulation.
A @ B.
Then explain the speed gap. What is BLAS actually doing that your loop is not?
import numpy as np, time
rng = np.random.default_rng(0)
n = 512
A, B = rng.normal(size=(n,n)), rng.normal(size=(n,n))
def mm_loops(A, B): # only run this on a smaller n!
m, p = A.shape; p2, q = B.shape
C = np.zeros((m, q))
for i in range(m):
for j in range(q):
s = 0.0
for k in range(p): s += A[i,k]*B[k,j]
C[i,j] = s
return C
def mm_cols(A, B):
return np.column_stack([A @ B[:, j] for j in range(B.shape[1])])
for name, f, size in [("triple loop", mm_loops, 128),
("column-wise", mm_cols, n),
("A @ B", lambda A,B: A@B, n)]:
a, b = A[:size,:size], B[:size,:size]
t0 = time.perf_counter(); C = f(a, b); t1 = time.perf_counter()
print(f"{name:12s} n={size:4d} {(t1-t0)*1000:9.1f} ms "
f"max err {np.abs(C - a@b).max():.2e}")
Typical result. All three agree to about \(10^{-13}\) — the small discrepancy is
floating-point summation order, not a bug. Timings: the triple loop takes on the order of a second at
\(n=128\); the column-wise version takes a few milliseconds at \(n=512\); A @ B takes a few
milliseconds too but does \(64\times\) more work than the \(n=128\) loop. Extrapolated to the same size, the
gap between the pure-Python loop and BLAS is typically three to four orders of magnitude.
Why. Four things, roughly in order of importance:
No interpreter overhead. The loop executes \(2n^3 \approx 2.7\times10^8\) Python
bytecode operations, each involving object allocation and type dispatch. BLAS runs compiled machine code.
Cache blocking. The naive loop walks \(\mB\) down a column, striding \(n\) floats between
accesses — a cache miss almost every time. BLAS partitions the matrices into tiles that fit in L1/L2 cache
and reuses each loaded tile many times. On modern hardware, matrix multiplication is memory-bound before it
is compute-bound, and this is the dominant fix.
SIMD vectorisation. AVX-512 or NEON instructions perform 8–16 multiply-accumulates per
cycle per core.
Multithreading. BLAS splits the work across cores automatically.
The lesson for ML. This is why everything in deep learning is expressed as matrix
multiplication even when another formulation would be more natural. A GPU is, to a first approximation, a
machine for doing exactly this operation, and an algorithm that cannot be written as dense linear algebra is
an algorithm that cannot use the hardware. That constraint has shaped the field's architectures at least as
much as any mathematical consideration.
Next: we solve \(\mA\vx=\vb\) when it has no solution — which is the usual
situation with real data, and whose answer is the least-squares estimator. Lecture 8's projection formula is
about to become a matrix.