TAI BUI
Back to curriculum
BuildPython

Sampling Methods

Every generative model (LLM, diffusion, GAN) is just a sampling algorithm wearing a costume.

TIME: ~120 minutesPREREQUISITES: Phase 1, Lessons 06-07 (Probability, Bayes' Theorem)

Learning Objectives

  • Implement inverse CDF, rejection, and importance sampling from scratch using only uniform random numbers
  • Build temperature, top-k, and top-p (nucleus) sampling for language model token generation
  • Explain the reparameterization trick and why it enables backpropagation through sampling in VAEs
  • Run Metropolis-Hastings MCMC to sample from an unnormalized target distribution

The Problem

A language model finishes processing your prompt and produces a vector of 50,000 logits, one for every token in its vocabulary. Now it has to pick one. How?

Always take the highest-probability token and every response comes out identical: deterministic, safe, boring. Pick uniformly at random instead and you get gibberish. The useful answer sits somewhere between those two extremes, and sampling is what decides exactly where.

Sampling isn't just a text-generation trick. Reinforcement learning estimates policy gradients by sampling trajectories. VAEs learn latent representations by sampling from a learned distribution and backpropagating through the randomness. Diffusion models generate images by sampling noise and denoising it step by step. Monte Carlo methods estimate integrals that have no closed form. MCMC explores posterior distributions too high-dimensional to enumerate directly.

Every generative AI system, underneath, is a sampling system. The sampling strategy is what decides the quality, diversity, and controllability of what comes out. This lesson builds every major sampling method from scratch, starting from uniform random numbers and ending at the techniques running inside modern LLMs and generative models.

Warm-up0/3
1. If a system always produces the exact same output for the exact same input, no matter how many times you run it, what property does it have?Warm-up
A small box labelled f, fed the same input of 5 on three separate runs, drawn three times side by side with nothing else changed between runs.
2. A weighted six-sided die is built so that rolling a 6 is much more likely than rolling a 1, but every number from 1 to 6 can still come up. If you rolled it many times, what would you expect to see?Warm-up
Six bars for a die's faces 1 through 6, face 6 drawn much taller than the rest, with a note that every face still carries some weight.
3. A fair six-sided die is rolled once. What is the probability of rolling a 4 or higher?Warm-up
A fair six-sided die with faces 1 through 6 drawn as six equal plain squares, none highlighted, asking for the probability of rolling 4 or higher.
Check: The Problem0/1
1. A chat integration keeps producing repetitive, degenerate text, and a colleague says to raise the temperature. What should you check first?Analyze
A pipeline of four boxes, prompt, context window, stop condition and sampling with temperature, feeding a repeating output, with a question mark over the whole pipeline asking what to check first.

The Concept

Why Sampling Matters

Sampling is the act of generating values according to a probability distribution instead of just picking the one you think is right. It shows up in four fundamental roles across AI and machine learning.

Generation. Language models, diffusion models, and GANs all produce output by sampling. A GAN's generator starts from a latent vector sampled from a simple prior, usually a standard normal, and maps it into an image, so the sample drawn fully determines what comes out. The sampling algorithm directly controls creativity, coherence, and diversity, and temperature, top-k, and nucleus sampling are the knobs engineers turn every day.

Training. Stochastic gradient descent samples mini-batches. Dropout samples which neurons to deactivate. Data augmentation samples random transformations. Importance sampling reweights samples to cut gradient variance in reinforcement learning (PPO, TRPO, both trust-region policy-gradient methods).

Estimation. Plenty of quantities in ML have no closed-form solution: the expected loss over a data distribution, the partition function of an energy-based model, the evidence in Bayesian inference. Monte Carlo estimation approximates all of these by averaging over samples.

Exploration. MCMC explores posterior distributions in Bayesian inference. Evolutionary strategies sample parameter perturbations. Thompson sampling balances exploration against exploitation in bandits.

The core challenge is that you can only sample directly from simple distributions like uniform and normal. For everything else, you need a method that converts those simple samples into samples from whatever distribution you actually care about.

Uniform Random Sampling

Every sampling method starts here. A uniform random number generator produces values in [0, 1), where every sub-interval of equal length has equal probability.

U ~ Uniform(0, 1)

P(a <= U <= b) = b - a    for 0 <= a <= b <= 1

Properties:
  E[U] = 0.5
  Var(U) = 1/12

To sample uniformly from a discrete set of n items, generate U and return floor(n * U). To sample from a continuous range [a, b], compute a + (b - a) * U.

Here's the key insight: a single uniform random number carries exactly enough randomness to produce one sample from any distribution you like. The trick is finding the right transformation.

Inverse CDF Method (Inverse Transform Sampling)

The cumulative distribution function (CDF) maps values to probabilities:

F(x) = P(X <= x)

Properties:
  F is non-decreasing
  F(-inf) = 0
  F(+inf) = 1
  F maps the real line to [0, 1]

The inverse CDF maps probabilities back to values. If U ~ Uniform(0, 1), then X = F_inverse(U) follows the target distribution.

Algorithm:
  1. Generate u ~ Uniform(0, 1)
  2. Return F_inverse(u)

Why it works:
  P(X <= x) = P(F_inverse(U) <= x) = P(U <= F(x)) = F(x)

Exponential distribution example:

PDF: f(x) = lambda * exp(-lambda * x),   x >= 0
CDF: F(x) = 1 - exp(-lambda * x)

Solve F(x) = u for x:
  u = 1 - exp(-lambda * x)
  exp(-lambda * x) = 1 - u
  x = -ln(1 - u) / lambda

Since (1 - U) and U have the same distribution:
  x = -ln(u) / lambda

This works perfectly when you can write F_inverse in closed form. The normal distribution has no closed-form inverse CDF, so it needs other methods instead (Box-Muller, or a numerical approximation).

Discrete version: For discrete distributions, build the CDF as a cumulative sum, generate U, and find the first index where the cumulative sum exceeds U. This is how sample_categorical works in Lesson 06.

Rejection Sampling

Rejection sampling works when you cannot invert the CDF, but you can still evaluate the target PDF up to a constant.

Target distribution: p(x)  (can evaluate, possibly unnormalized)
Proposal distribution: q(x)  (can sample from)
Bound: M such that p(x) <= M * q(x) for all x

Algorithm:
  1. Sample x ~ q(x)
  2. Sample u ~ Uniform(0, 1)
  3. If u < p(x) / (M * q(x)), accept x
  4. Otherwise, reject and go to step 1

Acceptance rate = 1/M

The tighter the bound M, the higher the acceptance rate. Rejection sampling works well in low dimensions (1 to 3). In high dimensions, the acceptance rate drops exponentially because almost all of the proposal volume gets rejected. That's the curse of dimensionality, showing up here for rejection sampling specifically.

Example: sampling from a truncated normal. Use a uniform proposal over the truncated range. The envelope M is the maximum of the normal PDF in that range.

Example: sampling from a semicircle. Propose uniformly in the bounding rectangle. Accept if the point falls inside the semicircle. This is how Monte Carlo computes pi: the acceptance rate equals the area ratio pi/4.

Importance Sampling

Importance sampling estimates an expectation under a target distribution p(x) using samples drawn from a different, easier-to-sample distribution q(x). You never need to draw a single sample from p(x) itself.

Goal: estimate E_p[f(x)] = integral of f(x) * p(x) dx

Rewrite:
  E_p[f(x)] = integral of f(x) * (p(x)/q(x)) * q(x) dx
            = E_q[f(x) * w(x)]

where w(x) = p(x) / q(x)  are the importance weights.

Estimator:
  E_p[f(x)] ~ (1/N) * sum(f(x_i) * w(x_i))    where x_i ~ q(x)

This is exactly what reinforcement learning needs. In PPO (Proximal Policy Optimization), you collect trajectories under an old policy pi_old but want to optimize a new policy pi_new. The importance weight is pi_new(a|s) / pi_old(a|s), and PPO clips these weights so the new policy can't drift too far from the old one.

The estimator's variance depends on how close q is to p. When q is very different from p, a handful of samples pick up enormous weights and dominate the whole estimate. Self-normalized importance sampling divides by the sum of the weights to soften that problem:

E_p[f(x)] ~ sum(w_i * f(x_i)) / sum(w_i)

Monte Carlo Estimation

Monte Carlo estimation approximates integrals by averaging random samples, and the law of large numbers guarantees it converges.

Goal: estimate I = integral of g(x) dx over domain D

Method:
  1. Sample x_1, ..., x_N uniformly from D
  2. I ~ (Volume of D / N) * sum(g(x_i))

Error: O(1 / sqrt(N))   regardless of dimension

That error rate doesn't care about dimension at all, which is exactly why Monte Carlo dominates in high-dimensional settings where grid-based integration falls apart.

Estimating pi:

Sample (x, y) uniformly from [-1, 1] x [-1, 1]
Count how many fall inside the unit circle: x^2 + y^2 <= 1
pi ~ 4 * (count inside) / (total count)

Estimating expectations:

E[f(X)] ~ (1/N) * sum(f(x_i))    where x_i ~ p(x)

The sample mean converges to the true expectation.
Variance of the estimator = Var(f(X)) / N

Markov Chain Monte Carlo (MCMC): Metropolis-Hastings

MCMC constructs a Markov chain whose stationary distribution is the target distribution p(x). After enough steps, samples from the chain are (approximately) samples from p(x).

Target: p(x)  (known up to a normalizing constant)
Proposal: q(x'|x)  (how to propose the next state given the current state)

Metropolis-Hastings algorithm:
  1. Start at some x_0
  2. For t = 1, 2, ..., T:
     a. Propose x' ~ q(x'|x_t)
     b. Compute acceptance ratio:
        alpha = [p(x') * q(x_t|x')] / [p(x_t) * q(x'|x_t)]
     c. Accept with probability min(1, alpha):
        - If u < alpha (u ~ Uniform(0,1)): x_{t+1} = x'
        - Otherwise: x_{t+1} = x_t
  3. Discard first B samples (burn-in)
  4. Return remaining samples

For symmetric proposals, where q(x'|x) equals q(x|x'), the ratio collapses to p(x')/p(x). That's the original Metropolis algorithm.

Why it works. The acceptance rule ensures detailed balance: the probability of being at x and moving to x' equals the probability of being at x' and moving to x. Detailed balance implies that p(x) is the stationary distribution of the chain.

Practical considerations:

  • Burn-in: discard early samples before the chain reaches equilibrium
  • Thinning: keep every k-th sample to reduce autocorrelation
  • Proposal scale: too small and the chain moves slowly (high acceptance, slow exploration); too large and most proposals are rejected (low acceptance, stuck in place)
  • The optimal acceptance rate for a Gaussian proposal in high dimensions is approximately 0.234

Gibbs Sampling

Gibbs sampling is a special case of MCMC for multivariate distributions. Instead of proposing a move in all dimensions at once, it updates one variable at a time from its conditional distribution.

Target: p(x_1, x_2, ..., x_d)

Algorithm:
  For each iteration t:
    Sample x_1^{t+1} ~ p(x_1 | x_2^t, x_3^t, ..., x_d^t)
    Sample x_2^{t+1} ~ p(x_2 | x_1^{t+1}, x_3^t, ..., x_d^t)
    ...
    Sample x_d^{t+1} ~ p(x_d | x_1^{t+1}, x_2^{t+1}, ..., x_{d-1}^{t+1})

Gibbs sampling needs you to be able to sample from each conditional distribution p(x_i | x_{-i}), which is straightforward for plenty of models:

  • Bayesian networks: conditionals follow from the graph structure
  • Gaussian mixtures: conditionals are Gaussian
  • Ising models: each spin's conditional depends only on its neighbors

Its acceptance rate is always 1 (every proposal gets accepted) because sampling from the exact conditional automatically satisfies detailed balance.

Limitation. When variables are highly correlated, Gibbs sampling mixes slowly because updating one variable at a time cannot make large diagonal moves through the distribution.

Temperature Sampling (Used in LLMs)

Language models output logits z_1, ..., z_V for each token in the vocabulary. Softmax converts these to probabilities. Temperature rescales the logits before softmax:

p_i = exp(z_i / T) / sum(exp(z_j / T))

T = 1.0: standard softmax (original distribution)
T -> 0:  argmax (deterministic, always picks highest logit)
T -> inf: uniform (all tokens equally likely)
T < 1.0: sharpens the distribution (more confident, less diverse)
T > 1.0: flattens the distribution (less confident, more diverse)

Why it works. Dividing logits by T < 1 amplifies differences between logits. If z_1 = 2 and z_2 = 1, dividing by T = 0.5 gives z_1/T = 4 and z_2/T = 2, making the gap larger. After softmax, the highest-logit token gets a much larger share.

In practice:

  • T = 0.0: greedy decoding, best for factual Q&A
  • T = 0.3-0.7: slightly creative, good for code generation
  • T = 0.7-1.0: balanced, good for general conversation
  • T = 1.0-1.5: creative writing, brainstorming
  • T > 1.5: increasingly random, rarely useful

Temperature does not change which tokens are possible. It changes the probability mass allocated to each token.

Top-k Sampling

Top-k sampling restricts the candidate set to the k tokens with the highest probabilities, then renormalizes and samples from that restricted set.

Algorithm:
  1. Compute softmax probabilities for all V tokens
  2. Sort tokens by probability (descending)
  3. Keep only the top k tokens
  4. Renormalize: p_i' = p_i / sum(p_j for j in top-k)
  5. Sample from the renormalized distribution

k = 1:  greedy decoding
k = V:  no filtering (standard sampling)
k = 40: typical setting, removes long tail of unlikely tokens

Top-k keeps the model from picking extremely unlikely tokens (typos, nonsense, anything sitting in the long tail of the vocabulary distribution). The problem is that k stays fixed no matter what the context looks like. When the model is confident and one token holds 95% of the probability, k = 40 still leaves 39 alternatives on the table. When the model is uncertain and probability is spread across a thousand tokens, that same k = 40 cuts off options that were actually plausible.

Top-p (Nucleus) Sampling

Top-p sampling adjusts the candidate set size on the fly. Instead of keeping a fixed number of tokens, it keeps the smallest set whose cumulative probability clears p.

Algorithm:
  1. Compute softmax probabilities for all V tokens
  2. Sort tokens by probability (descending)
  3. Find smallest k such that sum of top-k probabilities >= p
  4. Keep only those k tokens
  5. Renormalize and sample

p = 0.9:  keeps tokens covering 90% of probability mass
p = 1.0:  no filtering
p = 0.1:  very restrictive, nearly greedy

When the model is confident, nucleus sampling might keep only two or three tokens. When it's uncertain, it might keep two hundred. That adaptive behavior is exactly why nucleus sampling tends to beat top-k in practice.

Common combinations:

  • Temperature 0.7 + top-p 0.9: good general-purpose setting
  • Temperature 0.0 (greedy): best for deterministic tasks
  • Temperature 1.0 + top-k 50: Fan et al. (2018) original paper setting

You can combine the two: apply top-k first, then top-p on whatever survives.

Reparameterization Trick (Used in VAEs)

The reparameterization trick rewrites a random sample as a deterministic function of its parameters plus a separate, parameter-free noise term, so gradients can flow through what used to be a random draw. Variational autoencoders (VAEs) need exactly that: they learn by encoding inputs into a distribution in latent space, sampling from that distribution, and decoding the sample back, and without this trick you cannot backpropagate through the sampling step at all.

Standard sampling (not differentiable):
  z ~ N(mu, sigma^2)

  The randomness blocks gradient flow.
  d/d_mu [sample from N(mu, sigma^2)] = ???

The trick separates the randomness from the parameters:

Reparameterized sampling:
  epsilon ~ N(0, 1)          (fixed random noise, no parameters)
  z = mu + sigma * epsilon   (deterministic function of parameters)

  Now z is a deterministic, differentiable function of mu and sigma.
  d(z)/d(mu) = 1
  d(z)/d(sigma) = epsilon

  Gradients flow through mu and sigma.

This works because N(mu, sigma^2) has the same distribution as mu + sigma * N(0, 1). The key insight: move the randomness to a parameter-free source (epsilon), then express the sample as a differentiable transformation of the parameters.

In the VAE training loop:

  1. Encoder outputs mu and log(sigma^2) for each input
  2. Sample epsilon ~ N(0, 1)
  3. Compute z = mu + sigma * epsilon
  4. Decode z to reconstruct the input
  5. Backpropagate through steps 4, 3, 2, 1 (possible because step 3 is differentiable)

Without it, VAEs can't be trained with standard backpropagation at all. This one trick is what made VAEs practical in the first place.

Gumbel-Softmax (Differentiable Categorical Sampling)

The reparameterization trick works for continuous distributions like the Gaussian. Discrete categorical distributions need a different approach: Gumbel-Softmax, a differentiable approximation to categorical sampling.

The Gumbel-Max trick (non-differentiable):

To sample from a categorical distribution with log-probabilities log(p_1), ..., log(p_k):
  1. Sample g_i ~ Gumbel(0, 1) for each category
     (g = -log(-log(u)), where u ~ Uniform(0, 1))
  2. Return argmax(log(p_i) + g_i)

This produces exact categorical samples.

Gumbel-Softmax (differentiable approximation):

Replace the hard argmax with a soft softmax:
  y_i = exp((log(p_i) + g_i) / tau) / sum(exp((log(p_j) + g_j) / tau))

tau (temperature) controls the approximation:
  tau -> 0:  approaches a one-hot vector (hard categorical)
  tau -> inf: approaches uniform (1/k, 1/k, ..., 1/k)
  tau = 1.0: soft approximation

Gumbel-Softmax produces a continuous relaxation of a discrete sample. The output is a probability vector (soft one-hot) instead of a hard one-hot. Gradients flow through the softmax. During training, you can use the "straight-through" estimator: the hard argmax for the forward pass, the soft Gumbel-Softmax gradient for the backward pass.

Applications:

  • Discrete latent variables in VAEs
  • Neural architecture search (choosing discrete operations)
  • Hard attention mechanisms
  • Reinforcement learning with discrete actions

Stratified Sampling

Standard Monte Carlo sampling can leave gaps in the sample space purely by chance. Stratified sampling forces even coverage instead: divide the space into strata and sample from each one.

Standard Monte Carlo:
  Sample N points uniformly from [0, 1]
  Some regions may have clusters, others gaps

Stratified sampling:
  Divide [0, 1] into N equal strata: [0, 1/N), [1/N, 2/N), ..., [(N-1)/N, 1)
  Sample one point uniformly within each stratum
  x_i = (i + u_i) / N   where u_i ~ Uniform(0, 1),  i = 0, ..., N-1

Stratified sampling always has lower or equal variance compared to standard Monte Carlo:

Var(stratified) <= Var(standard Monte Carlo)

The improvement is largest when f(x) varies smoothly.
For piecewise-constant functions, stratified sampling is exact.

Applications:

  • Numerical integration (quasi-Monte Carlo)
  • Training data splits (ensuring class balance in each fold)
  • Importance sampling with stratification (combining both techniques)
  • NeRF (Neural Radiance Fields) uses stratified sampling along camera rays

Connection to Diffusion Models

Diffusion models generate images through a sampling process. The forward process adds Gaussian noise to an image over T steps until it becomes pure noise. The reverse process learns to denoise, recovering the original image step by step.

Forward process (known):
  x_t = sqrt(alpha_t) * x_{t-1} + sqrt(1 - alpha_t) * epsilon
  where epsilon ~ N(0, I)

  After T steps: x_T ~ N(0, I)  (pure noise)

Reverse process (learned):
  x_{t-1} = (1/sqrt(alpha_t)) * (x_t - (1 - alpha_t)/sqrt(1 - alpha_bar_t) * epsilon_theta(x_t, t)) + sigma_t * z
  where z ~ N(0, I)

  Each denoising step is a sampling step.

The connection to the methods in this lesson:

  • Each denoising step uses the reparameterization trick (sample noise, apply deterministic transform)
  • The noise schedule {alpha_t} controls a form of temperature annealing
  • Training uses Monte Carlo estimation to approximate the ELBO (evidence lower bound)
  • Ancestral sampling in diffusion models is a Markov chain (each step depends only on the current state)

The entire image generation process is iterative sampling: start from noise, and at each step, sample a slightly less noisy version conditioned on the learned denoising model.

Check: The Concept0/37
1. What is sampling, as the term is used for generative models?Define
A bar chart of a weighted bag with three outcomes, apple 0.5, banana 0.3 and cherry 0.2, with an arrow to a question mark standing in for one drawn outcome, asking what this operation is called.
2. What does it mean for a random number generator to be uniform on [0, 1)?Define
Nine points scattered along a number line from 0 to 1, each one a draw from a uniform random number generator, asking what it means for the generator to be uniform.
3. What does the inverse CDF method do with a uniform sample u?Define
A generic rising curve from 0 to 1 with a horizontal line from a point u on the vertical axis across to the curve, then down to an unlabeled x on the horizontal axis, asking what the inverse CDF method does with a uniform sample u.
4. What is rejection sampling?Define
A bell-shaped target curve p of x under a dashed envelope line, with about two dozen real randomly proposed points plotted by height, green points under the curve kept and gray points above it discarded, asking what this procedure is called.
Simulated with Python's random module and the lesson's own normal_pdf function.
5. What problem does importance sampling solve?Define
Five boxes, each a sample x drawn from an easy proposal distribution tagged with its own weight w, feeding toward a question mark standing in for a weighted average, asking what problem this reweighting solves.
Weights computed from the lesson's own normal_pdf function on real random draws.
6. What is Monte Carlo estimation?Define
A square with a circle inscribed inside it, scattered with real random points colored by whether they land inside the circle, next to the resulting count and the estimate four times the inside fraction, asking what general estimation method this demonstrates.
Simulated with Python's random module, seed 701, 85 points.
7. What is MCMC?Define
A wandering path of ten connected points labelled x_0 through x_9, each state built from the one before it, asking what the states of such a chain represent once it has run long enough.
8. In Metropolis-Hastings, how is a proposed move decided?Define
A box for the current state x_t with its density labelled, an arrow to a proposed state x prime with its own density labelled, and a question mark asking how the move is decided.
9. What does Gibbs sampling do on each update?Define
A short path inside a 2D panel made only of horizontal and vertical segments, never a diagonal move, asking what update rule produces moves shaped exactly like this.
10. What does temperature do in LLM sampling?Define
Five bars for raw logit values 1.5, 0.8, 0.3, -0.2 and -0.9, feeding into a flow of boxes reading logits, divide by T and softmax, then five question marks for the result, asking what the temperature parameter does.
11. What is top-k sampling?Define
Eight sorted probability bars for a vocabulary, a dashed line marking off the top three, asking what happens to the tokens on each side of this fixed boundary.
12. What is nucleus (top-p) sampling?Define
A cumulative bar split into five segments for probabilities 0.45, 0.25, 0.15, 0.10 and 0.05, with a marker at p equals 0.80 on the running total, asking how many tokens are kept and whether that count can change.
13. What is the reparameterization trick?Define
A blocked random draw z from a normal distribution with mu and sigma, marked with a cross over its derivative, next to a separate fixed noise term epsilon feeding into z equals mu plus sigma times epsilon, asking what moving the randomness into epsilon makes possible.
14. What is Gumbel-Softmax?Define
A hard argmax of log probabilities plus Gumbel noise, marked as blocking gradient flow, next to a soft softmax version of the same quantity divided by temperature tau, marked as letting gradients flow, asking what kind of categorical sample the soft version produces.
15. What is stratified sampling?Define
Ten real random points scattered along a line with visible gaps and clusters, above ten points placed one per equal-width slice of the same line with slice boundaries marked, asking what the second arrangement is called.
Simulated with Python's random module and the lesson's own stratified_sample_1d function.
16. What is burn-in in MCMC?Define
A real trace of a Metropolis-Hastings chain starting far above zero and settling toward zero over 60 steps, with the early part of the run shaded, asking which part of a run like this gets thrown away and why.
Simulated with the lesson's own metropolis_hastings function, started at x = 8.
17. What does detailed balance require of a Markov chain?Define
Two states x and y drawn as circles with illustrative probability masses 0.6 and 0.4, connected by two labelled but unresolved flow amounts in each direction, asking what must be true of the two flows.
Illustrative masses, not computed from the lesson's own code.
18. What is diffusion sampling?Define
Two rows of boxes: a forward row from a clean image through added noise to pure noise marked known, and a reverse row from pure noise through learned denoising steps to an image marked learned, asking which row is what sampling from a diffusion model means.
19. Why can't you backpropagate through a standard sampling operation z ~ N(mu, sigma^2)?Explain
A random draw z from a normal distribution with parameters mu and sigma, with an arrow to the unresolved derivative of z with respect to mu, asking why a gradient cannot pass back through this step.
20. In Metropolis-Hastings, what happens if the proposal standard deviation is set much too large?Explain
A bell-shaped target curve with the current state marked near its peak and an arrow showing a large proposed jump far out toward the thin tail, marked with a question mark, asking what happens when the proposal's step size is much too large.
21. In rejection sampling, what happens to the acceptance rate as the dimensionality of the target distribution increases?Explain
A square proposal region with a circle representing the target region inscribed inside it, asking what happens to the fraction of the square's volume the circle fills as the number of dimensions grows.
22. What role do importance weights play in PPO (Proximal Policy Optimization)?Explain
A box for an old policy that collected trajectories, an arrow to a highlighted box for the new policy being optimized, and a question mark asking what corrects for using the old data on the newer policy.
23. How does the error of a Monte Carlo estimate scale with the number of samples N, and why does that matter in high dimensions?Explain
Three panels for the same kind of Monte Carlo estimate run with N equal to 100, 10,000 and 1,000,000 samples, with a question mark asking how the error shrinks with N and whether dimension changes that.
24. What is the acceptance rate of Gibbs sampling, and why?Explain
A flow of four update boxes alternating x given y and y given x, with no accept or reject step drawn anywhere, and a question mark asking for the acceptance rate of this procedure.
25. What does the temperature parameter tau control in Gumbel-Softmax?Explain
Three faint, unresolved category bars for a Gumbel-Softmax output, with a question mark asking what happens to the output shape as tau shrinks toward 0 or grows large.
26. How does stratified sampling's variance compare to standard Monte Carlo sampling?Explain
The same two point placements, standard random and one-per-slice, with a question mark asking which gives a lower-variance estimate of an average over the range.
27. Which earlier sampling idea does each denoising step in a diffusion model rely on?Explain
One diffusion reverse step, from x_t through an unlabeled transform using a learned function of x_t and a freshly drawn standard normal z, to x_t-1, asking which earlier idea in the lesson this structure reuses.
28. Why does dividing logits by T < 1 make the output more confident?Explain
Two logits 3.0 and 1.0 with their gap of 2.0 labelled, next to the same logits divided by T equals 0.5 left as question marks, asking why this makes the resulting distribution more confident.
29. Why does nucleus sampling tend to beat a fixed top-k?Explain
Two real logit sets run through the lesson's top_p_distribution and top_k_distribution functions: a confident model where nucleus keeps very few tokens, and an uncertain model where it keeps several more, while top-k keeps exactly 5 tokens both times.
Computed with the lesson's own top_p_distribution and top_k_distribution functions.
30. Using the lesson's exponential inverse CDF with rate lambda = 2, a uniform draw comes out as u = 0.5. What value does the method return?Apply
The lesson's own exponential inverse CDF formula, x equals negative ln of u divided by lambda, with lambda equal to 2 and u equal to 0.5 given, and x left as a question mark.
31. A VAE encoder never improves. The code draws z = Normal(mu, sigma).sample() and decodes z. What is the problem?Analyze
A training pipeline, encoder to mu and sigma to a direct random sample from Normal mu sigma to decoder, with a cross marked over the sampling step showing no gradient reaches mu and sigma, asking what is wrong.
32. A Metropolis-Hastings chain accepts only 2 percent of proposals and its trace stays flat for long stretches. What is the most likely cause?Analyze
A real Metropolis-Hastings trace that stays flat for long stretches, with a measured acceptance rate near 2 percent, asking what most likely causes this pattern.
Simulated with the lesson's own metropolis_hastings function.
33. An importance sampling estimate swings widely between runs, and a few samples carry huge weights. What is the most likely cause?Analyze
A bar chart of twelve real importance weights, one of them towering over the rest, asking what causes one or two samples to carry almost all of the weight.
Weights computed from the lesson's own normal_pdf function on real random draws from a wide uniform proposal.
34. A model puts 95 percent of its probability on one token, yet top-k sampling with k = 40 still keeps a large set of alternatives. Why?Analyze
A heavily skewed probability chart, one token at 95 percent and the rest spread thin across many tokens, with the top-k equal 40 boundary drawn far past the dominant token, asking why top-k still keeps so many alternatives.
35. You must estimate an expected reward under a new policy but only have trajectories collected under the old one. Which approach fits?Design
A box of trajectories collected under an old policy, an arrow to a highlighted box for the expected reward wanted under a new policy, and a question mark asking which sampling idea fits reusing the old data.
36. You need samples from an unnormalized 100-dimensional posterior. You can evaluate its density up to a constant but cannot invert its CDF. Which method fits?Design
A box describing a target distribution over 100 dimensions that can only be evaluated up to a constant and whose CDF cannot be inverted, with a question mark asking which method produces samples from it.
37. You want an LLM to produce more diverse brainstorming output, but at temperature 1.0 it keeps repeating similar phrasing. Which change is most likely to help?Design
A terminal showing eight real words drawn at temperature 1.0 from a vocabulary of five words, several of them repeating the same word, asking what change would make the output more varied.
Drawn with the lesson's own temperature_sample function.

Build It

Step 1: Uniform and inverse CDF sampling

python
import math
import random
 
def sample_uniform(a, b):
    return a + (b - a) * random.random()
 
def sample_exponential_inverse_cdf(lam):
    u = random.random()
    return -math.log(u) / lam

Generate 10,000 exponential samples and verify the mean is 1/lambda.

Step 2: Rejection sampling

python
def rejection_sample(target_pdf, proposal_sample, proposal_pdf, M):
    while True:
        x = proposal_sample()
        u = random.random()
        if u < target_pdf(x) / (M * proposal_pdf(x)):
            return x

Use rejection sampling to draw from a truncated normal distribution. Verify the shape by histogramming the samples.

Step 3: Importance sampling

python
def importance_sampling_estimate(f, target_pdf, proposal_pdf, proposal_sample, n):
    total = 0
    for _ in range(n):
        x = proposal_sample()
        w = target_pdf(x) / proposal_pdf(x)
        total += f(x) * w
    return total / n

Estimate E[X^2] under a normal distribution using a uniform proposal. Compare to the known answer (mu^2 + sigma^2).

Step 4: Monte Carlo estimation of pi

python
def monte_carlo_pi(n):
    inside = 0
    for _ in range(n):
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)
        if x*x + y*y <= 1:
            inside += 1
    return 4 * inside / n

Step 5: Metropolis-Hastings MCMC

python
def metropolis_hastings(target_log_pdf, proposal_sample, proposal_log_pdf, x0, n_samples, burn_in):
    samples = []
    x = x0
    for i in range(n_samples + burn_in):
        x_new = proposal_sample(x)
        log_alpha = (target_log_pdf(x_new) + proposal_log_pdf(x, x_new)
                     - target_log_pdf(x) - proposal_log_pdf(x_new, x))
        if math.log(random.random()) < log_alpha:
            x = x_new
        if i >= burn_in:
            samples.append(x)
    return samples

Sample from a bimodal distribution (mixture of two Gaussians). Visualize the chain's trajectory.

Step 6: Gibbs sampling

python
def gibbs_sampling_2d(conditional_x_given_y, conditional_y_given_x, x0, y0, n_samples, burn_in):
    x, y = x0, y0
    samples = []
    for i in range(n_samples + burn_in):
        x = conditional_x_given_y(y)
        y = conditional_y_given_x(x)
        if i >= burn_in:
            samples.append((x, y))
    return samples

Step 7: Temperature sampling

python
def softmax(logits):
    max_l = max(logits)
    exps = [math.exp(z - max_l) for z in logits]
    total = sum(exps)
    return [e / total for e in exps]
 
def temperature_sample(logits, temperature):
    scaled = [z / temperature for z in logits]
    probs = softmax(scaled)
    return sample_from_probs(probs)

Show how temperature changes the output distribution for a set of token logits.

Step 8: Top-k and top-p sampling

python
def top_k_sample(logits, k):
    indexed = sorted(enumerate(logits), key=lambda x: -x[1])
    top = indexed[:k]
    top_logits = [l for _, l in top]
    probs = softmax(top_logits)
    idx = sample_from_probs(probs)
    return top[idx][0]
 
def top_p_sample(logits, p):
    probs = softmax(logits)
    indexed = sorted(enumerate(probs), key=lambda x: -x[1])
    cumsum = 0
    selected = []
    for token_idx, prob in indexed:
        cumsum += prob
        selected.append((token_idx, prob))
        if cumsum >= p:
            break
    sel_probs = [pr for _, pr in selected]
    total = sum(sel_probs)
    sel_probs = [pr / total for pr in sel_probs]
    idx = sample_from_probs(sel_probs)
    return selected[idx][0]

Step 9: Reparameterization trick

python
def reparam_sample(mu, sigma):
    epsilon = random.gauss(0, 1)
    return mu + sigma * epsilon
 
def reparam_gradient(mu, sigma, epsilon):
    dz_dmu = 1.0
    dz_dsigma = epsilon
    return dz_dmu, dz_dsigma

Demonstrate that gradients flow through the reparameterized sample but not through direct sampling.

Step 10: Gumbel-Softmax

python
def gumbel_sample():
    u = random.random()
    return -math.log(-math.log(u))
 
def gumbel_softmax(logits, temperature):
    gumbels = [math.log(p) + gumbel_sample() for p in logits]
    return softmax([g / temperature for g in gumbels])

Show how decreasing temperature makes the output approach a one-hot vector.

Full implementations with all visualizations are in code/sampling.py.

Check: Build It0/4
1. In temperature_sample, the logits are [2.0, 1.0] and temperature is 0.5. Which list goes into softmax?Apply
A code snippet from temperature_sample with logits 2.0 and 1.0 and temperature 0.5, computing scaled as each logit divided by temperature, asking which list goes into softmax next.
2. In top_p_sample, the sorted probabilities are [0.5, 0.3, 0.1, 0.05, 0.05] and p = 0.75. Which tokens get sampled from, and with what probabilities?Apply
A code snippet from top_p_sample with sorted probabilities 0.5, 0.3, 0.1, 0.05 and 0.05 and p equal to 0.75, walking the cumulative sum, asking which tokens get sampled from and with what probabilities.
3. In rejection_sample, M = 2, target_pdf(x) = 0.3 and proposal_pdf(x) = 0.5 at the drawn x, and u comes out as 0.4. What happens?Apply
A code snippet from rejection_sample with M equal to 2, target density 0.3, proposal density 0.5 and u equal to 0.4, asking whether this draw is accepted against the acceptance threshold.
4. In metropolis_hastings with a symmetric proposal, the target density is 0.4 at the current x and 0.2 at x_new. What is the probability that the chain moves to x_new?Apply
A symmetric Metropolis-Hastings proposal with the current state's density 0.4 and the proposed state's density 0.2 given, and the probability of moving left as a question mark.

Use It

Here's the production version, with NumPy and SciPy doing the heavy lifting:

python
import numpy as np
 
rng = np.random.default_rng(42)
 
exponential_samples = rng.exponential(scale=2.0, size=10000)
print(f"Exponential mean: {exponential_samples.mean():.4f} (expected 2.0)")
 
from scipy import stats
normal = stats.norm(loc=0, scale=1)
print(f"CDF at 1.96: {normal.cdf(1.96):.4f}")
print(f"Inverse CDF at 0.975: {normal.ppf(0.975):.4f}")
 
logits = np.array([2.0, 1.0, 0.5, 0.1, -1.0])
temperature = 0.7
scaled = logits / temperature
probs = np.exp(scaled - scaled.max()) / np.exp(scaled - scaled.max()).sum()
token = rng.choice(len(logits), p=probs)
print(f"Sampled token index: {token}")

For MCMC at scale, use dedicated libraries:

  • PyMC: full Bayesian modeling with NUTS (adaptive HMC)
  • emcee: ensemble MCMC sampler
  • NumPyro/JAX: GPU-accelerated MCMC

You built these from scratch. Now you know what the library calls are doing.

Author's Note

In production you will not write your own Metropolis-Hastings sampler or invert a CDF by hand. You will set two numbers, temperature and top-p, on an API call and move on. But the time I spent building rejection sampling and MCMC from scratch paid off in a different way: when an LLM integration starts producing weird, repetitive, or degenerate output, "just raise the temperature" is usually the wrong fix. The real bug is almost always upstream: a bad prompt, a broken stop condition, a context window doing something you didn't expect, and temperature is being asked to paper over it. Once you've built nucleus sampling from the algorithm up, you stop treating it as a vibe slider and start treating it for what it is: a precise statement about how much probability mass you're willing to keep. That makes it a debugging tool first, a creativity dial second.

Exercises

  1. Implement inverse CDF sampling for the Cauchy distribution. The CDF is F(x) = 0.5 + arctan(x)/pi. Generate 10,000 samples and plot the histogram against the true PDF. Notice the heavy tails (extreme values far from center).

  2. Use rejection sampling to generate samples from a Beta(2, 5) distribution using a Uniform(0, 1) proposal. Plot the accepted samples against the true Beta PDF. What is the theoretical acceptance rate?

  3. Estimate the integral of sin(x) from 0 to pi using Monte Carlo with 1,000, 10,000, and 100,000 samples. Compare the error at each level. Verify that the error scales as O(1/sqrt(N)).

  4. Implement Metropolis-Hastings to sample from a 2D distribution p(x, y) proportional to exp(-(x^2 * y^2 + x^2 + y^2 - 8x - 8y) / 2). Plot the samples and the chain trajectory. Experiment with different proposal standard deviations.

  5. Build a complete text generation demo: given a vocabulary of 10 words with logits, generate sequences of 20 tokens using (a) greedy, (b) temperature=0.7, (c) top-k=3, (d) top-p=0.9. Compare the diversity of outputs across 5 runs.

Key Terms

TermWhat people sayWhat it actually means
Sampling"Drawing random values"Generating values according to a probability distribution. The mechanism behind all generative AI
Uniform distribution"All equally likely"Every value in [a, b] has equal probability density 1/(b-a). The starting point for all sampling methods
Inverse CDF"Probability transform"F_inverse(U) converts a uniform sample into a sample from any distribution with known CDF. Exact and efficient
Rejection sampling"Propose and accept/reject"Generate from a simple proposal, accept with probability proportional to target/proposal ratio. Exact but wastes samples
Importance sampling"Reweight samples"Estimate expectations under p(x) using samples from q(x) by weighting each sample by p(x)/q(x). Core to PPO in RL
Monte Carlo"Average random samples"Approximate integrals as sample averages. Error O(1/sqrt(N)) regardless of dimension
MCMC"Random walk that converges"Construct a Markov chain whose stationary distribution is the target. Metropolis-Hastings is the foundational algorithm
Metropolis-Hastings"Accept uphill, sometimes downhill"Propose moves, accept based on density ratio. Detailed balance ensures convergence to target distribution
Gibbs sampling"One variable at a time"Update each variable from its conditional distribution holding others fixed. 100% acceptance rate
Temperature"Confidence knob"Divides logits by T before softmax. T<1 sharpens (more confident), T>1 flattens (more diverse)
Top-k sampling"Keep the k best"Zero out all but the k highest-probability tokens, renormalize, sample. Fixed candidate set size
Nucleus sampling (top-p)"Keep the probable ones"Keep the smallest set of tokens whose cumulative probability exceeds p. Adaptive candidate set size
Reparameterization trick"Move randomness outside"Write z = mu + sigma * epsilon where epsilon ~ N(0,1). Makes sampling differentiable. Essential for VAE training
Gumbel-Softmax"Soft categorical sampling"Differentiable approximation to categorical sampling using Gumbel noise + softmax with temperature
Stratified sampling"Forced coverage"Divide sample space into strata, sample from each. Always lower variance than naive Monte Carlo
Burn-in"Warm-up period"Initial MCMC samples discarded before the chain reaches its stationary distribution
Detailed balance"Reversibility condition"p(x) * T(x->y) = p(y) * T(y->x). Sufficient condition for p to be the stationary distribution of a Markov chain
Diffusion sampling"Iterative denoising"Generate data by starting from noise and applying learned denoising steps. Each step is a conditional sampling operation

Further Reading

Code

Ships With