├── .gitignore ├── 01_beta_vae.ipynb ├── 02_diffusion.ipynb ├── 03_normalizing_flows.ipynb ├── 04_continuous_normalizing_flows.ipynb ├── 05_consistency_models.ipynb ├── 06_flow_matching.ipynb ├── 07_diffusion_distillation.ipynb ├── 08_discrete_walk_jump_sampling.ipynb ├── LICENSE.md ├── README.md ├── assets └── midwit.png └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | .DS_Store 163 | data/* 164 | data/*.hdf5 165 | notebooks/ckpts 166 | notebooks/ckpts* 167 | scripts/*.out 168 | logging/ 169 | wandb 170 | wandb/* 171 | notebooks/data -------------------------------------------------------------------------------- /03_normalizing_flows.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 5, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from functools import partial\n", 10 | "\n", 11 | "import jax\n", 12 | "\n", 13 | "from jax import config\n", 14 | "config.update(\"jax_enable_x64\", True)\n", 15 | "\n", 16 | "import jax.numpy as np\n", 17 | "import flax.linen as nn\n", 18 | "import optax\n", 19 | "import diffrax as dfx\n", 20 | "import math\n", 21 | "from tensorflow_probability.substrates import jax as tfp\n", 22 | "\n", 23 | "from sklearn import datasets, preprocessing\n", 24 | "\n", 25 | "import matplotlib.pyplot as plt\n", 26 | "from tqdm import trange" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## The Dataset" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 6, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "data": { 43 | "text/plain": [ 44 | "(-2.0, 2.0)" 45 | ] 46 | }, 47 | "execution_count": 6, 48 | "metadata": {}, 49 | "output_type": "execute_result" 50 | }, 51 | { 52 | "data": { 53 | "image/png": "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y8Rs6Bw8eFB+9KMfycMPP3zCberr68Xr9UpmZqYpXlBQIPX19eo+a9eulTVr1gw8DoVCMm3aNKmurh7RN2q8+6QlPNl6snjePO/JgOfN854MPsnInKxDZCgG3dC555575KGHHjrpNnv37pXy8vKBx7W1tbJy5Uq5+uqrZfXq1YM/ypPw+Xzi8/kgHgwGJ9UN8omMjAye9yTC855ceN6Ty2Q9b6dzZMuHB93Queuuu+Smm2466TYzZswY+O/jx4/LsmXLZMmSJfL444+fdL/CwkKJRCISCoVMvToNDQ1SWFg42EMlIiKiSW7QDZ28vDzJy8uztG1tba0sW7ZMFi1aJE899dQpW2mLFi0Sj8cjmzZtkquuukpERPbv3y/V1dVSUVEx2EMlIiKiSS5pw8tra2tl6dKlUlpaKg8//LA0NTVJfX29qdamtrZWysvL5d133xWRv6SbbrnlFlmzZo1s3rxZdu7cKTfffLNUVFSohcgan88n999/v5rOsjOeN897MuB587wnA573yJ530oaXb9iwQW6++Wb1/33ykkeOHJGysjLZvHmzLF26VET+MmHgXXfdJb/61a8kHA7LihUr5Mc//jFTV0RERDRoozqPDhEREdFo4qKeREREZFts6BAREZFtsaFDREREtsWGDhEREdnWhG/oHDlyRG655RYpKyuTlJQUmTlzptx///0SiUROul9fX5/cfvvtkpOTI2lpaXLVVVdJQ0PDKB31yPjWt74lS5YskUAgAMtmnMhNN90kDofD9G/lypXJPdARNpTzNgxD7rvvPikqKpKUlBRZvny5HDhwILkHOsJaW1vl+uuvl4yMDMnMzJRbbrlFurq6TrrP0qVL4Xp/+ctfHqUjHprHHntMpk+fLn6/XxYvXjww/cSJPPfcc1JeXi5+v1/mz58vv/vd70bpSEfWYM57w4YNcF39fv8oHu3IeOONN+Szn/2sFBcXi8PhkBdffPGU+2zZskXOPvts8fl8MmvWLNmwYUPSj3OkDfa8t2zZAtfb4XCccGmk8WjdunVy7rnnSnp6uuTn58uqVatk//79p9xvJD7fE76hs2/fPonH4/LTn/5U9uzZI4888oisX79e7r333pPu97WvfU1eeuklee6552Tr1q1y/Phx+cIXvjBKRz0yIpGIXH311XLbbbcNar+VK1dKXV3dwL9f/epXSTrC5BjKeX/nO9+RH/7wh7J+/XrZvn27pKamyooVK6Svry+JRzqyrr/+etmzZ49s3LhRXn75ZXnjjTfk1ltvPeV+q1evNl3v73znO6NwtEPz7LPPypo1a+T++++X999/XxYsWCArVqyQxsZGdfu3335brrvuOrnllltk165dsmrVKlm1apXs3r17lI98eAZ73iJ/WR7g09f16NGjo3jEI6O7u1sWLFggjz32mKXtq6qq5IorrpBly5ZJZWWl3HnnnfLFL35R/vCHPyT5SEfWYM/7E/v37zdd8/z8/CQd4cjbunWr3H777fLOO+/Ixo0bpb+/Xy699FLp7u4+4T4j9vk2bOg73/mOUVZWdsL/HwqFDI/HYzz33HMDsb179xoiYmzbtm00DnFEPfXUU0YwGLS07Y033mhceeWVST2e0WL1vOPxuFFYWGh897vfHYiFQiHD5/MZv/rVr5J4hCPno48+MkTEeO+99wZiv//97w2Hw2HU1taecL+LL77Y+OpXvzoKRzgyzjvvPOP2228feByLxYzi4mJj3bp16vZ///d/b1xxxRWm2OLFi40vfelLST3OkTbY8x7MZ36iEBHjhRdeOOk2d999t3HGGWeYYtdcc42xYsWKJB5Zclk5782bNxsiYrS1tY3KMY2GxsZGQ0SMrVu3nnCbkfp8T/geHU17e/tJVz/duXOn9Pf3y/Llywdi5eXlUlpaKtu2bRuNQxxTW7Zskfz8fJkzZ47cdttt0tLSMtaHlFRVVVVSX19vut7BYFAWL148Ya73tm3bJDMzU84555yB2PLly8XpdMr27dtPuu8vf/lLyc3NlXnz5snatWulp6cn2Yc7JJFIRHbu3Gm6Tk6nU5YvX37C67Rt2zbT9iIiK1asmDDXVWRo5y0i0tXVJdOmTZOSkhK58sorZc+ePaNxuGPKDtd7OBYuXChFRUXymc98Rt56662xPpxhaW9vFxE56W/1SF3vQa91Nd4dPHhQfvSjH8nDDz98wm3q6+vF6/VCfUdBQcGEynkOxcqVK+ULX/iClJWVyaFDh+Tee++Vyy67TLZt2yYul2usDy8pPrmmBQUFpvhEut719fXQTe12uyU7O/uk5/AP//APMm3aNCkuLpYPPvhA/vVf/1X2798vzz//fLIPedCam5slFoup12nfvn3qPvX19RP6uooM7bznzJkjTz75pJx55pnS3t4u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", 54 | "text/plain": [ 55 | "
" 56 | ] 57 | }, 58 | "metadata": {}, 59 | "output_type": "display_data" 60 | } 61 | ], 62 | "source": [ 63 | "n_samples = 100_000\n", 64 | "\n", 65 | "x, _ = datasets.make(n_samples=n_samples, noise=.06)\n", 66 | "\n", 67 | "scaler = preprocessing.StandardScaler()\n", 68 | "x = scaler.fit_transform(x)\n", 69 | "\n", 70 | "plt.hist2d(x[:, 0], x[:, 1], bins=100)\n", 71 | "plt.xlim(-2 ,2)\n", 72 | "plt.ylim(-2, 2)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "## Implementation" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 10, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "class MLP(nn.Module):\n", 89 | " \"\"\" A simple MLP in Flax.\n", 90 | " \"\"\"\n", 91 | " hidden_dim: int = 32\n", 92 | " out_dim: int = 2\n", 93 | " n_layers: int = 3\n", 94 | "\n", 95 | " @nn.compact\n", 96 | " def __call__(self, x):\n", 97 | " for _ in range(self.n_layers):\n", 98 | " x = nn.Dense(features=self.hidden_dim)(x)\n", 99 | " x = nn.gelu(x)\n", 100 | " x = nn.Dense(features=self.out_dim)(x)\n", 101 | " return x" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 59, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "class AffineBijector:\n", 111 | " def __init__(self, shift_and_log_scale):\n", 112 | " self.shift_and_log_scale = shift_and_log_scale\n", 113 | "\n", 114 | " def forward_and_log_det(self, x):\n", 115 | " shift, log_scale = np.split(self.shift_and_log_scale, 2, axis=-1)\n", 116 | " y = x * np.exp(log_scale) + shift\n", 117 | " log_det = log_scale\n", 118 | " return y, log_det\n", 119 | "\n", 120 | " def inverse_and_log_det(self, y):\n", 121 | " shift, log_scale = np.split(self.shift_and_log_scale, 2, axis=-1)\n", 122 | " x = (y - shift) * np.exp(-log_scale)\n", 123 | " log_det = -log_scale\n", 124 | " return x, log_det\n", 125 | "\n", 126 | "class MaskedCoupling:\n", 127 | " def __init__(self, mask, conditioner, bijector):\n", 128 | " \"\"\"Coupling layer with masking and conditioner.\"\"\"\n", 129 | " self.mask = mask\n", 130 | " self.conditioner = conditioner\n", 131 | " self.bijector = bijector\n", 132 | "\n", 133 | " def forward_and_log_det(self, x):\n", 134 | " \"\"\"Transforms masked indices of `x` conditioned on unmasked indices using bijector.\"\"\"\n", 135 | " x_cond = np.where(self.mask, 0.0, x)\n", 136 | " bijector_params = self.conditioner(x_cond)\n", 137 | " y, log_det = self.bijector(bijector_params).forward_and_log_det(x)\n", 138 | " log_det = np.where(self.mask, log_det, 0.0)\n", 139 | " y = np.where(self.mask, y, x)\n", 140 | " return y, np.sum(log_det, axis=-1)\n", 141 | "\n", 142 | " def inverse_and_log_det(self, y):\n", 143 | " \"\"\"Transforms masked indices of `y` conditioned on unmasked indices using bijector.\"\"\"\n", 144 | " y_cond = np.where(self.mask, 0.0, y)\n", 145 | " bijector_params = self.conditioner(y_cond)\n", 146 | " x, log_det = self.bijector(bijector_params).inverse_and_log_det(y)\n", 147 | " log_det = np.where(self.mask, log_det, 0.0)\n", 148 | " x = np.where(self.mask, x, y)\n", 149 | " return x, np.sum(log_det, axis=-1)" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 102, 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [ 158 | "class RealNVP(nn.Module):\n", 159 | " n_transforms: int = 4\n", 160 | " d_params: int = 2\n", 161 | " d_hidden: int = 128\n", 162 | " n_layers: int = 4\n", 163 | "\n", 164 | " def setup(self):\n", 165 | " self.mask_list = [np.arange(self.d_params) % 2 == i % 2 for i in range(self.n_transforms)]\n", 166 | " self.conditioner_list = [MLP(self.d_hidden, 2 * self.d_params, self.n_layers) for _ in range(self.n_transforms)]\n", 167 | " self.base_dist = tfp.distributions.Normal(loc=np.zeros(self.d_params), scale=np.ones(self.d_params))\n", 168 | " \n", 169 | " def log_prob(self, x):\n", 170 | " log_prob = np.zeros(x.shape[:-1])\n", 171 | " for mask, conditioner in zip(self.mask_list[::-1], self.conditioner_list[::-1]):\n", 172 | " x, ldj = MaskedCoupling(mask, conditioner, AffineBijector).inverse_and_log_det(x)\n", 173 | " log_prob += ldj\n", 174 | " return log_prob + self.base_dist.log_prob(x).sum(-1)\n", 175 | "\n", 176 | " def sample(self, sample_shape, key, n_transforms=None):\n", 177 | " x = self.base_dist.sample(key, sample_shape)\n", 178 | " for mask, conditioner in zip(self.mask_list[:n_transforms], self.conditioner_list[:n_transforms]):\n", 179 | " x, _ = MaskedCoupling(mask, conditioner, AffineBijector).forward_and_log_det(x)\n", 180 | " return x\n", 181 | "\n", 182 | " def __call__(self, x):\n", 183 | " return self.log_prob(x)" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": 103, 189 | "metadata": {}, 190 | "outputs": [ 191 | { 192 | "data": { 193 | "text/plain": [ 194 | "Array([-3.35979128, -2.8513697 , -2.8622152 , -3.03087519], dtype=float64)" 195 | ] 196 | }, 197 | "execution_count": 103, 198 | "metadata": {}, 199 | "output_type": "execute_result" 200 | } 201 | ], 202 | "source": [ 203 | "model = RealNVP()\n", 204 | "\n", 205 | "key = jax.random.PRNGKey(0)\n", 206 | "params = model.init(key, x[:2])\n", 207 | "\n", 208 | "# Test log_prob\n", 209 | "model.apply(params, x[:4])" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 104, 215 | "metadata": {}, 216 | "outputs": [], 217 | "source": [ 218 | "opt = optax.adam(learning_rate=1e-3)\n", 219 | "opt_state = opt.init(params)" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": 105, 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [ 228 | "@jax.jit\n", 229 | "def train_step(params, opt_state, x):\n", 230 | " def loss_fn(params):\n", 231 | " return -model.apply(params, x).mean()\n", 232 | " loss, grad = jax.value_and_grad(loss_fn)(params)\n", 233 | " updates, opt_state = opt.update(grad, opt_state)\n", 234 | " params = optax.apply_updates(params, updates)\n", 235 | " return loss, params, opt_state" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 106, 241 | "metadata": {}, 242 | "outputs": [ 243 | { 244 | "name": "stderr", 245 | "output_type": "stream", 246 | "text": [ 247 | "100%|██████████| 10000/10000 [01:31<00:00, 108.79it/s, val=1.407515638835097]\n" 248 | ] 249 | } 250 | ], 251 | "source": [ 252 | "n_steps = 10_000\n", 253 | "n_batch = 128\n", 254 | "\n", 255 | "with trange(n_steps) as steps:\n", 256 | " for step in steps:\n", 257 | "\n", 258 | " # Draw a random batches from x\n", 259 | " key, subkey = jax.random.split(key)\n", 260 | " idx = jax.random.choice(key, x.shape[0], shape=(n_batch,))\n", 261 | " \n", 262 | " x_batch = x[idx]\n", 263 | "\n", 264 | " loss, params, opt_state = train_step(params, opt_state, x_batch)\n", 265 | "\n", 266 | " steps.set_postfix(val=loss)" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "Generate some samples." 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": 107, 279 | "metadata": {}, 280 | "outputs": [], 281 | "source": [ 282 | "n_samples = 100_000\n", 283 | "x_sample = model.apply(params, key, (n_samples,), method=model.sample)" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": 108, 289 | "metadata": {}, 290 | "outputs": [ 291 | { 292 | "data": { 293 | "text/plain": [ 294 | "(-2.0, 2.0)" 295 | ] 296 | }, 297 | "execution_count": 108, 298 | "metadata": {}, 299 | "output_type": "execute_result" 300 | }, 301 | { 302 | "data": { 303 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjoAAAGiCAYAAADulWxzAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABjjElEQVR4nO3de5RU5Zkv/mfXva9V3fQdGmguAioCYsBGR2EkAfVkZHQcY/yNlzE4euQsDU4cycroUSeH0ZhokjGDnowSZzQXJ4oZzZjBVnTUFgXpIAgt94aGavpWVX2t6/794bGT9vs07qar+rL7+1mLteKTvWu/VXtX9du1v/28hmmaphARERHZkGOkB0BERESUKZzoEBERkW1xokNERES2xYkOERER2RYnOkRERGRbnOgQERGRbXGiQ0RERLbFiQ4RERHZFic6REREZFuc6BAREZFtZXSis379evnSl74keXl5UlJSIqtWrZL6+vov3O/555+X2bNni8/nk7lz58pvf/vbTA6TiIiIbCqjE50333xTbr/9dnnvvfdk8+bNEo/H5Stf+Yp0dXUNuM+7774r1157rdx8882yY8cOWbVqlaxatUp27dqVyaESERGRDRnDuahnc3OzlJSUyJtvvikXXXSRus0111wjXV1d8vLLL/fVzj//fJk/f75s2LBhuIZKRERENuAazoOFw2ERESksLBxwm9raWlm7dm2/2ooVK2TTpk3q9tFoVKLRaN9/p1IpaWtrkwkTJohhGEMfNBEREWWcaZrS0dEhFRUV4nCk74bTsE10UqmU3HnnnXLBBRfI2WefPeB2wWBQSktL+9VKS0slGAyq269fv17uv//+tI6ViIiIRsbRo0dl0qRJaXu8YZvo3H777bJr1y55++230/q469at6/cNUDgclsmTJ8uFcpm4xJ3WYxGNJMPphJqZTJ72vobXA7VUd4+lx3N4lH1jMUvHtTpmIhofXgo/IyIikUhEKisrJS8vL62PPywTnTVr1sjLL78sb7311hfO0srKyqSpqalframpScrKytTtvV6veL1eqLvELS6DEx2yD8NQJg2Gta93tX0NQ5msGAlLj+dQ3lspA+N+QxkzEY0P+fn5/f473bGTjH7imKYpa9askRdffFFef/11qaqq+sJ9qqurpaampl9t8+bNUl1dnalhEhERkU1l9Bud22+/XZ57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", 304 | "text/plain": [ 305 | "
" 306 | ] 307 | }, 308 | "metadata": {}, 309 | "output_type": "display_data" 310 | } 311 | ], 312 | "source": [ 313 | "\n", 314 | "plt.hist2d(x_sample[:, 0], x_sample[:, 1], bins=100)\n", 315 | "plt.xlim(-2 ,2)\n", 316 | "plt.ylim(-2, 2)" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": null, 322 | "metadata": {}, 323 | "outputs": [], 324 | "source": [] 325 | } 326 | ], 327 | "metadata": { 328 | "kernelspec": { 329 | "display_name": "torch-mps", 330 | "language": "python", 331 | "name": "python3" 332 | }, 333 | "language_info": { 334 | "codemirror_mode": { 335 | "name": "ipython", 336 | "version": 3 337 | }, 338 | "file_extension": ".py", 339 | "mimetype": "text/x-python", 340 | "name": "python", 341 | "nbconvert_exporter": "python", 342 | "pygments_lexer": "ipython3", 343 | "version": "3.9.13" 344 | } 345 | }, 346 | "nbformat": 4, 347 | "nbformat_minor": 2 348 | } 349 | -------------------------------------------------------------------------------- /04_continuous_normalizing_flows.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Continuous normalizing flows" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [ 15 | { 16 | "name": "stderr", 17 | "output_type": "stream", 18 | "text": [ 19 | "/opt/homebrew/Caskroom/miniforge/base/envs/torch-mps/lib/python3.9/site-packages/equinox/_ad.py:753: UserWarning: As of Equinox 0.10.7, `equinox.filter_custom_vjp.defvjp` is deprecated in favour of `.def_fwd` and `.def_bwd`. This new API supports symbolic zeros, which allow for more efficient autodifferentiation rules. In particular:\n", 20 | "- the fwd and bwd functions take an extra `perturbed` argument, which indicates which primals actually need a gradient. You can use this to skip computing the gradient for any unperturbed value. (You can also safely just ignore this if you wish.)\n", 21 | "- `None` was previously passed to indicate a symbolic zero gradient for all objects that weren't inexact arrays, but all inexact arrays always had an array-valued gradient. Now, `None` may also be passed to indicate that an inexact array has a symbolic zero gradient.\n", 22 | " warnings.warn(\n" 23 | ] 24 | } 25 | ], 26 | "source": [ 27 | "from functools import partial\n", 28 | "\n", 29 | "import jax\n", 30 | "\n", 31 | "from jax import config\n", 32 | "config.update(\"jax_enable_x64\", True)\n", 33 | "\n", 34 | "import jax.numpy as np\n", 35 | "import flax.linen as nn\n", 36 | "import optax\n", 37 | "import diffrax as dfx\n", 38 | "import math\n", 39 | "from tensorflow_probability.substrates import jax as tfp\n", 40 | "\n", 41 | "from sklearn import datasets, preprocessing\n", 42 | "\n", 43 | "import matplotlib.pyplot as plt\n", 44 | "from tqdm import trange" 45 | ] 46 | }, 47 | { 48 | "attachments": {}, 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "## The Dataset" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 2, 58 | "metadata": {}, 59 | "outputs": [ 60 | { 61 | "data": { 62 | "text/plain": [ 63 | "(-2.0, 2.0)" 64 | ] 65 | }, 66 | "execution_count": 2, 67 | "metadata": {}, 68 | "output_type": "execute_result" 69 | }, 70 | { 71 | "data": { 72 | "image/png": "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yCOP9EJvg/PKK69g8eLFWLFiBXbu3ImJEydi1qxZOH78uHr8+++/j5tuugm33347PvroIxQWFqKwsBC7d+/u5Z73THdfN/D19gB/+74eOnSoF3scGi0tLZg4cSKefPJJq+MrKipw1VVXYebMmdi1axcWLVqEH/7wh3jrrbfC3NPQ6u7r/kZ5ebnrPR8xYkSYehh6W7ZswYIFC7Bt2zZs2rQJp06dwhVXXIGWlpYOzwnZ59vxoEceecTJysrq8P/X19c7MTExzrp16wJte/fudQA4paWlvdHFkHruueechIQEq2Pnzp3rXHPNNWHtT2+xfd3t7e1OWlqa8+ijjwba6uvrHb/f77z00kth7GHo/OlPf3IAOB988EGg7c0333SioqKco0ePdnjejBkznLvvvrsXehgaU6dOdRYsWBB4fObMGWfkyJFOUVGRevzf//3fO1dddZWrbdq0ac6dd94Z1n6GWndfd3c+8/0FAGf9+vWdHnPvvfc63/3ud11tN9xwgzNr1qww9iy8bF73O++84wBw6urqeqVPveH48eMOAGfLli0dHhOqz3e/n9HRNDQ0dLr7aVlZGU6dOoX8/PxAW3Z2NkaPHo3S0tLe6GKfKikpwYgRIzBu3DjcddddqK2t7esuhVVFRQWqq6td73dCQgKmTZvWb97v0tJSJCYm4qKLLgq05efnY9CgQdi+fXun5/72t7/F2WefjQkTJmDZsmU4efJkuLsblLa2NpSVlbnep0GDBiE/P7/D96m0tNR1PADMmjWr37yvQHCvGwCam5uRmZmJjIwMXHPNNdizZ09vdLdPeeH97olJkyYhPT0dl19+Od57772+7k6PNDQ0AECnf6tD9X53e6+rSLd//3488cQTWLVqVYfHVFdXw+fzifUdqamp/SrmGYyCggJcd911yMrKwoEDB3Dfffdh9uzZKC0txeDBg/u6e2HxzXuamprqau9P73d1dbWYpo6Ojsbw4cM7fQ0333wzMjMzMXLkSHzyySf42c9+hvLycrz66qvh7nK3/eUvf8GZM2fU92nfvn3qOdXV1f36fQWCe93jxo3Ds88+iwsuuAANDQ1YtWoVcnNzsWfPHpxzzjm90e0+0dH73djYiC+//BJDhw7to56FV3p6OtasWYOLLroIra2t+PWvf428vDxs374dF154YV93r9va29uxaNEifO9738OECRM6PC5Un++IndFZunSpuvjqb3/ML4GjR4+ioKAAc+bMwfz58/uo5z0TzOvujhtvvBHf//73cf7556OwsBCvv/46PvjgA5SUlITuRQQh3K87UoX7dd9xxx2YNWsWzj//fNxyyy144YUXsH79ehw4cCCEr4J6W05ODm699VZMmjQJM2bMwKuvvoqUlBQ8/fTTfd01CoNx48bhzjvvxJQpU5Cbm4tnn30Wubm5WL16dV93LSgLFizA7t278fLLL/fK80XsjM6SJUswb968To8ZM2ZM4L+PHTuGmTNnIjc3F88880yn56WlpaGtrQ319fWuWZ2ampo+31Oru6+7p8aMGYOzzz4b+/fvx2WXXRay63ZXOF/3N+9pTU0N0tPTA+01NTWYNGlSUNcMFdvXnZaWJhamnj59GidOnOjWPTtt2jQAX898jh07ttv9Daezzz4bgwcPFtmPnX0u09LSunV8JArmdZtiYmIwefJk7N+/PxxdjBgdvd/x8fGenc3pyNSpU7F169a+7ka3LVy4MJBM0dXsY6g+3xE70ElJSUFKSorVsUePHsXMmTMxZcoUPPfccxg0qPOJqilTpiAmJgbFxcW4/vrrAXy9mr2yshI5OTk97ntPdOd1h8KRI0dQW1vrGgD0hXC+7qysLKSlpaG4uDgwsGlsbMT27du7nbEWaravOycnB/X19SgrK8OUKVMAAG+//Tba29sDgxcbu3btAoA+f781Pp8PU6ZMQXFxMQoLCwF8PcVdXFyMhQsXqufk5OSguLgYixYtCrRt2rSpzz/H3RHM6zadOXMGn376Ka688sow9rTv5eTkiPTi/vZ+h8quXbsi8nPcEcdx8OMf/xjr169HSUkJsrKyujwnZJ/vYFZLR5IjR4445557rnPZZZc5R44ccaqqqgI/f3vMuHHjnO3btwfafvSjHzmjR4923n77befDDz90cnJynJycnL54CUE7dOiQ89FHHzkrV650hg0b5nz00UfORx995DQ1NQWOGTdunPPqq686juM4TU1Nzk9+8hOntLTUqaiocDZv3uxceOGFznnnned89dVXffUyuq27r9txHOdf/uVfnMTEROcPf/iD88knnzjXXHONk5WV5Xz55Zd98RKCUlBQ4EyePNnZvn27s3XrVue8885zbrrppsD/N+/z/fv3Ow888IDz4YcfOhUVFc4f/vAHZ8yYMc6ll17aVy+hSy+//LLj9/udtWvXOn/605+cO+64w0lMTHSqq6sdx3GcH/zgB87SpUsDx7/33ntOdHS0s2rVKmfv3r3OihUrnJiYGOfTTz/tq5cQlO6+7pUrVzpvvfWWc+DAAaesrMy58cYbnSFDhjh79uzpq5cQlKampsDnF4Dzi1/8wvnoo4+cQ4cOOY7jOEuXLnV+8IMfBI4/ePCgExsb6/z0pz919u7d6zz55JPO4MGDnY0bN/bVSwhKd1/36tWrnddee83585//7Hz66afO3Xff7QwaNMjZvHlzX72EbrvrrruchIQEp6SkxPV3+uTJk4FjwvX57vcDneeee84BoP58o6KiwgHgvPPOO4G2L7/80vnHf/xHJykpyYmNjXWuvfZa1+CoP5g7d676uv/2dQJwnnvuOcdxHOfkyZPOFVdc4aSkpDgxMTFOZmamM3/+/MCXaX/R3dftOF+nmN9///1Oamqq4/f7ncsuu8wpLy/v/c73QG1trXPTTTc5w4YNc+Lj453bbrvNNbgz7/PKykrn0ksvdYYPH+74/X7n3HPPdX760586DQ0NffQK7DzxxBPO6NGjHZ/P50ydOtXZtm1b4P/NmDHDmTt3ruv43/3ud863v/1tx+fzOd/97nedP/7xj73c49DozutetGhR4NjU1FTnyiuvdHbu3NkHve6Zb9KmzZ9vXuvcuXOdGTNmiHMmTZrk+Hw+Z8yYMa7PeX/R3df98MMPO2PHjnWGDBniDB8+3MnLy3Pefvvtvul8kDr6O/2371+4Pt9R/9sBIiIiIs+J2KwrIiIiop7iQIeIiIg8iwMdIiIi8iwOdIiIiMizONAhIiIiz+JAh4iIiDyLAx0iIiLyLA50iIiIyLM40CEiIiLP4kCHiIiIPIsDHSIiIvKs/x+yPq5UFwV/FwAAAABJRU5ErkJggg==", 73 | "text/plain": [ 74 | "
" 75 | ] 76 | }, 77 | "metadata": {}, 78 | "output_type": "display_data" 79 | } 80 | ], 81 | "source": [ 82 | "n_samples = 10_000\n", 83 | "\n", 84 | "x, _ = datasets.make_moons(n_samples=n_samples, noise=.06)\n", 85 | "\n", 86 | "scaler = preprocessing.StandardScaler()\n", 87 | "x = scaler.fit_transform(x)\n", 88 | "\n", 89 | "plt.hist2d(x[:, 0], x[:, 1], bins=100)\n", 90 | "plt.xlim(-2 ,2)\n", 91 | "plt.ylim(-2, 2)" 92 | ] 93 | }, 94 | { 95 | "attachments": {}, 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "## CNFs\n", 100 | "\n", 101 | "_TODO: Harmonize notation with code and add details._\n", 102 | "\n", 103 | "The evolution of the log-density follows the instantaneous change-of-variables formula:\n", 104 | "$$\\frac{\\partial \\log p({z}(t))}{\\partial t}=-\\operatorname{Tr}\\left(\\frac{\\partial f}{\\partial {z}(t)}\\right)$$\n", 105 | "\n", 106 | "Get total change in log-density by integrating across time:\n", 107 | "$$\\log p_1\\left({z}\\left(t_1\\right)\\right)=\\log p_0\\left({z}\\left(t_0\\right)\\right)-\\int_{t_0}^{t_1} \\operatorname{Tr}\\left(\\frac{\\partial f}{\\partial {z}(t)}\\right) d t$$\n", 108 | "\n", 109 | "We can get an unbiased estimate of the trace of a matrix by taking a double product of that matrix with a noise vector.\n", 110 | "$$\\operatorname{Tr}(A)=E_{p({\\epsilon})}\\left[{\\epsilon}^T A {\\epsilon}\\right]$$\n", 111 | "\n", 112 | "Typically we'd also need to implement backprop-ing through an ODE with e.g. adjoints, but Diffrax will take care of this for us here." 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 3, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "class MLP(nn.Module):\n", 122 | " \"\"\" A simple MLP in Flax.\n", 123 | " \"\"\"\n", 124 | " hidden_dim: int = 32\n", 125 | " out_dim: int = 2\n", 126 | " n_layers: int = 3\n", 127 | "\n", 128 | " @nn.compact\n", 129 | " def __call__(self, x):\n", 130 | " for _ in range(self.n_layers):\n", 131 | " x = nn.Dense(features=self.hidden_dim)(x)\n", 132 | " x = nn.gelu(x)\n", 133 | " x = nn.Dense(features=self.out_dim)(x)\n", 134 | " return x" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "## Implementation\n", 142 | "\n", 143 | "Adapted from [Diffrax](https://docs.kidger.site/diffrax/examples/continuous_normalising_flow/)." 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 4, 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [ 152 | "def logp_exact(t, y, args):\n", 153 | " \"\"\" Compute trace directly.\n", 154 | " \"\"\"\n", 155 | " y, _ = y\n", 156 | " _, func = args\n", 157 | " t = np.atleast_1d(t)\n", 158 | "\n", 159 | " fn = lambda y: func(np.concatenate([y, t])) # Augmented function\n", 160 | " f, f_vjp = jax.vjp(fn, y) # VJPs can be computed at the ~same cost as computing f through reverse-mode AD\n", 161 | "\n", 162 | " # Compute trace\n", 163 | " (size,) = y.shape\n", 164 | " (dfdy,) = jax.vmap(f_vjp)(np.eye(size))\n", 165 | " logp = np.trace(dfdy)\n", 166 | " return f, logp\n", 167 | "\n", 168 | "def logp_approx(t, y, args):\n", 169 | " \"\"\" Approx. trace using Hutchinson's trace estimator.\n", 170 | " \"\"\"\n", 171 | " y, _ = y\n", 172 | " z, func = args\n", 173 | " t = np.atleast_1d(t)\n", 174 | " \n", 175 | " fn = lambda y: func(np.concatenate([y, t])) # Augmented function\n", 176 | " f, f_vjp = jax.vjp(fn, y) # VJPs can be computed at the ~same cost as computing f through reverse-mode AD\n", 177 | " \n", 178 | " # Trace estimator\n", 179 | " (z_dfdy,) = f_vjp(z)\n", 180 | " logp = np.sum(z_dfdy * z)\n", 181 | " return f, logp" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 5, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "key = jax.random.PRNGKey(0)\n", 191 | "t = np.ones((x.shape[0], 1))\n", 192 | "\n", 193 | "f = MLP(hidden_dim=64, out_dim=2, n_layers=3)\n", 194 | "params = f.init(key, np.concatenate([x, t], axis=1))" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 6, 200 | "metadata": {}, 201 | "outputs": [ 202 | { 203 | "data": { 204 | "text/plain": [ 205 | "Array([3.156627 , 2.2985032, 3.1344635, 3.863559 , 2.96205 , 2.404113 ,\n", 206 | " 2.4348328, 3.7143767, 3.1841269, 3.1162772, 3.385356 , 3.1589055,\n", 207 | " 3.0098429, 2.634412 , 2.9587815, 3.777234 , 3.6070356, 3.055339 ,\n", 208 | " 3.1169815, 3.5339994, 1.9420253, 2.847947 , 1.9530888, 3.5562863,\n", 209 | " 3.1166618, 2.3222103, 2.065152 , 2.128791 , 3.3884706, 1.9760624,\n", 210 | " 3.2089186, 2.6790004], dtype=float32)" 211 | ] 212 | }, 213 | "execution_count": 6, 214 | "metadata": {}, 215 | "output_type": "execute_result" 216 | } 217 | ], 218 | "source": [ 219 | "t0 = 0.0\n", 220 | "t1 = 1.0\n", 221 | "dt0 = 1e-2\n", 222 | "logp = 'exact'\n", 223 | "\n", 224 | "# Runs backward-in-time to train the CNF\n", 225 | "def loss_fn(params, y, f):\n", 226 | " if logp == 'exact':\n", 227 | " term = dfx.ODETerm(logp_exact)\n", 228 | " elif logp == 'approx':\n", 229 | " term = dfx.ODETerm(logp_approx)\n", 230 | " else:\n", 231 | " raise NotImplementedError\n", 232 | " solver = dfx.Heun()\n", 233 | " eps = jax.random.normal(key, y.shape)\n", 234 | " delta_log_likelihood = 0.0\n", 235 | " y = (y, delta_log_likelihood)\n", 236 | " func = lambda x: f.apply(params, x)\n", 237 | " sol = dfx.diffeqsolve(term, solver, t1, t0, -dt0, y, (eps, func))\n", 238 | " (z,), (delta_log_likelihood,) = sol.ys\n", 239 | " log_prob = delta_log_likelihood + tfp.distributions.Normal(loc=0., scale=1.).log_prob(z).sum()\n", 240 | " return - log_prob\n", 241 | "\n", 242 | "jax.vmap(loss_fn, in_axes=(None, 0, None))(params, x[:32], f)" 243 | ] 244 | }, 245 | { 246 | "attachments": {}, 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "## Train" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 16, 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [ 259 | "opt = optax.adamw(learning_rate=3e-4, weight_decay=1e-4)\n", 260 | "opt_state = opt.init(params)\n", 261 | "\n", 262 | "@partial(jax.jit, static_argnums=(2,))\n", 263 | "def loss_fn_vmapped(params, x_batch, f):\n", 264 | " loss = jax.vmap(loss_fn, in_axes=(None, 0, None))(params, x_batch, f)\n", 265 | " return loss.mean()\n", 266 | "\n", 267 | "@partial(jax.jit, static_argnums=(3,))\n", 268 | "def train_step(params, opt_state, x_batch, f):\n", 269 | " loss, grad = jax.value_and_grad(loss_fn_vmapped)(params, x_batch, f)\n", 270 | " updates, opt_state = opt.update(grad, opt_state, params)\n", 271 | " params = optax.apply_updates(params, updates)\n", 272 | " return loss, params, opt_state" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": 17, 278 | "metadata": {}, 279 | "outputs": [ 280 | { 281 | "name": "stderr", 282 | "output_type": "stream", 283 | "text": [ 284 | "100%|██████████| 2000/2000 [06:38<00:00, 5.02it/s, val=1.4153568]\n" 285 | ] 286 | } 287 | ], 288 | "source": [ 289 | "n_steps = 2000\n", 290 | "n_batch = 32\n", 291 | "\n", 292 | "with trange(n_steps) as steps:\n", 293 | " for step in steps:\n", 294 | "\n", 295 | " # Draw a random batches from x\n", 296 | " key, _ = jax.random.split(key)\n", 297 | " idx = jax.random.choice(key, x.shape[0], shape=(n_batch,))\n", 298 | "\n", 299 | " x_batch = x[idx]\n", 300 | "\n", 301 | " loss, params, opt_state = train_step(params, opt_state, x_batch, f)\n", 302 | " \n", 303 | " # loss, grads = jax.value_and_grad(loss_fn_vmapped)(params, x_batch, f)\n", 304 | " # updates, opt_state = opt.update(grads, opt_state, params)\n", 305 | "\n", 306 | " # params = optax.apply_updates(params, updates)\n", 307 | "\n", 308 | " steps.set_postfix(val=loss)" 309 | ] 310 | }, 311 | { 312 | "attachments": {}, 313 | "cell_type": "markdown", 314 | "metadata": {}, 315 | "source": [ 316 | "## Sampling" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 18, 322 | "metadata": {}, 323 | "outputs": [ 324 | { 325 | "data": { 326 | "text/plain": [ 327 | "Array([ 0.60950206, -0.50103378], dtype=float64, weak_type=True)" 328 | ] 329 | }, 330 | "execution_count": 18, 331 | "metadata": {}, 332 | "output_type": "execute_result" 333 | } 334 | ], 335 | "source": [ 336 | "def single_sample_fn(params, key, n_dim=2):\n", 337 | " \"\"\" Produce single sample from the CNF by integrating forward.\n", 338 | " \"\"\"\n", 339 | " z = jax.random.normal(key, (n_dim,))\n", 340 | " def func(t, x, args):\n", 341 | " t = np.atleast_1d(t)\n", 342 | " return f.apply(params, np.concatenate([x, t]))\n", 343 | " term = dfx.ODETerm(func)\n", 344 | " solver = dfx.Heun()\n", 345 | " sol = dfx.diffeqsolve(term, solver, t0, t1, dt0, z)\n", 346 | " (y,) = sol.ys\n", 347 | " return y\n", 348 | "\n", 349 | "single_sample_fn(params, key)" 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": 19, 355 | "metadata": {}, 356 | "outputs": [], 357 | "source": [ 358 | "sample_fn = partial(single_sample_fn, params)\n", 359 | "\n", 360 | "n_samples = 100\n", 361 | "sample_key = jax.random.split(key, n_samples ** 2)\n", 362 | "x_sample = jax.vmap(sample_fn)(sample_key)" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": 20, 368 | "metadata": {}, 369 | "outputs": [ 370 | { 371 | "data": { 372 | "image/png": "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zjlH8vPzZebMmbJjx4726G6H6QyftR+jRo2S3Nxc+cpXviJ///vfO7o7HabTbczY0p49e+TRRx+Vhx9++LTHVFRUSEpKisqZyM7O7tTxxBkzZsjVV18tBQUFsnfvXrnnnnvksssuk+LiYklMTOzo7sXEp59ndnZ2s/agfNYVFRVqmjgpKUkyMzPP2P+vf/3rcs4550heXp6888478p3vfEd27dolL774Yqy73GofffSRNDY2mp/Rzp07zXMqKioC+5l+qi33PXToUHnqqafki1/8okQiEXn44Ydl4sSJsmPHDnOD2s7gdJ91VVWVfPLJJ9K9e/cO6lls5ebmyooVK2TcuHFSV1cnv/jFL2TKlCmyefNmGTNmTEd3r90FZuZlyZIlZrLS539a/oLv379fZsyYIbNnz5b58+d3UM/bri333BrXXXedfPWrX5URI0bIrFmzZO3atfLWW2/Jhg0boncTbRDr+45Hsb7nW265RaZPny4jRoyQG264QZ555hl56aWXZO/evVG8C7S3wsJCuemmm2TUqFEyefJkefHFFyUrK0ueeOKJju4aomzo0KHyrW99S8aOHSsTJ06Up556SiZOnCjLly/v6K51iMDMvCxevFjmzp17xmMGDRrU9N8HDhyQqVOnysSJE+XJJ58843k5OTlSX18vx44dazb7UllZKTk5OX667Utr79mvQYMGSd++fWXPnj1yySWXRO11WyuW9/3p51lZWSm5ublN7ZWVlTJq1Kg2vWY0uN5zTk6OSt48efKkHD16tFXP6oQJE0TkXzOT5513Xqv7G0t9+/aVxMREtdrvTL+POTk5rTo+HrXlvltKTk6W0aNHy549e2LRxbhwus86IyOj0866nM748ePl9ddf7+hudIjADF6ysrIkKyvL6dj9+/fL1KlTZezYsbJy5Urp1u3ME0xjx46V5ORkKSoqkmuuuUZE/pXRXVZWJoWFhb773latuedo2Ldvnxw5cqTZX+odIZb3XVBQIDk5OVJUVNQ0WKmqqpLNmze3eqVWNLnec2FhoRw7dky2bt0qY8eOFRGR1157TU6dOtU0IHFRUlIiItLhn7UlJSVFxo4dK0VFRTJr1iwRETl16pQUFRXJggULzHMKCwulqKhIFi5c2NS2bt26Dv39ba223HdLjY2N8u6778rll18ew552rMLCQrUMPmifdbSUlJTE5e9wu+jojOFo27dvnzd48GDvkksu8fbt2+cdPHiw6efzxwwdOtTbvHlzU9utt97qDRw40Hvttde8LVu2eIWFhV5hYWFH3EKbfPjhh97bb7/t3X///V5aWpr39ttve2+//bZXXV3ddMzQoUO9F1980fM8z6uurva+/e1ve8XFxV5paan36quvemPGjPGGDBni1dbWdtRttFpr79vzPO/HP/6x16tXL2/NmjXeO++8482cOdMrKCjwPvnkk464hVabMWOGN3r0aG/z5s3e66+/7g0ZMsS7/vrrm/5/y+d7z5493g9+8ANvy5YtXmlpqbdmzRpv0KBB3qRJkzrqFs7q+eef90KhkLdq1SrvH//4h3fLLbd4vXr18ioqKjzP87wbb7zRW7JkSdPxf//7372kpCTv4Ycf9t577z3vvvvu85KTk7133323o26hTVp73/fff7/3l7/8xdu7d6+3detW77rrrvNSU1O9HTt2dNQttFp1dXXT762IeD/5yU+8t99+2/vwww89z/O8JUuWeDfeeGPT8f/85z+9Hj16eHfddZf33nvveY899piXmJjovfLKKx11C23S2vtevny5t3r1am/37t3eu+++691xxx1et27dvFdffbWjbqFDdbrBy8qVKz0RMX8+VVpa6omIt379+qa2Tz75xPvf//t/e7179/Z69Ojh/du//VuzAU+8mzNnjnnPn79HEfFWrlzpeZ7nnThxwrv00ku9rKwsLzk52TvnnHO8+fPnN31JBkVr79vz/rVc+nvf+56XnZ3thUIh75JLLvF27drV/p1voyNHjnjXX3+9l5aW5mVkZHg333xzs8Fay+e7rKzMmzRpkpeZmemFQiFv8ODB3l133eVFIpEOugM3jz76qDdw4EAvJSXFGz9+vPfGG280/b/Jkyd7c+bMaXb8b3/7W+/888/3UlJSvAsvvNB7+eWX27nH0dGa+164cGHTsdnZ2d7ll1/ubdu2rQN63XafLgFu+fPpfc6ZM8ebPHmyOmfUqFFeSkqKN2jQoGa/30HR2vt+4IEHvPPOO89LTU31MjMzvSlTpnivvfZax3Q+DiR4nufFfn4HAAAgOgKz2ggAAECEwQsAAAgYBi8AACBQGLwAAIBAYfACAAAChcELAAAIFAYvAAAgUBi8AACAQGHwAgAAAoXBCwAACBQGLwAAIFAYvAAAgED5/wEDjeQgN3hMCgAAAABJRU5ErkJggg==", 373 | "text/plain": [ 374 | "
" 375 | ] 376 | }, 377 | "metadata": {}, 378 | "output_type": "display_data" 379 | } 380 | ], 381 | "source": [ 382 | "\n", 383 | "plt.hist2d(x_sample[:, 0], x_sample[:, 1], bins=100);" 384 | ] 385 | }, 386 | { 387 | "cell_type": "code", 388 | "execution_count": null, 389 | "metadata": {}, 390 | "outputs": [], 391 | "source": [] 392 | }, 393 | { 394 | "cell_type": "code", 395 | "execution_count": null, 396 | "metadata": {}, 397 | "outputs": [], 398 | "source": [] 399 | } 400 | ], 401 | "metadata": { 402 | "kernelspec": { 403 | "display_name": "torch-mps", 404 | "language": "python", 405 | "name": "python3" 406 | }, 407 | "language_info": { 408 | "codemirror_mode": { 409 | "name": "ipython", 410 | "version": 3 411 | }, 412 | "file_extension": ".py", 413 | "mimetype": "text/x-python", 414 | "name": "python", 415 | "nbconvert_exporter": "python", 416 | "pygments_lexer": "ipython3", 417 | "version": "3.9.13" 418 | }, 419 | "orig_nbformat": 4 420 | }, 421 | "nbformat": 4, 422 | "nbformat_minor": 2 423 | } 424 | -------------------------------------------------------------------------------- /07_diffusion_distillation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "af32d529", 6 | "metadata": {}, 7 | "source": [ 8 | "# Diffusion distillation (WiP)" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "8d2c8c5d", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from functools import partial\n", 19 | "\n", 20 | "import jax\n", 21 | "import jax.numpy as np\n", 22 | "import flax.linen as nn\n", 23 | "import optax\n", 24 | "import diffrax as dfx\n", 25 | "\n", 26 | "from sklearn import datasets, preprocessing\n", 27 | "\n", 28 | "import matplotlib.pyplot as plt\n", 29 | "from tqdm import trange" 30 | ] 31 | }, 32 | { 33 | "attachments": {}, 34 | "cell_type": "markdown", 35 | "id": "6fcdfbd3", 36 | "metadata": {}, 37 | "source": [ 38 | "## The dataset" 39 | ] 40 | }, 41 | { 42 | "attachments": {}, 43 | "cell_type": "markdown", 44 | "id": "c6de9e86", 45 | "metadata": {}, 46 | "source": [ 47 | "We'll use two moons to keep things simple." 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 34, 53 | "id": "cdab2cc6-b2dc-4834-b343-0386a25fd9a9", 54 | "metadata": {}, 55 | "outputs": [ 56 | { 57 | "data": { 58 | "text/plain": [ 59 | "(-2.0, 2.0)" 60 | ] 61 | }, 62 | "execution_count": 34, 63 | "metadata": {}, 64 | "output_type": "execute_result" 65 | }, 66 | { 67 | "data": { 68 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjoAAAGiCAYAAADulWxzAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABmGUlEQVR4nO3de3hU9bk3/HsOmck5Q84JBAgHAQUBUTDYKlQU1G2lutlqu+vhsdi6tW8t7rbi260Pdvdlt9Vit9sW3d1K+1Rba6tYbatFBKyKqEDkjJwTQs5hMjnOZGbW+4dPU9f63sBKyOSw8v1cV66LuVkzs2bWmskv675/v9tlGIYhRERERA7kHugdICIiIkoUDnSIiIjIsTjQISIiIsfiQIeIiIgciwMdIiIiciwOdIiIiMixONAhIiIix+JAh4iIiByLAx0iIiJyLA50iIiIyLESOtBZuXKlXHTRRZKRkSH5+fmyePFi2b9//xnv98ILL8jkyZMlOTlZpk2bJn/6058SuZtERETkUAkd6GzatEnuvvtuee+992TdunXS1dUlV155pbS1tZ3yPu+++67cfPPNcscdd8j27dtl8eLFsnjxYtm1a1cid5WIiIgcyNWfTT3r6+slPz9fNm3aJJdeeqm6zY033ihtbW3y6quvdscuvvhimTFjhqxevbq/dpWIiIgcwNufT9bc3CwiItnZ2afcZvPmzbJs2TJTbOHChbJ27Vp1+3A4LOFwuPt2PB6XpqYmycnJEZfLdfY7TURERAlnGIa0tLRIcXGxuN19l3Dqt4FOPB6Xe++9Vy655BKZOnXqKberqamRgoICU6ygoEBqamrU7VeuXCkrVqzo030lIiKigVFZWSmjRo3qs8frt4HO3XffLbt27ZK33367Tx93+fLlpitAzc3NMnr0aKmsrJTMzMw+fS4iIiJKjFAoJCUlJZKRkdGnj9svA5177rlHXn31VXnrrbfOOEorLCyU2tpaU6y2tlYKCwvV7f1+v/j9fohnZmZyoENERDTE9HXZSUJnXRmGIffcc4+89NJL8uabb0ppaekZ71NWVibr1683xdatWydlZWWJ2k0iIiJyqIRe0bn77rvlueeek5dfflkyMjK662yysrIkJSVFRERuueUWGTlypKxcuVJERL7xjW/IZZddJo8++qhcc8018pvf/EY+/PBDeeqppxK5q0RE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xKg1t7Y3JmKe038Sa3sCe3GqenCKMq0ziM8Zsy773W4vP8rp5UNHrNXcwsOjnP/hCVhDY7hx+f5YqnJeJWEe3+rc6ccgdmhDKcTq5mObkpxy3F+PF786otW4DMJwZfezqLUC0KZ/+4P4HSOitA2JmL8XkrHkT203Yv3uE9HbzFjPZTLTli2RkfjZjgRwuYC6C/G4eFrwe/62Kzaabh/pwOKb6IlUiGUfsdFPQkTiNXguWA213zUJHeh86Utfkurqalm3bp10dXXJ7bffLnfeeac899xzp73f0qVL5eGHH+6+nZqKB42IiIjoTBI20Nm7d6+89tpr8sEHH8iFF35SFf7444/L1VdfLY888ogUFxef8r6pqalSWFiYqF0jIiKiYSJhA53NmzdLIBDoHuSIiCxYsEDcbrds2bJFvvCFL5zyvs8++6z86le/ksLCQrn22mvl3/7t3055VSccDks4/PcVJUMhnCapXWZTu69au0wrKyMnteIlx5R6LHXqSsVLjqn1uB/JtWeetudKwsM01FamHM6s55rWZd63ZT/EXLOws3VrJZ63JecHIbaqaKvp9mcb8PMW17Ihikg2rsacfBDPWy0lZ32tQ+2Sd6Jpn21PNeabMpRjkNSG923xmL+LMqrw/fa04feaKKnIuM3p5dY0ulOPsfY7A7bJzcZgENN9viD+PkutxljWFWdOB2d68bOYXqp0Kv8ogPtxVElTZeLnOFrfcMb9GMwSNtCpqamR/HxzbtLr9Up2drbU1Jz64H3xi1+UMWPGSHFxsezYsUO+853vyP79++XFF19Ut1+5cqWsWLGiT/ediIiInKHHA537779ffvCDH5x2m7179/Z6h+68887uf0+bNk2Kiork8ssvl0OHDsn48eNh++XLl8uyZcu6b4dCISkpwT4iRERENPz0eKBz3333yW233XbabcaNGyeFhYVSV1dnikejUWlqaupR/c2cOXNEROTgwYPqQMfv94vfj5d1iYiIiHo80MnLy5O8vLwzbldWVibBYFC2bt0qs2bNEhGRN998U+LxePfgxY7y8nIRESkqwmmvZ0OrcYHplCHMrbpGYKfopFbMSeftOPOUXxERb3WTeb+yM2Eboylo67FocOptPVVSA55/7gjWCRz5Pf4BcO0XFpluT8s+AdtUfgZrhfamj4GYJ4LFPClpWE9gNNurjxuu1C7W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gUBA7rjjDmltbT3tfebNmwfH+2tf+1o/7XHvPPHEEzJ27FhJTk6WOXPmdC8/cSovvPCCTJ48WZKTk2XatGnypz/9qZ/2tG/15HWvWbMGjmtycnI/7m3feOutt+Taa6+V4uJicblcsnbt2jPeZ+PGjXLBBReI3++XCRMmyJo1axK+n32tp69748aNcLxdLtcpWyMNRitXrpSLLrpIMjIyJD8/XxYvXiz79+8/4/364vM95Ac6+/btk3g8Lk8++aTs3r1bVq1aJatXr5YHHnjgtPf75je/Ka+88oq88MILsmnTJjlx4oRcf/31/bTXfSMSiciSJUvkrrvu6tH9Fi1aJNXV1d0/v/71rxO0h4nRm9f9wx/+UP7zP/9TVq9eLVu2bJG0tDRZuHChdHZ2JnBP+9aXvvQl2b17t6xbt05effVVeeutt+TOO+884/2WLl1qOt4//OEP+2Fve+f555+XZcuWyUMPPSTbtm2T6dOny8KFC6Wurk7d/t1335Wbb75Z7rjjDtm+fbssXrxYFi9eLLt27ernPT87PX3dIp+0B/j0cT127Fg/7nHfaGtrk+nTp8sTTzxha/sjR47INddcI/Pnz5fy8nK599575Stf+Yq8/vrrCd7TvtXT1/03+/fvNx3z/Pz8BO1h39u0aZPcfffd8t5778m6deukq6tLrrzySmlrazvlffrs82040A9/+EOjtLT0lP8fDAaNpKQk44UXXuiO7d271xARY/Pmzf2xi33qmWeeMbKysmxte+uttxrXXXddQvenv9h93fF43CgsLDR+9KMfdceCwaDh9/uNX//61wncw76zZ88eQ0SMDz74oDv25z//2XC5XEZVVdUp73fZZZcZ3/jGN/phD/vG7Nmzjbvvvrv7diwWM4qLi42VK1eq2//TP/2Tcc0115hic+bMMb761a8mdD/7Wk9fd08+80OFiBgvvfTSabf59re/bZx33nmm2I033mgsXLgwgXuWWHZe94YNGwwRMU6ePNkv+9Qf6urqDBExNm3adMpt+urzPeSv6Giam5tP2/1069at0tXVJQsWLOiOTZ48WUaPHi2bN2/uj10cUBs3bpT8/HyZNGmS3HXXXdLY2DjQu5RQR44ckZqaGtPxzsrKkjlz5gyZ471582YJBAJy4YUXdscWLFggbrdbtmzZctr7Pvvss5KbmytTp06V5cuXS3t7e6J3t1cikYhs3brVdJzcbrcsWLDglMdp8+bNpu1FRBYuXDhkjqtI7163iEhra6uMGTNGSkpK5LrrrpPdu3f3x+4OKCcc77MxY8YMKSoqkiuuuELeeeedgd6ds9Lc3Cwictrf1X11vHvc62qwO3jwoDz++OPyyCOPnHKbmpoa8fl8UN9RUFAwpHKevbFo0SK5/vrrpbS0VA4dOiQPPPCAXHXVVbJ582bxeDwDvXsJ8bdjWlBQYIoPpeNdU1MDl6m9Xq9kZ2ef9jV88YtflDFjxkhxcbHs2LFDvvOd78j+/fvlxRdfTPQu91hDQ4PEYjH1OO3bt0+9T01NzZA+riK9e92TJk2Sp59+Ws4//3xpbm6WRx55RObOnSu7d++WUaNG9cduD4hTHe9QKCQdHR2SkpIyQHuWWEVFRbJ69Wq58MILJRwOy89//nOZN2+ebNmyRS644IKB3r0ei8fjcu+998oll1wiU6dOPeV2ffX5HrRXdO6//361+OrTP9YvgaqqKlm0aJEsWbJEli5dOkB7fnZ687p74qabbpLPf/7zMm3aNFm8eLG8+uqr8sEHH8jGjRv77kX0QqJf92CV6Nd95513ysKFC2XatGnypS99SX75y1/KSy+9JIcOHerDV0H9raysTG655RaZMWOGXHbZZfLiiy9KXl6ePPnkkwO9a5QAkyZNkq9+9asya9YsmTt3rjz99NMyd+5cWbVq1UDvWq/cfffdsmvXLvnNb37TL883aK/o3HfffXLbbbeddptx48Z1//vEiRMyf/58mTt3rjz11FOnvV9hYaFEIhEJBoOmqzq1tbUD3lOrp6/7bI0bN05yc3Pl4MGDcvnll/fZ4/ZUIl/3345pbW2tFBUVdcdra2tlxowZvXrMvmL3dRcWFkJhajQalaamph6ds3PmzBGRT658jh8/vsf7m0i5ubni8Xhg9uPpPpeFhYU92n4w6s3rtkpKSpKZM2fKwYMHE7GLg8apjndmZqZjr+acyuzZs+Xtt98e6N3osXvuuad7MsWZrj721ed70A508vLyJC8vz9a2VVVVMn/+fJk1a5Y888wz4naf/kLVrFmzJCkpSdavXy833HCDiHxSzV5RUSFlZWVnve9noyevuy8cP35cGhsbTQOAgZDI111aWiqFhYWyfv367oFNKBSSLVu29HjGWl+z+7rLysokGAzK1q1bZdasWSIi8uabb0o8Hu8evNhRXl4uIjLgx1vj8/lk1qxZsn79elm8eLGIfHKJe/369XLPPfeo9ykrK5P169fLvffe2x1bt27dgH+Oe6I3r9sqFovJzp075eqrr07gng68srIymF481I53XykvLx+Un+NTMQxDvv71r8tLL70kGzdulNLS0jPep88+372plh5Mjh8/bkyYMMG4/PLLjePHjxvV1dXdP5/eZtKkScaWLVu6Y1/72teM0aNHG2+++abx4YcfGmVlZUZZWdlAvIReO3bsmLF9+3ZjxYoVRnp6urF9+3Zj+/btRktLS/c2kyZNMl588UXDMAyjpaXF+Nd//Vdj8+bNxpEjR4w33njDuOCCC4yJEycanZ2dA/Uyeqynr9swDOM//uM/jEAgYLz88svGjh07jOuuu84oLS01Ojo6BuIl9MqiRYuMmTNnGlu2bDHefvttY+LEicbNN9/c/f/W8/zgwYPGww8/bHz44YfGkSNHjJdfftkYN26ccemllw7USzij3/zmN4bf7zfWrFlj7Nmzx7jzzjuNQCBg1NTUGIZhGF/+8peN+++/v3v7d955x/B6vcYjjzxi7N2713jooYeMpKQkY+fOnQP1Enqlp697xYoVxuuvv24cOnTI2Lp1q3HTTTcZycnJxu7duwfqJfRKS0tL9+dXRIwf//jHxvbt241jx44ZhmEY999/v/HlL3+5e/vDhw8bqampxre+9S1j7969xhNPPGF4PB7jtddeG6iX0Cs9fd2rVq0y1q5daxw4cMDYuXOn8Y1vfMNwu93GG2+8MVAvocfuuusuIysry9i4caPp93R7e3v3Non6fA/5gc4zzzxjiIj68zdHjhwxRMTYsGFDd6yjo8P4l3/5F2PEiBFGamqq8YUvfME0OBoKbr31VvV1f/p1iojxzDPPGIZhGO3t7caVV15p5OXlGUlJScaYMWOMpUuXdn+ZDhU9fd2G8ckU83/7t38zCgoKDL/fb1x++eXG/v37+3/nz0JjY6Nx8803G+np6UZmZqZx++23mwZ31vO8oqLCuPTSS43s7GzD7/cbEyZMML71rW8Zzc3NA/QK7Hn88ceN0aNHGz6fz5g9e7bx3nvvdf/fZZddZtx6662m7X/7298a55xzjuHz+YzzzjvP+OMf/9jPe9w3evK677333u5tCwoKjKuvvtrYtm3bAOz12fnbtGnrz99e66233mpcdtllcJ8ZM2YYPp/PGDdunOlzPlT09HX/4Ac/MMaPH28kJycb2dnZxrx584w333xzYHa+l071e/rTxy9Rn2/X/90BIiIiIscZtLOuiIiIiM4WBzpERETkWBzoEBERkWNxoENERESOxYEOERERORYHOkRERORYHOgQERGRY3GgQ0RERI7FgQ4RERE5Fgc6RERE5Fgc6BAREZFj/f9a4rz9RYpONwAAAABJRU5ErkJggg==", 69 | "text/plain": [ 70 | "
" 71 | ] 72 | }, 73 | "metadata": {}, 74 | "output_type": "display_data" 75 | } 76 | ], 77 | "source": [ 78 | "n_samples = 100_000\n", 79 | "\n", 80 | "x, _ = datasets.make_moons(n_samples=n_samples, noise=.06)\n", 81 | "\n", 82 | "scaler = preprocessing.StandardScaler()\n", 83 | "x = scaler.fit_transform(x)\n", 84 | "\n", 85 | "plt.hist2d(x[:, 0], x[:, 1], bins=100)\n", 86 | "plt.xlim(-2 ,2)\n", 87 | "plt.ylim(-2, 2)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 35, 93 | "id": "de9f55f9", 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "sigma = 1.\n", 98 | "\n", 99 | "def weight(t):\n", 100 | " \"\"\" \\lambda(t)\n", 101 | " \"\"\"\n", 102 | " return 0.5 / np.log(sigma) * (sigma ** (2 * t) - 1)\n", 103 | "\n", 104 | "@partial(jax.jit, static_argnums=(3,))\n", 105 | "def loss_fn(params, x, t, score, key):\n", 106 | "\n", 107 | " mu = x # x(0)\n", 108 | " std = np.sqrt(0.5 / np.log(sigma) * (sigma ** (2 * t) - 1)) # std of noise at time t\n", 109 | "\n", 110 | " eps = jax.random.normal(key, shape=x.shape) # Sampled noise\n", 111 | " y = mu + std * eps # x(t) = x(0) + std * eps # Corrupted data\n", 112 | "\n", 113 | " # Predicted score\n", 114 | " pred = score.apply(params, np.concatenate([t, y], -1)) \n", 115 | "\n", 116 | " # Score matching loss\n", 117 | " loss = weight(t) * np.mean((pred + eps / std) ** 2)\n", 118 | "\n", 119 | " return loss.mean()" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 36, 125 | "id": "e544f2d0-ae93-42d5-affa-9fb85bd5bc5c", 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "class MLP(nn.Module):\n", 130 | " \"\"\" A simple MLP in Flax. This is the score function.\n", 131 | " \"\"\"\n", 132 | " hidden_dim: int = 32\n", 133 | " out_dim: int = 2\n", 134 | " n_layers: int = 2\n", 135 | "\n", 136 | " @nn.compact\n", 137 | " def __call__(self, x):\n", 138 | " for _ in range(self.n_layers):\n", 139 | " x = nn.Dense(features=self.hidden_dim)(x)\n", 140 | " x = nn.gelu(x)\n", 141 | " x = nn.Dense(features=self.out_dim)(x)\n", 142 | " return x" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 37, 148 | "id": "337bbdfd", 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [ 152 | "def int_beta(t):\n", 153 | " return t\n", 154 | "\n", 155 | "def weight(t):\n", 156 | " return 1 - np.exp(-int_beta(t))\n", 157 | "\n", 158 | "@partial(jax.jit, static_argnums=(3,4,))\n", 159 | "def loss_fn(params, x, t, int_beta, score, key):\n", 160 | " mu = x * np.exp(-0.5 * int_beta(t))\n", 161 | " sigma = np.sqrt(1 - np.exp(-int_beta(t)))\n", 162 | " eps = jax.random.normal(key, shape=x.shape)\n", 163 | " y = mu + sigma * eps\n", 164 | "\n", 165 | " pred = score.apply(params, np.concatenate([y, t], -1))\n", 166 | " loss = weight(t) * np.mean((pred + eps / sigma) ** 2)\n", 167 | " return loss.mean()" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 38, 173 | "id": "7455e305", 174 | "metadata": {}, 175 | "outputs": [], 176 | "source": [ 177 | "key = jax.random.PRNGKey(0)\n", 178 | "t = np.ones((x.shape[0], 1))\n", 179 | "\n", 180 | "score = MLP(hidden_dim=128, out_dim=2, n_layers=5)\n", 181 | "params = score.init(key, np.concatenate([x, t], axis=1))" 182 | ] 183 | }, 184 | { 185 | "attachments": {}, 186 | "cell_type": "markdown", 187 | "id": "1ec7fbad", 188 | "metadata": {}, 189 | "source": [ 190 | "## Training" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 39, 196 | "id": "c98ab327", 197 | "metadata": {}, 198 | "outputs": [], 199 | "source": [ 200 | "opt = optax.adamw(learning_rate=3e-4, weight_decay=1e-4)\n", 201 | "opt_state = opt.init(params)" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 40, 207 | "id": "8c67c255", 208 | "metadata": {}, 209 | "outputs": [ 210 | { 211 | "name": "stderr", 212 | "output_type": "stream", 213 | "text": [ 214 | "100%|██████████| 2000/2000 [00:17<00:00, 112.91it/s, val=2.2799215] \n" 215 | ] 216 | } 217 | ], 218 | "source": [ 219 | "n_steps = 2_000\n", 220 | "n_batch = 128\n", 221 | "T = 1.\n", 222 | "\n", 223 | "with trange(n_steps) as steps:\n", 224 | " for step in steps:\n", 225 | "\n", 226 | " # Draw a random batches from x\n", 227 | " key, subkey = jax.random.split(key)\n", 228 | " idx = jax.random.choice(key, x.shape[0], shape=(n_batch,))\n", 229 | " \n", 230 | " x_batch = x[idx]\n", 231 | " t_batch = jax.random.uniform(key, shape=(x_batch.shape[0], 1), minval=0., maxval=T)\n", 232 | "\n", 233 | " loss, grads = jax.value_and_grad(loss_fn)(params, x_batch, t_batch, int_beta, score, key)\n", 234 | " updates, opt_state = opt.update(grads, opt_state, params)\n", 235 | "\n", 236 | " params = optax.apply_updates(params, updates)\n", 237 | "\n", 238 | " steps.set_postfix(val=loss)" 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "id": "a018c3d0", 244 | "metadata": {}, 245 | "source": [ 246 | "## Distillation" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 41, 252 | "id": "90b560b3", 253 | "metadata": {}, 254 | "outputs": [], 255 | "source": [ 256 | "key = jax.random.PRNGKey(0)\n", 257 | "t = np.ones((x.shape[0], 1))\n", 258 | "\n", 259 | "score_student = MLP(hidden_dim=128, out_dim=2, n_layers=5)\n", 260 | "params_student = score_student.init(key, np.concatenate([x, t], axis=1))" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 45, 266 | "id": "f8ce7cb4", 267 | "metadata": {}, 268 | "outputs": [ 269 | { 270 | "name": "stderr", 271 | "output_type": "stream", 272 | "text": [ 273 | "100%|██████████| 5000/5000 [00:50<00:00, 98.17it/s, val=4.15465] \n" 274 | ] 275 | } 276 | ], 277 | "source": [ 278 | "n_steps = 5_000\n", 279 | "n_batch = 128\n", 280 | "\n", 281 | "def alpha(t):\n", 282 | " return np.exp(-0.5 * int_beta(t))\n", 283 | "\n", 284 | "def sigma(t):\n", 285 | " return np.sqrt(1 - np.exp(-int_beta(t)))\n", 286 | "\n", 287 | "@partial(jax.jit, static_argnums=(4,5,))\n", 288 | "def loss_distillation_fn(params_student, params, x, t, score_student, score, key):\n", 289 | " eps = jax.random.normal(key, shape=x.shape)\n", 290 | "\n", 291 | " t = t[:, None] \n", 292 | "\n", 293 | " z_t = alpha(t) * x + sigma(t) * eps\n", 294 | "\n", 295 | " t_p = t - 0.5 / N\n", 296 | " t_pp = t - 1 / N\n", 297 | "\n", 298 | " x_hat_t = score.apply(params, np.concatenate([z_t, t], -1))\n", 299 | " z_tp = alpha(t_p) * x_hat_t + sigma(t_p) / sigma(t) * (z_t - x_hat_t * alpha(t))\n", 300 | "\n", 301 | " x_hat_tp = score.apply(params, np.concatenate([z_tp, t], -1))\n", 302 | " z_tpp = alpha(t_pp) * x_hat_tp + sigma(t_pp) / sigma(t_p) * (z_t - x_hat_tp * alpha(t_p))\n", 303 | "\n", 304 | " x_tilde = (z_tpp - (sigma(t_pp) / sigma(t)) * z_t) / (alpha(t_pp) - (sigma(t_pp) / sigma(t)) * alpha(t))\n", 305 | "\n", 306 | " pred = score_student.apply(params_student, np.concatenate([z_t, t], -1))\n", 307 | " loss = weight(t) * np.mean((pred + (x_tilde - x * alpha(t)) / sigma(t) ** 2) ** 2)\n", 308 | " return loss.mean()\n", 309 | "\n", 310 | "N = 100\n", 311 | "\n", 312 | "with trange(n_steps) as steps:\n", 313 | " for step in steps:\n", 314 | "\n", 315 | " # Draw a random batches from x\n", 316 | " key, subkey = jax.random.split(key)\n", 317 | " idx = jax.random.choice(key, x.shape[0], shape=(n_batch,))\n", 318 | " \n", 319 | " x_batch = x[idx]\n", 320 | " t_batch = jax.random.choice(key, np.arange(1, N + 1), shape=(n_batch,)) / N\n", 321 | "\n", 322 | " loss, grads = jax.value_and_grad(loss_distillation_fn)(params_student, params, x_batch, t_batch, score_student, score, key)\n", 323 | " updates, opt_state = opt.update(grads, opt_state, params_student)\n", 324 | "\n", 325 | " params_student = optax.apply_updates(params_student, updates)\n", 326 | "\n", 327 | " steps.set_postfix(val=loss)" 328 | ] 329 | }, 330 | { 331 | "attachments": {}, 332 | "cell_type": "markdown", 333 | "id": "ca323255", 334 | "metadata": {}, 335 | "source": [ 336 | "## Sampling" 337 | ] 338 | }, 339 | { 340 | "cell_type": "code", 341 | "execution_count": null, 342 | "id": "148b0f99", 343 | "metadata": {}, 344 | "outputs": [], 345 | "source": [] 346 | } 347 | ], 348 | "metadata": { 349 | "kernelspec": { 350 | "display_name": "Python 3 (ipykernel)", 351 | "language": "python", 352 | "name": "python3" 353 | }, 354 | "language_info": { 355 | "codemirror_mode": { 356 | "name": "ipython", 357 | "version": 3 358 | }, 359 | "file_extension": ".py", 360 | "mimetype": "text/x-python", 361 | "name": "python", 362 | "nbconvert_exporter": "python", 363 | "pygments_lexer": "ipython3", 364 | "version": "3.9.13" 365 | } 366 | }, 367 | "nbformat": 4, 368 | "nbformat_minor": 5 369 | } 370 | -------------------------------------------------------------------------------- /08_discrete_walk_jump_sampling.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# [Discrete Walk-Jump Sampling](https://arxiv.org/abs/2306.12360)\n" 9 | ] 10 | }, 11 | { 12 | "attachments": {}, 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Imports" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "from typing import Any, Callable, Iterable, Optional, Tuple, Union\n", 26 | "import functools\n", 27 | "from tqdm import trange\n", 28 | "\n", 29 | "import jax\n", 30 | "import jax.config\n", 31 | "jax.config.update(\"jax_enable_x64\", True)\n", 32 | "\n", 33 | "import chex\n", 34 | "import jax.numpy as jnp\n", 35 | "import flax.linen as nn\n", 36 | "from clu import parameter_overview\n", 37 | "import optax\n", 38 | "import tqdm\n", 39 | "from tensorflow_probability.substrates import jax as tfp\n", 40 | "import matplotlib.pyplot as plt\n", 41 | "import matplotlib.animation\n", 42 | "\n", 43 | "plt.rcParams[\"animation.html\"] = \"jshtml\"\n" 44 | ] 45 | }, 46 | { 47 | "attachments": {}, 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "## Problem Setup\n", 52 | "\n", 53 | "Fundamentally, discrete walk-jump sampling (dWJS) is a method for sampling noisy latents from an energy-based model, and denoising them with a neural network.\n", 54 | "\n", 55 | "Thus, to understand dWJS, we first need to understand energy-based models and neural empirical Bayes." 56 | ] 57 | }, 58 | { 59 | "attachments": {}, 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "## Energy-Based Models\n", 64 | "\n", 65 | "Energy-based models (EBMs) model the probability of a data point $y$ with an energy function $E_\\theta(y)$ parameterized by $\\theta$:\n", 66 | "$$\n", 67 | "p_\\theta(y) = \\frac{\\exp(-E_\\theta(y))}{Z(\\theta)}\n", 68 | "$$\n", 69 | "where $Z(\\theta)$ is the normalizing constant (called the partition function):\n", 70 | "$$\n", 71 | "Z(\\theta) = \\int \\exp(-E_\\theta(y)) dy\n", 72 | "$$\n", 73 | "Low energy samples have high probability, and vice versa." 74 | ] 75 | }, 76 | { 77 | "attachments": {}, 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "\n", 82 | "### How to train an EBM? \n", 83 | "\n", 84 | "We can train an EBM by minimizing the KL divergence between the true distribution $p$ and the model distribution $p_\\theta$. This is equivalent to maximizing the expected value of log-likelihood of the data sampled from $p(y)$:\n", 85 | "$$\n", 86 | "\\theta^* = \\argmin_\\theta \\text{KL}(p \\ || \\ p_\\theta) = \\argmax_\\theta \\mathbb{E}_{y\\sim p(y)} [\\log p_\\theta(y)] = \\argmin_\\theta \\mathbb{E}_{y\\sim p(y)} [-\\log p_\\theta(y)]\n", 87 | "$$\n", 88 | "\n", 89 | "This is all standard so far. The problem is that $Z(\\theta)$ is intractable to compute:\n", 90 | "$$\n", 91 | "\\mathbb{E}_{y\\sim p(y)} [-\\log p_\\theta(y)] = \\mathbb{E}_{y\\sim p(y)} [E_\\theta(y)] + \\log Z(\\theta) \n", 92 | "$$ \n", 93 | "If we use gradient descent to optimize $\\theta$, we need to compute:\n", 94 | "$$\n", 95 | "\\nabla_\\theta \\mathbb{E}_{y\\sim p(y)} [-\\log p_\\theta(y)] = \\mathbb{E}_{y\\sim p(y)} [\\nabla_\\theta E_\\theta(y)] + \\nabla_\\theta\\log Z(\\theta) \n", 96 | "$$ \n", 97 | "The first term is easy to compute, but the second term is (usually) intractable. The common approach is to approximate the second term via MCMC sampling:\n", 98 | "$$\n", 99 | "\\begin{aligned}\n", 100 | "\\nabla_\\theta\\log Z(\\theta) &= \\frac{\\nabla_\\theta Z(\\theta)}{Z(\\theta)} \\\\\n", 101 | "&= \\frac{\\nabla_\\theta \\int \\exp(-E_\\theta(y)) dy}{Z(\\theta)} \\\\\n", 102 | "&= \\frac{\\int \\nabla_\\theta \\exp(-E_\\theta(y)) dy}{Z(\\theta)} \\\\\n", 103 | "&= \\frac{\\int - \\nabla_\\theta E_\\theta(y) \\exp(-E_\\theta(y)) dy}{Z(\\theta)} \\\\\n", 104 | "&= \\frac{\\int - \\nabla_\\theta E_\\theta(y) Z(\\theta) p_\\theta(y) dy}{Z(\\theta)} \\\\\n", 105 | "&= \\int - \\nabla_\\theta E_\\theta(y) \\ p_\\theta(y) dy \\\\\n", 106 | "&= \\mathbb{E}_{y\\sim p_\\theta(y)} [- \\nabla_\\theta E_\\theta(y)] \\\\\n", 107 | "\\\\\n", 108 | "\\end{aligned}\n", 109 | "$$\n", 110 | "\n", 111 | "Thus, we seek to minimize:\n", 112 | "$$\n", 113 | "\\mathbb{E}_{y\\sim p(y)} [-\\log p_\\theta(y) = \n", 114 | "\\mathbb{E}_{y\\sim p(y)} [\\nabla_\\theta E_\\theta(y)] - \\mathbb{E}_{y\\sim p_\\theta(y)} [ \\nabla_\\theta E_\\theta(y)] \n", 115 | "$$\n", 116 | "We are seeking to minimize the energy of positive samples (from the data distribution) and maximize the energy of negative samples (from the model distribution). This is why this approach is also called contrastive divergence." 117 | ] 118 | }, 119 | { 120 | "attachments": {}, 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "# Langevin MCMC\n", 125 | "\n", 126 | "We have computed an estimator for the gradient of the negative log-likelihood (NLL) loss.\n", 127 | "\n", 128 | "Note that this estimator requires us to sample from the model distribution $p_\\theta(y)$ at each iteration. We perform this sampling from $p_\\theta(y)$ via Langevin MCMC. Langevin MCMC is similar to a noisy version of gradient ascent on the log-likelihood.\n", 129 | "\n", 130 | "Initialize a sample $y_0$ randomly.\n", 131 | "Then, for each iteration $t$, compute:\n", 132 | "$$\n", 133 | "y_{t+1} = y_t + \\delta \\nabla_{y_t} \\log p_\\theta(y_t) + \\sqrt{2\\delta} \\epsilon_t\n", 134 | "$$\n", 135 | "where $\\epsilon_t \\sim \\mathcal{N}(0, I)$.\n", 136 | "Then, as $t \\rightarrow \\infty$, $y_t$ will appear to be sampled from $p_\\theta(y)$.\n", 137 | "\n", 138 | "Note that the partition function $Z(\\theta)$ does not show up in the sampling procedure:\n", 139 | "$$\n", 140 | "\\nabla_{y_t} \\log p_\\theta(y_t) = - \\nabla_{y_t} E_\\theta(y_t)\n", 141 | "$$" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "@functools.partial(jax.jit, static_argnames=(\"grad_log_prob_fn\", \"num_steps\"))\n", 151 | "def langevin_sample(grad_log_prob_fn: Callable[[chex.Array], float], init: chex.Array, delta: float, rng: chex.PRNGKey, num_steps: int):\n", 152 | " \"\"\"Langevin sampling from a given log probability function.\"\"\"\n", 153 | "\n", 154 | " def one_step_langevin(y_t: chex.Array, rng: chex.PRNGKey):\n", 155 | " eps = jax.random.normal(rng, y_t.shape)\n", 156 | " y_next = y_t + delta * grad_log_prob_fn(y_t) + jnp.sqrt(2 * delta) * eps\n", 157 | " return y_next, y_next\n", 158 | "\n", 159 | " sampling_rngs = jax.random.split(rng, num_steps)\n", 160 | " _, samples = jax.lax.scan(one_step_langevin, init, xs=sampling_rngs, length=len(sampling_rngs))\n", 161 | " return samples" 162 | ] 163 | }, 164 | { 165 | "attachments": {}, 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## Example Time!\n", 170 | "\n", 171 | "To illustrate the math, we will use a simple 1D example. Let's assume that the data distribution is a mixture of two Gaussians centered at -1 and 1, respectively:\n", 172 | "$$\n", 173 | "p(y) = \\frac{1}{2} \\mathcal{N}(y; -1, 0.5) + \\frac{1}{2} \\mathcal{N}(y; 1, 0.5)\n", 174 | "$$" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "tfd = tfp.distributions\n", 184 | "px = tfd.Categorical(probs=[0.5, 0.5])\n", 185 | "p = tfd.Mixture(\n", 186 | " cat=px,\n", 187 | " components=[\n", 188 | " tfd.Normal(loc=-1., scale=0.5),\n", 189 | " tfd.Normal(loc=+1., scale=0.5),\n", 190 | " ]\n", 191 | ")\n", 192 | "\n", 193 | "# Plot the PDF.\n", 194 | "y = jnp.linspace(-5., 5., int(1e4))\n", 195 | "plt.grid()\n", 196 | "plt.plot(y, p.prob(y))\n", 197 | "plt.xlabel('y')\n", 198 | "plt.ylabel('p(y)')\n", 199 | "plt.title('True PDF')\n", 200 | "plt.show();" 201 | ] 202 | }, 203 | { 204 | "attachments": {}, 205 | "cell_type": "markdown", 206 | "metadata": {}, 207 | "source": [ 208 | "We can visualize the Langevin MCMC sampling process below:" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "metadata": {}, 215 | "outputs": [], 216 | "source": [ 217 | "rng = jax.random.PRNGKey(0)\n", 218 | "grad_log_prob_fn = jax.jit(jax.grad(lambda y: p.log_prob(y).squeeze()))" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [ 227 | "sampling_rng, rng = jax.random.split(rng)\n", 228 | "delta = 0.1\n", 229 | "langevin_samples_from_2 = langevin_sample(grad_log_prob_fn, init=2 * jnp.ones((1,)), delta=delta, rng=sampling_rng, num_steps=500)\n", 230 | "\n", 231 | "fig, ax = plt.subplots()\n", 232 | "ax.grid()\n", 233 | "ax.set_xlim(-5., 5.)\n", 234 | "ax.set_ylim(0., 1.)\n", 235 | "ax.set_xlabel('y')\n", 236 | "ax.set_ylabel('p(y)')\n", 237 | "scatter = ax.scatter([], [], lw=2, color='C0')\n", 238 | "\n", 239 | "def animate(i: int):\n", 240 | " offsets = [langevin_samples_from_2[:i], p.prob(langevin_samples_from_2[:i])]\n", 241 | " offsets = jnp.stack(offsets, axis=-1).squeeze()\n", 242 | " scatter.set_offsets(offsets)\n", 243 | " # Adjust opacity.\n", 244 | " if i > 0:\n", 245 | " scatter.set_alpha(jnp.arange(i) ** 2 / i ** 2)\n", 246 | " ax.set_title(r'Langevin Sampling Starting from 2 with $\\delta={}$: Step {}'.format(delta, i))\n", 247 | " return (scatter,)\n", 248 | "\n", 249 | "anim = matplotlib.animation.FuncAnimation(fig, animate, frames=100, interval=100, blit=True)\n", 250 | "plt.close()\n", 251 | "anim" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": null, 257 | "metadata": {}, 258 | "outputs": [], 259 | "source": [ 260 | "sampling_rng, rng = jax.random.split(rng)\n", 261 | "langevin_samples_from_neg_2 = langevin_sample(grad_log_prob_fn, init=-2*jnp.ones((1,)), delta=delta, rng=sampling_rng, num_steps=500)\n", 262 | "\n", 263 | "fig, ax = plt.subplots()\n", 264 | "ax.grid()\n", 265 | "ax.set_xlim(-5., 5.)\n", 266 | "ax.set_ylim(0., 1.)\n", 267 | "ax.set_xlabel('y')\n", 268 | "ax.set_ylabel('p(y)')\n", 269 | "scatter = ax.scatter([], [], lw=2, c='C1')\n", 270 | "\n", 271 | "def animate(i: int):\n", 272 | " offsets = [langevin_samples_from_neg_2[:i], p.prob(langevin_samples_from_neg_2[:i])]\n", 273 | " offsets = jnp.stack(offsets, axis=-1).squeeze()\n", 274 | " scatter.set_offsets(offsets)\n", 275 | " # Adjust opacity.\n", 276 | " # scatter.set_sizes(100 * jnp.ones(i))\n", 277 | " if i > 0:\n", 278 | " scatter.set_alpha(jnp.arange(i) ** 2 / i ** 2)\n", 279 | " ax.set_title(r'Langevin Sampling Starting from -2 with $\\delta={}$: Step {}'.format(delta, i))\n", 280 | " return (scatter,)\n", 281 | "\n", 282 | "anim = matplotlib.animation.FuncAnimation(fig, animate, frames=100, interval=100, blit=True)\n", 283 | "plt.close()\n", 284 | "anim" 285 | ] 286 | }, 287 | { 288 | "attachments": {}, 289 | "cell_type": "markdown", 290 | "metadata": {}, 291 | "source": [ 292 | "We can check that the histogram of the samples from Langevin MCMC match the data distribution somewhat closely:" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": null, 298 | "metadata": {}, 299 | "outputs": [], 300 | "source": [ 301 | "plt.hist(langevin_samples_from_2.flatten(), bins=20, density=True, alpha=0.5, color='C0', label='Starting from 2')\n", 302 | "plt.hist(langevin_samples_from_neg_2.flatten(), bins=20, density=True, alpha=0.5, color='C1', label='Starting from -2')\n", 303 | "plt.grid()\n", 304 | "plt.xlabel('y')\n", 305 | "plt.ylabel('p(y)')\n", 306 | "plt.legend()\n", 307 | "plt.title('Histograms of Langevin Samples')\n", 308 | "plt.show();" 309 | ] 310 | }, 311 | { 312 | "attachments": {}, 313 | "cell_type": "markdown", 314 | "metadata": {}, 315 | "source": [ 316 | "For our EBM, we will use a simple 2-layer neural network for the energy function:\n", 317 | "$$\n", 318 | "E_\\theta(y) = W_1\\text{softplus}(W_0y + b_0) + b_1\n", 319 | "$$" 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": null, 325 | "metadata": {}, 326 | "outputs": [], 327 | "source": [ 328 | "class EnergyBasedModel(nn.Module):\n", 329 | " \"\"\"A simple energy-based model.\"\"\"\n", 330 | " hidden_size: int\n", 331 | "\n", 332 | " @nn.compact\n", 333 | " def __call__(self, y: chex.Array) -> chex.Array:\n", 334 | " if len(y.shape) <= 1:\n", 335 | " y = jnp.expand_dims(y, axis=0)\n", 336 | " y = nn.Dense(self.hidden_size)(y)\n", 337 | " y = jax.nn.softplus(y)\n", 338 | " y = nn.Dense(self.hidden_size)(y)\n", 339 | " y = jax.nn.softplus(y)\n", 340 | " y = nn.Dense(1)(y)\n", 341 | " y = jnp.squeeze(y, axis=-1)\n", 342 | " return y\n", 343 | "\n", 344 | "# Initialize the model.\n", 345 | "model = EnergyBasedModel(hidden_size=10)\n", 346 | "dummy_input = jnp.ones((1,))\n", 347 | "init_params = model.init(rng, dummy_input)\n", 348 | "energy_fn = jax.jit(model.apply)\n", 349 | "\n", 350 | "# Overview of the model parameters.\n", 351 | "print(parameter_overview.get_parameter_overview(init_params))" 352 | ] 353 | }, 354 | { 355 | "attachments": {}, 356 | "cell_type": "markdown", 357 | "metadata": {}, 358 | "source": [ 359 | "We can visualize the unnormlized probability distribution of the EBM below:" 360 | ] 361 | }, 362 | { 363 | "cell_type": "code", 364 | "execution_count": null, 365 | "metadata": {}, 366 | "outputs": [], 367 | "source": [ 368 | "y = jnp.linspace(-10., 10., int(1e4))\n", 369 | "y_probs = jax.vmap(lambda y: jnp.exp(-energy_fn(init_params, y)))(y)\n", 370 | "y_probs /= y_probs.sum() * (y[-1] - y[0]) / len(y)\n", 371 | "plt.grid()\n", 372 | "plt.plot(y, y_probs, color='C2')\n", 373 | "plt.xlabel('y')\n", 374 | "plt.ylabel('p_model(y)')\n", 375 | "plt.title('(Approximately Normalized) Model PDF')\n", 376 | "plt.show();" 377 | ] 378 | }, 379 | { 380 | "cell_type": "code", 381 | "execution_count": null, 382 | "metadata": {}, 383 | "outputs": [], 384 | "source": [ 385 | "def create_grad_log_prob_fn(params: optax.Params, energy_fn: Callable[[optax.Params, chex.Array], float]) -> Callable[[chex.Array], chex.Array]:\n", 386 | " \"\"\"Creates a function that computes the gradient of the log probability under the EBM.\"\"\"\n", 387 | " def grad_log_prob_fn(y: chex.Array) -> chex.Array:\n", 388 | " return -jax.grad(lambda y: energy_fn(params, y).squeeze())(y)\n", 389 | " return grad_log_prob_fn\n", 390 | "\n", 391 | "sampling_rng, rng = jax.random.split(rng)\n", 392 | "langevin_samples_from_model = langevin_sample(create_grad_log_prob_fn(init_params, energy_fn), init=jnp.zeros((1,)), delta=1, rng=sampling_rng, num_steps=10000)\n", 393 | "plt.hist(langevin_samples_from_model.flatten(), bins=20, density=True, alpha=0.5, color='C2', label='Model Samples')\n", 394 | "plt.grid()\n", 395 | "plt.xlabel('y')\n", 396 | "plt.ylabel('p(y)')\n", 397 | "plt.legend()\n", 398 | "plt.title('Histograms of Langevin Samples')\n", 399 | "plt.show();" 400 | ] 401 | }, 402 | { 403 | "attachments": {}, 404 | "cell_type": "markdown", 405 | "metadata": {}, 406 | "source": [ 407 | "We now train the EBM using the gradient estimator from above. We use the last sample from Langevin MCMC as the negative sample.\n", 408 | "We can simply use automatic differentiation to compute the gradient of the loss!" 409 | ] 410 | }, 411 | { 412 | "cell_type": "code", 413 | "execution_count": null, 414 | "metadata": {}, 415 | "outputs": [], 416 | "source": [ 417 | "@functools.partial(jax.jit, static_argnames=(\"energy_fn\", \"num_sampling_steps\", \"take_every_sample\", \"burn_in_samples\"))\n", 418 | "def ebm_loss_fn(params: optax.Params, energy_fn: Callable[[optax.Params, chex.Array], float], y_true_samples: chex.Array, rng: chex.PRNGKey,\n", 419 | " delta: float, num_sampling_steps: int, take_every_sample: int, burn_in_samples: int) -> float:\n", 420 | " \"\"\"Computes the EBM loss function.\"\"\"\n", 421 | " init_rng, rng = jax.random.split(rng)\n", 422 | " init = jax.random.normal(init_rng, y_true_samples[0].shape)\n", 423 | " sampling_rng, rng = jax.random.split(rng)\n", 424 | " y_model_samples = langevin_sample(create_grad_log_prob_fn(params, energy_fn), init=init, delta=delta, rng=sampling_rng, num_steps=num_sampling_steps)\n", 425 | " y_model_samples = y_model_samples[burn_in_samples:]\n", 426 | " y_model_samples = y_model_samples[::take_every_sample]\n", 427 | " # We don't differentiate through the sampling procedure.\n", 428 | " y_model_samples = jax.lax.stop_gradient(y_model_samples)\n", 429 | " energy_fn_vmapped = jax.vmap(lambda y: energy_fn(params, y))\n", 430 | " return energy_fn_vmapped(y_true_samples).mean() - energy_fn_vmapped(y_model_samples).mean()" 431 | ] 432 | }, 433 | { 434 | "attachments": {}, 435 | "cell_type": "markdown", 436 | "metadata": {}, 437 | "source": [ 438 | "We are ready to train our model!" 439 | ] 440 | }, 441 | { 442 | "cell_type": "code", 443 | "execution_count": null, 444 | "metadata": {}, 445 | "outputs": [], 446 | "source": [ 447 | "def train_ebm_model(init_params: optax.Params, rng: chex.PRNGKey, num_training_steps: int, **loss_kwargs) -> optax.Params:\n", 448 | " \"\"\"Train the EBM model using the Adam optimizer.\"\"\"\n", 449 | " @jax.jit\n", 450 | " def train_step(params: optax.Params, opt_state: optax.OptState, y_true_samples: chex.Array, rng: chex.PRNGKey) -> Tuple[optax.Params, optax.OptState, float]:\n", 451 | " loss, grad = jax.value_and_grad(ebm_loss_fn, has_aux=False)(params, energy_fn, y_true_samples, rng, **loss_kwargs)\n", 452 | " updates, opt_state = tx.update(grad, opt_state)\n", 453 | " params = optax.apply_updates(params, updates)\n", 454 | " return params, opt_state, loss\n", 455 | "\n", 456 | " tx = optax.adam(5e-4)\n", 457 | " opt_state = tx.init(init_params)\n", 458 | "\n", 459 | " params_at_steps = {\n", 460 | " 0: init_params,\n", 461 | " }\n", 462 | " \n", 463 | " params = init_params\n", 464 | " for step in tqdm.trange(num_training_steps):\n", 465 | " step_rng, samples_rng, rng = jax.random.split(rng, num=3)\n", 466 | " y_true_samples = p.sample(32, seed=samples_rng)\n", 467 | "\n", 468 | " params, opt_state, loss = train_step(params, opt_state, y_true_samples, step_rng)\n", 469 | "\n", 470 | " # Log the training progress. \n", 471 | " if step % 500 == 0:\n", 472 | " params_at_steps[step] = params\n", 473 | " \n", 474 | " if step % 2000 == 0:\n", 475 | " print('Step {}: Loss = {}'.format(step, loss))\n", 476 | " \n", 477 | " return params, params_at_steps\n", 478 | "\n", 479 | "train_rng = jax.random.PRNGKey(0)\n", 480 | "energy_params, energy_params_at_steps = train_ebm_model(init_params, train_rng, num_training_steps=30000, delta=0.1, num_sampling_steps=10, take_every_sample=2, burn_in_samples=1)" 481 | ] 482 | }, 483 | { 484 | "attachments": {}, 485 | "cell_type": "markdown", 486 | "metadata": {}, 487 | "source": [ 488 | "We can visualize the learnt (unnormalized) probability distribution of the EBM:" 489 | ] 490 | }, 491 | { 492 | "cell_type": "code", 493 | "execution_count": null, 494 | "metadata": {}, 495 | "outputs": [], 496 | "source": [ 497 | "fig, ax = plt.subplots()\n", 498 | "ax.grid()\n", 499 | "ax.set_xlim(-5., 5.)\n", 500 | "ax.set_ylim(0., 1.)\n", 501 | "ax.set_xlabel('y')\n", 502 | "ax.set_ylabel('p_model(y)')\n", 503 | "line, = ax.plot([], [], lw=2, color='C2')\n", 504 | "steps = sorted(energy_params_at_steps.keys())\n", 505 | "\n", 506 | "def animate(i: int):\n", 507 | " y = jnp.linspace(-5., 5., int(1e3))\n", 508 | " y_probs = jax.vmap(lambda y: jnp.exp(-energy_fn(energy_params_at_steps[steps[i]], y)))(y)\n", 509 | " y_probs /= y_probs.sum() * (y[-1] - y[0]) / len(y)\n", 510 | " line.set_data(y, y_probs)\n", 511 | " ax.set_title('(Approximately Normalized) Model PDF: Step {}'.format(steps[i]))\n", 512 | " return (line,)\n", 513 | "\n", 514 | "anim = matplotlib.animation.FuncAnimation(fig, animate, frames=len(steps), interval=100, blit=True)\n", 515 | "plt.close()\n", 516 | "anim" 517 | ] 518 | }, 519 | { 520 | "attachments": {}, 521 | "cell_type": "markdown", 522 | "metadata": {}, 523 | "source": [ 524 | "## Neural Empirical Bayes" 525 | ] 526 | }, 527 | { 528 | "attachments": {}, 529 | "cell_type": "markdown", 530 | "metadata": {}, 531 | "source": [ 532 | "This section follows the work of [Saeed Saremi and Aapo Hyvarinen](https://arxiv.org/abs/1903.02334).\n", 533 | "\n", 534 | "Consider an observation denoted by the random variable $X \\in \\mathbb{R}^d$, and a noisy observation of $X$ denoted by $Y \\in \\mathbb{R}^d$:\n", 535 | "$$\n", 536 | "Y = X + \\epsilon, \\quad \\epsilon \\sim \\mathcal{N}(0, \\sigma^2 I_d)\n", 537 | "$$\n", 538 | "Thus, we are given that $p_{Y|X}$ is a Gaussian.\n", 539 | "\n", 540 | "Given the observation $Y = y$, the Bayes' least squares estimator for $X$ is:\n", 541 | "$$\n", 542 | "\\hat{X}(y) = \\mathbb{E}[X \\ | \\ Y = y]\n", 543 | "$$\n", 544 | "Computing this estimator requires knowledge of $p_{X|Y} \\propto p_{Y|X}\\cdot p_X$ by Bayes' rule.\n", 545 | "Thus, it seems that we need to know $p_X$ to compute this estimator.\n", 546 | "\n", 547 | "The trick (figured out by [Robbins](https://link.springer.com/chapter/10.1007/978-1-4612-0919-5_26) and [Miyasawa](https://mit.primo.exlibrisgroup.com/discovery/openurl?institution=01MIT_INST&vid=01MIT_INST:MIT&rft.epage=188&rft_val_fmt=info:ofi%2Ffmt:kev:mtx:journal&rft.stitle=B%20INT%20STATIST%20INST&rft.volume=38&rfr_id=info:sid%2Fwebofscience.com:WOS:WOS&rft.jtitle=BULLETIN%20OF%20THE%20INTERNATIONAL%20STATISTICAL%20INSTITUTE&rft.aufirst=K&rft.genre=article&rft.issue=4&rft.pages=181-188&url_ctx_fmt=info:ofi%2Ffmt:kev:mtx:ctx&rft.aulast=MIYASAWA&url_ver=Z39.88-2004&rft.auinit=K&rft.date=1960&rft.spage=181&rft.atitle=AN%20EMPIRICAL%20BAYES%20ESTIMATOR%20OF%20THE%20MEAN%20OF%20A%20NORMAL%20POPULATION&rft.issn=0074-8609)) turns out that we can compute this estimator without knowing $p_X$!\n", 548 | "\n", 549 | "For all $x$ and $y$, we have:\n", 550 | "$$\n", 551 | "p_{Y|X}(y|x) = \\mathcal{N}(y; x, \\sigma^2) = \\frac{1}{(2\\pi\\sigma^2)^{\\frac{d}{2}}} \\exp\\left(-\\frac{\\|y - x\\|^2}{2\\sigma^2} \\right)\n", 552 | "$$\n", 553 | "so:\n", 554 | "$$\n", 555 | "\\begin{aligned}\n", 556 | "\\nabla_y p_{Y|X}(y|x) &= -\\frac{y - x}{\\sigma^2} p_{Y|X}(y|x) \\\\\n", 557 | "\\implies (x - y) p_{Y|X}(y|x) &= \\sigma^2 \\nabla_y p_{Y|X}(y|x) \\\\\n", 558 | "\\implies \\int (x - y) p_{Y|X}(y|x) p_X(x) dx &= \\sigma^2 \\int \\nabla_y p_{Y|X}(y|x) p_X(x) dx\n", 559 | "\\end{aligned}\n", 560 | "$$\n", 561 | "Note that, by Bayes' rule: $p_{Y|X}(y|x) p_X(x) = p_{X,Y}(x, y) = p_{X|Y}(x|y) p_Y(y)$. \n", 562 | "Also, by definition of the marginals: $\\int p_{X,Y}(x, y) dx = p_{Y}(y)$. \n", 563 | "For the left-hand side, we have:\n", 564 | "$$\n", 565 | "\\begin{aligned}\n", 566 | "\\int (x - y) p_{Y|X}(y|x) p_X(x) dx &= \\int x p_{Y|X}(y|x) p_X(x) dx - \\int y p_{Y|X}(y|x) p_X(x) dx \\\\\n", 567 | "&= \\int x p_{X,Y}(x, y) dx - \\int y p_{X,Y}(x, y) dx \\\\\n", 568 | "&= p_Y(y) \\left(\\int x p_{X|Y}(x|y) dx - y \\int p_{X|Y}(x|y) dx\\right) \\\\\n", 569 | "&= p_Y(y) \\left(\\mathbb{E}[X \\ | \\ Y = y] - y \\right) \\\\\n", 570 | "&= p_Y(y) \\left(\\hat{X}(y) - y \\right)\n", 571 | "\\end{aligned}\n", 572 | "$$\n", 573 | "For the right-hand side, we have:\n", 574 | "$$\n", 575 | "\\begin{aligned}\n", 576 | "\\sigma^2 \\int \\nabla_y p_{Y|X}(y|x) p_X(x) dx &= \\sigma^2 \\nabla_y \\int p_{Y|X}(y|x) p_X(x) dx \\\\\n", 577 | "&= \\sigma^2 \\nabla_y \\int p_{X,Y}(x, y) dx \\\\\n", 578 | "&= \\sigma^2 \\nabla_y p_{Y}(y)\n", 579 | "\\end{aligned}\n", 580 | "$$\n", 581 | "Thus,\n", 582 | "$$\n", 583 | "\\begin{aligned}\n", 584 | "p_Y(y) \\left(\\hat{X}(y) - y \\right) &= \\sigma^2 \\nabla_y p_{Y}(y)\n", 585 | "\\\\\n", 586 | "\\implies \\hat{X}(y) &= y + \\sigma^2 \\frac{\\nabla_y p_{Y}(y)}{p_Y(y)}\n", 587 | "\\\\\n", 588 | "\\implies \\hat{X}(y) &= y + \\sigma^2 \\nabla_y \\log p_{Y}(y)\n", 589 | "\\end{aligned}\n", 590 | "$$\n", 591 | "Thus, the estimator $\\hat{X}(y)$ can be computed without knowledge of $p_X$, only the knowledge of the score $\\nabla_y \\log p_{Y}(y)$ is required.\n", 592 | "\n", 593 | "Now, there are two approaches to learning the score function $\\nabla_y \\log p_{Y}(y)$.\n", 594 | "The first is to approximate $p_{Y}(y)$ by an EBM:\n", 595 | "$$\n", 596 | "p_{Y}(y) \\approx \\frac{\\exp(-E_\\theta(y))}{Z(\\theta)} \\implies \\nabla_y \\log p_{Y}(y) = - \\nabla_y E_\\theta(y)\n", 597 | "$$\n", 598 | "The EBM can be learned using contrastive divergence described before.\n", 599 | "Then, the learned EBM can be used as a denoiser, by denoising $Y$ to obtain an estimate of $X$:\n", 600 | "$$\n", 601 | "\\hat{X}(y) = y - \\sigma^2 \\nabla_y E_\\theta(y)\n", 602 | "$$\n", 603 | "\n", 604 | "The second approach, proposed in this Discrete Walk-Jump Sampling paper, is to directly parametrize the score function by a 'denoising' neural network:\n", 605 | "$$\n", 606 | "g_\\phi(y) \\approx \\nabla_y \\log p_{Y}(y)\n", 607 | "$$\n", 608 | "Denoising $Y$ as before:\n", 609 | "$$\n", 610 | "\\hat{X}(y) = y + \\sigma^2 g_\\phi(y)\n", 611 | "$$\n", 612 | "The denoising network $g_\\phi$ can be trained from observations of $X$ and adding noise to obtain examples $Y$:\n", 613 | "* Sample $X_i \\sim p_X$.\n", 614 | "* Sample $\\epsilon_j \\sim \\mathcal{N}(0, \\sigma^2 I_d)$.\n", 615 | "* Compute $Y_{ij} = X_i + \\epsilon_j$.\n", 616 | "* Optimize $\\phi$:\n", 617 | "$$\n", 618 | "\\phi^* = \\argmin_\\phi \\sum_{i,j} \\|X_i - (Y_{ij} + g_\\phi(Y_{ij})) \\|^2\n", 619 | "$$" 620 | ] 621 | }, 622 | { 623 | "cell_type": "code", 624 | "execution_count": null, 625 | "metadata": {}, 626 | "outputs": [], 627 | "source": [ 628 | "# Define the score model that predicts the score.\n", 629 | "class ScoreNetwork(nn.Module):\n", 630 | " \"\"\"A simple score neural network.\"\"\"\n", 631 | " hidden_size: int\n", 632 | "\n", 633 | " @nn.compact\n", 634 | " def __call__(self, y: chex.Array) -> chex.Array:\n", 635 | " if len(y.shape) <= 1:\n", 636 | " y = jnp.expand_dims(y, axis=0)\n", 637 | " init_dims = y.shape[-1]\n", 638 | " y = nn.Dense(self.hidden_size)(y)\n", 639 | " y = jax.nn.softplus(y)\n", 640 | " y = nn.Dense(self.hidden_size)(y)\n", 641 | " y = jax.nn.softplus(y)\n", 642 | " y = nn.Dense(init_dims)(y)\n", 643 | " return y\n", 644 | "\n", 645 | "# Initialize the score model.\n", 646 | "score_model = ScoreNetwork(hidden_size=10)\n", 647 | "dummy_input = jnp.ones((1,))\n", 648 | "init_score_params = score_model.init(rng, dummy_input)\n", 649 | "score_fn = jax.jit(score_model.apply)\n", 650 | "\n", 651 | "# Overview of the model parameters.\n", 652 | "print(parameter_overview.get_parameter_overview(init_score_params))" 653 | ] 654 | }, 655 | { 656 | "cell_type": "code", 657 | "execution_count": null, 658 | "metadata": {}, 659 | "outputs": [], 660 | "source": [ 661 | "@functools.partial(jax.jit, static_argnames=(\"score_fn\", \"num_noise_samples\"))\n", 662 | "def score_loss_fn(\n", 663 | " params: optax.Params, score_fn: Callable[[optax.Params, chex.Array], chex.Array], x_true_samples: chex.Array, rng: chex.PRNGKey, noise_std: float, num_noise_samples: int) -> float:\n", 664 | " \"\"\"Computes the denoising loss.\"\"\"\n", 665 | " assert len(x_true_samples.shape) == 2\n", 666 | " num_true_samples, num_dims = x_true_samples.shape\n", 667 | "\n", 668 | " noise_rng, rng = jax.random.split(rng)\n", 669 | " noise = noise_std * jax.random.normal(noise_rng, (num_noise_samples, num_dims))\n", 670 | " y_samples = x_true_samples[:, None, :] + noise[None, ...]\n", 671 | " assert y_samples.shape == (num_true_samples, num_noise_samples, num_dims)\n", 672 | "\n", 673 | " predictions = score_fn(params, y_samples)\n", 674 | " assert predictions.shape == (num_true_samples, num_noise_samples, num_dims)\n", 675 | "\n", 676 | " l2_loss = jax.vmap(lambda x, ys, preds: jnp.linalg.norm(x - (ys + noise_std ** 2 * preds), axis=-1).mean())(x_true_samples, y_samples, predictions)\n", 677 | " assert l2_loss.shape == (num_true_samples,)\n", 678 | "\n", 679 | " return l2_loss.mean()\n", 680 | " " 681 | ] 682 | }, 683 | { 684 | "cell_type": "code", 685 | "execution_count": null, 686 | "metadata": {}, 687 | "outputs": [], 688 | "source": [ 689 | "def train_score_model(init_params: optax.Params, rng: chex.PRNGKey, num_training_steps: int, **loss_kwargs) -> optax.Params:\n", 690 | " \"\"\"Train the score model using the Adam optimizer.\"\"\"\n", 691 | " @jax.jit\n", 692 | " def train_step(params: optax.Params, opt_state: optax.OptState, x_true_samples: chex.Array, rng: chex.PRNGKey) -> Tuple[optax.Params, optax.OptState, float]:\n", 693 | " loss, grad = jax.value_and_grad(score_loss_fn, has_aux=False)(params, score_fn, x_true_samples, rng, **loss_kwargs)\n", 694 | " updates, opt_state = tx.update(grad, opt_state)\n", 695 | " params = optax.apply_updates(params, updates)\n", 696 | " return params, opt_state, loss\n", 697 | "\n", 698 | " tx = optax.adam(5e-4)\n", 699 | " opt_state = tx.init(init_params)\n", 700 | "\n", 701 | " params_at_steps = {\n", 702 | " 0: init_params,\n", 703 | " }\n", 704 | " \n", 705 | " params = init_params\n", 706 | " for step in tqdm.trange(num_training_steps + 1):\n", 707 | " step_rng, samples_rng, rng = jax.random.split(rng, num=3)\n", 708 | " x_true_samples = px.sample(32, seed=samples_rng)\n", 709 | " x_true_samples = x_true_samples[:, None]\n", 710 | "\n", 711 | " params, opt_state, loss = train_step(params, opt_state, x_true_samples, step_rng)\n", 712 | "\n", 713 | " # Log the training progress. \n", 714 | " if step % 500 == 0:\n", 715 | " params_at_steps[step] = params\n", 716 | " \n", 717 | " if step % 2000 == 0:\n", 718 | " print('Step {}: Loss = {}'.format(step, loss))\n", 719 | " \n", 720 | " return params, params_at_steps\n", 721 | "\n", 722 | "train_rng = jax.random.PRNGKey(0)\n", 723 | "noise_std = 0.5\n", 724 | "score_params, score_params_at_steps = train_score_model(init_score_params, train_rng, num_training_steps=30000, noise_std=noise_std, num_noise_samples=10)" 725 | ] 726 | }, 727 | { 728 | "attachments": {}, 729 | "cell_type": "markdown", 730 | "metadata": {}, 731 | "source": [ 732 | "Let's check that the score function works!" 733 | ] 734 | }, 735 | { 736 | "cell_type": "code", 737 | "execution_count": null, 738 | "metadata": {}, 739 | "outputs": [], 740 | "source": [ 741 | "noise_rng, rng = jax.random.split(rng)\n", 742 | "noise = noise_std * jax.random.normal(noise_rng, (10, 1))\n", 743 | "x_true = jnp.asarray([[-1.], [1.]])\n", 744 | "y = x_true[None, :] + noise[:, None]\n", 745 | "y = y.transpose((1, 0, 2)).reshape((-1, 1))\n", 746 | "preds = score_fn(score_params, y)\n", 747 | "x = y + noise_std ** 2 * preds\n", 748 | "labels = jnp.where(x < 0.5, 0, 1)\n", 749 | "plt.grid()\n", 750 | "plt.scatter(y, x, color=['C0' if label == 0 else 'C1' for label in labels])\n", 751 | "plt.xlabel('y')\n", 752 | "plt.ylabel('x')\n", 753 | "plt.title('Denoising Model Predictions')\n", 754 | "plt.show();" 755 | ] 756 | }, 757 | { 758 | "attachments": {}, 759 | "cell_type": "markdown", 760 | "metadata": {}, 761 | "source": [ 762 | "## Walk-Jump Sampling\n", 763 | "\n", 764 | "The idea behind Walk-Jump Sampling is that it is easier to walk in the space of noisy observations $Y$ than in the space of clean observations $X$. The noise helps connect different modes of the distribution. Given any noisy observation $Y$, we can always go back to the clean observation $X$ by denoising.\n", 765 | "\n", 766 | "* Walk in noisy observation space with Langevin MCMC:\n", 767 | "$$\n", 768 | " y_t = y_{t-1} + \\delta \\nabla_{y_{t-1}} \\log p_Y(y_{t-1}) + \\sqrt{2\\delta} \\epsilon_t\n", 769 | "$$\n", 770 | "* Jump to clean observation (at any time $\\tau$):\n", 771 | "$$\n", 772 | " x_\\tau = y_\\tau + \\sigma^2 \\nabla_{y_\\tau} \\log p_Y(y_\\tau)\n", 773 | "$$\n", 774 | "\n", 775 | "Note that both the walk and jump steps need an estimate of the score function $\\nabla_{y} \\log p_Y(y)$.\n", 776 | "We have choices for how we parametrize these in each step. Here, they find that using an EBM for the walker, and a denoising network for the jumper works best:\n", 777 | "* EBM:\n", 778 | "$$\n", 779 | "p_Y(y) = \\frac{\\exp(-E_\\theta(y))}{Z(\\theta)}\n", 780 | "$$\n", 781 | "* Denoiser:\n", 782 | "$$\n", 783 | "\\nabla_y \\log p_{Y}(y) \\approx g_\\phi(y)\n", 784 | "$$\n", 785 | "\n", 786 | "Note that unlike diffusion, every single sample from walk-jump sampling is approximately from the data distribution $p_X$." 787 | ] 788 | }, 789 | { 790 | "cell_type": "code", 791 | "execution_count": null, 792 | "metadata": {}, 793 | "outputs": [], 794 | "source": [ 795 | "@functools.partial(jax.jit, static_argnames=(\"energy_fn\", \"score_fn\", \"num_steps\", \"noise_std\"))\n", 796 | "def walk_jump_sampling(energy_fn_params: optax.Params, energy_fn: Callable[[optax.Params, chex.Array], float], score_fn_params: optax.Params, score_fn: Callable[[optax.Params, chex.Array], chex.Array], rng: chex.PRNGKey, delta: float, num_steps: int, noise_std: float):\n", 797 | " \"\"\"Performs walk-jump sampling.\"\"\"\n", 798 | " grad_log_prob_fn = create_grad_log_prob_fn(energy_fn_params, energy_fn)\n", 799 | " noisy_observations = langevin_sample(grad_log_prob_fn, init=jnp.zeros((1,)), delta=delta, rng=rng, num_steps=num_steps)\n", 800 | " scores = score_fn(score_fn_params, noisy_observations)\n", 801 | " denoised_observations = noisy_observations + noise_std ** 2 * scores\n", 802 | " return noisy_observations, denoised_observations\n", 803 | "\n", 804 | "\n", 805 | "walk_jump_sampling_rng, rng = jax.random.split(rng)\n", 806 | "noisy_observations, denoised_observations = walk_jump_sampling(energy_params, energy_fn, score_params, score_fn, rng=walk_jump_sampling_rng, delta=0.1, num_steps=1000, noise_std=noise_std)" 807 | ] 808 | }, 809 | { 810 | "cell_type": "code", 811 | "execution_count": null, 812 | "metadata": {}, 813 | "outputs": [], 814 | "source": [ 815 | "plt.hist(noisy_observations.flatten(), bins=20, density=True, alpha=0.5, color='C0', label='Noisy Observations')\n", 816 | "plt.legend()\n", 817 | "plt.grid()\n", 818 | "plt.title('Histogram of Noisy Observations')\n", 819 | "plt.show();" 820 | ] 821 | }, 822 | { 823 | "cell_type": "code", 824 | "execution_count": null, 825 | "metadata": {}, 826 | "outputs": [], 827 | "source": [ 828 | "plt.hist(denoised_observations.flatten(), bins=20, density=True, alpha=0.5, color='C1', label='Denoised Observations')\n", 829 | "plt.legend()\n", 830 | "plt.grid()\n", 831 | "plt.title('Histogram of Denoised Observations')\n", 832 | "plt.show();" 833 | ] 834 | } 835 | ], 836 | "metadata": { 837 | "kernelspec": { 838 | "display_name": ".venv", 839 | "language": "python", 840 | "name": "python3" 841 | }, 842 | "language_info": { 843 | "codemirror_mode": { 844 | "name": "ipython", 845 | "version": 3 846 | }, 847 | "file_extension": ".py", 848 | "mimetype": "text/x-python", 849 | "name": "python", 850 | "nbconvert_exporter": "python", 851 | "pygments_lexer": "ipython3", 852 | "version": "3.11.4" 853 | }, 854 | "orig_nbformat": 4 855 | }, 856 | "nbformat": 4, 857 | "nbformat_minor": 2 858 | } 859 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2023 Siddharth Mishra-Sharma 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minified generative models 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-red.svg)](https://opensource.org/licenses/MIT) 4 | 5 | Bare-bones, minified versions of some common (and not-so-common) generative models, for pedagogical purposes. 6 | 7 | ## Installation 8 | 9 | First, install JAX following these [instructions](https://jax.readthedocs.io/en/latest/installation.html). For CPU-only, this is as simple as: 10 | ```bash 11 | pip install "jax[cpu]" 12 | ``` 13 | Additional libraries: 14 | ```bash 15 | pip install flax optax diffrax tensorflow_probability scikit-learn tqdm matplotlib 16 | ``` 17 | 18 | ## List of notebooks 19 | 20 | 1. [β-VAEs](01_beta_vae.ipynb): Variational autoencoders and basic rate-distortion theory. 21 | 2. [Diffusion models](02_diffusion.ipynb): Diffusion models, covering likelihood-based and score-matching interpretations. 22 | 3. [Normalizing flows](03_normalizing_flows.ipynb) (WiP annotations): Normalizing flows, specifically [RealNVP](https://arxiv.org/abs/1605.08803). 23 | 4. [Continuous normalizing flows](03_continuous_normalizing_flows.ipynb): Continuous-time normalizing flows from e.g., [Grathwohl et al 2018](https://arxiv.org/abs/1810.01367). 24 | 5. [Consistency models](04_consistency_models.ipynb) (WiP annotations): Consistency models from [Song et al 2023](https://arxiv.org/abs/2303.01469). 25 | 6. [Flow matching](05_flow_matching.ipynb) (WiP annotations): From [Lipman et al 2022](https://arxiv.org/abs/2210.02747); see also [Albergo et al 2023](https://arxiv.org/abs/2303.08797). 26 | 7. [Diffusion distillation](06_diffusion_distillation.ipynb) (WiP): Progressive ([Salimans et al 2022](https://arxiv.org/abs/2202.00512)) and consistency ([Song et al 2023](https://arxiv.org/abs/2303.01469)) distillation. 27 | 8. [Discrete walk-jump sampling](07_discrete_walk_jump_sampling.ipynb) (WiP): From [Frey et al 2023](https://arxiv.org/abs/2306.12360). 28 | 29 | ## Inspiration 30 | 31 | ![assets/midwit.pngs](assets/midwit.png) 32 | -------------------------------------------------------------------------------- /assets/midwit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsharma/minified-generative-models/3354e368f83f81694bb833ba4492fa4f93ae18b5/assets/midwit.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==2.0.0 2 | appnope==0.1.3 3 | asttokens==2.4.0 4 | backcall==0.2.0 5 | cached-property==1.5.2 6 | chex==0.1.7 7 | cloudpickle==3.0.0 8 | clu==0.0.10 9 | comm==0.1.4 10 | contextlib2==21.6.0 11 | contourpy==1.1.1 12 | cycler==0.12.1 13 | debugpy==1.8.0 14 | decorator==5.1.1 15 | diffrax==0.4.1 16 | dm-tree==0.1.8 17 | equinox==0.11.1 18 | etils==1.5.1 19 | executing==2.0.0 20 | flax==0.7.4 21 | fonttools==4.43.1 22 | fsspec==2023.9.2 23 | gast==0.5.4 24 | importlib-resources==6.1.0 25 | ipykernel==6.25.2 26 | ipython==8.16.1 27 | jax==0.4.14 28 | jaxlib==0.4.14 29 | jaxtyping==0.2.23 30 | jedi==0.19.1 31 | joblib==1.3.2 32 | jupyter_client==8.4.0 33 | jupyter_core==5.4.0 34 | kiwisolver==1.4.5 35 | markdown-it-py==3.0.0 36 | matplotlib==3.8.0 37 | matplotlib-inline==0.1.6 38 | mdurl==0.1.2 39 | ml-collections==0.1.1 40 | ml-dtypes==0.3.1 41 | msgpack==1.0.7 42 | nest-asyncio==1.5.8 43 | numpy==1.26.1 44 | opt-einsum==3.3.0 45 | optax==0.1.7 46 | orbax-checkpoint==0.1.6 47 | packaging==23.2 48 | parso==0.8.3 49 | pexpect==4.8.0 50 | pickleshare==0.7.5 51 | Pillow==10.1.0 52 | platformdirs==3.11.0 53 | prompt-toolkit==3.0.39 54 | protobuf==4.24.4 55 | psutil==5.9.6 56 | ptyprocess==0.7.0 57 | pure-eval==0.2.2 58 | Pygments==2.16.1 59 | pyparsing==3.1.1 60 | python-dateutil==2.8.2 61 | PyYAML==6.0.1 62 | pyzmq==25.1.1 63 | rich==13.6.0 64 | scikit-learn==1.3.1 65 | scipy==1.11.3 66 | six==1.16.0 67 | stack-data==0.6.3 68 | tensorflow-probability==0.22.0 69 | tensorstore==0.1.45 70 | threadpoolctl==3.2.0 71 | toolz==0.12.0 72 | tornado==6.3.3 73 | tqdm==4.66.1 74 | traitlets==5.11.2 75 | typeguard==2.13.3 76 | typing_extensions==4.5.0 77 | wcwidth==0.2.8 78 | wrapt==1.15.0 79 | zipp==3.17.0 80 | --------------------------------------------------------------------------------