chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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__)
|
||||
@@ -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),
|
||||
),
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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 Cauchy–Lorentz distribution. It is a continuous probability distribution named after Augustin-Louis Cauchy and Hendrik Lorentz. It has a very wide range of applications in natural sciences.
|
||||
|
||||
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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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)}"
|
||||
)
|
||||
@@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user