Skip to main content
NumPy advanced Lesson 9 of 12

NumPy Linear Algebra

Solve linear systems, compute eigenvalues, perform matrix decompositions, and apply them to real ML problems using np.linalg.

Real-World Scenario

A data scientist building a recommendation system uses matrix factorization to decompose a 50,000 user × 10,000 movie rating matrix into latent factors. A quantitative researcher solves a portfolio optimization problem — finding optimal asset weights that minimize variance for a target return. Both reduce to linear algebra. np.linalg provides the tools without requiring a PhD in numerical methods.

Solving Linear Systems

The classic problem: given Ax = b, find x. Appears everywhere from curve fitting to portfolio optimization.

import numpy as np

# A 3×3 system of equations:
# 2x + y - z = 8
# -3x - y + 2z = -11
# -2x + y + 2z = -3
A = np.array([
    [ 2,  1, -1],
    [-3, -1,  2],
    [-2,  1,  2]
], dtype=float)

b = np.array([8., -11., -3.])

# Solve Ax = b — faster and more stable than computing inv(A) @ b
x = np.linalg.solve(A, b)
print(x)          # [2. 3. -1.]

# Verify: Ax should equal b
residual = np.linalg.norm(A @ x - b)
print(f"Residual: {residual:.2e}")  # ~0.0 — exact solution

Least Squares Regression

When the system is overdetermined (more equations than unknowns — the common case in ML), use least squares to find the best-fit solution.

import numpy as np

rng = np.random.default_rng(42)
n = 200

# Generate data: y = 3x + 2 + noise
x = rng.uniform(0, 10, n)
y = 3 * x + 2 + rng.normal(0, 1, n)

# Design matrix: column of ones for intercept, column of x for slope
X = np.column_stack([np.ones(n), x])   # (200, 2)

# Least squares: minimizes ||Xw - y||^2
# Returns coefficients, residuals, rank, singular values
coeffs, residuals, rank, sv = np.linalg.lstsq(X, y, rcond=None)
intercept, slope = coeffs
print(f"Intercept: {intercept:.4f}")  # ~2.0
print(f"Slope:     {slope:.4f}")      # ~3.0

# The normal equations approach (equivalent but less stable)
# w = (X^T X)^{-1} X^T y
w_normal = np.linalg.solve(X.T @ X, X.T @ y)
print(f"Normal eq: {w_normal}")  # same result

Eigenvalues and Eigenvectors

Eigendecomposition is at the heart of PCA, spectral clustering, and Markov chains.

import numpy as np

# Covariance matrix of a 3-feature dataset
rng = np.random.default_rng(42)
data = rng.standard_normal((100, 3))
data[:, 1] = data[:, 0] * 0.8 + rng.normal(0, 0.3, 100)  # correlated features

cov = np.cov(data.T)   # (3, 3) covariance matrix

# Eigendecomposition
eigenvalues, eigenvectors = np.linalg.eigh(cov)  # eigh for symmetric matrices

# Sort by descending eigenvalue (largest = most variance)
idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]

print("Eigenvalues:", eigenvalues.round(3))
print("Variance explained:", (eigenvalues / eigenvalues.sum()).round(3))
# First component likely explains >60% of variance due to correlation

PCA from Scratch using SVD

import numpy as np

rng = np.random.default_rng(42)

# Dataset: 500 samples, 50 features
X = rng.standard_normal((500, 50))
# Introduce correlation structure
X[:, :10] = X[:, :10] @ rng.standard_normal((10, 10))

# Step 1: center the data
X_centered = X - X.mean(axis=0)

# Step 2: SVD decomposition — X = U @ S_diag @ Vt
# U: (n, n), S: (k,) singular values, Vt: (k, p) principal components
U, S, Vt = np.linalg.svd(X_centered, full_matrices=False)
# U: (500, 50), S: (50,), Vt: (50, 50)

# Step 3: project onto first 2 principal components
n_components = 2
X_pca = X_centered @ Vt[:n_components].T   # (500, 2)

# Variance explained by each component
variance_ratio = (S ** 2) / (S ** 2).sum()
print("Variance explained by PC1:", f"{variance_ratio[0]:.1%}")
print("Variance explained by PC2:", f"{variance_ratio[1]:.1%}")
print("Projected shape:", X_pca.shape)  # (500, 2)

Matrix Norms and Condition Number

import numpy as np

A = np.array([[1., 2.], [3., 4.]])

# Frobenius norm — square root of sum of squared elements
print(np.linalg.norm(A, 'fro'))  # 5.477

# Spectral norm (largest singular value)
print(np.linalg.norm(A, 2))      # 5.465

# Condition number — ratio of largest to smallest singular value
# High condition number → ill-conditioned → numerical instability in solve()
print(np.linalg.cond(A))         # 14.93 — acceptable

# Rank — number of linearly independent rows/columns
print(np.linalg.matrix_rank(A))  # 2 — full rank

Determinant and Inverse

import numpy as np

A = np.array([[2., 1.], [5., 3.]])

# Determinant — zero means singular (non-invertible)
det = np.linalg.det(A)
print(f"det(A) = {det:.2f}")  # 1.0

# Matrix inverse — use solve() instead for systems of equations
A_inv = np.linalg.inv(A)
print(A_inv)
# [[ 3. -1.]
#  [-5.  2.]]

# Verify: A @ A_inv should equal identity
print(np.allclose(A @ A_inv, np.eye(2)))  # True

QR Decomposition

Useful for numerically stable least squares and Gram-Schmidt orthogonalization.

import numpy as np

# Design matrix for regression
rng = np.random.default_rng(0)
X = rng.standard_normal((100, 5))
y = rng.standard_normal(100)

# QR decomposition: X = Q @ R
# Q: orthonormal columns, R: upper triangular
Q, R = np.linalg.qr(X)

# Solve least squares via QR: w = R^{-1} Q^T y
# More numerically stable than (X^T X)^{-1} X^T y for large systems
w = np.linalg.solve(R, Q.T @ y)
print("Coefficients:", w.round(4))

# Verify against lstsq
w_lstsq, *_ = np.linalg.lstsq(X, y, rcond=None)
print("Match:", np.allclose(w, w_lstsq))  # True

Frequently Asked Questions

When should I use np.linalg.solve vs np.linalg.inv?
Always prefer np.linalg.solve(A, b) over np.linalg.inv(A) @ b. solve() uses a more numerically stable LU decomposition and is faster because it doesn't compute the full inverse. Computing the explicit inverse is rarely necessary and prone to numerical error for large or ill-conditioned matrices.
What is SVD used for in machine learning?
SVD (Singular Value Decomposition) is the mathematical foundation of PCA (dimensionality reduction), collaborative filtering (recommendation systems), pseudo-inverse computation, and latent semantic analysis. It decomposes any matrix into its most informative components, ranked by importance.