Repository: pyro-ppl/pyro

Issue:
Add logging support to Pyro
Using the `print` function is not ideal for multiple reasons:
 - It does not give us enough control and granularity over logging. A mature model will need less logging, whereas we need more of it while debugging or testing a model. Python's logging library provides us this functionality, as we can configure the logging level.
 - The users of Pyro may want to configure their own handler (e.g. email the user if the model throws an error), and we currently do not have a way of doing that.

Right now, we are mostly using the `print` method in the tests. However, since MCMC algorithms will be outputting verbose diagnostic messages or warnings, we should have a logger handle that rather than printing it.


Discussion:

Resolving patch:
diff --git a/pyro/__init__.py b/pyro/__init__.py
--- a/pyro/__init__.py
+++ b/pyro/__init__.py
@@ -5,6 +5,7 @@
 import warnings
 from collections import OrderedDict
 from inspect import isclass
+import logging
 
 import torch
 from torch.autograd import Variable
@@ -17,6 +18,9 @@
 
 __version__ = '0.1.2'
 
+# Default logger to prevent 'No handler found' warning.
+logging.getLogger(__name__).addHandler(logging.NullHandler())
+
 
 def get_param_store():
     """
diff --git a/pyro/infer/importance.py b/pyro/infer/importance.py
--- a/pyro/infer/importance.py
+++ b/pyro/infer/importance.py
@@ -1,9 +1,13 @@
 from __future__ import absolute_import, division, print_function
 
+import logging
+
 import pyro.poutine as poutine
 
 from .abstract_infer import TracePosterior
 
+logger = logging.getLogger(__name__)
+
 
 class Importance(TracePosterior):
     """
@@ -22,7 +26,7 @@ def __init__(self, model, guide=None, num_samples=None):
         super(Importance, self).__init__()
         if num_samples is None:
             num_samples = 10
-            print("num_samples not provided, defaulting to {}".format(num_samples))
+            logger.warn("num_samples not provided, defaulting to {}".format(num_samples))
         if guide is None:
             # propose from the prior by making a guide from the model by hiding observes
             guide = poutine.block(model, hide_types=["observe"])


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1 +1,14 @@
 from __future__ import absolute_import, division, print_function
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+# create log handler for tests
+handler = logging.StreamHandler()
+formatter = logging.Formatter('%(levelname).1s %(name)s \t %(message)s')
+handler.setFormatter(formatter)
+
+# set default logging level for tests
+logger.addHandler(handler)
+logger.setLevel(logging.DEBUG)
diff --git a/tests/infer/test_enum.py b/tests/infer/test_enum.py
--- a/tests/infer/test_enum.py
+++ b/tests/infer/test_enum.py
@@ -1,6 +1,7 @@
 from __future__ import absolute_import, division, print_function
 
 import itertools
+import logging
 import math
 
 import pytest
@@ -16,6 +17,8 @@
 from pyro.infer.tracegraph_elbo import TraceGraph_ELBO
 from tests.common import assert_equal, xfail_if_not_implemented
 
+logger = logging.getLogger(__name__)
+
 
 @pytest.mark.parametrize("graph_type", ["flat", "dense"])
 def test_iter_discrete_traces_scalar(graph_type):
@@ -65,8 +68,7 @@ def model():
     for scale, trace in traces:
         x = trace.nodes["x"]["value"].data.squeeze().long()[0]
         y = trace.nodes["y"]["value"].data.squeeze().long()[0]
-        expected_scale = torch.exp(dist.Bernoulli(p).log_pdf(x) *
-                                   dist.Categorical(ps).log_pdf(y))
+        expected_scale = torch.exp(dist.Bernoulli(p).log_pdf(x) * dist.Categorical(ps).log_pdf(y))
         expected_scale = expected_scale.data.view(-1)[0]
         assert_equal(scale, expected_scale)
 
@@ -103,7 +105,7 @@ def gmm_model(data, verbose=False):
         assert z.size() == (1,)
         z = z.long().data[0]
         if verbose:
-            print("M{} z_{} = {}".format("  " * i, i, z))
+            logger.debug("M{} z_{} = {}".format("  " * i, i, z))
         pyro.observe("x_{}".format(i), dist.Normal(mus[z], sigma), data[i])
 
 
@@ -114,7 +116,7 @@ def gmm_guide(data, verbose=False):
         assert z.size() == (1,)
         z = z.long().data[0]
         if verbose:
-            print("G{} z_{} = {}".format("  " * i, i, z))
+            logger.debug("G{} z_{} = {}".format("  " * i, i, z))
 
 
 @pytest.mark.parametrize("data_size", [1, 2, 3])
@@ -125,7 +127,7 @@ def test_gmm_iter_discrete_traces(model, data_size, graph_type):
     data = Variable(torch.arange(0, data_size))
     traces = list(iter_discrete_traces(graph_type, model, data=data, verbose=True))
     # This non-vectorized version is exponential in data_size:
-    assert len(traces) == 2 ** data_size
+    assert len(traces) == 2**data_size
 
 
 # A Gaussian mixture model, with vectorized batching.
@@ -213,7 +215,7 @@ def guide():
         p = pyro.param("p", Variable(torch.Tensor([0.5]), requires_grad=True))
         pyro.sample("z", dist.Bernoulli(p))
 
-    print("Computing gradients using surrogate loss")
+    logger.info("Computing gradients using surrogate loss")
     Elbo = TraceGraph_ELBO if trace_graph else Trace_ELBO
     elbo = Elbo(enum_discrete=enum_discrete,
                 num_particles=(1 if enum_discrete else num_particles))
@@ -223,12 +225,12 @@ def guide():
     assert params, "no params found"
     actual_grads = {name: pyro.param(name).grad.clone() for name in params}
 
-    print("Computing gradients using finite difference")
+    logger.info("Computing gradients using finite difference")
     elbo = Trace_ELBO(num_particles=num_particles)
     expected_grads = finite_difference(lambda: elbo.loss(model, guide), delta=0.2)
 
     for name in params:
-        print("\n".join([
+        logger.info("\n".join([
             "{} {}".format(name, "-" * 30),
             "expected = {}".format(expected_grads[name].data.cpu().numpy()),
             "  actual = {}".format(actual_grads[name].data.cpu().numpy()),
@@ -251,7 +253,7 @@ def test_gmm_elbo_gradient(model, guide, enum_discrete, trace_graph):
     num_particles = 4000
     data = Variable(torch.Tensor([-1, 1]))
 
-    print("Computing gradients using surrogate loss")
+    logger.info("Computing gradients using surrogate loss")
     Elbo = TraceGraph_ELBO if trace_graph else Trace_ELBO
     elbo = Elbo(enum_discrete=enum_discrete,
                 num_particles=(1 if enum_discrete else num_particles))
@@ -261,12 +263,12 @@ def test_gmm_elbo_gradient(model, guide, enum_discrete, trace_graph):
     assert params, "no params found"
     actual_grads = {name: pyro.param(name).grad.clone() for name in params}
 
-    print("Computing gradients using finite difference")
+    logger.info("Computing gradients using finite difference")
     elbo = Trace_ELBO(num_particles=num_particles)
     expected_grads = finite_difference(lambda: elbo.loss(model, guide, data))
 
     for name in params:
-        print("\n".join([
+        logger.info("\n".join([
             "{} {}".format(name, "-" * 30),
             "expected = {}".format(expected_grads[name].data.cpu().numpy()),
             "  actual = {}".format(actual_grads[name].data.cpu().numpy()),
diff --git a/tests/infer/test_gradient.py b/tests/infer/test_gradient.py
--- a/tests/infer/test_gradient.py
+++ b/tests/infer/test_gradient.py
@@ -1,5 +1,7 @@
 from __future__ import absolute_import, division, print_function
 
+import logging
+
 import numpy as np
 import pytest
 import torch
@@ -13,6 +15,8 @@
 from pyro.util import ng_ones, ng_zeros
 from tests.common import assert_equal
 
+logger = logging.getLogger(__name__)
+
 
 @pytest.mark.parametrize("reparameterized", [True, False], ids=["reparam", "nonreparam"])
 @pytest.mark.parametrize("subsample", [False, True], ids=["full", "subsample"])
@@ -48,6 +52,6 @@ def guide():
 
     expected_grads = {'mu': np.array([0.5, -2.0]), 'sigma': np.array([2.0])}
     for name in sorted(params):
-        print('\nexpected {} = {}'.format(name, expected_grads[name]))
-        print('actual   {} = {}'.format(name, actual_grads[name]))
+        logger.info('\nexpected {} = {}'.format(name, expected_grads[name]))
+        logger.info('actual   {} = {}'.format(name, actual_grads[name]))
     assert_equal(actual_grads, expected_grads, prec=precision)
diff --git a/tests/infer/test_valid_models.py b/tests/infer/test_valid_models.py
--- a/tests/infer/test_valid_models.py
+++ b/tests/infer/test_valid_models.py
@@ -1,5 +1,6 @@
 from __future__ import absolute_import, division, print_function
 
+import logging
 import warnings
 
 import pytest
@@ -11,6 +12,8 @@
 from pyro.infer import SVI
 from pyro.optim import Adam
 
+logger = logging.getLogger(__name__)
+
 # This file tests a variety of model,guide pairs with valid and invalid structure.
 
 
@@ -44,7 +47,7 @@ def assert_warning(model, guide, **kwargs):
         inference.step()
         assert len(w), 'No warnings were raised'
         for warning in w:
-            print(warning)
+            logger.info(warning)
 
 
 @pytest.mark.parametrize("trace_graph", [False, True], ids=["trace", "tracegraph"])
diff --git a/tests/integration_tests/test_conjugate_gaussian_models.py b/tests/integration_tests/test_conjugate_gaussian_models.py
--- a/tests/integration_tests/test_conjugate_gaussian_models.py
+++ b/tests/integration_tests/test_conjugate_gaussian_models.py
@@ -1,5 +1,6 @@
 from __future__ import absolute_import, division, print_function
 
+import logging
 import os
 import time
 
@@ -15,6 +16,8 @@
 from pyro.infer import SVI
 from tests.common import TestCase
 
+logger = logging.getLogger(__name__)
+
 
 def param_mse(name, target):
     return torch.sum(torch.pow(target - pyro.param(name), 2.0)).data.cpu().numpy()[0]
@@ -34,7 +37,6 @@ def setUp(self):
         self.data.append(Variable(torch.Tensor([0.10])))
         self.n_data = Variable(torch.Tensor([len(self.data)]))
         self.sum_data = self.data[0] + self.data[1] + self.data[2] + self.data[3]
-        self.verbose = True
 
     def setup_chain(self, N):
         self.N = N  # number of latent variables in the chain
@@ -94,20 +96,19 @@ def test_elbo_nonreparameterized_N_is_7(self):
         self.do_elbo_test(False, 5000, 0.001, 0.05, difficulty=0.6)
 
     def do_elbo_test(self, reparameterized, n_steps, lr, prec, difficulty=1.0):
-        if self.verbose:
-            n_repa_nodes = torch.sum(self.which_nodes_reparam) if not reparameterized else self.N
-            print(" - - - - - DO GAUSSIAN %d-CHAIN ELBO TEST  [reparameterized = %s; %d/%d] - - - - - " %
-                  (self.N, reparameterized, n_repa_nodes, self.N))
-            if self.N < 0:
-                def array_to_string(y):
-                    return str(map(lambda x: "%.3f" % x.data.cpu().numpy()[0], y))
-
-                print("lambdas: " + array_to_string(self.lambdas))
-                print("target_mus: " + array_to_string(self.target_mus[1:]))
-                print("target_kappas: " + array_to_string(self.target_kappas[1:]))
-                print("lambda_posts: " + array_to_string(self.lambda_posts[1:]))
-                print("lambda_tilde_posts: " + array_to_string(self.lambda_tilde_posts))
-        pyro.clear_param_store()
+        n_repa_nodes = torch.sum(self.which_nodes_reparam) if not reparameterized else self.N
+        logger.info(" - - - - - DO GAUSSIAN %d-CHAIN ELBO TEST  [reparameterized = %s; %d/%d] - - - - - " %
+                    (self.N, reparameterized, n_repa_nodes, self.N))
+        if self.N < 0:
+            def array_to_string(y):
+                return str(map(lambda x: "%.3f" % x.data.cpu().numpy()[0], y))
+
+            logger.debug("lambdas: " + array_to_string(self.lambdas))
+            logger.debug("target_mus: " + array_to_string(self.target_mus[1:]))
+            logger.debug("target_kappas: " + array_to_string(self.target_kappas[1:]))
+            logger.debug("lambda_posts: " + array_to_string(self.lambda_posts[1:]))
+            logger.debug("lambda_tilde_posts: " + array_to_string(self.lambda_tilde_posts))
+            pyro.clear_param_store()
 
         def model(*args, **kwargs):
             next_mean = self.mu0
@@ -154,7 +155,7 @@ def guide(*args, **kwargs):
             t0 = time.time()
             svi.step()
 
-            if (step % 5000 == 0 or step == n_steps - 1) and self.verbose:
+            if step % 5000 == 0 or step == n_steps - 1:
                 kappa_errors, log_sig_errors, mu_errors = [], [], []
                 for k in range(1, self.N + 1):
                     if k != self.N:
@@ -168,10 +169,10 @@ def guide(*args, **kwargs):
                 max_errors = (np.max(mu_errors), np.max(log_sig_errors), np.max(kappa_errors))
                 min_errors = (np.min(mu_errors), np.min(log_sig_errors), np.min(kappa_errors))
                 mean_errors = (np.mean(mu_errors), np.mean(log_sig_errors), np.mean(kappa_errors))
-                print("[max errors]   (mu, log_sigma, kappa) = (%.4f, %.4f, %.4f)" % max_errors)
-                print("[min errors]   (mu, log_sigma, kappa) = (%.4f, %.4f, %.4f)" % min_errors)
-                print("[mean errors]  (mu, log_sigma, kappa) = (%.4f, %.4f, %.4f)" % mean_errors)
-                print("[step time = %.3f;  N = %d;  step = %d]\n" % (time.time() - t0, self.N, step))
+                logger.debug("[max errors]   (mu, log_sigma, kappa) = (%.4f, %.4f, %.4f)" % max_errors)
+                logger.debug("[min errors]   (mu, log_sigma, kappa) = (%.4f, %.4f, %.4f)" % min_errors)
+                logger.debug("[mean errors]  (mu, log_sigma, kappa) = (%.4f, %.4f, %.4f)" % mean_errors)
+                logger.debug("[step time = %.3f;  N = %d;  step = %d]\n" % (time.time() - t0, self.N, step))
 
         self.assertEqual(0.0, max_errors[0], prec=prec)
         self.assertEqual(0.0, max_errors[1], prec=prec)
@@ -184,7 +185,6 @@ class GaussianPyramidTests(TestCase):
 
     def setUp(self):
         self.mu0 = Variable(torch.Tensor([0.52]))
-        self.verbose = True
 
     def setup_pyramid(self, N):
         # pyramid of normals with known covariances and latent means
@@ -360,13 +360,12 @@ def add_edge(s):
 
     def do_elbo_test(self, reparameterized, n_steps, lr, prec, beta1,
                      difficulty=1.0, model_permutation=False):
-        if self.verbose:
-            n_repa_nodes = torch.sum(self.which_nodes_reparam) if not reparameterized \
-                else len(self.q_topo_sort)
-            print((" - - - DO GAUSSIAN %d-LAYERED PYRAMID ELBO TEST " +
-                  "(with a total of %d RVs) [reparameterized=%s; %d/%d; perm=%s] - - -") %
-                  (self.N, (2 ** self.N) - 1, reparameterized, n_repa_nodes,
-                   len(self.q_topo_sort), model_permutation))
+        n_repa_nodes = torch.sum(self.which_nodes_reparam) if not reparameterized \
+            else len(self.q_topo_sort)
+        logger.info((" - - - DO GAUSSIAN %d-LAYERED PYRAMID ELBO TEST " +
+                     "(with a total of %d RVs) [reparameterized=%s; %d/%d; perm=%s] - - -") %
+                    (self.N, (2 ** self.N) - 1, reparameterized, n_repa_nodes,
+                     len(self.q_topo_sort), model_permutation))
         pyro.clear_param_store()
 
         def model(*args, **kwargs):
@@ -459,7 +458,7 @@ def guide(*args, **kwargs):
             t0 = time.time()
             svi.step()
 
-            if (step % 5000 == 0 or step == n_steps - 1) and self.verbose:
+            if step % 5000 == 0 or step == n_steps - 1:
                 log_sig_errors = []
                 for node in self.target_lambdas:
                     target_log_sig = -0.5 * torch.log(self.target_lambdas[node])
@@ -474,11 +473,11 @@ def guide(*args, **kwargs):
                 almost_leftmost_constant_error = param_mse('constant_term_' + leftmost_node[:-1] + 'R',
                                                            self.target_almost_leftmost_constant)
 
-                print("[mean function constant errors (partial)]   %.4f  %.4f" %
-                      (leftmost_constant_error, almost_leftmost_constant_error))
-                print("[min/mean/max log(sigma) errors]   %.4f  %.4f   %.4f" % (min_log_sig_error,
-                      mean_log_sig_error, max_log_sig_error))
-                print("[step time = %.3f;  N = %d;  step = %d]\n" % (time.time() - t0, self.N, step))
+                logger.debug("[mean function constant errors (partial)]   %.4f  %.4f" %
+                             (leftmost_constant_error, almost_leftmost_constant_error))
+                logger.debug("[min/mean/max log(sigma) errors]   %.4f  %.4f   %.4f" %
+                             (min_log_sig_error, mean_log_sig_error, max_log_sig_error))
+                logger.debug("[step time = %.3f;  N = %d;  step = %d]\n" % (time.time() - t0, self.N, step))
 
         self.assertEqual(0.0, max_log_sig_error, prec=prec)
         self.assertEqual(0.0, leftmost_constant_error, prec=prec)
diff --git a/tests/integration_tests/test_tracegraph_elbo.py b/tests/integration_tests/test_tracegraph_elbo.py
--- a/tests/integration_tests/test_tracegraph_elbo.py
+++ b/tests/integration_tests/test_tracegraph_elbo.py
@@ -7,6 +7,7 @@
 from torch.autograd import Variable
 from torch.nn import Parameter
 
+import logging
 import pyro
 import pyro.distributions as dist
 import pyro.optim as optim
@@ -17,6 +18,7 @@
 from tests.distributions.test_transformed_distribution import AffineExp
 
 pytestmark = pytest.mark.stage("integration", "integration_batch_2")
+logger = logging.getLogger(__name__)
 
 
 def param_mse(name, target):
@@ -48,7 +50,6 @@ def setUp(self):
         self.analytic_log_sig_n = -0.5 * torch.log(self.analytic_lam_n)
         self.analytic_mu_n = self.sum_data * (self.lam / self.analytic_lam_n) +\
             self.mu0 * (self.lam0 / self.analytic_lam_n)
-        self.verbose = True
 
     def test_elbo_reparameterized(self):
         self.do_elbo_test(True, 1000)
@@ -58,8 +59,7 @@ def test_elbo_nonreparameterized(self):
         self.do_elbo_test(False, 5000)
 
     def do_elbo_test(self, reparameterized, n_steps):
-        if self.verbose:
-            print(" - - - - - DO NORMALNORMAL ELBO TEST  [reparameterized = %s] - - - - - " % reparameterized)
+        logger.info(" - - - - - DO NORMALNORMAL ELBO TEST  [reparameterized = %s] - - - - - " % reparameterized)
         pyro.clear_param_store()
 
         def model():
@@ -91,8 +91,8 @@ def guide():
 
             mu_error = param_mse("mu_q", self.analytic_mu_n)
             log_sig_error = param_mse("log_sig_q", self.analytic_log_sig_n)
-            if k % 250 == 0 and self.verbose:
-                print("mu error, log(sigma) error:  %.4f, %.4f" % (mu_error, log_sig_error))
+            if k % 250 == 0:
+                logger.debug("mu error, log(sigma) error:  %.4f, %.4f" % (mu_error, log_sig_error))
 
         self.assertEqual(0.0, mu_error, prec=0.03)
         self.assertEqual(0.0, log_sig_error, prec=0.03)
@@ -119,7 +119,6 @@ def setUp(self):
         self.analytic_log_sig_n = -0.5 * torch.log(self.analytic_lam_n)
         self.analytic_mu_n = self.sum_data * (self.lam / self.analytic_lam_n) +\
             self.mu0 * (self.lam0 / self.analytic_lam_n)
-        self.verbose = True
 
     def test_elbo_reparameterized(self):
         self.do_elbo_test(True, True, 5000, 0.02, 0.002, False, False)
@@ -137,10 +136,9 @@ def test_elbo_nonreparameterized_nn_baseline(self):
                           use_decaying_avg_baseline=False)
 
     def do_elbo_test(self, repa1, repa2, n_steps, prec, lr, use_nn_baseline, use_decaying_avg_baseline):
-        if self.verbose:
-            print(" - - - - - DO NORMALNORMALNORMAL ELBO TEST - - - - - -")
-            print("[reparameterized = %s, %s; nn_baseline = %s, decaying_baseline = %s]" %
-                  (repa1, repa2, use_nn_baseline, use_decaying_avg_baseline))
+        logger.info(" - - - - - DO NORMALNORMALNORMAL ELBO TEST - - - - - -")
+        logger.info("[reparameterized = %s, %s; nn_baseline = %s, decaying_baseline = %s]" %
+                    (repa1, repa2, use_nn_baseline, use_decaying_avg_baseline))
         pyro.clear_param_store()
 
         if use_nn_baseline:
@@ -219,10 +217,10 @@ def guide():
             kappa_error = param_mse("kappa_q", 0.5 * ng_ones(1))
             log_sig_prime_error = param_mse("log_sig_q_prime", -0.5 * torch.log(2.0 * self.lam0))
 
-            if k % 500 == 0 and self.verbose:
-                print("errors:  %.4f, %.4f" % (mu_error, log_sig_error), end='')
-                print(", %.4f, %.4f" % (mu_prime_error, log_sig_prime_error), end='')
-                print(", %.4f" % kappa_error)
+            if k % 500 == 0:
+                logger.debug("errors:  %.4f, %.4f" % (mu_error, log_sig_error))
+                logger.debug(", %.4f, %.4f" % (mu_prime_error, log_sig_prime_error))
+                logger.debug(", %.4f" % kappa_error)
 
         self.assertEqual(0.0, mu_error, prec=prec)
         self.assertEqual(0.0, log_sig_error, prec=prec)
@@ -250,11 +248,9 @@ def setUp(self):
         # posterior beta
         self.log_alpha_n = torch.log(self.alpha_n)
         self.log_beta_n = torch.log(self.beta_n)
-        self.verbose = True
 
     def test_elbo_nonreparameterized(self):
-        if self.verbose:
-            print(" - - - - - DO BERNOULLI-BETA ELBO TEST - - - - - ")
+        logger.info(" - - - - - DO BERNOULLI-BETA ELBO TEST - - - - - ")
         pyro.clear_param_store()
 
         def model():
@@ -283,8 +279,8 @@ def guide():
             alpha_error = param_abs_error("alpha_q_log", self.log_alpha_n)
             beta_error = param_abs_error("beta_q_log", self.log_beta_n)
 
-            if k % 500 == 0 and self.verbose:
-                print("alpha_error, beta_error: %.4f, %.4f" % (alpha_error, beta_error))
+            if k % 500 == 0:
+                logger.debug("alpha_error, beta_error: %.4f, %.4f" % (alpha_error, beta_error))
 
         self.assertEqual(0.0, alpha_error, prec=0.03)
         self.assertEqual(0.0, beta_error, prec=0.04)
@@ -308,11 +304,9 @@ def setUp(self):
             Variable(torch.Tensor([self.n_data]))  # posterior beta
         self.log_alpha_n = torch.log(self.alpha_n)
         self.log_beta_n = torch.log(self.beta_n)
-        self.verbose = True
 
     def test_elbo_nonreparameterized(self):
-        if self.verbose:
-            print(" - - - - - DO POISSON-GAMMA ELBO TEST - - - - - ")
+        logger.info(" - - - - - DO POISSON-GAMMA ELBO TEST - - - - - ")
         pyro.clear_param_store()
 
         def model():
@@ -345,8 +339,8 @@ def guide():
             svi.step()
             alpha_error = param_abs_error("alpha_q_log", self.log_alpha_n)
             beta_error = param_abs_error("beta_q_log", self.log_beta_n)
-            if k % 500 == 0 and self.verbose:
-                print("alpha_q_log_error, beta_q_log_error: %.4f, %.4f" % (alpha_error, beta_error))
+            if k % 500 == 0:
+                logger.debug("alpha_q_log_error, beta_q_log_error: %.4f, %.4f" % (alpha_error, beta_error))
 
         self.assertEqual(0.0, alpha_error, prec=0.08)
         self.assertEqual(0.0, beta_error, prec=0.08)
@@ -366,11 +360,9 @@ def setUp(self):
         self.beta_n = self.beta0 + torch.sum(self.data)  # posterior beta
         self.log_alpha_n = torch.log(self.alpha_n)
         self.log_beta_n = torch.log(self.beta_n)
-        self.verbose = True
 
     def test_elbo_nonreparameterized(self):
-        if self.verbose:
-            print(" - - - - - DO EXPONENTIAL-GAMMA ELBO TEST - - - - - ")
+        logger.info(" - - - - - DO EXPONENTIAL-GAMMA ELBO TEST - - - - - ")
         pyro.clear_param_store()
 
         def model():
@@ -399,8 +391,8 @@ def guide():
             alpha_error = param_abs_error("alpha_q_log", self.log_alpha_n)
             beta_error = param_abs_error("beta_q_log", self.log_beta_n)
 
-            if k % 500 == 0 and self.verbose:
-                print("alpha_error, beta_error: %.4f, %.4f" % (alpha_error, beta_error))
+            if k % 500 == 0:
+                logger.debug("alpha_error, beta_error: %.4f, %.4f" % (alpha_error, beta_error))
 
         self.assertEqual(0.0, alpha_error, prec=0.03)
         self.assertEqual(0.0, beta_error, prec=0.03)
@@ -432,7 +424,6 @@ def setUp(self):
         self.mu_n = mu_numerator / self.tau_n  # posterior mu
         self.log_mu_n = torch.log(self.mu_n)
         self.log_tau_n = torch.log(self.tau_n)
-        self.verbose = True
 
     def test_elbo_reparameterized(self):
         self.do_elbo_test(True, 7000, 0.95, 0.001)
@@ -441,8 +432,7 @@ def test_elbo_nonreparameterized(self):
         self.do_elbo_test(False, 7000, 0.95, 0.001)
 
     def do_elbo_test(self, reparameterized, n_steps, beta1, lr):
-        if self.verbose:
-            print(" - - - - - DO LOGNORMAL-NORMAL ELBO TEST [repa = %s] - - - - - " % reparameterized)
+        logger.info(" - - - - - DO LOGNORMAL-NORMAL ELBO TEST [repa = %s] - - - - - " % reparameterized)
         pyro.clear_param_store()
         pt_guide = LogNormalNormalGuide(self.log_mu_n.data + 0.17,
                                         self.log_tau_n.data - 0.143)
@@ -471,15 +461,14 @@ def guide():
 
             mu_error = param_abs_error("mymodule$$$mu_q_log", self.log_mu_n)
             tau_error = param_abs_error("mymodule$$$tau_q_log", self.log_tau_n)
-            if k % 500 == 0 and self.verbose:
-                print("mu_error, tau_error = %.4f, %.4f" % (mu_error, tau_error))
+            if k % 500 == 0:
+                logger.debug("mu_error, tau_error = %.4f, %.4f" % (mu_error, tau_error))
 
         self.assertEqual(0.0, mu_error, prec=0.05)
         self.assertEqual(0.0, tau_error, prec=0.05)
 
     def test_elbo_with_transformed_distribution(self):
-        if self.verbose:
-            print(" - - - - - DO LOGNORMAL-NORMAL ELBO TEST [uses TransformedDistribution] - - - - - ")
+        logger.info(" - - - - - DO LOGNORMAL-NORMAL ELBO TEST [uses TransformedDistribution] - - - - - ")
         pyro.clear_param_store()
 
         def model():
@@ -512,8 +501,8 @@ def guide():
             mu_error = param_abs_error("mu_q_log", self.log_mu_n)
             tau_error = param_abs_error("tau_q_log", self.log_tau_n)
 
-            if k % 500 == 0 and self.verbose:
-                print("mu_error, tau_error = %.4f, %.4f" % (mu_error, tau_error))
+            if k % 500 == 0:
+                logger.debug("mu_error, tau_error = %.4f, %.4f" % (mu_error, tau_error))
 
         self.assertEqual(0.0, mu_error, prec=0.05)
         self.assertEqual(0.0, tau_error, prec=0.05)
@@ -543,7 +532,6 @@ def setUp(self):
         self.analytic_log_sig_n = -0.5 * torch.log(self.analytic_lam_n)
         self.analytic_mu_n = self.sum_data * (self.lam / self.analytic_lam_n) +\
             self.mu0 * (self.lam0 / self.analytic_lam_n)
-        self.verbose = True
 
     # this tests rao-blackwellization in elbo for nested list map_datas
     def test_nested_list_map_data_in_elbo(self, n_steps=4000):
@@ -604,8 +592,8 @@ def obs_outer(i, x):
 
             mu_error = param_mse("mu_q", self.analytic_mu_n)
             log_sig_error = param_mse("log_sig_q", self.analytic_log_sig_n)
-            if k % 500 == 0 and self.verbose:
-                print("mu error, log(sigma) error:  %.4f, %.4f" % (mu_error, log_sig_error))
+            if k % 500 == 0:
+                logger.debug("mu error, log(sigma) error:  %.4f, %.4f" % (mu_error, log_sig_error))
 
         self.assertEqual(0.0, mu_error, prec=0.04)
         self.assertEqual(0.0, log_sig_error, prec=0.04)
@@ -710,10 +698,10 @@ def per_param_callable(module_name, param_name, tags):
                     superfluous_error = torch.max(torch.max(mean_0_error, mean_1_error), mean_2_error)
                     superfluous_errors.append(superfluous_error.data.cpu().numpy()[0])
 
-            if step % 500 == 0 and self.verbose:
-                print("mu error, log(sigma) error:  %.4f, %.4f" % (mu_error, log_sig_error))
+            if step % 500 == 0:
+                logger.debug("mu error, log(sigma) error:  %.4f, %.4f" % (mu_error, log_sig_error))
                 if n_superfluous_top > 0 or n_superfluous_bottom > 0:
-                    print("superfluous error: %.4f" % np.max(superfluous_errors))
+                    logger.debug("superfluous error: %.4f" % np.max(superfluous_errors))
 
         self.assertEqual(0.0, mu_error, prec=0.04)
         self.assertEqual(0.0, log_sig_error, prec=0.05)
diff --git a/tests/nn/test_clipped_nn.py b/tests/nn/test_clipped_nn.py
--- a/tests/nn/test_clipped_nn.py
+++ b/tests/nn/test_clipped_nn.py
@@ -1,5 +1,7 @@
 from __future__ import absolute_import, division, print_function
 
+import logging
+
 import pytest
 import torch
 from torch.autograd import Variable
@@ -7,6 +9,8 @@
 from pyro.nn.clipped_nn import ClippedSigmoid, ClippedSoftmax
 from tests.common import assert_equal
 
+logger = logging.getLogger(__name__)
+
 
 @pytest.mark.parametrize('Tensor', [torch.FloatTensor, torch.DoubleTensor])
 def test_clipped_softmax(Tensor):
@@ -18,8 +22,8 @@ def test_clipped_softmax(Tensor):
         clipped_softmax = ClippedSoftmax(epsilon)
     ps = Variable(Tensor([[0, 1]]))
     softmax_ps = clipped_softmax(ps)
-    print("epsilon = {}".format(epsilon))
-    print("softmax_ps = {}".format(softmax_ps))
+    logger.info("epsilon = {}".format(epsilon))
+    logger.info("softmax_ps = {}".format(softmax_ps))
     assert (softmax_ps.data >= epsilon).all()
     assert (softmax_ps.data <= 1 - epsilon).all()
     assert_equal(softmax_ps.data.sum(), 1.0)
@@ -35,7 +39,7 @@ def test_clipped_sigmoid(Tensor):
         clipped_sigmoid = ClippedSigmoid(epsilon)
     ps = Variable(Tensor([0, 1]))
     sigmoid_ps = clipped_sigmoid(ps)
-    print("epsilon = {}".format(epsilon))
-    print("sigmoid_ps = {}".format(sigmoid_ps))
+    logger.info("epsilon = {}".format(epsilon))
+    logger.info("sigmoid_ps = {}".format(sigmoid_ps))
     assert (sigmoid_ps.data >= epsilon).all()
     assert (sigmoid_ps.data <= 1 - epsilon).all()
diff --git a/tests/poutine/test_mapdata.py b/tests/poutine/test_mapdata.py
--- a/tests/poutine/test_mapdata.py
+++ b/tests/poutine/test_mapdata.py
@@ -1,5 +1,7 @@
 from __future__ import absolute_import, division, print_function
 
+import logging
+
 import pytest
 import torch
 from torch.autograd import Variable
@@ -11,6 +13,8 @@
 from pyro.infer import SVI
 from tests.common import assert_equal, requires_cuda
 
+logger = logging.getLogger(__name__)
+
 
 @pytest.mark.stage("integration", "integration_batch_1")
 @pytest.mark.init(rng_seed=161)
@@ -43,12 +47,9 @@ def add_data_point(x, y):
     analytic_log_sig_n = -0.5 * torch.log(analytic_lam_n)
     analytic_mu_n = sum_data * (lam / analytic_lam_n) +\
         mu0 * (lam0 / analytic_lam_n)
-    verbose = True
     n_steps = 7000
 
-    if verbose:
-        print("DOING ELBO TEST [bs = {}, map_type = {}]".format(
-            batch_size, map_type))
+    logger.debug("DOING ELBO TEST [bs = {}, map_type = {}]".format(batch_size, map_type))
     pyro.clear_param_store()
 
     def model():
@@ -106,8 +107,8 @@ def guide():
                 pyro.param("log_sig_q"),
                 2.0))
 
-        if verbose and k % 500 == 0:
-            print("errors", mu_error.data.cpu().numpy()[0], log_sig_error.data.cpu().numpy()[0])
+        if k % 500 == 0:
+            logger.debug("errors - {}, {}".format(mu_error.data.cpu().numpy()[0], log_sig_error.data.cpu().numpy()[0]))
 
     assert_equal(Variable(torch.zeros(1)), mu_error, prec=0.05)
     assert_equal(Variable(torch.zeros(1)), log_sig_error, prec=0.06)