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