ML Foundations Lecture 9 of 14
Part III — Linear Algebra · Lecture 9

Matrices as Linear Maps

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

Assumed

  • Lecture 8: vectors, span, independence, dot products
  • Lecture 1: composition of functions

1Linear maps

Definition — linear map

A function \(T:\RR^n\to\RR^m\) is linear if for all \(\vu,\vv\in\RR^n\) and all \(\alpha,\beta\in\RR\),

\[ T(\alpha\vu + \beta\vv) = \alpha\,T(\vu) + \beta\,T(\vv). \]

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.

Every linear map ℝⁿ → ℝᵐ is a matrix0 / 5
\[\ve_1=\begin{bmatrix}1\\0\\\vdots\\0\end{bmatrix},\quad \ve_2=\begin{bmatrix}0\\1\\\vdots\\0\end{bmatrix},\quad\dots,\quad \ve_n\]
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.
\[\vx = \begin{bmatrix}x_1\\ \vdots\\ x_n\end{bmatrix} = x_1\ve_1 + x_2\ve_2+\cdots+x_n\ve_n\]
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.
\[T(\vx) = T\!\left(\sum_{j=1}^{n}x_j\ve_j\right) = \sum_{j=1}^{n}x_j\,T(\ve_j)\]
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.
\[\mA = \Big[\; T(\ve_1) \;\Big|\; T(\ve_2) \;\Big|\;\cdots\;\Big|\; T(\ve_n)\;\Big] \in \RR^{m\times n}\]
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.
\[T(\vx) = \sum_{j=1}^{n} x_j\,\big(\text{column } j \text{ of } \mA\big) \;=:\; \mA\vx\]
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.

\[\underbrace{\mA\vx = x_1\va_1 + x_2\va_2+\cdots+x_n\va_n}_{\textbf{column picture}} \qquad\qquad \underbrace{(\mA\vx)_i = \sum_{j} A_{ij}x_j = \text{row}_i(\mA)\cdot\vx}_{\textbf{row picture}}\]

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
1.40
0.60
0.30
1.10
1.00
0.50
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?

Deriving (AB)ᵢⱼ = Σₖ Aᵢₖ Bₖⱼ0 / 5
\[\mB\in\RR^{p\times n},\qquad \mA\in\RR^{m\times p}, \qquad \mA\circ\mB : \RR^{n}\to\RR^{m}\]
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.
\[\text{column } j \text{ of } \mA\mB \;=\; (\mA\mB)\ve_j \;=\; \mA\big(\mB\ve_j\big)\]
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.
\[\mB\ve_j = \text{column } j \text{ of } \mB \;=\; \vb_j\]
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.
\[(\mA\vb_j)_i = \sum_{k=1}^{p} A_{ik}\,(\vb_j)_k = \sum_{k=1}^{p} A_{ik}B_{kj}\]
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
40
1.00
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}\):

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:

5Matrices in NumPy: shapes, and the mistakes to make on purpose

Everything about shapes, including the bugsPython · NumPy

6A neural network layer, in these terms

A fully connected layer is

\[\vh = \sigma\big(\mW\vx + \vb\big), \qquad \mW\in\RR^{h\times d},\ \vx\in\RR^{d},\ \vb\in\RR^{h}.\]

7Exercises

★

Let \(\mA=\begin{bmatrix}2&1\\0&3\end{bmatrix}\), \(\mB=\begin{bmatrix}1&-1\\2&0\end{bmatrix}\).

  1. Compute \(\mA\mB\) and \(\mB\mA\) and confirm they differ.
  2. Compute \(\mA\vx\) for \(\vx=(1,2)\), both by rows and by columns, and check they agree.
  3. Compute \(\det\mA\), \(\det\mB\), \(\det(\mA\mB)\). What relationship do you observe?

1. \(\mA\mB = \begin{bmatrix}2(1)+1(2) & 2(-1)+1(0)\\ 0(1)+3(2) & 0(-1)+3(0)\end{bmatrix} = \begin{bmatrix}4&-2\\6&0\end{bmatrix}\).
\(\mB\mA = \begin{bmatrix}1(2)+(-1)(0) & 1(1)+(-1)(3)\\ 2(2)+0 & 2(1)+0\end{bmatrix} = \begin{bmatrix}2&-2\\4&2\end{bmatrix}\). Different ✓.

2. Rows: \((2\cdot1+1\cdot2,\ 0\cdot1+3\cdot2) = (4,6)\).
Columns: \(1\begin{bmatrix}2\\0\end{bmatrix}+2\begin{bmatrix}1\\3\end{bmatrix} = \begin{bmatrix}2\\0\end{bmatrix}+\begin{bmatrix}2\\6\end{bmatrix}=\begin{bmatrix}4\\6\end{bmatrix}\) ✓.

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

\[\dim\mathcal{N}(\mA) = n - \rank(\mA) \ge n-m > 0 \quad\text{when } n>m.\]

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\):

\[(S\circ T)(\alpha\vu+\beta\vv) = S\big(T(\alpha\vu+\beta\vv)\big) = S\big(\alpha T(\vu)+\beta T(\vv)\big) = \alpha S(T(\vu)) + \beta S(T(\vv))\] \[= \alpha (S\circ T)(\vu) + \beta (S\circ T)(\vv).\]

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:

  1. Triple nested Python loop, using the index formula.
  2. Column-by-column, using A @ b_j for each column of \(\mB\) — the derivation's own formulation.
  3. 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:

  1. 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.
  2. 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.
  3. SIMD vectorisation. AVX-512 or NEON instructions perform 8–16 multiply-accumulates per cycle per core.
  4. 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.