{"slug": "flows-and-diffusion-models-on-digits", "title": "Flows and diffusion models on digits", "summary": "A Stochastic Blog post reports that a RealNVP normalizing flow trained on a 6000-image MNIST subset beat a Gaussian baseline by 105.6 nats per image in exact negative log-likelihood, while a small DDPM diffusion model trained for three epochs on the same 6000 digits produced recognizable samples in 0.007 seconds using 25-step DDIM sampling. The author used 70,000-image MNIST (28x28 grayscale) with a 6000-image training subset and 1000-image test subset drawn with seed 42, rescaling pixels to -1 to 1 to handle the distribution's skew toward zero. The post frames both model families around one idea: a generative model turns noise into structure, and flows and diffusion models disagree about which direction to run that machine.", "body_md": "[Stochastic Blog](https://stochastic.blog/tag/stochastic-blog/)\n\n# Flows and diffusion models on digits\n\nIn Post 32 we built the machinery of generative modeling: latent variables, likelihoods, and the idea that a model can learn a distribution rather than a label. This post puts that machinery to work on MNIST. We train an exact-likelihood **RealNVP** flow, then a small **DDPM** diffusion model, and sample from the second with **DDIM** in 25 steps. The flow beats a Gaussian baseline by 105.6 nats per image, where a nat is the natural-log unit of information and lower negative log-likelihood is better. The diffusion model, trained for three epochs on 6000 digits, produces recognizable samples in 0.007 seconds. The through-line for the whole post is a single idea: a generative model is a machine for turning noise into structure, and the two families we build here disagree about which direction to run that machine.\n\nThe dataset is MNIST, 70,000 grayscale images at 28 by 28 pixels, downloaded through torchvision. Digits are small, fast on a laptop, and still show every behavior we care about. We work on a 6000-image training subset and a 1000-image test subset, both drawn with seed 42, because the full set is unnecessary for a run that finishes in minutes.\n\n## What the digits tell us\n\nBefore training anything we look at the raw images and the metadata. The label counts run from 6313 for the digit 5 up to 7877 for the digit 1, and the most frequent class sits at a base rate of 0.1125, close enough to uniform that a random subset keeps every class. The metadata has zero missing values, and the first hundred training images contain no NaN pixels, so nothing needs imputation. Every image is 28 by 28 with a single channel, which lets the models use fixed convolutional shapes. Here are five of them:\n\nThe pixel intensity histogram is the finding that shapes the modeling. MNIST is mostly black background with a bright stroke, so the distribution is heavily skewed toward zero. That skew is why we rescale pixels to the range from -1 to 1 before feeding them to a flow, and why the noise we add during diffusion has to be scaled to match.\n\nWe also check for duplicates and train/test leakage, where leakage means a test image that also appears in training. There are zero duplicate training images and zero leakage candidates, so the 1,000 held-out test images stay clean. The lowest-contrast digits have a pixel standard deviation of 41.9 on the original 0–255 scale, before the rescaling, and those are the visually hardest cases, which is why we judge those cases on the sample grid rather than the loss.\n\n## Exact likelihoods\n\nThat rescaling hands the flow a distribution on -1 to 1 that is still overwhelmingly dark, and the flow's job is to bend it into something we can sample from. A **normalizing flow** learns an invertible map from data to a simple base density. The exact likelihood is log p(x) = log p(z) + log |det J|, where J is the Jacobian of the map. **RealNVP** builds that map from coupling layers, each of which splits the image by a checkerboard mask, transforms one half conditioned on the other, and leaves the log-determinant cheap to compute. The same invertibility idea has cousins that give up the exact determinant for something else. **Glow** adds invertible 1x1 convolutions on top. **PixelCNN** models pixels autoregressively, meaning it predicts pixel n from pixels 1 to n−1. **Energy-based models** define an unnormalized energy, a score that ranks images by plausibility but isn't scaled to sum to one, and they need sampling to train or generate.\n\nThe coupling layer is the whole trick, so it is worth seeing in full. The mask splits the image, a small convolutional network predicts a scale and a shift from the masked half, and the unmasked half is transformed by those. The log-determinant is just the sum of the scale over the transformed pixels.\n\n``` python\ndef forward(self, x):\n    x1 = x * self.mask\n    st = self.net(x1)\n    s = torch.tanh(st[:, 0:1]) * 2.0\n    t = st[:, 1:2]\n    x2 = x * (1.0 - self.mask)\n    y = x1 + (1.0 - self.mask) * (x2 * torch.exp(s) + t)\n    logdet = (s * (1.0 - self.mask)).sum(dim=[1, 2, 3])  # exact log-determinant\n    return y, logdet\n```\n\nWe stack six of these layers with alternating masks and train for three epochs. The training NLL swings hard between epochs, from 572.4 nats per image in epoch 1 to -305.5 in epoch 2 and -350.4 in epoch 3, which is mostly an artifact of initialization: an untrained scale head makes epoch 1 a measure of where we started, not of the model. On the test set the numbers are the ones that matter: a plain Gaussian baseline gives 1083.1 nats per image, and RealNVP gives 977.5. The flow improves on the baseline by 105.6 nats. That gap is the whole lesson of exact likelihoods: the model is measurably better than noise, and we can say so in a single number.", "url": "https://wpnews.pro/news/flows-and-diffusion-models-on-digits", "canonical_source": "https://stochastic.blog/flows-and-diffusion-models-on-digits/", "published_at": "2026-11-09 12:11:58+00:00", "updated_at": "2026-11-09 12:23:21.403685+00:00", "lang": "en", "topics": ["machine-learning", "generative-ai", "ai-research", "neural-networks"], "entities": ["Stochastic Blog", "RealNVP", "DDPM", "DDIM", "MNIST", "Glow", "PixelCNN", "torchvision"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/flows-and-diffusion-models-on-digits", "markdown": "https://wpnews.pro/news/flows-and-diffusion-models-on-digits.md", "text": "https://wpnews.pro/news/flows-and-diffusion-models-on-digits.txt", "jsonld": "https://wpnews.pro/news/flows-and-diffusion-models-on-digits.jsonld"}}