diff --git a/docs/source/mediation.ipynb b/docs/source/mediation.ipynb index abf84fbb..5f6755d5 100644 --- a/docs/source/mediation.ipynb +++ b/docs/source/mediation.ipynb @@ -1,7 +1,6 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -9,7 +8,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -40,7 +38,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -48,34 +45,49 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "Lest start with loading all the dependencies we use in this example. " + "Let's start with loading all the dependencies we use in this example. " ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 28, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Automatic pdb calling has been turned OFF\n" + ] + } + ], "source": [ - "from typing import Dict, List, Optional, Tuple, Union, TypeVar, Callable\n", + "%reload_ext autoreload\n", + "%pdb off\n", "\n", "import torch\n", - "import torch.nn as nn\n", + "import pytorch_lightning as pl\n", "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", "\n", "import pyro\n", "import pyro.distributions as dist\n", - "from pyro.nn import PyroModule, PyroSample, PyroParam\n", + "from pyro import condition\n", "\n", + "from causal_pyro.counterfactual.handlers import MultiWorldCounterfactual\n", + "from causal_pyro.indexed.ops import IndexSet, gather\n", "from causal_pyro.interventional.handlers import do\n", - "from causal_pyro.counterfactual.handlers import MultiWorldCounterfactual" + "\n", + "pyro.clear_param_store()\n", + "pyro.set_rng_seed(1234)\n", + "pyro.settings.set(module_local_params=True)" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -83,7 +95,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -95,7 +106,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -105,7 +115,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -113,7 +122,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -125,15 +133,13 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "In such a case, by conditioning in a regression analysis on *Education*, we would go against the requirement that covariates to be conditioned on can't be post-treatment (see TODO: link to backdoor example for a discussion of this point)." + "In such a case, by conditioning in a regression analysis on *Education*, we would go against the requirement that covariates to be conditioned on can't be post-treatment (see [the backdoor adjustment example](backdoor.ipynb) for more discussion of this consideration)." ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -141,14 +147,13 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Definitions\n", "\n", "To properly handle the situation we need to carefully deal with multiple variables and counterfactuals-and in this setting, it turns out there are a few different notions in the vicinity that we need to be able to distinguish.\n", - "Since the distinctions that we have made are somewhat convoluted, unlike in the other Causal Pyro examples, we will also frontload the explanation with the corresponding definitions, starting with a piece of notation. Suppose we are given a model $M$ with graph $G$, we are looking at treatment of the intervention $X=x$ on $Y$, given a context $U=u$, assuming the mediator $M$ is set to $m$. The value that $Y$ would have after the intervention fixing $X$ to $x$ in a context $u$ is denoted as $Y_{x}(u)$. \n", + "Since the distinctions that we have made are somewhat convoluted, unlike in the other Causal Pyro examples, we will also frontload the explanation with the corresponding definitions, starting with a piece of notation. Suppose we are given a model $M$ with graph $G$. We are looking at treatment of the intervention $X=x$ on $Y$, given a context $U=u$, assuming the mediator $M$ is set to $m$. The value that $Y$ would have after the intervention fixing $X$ to $x$ in a context $u$ is denoted as $Y_{x}(u)$. \n", "\n", "To better understand the impact of a treatment or a policy change, we need a further distinction. For instance, suppose a treatment ($T$) has a direct impact on disease ($D$), and also causes nausea, which in turn may motivate the patient to use a countermeasure ($C$) that may affect $D$. One question we can ask is about the *total effect* of $T$ on $D$, $P(D_{t} = d) - P(D_{t'} = d)$, where $P(D_{t} = d)$ is the probability that $D=d$ in the intervened model in which $T$ is set to $t$. \n", "\n", @@ -187,7 +192,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -197,7 +201,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -205,7 +208,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -221,7 +223,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -231,7 +232,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -243,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 29, "metadata": {}, "outputs": [ { @@ -342,7 +342,7 @@ "5 Female 2.000000 1.0 1.0 0.0 0.0" ] }, - "execution_count": 21, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -366,13 +366,13 @@ " \"sub_disorder\": torch.tensor(df[\"sub_disorder\"].values, dtype=torch.float),\n", "}\n", "covariates = {\"conflict\": data[\"conflict\"], \"gender\": data[\"gender\"]} \n", - "#mediators = {\"dev_peer\": data[\"dev_peer\"], \"sub_exp\": data[\"sub_exp\"]} TODO: Rafal: I commented this out because it is not used, re-ran the notebook and it still works\n", "\n", "# Show the data\n", "df.head()" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -380,7 +380,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -389,57 +388,33 @@ "We can represent the causal assumptions made in this example as a Pyro model. This specification, however, is somewhat abstract, as we have not required the functions to be linear. Note how their values are not probabilities, but rather logits of the probabilities used in sampling. That is, for instance, for any subject $i$, we take `fam_int`$_i \\sim Bernoulli(p_i)$, where $p_i$ is the $i$-th subject's probability of family intervention, and $logit(p_i) = log\\frac{p_i}{1-p_i}$. " ] }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "def abstract_model(f_fam_int: Callable, f_dev_peer: Callable, f_sub_exp: Callable, f_sub_disorder: Callable):\n", - " \n", - " conflict = pyro.sample(\"conflict\", dist.LogNormal(0, 1))\n", - " gender = pyro.sample(\"gender\", dist.Bernoulli(0.5))\n", - " \n", - " logits_fam_int = f_fam_int(conflict, gender)\n", - " fam_int = pyro.sample(\"fam_int\", dist.Bernoulli(logits=logits_fam_int))\n", - " \n", - " logits_dev_peer = f_dev_peer(conflict, gender, fam_int)\n", - " dev_peer = pyro.sample(\"dev_peer\", dist.Bernoulli(logits=logits_dev_peer))\n", - " \n", - " logits_sub_exp = f_sub_exp(conflict, gender, fam_int)\n", - " sub_exp = pyro.sample(\"sub_exp\", dist.Bernoulli(logits=logits_sub_exp))\n", - " \n", - " logits_sub_disorder = f_sub_disorder(conflict, gender, dev_peer, sub_exp)\n", - " sub_disorder = pyro.sample(\"sub_disorder\", dist.Bernoulli(logits=logits_sub_disorder))\n", - " \n", - " return sub_disorder" - ] - }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "More concretely, we can build the linearity requirement into the way the model is constructed, by also requiring that and $logit(p_i) = \\alpha + \\beta_c($`conflict`$_i) + \\beta_g($`gender`$_i)$. One way to achieve this is by first defining a subclass of a `PyroModule`, which we call a `CausalModel`, and then obtaining the model by instantiating. The `forward` method specifies what happens when we call the resulting model." + "More concretely, we can build the linearity requirement into the way the model is constructed, by also requiring that and $logit(p_i) = \\alpha + \\beta_c($`conflict`$_i) + \\beta_g($`gender`$_i)$:" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ - "class CausalModel(PyroModule):\n", + "class MediationModel(pyro.nn.PyroModule):\n", " def __init__(self):\n", " super().__init__()\n", - " self.f_fam_int = PyroModule[nn.Linear](2, 1)\n", - " self.f_dev_peer = PyroModule[nn.Linear](3, 1)\n", - " self.f_sub_exp = PyroModule[nn.Linear](3, 1)\n", - " self.f_sub_disorder = PyroModule[nn.Linear](4, 1)\n", + " self.f_fam_int = torch.nn.Linear(2, 1)\n", + " self.f_dev_peer = torch.nn.Linear(3, 1)\n", + " self.f_sub_exp = torch.nn.Linear(3, 1)\n", + " self.f_sub_disorder = torch.nn.Linear(4, 1)\n", + " self.register_buffer(\"zero\", torch.tensor(0.))\n", + " self.register_buffer(\"one\", torch.tensor(1.))\n", "\n", " def forward(self) -> torch.Tensor:\n", - " gender = pyro.sample(\"gender\", dist.Bernoulli(0.5))\n", - " conflict = pyro.sample(\"conflict\", dist.LogNormal(0, 1))\n", + " gender = pyro.sample(\"gender\", dist.Bernoulli(0.5 * self.one))\n", + " conflict = pyro.sample(\"conflict\", dist.LogNormal(self.zero, self.one))\n", " \n", " covariates = torch.cat(torch.broadcast_tensors(\n", " conflict[..., None], gender[..., None]\n", @@ -469,7 +444,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -478,7 +452,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 31, "metadata": {}, "outputs": [ { @@ -490,11 +464,11 @@ "\n", "\n", - "\n", + "\n", "\n", "%3\n", - "\n", + "\n", "\n", "\n", "gender\n", @@ -508,7 +482,7 @@ "fam_int\n", "\n", "\n", - "\n", + "\n", "gender->fam_int\n", "\n", "\n", @@ -544,7 +518,7 @@ "sub_disorder\n", "\n", "\n", - "\n", + "\n", "gender->sub_disorder\n", "\n", "\n", @@ -556,19 +530,19 @@ "conflict\n", "\n", "\n", - "\n", + "\n", "conflict->fam_int\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "conflict->dev_peer\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "conflict->sub_exp\n", "\n", "\n", @@ -580,13 +554,13 @@ "\n", "\n", "\n", - "\n", + "\n", "fam_int->dev_peer\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "fam_int->sub_exp\n", "\n", "\n", @@ -598,7 +572,7 @@ "\n", "\n", "\n", - "\n", + "\n", "sub_exp->sub_disorder\n", "\n", "\n", @@ -612,34 +586,32 @@ "dev_peer ~ Bernoulli\n", "sub_exp ~ Bernoulli\n", "sub_disorder ~ Bernoulli\n", - "f_fam_int.weight : Real()\n", - "f_fam_int.bias : Real()\n", - "f_dev_peer.weight : Real()\n", - "f_dev_peer.bias : Real()\n", - "f_sub_exp.weight : Real()\n", - "f_sub_exp.bias : Real()\n", - "f_sub_disorder.weight : Real()\n", - "f_sub_disorder.bias : Real()\n", + "f_fam_int$$$weight : Real()\n", + "f_fam_int$$$bias : Real()\n", + "f_dev_peer$$$weight : Real()\n", + "f_dev_peer$$$bias : Real()\n", + "f_sub_exp$$$weight : Real()\n", + "f_sub_exp$$$bias : Real()\n", + "f_sub_disorder$$$weight : Real()\n", + "f_sub_disorder$$$bias : Real()\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 24, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "surrogate_model = CausalModel()\n", - "pyro.render_model(surrogate_model, render_distributions=True, render_params=True)" + "pyro.render_model(MediationModel(), render_distributions=True, render_params=True)" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -650,25 +622,30 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ - "def direct_effect(model: Callable, X: str, Z: str) -> Callable:\n", - " def wrapper(x, x_prime):\n", - " with MultiWorldCounterfactual(-2):\n", - " ys = do(actions={X: x})(\n", - " do(actions={X: x_prime})(\n", - " do(actions={Z: lambda Z_: Z_})(\n", - " pyro.plate(\"data\", size=x.shape[0], dim=-1)(\n", - " model))))()\n", - " \n", - " return ys\n", - " return wrapper" + "class NaturalDirectEffectModel(pyro.nn.PyroModule):\n", + " \n", + " def __init__(self, causal_model: MediationModel):\n", + " super().__init__()\n", + " self.causal_model = causal_model\n", + "\n", + " @pyro.infer.config_enumerate\n", + " def forward(self, x, x_prime):\n", + " with MultiWorldCounterfactual(), \\\n", + " do(actions=dict(fam_int=(x, x_prime))), \\\n", + " do(actions=dict(sub_exp=lambda Z_: gather(Z_, IndexSet(fam_int={2})))), \\\n", + " pyro.plate(\"data\", size=x.shape[0], dim=-1):\n", + "\n", + " ys = self.causal_model()\n", + " ys_xprime = gather(ys, IndexSet(fam_int={2}, sub_exp={0})) # y_x'\n", + " ys_x = gather(ys, IndexSet(fam_int={1}, sub_exp={1})) # y_x,z\n", + " return ys_xprime - ys_x" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -677,18 +654,169 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 33, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "cluster_data\n", + "\n", + "data\n", + "\n", + "\n", + "cluster___index_plate___fam_int\n", + "\n", + "__index_plate___fam_int\n", + "\n", + "\n", + "cluster___index_plate___sub_exp\n", + "\n", + "__index_plate___sub_exp\n", + "\n", + "\n", + "\n", + "gender\n", + "\n", + "gender\n", + "\n", + "\n", + "\n", + "fam_int\n", + "\n", + "fam_int\n", + "\n", + "\n", + "\n", + "gender->fam_int\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "dev_peer\n", + "\n", + "dev_peer\n", + "\n", + "\n", + "\n", + "gender->dev_peer\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sub_exp\n", + "\n", + "sub_exp\n", + "\n", + "\n", + "\n", + "gender->sub_exp\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sub_disorder\n", + "\n", + "sub_disorder\n", + "\n", + "\n", + "\n", + "gender->sub_disorder\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "conflict\n", + "\n", + "conflict\n", + "\n", + "\n", + "\n", + "conflict->fam_int\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "conflict->dev_peer\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "conflict->sub_exp\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "conflict->sub_disorder\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "fam_int->dev_peer\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "fam_int->sub_exp\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "dev_peer->sub_disorder\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sub_exp->sub_disorder\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "x0 = data[\"fam_int\"].new_full((num_data,), 0.)\n", "x1 = data[\"fam_int\"].new_full((num_data,), 1.)\n", "\n", - "query_model = direct_effect(surrogate_model, \"fam_int\", \"sub_exp\")" + "surrogate_model = MediationModel()\n", + "query_model = NaturalDirectEffectModel(surrogate_model)\n", + "\n", + "pyro.render_model(NaturalDirectEffectModel(MediationModel()), model_args=(x0, x1))" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -699,8 +827,10 @@ }, { "cell_type": "code", - "execution_count": 27, - "metadata": {}, + "execution_count": 34, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -724,17 +854,17 @@ "\n", "\n", "gender\n", - "\n", + "\n", "gender\n", "\n", "\n", "\n", "fam_int\n", - "\n", + "\n", "fam_int\n", "\n", "\n", - "\n", + "\n", "gender->fam_int\n", "\n", "\n", @@ -742,7 +872,7 @@ "\n", "\n", "dev_peer\n", - "\n", + "\n", "dev_peer\n", "\n", "\n", @@ -754,7 +884,7 @@ "\n", "\n", "sub_exp\n", - "\n", + "\n", "sub_exp\n", "\n", "\n", @@ -766,11 +896,11 @@ "\n", "\n", "sub_disorder\n", - "\n", + "\n", "sub_disorder\n", "\n", "\n", - "\n", + "\n", "gender->sub_disorder\n", "\n", "\n", @@ -778,23 +908,23 @@ "\n", "\n", "conflict\n", - "\n", + "\n", "conflict\n", "\n", "\n", - "\n", + "\n", "conflict->fam_int\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "conflict->dev_peer\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "conflict->sub_exp\n", "\n", "\n", @@ -806,13 +936,13 @@ "\n", "\n", "\n", - "\n", + "\n", "fam_int->dev_peer\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "fam_int->sub_exp\n", "\n", "\n", @@ -824,7 +954,7 @@ "\n", "\n", "\n", - "\n", + "\n", "sub_exp->sub_disorder\n", "\n", "\n", @@ -833,26 +963,30 @@ "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 27, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "conditioned_model = pyro.condition(data=data)(\n", - " pyro.plate(\"data\", size=num_data, dim=-1)(\n", - " surrogate_model\n", - " )\n", - ")\n", + "class ConditionedMediationModel(pyro.nn.PyroModule):\n", + " def __init__(self, causal_model: MediationModel):\n", + " super().__init__()\n", + " self.causal_model = causal_model\n", + " \n", + " def forward(self, data):\n", + " with condition(data=data), \\\n", + " pyro.plate(\"data\", size=num_data, dim=-1):\n", + " return self.causal_model()\n", "\n", - "pyro.render_model(conditioned_model)" + "conditioned_model = ConditionedMediationModel(surrogate_model)\n", + "pyro.render_model(conditioned_model, model_args=(data,))" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -861,46 +995,83 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 35, "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "[iteration 0000] loss: 375.0363\n", - "[iteration 0100] loss: 326.8220\n", - "[iteration 0200] loss: 325.5838\n", - "[iteration 0300] loss: 325.1749\n", - "[iteration 0400] loss: 325.0323\n", - "[iteration 0500] loss: 324.9909\n", - "[iteration 0600] loss: 324.9808\n", - "[iteration 0700] loss: 324.9788\n", - "[iteration 0800] loss: 324.9785\n", - "[iteration 0900] loss: 324.9785\n", - "[iteration 1000] loss: 324.9785\n", - "[iteration 1100] loss: 324.9785\n", - "[iteration 1200] loss: 324.9785\n", - "[iteration 1300] loss: 324.9785\n", - "[iteration 1400] loss: 324.9785\n" + "GPU available: True (cuda), used: True\n", + "TPU available: False, using: 0 TPU cores\n", + "IPU available: False, using: 0 IPUs\n", + "HPU available: False, using: 0 HPUs\n", + "You are using a CUDA device ('NVIDIA GeForce RTX 4090 Laptop GPU') that has Tensor Cores. To properly utilize them, you should set `torch.set_float32_matmul_precision('medium' | 'high')` which will trade-off precision for performance. For more details, read https://pytorch.org/docs/stable/generated/torch.set_float32_matmul_precision.html#torch.set_float32_matmul_precision\n", + "LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n", + "\n", + " | Name | Type | Params\n", + "------------------------------------\n", + "0 | elbo | ELBOModule | 16 \n", + "------------------------------------\n", + "16 Trainable params\n", + "0 Non-trainable params\n", + "16 Total params\n", + "0.000 Total estimated model params size (MB)\n", + "/home/eli/miniconda3/lib/python3.10/site-packages/pytorch_lightning/trainer/connectors/data_connector.py:430: PossibleUserWarning: The dataloader, train_dataloader, does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` (try 32 which is the number of cpus on this machine) in the `DataLoader` init to improve performance.\n", + " rank_zero_warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "df0a611b2db943a98b016b95f5fd1ea7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Training: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "`Trainer.fit` stopped: `max_epochs=1500` reached.\n" ] } ], "source": [ - "pyro.clear_param_store()\n", + "class LightningSVI(pl.LightningModule):\n", + " def __init__(self, elbo: pyro.infer.elbo.ELBOModule, **optim_params):\n", + " super().__init__()\n", + " self.optim_params = dict(optim_params)\n", + " self.elbo = elbo\n", + "\n", + " def configure_optimizers(self):\n", + " return torch.optim.Adam(self.elbo.parameters(), **self.optim_params)\n", + "\n", + " def training_step(self, batch, batch_idx):\n", + " return self.elbo(dict(zip(sorted(data.keys()), batch)))\n", + "\n", "\n", "guide = pyro.infer.autoguide.AutoDelta(conditioned_model)\n", - "adam = pyro.optim.Adam({\"lr\": 0.03})\n", - "svi = pyro.infer.SVI(conditioned_model, guide, adam, loss=pyro.infer.Trace_ELBO())\n", - "num_iterations = 1500\n", - "for j in range(num_iterations):\n", - " loss = svi.step()\n", - " if j % 100 == 0:\n", - " print(\"[iteration %04d] loss: %.4f\" % (j, loss / len(data)))" + "elbo = pyro.infer.Trace_ELBO()(conditioned_model, guide)\n", + "\n", + "# initialize\n", + "elbo(data)\n", + "\n", + "# fit\n", + "train_dataset = torch.utils.data.TensorDataset(*(v for k, v in sorted(data.items())))\n", + "train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=num_data)\n", + "svi = LightningSVI(elbo, lr=0.03)\n", + "trainer = pl.Trainer(max_epochs=1500, log_every_n_steps=1)\n", + "trainer.fit(svi, train_dataloaders=train_dataloader)" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -909,29 +1080,21 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ - "conditioned_query_model = pyro.condition(data=covariates)(\n", - " pyro.condition(data={\"fam_int\": data[\"fam_int\"]})( # TODO remove this line which has no effect on inference\n", - " query_model))\n", + "conditioned_query_model = condition(data=dict(fam_int=data[\"fam_int\"], **covariates))(query_model)\n", "\n", - "discrete_posterior = pyro.infer.infer_discrete(first_available_dim=-6)(\n", - " pyro.infer.config_enumerate()(\n", - " conditioned_query_model))\n", + "discrete_posterior = pyro.infer.infer_discrete(first_available_dim=-8)(conditioned_query_model)\n", "\n", - "predictive = pyro.infer.Predictive(conditioned_query_model, guide=discrete_posterior, num_samples=500)\n", + "predictive = pyro.infer.Predictive(discrete_posterior, guide=guide, num_samples=500, return_sites=[\"_RETURN\"])\n", "predictive_samples = predictive(x0, x1)\n", "\n", - "ys_all = predictive_samples[\"sub_disorder_unobserved\"]\n", - "ys_xprime = ys_all[..., 1, 1, 0, :] # TODO is this indexing into the right world?\n", - "ys_x = ys_all[..., 0, 0, 1, :] # TODO is this indexing into the right world?\n", - "individual_NDE_samples = ys_xprime - ys_x" + "individual_NDE_samples = predictive_samples[\"_RETURN\"]" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -939,7 +1102,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -948,26 +1110,25 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "tensor(-0.0348)\n" + "tensor(-0.0358)\n" ] } ], "source": [ - "individual_NDE_mean = torch.mean(individual_NDE_samples, dim=0)\n", - "NDE_samples = torch.mean(individual_NDE_samples, dim=-1) # avg over datapoints\n", + "individual_NDE_mean = torch.mean(individual_NDE_samples.squeeze(), dim=0)\n", + "NDE_samples = torch.mean(individual_NDE_samples.squeeze(), dim=-1) # avg over datapoints\n", "NDE_mean = torch.mean(NDE_samples, dim=0) # avg over posterior samples\n", "print(NDE_mean)" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -976,22 +1137,22 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Text(-0.105, 48, 'Original estimate: -.55')" + "Text(-0.105, 48, 'Original estimate: -.055')" ] }, - "execution_count": 39, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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1h133KFDhw5595k7d66uuuoqxcTEKDY2VjfeeKO2bt1apLXm5eXp4YcfVrVq1VS2bFm1atVKixcv9t7/yy+/6KabblL58uVVtmxZNWzYUHPmzNH27dvVtm1bSVL58uXl8XjUt29fSaeeKUlMTNRzzz2nPn36KDIyUjVr1tSsWbP022+/qUuXLoqMjFSTJk20evVq72P279+vHj16qFq1arrooovUuHFjffjhh977+/btqyVLluiNN97wnunYvn27JCkzM1OdOnVSZGSk4uLi1Lt3b+3bt69Iz0tRVK5cWfHx8d5bWJjvXyMnPxvq5K1ixYoBywIAQXWNTKh5vt3zpiO4duDAAc2bN0/333//KWdI4uPj1bNnT3300UdyHMe7/ZVXXtGll16q77//Xk8++eQpx9y2bZtuvfVWde3aVWvXrtWAAQN8ytCZbN26VZ9++qk+//xzff7551qyZIlGjx7tvf/IkSMaOnSoVq9erQULFigsLEzdunVTQUFBodc7cOBApaena+rUqfrhhx9022236frrr9fmzZsl/Vnq8vLy9PXXX2vdunV68cUXFRkZqYSEBP373/+W9OcZiT179uiNN9444+8ZM2aMWrdure+//16dO3dW79691adPH/Xq1Uvfffed6tSpoz59+nif12PHjql58+aaPXu2MjMzdc8996h3795auXKlJOmNN95QcnKy7r77bu+nYCckJOjgwYNq166dmjVrptWrV2vu3LnKzs5W9+7dvVkmT54sj8dT6OfoXJo2baoqVarouuuu09KlS0+5/0yfDQUgMC6EWeQG53xd+HLrl2oc19h0DFc2b94sx3FUv379095fv359/f777/rtt9+8LyG0a9dODz30kHefk2cGTvrHP/6hpKQkvfzyy5KkpKQkZWZm6vnnz/6HraCgQJMnT1a5cuUkSb1799aCBQu8j0tNTfXZf+LEiapUqZLWr19fqOtzduzYoUmTJmnHjh2qWrWqpD+v05o7d64mTZqkF154QTt27FBqaqoaN/7z32vt2rW9jz/5ElLlypXPeaHyDTfcoAEDBkiSnnrqKb3zzjtq0aKFbrvtNknSsGHDlJyc7H0XXrVq1fTwww97Hz9o0CDNmzdP06ZNU8uWLRUdHa3w8HBddNFFPmcl3377bTVr1kwvvPCCz/OSkJCgn376SXXr1lV0dLSSkpLO+fycS5UqVfTuu+/q8ssvV15env7f//t/atOmjVasWKHLLrtM0v9+NlRSUpL27NmjkSNH6uqrr1ZmZqb33ysA/7oQZpEbnJFxYckvS0xH8Ju/nnE5l8svv/ys92/atEktWrTw2dayZctzHjcxMdFn2FWpUkV79+71/rx582b16NFDtWvXVlRUlBITEyWp0P+Pf926dTpx4oTq1q2ryMhI723JkiXel6geeOABPffcc2rdurVGjBihH374oVDH/rsmTZp4/3dcXJwkecvRX7edXN+JEyf07LPPqnHjxqpQoYIiIyM1b968c65t7dq1WrRokc96Tl5ce3JN3bp108aNG894jA8++MDn8d98881p90tKStKAAQPUvHlzXXnllZo4caKuvPJKjRkzxrtPp06ddNttt6lJkyZKSUnRnDlzdPDgQU2bNu2s6wBQfBfSLCoOzsi4EBkeaTqCaxdffLE8Ho82bNigbt26nXL/hg0bVL58ee8F2JJUtmzZgGQpVaqUz88ej8fnZaObbrpJNWvW1Pjx41W1alUVFBSoUaNGys/PL9TxDx8+rBIlSigjI0MlSpTwuS8y8s9/l//1X/+llJQUzZ49W19++aVGjRqlV199VYMGDSr2Wk6+rHO6bSfX9/LLL+uNN97Q66+/rsaNG6ts2bIaPHjwOdd2+PBh3XTTTXrxxRdPua9KlSqFynrzzTerVatW3p+rVatWqMdJfxbUv37R69/99bOhAATGhTCL3KDIuDAldYrpCK7Fxsbquuuu07hx4zRkyBCf62SysrL0wQcfqE+fPkW6xiIpKUlz5szx2bZq1SpXOffv369NmzZp/PjxuvrqqyXprAP0dJo1a6YTJ05o79693mOcTkJCgu69917de++9Gj58uMaPH69BgwYpPDxc0p9nT/xt6dKl6tKli3r16iXpz4Lz008/qUGDBt59wsPDT/ndl112mf79738rMTGx2O8OKleuXLFf9lmzZs1ZC9PJz4bq3bt3sY4P4NwuhFnkBi8tuXDb9NtMR/CLt99+2/v29q+//lo7d+7U3Llzdd1116latWrnvLbl7wYMGKCNGzdq2LBh+umnnzRt2jRNnjxZkop90Wn58uUVGxur9957T1u2bNHChQs1dOjQIh2jbt266tmzp/r06aMZM2Zo27ZtWrlypUaNGqXZs2dLkgYPHqx58+Zp27Zt+u6777Ro0SLv9UM1a9aUx+PR559/rt9++02HDx8u1lpO55JLLtFXX32lZcuWacOGDRowYICys7N99klMTNSKFSu0fft27du3TwUFBUpLS9OBAwfUo0cPrVq1Slu3btW8efPUr18/b+n55JNPivVZLsOHD1efPn28P7/++uuaOXOmtmzZoszMTA0ePFgLFy5UWlqad5+zfTYUgMC4UGZRcVFkXMj7I890BL+45JJLtHr1atWuXVvdu3dXnTp1dM8996ht27ZKT08v8uek1KpVSx9//LFmzJihJk2a6J133vG+a6m434MVFhamqVOnKiMjQ40aNdKQIUO8FxMXxaRJk9SnTx899NBDSkpKUteuXbVq1SrVqFFD0p9nW9LS0lS/fn1df/31qlu3rvezi6pVq6aRI0fqscceU1xcnAYOHFistZzOE088ocsuu0wpKSlq06aN4uPj1bVrV599Hn74YZUoUUINGjRQpUqVvBctL126VCdOnFDHjh3VuHFjDR48WDExMd63Refk5GjTpk1FzrRnzx6fa3Ty8/P10EMPqXHjxrr22mu1du1azZ8/X+3bt/fuc/KzoZKSktS9e3fFxsb6fDYUAP+7UGZRcXmcolzlGYJyc3MVHR2tnJwcnw/x8oe3VrylQa2Kdu2ErZ5//nm9++67Af02coSg776TmjeXMjKk/3nnEwov8bHZfjnO9tGd/XIcmHGhzqLCzm+ukXGhdY3WpiMErXHjxqlFixaKjY3V0qVL9fLLL/v1DAYA4E+2zyJeWnLh6cVPm44QtDZv3qwuXbqoQYMGevbZZ/XQQw/p6aefNh0LAC44ts8izsggIMaMGePz+SIAAAQCZ2RcePKaUz+eHwCA88n2WUSRcWHlrpWmIwAALGf7LOKlJRfmbZ2ntJZp594RAIIY734KbbbPIs7IuFAyjB4IADDL9llEkXFhxu0zTEcAAFjO9llEkXGh14xepiMAACxn+yyiyLiQm5drOgIAwHK2zyKKjAtX1zjzNygDAHA+2D6L7L5CyKXOdblCH0DR+etdQoDELOKMjAuPzX/MdAQAgOVsn0UUGQAAELIoMi482vpR0xEAAJazfRZRZFzYtG+T6QgAAMvZPosoMi7M3DTTdAQAgOVsn0UUGQAAELIoMi58cvsnpiMAACxn+yyiyLgw4PMBpiMAACxn+yyiyLiw98he0xEA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5URHRzu7d+8+r+s7l0Cs33H+fNtxw4YNnaVLlzrr1q1zbrzxRqdBgwZB+fbzQKz/r4ryDkcTAvEcrFu3zqlUqZLTq1cvn78DAzHkimrq1KlORESEM3nyZGf9+vXOPffc48TExDhZWVmO4zhO7969nccee8y7/9KlS52SJUs6r7zyirNhwwZnxIgRp337dUxMjDNz5kznhx9+cLp06RLUb7/25/rz8/Odm2++2alevbqzZs0an3/feXl5fslMkTmL/fv3Oz169HAiIyOdqKgop1+/fs6hQ4e892/bts2R5CxatMi7bcSIEY6kU26TJk1yHMdxJk2adNr7g/HkWCDWX5jjBpPiPAdHjx517r//fqd8+fLORRdd5HTr1u2Uovrll186rVu3dqKjo53y5cs77dq1O+tbU00J1PpzcnKcu+66y4mJiXEqVKjgdOvWzeft6MEiUOv/q2AvMoF4Ds7090TNmjXP48rO7K233nJq1KjhhIeHOy1btnSWL1/uve/aa6917rzzTp/9p02b5tStW9cJDw93GjZs6MyePdvn/oKCAufJJ5904uLinIiICKd9+/bOpk2bzsdSisWf6z/538fpbn/9b8YNj+P8zwu0AAAAIca6dy0BAIALB0UGAACELIoMAAAIWRQZAAAQsigyAAAgZFFkAABAyKLIAACAkEWRAQAAIYsiAwAAQhZFBkBQ6Nu3rzwej0aPHu2z/dNPP5XH45EkLV68WB6PRx6PR2FhYYqOjlazZs306KOPas+ePT6Pe/rpp737/vVWr16987YmAIFHkQEQNEqXLq0XX3xRv//++1n327Rpk3bv3q1Vq1Zp2LBhmj9/vho1aqR169b57NewYUPt2bPH5/btt98GcgkAzjOKDICg0aFDB8XHx2vUqFFn3a9y5cqKj49X3bp1dccdd2jp0qWqVKmS7rvvPp/9SpYsqfj4eJ9bxYoVA7kEAOcZRQZA0ChRooReeOEFvfXWW/r1118L/bgyZcro3nvv1dKlS7V3794AJgQQbCgyAIJKt27d1LRpU40YMaJIjzt57cv27du929atW6fIyEif27333uvPuAAMK2k6AAD83Ysvvqh27drp4YcfLvRjHMeRJO+FwZKUlJSkWbNm+ewXFRXln5AAggJFBkDQueaaa5SSkqLhw4erb9++hXrMhg0bJEmJiYnebeHh4br44osDkBBAsKDIAAhKo0ePVtOmTZWUlHTOfY8ePar33ntP11xzjSpVqnQe0gEIFhQZAEGpcePG6tmzp958881T7tu7d6+OHTumQ4cOKSMjQy+99JL27dunGTNm+Oz3xx9/KCsry2ebx+NRXFxcQLMDOH8oMgCC1jPPPKOPPvrolO1JSUnyeDyKjIxU7dq11bFjRw0dOlTx8fE++/3444+qUqWKz7aIiAgdO3YsoLkBnD8e5+QVcgAAACGGt18DAICQRZEBAAAhiyIDAABCFkUGAACELIoMAAAIWRQZAAAQsigyAAAgZFFkAABAyKLIAACAkEWRAQAAIYsiAwAAQtb/Byqw+BekkduAAAAAAElFTkSuQmCC", + "image/png": "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xF/HL0nTp09Xy5YtValSJdWsWVNpaWn+Oowy8+fxS9L+/ftVt27dU/5ZCQT+eA3WrVunXr16KS4uTpUrV1aTJk302muv+ftQSm3ChAlKSEhQpUqV1K5dO61ateq028+ePVuNGzdWpUqV1KJFC3366ade6x3H0RNPPKFatWqpcuXKSk5O1pYtW/x5CK748viLi4v16KOPqkWLFqpSpYpq166tvn37as+ePb4L7OCUrr/+eueSSy5xVqxY4fzrX/9yLrroIqdXr16nfc748eOdsWPHOmPHjnUkOb/++qvX+s8++8zp16+fs3DhQmfbtm3OvHnznJo1azoPPfSQH4+kbPxx/GXdryllyTpo0CAnLi7OSU9Pd9asWeNcccUVzpVXXulZf/DgQad69epOv379nE2bNjlZWVlOamqqExMT4xQVFfn7kM6KP47fcRzn5ZdfdmrXru28++67ztatW51169Y58+bN8+ehlIm/jv+4bt26OTfccMMp/6wEAn+8BlOmTHEeeOABZ+nSpc62bducd955x6lcubLzxhtv+PtwzmjmzJlOaGioM3XqVOf777937r33XicqKsrJyck56fbLly93ypcv77zwwgvOhg0bnMcff9ypWLGis379es8248aNcyIjI50PP/zQWbdunXPzzTc79evXd44cOXKuDqvUfH38Bw4ccJKTk53333/f2bRpk5ORkeG0bdvWad26tc8yU2ROYcOGDY4kZ/Xq1Z5ln332mRMSEuLs3r37jM9fsmRJqf/j9MILLzj169d3E9fn/HX8bvd7LpUl64EDB5yKFSs6s2fP9izbuHGjI8nJyMhwHMdxVq9e7Uhydu7c6dnmu+++cyQ5W7Zs8dPRnD1/HX9eXp5TuXJlZ9GiRf49AJf8dfzHTZw40bn22mud9PT0gC0y/n4N/uj+++93OnTo4LvwZdS2bVsnLS3N8/OxY8ec2rVrO2PHjj3p9j179nS6dOnitaxdu3bOwIEDHcdxnJKSEic2NtZ58cUXPesPHDjghIWFOe+9954fjsAdXx//yaxatcqR5Pz0008+ycxbS6eQkZGhqKgoXX755Z5lycnJKleunFauXOnT35Wfn6/q1av7dJ9u+ev4z+Xr6lZZsmZmZqq4uFjJycmeZY0bN1Z8fLwyMjIkSYmJiYqOjtaUKVNUVFSkI0eOaMqUKWrSpIkSEhL8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", "text/plain": [ "
" ] @@ -1001,21 +1162,26 @@ } ], "source": [ - "import matplotlib.pyplot as plt\n", - "\n", "plt.hist(individual_NDE_mean.detach().cpu().numpy(), bins=25, range = (-.12, .02))\n", "plt.axvline(-.055, color='red', linestyle='solid', linewidth=1)\n", "plt.axvline(-.12, color='green', linestyle='dashed', linewidth=.5)\n", "plt.axvline(.01, color='green', linestyle='dashed', linewidth=.5)\n", "plt.xlabel('NDE')\n", "plt.ylabel('Count')\n", - "plt.text(s = 'Original estimate: -.55', x= -.105, y =48)" + "plt.text(s = 'Original estimate: -.055', x= -.105, y =48)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "causal_pyro", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -1038,5 +1204,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 }