├── anglican ├── .gitignore ├── samples │ ├── geo │ │ └── README │ ├── gmm │ │ └── README │ ├── rw │ │ └── README │ └── dpmm │ │ └── README ├── project.clj ├── README.md ├── icml.clj └── training_data.txt ├── .gitignore ├── samples_produced └── README ├── LICENSE ├── example_geometric.py ├── evaluation.py ├── example_dirichlet.py ├── example_gmm.py ├── evaluation_geometric.ipynb ├── evaluation_geometric_icml2022.ipynb ├── evaluation_icml2022.py ├── example_walk.py ├── README.md ├── ppl.py ├── infer.py ├── evaluation_dirichlet.ipynb └── evaluation_dirichlet_icml2022.ipynb /anglican/.gitignore: -------------------------------------------------------------------------------- 1 | .gorilla-port 2 | .nrepl-port 3 | target/ 4 | 5 | -------------------------------------------------------------------------------- /anglican/samples/geo/README: -------------------------------------------------------------------------------- 1 | This directory is for samples produced by Anglican. -------------------------------------------------------------------------------- /anglican/samples/gmm/README: -------------------------------------------------------------------------------- 1 | This directory is for samples produced by Anglican. -------------------------------------------------------------------------------- /anglican/samples/rw/README: -------------------------------------------------------------------------------- 1 | This directory is for samples produced by Anglican. -------------------------------------------------------------------------------- /anglican/samples/dpmm/README: -------------------------------------------------------------------------------- 1 | This directory is for samples produced by Anglican. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | .ipynb_checkpoints/ 3 | .vscode/ 4 | __pycache__/ 5 | anglican_samples/ 6 | samples_produced/ 7 | lookahead_samples/ 8 | -------------------------------------------------------------------------------- /samples_produced/README: -------------------------------------------------------------------------------- 1 | This directory is for samples produced by example programs. 2 | The serialization format is Python's `pickle`. 3 | 4 | -------------------------------------------------------------------------------- /anglican/project.clj: -------------------------------------------------------------------------------- 1 | (defproject anglican-examples "0.2.1-SNAPSHOT" 2 | :description "Anglican program examples expressed as Gorilla repl instances" 3 | :url "http://www.robots.ox.ac.uk/~fwood/anglican/" 4 | :license {:name "GNU General Public License Version 3; Other commercial licenses available." 5 | :url "http://www.gnu.org/licenses/gpl.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"] 7 | [org.clojure/data.csv "0.1.4"] 8 | [clj-auto-diff "0.1.3"] 9 | [anglican "1.0.0"] 10 | [plotly-clj "0.1.1"]] 11 | :plugins [[dtolpin/lein-gorilla "0.4.1-SNAPSHOT"]] 12 | :target-path "target/%s" 13 | :profiles {:uberjar {:aot :all}}) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fabian Zaiser, Carol Mak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /anglican/README.md: -------------------------------------------------------------------------------- 1 | # Anglican Experiments 2 | 3 | This folder is for producing the Anglican samples for comparison with our algorithm. 4 | 5 | It is a fork of the probabilistic programming language [Anglican](https://bitbucket.org/probprog/anglican/)’s [examples repository](https://bitbucket.org/probprog/anglican-examples/src/master/) with browser-based interaction via [Gorilla REPL](http://gorilla-repl.org/). 6 | 7 | ## Install 8 | 9 | 1. Install the Java Runtime Environment **Version 8** (on Ubuntu: `sudo apt install openjdk-8-jdk`) and select it (on Ubuntu: `sudo update-alternatives --config java`). Note that unfortunately, Anglican does not support newer Java versions. 10 | 2. Download and install [Leiningen](http://leiningen.org/). 11 | 12 | ## Usage 13 | 14 | 1. In this folder, run the command `lein gorilla`. 15 | 2. Open the indicated local website in a browser. 16 | 3. Hit `Alt+G Alt+L` and select `icml.clj` to load and run the worksheet. 17 | 4. Hit `Shift+Enter` to evaluate each code segment in order. The final computations will take several hours. 18 | 19 | The generated samples will be stored in the `samples/` directory. 20 | Please refer to the parent `../README.md` for how to evaluate the results. 21 | 22 | *Note:* Anglican does not seem to provide a way to set the random seed. 23 | Therefore, each execution can be different, which may explain slight discrepancies with the numbers reported in the paper. 24 | -------------------------------------------------------------------------------- /example_geometric.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from torch.distributions import Uniform 3 | 4 | from infer import run_inference, run_inference_icml2022 5 | from ppl import ProbCtx, run_prob_prog 6 | 7 | 8 | def geometric(ctx: ProbCtx) -> int: 9 | """Describes a geometric distribution""" 10 | sample = ctx.sample(Uniform(0.0, 1.0), is_cont=False) 11 | ctx.constrain(sample, 0.0, 1.0) 12 | if sample < 0.2: 13 | return 1 14 | else: 15 | return 1 + geometric(ctx) 16 | 17 | 18 | if __name__ == "__main__": 19 | count = 1_000 20 | repetitions = 10 21 | if len(sys.argv) > 1 and sys.argv[1] == "icml2022": 22 | configs = [ 23 | (L, alpha, K, eps) 24 | for L in [5, 2] 25 | for eps in [0.1] 26 | for alpha in [1.0, 0.5, 0.1] 27 | for K in [0, 1, 2] 28 | ] 29 | for rep in range(repetitions): 30 | for L, alpha, K, eps in configs: 31 | print( 32 | f"REPETITION {rep+1}/{repetitions} ({eps=}, {L=}, {alpha=}, {K=})" 33 | ) 34 | run_inference_icml2022( 35 | lambda trace: run_prob_prog(geometric, trace=trace), 36 | name=f"geometric_{rep}", 37 | count=count, 38 | burnin=0, # 100, 39 | L=L, 40 | eps=eps, 41 | K=K, 42 | alpha=alpha, 43 | seed=rep, 44 | ) 45 | else: 46 | for rep in range(repetitions): 47 | print(f"REPETITION {rep+1}/{repetitions}") 48 | run_inference( 49 | lambda trace: run_prob_prog(geometric, trace=trace), 50 | name=f"geometric{rep}", 51 | count=count, 52 | burnin=100, 53 | leapfrog_steps=5, 54 | eps=0.1, 55 | seed=rep, 56 | ) 57 | -------------------------------------------------------------------------------- /evaluation.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | import seaborn as sns 3 | 4 | # Use type 42 (TrueType) fonts instead of the default Type 3 fonts 5 | matplotlib.rcParams["pdf.fonttype"] = 42 6 | 7 | # Make plots more accessible: 8 | sns.set_palette("colorblind") 9 | 10 | palette = { 11 | "ours": "C0", 12 | "LMH": "C2", 13 | "PGibbs": "C1", 14 | "RMH": "C3", 15 | "IPMCMC": "C5", 16 | "ground truth": "C4", 17 | "Pyro HMC": "C8", 18 | "Pyro NUTS": "C9", 19 | } 20 | 21 | anglican_methods = ["lmh", "pgibbs", "rmh", "ipmcmc"] # replace with [] to disable 22 | all_methods = ["hmc", "is"] + anglican_methods 23 | compared_methods = ["hmc", "lmh", "pgibbs", "rmh"] if anglican_methods else ["hmc"] 24 | 25 | method_name = { 26 | "hmc": "ours", 27 | "is": "IS", 28 | "lmh": "LMH", 29 | "ipmcmc": "IPMCMC", 30 | "pgibbs": "PGibbs", 31 | "rmh": "RMH", 32 | } 33 | 34 | 35 | def parse_anglican_timings(filename: str) -> dict: 36 | timings = {} 37 | with open(filename) as f: 38 | method = None 39 | while True: 40 | line = f.readline().strip() 41 | if line == "": 42 | break 43 | words = line.split() 44 | if len(words) == 1: 45 | method = line 46 | timings[method] = [] 47 | continue 48 | if words[1] == "msecs": 49 | timings[method].append(float(words[0]) / 1000) 50 | return timings 51 | 52 | 53 | def thin_list(l: list, target_size: int) -> list: 54 | size = len(l) 55 | assert size >= target_size 56 | result = [] 57 | for i in range(target_size): 58 | result.append(l[i * size // target_size]) 59 | return result 60 | 61 | 62 | def thin_runs(all_methods: list, runs: list) -> list: 63 | thinned_runs = [] 64 | for run in runs: 65 | thinned_runs.append({}) 66 | N = len(run["hmc"]["samples"]) 67 | for method in all_methods: 68 | thinned_runs[-1][method] = thin_list(run[method]["samples"], N) 69 | return thinned_runs 70 | 71 | 72 | def collect_values(all_methods: list, thinned_runs: list) -> dict: 73 | values = {m: [] for m in all_methods} 74 | for run in thinned_runs: 75 | N = len(run["hmc"]) 76 | for method in all_methods: 77 | values[method] += run[method] 78 | return values 79 | 80 | 81 | def collect_chains(all_methods: list, thinned_runs: list) -> dict: 82 | chains = {m: [] for m in all_methods} 83 | for run in thinned_runs: 84 | for method in all_methods: 85 | chains[method].append(run[method]) 86 | return chains 87 | 88 | 89 | def print_running_time(all_methods: list, runs: list, thinned_runs: list): 90 | print("\nRunning times:") 91 | for method in all_methods: 92 | running_time = sum(run[method]["time"] for run in runs) 93 | count = sum(len(run[method]) for run in thinned_runs) 94 | per_sample = running_time / count 95 | print( 96 | f"{method}: {running_time:.2f}s {per_sample:.4f}s per sample (after thinning)" 97 | ) 98 | -------------------------------------------------------------------------------- /example_dirichlet.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import torch 4 | from torch.distributions import Beta, Normal 5 | 6 | import example_gmm 7 | from infer import run_inference, run_inference_icml2022 8 | from ppl import ProbCtx, run_prob_prog 9 | 10 | 11 | def dp(ctx: ProbCtx, alpha, dims): 12 | """Stick-breaking method for Dirichlet processes""" 13 | stick = 1.0 14 | beta = 0.0 15 | cumprod = 1.0 16 | weights = [] 17 | means = [] 18 | while stick > 0.01: 19 | cumprod *= 1 - beta 20 | beta_sample = ctx.sample(Beta(1, alpha), is_cont=False) 21 | ctx.constrain(beta_sample, 0.0, 1.0) 22 | beta = beta_sample.item() 23 | theta = ctx.sample_n(dims, standard_normal, is_cont=True) 24 | weights.append(beta * cumprod) 25 | means.append(theta) 26 | stick -= beta * cumprod 27 | weights_tensor = torch.tensor(weights) 28 | # Means should be sampled from [0,100]. 29 | # As in the GMM example we use the reparametrization with the standard normal 30 | # to avoid discontinuities. 31 | means_tensor = 100 * standard_normal.cdf(torch.stack(means)) 32 | return weights_tensor, means_tensor 33 | 34 | 35 | def loglikelihoods(weights, means, data): 36 | (K, d) = means.size() 37 | N = len(data) 38 | liks = Normal(means.view(1, K, d).expand(N, K, d), std).log_prob( 39 | data.view(N, 1, d).expand(N, K, d) 40 | ) 41 | liks = torch.sum(liks, dim=2) + torch.log(weights).view(1, K).expand(N, K) 42 | return torch.logsumexp(liks, dim=1) 43 | 44 | 45 | def loglikelihood(weights, means, data): 46 | return torch.sum(loglikelihoods(weights, means, data)) 47 | 48 | 49 | standard_normal = Normal(0, 1) 50 | dims = 3 51 | num_training_data = 200 52 | num_test_data = 50 53 | true_alpha = 5.0 54 | true_weights = torch.ones((example_gmm.num_mixtures)) / example_gmm.num_mixtures 55 | true_means = example_gmm.data_means 56 | std = example_gmm.std 57 | 58 | training_data = example_gmm.training_data 59 | test_data = example_gmm.test_data 60 | 61 | 62 | def dp_mixture(ctx: ProbCtx): 63 | """Dirichlet Process Mixture Model from the paper""" 64 | weights, means = dp(ctx, true_alpha, dims) 65 | lik = loglikelihood(weights, means, training_data) 66 | ctx.score_log(lik) 67 | return weights.tolist(), means.tolist() 68 | 69 | 70 | if __name__ == "__main__": 71 | print( 72 | f"True training LPPD: {loglikelihood(true_weights, true_means, training_data)}" 73 | ) 74 | print(f"True test LPPD: {loglikelihood(true_weights, true_means, test_data)}") 75 | repetitions = 10 76 | if len(sys.argv) > 1 and sys.argv[1] == "icml2022": 77 | configs = [ 78 | (L, alpha, K, eps) 79 | for L in [20] 80 | for eps in [0.05] 81 | for alpha in [1.0, 0.5] 82 | for K in [0, 1, 2] 83 | ] 84 | for rep in range(repetitions): 85 | for L, alpha, K, eps in configs: 86 | print( 87 | f"REPETITION {rep+1}/{repetitions} ({eps=}, {L=}, {alpha=}, {K=})" 88 | ) 89 | run_inference_icml2022( 90 | lambda trace: run_prob_prog(dp_mixture, trace=trace), 91 | name=f"dp_mixture_gmm_{rep}", 92 | count=150, 93 | burnin=0, # 50, 94 | eps=eps, 95 | L=L, 96 | K=K, 97 | alpha=alpha, 98 | seed=rep, 99 | ) 100 | else: 101 | for rep in range(repetitions): 102 | print(f"REPETITION {rep+1}/{repetitions}") 103 | run_inference( 104 | lambda trace: run_prob_prog(dp_mixture, trace=trace), 105 | name=f"dp_mixture_gmm_{rep}", 106 | count=100, 107 | burnin=50, 108 | eps=0.05, 109 | leapfrog_steps=20, 110 | seed=rep, 111 | ) 112 | -------------------------------------------------------------------------------- /example_gmm.py: -------------------------------------------------------------------------------- 1 | import math 2 | import sys 3 | 4 | import torch 5 | from torch.distributions import Normal, Poisson, Uniform 6 | 7 | from infer import run_inference, run_inference_icml2022 8 | from ppl import ProbCtx, run_prob_prog 9 | 10 | 11 | def loglikelihoods(means, data): 12 | (K, d) = means.size() 13 | N = len(data) 14 | liks = Normal(means.view(1, K, d).expand(N, K, d), std).log_prob( 15 | data.view(N, 1, d).expand(N, K, d) 16 | ) 17 | liks = torch.sum(liks, dim=2) 18 | return torch.logsumexp(liks, dim=1) - torch.log(torch.tensor(float(K))) 19 | 20 | 21 | def loglikelihood(means, data): 22 | return torch.sum(loglikelihoods(means, data)) 23 | 24 | 25 | # Data for the following more complex GMM example 26 | torch.manual_seed(0) 27 | num_data = 200 28 | num_mixtures = 9 29 | dims = 3 30 | data_means = Uniform(0.0, 100.0).sample((num_mixtures, dims)) 31 | """This random draw gives this result on torch 1.6.0: 32 | 33 | data_means = torch.tensor( 34 | [ 35 | [49.6256599426, 76.8221817017, 8.8477430344], 36 | [13.2030487061, 30.7422809601, 63.4078674316], 37 | [49.0093421936, 89.6444702148, 45.5627975464], 38 | [63.2306289673, 34.8893470764, 40.1717300415], 39 | [2.2325754166, 16.8858947754, 29.3888454437], 40 | [51.8521804810, 69.7667617798, 80.0011367798], 41 | [16.1029453278, 28.2268581390, 68.1608581543], 42 | [91.5193939209, 39.7099914551, 87.4155883789], 43 | [41.9408340454, 55.2907066345, 95.2738113403], 44 | ] 45 | ) 46 | """ 47 | std = 10.0 48 | 49 | 50 | def sample_prior(num_samples): 51 | return torch.stack( 52 | [ 53 | Normal( 54 | data_means[math.floor(torch.rand(()).item() * num_mixtures)], 55 | torch.tensor(std), 56 | ).sample(()) 57 | for n in range(num_samples) 58 | ] 59 | ) 60 | 61 | 62 | training_data = sample_prior(num_data) 63 | num_test_data = 50 64 | test_data = sample_prior(num_test_data) 65 | standard_normal = Normal(0, 1) 66 | 67 | 68 | def gmm(ctx: ProbCtx): 69 | """Gaussian Mixture Model from the paper""" 70 | poisson = ctx.sample(dist=Poisson(10), is_cont=False) 71 | ctx.constrain(poisson, geq=0) 72 | K = math.floor(poisson.item()) + 1 73 | # The following statement samples from Uniform(0,1) in a continuous way. 74 | # Sampling directly from Uniform(0,1) would lead to discontinuities at 0 and 1. 75 | # Instead, we sample from the standard normal and apply its CDF. 76 | # This is a standard reparametrization. 77 | means = standard_normal.cdf(ctx.sample_n(K * dims, standard_normal, is_cont=True)) 78 | means = means.reshape(K, dims) * 100 79 | lik = loglikelihood(means, training_data) 80 | ctx.score_log(lik) 81 | return means.tolist() 82 | 83 | 84 | if __name__ == "__main__": 85 | print(f"True training LPPD: {loglikelihood(data_means, training_data)}") 86 | print(f"True test LPPD: {loglikelihood(data_means, test_data)}") 87 | repetitions = 10 88 | count = 1_000 89 | if len(sys.argv) > 1 and sys.argv[1] == "icml2022": 90 | configs = [ 91 | (L, alpha, K, eps) 92 | for L in [25] 93 | for eps in [0.05] 94 | for alpha in [1.0, 0.5] 95 | for K in [0, 1, 2] 96 | ] 97 | for rep in range(repetitions): 98 | for L, alpha, K, eps in configs: 99 | print( 100 | f"REPETITION {rep+1}/{repetitions} ({eps=}, {L=}, {alpha=}, {K=})" 101 | ) 102 | run_inference_icml2022( 103 | lambda trace: run_prob_prog(gmm, trace=trace), 104 | name=f"gmm_{rep}", 105 | count=count, 106 | burnin=0, # 100, 107 | eps=eps, 108 | L=L, 109 | K=K, 110 | alpha=alpha, 111 | seed=rep, 112 | ) 113 | else: 114 | for rep in range(repetitions): 115 | print(f"REPETITION {rep+1}/{repetitions}") 116 | run_inference( 117 | lambda trace: run_prob_prog(gmm, trace=trace), 118 | count=count, 119 | burnin=100, 120 | eps=0.05, 121 | leapfrog_steps=50, 122 | name=f"gmm_{rep}", 123 | seed=rep, 124 | ) 125 | -------------------------------------------------------------------------------- /evaluation_geometric.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Geometric distribution example" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "tags": [] 15 | }, 16 | "outputs": [], 17 | "source": [ 18 | "# Load generated data from file:\n", 19 | "import pickle\n", 20 | "\n", 21 | "from evaluation import *\n", 22 | "\n", 23 | "runs = []\n", 24 | "num_chains = 10\n", 25 | "for i in range(num_chains):\n", 26 | " with open(f\"samples_produced/geometric{i}__count1000_eps0.1_leapfrogsteps5.pickle\", \"rb\") as f:\n", 27 | " runs.append(pickle.load(f))" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 2, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "# Read Anglican files:\n", 37 | "for i in range(num_chains):\n", 38 | " for method in anglican_methods:\n", 39 | " runs[i][method] = {}\n", 40 | " with open(f\"anglican_samples/geo/{method}{i}.txt\") as f:\n", 41 | " contents = f.read()\n", 42 | " # Skip parentheses:\n", 43 | " contents = contents[1:-1]\n", 44 | " # Extract numbers:\n", 45 | " numbers = [int(x) for x in contents.split()]\n", 46 | " runs[i][method][\"samples\"] = numbers\n", 47 | "\n", 48 | "# Read timings:\n", 49 | "if anglican_methods:\n", 50 | " timings = parse_anglican_timings(\"anglican_samples/geo/timing.txt\")\n", 51 | " for method in anglican_methods:\n", 52 | " for i in range(len(runs)):\n", 53 | " runs[i][method][\"time\"] = timings[method][i]" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "name": "stdout", 63 | "output_type": "stream", 64 | "text": [ 65 | "\n", 66 | "Running times:\n", 67 | "hmc: 405.90s 0.0406s per sample (after thinning)\n", 68 | "is: 68.15s 0.0068s per sample (after thinning)\n", 69 | "lmh: 3.42s 0.0003s per sample (after thinning)\n", 70 | "pgibbs: 1.43s 0.0001s per sample (after thinning)\n", 71 | "rmh: 4.87s 0.0005s per sample (after thinning)\n", 72 | "ipmcmc: 1.07s 0.0001s per sample (after thinning)\n" 73 | ] 74 | } 75 | ], 76 | "source": [ 77 | "thinned_runs = thin_runs(all_methods, runs)\n", 78 | "values = collect_values(all_methods, thinned_runs)\n", 79 | "print_running_time(all_methods, runs, thinned_runs)" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 4, 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "name": "stdout", 89 | "output_type": "stream", 90 | "text": [ 91 | "Total variation distance from the ground truth:\n", 92 | "hmc: 0.0136\n", 93 | "is: 0.0165\n", 94 | "lmh: 0.0224\n", 95 | "pgibbs: 0.0158\n", 96 | "rmh: 0.0196\n", 97 | "ipmcmc: 0.0164\n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "def total_variational_distance(samples, p = 0.2):\n", 103 | " samples = sorted(samples)\n", 104 | " N = len(samples)\n", 105 | " mx = int(samples[-1])\n", 106 | " freq = [0 for _ in range(mx + 1)]\n", 107 | " for sample in samples:\n", 108 | " freq[int(sample)] += 1\n", 109 | " dist = 0\n", 110 | " for x in range(1,mx + 1):\n", 111 | " dist += abs(freq[x] / N - (1 - p)**(x-1)*p)\n", 112 | " dist += (1 - p)**mx # unsampled tail\n", 113 | " return dist / 2\n", 114 | "\n", 115 | "print(\"Total variation distance from the ground truth:\")\n", 116 | "for method in all_methods:\n", 117 | " print(f\"{method}: {total_variational_distance(values[method]):.4f}\")" 118 | ] 119 | } 120 | ], 121 | "metadata": { 122 | "kernelspec": { 123 | "display_name": "Python 3", 124 | "language": "python", 125 | "name": "python3" 126 | }, 127 | "language_info": { 128 | "codemirror_mode": { 129 | "name": "ipython", 130 | "version": 3 131 | }, 132 | "file_extension": ".py", 133 | "mimetype": "text/x-python", 134 | "name": "python", 135 | "nbconvert_exporter": "python", 136 | "pygments_lexer": "ipython3", 137 | "version": "3.8.10" 138 | }, 139 | "vscode": { 140 | "interpreter": { 141 | "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" 142 | } 143 | } 144 | }, 145 | "nbformat": 4, 146 | "nbformat_minor": 2 147 | } 148 | -------------------------------------------------------------------------------- /evaluation_geometric_icml2022.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Geometric distribution example" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "tags": [] 15 | }, 16 | "outputs": [], 17 | "source": [ 18 | "# Load generated data from file:\n", 19 | "import pickle\n", 20 | "\n", 21 | "from evaluation_icml2022 import *\n", 22 | "\n", 23 | "experiments = {}\n", 24 | "num_chains = 10\n", 25 | "configs = [\n", 26 | " (L, alpha, K) for L in [5, 2] for alpha in [1.0, 0.5, 0.1] for K in [0]\n", 27 | "]\n", 28 | "for L, alpha, K in configs:\n", 29 | " key = toconfigstr(L, alpha, K)\n", 30 | " experiments[key] = []\n", 31 | " for i in range(num_chains):\n", 32 | " filename = f\"lookahead_samples/geometric_{i}__count1000_eps0.1_L{L}_alpha{alpha}_K{K}.pickle\"\n", 33 | " with open(filename, \"rb\") as f:\n", 34 | " experiments[key].append(pickle.load(f))" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 2, 40 | "metadata": {}, 41 | "outputs": [ 42 | { 43 | "name": "stdout", 44 | "output_type": "stream", 45 | "text": [ 46 | "L=5\n", 47 | "npdhmc: 355.91s 0.0356s per sample (after thinning)\n", 48 | "L=5, α=0.5\n", 49 | "npdhmc-persistent: 362.65s 0.0363s per sample (after thinning)\n", 50 | "L=5, α=0.1\n", 51 | "npdhmc-persistent: 366.57s 0.0367s per sample (after thinning)\n", 52 | "L=2\n", 53 | "npdhmc: 152.71s 0.0153s per sample (after thinning)\n", 54 | "L=2, α=0.5\n", 55 | "npdhmc-persistent: 150.25s 0.0150s per sample (after thinning)\n", 56 | "L=2, α=0.1\n", 57 | "npdhmc-persistent: 149.60s 0.0150s per sample (after thinning)\n" 58 | ] 59 | } 60 | ], 61 | "source": [ 62 | "values = {}\n", 63 | "chains = {}\n", 64 | "for config, runs in experiments.items():\n", 65 | " print(f\"{config}\")\n", 66 | " thinned_runs = thin_runs(runs)\n", 67 | " chains.update(collect_chains(thinned_runs, config=config))\n", 68 | " values.update(collect_values(thinned_runs, config=config))\n", 69 | " print_running_time(runs, thinned_runs)" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 3, 75 | "metadata": {}, 76 | "outputs": [ 77 | { 78 | "name": "stdout", 79 | "output_type": "stream", 80 | "text": [ 81 | "Total variation distance from the ground truth (mean over all chains):\n", 82 | "NP-DHMC (L=5): 0.0524 +- 0.0069 (std)\n", 83 | "NP-DHMC pers. (L=5, α=0.5): 0.0464 +- 0.0074 (std)\n", 84 | "NP-DHMC pers. (L=5, α=0.1): 0.0461 +- 0.0083 (std)\n", 85 | "NP-DHMC (L=2): 0.0768 +- 0.0181 (std)\n", 86 | "NP-DHMC pers. (L=2, α=0.5): 0.0570 +- 0.0115 (std)\n", 87 | "NP-DHMC pers. (L=2, α=0.1): 0.0534 +- 0.0058 (std)\n" 88 | ] 89 | } 90 | ], 91 | "source": [ 92 | "def total_variational_distance(samples, p = 0.2):\n", 93 | " samples = sorted(samples)\n", 94 | " N = len(samples)\n", 95 | " mx = int(samples[-1])\n", 96 | " freq = [0 for _ in range(mx + 1)]\n", 97 | " for sample in samples:\n", 98 | " freq[int(sample)] += 1\n", 99 | " dist = 0\n", 100 | " for x in range(1,mx + 1):\n", 101 | " dist += abs(freq[x] / N - (1 - p)**(x-1)*p)\n", 102 | " dist += (1 - p)**mx # unsampled tail\n", 103 | " return dist / 2\n", 104 | "import torch\n", 105 | "print(\"Total variation distance from the ground truth (mean over all chains):\")\n", 106 | "for method in chains.keys():\n", 107 | " tvds = torch.tensor([total_variational_distance(chain) for chain in chains[method]])\n", 108 | " std, mean = torch.std_mean(tvds)\n", 109 | " print(f\"{legend_str(method)}: {mean:.4f} +- {std:.4f} (std)\")" 110 | ] 111 | } 112 | ], 113 | "metadata": { 114 | "kernelspec": { 115 | "display_name": "Python 3", 116 | "language": "python", 117 | "name": "python3" 118 | }, 119 | "language_info": { 120 | "codemirror_mode": { 121 | "name": "ipython", 122 | "version": 3 123 | }, 124 | "file_extension": ".py", 125 | "mimetype": "text/x-python", 126 | "name": "python", 127 | "nbconvert_exporter": "python", 128 | "pygments_lexer": "ipython3", 129 | "version": "3.8.10" 130 | }, 131 | "vscode": { 132 | "interpreter": { 133 | "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" 134 | } 135 | } 136 | }, 137 | "nbformat": 4, 138 | "nbformat_minor": 2 139 | } 140 | -------------------------------------------------------------------------------- /evaluation_icml2022.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from copy import deepcopy 3 | 4 | import matplotlib 5 | import seaborn as sns 6 | 7 | # Use type 42 (TrueType) fonts instead of the default Type 3 fonts 8 | matplotlib.rcParams["pdf.fonttype"] = 42 9 | 10 | # Make plots more accessible: 11 | sns.set_palette("colorblind") 12 | 13 | anglican_methods = [] 14 | # all_methods = ["npdhmc", "npdhmc-persistent", "is"] 15 | # compared_methods = ["npdhmc", "npdhmc-persistent", "is"] 16 | 17 | method_name = { 18 | "npdhmc": "NP-DHMC", 19 | "is": "IS", 20 | "npdhmc-persistent": "NP-DHMC pers.", 21 | "np-la-dhmc": "NP-Lookahead-DHMC", 22 | "npladhmc": "NP-Lookahead-DHMC", 23 | "npladhmc-persistent": "NP-Lookahead-DHMC pers.", 24 | } 25 | 26 | 27 | def legend_str(config) -> str: 28 | if type(config) == tuple: 29 | assert len(config) == 2 30 | if config[1]: 31 | return f"{method_name[config[0]]} ({config[1]})" 32 | else: 33 | return f"{method_name[config[0]]}" 34 | elif config: 35 | return str(config) 36 | else: 37 | return "" 38 | 39 | 40 | def thin_list(l: list, target_size: int) -> list: 41 | size = len(l) 42 | assert size >= target_size 43 | result = [] 44 | for i in range(target_size): 45 | result.append(l[i * size // target_size]) 46 | return result 47 | 48 | 49 | def thin_runs(runs: list, burnin: int = 0) -> list: 50 | thinned_runs = [] 51 | for run in runs: 52 | thinned_runs.append(defaultdict(list)) 53 | N = min(len(r["samples"][burnin:]) for r in run.values()) 54 | for method in run.keys(): 55 | thinned_runs[-1][method] = thin_list(run[method]["samples"][burnin:], N) 56 | return thinned_runs 57 | 58 | 59 | def toconfigstr(L, alpha, K): 60 | alphastr = [] if alpha == 1.0 else [f"α={alpha}"] 61 | Kstr = [] if K == 0 else [f"K={K}"] 62 | if L is not None: 63 | return ", ".join([f"L={L}"] + alphastr + Kstr) 64 | else: 65 | return ", ".join(alphastr + Kstr) 66 | 67 | 68 | def collect_values(thinned_runs: list, config=None) -> dict: 69 | values = defaultdict(list) 70 | for run in thinned_runs: 71 | for method in run.keys(): 72 | if config is None: 73 | values[method] += run[method] 74 | else: 75 | values[(method, config)] += run[method] 76 | return values 77 | 78 | 79 | def collect_chains(thinned_runs: list, config=None) -> dict: 80 | chains = defaultdict(list) 81 | for run in thinned_runs: 82 | for method in run.keys(): 83 | if config is None: 84 | chains[method].append(run[method]) 85 | else: 86 | chains[(method, config)].append(run[method]) 87 | return chains 88 | 89 | 90 | def print_running_time(runs: list, thinned_runs: list): 91 | for method in runs[0].keys(): 92 | running_time = sum(run[method]["time"] for run in runs) 93 | count = sum(len(run[method]) for run in thinned_runs) 94 | per_sample = running_time / count 95 | print( 96 | f"{legend_str(method)}: {running_time:.2f}s {per_sample:.4f}s per sample (after thinning)" 97 | ) 98 | 99 | 100 | def compute_iteration_count(L: int, K: int, lookahead_stats: list) -> int: 101 | lookahead_iterations = lookahead_stats[0] * (K + 1) + sum( 102 | lookahead_stats[i] * i for i in range(1, K + 2) 103 | ) 104 | return L * lookahead_iterations 105 | 106 | 107 | def adjust_for_iteration_time(experiments: dict) -> dict: 108 | effort = defaultdict(list) 109 | for config in experiments.keys(): 110 | for run in experiments[config]: 111 | for method, data in run.items(): 112 | L = data["L"] 113 | K = data.get("K", 0) 114 | stats = data["stats"] 115 | num_iterations = compute_iteration_count(L, K, stats) 116 | data["effort"] = num_iterations 117 | effort[method, config].append(num_iterations) 118 | min_effort = min(min(e) for e in effort.values()) 119 | print(f"{effort=}") 120 | reduction_factor = {k: [x / min_effort for x in v] for k, v in effort.items()} 121 | print(f"{reduction_factor=}") 122 | adjusted_experiments = deepcopy(experiments) 123 | min_len = None 124 | for key, runs in experiments.items(): 125 | for i, run in enumerate(runs): 126 | for method, data in run.items(): 127 | cutoff = int(len(data["samples"]) / reduction_factor[method, key][i]) 128 | if min_len is None or min_len > cutoff: 129 | min_len = cutoff 130 | adjusted_experiments[key][i][method]["samples"] = data["samples"][ 131 | :cutoff 132 | ] 133 | adjusted_experiments[key][i][method]["time"] /= reduction_factor[ 134 | method, key 135 | ][i] 136 | print(f"{min_len=}") 137 | return adjusted_experiments 138 | -------------------------------------------------------------------------------- /example_walk.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import sys 3 | import torch 4 | 5 | import pyro 6 | import pyro.infer.mcmc as pyromcmc # type: ignore 7 | from torch.distributions import Normal, Uniform 8 | 9 | from infer import run_inference, importance_resample, run_inference_icml2022 10 | from ppl import ProbCtx, run_prob_prog 11 | 12 | distance_limit = 10 13 | 14 | 15 | def walk_model(ctx: ProbCtx) -> float: 16 | """Random walk model. 17 | 18 | Mak et al. (2020): Densities of almost-surely terminating probabilistic programs are differentiable almost everywhere. 19 | """ 20 | distance = torch.tensor(0.0, requires_grad=True) 21 | start = ctx.sample(Uniform(0, 3), is_cont=False) 22 | position = start 23 | while position > 0 and distance < distance_limit: 24 | step = ctx.sample(Uniform(-1, 1), is_cont=False) 25 | distance = distance + torch.abs(step) 26 | position = position + step 27 | ctx.observe(distance, Normal(1.1, 0.1)) 28 | return start.item() 29 | 30 | 31 | def pyro_walk_model() -> float: 32 | """The same model written in Pyro.""" 33 | start = pyro.sample("start", pyro.distributions.Uniform(0, 3)) 34 | t = 0 35 | position = start 36 | distance = torch.tensor(0.0) 37 | while position > 0 and position < distance_limit: 38 | step = pyro.sample(f"step_{t}", pyro.distributions.Uniform(-1, 1)) 39 | distance = distance + torch.abs(step) 40 | position = position + step 41 | t = t + 1 42 | pyro.sample("obs", pyro.distributions.Normal(1.1, 0.1), obs=distance) 43 | return start.item() 44 | 45 | 46 | def run_pyro(use_nuts, rep, count, eps, num_steps): 47 | """Runs Pyro HMC and NUTS.""" 48 | torch.manual_seed(rep) 49 | if use_nuts: 50 | info = f"nuts_count{count}" 51 | kernel = pyromcmc.NUTS(pyro_walk_model) 52 | else: 53 | info = f"hmc_count{count}_eps{eps}_steps{num_steps}" 54 | kernel = pyromcmc.HMC( 55 | pyro_walk_model, 56 | step_size=eps, 57 | num_steps=num_steps, 58 | adapt_step_size=False, 59 | ) 60 | mcmc = pyromcmc.MCMC(kernel, num_samples=count, warmup_steps=count // 10) 61 | mcmc.run() 62 | samples = mcmc.get_samples() 63 | mcmc.summary() 64 | acceptance_rate = list(mcmc.diagnostics()["acceptance rate"].values())[0] 65 | if acceptance_rate < 0.1: 66 | # discard runs with a very low acceptance rate: 67 | print(f"{acceptance_rate=}, skipping") 68 | raw_samples = None 69 | else: 70 | raw_samples = [value.item() for value in samples["start"]] 71 | with open(f"samples_produced/walk_model{rep}_pyro_{info}.pickle", "wb") as f: 72 | pickle.dump(raw_samples, f) 73 | 74 | 75 | if __name__ == "__main__": 76 | count = 1_000 77 | repetitions = 10 78 | eps = 0.1 79 | num_steps = 50 80 | if len(sys.argv) > 1 and sys.argv[1] == "pyro-hmc": 81 | print("Running Pyro HMC...") 82 | for rep in range(repetitions): 83 | print(f"REPETITION {rep+1}/{repetitions}") 84 | run_pyro(False, rep, count, eps, num_steps) 85 | sys.exit() 86 | if len(sys.argv) > 1 and sys.argv[1] == "pyro-nuts": 87 | print("Running Pyro NUTS...") 88 | for rep in range(repetitions): 89 | print(f"REPETITION {rep+1}/{repetitions}") 90 | run_pyro(True, rep, count, eps, num_steps) 91 | sys.exit() 92 | if len(sys.argv) > 1 and sys.argv[1] == "icml2022": 93 | configs = [ 94 | (L, alpha, K, eps) 95 | for L in [5] 96 | for eps in [0.1] 97 | for alpha in [1.0, 0.5, 0.1] 98 | for K in [0, 1, 2] 99 | ] 100 | for rep in range(repetitions): 101 | for L, alpha, K, eps in configs: 102 | print( 103 | f"REPETITION {rep+1}/{repetitions} ({eps=}, {L=}, {alpha=}, {K=})" 104 | ) 105 | run_inference_icml2022( 106 | lambda trace: run_prob_prog(walk_model, trace=trace), 107 | name=f"walk_model_{rep}", 108 | count=count, 109 | burnin=0, # 100, 110 | eps=eps, 111 | L=L, 112 | K=K, 113 | alpha=alpha, 114 | seed=rep, 115 | ) 116 | else: 117 | for rep in range(repetitions): 118 | print(f"REPETITION {rep+1}/{repetitions}") 119 | run_inference( 120 | lambda trace: run_prob_prog(walk_model, trace=trace), 121 | name=f"walk_model{rep}", 122 | count=count, 123 | burnin=100, 124 | eps=eps, 125 | leapfrog_steps=num_steps, 126 | seed=rep, 127 | ) 128 | print("Generating importance samples as ground truth...") 129 | ground_truth_count = 1000 * count * repetitions 130 | torch.manual_seed(0) 131 | weighted, samples = importance_resample( 132 | (lambda trace: run_prob_prog(walk_model, trace)), 133 | count=ground_truth_count, 134 | ) 135 | with open(f"samples_produced/walk_is_{ground_truth_count}.pickle", "wb") as f: 136 | pickle.dump((weighted, samples), f) 137 | -------------------------------------------------------------------------------- /anglican/icml.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; # Anglican Experiments 5 | ;;; 6 | ;;; We define and run the models described in Section 5 in Anglican. Hit `Shift+Enter` to evaluate each code segment. 7 | ;; ** 8 | 9 | ;; ** 10 | ;;; ## Helper functions 11 | ;;; 12 | ;;; We start our experiments by defining some helper functions and loading `training_data.txt`. 13 | ;; ** 14 | 15 | ;; @@ 16 | (ns icml 17 | (:require [anglican.state :as state] 18 | [clojure.string :as str] 19 | :reload) 20 | (:use clojure.repl 21 | [anglican core runtime emit])) 22 | 23 | ; run model with arg for n times 24 | (defn model-multi-run [model arg infs id number burnin n] 25 | (doall 26 | (map (fn [alg] (do 27 | (println (name alg)) 28 | (def k (atom 0)) 29 | (while (< @k n) 30 | (do 31 | (spit (str/join ["samples/" id "/" (name alg) (str @k) ".txt"]) (time (with-out-str (pr (map :result (take number (drop burnin (doquery alg model [arg])))))))) 32 | (swap! k inc))))) 33 | infs))) 34 | 35 | ; pretty print times 36 | (defn pretty-print [time-str] 37 | (print (str/replace (str/replace time-str #"\"Elapsed time: " "") #"\"" ""))) 38 | 39 | ; load training_data for gmm and dpmm 40 | (defn getfloats [string] 41 | (map #(Float/parseFloat %1) 42 | (str/split string #","))) 43 | (def training-data (map getfloats (str/split (slurp "training_data.txt") #"\n"))) 44 | ;; @@ 45 | 46 | ;; ** 47 | ;;; ## Models 48 | ;;; 49 | ;;; The geometric, random walk, Gaussian mixture and Dirichlet process mixture models are defined as below. 50 | ;; ** 51 | 52 | ;; @@ 53 | ; geometric 54 | (defquery geometric 55 | (loop [n 1] 56 | (if (< (sample (uniform-continuous 0 1)) 0.2) 57 | n 58 | (recur (+ n 1))))) 59 | 60 | ; random walk 61 | (defquery random-walk 62 | (let [start (sample (uniform-continuous 0 3))] 63 | (loop [position start 64 | distance 0] 65 | (if (and (> position 0) (< distance 10)) 66 | (let [step (sample (uniform-continuous -1 1))] 67 | (recur (+ position step) 68 | (+ distance (abs step)))) 69 | (observe (normal 1.1 0.1) distance))) 70 | start)) 71 | 72 | ; gmm 73 | (defdist lik-dist 74 | [mus std-scalar] 75 | [] 76 | (sample* [this] nil) 77 | (observe* [this y] 78 | (reduce log-sum-exp 79 | (map #(- (sum [(observe* (normal (nth %1 0) std-scalar) (nth y 0)) 80 | (observe* (normal (nth %1 1) std-scalar) (nth y 1)) 81 | (observe* (normal (nth %1 2) std-scalar) (nth y 2))]) 82 | (log (count mus))) 83 | mus)))) 84 | 85 | (with-primitive-procedures [lik-dist] 86 | (defquery gmm [data] 87 | (let [K (+ 1 (sample (poisson 10))) 88 | mus (loop [k 0 89 | mus []] 90 | (if (= k K) 91 | mus 92 | (let [mu1 (sample (uniform-continuous 0 100)) 93 | mu2 (sample (uniform-continuous 0 100)) 94 | mu3 (sample (uniform-continuous 0 100))] 95 | (recur (inc k) 96 | (conj mus [mu1 mu2 mu3])))))] 97 | (map #(observe (lik-dist mus 10) %1) data) 98 | mus))) 99 | 100 | ; dpmm 101 | (defdist dp-dist 102 | [weights means] 103 | [] 104 | (sample* [this] nil) 105 | (observe* [this y] 106 | (reduce log-sum-exp 107 | (map #(+ (sum [(observe* (normal (nth %2 0) 10) (nth y 0)) 108 | (observe* (normal (nth %2 1) 10) (nth y 1)) 109 | (observe* (normal (nth %2 2) 10) (nth y 2))]) 110 | (log %1)) 111 | weights means)))) 112 | 113 | (with-primitive-procedures [dp-dist] 114 | (defquery dpmm [data] 115 | (let [dists (loop [cumprod 1 116 | beta-val 0 117 | stick 1 118 | weights [] 119 | means []] 120 | (if (< stick 0.01) 121 | [weights means] 122 | (let [newcumprod (* cumprod (- 1 beta-val)) 123 | newbeta (sample (beta 1 5))] 124 | (recur newcumprod 125 | newbeta 126 | (- stick (* newbeta newcumprod)) 127 | (conj weights (* newbeta newcumprod)) 128 | (conj means (repeatedly 3 #(sample (uniform-continuous 0 100)))))))) 129 | weights (first dists) 130 | means (second dists)] 131 | (map #(observe (dp-dist weights means) %1) data) 132 | (list weights means)))) 133 | ;; @@ 134 | 135 | ;; ** 136 | ;;; ## Run 137 | ;;; 138 | ;;; We run our models below where samples will be stored in the `samples/` directory and the elapsed times for each run are printed below. 139 | ;; ** 140 | 141 | ;; @@ 142 | (println "10 runs of geometric with 5000 samples and 500 burn-in") 143 | (pretty-print (with-out-str (model-multi-run geometric [] [:lmh :rmh :pgibbs :ipmcmc] "geo" 5000 500 10))) 144 | 145 | (println "10 runs of random walk with 50000 samples and 5000 burn-in") 146 | (pretty-print (with-out-str (model-multi-run random-walk [] [:lmh :rmh :pgibbs :ipmcmc] "rw" 50000 5000 10))) 147 | 148 | (println "10 runs of gmm with 50000 samples and 5000 burn-in") 149 | (pretty-print (with-out-str (model-multi-run gmm training-data [:lmh :rmh :pgibbs :ipmcmc] "gmm" 50000 5000 10))) 150 | 151 | (println "10 runs of dpmm with 2000 samples and 1000 burn-in") 152 | (pretty-print (with-out-str (model-multi-run dpmm training-data [:lmh :rmh :pgibbs :ipmcmc] "dpmm" 2000 1000 10))) 153 | ;; @@ 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nonparametric HMC implementation 2 | ================================ 3 | 4 | This repository contains the implementation of the *Nonparametric Discontinuous Hamiltonian Monte Carlo (NP-DHMC)* algorithm, as described in 5 | 6 | > Carol Mak, Fabian Zaiser, Luke Ong. *Nonparametric Hamiltonian Monte Carlo.* ICML 2021. [(proceedings)](https://proceedings.mlr.press/v139/mak21a.html) [(updated arxiv)](https://arxiv.org/abs/2106.10238) 7 | 8 | It also contains the implementation of *Nonparametric Lookahead Discontinuous Hamilton Monte Carlo (NP-Lookahead-DHMC)*, as described in 9 | 10 | > Carol Mak, Fabian Zaiser, Luke Ong. *Nonparametric Involutive Markove Chain Monte Carlo.* ICML 2022. [(proceedings)](https://proceedings.mlr.press/v162/mak22a.html) [(updated arxiv)](https://arxiv.org/abs/2211.01100) 11 | 12 | Modifications since publication 13 | ------------------------------- 14 | 15 | * We have been made aware of a small bug in the generation of Pyro's samples. 16 | (Thanks to Zirui Zhao for finding this and letting us know!) 17 | We have fixed the bug and the samples obtained using Pyro's HMC and NUTS samplers are still wrong. 18 | While the resulting plot looks somewhat different, this does not affect the conclusions of the paper. 19 | * In the original ICML 2021 code submission, the acceptance probability differed from the pseudocode in the paper. In addition, the extended trace was not updated to the correct time. Both issues are now resolved and the fixes led to a small performance improvement. 20 | 21 | Setup 22 | ----- 23 | 24 | You need an installation of Python 3, Jupyter and the following packages (I used the pip package manager) 25 | 26 | $ pip3 install matplotlib numpy scipy pandas torch seaborn tqdm pyro-ppl numpyro 27 | 28 | We carried the experiments out on a computer with a Intel Core i7-8700 CPU, @ 3.20 GHz x 12 and 16 GB RAM, running Ubuntu 20.04. 29 | The exact versions of the installed packages shouldn't matter. 30 | For reference, they were: `matplotlib-3.3.1`, `numpy-1.19.1`, `scipy-1.5.2`, `pandas-1.1.2`, `torch-1.6.0`, `seaborn-0.11.0`, `tqdm-4.48.2`, `pyro-ppl-1.4.0`, and `numpyro-0.6.0`. 31 | 32 | Reproducing the experiments 33 | --------------------------- 34 | 35 | To reproduce the experiments from the paper, you need to generate the samples: 36 | 37 | 1. Generate samples using Nonparametric Hamiltonian Monte Carlo (NP-DHMC). 38 | 2. Generate samples using Anglican (for comparison). 39 | 3. Run the evaluation scripts to produce the plots and tables from the paper. 40 | 41 | If you just want to view the results of the evaluation, you can simply view the `evaluation_*.ipynb` notebooks from step 3. 42 | But if you want to run the evaluation yourself, you'll need to complete steps 1 and 2 first. 43 | 44 | ### Step 1: Generating the NP-DHMC samples 45 | 46 | The model code for the 4 examples is in the `example_*.py` files. 47 | 48 | *ICML 2021:* You can simply run the experiments from our ICML 2021 paper as follows: 49 | 50 | $ python3 example_geometric.py 51 | $ python3 example_walk.py 52 | $ python3 example_walk.py pyro-hmc # to run Pyro's HMC sampler on this model 53 | $ python3 example_walk.py pyro-nuts # to run Pyro's NUTS sampler on this model 54 | $ python3 example_gmm.py 55 | $ python3 example_dirichlet.py 56 | 57 | These runs will save their results in `samples_produced/{experiment_name}_{run}_{sample_count}_{hyperparameters}.pickle`. 58 | 59 | Note that some of those runs can take a while to complete. 60 | Especially generating the ground truth in `example_walk.py` takes several hours and Pyro is also very slow in this example. 61 | Overall, the runs took over a day to complete. 62 | 63 | *ICML 2022:* For the experiments from our ICML 2022 paper, run the following commands: 64 | 65 | $ python3 example_geometric.py icml2022 66 | $ python3 example_walk.py icml2022 67 | $ python3 example_gmm.py icml2022 68 | $ python3 example_dirichlet.py icml2022 69 | 70 | These runs will save their results in `lookahead_samples/{experiment_name}_{run}_{sample_count}_{hyperparameters}.pickle`. 71 | 72 | ### Step 2: Generating the Anglican samples 73 | 74 | Note: You can run the evaluation scripts without Anglican by setting `anglican_methods = []` in `evaluation.py`. 75 | This will still plot the NP-DHMC samples, but won't do a comparison between Anglican and NP-DHMC. 76 | 77 | 1. To produce the Anglican samples for comparison, follow the instructions in `anglican/README.md`. 78 | The next steps below make the results available to the evaluation script. 79 | 2. Copy the contents of `anglican/samples/` into the folder `anglican_samples/`. 80 | 81 | $ cp -r anglican/samples/ anglican_samples/ 82 | 83 | 3. Copy the output of each of the four experiments in Anglican into `anglican_samples/{experiment_name}/timing.txt`. Concretely, the Anglican output will look like this: 84 | 85 | 10 runs of geometric with 5000 samples and 500 burn-in 86 | [...] 87 | 10 runs of random walk with 50000 samples and 5000 burn-in 88 | [...] 89 | 10 runs of gmm with 50000 samples and 5000 burn-in 90 | [...] 91 | 10 runs of dpmm with 2000 samples and 1000 burn-in 92 | [...] 93 | 94 | The first `[...]` needs to go into `anglican_samples/geo/timing.txt` and similarly for the others. 95 | 96 | *Note:* Anglican does not seem to provide a way to set the random seed. 97 | Therefore, each execution can be different, which may explain slight discrepancies with the numbers reported in the paper. 98 | 99 | ### Step 3: Evaluation 100 | 101 | The evaluation (ESS, LPPD, plots) for the experiments is in the Jupyter notebooks `evaluation_*.ipynb` files. 102 | If you use Jupyter, you can see the saved results and don't have to run the code. 103 | To view the notebooks, run the following: 104 | 105 | $ jupyter notebook evaluation_geometric.ipynb 106 | $ jupyter notebook evaluation_walk.ipynb 107 | $ jupyter notebook evaluation_gmm.ipynb 108 | $ jupyter notebook evaluation_dirichlet.ipynb 109 | 110 | For the ICML 2022 experiments, view the following notebooks: 111 | 112 | $ jupyter notebook evaluation_geometric_icml2022.ipynb 113 | $ jupyter notebook evaluation_walk_icml2022.ipynb 114 | $ jupyter notebook evaluation_gmm_icml2022.ipynb 115 | $ jupyter notebook evaluation_dirichlet_icml2022.ipynb 116 | 117 | *Note:* If you not only want to view the notebooks but also run the code, you'll need to produce the samples for all the inference algorithms first. 118 | 119 | When running the notebooks, if you are prompted to select a Python kernel, select *Python 3*. 120 | 121 | The above notebooks will output the data reported in our paper. 122 | Additionally, they produce our plots and if you execute the notebooks, the plots are saved as PDF files in the current directory. 123 | 124 | Project architecture 125 | -------------------- 126 | 127 | As a help to find your way around the codebase, here is a brief description of each file: 128 | 129 | - `ppl.py` includes the probabilistic programming primitives as methods of the `ProbCtx` (probabilistic context) class 130 | - `infer.py` implements NP-DHMC (and importance sampling for illustration) used in our experiments. 131 | - `evaluation.py` contains utilities needed for the evaluation of the results from ICML 2021. 132 | - `evaluation_icml2022.py` contains utilities needed for the evaluation of the results for the ICMl 2022 experiments. 133 | - `example_*.py` are for running the experiments from the paper. 134 | - `evaluation_*.ipynb` are for evaluating the results of each experiment from ICML 2021. 135 | - `evaluation_*_icml2022.ipynb` are for evaluating the results of each experiment from ICML 2022. 136 | -------------------------------------------------------------------------------- /ppl.py: -------------------------------------------------------------------------------- 1 | """This file contains helpers for writing probabilistic programs. 2 | 3 | A probabilistic context (ProbCtx) provides the constructs sample() and score(). 4 | 5 | A simple probabilistic program would look like this 6 | 7 | def simple_prog(ctx: ProbCtx) -> float: 8 | # sampling: 9 | sample = ctx.sample(torch.distributions.Normal(0., 1.)) 10 | if sample < 0: 11 | ctx.score(torch.tensor(0.5)) 12 | return sample.item() 13 | else: 14 | return sample.item() 15 | 16 | This program describes a standard normal distribution where the left (negative) half is 17 | multiplied/weighted by 0.5. 18 | """ 19 | 20 | import math 21 | from typing import Callable, Generic, TypeVar 22 | 23 | import torch 24 | 25 | T = TypeVar("T") 26 | 27 | 28 | class ProbCtx: 29 | """Probabilistic context: keeps track of a probabilistic execution (samples, weight, etc.)""" 30 | 31 | def __init__(self, trace: torch.Tensor) -> None: 32 | self.idx = 0 33 | """Index/address of the next sample variable""" 34 | self.samples = trace.clone().detach() 35 | """Sampled values so far in the trace""" 36 | self.samples.requires_grad_(True) 37 | """The given sample vector""" 38 | self.is_cont: torch.Tensor = torch.ones(self.samples.shape, dtype=torch.bool) 39 | """Whether the sampled value is continuous. 40 | 41 | A sample is discontinuous if a branch in the program depends on it.""" 42 | self.log_weight = torch.tensor(0.0, requires_grad=True) 43 | """Logarithm of the weight. 44 | 45 | The weight of the score()s, but also the pdf for sample()s (deviating from the paper). 46 | """ 47 | self.log_score = torch.tensor(0.0, requires_grad=True) 48 | """Logarithm of the score. 49 | 50 | The score is only multiplied for score()""" 51 | 52 | """ The log weight of the trace given """ 53 | 54 | def constrain( 55 | self, 56 | sample, 57 | geq: float = None, 58 | lt: float = None, 59 | ) -> None: 60 | """Constrains the sample to be >= geq and < lt. 61 | 62 | This is necessary for random variables whose support isn't all reals. 63 | 64 | Args: 65 | sample: the sample to be constrained 66 | geq (float, optional): The lower bound. Defaults to None. 67 | lt (float, optional): The upper bound. Defaults to None. 68 | """ 69 | if lt is not None: 70 | if sample >= lt: 71 | self.score_log(torch.tensor(-math.inf)) 72 | if geq is not None: 73 | if sample <= geq: 74 | self.score_log(torch.tensor(-math.inf)) 75 | 76 | def sample( 77 | self, 78 | dist: torch.distributions.Distribution, 79 | is_cont: bool, 80 | ) -> torch.Tensor: 81 | """Samples from the given distribution. 82 | 83 | If the distribution has support not on all reals, this needs to be followed by suitable constrain() calls. 84 | 85 | Args: 86 | dist (torch.distributions.Distribution): the distribution to sample from 87 | is_cont (bool): whether or not the weight function is continuous in this variable 88 | 89 | Returns: 90 | the sample 91 | """ 92 | samples = self.sample_n(1, dist, is_cont) 93 | return samples[0] 94 | 95 | def sample_n( 96 | self, 97 | n: int, 98 | dist: torch.distributions.Distribution, 99 | is_cont: bool, 100 | ) -> torch.Tensor: 101 | """Samples n times from the given distribution. 102 | 103 | Args: 104 | n (int): the number of samples 105 | dist (torch.distributions.Distribution): the distribution to sample from 106 | is_cont (bool): whether or not the weight function is continuous in this variable 107 | 108 | Returns: 109 | the samples 110 | """ 111 | needed = self.idx + n - len(self.samples) 112 | if needed > 0: 113 | values = dist.sample((needed,)) 114 | values.requires_grad_(True) 115 | self.samples = torch.cat((self.samples, values)) 116 | self.is_cont = torch.cat( 117 | (self.is_cont, torch.ones(needed, dtype=torch.bool)) 118 | ) 119 | for i in range(self.idx, self.idx + n): 120 | if math.isnan(self.samples[i]): # TODO: can this loop be removed? 121 | self.samples[i] = dist.sample(()) 122 | values = self.samples[self.idx : self.idx + n] 123 | self.is_cont[self.idx : self.idx + n] = torch.tensor(is_cont).repeat(n) 124 | try: 125 | self.log_weight = self.log_weight + torch.sum(dist.log_prob(values)) 126 | except ValueError: 127 | self.log_weight = torch.tensor(-math.inf) 128 | self.idx += n 129 | return values 130 | 131 | def score(self, weight: torch.Tensor) -> None: 132 | """Multiplies the current trace by the given weight. 133 | 134 | Args: 135 | weight (torch.Tensor): the weight. 136 | """ 137 | assert torch.is_tensor(weight), "weight is not a tensor" 138 | self.score_log(torch.log(weight)) 139 | 140 | def score_log(self, log_weight: torch.Tensor) -> None: 141 | assert torch.is_tensor(log_weight), "weight is not a tensor" 142 | self.log_weight = self.log_weight + log_weight 143 | self.log_score = self.log_score + log_weight 144 | 145 | def observe( 146 | self, 147 | obs: torch.Tensor, 148 | dist: torch.distributions.Distribution, 149 | ) -> None: 150 | self.score_log(dist.log_prob(obs)) 151 | 152 | 153 | class ProbRun(Generic[T]): 154 | """Result of a probabilistic run""" 155 | 156 | def __init__(self, ctx: ProbCtx, value: T) -> None: 157 | """Creates a probabilistic run result. 158 | 159 | Undocumented fields are the same as for ProbCtx 160 | 161 | Args: 162 | ctx (ProbCtx): the probabilistic context used for the program. 163 | value (T): the return value of the probabilistic program. 164 | """ 165 | self._gradU: torch.Tensor = None 166 | """Caches the gradient.""" 167 | self.log_weight = ctx.log_weight 168 | self.log_score = ctx.log_score 169 | self.samples = ctx.samples 170 | self.len = ctx.idx 171 | """Number of sample statements encountered, i.e. length of the trace.""" 172 | self.is_cont = ctx.is_cont 173 | self.value = value 174 | """Returned value of the probabilistic program.""" 175 | 176 | def gradU(self) -> torch.Tensor: 177 | if self._gradU is not None: 178 | return self._gradU 179 | U = -self.log_weight 180 | (self._gradU,) = torch.autograd.grad(U, self.samples, allow_unused=True) 181 | if self._gradU is None: 182 | self._gradU = torch.zeros(self.samples.shape) 183 | return self._gradU 184 | 185 | def used_samples(self) -> torch.Tensor: 186 | return self.samples[: self.len] 187 | 188 | 189 | def run_prob_prog(program: Callable[[ProbCtx], T], trace: torch.Tensor) -> ProbRun[T]: 190 | """Runs the given probabilistic program on the given trace. 191 | 192 | Args: 193 | program (Callable[[ProbCtx], T]): the probabilistic program. 194 | trace (torch.Tensor): the trace to replay. 195 | 196 | Returns: 197 | ProbRun: the result of the probabilistic run. 198 | """ 199 | tensor_trace = trace 200 | while True: 201 | ctx = ProbCtx(tensor_trace) 202 | ret = None 203 | try: 204 | ret = program(ctx) 205 | except Exception as e: 206 | if ctx.log_score.item() > -math.inf or ctx.log_weight.item() > -math.inf: 207 | print("Exception in code with nonzero weight!") 208 | raise e 209 | else: 210 | print("Info: exception in branch with zero weight") 211 | if ctx.idx > len(tensor_trace): 212 | tensor_trace = ctx.samples 213 | continue 214 | return ProbRun(ctx, ret) 215 | -------------------------------------------------------------------------------- /anglican/training_data.txt: -------------------------------------------------------------------------------- 1 | 3.8913787842e+01, 7.8049194336e+01, 3.1845679283e+00 2 | 1.6934194565e+01, 8.8608798981e+00, 7.2328002930e+01 3 | 4.6867481232e+01, 8.5324783325e+01, 3.8484020233e+01 4 | 9.0455055237e+01, 5.2043354034e+01, 8.5583732605e+01 5 | 1.2994254112e+01, 2.3557479858e+01, 6.8594238281e+01 6 | 2.9777259827e+00, 5.3691635132e+00, 6.8860382080e+01 7 | 5.3258140564e+01, 4.8835975647e+01, 7.7570892334e+01 8 | 4.3753051758e+00, 4.1801290512e+00, 2.9582151413e+01 9 | 4.9550285339e+01, 6.7677230835e+01, -2.0380945206e+00 10 | 8.8853431702e+01, 2.9520172119e+01, 8.7694511414e+01 11 | 6.4197280884e+01, 5.8124809265e+01, 7.9760818481e+01 12 | 1.9682994843e+01, 4.2385089874e+01, 5.6480129242e+01 13 | 7.6464492798e+01, 3.3100166321e+01, 1.0064760590e+02 14 | 1.6474088669e+01, 2.9606607437e+01, 8.9247024536e+01 15 | 2.6752193451e+01, 3.0629985809e+01, 6.9335884094e+01 16 | 9.9586540222e+01, 2.7334651947e+01, 7.9692291260e+01 17 | 9.5854515076e+01, 2.2367488861e+01, 7.4055099487e+01 18 | 5.7880302429e+01, 1.0247180176e+02, 3.9988262177e+01 19 | 2.2207679749e+01, 3.8197937012e+01, 7.2244087219e+01 20 | 5.5582218170e+01, 7.3699531555e+01, 7.9139051437e+00 21 | 6.3437013626e+00, 3.5339229584e+01, 6.0578594208e+01 22 | 5.6794799805e+01, 3.8681831360e+01, 5.6829841614e+01 23 | 7.7237754822e+01, 1.7167461395e+01, 5.5323692322e+01 24 | 4.5639129639e+01, 5.4775276184e+01, 8.9026046753e+01 25 | 4.2278316498e+01, 7.7549148560e+01, 5.9003501892e+01 26 | 3.7035243988e+01, 3.3717613220e+01, 5.4198417664e+01 27 | 6.3451980591e+01, 6.7570907593e+01, 9.9931480408e+01 28 | 3.8384254456e+01, 5.5891632080e+01, 7.0199073792e+01 29 | 3.3600373268e+00, 1.5688808441e+01, 2.7753387451e+01 30 | 4.9347549438e+01, 5.9971626282e+01, 7.4391914368e+01 31 | 1.0239339828e+01, 3.9023292542e+01, 6.2588157654e+01 32 | 8.8120414734e+01, 3.6596214294e+01, 7.9204788208e+01 33 | 4.6885513306e+01, 8.2867050171e+01, 8.3956703186e+01 34 | 1.4097843170e+01, 2.9568243027e+01, 6.7034790039e+01 35 | 5.5097145081e+01, 8.4084503174e+01, 5.1039840698e+01 36 | 1.7586402893e+01, 2.1463569641e+01, 6.7513633728e+01 37 | 6.3730674744e+01, 8.2014152527e+01, 7.1479942322e+01 38 | 5.1289691925e+01, 3.8973087311e+01, 3.1784584045e+01 39 | 1.6000141144e+01, 2.6940460205e+01, 2.6933504105e+01 40 | 4.6091629028e+01, 9.1820892334e+01, 4.4394981384e+01 41 | 2.2677240372e+00, 2.0240131378e+01, 5.8884960175e+01 42 | 5.6758834839e+01, 9.8053115845e+01, 4.7690673828e+01 43 | 6.2393115997e+01, 5.6249401093e+01, 6.8068603516e+01 44 | 6.5833419800e+01, 6.5257919312e+01, 4.2425775528e+00 45 | 2.7737142563e+01, 2.9746440887e+01, 7.5252174377e+01 46 | 1.7275262833e+01, 2.5546920776e+01, 5.9825862885e+01 47 | 4.5224594116e+01, 6.5093208313e+01, 7.6485404968e+01 48 | 6.6611289978e-01, 3.3475513458e+01, 6.1510429382e+01 49 | 4.5415351868e+01, 6.1094238281e+01, 9.3984855652e+01 50 | 2.3265960693e+01, 4.6160800934e+01, 6.8246246338e+01 51 | 5.0277126312e+01, 4.5635204315e+01, 9.5829177856e+01 52 | 7.2830991745e+00, 5.8691921234e+00, 7.0048622131e+01 53 | 6.2543823242e+01, 1.2307619095e+01, 4.8617301941e+01 54 | 7.8875358582e+01, 3.5309040070e+01, 1.0177359009e+02 55 | 1.9108751297e+01, 1.9727924347e+01, 6.1976051331e+01 56 | 3.2571821213e+00, 2.7002676010e+01, 5.7256168365e+01 57 | 4.3453338623e+01, 6.1893684387e+01, 1.0341133118e+02 58 | 4.8196834564e+01, 7.8872169495e+01, 4.9736209869e+01 59 | 8.0131006241e+00, 3.4448928833e+01, 3.5170452118e+01 60 | 6.2766159058e+01, 3.1613162994e+01, 3.1937610626e+01 61 | 5.5452209473e+01, 7.7728202820e+01, 8.9336875916e+01 62 | 8.1298339367e-01, 3.2402313232e+01, 1.7641874313e+01 63 | 5.4234672546e+01, 8.3559371948e+01, 2.5531499863e+01 64 | 8.5714826584e+00, 4.0125499725e+01, 6.3926498413e+01 65 | 3.5538330078e+01, 6.8657958984e+01, 1.5832458496e+01 66 | 1.6215976715e+01, 2.2990257263e+01, 3.2665489197e+01 67 | 8.2713684082e+01, 5.5708290100e+01, 3.1161375046e+01 68 | 5.3244022369e+01, 4.5162956238e+01, 7.9726493835e+01 69 | 8.5621757507e+01, 4.4836326599e+01, 9.5478019714e+01 70 | 4.5530723572e+01, 2.4487869263e+01, 3.9594879150e+01 71 | 6.7408514023e-01, 2.3254699707e+01, 1.7594032288e+01 72 | 5.0286010742e+01, 5.8465335846e+01, 9.3622543335e+01 73 | 6.8036514282e+01, 5.2534755707e+01, 1.0808423615e+02 74 | 4.7422451019e+01, 9.2342239380e+01, 4.3224182129e+01 75 | 3.8486675262e+01, 6.6992294312e+01, 1.0192984009e+02 76 | 7.4734054565e+01, 6.7092300415e+01, 7.6077682495e+01 77 | 1.3442461967e+01, 2.5642494202e+01, 2.5307182312e+01 78 | 4.4478717804e+01, 6.3950256348e+01, 9.5842163086e+01 79 | 8.8886756897e+00, 1.6753177643e+01, 5.0711143494e+01 80 | 3.4334014893e+01, 1.0479965210e+02, 9.2693014145e+00 81 | 2.0341861725e+01, 3.7425720215e+01, 5.5298290253e+01 82 | 6.0892807007e+01, 7.2908416748e+01, -4.3334856033e+00 83 | 4.9063220978e+01, 9.7771820068e+01, 4.7595432281e+01 84 | 3.6261558533e+00, 2.2355331421e+01, 2.4510793686e+01 85 | 5.2045291901e+01, 5.6391201019e+01, 1.0958501434e+02 86 | 8.2766174316e+01, 3.3446140289e+01, 4.9163036346e+01 87 | 5.5888900757e+00, 1.6292490005e+01, 1.9151445389e+01 88 | 1.3764819145e+01, 2.9399599075e+01, 5.0415657043e+01 89 | 1.0234851837e+01, 2.5176630020e+01, 3.1965038300e+01 90 | 3.6279567719e+01, 5.5399772644e+01, 1.0188439178e+02 91 | 7.0820396423e+01, 7.3719390869e+01, 1.0508234406e+02 92 | 5.9433418274e+01, 4.0436706543e+01, 4.4516883850e+01 93 | 5.9420742035e+01, 4.7297325134e+01, 2.6665657043e+01 94 | 5.1237125397e+01, 4.6197731018e+01, 1.1027004242e+02 95 | 7.2646835327e+01, 6.0851425171e+01, 7.7178848267e+01 96 | 5.8946695328e+00, 1.6657341003e+01, 3.2016868591e+01 97 | 5.9125244141e+01, 7.7305473328e+01, 6.8282333374e+01 98 | 5.1690998077e+01, 8.5397491455e+01, 4.3802246094e+01 99 | 5.5181571960e+01, 7.3119567871e+01, 7.3965773582e+00 100 | 5.5892028809e+01, 1.8541900635e+01, 2.2080980301e+01 101 | 4.3416534424e+01, 8.0194244385e+01, 1.4425119400e+01 102 | 6.7037384033e+01, 4.0086730957e+01, 4.2796871185e+01 103 | 1.2048503876e+01, 3.5112148285e+01, 2.2041221619e+01 104 | 3.9094703674e+01, 8.6435562134e+01, 8.9475984573e+00 105 | 5.0813987732e+01, 7.8749313354e+01, 7.2538619995e+01 106 | 9.4422283173e+00, 3.1875314713e+01, 7.8827873230e+01 107 | 6.0748016357e+01, 4.0945060730e+01, 1.1621139526e+01 108 | 4.5397872925e+01, 6.1137573242e+01, 1.0464740753e+02 109 | 6.3384237289e+00, -1.5006217957e+00, 3.8212162018e+01 110 | 7.4494552612e+01, 6.3901103973e+01, 9.8906219482e+01 111 | 2.4770936966e+00, 4.4427318573e+01, 6.3975009918e+01 112 | 4.3342685699e+01, 5.0665702820e+01, 8.4952606201e+01 113 | 6.1726131439e+01, 9.6995323181e+01, 3.1065490723e+01 114 | 4.9475074768e+01, 5.8653709412e+01, 7.5880088806e+01 115 | 8.6495590210e+01, 3.1226961136e+01, 3.0297269821e+01 116 | 4.7381977081e+01, 6.9419837952e+01, 2.0120601654e+00 117 | 5.0653800964e+01, 7.6834190369e+01, 7.9267120361e+01 118 | 1.3948193550e+01, 2.0388511658e+01, 1.2619625092e+01 119 | 4.8849590302e+01, 8.6007125854e+01, 3.8860263824e+01 120 | 4.3736267090e+01, 6.8908889771e+01, 7.1759246826e+01 121 | 3.7305408478e+01, 9.3624084473e+01, 6.6216674805e+01 122 | 9.1602157593e+01, 5.2418731689e+01, 7.6635360718e+01 123 | 5.7232849121e+01, 8.1607299805e+01, 1.0611920357e+01 124 | 2.3491861343e+01, 1.3616292953e+01, 5.6828342438e+01 125 | 7.4565820694e+00, 3.0794956207e+01, 5.9971996307e+01 126 | 1.0321270752e+02, 2.7740055084e+01, 8.8632049561e+01 127 | 6.2845443726e+01, 9.3634422302e+01, 3.3963993073e+01 128 | 1.5094000816e+01, 2.7254026413e+01, 6.7877449036e+01 129 | 8.0241691589e+01, 3.5360580444e+01, 1.1191390228e+02 130 | 5.4176467896e+01, 7.3729103088e+01, -1.6528444290e+00 131 | 6.6373146057e+01, 6.3874416351e+01, -1.0972881317e-01 132 | 5.5214527130e+01, 7.7974929810e+01, 1.7332050323e+01 133 | 4.7738883972e+01, 7.9725166321e+01, 3.6789611816e+01 134 | 1.1368129730e+01, 3.3103446960e+01, 6.0581275940e+01 135 | 7.2407516479e+01, 7.1331176758e+01, 8.3218910217e+01 136 | 4.3692996979e+01, 2.3810710907e+01, 7.3505157471e+01 137 | 2.7164995193e+01, 1.0470252991e+01, 4.0484901428e+01 138 | 3.6649230957e+01, 8.6923004150e+01, 4.9883537292e+01 139 | 5.9017593384e+01, 1.8760990143e+01, 5.4799476624e+01 140 | 3.5555644035e+00, 1.3150630951e+01, 2.1473844528e+01 141 | 2.8079353333e+01, 3.2935279846e+01, 7.9060531616e+01 142 | 4.4762561798e+01, 5.3811946869e+01, 8.9770912170e+01 143 | 6.3056178093e+00, 1.6360685349e+01, 5.8430480957e+01 144 | 2.8472488403e+01, 4.1197303772e+01, 8.7307708740e+01 145 | 6.5903312683e+01, 6.4667724609e+01, 5.4814979553e+01 146 | 2.3824392319e+01, 1.5982749939e+01, 5.9690853119e+01 147 | 9.1774353027e+01, 5.2173320770e+01, 7.9267349243e+01 148 | 4.2748409271e+01, 4.7884075165e+01, 8.7465065002e+01 149 | 3.6064636230e+01, 6.6105712891e+01, 7.2154953003e+01 150 | 5.5941753387e+00, 2.6151363373e+01, 6.8679946899e+01 151 | 6.1879837036e+01, 4.7211212158e+01, 2.7027807236e+01 152 | 1.9588584900e+01, 2.1981372833e+01, 5.8354476929e+01 153 | 8.3311744690e+01, 4.5993156433e+01, 1.0924957275e+02 154 | 1.5169075966e+01, 2.4443426132e+01, 5.2840530396e+01 155 | 2.0712226868e+01, 2.2828012466e+01, 7.5460914612e+01 156 | 5.7799201965e+01, 8.0605606079e+01, 1.2147049904e+01 157 | 4.3489269257e+01, 9.6669616699e+01, 4.6987277985e+01 158 | 2.6396120071e+01, 3.5377079010e+01, 6.4302330017e+01 159 | 5.4727668762e+01, 4.9353797913e+01, 1.1032926941e+02 160 | 5.3961593628e+01, 9.5568611145e+01, 4.4333515167e+01 161 | 2.4261039734e+01, 2.9924247742e+01, 5.9685504913e+01 162 | 4.4638061523e+01, 7.5062568665e+01, 4.4357021332e+01 163 | 7.7638946533e+01, 3.2015964508e+01, 3.4412216187e+01 164 | 5.5814350128e+01, 3.8163066864e+01, 4.4855110168e+01 165 | 4.5647954941e+00, 1.8342391968e+01, 2.7926740646e+01 166 | 2.0315097809e+01, 3.0605566025e+01, 5.8419025421e+01 167 | 5.7491859436e+01, 6.1781490326e+01, 1.9898658752e+01 168 | 6.8488721848e+00, 4.1667713165e+01, 6.9367874146e+01 169 | 4.2981960297e+01, 8.5490318298e+01, 4.5873446465e+00 170 | 1.0485815048e+01, 2.4810356140e+01, 6.7924446106e+01 171 | 8.4837474823e+00, 2.5796062469e+01, 7.5563873291e+01 172 | 2.3903367996e+01, 4.3642799377e+01, 6.5735450745e+01 173 | 1.3872167587e+01, 7.0020324707e+01, 8.4704734802e+01 174 | 4.3655822754e+01, 7.9142501831e+01, 1.1362280846e+00 175 | 5.5352397919e+01, 8.3261276245e+01, 3.3869657516e+00 176 | 3.3891166687e+01, 2.4930055618e+01, 6.7623008728e+01 177 | 4.0094699860e+01, 6.5474777222e+01, 9.4069297791e+01 178 | 4.4849960327e+01, 4.6774333954e+01, 9.9163024902e+01 179 | 5.8491821289e+01, 1.0178640747e+02, 4.4180233002e+01 180 | 5.1144962311e+01, 7.9684684753e+01, 8.2572250366e+01 181 | 5.6067905426e+01, 4.8255393982e+01, 9.5189285278e+01 182 | 1.2951458931e+01, 2.3651182175e+01, 8.5426963806e+01 183 | 1.6985980988e+01, 2.2280183792e+01, 3.4770149231e+01 184 | 5.0914237976e+01, 5.5562103271e+01, 9.9689498901e+01 185 | 1.1558896637e+02, 4.8604614258e+01, 8.7454162598e+01 186 | 3.9652725220e+01, 6.8639419556e+01, 4.2205810547e-02 187 | 6.1638046265e+01, 6.0594875336e+01, 6.3248722076e+01 188 | 4.9923767090e+01, 7.0276268005e+01, 8.0378044128e+01 189 | 3.1657131195e+01, 5.1753890991e+01, 9.3623886108e+01 190 | 4.8539718628e+01, 5.2805778503e+01, 8.1614700317e+01 191 | 4.2256114960e+01, 8.9094581604e+01, 3.6478466034e+01 192 | 1.6328792572e+01, 2.1519451141e+01, 6.8004043579e+01 193 | 9.2612823486e+01, 4.2442852020e+01, 8.1760742188e+01 194 | 5.3333335876e+01, 4.5664875031e+01, 1.0123160553e+02 195 | 5.1434875488e+01, 4.7528900146e+01, 9.3980125427e+01 196 | 3.3920951843e+01, 4.7876041412e+01, 1.0061608124e+02 197 | 3.7514200211e+00, 2.5540534973e+01, 4.3741695404e+01 198 | 9.2984724045e+00, 2.9465957642e+01, 6.4083358765e+01 199 | 3.2638126373e+01, 5.6818378448e+01, 9.4064697266e+01 200 | 4.7262874603e+01, 9.6274139404e+01, 5.2783168793e+01 -------------------------------------------------------------------------------- /infer.py: -------------------------------------------------------------------------------- 1 | import math 2 | import pickle 3 | import time 4 | from typing import Any, Callable, Iterator, List, Tuple 5 | 6 | import torch 7 | from torch.distributions import Uniform, Laplace, Normal 8 | from tqdm import tqdm 9 | 10 | from ppl import ProbRun, T 11 | 12 | torch.manual_seed(0) # makes executions deterministic 13 | torch.set_printoptions(precision=10) # more precise printing for debugging 14 | 15 | 16 | class State: 17 | """Describes a state in phase space (position q, momentum p) for NP-DHMC 18 | 19 | The field `is_cont` stores which variables are continuous. 20 | """ 21 | 22 | def __init__( 23 | self, 24 | q: torch.Tensor, 25 | p: torch.Tensor, 26 | is_cont: torch.Tensor, 27 | ) -> None: 28 | self.q = q 29 | """position""" 30 | self.p = p 31 | """momentum""" 32 | self.is_cont = is_cont 33 | """is_cont[i] == True if the density function is continuous in coordinate i. 34 | 35 | If a branch (if-statement) in a program depends on self.q[i], it is discontinuous and is_cont[i] == False.""" 36 | 37 | def kinetic_energy(self) -> torch.Tensor: 38 | """Computes the kinetic energy of the particle. 39 | 40 | In discontinuous HMC, discontinuous coordinates use Laplace momentum, not Gaussian momentum.""" 41 | gaussian = self.p * self.is_cont 42 | laplace = self.p * ~self.is_cont 43 | return gaussian.dot(gaussian) / 2 + torch.sum(torch.abs(laplace)) 44 | 45 | 46 | def importance_sample( 47 | run_prog: Callable[[torch.Tensor], ProbRun[T]], 48 | count: int = 10_000, 49 | ) -> Iterator[Tuple[float, T]]: 50 | """Samples from a probabilistic program using importance sampling. 51 | 52 | The resulting samples are weighted. 53 | 54 | Note: This is not needed to reproduce the results, but hopefully makes the code easier to understand. 55 | 56 | Args: 57 | run_prog (Callable[[torch.Tensor], ProbRun[T]]): runs the probabilistic program on a trace. 58 | count (int, optional): the desired number of samples. Defaults to 10_000. 59 | 60 | Yields: 61 | Iterator[Tuple[torch.Tensor, T]]: samples of the form (log_score, value) 62 | """ 63 | for _ in tqdm(range(count)): 64 | result = run_prog(torch.tensor([])) 65 | yield result.log_score.item(), result.value 66 | 67 | 68 | def importance_resample( 69 | run_prog: Callable[[torch.Tensor], ProbRun[T]], 70 | count: int = 10_000, 71 | ) -> Tuple[List[Tuple[float, T]], List[T]]: 72 | """Samples from a probabilistic program using importance sampling and systematic resampling. 73 | 74 | It uses systematic resampling on the weighted importance samples to obtain unweighted samples. 75 | 76 | Note: This is not needed to reproduce the results, but hopefully makes the code easier to understand. 77 | 78 | Args: 79 | run_prog (Callable[[torch.Tensor], ProbRun[T]]): runs the probabilistic program on a trace. 80 | count (int, optional): the desired number of samples. Defaults to 10_000. 81 | 82 | Returns: 83 | Tuple[List[Tuple[float, T]], List[T]]: weighted samples, resamples 84 | """ 85 | weighted_samples = list(importance_sample(run_prog, count)) 86 | count = len(weighted_samples) 87 | mx = max(log_weight for (log_weight, _) in weighted_samples) 88 | weight_sum = sum(math.exp(log_weight - mx) for (log_weight, _) in weighted_samples) 89 | # systematic resampling: 90 | u_n = Uniform(0, 1).sample().item() 91 | sum_acc = 0.0 92 | resamples: List[T] = [] 93 | for (log_weight, value) in weighted_samples: 94 | weight = math.exp(log_weight - mx) * count / weight_sum 95 | sum_acc += weight 96 | while u_n < sum_acc: 97 | u_n += 1 98 | resamples.append(value) 99 | return weighted_samples, resamples 100 | 101 | 102 | def coord_integrator( 103 | run_prog: Callable[[torch.Tensor], ProbRun[T]], 104 | i: int, 105 | t: float, 106 | eps: float, 107 | state: State, 108 | state_0: State, 109 | result: ProbRun, 110 | ) -> ProbRun[T]: 111 | """Coordinate integrator adapted from discontinuous HMC. 112 | 113 | For NP-DHMC, it also has to deal with possible changes in dimension.""" 114 | U = -result.log_weight 115 | q = state.q.clone().detach() 116 | q[i] += eps * torch.sign(state.p[i]) 117 | new_result = run_prog(q) 118 | new_U = -new_result.log_weight.item() 119 | delta_U = new_U - U 120 | if not math.isfinite(new_U) or torch.abs(state.p[i]) <= delta_U: 121 | state.p[i] = -state.p[i] 122 | else: 123 | state.p[i] -= torch.sign(state.p[i]) * delta_U 124 | N2 = new_result.len 125 | N = result.len 126 | result = new_result 127 | if N2 > N: 128 | # extend everything to the higher dimension 129 | state.q = result.samples.clone().detach() 130 | is_cont = result.is_cont.clone().detach() 131 | # pad the momentum vector: 132 | gauss = Normal(0, 1).sample([N2 - N]) 133 | laplace = Laplace(0, 1).sample([N2 - N]) 134 | p_padding = gauss * is_cont[N:N2] + laplace * ~is_cont[N:N2] 135 | state_0.p = torch.cat((state_0.p, p_padding)) 136 | state_0.is_cont = torch.cat((state_0.is_cont, is_cont[N:N2])) 137 | state.p = torch.cat((state.p, p_padding)) 138 | state.is_cont = is_cont 139 | # adjust the position vector: 140 | q0_padding = ( 141 | state.q[N:N2].clone().detach() 142 | - t * state.p[N:N2] * is_cont[N:N2] 143 | - t * torch.sign(state.p[N:N2]) * ~is_cont[N:N2] 144 | ) 145 | state_0.q = torch.cat((state_0.q, q0_padding)) 146 | else: 147 | # truncate everything to the lower dimension 148 | state.q = result.samples[:N2].clone().detach() 149 | state.p = state.p[:N2] 150 | state.is_cont = result.is_cont[:N2] 151 | state_0.q = state_0.q[:N2] 152 | state_0.p = state_0.p[:N2] 153 | state_0.is_cont = state_0.is_cont[:N2] 154 | assert len(state.p) == len(state_0.p) 155 | assert len(state.p) == len(state.q) 156 | assert len(state.is_cont) == len(state.p) 157 | assert len(state_0.is_cont) == len(state_0.p) 158 | assert len(state_0.p) == len(state_0.q) 159 | return result 160 | 161 | 162 | def integrator_step( 163 | run_prog: Callable[[torch.Tensor], ProbRun[T]], 164 | t: float, 165 | eps: float, 166 | state: State, 167 | state_0: State, 168 | ) -> ProbRun[T]: 169 | """Performs one integrator step (called "leapfrog step" in standard HMC).""" 170 | result = run_prog(state.q) 171 | # first half of leapfrog step for continuous variables: 172 | state.p = state.p - eps / 2 * result.gradU() * state.is_cont 173 | state.q = state.q + eps / 2 * state.p * state.is_cont 174 | result = run_prog(state.q) 175 | # Integrate the discontinuous coordinates in a random order: 176 | disc_indices = torch.flatten(torch.nonzero(~state.is_cont, as_tuple=False)) 177 | perm = torch.randperm(len(disc_indices)) 178 | disc_indices_permuted = disc_indices[perm] 179 | for j in disc_indices_permuted: 180 | if j >= len(state.q): 181 | continue # out-of-bounds can happen if q changes length during the loop 182 | result = coord_integrator( 183 | run_prog, int(j.item()), t, eps, state, state_0, result 184 | ) 185 | # second half of leapfrog step for continuous variables 186 | state.q = state.q + eps / 2 * state.p * state.is_cont 187 | result = run_prog(state.q) 188 | state.p = state.p - eps / 2 * result.gradU() * state.is_cont 189 | return result 190 | 191 | 192 | def np_dhmc( 193 | run_prog: Callable[[torch.Tensor], ProbRun[T]], 194 | count: int, 195 | leapfrog_steps: int, 196 | eps: float, 197 | burnin: int = None, 198 | ) -> List[T]: 199 | """Samples from a probabilistic program using NP-DHMC. 200 | 201 | Args: 202 | run_prog (Callable[[torch.Tensor], ProbRun[T]]): runs the probabilistic program on a trace. 203 | count (int, optional): the desired number of samples. Defaults to 10_000. 204 | burnin (int): number of samples to discard at the start. Defaults to `count // 10`. 205 | leapfrog_steps (int): number of leapfrog steps the integrator performs. 206 | eps (float): the step size of the leapfrog steps. 207 | 208 | Returns: 209 | List[T]: list of samples 210 | """ 211 | if burnin is None: 212 | burnin = count // 10 213 | final_samples = [] 214 | result = run_prog(torch.tensor([])) 215 | U = -result.log_weight 216 | q = result.samples.clone().detach() 217 | is_cont = result.is_cont.clone().detach() 218 | count += burnin 219 | accept_count = 0 220 | for _ in tqdm(range(count)): 221 | N = len(q) 222 | dt = ((torch.rand(()) + 0.5) * eps).item() 223 | gaussian = Normal(0, 1).sample([N]) * is_cont 224 | laplace = Laplace(0, 1).sample([N]) * ~is_cont 225 | p = gaussian + laplace 226 | state_0 = State(q, p, is_cont) 227 | state = State(q, p, is_cont) 228 | prev_res = result 229 | for step in range(leapfrog_steps): 230 | if not math.isfinite(result.log_weight.item()): 231 | break 232 | result = integrator_step(run_prog, step * dt, dt, state, state_0) 233 | # Note that the acceptance probability differs from the paper in the following way: 234 | # In the implementation, we can have other continuous distributions than 235 | # just normal distributions. 236 | # We treat `x = sample(D)` as `x = sample(normal); score(pdf_D(x) / pdf_normal(x));`. 237 | # this means that the `w(q) * pdfnormal(q)` in the acceptance probability just becomes 238 | # `w(q) * pdf_D(q) = weight(q)` 239 | # because the weight in the implementation includes the prior. 240 | # (`w` refers to the weight as defined in the paper and 241 | # `weight` to the weight as used in the implementation.) 242 | # Similarly 243 | # w(q0 after extension) * pdfnormal(q0 after extension) 244 | # = w(q0 before extension) * pdfnormal(q0 before extension) * pdfnormal(extended part of q0) 245 | # = weight(q0 before extensions) * pdfnormal(extended part of q0) 246 | # For this reason, we add the factor pdfnormal(extended part of q0) in U_0 below. 247 | K_0 = state_0.kinetic_energy() 248 | N2 = len(state_0.q) 249 | U_0 = -prev_res.log_weight - Normal(0, 1).log_prob(state_0.q[N:N2]).sum() 250 | K = state.kinetic_energy() 251 | U = -result.log_weight 252 | accept_prob = torch.exp(U_0 + K_0 - U - K) 253 | if U.item() != math.inf and torch.rand(()) < accept_prob: 254 | q = state.q 255 | is_cont = state.is_cont 256 | accept_count += 1 257 | final_samples.append(result.value) 258 | else: 259 | result = prev_res 260 | final_samples.append(prev_res.value) 261 | count = len(final_samples) 262 | final_samples = final_samples[burnin:] # discard first samples (burn-in) 263 | print(f"acceptance ratio: {accept_count / count * 100}%") 264 | return final_samples 265 | 266 | 267 | def np_lookahead_dhmc( 268 | run_prog: Callable[[torch.Tensor], ProbRun[T]], 269 | count: int, 270 | L: int, 271 | eps: float, 272 | K: int = 0, 273 | alpha: float = 1, 274 | burnin: int = None, 275 | ) -> Tuple[List[T], Any]: 276 | """Samples from a probabilistic program using "Lookahead" NP-DHMC. 277 | 278 | Returns a list of samples and additional information, together as a pair. 279 | 280 | The acceptance condition is taken from [1] (Figure 3). 281 | They prove that it's equivalent to Sohl-Dickstein et al.'s version in Appendix C. 282 | 283 | [1] Campos, Sanz-Serna: Extra Chance Generalized Hybrid Monte Carlo (https://arxiv.org/pdf/1407.8107.pdf) 284 | 285 | Args: 286 | run_prog (Callable[[torch.Tensor], ProbRun[T]]): runs the probabilistic program on a trace. 287 | count (int, optional): the desired number of samples. Defaults to 10_000. 288 | L (int): number of leapfrog steps the integrator performs. 289 | eps (float): the step size of the leapfrog steps. 290 | K (int): number of "extra chances" for Lookahead HMC (0: standard HMC) 291 | alpha (float): persistence factor (0: full persistence, 1: stanard HMC) 292 | burnin (int): number of samples to discard at the start. Defaults to `count // 10`. 293 | 294 | Returns: 295 | List[T], Any: list of samples, stats 296 | """ 297 | if burnin is None: 298 | burnin = count // 10 299 | final_samples = [] 300 | lookahead_stats = [0] * (K + 2) 301 | result = run_prog(torch.tensor([])) 302 | q = result.samples.clone().detach() 303 | N = len(q) 304 | is_cont = result.is_cont.clone().detach() 305 | gaussian = Normal(0, 1).sample([N]) * is_cont 306 | laplace = Laplace(0, 1).sample([N]) * ~is_cont 307 | p = gaussian + laplace 308 | count += burnin 309 | accept_count = 0 310 | for _ in tqdm(range(count)): 311 | N = len(q) 312 | dt = ((torch.rand(()) + 0.5) * eps).item() 313 | p_cont = p * math.sqrt(1 - alpha * alpha) + Normal(0, alpha).sample([N]) 314 | p_disc = p * math.sqrt(1 - alpha * alpha) + Laplace(0, alpha).sample([N]) 315 | p = p_cont * is_cont + p_disc * ~is_cont 316 | state_0 = State(q, p, is_cont) 317 | state = State(q, p, is_cont) 318 | prev_res = result 319 | rand_uniform = torch.rand(()) 320 | for k in range(K + 1): 321 | for step in range(L): 322 | if not math.isfinite(result.log_weight.item()): 323 | break 324 | result = integrator_step(run_prog, step * dt, dt, state, state_0) 325 | # Note that the acceptance probability differs from the paper in the following way: 326 | # In the implementation, we can have other continuous distributions than 327 | # just normal distributions. 328 | # We treat `x = sample(D)` as `x = sample(normal); score(pdf_D(x) / pdf_normal(x));`. 329 | # this means that the `w(q) * pdfnormal(q)` in the acceptance probability just becomes 330 | # `w(q) * pdf_D(q) = weight(q)` 331 | # because the weight in the implementation includes the prior. 332 | # (`w` refers to the weight as defined in the paper and 333 | # `weight` to the weight as used in the implementation.) 334 | # Similarly 335 | # w(q0 after extension) * pdfnormal(q0 after extension) 336 | # = w(q0 before extension) * pdfnormal(q0 before extension) * pdfnormal(extended part of q0) 337 | # = weight(q0 before extensions) * pdfnormal(extended part of q0) 338 | # For this reason, we add the factor pdfnormal(extended part of q0) in U_0 below. 339 | K_0 = state_0.kinetic_energy() 340 | N2 = len(state_0.q) 341 | U_0 = -prev_res.log_weight - Normal(0, 1).log_prob(state_0.q[N:N2]).sum() 342 | K_new = state.kinetic_energy() 343 | U_new = -result.log_weight 344 | accept_prob = torch.exp(U_0 + K_0 - U_new - K_new) 345 | if U_new.item() != math.inf and rand_uniform < accept_prob: 346 | q = state.q 347 | p = state.p 348 | is_cont = state.is_cont 349 | accept_count += 1 350 | final_samples.append(result.value) 351 | lookahead_stats[k + 1] += 1 352 | break 353 | else: # if we didn't accept the loop and exit before 354 | result = prev_res 355 | p = -p # momentum flip 356 | final_samples.append(prev_res.value) 357 | lookahead_stats[0] += 1 358 | count = len(final_samples) 359 | final_samples = final_samples[burnin:] # discard first samples (burn-in) 360 | print(f"acceptance ratio: {accept_count / count * 100}%") 361 | print(f"lookahead stats: {lookahead_stats}") 362 | return final_samples, lookahead_stats 363 | 364 | 365 | def run_inference( 366 | run_prog: Callable[[torch.Tensor], ProbRun[T]], 367 | name: str, 368 | count: int, 369 | eps: float, 370 | leapfrog_steps: int, 371 | burnin: int = None, 372 | seed: int = None, 373 | **kwargs, 374 | ) -> dict: 375 | """Runs importance sampling and NP-DHMC, then saves the samples to a .pickle file. 376 | 377 | The file is located in the `samples_produced/` folder. 378 | 379 | Note: This is not needed to reproduce the results, but hopefully makes the code easier to understand. 380 | """ 381 | 382 | def run(sampler: Callable) -> dict: 383 | if seed is not None: 384 | torch.manual_seed(seed) 385 | start = time.time() 386 | results = sampler() 387 | stop = time.time() 388 | elapsed = stop - start 389 | return { 390 | "time": elapsed, 391 | "samples": results, 392 | } 393 | 394 | adjusted_count = count * leapfrog_steps 395 | samples = {} 396 | print("Running NP-DHMC...") 397 | samples["hmc"] = run( 398 | lambda: np_dhmc( 399 | run_prog, 400 | count=count, 401 | eps=eps, 402 | leapfrog_steps=leapfrog_steps, 403 | burnin=burnin, 404 | **kwargs, 405 | ), 406 | ) 407 | samples["hmc"]["burnin"] = burnin 408 | samples["hmc"]["eps"] = eps 409 | samples["hmc"]["leapfrog_steps"] = leapfrog_steps 410 | print("Running importance sampling...") 411 | samples["is"] = run( 412 | lambda: importance_resample(run_prog, count=adjusted_count), 413 | ) 414 | weighted, values = samples["is"]["samples"] 415 | samples["is"]["samples"] = values 416 | samples["is"]["weighted"] = weighted 417 | 418 | filename = f"{name}__count{count}_eps{eps}_leapfrogsteps{leapfrog_steps}" 419 | samples["filename"] = filename 420 | with open(f"samples_produced/{filename}.pickle", "wb") as f: 421 | pickle.dump(samples, f) 422 | return samples 423 | 424 | 425 | def run_inference_icml2022( 426 | run_prog: Callable[[torch.Tensor], ProbRun[T]], 427 | name: str, 428 | count: int, 429 | eps: float, 430 | L: int, 431 | K: int = 0, 432 | alpha: float = 1, 433 | burnin: int = None, 434 | seed: int = None, 435 | **kwargs, 436 | ) -> dict: 437 | """Runs NP-LA-DHMC with persistence, then saves the samples to a .pickle file. 438 | 439 | The file is located in the `samples_produced/` folder. 440 | 441 | Note: This is not needed to reproduce the results, but hopefully makes the code easier to understand. 442 | """ 443 | 444 | def run(sampler: Callable) -> dict: 445 | if seed is not None: 446 | torch.manual_seed(seed) 447 | start = time.time() 448 | results, stats = sampler() 449 | stop = time.time() 450 | elapsed = stop - start 451 | return { 452 | "time": elapsed, 453 | "samples": results, 454 | "stats": stats, 455 | } 456 | 457 | # adjusted_count = count * L 458 | samples = {} 459 | # NP-LA-DHMC with persistence 460 | # print(f"Running NP-Lookahead-DHMC with (L = {L}, α = {alpha}, K = {K})...") 461 | persistentstr = "" if alpha == 1.0 else "-persistent" 462 | lastr = "" if K == 0 else "la" 463 | method = f"np{lastr}dhmc{persistentstr}" 464 | samples[method] = run( 465 | lambda: np_lookahead_dhmc( 466 | run_prog, 467 | count=count, 468 | eps=eps, 469 | L=L, 470 | K=K, 471 | burnin=burnin, 472 | alpha=alpha, 473 | **kwargs, 474 | ) 475 | ) 476 | samples[method]["burnin"] = burnin 477 | samples[method]["eps"] = eps 478 | samples[method]["L"] = L 479 | if alpha != 1.0: 480 | samples[method]["alpha"] = alpha 481 | if K != 0: 482 | samples[method]["K"] = K 483 | filename = f"{name}__count{count}_eps{eps}_L{L}_alpha{alpha}_K{K}" 484 | with open(f"lookahead_samples/{filename}.pickle", "wb") as f: 485 | pickle.dump(samples, f) 486 | return samples 487 | -------------------------------------------------------------------------------- /evaluation_dirichlet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Dirichlet Process GMM" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import example_gmm\n", 17 | "import example_dirichlet as dp\n", 18 | "import torch\n", 19 | "\n", 20 | "# Check that the same means and weights were used in the GMM example:\n", 21 | "assert torch.equal(dp.true_means, example_gmm.data_means)\n", 22 | "assert torch.equal(dp.true_weights, torch.ones((example_gmm.num_mixtures,)) / example_gmm.num_mixtures)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "# Load generated data from file:\n", 32 | "import pickle\n", 33 | "\n", 34 | "from evaluation import *\n", 35 | "\n", 36 | "runs = []\n", 37 | "num_chains = 10\n", 38 | "for i in range(num_chains):\n", 39 | " with open(f\"samples_produced/dp_mixture_gmm_{i}__count100_eps0.05_leapfrogsteps20.pickle\", \"rb\") as f:\n", 40 | " runs.append(pickle.load(f))" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 3, 46 | "metadata": { 47 | "tags": [] 48 | }, 49 | "outputs": [ 50 | { 51 | "name": "stderr", 52 | "output_type": "stream", 53 | "text": [ 54 | "100%|██████████| 10/10 [00:07<00:00, 1.38it/s]\n" 55 | ] 56 | } 57 | ], 58 | "source": [ 59 | "from tqdm import tqdm\n", 60 | "\n", 61 | "# Read Anglican files\n", 62 | "for i in tqdm(range(num_chains)):\n", 63 | " for method in anglican_methods:\n", 64 | " runs[i][method] = {}\n", 65 | " with open(f\"anglican_samples/dpmm/{method}{i}.txt\") as f:\n", 66 | " contents = f.read()\n", 67 | " # Skip parentheses:\n", 68 | " assert contents[0] == \"(\"\n", 69 | " assert contents[-1] == \")\"\n", 70 | " contents = contents[1:-1]\n", 71 | " sample_list = contents.split(\")]) ([\")\n", 72 | " # Skip \"([\" at the start:\n", 73 | " assert sample_list[0][:2] == \"([\"\n", 74 | " sample_list[0] = sample_list[0][2:]\n", 75 | " # Skip \")])\" at the end:\n", 76 | " assert sample_list[-1][-3:] == \")])\"\n", 77 | " sample_list[-1] = sample_list[-1][:-3]\n", 78 | " # Parse each sample:\n", 79 | " def parse_sample(sample_string):\n", 80 | " weights, means = sample_string.split(\"] [(\")\n", 81 | " weights = [float(x) for x in weights.split()]\n", 82 | " means = [[float(x) for x in mean.split()] for mean in means.split(\") (\")]\n", 83 | " return weights, means\n", 84 | "\n", 85 | " sample_list = [parse_sample(sample) for sample in sample_list]\n", 86 | " runs[i][method][\"samples\"] = sample_list\n", 87 | "\n", 88 | "# Read timings:\n", 89 | "if anglican_methods:\n", 90 | " timings = parse_anglican_timings(\"anglican_samples/dpmm/timing.txt\")\n", 91 | " for method in anglican_methods:\n", 92 | " for i in range(len(runs)):\n", 93 | " runs[i][method][\"time\"] = timings[method][i]" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 4, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "name": "stdout", 103 | "output_type": "stream", 104 | "text": [ 105 | "\n", 106 | "Running times:\n", 107 | "hmc: 1287.31s 1.2873s per sample (after thinning)\n", 108 | "is: 285.63s 0.2856s per sample (after thinning)\n", 109 | "lmh: 2149.08s 2.1491s per sample (after thinning)\n", 110 | "pgibbs: 1785.46s 1.7855s per sample (after thinning)\n", 111 | "rmh: 2058.37s 2.0584s per sample (after thinning)\n", 112 | "ipmcmc: 369.71s 0.3697s per sample (after thinning)\n" 113 | ] 114 | } 115 | ], 116 | "source": [ 117 | "thinned_runs = thin_runs(all_methods, runs)\n", 118 | "chains = collect_chains(all_methods, thinned_runs)\n", 119 | "print_running_time(all_methods, runs, thinned_runs)" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 5, 125 | "metadata": {}, 126 | "outputs": [ 127 | { 128 | "name": "stdout", 129 | "output_type": "stream", 130 | "text": [ 131 | "Log posterior predictive densities:\n", 132 | "True: -674.81\n", 133 | "hmc: -676.29 +- 0.45 (standard deviation)\n", 134 | "is: -739.74 +- 13.40 (standard deviation)\n", 135 | "lmh: -679.24 +- 1.97 (standard deviation)\n", 136 | "pgibbs: -725.96 +- 9.83 (standard deviation)\n", 137 | "rmh: -679.68 +- 2.97 (standard deviation)\n", 138 | "ipmcmc: -713.23 +- 3.67 (standard deviation)\n" 139 | ] 140 | } 141 | ], 142 | "source": [ 143 | "import torch\n", 144 | "import math\n", 145 | "\n", 146 | "# log posterior predictive density\n", 147 | "def dp_lppd(samples):\n", 148 | " M = len(samples)\n", 149 | " N = len(dp.test_data)\n", 150 | " #compute the probability density for each data point and sapmle (size M x N):\n", 151 | " log_ps = torch.stack([dp.loglikelihoods(torch.tensor(w), torch.tensor(m), dp.test_data) for w, m in samples])\n", 152 | " # average over all samples:\n", 153 | " logp_avg = torch.logsumexp(log_ps, dim=0) - math.log(float(M))\n", 154 | " # sum over all test data points:\n", 155 | " lppd = torch.sum(logp_avg)\n", 156 | " return lppd\n", 157 | "\n", 158 | "def dp_lppd_stats(chains):\n", 159 | " lppds = torch.tensor([dp_lppd(chain) for chain in chains])\n", 160 | " std, mean = torch.std_mean(lppds)\n", 161 | " return f\"{mean.item():.2f} +- {std.item():.2f} (standard deviation)\"\n", 162 | "\n", 163 | "print(\"Log posterior predictive densities:\")\n", 164 | "print(f\"True: {dp_lppd([(dp.true_weights.tolist(), dp.true_means.tolist())]):.2f}\")\n", 165 | "for method in all_methods:\n", 166 | " print(f\"{method}: {dp_lppd_stats(chains[method])}\")" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 6, 172 | "metadata": {}, 173 | "outputs": [ 174 | { 175 | "name": "stderr", 176 | "output_type": "stream", 177 | "text": [ 178 | "100%|██████████| 20/20 [00:19<00:00, 1.01it/s]\n" 179 | ] 180 | }, 181 | { 182 | "data": { 183 | "image/png": "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\n", 184 | "text/plain": [ 185 | "
" 186 | ] 187 | }, 188 | "metadata": { 189 | "needs_background": "light" 190 | }, 191 | "output_type": "display_data" 192 | }, 193 | { 194 | "data": { 195 | "image/png": "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\n", 196 | "text/plain": [ 197 | "
" 198 | ] 199 | }, 200 | "metadata": { 201 | "needs_background": "light" 202 | }, 203 | "output_type": "display_data" 204 | } 205 | ], 206 | "source": [ 207 | "# graph LPPD progression:\n", 208 | "import seaborn as sns\n", 209 | "import matplotlib.pyplot as plt\n", 210 | "import pandas\n", 211 | "from tqdm import tqdm\n", 212 | "lppd_data = []\n", 213 | "for i in tqdm(range(1, 21)):\n", 214 | " for method in compared_methods:\n", 215 | " num_chains = len(chains[method])\n", 216 | " num_samples = len(chains[method][0])\n", 217 | " n = i * num_samples // 20\n", 218 | " truncated_lppds = [dp_lppd(chain[:n]).item() for chain in chains[method]]\n", 219 | " lppd_data += [(method_name[method], n * num_chains, lppd) for lppd in truncated_lppds]\n", 220 | " ground_truth = dp_lppd([(dp.true_weights.tolist(), dp.true_means.tolist())]).item()\n", 221 | " lppd_data.append((\"ground truth\", n * num_chains, ground_truth))\n", 222 | "df = pandas.DataFrame(lppd_data, columns=[\"method\", \"number of samples\", \"log pointwise predictive density\"])\n", 223 | "plt.figure(figsize=(4,4))\n", 224 | "plot = sns.lineplot(\n", 225 | " data=df,\n", 226 | " x=\"number of samples\",\n", 227 | " y=\"log pointwise predictive density\",\n", 228 | " hue=\"method\",\n", 229 | " style=\"method\",\n", 230 | " palette=palette,\n", 231 | ")\n", 232 | "plt.legend(loc=\"lower right\")\n", 233 | "plt.show()\n", 234 | "plot.get_figure().savefig(\"dpmm-lppd-plot.pdf\", bbox_inches=\"tight\")\n", 235 | "\n", 236 | "df_zoomed_in = pandas.DataFrame([lppd for lppd in lppd_data if lppd[0] in [\"ours\", \"LMH\", \"RMH\", \"ground truth\"]], columns=[\"method\", \"number of samples\", \"log pointwise predictive density\"])\n", 237 | "plt.figure(figsize=(4,4))\n", 238 | "plot = sns.lineplot(\n", 239 | " data=df_zoomed_in,\n", 240 | " x=\"number of samples\",\n", 241 | " y=\"log pointwise predictive density\",\n", 242 | " hue=\"method\",\n", 243 | " style=\"method\",\n", 244 | " palette=palette,\n", 245 | ")\n", 246 | "plt.show()\n", 247 | "plot.get_figure().savefig(\"dpmm-lppd-zoomed-in.pdf\", bbox_inches=\"tight\")" 248 | ] 249 | } 250 | ], 251 | "metadata": { 252 | "kernelspec": { 253 | "display_name": "Python 3", 254 | "language": "python", 255 | "name": "python3" 256 | }, 257 | "language_info": { 258 | "codemirror_mode": { 259 | "name": "ipython", 260 | "version": 3 261 | }, 262 | "file_extension": ".py", 263 | "mimetype": "text/x-python", 264 | "name": "python", 265 | "nbconvert_exporter": "python", 266 | "pygments_lexer": "ipython3", 267 | "version": "3.8.10" 268 | }, 269 | "metadata": { 270 | "interpreter": { 271 | "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" 272 | } 273 | }, 274 | "vscode": { 275 | "interpreter": { 276 | "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" 277 | } 278 | } 279 | }, 280 | "nbformat": 4, 281 | "nbformat_minor": 2 282 | } 283 | -------------------------------------------------------------------------------- /evaluation_dirichlet_icml2022.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Dirichlet Process GMM" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import example_gmm\n", 17 | "import example_dirichlet as dp\n", 18 | "import torch\n", 19 | "\n", 20 | "# Check that the same means and weights were used in the GMM example:\n", 21 | "assert torch.equal(dp.true_means, example_gmm.data_means)\n", 22 | "assert torch.equal(dp.true_weights, torch.ones((example_gmm.num_mixtures,)) / example_gmm.num_mixtures)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "# Load generated data from file:\n", 32 | "import pickle\n", 33 | "\n", 34 | "from evaluation_icml2022 import *\n", 35 | "\n", 36 | "experiments = {}\n", 37 | "num_chains = 10\n", 38 | "configs = [\n", 39 | " (L, alpha, K) for L in [20] for alpha in [1.0, 0.5] for K in [0, 1, 2]\n", 40 | "]\n", 41 | "for L, alpha, K in configs:\n", 42 | " key = toconfigstr(None, alpha, K)\n", 43 | " experiments[key] = []\n", 44 | " for i in range(num_chains):\n", 45 | " with open(f\"lookahead_samples/dp_mixture_gmm_{i}__count150_eps0.05_L{L}_alpha{alpha}_K{K}.pickle\", \"rb\") as f:\n", 46 | " experiments[key].append(pickle.load(f))" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 3, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "name": "stdout", 56 | "output_type": "stream", 57 | "text": [ 58 | "effort=defaultdict(, {('npdhmc', ''): [3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000], ('npladhmc', 'K=1'): [3960, 3820, 3880, 4020, 3800, 3900, 3780, 3940, 3900, 3940], ('npladhmc', 'K=2'): [4540, 4500, 4780, 4720, 4720, 4800, 4400, 4460, 4660, 4700], ('npdhmc-persistent', 'α=0.5'): [3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000], ('npladhmc-persistent', 'α=0.5, K=1'): [3960, 4220, 4100, 3960, 4080, 4080, 3920, 4000, 4200, 4000], ('npladhmc-persistent', 'α=0.5, K=2'): [4560, 4660, 4380, 4520, 4520, 4800, 4420, 4520, 4900, 4480]})\n", 59 | "reduction_factor={('npdhmc', ''): [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], ('npladhmc', 'K=1'): [1.32, 1.2733333333333334, 1.2933333333333332, 1.34, 1.2666666666666666, 1.3, 1.26, 1.3133333333333332, 1.3, 1.3133333333333332], ('npladhmc', 'K=2'): [1.5133333333333334, 1.5, 1.5933333333333333, 1.5733333333333333, 1.5733333333333333, 1.6, 1.4666666666666666, 1.4866666666666666, 1.5533333333333332, 1.5666666666666667], ('npdhmc-persistent', 'α=0.5'): [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], ('npladhmc-persistent', 'α=0.5, K=1'): [1.32, 1.4066666666666667, 1.3666666666666667, 1.32, 1.36, 1.36, 1.3066666666666666, 1.3333333333333333, 1.4, 1.3333333333333333], ('npladhmc-persistent', 'α=0.5, K=2'): [1.52, 1.5533333333333332, 1.46, 1.5066666666666666, 1.5066666666666666, 1.6, 1.4733333333333334, 1.5066666666666666, 1.6333333333333333, 1.4933333333333334]}\n", 60 | "min_len=91\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "adjusted_experiments = adjust_for_iteration_time(experiments)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 4, 71 | "metadata": {}, 72 | "outputs": [ 73 | { 74 | "name": "stdout", 75 | "output_type": "stream", 76 | "text": [ 77 | "\n", 78 | "npdhmc: 1211.31s 0.8075s per sample (after thinning)\n", 79 | "K=1\n", 80 | "npladhmc: 1109.07s 0.9636s per sample (after thinning)\n", 81 | "K=2\n", 82 | "npladhmc: 1106.90s 1.1423s per sample (after thinning)\n", 83 | "α=0.5\n", 84 | "npdhmc-persistent: 1229.99s 0.8200s per sample (after thinning)\n", 85 | "α=0.5, K=1\n", 86 | "npladhmc-persistent: 1048.32s 0.9478s per sample (after thinning)\n", 87 | "α=0.5, K=2\n", 88 | "npladhmc-persistent: 1088.93s 1.1134s per sample (after thinning)\n" 89 | ] 90 | } 91 | ], 92 | "source": [ 93 | "chains = {}\n", 94 | "for config, runs in adjusted_experiments.items():\n", 95 | " print(f\"{config}\")\n", 96 | " thinned_runs = thin_runs(runs)\n", 97 | " chains.update(collect_chains(thinned_runs, config=config))\n", 98 | " print_running_time(runs, thinned_runs)" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 5, 104 | "metadata": {}, 105 | "outputs": [ 106 | { 107 | "name": "stdout", 108 | "output_type": "stream", 109 | "text": [ 110 | "Acceptance ratios (lookahead statistics in parentheses):\n", 111 | "NP-DHMC: 69.7% (69.7%)\n", 112 | "NP-Lookahead-DHMC (K=1): 78.3% (70.2%, 8.1%)\n", 113 | "NP-Lookahead-DHMC (K=2): 80.4% (68.9%, 7.9%, 3.6%)\n", 114 | "NP-DHMC pers. (α=0.5): 66.7% (66.7%)\n", 115 | "NP-Lookahead-DHMC pers. (α=0.5, K=1): 74.0% (64.9%, 9.1%)\n", 116 | "NP-Lookahead-DHMC pers. (α=0.5, K=2): 82.1% (69.4%, 8.7%, 4.0%)\n" 117 | ] 118 | } 119 | ], 120 | "source": [ 121 | "print(\"Acceptance ratios (lookahead statistics in parentheses):\")\n", 122 | "for config, runs in experiments.items():\n", 123 | " for method in runs[0].keys():\n", 124 | " stats = sum(torch.tensor(run[method][\"stats\"]) for run in runs)\n", 125 | " ratios = (stats.true_divide(torch.sum(stats))).tolist()\n", 126 | " accept_ratio = 1.0 - ratios[0]\n", 127 | " ratios_str = \", \".join(f\"{r*100:.1f}%\" for r in ratios[1:])\n", 128 | " print(f\"{legend_str((method, config))}: {accept_ratio * 100:.1f}% ({ratios_str})\")" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 6, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "name": "stdout", 138 | "output_type": "stream", 139 | "text": [ 140 | "Log posterior predictive densities:\n", 141 | "True: -674.81\n", 142 | "NP-DHMC: -676.11 +- 0.43 (standard deviation)\n", 143 | "NP-Lookahead-DHMC (K=1): -676.14 +- 0.57 (standard deviation)\n", 144 | "NP-Lookahead-DHMC (K=2): -676.19 +- 0.75 (standard deviation)\n", 145 | "NP-DHMC pers. (α=0.5): -676.13 +- 0.87 (standard deviation)\n", 146 | "NP-Lookahead-DHMC pers. (α=0.5, K=1): -675.94 +- 1.23 (standard deviation)\n", 147 | "NP-Lookahead-DHMC pers. (α=0.5, K=2): -675.59 +- 0.80 (standard deviation)\n" 148 | ] 149 | } 150 | ], 151 | "source": [ 152 | "import torch\n", 153 | "import math\n", 154 | "\n", 155 | "# log posterior predictive density\n", 156 | "def dp_lppd(samples):\n", 157 | " M = len(samples)\n", 158 | " N = len(dp.test_data)\n", 159 | " #compute the probability density for each data point and sapmle (size M x N):\n", 160 | " log_ps = torch.stack([dp.loglikelihoods(torch.tensor(w), torch.tensor(m), dp.test_data) for w, m in samples])\n", 161 | " # average over all samples:\n", 162 | " logp_avg = torch.logsumexp(log_ps, dim=0) - math.log(float(M))\n", 163 | " # sum over all test data points:\n", 164 | " lppd = torch.sum(logp_avg)\n", 165 | " return lppd\n", 166 | "\n", 167 | "def dp_lppd_stats(chains):\n", 168 | " lppds = torch.tensor([dp_lppd(chain) for chain in chains])\n", 169 | " std, mean = torch.std_mean(lppds)\n", 170 | " return f\"{mean.item():.2f} +- {std.item():.2f} (standard deviation)\"\n", 171 | "\n", 172 | "print(\"Log posterior predictive densities:\")\n", 173 | "print(f\"True: {dp_lppd([(dp.true_weights.tolist(), dp.true_means.tolist())]):.2f}\")\n", 174 | "for method in chains.keys():\n", 175 | " print(f\"{legend_str(method)}: {dp_lppd_stats(chains[method])}\")" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 7, 181 | "metadata": {}, 182 | "outputs": [ 183 | { 184 | "name": "stderr", 185 | "output_type": "stream", 186 | "text": [ 187 | "100%|██████████| 19/19 [00:21<00:00, 1.15s/it]\n" 188 | ] 189 | } 190 | ], 191 | "source": [ 192 | "# graph LPPD progression:\n", 193 | "from tqdm import tqdm\n", 194 | "lppd_data = []\n", 195 | "num_samples = max(len(c) for cs in chains.values() for c in cs)\n", 196 | "resolution = 20\n", 197 | "# start = 1\n", 198 | "start = 2\n", 199 | "for i in tqdm(range(start, resolution + 1)):\n", 200 | " for method in chains.keys():\n", 201 | " num_chains = len(chains[method])\n", 202 | " min_chain_length = min(len(c) for c in chains[method])\n", 203 | " n = i * num_samples // resolution\n", 204 | " if n <= min_chain_length:\n", 205 | " truncated_lppds = [dp_lppd(chain[:n]).item() for chain in chains[method]]\n", 206 | " else:\n", 207 | " truncated_lppds = [None for _ in chains[method]]\n", 208 | " lppd_data += [(legend_str(method), n, lppd) for lppd in truncated_lppds]\n", 209 | " ground_truth = dp_lppd([(dp.true_weights.tolist(), dp.true_means.tolist())]).item()\n", 210 | " lppd_data.append((\"ground truth\", n, ground_truth))" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 8, 216 | "metadata": {}, 217 | "outputs": [ 218 | { 219 | "data": { 220 | "image/png": "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k8P111/PiBEjGDduHCEhIbz66qvs3LmTDRs2MGXKFE+jfPDgQSZOnMiePXto2LAhK1euBGD8+PE8/fTT7Nixg9mzZ3P77c5gXF26dOHjjz8mPT2d4cOHM2vWLAAeeughunTpwp49exg0aBDffvut1+9bUlKS50sFwLRp0+jQoYPnVdrw4cNZvnw53333HVar1dPAA+zevZuOHTtWWebmzZvL5PvTn/5Ely5duOuuuzxp2dnZJepS/PX5559XWU5ycjKbNp1tYFflVuVEPmNM9aJV1HJF339C3q4FhPeZSdBFCT4d2jcO4wyk4wqf68jLxtagOdZQDeihzq2Lhrxb6f4maY9Xuj8ibiQRceWt61W5+vXrM3LkSObOnUtoaMlRtCeffJIXX3yRiIgIVqxY4dXf3v79+4mKiqJNmzYAjBo1innz5tGtW7dy0ydPngzAwIEDufvuu7n55psB53LY9913Hxs3bsRisfD99997euhRUVGehrVjx45kZmaSk5PDli1bGDJkiKcu7pGEQ4cOMWzYMLKyssjPzycqKgqAjRs3smrVKsA5rN2oUSOv37fSowKPPfYYN954o2fbfU/frU+fPvzlL3+hefPmDBs2zOtyisvKyiqz5vu1117La6+9xtSpU2nWrBngDGBUPBpidTVr1qxEtEN1dirs6YvI3a5/nxaRuaVf566KNUvhN+9jqd8CS6MobHbfTqwryjcEuXr5AKYgl6BmGlRH1S2TJ09mwYIF/PbbbyXS3ff0N23aRGpqKlu3bvX0GMub2Hc2OnfuzLp16zyN6bJlyzhy5Ag7duxg165dNG/enLy8PMA5/O1mtVopLCzE4XDQsGFDdu3a5Xm577nfcccdTJo0iYyMDP797397zlORefPmea6zosYvPT29WgGTgoKC6NixI48//niJLwcAcXFx7Nixo8pzhIaGlqn78OHDmTBhAtdddx3Z2dnA2ff08/LyynwBVGeusuF996yQ7cCOcl51Tt43uzC/fElQmz6IxYLF5ruhfWMMgmBzhc8tys3GGtEMa1jZCTRK1WaNGzdm6NChLFiwoNJ8nTp18jSoAwYMKDdPTEwMmZmZfPHFFwAsXbqUa665psJ0t4cffphGjRoxceJEwDk5rVmzZtjtdjZs2MA333xTad3q169PVFQUL7/8MuD8+/70008952rRogUAixcv9hzT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\n", 221 | "text/plain": [ 222 | "
" 223 | ] 224 | }, 225 | "metadata": { 226 | "needs_background": "light" 227 | }, 228 | "output_type": "display_data" 229 | } 230 | ], 231 | "source": [ 232 | "# graph LPPD progression:\n", 233 | "import seaborn as sns\n", 234 | "import matplotlib.pyplot as plt\n", 235 | "import pandas\n", 236 | "\n", 237 | "df = pandas.DataFrame([lppd for lppd in lppd_data if lppd[0] not in [\"IS (L=20)\", \"IS (L=4)\"]], columns=[\"method\", \"number of samples\", \"log pointwise predictive density\"])\n", 238 | "plt.figure(figsize=(8,5))\n", 239 | "plot = sns.lineplot(\n", 240 | " data=df,\n", 241 | " x=\"number of samples\",\n", 242 | " y=\"log pointwise predictive density\",\n", 243 | " hue=\"method\",\n", 244 | " style=\"method\",\n", 245 | ")\n", 246 | "plt.legend(loc=\"lower right\")\n", 247 | "plt.show()\n", 248 | "plot.get_figure().savefig(\"icml2022-dpmm-lppd-plot.pdf\", bbox_inches=\"tight\")" 249 | ] 250 | } 251 | ], 252 | "metadata": { 253 | "kernelspec": { 254 | "display_name": "Python 3", 255 | "language": "python", 256 | "name": "python3" 257 | }, 258 | "language_info": { 259 | "codemirror_mode": { 260 | "name": "ipython", 261 | "version": 3 262 | }, 263 | "file_extension": ".py", 264 | "mimetype": "text/x-python", 265 | "name": "python", 266 | "nbconvert_exporter": "python", 267 | "pygments_lexer": "ipython3", 268 | "version": "3.8.10" 269 | }, 270 | "metadata": { 271 | "interpreter": { 272 | "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" 273 | } 274 | }, 275 | "vscode": { 276 | "interpreter": { 277 | "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" 278 | } 279 | } 280 | }, 281 | "nbformat": 4, 282 | "nbformat_minor": 2 283 | } 284 | --------------------------------------------------------------------------------