Skip to content
AI360Xpert
Beta

Randomized SVD

Find a huge matrix's top directions with randomness: sketch it down to a thin sample, solve the small problem, lift the answer back up.

A tall thin random sketch compresses the giant matrix to a small core, the small SVD solves the core, and lifting back recovers the top directions
A tall thin random sketch compresses the giant matrix to a small core, the small SVD solves the core, and lifting back recovers the top directions

Why Does This Exist?

Truncated SVD on a million-by-million matrix costs O(n3)O(n^3) and materialises dense factors that dwarf memory. Yet the goal is only the top kk directions, and randomness finds them cheaply. Randomized SVD multiplies AA by a thin random sketch Ω\Omega (say n×(k+10)n \times (k + 10)), orthogonalises the sample Y=AΩY = A\Omega into QQ, compresses to the small core B=QTAB = Q^T A, and decomposes BB instead. Cost drops to O(mnlog⁡k)O(mn \log k) with structured sketches, and the error sits within a small factor of the optimal rank-kk error with overwhelming probability.

This powers PCA on data that never fits in RAM, fast LSA on term-document matrices, and near-real-time recommender updates. Exact foundations are in singular value decomposition; the compression payoff in PCA derivation.

The one idea you need first

A random projection preserves geometry: Y=AΩY = A\Omega captures the range of AA's top directions because random vectors have components along everything, amplified where AA stretches most. Oversampling (a few extra columns beyond kk) plus one or two power iterations (AATA A^T applied to the sketch) sharpens the capture on flat spectra.

Think of It Like This

Polling instead of a census

A census interviews everyone at enormous cost. A poll interviews a random sample and estimates the totals within a known margin of error. Nobody confuses the poll with the census, but decisions run on polls because the error bars suffice.

Randomized SVD polls a matrix. The sketch Ω\Omega is the random sample of directions; the small core BB is the tabulated poll; the lifted factors are the projected totals. Oversampling is polling extra respondents in volatile districts; power iterations are callback rounds that sharpen fuzzy answers. The Johnson-Lindenstrauss intuition (random projections preserve lengths) is why the pollster's math works.

How It Actually Works

The four stages for target rank kk with oversampling pp:

  • Sketch: draw Gaussian Ω\Omega (n×(k+p)n \times (k+p)) and form Y=AΩY = A\Omega. Optionally sharpen: Y=(AAT)qAΩY = (AA^T)^q A\Omega with q=1q = 1 or 22.
  • Orthogonalise: thin QR, Y=QRY = QR. QQ's columns span the sampled range.
  • Compress: B=QTAB = Q^T A, small ((k+p)×n(k+p) \times n). The giant problem now fits in memory.
  • Solve small: SVD of B=U~ΣVTB = \tilde{U}\Sigma V^T, lift U=QU~U = Q\tilde{U}. Truncate to kk.

Error bounds: within (1+ϵ)(1 + \epsilon) of optimal with probability 1−δ1 - \delta, where oversampling and power iterations trade cost against both. Fixed seeds make it reproducible; without them, two runs return different-but-equally-valid bases.

Worked example

Approximate the rank-one A=[3468]A = \begin{bmatrix} 3 & 4 \\ 6 & 8 \end{bmatrix}. Its Gram matrix ATA=[45606080]A^T A = \begin{bmatrix} 45 & 60 \\ 60 & 80 \end{bmatrix} has trace 125 and determinant 3600−3600=03600 - 3600 = 0, so singular values 125≈11.1803\sqrt{125} \approx 11.1803 and 00. Sketch with the deterministic Ω=[1,0]T\Omega = [1, 0]^T:

Y=AΩ=[36]Y = A\Omega = \begin{bmatrix} 3 \\ 6 \end{bmatrix}

One column captures the entire range (the matrix is rank one, so any in-range sketch suffices). Normalise to Q=[1,2]T/5Q = [1, 2]^T/\sqrt{5}, compress B=QTA=[15/5,20/5]=[35,45]B = Q^T A = [15/\sqrt{5}, 20/\sqrt{5}] = [3\sqrt{5}, 4\sqrt{5}], and ∥B∥=45+80=125≈11.1803\lVert B \rVert = \sqrt{45 + 80} = \sqrt{125} \approx 11.1803. The sketch route recovers σ1\sigma_1 exactly, with zero dense m×nm \times n factor ever formed.

Code

The point is the check: confirm the sketched singular value matches the exact one.

import numpy as np

A = np.array([[3.0, 4.0], [6.0, 8.0]])exact = np.linalg.svd(A, compute_uv=False)Y = A @ np.array([[1.0], [0.0]])  # one-column sketchQ, _ = np.linalg.qr(Y)B = Q.T @ Asketched = np.linalg.svd(B, compute_uv=False)print([round(float(v), 4) for v in exact])     # -> [11.1803, 0.0]print(round(float(sketched[0]), 4))            # -> 11.1803

Watch Out For

Skimping on oversampling with a flat spectrum

When the top singular values decay slowly, a tight sketch (p=0p = 0) mixes the kk-th direction with the tail and the truncation wobbles between runs. Oversample by 5 to 10 and add one power iteration: both are cheap insurance, and the error bounds assume you bought it. Flat spectra are exactly where naive sketches embarrass.

Leaving the sketch unseeded in a pipeline

Unseeded randomness returns a different valid basis every run: components flip, signs change, downstream caches invalidate and tests flake. Seed the generator and log the seed with the artefacts. Randomised does not have to mean irreproducible.

The Quick Version

  • Randomized SVD sketches Y=AΩY = A\Omega, orthogonalises to QQ, solves small B=QTAB = Q^T A, lifts back.
  • The rank-one demo recovers σ1=11.1803\sigma_1 = 11.1803 through a single sketch column.
  • Cost falls from O(n3)O(n^3) to sketch-plus-small-solve; error stays near optimal with high probability.
  • Oversample 5 to 10 and power-iterate once on flat spectra.
  • Seed every sketch: different valid bases per run break caches and tests.