Introduction to Probabilistic Programming

models & model-based reasoning · conditioning and the inference problem · sample & observe · a first probabilistic program and its evaluator

Javier Burroni

2026-06-17

Today

Learning Objectives

  1. Explain what a generative model is and why the interesting questions run it backwards (inference).
  2. State Bayes’ rule, and explain why the central computational problem is computing expectations under the posterior (the queries we actually ask).
  3. Define a probabilistic program: an ordinary program + sample + observe. Conditioning is the evaluator’s job, not the program’s.
  4. Hand-execute a first probabilistic program and its weighted evaluator in Python.
  5. State the question of this course: how to implement such a language inside Python.

Section 1: Models and Model-Based Reasoning

Question 1

import random

def flip():
    x = random.random()              # bias: uniform on [0, 1]
    y = int(random.random() < x)     # one coin flip with bias x
    return y

We call flip() once and it returns 1.

What can we rationally conclude about the value x used in that call?

A) Nothing: every \(x \in [0,1]\) can produce 1, so our beliefs about \(x\) are unchanged.

B) Larger values of \(x\) are now more plausible: beliefs shift from uniform toward high \(x\).

C) That \(x > 0.5\): heads happened, so heads must have been the more likely outcome.

D) That \(x = 1\), the value under which the observation is most probable.

Vote: https://pe.app/ppls

A model is a stochastic simulator

  • A model is a stand-in for the system you care about: mice for humans, scale dams for dams, programs for coins.
  • Each run makes random choices (latents \(X\)) and produces measurable values (observations \(Y\)).
  • flip() is the textbook model, written as code:

Math \[ \begin{aligned} x &\sim \text{Beta}(1,1) \\ y &\sim \text{Bernoulli}(x) \end{aligned} \]

Code

x = random.random()
y = int(random.random() < x)

Two directions through the same model

Computer science

parameters → program → output

run it forward: simulate

Statistics

observations → model → latents

run it backward: infer


  • A model denotes the joint \(p(x, y) = p(x)\,p(y \given x)\); simulation reads it left to right.
  • Q1 asked for the other direction: characterize \(p(x \given y{=}1)\).

Model-based reasoning

The model is written forwards; the questions we ask of it run backwards.

Forward simulation computes integrals

hits = 0
for _ in range(100_000):
    x = random.random()
    y = int(random.random() < x)
    hits += (y == 1)
print(hits / 100_000)            # prints ≈ 0.5
  • Each run draws \(x \sim p(x)\), then \(y \sim p(y \given x)\): one sample of the joint, with \(y \in \{0,1\}\).
  • The printed number is the sample mean of \(y\), and a sample mean approaches its expectation, the evidence: \[\frac{1}{L}\sum_{\ell=1}^{L} y^{(\ell)} \;\longrightarrow\; \mathbb{E}[y] \;=\; p(y{=}1) \;=\; \int\! p(y{=}1 \given x)\,p(x)\,dx\]

Monte Carlo

Simulators turn integrals into averages: forward questions are just sample means. The catch returns in Section 2, where the answers we want are averages under the posterior, which forward runs never produce.

What can \(X\) and \(Y\) be?

latent \(X\) observed \(Y\)
scene description image
simulation simulator output
program source code program return value
policy + world simulator rewards
cognitive process observed behavior

For these pairs the joint \(p(X, Y)\) is realistically only denotable as a program: e.g. \(p(\text{image} \given \text{scene})\) is a renderer plus pixel noise.

One pattern, many applications (§1.3)

  • Captcha breaking: generative Captcha program, condition on the image; the posterior even calibrates ambiguity (“aG8BPY” vs “aG8RPY”).
  • Constrained procedural graphics: tree simulator + observe “do not touch the logo” → posterior over trees.
  • Program induction: prior over source code, observe input/output examples.
  • Science: condition the LHC simulation pipeline on detector outputs.

The pattern

Write the simulator you already know how to write; let conditioning answer the inverse question.

Question 2

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).

def model():
    mu = random.gauss(0, 1)
    y  = random.gauss(mu, 1)
    return mu, y

pairs = [model() for _ in range(100_000)]
print(sum(y > 2 for _, y in pairs) / len(pairs))   # ≈ 0.079

Forward questions: easy. The whole course is the other one: given \(y = 2.3\), what is \(\mu\)?

Summary

Models run both ways

A model is a stochastic simulator denoting a joint \(p(x,y)\). Simulation runs it forward; the interesting questions (Q1!) run it backward.

Next class

Reading

Before next class, read:

  • Chapter 2.1: Syntax
  • Chapter 2.2.1: Let forms
  • Chapter 2.2.2: For loops
  • Chapter 4.1: Likelihood Weighting
    • especially 4.1.1 and 4.1.3

Focus while reading

Pay attention to:

  • the difference between sample and observe;
  • what information an execution produces besides a return value;
  • why fixed loops make the first-order language easier to evaluate;
  • how likelihood weighting uses repeated executions.

Questions: Answer Key

Solutions

  • Q1: B (larger \(x\) becomes more plausible). Observing \(y{=}1\) tilts beliefs toward larger \(x\), since the posterior is proportional to \(x\); A is the no-update fallacy, C and D over-read a single flip.
  • Q2: Freeform. Simulate mu = gauss(0,1); y = gauss(mu,1) many times and take the fraction with \(y>2\), giving \(p(y>2)\approx 0.079\).