cauchy.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. # Copyright (c) 2021 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 Cauchy(distribution.Distribution):
  20. r"""Cauchy distribution is also called Cauchy–Lorentz distribution. It is a continuous probability distribution named after Augustin-Louis Cauchy and Hendrik Lorentz. It has a very wide range of applications in natural sciences.
  21. The Cauchy distribution has the probability density function (PDF):
  22. .. math::
  23. { f(x; loc, scale) = \frac{1}{\pi scale \left[1 + \left(\frac{x - loc}{ scale}\right)^2\right]} = { 1 \over \pi } \left[ { scale \over (x - loc)^2 + scale^2 } \right], }
  24. Args:
  25. loc (float|Tensor): Location of the peak of the distribution. The data type is float32 or float64.
  26. scale (float|Tensor): The half-width at half-maximum (HWHM). The data type is float32 or float64. Must be positive values.
  27. name (str, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
  28. Examples:
  29. .. code-block:: python
  30. >>> import paddle
  31. >>> from paddle.distribution import Cauchy
  32. >>> # init Cauchy with float
  33. >>> rv = Cauchy(loc=0.1, scale=1.2)
  34. >>> print(rv.entropy())
  35. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  36. 2.71334577)
  37. >>> # init Cauchy with N-Dim tensor
  38. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  39. >>> print(rv.entropy())
  40. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  41. [2.53102422, 3.22417140])
  42. """
  43. def __init__(self, loc, scale, name=None):
  44. self.name = name if name is not None else 'Cauchy'
  45. if not isinstance(loc, (numbers.Real, framework.Variable)):
  46. raise TypeError(
  47. f"Expected type of loc is Real|Variable, but got {type(loc)}"
  48. )
  49. if not isinstance(scale, (numbers.Real, framework.Variable)):
  50. raise TypeError(
  51. f"Expected type of scale is Real|Variable, but got {type(scale)}"
  52. )
  53. if isinstance(loc, numbers.Real):
  54. loc = paddle.full(shape=(), fill_value=loc)
  55. if isinstance(scale, numbers.Real):
  56. scale = paddle.full(shape=(), fill_value=scale)
  57. if loc.shape != scale.shape:
  58. self.loc, self.scale = paddle.broadcast_tensors([loc, scale])
  59. else:
  60. self.loc, self.scale = loc, scale
  61. self.dtype = self.loc.dtype
  62. super().__init__(batch_shape=self.loc.shape, event_shape=())
  63. @property
  64. def mean(self):
  65. """Mean of Cauchy distribution."""
  66. raise ValueError("Cauchy distribution has no mean.")
  67. @property
  68. def variance(self):
  69. """Variance of Cauchy distribution."""
  70. raise ValueError("Cauchy distribution has no variance.")
  71. @property
  72. def stddev(self):
  73. """Standard Deviation of Cauchy distribution."""
  74. raise ValueError("Cauchy distribution has no stddev.")
  75. def sample(self, shape, name=None):
  76. """Sample from Cauchy distribution.
  77. Note:
  78. `sample` method has no grad, if you want so, please use `rsample` instead.
  79. Args:
  80. shape (Sequence[int]): Sample shape.
  81. name (str, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
  82. Returns:
  83. Tensor: Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`.
  84. Examples:
  85. .. code-block:: python
  86. >>> import paddle
  87. >>> from paddle.distribution import Cauchy
  88. >>> # init Cauchy with float
  89. >>> rv = Cauchy(loc=0.1, scale=1.2)
  90. >>> print(rv.sample([10]).shape)
  91. [10]
  92. >>> # init Cauchy with 0-Dim tensor
  93. >>> rv = Cauchy(loc=paddle.full((), 0.1), scale=paddle.full((), 1.2))
  94. >>> print(rv.sample([10]).shape)
  95. [10]
  96. >>> # init Cauchy with N-Dim tensor
  97. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  98. >>> print(rv.sample([10]).shape)
  99. [10, 2]
  100. >>> # sample 2-Dim data
  101. >>> rv = Cauchy(loc=0.1, scale=1.2)
  102. >>> print(rv.sample([10, 2]).shape)
  103. [10, 2]
  104. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  105. >>> print(rv.sample([10, 2]).shape)
  106. [10, 2, 2]
  107. """
  108. name = name if name is not None else (self.name + '_sample')
  109. with paddle.no_grad():
  110. return self.rsample(shape, name)
  111. def rsample(self, shape, name=None):
  112. """Sample from Cauchy distribution (reparameterized).
  113. Args:
  114. shape (Sequence[int]): Sample shape.
  115. name (str, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
  116. Returns:
  117. Tensor: Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`.
  118. Examples:
  119. .. code-block:: python
  120. >>> import paddle
  121. >>> from paddle.distribution import Cauchy
  122. >>> # init Cauchy with float
  123. >>> rv = Cauchy(loc=0.1, scale=1.2)
  124. >>> print(rv.rsample([10]).shape)
  125. [10]
  126. >>> # init Cauchy with 0-Dim tensor
  127. >>> rv = Cauchy(loc=paddle.full((), 0.1), scale=paddle.full((), 1.2))
  128. >>> print(rv.rsample([10]).shape)
  129. [10]
  130. >>> # init Cauchy with N-Dim tensor
  131. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  132. >>> print(rv.rsample([10]).shape)
  133. [10, 2]
  134. >>> # sample 2-Dim data
  135. >>> rv = Cauchy(loc=0.1, scale=1.2)
  136. >>> print(rv.rsample([10, 2]).shape)
  137. [10, 2]
  138. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  139. >>> print(rv.rsample([10, 2]).shape)
  140. [10, 2, 2]
  141. """
  142. name = name if name is not None else (self.name + '_rsample')
  143. if not isinstance(shape, (np.ndarray, framework.Variable, list, tuple)):
  144. raise TypeError(
  145. f"Expected type of shape is Sequence[int], but got {type(shape)}"
  146. )
  147. shape = shape if isinstance(shape, tuple) else tuple(shape)
  148. shape = self._extend_shape(shape)
  149. loc = self.loc.expand(shape)
  150. scale = self.scale.expand(shape)
  151. uniforms = paddle.rand(shape, dtype=self.dtype)
  152. return paddle.add(
  153. loc,
  154. paddle.multiply(scale, paddle.tan(np.pi * (uniforms - 0.5))),
  155. name=name,
  156. )
  157. def prob(self, value):
  158. r"""Probability density function(PDF) evaluated at value.
  159. .. math::
  160. { f(x; loc, scale) = \frac{1}{\pi scale \left[1 + \left(\frac{x - loc}{ scale}\right)^2\right]} = { 1 \over \pi } \left[ { scale \over (x - loc)^2 + scale^2 } \right], }
  161. Args:
  162. value (Tensor): Value to be evaluated.
  163. Returns:
  164. Tensor: PDF evaluated at value.
  165. Examples:
  166. .. code-block:: python
  167. >>> import paddle
  168. >>> from paddle.distribution import Cauchy
  169. >>> # init Cauchy with float
  170. >>> rv = Cauchy(loc=0.1, scale=1.2)
  171. >>> print(rv.prob(paddle.to_tensor(1.5)))
  172. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  173. 0.11234467)
  174. >>> # broadcast to value
  175. >>> rv = Cauchy(loc=0.1, scale=1.2)
  176. >>> print(rv.prob(paddle.to_tensor([1.5, 5.1])))
  177. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  178. [0.11234467, 0.01444674])
  179. >>> # init Cauchy with N-Dim tensor
  180. >>> rv = Cauchy(loc=paddle.to_tensor([0.1, 0.1]), scale=paddle.to_tensor([1.0, 2.0]))
  181. >>> print(rv.prob(paddle.to_tensor([1.5, 5.1])))
  182. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  183. [0.10753712, 0.02195240])
  184. >>> # init Cauchy with N-Dim tensor with broadcast
  185. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  186. >>> print(rv.prob(paddle.to_tensor([1.5, 5.1])))
  187. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  188. [0.10753712, 0.02195240])
  189. """
  190. name = self.name + '_prob'
  191. if not isinstance(value, (framework.Variable, paddle.pir.Value)):
  192. raise TypeError(
  193. f"Expected type of value is Variable or Value, but got {type(value)}"
  194. )
  195. return self.log_prob(value).exp(name=name)
  196. def log_prob(self, value):
  197. """Log of probability density function.
  198. Args:
  199. value (Tensor): Value to be evaluated.
  200. Returns:
  201. Tensor: Log of probability density evaluated at value.
  202. Examples:
  203. .. code-block:: python
  204. >>> import paddle
  205. >>> from paddle.distribution import Cauchy
  206. >>> # init Cauchy with float
  207. >>> rv = Cauchy(loc=0.1, scale=1.2)
  208. >>> print(rv.log_prob(paddle.to_tensor(1.5)))
  209. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  210. -2.18618369)
  211. >>> # broadcast to value
  212. >>> rv = Cauchy(loc=0.1, scale=1.2)
  213. >>> print(rv.log_prob(paddle.to_tensor([1.5, 5.1])))
  214. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  215. [-2.18618369, -4.23728657])
  216. >>> # init Cauchy with N-Dim tensor
  217. >>> rv = Cauchy(loc=paddle.to_tensor([0.1, 0.1]), scale=paddle.to_tensor([1.0, 2.0]))
  218. >>> print(rv.log_prob(paddle.to_tensor([1.5, 5.1])))
  219. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  220. [-2.22991920, -3.81887865])
  221. >>> # init Cauchy with N-Dim tensor with broadcast
  222. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  223. >>> print(rv.log_prob(paddle.to_tensor([1.5, 5.1])))
  224. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  225. [-2.22991920, -3.81887865])
  226. """
  227. name = self.name + '_log_prob'
  228. if not isinstance(value, (framework.Variable, paddle.pir.Value)):
  229. raise TypeError(
  230. f"Expected type of value is Variable or Value, but got {type(value)}"
  231. )
  232. value = self._check_values_dtype_in_probs(self.loc, value)
  233. loc, scale, value = paddle.broadcast_tensors(
  234. [self.loc, self.scale, value]
  235. )
  236. return paddle.subtract(
  237. -(
  238. paddle.square(paddle.divide(paddle.subtract(value, loc), scale))
  239. ).log1p(),
  240. paddle.add(
  241. paddle.full(loc.shape, np.log(np.pi), dtype=self.dtype),
  242. scale.log(),
  243. ),
  244. name=name,
  245. )
  246. def cdf(self, value):
  247. r"""Cumulative distribution function(CDF) evaluated at value.
  248. .. math::
  249. { \frac{1}{\pi} \arctan\left(\frac{x-loc}{ scale}\right)+\frac{1}{2}\! }
  250. Args:
  251. value (Tensor): Value to be evaluated.
  252. Returns:
  253. Tensor: CDF evaluated at value.
  254. Examples:
  255. .. code-block:: python
  256. >>> import paddle
  257. >>> from paddle.distribution import Cauchy
  258. >>> # init Cauchy with float
  259. >>> rv = Cauchy(loc=0.1, scale=1.2)
  260. >>> print(rv.cdf(paddle.to_tensor(1.5)))
  261. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  262. 0.77443725)
  263. >>> # broadcast to value
  264. >>> rv = Cauchy(loc=0.1, scale=1.2)
  265. >>> print(rv.cdf(paddle.to_tensor([1.5, 5.1])))
  266. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  267. [0.77443725, 0.92502367])
  268. >>> # init Cauchy with N-Dim tensor
  269. >>> rv = Cauchy(loc=paddle.to_tensor([0.1, 0.1]), scale=paddle.to_tensor([1.0, 2.0]))
  270. >>> print(rv.cdf(paddle.to_tensor([1.5, 5.1])))
  271. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  272. [0.80256844, 0.87888104])
  273. >>> # init Cauchy with N-Dim tensor with broadcast
  274. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  275. >>> print(rv.cdf(paddle.to_tensor([1.5, 5.1])))
  276. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  277. [0.80256844, 0.87888104])
  278. """
  279. name = self.name + '_cdf'
  280. if not isinstance(value, (framework.Variable, paddle.pir.Value)):
  281. raise TypeError(
  282. f"Expected type of value is Variable or Value, but got {type(value)}"
  283. )
  284. value = self._check_values_dtype_in_probs(self.loc, value)
  285. loc, scale, value = paddle.broadcast_tensors(
  286. [self.loc, self.scale, value]
  287. )
  288. return (
  289. paddle.atan(
  290. paddle.divide(paddle.subtract(value, loc), scale), name=name
  291. )
  292. / np.pi
  293. + 0.5
  294. )
  295. def entropy(self):
  296. r"""Entropy of Cauchy distribution.
  297. .. math::
  298. { \log(4\pi scale)\! }
  299. Returns:
  300. Tensor: Entropy of distribution.
  301. Examples:
  302. .. code-block:: python
  303. >>> import paddle
  304. >>> from paddle.distribution import Cauchy
  305. >>> # init Cauchy with float
  306. >>> rv = Cauchy(loc=0.1, scale=1.2)
  307. >>> print(rv.entropy())
  308. Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
  309. 2.71334577)
  310. >>> # init Cauchy with N-Dim tensor
  311. >>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
  312. >>> print(rv.entropy())
  313. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  314. [2.53102422, 3.22417140])
  315. """
  316. name = self.name + '_entropy'
  317. return paddle.add(
  318. paddle.full(self.loc.shape, np.log(4 * np.pi), dtype=self.dtype),
  319. self.scale.log(),
  320. name=name,
  321. )
  322. def kl_divergence(self, other):
  323. """The KL-divergence between two Cauchy distributions.
  324. Note:
  325. [1] Frédéric Chyzak, Frank Nielsen, A closed-form formula for the Kullback-Leibler divergence between Cauchy distributions, 2019
  326. Args:
  327. other (Cauchy): instance of Cauchy.
  328. Returns:
  329. Tensor: kl-divergence between two Cauchy distributions.
  330. Examples:
  331. .. code-block:: python
  332. >>> import paddle
  333. >>> from paddle.distribution import Cauchy
  334. >>> rv = Cauchy(loc=0.1, scale=1.2)
  335. >>> rv_other = Cauchy(loc=paddle.to_tensor(1.2), scale=paddle.to_tensor([2.3, 3.4]))
  336. >>> print(rv.kl_divergence(rv_other))
  337. Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
  338. [0.19819736, 0.31532931])
  339. """
  340. name = self.name + '_kl_divergence'
  341. if not isinstance(other, Cauchy):
  342. raise TypeError(
  343. f"Expected type of other is Cauchy, but got {type(other)}"
  344. )
  345. a_loc = self.loc
  346. b_loc = other.loc
  347. a_scale = self.scale
  348. b_scale = other.scale
  349. t1 = paddle.add(
  350. paddle.pow(paddle.add(a_scale, b_scale), 2),
  351. paddle.pow(paddle.subtract(a_loc, b_loc), 2),
  352. ).log()
  353. t2 = (4 * paddle.multiply(a_scale, b_scale)).log()
  354. return paddle.subtract(t1, t2, name=name)