Qm
Advanced

Probabilistic Programming Languages

A probabilistic programming language lets you write a generative model as ordinary code, draw this from a distribution, feed it into that formula, and have the language automatically run the hard inference math for you, turning weeks of custom sampler derivation into a few lines of model-writing.

Prerequisites: Bayesian Linear Regression, Gibbs Sampling

Building a custom Bayesian model the traditional way means writing down a prior, a likelihood, deriving the posterior's exact or approximate form by hand, and then hand-coding a sampler (Gibbs, Metropolis-Hastings, or otherwise) specific to that exact model, real work, redone from scratch for every new model. A probabilistic programming language (PPL), Stan, PyMC, NumPyro, removes almost all of that: you write the generative story as a short program (this parameter is drawn from a Normal, that observation is drawn from a Poisson whose rate depends on the parameter), and the language's built-in engine automatically produces posterior samples, no custom derivation or sampler-coding required.

The analogy: describing a recipe versus writing the kitchen's operating manual

Building a custom sampler by hand is like being asked, every time you want a new dish, to also write out the complete operating manual for a fully automated kitchen capable of cooking exactly that dish, an enormous amount of engineering for one specific outcome. A probabilistic programming language is like handing a general-purpose chef (the inference engine) a recipe written in plain steps, "start with 2 cups flour drawn from this distribution, then combine with...", and trusting the chef to figure out how to execute it correctly, no matter what specific recipe you hand over next. You describe what the model is; the engine figures out how to fit it.

What the code actually specifies

A PPL program specifies a generative model: a sequence of random variable declarations, each with a distribution, often depending on variables declared earlier. Schematically, in Stan-like pseudocode:

mu ~ Normal(0, 10)          # prior on the mean
sigma ~ HalfNormal(5)       # prior on the standard deviation
y ~ Normal(mu, sigma)       # likelihood: observed data y

The engine reads this declaration, automatically constructs the joint density p(μ,σ,y)=p(μ)p(σ)p(yμ,σ)p(\mu, \sigma, y) = p(\mu)\,p(\sigma)\,p(y\mid \mu,\sigma) implied by the code, exactly the Bayesian network factorization idea, just written as executable code instead of drawn as a graph, and then runs a general-purpose sampler (typically Hamiltonian Monte Carlo) against that joint density to produce samples from the posterior p(μ,σy)p(\mu,\sigma\mid y), with no model-specific math derived by the user at all.

Worked example 1: fitting a simple mean-estimation model

Suppose you observe 5 data points, y=[5.1,4.8,5.3,5.0,4.9]y = [5.1, 4.8, 5.3, 5.0, 4.9], and want the posterior for the true mean μ\mu, assuming known standard deviation σ=0.3\sigma=0.3 and prior μNormal(0,10)\mu \sim \text{Normal}(0, 10). The full derivation by hand: with a conjugate Normal-Normal setup, the exact posterior is Normal(μpost,σpost2)\text{Normal}(\mu_{\text{post}}, \sigma_{\text{post}}^2) where σpost2=(1102+50.32)1(0.01+55.6)10.018\sigma_{\text{post}}^2 = \left(\frac{1}{10^2} + \frac{5}{0.3^2}\right)^{-1} \approx \left(0.01 + 55.6\right)^{-1} \approx 0.018, and μpost=σpost2(0102+5×5.020.32)0.018×278.95.02\mu_{\text{post}} = \sigma_{\text{post}}^2\left(\frac{0}{10^2} + \frac{5\times 5.02}{0.3^2}\right) \approx 0.018 \times 278.9 \approx 5.02 (using yˉ=5.02\bar y = 5.02). A PPL user never derives this: they write the three-line model above, call the sampler, and get back thousands of posterior samples for μ\mu whose mean and spread numerically match 5.025.02 and 0.0180.135\sqrt{0.018}\approx 0.135 almost exactly, validating that the automatic engine reproduces what the hand-derivation would have given, without the user ever doing the algebra.

Worked example 2: the payoff shows up on a harder model

Now add a twist: σ\sigma is also unknown, with its own prior, and the model has a hierarchical structure, say, 5 different desks each with their own mean μk\mu_k, all drawn from a shared "meta-mean." No clean, closed-form posterior exists for this model at all; deriving a custom Gibbs sampler by hand would require working out several conditional distributions analytically, a multi-page derivation prone to errors. In a PPL, the change from the simple model is just a few more lines declaring the hierarchical structure, the sampler code itself does not change at all, because it was never written for a specific model in the first place; it works against the joint density mechanically, regardless of how many layers or how much structure the model has.

Bayes updater
00.51dashed = prior · solid = posterior
data 7/10posterior mean 0.643prior mean 0.500

The prior-to-posterior update shown above is exactly the computation a PPL automates at scale, instead of one Beta-Binomial pair worked by hand, a general engine runs the same logic for models with dozens of interacting parameters.

A probabilistic programming language lets you specify a generative model as code, priors and likelihoods, written directly, and automatically fits it using a general-purpose sampler, eliminating the need to hand-derive a custom posterior or sampler for every new model.

What this means in practice

PPLs are the practical tool of choice for real-world Bayesian modeling in finance: hierarchical models pooling information across many similar but distinct groups (desks, sectors, strategies), custom risk models with non-standard likelihoods that have no closed-form conjugate solution, and any model complex enough that hand-deriving a sampler would be impractical. The tradeoff is computational cost and diagnostic burden: general-purpose samplers like Hamiltonian Monte Carlo can still be slow on very large datasets or badly-specified models, and every fit still needs convergence diagnostics to confirm the sampler actually explored the posterior properly, the engine automates the mechanics, not the judgment.

Practice

  1. Redo the mean-estimation posterior calculation in worked example 1 with σ=1.0\sigma = 1.0 instead of 0.30.3, and note how much wider the resulting posterior becomes.
  2. Explain in one sentence why a PPL's sampler code does not need to change when the model becomes hierarchical.
  3. Name one situation where deriving a model by hand (rather than using a PPL) would still be the better choice.

The common confusion is treating a PPL's automatic sampler as a black box that always "just works" regardless of what model gets written, without checking whether the sampler actually converged. A syntactically valid model can still produce a badly-mixed or non-converged chain, for instance from a poorly identified or highly correlated parameterization, and the engine will still hand back samples that look plausible at a glance. Automating the mechanics of inference does not automate the responsibility to check convergence diagnostics before trusting the output.

Discussion

Sign in to join the discussion · reading is open to everyone

💡 Discussion rules

  1. Ask and answer about this concept. Off-topic gets removed.
  2. No homework dumps. Show what you tried first.
  3. Corrections are welcome. Cite a source when you claim an error.

Loading discussion…

Related concepts

Practice in interviews

Further reading

  • Carpenter et al., Stan: A Probabilistic Programming Language (2017)
  • Salvatier, Wiecki & Fonnesbeck, Probabilistic Programming in Python: PyMC3 (2016)
ShareTwitterLinkedIn