• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""The Laplace distribution class."""
16
17import math
18
19import numpy as np
20
21from tensorflow.python.framework import constant_op
22from tensorflow.python.framework import dtypes
23from tensorflow.python.framework import ops
24from tensorflow.python.framework import tensor_shape
25from tensorflow.python.ops import array_ops
26from tensorflow.python.ops import check_ops
27from tensorflow.python.ops import math_ops
28from tensorflow.python.ops import nn
29from tensorflow.python.ops import random_ops
30from tensorflow.python.ops.distributions import distribution
31from tensorflow.python.ops.distributions import special_math
32from tensorflow.python.util import deprecation
33from tensorflow.python.util.tf_export import tf_export
34
35
36__all__ = [
37    "Laplace",
38    "LaplaceWithSoftplusScale",
39]
40
41
42@tf_export(v1=["distributions.Laplace"])
43class Laplace(distribution.Distribution):
44  """The Laplace distribution with location `loc` and `scale` parameters.
45
46  #### Mathematical details
47
48  The probability density function (pdf) of this distribution is,
49
50  ```none
51  pdf(x; mu, sigma) = exp(-|x - mu| / sigma) / Z
52  Z = 2 sigma
53  ```
54
55  where `loc = mu`, `scale = sigma`, and `Z` is the normalization constant.
56
57  Note that the Laplace distribution can be thought of two exponential
58  distributions spliced together "back-to-back."
59
60  The Lpalce distribution is a member of the [location-scale family](
61  https://en.wikipedia.org/wiki/Location-scale_family), i.e., it can be
62  constructed as,
63
64  ```none
65  X ~ Laplace(loc=0, scale=1)
66  Y = loc + scale * X
67  ```
68
69  """
70
71  @deprecation.deprecated(
72      "2019-01-01",
73      "The TensorFlow Distributions library has moved to "
74      "TensorFlow Probability "
75      "(https://github.com/tensorflow/probability). You "
76      "should update all references to use `tfp.distributions` "
77      "instead of `tf.distributions`.",
78      warn_once=True)
79  def __init__(self,
80               loc,
81               scale,
82               validate_args=False,
83               allow_nan_stats=True,
84               name="Laplace"):
85    """Construct Laplace distribution with parameters `loc` and `scale`.
86
87    The parameters `loc` and `scale` must be shaped in a way that supports
88    broadcasting (e.g., `loc / scale` is a valid operation).
89
90    Args:
91      loc: Floating point tensor which characterizes the location (center)
92        of the distribution.
93      scale: Positive floating point tensor which characterizes the spread of
94        the distribution.
95      validate_args: Python `bool`, default `False`. When `True` distribution
96        parameters are checked for validity despite possibly degrading runtime
97        performance. When `False` invalid inputs may silently render incorrect
98        outputs.
99      allow_nan_stats: Python `bool`, default `True`. When `True`,
100        statistics (e.g., mean, mode, variance) use the value "`NaN`" to
101        indicate the result is undefined. When `False`, an exception is raised
102        if one or more of the statistic's batch members are undefined.
103      name: Python `str` name prefixed to Ops created by this class.
104
105    Raises:
106      TypeError: if `loc` and `scale` are of different dtype.
107    """
108    parameters = dict(locals())
109    with ops.name_scope(name, values=[loc, scale]) as name:
110      with ops.control_dependencies([check_ops.assert_positive(scale)] if
111                                    validate_args else []):
112        self._loc = array_ops.identity(loc, name="loc")
113        self._scale = array_ops.identity(scale, name="scale")
114        check_ops.assert_same_float_dtype([self._loc, self._scale])
115      super(Laplace, self).__init__(
116          dtype=self._loc.dtype,
117          reparameterization_type=distribution.FULLY_REPARAMETERIZED,
118          validate_args=validate_args,
119          allow_nan_stats=allow_nan_stats,
120          parameters=parameters,
121          graph_parents=[self._loc, self._scale],
122          name=name)
123
124  @staticmethod
125  def _param_shapes(sample_shape):
126    return dict(
127        zip(("loc", "scale"), ([ops.convert_to_tensor(
128            sample_shape, dtype=dtypes.int32)] * 2)))
129
130  @property
131  def loc(self):
132    """Distribution parameter for the location."""
133    return self._loc
134
135  @property
136  def scale(self):
137    """Distribution parameter for scale."""
138    return self._scale
139
140  def _batch_shape_tensor(self):
141    return array_ops.broadcast_dynamic_shape(
142        array_ops.shape(self.loc), array_ops.shape(self.scale))
143
144  def _batch_shape(self):
145    return array_ops.broadcast_static_shape(
146        self.loc.get_shape(), self.scale.get_shape())
147
148  def _event_shape_tensor(self):
149    return constant_op.constant([], dtype=dtypes.int32)
150
151  def _event_shape(self):
152    return tensor_shape.TensorShape([])
153
154  def _sample_n(self, n, seed=None):
155    shape = array_ops.concat([[n], self.batch_shape_tensor()], 0)
156    # Uniform variates must be sampled from the open-interval `(-1, 1)` rather
157    # than `[-1, 1)`. In the case of `(0, 1)` we'd use
158    # `np.finfo(self.dtype.as_numpy_dtype).tiny` because it is the smallest,
159    # positive, "normal" number. However, the concept of subnormality exists
160    # only at zero; here we need the smallest usable number larger than -1,
161    # i.e., `-1 + eps/2`.
162    uniform_samples = random_ops.random_uniform(
163        shape=shape,
164        minval=np.nextafter(self.dtype.as_numpy_dtype(-1.),
165                            self.dtype.as_numpy_dtype(0.)),
166        maxval=1.,
167        dtype=self.dtype,
168        seed=seed)
169    return (self.loc - self.scale * math_ops.sign(uniform_samples) *
170            math_ops.log1p(-math_ops.abs(uniform_samples)))
171
172  def _log_prob(self, x):
173    return self._log_unnormalized_prob(x) - self._log_normalization()
174
175  def _prob(self, x):
176    return math_ops.exp(self._log_prob(x))
177
178  def _log_cdf(self, x):
179    return special_math.log_cdf_laplace(self._z(x))
180
181  def _log_survival_function(self, x):
182    return special_math.log_cdf_laplace(-self._z(x))
183
184  def _cdf(self, x):
185    z = self._z(x)
186    return (0.5 + 0.5 * math_ops.sign(z) *
187            (1. - math_ops.exp(-math_ops.abs(z))))
188
189  def _log_unnormalized_prob(self, x):
190    return -math_ops.abs(self._z(x))
191
192  def _log_normalization(self):
193    return math.log(2.) + math_ops.log(self.scale)
194
195  def _entropy(self):
196    # Use broadcasting rules to calculate the full broadcast scale.
197    scale = self.scale + array_ops.zeros_like(self.loc)
198    return math.log(2.) + 1. + math_ops.log(scale)
199
200  def _mean(self):
201    return self.loc + array_ops.zeros_like(self.scale)
202
203  def _stddev(self):
204    return math.sqrt(2.) * self.scale + array_ops.zeros_like(self.loc)
205
206  def _median(self):
207    return self._mean()
208
209  def _mode(self):
210    return self._mean()
211
212  def _z(self, x):
213    return (x - self.loc) / self.scale
214
215
216class LaplaceWithSoftplusScale(Laplace):
217  """Laplace with softplus applied to `scale`."""
218
219  @deprecation.deprecated(
220      "2019-01-01",
221      "Use `tfd.Laplace(loc, tf.nn.softplus(scale)) "
222      "instead.",
223      warn_once=True)
224  def __init__(self,
225               loc,
226               scale,
227               validate_args=False,
228               allow_nan_stats=True,
229               name="LaplaceWithSoftplusScale"):
230    parameters = dict(locals())
231    with ops.name_scope(name, values=[loc, scale]) as name:
232      super(LaplaceWithSoftplusScale, self).__init__(
233          loc=loc,
234          scale=nn.softplus(scale, name="softplus_scale"),
235          validate_args=validate_args,
236          allow_nan_stats=allow_nan_stats,
237          name=name)
238    self._parameters = parameters
239