ml-models / Mathematical foundations / Probability and distributions intro

Probability and distributions

Every model that predicts something uncertain predicts a distribution: a classifier outputs a categorical distribution over labels, a language model one over tokens, and a regression with noise a Gaussian. This page builds the vocabulary (random variables, PMFs and PDFs, joint, marginal and conditional probability, independence, sampling) and ties each idea to torch.distributions at v2.15.0.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. Random variables, PMFs and PDFs
  5. The three distributions you meet first
  6. Joint, marginal and conditional
  7. Sampling
  8. In PyTorch
  9. Worked example
  10. Implementation
  11. Tradeoffs
  12. Check yourself
  13. Connections
  14. Read first
  15. Read next
  16. References

Problem

A spam filter cannot know whether the next email is spam. A language model cannot know which word comes next. A weather model cannot know tomorrow's temperature to the degree. What each of them can do is say how likely each outcome is, and that statement has to obey rules: the chances of all outcomes add up to one, and combining facts ("it is raining" and "you carried an umbrella") has to give consistent answers.

Probability is the language for those statements. Training a model mostly means making the probability it assigns to what actually happened as large as possible, so you need to be able to write that probability down, compute it stably, and draw samples from it.

Intuition

Picture probability as a fixed amount of sand, exactly one unit, spread over all the things that could happen. An event is a region; its probability is how much sand sits on it. A random variable is a rule that turns each outcome into a number (1 if spam, 0 if not; tomorrow's temperature), and its distribution says how the sand lands on those numbers.

When the possible values can be listed (spam or not, one of 50,000 tokens), the sand sits in piles and a probability mass function (PMF) gives each pile's height. When values are continuous (a temperature), any single exact value has zero sand on it; instead a probability density function (PDF) gives how thickly the sand is spread, and probability is the area under it over a range.

Two probability mass functions Left, a Bernoulli distribution with p = 0.3: a bar of height 0.7 at 0 and a bar of height 0.3 at 1. Right, a categorical distribution over cat, dog and bird from the logits 2.0, 1.0, 0.0: bars of height 0.665, 0.245 and 0.090. In both the bars sum to 1. Bernoulli(p = 0.3) Categorical(logits = [2, 1, 0]) 0.7 0 0.3 1 0.665 cat 0.245 dog 0.090 bird bar heights are probabilities; each panel sums to 1

Discrete distributions put mass on listed values. The categorical one is what a classifier's softmax produces.

Mechanics

Random variables, PMFs and PDFs

A discrete random variable X has a PMF p(x) = P(X = x) with every p(x) ≥ 0 and Σx p(x) = 1. A continuous one has a PDF p(x) ≥ 0 with ∫p(x) dx = 1, and P(a ≤ X ≤ b) = ∫ab p(x) dx. A density can exceed 1 (a very narrow Gaussian does); only its area is bounded. The cumulative distribution function F(x) = P(X ≤ x) works for both kinds and climbs from 0 to 1.

The three distributions you meet first

Bernoulli(p):   P(X = 1) = p,   P(X = 0) = 1 − p

One yes/no outcome: spam or not, click or not. Binary classifiers output p through a sigmoid.

Categorical(π):   P(X = k) = πk,   πk = ezk / Σj ezj

One of K outcomes. Models emit unnormalized scores z (logits) and the softmax turns them into π. PyTorch's Categorical accepts either and normalizes in log space, subtracting the logsumexp of the logits, which never overflows even when the logits are in the thousands.

Normal(μ, σ):   p(x) = (1 / σ√(2π)) · exp(−(x − μ)² / 2σ²)

The bell curve, centred at μ with spread σ. Sums of many small independent effects tend toward it (the central limit theorem), which is why measurement noise and initial weights are usually Gaussian. Its log-density is a quadratic, log p(x) = −(x − μ)²/2σ² − log σ − ½ log 2π, so maximizing a Gaussian likelihood is the same as minimizing squared error.

The standard normal density and its 68–95–99.7 bands The bell-shaped density of Normal(0, 1) drawn from −4 to 4. The area within one standard deviation of the mean is shaded darkest and holds 68.3% of the probability; within two standard deviations, 95.4%; within three, 99.7%. The peak height at 0 is 0.399. -3σ -2σ -1σ 0σ 1σ 2σ 3σ 68.3% 95.4% within 2σ 99.7% within 3σ peak 0.399 area, not height, is probability

The 68–95–99.7 rule: these areas are F(k) − F(−k) for k = 1, 2, 3, computed from the Gaussian CDF ½(1 + erf(x / √2)).

Joint, marginal and conditional

With two random variables, the joint distribution p(x, y) gives the probability of each pair. Summing out one variable gives the marginal of the other, and dividing a joint by a marginal gives a conditional:

p(x) = Σy p(x, y)     p(y | x) = p(x, y) / p(x)     p(x, y) = p(x) · p(y | x)

The last form, the product rule, chains: p(a, b, c) = p(a) p(b | a) p(c | a, b). A language model is exactly this chain over tokens, predicting each one conditioned on all the previous ones.

X and Y are independent when p(x, y) = p(x) p(y) for every pair, which is the same as saying p(y | x) = p(y): learning X tells you nothing about Y. Assuming independence is what lets a dataset's likelihood factor into a product over examples, and what lets a loss be an average over examples.

Sampling

To draw from a categorical distribution, line the probabilities up end to end on [0, 1), draw a uniform number u, and return the segment u lands in. That is inverse-CDF sampling: find the first k whose cumulative sum exceeds u. For a continuous distribution the same idea is x = F⁻¹(u), and for a Gaussian there is a shortcut: draw ε from Normal(0, 1) and return μ + σε.

Inverse-CDF sampling from a categorical distribution The interval from 0 to 1 split into three segments whose lengths are the probabilities of cat (0.665), dog (0.245) and bird (0.090). Four uniform draws, 0.1, 0.5, 0.7, 0.95, are marked as arrows; each falls in a segment and returns that label: cat, cat, dog, bird. cat 0.665 dog 0.910 bird 1.000 0 u = 0.1 u = 0.5 u = 0.7 u = 0.95 labels sit at their cumulative sums; a draw returns the segment it lands in

Draws 0.1, 0.5, 0.7, 0.95 give cat, cat, dog, bird. Over many draws each label turns up in proportion to its segment's length.

In PyTorch

torch.distributions wraps each family in an object with the same interface: sample, log_prob, entropy, and for continuous families cdf, icdf and rsample. Normal.log_prob is the quadratic above, line for line; Normal.cdf is the erf formula. Categorical.__init__ normalizes probabilities by their sum or logits by their logsumexp, and Categorical.sample hands the probabilities to torch.multinomial. Bernoulli.log_prob is the negative of binary cross-entropy with logits, which is the first sign that the usual classification losses are log-probabilities in disguise.

TermShape/ParamsMeaningSource
PMF p(x) discrete x; Σ p(x) = 1 Probability of each listed value. —
PDF p(x) continuous x; ∫ p(x) dx = 1 Density; probability is area under it over a range. —
Bernoulli probs or logits, any shape 1 with probability p, else 0. bernoulli.py
Categorical probs or logits: [..., K] One of K classes; the last dimension is the categories, the rest is the batch. categorical.py
Normal loc μ, scale σ > 0, broadcast together Gaussian with mean μ and standard deviation σ. normal.py
log_prob(value) value: sample_shape + batch_shape log p(value); what maximum-likelihood training maximizes. Categorical.log_prob
sample(shape) returns shape + batch_shape Draw values; no gradient flows through the draw. Bernoulli.sample
rsample(shape) continuous families only Draw as μ + σε so gradients reach μ and σ (the reparameterization trick). Normal.rsample
cdf / icdf value or probability F(x) = P(X ≤ x) and its inverse F⁻¹(u). Normal.cdf, icdf

Worked example

Rain and umbrellas. Out of 100 days, you record the weather and whether a passer-by carried an umbrella. Divide each count by 100 to get the joint distribution, then add along rows and columns for the marginals.

umbrellano umbrellap(weather)
rain 0.240.06 0.30
sun 0.070.63 0.70
p(umbrella)0.310.691.00
  1. Marginal. p(rain) = 0.24 + 0.06 = 0.30, and p(umbrella) = 0.24 + 0.07 = 0.31.
  2. Conditional. p(umbrella | rain) = 0.24 / 0.30 = 0.80, while p(umbrella | sun) = 0.07 / 0.70 = 0.10. Each row of conditionals sums to 1.
  3. The other direction. p(rain | umbrella) = 0.24 / 0.31 = 0.774. Seeing an umbrella raises the chance of rain from 0.30 to about 0.77. Turning one conditional into the other is Bayes' rule, the subject of a later page.
  4. Independence. If weather and umbrellas were independent, p(rain, umbrella) would equal p(rain) · p(umbrella) = 0.30 × 0.31 = 0.093. It is 0.24, so they are not independent.

Scoring a prediction. A classifier outputs logits 2, 1, 0 for cat, dog, bird. The softmax gives probabilities [0.665, 0.245, 0.090]. If the true label is dog, the log-probability of the data is log 0.245 = -1.408, and its negative, 1.408, is the cross-entropy loss for this example. Under a Bernoulli(p = 0.3), the outcome 1 has log-probability log 0.3 = -1.204.

Implementation

The same numbers from torch.distributions:

import torch
from torch.distributions import Bernoulli, Categorical, Normal

coin = Bernoulli(probs=torch.tensor(0.3))
coin.log_prob(torch.tensor(1.0))     # tensor(-1.2040), log 0.3
coin.log_prob(torch.tensor(0.0))     # tensor(-0.3567), log 0.7

animal = Categorical(logits=torch.tensor([2.0, 1.0, 0.0]))
animal.probs                         # tensor([0.6652, 0.2447, 0.0900])
animal.log_prob(torch.tensor(1))     # tensor(-1.4076), cross-entropy 1.4076
animal.entropy()                     # tensor(0.8324)
animal.sample((5,))                  # five class indices, e.g. tensor([0, 0, 1, 0, 2])

z = Normal(loc=0.0, scale=1.0)
z.log_prob(torch.tensor(2.0))        # tensor(-2.9189)
z.cdf(torch.tensor(1.96))            # tensor(0.9750)

mu = torch.tensor(1.0, requires_grad=True)
x = Normal(mu, 2.0).rsample()        # mu + 2 * eps, differentiable in mu
x.backward()
mu.grad                              # tensor(1.)

And inverse-CDF sampling without any library, as in the diagram above:

import math

def softmax(logits):
    top = max(logits)
    exps = [math.exp(z - top) for z in logits]
    total = sum(exps)
    return [e / total for e in exps]

def sample(probs, u):
    total = 0.0
    for k, p in enumerate(probs):
        total += p
        if u < total:
            return k
    return len(probs) - 1

probs = softmax([2.0, 1.0, 0.0])
[sample(probs, u) for u in [0.10, 0.50, 0.70, 0.95]]   # [0, 0, 1, 2]

Subtracting the largest logit before exponentiating changes nothing mathematically (it cancels in the ratio) but keeps exp from overflowing; PyTorch does the same inside logsumexp.

Tradeoffs

  • Work in log space. The probability of a 1,000-token sentence is a product of 1,000 numbers below 1, which underflows to 0 in floating point. Its log is a sum, which doesn't. That's why every API returns log_prob, and why you should pass logits rather than probs when you have them.
  • The Gaussian is convenient, not always right. Its tails fall off so fast that a value 6σ out is treated as nearly impossible. Data with outliers or heavy tails (incomes, latencies) is better served by Laplace, Student-t or a mixture.
  • Independence is an assumption. Treating examples as independent makes the loss an average and lets you shuffle and batch, but time series, duplicated data and leaked test examples break it, and the model's confidence ends up too high.
  • Sampling is not differentiable by default. sample cuts the gradient. Continuous families offer rsample; discrete ones need score-function estimators or relaxations such as Gumbel-softmax, both noisier.

Check yourself

Can a PDF take the value 3? Can a PMF?

A PDF can: Normal(0, 0.1) peaks near 3.99. Only the area under a density must be 1. A PMF cannot, since each value is itself a probability and must be at most 1.

Logits [5, 5, 5] and [105, 105, 105]: which categorical distributions do they give?

The same one, uniform with ⅓ each. Adding a constant to every logit cancels in the softmax.

In the umbrella table, what is p(sun | no umbrella)?

0.63 / 0.69 = 0.913.

Why does maximizing a Gaussian likelihood with fixed σ give least squares?

log p(y | μ) = −(y − μ)²/2σ² plus terms that don't depend on μ. Maximizing it over μ is minimizing (y − μ)².

Connections

References

  • Blitzstein and Hwang. Introduction to Probability, 2nd ed., chapters 2–5. CRC Press, 2019.
  • Bishop. Pattern Recognition and Machine Learning, sections 1.2 and 2.1–2.3. Springer, 2006.
  • Goodfellow, Bengio and Courville. Deep Learning, chapter 3 (probability and information theory). MIT Press, 2016.
  • Kingma and Welling. Auto-Encoding Variational Bayes. ICLR 2014. arXiv:1312.6114 (the reparameterization trick).
  • PyTorch v2.15.0: Normal, Categorical, Bernoulli.