From 7421b6e69e345c3cabb48563dc029a5d2a51fbd7 Mon Sep 17 00:00:00 2001 From: Michael Osthege Date: Mon, 9 May 2022 20:57:20 +0200 Subject: [PATCH] Remove `EllipticalSlice` sampler Closes #5137 --- RELEASE-NOTES.md | 2 +- docs/source/api/samplers.rst | 1 - pymc/step_methods/__init__.py | 1 - pymc/step_methods/elliptical_slice.py | 131 -------------------------- pymc/tests/models.py | 26 ----- pymc/tests/test_step.py | 16 ---- scripts/run_mypy.py | 1 - 7 files changed, 1 insertion(+), 177 deletions(-) delete mode 100644 pymc/step_methods/elliptical_slice.py diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c448f1781c6..1b23e0af027 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -14,7 +14,6 @@ Instead update the vNext section until 4.0.0 is out. We plan to get these working again, but at this point their inner workings have not been refactored. - Timeseries distributions (see [#4642](https://github.com/pymc-devs/pymc/issues/4642)) - Nested Mixture distributions (see [#5533](https://github.com/pymc-devs/pymc/issues/5533)) -- Elliptical slice sampling (see [#5137](https://github.com/pymc-devs/pymc/issues/5137)) - `pm.sample_posterior_predictive_w` (see [#4807](https://github.com/pymc-devs/pymc/issues/4807)) - Partially observed Multivariate distributions (see [#5260](https://github.com/pymc-devs/pymc/issues/5260)) @@ -32,6 +31,7 @@ Signature and default parameters changed for several distributions (see [#5628]( - `pm.AsymmetricLaplace` positional arguments re-ordered - `pm.AsymmetricLaplace` now requires `mu` to be specified (no longer defaults to 0) - BART was removed [#5566](https://github.com/pymc-devs/pymc/pull/5566). It is now available from [pymc-experimental](https://github.com/pymc-devs/pymc-experimental) +- The `pm.EllipticalSlice` sampler was removed (see [#5756](https://github.com/pymc-devs/pymc/issues/5756)). - `BaseStochasticGradient` was removed (see [#5630](https://github.com/pymc-devs/pymc/pull/5630)) - ⚠ The library is now named, installed and imported as "pymc". For example: `pip install pymc`. - ⚠ Theano-PyMC has been replaced with Aesara, so all external references to `theano`, `tt`, and `pymc3.theanof` need to be replaced with `aesara`, `at`, and `pymc.aesaraf` (see [4471](https://github.com/pymc-devs/pymc/pull/4471)). diff --git a/docs/source/api/samplers.rst b/docs/source/api/samplers.rst index 614ac47d6e2..1cd43b1da3a 100644 --- a/docs/source/api/samplers.rst +++ b/docs/source/api/samplers.rst @@ -70,5 +70,4 @@ Other step methods :toctree: generated/ CompoundStep - EllipticalSlice Slice diff --git a/pymc/step_methods/__init__.py b/pymc/step_methods/__init__.py index 12f404850cf..8eb79a44b5d 100644 --- a/pymc/step_methods/__init__.py +++ b/pymc/step_methods/__init__.py @@ -13,7 +13,6 @@ # limitations under the License. from pymc.step_methods.compound import CompoundStep -from pymc.step_methods.elliptical_slice import EllipticalSlice from pymc.step_methods.hmc import NUTS, HamiltonianMC from pymc.step_methods.metropolis import ( BinaryGibbsMetropolis, diff --git a/pymc/step_methods/elliptical_slice.py b/pymc/step_methods/elliptical_slice.py deleted file mode 100644 index ad95f831c17..00000000000 --- a/pymc/step_methods/elliptical_slice.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2020 The PyMC Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import aesara.tensor as at -import numpy as np -import numpy.random as nr - -from pymc.aesaraf import inputvars -from pymc.model import modelcontext -from pymc.step_methods.arraystep import ArrayStep, Competence - -__all__ = ["EllipticalSlice"] - - -def get_chol(cov, chol): - """Get Cholesky decomposition of the prior covariance. - - Ensure that exactly one of the prior covariance or Cholesky - decomposition is passed. If the prior covariance was passed, then - return its Cholesky decomposition. - - Parameters - ---------- - cov: array, optional - Covariance matrix of the multivariate Gaussian prior. - chol: array, optional - Cholesky decomposition of the covariance matrix of the - multivariate Gaussian prior. - """ - - if len([i for i in [cov, chol] if i is not None]) != 1: - raise ValueError("Must pass exactly one of cov or chol") - - if cov is not None: - chol = at.slinalg.cholesky(cov) - return chol - - -class EllipticalSlice(ArrayStep): - """Multivariate elliptical slice sampler step. - - Elliptical slice sampling (ESS) [1]_ is a variant of slice sampling - that allows sampling from distributions with multivariate Gaussian - prior and arbitrary likelihood. It is generally about as fast as - regular slice sampling, mixes well even when the prior covariance - might otherwise induce a strong dependence between samples, and - does not depend on any tuning parameters. - - The Gaussian prior is assumed to have zero mean. - - Parameters - ---------- - vars: list - List of value variables for sampler. - prior_cov: array, optional - Covariance matrix of the multivariate Gaussian prior. - prior_chol: array, optional - Cholesky decomposition of the covariance matrix of the - multivariate Gaussian prior. - model: PyMC Model - Optional model for sampling step. Defaults to None (taken from - context). - - References - ---------- - .. [1] I. Murray, R. P. Adams, and D. J. C. MacKay. "Elliptical Slice - Sampling", The Proceedings of the 13th International Conference on - Artificial Intelligence and Statistics (AISTATS), JMLR W&CP - 9:541-548, 2010. - """ - - default_blocked = True - - def __init__(self, vars=None, prior_cov=None, prior_chol=None, model=None, **kwargs): - self.model = modelcontext(model) - chol = get_chol(prior_cov, prior_chol) - self.prior_chol = at.as_tensor_variable(chol) - - if vars is None: - vars = self.model.cont_vars - else: - vars = [self.model.rvs_to_values.get(var, var) for var in vars] - vars = inputvars(vars) - - super().__init__(vars, [self.model.compile_logp()], **kwargs) - - def astep(self, q0, logp): - """q0: current state - logp: log probability function - """ - - # Draw from the normal prior by multiplying the Cholesky decomposition - # of the covariance with draws from a standard normal - # XXX: This needs to be refactored - chol = None # draw_values([self.prior_chol])[0] - nu = np.dot(chol, nr.randn(chol.shape[0])) - y = logp(q0) - nr.standard_exponential() - - # Draw initial proposal and propose a candidate point - theta = nr.uniform(0, 2 * np.pi) - theta_max = theta - theta_min = theta - 2 * np.pi - q_new = q0 * np.cos(theta) + nu * np.sin(theta) - - while logp(q_new) <= y: - # Shrink the bracket and propose a new point - if theta < 0: - theta_min = theta - else: - theta_max = theta - theta = nr.uniform(theta_min, theta_max) - q_new = q0 * np.cos(theta) + nu * np.sin(theta) - - return q_new - - @staticmethod - def competence(var, has_grad): - # Because it requires a specific type of prior, this step method - # should only be assigned explicitly. - return Competence.INCOMPATIBLE diff --git a/pymc/tests/models.py b/pymc/tests/models.py index d88a3ad8ecd..0c1f176a754 100644 --- a/pymc/tests/models.py +++ b/pymc/tests/models.py @@ -163,32 +163,6 @@ def mv_simple_discrete(): return model.initial_point(), model, (mu, C) -def mv_prior_simple(): - n = 3 - noise = 0.1 - X = np.linspace(0, 1, n)[:, None] - - K = pm.gp.cov.ExpQuad(1, 1)(X).eval() - L = np.linalg.cholesky(K) - K_noise = K + noise * np.eye(n) - obs = floatX_array([-0.1, 0.5, 1.1]) - - # Posterior mean - L_noise = np.linalg.cholesky(K_noise) - alpha = np.linalg.solve(L_noise.T, np.linalg.solve(L_noise, obs)) - mu_post = np.dot(K.T, alpha) - - # Posterior standard deviation - v = np.linalg.solve(L_noise, K) - std_post = (K - np.dot(v.T, v)).diagonal() ** 0.5 - - with pm.Model() as model: - x = pm.Flat("x", size=n) - x_obs = pm.MvNormal("x_obs", observed=obs, mu=x, cov=noise * np.eye(n)) - - return model.initial_point(), model, (K, L, mu_post, std_post, noise) - - def non_normal(n=2): with pm.Model() as model: pm.Beta("x", 3, 3, size=n, transform=None) diff --git a/pymc/tests/test_step.py b/pymc/tests/test_step.py index a450a4d1b39..0a9b923017b 100644 --- a/pymc/tests/test_step.py +++ b/pymc/tests/test_step.py @@ -50,7 +50,6 @@ CompoundStep, DEMetropolis, DEMetropolisZ, - EllipticalSlice, HamiltonianMC, Metropolis, MultivariateNormalProposal, @@ -62,7 +61,6 @@ from pymc.step_methods.mlda import extract_Q_estimate from pymc.tests.checks import close_to from pymc.tests.models import ( - mv_prior_simple, mv_simple, mv_simple_coarse, mv_simple_discrete, @@ -154,19 +152,6 @@ def test_step_categorical(self): idata = sample(8000, tune=0, step=step, start=start, model=model, random_seed=1) self.check_stat(check, idata, step.__class__.__name__) - @pytest.mark.xfail(reason="EllipticalSlice not refactored for v4") - def test_step_elliptical_slice(self): - start, model, (K, L, mu, std, noise) = mv_prior_simple() - unc = noise**0.5 - check = (("x", np.mean, mu, unc / 10.0), ("x", np.std, std, unc / 10.0)) - with model: - steps = (EllipticalSlice(prior_cov=K), EllipticalSlice(prior_chol=L)) - for step in steps: - idata = sample( - 5000, tune=0, step=step, start=start, model=model, random_seed=1, chains=1 - ) - self.check_stat(check, idata, step.__class__.__name__) - class TestMetropolisProposal: def test_proposal_choice(self): @@ -1311,7 +1296,6 @@ class TestRVsAssignmentSteps: (HamiltonianMC, {}), (Metropolis, {}), (Slice, {}), - (EllipticalSlice, {"prior_cov": np.eye(1)}), (DEMetropolis, {}), (DEMetropolisZ, {}), # (MLDA, {}), # TODO diff --git a/scripts/run_mypy.py b/scripts/run_mypy.py index 17d9fb18f75..216a069274e 100644 --- a/scripts/run_mypy.py +++ b/scripts/run_mypy.py @@ -56,7 +56,6 @@ pymc/stats/__init__.py pymc/step_methods/__init__.py pymc/step_methods/compound.py -pymc/step_methods/elliptical_slice.py pymc/step_methods/hmc/__init__.py pymc/step_methods/hmc/base_hmc.py pymc/step_methods/hmc/hmc.py