From 9788171ca2e62394834d44170eff0ab49d677338 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 17:44:03 +1100 Subject: [PATCH 01/17] first pass implementation of ed.marginal --- edward/util/random_variables.py | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 2784e2632..11f7dd23a 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -768,3 +768,37 @@ def transform(x, *args, **kwargs): raise NotImplementedError(msg) return TransformedDistribution(x, bij, *args, **kwargs) + +def marginal(x, n): + """Performs a full graph sample on the provided random variable. + + Given a random variable and a sample size, adds an additional sample + dimension to the root random variables in x's graph, and samples from + a new graph in terms of that sample size. + + Args: + x : RandomVariable. + Random variable to perform full graph sample on. + n : tf.Tensor or int + The size of the full graph sample to take. + + Returns: + tf.Tensor. + The fully sampled values from x, of shape [n] + x.shape + + """ + old_roots = [rv for rv in ed.get_ancestors(x) if ed.get_ancestors(rv) == []] + new_roots = [] + for rv in old_roots: + new_rv = ed.copy(rv) + if new_rv.shape == (): + new_rv._sample_shape = tf.TensorShape([n, 1]) + else: + new_rv._sample_shape = tf.TensorShape(n).concatenate(new_rv._sample_shape) + new_rv._value = new_rv.sample(new_rv._sample_shape) + new_roots.append(new_rv) + dict_swap = dict(zip(old_roots, new_roots)) + x_full = ed.copy(x, dict_swap) + if x_full.shape[1:] != x.shape: + raise ValueError('Could not transform graph for bulk sampling.') + return x_full.sample() From 05250266c4ebb33d390559795e85c127b05c1ea6 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 17:59:43 +1100 Subject: [PATCH 02/17] failing tests for single RV case --- edward/__init__.py | 3 ++- edward/util/__init__.py | 1 + edward/util/random_variables.py | 9 +++++--- tests/test-util/test_marginal.py | 37 ++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 tests/test-util/test_marginal.py diff --git a/edward/__init__.py b/edward/__init__.py index ce3305795..833a4300a 100644 --- a/edward/__init__.py +++ b/edward/__init__.py @@ -20,7 +20,7 @@ from edward.util import check_data, check_latent_vars, copy, dot, \ get_ancestors, get_blanket, get_children, get_control_variate_coef, \ get_descendants, get_parents, get_session, get_siblings, get_variables, \ - Progbar, random_variables, rbf, set_seed, to_simplex, transform + marginal, Progbar, random_variables, rbf, set_seed, to_simplex, transform from edward.version import __version__, VERSION from tensorflow.python.util.all_util import remove_undocumented @@ -74,6 +74,7 @@ 'get_session', 'get_siblings', 'get_variables', + 'marginal', 'Progbar', 'random_variables', 'rbf', diff --git a/edward/util/__init__.py b/edward/util/__init__.py index dce454aed..7320d77df 100644 --- a/edward/util/__init__.py +++ b/edward/util/__init__.py @@ -25,6 +25,7 @@ 'get_session', 'get_siblings', 'get_variables', + 'marginal', 'Progbar', 'random_variables', 'rbf', diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 11f7dd23a..280e6ae84 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -787,10 +787,13 @@ def marginal(x, n): The fully sampled values from x, of shape [n] + x.shape """ - old_roots = [rv for rv in ed.get_ancestors(x) if ed.get_ancestors(rv) == []] + if get_ancestors(x) != []: + old_roots = [rv for rv in get_ancestors(x) if get_ancestors(rv) == []] + else: + old_roots = [x] new_roots = [] for rv in old_roots: - new_rv = ed.copy(rv) + new_rv = copy(rv) if new_rv.shape == (): new_rv._sample_shape = tf.TensorShape([n, 1]) else: @@ -798,7 +801,7 @@ def marginal(x, n): new_rv._value = new_rv.sample(new_rv._sample_shape) new_roots.append(new_rv) dict_swap = dict(zip(old_roots, new_roots)) - x_full = ed.copy(x, dict_swap) + x_full = copy(x, dict_swap) if x_full.shape[1:] != x.shape: raise ValueError('Could not transform graph for bulk sampling.') return x_full.sample() diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py new file mode 100644 index 000000000..d15e794a9 --- /dev/null +++ b/tests/test-util/test_marginal.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import edward as ed +import numpy as np +import tensorflow as tf + +from edward.models import Normal +from tensorflow.contrib.distributions import bijectors + + +class test_marginal_class(tf.test.TestCase): + + def test_bad_graph(self): + with self.test_session(): + loc = Normal(0.0, 5.0, sample_shape=5) + y_loc = tf.expand_dims(loc, 1) + inv_scale = Normal(0.0, 1.0, sample_shape=3) + y_scale = tf.expand_dims(tf.nn.softplus(inv_scale), 0) + y = Normal(y_loc, y_scale) + with self.assertRaises(ValueError): + ed.marginal(y, 20) + + def test_single(self): + with self.test_session(): + y = Normal(0.0, 1.0) + print(ed.get_ancestors(y)) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4]) + + def test_single_expand(self): + with self.test_session(): + y = Normal(0.0, 1.0, sample_shape=5) + print(ed.get_ancestors(y)) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4, 5]) From bee3b3bba3618e9fcdfa057902d91616b1419eb6 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 19:57:55 +1100 Subject: [PATCH 03/17] more limited marginal implementation with basic tests passing --- edward/util/random_variables.py | 31 ++++++++++++++++------- tests/test-util/test_marginal.py | 43 ++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 280e6ae84..df4661dd7 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -786,22 +786,35 @@ def marginal(x, n): tf.Tensor. The fully sampled values from x, of shape [n] + x.shape + #### Notes + + The current implementation only works for graphs of RVs that don't use + the `sample_shape` kwarg. """ - if get_ancestors(x) != []: - old_roots = [rv for rv in get_ancestors(x) if get_ancestors(rv) == []] - else: + # Get the ancestors + # If any has a sample_shape, break + # otherwise, grab the roots + # if there are no roots, just use the RV + ancestors = get_ancestors(x) + if any([rv.sample_shape != () for rv in ancestors]) or x.sample_shape != (): + raise NotImplementedError("`marginal` doesn't support graphs of RVs " + "with non scalar sample_shape args.") + elif ancestors == []: old_roots = [x] + else: + old_roots = [rv for rv in ancestors if get_ancestors(rv) == []] + new_roots = [] for rv in old_roots: new_rv = copy(rv) - if new_rv.shape == (): - new_rv._sample_shape = tf.TensorShape([n, 1]) - else: - new_rv._sample_shape = tf.TensorShape(n).concatenate(new_rv._sample_shape) + new_rv._sample_shape = tf.TensorShape(n).concatenate(new_rv._sample_shape) new_rv._value = new_rv.sample(new_rv._sample_shape) new_roots.append(new_rv) dict_swap = dict(zip(old_roots, new_roots)) - x_full = copy(x, dict_swap) + x_full = copy(x, dict_swap, replace_itself=True) if x_full.shape[1:] != x.shape: + print(x_full.shape) + print(x.shape) raise ValueError('Could not transform graph for bulk sampling.') - return x_full.sample() + + return x_full diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py index d15e794a9..f50683990 100644 --- a/tests/test-util/test_marginal.py +++ b/tests/test-util/test_marginal.py @@ -14,24 +14,51 @@ class test_marginal_class(tf.test.TestCase): def test_bad_graph(self): with self.test_session(): - loc = Normal(0.0, 5.0, sample_shape=5) + loc = Normal(tf.zeros(5), 5.0) y_loc = tf.expand_dims(loc, 1) - inv_scale = Normal(0.0, 1.0, sample_shape=3) + inv_scale = Normal(tf.zeros(3), 1.0) y_scale = tf.expand_dims(tf.nn.softplus(inv_scale), 0) y = Normal(y_loc, y_scale) with self.assertRaises(ValueError): ed.marginal(y, 20) - def test_single(self): + def test_sample_arg(self): + with self.test_session(): + y = Normal(0.0, 1.0, sample_shape=10) + with self.assertRaises(NotImplementedError): + ed.marginal(y, 20) + + def test_sample_arg_ancestor(self): + with self.test_session(): + x = Normal(0.0, 1.0, sample_shape=10) + y = Normal(x, 0.0) + with self.assertRaises(NotImplementedError): + ed.marginal(y, 20) + + def test_no_ancestor(self): with self.test_session(): y = Normal(0.0, 1.0) - print(ed.get_ancestors(y)) sample = ed.marginal(y, 4) self.assertEqual(sample.shape, [4]) - def test_single_expand(self): + def test_no_ancestor_batch(self): with self.test_session(): - y = Normal(0.0, 1.0, sample_shape=5) - print(ed.get_ancestors(y)) + y = Normal(tf.zeros([2, 3, 4]), 1.0) + sample = ed.marginal(y, 5) + self.assertEqual(sample.shape, [5, 2, 3, 4]) + + def test_single_ancestor(self): + with self.test_session(): + loc = Normal(0.0, 1.0) + y = Normal(loc, 1.0) sample = ed.marginal(y, 4) - self.assertEqual(sample.shape, [4, 5]) + self.assertEqual(sample.shape, [4]) + + def test_single_ancestor_batch(self): + with self.test_session(): + loc = Normal(tf.zeros([2, 3, 4]), 1.0) + y = Normal(loc, 1.0) + sample = ed.marginal(y, 5) + self.assertEqual(sample.shape, [5, 2, 3, 4]) + + From 4dbb36fcc13503f4e3dbdae0263dc41e104a2287 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:04:16 +1100 Subject: [PATCH 04/17] added more complex test cases --- edward/util/random_variables.py | 4 ---- tests/test-util/test_marginal.py | 26 ++++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index df4661dd7..5eabac217 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -791,10 +791,6 @@ def marginal(x, n): The current implementation only works for graphs of RVs that don't use the `sample_shape` kwarg. """ - # Get the ancestors - # If any has a sample_shape, break - # otherwise, grab the roots - # if there are no roots, just use the RV ancestors = get_ancestors(x) if any([rv.sample_shape != () for rv in ancestors]) or x.sample_shape != (): raise NotImplementedError("`marginal` doesn't support graphs of RVs " diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py index f50683990..60ad9cc9a 100644 --- a/tests/test-util/test_marginal.py +++ b/tests/test-util/test_marginal.py @@ -6,7 +6,7 @@ import numpy as np import tensorflow as tf -from edward.models import Normal +from edward.models import Normal, InverseGamma from tensorflow.contrib.distributions import bijectors @@ -15,7 +15,7 @@ class test_marginal_class(tf.test.TestCase): def test_bad_graph(self): with self.test_session(): loc = Normal(tf.zeros(5), 5.0) - y_loc = tf.expand_dims(loc, 1) + y_loc = tf.expand_dims(loc, 1) # this displaces the sample dimension inv_scale = Normal(tf.zeros(3), 1.0) y_scale = tf.expand_dims(tf.nn.softplus(inv_scale), 0) y = Normal(y_loc, y_scale) @@ -61,4 +61,26 @@ def test_single_ancestor_batch(self): sample = ed.marginal(y, 5) self.assertEqual(sample.shape, [5, 2, 3, 4]) + def test_multiple_ancestors(self): + with self.test_session(): + loc = Normal(0.0, 1.0) + scale = InverseGamma(1.0, 1.0) + y = Normal(loc, scale) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4]) + def test_multiple_ancestors_batch(self): + with self.test_session(): + loc = Normal(tf.zeros(5), 1.0) + scale = InverseGamma(tf.ones(5), 1.0) + y = Normal(loc, scale) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4, 5]) + + def test_multiple_ancestors_batch_broadcast(self): + with self.test_session(): + loc = Normal(tf.zeros([5, 1]), 1.0) + scale = InverseGamma(tf.ones([1, 6]), 1.0) + y = Normal(loc, scale) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4, 5, 6]) From c4d7b9e702a5a01f87b2a6bf8af0a9ea7bc1d232 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:12:10 +1100 Subject: [PATCH 05/17] test confirming sampling passthrough --- tests/test-util/test_marginal.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py index 60ad9cc9a..5c1d3fd54 100644 --- a/tests/test-util/test_marginal.py +++ b/tests/test-util/test_marginal.py @@ -61,6 +61,15 @@ def test_single_ancestor_batch(self): sample = ed.marginal(y, 5) self.assertEqual(sample.shape, [5, 2, 3, 4]) + def test_sample_passthrough(self): + with self.test_session(): + loc = Normal(0.0, 100.0) + y = Normal(loc, 0.0001) + conditional_sample = y.sample(50) + marginal_sample = ed.marginal(y, 50) + self.assertTrue(np.std(conditional_sample.eval()) < 1.0) + self.assertTrue(np.std(marginal_sample.eval()) > 1.0) + def test_multiple_ancestors(self): with self.test_session(): loc = Normal(0.0, 1.0) @@ -84,3 +93,11 @@ def test_multiple_ancestors_batch_broadcast(self): y = Normal(loc, scale) sample = ed.marginal(y, 4) self.assertEqual(sample.shape, [4, 5, 6]) + + def test_multiple_ancestors_failed_broadcast(self): + with self.test_session(): + loc = Normal(tf.zeros([5, 1]), 1.0) + scale = InverseGamma(tf.ones([6]), 1.0) + y = Normal(loc, scale) + with self.assertRaises(ValueError): + sample = ed.marginal(y, 4) From b7f4e8698ac5c88919fd8c5c00e81633833c98d9 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:18:28 +1100 Subject: [PATCH 06/17] pep8 fix --- edward/util/random_variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 5eabac217..502b0ee3a 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -769,6 +769,7 @@ def transform(x, *args, **kwargs): return TransformedDistribution(x, bij, *args, **kwargs) + def marginal(x, n): """Performs a full graph sample on the provided random variable. From 5c78557087cb70b78606b99f19ce24f43b6e971e Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:23:20 +1100 Subject: [PATCH 07/17] added example to docstring --- edward/util/random_variables.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 502b0ee3a..b7818c3cf 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -787,6 +787,22 @@ def marginal(x, n): tf.Tensor. The fully sampled values from x, of shape [n] + x.shape + #### Examples + + ```python + ed.get_session() + loc = Normal(0.0, 100.0) + y = Normal(loc, 0.0001) + conditional_sample = y.sample(50) + marginal_sample = ed.marginal(y, 50) + + np.std(conditional_sample.eval()) + 0.000100221 + + np.std(marginal_sample.eval()) + 106.55982 + ``` + #### Notes The current implementation only works for graphs of RVs that don't use From e6520791c859105c9d885192e64eb77af4b5fd05 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 17:44:03 +1100 Subject: [PATCH 08/17] first pass implementation of ed.marginal --- edward/util/random_variables.py | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 5b5f3d137..3db98e50e 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -778,3 +778,38 @@ def transform(x, *args, **kwargs): new_x = TransformedDistribution(x, bij, *args, **kwargs) new_x.support = new_support return new_x + + +def marginal(x, n): + """Performs a full graph sample on the provided random variable. + + Given a random variable and a sample size, adds an additional sample + dimension to the root random variables in x's graph, and samples from + a new graph in terms of that sample size. + + Args: + x : RandomVariable. + Random variable to perform full graph sample on. + n : tf.Tensor or int + The size of the full graph sample to take. + + Returns: + tf.Tensor. + The fully sampled values from x, of shape [n] + x.shape + + """ + old_roots = [rv for rv in ed.get_ancestors(x) if ed.get_ancestors(rv) == []] + new_roots = [] + for rv in old_roots: + new_rv = ed.copy(rv) + if new_rv.shape == (): + new_rv._sample_shape = tf.TensorShape([n, 1]) + else: + new_rv._sample_shape = tf.TensorShape(n).concatenate(new_rv._sample_shape) + new_rv._value = new_rv.sample(new_rv._sample_shape) + new_roots.append(new_rv) + dict_swap = dict(zip(old_roots, new_roots)) + x_full = ed.copy(x, dict_swap) + if x_full.shape[1:] != x.shape: + raise ValueError('Could not transform graph for bulk sampling.') + return x_full.sample() From 7b4020b24813ae41c5c500abbc35604f0943260e Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 17:59:43 +1100 Subject: [PATCH 09/17] failing tests for single RV case --- edward/__init__.py | 3 ++- edward/util/__init__.py | 1 + edward/util/random_variables.py | 9 +++++--- tests/test-util/test_marginal.py | 37 ++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 tests/test-util/test_marginal.py diff --git a/edward/__init__.py b/edward/__init__.py index ce3305795..833a4300a 100644 --- a/edward/__init__.py +++ b/edward/__init__.py @@ -20,7 +20,7 @@ from edward.util import check_data, check_latent_vars, copy, dot, \ get_ancestors, get_blanket, get_children, get_control_variate_coef, \ get_descendants, get_parents, get_session, get_siblings, get_variables, \ - Progbar, random_variables, rbf, set_seed, to_simplex, transform + marginal, Progbar, random_variables, rbf, set_seed, to_simplex, transform from edward.version import __version__, VERSION from tensorflow.python.util.all_util import remove_undocumented @@ -74,6 +74,7 @@ 'get_session', 'get_siblings', 'get_variables', + 'marginal', 'Progbar', 'random_variables', 'rbf', diff --git a/edward/util/__init__.py b/edward/util/__init__.py index dce454aed..7320d77df 100644 --- a/edward/util/__init__.py +++ b/edward/util/__init__.py @@ -25,6 +25,7 @@ 'get_session', 'get_siblings', 'get_variables', + 'marginal', 'Progbar', 'random_variables', 'rbf', diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 3db98e50e..45e6139c3 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -798,10 +798,13 @@ def marginal(x, n): The fully sampled values from x, of shape [n] + x.shape """ - old_roots = [rv for rv in ed.get_ancestors(x) if ed.get_ancestors(rv) == []] + if get_ancestors(x) != []: + old_roots = [rv for rv in get_ancestors(x) if get_ancestors(rv) == []] + else: + old_roots = [x] new_roots = [] for rv in old_roots: - new_rv = ed.copy(rv) + new_rv = copy(rv) if new_rv.shape == (): new_rv._sample_shape = tf.TensorShape([n, 1]) else: @@ -809,7 +812,7 @@ def marginal(x, n): new_rv._value = new_rv.sample(new_rv._sample_shape) new_roots.append(new_rv) dict_swap = dict(zip(old_roots, new_roots)) - x_full = ed.copy(x, dict_swap) + x_full = copy(x, dict_swap) if x_full.shape[1:] != x.shape: raise ValueError('Could not transform graph for bulk sampling.') return x_full.sample() diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py new file mode 100644 index 000000000..d15e794a9 --- /dev/null +++ b/tests/test-util/test_marginal.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import edward as ed +import numpy as np +import tensorflow as tf + +from edward.models import Normal +from tensorflow.contrib.distributions import bijectors + + +class test_marginal_class(tf.test.TestCase): + + def test_bad_graph(self): + with self.test_session(): + loc = Normal(0.0, 5.0, sample_shape=5) + y_loc = tf.expand_dims(loc, 1) + inv_scale = Normal(0.0, 1.0, sample_shape=3) + y_scale = tf.expand_dims(tf.nn.softplus(inv_scale), 0) + y = Normal(y_loc, y_scale) + with self.assertRaises(ValueError): + ed.marginal(y, 20) + + def test_single(self): + with self.test_session(): + y = Normal(0.0, 1.0) + print(ed.get_ancestors(y)) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4]) + + def test_single_expand(self): + with self.test_session(): + y = Normal(0.0, 1.0, sample_shape=5) + print(ed.get_ancestors(y)) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4, 5]) From 4e8c946317ec4dbacd07fb24ad9ae34cbfc749ef Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 19:57:55 +1100 Subject: [PATCH 10/17] more limited marginal implementation with basic tests passing --- edward/util/random_variables.py | 31 ++++++++++++++++------- tests/test-util/test_marginal.py | 43 ++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 45e6139c3..6d3b945fc 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -797,22 +797,35 @@ def marginal(x, n): tf.Tensor. The fully sampled values from x, of shape [n] + x.shape + #### Notes + + The current implementation only works for graphs of RVs that don't use + the `sample_shape` kwarg. """ - if get_ancestors(x) != []: - old_roots = [rv for rv in get_ancestors(x) if get_ancestors(rv) == []] - else: + # Get the ancestors + # If any has a sample_shape, break + # otherwise, grab the roots + # if there are no roots, just use the RV + ancestors = get_ancestors(x) + if any([rv.sample_shape != () for rv in ancestors]) or x.sample_shape != (): + raise NotImplementedError("`marginal` doesn't support graphs of RVs " + "with non scalar sample_shape args.") + elif ancestors == []: old_roots = [x] + else: + old_roots = [rv for rv in ancestors if get_ancestors(rv) == []] + new_roots = [] for rv in old_roots: new_rv = copy(rv) - if new_rv.shape == (): - new_rv._sample_shape = tf.TensorShape([n, 1]) - else: - new_rv._sample_shape = tf.TensorShape(n).concatenate(new_rv._sample_shape) + new_rv._sample_shape = tf.TensorShape(n).concatenate(new_rv._sample_shape) new_rv._value = new_rv.sample(new_rv._sample_shape) new_roots.append(new_rv) dict_swap = dict(zip(old_roots, new_roots)) - x_full = copy(x, dict_swap) + x_full = copy(x, dict_swap, replace_itself=True) if x_full.shape[1:] != x.shape: + print(x_full.shape) + print(x.shape) raise ValueError('Could not transform graph for bulk sampling.') - return x_full.sample() + + return x_full diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py index d15e794a9..f50683990 100644 --- a/tests/test-util/test_marginal.py +++ b/tests/test-util/test_marginal.py @@ -14,24 +14,51 @@ class test_marginal_class(tf.test.TestCase): def test_bad_graph(self): with self.test_session(): - loc = Normal(0.0, 5.0, sample_shape=5) + loc = Normal(tf.zeros(5), 5.0) y_loc = tf.expand_dims(loc, 1) - inv_scale = Normal(0.0, 1.0, sample_shape=3) + inv_scale = Normal(tf.zeros(3), 1.0) y_scale = tf.expand_dims(tf.nn.softplus(inv_scale), 0) y = Normal(y_loc, y_scale) with self.assertRaises(ValueError): ed.marginal(y, 20) - def test_single(self): + def test_sample_arg(self): + with self.test_session(): + y = Normal(0.0, 1.0, sample_shape=10) + with self.assertRaises(NotImplementedError): + ed.marginal(y, 20) + + def test_sample_arg_ancestor(self): + with self.test_session(): + x = Normal(0.0, 1.0, sample_shape=10) + y = Normal(x, 0.0) + with self.assertRaises(NotImplementedError): + ed.marginal(y, 20) + + def test_no_ancestor(self): with self.test_session(): y = Normal(0.0, 1.0) - print(ed.get_ancestors(y)) sample = ed.marginal(y, 4) self.assertEqual(sample.shape, [4]) - def test_single_expand(self): + def test_no_ancestor_batch(self): with self.test_session(): - y = Normal(0.0, 1.0, sample_shape=5) - print(ed.get_ancestors(y)) + y = Normal(tf.zeros([2, 3, 4]), 1.0) + sample = ed.marginal(y, 5) + self.assertEqual(sample.shape, [5, 2, 3, 4]) + + def test_single_ancestor(self): + with self.test_session(): + loc = Normal(0.0, 1.0) + y = Normal(loc, 1.0) sample = ed.marginal(y, 4) - self.assertEqual(sample.shape, [4, 5]) + self.assertEqual(sample.shape, [4]) + + def test_single_ancestor_batch(self): + with self.test_session(): + loc = Normal(tf.zeros([2, 3, 4]), 1.0) + y = Normal(loc, 1.0) + sample = ed.marginal(y, 5) + self.assertEqual(sample.shape, [5, 2, 3, 4]) + + From 0f26cd7e1379a9aafb6f1c9bcc211b2cc225543c Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:04:16 +1100 Subject: [PATCH 11/17] added more complex test cases --- edward/util/random_variables.py | 4 ---- tests/test-util/test_marginal.py | 26 ++++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 6d3b945fc..149e3e942 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -802,10 +802,6 @@ def marginal(x, n): The current implementation only works for graphs of RVs that don't use the `sample_shape` kwarg. """ - # Get the ancestors - # If any has a sample_shape, break - # otherwise, grab the roots - # if there are no roots, just use the RV ancestors = get_ancestors(x) if any([rv.sample_shape != () for rv in ancestors]) or x.sample_shape != (): raise NotImplementedError("`marginal` doesn't support graphs of RVs " diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py index f50683990..60ad9cc9a 100644 --- a/tests/test-util/test_marginal.py +++ b/tests/test-util/test_marginal.py @@ -6,7 +6,7 @@ import numpy as np import tensorflow as tf -from edward.models import Normal +from edward.models import Normal, InverseGamma from tensorflow.contrib.distributions import bijectors @@ -15,7 +15,7 @@ class test_marginal_class(tf.test.TestCase): def test_bad_graph(self): with self.test_session(): loc = Normal(tf.zeros(5), 5.0) - y_loc = tf.expand_dims(loc, 1) + y_loc = tf.expand_dims(loc, 1) # this displaces the sample dimension inv_scale = Normal(tf.zeros(3), 1.0) y_scale = tf.expand_dims(tf.nn.softplus(inv_scale), 0) y = Normal(y_loc, y_scale) @@ -61,4 +61,26 @@ def test_single_ancestor_batch(self): sample = ed.marginal(y, 5) self.assertEqual(sample.shape, [5, 2, 3, 4]) + def test_multiple_ancestors(self): + with self.test_session(): + loc = Normal(0.0, 1.0) + scale = InverseGamma(1.0, 1.0) + y = Normal(loc, scale) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4]) + def test_multiple_ancestors_batch(self): + with self.test_session(): + loc = Normal(tf.zeros(5), 1.0) + scale = InverseGamma(tf.ones(5), 1.0) + y = Normal(loc, scale) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4, 5]) + + def test_multiple_ancestors_batch_broadcast(self): + with self.test_session(): + loc = Normal(tf.zeros([5, 1]), 1.0) + scale = InverseGamma(tf.ones([1, 6]), 1.0) + y = Normal(loc, scale) + sample = ed.marginal(y, 4) + self.assertEqual(sample.shape, [4, 5, 6]) From c81e3818f44b898420e17434d96862298415580d Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:12:10 +1100 Subject: [PATCH 12/17] test confirming sampling passthrough --- tests/test-util/test_marginal.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py index 60ad9cc9a..5c1d3fd54 100644 --- a/tests/test-util/test_marginal.py +++ b/tests/test-util/test_marginal.py @@ -61,6 +61,15 @@ def test_single_ancestor_batch(self): sample = ed.marginal(y, 5) self.assertEqual(sample.shape, [5, 2, 3, 4]) + def test_sample_passthrough(self): + with self.test_session(): + loc = Normal(0.0, 100.0) + y = Normal(loc, 0.0001) + conditional_sample = y.sample(50) + marginal_sample = ed.marginal(y, 50) + self.assertTrue(np.std(conditional_sample.eval()) < 1.0) + self.assertTrue(np.std(marginal_sample.eval()) > 1.0) + def test_multiple_ancestors(self): with self.test_session(): loc = Normal(0.0, 1.0) @@ -84,3 +93,11 @@ def test_multiple_ancestors_batch_broadcast(self): y = Normal(loc, scale) sample = ed.marginal(y, 4) self.assertEqual(sample.shape, [4, 5, 6]) + + def test_multiple_ancestors_failed_broadcast(self): + with self.test_session(): + loc = Normal(tf.zeros([5, 1]), 1.0) + scale = InverseGamma(tf.ones([6]), 1.0) + y = Normal(loc, scale) + with self.assertRaises(ValueError): + sample = ed.marginal(y, 4) From 48600dd5cc1cf21c0181ff2e2a5cb6cb04c7b3c6 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:18:28 +1100 Subject: [PATCH 13/17] pep8 fix --- edward/util/random_variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 149e3e942..5a4436492 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -780,6 +780,7 @@ def transform(x, *args, **kwargs): return new_x + def marginal(x, n): """Performs a full graph sample on the provided random variable. From f4b69d9bd60a9a9a4991a7e31d338a4e34dad85c Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:23:20 +1100 Subject: [PATCH 14/17] added example to docstring --- edward/util/random_variables.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 5a4436492..aecb70913 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -798,6 +798,22 @@ def marginal(x, n): tf.Tensor. The fully sampled values from x, of shape [n] + x.shape + #### Examples + + ```python + ed.get_session() + loc = Normal(0.0, 100.0) + y = Normal(loc, 0.0001) + conditional_sample = y.sample(50) + marginal_sample = ed.marginal(y, 50) + + np.std(conditional_sample.eval()) + 0.000100221 + + np.std(marginal_sample.eval()) + 106.55982 + ``` + #### Notes The current implementation only works for graphs of RVs that don't use From ac90792f9d8813c73f69362662bbcaa65770e427 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:31:36 +1100 Subject: [PATCH 15/17] moved marginal tests to new util test pacakge --- edward/util/random_variables.py | 1 - tests/{test-util => util}/test_marginal.py | 0 2 files changed, 1 deletion(-) rename tests/{test-util => util}/test_marginal.py (100%) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index aecb70913..4d189e9fc 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -780,7 +780,6 @@ def transform(x, *args, **kwargs): return new_x - def marginal(x, n): """Performs a full graph sample on the provided random variable. diff --git a/tests/test-util/test_marginal.py b/tests/util/test_marginal.py similarity index 100% rename from tests/test-util/test_marginal.py rename to tests/util/test_marginal.py From 4a598320897fb6a55e7aa9023ae91cfa2d3dfb98 Mon Sep 17 00:00:00 2001 From: cshenton Date: Mon, 2 Oct 2017 20:56:58 +1100 Subject: [PATCH 16/17] removed duplicate test module --- tests/test-util/test_marginal.py | 103 ------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 tests/test-util/test_marginal.py diff --git a/tests/test-util/test_marginal.py b/tests/test-util/test_marginal.py deleted file mode 100644 index 5c1d3fd54..000000000 --- a/tests/test-util/test_marginal.py +++ /dev/null @@ -1,103 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import edward as ed -import numpy as np -import tensorflow as tf - -from edward.models import Normal, InverseGamma -from tensorflow.contrib.distributions import bijectors - - -class test_marginal_class(tf.test.TestCase): - - def test_bad_graph(self): - with self.test_session(): - loc = Normal(tf.zeros(5), 5.0) - y_loc = tf.expand_dims(loc, 1) # this displaces the sample dimension - inv_scale = Normal(tf.zeros(3), 1.0) - y_scale = tf.expand_dims(tf.nn.softplus(inv_scale), 0) - y = Normal(y_loc, y_scale) - with self.assertRaises(ValueError): - ed.marginal(y, 20) - - def test_sample_arg(self): - with self.test_session(): - y = Normal(0.0, 1.0, sample_shape=10) - with self.assertRaises(NotImplementedError): - ed.marginal(y, 20) - - def test_sample_arg_ancestor(self): - with self.test_session(): - x = Normal(0.0, 1.0, sample_shape=10) - y = Normal(x, 0.0) - with self.assertRaises(NotImplementedError): - ed.marginal(y, 20) - - def test_no_ancestor(self): - with self.test_session(): - y = Normal(0.0, 1.0) - sample = ed.marginal(y, 4) - self.assertEqual(sample.shape, [4]) - - def test_no_ancestor_batch(self): - with self.test_session(): - y = Normal(tf.zeros([2, 3, 4]), 1.0) - sample = ed.marginal(y, 5) - self.assertEqual(sample.shape, [5, 2, 3, 4]) - - def test_single_ancestor(self): - with self.test_session(): - loc = Normal(0.0, 1.0) - y = Normal(loc, 1.0) - sample = ed.marginal(y, 4) - self.assertEqual(sample.shape, [4]) - - def test_single_ancestor_batch(self): - with self.test_session(): - loc = Normal(tf.zeros([2, 3, 4]), 1.0) - y = Normal(loc, 1.0) - sample = ed.marginal(y, 5) - self.assertEqual(sample.shape, [5, 2, 3, 4]) - - def test_sample_passthrough(self): - with self.test_session(): - loc = Normal(0.0, 100.0) - y = Normal(loc, 0.0001) - conditional_sample = y.sample(50) - marginal_sample = ed.marginal(y, 50) - self.assertTrue(np.std(conditional_sample.eval()) < 1.0) - self.assertTrue(np.std(marginal_sample.eval()) > 1.0) - - def test_multiple_ancestors(self): - with self.test_session(): - loc = Normal(0.0, 1.0) - scale = InverseGamma(1.0, 1.0) - y = Normal(loc, scale) - sample = ed.marginal(y, 4) - self.assertEqual(sample.shape, [4]) - - def test_multiple_ancestors_batch(self): - with self.test_session(): - loc = Normal(tf.zeros(5), 1.0) - scale = InverseGamma(tf.ones(5), 1.0) - y = Normal(loc, scale) - sample = ed.marginal(y, 4) - self.assertEqual(sample.shape, [4, 5]) - - def test_multiple_ancestors_batch_broadcast(self): - with self.test_session(): - loc = Normal(tf.zeros([5, 1]), 1.0) - scale = InverseGamma(tf.ones([1, 6]), 1.0) - y = Normal(loc, scale) - sample = ed.marginal(y, 4) - self.assertEqual(sample.shape, [4, 5, 6]) - - def test_multiple_ancestors_failed_broadcast(self): - with self.test_session(): - loc = Normal(tf.zeros([5, 1]), 1.0) - scale = InverseGamma(tf.ones([6]), 1.0) - y = Normal(loc, scale) - with self.assertRaises(ValueError): - sample = ed.marginal(y, 4) From 005337511ec2f23d350c2d21e540d69d19011cb4 Mon Sep 17 00:00:00 2001 From: cshenton Date: Tue, 3 Oct 2017 10:23:37 +1100 Subject: [PATCH 17/17] test seed and formatting --- edward/util/random_variables.py | 126 ++++++++++++++++---------------- tests/util/test_marginal.py | 2 +- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/edward/util/random_variables.py b/edward/util/random_variables.py index 4d189e9fc..febb0f41f 100644 --- a/edward/util/random_variables.py +++ b/edward/util/random_variables.py @@ -716,6 +716,69 @@ def get_variables(x, collection=None): return list(output) +def marginal(x, n): + """Performs a full graph sample on the provided random variable. + + Given a random variable and a sample size, adds an additional sample + dimension to the root random variables in x's graph, and samples from + a new graph in terms of that sample size. + + Args: + x : RandomVariable. + Random variable to perform full graph sample on. + n : tf.Tensor or int + The size of the full graph sample to take. + + Returns: + tf.Tensor. + Full graph sample of shape [n] + x.batch_shape + x.event_shape. + + #### Examples + + ```python + ed.get_session() + loc = Normal(0.0, 100.0) + y = Normal(loc, 0.0001) + conditional_sample = y.sample(50) + marginal_sample = ed.marginal(y, 50) + + np.std(conditional_sample.eval()) + 0.000100221 + + np.std(marginal_sample.eval()) + 106.55982 + ``` + + #### Notes + + The current implementation only works for graphs of RVs that don't use + the `sample_shape` kwarg. + """ + ancestors = get_ancestors(x) + if any([rv.sample_shape != () for rv in ancestors]) or x.sample_shape != (): + raise NotImplementedError("`marginal` doesn't support graphs of RVs " + "with non scalar sample_shape args.") + elif ancestors == []: + old_roots = [x] + else: + old_roots = [rv for rv in ancestors if get_ancestors(rv) == []] + + new_roots = [] + for rv in old_roots: + new_rv = copy(rv) + new_rv._sample_shape = tf.TensorShape(n).concatenate(new_rv._sample_shape) + new_rv._value = new_rv.sample(new_rv._sample_shape) + new_roots.append(new_rv) + dict_swap = dict(zip(old_roots, new_roots)) + x_full = copy(x, dict_swap, replace_itself=True) + if x_full.shape[1:] != x.shape: + print(x_full.shape) + print(x.shape) + raise ValueError('Could not transform graph for bulk sampling.') + + return x_full + + def transform(x, *args, **kwargs): """Transform a continuous random variable to the unconstrained space. @@ -778,66 +841,3 @@ def transform(x, *args, **kwargs): new_x = TransformedDistribution(x, bij, *args, **kwargs) new_x.support = new_support return new_x - - -def marginal(x, n): - """Performs a full graph sample on the provided random variable. - - Given a random variable and a sample size, adds an additional sample - dimension to the root random variables in x's graph, and samples from - a new graph in terms of that sample size. - - Args: - x : RandomVariable. - Random variable to perform full graph sample on. - n : tf.Tensor or int - The size of the full graph sample to take. - - Returns: - tf.Tensor. - The fully sampled values from x, of shape [n] + x.shape - - #### Examples - - ```python - ed.get_session() - loc = Normal(0.0, 100.0) - y = Normal(loc, 0.0001) - conditional_sample = y.sample(50) - marginal_sample = ed.marginal(y, 50) - - np.std(conditional_sample.eval()) - 0.000100221 - - np.std(marginal_sample.eval()) - 106.55982 - ``` - - #### Notes - - The current implementation only works for graphs of RVs that don't use - the `sample_shape` kwarg. - """ - ancestors = get_ancestors(x) - if any([rv.sample_shape != () for rv in ancestors]) or x.sample_shape != (): - raise NotImplementedError("`marginal` doesn't support graphs of RVs " - "with non scalar sample_shape args.") - elif ancestors == []: - old_roots = [x] - else: - old_roots = [rv for rv in ancestors if get_ancestors(rv) == []] - - new_roots = [] - for rv in old_roots: - new_rv = copy(rv) - new_rv._sample_shape = tf.TensorShape(n).concatenate(new_rv._sample_shape) - new_rv._value = new_rv.sample(new_rv._sample_shape) - new_roots.append(new_rv) - dict_swap = dict(zip(old_roots, new_roots)) - x_full = copy(x, dict_swap, replace_itself=True) - if x_full.shape[1:] != x.shape: - print(x_full.shape) - print(x.shape) - raise ValueError('Could not transform graph for bulk sampling.') - - return x_full diff --git a/tests/util/test_marginal.py b/tests/util/test_marginal.py index 5c1d3fd54..d35360692 100644 --- a/tests/util/test_marginal.py +++ b/tests/util/test_marginal.py @@ -7,7 +7,6 @@ import tensorflow as tf from edward.models import Normal, InverseGamma -from tensorflow.contrib.distributions import bijectors class test_marginal_class(tf.test.TestCase): @@ -63,6 +62,7 @@ def test_single_ancestor_batch(self): def test_sample_passthrough(self): with self.test_session(): + tf.set_random_seed(1) loc = Normal(0.0, 100.0) y = Normal(loc, 0.0001) conditional_sample = y.sample(50)