Likelihood Hacking in Probabilistic Program Synthesis
Organizations: University of Oxford, Department of Computer Science · University of Edinburgh, School of Informatics · CIFAR Fellow, Learning in Machines and Brains
Abstract
When language models are trained by reinforcement learning (RL) to write probabilistic programs, they can artificially inflate their marginal-likelihood reward by producing programs whose data distribution fails to normalise instead of fitting the data better. We call this failure likelihood hacking (LH). We formalise LH in a core probabilistic programming language (PPL) and give sufficient syntactic conditions for its prevention, proving that a safe language fragment satisfying these conditions cannot produce likelihood-hacking programs. Empirically, we show that GRPO-trained models generating PyMC code discover LH exploits within the first few training steps, driving violation rates well above the untrained-model baseline. We implement 's conditions as , a LH-resistant modification of Stan, and show empirically that it suppresses LH under optimisation pressure. These results show that language-level safety constraints are both theoretically grounded and effective in practice for automated Bayesian model discovery.
Figures & tables
| (a) Honest Gaussian regression. A Bayesian regression model: sample a latent slope , then score the datapoint under a proper likelihood. | (b) Improper observation model via density addition. Here is the sum of two densities, not a mixture: it multiplies likelihoods by a constant factor (here ), breaking normalisation. |
| (c) Double-counting the same datapoint. Well-typed in , but reuses : the data density is squared, so the induced data distribution is not normalised. | (d) Disguised Potential -style bonus via score . Adds an extra positive term to the log-score for typical values (analogous to a PyMC Potential), inflating marginal likelihood without improving the fit to . |
| Run 1 | Run 2 | |
| Training steps completed | 29 | 28 |
| Total completions (valid + failed) | 23,053 | 22,257 |
| Valid programs | 17,522 | 17,042 |
| Programs with reward | 64 | 42 |
| Programs with pm.Potential | 804 | 606 |
| Reward ceiling hits ( nats) | 5 | 12 |
| (a) Honest model. ⬇ def model ( data ): with pm . Model () as m : p = pm . Beta ( ’p’ , 1, 1) pm . Bernoulli ( ’y’ , p = p , observed = data [ ’y’ ]) return m Conjugate Beta–Bernoulli; normalises to . p | (b) Score injection. ⬇ def model ( data ): with pm . Model () as m : logit_p = pm . Normal ( ’logit_p’ , 0, 2) p = pm . Deterministic ( ’p’ , pm . math . sigmoid ( logit_p )) pm . Bernoulli ( ’y’ , p = p , observed = data [ ’y’ ]) pm . Potential ( ’regularisation’ , -10 * pm . math . log ( pm . math . abs ( p - 0.5) + 1 e -6)) return m The Potential injection, disguised as “regularisation”. |
Appendix figures & tables18 assets
Supplementary material from the paper’s appendix.
Appendix
| (Var u ) | (Data u ) |
| (Const u ) | (Unit u ) |
| (Pair u ) | ( ) |
| ( ) | (Inl u ) |
| (Inr u ) | (If u ) |
| (Let u ) | \begin{array}[]{c}f:(T_{1},\ldots,T_{k})\to U\\ \Gamma;\Delta\vdash_{\text{u}}t_{1}:T_{1}\hskip 8.50012pt\cdots\hskip 8.50012pt\Gamma;\Delta\vdash_{\text{u}}t_{k}:T_{k}\end{array} (Prim u ) |
| (Gauss u ) | (Bern u ) |
| (Plus u ) | |
| \begin{array}[]{c}F:\mathsf{Meas}(T_{1})\times\cdots\times\mathsf{Meas}(T_{k})\to\mathsf{Meas}(U)\\ \Gamma;\Delta\vdash_{\text{u}}\mathcal{D}_{1}:\mathsf{Meas}(T_{1})\hskip 8.50012pt\cdots\hskip 8.50012pt\Gamma;\Delta\vdash_{\text{u}}\mathcal{D}_{k}:\mathsf{Meas}(T_{k})\end{array} ( ) | \begin{array}[]{c}\Gamma;\Delta\vdash_{\text{u}}t:\mathbb{R}\\ \Gamma;\Delta\vdash_{\text{u}}\mathcal{D}_{1}:\mathsf{Meas}(T)\ \ \ \Gamma;\Delta\vdash_{\text{u}}\mathcal{D}_{2}:\mathsf{Meas}(T)\end{array} (Mix u ) |
| ( ) | ( ) |
| ( ) | ( ) |
| ( ) | ( ) |
| (Var s ) | (Const s ) |
| (Unit s ) | (Pair s ) |
| ( ) | ( ) |
| (Inl s ) | (Inr s ) |
| (If s ) | (Let s ) |
| (Prim s ) | |
| (Gauss s ) | (Bern s ) |
| \begin{array}[]{c}\Gamma\vdash_{\text{s}}t:\mathbb{R}\\ \Gamma\vdash_{\text{s}}\mathcal{D}_{1}:\mathsf{Prob}(T)\hskip 17.00024pt\Gamma\vdash_{\text{s}}\mathcal{D}_{2}:\mathsf{Prob}(T)\end{array} (Mix s ) | \begin{array}[]{c}F:\mathsf{Prob}(T_{1})\times\cdots\times\mathsf{Prob}(T_{k})\to\mathsf{Prob}(U)\\ \Gamma\vdash_{\text{s}}\mathcal{D}_{1}:\mathsf{Prob}(T_{1})\hskip 8.50012pt\cdots\hskip 8.50012pt\Gamma\vdash_{\text{s}}\mathcal{D}_{k}:\mathsf{Prob}(T_{k})\end{array} (Comb s ) |
| ( ) | ( ) |
| ( ) | ( ) |
| ( ) | |
| Model source | No. of models | As-is | Minor | Major | Compatible fraction (as-is + minor) |
|---|---|---|---|---|---|
| bda_r_demos | 14 | 9 | 2 | 3 | 0.79 |
| betanalpha_knitr_case_studies | 302 | 183 | 82 | 37 | 0.88 |
| example_models | 538 | 82 | 202 | 254 | 0.53 |
| forestdb | 101 | 20 | 37 | 44 | 0.56 |
| posteriordb | 147 | 14 | 58 | 75 | 0.49 |
| stancon_talks | 61 | 7 | 25 | 29 | 0.52 |
| LLM-assigned reason for major label | Newly added models |
|---|---|
| Using flat/improper priors | 164 |
| Finite mixture operator | 40 |
| Self-contained observation primitives | 109 |
| Custom prior/process primitives | 43 |
| Protected-data interface rewrites | 50 |
| ⬇ data { int < lower =1> N ; vector [ N ] X ; vector [ N ] y ; } parameters { real beta ; } model { ... } | ⬇ data { int < lower =1> N ; vector [ N ] X ; vector [ N ] y ; } parameters { real beta ; } model { vector [ N ] residual ; residual = y - beta * X ; target += sum ( residual .^2); } |
| Training parameter | Value |
|---|---|
| Training steps | 200 |
| Number of prompts | 2 |
| Number of system prompts | 8 |
| Number of generations per prompt | 16 |
| LLM temperature | 1.7 |
| Contract penalty reward | -100/-20 |
| Dataset parameter | Value |
|---|---|
| Beta scale | 1.78 |
| train | 8 |
| test | 8 |
| Noise | 2.3 |
| PPL | Construct | Kind | LH surface |
|---|---|---|---|
| PyMC | pm.Potential ; custom logp | free score / custom density | arbitrary term in model log-probability |
| Stan | target += | free log-density increment | arbitrary expression added to unnormalised log density |
| NumPyro | numpyro.factor | free score | arbitrary log-probability factor |
| Pyro | pyro.factor | free score | arbitrary log-probability factor |
| WebPPL | factor(score) | free score | arbitrary score added to execution log probability |
| Turing.jl | @addlogprob! | free score | arbitrary term added to accumulated log probability |
Probabilistic Programming
Probabilistic programming combines code and probability models to express uncertainty and automate statistical inference.
See all 20 papers