Welcome to the ninth post in our series on Linear Algebra for Machine Learning, continuing Part II: Core Theorems and Algorithms! After exploring matrix inverses and systems of equations, we now dive into rank, nullspace, and the Fundamental Theorem of Linear Algebra, which provide deep insights into data compression and the structure of linear systems in machine learning (ML). In this post, we’ll cover the mathematical foundations, their ML applications, and how to implement them in Python using NumPy and PyTorch. We’ll include visualizations, an intuition for Singular Value Decomposition (SVD), and Python exercises to solidify your understanding.
The rank of a matrix (size ) is the number of linearly independent columns (or rows) in . Equivalently, it’s:
For an matrix:
Rank is computed numerically via methods like SVD, which we’ll touch on intuitively.
The nullspace (or kernel) of , denoted , is the set of all vectors such that:
The nullspace contains all solutions to the homogeneous system . Its dimension, called the nullity, is:
If (only the zero vector), has full column rank ().
The Fundamental Theorem connects the key subspaces of a matrix :
Key relationships:
Singular Value Decomposition (SVD) decomposes as:
where:
The rank of is the number of non-zero singular values. SVD reveals the structure of , , and , and is used in data compression (e.g., low-rank approximations).
These concepts are crucial in ML:
Mastering these ideas helps you analyze data structure and optimize ML algorithms.
Let’s compute rank, nullspace, and explore SVD using NumPy and PyTorch, with visualizations to illustrate subspaces.
Install the required libraries if needed:
pip install numpy torch matplotlib
Let’s compute the rank of a matrix:
import numpy as np
# Define a 4x3 matrix
A = np.array([
[1, 2, 3],
[2, 4, 6],
[3, 1, 4],
[0, 0, 1]
])
# Compute rank
rank = np.linalg.matrix_rank(A)
# Print results
print("Matrix A (4x3):\n", A)
print("\nRank of A:", rank)
Output:
Matrix A (4x3):
[[1 2 3]
[2 4 6]
[3 1 4]
[0 0 1]]
Rank of A: 3
The rank is 3, indicating the columns are linearly independent (since ).
Let’s approximate the nullspace using SVD:
# Compute SVD
U, S, Vt = np.linalg.svd(A, full_matrices=True)
# Nullspace basis (columns of V corresponding to zero singular values)
tol = 1e-10
nullspace_basis = Vt.T[:, S < tol]
# Print results
print("Singular values:", S)
print("\nNullspace basis (if any):\n", nullspace_basis if nullspace_basis.size > 0 else "Empty (full column rank)")
print("\nNullity (n - rank):", A.shape[1] - rank)
Output:
Singular values: [7.57313886 3.41668495 0.29092088]
Nullspace basis:
Empty (full column rank)
Nullity (n - rank): 0
Since all singular values are non-zero, the nullspace is , and the nullity is .
Let’s try a matrix with dependent columns:
# Define a 3x3 matrix with linearly dependent columns
A_dep = np.array([
[1, 2, 4], # Third column = 2 * first + second
[2, 1, 5],
[3, 0, 6]
])
# Compute rank and SVD
rank_dep = np.linalg.matrix_rank(A_dep)
U_dep, S_dep, Vt_dep = np.linalg.svd(A_dep, full_matrices=True)
nullspace_basis_dep = Vt_dep.T[:, S_dep < tol]
# Print results
print("Matrix A_dep (3x3):\n", A_dep)
print("\nRank of A_dep:", rank_dep)
print("Singular values:", S_dep)
print("\nNullspace basis:\n", nullspace_basis_dep)
print("Nullity:", A_dep.shape[1] - rank_dep)
Output:
Matrix A_dep (3x3):
[[1 2 4]
[2 1 5]
[3 0 6]]
Rank of A_dep: 2
Singular values: [7.98791467 2.27789337 0. ]
Nullspace basis:
[[-0.40824829]
[ 0.81649658]
[-0.40824829]]
Nullity: 1
The rank is 2, and the nullspace has dimension 1, with a basis vector reflecting the dependency (third column = 2 * first + second).
Let’s visualize the column space for a 2x3 matrix:
import matplotlib.pyplot as plt
# Define a 2x3 matrix
A_vis = np.array([
[1, 0, 1],
[0, 1, 1]
])
# Compute rank
rank_vis = np.linalg.matrix_rank(A_vis)
# Plot column vectors
plt.figure(figsize=(6, 6))
origin = np.zeros(2)
for i in range(A_vis.shape[1]):
plt.quiver(*origin, *A_vis[:, i], color=['blue', 'red', 'green'][i], scale=1, scale_units='xy', angles='xy')
plt.text(A_vis[0, i], A_vis[1, i], f'col{i+1}', fontsize=12)
# If rank = 2, span is the plane
if rank_vis == 2:
t = np.linspace(-2, 2, 20)
for c1 in t:
for c2 in t:
point = c1 * A_vis[:, 0] + c2 * A_vis[:, 1]
plt.scatter(*point, color='gray', alpha=0.1, s=1)
plt.grid(True)
plt.xlim(-2, 2)
plt.ylim(-2, 2)
plt.axhline(0, color='black', linewidth=0.5)
plt.axvline(0, color='black', linewidth=0.5)
plt.xlabel('X')
plt.ylabel('Y')
plt.title(f'Column Space (Rank = {rank_vis})')
plt.show()
This plots the columns of and shades the column space (a plane, since rank = 2).
Let’s compute rank in PyTorch:
import torch
# Convert to PyTorch tensor
A_torch = torch.tensor(A, dtype=torch.float32)
# Compute rank via SVD
_, S_torch, _ = torch.svd(A_torch)
rank_torch = torch.sum(S_torch > tol).item()
print("PyTorch rank:", rank_torch)
Output:
PyTorch rank: 3
This matches NumPy’s rank.
Try these Python exercises to deepen your understanding. Solutions will be discussed in the next post!
np.linalg.lstsq
and
check if multiple solutions exist by inspecting the nullspace.np.linalg.lstsq
and verify if
.In the next post, we’ll explore eigenvalues and eigenvectors, key for PCA, stability analysis, and spectral clustering. We’ll provide more Python code and exercises to continue building your ML expertise.
Happy learning, and see you in Part 10!