|
20 | 20 | from . import transforms
|
21 | 21 | from pymc3.util import get_variable_name
|
22 | 22 | from .special import log_i0
|
23 |
| -from ..math import invlogit, logit |
24 |
| -from .dist_math import bound, logpow, gammaln, betaln, std_cdf, alltrue_elemwise, SplineWrapper, i0e |
| 23 | +from ..math import invlogit, logit, logdiffexp |
| 24 | +from .dist_math import ( |
| 25 | + bound, logpow, gammaln, betaln, std_cdf, alltrue_elemwise, |
| 26 | + SplineWrapper, i0e, normal_lcdf, normal_lccdf |
| 27 | +) |
25 | 28 | from .distribution import Continuous, draw_values, generate_samples
|
26 | 29 |
|
27 | 30 | __all__ = ['Uniform', 'Flat', 'HalfFlat', 'Normal', 'TruncatedNormal', 'Beta',
|
@@ -51,10 +54,21 @@ def __init__(self, transform=transforms.logodds, *args, **kwargs):
|
51 | 54 | class BoundedContinuous(Continuous):
|
52 | 55 | """Base class for bounded continuous distributions"""
|
53 | 56 |
|
54 |
| - def __init__(self, transform='interval', *args, **kwargs): |
| 57 | + def __init__(self, transform='auto', lower=None, upper=None, |
| 58 | + *args, **kwargs): |
| 59 | + |
| 60 | + lower = tt.as_tensor_variable(lower) if lower is not None else None |
| 61 | + upper = tt.as_tensor_variable(upper) if upper is not None else None |
55 | 62 |
|
56 |
| - if transform == 'interval': |
57 |
| - transform = transforms.interval(self.lower, self.upper) |
| 63 | + if transform == 'auto': |
| 64 | + if lower is None and upper is None: |
| 65 | + transform = None |
| 66 | + elif lower is not None and upper is None: |
| 67 | + transform = transforms.lowerbound(lower) |
| 68 | + elif lower is None and upper is not None: |
| 69 | + transform = transforms.upperbound(upper) |
| 70 | + else: |
| 71 | + transform = transforms.interval(lower, upper) |
58 | 72 |
|
59 | 73 | super(BoundedContinuous, self).__init__(
|
60 | 74 | transform=transform, *args, **kwargs)
|
@@ -173,7 +187,8 @@ def __init__(self, lower=0, upper=1, *args, **kwargs):
|
173 | 187 | self.mean = (upper + lower) / 2.
|
174 | 188 | self.median = self.mean
|
175 | 189 |
|
176 |
| - super(Uniform, self).__init__(*args, **kwargs) |
| 190 | + super(Uniform, self).__init__( |
| 191 | + lower=lower, upper=upper, *args, **kwargs) |
177 | 192 |
|
178 | 193 | def random(self, point=None, size=None):
|
179 | 194 | """
|
@@ -446,7 +461,7 @@ def _repr_latex_(self, name=None, dist=None):
|
446 | 461 | get_variable_name(sd))
|
447 | 462 |
|
448 | 463 |
|
449 |
| -class TruncatedNormal(Continuous): |
| 464 | +class TruncatedNormal(BoundedContinuous): |
450 | 465 | R"""
|
451 | 466 | Univariate truncated normal log-likelihood.
|
452 | 467 |
|
@@ -521,34 +536,29 @@ class TruncatedNormal(Continuous):
|
521 | 536 | """
|
522 | 537 |
|
523 | 538 | def __init__(self, mu=0, sd=None, tau=None, lower=None, upper=None,
|
524 |
| - transform='infer', *args, **kwargs): |
| 539 | + transform='auto', *args, **kwargs): |
525 | 540 | tau, sd = get_tau_sd(tau=tau, sd=sd)
|
526 | 541 | self.sd = tt.as_tensor_variable(sd)
|
527 | 542 | self.tau = tt.as_tensor_variable(tau)
|
528 | 543 | self.lower = tt.as_tensor_variable(lower) if lower is not None else lower
|
529 | 544 | self.upper = tt.as_tensor_variable(upper) if upper is not None else upper
|
530 |
| - self.mu = tt.as_tensor_variable(mu) |
| 545 | + self.mu = tt.as_tensor_variable(mu) |
531 | 546 |
|
532 |
| - # Calculate mean |
533 |
| - pdf_a, pdf_b, cdf_a, cdf_b = self._get_boundary_parameters() |
534 |
| - z = cdf_b - cdf_a |
535 |
| - self.mean = self.mu + (pdf_a+pdf_b) / z * self.sd |
| 547 | + if self.lower is None and self.upper is None: |
| 548 | + self._defaultval = mu |
| 549 | + elif self.lower is None and self.upper is not None: |
| 550 | + self._defaultval = self.upper - 1. |
| 551 | + elif self.lower is not None and self.upper is None: |
| 552 | + self._defaultval = self.lower + 1. |
| 553 | + else: |
| 554 | + self._defaultval = (self.lower + self.upper) / 2 |
536 | 555 |
|
537 | 556 | assert_negative_support(sd, 'sd', 'TruncatedNormal')
|
538 | 557 | assert_negative_support(tau, 'tau', 'TruncatedNormal')
|
539 | 558 |
|
540 |
| - if transform == 'infer': |
541 |
| - if lower is None and upper is None: |
542 |
| - transform = None |
543 |
| - elif lower is not None and upper is not None: |
544 |
| - transform = transforms.interval(lower, upper) |
545 |
| - elif upper is not None: |
546 |
| - transform = transforms.upperbound(upper) |
547 |
| - else: |
548 |
| - transform = transforms.lowerbound(lower) |
549 |
| - |
550 | 559 | super(TruncatedNormal, self).__init__(
|
551 |
| - transform=transform, *args, **kwargs) |
| 560 | + defaults=('_defaultval',), transform=transform, |
| 561 | + lower=lower, upper=upper, *args, **kwargs) |
552 | 562 |
|
553 | 563 | def random(self, point=None, size=None):
|
554 | 564 | """
|
@@ -592,89 +602,57 @@ def logp(self, value):
|
592 | 602 | -------
|
593 | 603 | TensorVariable
|
594 | 604 | """
|
595 |
| - sd = self.sd |
596 |
| - tau = self.tau |
597 | 605 | mu = self.mu
|
598 |
| - a = self.lower |
599 |
| - b = self.upper |
600 |
| - |
601 |
| - # In case either a or b are not specified, normalization terms simplify to 1.0 and 0.0 |
602 |
| - # https://en.wikipedia.org/wiki/Truncated_normal_distribution |
603 |
| - norm_left, norm_right = 1.0, 0.0 |
604 |
| - |
605 |
| - # Define normalization |
606 |
| - if b is not None: |
607 |
| - norm_left = self._cdf((b - mu) / sd) |
608 |
| - |
609 |
| - if a is not None: |
610 |
| - norm_right = self._cdf((a - mu) / sd) |
611 |
| - |
612 |
| - f = self._pdf((value - mu) / sd) / sd / (norm_left - norm_right) |
| 606 | + sd = self.sd |
613 | 607 |
|
614 |
| - return bound(tt.log(f), value >= a, value <= b, sd > 0) |
| 608 | + norm = self._normalization() |
| 609 | + logp = Normal.dist(mu=mu, sd=sd).logp(value) - norm |
615 | 610 |
|
616 |
| - def _cdf(self, value): |
617 |
| - """ |
618 |
| - Calculate cdf of standard normal distribution |
| 611 | + bounds = [sd > 0] |
| 612 | + if self.lower is not None: |
| 613 | + bounds.append(value >= self.lower) |
| 614 | + if self.upper is not None: |
| 615 | + bounds.append(value <= self.upper) |
| 616 | + return bound(logp, *bounds) |
619 | 617 |
|
620 |
| - Parameters |
621 |
| - ---------- |
622 |
| - value : numeric |
623 |
| - Value(s) for which log-probability is calculated. If the log probabilities for multiple |
624 |
| - values are desired the values must be provided in a numpy array or theano tensor |
| 618 | + def _normalization(self): |
| 619 | + mu, sd = self.mu, self.sd |
625 | 620 |
|
626 |
| - Returns |
627 |
| - ------- |
628 |
| - TensorVariable |
629 |
| - """ |
630 |
| - return 0.5 * (1.0 + tt.erf(value / tt.sqrt(2))) |
| 621 | + if self.lower is None and self.upper is None: |
| 622 | + return 0. |
631 | 623 |
|
632 |
| - def _pdf(self, value): |
633 |
| - """ |
634 |
| - Calculate pdf of standard normal distribution |
| 624 | + if self.lower is not None and self.upper is not None: |
| 625 | + lcdf_a = normal_lcdf(mu, sd, self.lower) |
| 626 | + lcdf_b = normal_lcdf(mu, sd, self.upper) |
| 627 | + lsf_a = normal_lccdf(mu, sd, self.lower) |
| 628 | + lsf_b = normal_lccdf(mu, sd, self.upper) |
635 | 629 |
|
636 |
| - Parameters |
637 |
| - ---------- |
638 |
| - value : numeric |
639 |
| - Value(s) for which log-probability is calculated. If the log probabilities for multiple |
640 |
| - values are desired the values must be provided in a numpy array or theano tensor |
| 630 | + return tt.switch( |
| 631 | + self.lower > 0, |
| 632 | + logdiffexp(lsf_a, lsf_b), |
| 633 | + logdiffexp(lcdf_b, lcdf_a), |
| 634 | + ) |
641 | 635 |
|
642 |
| - Returns |
643 |
| - ------- |
644 |
| - TensorVariable |
645 |
| - """ |
646 |
| - return 1.0 / tt.sqrt(2 * np.pi) * tt.exp(-0.5 * (value ** 2)) |
| 636 | + if self.lower is not None: |
| 637 | + return normal_lccdf(mu, sd, self.lower) |
| 638 | + else: |
| 639 | + return normal_lcdf(mu, sd, self.upper) |
647 | 640 |
|
648 | 641 | def _repr_latex_(self, name=None, dist=None):
|
649 | 642 | if dist is None:
|
650 | 643 | dist = self
|
651 |
| - sd = dist.sd |
652 |
| - mu = dist.mu |
653 |
| - a = dist.a |
654 |
| - b = dist.b |
655 | 644 | name = r'\text{%s}' % name
|
656 |
| - return r'${} \sim \text{{TruncatedNormal}}(\mathit{{mu}}={},~\mathit{{sd}}={},a={},b={})$'.format(name, |
657 |
| - get_variable_name(mu), |
658 |
| - get_variable_name(sd), |
659 |
| - get_variable_name(a), |
660 |
| - get_variable_name(b)) |
661 |
| - |
662 |
| - def _get_boundary_parameters(self): |
663 |
| - """ |
664 |
| - Calcualte values of cdf and pdf at boundary points a and b |
665 |
| -
|
666 |
| - Returns |
667 |
| - ------- |
668 |
| - pdf(a), pdf(b), cdf(a), cdf(b) if a,b defined, otherwise 0.0, 0.0, 0.0, 1.0 |
669 |
| - """ |
670 |
| - # pdf = 0 at +-inf |
671 |
| - pdf_a = self._pdf(self.lower) if not self.lower is None else 0.0 |
672 |
| - pdf_b = self._pdf(self.upper) if not self.upper is None else 0.0 |
673 |
| - |
674 |
| - # b-> inf, cdf(b) = 1.0, a->-inf, cdf(a) = 0 |
675 |
| - cdf_a = self._cdf(self.lower) if not self.lower is None else 0.0 |
676 |
| - cdf_b = self._cdf(self.upper) if not self.upper is None else 1.0 |
677 |
| - return pdf_a, pdf_b, cdf_a, cdf_b |
| 645 | + return ( |
| 646 | + r'${} \sim \text{{TruncatedNormal}}(' |
| 647 | + '\mathit{{mu}}={},~\mathit{{sd}}={},a={},b={})$' |
| 648 | + .format( |
| 649 | + name, |
| 650 | + get_variable_name(self.mu), |
| 651 | + get_variable_name(self.sd), |
| 652 | + get_variable_name(self.lower), |
| 653 | + get_variable_name(self.upper), |
| 654 | + ) |
| 655 | + ) |
678 | 656 |
|
679 | 657 |
|
680 | 658 | class HalfNormal(PositiveContinuous):
|
@@ -3054,7 +3032,8 @@ def __init__(self, lower=0, upper=1, c=0.5,
|
3054 | 3032 | self.lower = lower = tt.as_tensor_variable(lower)
|
3055 | 3033 | self.upper = upper = tt.as_tensor_variable(upper)
|
3056 | 3034 |
|
3057 |
| - super(Triangular, self).__init__(*args, **kwargs) |
| 3035 | + super(Triangular, self).__init__(lower=lower, upper=upper, |
| 3036 | + *args, **kwargs) |
3058 | 3037 |
|
3059 | 3038 | def random(self, point=None, size=None):
|
3060 | 3039 | """
|
@@ -3553,7 +3532,8 @@ def __init__(self, x_points, pdf_points, *args, **kwargs):
|
3553 | 3532 | self.lower = lower = tt.as_tensor_variable(x_points[0])
|
3554 | 3533 | self.upper = upper = tt.as_tensor_variable(x_points[-1])
|
3555 | 3534 |
|
3556 |
| - super(Interpolated, self).__init__(*args, **kwargs) |
| 3535 | + super(Interpolated, self).__init__(lower=lower, upper=upper, |
| 3536 | + *args, **kwargs) |
3557 | 3537 |
|
3558 | 3538 | interp = InterpolatedUnivariateSpline(
|
3559 | 3539 | x_points, pdf_points, k=1, ext='zeros')
|
|
0 commit comments