Sinkhorn Algorithm
Sinkhorn solves entropy-smoothed transport by alternately rescaling matrix rows and columns, giving fast GPU-friendly approximate couplings.
Why Does This Exist?
Exact transport linear programs scale cubically and resist GPUs, yet ML needs transport daily: matching batches, aligning embeddings, averaging distributions. Adding a little entropy turns the hard program into matrix scaling with a unique solution reached by alternating normalization. Divide rows to hit source margins, divide columns to hit target margins, repeat. Each sweep is a batched multiply, so thousands of small problems solve side by side on accelerators.
This page covers the iteration itself: the kernel, the updates, and what the smoothing costs. The linear algebra underneath lives in linear systems, and the cost language lives in complexity analysis.
The one idea you need first
The Gibbs kernel turns costs into affinities, where is the cost of moving mass from to and controls smoothing. Cheap routes get weight near 1, expensive ones near 0. The optimal smoothed coupling has factored form , where and are scaling vectors that enforce the margins.
Think of It Like This
Balancing a spreadsheet two ways
A budget table must hit row totals and column totals at once. Fix the rows and the columns break. Fix the columns and the rows break. So you alternate: scale each row to its target, then each column, then rows again.
Each pass cuts the remaining error, and the table settles on the unique sheet satisfying both sides. Sinkhorn is that alternating fix with multiplicative corrections instead of additive ones.
Where the analogy stops: the kernel encodes costs exponentially, so tiny smoothing makes entries underflow without log-domain care.
How It Actually Works
Start from , where means a vector of ones. Then iterate:
In plain words: rescales rows so hits the source margins , then rescales columns so hits the target margins . Each sweep costs for sources and targets. Convergence is linear with a rate tied to the kernel contraction. Small sharpens the plan toward exact transport but slows and destabilizes the iteration. Log-domain updates on potentials and with softmin operations fix the underflow.
Worked example
Take , costs , and . Then , because .
- Init : . Then .
- Then , so . Symmetry converges in one step.
- Plan : diagonal , off-diagonal . Row sums are , and column sums match.
- Cost is . Exact transport would put on the diagonal with cost . The gap is the smoothing price at .
Code
The point is the alternating rescaling, not the formula retyped. This runs the iteration on the worked fixture and prints the plan the hand trace found.
import numpy as np
def sinkhorn(mu: np.ndarray, nu: np.ndarray, C: np.ndarray, eps: float, iters: int = 100) -> np.ndarray: K = np.exp(-C / eps) # Gibbs kernel turns costs into affinities v = np.ones_like(nu) for _ in range(iters): u = mu / (K @ v) # match row margins v = nu / (K.T @ u) # match column margins return np.diag(u) @ K @ np.diag(v)
C = np.array([[0.0, 1.0], [1.0, 0.0]])print(np.round(sinkhorn(np.array([0.5, 0.5]), np.array([0.5, 0.5]), C, 0.5), 3))# -> [[0.44 0.06] [0.06 0.44]] matching the hand computationDiagonal and off-diagonal agree with the hand-derived and .
Watch Out For
Running Sinkhorn in raw space with tiny epsilon
Entries underflow to zero in float32 once the exponent drops below about , producing division by zero and NaN plans. Small triggers it first, exactly when you wanted sharper answers.
Symptom: NaNs appear as shrinks, while larger runs fine. Fix: switch to log-domain softmin updates on and , or stabilize with periodic rescaling once drops under the cost median.
Stopping after a fixed 10 iterations always
Loose tolerance leaves margin errors that downstream barycenters amplify into visible artifacts. Fixed counts ignore problem difficulty: easy symmetric cases converge in one step, while small cases need hundreds.
Symptom: rows sum to instead of , and averages drift. Fix: check marginal violation and iterate to or your loss tolerance instead of trusting a count.
The Quick Version
- Sinkhorn solves entropic transport by alternating row and column rescaling to meet both margins.
- The Gibbs kernel encodes costs exponentially, while scaling vectors and enforce the margins.
- Each iteration is matrix-vector products, which is why it runs well on GPUs.
- On the worked case, and give diagonal and cost versus exact cost .
- Small smoothing sharpens plans but needs log-domain arithmetic to avoid underflow.
- Verify marginal errors instead of trusting a fixed iteration count.