Orthonormal Basis
A basis (Chapter 2) gives you a coordinate system. An orthonormal basis (ONB) gives you the best possible coordinate system: its axes are mutually perpendicular and each has unit length. In such a basis, coordinates are trivial to compute, projections are cheap, and numerical routines are stable — which is why PCA, QR, and SVD all produce orthonormal bases.
A real-life example: good coordinate axes
Think of standard map coordinates: “east” and “north” are perpendicular and measured in the same unit (kilometers). That’s an orthonormal basis, and it’s why reading a position off the map is easy — you just measure along each axis independently. A skewed, differently-scaled grid would make the same task a headache. ONBs are the “east/north” of any vector space.
Definition
A basis of an -dimensional space is an orthonormal basis if
The first condition says the vectors are mutually orthogonal; the second says each has unit length. Drop the unit-length requirement and you have merely an orthogonal basis. The canonical basis is the standard ONB under the dot product.
Why ONBs are so convenient
In an orthonormal basis, the coordinates of any vector are just inner products:
No solving a linear system, no matrix inverse — each coordinate is a single dot product. This is the shortcut that makes projections onto an ONB nearly free (a fact we’ll cash in on the projections page).
Building one: the Gram-Schmidt process
Given any basis of non-orthogonal vectors, Gram-Schmidt turns it into an orthonormal one, one vector at a time:
- Normalize the first vector: .
- Subtract from its component along , then normalize: .
- Repeat, always subtracting off the parts already covered.
Watch it happen on two vectors: the blue vector rotates, and Gram-Schmidt produces the green — always perpendicular to the amber — by removing the shadow (grey) that casts along :
No matter how points, the green output is perpendicular to the amber and unit length — a fresh orthonormal basis.
NumPy: Gram-Schmidt and QR
import numpy as np
def gram_schmidt(vectors):
basis = []
for v in vectors:
w = v - sum((v @ u) * u for u in basis) # remove components along earlier vectors
if np.linalg.norm(w) > 1e-10:
basis.append(w / np.linalg.norm(w)) # normalize
return np.array(basis)
vs = [np.array([2.0, 0.0]), np.array([1.0, 1.0])]
Q = gram_schmidt(vs)
print("orthonormal basis:\n", np.round(Q, 4))
print("QᵀQ = I:", np.allclose(Q @ Q.T, np.eye(2)))
# NumPy's built-in: QR decomposition gives an orthonormal Q directly
A = np.array([[2.0, 1.0], [0.0, 1.0]])
Q2, R = np.linalg.qr(A)
print("QR's Q orthonormal:", np.allclose(Q2.T @ Q2, np.eye(2)))import numpy as np
def gram_schmidt(vectors):
basis = []
for v in vectors:
w = v - sum((v @ u) * u for u in basis) # remove components along earlier vectors
if np.linalg.norm(w) > 1e-10:
basis.append(w / np.linalg.norm(w)) # normalize
return np.array(basis)
vs = [np.array([2.0, 0.0]), np.array([1.0, 1.0])]
Q = gram_schmidt(vs)
print("orthonormal basis:\n", np.round(Q, 4))
print("QᵀQ = I:", np.allclose(Q @ Q.T, np.eye(2)))
# NumPy's built-in: QR decomposition gives an orthonormal Q directly
A = np.array([[2.0, 1.0], [0.0, 1.0]])
Q2, R = np.linalg.qr(A)
print("QR's Q orthonormal:", np.allclose(Q2.T @ Q2, np.eye(2)))orthonormal basis:
[[1. 0.]
[0. 1.]]
QᵀQ = I: True
QR's Q orthonormal: Trueorthonormal basis:
[[1. 0.]
[0. 1.]]
QᵀQ = I: True
QR's Q orthonormal: TrueWhy this matters for ML
- PCA returns an orthonormal basis of “principal directions”; projecting onto the top few compresses data with minimal loss.
- QR / SVD rely on orthonormal bases for numerically stable solutions to least-squares and eigenvalue problems.
- Orthonormal weight init/constraints keep signal magnitudes stable across deep-network layers.
🧪 Try It Yourself
Exercise 1 – Check an orthonormal basis
Exercise 2 – Coordinates are just dot products
Exercise 3 – Orthogonalize with Gram-Schmidt
Recap
- An orthonormal basis has mutually orthogonal, unit-length vectors.
- In an ONB, coordinates are just inner products — no linear solve, no inverse.
- Gram-Schmidt builds an ONB from any basis by removing overlaps and normalizing; NumPy’s
qrqrdoes it for you. - ONBs power PCA, QR/SVD, and stable deep-net weights.
Next: everything perpendicular to a subspace — the orthogonal complement.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
