Repository: pyro-ppl/pyro

Issue:
Default guide for `Importance` gives non-zero `log_weight`
The default `guide` in `Importance` blocks _all_ sites (and not just those observed). This leads to the `guide_trace` being empty (except for the input and return values). As a result, no samples are reused when replayed on the model _and_ the `guide_trace.log_pdf()` evaluates to `0.0`. The `log_weight` is then equal to the `model_trace.log_pdf()` (which is also evaluated on different samples), which I believe is unintended and incorrect.

The program below illustrates this for a simple univariate Gaussian, where my proposal and target distribution are identical and I would expect the log of the weights to be `0.0`. The latter is only the case when the sites are _explicitly_ exposed.

```python
import torch
from torch.autograd import Variable

import pyro
from pyro import distributions as dist
from pyro import infer
from pyro import poutine


def gaussian():
    return pyro.sample('x', dist.normal,
                       Variable(torch.Tensor([0.0])),
                       Variable(torch.Tensor([1.0])))


# Using `Importance` with the default `guide`, the `log_weight` is equal to the
# `model_trace.log_pdf()`. That is, the `guide_trace.log_pdf()` (evaluated
# internally) is incorrectly `0.0`.
print('importance_default_guide:')
importance_default_guide = infer.Importance(gaussian, num_samples=10)
for model_trace, log_weight in importance_default_guide._traces():
    model_trace_log_pdf = model_trace.log_pdf()
    are_equal = log_weight.data[0] == model_trace_log_pdf.data[0]
    print(log_weight.data[0], are_equal)

# However, setting the `guide` to expose `x` ensures that it is replayed so
# that the `log_weight` is exactly zero for each sample.
print('importance_exposed_guide:')
importance_exposed_guide = infer.Importance(
    gaussian,
    guide=poutine.block(gaussian, expose=['x']),
    num_samples=10)
for model_trace, log_weight in importance_exposed_guide._traces():
    print(log_weight.data[0])
```
```
importance_default_guide:
-0.9368391633033752 True
-1.3421428203582764 True
-0.9189755320549011 True
-2.1423826217651367 True
-2.301940679550171 True
-1.142196774482727 True
-0.9449963569641113 True
-2.7146053314208984 True
-3.420013904571533 True
-1.7994171380996704 True
importance_exposed_guide:
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
```


Discussion:
@rstebbing Thanks for the clear reproducible example!

@eb8680 Can you confirm that this is a bug and not intended behavior?
Thanks @rstebbing, this seems like an issue with `BlockPoutine`.  I'll push a fix shortly.
@eb8680 did you push a fix for this?

Resolving patch:
diff --git a/pyro/poutine/block_poutine.py b/pyro/poutine/block_poutine.py
--- a/pyro/poutine/block_poutine.py
+++ b/pyro/poutine/block_poutine.py
@@ -49,7 +49,7 @@ def __init__(self, fn,
         1. msg["name"] in hide
         2. msg["type"] in hide_types
         3. msg["name"] not in expose and msg["type"] not in expose_types
-        4. hide_all == True
+        4. hide_all == True and hide, hide_types, and expose_types are all None
         """
         super(BlockPoutine, self).__init__(fn)
         # first, some sanity checks:
@@ -65,14 +65,23 @@ def __init__(self, fn,
 
         if expose is None:
             expose = []
+        else:
+            hide_all = True
+
         assert set(hide).isdisjoint(set(expose)), \
             "cannot hide and expose a site"
 
         # hide_types and expose_types intersect?
         if hide_types is None:
             hide_types = []
+        else:
+            hide_all = False
+
         if expose_types is None:
             expose_types = []
+        else:
+            hide_all = True
+
         assert set(hide_types).isdisjoint(set(expose_types)), \
             "cannot hide and expose a site type"
 
@@ -93,7 +102,7 @@ def _block_up(self, msg):
         1. msg["name"] in self.hide
         2. msg["type"] in self.hide_types
         3. msg["name"] not in self.expose and msg["type"] not in self.expose_types
-        4. self.hide_all == True
+        4. self.hide_all == True and hide, hide_types, and expose_types are all None
         """
         # handle observes
         if msg["type"] == "sample" and msg["is_observed"]:
@@ -101,11 +110,13 @@ def _block_up(self, msg):
         else:
             msg_type = msg["type"]
 
+        is_not_exposed = (msg["name"] not in self.expose) and \
+                         (msg_type not in self.expose_types)
+
         # decision rule for hiding:
         if (msg["name"] in self.hide) or \
            (msg_type in self.hide_types) or \
-           ((msg["name"] not in self.expose) and
-            (msg_type not in self.expose_types) and self.hide_all):  # noqa: E129
+           (is_not_exposed and self.hide_all):  # noqa: E129
 
             return True
         # otherwise expose


Test patch:
diff --git a/tests/poutine/test_poutines.py b/tests/poutine/test_poutines.py
--- a/tests/poutine/test_poutines.py
+++ b/tests/poutine/test_poutines.py
@@ -185,6 +185,16 @@ def test_block_partial_expose(self):
                 assert name not in model_trace
                 assert name not in guide_trace
 
+    def test_block_tutorial_case(self):
+        model_trace = poutine.trace(self.model).get_trace()
+        guide_trace = poutine.trace(
+            poutine.block(self.guide, hide_types=["observe"])).get_trace()
+
+        assert "latent1" in model_trace
+        assert "latent1" in guide_trace
+        assert "obs" in model_trace
+        assert "obs" not in guide_trace
+
 
 class QueuePoutineDiscreteTest(TestCase):