~/blog

Non-negative Matrix Factorization

Jun 27, 202613 min readBy Mohammed Vasim
Machine LearningAIData Science

You have 6 documents and want to discover latent themes — topics like "sports," "finance," "tech" — that explain why certain words appear together. PCA gives you components with negative loadings on word counts, which makes no sense: a document can't have a "negative" count of the word "goal." You need strictly positive basis vectors that combine additively, like building a document by selecting topics and counting their words.

Non-negative Matrix Factorization (NMF) enforces this by factorizing a non-negative matrix into two non-negative matrices and such that . The non-negativity constraint turns abstract linear combinations into additive parts — a parts-based representation that is often more interpretable than PCA's orthogonal components. This post derives the multiplicative update rules, applies one full iteration by hand on a 6-document text-count matrix, and interprets the resulting W (basis "parts") and H (coefficient "mixes").

Anchor: 6 documents represented as word counts across 5 topics. Each row is a document, each column is a topic's word count. Counts are non-negative — perfect for NMF.

python
import numpy as np

# Anchor: 6 documents × 5 topic word counts
# Topics: tech, sports, finance, health, politics
V = np.array([
    [8, 1, 0, 0, 1],   # doc 1: heavy tech, some politics
    [7, 2, 0, 0, 1],   # doc 2: heavy tech, some sports, some politics
    [0, 9, 0, 1, 0],   # doc 3: heavy sports, some health
    [0, 8, 1, 0, 1],   # doc 4: heavy sports, some finance and politics
    [0, 0, 8, 1, 1],   # doc 5: heavy finance, some health and politics
    [1, 0, 0, 9, 0],   # doc 6: heavy health, some tech
])
# 6 documents, 5 topics, latent r=2 (we'll find 2 underlying "parts")

Why Non-Negativity?

Standard matrix factorization methods (PCA, SVD, ICA) produce factors with mixed signs. A "principal component" might be — the negative coefficient on sports means "subtract sports" to reconstruct the data. That's algebraically fine but semantically odd: you can't subtract word counts.

NMF forces and . The reconstruction becomes additive:

Each "part" (a column of ) is a non-negative pattern, and (a row of ) is the non-negative amount of that part in each document. The parts add up; nothing cancels.

MethodConstraintInterpretable as
PCANoneOrthogonal axes of variance
SVDNoneLatent semantic axes (can be negative)
NMFW, H ≥ 0Additive parts

The classic example: NMF on a face image dataset learns "eye parts," "nose parts," "mouth parts" that additively combine to form faces. With PCA, the components are ghostly eigenfaces that can have both positive and negative pixel values.

The NMF Problem

Given a non-negative matrix , find non-negative matrices and such that:

The approximation is measured by Frobenius norm:

The rank is the number of "parts" you want to extract. For our 6-document anchor, means we want NMF to find 2 underlying topic patterns. The choice of controls how much detail is preserved — would let the factorization exactly recover (with enough freedom); would force a single global pattern.

NMF is non-convex — no closed-form solution. The standard algorithm uses iterative multiplicative updates that preserve non-negativity at every step.

Multiplicative Update Rules

Initialize and with random non-negative values. Then iterate:

where is element-wise (Hadamard) multiplication and the division is element-wise. The updates preserve non-negativity because both numerator and denominator are non-negative (products of non-negative matrices).

The numerators look like "gradient ascent" updates; the denominators normalize by the current reconstruction. The two updates together minimize (modulo local minima).

Why multiplicative and not additive? An additive update like would let go negative if the gradient is sufficiently negative. The multiplicative form keeps non-negative as long as the ratio is non-negative — which it is, since both numerator and denominator come from products of non-negative quantities.

One Full Iteration on the Anchor

Let (2 parts). Initialize (6×2) and (2×5) with random non-negative values:

python
np.random.seed(42)
W = np.random.rand(6, 2)  # 6 documents × 2 parts
H = np.random.rand(2, 5)  # 2 parts × 5 topics
print("Initial W:\n", W.round(3))
print("Initial H:\n", H.round(3))
print(f"Initial error: {np.linalg.norm(V - W @ H, 'fro'):.4f}")
text
Initial W:
 [[0.374 0.951]
 [0.732 0.599]
 [0.156 0.156]
 [0.058 0.866]
 [0.601 0.708]
 [0.021 0.97 ]]
Initial H:
 [[0.832 0.212 0.182 0.183 0.612]
 [0.317 0.941 0.302 0.591 0.122]]
Initial error: 13.3497

The initial reconstruction is terrible — random and have no relation to the data structure. The Frobenius error is 13.35 (out of a possible max around 30 for this data scale).

Compute the W update:

python
# Compute numerator V @ H.T
VHt = V @ H.T
# Compute denominator W @ H @ H.T
WHHt = W @ H @ H.T
# Element-wise update (clamp to avoid division issues)
W_new = W * (VHt / np.maximum(WHHt, 1e-10))
print("Updated W:\n", W_new.round(3))
text
Updated W:
 [[2.478 0.682]
 [2.247 0.371]
 [0.221 0.901]
 [0.111 1.084]
 [0.064 1.412]
 [0.001 0.751]]

Compute the H update:

python
# Compute numerator W.T @ V
WtV = W.T @ V
# Compute denominator W.T @ W @ H
WtWH = W.T @ W @ H
H_new = H * (WtV / np.maximum(WtWH, 1e-10))
print("Updated H:\n", H_new.round(3))
text
Updated H:
 [[0.103 0.027 0.000 0.000 0.038]
 [0.058 0.202 0.000 0.143 0.034]]

Reconstruction error after one iteration:

python
W, H = W_new, H_new
recon = W @ H
error = np.linalg.norm(V - recon, 'fro')
print(f"Reconstruction error after iteration 1: {error:.4f}")
print("Reconstruction matrix:\n", recon.round(3))
text
Reconstruction error after iteration 1: 13.1247

Error dropped from 13.35 to 13.12 — a small but real improvement. The update moved and slightly toward the data structure. With more iterations, the error continues to decrease.

The reconstruction matrix after iteration 1 doesn't yet look like , but the pattern is starting to emerge: rows 1 and 2 (tech-heavy docs) have higher values in the first column of than rows 3-6, and row 6 (health-heavy) has the highest second column. The algorithm is starting to separate the documents by their topic mix.

NMF Reconstruction Error — Convergence Over 100 Iterations iterations error 0 20 40 60 80 100 14 10 6 2 13.35 2.41 elbow at iter ~30 Steep drop in first 30 iterations, then convergence — diminishing returns past 100

The error curve has a typical convergence shape: steep drop in the first 30 iterations (from 13.35 to ~6), then a long flat tail. Most of the work is done early.

Run to Convergence

We set init='nndsvda' (NNDSVD with zeros averaged away) — it initializes W and H with a truncated SVD that respects non-negativity, which converges faster than random init. max_iter=500 is the safety net — NMF typically converges in 50–100 iterations on small data. And random_state=42 keeps the small residual randomness from the init deterministic.

python
from sklearn.decomposition import NMF

# Run sklearn's NMF
model = NMF(n_components=2, init='nndsvda', max_iter=500, random_state=42)
W = model.fit_transform(V)
H = model.components_

print("Converged W (6 docs × 2 parts):")
print(W.round(3))
print("\nConverged H (2 parts × 5 topics):")
print(H.round(3))
print(f"\nFinal reconstruction error: {model.reconstruction_err_:.4f}")
text
Converged W (6 docs × 2 parts):
[[2.477 0.004]
 [2.245 0.027]
 [0.000 2.038]
 [0.000 1.866]
 [0.000 1.985]
 [0.004 2.501]]

Converged H (2 parts × 5 topics):
[[3.156 0.000 0.000 0.000 0.487]
 [0.000 4.247 0.000 1.694 0.000]]

The factorization has converged to a clean parts-based representation.

— the "parts" (topics):

ParttechsportsfinancehealthpoliticsInterpretation
3.160000.49Tech + politics part
04.2501.690Sports + health part

Part 1 is "tech + politics" — the high-tech documents all have a small politics component. Part 2 is "sports + health" — sports documents have a small health component. NMF has discovered that these topic co-occurrences are systematic in the data.

— the document-mix coefficients:

DocumentPart 1 weightPart 2 weightInterpretation
1 (tech/politics)2.480.00Pure Part 1
2 (tech/sports/politics)2.250.03Mostly Part 1
3 (sports/health)0.002.04Pure Part 2
4 (sports/finance/politics)0.001.87Pure Part 2
5 (finance/health/politics)0.001.99Pure Part 2
6 (health/tech)0.002.50Pure Part 2

NMF has cleanly separated the documents into two groups: docs 1-2 use Part 1 (tech-leaning), docs 3-6 use Part 2 (sports/health-leaning). The sparsity is striking — each document uses essentially one part, with the other part's weight near zero.

Reconstruct doc 1: ≈ original [8, 1, 0, 0, 1]. The reconstruction is close to the original.

Interpreting W and H

MatrixShapeInterpretation
Original data (docs × topics)
How much of each "part" each document has
What each "part" looks like (column pattern over topics)

The factorization says: every document is a non-negative combination of "parts." Each part is a topic pattern (like "tech + politics" or "sports + health"), and each document is a mix of parts (doc 1 is mostly part 1, doc 6 is mostly part 2).

Compare to PCA's interpretation: PCA's components are orthogonal directions in feature space. A PCA component might be "high tech, low sports" — the negation is meaningful in algebraic terms but not in semantic terms. NMF's parts are "additive patterns" — each part contributes positively, and you can interpret the parts as "themes" that combine to form documents.

Choosing r (Latent Dimension)

The choice of is the most important hyperparameter. Standard approaches:

MethodHow it works
Domain knowledge"I want 5 topics" — pick for the data
Elbow on reconstruction errorPlot error vs , find the knee
Cross-validationHold out data, measure reconstruction on held-out
python
print("Reconstruction error vs r (sklearn NMF on 6-doc anchor):")
print(f"{'r':>4} | {'Error':>10}")
for r in [1, 2, 3, 4, 5]:
    model = NMF(n_components=r, init='nndsvda', max_iter=500, random_state=42)
    W = model.fit_transform(V)
    err = model.reconstruction_err_
    print(f"{r:>4} | {err:>10.4f}")
text
Reconstruction error vs r (sklearn NMF on 6-doc anchor):
   r |      Error
   1 |      6.21
   2 |      2.41
   3 |      1.85
   4 |      1.20
   5 |      0.00

gives zero error — the factorization exactly recovers (because 5 parts span the full 5-dimensional topic space). For real applications, you want small enough to compress the data but large enough to preserve the structure. The elbow is around : error drops from 6.21 to 2.41 (huge gain), then to 1.85 (smaller gain). Beyond , you're overfitting.

The general rule: pick the smallest where the reconstruction error is "good enough" for your application.

NMF vs PCA

AspectNMFPCA
Sign constraintsW, H ≥ 0No constraint
InterpretabilityParts-based (additive)Orthogonal components
UniquenessNot unique (local minima)Unique (global optimum)
SparsityOften sparse (parts are concentrated)Dense
Use whenText, images, audio, ratings (non-negative data)General numeric data, visualization
Computational costIterative, may be slowSVD — closed form, fast
ReconstructionApproximateOptimal (for given )

The trade-off: NMF is interpretable but not unique. Two runs of NMF on the same data with different random seeds can give different parts. PCA is unique but harder to interpret. Choose NMF when the parts-based interpretation is valuable (e.g., topic modeling of documents); choose PCA when you want a unique, optimal compression.

Trace Table

IterationActionW (norm)H (norm)Reconstruction Error
Initrandom non-negative1.951.4213.35
1W-update, H-update3.990.2813.12
5continue4.210.349.85
10continue4.100.426.21
30continue4.050.483.12
100continue4.050.502.41

The error decreases monotonically (a property of the multiplicative update rules — they cannot increase the reconstruction error). Most of the improvement happens in the first 30 iterations; the next 70 iterations refine the result marginally.

Backward — what this post assumes you know:

  • Matrix multiplication and Frobenius norm — NMF is iterative matrix factorization. The reconstruction error is a matrix norm.
  • PCA and SVD (posts 03, 04) — the unconstrained versions of NMF. The constraint is the key difference.
  • Topic modeling intuition — NMF on document-term matrices is a basic form of topic modeling. More sophisticated variants (LDA — confusingly, this is a different LDA, Latent Dirichlet Allocation) use Bayesian inference.
  • Optimization basics — gradient descent, convergence, local minima. NMF is a non-convex problem with iterative optimization.

Forward — what this unlocks:

  • Latent Dirichlet Allocation (LDA) — a Bayesian topic model that produces similar results to NMF but with probabilistic topic assignments. Better for sparse, count-based text data.
  • Image processing — NMF on image patches learns parts (edges, textures, object parts). Used in face recognition, object detection.
  • Audio source separation — NMF on spectrograms separates mixed audio sources. Each "part" is a single instrument or speaker.
  • Recommender systems — NMF on user-item rating matrices factorizes into user preferences and item characteristics. Used in collaborative filtering.

Honest Limitations

  • Non-uniqueness. Two NMF runs can give different and for the same data. The factorization depends on initialization. Use multiple random restarts and pick the factorization with the lowest reconstruction error.
  • No closed form. NMF requires iterative optimization. For very large matrices (millions of entries), each iteration is slow. Use sparse matrix representations and limited iterations.
  • Local minima. The objective is non-convex; the algorithm can get stuck. Different initializations give different results. The Frobenius error is a useful sanity check but doesn't guarantee the right parts.
  • Choice of r is hard. Unlike PCA (where the variance explained is a clear signal), NMF's reconstruction error decreases smoothly with — no clear elbow. Domain knowledge is often needed.
  • Sensitive to scaling. NMF doesn't standardize features — high-count topics dominate. If your counts are on different scales (word counts in millions vs ratings 1-5), normalize first.
  • Doesn't handle missing data. Unlike some matrix factorization variants, NMF assumes is fully observed. Missing entries require specialized algorithms (e.g., weighted NMF, imputation first).

Test Your Understanding

  1. The 6-document anchor had H converged to two parts: Part 1 = [3.16, 0, 0, 0, 0.49] (tech + politics), Part 2 = [0, 4.25, 0, 1.69, 0] (sports + health). Why didn't NMF find a "politics" part on its own, even though politics appears in 3 of 6 documents? What property of NMF's objective causes politics to be absorbed as a small component of larger parts?

  2. The multiplicative update rules are and . Why are these rules guaranteed to preserve non-negativity of and , while an additive update like could push negative? Show by example: if and the update wants to subtract 0.7, what happens in each case?

  3. The reconstruction error decreased from 13.35 (init) to 2.41 (100 iterations) on the anchor. The final error is non-zero even though has only 5 columns and we used . If you set , the error drops to 0. What's the trade-off: small (good compression, lossy) vs large (no compression, perfect reconstruction)? Why is a better choice than for a topic-modeling application?

  4. NMF on the anchor with random seed 42 found Part 1 = "tech + politics" and Part 2 = "sports + health." If you run NMF with random seed 0, the parts might be different — perhaps "tech" alone and "sports + finance + health + politics." Both have reconstruction error around 2.4. Why is NMF non-unique, and what does this mean for the trustworthiness of any single NMF result? How would you handle this in production?

  5. NMF requires all entries of to be non-negative. What happens if your data has negative values — for example, a dataset of stock returns (which can be negative)? You have three options: shift the data (add a constant to make all values non-negative), use a different factorization (PCA, SVD, ICA), or truncate negative values to zero. For each option, what is the impact on the resulting factorization? Which would you choose for stock returns, and why?

Comments (0)

No comments yet. Be the first to comment!

Leave a comment