Skip to content

Commit

Permalink
Improve ExGaussian logcdf and refactor logp (#4407)
Browse files Browse the repository at this point in the history
* - Improve ExGaussian logcdf and refactor logp

* - Remove unused std_cdf

* - Add release note

* - Remove unused import `theano`
  • Loading branch information
ricardoV94 authored Jan 5, 2021
1 parent 044c407 commit 8b1f64c
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 25 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ It also brings some dreadfully awaited fixes, so be sure to go through the chang
- Fix issue in `logp` method of `HyperGeometric`. It now returns `-inf` for invalid parameters (see [4367](https://github.com/pymc-devs/pymc3/pull/4367))
- Fixed `MatrixNormal` random method to work with parameters as random variables. (see [#4368](https://github.com/pymc-devs/pymc3/pull/4368))
- Update the `logcdf` method of several continuous distributions to return -inf for invalid parameters and values, and raise an informative error when multiple values cannot be evaluated in a single call. (see [4393](https://github.com/pymc-devs/pymc3/pull/4393))
- Improve numerical stability in `logp` and `logcdf` methods of `ExGaussian` (see [#4407](https://github.com/pymc-devs/pymc3/pull/4407))

## PyMC3 3.10.0 (7 December 2020)

Expand Down
52 changes: 27 additions & 25 deletions pymc3/distributions/continuous.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import warnings

import numpy as np
import theano
import theano.tensor as tt

from scipy import stats
Expand All @@ -37,10 +36,10 @@
gammaln,
i0e,
incomplete_beta,
log_normal,
logpow,
normal_lccdf,
normal_lcdf,
std_cdf,
zvalue,
)
from pymc3.distributions.distribution import Continuous, draw_values, generate_samples
Expand Down Expand Up @@ -3214,22 +3213,22 @@ def logp(self, value):
sigma = self.sigma
nu = self.nu

standardized_val = (value - mu) / sigma
cdf_val = std_cdf(standardized_val - sigma / nu)
cdf_val_safe = tt.switch(tt.eq(cdf_val, 0), np.finfo(theano.config.floatX).eps, cdf_val)

# This condition is suggested by exGAUS.R from gamlss
lp = tt.switch(
tt.gt(nu, 0.05 * sigma),
-tt.log(nu) + (mu - value) / nu + 0.5 * (sigma / nu) ** 2 + logpow(cdf_val_safe, 1.0),
-tt.log(sigma * tt.sqrt(2 * np.pi)) - 0.5 * standardized_val ** 2,
# Alogithm is adapted from dexGAUS.R from gamlss
return bound(
tt.switch(
tt.gt(nu, 0.05 * sigma),
(
-tt.log(nu)
+ (mu - value) / nu
+ 0.5 * (sigma / nu) ** 2
+ normal_lcdf(mu + (sigma ** 2) / nu, sigma, value)
),
log_normal(value, mean=mu, sigma=sigma),
),
0 < sigma,
0 < nu,
)

return bound(lp, sigma > 0.0, nu > 0.0)

def _distr_parameters_for_repr(self):
return ["mu", "sigma", "nu"]

def logcdf(self, value):
"""
Compute the log of the cumulative distribution function for ExGaussian distribution
Expand All @@ -3253,22 +3252,25 @@ def logcdf(self, value):
"""
mu = self.mu
sigma = self.sigma
sigma_2 = sigma ** 2
nu = self.nu
z = value - mu - sigma_2 / nu

# Alogithm is adapted from pexGAUS.R from gamlss
return tt.switch(
tt.gt(nu, 0.05 * sigma),
tt.log(
std_cdf((value - mu) / sigma)
- std_cdf(z / sigma)
* tt.exp(
((mu + (sigma_2 / nu)) ** 2 - (mu ** 2) - 2 * value * ((sigma_2) / nu))
/ (2 * sigma_2)
)
logdiffexp(
normal_lcdf(mu, sigma, value),
(
(mu - value) / nu
+ 0.5 * (sigma / nu) ** 2
+ normal_lcdf(mu + (sigma ** 2) / nu, sigma, value)
),
),
normal_lcdf(mu, sigma, value),
)

def _distr_parameters_for_repr(self):
return ["mu", "sigma", "nu"]


class VonMises(Continuous):
r"""
Expand Down
6 changes: 6 additions & 0 deletions pymc3/tests/test_distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,9 @@ def test_get_tau_sigma(self):
(15.0, 5.000, 7.500, 7.500, -3.3093854),
(50.0, 50.000, 10.000, 10.000, -3.6436067),
(1000.0, 500.000, 10.000, 20.000, -27.8707323),
(-1.0, 1.0, 20.0, 0.9, -3.91967108), # Fails in scipy version
(0.01, 0.01, 100.0, 0.01, -5.5241087), # Fails in scipy version
(-1.0, 0.0, 0.1, 0.1, -51.022349), # Fails in previous pymc3 version
],
)
def test_ex_gaussian(self, value, mu, sigma, nu, logp):
Expand All @@ -1851,6 +1854,9 @@ def test_ex_gaussian(self, value, mu, sigma, nu, logp):
(15.0, 5.000, 7.500, 7.500, -0.4545255),
(50.0, 50.000, 10.000, 10.000, -1.433714),
(1000.0, 500.000, 10.000, 20.000, -1.573708e-11),
(0.01, 0.01, 100.0, 0.01, -0.69314718), # Fails in scipy version
(-0.43402407, 0.0, 0.1, 0.1, -13.59615423), # Previous 32-bit version failed here
(-0.72402009, 0.0, 0.1, 0.1, -31.26571842), # Previous 64-bit version failed here
],
)
def test_ex_gaussian_cdf(self, value, mu, sigma, nu, logcdf):
Expand Down

0 comments on commit 8b1f64c

Please sign in to comment.