chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit e25996e7db
15472 changed files with 3536181 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import constraint as constraint, transform
from .bernoulli import Bernoulli
from .beta import Beta
from .binomial import Binomial
from .categorical import Categorical
from .cauchy import Cauchy
from .chi2 import Chi2
from .continuous_bernoulli import ContinuousBernoulli
from .dirichlet import Dirichlet
from .distribution import Distribution
from .exponential import Exponential
from .exponential_family import ExponentialFamily
from .gamma import Gamma
from .geometric import Geometric
from .gumbel import Gumbel
from .independent import Independent
from .kl import kl_divergence, register_kl
from .laplace import Laplace
from .lkj_cholesky import LKJCholesky
from .lognormal import LogNormal
from .multinomial import Multinomial
from .multivariate_normal import MultivariateNormal
from .normal import Normal
from .poisson import Poisson
from .student_t import StudentT
from .transform import ( # noqa:F401
AbsTransform,
AffineTransform,
ChainTransform,
ExpTransform,
IndependentTransform,
PowerTransform,
ReshapeTransform,
SigmoidTransform,
SoftmaxTransform,
StackTransform,
StickBreakingTransform,
TanhTransform,
Transform,
)
from .transformed_distribution import TransformedDistribution
from .uniform import Uniform
constraints = constraint
__all__ = [
'Bernoulli',
'Beta',
'Categorical',
'Cauchy',
'Chi2',
'ContinuousBernoulli',
'Dirichlet',
'Distribution',
'Exponential',
'ExponentialFamily',
'Multinomial',
'MultivariateNormal',
'Normal',
'Uniform',
'kl_divergence',
'register_kl',
'Independent',
'TransformedDistribution',
'Laplace',
'LogNormal',
'LKJCholesky',
'Gamma',
'Gumbel',
'Geometric',
'Binomial',
'Poisson',
'StudentT',
]
__all__.extend(transform.__all__)
+506
View File
@@ -0,0 +1,506 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
import paddle
from paddle.base.data_feeder import check_type, convert_dtype
from paddle.base.framework import Variable
from paddle.distribution import exponential_family
from paddle.framework import in_dynamic_mode
from paddle.nn.functional import (
binary_cross_entropy_with_logits,
sigmoid,
softplus,
)
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
from paddle._typing.dtype_like import _DTypeLiteral
# Smallest representable number
EPS = {
'float32': paddle.finfo(paddle.float32).eps,
'float64': paddle.finfo(paddle.float64).eps,
}
def _clip_probs(probs, dtype):
"""Clip probs from [0, 1] to (0, 1) with ``eps``.
Args:
probs (Tensor): probs of Bernoulli.
dtype (str): data type.
Returns:
Tensor: Clipped probs.
"""
eps = EPS.get(dtype)
return paddle.clip(probs, min=eps, max=1 - eps).astype(dtype)
class Bernoulli(exponential_family.ExponentialFamily):
r"""Bernoulli distribution parameterized by ``probs``, which is the probability of value 1.
In probability theory and statistics, the Bernoulli distribution, named after Swiss
mathematician Jacob Bernoulli, is the discrete probability distribution of a random
variable which takes the value 1 with probability ``p`` and the value 0 with
probability ``q=1-p``.
The probability mass function of this distribution, over possible outcomes ``k``, is
.. math::
{\begin{cases}
q=1-p & \text{if }value=0 \\
p & \text{if }value=1
\end{cases}}
Args:
probs (float|Tensor): The ``probs`` input of Bernoulli distribution. The data type is float32 or float64. The range must be in [0, 1].
name (str, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Bernoulli
>>> # init `probs` with a float
>>> rv = Bernoulli(probs=0.3)
>>> print(rv.mean)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.30000001)
>>> print(rv.variance)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.21000001)
>>> print(rv.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.61086434)
"""
name: str
probs: Tensor
logits: Tensor
dtype: _DTypeLiteral
def __init__(self, probs: float | Tensor, name: str | None = None) -> None:
self.name = name or 'Bernoulli'
if not in_dynamic_mode():
check_type(
probs,
'probs',
(float, Variable, paddle.pir.Value),
self.name,
)
# Get/convert probs to tensor.
if self._validate_args(probs):
self.probs = probs
self.dtype = convert_dtype(probs.dtype)
else:
[self.probs] = self._to_tensor(probs)
self.dtype = paddle.get_default_dtype()
# Clip probs from [0, 1] to (0, 1) with smallest representable number `eps`.
self.probs = _clip_probs(self.probs, self.dtype)
self.logits = self._probs_to_logits(self.probs, is_binary=True)
super().__init__(batch_shape=self.probs.shape, event_shape=())
@property
def mean(self) -> Tensor:
"""Mean of Bernoulli distribution.
Returns:
Tensor: Mean value of distribution.
"""
return self.probs
@property
def variance(self) -> Tensor:
"""Variance of Bernoulli distribution.
Returns:
Tensor: Variance value of distribution.
"""
return paddle.multiply(self.probs, (1 - self.probs))
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Sample from Bernoulli distribution.
Args:
shape (Sequence[int], optional): Sample shape.
Returns:
Tensor: Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Bernoulli
>>> rv = Bernoulli(paddle.full([1], 0.3))
>>> print(rv.sample([100]).shape)
paddle.Size([100, 1])
>>> rv = Bernoulli(paddle.to_tensor(0.3))
>>> print(rv.sample([100]).shape)
paddle.Size([100])
>>> rv = Bernoulli(paddle.to_tensor([0.3, 0.5]))
>>> print(rv.sample([100]).shape)
paddle.Size([100, 2])
>>> rv = Bernoulli(paddle.to_tensor([0.3, 0.5]))
>>> print(rv.sample([100, 2]).shape)
paddle.Size([100, 2, 2])
"""
name = self.name + '_sample'
if not in_dynamic_mode():
check_type(
shape,
'shape',
(np.ndarray, Variable, list, tuple, paddle.pir.Value),
name,
)
shape = shape if isinstance(shape, tuple) else tuple(shape)
shape = self._extend_shape(shape)
with paddle.no_grad():
return paddle.bernoulli(self.probs.expand(shape), name=name)
@param_one_alias(["shape", "sample_shape"])
def rsample(
self, shape: Sequence[int] = [], temperature: float = 1.0
) -> Tensor:
"""Sample from Bernoulli distribution (reparameterized).
The `rsample` is a continuously approximate of Bernoulli distribution reparameterized sample method.
[1] Chris J. Maddison, Andriy Mnih, and Yee Whye Teh. The Concrete Distribution: A Continuous Relaxation of Discrete Random Variables. 2016.
[2] Eric Jang, Shixiang Gu, and Ben Poole. Categorical Reparameterization with Gumbel-Softmax. 2016.
Note:
`rsample` need to be followed by a `sigmoid`, which converts samples' value to unit interval (0, 1).
Args:
shape (Sequence[int], optional): Sample shape.
temperature (float): temperature for rsample, must be positive.
Returns:
Tensor: Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.seed(1)
>>> from paddle.distribution import Bernoulli
>>> rv = Bernoulli(paddle.full([1], 0.3))
>>> print(rv.sample([100]).shape)
paddle.Size([100, 1])
>>> rv = Bernoulli(0.3)
>>> print(rv.rsample([100]).shape)
paddle.Size([100])
>>> rv = Bernoulli(paddle.to_tensor([0.3, 0.5]))
>>> print(rv.rsample([100]).shape)
paddle.Size([100, 2])
>>> rv = Bernoulli(paddle.to_tensor([0.3, 0.5]))
>>> print(rv.rsample([100, 2]).shape)
paddle.Size([100, 2, 2])
>>> # `rsample` has to be followed by a `sigmoid`
>>> rv = Bernoulli(0.3)
>>> rsample = rv.rsample([3])
>>> rsample_sigmoid = paddle.nn.functional.sigmoid(rsample)
>>> print(rsample)
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[-1.46112013, -0.01239836, -1.32765460])
>>> print(rsample_sigmoid)
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.18829606, 0.49690047, 0.20954758])
>>> # The smaller the `temperature`, the distribution of `rsample` closer to `sample`, with `probs` of 0.3.
>>> print(
... paddle.nn.functional.sigmoid(
... rv.rsample(
... [1000],
... temperature=1.0,
... )
... ).sum()
... )
>>> # doctest: +SKIP('output will be different')
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
365.63122559)
>>> # doctest: -SKIP
>>> print(
... paddle.nn.functional.sigmoid(
... rv.rsample(
... [1000],
... temperature=0.1,
... )
... ).sum()
... )
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
320.15057373)
"""
name = self.name + '_rsample'
if not in_dynamic_mode():
check_type(
shape,
'shape',
(np.ndarray, Variable, paddle.pir.Value, list, tuple),
name,
)
check_type(
temperature,
'temperature',
(float,),
name,
)
shape = shape if isinstance(shape, tuple) else tuple(shape)
shape = self._extend_shape(shape)
temperature = paddle.full(
shape=(), fill_value=temperature, dtype=self.dtype
)
probs = self.probs.expand(shape)
uniforms = paddle.rand(shape, dtype=self.dtype)
return paddle.divide(
paddle.add(
paddle.subtract(uniforms.log(), (-uniforms).log1p()),
paddle.subtract(probs.log(), (-probs).log1p()),
),
temperature,
)
def cdf(self, value: Tensor) -> Tensor:
r"""Cumulative distribution function(CDF) evaluated at value.
.. math::
{ \begin{cases}
0 & \text{if } value \lt 0 \\
1 - p & \text{if } 0 \leq value \lt 1 \\
1 & \text{if } value \geq 1
\end{cases}
}
Args:
value (Tensor): Value to be evaluated.
Returns:
Tensor: CDF evaluated at value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Bernoulli
>>> rv = Bernoulli(0.3)
>>> print(rv.cdf(paddle.to_tensor([1.0])))
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.])
"""
name = self.name + '_cdf'
if not in_dynamic_mode():
check_type(value, 'value', (Variable, paddle.pir.Value), name)
value = self._check_values_dtype_in_probs(self.probs, value)
probs, value = paddle.broadcast_tensors([self.probs, value])
zeros = paddle.zeros_like(probs)
ones = paddle.ones_like(probs)
return paddle.where(
value < 0,
zeros,
paddle.where(value < 1, paddle.subtract(ones, probs), ones),
name=name,
)
def log_prob(self, value: Tensor) -> Tensor:
"""Log of probability density function.
Args:
value (Tensor): Value to be evaluated.
Returns:
Tensor: Log of probability density evaluated at value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Bernoulli
>>> rv = Bernoulli(0.3)
>>> print(rv.log_prob(paddle.to_tensor([1.0])))
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[-1.20397282])
"""
name = self.name + '_log_prob'
if not in_dynamic_mode():
check_type(value, 'value', (Variable, paddle.pir.Value), name)
value = self._check_values_dtype_in_probs(self.probs, value)
logits, value = paddle.broadcast_tensors([self.logits, value])
return -binary_cross_entropy_with_logits(
logits, value, reduction='none', name=name
)
def prob(self, value: Tensor) -> Tensor:
r"""Probability density function(PDF) evaluated at value.
.. math::
{ \begin{cases}
q=1-p & \text{if }value=0 \\
p & \text{if }value=1
\end{cases}
}
Args:
value (Tensor): Value to be evaluated.
Returns:
Tensor: PDF evaluated at value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Bernoulli
>>> rv = Bernoulli(0.3)
>>> print(rv.prob(paddle.to_tensor([1.0])))
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.29999998])
"""
name = self.name + '_prob'
if not in_dynamic_mode():
check_type(value, 'value', (Variable, paddle.pir.Value), name)
return self.log_prob(value).exp(name=name)
def entropy(self) -> Tensor:
r"""Entropy of Bernoulli distribution.
.. math::
{
entropy = -(q \log q + p \log p)
}
Returns:
Tensor: Entropy of distribution.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Bernoulli
>>> rv = Bernoulli(0.3)
>>> print(rv.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.61086434)
"""
name = self.name + '_entropy'
return binary_cross_entropy_with_logits(
self.logits, self.probs, reduction='none', name=name
)
def kl_divergence(self, other: Bernoulli) -> Tensor:
r"""The KL-divergence between two Bernoulli distributions.
.. math::
{
KL(a || b) = p_a \log(p_a / p_b) + (1 - p_a) \log((1 - p_a) / (1 - p_b))
}
Args:
other (Bernoulli): instance of Bernoulli.
Returns:
Tensor: kl-divergence between two Bernoulli distributions.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Bernoulli
>>> rv = Bernoulli(0.3)
>>> rv_other = Bernoulli(0.7)
>>> print(rv.kl_divergence(rv_other))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.33891910)
"""
name = self.name + '_kl_divergence'
if not in_dynamic_mode():
check_type(other, 'other', Bernoulli, name)
a_logits = self.logits
b_logits = other.logits
log_pa = -softplus(-a_logits)
log_pb = -softplus(-b_logits)
pa = sigmoid(a_logits)
one_minus_pa = sigmoid(-a_logits)
log_one_minus_pa = -softplus(a_logits)
log_one_minus_pb = -softplus(b_logits)
return paddle.add(
paddle.subtract(
paddle.multiply(log_pa, pa), paddle.multiply(log_pb, pa)
),
paddle.subtract(
paddle.multiply(log_one_minus_pa, one_minus_pa),
paddle.multiply(log_one_minus_pb, one_minus_pa),
),
)
+175
View File
@@ -0,0 +1,175 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import numbers
from typing import TYPE_CHECKING
import paddle
from paddle.distribution import dirichlet, exponential_family
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
class Beta(exponential_family.ExponentialFamily):
r"""
Beta distribution parameterized by alpha and beta.
In probability theory and statistics, the beta distribution is a family of
continuous probability distributions defined on the interval [0, 1]
parameterized by two positive shape parameters, denoted by alpha and beta,
that appear as exponents of the random variable and control the shape of
the distribution. The generalization to multiple variables is called a
Dirichlet distribution.
The probability density function (pdf) is
.. math::
f(x; \alpha, \beta) = \frac{1}{B(\alpha, \beta)}x^{\alpha-1}(1-x)^{\beta-1}
where the normalization, B, is the beta function,
.. math::
B(\alpha, \beta) = \int_{0}^{1} t^{\alpha - 1} (1-t)^{\beta - 1}\mathrm{d}t
Args:
alpha (float|Tensor): Alpha parameter. It supports broadcast semantics.
The value of alpha must be positive. When the parameter is a tensor,
it represents multiple independent distribution with
a batch_shape(refer to ``Distribution`` ).
beta (float|Tensor): Beta parameter. It supports broadcast semantics.
The value of beta must be positive(>0). When the parameter is tensor,
it represent multiple independent distribution with
a batch_shape(refer to ``Distribution`` ).
Examples:
.. code-block:: pycon
>>> import paddle
>>> # scale input
>>> beta = paddle.distribution.Beta(alpha=0.5, beta=0.5)
>>> print(beta.mean)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.50000000)
>>> print(beta.variance)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.12500000)
>>> print(beta.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
-0.24156499)
>>> # tensor input with broadcast
>>> beta = paddle.distribution.Beta(alpha=paddle.to_tensor([0.2, 0.4]), beta=0.6)
>>> print(beta.mean)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.25000000, 0.40000001])
>>> print(beta.variance)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.10416666, 0.12000000])
>>> print(beta.entropy())
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[-1.91923141, -0.38095081])
"""
alpha: Tensor
beta: Tensor
def __init__(self, alpha: float | Tensor, beta: float | Tensor) -> None:
if isinstance(alpha, numbers.Real):
alpha = paddle.full(shape=[], fill_value=alpha)
if isinstance(beta, numbers.Real):
beta = paddle.full(shape=[], fill_value=beta)
self.alpha, self.beta = paddle.broadcast_tensors([alpha, beta])
self._dirichlet = dirichlet.Dirichlet(
paddle.stack([self.alpha, self.beta], -1)
)
super().__init__(self._dirichlet._batch_shape)
@property
def mean(self) -> Tensor:
"""Mean of beta distribution."""
return self.alpha / (self.alpha + self.beta)
@property
def variance(self) -> Tensor:
"""Variance of beat distribution"""
sum = self.alpha + self.beta
return self.alpha * self.beta / (sum.pow(2) * (sum + 1))
def prob(self, value: Tensor) -> Tensor:
"""Probability density function evaluated at value
Args:
value (Tensor): Value to be evaluated.
Returns:
Tensor: Probability.
"""
return paddle.exp(self.log_prob(value))
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density function evaluated at value
Args:
value (Tensor): Value to be evaluated
Returns:
Tensor: Log probability.
"""
return self._dirichlet.log_prob(paddle.stack([value, 1.0 - value], -1))
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Sample from beta distribution with sample shape.
Args:
shape (Sequence[int], optional): Sample shape.
Returns:
Tensor, Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`.
"""
shape = shape if isinstance(shape, tuple) else tuple(shape)
return paddle.squeeze(self._dirichlet.sample(shape)[..., 0], axis=-1)
def entropy(self) -> Tensor:
"""Entropy of dirichlet distribution
Returns:
Tensor: Entropy.
"""
return self._dirichlet.entropy()
@property
def _natural_parameters(self) -> tuple[Tensor, Tensor]:
return (self.alpha, self.beta)
def _log_normalizer(self, x: Tensor, y: Tensor) -> Tensor:
return paddle.lgamma(x) + paddle.lgamma(y) - paddle.lgamma(x + y)
+260
View File
@@ -0,0 +1,260 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
import paddle
from paddle.distribution import distribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing.dtype_like import _DTypeLiteral
class Binomial(distribution.Distribution):
r"""
The Binomial distribution with size `total_count` and `probs` parameters.
In probability theory and statistics, the binomial distribution is the most basic discrete probability distribution defined on :math:`[0, n] \cap \mathbb{N}`,
which can be viewed as the number of times a potentially unfair coin is tossed to get heads, and the result
of its random variable can be viewed as the sum of a series of independent Bernoulli experiments.
The probability mass function (pmf) is
.. math::
pmf(x; n, p) = \frac{n!}{x!(n-x)!}p^{x}(1-p)^{n-x}
In the above equation:
* :math:`total\_count = n`: is the size, meaning the total number of Bernoulli experiments.
* :math:`probs = p`: is the probability of the event happening in one Bernoulli experiments.
Args:
total_count(int|Tensor): The size of Binomial distribution which should be greater than 0, meaning the number of independent bernoulli
trials with probability parameter :math:`p`. The data type will be converted to 1-D Tensor with paddle global default dtype if the input
:attr:`probs` is not Tensor, otherwise will be converted to the same as :attr:`probs`.
probs(float|Tensor): The probability of Binomial distribution which should reside in [0, 1], meaning the probability of success
for each individual bernoulli trial. If the input data type is float, it will be converted to a 1-D Tensor with paddle global default dtype.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Binomial
>>> paddle.set_device('cpu')
>>> paddle.seed(100)
>>> rv = Binomial(100, paddle.to_tensor([0.3, 0.6, 0.9]))
>>> print(rv.sample([2]))
Tensor(shape=[2, 3], dtype=float32, place=Place(cpu), stop_gradient=True,
[[31., 62., 93.],
[29., 54., 91.]])
>>> print(rv.mean)
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[30.00000191, 60.00000381, 90. ])
>>> print(rv.entropy())
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[2.94053698, 3.00781751, 2.51124287])
"""
dtype: _DTypeLiteral
total_count: Tensor
probs: Tensor
def __init__(
self, total_count: int | Tensor, probs: float | Tensor
) -> None:
self.dtype = paddle.get_default_dtype()
self.total_count, self.probs = self._to_tensor(total_count, probs)
batch_shape = self.total_count.shape
super().__init__(batch_shape)
def _to_tensor(
self, total_count: int | Tensor, probs: float | Tensor
) -> list[Tensor]:
"""Convert the input parameters into Tensors if they were not and broadcast them
Returns:
list[Tensor]: converted total_count and probs.
"""
# convert type
if isinstance(probs, float):
probs = paddle.to_tensor(probs, dtype=self.dtype)
else:
self.dtype = probs.dtype
if isinstance(total_count, int):
total_count = paddle.to_tensor(total_count, dtype=self.dtype)
else:
total_count = paddle.cast(total_count, dtype=self.dtype)
# broadcast tensor
return paddle.broadcast_tensors([total_count, probs])
@property
def mean(self) -> Tensor:
"""Mean of binomial distribution.
Returns:
Tensor: mean value.
"""
return self.total_count * self.probs
@property
def variance(self) -> Tensor:
"""Variance of binomial distribution.
Returns:
Tensor: variance value.
"""
return self.total_count * self.probs * (1 - self.probs)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate binomial samples of the specified shape. The final shape would be ``shape+batch_shape`` .
Args:
shape (Sequence[int], optional): Prepended shape of the generated samples.
Returns:
Tensor: Sampled data with shape `sample_shape` + `batch_shape`. The returned data type is the same as `probs`.
"""
if not isinstance(shape, Sequence):
raise TypeError('sample shape must be Sequence object.')
with paddle.set_grad_enabled(False):
shape = tuple(shape)
batch_shape = tuple(self.batch_shape)
output_shape = tuple(shape + batch_shape)
output_size = paddle.broadcast_to(
self.total_count, shape=output_shape
)
output_prob = paddle.broadcast_to(self.probs, shape=output_shape)
sample = paddle.binomial(
paddle.cast(output_size, dtype="int32"), output_prob
)
return paddle.cast(sample, self.dtype)
def entropy(self) -> Tensor:
r"""Shannon entropy in nats.
The entropy is
.. math::
\mathcal{H}(X) = - \sum_{x \in \Omega} p(x) \log{p(x)}
In the above equation:
* :math:`\Omega`: is the support of the distribution.
Returns:
Tensor: Shannon entropy of binomial distribution. The data type is the same as `probs`.
"""
values = self._enumerate_support()
log_prob = self.log_prob(values)
return -(paddle.exp(log_prob) * log_prob).sum(0)
def _enumerate_support(self) -> Tensor:
"""Return the support of binomial distribution [0, 1, ... ,n]
Returns:
Tensor: the support of binomial distribution
"""
values = paddle.arange(
1 + paddle.max(self.total_count), dtype=self.dtype
)
values = values.reshape((-1,) + (1,) * len(self.batch_shape))
return values
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: log probability. The data type is the same as `probs`.
"""
value = paddle.cast(value, dtype=self.dtype)
# combination
log_comb = (
paddle.lgamma(self.total_count + 1.0)
- paddle.lgamma(self.total_count - value + 1.0)
- paddle.lgamma(value + 1.0)
)
eps = paddle.finfo(self.probs.dtype).eps
probs = paddle.clip(self.probs, min=eps, max=1 - eps)
# log_p
return paddle.nan_to_num(
(
log_comb
+ value * paddle.log(probs)
+ (self.total_count - value) * paddle.log(1 - probs)
),
neginf=-eps,
)
def prob(self, value: Tensor) -> Tensor:
"""Probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: probability. The data type is the same as `probs`.
"""
return paddle.exp(self.log_prob(value))
def kl_divergence(self, other: Binomial) -> Tensor:
r"""The KL-divergence between two binomial distributions with the same :attr:`total_count`.
The probability density function (pdf) is
.. math::
KL\_divergence(n_1, p_1, n_2, p_2) = \sum_x p_1(x) \log{\frac{p_1(x)}{p_2(x)}}
.. math::
p_1(x) = \frac{n_1!}{x!(n_1-x)!}p_1^{x}(1-p_1)^{n_1-x}
.. math::
p_2(x) = \frac{n_2!}{x!(n_2-x)!}p_2^{x}(1-p_2)^{n_2-x}
Args:
other (Binomial): instance of ``Binomial``.
Returns:
Tensor: kl-divergence between two binomial distributions. The data type is the same as `probs`.
"""
support = self._enumerate_support()
log_prob_1 = self.log_prob(support)
log_prob_2 = other.log_prob(support)
return (
paddle.multiply(
paddle.exp(log_prob_1),
(paddle.subtract(log_prob_1, log_prob_2)),
)
).sum(0)
+385
View File
@@ -0,0 +1,385 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
import paddle
from paddle.base.data_feeder import check_type, convert_dtype
from paddle.base.framework import Variable
from paddle.distribution import distribution
from paddle.framework import in_dynamic_mode
from paddle.tensor import multinomial
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import TypeAlias
import numpy.typing as npt
from paddle import Tensor
from paddle._typing import NestedSequence
from paddle._typing.dtype_like import _DTypeLiteral
_CategoricalBoundary: TypeAlias = (
Sequence[float]
| NestedSequence[float]
| npt.NDArray[np.float32 | np.float64]
| Tensor
)
class Categorical(distribution.Distribution):
r"""
Categorical distribution is a discrete probability distribution that
describes the possible results of a random variable that can take on
one of K possible categories, with the probability of each category
separately specified.
The probability mass function (pmf) is:
.. math::
pmf(k; p_i) = \prod_{i=1}^{k} p_i^{[x=i]}
In the above equation:
* :math:`[x=i]` : it evaluates to 1 if :math:`x==i` , 0 otherwise.
Args:
logits(list|tuple|numpy.ndarray|Tensor): The logits input of categorical distribution. The data type is float32 or float64.
name(str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Categorical
>>> paddle.seed(100) # on CPU device
>>> x = paddle.rand([6])
>>> print(x)
Tensor(shape=[6], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.55355281, 0.20714243, 0.01162981, 0.51577556, 0.36369765, 0.26091650])
>>> paddle.seed(200) # on CPU device
>>> y = paddle.rand([6])
>>> print(y)
Tensor(shape=[6], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.77663314, 0.90824795, 0.15685187, 0.04279523, 0.34468332, 0.79557180])
>>> cat = Categorical(x)
>>> cat2 = Categorical(y)
>>> paddle.seed(1000) # on CPU device
>>> print(cat.sample([2, 3]))
Tensor(shape=[2, 3], dtype=int64, place=Place(cpu), stop_gradient=True,
[[0, 1, 5],
[3, 4, 5]])
>>> print(cat.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.77528250)
>>> print(cat.kl_divergence(cat2))
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.07195196])
>>> value = paddle.to_tensor([2, 1, 3])
>>> print(cat.probs(value))
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.00608027, 0.10829761, 0.26965630])
>>> print(cat.log_prob(value))
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[-5.10270691, -2.22287226, -1.31060708])
"""
logits: Tensor
dtype: _DTypeLiteral
def __init__(
self,
logits: _CategoricalBoundary,
name: str | None = None,
) -> None:
"""
Args:
logits(list|tuple|numpy.ndarray|Tensor): The logits input of categorical distribution. The data type is float32 or float64.
name(str, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
"""
if not in_dynamic_mode():
check_type(
logits,
'logits',
(np.ndarray, Variable, paddle.pir.Value, list, tuple),
'Categorical',
)
self.name = name if name is not None else 'Categorical'
self.dtype = 'float32'
if self._validate_args(logits):
self.logits = logits
self.dtype = convert_dtype(logits.dtype)
else:
if isinstance(logits, np.ndarray) and str(logits.dtype) in [
'float32',
'float64',
]:
self.dtype = convert_dtype(logits.dtype)
self.logits = self._to_tensor(logits)[0]
if self.dtype != convert_dtype(self.logits.dtype):
self.logits = paddle.cast(self.logits, dtype=self.dtype)
dist_sum = paddle.sum(self.logits, axis=-1, keepdim=True)
self._prob = self.logits / dist_sum
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate samples of the specified shape.
Args:
shape (Sequence[int], optional): Shape of the generated samples.
Returns:
Tensor: A tensor with prepended dimensions shape.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Categorical
>>> paddle.seed(100) # on CPU device
>>> x = paddle.rand([6])
>>> print(x)
Tensor(shape=[6], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.55355281, 0.20714243, 0.01162981, 0.51577556, 0.36369765, 0.26091650])
>>> # doctest: +SKIP('Random output')
>>> cat = Categorical(x)
>>> paddle.seed(1000) # on CPU device
>>> print(cat.sample([2, 3]))
Tensor(shape=[2, 3], dtype=int64, place=Place(cpu), stop_gradient=True,
[[0, 1, 5],
[3, 4, 5]])
"""
name = self.name + '_sample'
if not in_dynamic_mode():
check_type(shape, 'shape', (list, tuple), 'sample')
num_samples = np.prod(np.array(shape))
logits_shape = list(self.logits.shape)
if len(logits_shape) > 1:
sample_shape = shape + logits_shape[:-1]
logits = paddle.reshape(
self.logits, [np.prod(logits_shape[:-1]), logits_shape[-1]]
)
else:
sample_shape = shape
logits = self.logits
sample_index = multinomial(
self._logits_to_probs(logits), num_samples, True
)
# multinomial sample shape is (logits.shape[:-1], num_samples), need to
# transpose to (num_samples, logits.shape[:-1])
permute = list(range(sample_index.dim()))
permute.insert(0, permute.pop(-1))
sample_index = sample_index.transpose(permute)
return paddle.reshape(sample_index, sample_shape, name=name)
def kl_divergence(self, other: Categorical) -> Tensor:
"""The KL-divergence between two Categorical distributions.
Args:
other (Categorical): instance of Categorical. The data type is float32.
Returns:
Tensor: kl-divergence between two Categorical distributions.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Categorical
>>> paddle.seed(100) # on CPU device
>>> x = paddle.rand([6])
>>> print(x)
Tensor(shape=[6], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.55355281, 0.20714243, 0.01162981, 0.51577556, 0.36369765, 0.26091650])
>>> paddle.seed(200) # on CPU device
>>> y = paddle.rand([6])
>>> print(y)
Tensor(shape=[6], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.77663314, 0.90824795, 0.15685187, 0.04279523, 0.34468332, 0.79557180])
>>> cat = Categorical(x)
>>> cat2 = Categorical(y)
>>> print(cat.kl_divergence(cat2))
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.07195196])
"""
name = self.name + '_kl_divergence'
if not in_dynamic_mode():
check_type(other, 'other', Categorical, 'kl_divergence')
logits = self.logits - paddle.max(self.logits, axis=-1, keepdim=True)
other_logits = other.logits - paddle.max(
other.logits, axis=-1, keepdim=True
)
e_logits = paddle.exp(logits)
other_e_logits = paddle.exp(other_logits)
z = paddle.sum(e_logits, axis=-1, keepdim=True)
other_z = paddle.sum(other_e_logits, axis=-1, keepdim=True)
prob = e_logits / z
kl = paddle.sum(
prob
* (logits - paddle.log(z) - other_logits + paddle.log(other_z)),
axis=-1,
keepdim=True,
name=name,
)
return kl
def entropy(self) -> Tensor:
"""Shannon entropy in nats.
Returns:
Tensor: Shannon entropy of Categorical distribution. The data type is float32.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Categorical
>>> paddle.seed(100) # on CPU device
>>> x = paddle.rand([6])
>>> print(x)
Tensor(shape=[6], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.55355281, 0.20714243, 0.01162981, 0.51577556, 0.36369765, 0.26091650])
>>> cat = Categorical(x)
>>> print(cat.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.77528250)
"""
name = self.name + '_entropy'
logits = self.logits - paddle.max(self.logits, axis=-1, keepdim=True)
e_logits = paddle.exp(logits)
z = paddle.sum(e_logits, axis=-1, keepdim=True)
prob = e_logits / z
neg_entropy = paddle.sum(prob * (logits - paddle.log(z)), axis=-1)
entropy = paddle.scale(neg_entropy, scale=-1.0, name=name)
return entropy
def probs(self, value: Tensor) -> Tensor:
"""Probabilities of the given category (``value``).
If ``logits`` is 2-D or higher dimension, the last dimension will be regarded as
category, and the others represents the different distributions.
At the same time, if ``value`` is 1-D Tensor, ``value`` will be broadcast to the
same number of distributions as ``logits``.
If ``value`` is not 1-D Tensor, ``value`` should have the same number distributions
with ``logits. That is, ``value[:-1] = logits[:-1]``.
Args:
value (Tensor): The input tensor represents the selected category index.
Returns:
Tensor: probability according to the category index.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Categorical
>>> paddle.seed(100) # on CPU device
>>> x = paddle.rand([6])
>>> print(x)
Tensor(shape=[6], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.55355281, 0.20714243, 0.01162981, 0.51577556, 0.36369765, 0.26091650])
>>> cat = Categorical(x)
>>> value = paddle.to_tensor([2, 1, 3])
>>> print(cat.probs(value))
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.00608027, 0.10829761, 0.26965630])
"""
name = self.name + '_probs'
if len(self._prob.shape) == 1: # batch_shape is empty
return paddle.gather(
self._prob, value.reshape([-1], name=name), name=name
).reshape(value.shape, name=name)
else:
if len(value.shape) == 1:
return paddle.take_along_axis(
self._prob,
paddle.reshape(
value,
(len(self._prob.shape) - 1) * [1] + [-1],
name=name,
),
axis=-1,
)
else:
return paddle.take_along_axis(self._prob, value, axis=-1)
def log_prob(self, value: Tensor) -> Tensor:
"""Log probabilities of the given category. Refer to ``probs`` method.
Args:
value (Tensor): The input tensor represents the selected category index.
Returns:
Tensor: Log probability.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Categorical
>>> paddle.seed(100) # on CPU device
>>> x = paddle.rand([6])
>>> print(x)
Tensor(shape=[6], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.55355281, 0.20714243, 0.01162981, 0.51577556, 0.36369765, 0.26091650])
>>> cat = Categorical(x)
>>> value = paddle.to_tensor([2, 1, 3])
>>> print(cat.log_prob(value))
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[-5.10270691, -2.22287226, -1.31060708])
"""
name = self.name + '_log_prob'
return paddle.log(self.probs(value), name=name)
+515
View File
@@ -0,0 +1,515 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import numbers
from typing import TYPE_CHECKING
import numpy as np
import paddle
from paddle.base import framework
from paddle.distribution import distribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from typing_extensions import Never
from paddle import Tensor, dtype
class Cauchy(distribution.Distribution):
r"""Cauchy distribution is also called CauchyLorentz 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.
The Cauchy distribution has the probability density function (PDF):
.. math::
{ 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], }
Args:
loc (float|Tensor): Location of the peak of the distribution. The data type is float32 or float64.
scale (float|Tensor): The half-width at half-maximum (HWHM). The data type is float32 or float64. Must be positive values.
name (str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Cauchy
>>> # init Cauchy with float
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
2.71334577)
>>> # init Cauchy with N-Dim tensor
>>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
>>> print(rv.entropy())
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[2.53102422, 3.22417140])
"""
loc: Tensor
scale: Tensor
dtype: dtype
name: str
def __init__(
self,
loc: float | Tensor,
scale: float | Tensor,
name: str | None = None,
) -> None:
self.name = name if name is not None else 'Cauchy'
if not isinstance(
loc, (numbers.Real, framework.Variable, paddle.pir.Value)
):
raise TypeError(
f"Expected type of loc is Real|Variable|Value, but got {type(loc)}"
)
if not isinstance(
scale, (numbers.Real, framework.Variable, paddle.pir.Value)
):
raise TypeError(
f"Expected type of scale is Real|Variable|Value, but got {type(scale)}"
)
if isinstance(loc, numbers.Real):
loc = paddle.full(shape=(), fill_value=loc)
if isinstance(scale, numbers.Real):
scale = paddle.full(shape=(), fill_value=scale)
if loc.shape != scale.shape:
self.loc, self.scale = paddle.broadcast_tensors([loc, scale])
else:
self.loc, self.scale = loc, scale
self.dtype = self.loc.dtype
super().__init__(batch_shape=self.loc.shape, event_shape=())
@property
def mean(self) -> Never:
"""Mean of Cauchy distribution."""
raise ValueError("Cauchy distribution has no mean.")
@property
def variance(self) -> Never:
"""Variance of Cauchy distribution."""
raise ValueError("Cauchy distribution has no variance.")
@property
def stddev(self) -> Never:
"""Standard Deviation of Cauchy distribution."""
raise ValueError("Cauchy distribution has no stddev.")
@param_one_alias(["shape", "sample_shape"])
def sample(
self, shape: Sequence[int] = [], name: str | None = None
) -> Tensor:
"""Sample from Cauchy distribution.
Note:
`sample` method has no grad, if you want so, please use `rsample` instead.
Args:
shape (Sequence[int], optional): Sample shape.
name (str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
Returns:
Tensor: Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Cauchy
>>> # init Cauchy with float
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.sample([10]).shape)
paddle.Size([10])
>>> # init Cauchy with 0-Dim tensor
>>> rv = Cauchy(loc=paddle.full((), 0.1), scale=paddle.full((), 1.2))
>>> print(rv.sample([10]).shape)
paddle.Size([10])
>>> # init Cauchy with N-Dim tensor
>>> rv = Cauchy(
... loc=paddle.to_tensor(0.1),
... scale=paddle.to_tensor([1.0, 2.0]),
... )
>>> print(rv.sample([10]).shape)
paddle.Size([10, 2])
>>> # sample 2-Dim data
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.sample([10, 2]).shape)
paddle.Size([10, 2])
>>> rv = Cauchy(
... loc=paddle.to_tensor(0.1),
... scale=paddle.to_tensor([1.0, 2.0]),
... )
>>> print(rv.sample([10, 2]).shape)
paddle.Size([10, 2, 2])
"""
name = name if name is not None else (self.name + '_sample')
with paddle.no_grad():
return self.rsample(shape, name)
@param_one_alias(["shape", "sample_shape"])
def rsample(
self, shape: Sequence[int] = [], name: str | None = None
) -> Tensor:
"""Sample from Cauchy distribution (reparameterized).
Args:
shape (Sequence[int], optional): Sample shape.
name (str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
Returns:
Tensor: Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Cauchy
>>> # init Cauchy with float
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.rsample([10]).shape)
paddle.Size([10])
>>> # init Cauchy with 0-Dim tensor
>>> rv = Cauchy(loc=paddle.full((), 0.1), scale=paddle.full((), 1.2))
>>> print(rv.rsample([10]).shape)
paddle.Size([10])
>>> # init Cauchy with N-Dim tensor
>>> rv = Cauchy(
... loc=paddle.to_tensor(0.1),
... scale=paddle.to_tensor([1.0, 2.0]),
... )
>>> print(rv.rsample([10]).shape)
paddle.Size([10, 2])
>>> # sample 2-Dim data
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.rsample([10, 2]).shape)
paddle.Size([10, 2])
>>> rv = Cauchy(
... loc=paddle.to_tensor(0.1),
... scale=paddle.to_tensor([1.0, 2.0]),
... )
>>> print(rv.rsample([10, 2]).shape)
paddle.Size([10, 2, 2])
"""
name = name if name is not None else (self.name + '_rsample')
if not isinstance(
shape,
(np.ndarray, framework.Variable, paddle.pir.Value, list, tuple),
):
raise TypeError(
f"Expected type of shape is Sequence[int], but got {type(shape)}"
)
shape = shape if isinstance(shape, tuple) else tuple(shape)
shape = self._extend_shape(shape)
loc = self.loc.expand(shape)
scale = self.scale.expand(shape)
uniforms = paddle.rand(shape, dtype=self.dtype)
return paddle.add(
loc,
paddle.multiply(scale, paddle.tan(np.pi * (uniforms - 0.5))),
name=name,
)
def prob(self, value: Tensor) -> Tensor:
r"""Probability density function(PDF) evaluated at value.
.. math::
{ 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], }
Args:
value (Tensor): Value to be evaluated.
Returns:
Tensor: PDF evaluated at value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Cauchy
>>> # init Cauchy with float
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.prob(paddle.to_tensor(1.5)))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.11234467)
>>> # broadcast to value
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.prob(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.11234467, 0.01444674])
>>> # init Cauchy with N-Dim tensor
>>> rv = Cauchy(loc=paddle.to_tensor([0.1, 0.1]), scale=paddle.to_tensor([1.0, 2.0]))
>>> print(rv.prob(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.10753712, 0.02195240])
>>> # init Cauchy with N-Dim tensor with broadcast
>>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
>>> print(rv.prob(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.10753712, 0.02195240])
"""
name = self.name + '_prob'
if not isinstance(value, (framework.Variable, paddle.pir.Value)):
raise TypeError(
f"Expected type of value is Variable or Value, but got {type(value)}"
)
return self.log_prob(value).exp(name=name)
def log_prob(self, value: Tensor) -> Tensor:
"""Log of probability density function.
Args:
value (Tensor): Value to be evaluated.
Returns:
Tensor: Log of probability density evaluated at value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Cauchy
>>> # init Cauchy with float
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.log_prob(paddle.to_tensor(1.5)))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
-2.18618369)
>>> # broadcast to value
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.log_prob(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[-2.18618369, -4.23728657])
>>> # init Cauchy with N-Dim tensor
>>> rv = Cauchy(loc=paddle.to_tensor([0.1, 0.1]), scale=paddle.to_tensor([1.0, 2.0]))
>>> print(rv.log_prob(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[-2.22991920, -3.81887865])
>>> # init Cauchy with N-Dim tensor with broadcast
>>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
>>> print(rv.log_prob(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[-2.22991920, -3.81887865])
"""
name = self.name + '_log_prob'
if not isinstance(value, (framework.Variable, paddle.pir.Value)):
raise TypeError(
f"Expected type of value is Variable or Value, but got {type(value)}"
)
value = self._check_values_dtype_in_probs(self.loc, value)
loc, scale, value = paddle.broadcast_tensors(
[self.loc, self.scale, value]
)
return paddle.subtract(
-(
paddle.square(paddle.divide(paddle.subtract(value, loc), scale))
).log1p(),
paddle.add(
paddle.full(loc.shape, np.log(np.pi), dtype=self.dtype),
scale.log(),
),
name=name,
)
def cdf(self, value: Tensor) -> Tensor:
r"""Cumulative distribution function(CDF) evaluated at value.
.. math::
{ \frac{1}{\pi} \arctan\left(\frac{x-loc}{ scale}\right)+\frac{1}{2}\! }
Args:
value (Tensor): Value to be evaluated.
Returns:
Tensor: CDF evaluated at value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Cauchy
>>> # init Cauchy with float
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.cdf(paddle.to_tensor(1.5)))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.77443725)
>>> # broadcast to value
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.cdf(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.77443725, 0.92502367])
>>> # init Cauchy with N-Dim tensor
>>> rv = Cauchy(loc=paddle.to_tensor([0.1, 0.1]), scale=paddle.to_tensor([1.0, 2.0]))
>>> print(rv.cdf(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.80256844, 0.87888104])
>>> # init Cauchy with N-Dim tensor with broadcast
>>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
>>> print(rv.cdf(paddle.to_tensor([1.5, 5.1])))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.80256844, 0.87888104])
"""
name = self.name + '_cdf'
if not isinstance(value, (framework.Variable, paddle.pir.Value)):
raise TypeError(
f"Expected type of value is Variable or Value, but got {type(value)}"
)
value = self._check_values_dtype_in_probs(self.loc, value)
loc, scale, value = paddle.broadcast_tensors(
[self.loc, self.scale, value]
)
return (
paddle.atan(
paddle.divide(paddle.subtract(value, loc), scale), name=name
)
/ np.pi
+ 0.5
)
def entropy(self) -> Tensor:
r"""Entropy of Cauchy distribution.
.. math::
{ \log(4\pi scale)\! }
Returns:
Tensor: Entropy of distribution.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Cauchy
>>> # init Cauchy with float
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> print(rv.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
2.71334577)
>>> # init Cauchy with N-Dim tensor
>>> rv = Cauchy(loc=paddle.to_tensor(0.1), scale=paddle.to_tensor([1.0, 2.0]))
>>> print(rv.entropy())
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[2.53102422, 3.22417140])
"""
name = self.name + '_entropy'
return paddle.add(
paddle.full(self.loc.shape, np.log(4 * np.pi), dtype=self.dtype),
self.scale.log(),
name=name,
)
def kl_divergence(self, other: Cauchy) -> Tensor:
"""The KL-divergence between two Cauchy distributions.
Note:
[1] Frédéric Chyzak, Frank Nielsen, A closed-form formula for the Kullback-Leibler divergence between Cauchy distributions, 2019
Args:
other (Cauchy): instance of Cauchy.
Returns:
Tensor: kl-divergence between two Cauchy distributions.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Cauchy
>>> rv = Cauchy(loc=0.1, scale=1.2)
>>> rv_other = Cauchy(loc=paddle.to_tensor(1.2), scale=paddle.to_tensor([2.3, 3.4]))
>>> print(rv.kl_divergence(rv_other))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.19819736, 0.31532931])
"""
name = self.name + '_kl_divergence'
if not isinstance(other, Cauchy):
raise TypeError(
f"Expected type of other is Cauchy, but got {type(other)}"
)
a_loc = self.loc
b_loc = other.loc
a_scale = self.scale
b_scale = other.scale
t1 = paddle.add(
paddle.pow(paddle.add(a_scale, b_scale), 2),
paddle.pow(paddle.subtract(a_loc, b_loc), 2),
).log()
t2 = (4 * paddle.multiply(a_scale, b_scale)).log()
return paddle.subtract(t1, t2, name=name)
+77
View File
@@ -0,0 +1,77 @@
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import paddle
from paddle.base.data_feeder import check_type, convert_dtype
from paddle.base.framework import Variable
from paddle.distribution.gamma import Gamma
from paddle.framework import in_dynamic_mode
if TYPE_CHECKING:
from paddle import Tensor, dtype
__all__ = ["Chi2"]
class Chi2(Gamma):
r"""
Creates a Chi-squared distribution parameterized by shape parameter.
This is exactly equivalent to Gamma(concentration=0.5*df, rate=0.5), :ref:`api_paddle_distribution_Gamma`.
Args:
df (float or Tensor): The degree of freedom of the distribution, which should be non-negative. If the input data type is Tensor, it indicates the batch creation of distributions with multiple different parameters, and the `batch_shape` (refer to the :ref:`api_paddle_distribution_Distribution` base class) is the parameter.
Example:
.. code-block:: pycon
>>> import paddle
>>> m = paddle.distribution.Chi2(paddle.to_tensor([1.0]))
>>> sample = m.sample()
>>> sample.shape
paddle.Size([1])
"""
df: Tensor
rate: Tensor
dtype: dtype
def __init__(self, df: float | Tensor) -> None:
if not in_dynamic_mode():
check_type(
df,
'df',
(float, Variable, paddle.pir.Value),
'Chi2',
)
# Get/convert concentration to tensor.
if self._validate_args(df):
self.df = df
self.dtype = convert_dtype(df.dtype)
else:
[self.df] = self._to_tensor(df)
self.dtype = paddle.get_default_dtype()
self.rate = paddle.full_like(self.df, 0.5)
if in_dynamic_mode():
if not paddle.all(self.df > 0):
raise ValueError("The arg of `df` must be positive.")
super().__init__(self.df * 0.5, self.rate)
+152
View File
@@ -0,0 +1,152 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import paddle
if TYPE_CHECKING:
from paddle import Tensor
class Constraint:
"""Constraint condition for random variable."""
def __call__(self, value: Tensor) -> Tensor:
raise NotImplementedError
def check(self, value: Tensor) -> Tensor:
return self(value)
class Real(Constraint):
def __call__(self, value: Tensor) -> Tensor:
return value == value
class RealVector(Constraint):
event_dim = 1
def __call__(self, value: Tensor) -> Tensor:
if value.dim() < 1:
return paddle.zeros(value.shape[:-1], dtype='bool')
return (value == value).reshape((*value.shape[:-1], -1)).all(-1)
class Range(Constraint):
def __init__(self, lower: float | Tensor, upper: float | Tensor) -> None:
self._lower = lower
self._upper = upper
super().__init__()
def __call__(self, value: Tensor) -> Tensor:
return self._lower <= value <= self._upper
class IntegerInterval(Constraint):
event_dim = 0
is_discrete = True
def __init__(self, lower: int, upper: int) -> None:
self._lower = lower
self._upper = upper
super().__init__()
def __call__(self, value: Tensor) -> Tensor:
return (
(value >= self._lower) & (value <= self._upper) & (value % 1 == 0)
)
class Positive(Constraint):
def __call__(self, value: Tensor) -> Tensor:
return value >= 0.0
class LowerTriangular(Constraint):
event_dim = 2
def __call__(self, value: Tensor) -> Tensor:
if value.dim() < 2:
return paddle.zeros(value.shape[:-2], dtype='bool')
value_tril = paddle.tril(value)
return (value_tril == value).reshape((*value.shape[:-2], -1)).all(-1)
class LowerCholesky(Constraint):
event_dim = 2
def __call__(self, value: Tensor) -> Tensor:
if value.dim() < 2:
return paddle.zeros(value.shape[:-2], dtype='bool')
value_tril = paddle.tril(value)
lower_triangular = (
(value_tril == value).reshape((*value.shape[:-2], -1)).all(-1)
)
positive_diagonal = (value.diagonal(axis1=-2, axis2=-1) > 0).all(-1)
return lower_triangular & positive_diagonal
class Square(Constraint):
event_dim = 2
def __call__(self, value: Tensor) -> Tensor:
if value.dim() < 2:
return paddle.full_like(value.sum(), False, dtype='bool')
batch_value = value.reshape((*value.shape[:-2], -1)).sum(-1)
return paddle.full_like(
batch_value, value.shape[-2] == value.shape[-1], dtype='bool'
)
class Symmetric(Square):
def __call__(self, value: Tensor) -> Tensor:
square_check = super().__call__(value)
if value.dim() < 2:
return square_check
if value.shape[-2] != value.shape[-1]:
return square_check
return square_check & paddle.isclose(value, value.mT, atol=1e-6).all(
-2
).all(-1)
class PositiveDefinite(Symmetric):
def __call__(self, value: Tensor) -> Tensor:
if value.dim() < 2:
return paddle.zeros(value.shape[:-2], dtype='bool')
sym_check = super().__call__(value)
if value.shape[-2] != value.shape[-1]:
return sym_check
return sym_check & (paddle.linalg.eigvalsh(value) > 0).all(-1)
class Simplex(Constraint):
def __call__(self, value: Tensor) -> Tensor:
return paddle.all(value >= 0, axis=-1) and (
(value.sum(-1) - 1).abs() < 1e-6
)
real = Real()
real_vector = RealVector()
integer_interval = IntegerInterval
positive = Positive()
lower_triangular = LowerTriangular()
lower_cholesky = LowerCholesky()
square = Square()
symmetric = Symmetric()
positive_definite = PositiveDefinite()
simplex = Simplex()
@@ -0,0 +1,439 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
import paddle
from paddle.distribution import distribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from paddle import Tensor, dtype
class ContinuousBernoulli(distribution.Distribution):
r"""The Continuous Bernoulli distribution with parameter: `probs` characterizing the shape of the density function.
The Continuous Bernoulli distribution is defined on [0, 1], and it can be viewed as a continuous version of the Bernoulli distribution.
`The continuous Bernoulli: fixing a pervasive error in variational autoencoders. <https://arxiv.org/abs/1907.06845>`_
Mathematical details
The probability density function (pdf) is
.. math::
p(x;\lambda) = C(\lambda)\lambda^x (1-\lambda)^{1-x}
In the above equation:
* :math:`x`: is continuous between 0 and 1
* :math:`probs = \lambda`: is the probability.
* :math:`C(\lambda)`: is the normalizing constant factor
.. math::
C(\lambda) =
\left\{
\begin{aligned}
&2 & \text{ if $\lambda = \frac{1}{2}$} \\
&\frac{2\tanh^{-1}(1-2\lambda)}{1 - 2\lambda} & \text{ otherwise}
\end{aligned}
\right.
Args:
probs(int|float|Tensor): The probability of Continuous Bernoulli distribution between [0, 1],
which characterize the shape of the pdf. If the input data type is int or float, the data type of
`probs` will be convert to a 1-D Tensor the paddle global default dtype.
lims(tuple): Specify the unstable calculation region near 0.5, where the calculation is approximated
by talyor expansion. The default value is (0.499, 0.501).
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import ContinuousBernoulli
>>> paddle.set_device("cpu")
>>> paddle.seed(100)
>>> rv = ContinuousBernoulli(paddle.to_tensor([0.2, 0.5]))
>>> print(rv.sample([2]))
Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0.38694882, 0.20714243],
[0.00631948, 0.51577556]])
>>> print(rv.mean)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.38801414, 0.50000000])
>>> print(rv.variance)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.07589778, 0.08333334])
>>> print(rv.entropy())
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[-0.07641457, 0. ])
>>> print(rv.cdf(paddle.to_tensor(0.1)))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.17259926, 0.10000000])
>>> print(rv.icdf(paddle.to_tensor(0.1)))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.05623737, 0.10000000])
>>> rv1 = ContinuousBernoulli(paddle.to_tensor([0.2, 0.8]))
>>> rv2 = ContinuousBernoulli(paddle.to_tensor([0.7, 0.5]))
>>> print(rv1.kl_divergence(rv2))
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.20103608, 0.07641447])
"""
probs: Tensor
lims: Tensor
dtype: dtype
def __init__(
self, probs: float | Tensor, lims: tuple[float] = (0.499, 0.501)
) -> None:
self.dtype = paddle.get_default_dtype()
self.probs = self._to_tensor(probs)
self.lims = paddle.to_tensor(lims, dtype=self.dtype)
# eps_prob is used to clip the input `probs` in the range of [eps_prob, 1-eps_prob]
eps_prob = paddle.finfo(self.probs.dtype).eps
self.probs = paddle.clip(self.probs, min=eps_prob, max=1 - eps_prob)
batch_shape = self.probs.shape
super().__init__(batch_shape)
def _to_tensor(self, probs: float | Tensor) -> Tensor:
"""Convert the input parameters into tensors
Returns:
Tensor: converted probability.
"""
# convert type
if isinstance(probs, (float, int)):
probs = paddle.to_tensor([probs], dtype=self.dtype)
else:
self.dtype = probs.dtype
return probs
def _cut_support_region(self) -> Tensor:
"""Generate stable support region indicator (prob < self.lims[0] && prob >= self.lims[1] )
Returns:
Tensor: the element of the returned indicator tensor corresponding to stable region is True, and False otherwise
"""
return paddle.logical_or(
paddle.less_equal(self.probs, self.lims[0]),
paddle.greater_than(self.probs, self.lims[1]),
)
def _cut_probs(self) -> Tensor:
"""Cut the probability parameter with stable support region
Returns:
Tensor: the element of the returned probability tensor corresponding to unstable region is set to be self.lims[0], and unchanged otherwise
"""
return paddle.where(
self._cut_support_region(),
self.probs,
self.lims[0] * paddle.ones_like(self.probs),
)
def _tanh_inverse(self, value: Tensor) -> Tensor:
"""Calculate the tanh inverse of value
Args:
value (Tensor)
Returns:
Tensor: tanh inverse of value
"""
return 0.5 * (paddle.log1p(value) - paddle.log1p(-value))
def _log_constant(self) -> Tensor:
"""Calculate the logarithm of the constant factor :math:`C(lambda)` in the pdf of the Continuous Bernoulli distribution
Returns:
Tensor: logarithm of the constant factor
"""
cut_probs = self._cut_probs()
half = paddle.to_tensor(0.5, dtype=self.dtype)
cut_probs_below_half = paddle.where(
paddle.less_equal(cut_probs, half),
cut_probs,
paddle.zeros_like(cut_probs),
)
cut_probs_above_half = paddle.where(
paddle.greater_equal(cut_probs, half),
cut_probs,
paddle.ones_like(cut_probs),
)
log_constant_propose = paddle.log(
2.0 * paddle.abs(self._tanh_inverse(1.0 - 2.0 * cut_probs))
) - paddle.where(
paddle.less_equal(cut_probs, half),
paddle.log1p(-2.0 * cut_probs_below_half),
paddle.log(2.0 * cut_probs_above_half - 1.0),
)
x = paddle.square(self.probs - 0.5)
taylor_expansion = (
paddle.log(paddle.to_tensor(2.0, dtype=self.dtype))
+ (4.0 / 3.0 + 104.0 / 45.0 * x) * x
)
return paddle.where(
self._cut_support_region(), log_constant_propose, taylor_expansion
)
@property
def mean(self) -> Tensor:
"""Mean of Continuous Bernoulli distribution.
Returns:
Tensor: mean value.
"""
cut_probs = self._cut_probs()
tmp = paddle.divide(cut_probs, 2.0 * cut_probs - 1.0)
propose = tmp + paddle.divide(
paddle.to_tensor(1.0, dtype=self.dtype),
2.0 * self._tanh_inverse(1.0 - 2.0 * cut_probs),
)
x = self.probs - 0.5
taylor_expansion = (
0.5 + (1.0 / 3.0 + 16.0 / 45.0 * paddle.square(x)) * x
)
return paddle.where(
self._cut_support_region(), propose, taylor_expansion
)
@property
def variance(self) -> Tensor:
"""Variance of Continuous Bernoulli distribution.
Returns:
Tensor: variance value.
"""
cut_probs = self._cut_probs()
tmp = paddle.divide(
cut_probs * (cut_probs - 1.0),
paddle.square(1.0 - 2.0 * cut_probs),
)
propose = tmp + paddle.divide(
paddle.to_tensor(1.0, dtype=self.dtype),
paddle.square(paddle.log1p(-cut_probs) - paddle.log(cut_probs)),
)
x = paddle.square(self.probs - 0.5)
taylor_expansion = 1.0 / 12.0 - (1.0 / 15.0 - 128.0 / 945.0 * x) * x
return paddle.where(
self._cut_support_region(), propose, taylor_expansion
)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate Continuous Bernoulli samples of the specified shape. The final shape would be ``sample_shape + batch_shape``.
Args:
shape (Sequence[int], optional): Prepended shape of the generated samples.
Returns:
Tensor, Sampled data with shape `sample_shape` + `batch_shape`.
"""
with paddle.no_grad():
return self.rsample(shape)
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate Continuous Bernoulli samples of the specified shape. The final shape would be ``sample_shape + batch_shape``.
Args:
shape (Sequence[int], optional): Prepended shape of the generated samples.
Returns:
Tensor, Sampled data with shape `sample_shape` + `batch_shape`.
"""
if not isinstance(shape, Sequence):
raise TypeError('sample shape must be Sequence object.')
shape = tuple(shape)
batch_shape = tuple(self.batch_shape)
output_shape = tuple(shape + batch_shape)
u = paddle.uniform(shape=output_shape, dtype=self.dtype, min=0, max=1)
return self.icdf(u)
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: log probability. The data type is the same as `self.probs`.
"""
value = paddle.cast(value, dtype=self.dtype)
eps = paddle.finfo(self.probs.dtype).eps
cross_entropy = paddle.nan_to_num(
value * paddle.log(self.probs)
+ (1.0 - value) * paddle.log(1 - self.probs),
neginf=-eps,
)
return self._log_constant() + cross_entropy
def prob(self, value: Tensor) -> Tensor:
"""Probability density function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: probability. The data type is the same as `self.probs`.
"""
return paddle.exp(self.log_prob(value))
def entropy(self) -> Tensor:
r"""Shannon entropy in nats.
The entropy is
.. math::
\mathcal{H}(X) = -\log C + \left[ \log (1 - \lambda) -\log \lambda \right] \mathbb{E}(X) - \log(1 - \lambda)
In the above equation:
* :math:`\Omega`: is the support of the distribution.
Returns:
Tensor, Shannon entropy of Continuous Bernoulli distribution.
"""
log_p = paddle.log(self.probs)
log_1_minus_p = paddle.log1p(-self.probs)
return paddle.where(
paddle.equal(self.probs, paddle.to_tensor(0.5, dtype=self.dtype)),
paddle.full_like(self.probs, 0.0),
(
-self._log_constant()
+ self.mean * (log_1_minus_p - log_p)
- log_1_minus_p
),
)
def cdf(self, value: Tensor) -> Tensor:
r"""Cumulative distribution function
.. math::
{ P(X \le t; \lambda) =
F(t;\lambda) =
\left\{
\begin{aligned}
&t & \text{ if $\lambda = \frac{1}{2}$} \\
&\frac{\lambda^t (1 - \lambda)^{1 - t} + \lambda - 1}{2\lambda - 1} & \text{ otherwise}
\end{aligned}
\right. }
Args:
value (Tensor): The input tensor.
Returns:
Tensor: quantile of :attr:`value`. The data type is the same as `self.probs`.
"""
value = paddle.cast(value, dtype=self.dtype)
cut_probs = self._cut_probs()
cdfs = (
paddle.pow(cut_probs, value)
* paddle.pow(1.0 - cut_probs, 1.0 - value)
+ cut_probs
- 1.0
) / (2.0 * cut_probs - 1.0)
unbounded_cdfs = paddle.where(self._cut_support_region(), cdfs, value)
return paddle.where(
paddle.less_equal(value, paddle.to_tensor(0.0, dtype=self.dtype)),
paddle.zeros_like(value),
paddle.where(
paddle.greater_equal(
value, paddle.to_tensor(1.0, dtype=self.dtype)
),
paddle.ones_like(value),
unbounded_cdfs,
),
)
def icdf(self, value: Tensor) -> Tensor:
r"""Inverse cumulative distribution function
.. math::
{ F^{-1}(x;\lambda) =
\left\{
\begin{aligned}
&x & \text{ if $\lambda = \frac{1}{2}$} \\
&\frac{\log(1+(\frac{2\lambda - 1}{1 - \lambda})x)}{\log(\frac{\lambda}{1-\lambda})} & \text{ otherwise}
\end{aligned}
\right. }
Args:
value (Tensor): The input tensor, meaning the quantile.
Returns:
Tensor: the value of the r.v. corresponding to the quantile. The data type is the same as `self.probs`.
"""
value = paddle.cast(value, dtype=self.dtype)
cut_probs = self._cut_probs()
return paddle.where(
self._cut_support_region(),
(
paddle.log1p(-cut_probs + value * (2.0 * cut_probs - 1.0))
- paddle.log1p(-cut_probs)
)
/ (paddle.log(cut_probs) - paddle.log1p(-cut_probs)),
value,
)
def kl_divergence(self, other: ContinuousBernoulli) -> Tensor:
r"""The KL-divergence between two Continuous Bernoulli distributions with the same `batch_shape`.
The probability density function (pdf) is
.. math::
KL\_divergence(\lambda_1, \lambda_2) = - H - \{\log C_2 + [\log \lambda_2 - \log (1-\lambda_2)] \mathbb{E}_1(X) + \log (1-\lambda_2) \}
Args:
other (ContinuousBernoulli): instance of Continuous Bernoulli.
Returns:
Tensor, kl-divergence between two Continuous Bernoulli distributions.
"""
if self.batch_shape != other.batch_shape:
raise ValueError(
"KL divergence of two Continuous Bernoulli distributions should share the same `batch_shape`."
)
part1 = -self.entropy()
log_q = paddle.log(other.probs)
log_1_minus_q = paddle.log1p(-other.probs)
part2 = -(
other._log_constant()
+ self.mean * (log_q - log_1_minus_q)
+ log_1_minus_q
)
return part1 + part2
+194
View File
@@ -0,0 +1,194 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
from typing import TYPE_CHECKING
import paddle
from paddle.base.data_feeder import check_variable_and_dtype
from paddle.base.layer_helper import LayerHelper
from paddle.distribution import exponential_family
from paddle.framework import in_dynamic_or_pir_mode
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
class Dirichlet(exponential_family.ExponentialFamily):
r"""
Dirichlet distribution with parameter "concentration".
The Dirichlet distribution is defined over the `(k-1)-simplex` using a
positive, length-k vector concentration(`k > 1`).
The Dirichlet is identically the Beta distribution when `k = 2`.
For independent and identically distributed continuous random variable
:math:`\boldsymbol X \in R_k` , and support
:math:`\boldsymbol X \in (0,1), ||\boldsymbol X|| = 1` ,
The probability density function (pdf) is
.. math::
f(\boldsymbol X; \boldsymbol \alpha) = \frac{1}{B(\boldsymbol \alpha)} \prod_{i=1}^{k}x_i^{\alpha_i-1}
where :math:`\boldsymbol \alpha = {\alpha_1,...,\alpha_k}, k \ge 2` is
parameter, the normalizing constant is the multivariate beta function.
.. math::
B(\boldsymbol \alpha) = \frac{\prod_{i=1}^{k} \Gamma(\alpha_i)}{\Gamma(\alpha_0)}
:math:`\alpha_0=\sum_{i=1}^{k} \alpha_i` is the sum of parameters,
:math:`\Gamma(\alpha)` is gamma function.
Args:
concentration (Tensor): "Concentration" parameter of dirichlet
distribution, also called :math:`\alpha`. When it's over one
dimension, the last axis denotes the parameter of distribution,
``event_shape=concentration.shape[-1:]`` , axes other than last are
consider batch dimensions with ``batch_shape=concentration.shape[:-1]`` .
Examples:
.. code-block:: pycon
>>> import paddle
>>> dirichlet = paddle.distribution.Dirichlet(paddle.to_tensor([1.0, 2.0, 3.0]))
>>> print(dirichlet.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
-1.24434423)
>>> print(dirichlet.prob(paddle.to_tensor([0.3, 0.5, 0.6])))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
10.80000019)
"""
concentration: Tensor
def __init__(self, concentration: Tensor) -> None:
if concentration.dim() < 1 or math.prod(concentration.shape) == 0:
# 0-dim tensor or 0-sized tensor is invalid
raise ValueError(
"`concentration` parameter must be at least one dimensional"
)
self.concentration = concentration
super().__init__(concentration.shape[:-1], concentration.shape[-1:])
@property
def mean(self) -> Tensor:
"""Mean of Dirichlet distribution.
Returns:
Mean value of distribution.
"""
return self.concentration / self.concentration.sum(-1, keepdim=True)
@property
def variance(self) -> Tensor:
"""Variance of Dirichlet distribution.
Returns:
Variance value of distribution.
"""
concentration0 = self.concentration.sum(-1, keepdim=True)
return (self.concentration * (concentration0 - self.concentration)) / (
concentration0.pow(2) * (concentration0 + 1)
)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Sample from dirichlet distribution.
Args:
shape (Sequence[int], optional): Sample shape. Defaults to empty list.
"""
shape = shape if isinstance(shape, tuple) else tuple(shape)
return _dirichlet(self.concentration.expand(self._extend_shape(shape)))
def prob(self, value: Tensor) -> Tensor:
"""Probability density function(PDF) evaluated at value.
Args:
value (Tensor): Value to be evaluated.
Returns:
PDF evaluated at value.
"""
return paddle.exp(self.log_prob(value))
def log_prob(self, value: Tensor) -> Tensor:
"""Log of probability density function.
Args:
value (Tensor): Value to be evaluated.
"""
return (
(paddle.log(value) * (self.concentration - 1.0)).sum(-1)
+ paddle.lgamma(self.concentration.sum(-1))
- paddle.lgamma(self.concentration).sum(-1)
)
def entropy(self) -> Tensor:
"""Entropy of Dirichlet distribution.
Returns:
Entropy of distribution.
"""
concentration0 = self.concentration.sum(-1)
k = self.concentration.shape[-1]
return (
paddle.lgamma(self.concentration).sum(-1)
- paddle.lgamma(concentration0)
- (k - concentration0) * paddle.digamma(concentration0)
- (
(self.concentration - 1.0) * paddle.digamma(self.concentration)
).sum(-1)
)
@property
def _natural_parameters(self) -> tuple[Tensor]:
return (self.concentration,)
def _log_normalizer(self, x: Tensor) -> Tensor:
return x.lgamma().sum(-1) - paddle.lgamma(x.sum(-1))
def _dirichlet(concentration: Tensor, name: str | None = None) -> Tensor:
if in_dynamic_or_pir_mode():
return paddle._C_ops.dirichlet(concentration)
else:
op_type = 'dirichlet'
check_variable_and_dtype(
concentration,
'concentration',
['float16', 'float32', 'float64', 'uint16'],
op_type,
)
helper = LayerHelper(op_type, **locals())
out = helper.create_variable_for_type_inference(
dtype=concentration.dtype
)
helper.append_op(
type=op_type,
inputs={"Alpha": concentration},
outputs={'Out': out},
attrs={},
)
return out
+448
View File
@@ -0,0 +1,448 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import warnings
from typing import TYPE_CHECKING
import numpy as np
import paddle
from paddle import _C_ops
from paddle.base.data_feeder import check_variable_and_dtype, convert_dtype
from paddle.base.framework import Variable
from paddle.framework import (
in_dynamic_or_pir_mode,
in_pir_mode,
)
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import TypeGuard
from paddle import Tensor
from paddle._typing import NestedNumericSequence, TensorLike
from paddle.distribution.constraint import Constraint
class Distribution:
"""
The abstract base class for probability distributions. Functions are
implemented in specific distributions.
Args:
batch_shape(Sequence[int], optional): independent, not identically
distributed draws, aka a "collection" or "bunch" of distributions.
event_shape(Sequence[int], optional): the shape of a single
draw from the distribution; it may be dependent across dimensions.
For scalar distributions, the event shape is []. For n-dimension
multivariate distribution, the event shape is [n].
"""
has_rsample = False
has_enumerate_support = False
_default_validate_args = __debug__
@staticmethod
def set_default_validate_args(value: bool) -> None:
"""Sets whether argument validation is enabled by default."""
if value not in [True, False]:
raise ValueError
Distribution._default_validate_args = value
def __init__(
self,
batch_shape: Sequence[int] = (),
event_shape: Sequence[int] = (),
validate_args: bool | None = None,
) -> None:
self._batch_shape = (
batch_shape
if isinstance(batch_shape, tuple)
else tuple(batch_shape)
)
self._event_shape = (
event_shape
if isinstance(event_shape, tuple)
else tuple(event_shape)
)
self._validate_args_enabled = (
Distribution._default_validate_args
if validate_args is None
else validate_args
)
super().__init__()
@property
def batch_shape(self) -> Sequence[int]:
"""Returns batch shape of distribution
Returns:
Sequence[int]: batch shape
"""
return self._batch_shape
@property
def event_shape(self) -> Sequence[int]:
"""Returns event shape of distribution
Returns:
Sequence[int]: event shape
"""
return self._event_shape
@property
def arg_constraints(self) -> dict[str, Constraint]:
"""Returns constraints that should be satisfied by distribution arguments."""
raise NotImplementedError
@property
def support(self) -> Constraint | None:
"""Returns a constraint object representing this distribution's support."""
raise NotImplementedError
@property
def mean(self) -> Tensor:
"""Mean of distribution"""
raise NotImplementedError
@property
def mode(self) -> Tensor:
"""Mode of distribution"""
raise NotImplementedError(f"{self.__class__} does not implement mode")
@property
def variance(self) -> Tensor:
"""Variance of distribution"""
raise NotImplementedError
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Sampling from the distribution.
Alias: ``sample_shape``.
"""
raise NotImplementedError
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""Reparameterized sample from the distribution.
Alias: ``sample_shape``.
"""
raise NotImplementedError
def sample_n(self, n: int) -> Tensor:
"""Generates n samples from the distribution."""
return self.sample((n,))
def entropy(self) -> Tensor:
"""The entropy of the distribution."""
raise NotImplementedError
def kl_divergence(self, other: Distribution) -> Tensor:
"""The KL-divergence between self distributions and other."""
raise NotImplementedError
def prob(self, value: Tensor) -> Tensor:
"""Probability density/mass function evaluated at value.
Args:
value (Tensor): value which will be evaluated
"""
return self.log_prob(value).exp()
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density/mass function."""
raise NotImplementedError
def cdf(self, value: Tensor) -> Tensor:
"""Cumulative density/mass function evaluated at value."""
raise NotImplementedError
def icdf(self, value: Tensor) -> Tensor:
"""Inverse cumulative density/mass function evaluated at value."""
raise NotImplementedError
def enumerate_support(self, expand: bool = True) -> Tensor:
"""Returns tensor containing all values supported by a discrete distribution."""
raise NotImplementedError
def perplexity(self) -> Tensor:
"""Returns perplexity of the distribution."""
return paddle.exp(self.entropy())
def probs(self, value: Tensor) -> Tensor:
"""Probability density/mass function.
Note:
This method will be deprecated in the future, please use `prob`
instead.
"""
raise NotImplementedError
def _extend_shape(self, sample_shape: Sequence[int] | Tensor) -> Tensor:
"""compute shape of the sample
Args:
sample_shape (Sequence[int]|Tensor): sample shape
Returns:
Tensor: generated sample data shape
"""
return (
tuple(sample_shape)
+ tuple(self._batch_shape)
+ tuple(self._event_shape)
)
def _validate_sample(self, value: Tensor) -> None:
event_dim_start = len(value.shape) - len(self._event_shape)
if tuple(value.shape[event_dim_start:]) != self._event_shape:
raise ValueError(
f"The right-most size of value must match event_shape: {value.shape} vs {self._event_shape}."
)
actual_shape = tuple(value.shape)
expected_shape = self._batch_shape + self._event_shape
for i, j in zip(reversed(actual_shape), reversed(expected_shape)):
if i != 1 and j != 1 and i != j:
raise ValueError(
f"Value is not broadcastable with batch_shape+event_shape: {actual_shape} vs {expected_shape}."
)
try:
support = self.support
except NotImplementedError:
warnings.warn(
f"{self.__class__} does not define `support` to enable "
+ "sample validation. Please initialize the distribution with "
+ "`validate_args=False` to turn off validation.",
stacklevel=2,
)
return
if support is None:
raise AssertionError("support is unexpectedly None")
valid = support.check(value)
if not bool(valid.all()):
raise ValueError(
"Expected value argument "
f"({type(value).__name__} of shape {tuple(value.shape)}) "
f"to be within the support ({support!r}) "
f"of the distribution {self!r}, "
f"but found invalid values:\n{value}"
)
def _validate_args(
self, *args: TensorLike | NestedNumericSequence
) -> TypeGuard[Tensor]:
"""
Argument validation for distribution args
Args:
value (float, list, numpy.ndarray, Tensor)
Raises
ValueError: if one argument is Tensor, all arguments should be Tensor
"""
is_variable = False
is_number = False
for arg in args:
if isinstance(arg, (Variable, paddle.pir.Value)):
is_variable = True
else:
is_number = True
if is_variable and is_number:
raise ValueError(
'if one argument is Tensor, all arguments should be Tensor'
)
return is_variable
def _to_tensor(
self, *args: TensorLike | NestedNumericSequence
) -> tuple[Tensor, ...]:
"""
Argument convert args to Tensor
Args:
value (float, list, numpy.ndarray, Tensor)
Returns:
Tensor of args.
"""
numpy_args = []
variable_args = []
tmp = 0.0
for arg in args:
if not isinstance(
arg,
(float, list, tuple, np.ndarray, Variable, paddle.pir.Value),
):
raise TypeError(
f"Type of input args must be float, list, tuple, numpy.ndarray or Tensor, but received type {type(arg)}"
)
if isinstance(arg, paddle.pir.Value):
# pir.Value does not need to be converted to numpy.ndarray, so we skip here
numpy_args.append(arg)
continue
arg_np = np.array(arg)
arg_dtype = arg_np.dtype
if str(arg_dtype) != 'float32':
if str(arg_dtype) != 'float64':
# "assign" op doesn't support float64. if dtype is float64, float32 variable will be generated
# and converted to float64 later using "cast".
warnings.warn(
"data type of argument only support float32 and float64, your argument will be convert to float32."
)
arg_np = arg_np.astype('float32')
# tmp is used to support broadcast, it summarizes shapes of all the args and get the mixed shape.
tmp = tmp + arg_np
numpy_args.append(arg_np)
dtype = tmp.dtype
for arg in numpy_args:
if isinstance(arg, paddle.pir.Value):
# pir.Value does not need to be converted to numpy.ndarray, so we skip here
variable_args.append(arg)
continue
arg_broadcasted, _ = np.broadcast_arrays(arg, tmp)
if in_pir_mode():
arg_variable = paddle.zeros(arg_broadcasted.shape)
else:
arg_variable = paddle.tensor.create_tensor(dtype=dtype)
paddle.assign(arg_broadcasted, arg_variable)
variable_args.append(arg_variable)
return tuple(variable_args)
def _check_values_dtype_in_probs(
self, param: Tensor, value: Tensor
) -> Tensor:
"""
Log_prob and probs methods have input ``value``, if value's dtype is different from param,
convert value's dtype to be consistent with param's dtype.
Args:
param (Tensor): low and high in Uniform class, loc and scale in Normal class.
value (Tensor): The input tensor.
Returns:
value (Tensor): Change value's dtype if value's dtype is different from param.
"""
if paddle.is_complex(param):
return value.astype(param.dtype)
if in_dynamic_or_pir_mode():
if in_pir_mode():
check_variable_and_dtype(
value, 'value', ['float32', 'float64'], 'log_prob'
)
if value.dtype != param.dtype and convert_dtype(value.dtype) in [
'float32',
'float64',
]:
warnings.warn(
"dtype of input 'value' needs to be the same as parameters of distribution class. dtype of 'value' will be converted."
)
return _C_ops.cast(value, param.dtype)
return value
check_variable_and_dtype(
value,
'value',
['float32', 'float64'],
'log_prob',
)
if value.dtype != param.dtype:
warnings.warn(
"dtype of input 'value' needs to be the same as parameters of distribution class. dtype of 'value' will be converted."
)
return paddle.cast(value, dtype=param.dtype)
return value
def _probs_to_logits(
self, probs: float | Tensor, is_binary: bool = False
) -> Tensor:
r"""
Converts probabilities into logits. For the binary, probs denotes the
probability of occurrence of the event indexed by `1`. For the
multi-dimensional, values of last axis denote the probabilities of
occurrence of each of the events.
"""
return (
(paddle.log(probs) - paddle.log1p(-probs))
if is_binary
else paddle.log(probs)
)
def _logits_to_probs(
self, logits: float | Tensor, is_binary: bool = False
) -> Tensor:
r"""
Converts logits into probabilities. For the binary, each value denotes
log odds, whereas for the multi-dimensional case, the values along the
last dimension denote the log probabilities of the events.
"""
return (
paddle.nn.functional.sigmoid(logits)
if is_binary
else paddle.nn.functional.softmax(logits, axis=-1)
)
def _broadcast_all(
self, *args: TensorLike | NestedNumericSequence
) -> tuple[Tensor, ...]:
r"""
Returns a list where each arg is broadcasted. Scalar args are upcast to tensors
having the same data type as the first Tensor passed to `args`. If all the
args are scalars, then they are upcasted to Tensors with paddle default data type.
Args:
value (float, list, numpy.ndarray, Tensor)
Returns:
Broadcasted Tensor of args.
"""
for arg in args:
if not isinstance(
arg,
(float, list, tuple, np.ndarray, Variable, paddle.pir.Value),
):
raise TypeError(
f"Type of input args must be float, list, tuple, numpy.ndarray or Tensor, but received type {type(arg)}"
)
if not all(
isinstance(arg, (Variable, paddle.pir.Value)) for arg in args
):
dtype = paddle.get_default_dtype()
for arg in args:
if isinstance(arg, (Variable, paddle.pir.Value)):
dtype = arg.dtype
break
new_args = [
(
arg
if isinstance(arg, (Variable, paddle.pir.Value))
else paddle.to_tensor(arg, dtype=dtype)
)
for arg in args
]
return paddle.broadcast_tensors(new_args)
return paddle.broadcast_tensors(args)
+236
View File
@@ -0,0 +1,236 @@
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
import paddle
from paddle import distribution
from paddle.base.data_feeder import check_type, convert_dtype
from paddle.base.framework import Variable
from paddle.distribution import exponential_family
from paddle.framework import in_dynamic_mode
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor, dtype
class Exponential(exponential_family.ExponentialFamily):
r"""
Exponential distribution parameterized by :attr:`rate`.
The probability density function (pdf) is
.. math::
f(x; \theta) = \theta e^{- \theta x }, (x \ge 0) $$
In the above equation:
* :math:`rate = \theta`: is the rate parameter.
Args:
rate (float|Tensor): Rate parameter. The value of rate must be positive.
Example:
.. code-block:: pycon
>>> import paddle
>>> expon = paddle.distribution.Exponential(paddle.to_tensor([0.5]))
>>> print(expon.mean)
Tensor(shape=[1], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[2.])
>>> print(expon.variance)
Tensor(shape=[1], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[4.])
>>> print(expon.entropy())
Tensor(shape=[1], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[1.69314718])
"""
rate: Tensor
dtype: dtype
def __init__(self, rate: float | Tensor) -> None:
if not in_dynamic_mode():
check_type(
rate,
'rate',
(float, Variable, paddle.pir.Value),
'Exponential',
)
# Get/convert rate to tensor.
if self._validate_args(rate):
self.rate = rate
self.dtype = convert_dtype(rate.dtype)
else:
[self.rate] = self._to_tensor(rate)
self.dtype = paddle.get_default_dtype()
super().__init__(self.rate.shape)
@property
def mean(self) -> Tensor:
"""Mean of exponential distribution.
Returns:
Tensor: mean value.
"""
return self.rate.reciprocal()
@property
def variance(self) -> Tensor:
"""Variance of exponential distribution.
Returns:
Tensor: variance value.
"""
return self.rate.pow(-2)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate samples of the specified shape.
Args:
shape (Sequence[int], optional): Shape of the generated samples.
Returns:
Tensor, A tensor with prepended dimensions shape. The data type is float32.
"""
with paddle.no_grad():
return self.rsample(shape)
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate reparameterized samples of the specified shape.
Args:
shape (Sequence[int], optional): Shape of the generated samples.
Returns:
Tensor: A tensor with prepended dimensions shape. The data type is float32.
"""
shape = distribution.Distribution._extend_shape(
self, sample_shape=shape
)
uniform = paddle.uniform(
shape=shape,
min=float(np.finfo(dtype='float32').tiny),
max=1.0,
dtype=self.rate.dtype,
)
return -paddle.log(uniform) / self.rate
def prob(self, value: float | Tensor) -> Tensor:
r"""Probability density function evaluated at value.
.. math::
{ f(x; \theta) = \theta e^{- \theta x}, (x \ge 0 ) }
Args:
value (float|Tensor): Value to be evaluated.
Returns:
Tensor: Probability.
"""
return self.rate * paddle.exp(-self.rate * value)
def log_prob(self, value: float | Tensor) -> Tensor:
"""Log probability density function evaluated at value.
Args:
value (float|Tensor): Value to be evaluated
Returns:
Tensor: Log probability.
"""
return paddle.log(self.rate) - self.rate * value
def entropy(self) -> Tensor:
"""Entropy of exponential distribution.
Returns:
Tensor: Entropy.
"""
return 1.0 - paddle.log(self.rate)
def cdf(self, value: float | Tensor) -> Tensor:
r"""Cumulative distribution function(CDF) evaluated at value.
.. math::
{ cdf(x; \theta) = 1 - e^{- \theta x }, (x \ge 0) }
Args:
value (float|Tensor): Input value to evaluate the cumulative probability.
Returns:
Tensor: The evaluated cumulative probability.
"""
return 1.0 - paddle.exp(-self.rate * value)
def icdf(self, value: float | Tensor) -> Tensor:
r"""Inverse cumulative distribution function(CDF) evaluated at value.
.. math::
{ icdf(x; \theta) = -\frac{ 1 }{ \theta } ln(1 - x), (0 < x < 1) }
Args:
value (float|Tensor): Input probability to evaluate the quantile.
Returns:
Tensor: The evaluated quantile value.
"""
return -paddle.log1p(-value) / self.rate
def kl_divergence(self, other: Exponential) -> Tensor:
"""The KL-divergence between two exponential distributions.
Args:
other (Exponential): instance of Exponential.
Returns:
Tensor: kl-divergence between two exponential distributions.
"""
if not isinstance(other, Exponential):
raise TypeError(
f"Expected type of other is Exponential, but got {type(other)}"
)
rate_ratio = other.rate / self.rate
t1 = -paddle.log(rate_ratio)
return t1 + rate_ratio - 1
@property
def _natural_parameters(self) -> tuple[Tensor]:
return (-self.rate,)
def _log_normalizer(self, x: Tensor) -> Tensor:
return -paddle.log(-x)
@@ -0,0 +1,82 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import paddle
from paddle.distribution import distribution
from paddle.framework import in_dynamic_mode
if TYPE_CHECKING:
from paddle import Tensor
class ExponentialFamily(distribution.Distribution):
r"""
ExponentialFamily is the base class for probability distributions belonging
to exponential family, whose probability mass/density function has the
form is defined below
ExponentialFamily is derived from `paddle.distribution.Distribution`.
.. math::
f_{F}(x; \theta) = \exp(\langle t(x), \theta\rangle - F(\theta) + k(x))
where :math:`\theta` denotes the natural parameters, :math:`t(x)` denotes
the sufficient statistic, :math:`F(\theta)` is the log normalizer function
for a given family and :math:`k(x)` is the carrier measure.
Distribution belongs to exponential family referring to https://en.wikipedia.org/wiki/Exponential_family
"""
@property
def _natural_parameters(self):
raise NotImplementedError
def _log_normalizer(self):
raise NotImplementedError
@property
def _mean_carrier_measure(self):
raise NotImplementedError
def entropy(self) -> Tensor:
"""calculate entropy use `bregman divergence`
https://www.lix.polytechnique.fr/~nielsen/EntropyEF-ICIP2010.pdf
"""
entropy_value = -self._mean_carrier_measure
natural_parameters = []
for parameter in self._natural_parameters:
parameter = parameter.detach()
parameter.stop_gradient = False
natural_parameters.append(parameter)
log_norm = self._log_normalizer(*natural_parameters)
if in_dynamic_mode():
grads = paddle.grad(
log_norm.sum(), natural_parameters, create_graph=True
)
else:
grads = paddle.static.gradients(log_norm.sum(), natural_parameters)
entropy_value += log_norm
for p, g in zip(natural_parameters, grads):
entropy_value -= p * g
return entropy_value
+239
View File
@@ -0,0 +1,239 @@
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import paddle
from paddle import distribution
from paddle.base.data_feeder import check_type, convert_dtype
from paddle.base.framework import Variable
from paddle.distribution import exponential_family
from paddle.framework import in_dynamic_mode
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor, dtype
class Gamma(exponential_family.ExponentialFamily):
r"""
Gamma distribution parameterized by :attr:`concentration` (aka "alpha") and :attr:`rate` (aka "beta").
The probability density function (pdf) is
.. math::
f(x; \alpha, \beta, x > 0) = \frac{\beta^{\alpha}}{\Gamma(\alpha)} x^{\alpha-1}e^{-\beta x}
\Gamma(\alpha)=\int_{0}^{\infty} x^{\alpha-1} e^{-x} \mathrm{~d} x, (\alpha>0)
Args:
concentration (float|Tensor): Concentration parameter. It supports broadcast semantics.
The value of concentration must be positive. When the parameter is a tensor,
it represents multiple independent distribution with
a batch_shape(refer to :ref:`api_paddle_distribution_Distribution`).
rate (float|Tensor): Rate parameter. It supports broadcast semantics.
The value of rate must be positive. When the parameter is tensor,
it represent multiple independent distribution with
a batch_shape(refer to :ref:`api_paddle_distribution_Distribution`).
Example:
.. code-block:: pycon
>>> import paddle
>>> # scale input
>>> gamma = paddle.distribution.Gamma(0.5, 0.5)
>>> print(gamma.mean)
Tensor(shape=[], dtype=float32, place=Place(gpu:0), stop_gradient=True,
1.)
>>> print(gamma.variance)
Tensor(shape=[], dtype=float32, place=Place(gpu:0), stop_gradient=True,
2.)
>>> print(gamma.entropy())
Tensor(shape=[], dtype=float32, place=Place(gpu:0), stop_gradient=True,
0.78375685)
>>> # tensor input with broadcast
>>> gamma = paddle.distribution.Gamma(paddle.to_tensor([0.2, 0.4]), paddle.to_tensor(0.6))
>>> print(gamma.mean)
Tensor(shape=[2], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[0.33333331, 0.66666663])
>>> print(gamma.variance)
Tensor(shape=[2], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[0.55555552, 1.11111104])
>>> print(gamma.entropy())
Tensor(shape=[2], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[-1.99634242, 0.17067254])
"""
concentration: Tensor
rate: Tensor
dtype: dtype
def __init__(
self, concentration: float | Tensor, rate: float | Tensor
) -> None:
if not in_dynamic_mode():
check_type(
concentration,
'concentration',
(float, Variable, paddle.pir.Value),
'Gamma',
)
check_type(
rate,
'rate',
(float, Variable, paddle.pir.Value),
'Gamma',
)
# Get/convert concentration/rate to tensor.
if self._validate_args(concentration, rate):
self.concentration = concentration
self.rate = rate
self.dtype = convert_dtype(concentration.dtype)
else:
[self.concentration, self.rate] = self._to_tensor(
concentration, rate
)
self.dtype = paddle.get_default_dtype()
super().__init__(self.concentration.shape)
@property
def mean(self) -> Tensor:
"""Mean of gamma distribution.
Returns:
Tensor: mean value.
"""
return self.concentration / self.rate
@property
def variance(self) -> Tensor:
"""Variance of gamma distribution.
Returns:
Tensor: variance value.
"""
return self.concentration / self.rate.pow(2)
def prob(self, value: float | Tensor) -> Tensor:
"""Probability density function evaluated at value
Args:
value (float|Tensor): Value to be evaluated.
Returns:
Tensor: Probability.
"""
return paddle.exp(self.log_prob(value))
def log_prob(self, value: float | Tensor) -> Tensor:
"""Log probability density function evaluated at value
Args:
value (float|Tensor): Value to be evaluated
Returns:
Tensor: Log probability.
"""
return (
self.concentration * paddle.log(self.rate)
+ (self.concentration - 1) * paddle.log(value)
- self.rate * value
- paddle.lgamma(self.concentration)
)
def entropy(self) -> Tensor:
"""Entropy of gamma distribution
Returns:
Tensor: Entropy.
"""
return (
self.concentration
- paddle.log(self.rate)
+ paddle.lgamma(self.concentration)
+ (1.0 - self.concentration) * paddle.digamma(self.concentration)
)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate samples of the specified shape.
Args:
shape (Sequence[int], optional): Shape of the generated samples.
Returns:
Tensor, A tensor with prepended dimensions shape.The data type is float32.
"""
with paddle.no_grad():
return self.rsample(shape)
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate reparameterized samples of the specified shape.
Args:
shape (Sequence[int], optional): Shape of the generated samples.
Returns:
Tensor: A tensor with prepended dimensions shape.The data type is float32.
"""
shape = distribution.Distribution._extend_shape(
self, sample_shape=shape
)
return paddle.standard_gamma(
self.concentration.expand(shape)
) / self.rate.expand(shape)
def kl_divergence(self, other: Gamma) -> Tensor:
"""The KL-divergence between two gamma distributions.
Args:
other (Gamma): instance of Gamma.
Returns:
Tensor: kl-divergence between two gamma distributions.
"""
if not isinstance(other, Gamma):
raise TypeError(
f"Expected type of other is Exponential, but got {type(other)}"
)
t1 = other.concentration * paddle.log(self.rate / other.rate)
t2 = paddle.lgamma(other.concentration) - paddle.lgamma(
self.concentration
)
t3 = (self.concentration - other.concentration) * paddle.digamma(
self.concentration
)
t4 = (other.rate - self.rate) * (self.concentration / self.rate)
return t1 + t2 + t3 + t4
def _natural_parameters(self) -> Tensor:
return (self.concentration - 1, -self.rate)
def _log_normalizer(self, x: Tensor, y: Tensor) -> Tensor:
return paddle.lgamma(x + 1) + (x + 1) * paddle.log(-y.reciprocal())
+359
View File
@@ -0,0 +1,359 @@
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import numbers
from typing import TYPE_CHECKING
import numpy as np
import paddle
from paddle.base import framework
from paddle.distribution import distribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
class Geometric(distribution.Distribution):
r"""
Geometric distribution parameterized by probs.
In probability theory and statistics, the geometric distribution is one of
discrete probability distributions, parameterized by one positive shape parameter, denoted by probs.
In n Bernoulli trials, it takes k+1 trials to get the probability of success for the first time.
In detail, it is: the probability that the first k times failed and the kth time succeeded.
The geometric distribution is a special case of the Pascal distribution when r=1.
The probability mass function (pmf) is
.. math::
Pr(Y=k)=(1-p)^kp
where k is number of trials failed before seeing a success, and p is probability of success for each trial and k=0,1,2,3,4..., p belong to (0,1].
Args:
probs (Real|Tensor): Probability parameter.
The value of probs must be positive. When the parameter is a tensor, probs is probability of success for each trial.
Returns:
Geometric distribution for instantiation of probs.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Geometric
>>> geom = Geometric(0.5)
>>> print(geom.mean)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.)
>>> print(geom.variance)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
2.)
>>> print(geom.stddev)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.41421354)
"""
probs: Tensor
def __init__(self, probs: float | Tensor) -> None:
if isinstance(
probs,
(numbers.Real, paddle.Tensor, framework.Variable, paddle.pir.Value),
):
if isinstance(probs, numbers.Real):
probs = paddle.full(
shape=(), fill_value=probs, dtype=paddle.float32
)
all_ones = paddle.full(
shape=probs.shape, fill_value=1, dtype=probs.dtype
)
all_zeros = paddle.full(
shape=probs.shape, fill_value=0, dtype=probs.dtype
)
all_false = paddle.full(
shape=probs.shape, fill_value=False, dtype=bool
)
lessthen_0 = probs <= all_zeros
morethen_1 = probs > all_ones
else:
raise TypeError(
f"Expected type of probs is Number.Real|Tensor|framework.Variable|Value, but got {type(probs)}"
)
batch_shape = tuple(probs.shape)
self.probs = probs
super().__init__(batch_shape)
@property
def mean(self) -> Tensor:
"""Mean of geometric distribution."""
return 1.0 / self.probs - 1.0
@property
def variance(self) -> Tensor:
"""Variance of geometric distribution."""
return paddle.to_tensor(
(1.0 / self.probs - 1.0) / self.probs,
dtype=self.probs.dtype,
)
@property
def stddev(self) -> Tensor:
"""Standard deviation of Geometric distribution."""
return paddle.sqrt(self.variance)
def pmf(self, k: int | Tensor) -> Tensor:
r"""Probability mass function evaluated at k.
.. math::
P(X=k) = (1-p)^{k} p, \quad k=0,1,2,3,\ldots
Args:
k (int): Value to be evaluated.
Returns:
Tensor: Probability.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Geometric
>>> geom = Geometric(0.5)
>>> print(geom.pmf(2))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.12500000)
"""
if isinstance(
k, (numbers.Integral, framework.Variable, paddle.pir.Value)
):
return paddle.pow((1.0 - self.probs), k) * self.probs
else:
raise TypeError(
f"Expected type of k is number.Real|framework.Variable|Value, but got {type(k)}"
)
def log_pmf(self, k: int | Tensor) -> Tensor:
r"""Log probability mass function evaluated at k.
.. math::
\log P(X = k) = \log(1-p)^k p
Args:
k (int): Value to be evaluated.
Returns:
Tensor: Log probability.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Geometric
>>> geom = Geometric(0.5)
>>> print(geom.log_pmf(2))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
-2.07944131)
"""
if isinstance(
k, (numbers.Integral, framework.Variable, paddle.pir.Value)
):
return paddle.log(self.pmf(k))
else:
raise TypeError(
f"Expected type of k is number.Real|framework.Variable|Value, but got {type(k)}"
)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Sample from Geometric distribution with sample shape.
Args:
shape (Sequence[int]): Sample shape.
Returns:
Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Geometric
>>> paddle.seed(2023)
>>> geom = Geometric(0.5)
>>> print(geom.sample((2, 2)))
Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0., 0.],
[1., 0.]])
"""
with paddle.no_grad():
return self.rsample(shape)
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate samples of the specified shape.
Args:
shape(Sequence[int]): The shape of generated samples.
Returns:
Tensor: A sample tensor that fits the Geometric distribution.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Geometric
>>> paddle.seed(2023)
>>> geom = Geometric(0.5)
>>> print(geom.rsample((2, 2)))
Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0., 0.],
[1., 0.]])
"""
shape = distribution.Distribution._extend_shape(
self, sample_shape=shape
)
uniform = paddle.uniform(
shape=shape,
min=float(np.finfo(dtype='float32').tiny),
max=1.0,
dtype=self.probs.dtype,
)
return paddle.floor(paddle.log(uniform) / paddle.log1p(-(self.probs)))
def entropy(self) -> Tensor:
r"""Entropy of dirichlet distribution.
.. math::
H(X) = -\left[\frac{1}{p} \log p + \frac{1-p}{p^2} \log (1-p) \right]
Returns:
Tensor: Entropy.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Geometric
>>> geom = Geometric(0.5)
>>> print(geom.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.38629425)
"""
x = (1.0 - self.probs) * paddle.log(1.0 - self.probs)
y = self.probs * paddle.log(self.probs)
return -(x + y) / self.probs
def cdf(self, k: int | Tensor) -> Tensor:
r"""Cdf of geometric distribution.
.. math::
F(X \leq k) = 1 - (1-p)^(k+1), \quad k=0,1,2,\ldots
Args:
k: The number of trials performed.
Returns:
Tensor: Entropy.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Geometric
>>> geom = Geometric(0.5)
>>> print(geom.cdf(4))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.96875000)
"""
if isinstance(
k, (numbers.Integral, framework.Variable, paddle.pir.Value)
):
return 1.0 - paddle.pow((1.0 - self.probs), k + 1)
else:
raise TypeError(
f"Expected type of k is number.Real|framework.Variable|Value, but got {type(k)}"
)
def kl_divergence(self, other: Geometric) -> Tensor:
r"""Calculate the KL divergence KL(self || other) with two Geometric instances.
.. math::
KL(P \| Q) = \frac{p}{q} \log \frac{p}{q} + \log (1-p) - \log (1-q)
Args:
other (Geometric): An instance of Geometric.
Returns:
Tensor: The kl-divergence between two geometric distributions.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Geometric
>>> geom_p = Geometric(0.5)
>>> geom_q = Geometric(0.1)
>>> print(geom_p.kl_divergence(geom_q))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.51082563)
"""
if isinstance(other, Geometric):
p, q = self.probs, other.probs
return p * paddle.log(p / q) + (1.0 - p) * paddle.log(
(1.0 - p) / (1.0 - q)
)
else:
raise TypeError(
f"Exacted type of other is geometric.Geometric, but got {type(other)}"
)
+295
View File
@@ -0,0 +1,295 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
import numbers
from typing import TYPE_CHECKING
import numpy as np
import paddle
from paddle.base import framework
from paddle.distribution.transformed_distribution import TransformedDistribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
from paddle.distribution import Transform, Uniform
class Gumbel(TransformedDistribution):
r"""The Gumbel distribution with location `loc` and `scale` parameters.
Mathematical details
The probability density function (pdf) is
.. math::
pdf(x; mu, sigma) = exp(-(x - mu) / sigma - exp(-(x - mu) / sigma)) / sigma
In the above equation:
* :math:`loc = \mu`: is the mean.
* :math:`scale = \sigma`: is the std.
Args:
loc(int|float|tensor): The mean of gumbel distribution.The data type is int, float, tensor.
scale(int|float|tensor): The std of gumbel distribution.The data type is int, float, tensor.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution.gumbel import Gumbel
>>> # Gumbel distributed with loc=0, scale=1
>>> dist = Gumbel(paddle.full([1], 0.0), paddle.full([1], 1.0))
>>> # doctest: +SKIP("The sample results is randomized.")
>>> print(dist.sample([2]))
Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0.40484068],
[3.19400501]])
>>> print(dist.rsample([2]))
Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
[[-0.95093185],
[ 0.32422572]])
>>> # doctest: -SKIP
>>> value = paddle.full([1], 0.5)
>>> print(dist.prob(value))
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.33070430])
>>> print(dist.log_prob(value))
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[-1.10653067])
>>> print(dist.cdf(value))
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.54523921])
>>> print(dist.entropy())
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.57721567])
"""
loc: Tensor
scale: Tensor
base_dist: Uniform
transforms: tuple[Transform, ...]
def __init__(self, loc: float | Tensor, scale: float | Tensor) -> None:
if not isinstance(
loc, (numbers.Real, framework.Variable, paddle.pir.Value)
):
raise TypeError(
f"Expected type of loc is Real|Variable|Value, but got {type(loc)}"
)
if not isinstance(
scale, (numbers.Real, framework.Variable, paddle.pir.Value)
):
raise TypeError(
f"Expected type of scale is Real|Variable|Value, but got {type(scale)}"
)
if isinstance(loc, numbers.Real):
loc = paddle.full(shape=(), fill_value=loc)
if isinstance(scale, numbers.Real):
scale = paddle.full(shape=(), fill_value=scale)
if loc.shape != scale.shape:
self.loc, self.scale = paddle.broadcast_tensors([loc, scale])
else:
self.loc, self.scale = loc, scale
finfo = np.finfo(dtype='float32')
self.base_dist = paddle.distribution.Uniform(
paddle.full_like(self.loc, float(finfo.tiny)),
paddle.full_like(self.loc, float(1 - finfo.eps)),
)
self.transforms = ()
super().__init__(self.base_dist, self.transforms)
@property
def mean(self) -> Tensor:
r"""Mean of distribution
The mean is
.. math::
mean = \mu + \sigma * γ
In the above equation:
* :math:`loc = \mu`: is the location parameter.
* :math:`scale = \sigma`: is the scale parameter.
* :math:`γ`: is the euler's constant.
Returns:
Tensor: mean value.
"""
return self.loc + self.scale * np.euler_gamma
@property
def variance(self) -> Tensor:
r"""Variance of distribution.
The variance is
.. math::
variance = \sigma^2 * \pi^2 / 6
In the above equation:
* :math:`scale = \sigma`: is the scale parameter.
Returns:
Tensor: The variance value.
"""
temp = paddle.full(
shape=self.loc.shape,
fill_value=math.pi * math.pi,
dtype=self.scale.dtype,
)
return paddle.pow(self.scale, 2) * temp / 6
@property
def stddev(self) -> Tensor:
r"""Standard deviation of distribution
The standard deviation is
.. math::
stddev = \sqrt{\sigma^2 * \pi^2 / 6}
In the above equation:
* :math:`scale = \sigma`: is the scale parameter.
Returns:
Tensor: std value
"""
return paddle.sqrt(self.variance)
def prob(self, value: Tensor) -> Tensor:
"""Probability density/mass function
Args:
value (Tensor): The input tensor.
Returns:
Tensor: probability.The data type is same with value.
"""
y = (self.loc - value.astype(self.loc.dtype)) / self.scale.astype(
self.loc.dtype
)
return paddle.exp(y - paddle.exp(y)) / self.scale.astype(y.dtype)
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: log probability.The data type is same with value.
"""
return paddle.log(self.prob(value))
def cdf(self, value: Tensor) -> Tensor:
"""Cumulative distribution function.
Args:
value (Tensor): value to be evaluated.
Returns:
Tensor: cumulative probability of value.
"""
return paddle.exp(
-paddle.exp(
-(value - self.loc.astype(value.dtype))
/ self.scale.astype(value.dtype)
)
)
def entropy(self) -> Tensor:
"""Entropy of Gumbel distribution.
Returns:
Entropy of distribution.
"""
return paddle.log(self.scale) + 1 + np.euler_gamma
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Sample from ``Gumbel``.
Args:
shape (Sequence[int], optional): The sample shape. Defaults to [].
Returns:
Tensor: A tensor with prepended dimensions shape.The data type is float32.
"""
with paddle.no_grad():
return self.rsample(shape)
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""reparameterized sample
Args:
shape (Sequence[int], optional): 1D `int32`. Shape of the generated samples. Defaults to [].
Returns:
Tensor: A tensor with prepended dimensions shape.The data type is float32.
"""
exp_trans = paddle.distribution.ExpTransform()
affine_trans_1 = paddle.distribution.AffineTransform(
paddle.full(
shape=self.scale.shape, fill_value=0, dtype=self.loc.dtype
),
-paddle.ones_like(self.scale),
)
affine_trans_2 = paddle.distribution.AffineTransform(
self.loc, -self.scale
)
return affine_trans_2.forward(
exp_trans.inverse(
affine_trans_1.forward(
exp_trans.inverse(self._base.sample(shape))
)
)
)
+111
View File
@@ -0,0 +1,111 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
from paddle.distribution import distribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
class Independent(distribution.Distribution):
r"""
Reinterprets some of the batch dimensions of a distribution as event dimensions.
This is mainly useful for changing the shape of the result of
:meth:`log_prob`.
Args:
base (Distribution): The base distribution.
reinterpreted_batch_rank (int): The number of batch dimensions to
reinterpret as event dimensions.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import independent
>>> beta = paddle.distribution.Beta(paddle.to_tensor([0.5, 0.5]), paddle.to_tensor([0.5, 0.5]))
>>> print(beta.batch_shape, beta.event_shape)
(2,) ()
>>> print(beta.log_prob(paddle.to_tensor(0.2)))
Tensor(shape=[2], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[-0.22843921, -0.22843921])
>>> reinterpreted_beta = independent.Independent(beta, 1)
>>> print(reinterpreted_beta.batch_shape, reinterpreted_beta.event_shape)
() (2,)
>>> print(reinterpreted_beta.log_prob(paddle.to_tensor([0.2, 0.2])))
Tensor(shape=[], dtype=float32, place=Place(gpu:0), stop_gradient=True,
-0.45687842)
"""
def __init__(
self, base: distribution.Distribution, reinterpreted_batch_rank: int
) -> None:
if not isinstance(base, distribution.Distribution):
raise TypeError(
f"Expected type of 'base' is Distribution, but got {type(base)}"
)
if not (0 < reinterpreted_batch_rank <= len(base.batch_shape)):
raise ValueError(
f"Expected 0 < reinterpreted_batch_rank <= {len(base.batch_shape)}, but got {reinterpreted_batch_rank}"
)
self._base = base
self._reinterpreted_batch_rank = reinterpreted_batch_rank
shape = base.batch_shape + base.event_shape
super().__init__(
batch_shape=shape[
: len(base.batch_shape) - reinterpreted_batch_rank
],
event_shape=shape[
len(base.batch_shape) - reinterpreted_batch_rank :
],
)
@property
def mean(self) -> Tensor:
return self._base.mean
@property
def variance(self) -> Tensor:
return self._base.variance
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
return self._base.sample(shape)
def log_prob(self, value: Tensor) -> Tensor:
return self._sum_rightmost(
self._base.log_prob(value), self._reinterpreted_batch_rank
)
def prob(self, value: Tensor) -> Tensor:
return self.log_prob(value).exp()
def entropy(self) -> Tensor:
return self._sum_rightmost(
self._base.entropy(), self._reinterpreted_batch_rank
)
def _sum_rightmost(self, value: Tensor, n: int) -> Tensor:
return value.sum(list(range(-n, 0))) if n > 0 else value
+305
View File
@@ -0,0 +1,305 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import functools
import warnings
from typing import TYPE_CHECKING, TypeVar
import paddle
from paddle.distribution.bernoulli import Bernoulli
from paddle.distribution.beta import Beta
from paddle.distribution.binomial import Binomial
from paddle.distribution.categorical import Categorical
from paddle.distribution.cauchy import Cauchy
from paddle.distribution.continuous_bernoulli import ContinuousBernoulli
from paddle.distribution.dirichlet import Dirichlet
from paddle.distribution.distribution import Distribution
from paddle.distribution.exponential import Exponential
from paddle.distribution.exponential_family import ExponentialFamily
from paddle.distribution.gamma import Gamma
from paddle.distribution.geometric import Geometric
from paddle.distribution.laplace import Laplace
from paddle.distribution.lognormal import LogNormal
from paddle.distribution.multivariate_normal import MultivariateNormal
from paddle.distribution.normal import Normal
from paddle.distribution.poisson import Poisson
from paddle.distribution.uniform import Uniform
from paddle.framework import in_dynamic_mode
if TYPE_CHECKING:
from collections.abc import Callable
from paddle import Tensor
_T = TypeVar('_T')
__all__ = ["register_kl", "kl_divergence"]
_REGISTER_TABLE = {}
def kl_divergence(p: Distribution, q: Distribution) -> Tensor:
r"""
Kullback-Leibler divergence between distribution p and q.
.. math::
KL(p||q) = \int p(x)log\frac{p(x)}{q(x)} \mathrm{d}x
Args:
p (Distribution): ``Distribution`` object. Inherits from the Distribution Base class.
q (Distribution): ``Distribution`` object. Inherits from the Distribution Base class.
Returns:
Tensor, Batchwise KL-divergence between distribution p and q.
Examples:
.. code-block:: pycon
>>> import paddle
>>> p = paddle.distribution.Beta(alpha=0.5, beta=0.5)
>>> q = paddle.distribution.Beta(alpha=0.3, beta=0.7)
>>> print(paddle.distribution.kl_divergence(p, q))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.21193528)
"""
return _dispatch(type(p), type(q))(p, q)
def register_kl(
cls_p: type[Distribution], cls_q: type[Distribution]
) -> Callable[[_T], _T]:
"""Decorator for register a KL divergence implementation function.
The ``kl_divergence(p, q)`` function will search concrete implementation
functions registered by ``register_kl``, according to multi-dispatch pattern.
If an implementation function is found, it will return the result, otherwise,
it will raise ``NotImplementError`` exception. Users can register
implementation function by the decorator.
Args:
cls_p (type[Distribution]): The Distribution type of Instance p. Subclass derived from ``Distribution``.
cls_q (type[Distribution]): The Distribution type of Instance q. Subclass derived from ``Distribution``.
Examples:
.. code-block:: pycon
>>> import paddle
>>> @paddle.distribution.register_kl(paddle.distribution.Beta, paddle.distribution.Beta)
>>> def kl_beta_beta():
... pass # insert implementation here
"""
if not issubclass(cls_p, Distribution) or not issubclass(
cls_q, Distribution
):
raise TypeError('cls_p and cls_q must be subclass of Distribution')
def decorator(f):
_REGISTER_TABLE[cls_p, cls_q] = f
return f
return decorator
def _dispatch(cls_p, cls_q):
"""Multiple dispatch into concrete implement function."""
# find all matched super class pair of p and q
matches = [
(super_p, super_q)
for super_p, super_q in _REGISTER_TABLE
if issubclass(cls_p, super_p) and issubclass(cls_q, super_q)
]
if not matches:
raise NotImplementedError
left_p, left_q = min(_Compare(*m) for m in matches).classes
right_p, right_q = min(_Compare(*reversed(m)) for m in matches).classes
if _REGISTER_TABLE[left_p, left_q] is not _REGISTER_TABLE[right_p, right_q]:
warnings.warn(
f'Ambiguous kl_divergence({cls_p.__name__}, {cls_q.__name__}). Please register_kl({left_p.__name__}, {right_q.__name__})',
RuntimeWarning,
)
return _REGISTER_TABLE[left_p, left_q]
@functools.total_ordering
class _Compare:
def __init__(self, *classes):
self.classes = classes
def __eq__(self, other):
return self.classes == other.classes
def __le__(self, other):
for cls_x, cls_y in zip(self.classes, other.classes):
if not issubclass(cls_x, cls_y):
return False
if cls_x is not cls_y:
break
return True
@register_kl(Bernoulli, Bernoulli)
def _kl_bernoulli_bernoulli(p, q):
return p.kl_divergence(q)
@register_kl(Beta, Beta)
def _kl_beta_beta(p, q):
return (
(q.alpha.lgamma() + q.beta.lgamma() + (p.alpha + p.beta).lgamma())
- (p.alpha.lgamma() + p.beta.lgamma() + (q.alpha + q.beta).lgamma())
+ ((p.alpha - q.alpha) * p.alpha.digamma())
+ ((p.beta - q.beta) * p.beta.digamma())
+ (
((q.alpha + q.beta) - (p.alpha + p.beta))
* (p.alpha + p.beta).digamma()
)
)
@register_kl(Binomial, Binomial)
def _kl_binomial_binomial(p, q):
return p.kl_divergence(q)
@register_kl(Dirichlet, Dirichlet)
def _kl_dirichlet_dirichlet(p, q):
return (
(p.concentration.sum(-1).lgamma() - q.concentration.sum(-1).lgamma())
- ((p.concentration.lgamma() - q.concentration.lgamma()).sum(-1))
+ (
(
(p.concentration - q.concentration)
* (
p.concentration.digamma()
- p.concentration.sum(-1).digamma().unsqueeze(-1)
)
).sum(-1)
)
)
@register_kl(Categorical, Categorical)
def _kl_categorical_categorical(p, q):
return p.kl_divergence(q)
@register_kl(Cauchy, Cauchy)
def _kl_cauchy_cauchy(p, q):
return p.kl_divergence(q)
@register_kl(ContinuousBernoulli, ContinuousBernoulli)
def _kl_continuousbernoulli_continuousbernoulli(p, q):
return p.kl_divergence(q)
@register_kl(Normal, Normal)
def _kl_normal_normal(p, q):
return p.kl_divergence(q)
@register_kl(MultivariateNormal, MultivariateNormal)
def _kl_mvn_mvn(p, q):
return p.kl_divergence(q)
@register_kl(Uniform, Uniform)
def _kl_uniform_uniform(p, q):
return p.kl_divergence(q)
@register_kl(Laplace, Laplace)
def _kl_laplace_laplace(p, q):
return p.kl_divergence(q)
@register_kl(Geometric, Geometric)
def _kl_geometric_geometric(p, q):
return p.kl_divergence(q)
@register_kl(ExponentialFamily, ExponentialFamily)
def _kl_expfamily_expfamily(p, q):
"""Compute kl-divergence using `Bregman divergences <https://www.lix.polytechnique.fr/~nielsen/EntropyEF-ICIP2010.pdf>`_"""
if not type(p) == type(q):
raise NotImplementedError
p_natural_params = []
for param in p._natural_parameters:
param = param.detach()
param.stop_gradient = False
p_natural_params.append(param)
q_natural_params = q._natural_parameters
p_log_norm = p._log_normalizer(*p_natural_params)
try:
if in_dynamic_mode():
p_grads = paddle.grad(
p_log_norm, p_natural_params, create_graph=True
)
else:
p_grads = paddle.static.gradients(p_log_norm, p_natural_params)
except RuntimeError as e:
raise TypeError(
"Can't compute kl_divergence({cls_p}, {cls_q}) use bregman divergence. Please register_kl({cls_p}, {cls_q}).".format(
cls_p=type(p).__name__, cls_q=type(q).__name__
)
) from e
kl = q._log_normalizer(*q_natural_params) - p_log_norm
for p_param, q_param, p_grad in zip(
p_natural_params, q_natural_params, p_grads
):
term = (q_param - p_param) * p_grad
kl -= _sum_rightmost(term, len(q.event_shape))
return kl
@register_kl(Exponential, Exponential)
def _kl_exponential_exponential(p, q):
return p.kl_divergence(q)
@register_kl(Gamma, Gamma)
def _kl_gamma_gamma(p, q):
return p.kl_divergence(q)
@register_kl(LogNormal, LogNormal)
def _kl_lognormal_lognormal(p, q):
return p._base.kl_divergence(q._base)
@register_kl(Poisson, Poisson)
def _kl_poisson_poisson(p, q):
return p.kl_divergence(q)
def _sum_rightmost(value, n):
return value.sum(list(range(-n, 0))) if n > 0 else value
+432
View File
@@ -0,0 +1,432 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import numbers
from typing import TYPE_CHECKING
import numpy as np
import paddle
from paddle.base import framework
from paddle.distribution import distribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
class Laplace(distribution.Distribution):
r"""
Creates a Laplace distribution parameterized by :attr:`loc` and :attr:`scale`.
Mathematical details
The probability density function (pdf) is
.. math::
pdf(x; \mu, \sigma) = \frac{1}{2 * \sigma} * e^{\frac{-|x - \mu|}{\sigma}}
In the above equation:
* :math:`loc = \mu`: is the location parameter.
* :math:`scale = \sigma`: is the scale parameter.
Args:
loc (scalar|Tensor): The mean of the distribution.
scale (scalar|Tensor): The scale of the distribution.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.seed(2023)
>>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
>>> m.sample() # Laplace distributed with loc=0, scale=1
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.31554604)
"""
loc: Tensor
scale: Tensor
def __init__(self, loc: float | Tensor, scale: float | Tensor) -> None:
if not isinstance(
loc, (numbers.Real, framework.Variable, paddle.pir.Value)
):
raise TypeError(
f"Expected type of loc is Real|Variable, but got {type(loc)}"
)
if not isinstance(
scale, (numbers.Real, framework.Variable, paddle.pir.Value)
):
raise TypeError(
f"Expected type of scale is Real|Variable, but got {type(scale)}"
)
if isinstance(loc, numbers.Real):
loc = paddle.full(shape=(), fill_value=loc)
if isinstance(scale, numbers.Real):
scale = paddle.full(shape=(), fill_value=scale)
if (len(scale.shape) > 0 or len(loc.shape) > 0) and (
loc.dtype == scale.dtype
):
self.loc, self.scale = paddle.broadcast_tensors([loc, scale])
else:
self.loc, self.scale = loc, scale
super().__init__(self.loc.shape)
@property
def mean(self) -> Tensor:
"""Mean of distribution.
Returns:
Tensor: The mean value.
"""
return self.loc
@property
def stddev(self) -> Tensor:
r"""Standard deviation.
The stddev is
.. math::
stddev = \sqrt{2} * \sigma
In the above equation:
* :math:`scale = \sigma`: is the scale parameter.
Returns:
Tensor: The std value.
"""
return (2**0.5) * self.scale
@property
def variance(self) -> Tensor:
r"""Variance of distribution.
The variance is
.. math::
variance = 2 * \sigma^2
In the above equation:
* :math:`scale = \sigma`: is the scale parameter.
Returns:
Tensor: The variance value.
"""
return self.stddev.pow(2)
def _validate_value(
self, value: float | Tensor
) -> tuple[Tensor, Tensor, Tensor]:
"""Argument dimension check for distribution methods such as `log_prob`,
`cdf` and `icdf`.
Args:
value (Tensor|Scalar): The input value, which can be a scalar or a tensor.
Returns:
loc, scale, value: The broadcasted loc, scale and value, with the same dimension and data type.
"""
if isinstance(value, numbers.Real):
value = paddle.full(shape=(), fill_value=value)
if value.dtype != self.scale.dtype:
value = paddle.cast(value, self.scale.dtype)
if (
len(self.scale.shape) > 0
or len(self.loc.shape) > 0
or len(value.shape) > 0
):
loc, scale, value = paddle.broadcast_tensors(
[self.loc, self.scale, value]
)
else:
loc, scale = self.loc, self.scale
return loc, scale, value
def log_prob(self, value: float | Tensor) -> Tensor:
r"""Log probability density/mass function.
The log_prob is
.. math::
log\_prob(value) = \frac{-log(2 * \sigma) - |value - \mu|}{\sigma}
In the above equation:
* :math:`loc = \mu`: is the location parameter.
* :math:`scale = \sigma`: is the scale parameter.
Args:
value (Tensor|Scalar): The input value, can be a scalar or a tensor.
Returns:
Tensor: The log probability, whose data type is same with value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
>>> value = paddle.to_tensor(0.1)
>>> m.log_prob(value)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
-0.79314721)
"""
loc, scale, value = self._validate_value(value)
log_scale = -paddle.log(2 * scale)
return log_scale - paddle.abs(value - loc) / scale
def entropy(self) -> Tensor:
r"""Entropy of Laplace distribution.
The entropy is:
.. math::
entropy() = 1 + log(2 * \sigma)
In the above equation:
* :math:`scale = \sigma`: is the scale parameter.
Returns:
The entropy of distribution.
Examples:
.. code-block:: pycon
>>> import paddle
>>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
>>> m.entropy()
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.69314718)
"""
return 1 + paddle.log(2 * self.scale)
def cdf(self, value: float | Tensor) -> Tensor:
r"""Cumulative distribution function.
The cdf is
.. math::
cdf(value) = 0.5 - 0.5 * sign(value - \mu) * e^\frac{-|(\mu - \sigma)|}{\sigma}
In the above equation:
* :math:`loc = \mu`: is the location parameter.
* :math:`scale = \sigma`: is the scale parameter.
Args:
value (Tensor): The value to be evaluated.
Returns:
Tensor: The cumulative probability of value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
>>> value = paddle.to_tensor(0.1)
>>> m.cdf(value)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
0.54758132)
"""
loc, scale, value = self._validate_value(value)
item = (
0.5
* (value - loc).sign()
* paddle.expm1(-(value - loc).abs() / scale)
)
return 0.5 - item
def icdf(self, value: float | Tensor) -> Tensor:
r"""Inverse Cumulative distribution function.
The icdf is
.. math::
cdf^{-1}(value)= \mu - \sigma * sign(value - 0.5) * ln(1 - 2 * |value-0.5|)
In the above equation:
* :math:`loc = \mu`: is the location parameter.
* :math:`scale = \sigma`: is the scale parameter.
Args:
value (Tensor): The value to be evaluated.
Returns:
Tensor: The cumulative probability of value.
Examples:
.. code-block:: pycon
>>> import paddle
>>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
>>> value = paddle.to_tensor(0.1)
>>> m.icdf(value)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
-1.60943794)
"""
loc, scale, value = self._validate_value(value)
term = value - 0.5
return loc - scale * (term).sign() * paddle.log1p(-2 * term.abs())
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
r"""Generate samples of the specified shape.
Args:
shape(Sequence[int], optional): The shape of generated samples.
Defaults to [].
Returns:
Tensor: A sample tensor that fits the Laplace distribution.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.seed(2023)
>>> m = paddle.distribution.Laplace(paddle.to_tensor(0.0), paddle.to_tensor(1.0))
>>> m.sample() # Laplace distributed with loc=0, scale=1
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.31554604)
"""
shape = shape if isinstance(shape, tuple) else tuple(shape)
with paddle.no_grad():
return self.rsample(shape)
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
r"""Reparameterized sample.
Args:
shape(Sequence[int], optional): The shape of generated samples.
Defaults to [].
Returns:
Tensor: A sample tensor that fits the Laplace distribution.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.seed(2023)
>>> m = paddle.distribution.Laplace(paddle.to_tensor([0.0]), paddle.to_tensor([1.0]))
>>> m.rsample((1,)) # Laplace distributed with loc=0, scale=1
Tensor(shape=[1, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
[[1.31554604]])
"""
eps = self._get_eps()
shape = self._extend_shape(shape)
uniform = paddle.uniform(
shape=shape,
min=float(np.nextafter(-1, 1)) + eps / 2,
max=1.0 - eps / 2,
dtype=self.loc.dtype,
)
return self.loc - self.scale * uniform.sign() * paddle.log1p(
-uniform.abs()
)
def _get_eps(self) -> float:
"""
Get the eps of certain data type.
Note:
Since paddle.finfo is temporarily unavailable, we
use hard-coding style to get eps value.
Returns:
Float: An eps value by different data types.
"""
eps = 1.19209e-07
if (
self.loc.dtype == paddle.float64
or self.loc.dtype == paddle.complex128
):
eps = 2.22045e-16
return eps
def kl_divergence(self, other: Laplace) -> Tensor:
r"""Calculate the KL divergence KL(self || other) with two Laplace instances.
The kl_divergence between two Laplace distribution is
.. math::
KL\_divergence(\mu_0, \sigma_0; \mu_1, \sigma_1) = 0.5 (ratio^2 + (\frac{diff}{\sigma_1})^2 - 1 - 2 \ln {ratio})
.. math::
ratio = \frac{\sigma_0}{\sigma_1}
.. math::
diff = \mu_1 - \mu_0
In the above equation:
* :math:`loc = \mu`: is the location parameter of self.
* :math:`scale = \sigma`: is the scale parameter of self.
* :math:`loc = \mu_1`: is the location parameter of the reference Laplace distribution.
* :math:`scale = \sigma_1`: is the scale parameter of the reference Laplace distribution.
* :math:`ratio`: is the ratio between the two distribution.
* :math:`diff`: is the difference between the two distribution.
Args:
other (Laplace): An instance of Laplace.
Returns:
Tensor: The kl-divergence between two laplace distributions.
Examples:
.. code-block:: pycon
>>> import paddle
>>> m1 = paddle.distribution.Laplace(paddle.to_tensor([0.0]), paddle.to_tensor([1.0]))
>>> m2 = paddle.distribution.Laplace(paddle.to_tensor([1.0]), paddle.to_tensor([0.5]))
>>> m1.kl_divergence(m2)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.04261160])
"""
var_ratio = other.scale / self.scale
t = paddle.abs(self.loc - other.loc)
term1 = (self.scale * paddle.exp(-t / self.scale) + t) / other.scale
term2 = paddle.log(var_ratio)
return term1 + term2 - 1
+370
View File
@@ -0,0 +1,370 @@
# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
import operator
from collections.abc import Sequence
from functools import reduce
from typing import TYPE_CHECKING, Literal
import paddle
from paddle.base.data_feeder import check_type, convert_dtype
from paddle.base.framework import Variable
from paddle.distribution import distribution
from paddle.distribution.beta import Beta
from paddle.framework import in_dynamic_mode
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing.dtype_like import _DTypeLiteral
__all__ = ["LKJCholesky"]
def mvlgamma(a, p):
"""
Computes the multivariate log gamma function for input `a` and dimension `p`.
"""
pi = paddle.to_tensor(math.pi, dtype=a.dtype)
j = paddle.arange(1, p + 1, dtype=a.dtype)
gammaln_terms = paddle.lgamma(a.unsqueeze(-1) + (1 - j) / 2)
gammaln_sum = paddle.sum(gammaln_terms, axis=-1)
return (p * (p - 1) / 4) * paddle.log(pi) + gammaln_sum
def tril_indices(n, k=0):
"""
Returns the indices of the lower triangular part of an n x n matrix, including the k-th diagonal.
"""
full_matrix = paddle.ones((n, n), dtype='int32')
tril_matrix = paddle.tril(full_matrix, diagonal=k)
rows, cols = paddle.nonzero(tril_matrix, as_tuple=True)
return rows.flatten(), cols.flatten()
def matrix_to_tril(x, diagonal=0):
"""
Extracts the lower triangular part of the input matrix or batch of matrices `x`, including the specified diagonal.
"""
tril_mask = paddle.tril(paddle.ones_like(x), diagonal=diagonal)
tril_elements = paddle.masked_select(x, tril_mask.astype('bool'))
return tril_elements
def vec_to_tril_matrix(
p_flatten, dim, last_dim, flatten_shape, sample_shape=(), diag=0
):
"""
Constructs a batch of lower triangular matrices from a given input tensor `p`.
"""
# Calculate the dimension of the square matrix based on the last but one dimension of `p`
# Define the output shape, which adds two dimensions for the square matrix
shape0 = flatten_shape // last_dim
output_shape = (
*sample_shape,
shape0 // reduce(operator.mul, sample_shape, 1),
dim,
dim,
)
# Create index_matrix = [index0, rows, cols]
rows, cols = paddle.meshgrid(paddle.arange(dim), paddle.arange(dim))
mask = rows > cols
lower_indices = paddle.stack([rows[mask], cols[mask]], axis=1)
repeated_lower_indices = paddle.repeat_interleave(
lower_indices, shape0, axis=0
)
index0 = paddle.arange(shape0).unsqueeze(1).tile([last_dim, 1])
index_matrix = paddle.concat([index0, repeated_lower_indices], axis=1)
# Sort the indices
sorted_indices = paddle.argsort(index_matrix[:, 0])
index_matrix = index_matrix[sorted_indices]
# Set the value
matrix = paddle.zeros(shape=(shape0, dim, dim), dtype=p_flatten.dtype)
matrix = paddle.scatter_nd_add(matrix, index_matrix, p_flatten).reshape(
output_shape
)
return matrix
def tril_matrix_to_vec(mat: Tensor, diag: int = 0) -> Tensor:
r"""
Convert a `D x D` matrix or a batch of matrices into a (batched) vector
which comprises of lower triangular elements from the matrix in row order.
"""
out_shape = mat.shape[:-2]
n = mat.shape[-1]
if diag < -n or diag >= n:
raise ValueError(f"diag ({diag}) provided is outside [{-n}, {n - 1}].")
rows, cols = paddle.meshgrid(paddle.arange(n), paddle.arange(n))
tril_mask = diag + rows >= cols
vec_len = (n + diag) * (n + diag + 1) // 2
out_shape += (vec_len,)
# Use the mask to index the lower triangular elements from the input matrix
tril_mask = paddle.broadcast_to(tril_mask, mat.shape)
vec = paddle.masked_select(mat, tril_mask).reshape(out_shape)
return vec
class LKJCholesky(distribution.Distribution):
"""
The LKJCholesky class represents the LKJ distribution over Cholesky factors of correlation matrices.
This class implements the LKJ distribution over Cholesky factors of correlation matrices, as described in
Lewandowski, Kurowicka, and Joe (2009). It supports two sampling methods: "onion" and "cvine".
Args:
dim (int): The dimension of the correlation matrices.
concentration (float, optional): The concentration parameter of the LKJ distribution. Default is 1.0.
sample_method (str, optional): The sampling method to use, either "onion" or "cvine". Default is "onion".
Example:
.. code-block:: pycon
>>> import paddle
>>> dim = 3
>>> lkj = paddle.distribution.LKJCholesky(dim=dim)
>>> sample = lkj.sample()
>>> sample.shape
paddle.Size([3, 3])
"""
concentration: Tensor
dtype: _DTypeLiteral
dim: int
sample_method: Literal["onion", "cvine"]
def __init__(
self,
dim: int = 2,
concentration: float = 1.0,
sample_method: Literal["onion", "cvine"] = "onion",
) -> None:
if not in_dynamic_mode():
check_type(
dim,
"dim",
(int, Variable, paddle.pir.Value),
"LKJCholesky",
)
check_type(
concentration,
"concentration",
(float, list, tuple, Variable, paddle.pir.Value),
"LKJCholesky",
)
# Get/convert concentration/rate to tensor.
if self._validate_args(concentration):
self.concentration = concentration
self.dtype = convert_dtype(concentration.dtype)
else:
[self.concentration] = self._to_tensor(concentration)
self.dtype = paddle.get_default_dtype()
self.dim = dim
if not self.dim >= 2:
raise ValueError(
f"Expected dim greater than or equal to 2. Found dim={dim}."
)
elif not isinstance(self.dim, int):
raise TypeError(f"Expected dim to be an integer. Found dim={dim}.")
if in_dynamic_mode():
if not paddle.all(self.concentration > 0):
raise ValueError("The arg of `concentration` must be positive.")
self.sample_method = sample_method
batch_shape = self.concentration.shape
event_shape = (dim, dim)
# This is used to draw vectorized samples from the beta distribution in Sec. 3.2 of [1].
marginal_conc = self.concentration + 0.5 * (self.dim - 2)
offset = paddle.arange(
self.dim - 1,
dtype=self.concentration.dtype,
)
if sample_method == "onion":
offset = paddle.concat(
[paddle.zeros((1,), dtype=offset.dtype), offset]
)
beta_conc1 = offset + 0.5
beta_conc0 = marginal_conc.unsqueeze(-1) - 0.5 * offset
self._beta = Beta(beta_conc1, beta_conc0)
elif sample_method == "cvine":
offset_tril = matrix_to_tril(
paddle.broadcast_to(0.5 * offset, [self.dim - 1, self.dim - 1])
)
beta_conc = marginal_conc.unsqueeze(-1) - offset_tril
self._beta = Beta(beta_conc, beta_conc)
else:
raise ValueError("`method` should be one of 'cvine' or 'onion'.")
super().__init__(batch_shape, event_shape)
def _onion(self, sample_shape: Sequence[int]) -> Tensor:
"""Generate a sample using the "onion" method.
Args:
sample_shape (tuple): The shape of the samples to be generated.
Returns:
w (Tensor): The Cholesky factor of the sampled correlation matrix.
"""
# Sample y from the Beta distribution
y = self._beta.sample(sample_shape).unsqueeze(-1)
# Sample u from the standard normal distribution and create a lower triangular matrix
u_normal = paddle.randn(
self._extend_shape(sample_shape), dtype=y.dtype
).tril(-1)
# Normalize u to get u_hypersphere
u_hypersphere = u_normal / u_normal.norm(axis=-1, keepdim=True)
# Replace NaNs in first row
# TODO: check if static graph can use fill_
# u_hypersphere[..., 0, :].fill_(0.0)
# u_hypersphere[..., 0, :] = 0.0
u_hypersphere_other = u_hypersphere[..., 1:, :]
zero_shape = (*tuple(u_hypersphere.shape[:-2]), 1, self.dim)
zero_row = paddle.zeros(shape=zero_shape, dtype=u_hypersphere.dtype)
u_hypersphere = paddle.concat([zero_row, u_hypersphere_other], axis=-2)
w = paddle.sqrt(y) * u_hypersphere
# Fill diagonal elements; clamp for numerical stability
eps = paddle.finfo(w.dtype).tiny
diag_elems = paddle.clip(1 - paddle.sum(w**2, axis=-1), min=eps).sqrt()
w += paddle.diag_embed(diag_elems)
return w
def _cvine(self, sample_shape: Sequence[int]) -> Tensor:
"""Generate a sample using the "cvine" method.
Args:
sample_shape (tuple): The shape of the samples to be generated.
Returns:
r (Tensor): The Cholesky factor of the sampled correlation matrix.
"""
# Sample beta and calculate partial correlations
beta_sample = self._beta.sample(sample_shape).unsqueeze(-1)
partial_correlation = 2 * beta_sample - 1
if self.dim == 2:
partial_correlation = partial_correlation.unsqueeze(-2)
# Construct the lower triangular matrix from the partial correlations
last_dim = self.dim * (self.dim - 1) // 2
flatten_shape = last_dim * reduce(operator.mul, sample_shape, 1)
if len(self.concentration.shape) != 0:
flatten_shape *= self.concentration.shape[-1]
partial_correlation = partial_correlation.reshape((flatten_shape,))
partial_correlation = vec_to_tril_matrix(
partial_correlation,
self.dim,
last_dim,
flatten_shape,
sample_shape,
-1,
)
# Clip partial correlations for numerical stability
eps = paddle.finfo(beta_sample.dtype).tiny
r = paddle.clip(partial_correlation, min=(-1 + eps), max=(1 - eps))
# Calculate the cumulative product of the square root of 1 - z
z = r**2
z1m_cumprod_sqrt = paddle.cumprod(paddle.sqrt(1 - z), dim=-1)
# Shift the elements and pad with 1.0
pad_width = [0, 0] * (z1m_cumprod_sqrt.ndim - 1) + [1, 0]
z1m_cumprod_sqrt_shifted = paddle.nn.functional.pad(
z1m_cumprod_sqrt[..., :-1],
pad=pad_width,
mode="constant",
value=1.0,
)
# Calculate the final Cholesky factor
r += paddle.eye(
partial_correlation.shape[-2], partial_correlation.shape[-1]
)
r = r * z1m_cumprod_sqrt_shifted
return r
def sample(self, sample_shape: Sequence[int] = []) -> Tensor:
"""Generate a sample using the specified sampling method."""
if not isinstance(sample_shape, Sequence):
raise TypeError('sample shape must be Sequence object.')
if self.sample_method == "onion":
res = self._onion(sample_shape)
else:
res = self._cvine(sample_shape)
output_shape = list(sample_shape)
output_shape.extend(self.concentration.shape)
output_shape.extend([self.dim, self.dim])
return res.reshape(output_shape)
def log_prob(self, value: Tensor) -> Tensor:
r"""Compute the log probability density of the given Cholesky factor under the LKJ distribution.
Args:
value (Tensor): The Cholesky factor of the correlation matrix for which the log probability density is to be computed.
Returns:
log_prob (Tensor): The log probability density of the given Cholesky factor under the LKJ distribution.
"""
# 1.Compute the order vector.
diag_elems = paddle.diagonal(value, offset=0, axis1=-1, axis2=-2)[
..., 1:
]
order = paddle.arange(2, self.dim + 1, dtype=self.concentration.dtype)
order = 2 * (self.concentration - 1).unsqueeze(-1) + self.dim - order
# 2.Compute the unnormalized log probability density
unnormalized_log_pdf = paddle.sum(
order * paddle.log(diag_elems), axis=-1
)
# 3.Compute the normalization constant (page 1999 of [1])
dm1 = self.dim - 1
alpha = self.concentration + 0.5 * dm1
denominator = paddle.lgamma(alpha) * dm1
numerator = mvlgamma(alpha - 0.5, dm1)
# 4.Compute the constant term related to pi
# pi_constant in [1] is D * (D - 1) / 4 * log(pi)
# pi_constant in multigammaln is (D - 1) * (D - 2) / 4 * log(pi)
# hence, we need to add a pi_constant = (D - 1) * log(pi) / 2
pi_constant = 0.5 * dm1 * math.log(math.pi)
# 5.Compute the normalization term and return the final log probability density:
normalize_term = pi_constant + numerator - denominator
return unnormalized_log_pdf - normalize_term
+218
View File
@@ -0,0 +1,218 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import paddle
from paddle.distribution.normal import Normal
from paddle.distribution.transform import ExpTransform
from paddle.distribution.transformed_distribution import TransformedDistribution
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import TypeAlias
import numpy as np
import numpy.typing as npt
from paddle import Tensor
from paddle._typing import NestedSequence
_LognormalLocBase: TypeAlias = float | complex
_LognormalLocNDArray: TypeAlias = (
np.float32 | np.float64 | np.complex64 | np.complex128
)
_LognormalLoc: TypeAlias = (
_LognormalLocBase
| Sequence[_LognormalLocBase]
| NestedSequence[_LognormalLocBase]
| npt.NDArray[_LognormalLocNDArray]
| Tensor
)
_LognormalScale: TypeAlias = (
float
| Sequence[float]
| NestedSequence[float]
| npt.NDArray[np.float32 | np.float64]
| Tensor
)
class LogNormal(TransformedDistribution):
r"""The LogNormal distribution with location `loc` and `scale` parameters.
.. math::
X \sim Normal(\mu, \sigma)
Y = exp(X) \sim LogNormal(\mu, \sigma)
Due to LogNormal distribution is based on the transformation of Normal distribution, we call that :math:`Normal(\mu, \sigma)` is the underlying distribution of :math:`LogNormal(\mu, \sigma)`
Mathematical details
The probability density function (pdf) is
.. math::
pdf(x; \mu, \sigma) = \frac{1}{\sigma x \sqrt{2\pi}}e^{(-\frac{(ln(x) - \mu)^2}{2\sigma^2})}
In the above equation:
* :math:`loc = \mu`: is the means of the underlying Normal distribution.
* :math:`scale = \sigma`: is the stddevs of the underlying Normal distribution.
Args:
loc(int|float|complex|list|tuple|numpy.ndarray|Tensor): The means of the underlying Normal distribution.The data type is float32, float64, complex64 and complex128.
scale(int|float|list|tuple|numpy.ndarray|Tensor): The stddevs of the underlying Normal distribution.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import LogNormal
>>> # Define a single scalar LogNormal distribution.
>>> dist = LogNormal(loc=0.0, scale=3.0)
>>> # Define a batch of two scalar valued LogNormals.
>>> # The underlying Normal of first has mean 1 and standard deviation 11, the underlying Normal of second 2 and 22.
>>> dist = LogNormal(loc=[1.0, 2.0], scale=[11.0, 22.0])
>>> # Get 3 samples, returning a 3 x 2 tensor.
>>> dist.sample((3,))
>>> # Define a batch of two scalar valued LogNormals.
>>> # Their underlying Normal have mean 1, but different standard deviations.
>>> dist = LogNormal(loc=1.0, scale=[11.0, 22.0])
>>> # Complete example
>>> value_tensor = paddle.to_tensor([0.8], dtype="float32")
>>> lognormal_a = LogNormal([0.0], [1.0])
>>> lognormal_b = LogNormal([0.5], [2.0])
>>> sample = lognormal_a.sample((2,))
>>> # a random tensor created by lognormal distribution with shape: [2, 1]
>>> entropy = lognormal_a.entropy()
>>> print(entropy)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.41893852])
>>> lp = lognormal_a.log_prob(value_tensor)
>>> print(lp)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[-0.72069150])
>>> p = lognormal_a.probs(value_tensor)
>>> print(p)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.48641577])
>>> kl = lognormal_a.kl_divergence(lognormal_b)
>>> print(kl)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.34939718])
"""
loc: Tensor
scale: Tensor
def __init__(self, loc: _LognormalLoc, scale: _LognormalScale) -> None:
self._base = Normal(loc=loc, scale=scale)
self.loc = self._base.loc
self.scale = self._base.scale
super().__init__(self._base, [ExpTransform()])
@property
def mean(self) -> Tensor:
"""Mean of lognormal distribution.
Returns:
Tensor: mean value.
"""
return paddle.exp(self._base.mean + self._base.variance / 2)
@property
def variance(self) -> Tensor:
"""Variance of lognormal distribution.
Returns:
Tensor: variance value.
"""
return paddle.expm1(self._base.variance) * paddle.exp(
2 * self._base.mean + self._base.variance
)
def entropy(self) -> Tensor:
r"""Shannon entropy in nats.
The entropy is
.. math::
entropy(\sigma) = 0.5 \log (2 \pi e \sigma^2) + \mu
In the above equation:
* :math:`loc = \mu`: is the mean of the underlying Normal distribution.
* :math:`scale = \sigma`: is the stddevs of the underlying Normal distribution.
Returns:
Tensor: Shannon entropy of lognormal distribution.
"""
return self._base.entropy() + self._base.mean
def probs(self, value: Tensor) -> Tensor:
"""Probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: probability.The data type is same with :attr:`value` .
"""
return paddle.exp(self.log_prob(value))
def kl_divergence(self, other: LogNormal) -> Tensor:
r"""The KL-divergence between two lognormal distributions.
The probability density function (pdf) is
.. math::
KL\_divergence(\mu_0, \sigma_0; \mu_1, \sigma_1) = 0.5 (ratio^2 + (\frac{diff}{\sigma_1})^2 - 1 - 2 \ln {ratio})
.. math::
ratio = \frac{\sigma_0}{\sigma_1}
.. math::
diff = \mu_1 - \mu_0
In the above equation:
* :math:`loc = \mu_0`: is the means of current underlying Normal distribution.
* :math:`scale = \sigma_0`: is the stddevs of current underlying Normal distribution.
* :math:`loc = \mu_1`: is the means of other underlying Normal distribution.
* :math:`scale = \sigma_1`: is the stddevs of other underlying Normal distribution.
* :math:`ratio`: is the ratio of scales.
* :math:`diff`: is the difference between means.
Args:
other (LogNormal): instance of LogNormal.
Returns:
Tensor: kl-divergence between two lognormal distributions.
"""
return self._base.kl_divergence(other._base)
+212
View File
@@ -0,0 +1,212 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from collections.abc import Iterable
from typing import TYPE_CHECKING
import paddle
from paddle.distribution import categorical, distribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from paddle import Tensor
class Multinomial(distribution.Distribution):
r"""
Multinomial distribution parameterized by :attr:`total_count` and
:attr:`probs`.
In probability theory, the multinomial distribution is a generalization of
the binomial distribution, it models the probability of counts for each side
of a k-sided die rolled n times. When k is 2 and n is 1, the multinomial is
the bernoulli distribution, when k is 2 and n is grater than 1, it is the
binomial distribution, when k is grater than 2 and n is 1, it is the
categorical distribution.
The probability mass function (PMF) for multinomial is
.. math::
f(x_1, ..., x_k; n, p_1,...,p_k) = \frac{n!}{x_1!...x_k!}p_1^{x_1}...p_k^{x_k}
where, :math:`n` is number of trials, k is the number of categories,
:math:`p_i` denote probability of a trial falling into each category,
:math:`{\textstyle \sum_{i=1}^{k}p_i=1}, p_i \ge 0`, and :math:`x_i` denote
count of each category.
Args:
total_count (int): Number of trials.
probs (Tensor): Probability of a trial falling into each category. Last
axis of probs indexes over categories, other axes index over batches.
Probs value should between [0, 1], and sum to 1 along last axis. If
the value over 1, it will be normalized to sum to 1 along the last
axis.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.seed(2023)
>>> multinomial = paddle.distribution.Multinomial(10, paddle.to_tensor([0.2, 0.3, 0.5]))
>>> print(multinomial.sample((2, 3)))
Tensor(shape=[2, 3, 3], dtype=float32, place=Place(cpu), stop_gradient=True,
[[[1., 5., 4.],
[0., 4., 6.],
[1., 3., 6.]],
[[2., 2., 6.],
[0., 6., 4.],
[3., 3., 4.]]])
"""
total_count: int
probs: Tensor
def __init__(self, total_count: int, probs: Tensor) -> None:
if not isinstance(total_count, int) or total_count < 1:
raise ValueError(
'input parameter total_count must be int type and grater than zero.'
)
if probs.dim() < 1:
raise ValueError(
'probs parameter should not be none and over one dimension'
)
self.probs = probs / probs.sum(-1, keepdim=True)
self.total_count = total_count
self._categorical = categorical.Categorical(
logits=self._probs_to_logits(probs)
)
super().__init__(probs.shape[:-1], probs.shape[-1:])
@property
def mean(self) -> Tensor:
"""mean of multinomial distribution.
Returns:
Tensor: mean value.
"""
return self.probs * self.total_count
@property
def variance(self) -> Tensor:
"""variance of multinomial distribution.
Returns:
Tensor: variance value.
"""
return self.total_count * self.probs * (1 - self.probs)
def prob(self, value: Tensor) -> Tensor:
"""probability mass function evaluated at value.
Args:
value (Tensor): value to be evaluated.
Returns:
Tensor: probability of value.
"""
return paddle.exp(self.log_prob(value))
def log_prob(self, value: Tensor) -> Tensor:
"""probability mass function evaluated at value.
Args:
value (Tensor): value to be evaluated.
Returns:
Tensor: probability of value.
"""
if paddle.is_integer(value):
value = paddle.cast(value, self.probs.dtype)
logits, value = paddle.broadcast_tensors(
[paddle.log(self.probs), value]
)
if paddle.in_dynamic_mode():
logits[(value == 0) & (paddle.isinf(logits))] = 0
else:
logits = paddle.static.setitem(
logits, (value == 0) & (paddle.isinf(logits)), 0
)
return (
paddle.lgamma(value.sum(-1) + 1)
- paddle.lgamma(value + 1).sum(-1)
+ (value * logits).sum(-1)
)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Iterable[int] = []) -> Tensor:
"""draw sample data from multinomial distribution
Args:
sample_shape (list|tuple, optional): [description]. Defaults to [].
"""
if not isinstance(shape, Iterable):
raise TypeError('sample shape must be Iterable object.')
samples = self._categorical.sample([self.total_count, *list(shape)])
return (
paddle.nn.functional.one_hot(samples, self.probs.shape[-1])
.cast(self.probs.dtype)
.sum(0)
)
def entropy(self) -> Tensor:
"""entropy of multinomial distribution
Returns:
Tensor: entropy value
"""
n = paddle.full(
shape=[], fill_value=self.total_count, dtype=self.probs.dtype
)
support = paddle.arange(
self.total_count + 1, dtype=self.probs.dtype
).reshape((-1,) + (1,) * len(self.probs.shape))[1:]
binomial_pmf = paddle.exp(self._binomial_logpmf(n, support))
return (n * self._categorical.entropy() - paddle.lgamma(n + 1)) + (
(binomial_pmf * paddle.lgamma(support + 1)).sum([0, -1])
)
def _binomial_logpmf(self, count: Tensor, value: Tensor) -> Tensor:
logits = self._probs_to_logits(self.probs, is_binary=True)
factor_n = paddle.lgamma(count + 1)
factor_k = paddle.lgamma(value + 1)
factor_nmk = paddle.lgamma(count - value + 1)
norm = (
count * _clip_by_zero(logits)
+ count * paddle.log1p(paddle.exp(-paddle.abs(logits)))
- factor_n
)
return value * logits - factor_k - factor_nmk - norm
def _binomial_support(count, dtype):
return paddle.arange(count + 1, dtype=dtype)
def _clip_by_zero(x):
# like clip(x, min=0) but grad at 0 is 0.5
return (x.clip(min=0) + x - x.clip(max=0)) / 2
@@ -0,0 +1,563 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
from collections.abc import Sequence
from typing import TYPE_CHECKING
import paddle
from paddle.base.data_feeder import convert_dtype
from paddle.distribution import constraint, distribution
from paddle.framework import in_dynamic_mode
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing.dtype_like import _DTypeLiteral
class MultivariateNormal(distribution.Distribution):
r"""The Multivariate Normal distribution is a type multivariate continuous distribution defined on the real set, with parameter: `loc` and any one
of the following parameters characterizing the variance: `covariance_matrix`, `precision_matrix`, `scale_tril`.
Mathematical details
The probability density function (pdf) is
.. math::
p(X ;\mu, \Sigma) = \frac{1}{\sqrt{(2\pi)^k |\Sigma|}} \exp(-\frac{1}{2}(X - \mu)^{\intercal} \Sigma^{-1} (X - \mu))
In the above equation:
* :math:`X`: is a k-dim random vector.
* :math:`loc = \mu`: is the k-dim mean vector.
* :math:`covariance_matrix = \Sigma`: is the k-by-k covariance matrix.
Args:
loc(int|float|Tensor): The mean of Multivariate Normal distribution. If the input data type is int or float, the data type of `loc` will be
convert to a 1-D Tensor the paddle global default dtype.
covariance_matrix(Tensor|None): The covariance matrix of Multivariate Normal distribution. The data type of `covariance_matrix` will be convert
to be the same as the type of loc.
precision_matrix(Tensor|None): The inverse of the covariance matrix. The data type of `precision_matrix` will be convert to be the same as the
type of loc.
scale_tril(Tensor|None): The cholesky decomposition (lower triangular matrix) of the covariance matrix. The data type of `scale_tril` will be
convert to be the same as the type of loc.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import MultivariateNormal
>>> paddle.set_device("cpu")
>>> paddle.seed(100)
>>> rv = MultivariateNormal(
... loc=paddle.to_tensor([2.0, 5.0]),
... covariance_matrix=paddle.to_tensor([[2.0, 1.0], [1.0, 2.0]]),
... )
>>> print(rv.sample([3, 2]))
Tensor(shape=[3, 2, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
[[[-0.00339603, 4.31556797],
[ 2.01385283, 4.63553190]],
[[ 0.10132277, 3.11323833],
[ 2.37435842, 3.56635118]],
[[ 2.89701366, 5.10602522],
[-0.46329355, 3.14768648]]])
>>> print(rv.mean)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[2., 5.])
>>> print(rv.variance)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.99999988, 2. ])
>>> print(rv.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
3.38718319)
>>> rv1 = MultivariateNormal(
... loc=paddle.to_tensor([2.0, 5.0]),
... covariance_matrix=paddle.to_tensor([[2.0, 1.0], [1.0, 2.0]]),
... )
>>> rv2 = MultivariateNormal(
... loc=paddle.to_tensor([-1.0, 3.0]), covariance_matrix=paddle.to_tensor([[3.0, 2.0], [2.0, 3.0]])
... )
>>> print(rv1.kl_divergence(rv2))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
1.55541301)
"""
loc: Tensor
covariance_matrix: Tensor | None
precision_matrix: Tensor | None
scale_tril: Tensor | None
dtype: _DTypeLiteral
arg_constraints = {
"loc": constraint.real_vector,
"covariance_matrix": constraint.positive_definite,
"precision_matrix": constraint.positive_definite,
"scale_tril": constraint.lower_cholesky,
}
support = constraint.real_vector
has_rsample = True
def __init__(
self,
loc: float | Tensor,
covariance_matrix: Tensor | None = None,
precision_matrix: Tensor | None = None,
scale_tril: Tensor | None = None,
validate_args: bool | None = None,
):
self.dtype = paddle.get_default_dtype()
if isinstance(loc, (float, int)):
loc = paddle.to_tensor([loc], dtype=self.dtype)
else:
self.dtype = convert_dtype(loc.dtype)
if loc.dim() < 1:
raise ValueError("loc must be at least one-dimensional.")
if (covariance_matrix is not None) + (scale_tril is not None) + (
precision_matrix is not None
) != 1:
raise ValueError(
"Exactly one of covariance_matrix or precision_matrix or scale_tril may be specified."
)
if scale_tril is not None:
if scale_tril.dim() < 2:
raise ValueError(
"scale_tril matrix must be at least two-dimensional, "
"with optional leading batch dimensions"
)
scale_tril = paddle.cast(scale_tril, dtype=self.dtype)
batch_shape = paddle.broadcast_shape(
scale_tril.shape[:-2], loc.shape[:-1]
)
self.scale_tril = scale_tril.expand(
[*batch_shape, scale_tril.shape[-2], scale_tril.shape[-1]]
)
elif covariance_matrix is not None:
if covariance_matrix.dim() < 2:
raise ValueError(
"covariance_matrix must be at least two-dimensional, "
"with optional leading batch dimensions"
)
covariance_matrix = paddle.cast(covariance_matrix, dtype=self.dtype)
batch_shape = paddle.broadcast_shape(
covariance_matrix.shape[:-2], loc.shape[:-1]
)
self.covariance_matrix = covariance_matrix.expand(
[
*batch_shape,
covariance_matrix.shape[-2],
covariance_matrix.shape[-1],
]
)
else:
if precision_matrix.dim() < 2:
raise ValueError(
"precision_matrix must be at least two-dimensional, "
"with optional leading batch dimensions"
)
precision_matrix = paddle.cast(precision_matrix, dtype=self.dtype)
batch_shape = paddle.broadcast_shape(
precision_matrix.shape[:-2], loc.shape[:-1]
)
self.precision_matrix = precision_matrix.expand(
[
*batch_shape,
precision_matrix.shape[-2],
precision_matrix.shape[-1],
]
)
self.loc = loc.expand([*batch_shape, -1])
event_shape = self.loc.shape[-1:]
super().__init__(batch_shape, event_shape, validate_args=validate_args)
if in_dynamic_mode() and self._validate_args_enabled:
self._validate_parameters(
scale_tril=scale_tril,
covariance_matrix=covariance_matrix,
precision_matrix=precision_matrix,
)
if scale_tril is not None:
self._unbroadcasted_scale_tril = scale_tril
elif covariance_matrix is not None:
self._unbroadcasted_scale_tril = paddle.linalg.cholesky(
covariance_matrix
)
else:
self._unbroadcasted_scale_tril = precision_to_scale_tril(
precision_matrix
)
def _validate_parameters(
self,
*,
scale_tril: Tensor | None = None,
covariance_matrix: Tensor | None = None,
precision_matrix: Tensor | None = None,
) -> None:
if scale_tril is not None:
matrix_name = "scale_tril"
matrix_value = self.scale_tril
elif covariance_matrix is not None:
matrix_name = "covariance_matrix"
matrix_value = self.covariance_matrix
else:
matrix_name = "precision_matrix"
matrix_value = self.precision_matrix
for param, value in (
("loc", self.loc),
(matrix_name, matrix_value),
):
constraint_ = self.arg_constraints[param]
valid = constraint_.check(value)
if not bool(valid.all()):
raise ValueError(
f"Expected parameter {param} "
f"({type(value).__name__} of shape {tuple(value.shape)}) "
f"of distribution {self!r} "
f"to satisfy the constraint {constraint_!r}, "
f"but found invalid values:\n{value}"
)
def expand(self, batch_shape, _instance=None):
new = (
self.__class__.__new__(self.__class__)
if _instance is None
else _instance
)
batch_shape = tuple(batch_shape)
loc_shape = batch_shape + self.event_shape
cov_shape = batch_shape + self.event_shape + self.event_shape
new.loc = self.loc.expand(loc_shape)
new._unbroadcasted_scale_tril = self._unbroadcasted_scale_tril
if "_covariance_matrix" in self.__dict__:
new._covariance_matrix = self.covariance_matrix.expand(cov_shape)
if "_scale_tril" in self.__dict__:
new._scale_tril = self.scale_tril.expand(cov_shape)
if "_precision_matrix" in self.__dict__:
new._precision_matrix = self.precision_matrix.expand(cov_shape)
super(MultivariateNormal, new).__init__(
batch_shape, self.event_shape, validate_args=False
)
new._validate_args_enabled = self._validate_args_enabled
return new
@property
def scale_tril(self) -> Tensor:
if "_scale_tril" not in self.__dict__:
self._scale_tril = self._unbroadcasted_scale_tril.expand(
self._batch_shape + self._event_shape + self._event_shape
)
return self._scale_tril
@scale_tril.setter
def scale_tril(self, value: Tensor) -> None:
self._scale_tril = value
@property
def covariance_matrix(self) -> Tensor:
if "_covariance_matrix" not in self.__dict__:
new_perm = list(range(len(self._unbroadcasted_scale_tril.shape)))
new_perm[-1], new_perm[-2] = new_perm[-2], new_perm[-1]
self._covariance_matrix = paddle.matmul(
self._unbroadcasted_scale_tril,
self._unbroadcasted_scale_tril.transpose(new_perm),
).expand(self._batch_shape + self._event_shape + self._event_shape)
return self._covariance_matrix
@covariance_matrix.setter
def covariance_matrix(self, value: Tensor) -> None:
self._covariance_matrix = value
@property
def precision_matrix(self) -> Tensor:
if "_precision_matrix" not in self.__dict__:
self._precision_matrix = paddle.linalg.cholesky_inverse(
self._unbroadcasted_scale_tril
).expand(self._batch_shape + self._event_shape + self._event_shape)
return self._precision_matrix
@precision_matrix.setter
def precision_matrix(self, value: Tensor) -> None:
self._precision_matrix = value
@property
def mean(self) -> Tensor:
"""Mean of Multivariate Normal distribution.
Returns:
Tensor: mean value.
"""
return self.loc
@property
def variance(self) -> Tensor:
"""Variance of Multivariate Normal distribution.
Returns:
Tensor: variance value.
"""
return (
paddle.square(self._unbroadcasted_scale_tril)
.sum(-1)
.expand(self._batch_shape + self._event_shape)
)
@property
def mode(self) -> Tensor:
return self.loc
@mode.setter
def mode(self, value: Tensor) -> None:
self.loc = value
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate Multivariate Normal samples of the specified shape. The final shape would be ``sample_shape + batch_shape + event_shape``.
Args:
shape (Sequence[int], optional): Prepended shape of the generated samples.
Returns:
Tensor, Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`. The data type is the same as `self.loc`.
"""
with paddle.no_grad():
return self.rsample(shape)
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate Multivariate Normal samples of the specified shape. The final shape would be ``sample_shape + batch_shape + event_shape``.
Args:
shape (Sequence[int], optional): Prepended shape of the generated samples.
Returns:
Tensor, Sampled data with shape `sample_shape` + `batch_shape` + `event_shape`. The data type is the same as `self.loc`.
"""
if not isinstance(shape, Sequence):
raise TypeError('sample shape must be Sequence object.')
output_shape = self._extend_shape(shape)
eps = paddle.cast(paddle.normal(shape=output_shape), dtype=self.dtype)
return self.loc + paddle.matmul(
self._unbroadcasted_scale_tril, eps.unsqueeze(-1)
).squeeze(-1)
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: log probability. The data type is the same as `self.loc`.
"""
value = paddle.cast(value, dtype=self.dtype)
if in_dynamic_mode() and self._validate_args_enabled:
self._validate_sample(value)
diff = value - self.loc
M = batch_mahalanobis(self._unbroadcasted_scale_tril, diff)
half_log_det = (
self._unbroadcasted_scale_tril.diagonal(axis1=-2, axis2=-1)
.log()
.sum(-1)
)
return (
-0.5 * (self._event_shape[0] * math.log(2 * math.pi) + M)
- half_log_det
)
def prob(self, value: Tensor) -> Tensor:
"""Probability density function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: probability. The data type is the same as `self.loc`.
"""
return paddle.exp(self.log_prob(value))
def entropy(self) -> Tensor:
r"""Shannon entropy in nats.
The entropy is
.. math::
\mathcal{H}(X) = \frac{n}{2} \log(2\pi) + \log {\det A} + \frac{n}{2}
In the above equation:
* :math:`\Omega`: is the support of the distribution.
Returns:
Tensor, Shannon entropy of Multivariate Normal distribution. The data type is the same as `self.loc`.
"""
half_log_det = (
self._unbroadcasted_scale_tril.diagonal(axis1=-2, axis2=-1)
.log()
.sum(-1)
)
H = (
0.5 * self._event_shape[0] * (1.0 + math.log(2 * math.pi))
+ half_log_det
)
if len(self._batch_shape) == 0:
return H
else:
return H.expand(self._batch_shape)
def kl_divergence(self, other: MultivariateNormal) -> Tensor:
r"""The KL-divergence between two poisson distributions with the same `batch_shape` and `event_shape`.
The probability density function (pdf) is
.. math::
KL\_divergence(\lambda_1, \lambda_2) = \log(\det A_2) - \log(\det A_1) -\frac{n}{2} +\frac{1}{2}[tr [\Sigma_2^{-1} \Sigma_1] + (\mu_1 - \mu_2)^{\intercal} \Sigma_2^{-1} (\mu_1 - \mu_2)]
Args:
other (MultivariateNormal): instance of Multivariate Normal.
Returns:
Tensor, kl-divergence between two Multivariate Normal distributions. The data type is the same as `self.loc`.
"""
if (
self._batch_shape != other._batch_shape
and self._event_shape != other._event_shape
):
raise ValueError(
"KL divergence of two Multivariate Normal distributions should share the same `batch_shape` and `event_shape`."
)
half_log_det_1 = (
self._unbroadcasted_scale_tril.diagonal(axis1=-2, axis2=-1)
.log()
.sum(-1)
)
half_log_det_2 = (
other._unbroadcasted_scale_tril.diagonal(axis1=-2, axis2=-1)
.log()
.sum(-1)
)
new_perm = list(range(len(self._unbroadcasted_scale_tril.shape)))
new_perm[-1], new_perm[-2] = new_perm[-2], new_perm[-1]
cov_mat_1 = paddle.matmul(
self._unbroadcasted_scale_tril,
self._unbroadcasted_scale_tril.transpose(new_perm),
)
cov_mat_2 = paddle.matmul(
other._unbroadcasted_scale_tril,
other._unbroadcasted_scale_tril.transpose(new_perm),
)
expectation = (
paddle.linalg.solve(cov_mat_2, cov_mat_1)
.diagonal(axis1=-2, axis2=-1)
.sum(-1)
)
expectation += batch_mahalanobis(
other._unbroadcasted_scale_tril, self.loc - other.loc
)
return (
half_log_det_2
- half_log_det_1
+ 0.5 * (expectation - self._event_shape[0])
)
def precision_to_scale_tril(P: Tensor) -> Tensor:
"""Convert precision matrix to scale tril matrix
Args:
P (Tensor): input precision matrix
Returns:
Tensor: scale tril matrix
"""
Lf = paddle.linalg.cholesky(paddle.flip(P, (-2, -1)))
tmp = paddle.flip(Lf, (-2, -1))
new_perm = list(range(len(tmp.shape)))
new_perm[-2], new_perm[-1] = new_perm[-1], new_perm[-2]
L_inv = paddle.transpose(tmp, new_perm)
Id = paddle.eye(P.shape[-1], dtype=P.dtype)
L = paddle.linalg.triangular_solve(L_inv, Id, upper=False)
return L
def batch_mahalanobis(bL: Tensor, bx: Tensor) -> Tensor:
r"""
Computes the squared Mahalanobis distance of the Multivariate Normal distribution with cholesky decomposition of the covariance matrix.
Accepts batches for both bL and bx.
Args:
bL (Tensor): scale trial matrix (batched)
bx (Tensor): difference vector(batched)
Returns:
Tensor: squared Mahalanobis distance
"""
n = bx.shape[-1]
bx_batch_shape = bx.shape[:-1]
# Assume that bL.shape = (i, 1, n, n), bx.shape = (..., i, j, n),
# we are going to make bx have shape (..., 1, j, i, 1, n) to apply batched tri.solve
bx_batch_dims = len(bx_batch_shape)
bL_batch_dims = bL.dim() - 2
outer_batch_dims = bx_batch_dims - bL_batch_dims
old_batch_dims = outer_batch_dims + bL_batch_dims
new_batch_dims = outer_batch_dims + 2 * bL_batch_dims
# Reshape bx with the shape (..., 1, i, j, 1, n)
bx_new_shape = bx.shape[:outer_batch_dims]
for sL, sx in zip(bL.shape[:-2], bx.shape[outer_batch_dims:-1]):
bx_new_shape += (sx // sL, sL)
bx_new_shape += (n,)
bx = bx.reshape(bx_new_shape)
# Permute bx to make it have shape (..., 1, j, i, 1, n)
permute_dims = (
list(range(outer_batch_dims))
+ list(range(outer_batch_dims, new_batch_dims, 2))
+ list(range(outer_batch_dims + 1, new_batch_dims, 2))
+ [new_batch_dims]
)
bx = bx.transpose(permute_dims)
flat_L = bL.reshape((-1, n, n)) # shape = b x n x n
flat_x = bx.reshape((-1, flat_L.shape[0], n)) # shape = c x b x n
flat_x_swap = flat_x.transpose((1, 2, 0)) # shape = b x n x c
M_swap = (
paddle.linalg.triangular_solve(flat_L, flat_x_swap, upper=False)
.pow(2)
.sum(-2)
) # shape = b x c
M = M_swap.t() # shape = c x b
# Now we revert the above reshape and permute operators.
permuted_M = M.reshape(bx.shape[:-1]) # shape = (..., 1, j, i, 1)
permute_inv_dims = list(range(outer_batch_dims))
for i in range(bL_batch_dims):
permute_inv_dims += [outer_batch_dims + i, old_batch_dims + i]
reshaped_M = permuted_M.transpose(
permute_inv_dims
) # shape = (..., 1, i, j, 1)
return reshaped_M.reshape(bx_batch_shape)
+553
View File
@@ -0,0 +1,553 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
from collections.abc import Iterable, Sequence
from typing import TYPE_CHECKING
import numpy as np
import numpy.typing as npt
import paddle
from paddle.base.data_feeder import check_type, convert_dtype
from paddle.base.framework import Variable
from paddle.distribution import constraint, distribution
from paddle.framework import in_dynamic_mode
from paddle.tensor import random
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from typing import TypeAlias
from paddle import Tensor, dtype
from paddle._typing import NestedSequence
_NormalLocBase: TypeAlias = float | complex
_NormalLocNDArray: TypeAlias = (
np.float32 | np.float64 | np.complex64 | np.complex128
)
_NormalLoc: TypeAlias = (
_NormalLocBase
| Sequence[_NormalLocBase]
| NestedSequence[_NormalLocBase]
| npt.NDArray[_NormalLocNDArray]
| Tensor
)
_NormalScale: TypeAlias = (
float
| Sequence[float]
| NestedSequence[float]
| npt.NDArray[np.float32 | np.float64]
| Tensor
)
class Normal(distribution.Distribution):
r"""The Normal distribution with location `loc` and `scale` parameters.
Mathematical details
If 'loc' is real number, the probability density function (pdf) is
.. math::
pdf(x; \mu, \sigma) = \frac{1}{Z}e^{\frac {-0.5 (x - \mu)^2} {\sigma^2} }
.. math::
Z = (2 \pi \sigma^2)^{0.5}
If 'loc' is complex number, the probability density function (pdf) is
.. math::
pdf(x; \mu, \sigma) = \frac{1}{Z}e^{\frac {-(x - \mu)^2} {\sigma^2} }
.. math::
Z = \pi \sigma^2
In the above equations:
* :math:`loc = \mu`: is the mean.
* :math:`scale = \sigma`: is the std.
* :math:`Z`: is the normalization constant.
Args:
loc(int|float|complex|list|tuple|numpy.ndarray|Tensor): The mean of normal distribution.The data type is float32, float64, complex64 and complex128.
scale(int|float|list|tuple|numpy.ndarray|Tensor): The std of normal distribution.The data type is float32 and float64.
validate_args(bool|None, optional): Whether to validate input arguments. Default is None.
name(str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Normal
>>> # Define a single scalar Normal distribution.
>>> dist = Normal(loc=0.0, scale=3.0)
>>> # Define a batch of two scalar valued Normals.
>>> # The first has mean 1 and standard deviation 11, the second 2 and 22.
>>> dist = Normal(loc=[1.0, 2.0], scale=[11.0, 22.0])
>>> # Get 3 samples, returning a 3 x 2 tensor.
>>> dist.sample([3])
>>> # Define a batch of two scalar valued Normals.
>>> # Both have mean 1, but different standard deviations.
>>> dist = Normal(loc=1.0, scale=[11.0, 22.0])
>>> # Complete example
>>> value_tensor = paddle.to_tensor([0.8], dtype="float32")
>>> normal_a = Normal([0.0], [1.0])
>>> normal_b = Normal([0.5], [2.0])
>>> sample = normal_a.sample([2])
>>> # a random tensor created by normal distribution with shape: [2, 1]
>>> entropy = normal_a.entropy()
>>> print(entropy)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.41893852])
>>> lp = normal_a.log_prob(value_tensor)
>>> print(lp)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[-1.23893857])
>>> p = normal_a.probs(value_tensor)
>>> print(p)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.28969154])
>>> kl = normal_a.kl_divergence(normal_b)
>>> print(kl)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.34939718])
"""
loc: Tensor
scale: Tensor
name: str
dtype: dtype
arg_constraints = {
"loc": constraint.real,
"scale": constraint.positive,
}
support = constraint.real
def __init__(
self,
loc: _NormalLoc,
scale: _NormalScale,
validate_args: bool | None = None,
name: str | None = None,
) -> None:
if not in_dynamic_mode():
check_type(
loc,
'loc',
(
int,
float,
complex,
np.ndarray,
Variable,
paddle.pir.Value,
list,
tuple,
),
'Normal',
)
check_type(
scale,
'scale',
(
int,
float,
np.ndarray,
Variable,
paddle.pir.Value,
list,
tuple,
),
'Normal',
)
self.all_arg_is_float = False
self.name = name if name is not None else 'Normal'
self.dtype = 'float32'
self._complex_gaussian = False
if isinstance(loc, int):
loc = float(loc)
if isinstance(scale, int):
scale = float(scale)
if isinstance(loc, (tuple, list)):
loc = np.array(loc)
if loc.dtype == np.float64:
loc = loc.astype('float32')
if loc.dtype == np.complex128:
loc = loc.astype('complex64')
if isinstance(scale, (tuple, list)):
scale = np.array(scale, dtype=np.float32)
if (
isinstance(loc, complex)
or (
isinstance(loc, np.ndarray)
and loc.dtype in [np.complex64, np.complex128]
)
or (self._validate_args(loc) and loc.is_complex())
):
self._complex_gaussian = True
if isinstance(loc, complex) and isinstance(scale, float):
self.all_arg_is_float = True
if isinstance(loc, np.ndarray):
real_dtype = (
'float32' if loc.dtype == np.complex64 else 'float64'
)
imag_dtype = (
'float32' if loc.dtype == np.complex64 else 'float64'
)
real = paddle.to_tensor(loc.real, real_dtype)
imag = paddle.to_tensor(loc.imag, imag_dtype)
self.loc = paddle.complex(real, imag)
elif isinstance(loc, complex):
real = paddle.to_tensor(loc.real, dtype='float32')
imag = paddle.to_tensor(loc.imag, dtype='float32')
self.loc = paddle.complex(real, imag)
else:
self.loc = loc
if isinstance(scale, np.ndarray):
self.scale = paddle.to_tensor(scale, dtype=scale.dtype)
elif isinstance(scale, float):
self.scale = paddle.to_tensor(scale, dtype='float32')
else:
self.scale = scale
self.dtype = convert_dtype(self.loc.dtype)
else:
if self._validate_args(loc, scale):
self.loc = loc
self.scale = scale
self.dtype = convert_dtype(loc.dtype)
else:
if isinstance(loc, float) and isinstance(scale, float):
self.all_arg_is_float = True
if isinstance(loc, np.ndarray) and str(loc.dtype) in [
'float32',
'float64',
]:
self.dtype = loc.dtype
elif isinstance(scale, np.ndarray) and str(scale.dtype) in [
'float32',
'float64',
]:
self.dtype = scale.dtype
self.loc, self.scale = self._to_tensor(loc, scale)
if self.dtype != convert_dtype(self.loc.dtype):
self.loc = paddle.cast(self.loc, dtype=self.dtype)
self.scale = paddle.cast(self.scale, dtype=self.dtype)
super().__init__(self.loc.shape, validate_args=validate_args)
if in_dynamic_mode() and self._validate_args_enabled:
self._validate_parameters()
def _validate_parameters(self) -> None:
for param, value in (("loc", self.loc), ("scale", self.scale)):
constraint_ = self.arg_constraints[param]
valid = constraint_.check(value)
if not bool(valid.all()):
raise ValueError(
f"Expected parameter {param} "
f"({type(value).__name__} of shape {tuple(value.shape)}) "
f"of distribution {self!r} "
f"to satisfy the constraint {constraint_!r}, "
f"but found invalid values:\n{value}"
)
@property
def mean(self) -> Tensor:
"""Mean of normal distribution.
Returns:
Tensor: mean value.
"""
return self.loc
@property
def variance(self) -> Tensor:
"""Variance of normal distribution.
Returns:
Tensor: variance value.
"""
return self.scale.pow(2)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = [], seed: int = 0) -> Tensor:
"""Generate samples of the specified shape.
Args:
shape (Sequence[int], optional): Shape of the generated samples.
Alias: ``sample_shape``.
seed (int): Python integer number.
Returns:
Tensor, A tensor with prepended dimensions shape.The data type is float32.
"""
if not isinstance(shape, Iterable):
raise TypeError('sample shape must be Iterable object.')
if not in_dynamic_mode():
check_type(seed, 'seed', (int), 'sample')
shape = list(shape)
batch_shape = list((self.loc + self.scale).shape)
name = self.name + '_sample'
if -1 in batch_shape:
output_shape = shape + batch_shape
fill_shape = list(batch_shape + shape)
fill_shape[0] = paddle.shape(self.loc + self.scale)[0].item()
zero_tmp = paddle.full(fill_shape, 0.0, self.dtype)
zero_tmp_reshape = paddle.reshape(zero_tmp, output_shape)
zero_tmp_shape = paddle.shape(zero_tmp_reshape)
normal_random_tmp = random.gaussian(
zero_tmp_shape,
mean=(0.0 + 0.0j) if self._complex_gaussian else 0.0,
std=1.0,
seed=seed,
dtype=self.dtype,
)
output = normal_random_tmp * (zero_tmp_reshape + self.scale)
output = paddle.add(output, self.loc, name=name)
return output
else:
output_shape = shape + batch_shape
output = random.gaussian(
output_shape,
mean=(0.0 + 0.0j) if self._complex_gaussian else 0.0,
std=1.0,
seed=seed,
dtype=self.dtype,
) * (paddle.zeros(output_shape, dtype=self.dtype) + self.scale)
output = paddle.add(output, self.loc, name=name)
if self.all_arg_is_float:
return paddle.reshape(output, shape, name=name)
else:
return output
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate reparameterized samples of the specified shape.
Args:
shape (Sequence[int], optional): Shape of the generated samples.
Alias: ``sample_shape``.
Returns:
Tensor: A tensor with prepended dimensions shape.The data type is float32.
"""
if not isinstance(shape, Iterable):
raise TypeError('sample shape must be Iterable object.')
shape = self._extend_shape(tuple(shape))
eps = paddle.normal(
mean=(0.0 + 0.0j) if self._complex_gaussian else 0.0, shape=shape
)
return self.loc + eps * self.scale
def entropy(self) -> Tensor:
r"""Shannon entropy in nats.
If non-complex, the entropy is
.. math::
entropy(\sigma) = 0.5 \log (2 \pi e \sigma^2)
If complex gaussian, the entropy is
.. math::
entropy(\sigma) = \log (\pi e \sigma^2) + 1
In the above equation:
* :math:`scale = \sigma`: is the std.
Returns:
Tensor, Shannon entropy of normal distribution.The data type is float32.
"""
name = self.name + '_entropy'
batch_shape = list((self.loc + self.scale).shape)
if self._complex_gaussian:
if -1 in batch_shape:
fill_shape = list(batch_shape)
fill_shape[0] = paddle.shape(self.loc + self.scale)[0].item()
fill_dtype = self.scale.dtype
zero_tmp = paddle.full(fill_shape, 0.0, fill_dtype)
else:
zero_tmp = paddle.full(batch_shape, 0.0, self.scale.dtype)
return paddle.add(
1.0 + zero_tmp,
math.log(math.pi) + 2.0 * paddle.log(self.scale + zero_tmp),
name=name,
)
else:
if -1 in batch_shape:
fill_shape = list(batch_shape)
fill_shape[0] = paddle.shape(self.loc + self.scale)[0].item()
fill_dtype = (self.loc + self.scale).dtype
zero_tmp = paddle.full(fill_shape, 0.0, fill_dtype)
else:
zero_tmp = paddle.full(batch_shape, 0.0, self.dtype)
return paddle.add(
0.5 + zero_tmp,
0.5 * math.log(2 * math.pi) + paddle.log(self.scale + zero_tmp),
name=name,
)
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: log probability.The data type is same with :attr:`value` .
"""
name = self.name + '_log_prob'
value = self._check_values_dtype_in_probs(self.loc, value)
if in_dynamic_mode() and self._validate_args_enabled:
self._validate_sample(value)
var = self.scale * self.scale
log_scale = paddle.log(self.scale)
if self._complex_gaussian:
return paddle.subtract(
-1.0 * ((value - self.loc).conj() * (value - self.loc)) / (var),
2.0 * log_scale + math.log(math.pi),
name=name,
)
else:
return paddle.subtract(
-1.0 * ((value - self.loc) * (value - self.loc)) / (2.0 * var),
log_scale + math.log(math.sqrt(2.0 * math.pi)),
name=name,
)
def probs(self, value: Tensor) -> Tensor:
"""Probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor, probability. The data type is same with :attr:`value` .
"""
name = self.name + '_probs'
value = self._check_values_dtype_in_probs(self.loc, value)
var = self.scale * self.scale
if self._complex_gaussian:
return paddle.divide(
paddle.exp(
-1.0
* ((value - self.loc).conj() * (value - self.loc))
/ (var)
),
(math.pi * var),
name=name,
)
else:
return paddle.divide(
paddle.exp(
-1.0
* ((value - self.loc) * (value - self.loc))
/ (2.0 * var)
),
(math.sqrt(2 * math.pi) * self.scale),
name=name,
)
def kl_divergence(self, other: Normal) -> Tensor:
r"""The KL-divergence between two normal distributions.
If non-complex, the KL-divergence is
.. math::
KL\_divergence(\mu_0, \sigma_0; \mu_1, \sigma_1) = 0.5 (ratio^2 + (\frac{diff}{\sigma_1})^2 - 1 - 2 \ln {ratio})
If complex gaussian:
.. math::
KL\_divergence(\mu_0, \sigma_0; \mu_1, \sigma_1) = ratio^2 + (\frac{diff}{\sigma_1})^2 - 1 - 2 \ln {ratio}
.. math::
ratio = \frac{\sigma_0}{\sigma_1}
.. math::
diff = \mu_1 - \mu_0
In the above equation:
* :math:`loc = \mu_0`: is the mean of current Normal distribution.
* :math:`scale = \sigma_0`: is the std of current Normal distribution.
* :math:`loc = \mu_1`: is the mean of other Normal distribution.
* :math:`scale = \sigma_1`: is the std of other Normal distribution.
* :math:`ratio`: is the ratio of scales.
* :math:`diff`: is the difference between means.
Args:
other (Normal): instance of Normal.
Returns:
Tensor, kl-divergence between two normal distributions.The data type is float32.
"""
if not in_dynamic_mode():
check_type(other, 'other', Normal, 'kl_divergence')
if self._complex_gaussian != other._complex_gaussian:
raise ValueError(
"The kl divergence must be computed between two distributions in the same number field."
)
name = self.name + '_kl_divergence'
var_ratio = self.scale / other.scale
var_ratio = var_ratio * var_ratio
t1 = (self.loc - other.loc) / other.scale
if self._complex_gaussian:
t1 = t1.conj() * t1
return var_ratio + t1 - 1.0 - paddle.log(var_ratio)
else:
t1 = t1 * t1
return paddle.add(
0.5 * var_ratio,
0.5 * (t1 - 1.0 - paddle.log(var_ratio)),
name=name,
)
+282
View File
@@ -0,0 +1,282 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
import paddle
from paddle.base.data_feeder import convert_dtype
from paddle.distribution import distribution
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing.dtype_like import _DTypeLiteral
class Poisson(distribution.Distribution):
r"""
The Poisson distribution with occurrence rate parameter: `rate`.
In probability theory and statistics, the Poisson distribution is the most basic discrete probability
distribution defined on the nonnegative integer set, which is used to describe the probability distribution of the number of random
events occurring per unit time.
The probability mass function (pmf) is
.. math::
pmf(x; \lambda) = \frac{e^{-\lambda} \cdot \lambda^x}{x!}
In the above equation:
* :math:`rate = \lambda`: is the mean occurrence rate.
Args:
rate(int|float|Tensor): The mean occurrence rate of Poisson distribution which should be greater than 0, meaning the expected occurrence
times of an event in a fixed time interval. If the input data type is int or float, the data type of `rate` will be converted to a
1-D Tensor with paddle global default dtype.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Poisson
>>> paddle.set_device('cpu')
>>> paddle.seed(100)
>>> rv = Poisson(paddle.to_tensor(30.0))
>>> print(rv.sample([3]))
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[32., 27., 25.])
>>> print(rv.mean)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
30.)
>>> print(rv.entropy())
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
3.11671519)
>>> rv1 = Poisson(paddle.to_tensor([[30.0, 40.0], [8.0, 5.0]]))
>>> rv2 = Poisson(paddle.to_tensor([[1000.0, 40.0], [7.0, 10.0]]))
>>> print(rv1.kl_divergence(rv2))
Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
[[864.80499268, 0. ],
[0.06825146 , 1.53426409 ]])
"""
rate: Tensor
dtype: _DTypeLiteral
def __init__(self, rate: float | Tensor) -> None:
self.dtype = paddle.get_default_dtype()
self.rate = self._to_tensor(rate)
batch_shape = self.rate.shape
super().__init__(batch_shape)
def _to_tensor(self, rate: float | Tensor) -> Tensor:
"""Convert the input parameters into tensors.
Returns:
Tensor: converted rate.
"""
# convert type
if isinstance(rate, (float, int)):
rate = paddle.to_tensor([rate], dtype=self.dtype)
else:
self.dtype = convert_dtype(rate.dtype)
return rate
@property
def mean(self) -> Tensor:
"""Mean of poisson distribution.
Returns:
Tensor: mean value.
"""
return self.rate
@property
def variance(self) -> Tensor:
"""Variance of poisson distribution.
Returns:
Tensor: variance value.
"""
return self.rate
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate poisson samples of the specified shape. The final shape would be ``shape+batch_shape`` .
Args:
shape (Sequence[int], optional): Prepended shape of the generated samples.
Returns:
Tensor: Sampled data with shape `sample_shape` + `batch_shape`.
"""
if not isinstance(shape, Sequence):
raise TypeError('sample shape must be Sequence object.')
shape = tuple(shape)
batch_shape = tuple(self.batch_shape)
output_shape = tuple(shape + batch_shape)
output_rate = paddle.broadcast_to(self.rate, shape=output_shape)
with paddle.no_grad():
return paddle.poisson(output_rate)
def entropy(self) -> Tensor:
r"""Shannon entropy in nats.
The entropy is
.. math::
\mathcal{H}(X) = - \sum_{x \in \Omega} p(x) \log{p(x)}
In the above equation:
* :math:`\Omega`: is the support of the distribution.
Returns:
Tensor: Shannon entropy of poisson distribution. The data type is the same as `rate`.
"""
values = self._enumerate_bounded_support(self.rate).reshape(
(-1,) + (1,) * len(self.batch_shape)
)
log_prob = self.log_prob(values)
proposed = -(paddle.exp(log_prob) * log_prob).sum(0)
mask = paddle.cast(
paddle.not_equal(
self.rate, paddle.to_tensor(0.0, dtype=self.dtype)
),
dtype=self.dtype,
)
return paddle.multiply(proposed, mask)
def _enumerate_bounded_support(self, rate: float | Tensor) -> Tensor:
"""Generate a bounded approximation of the support. Approximately view Poisson r.v. as a
Normal r.v. with mu = rate and sigma = sqrt(rate). Then by 30-sigma rule, generate a bounded
approximation of the support.
Args:
rate (float): rate of one poisson r.v.
Returns:
Tensor: the bounded approximation of the support
"""
if paddle.framework.in_dynamic_mode():
s_max = (
paddle.sqrt(paddle.max(rate))
if paddle.greater_equal(
paddle.max(rate), paddle.to_tensor(1.0, dtype=self.dtype)
)
else paddle.ones_like(rate, dtype=self.dtype)
)
upper = paddle.max(paddle.cast(rate + 30 * s_max, dtype="int32"))
values = paddle.arange(0, upper, dtype=self.dtype)
return values
else:
def true_func():
return paddle.sqrt(paddle.max(rate))
def false_func():
return paddle.to_tensor(1.0, dtype=self.dtype)
s_max = paddle.static.nn.cond(
paddle.greater_equal(
paddle.max(rate), paddle.to_tensor(1.0, dtype=self.dtype)
),
true_func,
false_func,
)
upper = paddle.max(paddle.cast(rate + 30 * s_max, dtype="int32"))
values = paddle.arange(0, upper, dtype=self.dtype)
return values
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: log probability. The data type is the same as `rate`.
"""
value = paddle.cast(value, dtype=self.dtype)
eps = paddle.finfo(self.rate.dtype).eps
return paddle.nan_to_num(
(
-self.rate
+ value * paddle.log(self.rate)
- paddle.lgamma(value + 1)
),
neginf=-eps,
)
def prob(self, value: Tensor) -> Tensor:
"""Probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: probability. The data type is the same as `rate`.
"""
return paddle.exp(self.log_prob(value))
def kl_divergence(self, other: Poisson) -> Tensor:
r"""The KL-divergence between two poisson distributions with the same `batch_shape`.
The probability density function (pdf) is
.. math::
KL\_divergence\lambda_1, \lambda_2) = \sum_x p_1(x) \log{\frac{p_1(x)}{p_2(x)}}
.. math::
p_1(x) = \frac{e^{-\lambda_1} \cdot \lambda_1^x}{x!}
.. math::
p_2(x) = \frac{e^{-\lambda_2} \cdot \lambda_2^x}{x!}
Args:
other (Poisson): instance of ``Poisson``.
Returns:
Tensor, kl-divergence between two poisson distributions. The data type is the same as `rate`.
"""
if self.batch_shape != other.batch_shape:
raise ValueError(
"KL divergence of two poisson distributions should share the same `batch_shape`."
)
rate_max = paddle.max(paddle.maximum(self.rate, other.rate))
support_max = self._enumerate_bounded_support(rate_max)
a_max = paddle.max(support_max)
common_support = paddle.arange(0, a_max, dtype=self.dtype).reshape(
(-1,) + (1,) * len(self.batch_shape)
)
log_prob_1 = self.log_prob(common_support)
log_prob_2 = other.log_prob(common_support)
return (paddle.exp(log_prob_1) * (log_prob_1 - log_prob_2)).sum(0)
+279
View File
@@ -0,0 +1,279 @@
# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
from collections.abc import Sequence
from typing import TYPE_CHECKING
import paddle
from paddle.base.data_feeder import check_type
from paddle.base.framework import Variable
from paddle.distribution import Gamma, distribution
from paddle.framework import in_dynamic_mode
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from paddle import Tensor, dtype
class StudentT(distribution.Distribution):
r"""
The StudentT distribution with parameters: `df`, `loc`, `scale`.
In probability theory and statistics, the StudentT distribution is one of the basic continuous probability distributions
defined on the real number set.
The probability density function (pdf) is
.. math::
pdf(x; \nu, \mu, \sigma) = \frac{\Gamma[(\nu+1)/2]}{\sigma\sqrt{\nu\pi}\Gamma(\nu/2)[1+(\frac{x-\mu}{\sigma})^2/\nu]^{(1+\nu)/2}}
In the above equation:
* :math:`df = \nu`: is the degree of freedom.
* :math:`loc = \mu`: is the center parameter.
* :math:`scale = \sigma`: is the scale parameter.
* :math:`\Gamma(\cdot)`: is the gamma function.
Args:
df (float|Tensor): The degree of freedom of the distribution, which should be non-negative. If the input data type is float,
the data type of `df` will be converted to a 1-D Tensor with paddle global default dtype. Supported dtype: float32, float64.
loc (float|Tensor): The center of the distribution. If the input data type is float, the data type of `loc` will be converted to a
1-D Tensor with paddle global default dtype. Supported dtype: float32, float64.
scale (float|Tensor): The scale of the distribution, which should be non-negative. If the input data type is float, the data type
of `scale` will be converted to a 1-D Tensor with paddle global default dtype. Supported dtype: float32, float64.
name(str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import StudentT
>>> paddle.set_device('cpu')
>>> paddle.seed(100)
>>> dist = StudentT(df=10.0, loc=0.0, scale=1.0)
>>> dist.sample([3])
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[-2.07709980, 0.27981189, 0.00881413])
>>> dist2 = StudentT(df=paddle.to_tensor([10.0, 5.0]), loc=paddle.to_tensor([0.0, 0.0]), scale=paddle.to_tensor([1.0, 2.0]))
>>> value_tensor = paddle.to_tensor([0.8], dtype="float32")
>>> lp = dist2.log_prob(value_tensor)
>>> print(lp)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[-1.28509212, -1.75626254])
>>> p = dist2.prob(value_tensor)
>>> print(p)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.27662510, 0.17268908])
>>> entropy = dist2.entropy()
>>> print(entropy)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.52126288, 2.32064891])
"""
df: Tensor
loc: Tensor
scale: Tensor
name: str
dtype: dtype
def __init__(
self,
df: float | Tensor,
loc: float | Tensor,
scale: float | Tensor,
name: str | None = None,
) -> None:
if not in_dynamic_mode():
check_type(
df,
'df',
(
float,
Variable,
paddle.pir.Value,
),
'StudentT',
)
check_type(
loc,
'loc',
(
float,
Variable,
paddle.pir.Value,
),
'StudentT',
)
check_type(
scale,
'scale',
(
float,
Variable,
paddle.pir.Value,
),
'StudentT',
)
self.name = name if name is not None else 'StudentT'
self.df, self.loc, self.scale = self._broadcast_all(df, loc, scale)
if not self._check_nonnegative(self.df):
raise ValueError(
'Every element of input parameter `df` should be nonnegative.'
)
if not self._check_nonnegative(self.scale):
raise ValueError(
'Every element of input parameter `scale` should be nonnegative.'
)
batch_shape = self.df.shape
super().__init__(batch_shape)
self._chi2 = Gamma(0.5 * self.df, paddle.full_like(self.df, 0.5))
def _check_nonnegative(self, value: Tensor) -> bool:
"""Check the non-negative constraint for input parameters
Args:
value (Tensor)
Returns:
bool: pass or not.
"""
return (value >= 0.0).all()
@property
def mean(self) -> Tensor:
"""Mean of StudentT distribution.
Returns:
Tensor: mean value.
"""
return paddle.where(
self.df > 1.0,
self.loc,
paddle.full_like(self.loc, fill_value=float('nan')),
)
@property
def variance(self) -> Tensor:
"""Variance of StudentT distribution.
Returns:
Tensor: variance value.
"""
var = self.df.clone().detach()
var_condition = self.df > 2.0
var = paddle.where(
var_condition,
self.scale.pow(2) * var / (var - 2),
paddle.full_like(var, fill_value=float('nan')),
)
inf_condition = (self.df <= 2.0).logical_and(self.df > 1.0)
var = paddle.where(
inf_condition, paddle.full_like(var, fill_value=float('inf')), var
)
return var
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Generate StudentT samples of the specified shape. The final shape would be ``shape+batch_shape`` .
Args:
shape (Sequence[int], optional): Prepended shape of the generated samples.
Returns:
Tensor: Sampled data with shape `sample_shape` + `batch_shape`.
"""
if not isinstance(shape, Sequence):
raise TypeError('sample shape must be Sequence object.')
output_shape = self._extend_shape(shape)
z = paddle.normal(shape=output_shape)
chi2 = self._chi2.sample(shape)
x = z * paddle.rsqrt(chi2 / self.df)
return self.loc + self.scale * x
def entropy(self) -> Tensor:
r"""Shannon entropy in nats.
The entropy is
.. math::
H = \log(\frac{\Gamma(\nu/2)\Gamma(1/2) \sigma \sqrt{\nu}}{\Gamma[(1+\nu)/2]}) + \frac{(1+\nu)}{2} \cdot \{\psi[(1+\nu)/2] - \psi(\nu/2)\}
In the above equation:
* :math:`\nu`: is the degree of freedom.
* :math:`\Gamma()`: is the gamma function.
* :math:`\psi()`: is the digamma function.
Returns:
Tensor: Shannon entropy of StudentT distribution. The data type is the same as `df`.
"""
lbeta = (
paddle.lgamma(0.5 * self.df)
+ math.lgamma(0.5)
- paddle.lgamma(0.5 * (self.df + 1))
)
return (
self.scale.log()
+ 0.5
* (self.df + 1)
* (
paddle.digamma(0.5 * (self.df + 1))
- paddle.digamma(0.5 * self.df)
)
+ 0.5 * self.df.log()
+ lbeta
)
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: log probability density. The data type is the same as `df`.
"""
value = self._check_values_dtype_in_probs(self.df, value)
y = (value - self.loc) / self.scale
Z = (
self.scale.log()
+ 0.5 * self.df.log()
+ 0.5 * math.log(math.pi)
+ paddle.lgamma(0.5 * self.df)
- paddle.lgamma(0.5 * (self.df + 1.0))
)
return -0.5 * (self.df + 1.0) * paddle.log1p(y**2.0 / self.df) - Z
def prob(self, value: Tensor) -> Tensor:
"""Probability density function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor: probability density. The data type is the same as `df`.
"""
return paddle.exp(self.log_prob(value))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,166 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import typing
from typing import TYPE_CHECKING
from paddle.distribution import distribution, independent, transform
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
from paddle.distribution.distribution import Distribution
from paddle.distribution.transform import Transform
class TransformedDistribution(distribution.Distribution):
r"""
Applies a sequence of Transforms to a base distribution.
Args:
base (Distribution): The base distribution.
transforms (Sequence[Transform]): A sequence of ``Transform`` .
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.seed(2023)
>>> from paddle.distribution import transformed_distribution
>>> d = transformed_distribution.TransformedDistribution(
... paddle.distribution.Normal(0.0, 1.0),
... [paddle.distribution.AffineTransform(paddle.to_tensor(1.0), paddle.to_tensor(2.0))],
... )
>>> # doctest: +SKIP('random sample')
>>> print(d.sample([10]))
Tensor(shape=[10], dtype=float32, place=Place(cpu), stop_gradient=True,
[ 3.22699189, 1.12264419, 0.50283587, 1.83812487, -2.00740123,
-2.70338631, 1.26663208, 4.47909021, -0.11529565, 4.32719326])
>>> print(d.log_prob(paddle.to_tensor(0.5)))
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
-1.64333570)
>>> # doctest: -SKIP
"""
base: Distribution
transforms: Sequence[Transform]
def __init__(
self, base: Distribution, transforms: Sequence[Transform]
) -> None:
if not isinstance(base, distribution.Distribution):
raise TypeError(
f"Expected type of 'base' is Distribution, but got {type(base)}."
)
if not isinstance(transforms, typing.Sequence):
raise TypeError(
f"Expected type of 'transforms' is Sequence[Transform] or Chain, but got {type(transforms)}."
)
if not all(isinstance(t, transform.Transform) for t in transforms):
raise TypeError("All element of transforms must be Transform type.")
chain = transform.ChainTransform(transforms)
base_shape = base.batch_shape + base.event_shape
self._base = base
self._transforms = transforms
if not transforms:
super().__init__(base.batch_shape, base.event_shape)
return
if len(base.batch_shape + base.event_shape) < chain._domain.event_rank:
raise ValueError(
f"'base' needs to have shape with size at least {chain._domain.event_rank}, bug got {len(base_shape)}."
)
if chain._domain.event_rank > len(base.event_shape):
base = independent.Independent(
base, chain._domain.event_rank - len(base.event_shape)
)
transformed_shape = chain.forward_shape(
base.batch_shape + base.event_shape
)
transformed_event_rank = chain._codomain.event_rank + max(
len(base.event_shape) - chain._domain.event_rank, 0
)
super().__init__(
transformed_shape[
: len(transformed_shape) - transformed_event_rank
],
transformed_shape[
len(transformed_shape) - transformed_event_rank :
],
)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = []) -> Tensor:
"""Sample from ``TransformedDistribution``.
Args:
shape (Sequence[int], optional): The sample shape. Defaults to [].
Returns:
[Tensor]: The sample result.
"""
x = self._base.sample(shape)
for t in self._transforms:
x = t.forward(x)
return x
@param_one_alias(["shape", "sample_shape"])
def rsample(self, shape: Sequence[int] = []) -> Tensor:
"""Reparameterized sample from ``TransformedDistribution``.
Args:
shape (Sequence[int], optional): The sample shape. Defaults to [].
Returns:
[Tensor]: The sample result.
"""
x = self._base.rsample(shape)
for t in self._transforms:
x = t.forward(x)
return x
def log_prob(self, value: Tensor) -> Tensor:
"""The log probability evaluated at value.
Args:
value (Tensor): The value to be evaluated.
Returns:
Tensor: The log probability.
"""
log_prob = 0.0
y = value
event_rank = len(self.event_shape)
for t in reversed(self._transforms):
x = t.inverse(y)
event_rank += t._domain.event_rank - t._codomain.event_rank
log_prob = log_prob - _sum_rightmost(
t.forward_log_det_jacobian(x), event_rank - t._domain.event_rank
)
y = x
log_prob += _sum_rightmost(
self._base.log_prob(y), event_rank - len(self._base.event_shape)
)
return log_prob
def _sum_rightmost(value: Tensor, n: int) -> Tensor:
return value.sum(list(range(-n, 0))) if n > 0 else value
+320
View File
@@ -0,0 +1,320 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
import numpy.typing as npt
import paddle
from paddle import _C_ops
from paddle.base.data_feeder import check_type, convert_dtype
from paddle.base.framework import Variable
from paddle.distribution import distribution
from paddle.framework import in_dynamic_mode
from paddle.tensor import random
from paddle.utils.decorator_utils import param_one_alias
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import TypeAlias
from paddle import Tensor
from paddle._typing import NestedSequence
_UniformBoundary: TypeAlias = (
float
| Sequence[float]
| NestedSequence[float]
| npt.NDArray[np.float32 | np.float64]
| Tensor
)
class Uniform(distribution.Distribution):
r"""Uniform distribution with `low` and `high` parameters.
Mathematical Details
The probability density function (pdf) is
.. math::
pdf(x; a, b) = \frac{1}{Z}, \ a <=x <b
.. math::
Z = b - a
In the above equation:
* :math:`low = a`,
* :math:`high = b`,
* :math:`Z`: is the normalizing constant.
The parameters `low` and `high` must be shaped in a way that supports
`Broadcasting` (e.g., `high - low` is a valid operation).
Note:
If you want know more about broadcasting, please refer to `Introduction to Tensor`_ .
.. _Introduction to Tensor: ../../guides/beginner/tensor_en.html#chapter5-broadcasting-of-tensor
Args:
low(int|float|list|tuple|numpy.ndarray|Tensor): The lower boundary of
uniform distribution.The data type is float32 and float64.
high(int|float|list|tuple|numpy.ndarray|Tensor): The higher boundary
of uniform distribution.The data type is float32 and float64.
name (str, optional): For details, please refer to :ref:`api_guide_Name`. Generally, no setting is required. Default: None.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.distribution import Uniform
>>> paddle.seed(2023)
>>> # Without broadcasting, a single uniform distribution [3, 4]:
>>> u1 = Uniform(low=3.0, high=4.0)
>>> # 2 distributions [1, 3], [2, 4]
>>> u2 = Uniform(low=[1.0, 2.0], high=[3.0, 4.0])
>>> # 4 distributions
>>> u3 = Uniform(
... low=[[1.0, 2.0], [3.0, 4.0]], # type: ignore[list-item]
... high=[[1.5, 2.5], [3.5, 4.5]], # type: ignore[list-item]
... )
>>> # With broadcasting:
>>> u4 = Uniform(low=3.0, high=[5.0, 6.0, 7.0])
>>> # Complete example
>>> value_tensor = paddle.to_tensor([0.8], dtype="float32")
>>> uniform = Uniform([0.0], [2.0])
>>> sample = uniform.sample([2])
>>> # a random tensor created by uniform distribution with shape: [2, 1]
>>> entropy = uniform.entropy()
>>> print(entropy)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.69314718])
>>> lp = uniform.log_prob(value_tensor)
>>> print(lp)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[-0.69314718])
>>> p = uniform.probs(value_tensor)
>>> print(p)
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.50000000])
"""
low: Tensor
high: Tensor
def __init__(
self,
low: _UniformBoundary,
high: _UniformBoundary,
name: str | None = None,
) -> None:
if not in_dynamic_mode():
check_type(
low,
'low',
(
int,
float,
np.ndarray,
Variable,
paddle.pir.Value,
list,
tuple,
),
'Uniform',
)
check_type(
high,
'high',
(
int,
float,
np.ndarray,
Variable,
paddle.pir.Value,
list,
tuple,
),
'Uniform',
)
self.all_arg_is_float = False
self.batch_size_unknown = False
self.name = name if name is not None else 'Uniform'
self.dtype = 'float32'
if isinstance(low, int):
low = float(low)
if isinstance(high, int):
high = float(high)
if self._validate_args(low, high):
self.low = low
self.high = high
self.dtype = convert_dtype(low.dtype)
else:
if isinstance(low, float) and isinstance(high, float):
self.all_arg_is_float = True
if isinstance(low, np.ndarray) and str(low.dtype) in [
'float32',
'float64',
]:
self.dtype = low.dtype
elif isinstance(high, np.ndarray) and str(high.dtype) in [
'float32',
'float64',
]:
self.dtype = high.dtype
self.low, self.high = self._to_tensor(low, high)
if self.dtype != convert_dtype(self.low.dtype):
self.low = paddle.cast(self.low, dtype=self.dtype)
self.high = paddle.cast(self.high, dtype=self.dtype)
super().__init__(self.low.shape)
@param_one_alias(["shape", "sample_shape"])
def sample(self, shape: Sequence[int] = [], seed: int = 0) -> Tensor:
"""Generate samples of the specified shape.
Args:
shape (Sequence[int], optional): 1D `int32`. Shape of the generated samples.
Defaults to [].
seed (int): Python integer number.
Returns:
Tensor, A tensor with prepended dimensions shape. The data type is float32.
"""
if not in_dynamic_mode():
check_type(shape, 'shape', (list, tuple), 'sample')
check_type(seed, 'seed', (int), 'sample')
shape = list(shape)
name = self.name + '_sample'
batch_shape = list((self.low + self.high).shape)
if -1 in batch_shape:
output_shape = shape + batch_shape
fill_shape = list(batch_shape + shape)
fill_shape[0] = paddle.shape(self.low + self.high)[0].item()
zero_tmp = paddle.full(fill_shape, 0.0, self.dtype)
uniform_random_tmp = random.uniform_random_batch_size_like(
zero_tmp,
zero_tmp.shape,
dtype=self.dtype,
min=0.0,
max=1.0,
seed=seed,
)
zero_tmp_reshape = paddle.reshape(zero_tmp, output_shape)
uniform_random_tmp_reshape = paddle.reshape(
uniform_random_tmp, output_shape
)
output = uniform_random_tmp_reshape * (
zero_tmp_reshape + self.high - self.low
)
output = paddle.add(output, self.low, name=name)
return output
else:
output_shape = shape + batch_shape
output = paddle.uniform(
output_shape, dtype=self.dtype, min=0.0, max=1.0, seed=seed
) * (
paddle.zeros(output_shape, dtype=self.dtype)
+ (self.high - self.low)
)
output = paddle.add(output, self.low, name=name)
if self.all_arg_is_float:
return paddle.reshape(output, shape, name=name)
else:
return output
def log_prob(self, value: Tensor) -> Tensor:
"""Log probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor, log probability.The data type is same with value.
"""
value = self._check_values_dtype_in_probs(self.low, value)
if in_dynamic_mode():
# ensure value in [low, high]
lb_bool = self.low < value
ub_bool = value < self.high
lb = _C_ops.cast(lb_bool, value.dtype)
ub = _C_ops.cast(ub_bool, value.dtype)
return paddle.log(lb * ub) - paddle.log(self.high - self.low)
else:
name = self.name + '_log_prob'
lb_bool = self.low < value
ub_bool = value < self.high
lb = paddle.cast(lb_bool, dtype=value.dtype)
ub = paddle.cast(ub_bool, dtype=value.dtype)
return paddle.subtract(
paddle.log(lb * ub), paddle.log(self.high - self.low), name=name
)
def probs(self, value: Tensor) -> Tensor:
"""Probability density/mass function.
Args:
value (Tensor): The input tensor.
Returns:
Tensor, probability. The data type is same with value.
"""
value = self._check_values_dtype_in_probs(self.low, value)
if in_dynamic_mode():
lb_bool = self.low < value
ub_bool = value < self.high
lb = _C_ops.cast(lb_bool, value.dtype)
ub = _C_ops.cast(ub_bool, value.dtype)
return (lb * ub) / (self.high - self.low)
else:
name = self.name + '_probs'
lb_bool = self.low < value
ub_bool = value < self.high
lb = paddle.cast(lb_bool, dtype=value.dtype)
ub = paddle.cast(ub_bool, dtype=value.dtype)
return paddle.divide((lb * ub), (self.high - self.low), name=name)
def entropy(self) -> Tensor:
r"""Shannon entropy in nats.
The entropy is
.. math::
entropy(low, high) = \\log (high - low)
Returns:
Tensor, Shannon entropy of uniform distribution.The data type is float32.
"""
name = self.name + '_entropy'
return paddle.log(self.high - self.low, name=name)
+135
View File
@@ -0,0 +1,135 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import paddle
from paddle.distribution import constraint
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
from paddle.distribution.constraint import Constraint
class Variable:
"""Random variable of probability distribution.
Args:
is_discrete (bool): Is the variable discrete or continuous.
event_rank (int): The rank of event dimensions.
constraint (Constraint|None, optional): The constraint of the variable.
"""
def __init__(
self,
is_discrete: bool = False,
event_rank: int = 0,
constraint: Constraint | None = None,
) -> None:
self._is_discrete = is_discrete
self._event_rank = event_rank
self._constraint = constraint
@property
def is_discrete(self) -> bool:
return self._is_discrete
@property
def event_rank(self) -> int:
return self._event_rank
def constraint(self, value: Tensor) -> Tensor:
"""Check whether the 'value' meet the constraint conditions of this
random variable."""
assert self._constraint is not None
return self._constraint.check(value)
class Real(Variable):
def __init__(self, event_rank: int = 0) -> None:
super().__init__(False, event_rank, constraint.real)
class Positive(Variable):
def __init__(self, event_rank: int = 0) -> None:
super().__init__(False, event_rank, constraint.positive)
class Independent(Variable):
"""Reinterprets some of the batch axes of variable as event axes.
Args:
base (Variable): Base variable.
reinterpreted_batch_rank (int): The rightmost batch rank to be
reinterpreted.
"""
def __init__(self, base: Variable, reinterpreted_batch_rank: int) -> None:
self._base = base
self._reinterpreted_batch_rank = reinterpreted_batch_rank
super().__init__(
base.is_discrete, base.event_rank + reinterpreted_batch_rank
)
def constraint(self, value: Tensor) -> Tensor:
ret = self._base.constraint(value)
if ret.dim() < self._reinterpreted_batch_rank:
raise ValueError(
f"Input dimensions must be equal or grater than {self._reinterpreted_batch_rank}"
)
return ret.reshape(
(*ret.shape[: ret.dim() - self.reinterpreted_batch_rank], -1)
).all(-1)
class Stack(Variable):
def __init__(self, vars: Sequence[Variable], axis: int = 0) -> None:
self._vars = vars
self._axis = axis
@property
def is_discrete(self) -> bool:
return any(var.is_discrete for var in self._vars)
@property
def event_rank(self) -> int:
rank = max(var.event_rank for var in self._vars)
if self._axis + rank < 0:
rank += 1
return rank
def constraint(self, value: Tensor) -> Tensor:
if not (-value.dim() <= self._axis < value.dim()):
raise ValueError(
f'Input dimensions {value.dim()} should be grater than stack '
f'constraint axis {self._axis}.'
)
return paddle.stack(
[
var.check(value)
for var, value in zip(
self._vars, paddle.unstack(value, self._axis)
)
],
self._axis,
)
real = Real()
positive = Positive()