Geometry and Linear Algebraic Operations

Dive into Deep Learning · §23.1

The geometry under the algebra
angles, projections, hyperplanes, and how matrices move space.

Why a geometric view

Motivation

A vector is a list of numbers, but reading it as a picture is where the intuition lives.

  • A vector is a point, or equally an arrow (a direction).
  • The dot product measures alignment, giving angles and similarity.
  • A hyperplane is the decision boundary of every linear classifier.
  • A matrix is a map that skews, rotates, and scales space.

These pictures are the foundation for the eigendecomposition and the SVD in the sections that follow.

01

Vectors and their geometry

points, directions, dot products, projection

Two readings of a vector

Vectors

The same array names a point (its coordinates) or a direction (an arrow that may start anywhere). Deep learning mostly uses the direction view.

Reading a vector as a direction makes addition visual: follow one arrow, then the next, tip to tail.

Dot products and the angle

Vectors

Why is the dot product tied to the angle? The two vectors span a plane, so measure \|\mathbf{v}-\mathbf{w}\|^2 two ways and equate.

Algebra gives \|\mathbf{v}\|^2 - 2\,\mathbf{v}\cdot\mathbf{w} + \|\mathbf{w}\|^2; the law of cosines gives \|\mathbf{v}\|^2 + \|\mathbf{w}\|^2 - 2\,\|\mathbf{v}\|\,\|\mathbf{w}\|\cos\theta.

Cancel the common terms: \;\mathbf{v}\cdot\mathbf{w} = \|\mathbf{v}\|\,\|\mathbf{w}\|\cos\theta, so \theta=\arccos\!\big(\mathbf{v}\cdot\mathbf{w}/\|\mathbf{v}\|\,\|\mathbf{w}\|\big).

import numpy as onp
def angle(v, w):
    return onp.arccos(v.dot(w) / (onp.linalg.norm(v) * onp.linalg.norm(w)))

angle(onp.array([0, 1, 2]), onp.array([2, 3, 4]))
0.4189900840328574

Why the angle is always defined

Vectors

\arccos only accepts inputs in [-1, 1], so we need a guarantee.

Cauchy–Schwarz. \;|\mathbf{v}\cdot\mathbf{w}| \le \|\mathbf{v}\|\,\|\mathbf{w}\|, with equality iff \mathbf{v} and \mathbf{w} are collinear.

The proof needs only one fact: a squared length is never negative, so q(t) = \|\mathbf{v} - t\mathbf{w}\|^2 \ge 0 is a parabola that cannot dip below zero, forcing its discriminant \le 0. Dividing through shows the cosine never leaves [-1, 1], so \theta is always a genuine angle.

Projection and orthogonality

Vectors

How much of \mathbf{v} points along \mathbf{w}? Drop \mathbf{v} onto the line through \mathbf{w}:

\operatorname{proj}_{\mathbf{w}}\mathbf{v} = \frac{\mathbf{v}\cdot\mathbf{w}}{\mathbf{w}\cdot\mathbf{w}}\,\mathbf{w}.

The residual \mathbf{r} leaves at a right angle, so Cauchy–Schwarz is just “a leg is no longer than the hypotenuse.” Two vectors are orthogonal when \mathbf{v}\cdot\mathbf{w} = 0.

This is least squares in one dimension (the best fit leaves an orthogonal residual); the SVD scales the same idea to any matrix.

Projection onto a subspace

Vectors

The same formula, one matrix heavier: stack an orthonormal basis of a subspace S as the columns of \mathbf{Q} (so \mathbf{Q}^\top\mathbf{Q} = \mathbf{I}), and \mathbf{P} = \mathbf{Q}\mathbf{Q}^\top drops any vector onto S.

\mathbf{P}\mathbf{x} is the unique closest point of S; the residual \mathbf{x} - \mathbf{P}\mathbf{x} is orthogonal to all of S; and \mathbf{P}^2 = \mathbf{P}: projecting twice changes nothing.

qr manufactures the orthonormal basis. Both claims check to roundoff on a random 3-dimensional subspace of \mathbb{R}^5, and residual-orthogonality is least squares: the optimal fit’s error is orthogonal to every feature.

import numpy as onp
# A random 5x3 matrix whose columns span a 3-dim subspace of R^5
onp.random.seed(0)
A = onp.random.randn(5, 3)
Q, _ = onp.linalg.qr(A)  # orthonormal basis of the column space
P = Q.dot(Q.T)          # projection matrix onto that subspace
x = onp.random.randn(5)
r = x - P.dot(x)        # residual
(onp.linalg.norm(P.dot(P) - P),  # P^2 = P (idempotent)
 onp.linalg.norm(Q.T.dot(r)))    # residual is orthogonal to the subspace
(4.153077993666847e-16, 4.211628519682908e-16)

Span, basis, dimension

Vectors

  • Span: everything reachable by scaling and adding a set of vectors.
  • Basis: an independent spanning set, so every vector gets unique coordinates.
  • Dimension: the size of any basis.

Two subspaces ride on every matrix: the column space is what it can produce, the null space is what it sends to zero.

02

Similarity in high dimensions

cosine similarity and near-orthogonality

Cosine similarity

High dimensions

Comparing direction, not magnitude, is often what we want: an image and a dimmed copy point the same way, so they should score as identical.

\cos\theta = \frac{\mathbf{v}\cdot\mathbf{w}}{\|\mathbf{v}\|\,\|\mathbf{w}\|} \;\in\; [-1, 1].

This is the signal behind embedding retrieval, the scaled dot products inside attention, and the alignment objective of contrastive learning.

Random vectors are nearly orthogonal

High dimensions

Drop two unrelated unit vectors into \mathbb{R}^d: what cosine do we expect?

For a random unit vector, \;\mathbb{E}[\cos\theta] = 0 and \operatorname{Var}(\cos\theta) = \tfrac{1}{d}.

Why: rotate so \mathbf{u}=\mathbf{e}_1, so \cos\theta=v_1. Symmetry \mathbf{v}\mapsto-\mathbf{v} kills the mean; \sum_i v_i^2=1 with all coordinates alike gives \mathbb{E}[v_1^2]=\tfrac{1}{d}.

So the cosine concentrates at 0 with width 1/\sqrt{d}: a cosine well above 0 is unlikely to be an accident, which is why cosine similarity works.

Why attention divides by √d

High dimensions

Attention compares one query against thousands of keys by dot product, and near-orthogonality is its operating environment: the many unrelated keys score near zero, so the few that share structure with the query stand out against a quiet background.

The mysterious \sqrt{d} falls out of our variance result. With entries of typical size 1, \|\mathbf{q}\| \approx \|\mathbf{k}\| \approx \sqrt{d} while \operatorname{sd}(\cos\theta) = 1/\sqrt{d}, so

\operatorname{sd}(\mathbf{q}\cdot\mathbf{k}) \approx \sqrt{d}\cdot\sqrt{d}\cdot\tfrac{1}{\sqrt{d}} = \sqrt{d}.

\mathbf{Q}\mathbf{K}^\top/\sqrt{d} is standardization: dividing the raw scores by their standard deviation keeps them O(1), so the softmax stays in its responsive range instead of saturating.

03

Hyperplanes and decision boundaries

the primitive every classifier shares

A hyperplane as a decision boundary

Hyperplanes

The set \{\mathbf{x} : \mathbf{w}\cdot\mathbf{x} = b\} is a plane with normal \mathbf{w}, sitting at distance b/\|\mathbf{w}\| from the origin. Sliding b translates it without rotating.

For any point, the signed distance (\mathbf{w}\cdot\mathbf{x} - b)/\|\mathbf{w}\| is positive on one side and negative on the other. That number is exactly a linear classifier’s margin.

A classifier with nothing trained

Hyperplanes

Take Fashion-MNIST t-shirts and trousers. Average each class: the means are blurry but recognizable. The line between them, \mathbf{w} = \overline{\mathbf{x}}_1 - \overline{\mathbf{x}}_0, is our normal.

One hyperplane, ~92% correct

Hyperplanes

Classify each 784-dimensional image by the side it falls on, with the threshold at the midpoint of the two means’ projections:

import numpy as onp
def as_numpy(x):
    if hasattr(x, 'asnumpy'):                   # MXNet
        return x.asnumpy()
    if hasattr(x, 'detach'):                    # PyTorch
        return x.detach().cpu().numpy()
    if hasattr(x, 'numpy'):                     # TensorFlow
        return x.numpy()
    return onp.asarray(x)                       # JAX / NumPy

X_test_np, y_test_np = as_numpy(X_test), as_numpy(y_test)
ave_0_np, ave_1_np = as_numpy(ave_0), as_numpy(ave_1)
# Normal = difference of class means; threshold = midpoint of their projections
w = (ave_1_np - ave_0_np).ravel()
b = onp.dot(w, (ave_0_np + ave_1_np).ravel()) / 2
predictions = X_test_np.reshape(len(X_test_np), -1).dot(w) > b

# Accuracy
onp.mean(predictions.astype(y_test_np.dtype) == y_test_np, dtype=onp.float64)
0.922

Over 2{,}000 test images, this hand-built rule is right about 92% of the time, and nothing was learned.

The whole picture in one projection

Hyperplanes

Reduce every image to one number, \mathbf{w}\cdot\mathbf{x}, its position along the normal. The two classes form two humps; the dashed threshold cuts between them.

A learned classifier just tilts this boundary to trim the overlap; a deep net learns features that pull the humps apart.

04

Matrices as linear maps

skew, rotate, scale, and the determinant

A matrix moves the whole grid

Linear maps

A matrix is fixed by where it sends the basis vectors, namely its columns. Every other vector follows as a weighted sum, so the entire grid is carried along: lines stay lines, the origin stays put, cells stay evenly spaced.

Matrices cannot bend space, only skew, rotate, and scale it. Multiplying two matrices composes their maps.

What a matrix destroys

Linear maps

\mathbf{B} = \bigl[\begin{smallmatrix}2&-1\\4&-2\end{smallmatrix}\bigr] has dependent columns, \mathbf{b}_1 + 2\,\mathbf{b}_2 = \mathbf{0}. The whole plane lands on one line (the column space), and the direction (1,2)^\top is crushed to the origin (the null space). Once inputs collide, no inverse can tell them apart.

Rank–nullity. \operatorname{rank}\mathbf{A} + \dim\ker\mathbf{A} = n: of the n directions coming in, \dim\ker\mathbf{A} are destroyed and \operatorname{rank}\mathbf{A} survive. For \mathbf{B}: 1 + 1 = 2.

Orthogonal matrices: the rigid motions

Linear maps

A square matrix is orthogonal when \mathbf{Q}^\top\mathbf{Q} = \mathbf{I}. Such maps preserve every dot product, hence all lengths and angles:

(\mathbf{Q}\mathbf{x})\cdot(\mathbf{Q}\mathbf{y}) = \mathbf{x}^\top\mathbf{Q}^\top\mathbf{Q}\,\mathbf{y} = \mathbf{x}\cdot\mathbf{y}.

They are the pure rotations and reflections: the distortion-free building blocks of both the spectral theorem and the SVD.

The determinant is signed area

Linear maps

The unit square maps to a parallelogram; its signed area is \det\mathbf{A}. The sign records orientation, and a zero determinant means space was crushed to a lower dimension.

For our grid matrix, \det = 1\cdot3 - 2\cdot(-1) = 5:

import numpy as onp
onp.linalg.det(onp.array([[1, 2], [-1, 3]]))
4.999999999999999

Composed maps multiply their area scalings, \det(\mathbf{A}\mathbf{B})=\det\mathbf{A}\,\det\mathbf{B}. Two payoffs: \det\mathbf{Q}=\pm1 for orthogonal \mathbf{Q}, and (next section) \det\mathbf{A}=\prod_i\lambda_i, the per-axis stretches multiplied.

The determinant, defined

Linear maps

Three properties pin the determinant down in every dimension, as a function of the columns: multilinear (linear in each column), alternating (zero when two columns are equal), normalized (\det\mathbf{I} = 1). Exactly one such function exists.

Consequences: swapping two columns flips the sign, and a triangular determinant is the product of the diagonal:

import numpy as onp
A = onp.array([[2, 1, 0], [1, 3, 1], [0, 1, 2]])
A_swap = onp.array([[1, 2, 0], [3, 1, 1], [1, 0, 2]])  # first two columns swapped
T = onp.array([[2, 5, 1], [0, 3, 4], [0, 0, 1]])       # upper triangular
(onp.linalg.det(A),       # 8, matching the cofactor expansion
 onp.linalg.det(A_swap),  # -8: a column swap flips the sign
 onp.linalg.det(T))       # 6 = 2 * 3 * 1, the diagonal product
(8.000000000000002, -8.000000000000002, 6.0)

\det(\mathbf{A}\mathbf{B}) = \det\mathbf{A}\,\det\mathbf{B} = \det(\mathbf{B}\mathbf{A}): determinants commute even when matrices do not.

One equivalence ties it together

Linear maps

For a square matrix: \det\mathbf{A} = 0 \iff the columns are linearly dependent \iff \mathbf{A} is not invertible.

Our collapsing \mathbf{B} scores 2\cdot(-2) - (-1)\cdot 4 = 0, exactly as it must.

When invertible, \mathbf{A}^{-1} undoes the map; the 2\times2 formula checks out, multiplying back to the identity:

import numpy as onp
M = onp.array([[1, 2], [1, 4]])
M_inv = onp.array([[2, -1], [-0.5, 0.5]])
M_inv.dot(M)
array([[1., 0.],
       [0., 1.]])

Prefer linalg.solve(A, b) over inv(A) @ b: stabler, and it never forms the inverse.

05

Einstein summation

one rule for every product

Sum over the repeated index

Einstein notation

Dot products, matrix–vector, matrix–matrix, and traces are one pattern, (\mathbf{A}\mathbf{B})_{ik} = a_{ij}\,b_{jk}: multiply entries, sum the repeated index. einsum makes that index string the whole definition (the matrix–vector call recovers our worked \mathbf{A}\mathbf{v} = [0, -5]^\top):

import numpy as onp
A = onp.array([[1.0, 2.0], [-1.0, 3.0]])
v = onp.array([2.0, -1.0])
(onp.einsum('i,i->', v, v),      # dot product: v.v
 onp.einsum('ij,j->i', A, v),    # matrix-vector product: Av
 onp.einsum('ij,jk->ik', A, A),  # matrix product: AA
 onp.einsum('ii->', A))          # trace: tr(A)

Recap

Wrap-up

  • A vector is a point or a direction; the dot product gives angles.
  • Cauchy–Schwarz makes the angle well-defined; projection (\mathbf{P}=\mathbf{Q}\mathbf{Q}^\top for a subspace) leaves an orthogonal residual: the geometry of least squares.
  • In high dimensions, random directions are nearly orthogonal: why cosine similarity works and why attention divides by \sqrt{d}.
  • A hyperplane is the decision boundary of every linear classifier.
  • A matrix skews, rotates, and scales; rank–nullity counts what survives; the determinant is its volume scale, zero exactly when it collapses space.
  • Einstein notation writes every product as one index pattern.

These pictures carry straight into the eigendecomposition and the SVD, and all the way up to attention and embeddings.