15

Generating is learning a distribution

What does it mean for a network to "generate" something new?

10 min

Generating is learning a distribution

With voice. You can pause and jump to any step.

Lesson complete

Subtitles

In part one, networks learned to decide: they look at a drawing like this one and answer: it is a seven.

Voice

No voice: the lesson advances on its own with subtitles.

The voice for this lesson is being recorded: for now it advances on its own with subtitles.

0:00 / 0:00

What you will achieve

  • Explain the difference between classifying (drawing a boundary) and generating (knowing the distribution of the data and drawing new samples from it).
  • Describe, piece by piece, how an autoencoder sums up an image in two numbers and rebuilds it, and what the latent space is.
  • Recognize why a simple autoencoder’s plane has holes and how a VAE’s clouds fill them so that you can sample.

Key ideas

  1. Classifying means drawing a boundary between groups. Generating asks for more: knowing the distribution of the data, where it crowds and how far it reaches, to draw new samples.
  2. Sampling means drawing a new data point that follows the distribution: common things come out often and rare things seldom.
  3. The pixels of an image depend on each other: drawing them separately gives smudges. That is why each image is compressed into a few numbers that are enough to rebuild it.
  4. An autoencoder has two pieces: the encoder sums up the image in a code (here, two numbers) and the decoder rebuilds it. The plane of codes is the latent space.
  5. A simple autoencoder’s plane ends up untidy and full of holes. A VAE sums up each image as a cloud and gathers them in a known zone, so you can sample. The price: somewhat blurry images.

What many people believe

Common belief Generative AI cuts and pastes pieces of images it has stored: it is a collage.

What is true It learns a distribution: it stores weights, not images, and generates by drawing a random point and decoding it. What it generates resembles the data without copying any of it. The exception is memorization (mostly of images repeated many times in the data): a real failure that is measured and guarded against.

Check yourself

Four questions. No grade, no account: they just show whether the idea stuck.

ConceptWhat distinguishes a generative model from one that only classifies?
ConceptWhy does a VAE allow better sampling than a simple autoencoder?
CalculationThe lesson’s autoencoder sums up each 8 × 8 pixel image in a code of 2 numbers. How many times fewer numbers does the code have than the image?
ApplicationA vendor shows you five flawless synthetic images from their generator so that you buy it. What do you ask for first?

Under the hoodFor those who want the math and the code. You do not need it to follow the course.

Classifying versus generating

A model that classifies learns p(y∣x)p(y\mid x): given the data, the label; the boundary is enough. A generative model learns the distribution of the data itself, pθ(x)p_\theta(x), and lets you sample x∼pθ(x)x\sim p_\theta(x). It is usually trained by maximum likelihood:

θ⋆=arg⁡max⁡θ  1N∑i=1Nlog⁡pθ(xi)\theta^\star=\arg\max_\theta\;\frac1N\sum_{i=1}^{N}\log p_\theta(x_i)

The cloud of sevens

Each digit is measured as m=(m1,m2)m=(m_1,m_2): the ink in its top half and the ink in its bottom half (each pixel is worth 0 to 1; each half has 32 pixels). The lesson’s ovals are a two-dimensional Gaussian fitted by maximum likelihood to the 179 sevens:

μ^=1N∑imi=(10.82,  8.14),Σ^=1N∑i(mi−μ^)(mi−μ^)⊤=(2.24−0.65−0.652.45)\hat\mu=\frac1N\sum_i m_i=(10.82,\;8.14),\qquad \hat\Sigma=\frac1N\sum_i (m_i-\hat\mu)(m_i-\hat\mu)^\top=\begin{pmatrix}2.24&-0.65\\-0.65&2.45\end{pmatrix}

The small oval is (m−μ^)⊤Σ^−1(m−μ^)=1(m-\hat\mu)^\top\hat\Sigma^{-1}(m-\hat\mu)=1 and the large one is =4=4. In a two-dimensional Gaussian, 1−e−1/2≈39%1-e^{-1/2}\approx 39\% and 1−e−2≈86%1-e^{-2}\approx 86\% of samples fall inside them. To sample, take ε∼N(0,I)\varepsilon\sim\mathcal N(0,I) and m=μ^+Lεm=\hat\mu+L\varepsilon, with LL⊤=Σ^LL^\top=\hat\Sigma (Cholesky). The boundary between sixes and sevens in the scene is the linear discriminant analysis one: the line where the two Gaussians, with their averaged covariance, give the same density.

Why each pixel on its own is not enough

Drawing each pixel from its own distribution amounts to assuming p(x)=∏j=164p(xj)p(x)=\prod_{j=1}^{64}p(x_j): independent pixels. For digits that is false (a stroke lights up its neighbors), which is why you get smudges. You need a model of how pixels vary together.

Autoencoder

An encoder z=Eϕ(x)z=E_\phi(x) and a decoder x^=Dθ(z)\hat x=D_\theta(z), trained together to minimize the reconstruction error:

LAE=∥x−x^∥2=∑j=164(xj−x^j)2\mathcal L_{\text{AE}}=\lVert x-\hat x\rVert^2=\sum_{j=1}^{64}(x_j-\hat x_j)^2

The lesson’s models are tiny: 64→32→2→32→6464\to32\to2\to32\to64 with tanh⁡\tanh in the hidden layer and a sigmoid at the output (4,354 parameters), trained with gradient descent (Adam) on 1,500 images; another 297 were held back so the network could rebuild images it never saw. Mean error per held-out image by code size: 3.14 with 1 number, 2.34 with 2, 1.46 with 4, 0.95 with 8 and 0.54 with 16 (always guessing the mean image would give 4.71). With a linear decoder and squared error, an autoencoder learns the same subspace as PCA (Goodfellow et al., 2016, §14.1); nonlinear layers generalize it, and Hinton and Salakhutdinov (2006) showed that deep autoencoders learn codes that work much better than PCA.

Variational autoencoder (VAE)

We postulate a code with a known prior, p(z)=N(0,I)p(z)=\mathcal N(0,I) (the “round zone”), and a decoder pθ(x∣z)=N(Dθ(z),σobs2I)p_\theta(x\mid z)=\mathcal N(D_\theta(z),\sigma_{\text{obs}}^2 I). The encoder returns a cloud, qϕ(z∣x)=N(μϕ(x),diag⁡σϕ2(x))q_\phi(z\mid x)=\mathcal N\big(\mu_\phi(x),\operatorname{diag}\sigma^2_\phi(x)\big): its center and its size. During training a point is drawn with the reparameterization trick, which lets the gradient through:

z=μ+σ⊙ε,ε∼N(0,I)z=\mu+\sigma\odot\varepsilon,\qquad \varepsilon\sim\mathcal N(0,I)

What is maximized is a lower bound on log⁡pθ(x)\log p_\theta(x) (the ELBO):

log⁡pθ(x)  ≥  Eqϕ(z∣x)[log⁡pθ(x∣z)]−DKL(qϕ(z∣x) ∥ p(z))\log p_\theta(x)\;\ge\;\mathbb E_{q_\phi(z\mid x)}\big[\log p_\theta(x\mid z)\big]-D_{\mathrm{KL}}\big(q_\phi(z\mid x)\,\Vert\,p(z)\big)

The first term is the reconstruction error; the second is the penalty that pulls each cloud toward the standard zone. For diagonal Gaussians it has a closed form:

DKL=12∑k(μk2+σk2−ln⁡σk2−1)D_{\mathrm{KL}}=\tfrac12\sum_{k}\big(\mu_k^2+\sigma_k^2-\ln\sigma_k^2-1\big)

The loss used in the lesson is ∥x−x^∥2/(2σobs2)+DKL\lVert x-\hat x\rVert^2/(2\sigma_{\text{obs}}^2)+D_{\mathrm{KL}} with σobs2=0.1\sigma_{\text{obs}}^2=0.1 (4,420 parameters). The two terms compete: reconstruction separates and shrinks the clouds; the penalty pulls them together and widens them. To generate: z∼N(0,I)z\sim\mathcal N(0,I) and x=Dθ(z)x=D_\theta(z). With a fixed-variance Gaussian decoder, the best reconstruction of a code is the average of the images that fit it: hence the blur. Kingma and Welling (2019, §2.8.2) attribute it to the fit never being perfect and note that it eases with more flexible inference and generative models.

Sampling with each model

The autoencoder defines no zone to sample from; the naive fallback is to draw uniform points in the box of its codes. A simple classifier (logistic regression trained on the same 1,500 images) labeled 3,000 samples from each model: the autoencoder gave 22% fours, 21% sixes and 0.7% eights; the VAE, between 6% and 14% of each digit (the real data has about 10% of each). With eight different seeds the result holds: the VAE always comes out more even (see train_toy.py).

In code

import numpy as np

# five sevens measured as (top ink, bottom ink): example
m = np.array([[12.0, 7.8], [10.4, 8.9], [11.3, 7.4], [9.6, 8.5], [10.9, 8.1]])
mu = m.mean(axis=0)                        # center of the cloud
cov = np.cov(m.T, bias=True)               # shape of the oval (maximum likelihood: divide by N)
L = np.linalg.cholesky(cov)                # L @ L.T == cov
rng = np.random.default_rng(0)
new = mu + rng.standard_normal((3, 2)) @ L.T   # sampling: three new points from the cloud
print(mu.round(2).tolist(), new.round(1).tolist())
# [10.84, 8.14] [[10.9, 8.0], [11.4, 7.9], [10.4, 8.5]]

# A VAE cloud: center and size (one per number in the code)
c, s = np.array([0.8, -0.3]), np.array([0.5, 0.8])
kl = 0.5 * np.sum(c**2 + s**2 - np.log(s**2) - 1)   # penalty against the standard zone N(0, I)
z = c + s * np.array([0.2, -1.0])                   # a point drawn from the cloud (reparameterization trick)
print(round(float(kl), 4), z.round(2).tolist())
# 0.7263 [0.9, -1.1]

What comes next

VAEs were proposed independently by Kingma and Welling (2013) and by Rezende, Mohamed and Wierstra (2014). Generative adversarial networks (lesson 16) learn to generate without writing down a density: by competing. Diffusion (lesson 17) learns to remove noise, and its latent version works inside the compressed space of an autoencoder.

Decision lens

What this idea means if your job is to decide, not to code.

  • What is generated reflects the mix of the training data, and sometimes distorts it: in the demo, the autoencoder almost never produced eights. Ask them to measure the mix of what is generated, not just show examples.
  • Five flawless images are not evidence. Ask for random samples, with no selection, and a measure of quality and variety (for example, how many of a hundred attempts are usable and how many look alike).
  • “It is not a collage” is the rule, not a guarantee: with large models and repeated data, memorization is documented. Ask about data deduplication, memorization tests, and the origin and license of what was used for training.
  • Generating is sampling: the same instruction gives different results. If you need to reproduce a result, fix the seed and log the configuration; and remember that synthetic data carries no information that was not already in the original data.

Terms in this lesson

Full transcript
CommentsThis lesson’s public conversation: questions, facts to check, ideas.

Comments live in GitHub Discussions, in the course’s public repository; writing one requires a GitHub account. They load from giscus.app when you open this section. The course reviewers read them, and if a change is accepted, it is applied in both languages. Open the thread on GitHub

Sources