A plain-words, poke-the-pictures introduction

Bending the Bell Curve

Normalizing flows are one of the friendliest big ideas in machine learning: take a plain bell curve and bend it, carefully, until it matches your data. This page explains how — no background needed.

650 points from a plain bell curve, flowing into a spiral — and back. This loop is the whole idea. The rest of the page just slows it down. forward →
Part 1The problem: machines need a map of plausibility

Where do things tend to land?

Ask a thousand adults their height and you won't get a thousand random numbers. The answers pile up around 170 cm, thin out toward 150 and 190, and almost never reach 250. There is a shape to the answers. Statisticians call that shape a probability distribution — a map of where values tend to land, and where they basically never do.

Everything a machine-learning model generates — a face, a voice, a molecule — is a draw from some distribution. So two abilities matter enormously. First, sampling: produce a brand-new example that looks like it belongs with the real ones. Second, scoring: given an example, say how plausible it is. Scoring is what lets a model whisper “this jet-engine reading looks weird” or “this molecule is unlikely to occur.”

For a bell curve, both are easy — it's one tidy formula with two knobs (where's the middle, how wide is the spread). But the distribution of, say, real handwritten digits lives in hundreds of dimensions and has a wildly complicated shape. Nobody can write its formula down by hand.

The bell curve (a “normal” distribution). One clean formula. Easy to sample from, easy to score. Also: boring — nothing real looks like this.
A realistic distribution. Lumpy, skewed, multiple clusters — and this is just one dimension. No formula exists for this. Yet it's what we actually want.
The tension

Simple distributions are workable but wrong. Real distributions are right but unworkable. Normalizing flows resolve this with a trick: keep the simple one — and reshape it.

Part 2The idea: reshape, don't rebuild

Probability as clay

Picture the bell curve as a mound of soft clay. Its thickness at any spot is the probability of values landing there. Now reshape the mound — stretch it here, squeeze it there — until it matches the complicated shape you actually want. You never created or destroyed clay. You only moved it.

A normalizing flow is exactly this, done with math. It's a chain of small reshaping steps applied to a simple distribution. But the steps must obey two house rules:

Rule 1 — no tearing. The reshaping must be smooth. Neighboring points stay neighbors; nothing teleports. (This is why it's called a flow — the points drift like particles in slow water.)

Rule 2 — no folding. Two different points may never land on the same spot. Every destination has exactly one origin, so the whole reshaping can be run backwards. In math terms, each step is invertible.

Follow the two rules and you get a superpower: any point in the final shape can be traced back to the exact point in the bell curve it came from — and probability can be carried along the way.
bell curve
step 1stretch
a bit bent
step 2push
bent further
step 3swirl
data shape
run forward →Sampling. Draw a boring bell-curve point, push it through the steps, out comes realistic data.
← run backwardScoring. Take real data, trace it back to the bell curve, and read off exactly how plausible it is.
Part 3Try it: bend a bell curve yourself

One slider, one transformation

Below is a real normalizing flow — a tiny one, with a single reshaping step. The dots are samples from a bell curve; the curve above them is the distribution's shape. The bend slider applies a smooth, invertible stretch that pushes points away from the center. Drag it and watch a valley form: one bump becomes two.

Interactive · a one-step flow in 1-D drag the sliders
before — the bell curve after — the reshaped distribution dots: 90 samples being carried along

Notice where the curve gets low: exactly where the dots got stretched apart. That's not a coincidence — it's the entire secret, and it's next.

Part 4The one rule of the game

Probability is never created or destroyed

Look at the shaded slice in the demo above. Before the bend, it holds some fixed share of all probability — say 6%. After the bend it holds… still exactly 6%. The slice got wider, so the curve over it must get lower, because the amount of clay inside is locked. Stretch a region by 2× and its density drops to ½. Squeeze it to half and the density doubles.

new height=old height÷stretch
pafter(y) = pbefore(x) |stretch(x)|
“stretch” is how much the map stretched space right there, and x is the point that lands on y. Mathematicians call this the change-of-variables formula — the only equation this page needs.

This is why the two house rules matter so much. No folding means each y has exactly one x to look up. No tearing (smoothness) means “how much it stretched here” is a well-defined number at every point. The transformation effectively keeps a receipt for every stretch and squeeze it performs — and the receipts are what let us compute exact probabilities in the final, complicated shape.

In two or more dimensions the idea is identical, just with area instead of width: take a tiny square, see how much the transformation inflates or deflates it. That local area-stretch factor has a fancy name — the Jacobian determinant — but it is still just “how much did this patch stretch.”

So why is it called a “normalizing flow”?

Flow, because samples drift smoothly through each small step, like fluid. Normalizing, because of the backward direction: run real data backwards through the chain and it settles into a normal distribution — the bell curve's formal name.

And how does it learn?

The reshaping steps have adjustable knobs (in practice, neural networks). Training shows the model real examples and asks: run these backwards — did they land in the fat middle of the bell curve, or out in the tails? Then it nudges the knobs so the real data becomes as plausible as possible. The stretch receipts keep the model honest: it can't just squash everything into the middle, because extreme squeezing is penalized by exactly the amount it squeezed. That's the whole training loop — no adversary, no approximation, just “make the data likely.”

A peek at the actual equation (optional)

Write the whole chain of steps as one invertible function f taking bell-curve points to data. For a data point x, its exact log-probability is:

logp(x) = log pbell (f1(x)) + log |det Jf1 (x)|

First term: “where did x land when traced back, and how likely is that spot on the bell curve?” Second term: “sum of the stretch receipts along the way.” Training maximizes this quantity, averaged over real data, by gradient descent. Everything is exact — which is the signature feature of flows.

Part 5Try it: stack simple steps

Simple moves, stacked, get you anywhere

One step can only reshape so much. The real power move is stacking: each layer is almost embarrassingly simple, but their composition is expressive. Below, 750 points from a 2-D bell-curve cloud pass through just two layers. Layer 1 is as plain as it gets — stretch the horizontal axis, squeeze the vertical — turning the blob into a bar. Layer 2 is a swirl: each point rotates by an amount that grows with its distance from the center, winding the bar's ends into arms. A galaxy, from a blob, in two moves.

Interactive · a two-layer flow in 2-D drag the slider, or jump by stage
— layer 1: stretch → — layer 2: swirl →
each dot keeps its color — started far left … started far right — so you can see nothing tears or folds

Drag the slider leftward and you're running the flow in its normalizing direction — the spiral melts back into a bell curve. Real flows stack dozens of learned layers like these (with names like coupling layers), each simple enough that its stretch receipt is cheap to compute, together flexible enough to model faces, audio, or molecules.

Part 6Where flows show up

In the wild

Flows had a star turn in image generation — OpenAI's Glow produced sharp faces and let you smoothly morph attributes, because every image maps to a point in bell-curve space where arithmetic is easy. NVIDIA's WaveGlow synthesized speech. In the sciences, flows act as Boltzmann generators, proposing plausible molecule configurations in one shot instead of simulating them for hours. And because flows compute exact plausibility scores, they quietly power anomaly detection — flagging the sensor reading, transaction, or particle event that real data says shouldn't happen.

Generative modelExact plausibility score?Its character, in one line
Normalizing flowyes — exactReversible by construction; its honesty (exact scores) costs it some architectural freedom.
GANnoA forger and a detective train against each other; sharp samples, but it can't score anything.
VAEapproximate onlyCompresses data through a bottleneck; scores exist but are estimates, not exact.
Diffusionindirect / costlyLearns to undo noise, step by step; today's image champion, but slower and scores take work.

Flows are no longer the loudest model in the room — diffusion took that seat — but the idea didn't fade; it dissolved into everything. Modern “flow matching” methods, used inside several state-of-the-art generators, are the same picture you've been playing with: probability, flowing smoothly from a bell curve to data.

BonusOne more trick: computing integrals

Point the samples where it counts

Away from generative modeling, flows have a quieter superpower: computing integrals — the total area under a complicated curve. In serious applications that curve lives in dozens or thousands of dimensions: the odds of particle-collision outcomes, the light arriving at a camera pixel, the energies of a molecule's configurations. No grid can cover that much space, so the one method that survives is sampling — drop random points, average what you find.

But random points are wasteful. When nearly all of the area hides in a couple of narrow bumps, samples from a plain bell curve mostly land where there is nothing, and the estimate wobbles badly. The classic fix, importance sampling, says: draw your samples from a distribution q shaped like the thing you're integrating, then undo the favoritism by dividing each sample's contribution by q's height there. The catch: you must be able to draw from q and know its exact, honestly-normalized height at any point. Very few flexible models can do both. A flow can — sampling is its forward direction, and exact heights are its stretch receipts.

area average of (target ÷ proposal)
I 1n i=1n f(xi) q(xi)
Each sample xi is drawn from the proposal q. Remarkably, the average is correct for any proposal that covers the target — its shape decides only how much the estimate wobbles.

But what is a vote, really? A single sample can only see one thing — the integrand's height where it landed. A height becomes an area only when multiplied by a width, so each sample speaks for the stretch of axis around it. And the width of that stretch is set by the crowding: where q is tall, samples pack densely and each owns a thin sliver; where q is low, a lone sample must represent a wide stretch — width about 1 / (n·q). So a sample's honest contribution is its patch of area: height × the width it speaks for. The vote f ÷ q is exactly that patch scaled up n-fold — the sample saying, “if everyone's patch looked like mine, the total would be this.” Averaging the votes stitches the patches back into the whole area. Dividing by q is no trick; it converts a height into that sample's fair share of the area.

Check the vote by hand — a two-zone toy you can do in your head

Two zones, each 1 wide: zone A has height 3, zone B has height 1. True area = 4. Use a matched sampler that visits A three-quarters of the time (q = 0.75 in A, 0.25 in B). A sample in A votes 3 ÷ 0.75 = 4. A sample in B votes 1 ÷ 0.25 = 4. Every vote says 4 — each sample's patch (thin-but-tall in A, wide-but-short in B) is an equal share of the area.

Now use a 50/50 sampler instead: the votes become 3 ÷ 0.5 = 6 and 1 ÷ 0.5 = 2. Half say 6, half say 2 — the average is still 4, but now it is right by cancellation, and a small batch can get unlucky. That, in miniature, is the whole demo below.

Try it. The orange shape below is an integrand f; we want its area. The blue curve is the proposal q — the very same bendable bell curve from Part 3. Two hundred samples are drawn from q, and each casts a vote of f ÷ q, drawn as a stem: tall stem, loud vote. Tick “show each vote's patch” to see the patches themselves — the tinted rectangles' combined area literally is the estimate, and at the sweet spot they tile the orange shape almost seamlessly. Bend the proposal until it hugs the integrand, and watch the votes even out while the estimate locks onto the true area.

Interactive · integration by importance sampling drag the slider
the integrand f — we want this area the proposal q — a bell curve, bent one sample's vote: f ÷ q

Why is bend = 0 so bad? Because the votes are wildly unequal. A sample landing at x = 0 — where the plain bell curve loves to put samples but the integrand is low — votes about 0.7. A sample landing out on a bump votes about 17. The average becomes a tug-of-war: dozens of samples muttering “0.7” against the rare one shouting “17!”. Over enough samples these exactly balance — the estimate is unbiased — but any single batch of 200 depends on how many shouters it happened to catch; hence the ±30% wobble. Near the sweet spot every sample casts nearly the same vote, so the same 200 points pin the area to under a percent. And since sampling error shrinks only with the square root of the sample count, matching the shapes buys what brute force would pay for with roughly a thousand times more samples.

Why the blue curve never reaches the orange one — and why that's the point

The proposal q is a probability distribution, so the area under it is locked at exactly 1. The area under the integrand f is the unknown answer — here, 2.363. Two curves with different total areas can never lie on top of each other, so the best q can possibly be is a perfect shrunk copy of f: the same shape, exactly 2.363× shorter everywhere.

Now watch what a vote does. Land anywhere — say where f = 0.40. The shrunk copy has q = 0.40 ÷ 2.363 = 0.169 there, so the vote is 0.40 ÷ 0.169 = 2.363. Land somewhere thinner, where f = 0.10: there q = 0.042, and the vote is 0.10 ÷ 0.042 = 2.363 again. Dividing f by its own shrunk copy recovers the shrink factor — the same number everywhere — and that factor is the ratio of the two areas: 2.363 to 1. The vote is the answer. With a perfectly matched proposal, the very first sample announces the area and the other 199 just agree. Real matches are imperfect, which is why the sweet spot reads ±0.8% rather than ±0.

This exact recipe — with learned bends, in many dimensions — is how flows do serious integrals. In particle physics the integrand is the rate of a collision outcome, summed over every way it can happen (flows are the neural descendants of the classic VEGAS method). In film rendering it is a pixel's brightness, summed over every path light can take to reach it. In chemistry it is a molecule's free energy, summed over its possible configurations. Each time, a flow is trained to be the shrunk copy of a monstrous integrand — and then a modest number of samples does an impossible-looking integral.

Who does what

The flow brings the pdf: flexible in shape yet exactly normalized by construction — area 1, receipts and all — and usable in both directions, sampling and scoring. Importance sampling brings the number: it weighs the unnormalized integrand f against that area-1 proposal, and the average vote reads off the ratio of their areas — f's missing normalization factor. Sanity check: if f were already normalized, the votes would average to exactly 1.

How is this different from the classic VEGAS method?

It's the same game. VEGAS (1978) also bends a simple distribution toward the integrand, then importance-samples. Its bending is, in modern language, a tiny normalizing flow: each coordinate axis gets its own invertible, adaptive stretch — a row of histogram bins that narrow wherever the integrand is large, and narrow bins mean crowded samples.

The limitation hides in the word each. VEGAS bends every axis independently, so its proposal is always a product of 1-D shapes — in 2-D, a patchwork of axis-aligned rectangles whose density in any cell is forced to be (row weight) × (column weight). Aim it at two peaks placed diagonally and it must also light up the two empty corners of their rectangle — “ghost peaks” it cannot avoid. And no adjustment of rows and columns ever makes a grid hug a tilted ridge, or the spiral from Part 5.

A neural flow removes exactly that constraint. Its layers bend coordinates based on each other — Part 5's swirl rotated each point by an amount depending on its radius, which is coordinates talking to one another — so the proposal can be tilted, curved, and correlated. The price: a flow trains a neural network by gradient descent, where VEGAS adapts its bins with a cheap, robust little iteration. That's the real trade. VEGAS remains nearly free and excellent when the integrand is roughly separable in well-chosen coordinates (physicists pick their variables cleverly to make this true); flows earn their keep when the coordinates are genuinely tangled. Modern integrators often combine the two.

So what is the loss function?

You have already been staring at it. The demo's “typical wobble” readout is a loss: one number measuring how badly the proposal's shape mismatches the integrand, bottoming out exactly when every vote is equal. The bend slider is a model with a single knob, and dragging it toward the sweet spot by hand is precisely what training does by gradient descent — a real flow just has millions of knobs (the weights of the small neural networks inside each layer), far too many for any hand.

Which loss you write down depends on what you hold. If you hold samples — real faces, real molecule snapshots — you train exactly as Part 4 described: run each example backwards and maximize its log-probability under the flow, receipts included. Statisticians call this minimizing a “forward” Kullback–Leibler divergence; the tutorial calls it make the data likely. If you hold only a formula — an unnormalized f, like a physics energy landscape, with no samples to learn from — the flow trains on its own output: draw samples from q and penalize either the average mismatch log q − log f (the “reverse” KL divergence) or, most directly, the variance of the votes themselves. Here is the elegant part: f's unknown normalization constant only shifts these losses up or down by a constant, so it never gets in the way of training — and when training ends, that constant is exactly what the average vote hands you.

Notice the quiet dependency: every one of these losses contains log q or a vote f ÷ q, so every one needs the proposal's exact density — the receipts again. That is why this game belongs to flows: the loss is exactly computable, differentiable through every bend, and flat votes are its minimum. (And VEGAS's bin re-weighting, from the box above, is the variance loss once more — minimized axis-by-axis with a cheap iteration instead of gradients.)

The three losses, spelled out (optional)

Holding real examples x1…xn (generative training, as in Part 4):

loss= 1n i=1n log q(xi)

Holding only an unnormalized f, training on the flow's own samples — the reverse KL:

loss= avgxq [ logq(x) logf(x) ]

…or the votes' variance, which targets the integrator's error directly:

loss= Varxq [ f(x) q(x) ]

All three share the same global minimum: q exactly proportional to the target. Their personalities differ on the way down. The first is mass-covering — it hates assigning low probability to any real example. The reverse KL is mode-seeking — it can score well while ignoring an entire bump, which for integration means an overconfident, quietly biased estimator — so integration work leans on the variance loss or mixes objectives.

Part 7Check yourself

Three questions

RecapIf you remember three things
1
Reshape, don't rebuild. A flow turns a bell curve into a complicated distribution through a chain of small, smooth, reversible steps.
2
Probability is conserved. Stretch a region and its density thins; squeeze and it thickens. New height = old height ÷ stretch. The “receipts” make scores exact.
3
Two directions, two gifts. Forward = generate new data. Backward = “normalize” data and read off exactly how plausible it is.
Pocket glossary
Distribution
A map of plausibility: which values tend to occur, and how often.
Density
The height of that map at one spot. Higher = values land here more often.
Base distribution
The simple starting shape, almost always a bell curve (a “standard normal”).
Invertible
Undoable. Every output has exactly one input, so the map runs backwards too.
Jacobian determinant (juh-KOH-bee-un)
The local stretch factor: how much a tiny patch of space grew or shrank at a given point.
Change of variables
The rule that carries density through a transformation: new height = old height ÷ stretch.
Coupling layer
A popular flow layer that shifts and scales half the coordinates based on the other half — expressive, yet trivially invertible.
Likelihood
The plausibility score a model assigns to data. Flows compute it exactly; training pushes it up.
If you want to go deeper