Welcome to the fifth post in our series on Linear Algebra for Machine Learning! After diving into dot products and cosine similarity, we now explore linear independence and span, concepts that help us understand feature redundancy and the expressive power of data representations in machine learning (ML). In this post, we’ll cover the mathematical foundations, their relevance to ML, and how to work with them in Python using NumPy and PyTorch. We’ll include visualizations, a Gram matrix to assess independence, and Python exercises to reinforce your understanding.
A set of vectors in is linearly independent if no vector can be written as a linear combination of the others. Mathematically, the only solution to:
is , where are scalars. If any non-trivial combination (i.e., some ) yields the zero vector, the vectors are linearly dependent, meaning at least one vector is redundant.
For example, in :
The span of a set of vectors is the set of all possible linear combinations:
Geometrically, the span is:
The Gram matrix helps assess linear independence. For vectors , the Gram matrix is:
If is invertible (non-zero determinant), the vectors are linearly independent. If not, they are linearly dependent.
In machine learning:
Understanding these concepts helps you design efficient, expressive ML models.
Let’s explore linear independence and span using NumPy and PyTorch, with visualizations and a Gram matrix to test independence.
Install the required libraries if needed:
pip install numpy torch matplotlib
Let’s create sets of vectors and check their independence using the Gram matrix:
import numpy as np
import matplotlib.pyplot as plt
# Define two sets of 2D vectors
independent_vectors = np.array([[1, 0], [0, 1]]) # Linearly independent
dependent_vectors = np.array([[1, 2], [2, 4]]) # Linearly dependent
# Compute Gram matrices
gram_independent = independent_vectors.T @ independent_vectors
gram_dependent = dependent_vectors.T @ dependent_vectors
# Check determinants
det_independent = np.linalg.det(gram_independent)
det_dependent = np.linalg.det(gram_dependent)
# Print results
print("Independent vectors:\n", independent_vectors)
print("Gram matrix (independent):\n", gram_independent)
print("Determinant (independent):", det_independent)
print("\nDependent vectors:\n", dependent_vectors)
print("Gram matrix (dependent):\n", gram_dependent)
print("Determinant (dependent):", det_dependent)
Output:
Independent vectors:
[[1 0]
[0 1]]
Gram matrix (independent):
[[1 0]
[0 1]]
Determinant (independent): 1.0
Dependent vectors:
[[1 2]
[2 4]]
Gram matrix (dependent):
[[ 5 10]
[10 20]]
Determinant (dependent): 0.0
The non-zero determinant for the independent set confirms linear independence, while the zero determinant for the dependent set indicates linear dependence.
Let’s visualize the span of two vectors in 2D:
# Visualize span of independent vectors
def plot_2d_vectors(vectors, labels, colors, title):
plt.figure(figsize=(6, 6))
origin = np.zeros(2)
for vec, label, color in zip(vectors, labels, colors):
plt.quiver(*origin, *vec, color=color, scale=1, scale_units='xy', angles='xy')
plt.text(vec[0], vec[1], label, color=color, fontsize=12)
# Plot span as a shaded region (for independent vectors)
if len(vectors) == 2 and np.linalg.det(vectors) != 0:
t = np.linspace(-10, 10, 100)
for c1 in np.linspace(-2, 2, 20):
for c2 in np.linspace(-2, 2, 20):
point = c1 * vectors[0] + c2 * vectors[1]
plt.scatter(point[0], point[1], color='gray', alpha=0.1, s=1)
plt.grid(True)
plt.xlim(-3, 3)
plt.ylim(-3, 3)
plt.axhline(0, color='black', linewidth=0.5)
plt.axvline(0, color='black', linewidth=0.5)
plt.xlabel('X')
plt.ylabel('Y')
plt.title(title)
plt.show()
# Plot independent vectors
plot_2d_vectors(
independent_vectors,
['v1', 'v2'],
['blue', 'red'],
"Span of Linearly Independent Vectors"
)
# Plot dependent vectors (span is a line)
plt.figure(figsize=(6, 6))
plt.quiver(0, 0, dependent_vectors[0, 0], dependent_vectors[0, 1], color='blue', scale=1, scale_units='xy', angles='xy')
plt.text(dependent_vectors[0, 0], dependent_vectors[0, 1], 'v1, v2', color='blue', fontsize=12)
t = np.linspace(-3, 3, 100)
line = t[:, np.newaxis] * dependent_vectors[0]
plt.plot(line[:, 0], line[:, 1], 'gray', alpha=0.5)
plt.grid(True)
plt.xlim(-3, 3)
plt.ylim(-3, 3)
plt.axhline(0, color='black', linewidth=0.5)
plt.axvline(0, color='black', linewidth=0.5)
plt.xlabel('X')
plt.ylabel('Y')
plt.title("Span of Linearly Dependent Vectors (Line)")
plt.show()
This visualizes:
Let’s compute a Gram matrix in PyTorch:
import torch
# Convert to PyTorch tensors
ind_vectors_torch = torch.tensor(independent_vectors, dtype=torch.float32)
# Compute Gram matrix
gram_torch = ind_vectors_torch.T @ ind_vectors_torch
# Print result
print("PyTorch Gram matrix (independent):\n", gram_torch.numpy())
Output:
PyTorch Gram matrix (independent):
[[1. 0.]
[0. 1.]]
This confirms PyTorch’s Gram matrix matches NumPy’s.
Try these Python exercises to deepen your understanding. Solutions will be discussed in the next post!
np.linalg.matrix_rank
and determine if the columns
are linearly independent.In the next post, we’ll explore norms and distances, critical for loss functions, regularization, and gradient scaling in ML. We’ll provide more Python code and exercises to keep building your skills.
Happy learning, and see you in Part 6!