laplace.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. # Copyright (c) 2022 PaddlePaddle 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. import numbers
  15. import numpy as np
  16. import paddle
  17. from paddle.base import framework
  18. from paddle.distribution import distribution
  19. class Laplace(distribution.Distribution):
  20. r"""
  21. Creates a Laplace distribution parameterized by :attr:`loc` and :attr:`scale`.
  22. Mathematical details
  23. The probability density function (pdf) is
  24. .. math::
  25. pdf(x; \mu, \sigma) = \frac{1}{2 * \sigma} * e^{\frac{-|x - \mu|}{\sigma}}
  26. In the above equation:
  27. * :math:`loc = \mu`: is the location parameter.
  28. * :math:`scale = \sigma`: is the scale parameter.
  29. Args:
  30. loc (scalar|Tensor): The mean of the distribution.
  31. scale (scalar|Tensor): The scale of the distribution.
  32. Examples:
  33. .. code-block:: python
  34. >>> import paddle
  35. >>> paddle.seed(2023)
  36. >>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
  37. >>> m.sample() # Laplace distributed with loc=0, scale=1
  38. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  39. 1.31554604)
  40. """
  41. def __init__(self, loc, scale):
  42. if not isinstance(
  43. loc, (numbers.Real, framework.Variable, paddle.pir.Value)
  44. ):
  45. raise TypeError(
  46. f"Expected type of loc is Real|Variable, but got {type(loc)}"
  47. )
  48. if not isinstance(
  49. scale, (numbers.Real, framework.Variable, paddle.pir.Value)
  50. ):
  51. raise TypeError(
  52. f"Expected type of scale is Real|Variable, but got {type(scale)}"
  53. )
  54. if isinstance(loc, numbers.Real):
  55. loc = paddle.full(shape=(), fill_value=loc)
  56. if isinstance(scale, numbers.Real):
  57. scale = paddle.full(shape=(), fill_value=scale)
  58. if (len(scale.shape) > 0 or len(loc.shape) > 0) and (
  59. loc.dtype == scale.dtype
  60. ):
  61. self.loc, self.scale = paddle.broadcast_tensors([loc, scale])
  62. else:
  63. self.loc, self.scale = loc, scale
  64. super().__init__(self.loc.shape)
  65. @property
  66. def mean(self):
  67. """Mean of distribution.
  68. Returns:
  69. Tensor: The mean value.
  70. """
  71. return self.loc
  72. @property
  73. def stddev(self):
  74. r"""Standard deviation.
  75. The stddev is
  76. .. math::
  77. stddev = \sqrt{2} * \sigma
  78. In the above equation:
  79. * :math:`scale = \sigma`: is the scale parameter.
  80. Returns:
  81. Tensor: The std value.
  82. """
  83. return (2**0.5) * self.scale
  84. @property
  85. def variance(self):
  86. r"""Variance of distribution.
  87. The variance is
  88. .. math::
  89. variance = 2 * \sigma^2
  90. In the above equation:
  91. * :math:`scale = \sigma`: is the scale parameter.
  92. Returns:
  93. Tensor: The variance value.
  94. """
  95. return self.stddev.pow(2)
  96. def _validate_value(self, value):
  97. """Argument dimension check for distribution methods such as `log_prob`,
  98. `cdf` and `icdf`.
  99. Args:
  100. value (Tensor|Scalar): The input value, which can be a scalar or a tensor.
  101. Returns:
  102. loc, scale, value: The broadcasted loc, scale and value, with the same dimension and data type.
  103. """
  104. if isinstance(value, numbers.Real):
  105. value = paddle.full(shape=(), fill_value=value)
  106. if value.dtype != self.scale.dtype:
  107. value = paddle.cast(value, self.scale.dtype)
  108. if (
  109. len(self.scale.shape) > 0
  110. or len(self.loc.shape) > 0
  111. or len(value.shape) > 0
  112. ):
  113. loc, scale, value = paddle.broadcast_tensors(
  114. [self.loc, self.scale, value]
  115. )
  116. else:
  117. loc, scale = self.loc, self.scale
  118. return loc, scale, value
  119. def log_prob(self, value):
  120. r"""Log probability density/mass function.
  121. The log_prob is
  122. .. math::
  123. log\_prob(value) = \frac{-log(2 * \sigma) - |value - \mu|}{\sigma}
  124. In the above equation:
  125. * :math:`loc = \mu`: is the location parameter.
  126. * :math:`scale = \sigma`: is the scale parameter.
  127. Args:
  128. value (Tensor|Scalar): The input value, can be a scalar or a tensor.
  129. Returns:
  130. Tensor: The log probability, whose data type is same with value.
  131. Examples:
  132. .. code-block:: python
  133. >>> import paddle
  134. >>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
  135. >>> value = paddle.to_tensor(0.1)
  136. >>> m.log_prob(value)
  137. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  138. -0.79314721)
  139. """
  140. loc, scale, value = self._validate_value(value)
  141. log_scale = -paddle.log(2 * scale)
  142. return log_scale - paddle.abs(value - loc) / scale
  143. def entropy(self):
  144. r"""Entropy of Laplace distribution.
  145. The entropy is:
  146. .. math::
  147. entropy() = 1 + log(2 * \sigma)
  148. In the above equation:
  149. * :math:`scale = \sigma`: is the scale parameter.
  150. Returns:
  151. The entropy of distribution.
  152. Examples:
  153. .. code-block:: python
  154. >>> import paddle
  155. >>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
  156. >>> m.entropy()
  157. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  158. 1.69314718)
  159. """
  160. return 1 + paddle.log(2 * self.scale)
  161. def cdf(self, value):
  162. r"""Cumulative distribution function.
  163. The cdf is
  164. .. math::
  165. cdf(value) = 0.5 - 0.5 * sign(value - \mu) * e^\frac{-|(\mu - \sigma)|}{\sigma}
  166. In the above equation:
  167. * :math:`loc = \mu`: is the location parameter.
  168. * :math:`scale = \sigma`: is the scale parameter.
  169. Args:
  170. value (Tensor): The value to be evaluated.
  171. Returns:
  172. Tensor: The cumulative probability of value.
  173. Examples:
  174. .. code-block:: python
  175. >>> import paddle
  176. >>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
  177. >>> value = paddle.to_tensor(0.1)
  178. >>> m.cdf(value)
  179. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  180. 0.54758132)
  181. """
  182. loc, scale, value = self._validate_value(value)
  183. iterm = (
  184. 0.5
  185. * (value - loc).sign()
  186. * paddle.expm1(-(value - loc).abs() / scale)
  187. )
  188. return 0.5 - iterm
  189. def icdf(self, value):
  190. r"""Inverse Cumulative distribution function.
  191. The icdf is
  192. .. math::
  193. cdf^{-1}(value)= \mu - \sigma * sign(value - 0.5) * ln(1 - 2 * |value-0.5|)
  194. In the above equation:
  195. * :math:`loc = \mu`: is the location parameter.
  196. * :math:`scale = \sigma`: is the scale parameter.
  197. Args:
  198. value (Tensor): The value to be evaluated.
  199. Returns:
  200. Tensor: The cumulative probability of value.
  201. Examples:
  202. .. code-block:: python
  203. >>> import paddle
  204. >>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
  205. >>> value = paddle.to_tensor(0.1)
  206. >>> m.icdf(value)
  207. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  208. -1.60943794)
  209. """
  210. loc, scale, value = self._validate_value(value)
  211. term = value - 0.5
  212. return loc - scale * (term).sign() * paddle.log1p(-2 * term.abs())
  213. def sample(self, shape=()):
  214. r"""Generate samples of the specified shape.
  215. Args:
  216. shape(tuple[int]): The shape of generated samples.
  217. Returns:
  218. Tensor: A sample tensor that fits the Laplace distribution.
  219. Examples:
  220. .. code-block:: python
  221. >>> import paddle
  222. >>> paddle.seed(2023)
  223. >>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
  224. >>> m.sample() # Laplace distributed with loc=0, scale=1
  225. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  226. 1.31554604)
  227. """
  228. shape = shape if isinstance(shape, tuple) else tuple(shape)
  229. with paddle.no_grad():
  230. return self.rsample(shape)
  231. def rsample(self, shape):
  232. r"""Reparameterized sample.
  233. Args:
  234. shape(tuple[int]): The shape of generated samples.
  235. Returns:
  236. Tensor: A sample tensor that fits the Laplace distribution.
  237. Examples:
  238. .. code-block:: python
  239. >>> import paddle
  240. >>> paddle.seed(2023)
  241. >>> m = paddle.distribution.Laplace(paddle.to_tensor([0.0]), paddle.to_tensor([1.0]))
  242. >>> m.rsample((1,)) # Laplace distributed with loc=0, scale=1
  243. Tensor(shape=[1, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
  244. [[1.31554604]])
  245. """
  246. eps = self._get_eps()
  247. shape = self._extend_shape(shape)
  248. uniform = paddle.uniform(
  249. shape=shape,
  250. min=float(np.nextafter(-1, 1)) + eps / 2,
  251. max=1.0 - eps / 2,
  252. dtype=self.loc.dtype,
  253. )
  254. return self.loc - self.scale * uniform.sign() * paddle.log1p(
  255. -uniform.abs()
  256. )
  257. def _get_eps(self):
  258. """
  259. Get the eps of certain data type.
  260. Note:
  261. Since paddle.finfo is temporarily unavailable, we
  262. use hard-coding style to get eps value.
  263. Returns:
  264. Float: An eps value by different data types.
  265. """
  266. eps = 1.19209e-07
  267. if (
  268. self.loc.dtype == paddle.float64
  269. or self.loc.dtype == paddle.complex128
  270. ):
  271. eps = 2.22045e-16
  272. return eps
  273. def kl_divergence(self, other):
  274. r"""Calculate the KL divergence KL(self || other) with two Laplace instances.
  275. The kl_divergence between two Laplace distribution is
  276. .. math::
  277. KL\_divergence(\mu_0, \sigma_0; \mu_1, \sigma_1) = 0.5 (ratio^2 + (\frac{diff}{\sigma_1})^2 - 1 - 2 \ln {ratio})
  278. .. math::
  279. ratio = \frac{\sigma_0}{\sigma_1}
  280. .. math::
  281. diff = \mu_1 - \mu_0
  282. In the above equation:
  283. * :math:`loc = \mu`: is the location parameter of self.
  284. * :math:`scale = \sigma`: is the scale parameter of self.
  285. * :math:`loc = \mu_1`: is the location parameter of the reference Laplace distribution.
  286. * :math:`scale = \sigma_1`: is the scale parameter of the reference Laplace distribution.
  287. * :math:`ratio`: is the ratio between the two distribution.
  288. * :math:`diff`: is the difference between the two distribution.
  289. Args:
  290. other (Laplace): An instance of Laplace.
  291. Returns:
  292. Tensor: The kl-divergence between two laplace distributions.
  293. Examples:
  294. .. code-block:: python
  295. >>> import paddle
  296. >>> m1 = paddle.distribution.Laplace(paddle.to_tensor([0.0]), paddle.to_tensor([1.0]))
  297. >>> m2 = paddle.distribution.Laplace(paddle.to_tensor([1.0]), paddle.to_tensor([0.5]))
  298. >>> m1.kl_divergence(m2)
  299. Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
  300. [1.04261160])
  301. """
  302. var_ratio = other.scale / self.scale
  303. t = paddle.abs(self.loc - other.loc)
  304. term1 = (self.scale * paddle.exp(-t / self.scale) + t) / other.scale
  305. term2 = paddle.log(var_ratio)
  306. return term1 + term2 - 1