Repository: pyro-ppl/pyro

Issue:
scipy imresize is deprecated
`imresize` is used in `air` example, but it is deprecated now. We need to replace it by something else to make tests pass.

More context about this issue: `imresize` is used in [observations](https://github.com/edwardlib/observations) but this library is deprecated now in favour of tensorflow [datasets](https://github.com/tensorflow/datasets). But multi-mnist is not available in tensorflow's datasets yet. So I'm not sure what is a good solution for this issue.


Discussion:
The problem is with the `observations` library (that uses this function) which is not being maintained anymore. I think we need to remove dependency on `observations`. 
> We need to replace it by something else

The [docs](https://docs.scipy.org/doc/scipy-1.2.0/reference/generated/scipy.misc.imresize.html) suggest using `numpy.array(Image.fromarray(arr).resize())` inplace of `imresize`. This requires `PIL` which AIR already has as a dependency.

Resolving patch:
diff --git a/examples/air/main.py b/examples/air/main.py
--- a/examples/air/main.py
+++ b/examples/air/main.py
@@ -16,13 +16,12 @@
 import numpy as np
 import torch
 import visdom
-from observations import multi_mnist
 
 import pyro
+import pyro.contrib.examples.multi_mnist as multi_mnist
 import pyro.optim as optim
 import pyro.poutine as poutine
 from air import AIR, latents_to_tensor
-
 from pyro.contrib.examples.util import get_data_directory
 from pyro.infer import SVI, JitTraceGraph_ELBO, TraceGraph_ELBO
 from viz import draw_many, tensor_to_objs
@@ -113,7 +112,7 @@ def exp_decay(initial, final, begin, duration, t):
 
 def load_data():
     inpath = get_data_directory(__file__)
-    (X_np, Y), _ = multi_mnist(inpath, max_digits=2, canvas_size=50, seed=42)
+    X_np, Y = multi_mnist.load(inpath)
     X_np = X_np.astype(np.float32)
     X_np /= 255.0
     X = torch.from_numpy(X_np)
diff --git a/pyro/contrib/examples/multi_mnist.py b/pyro/contrib/examples/multi_mnist.py
new file mode 100644
--- /dev/null
+++ b/pyro/contrib/examples/multi_mnist.py
@@ -0,0 +1,91 @@
+"""
+This script generates a dataset similar to the Multi-MNIST dataset
+described in [1].
+
+[1] Eslami, SM Ali, et al. "Attend, infer, repeat: Fast scene
+understanding with generative models." Advances in Neural Information
+Processing Systems. 2016.
+"""
+
+import os
+
+import numpy as np
+from PIL import Image
+
+from pyro.contrib.examples.util import get_data_loader
+
+
+def imresize(arr, size):
+    return np.array(Image.fromarray(arr).resize(size))
+
+
+def sample_one(canvas_size, mnist):
+    i = np.random.randint(mnist['digits'].shape[0])
+    digit = mnist['digits'][i]
+    label = mnist['labels'][i].item()
+    scale = 0.1 * np.random.randn() + 1.3
+    new_size = tuple(int(s / scale) for s in digit.shape)
+    resized = imresize(digit, new_size)
+    w = resized.shape[0]
+    assert w == resized.shape[1]
+    padding = canvas_size - w
+    pad_l = np.random.randint(0, padding)
+    pad_r = np.random.randint(0, padding)
+    pad_width = ((pad_l, padding - pad_l), (pad_r, padding - pad_r))
+    positioned = np.pad(resized, pad_width, 'constant', constant_values=0)
+    return positioned, label
+
+
+def sample_multi(num_digits, canvas_size, mnist):
+    canvas = np.zeros((canvas_size, canvas_size))
+    labels = []
+    for _ in range(num_digits):
+        positioned_digit, label = sample_one(canvas_size, mnist)
+        canvas += positioned_digit
+        labels.append(label)
+    # Crude check for overlapping digits.
+    if np.max(canvas) > 255:
+        return sample_multi(num_digits, canvas_size, mnist)
+    else:
+        return canvas, labels
+
+
+def mk_dataset(n, mnist, max_digits, canvas_size):
+    x = []
+    y = []
+    for _ in range(n):
+        num_digits = np.random.randint(max_digits + 1)
+        canvas, labels = sample_multi(num_digits, canvas_size, mnist)
+        x.append(canvas)
+        y.append(labels)
+    return np.array(x, dtype=np.uint8), y
+
+
+def load_mnist(root_path):
+    loader = get_data_loader('MNIST', root_path)
+    return {
+        'digits': loader.dataset.data.cpu().numpy(),
+        'labels': loader.dataset.targets
+    }
+
+
+def load(root_path):
+    file_path = os.path.join(root_path, 'multi_mnist_uint8.npz')
+    if os.path.exists(file_path):
+        data = np.load(file_path)
+        return data['x'], data['y']
+    else:
+        # Set RNG to known state.
+        rng_state = np.random.get_state()
+        np.random.seed(681307)
+        mnist = load_mnist(root_path)
+        print('Generating multi-MNIST dataset...')
+        x, y = mk_dataset(60000, mnist, 2, 50)
+        # Revert RNG state.
+        np.random.set_state(rng_state)
+        # Crude checksum.
+        # assert x.sum() == 883114919, 'Did not generate the expected data.'
+        with open(file_path, 'wb') as f:
+            np.savez_compressed(f, x=x, y=y)
+        print('Done!')
+        return x, y


Test patch: