models & model-based reasoning · conditioning and the inference problem · sample & observe · a first probabilistic program and its evaluator
2026-06-19
\[ \newcommand\given{{\,\vert\,}} \]
Learning Objectives
sample + observe. Conditioning is the evaluator’s job, not the program’s.One latent \(x\), one observation \(y\). Bayes’ rule names each factor:
| quantity | formula | in words |
|---|---|---|
| Prior | \(p(x)\) | belief about \(x\) before data |
| Likelihood | \(p(y \given x)\) | how \(y\) is generated from a given \(x\) |
| Joint | \(p(x, y) = p(x)\,p(y \given x)\) | the model, read forward as a simulator |
| Evidence | \(p(y) = \int p(x, y)\,dx\) | marginal probability of the data, the normalizer |
| Posterior | \(p(x \given y) = \dfrac{p(x)\,p(y \given x)}{p(y)}\) | belief about \(x\) after seeing \(y\) |
| Posterior predictive | \(p(\tilde{y} \given y) = \int p(\tilde{y} \given x)\,p(x \given y)\,dx\) | distribution of a new observation \(\tilde{y}\) |
No observation, no names: a generative model alone is just a joint \(p(x, y)\).
Our second model, the course “hello world”. One unknown quantity \(\mu\), one noisy measurement \(y\):
\[\mu \sim \mathcal{N}(0, 1) \qquad y \sim \mathcal{N}(\mu, 1)\]
In your notebook: write a Python function model() that simulates one \((\mu, y)\) pair, then use it to estimate \(p(y > 2)\). Hint: random.gauss(m, s).
Forward questions: easy. The whole course is the other one: given \(y = 2.3\), what is \(\mu\)?
Suppose we can generate many forward simulations from the captcha model:
Now we are given a fixed observed image \(y_{\text{obs}}\).
What extra information would be needed to turn these forward simulations into information about the posterior \(p(x \given y_{\text{obs}})\)?
A) Nothing extra: enough forward samples make the histogram of \(x\) approximate \(p(x \given y_{\text{obs}})\).
B) The likelihood \(p(y_{\text{obs}} \given x)\): how compatible the observed image is with each text \(x\).
C) Only the prior \(p(x)\): it tells us which texts are more plausible candidates.
D) Only the evidence \(p(y_{\text{obs}})\): it supplies the missing normalizing constant.
For each candidate text \(x\), the model renders a blurry image \(\operatorname{render}(x)\) and compares it with the observed image \(y_{\text{obs}}\).
The likelihood is higher when the squared pixel error is smaller:
\[ y_{\text{obs}} \sim \operatorname{Normal}(\operatorname{render}(x), \sigma), \qquad \log p(y_{\text{obs}} \given x) = \text{constant} - \frac{1}{2\sigma^2} \|y_{\text{obs}}-\operatorname{render}(x)\|^2. \]
Likelihood is a soft pixel-level match between the rendered candidate and the observed degraded text.
\[p(x \given y) \;=\; \frac{p(y \given x)\,p(x)}{p(y)}\]
The complicating crux
Bayes’ rule gives the posterior in one line. What we actually want are expectations under it, like \(P(x > 0.7 \given y)\) or \(\mathbb{E}[x \given y]\). Computing those is the hard problem the rest of the course attacks.
In rare cases prior and likelihood are conjugate: the posterior stays in the same family and is available in closed form.
The running coin’s \(\text{Beta}(1,1)\) prior with one observed flip gives \(\text{Beta}(2,1)\), density \(2x\).
Full derivation in the Appendix. Conjugacy is the exception: table of conjugate distributions.
Outside conjugacy there is no formula for \(p(x \given y)\), yet samples drawn from it still answer the queries:
\[\frac{1}{L} \sum_{\ell=1}^{L} f\!\left(x^{(\ell)}\right) \;\xrightarrow{\;L \to \infty\;}\; \int f(x)\, p(x \given y)\, dx, \qquad x^{(\ell)} \sim p(x \given y)\]
The goal of this course’s algorithms
Characterize \(p(x \given y)\) by (weighted) samples. Every evaluator we build outputs samples, not formulas.
sample + observeBack to Q1’s coin, but now we insist on conditioning, using plain Python:
The returned values of x are distributed according to…
A) the posterior \(p(x \given y{=}1)\).
B) the prior \(\text{Beta}(1,1)\) unchanged: \(x\) is drawn before the if, so keeping or dropping whole runs cannot reshape it.
C) a biased version of the posterior, because discarding rejected runs distorts the original simulation.
D) almost all mass at \(x = 1\): you kept only heads, and \(x = 1\) is the bias that makes heads certain.
if: no algebra, no integral, just the simulator plus a filter.Apply the same trick to Q2’s hello world, conditioning on \(y = 2.3\) (treat floats as ideal real numbers):
What happens when we call it?
A) As in Q4: it returns exact posterior samples, just somewhat more slowly.
B) It returns samples from the prior over x, because the condition y == 2.3 does not change how x was sampled.
C) It almost surely never returns, because under the continuous model the event y = 2.3 has probability zero.
D) It returns samples from the likelihood p(y = 2.3 | x), because the loop filters executions using the observed value.
observeWhat is a probabilistic program? (Gordon et al., 2014)
“Ordinary programs with two added constructs: the ability to draw values at random from distributions, and the ability to condition values of variables via observations.”
(sample d): draw a value from distribution \(d\) and move on.(observe d v): declare that the value \(v\) was observed from \(d\).random library makes a simulation language. observe plus an evaluator that implements conditioning makes a probabilistic programming language.observe + its return value, the inference problem.Two programs differing in one construct:
How do the denoted distributions of the return value differ?
A) They are identical: both introduce a Bernoulli variable connected to \(x\).
B) Program 🚃 also conditions on \(y\), because sampling \(y\) creates the same evidence variable that observe uses.
C) 🚲 returns \(p(x,y=1)\); 🚃 returns \(p(x,y)\).
D) 🚲 denotes the posterior \(p(x \given y{=}1)\); 🚃 denotes the prior \(p(x)\).
observe marks which variables carry data; the return value names the posterior you want.sample ≠ observe
Swapping one for the other changes the inference problem, not the style: Q6’s two programs share a joint but denote different distributions.
The program denotes:
\[ p(x \given y{=}1) \]
But one execution only produces one sampled candidate for \(x\).
At the observe, the evaluator does not draw \(y\).
It must instead ask:
\[ \text{How likely is the observed value } y=1 \text{ under this sampled } x? \]
Important
The evaluator records that compatibility in the execution’s score.
Operationally:
sample draws values;observe draws nothing;observe updates the score.For the Gaussian model, rejection tried to draw a fresh \(y'\) and test:
\[ y' = 2.3 \]
That event has probability \(0\), so the loop almost surely never returns.
observe asks a different question. For a sampled \(\mu\), it evaluates the density of the fixed observed value:
\[ w \;=\; p(2.3 \given \mu) \;=\; \frac{1}{\sqrt{2\pi}} e^{-(2.3-\mu)^2/2}. \]
Match vs score
Rejection tests equality against a probability-zero event.
observe evaluates a density and records it in the score.
We can describe the evaluator in words. How do you implement such a language, inside Python?
| stage | the PPL is… | inference gets… |
|---|---|---|
| 1 | an interpreted s-expression mini-language | a recursive eval threading weights \(\sigma\) |
| 2 | the same language on an abstract machine | pause at sample/observe; fork = deepcopy |
| 3 | Python itself + sample/observe calls |
effect handlers over PyTorch, “Pyro in 50 lines” |
Same observe, three embeddings: the spine of the course.
What inference computes
Conditioning yields a new distribution \(p(x \given y) = p(x,y)/p(y)\), not the prior the program samples forward. Inference means computing expectations under it, such as tail probabilities or posterior means; the general tool is (weighted) samples read off the unnormalized joint \(g\).
sample + observe
Probabilistic program = ordinary program + sample (draw) + observe (score data; draws nothing). Conditioning is the evaluator’s job.
Solutions
Solutions
observe conditions; an unused sample draw conditions nothing.The coin’s prior is a Beta density on \(x \in [0,1]\), normalized by the Beta function \(B\):
\[ \begin{aligned} \text{Beta}(x;\, a, b) &= \frac{x^{a-1}(1-x)^{b-1}}{B(a,b)}, \\ B(a,b) &= \int_0^1 x^{a-1}(1-x)^{b-1}\,dx = \frac{\Gamma(a)\,\Gamma(b)}{\Gamma(a+b)}. \end{aligned} \]
With prior \(\text{Beta}(x;\alpha,\beta)\) and one observed flip, algebra returns a posterior in the same family:
\[p(x \given y) \;=\; \text{Beta}(x;\; \alpha + y,\; \beta - y + 1)\]
Do not get used to this: Conjugacy is the exception.
For a renderer, a tree simulator, or your Q2 model() with a max() inside, no algebra applies.
Prior \(p(x) = x^{\alpha-1}(1-x)^{\beta-1}/B(\alpha,\beta)\) and one Bernoulli observation \(p(y \given x) = x^{y}(1-x)^{1-y}\). Their product collects the exponents:
\[p(x,y) \;=\; p(x)\,p(y \given x) \;=\; \frac{x^{(\alpha+y)-1}\,(1-x)^{(\beta-y+1)-1}}{B(\alpha,\beta)}\]
Integrate over \(x\) for the evidence; the integral is again a Beta function:
\[p(y) \;=\; \int_0^1 p(x,y)\,dx \;=\; \frac{B(\alpha+y,\; \beta-y+1)}{B(\alpha,\beta)}\]
Divide; the constant \(B(\alpha,\beta)\) cancels and a normalized Beta density remains:
\[p(x \given y) \;=\; \frac{p(x,y)}{p(y)} \;=\; \frac{x^{(\alpha+y)-1}\,(1-x)^{(\beta-y+1)-1}}{B(\alpha+y,\; \beta-y+1)} \;=\; \text{Beta}(x;\; \alpha+y,\; \beta-y+1)\]
Running coin
\(\alpha=\beta=1\), observe \(y=1\): \(B(2,1)=\tfrac12\), so \(p(x \given y) = x/\tfrac12 = 2x\), the \(\text{Beta}(2,1)\) our samplers reproduce.
Introduction to Probabilistic Programming