Files
2026-07-13 12:40:42 +08:00

1168 lines
43 KiB
Python

# 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, Any, Literal, TypeAlias
from typing_extensions import overload
import paddle
from paddle import _C_ops
from paddle.framework import (
in_dynamic_mode,
in_dynamic_or_pir_mode,
)
from paddle.utils.decorator_utils import (
param_two_alias,
param_two_alias_one_default,
use_first_signature,
)
from ..base.data_feeder import check_type, check_variable_and_dtype
from ..common_ops_import import Variable
from ..framework import LayerHelper, convert_nptype_to_datatype_or_vartype, core
from .manipulation import cast
from .math import _get_reduce_axis_with_tensor
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle import Tensor
from paddle._typing import DTypeLike
_Interpolation: TypeAlias = Literal[
'linear', 'higher', 'lower', 'midpoint', 'nearest'
]
__all__ = []
@param_two_alias(["x", "input"], ["axis", "dim"])
def mean(
x: Tensor,
axis: int | Sequence[int] | None = None,
keepdim: bool = False,
name: str | None = None,
*,
dtype: DTypeLike | None = None,
out: Tensor | None = None,
) -> Tensor:
"""
Computes the mean of the input tensor's elements along ``axis``.
Args:
x (Tensor): The input Tensor with data type bool, bfloat16, float16, float32,
float64, int32, int64, complex64, complex128.
alias: ``input``
axis (int|list|tuple|None, optional): The axis along which to perform mean
calculations. ``axis`` should be int, list(int) or tuple(int). If
``axis`` is a list/tuple of dimension(s), mean is calculated along
all element(s) of ``axis`` . ``axis`` or element(s) of ``axis``
should be in range [-D, D), where D is the dimensions of ``x`` . If
``axis`` or element(s) of ``axis`` is less than 0, it works the
same way as :math:`axis + D` . If ``axis`` is None, mean is
calculated over all elements of ``x``. Default is None.
alias: ``dim``
keepdim (bool, optional): Whether to reserve the reduced dimension(s)
in the output Tensor. If ``keepdim`` is True, the dimensions of
the output Tensor is the same as ``x`` except in the reduced
dimensions(it is of size 1 in this case). Otherwise, the shape of
the output Tensor is squeezed in ``axis`` . Default is False.
name (str|None, optional): Name for the operation (optional, default is None).
For more information, please refer to :ref:`api_guide_Name`.
dtype (str): The desired data type of returned tensor. Default: None.
out(Tensor|None, optional): The output tensor. Default: None.
Returns:
Tensor, results of average along ``axis`` of ``x``, with the same data
type as ``x``.
Examples:
.. code-block:: pycon
>>> import paddle
>>> x = paddle.to_tensor(
... [
... [
... [1.0, 2.0, 3.0, 4.0],
... [5.0, 6.0, 7.0, 8.0],
... [9.0, 10.0, 11.0, 12.0],
... ],
... [
... [13.0, 14.0, 15.0, 16.0],
... [17.0, 18.0, 19.0, 20.0],
... [21.0, 22.0, 23.0, 24.0],
... ],
... ]
... )
>>> out1 = paddle.mean(x)
>>> print(out1.numpy())
12.5
>>> out2 = paddle.mean(x, axis=-1)
>>> print(out2.numpy())
[[ 2.5 6.5 10.5]
[14.5 18.5 22.5]]
>>> out3 = paddle.mean(x, axis=-1, keepdim=True)
>>> print(out3.numpy())
[[[ 2.5]
[ 6.5]
[10.5]]
[[14.5]
[18.5]
[22.5]]]
>>> out4 = paddle.mean(x, axis=[0, 2])
>>> print(out4.numpy())
[ 8.5 12.5 16.5]
>>> out5 = paddle.mean(x, dtype='float64')
>>> out5
Tensor(shape=[], dtype=float64, place=Place(cpu), stop_gradient=True,
12.50000000)
"""
if dtype is not None:
if not isinstance(dtype, (core.VarDesc.VarType, core.DataType)):
dtype = convert_nptype_to_datatype_or_vartype(dtype)
if x.dtype != dtype:
x = cast(x, dtype)
if in_dynamic_or_pir_mode():
return _C_ops.mean(x, axis, keepdim, out=out)
else:
reduce_all, axis = _get_reduce_axis_with_tensor(axis, x)
check_variable_and_dtype(
x,
'x/input',
[
'bool',
'uint16',
'float16',
'float32',
'float64',
'int32',
'int64',
'complex64',
'complex128',
],
'mean/reduce_mean',
)
check_type(
axis, 'axis/dim', (int, list, tuple, Variable), 'mean/reduce_mean'
)
if isinstance(axis, (list, tuple)):
for item in axis:
check_type(
item,
'elements of axis/dim',
(int, Variable),
'mean/reduce_mean',
)
helper = LayerHelper('mean', **locals())
attrs = {'dim': axis, 'keep_dim': keepdim, 'reduce_all': reduce_all}
out_tensor = helper.create_variable_for_type_inference(x.dtype)
helper.append_op(
type='reduce_mean',
inputs={'X': x},
outputs={'Out': out_tensor},
attrs=attrs,
)
return out_tensor
@param_two_alias(["x", "input"], ["axis", "dim"])
def var(
x: Tensor,
axis: int | Sequence[int] | None = None,
unbiased: bool | None = None,
keepdim: bool = False,
name: str | None = None,
*,
correction: float = 1,
out: Tensor | None = None,
) -> Tensor:
"""
Computes the variance of ``x`` along ``axis`` .
Args:
x (Tensor): The input Tensor with data type float16, float32, float64.
Alias: ``input``.
axis (int|list|tuple|None, optional): The axis along which to perform variance calculations. ``axis`` should be int, list(int) or tuple(int).
Alias: ``dim``.
- If ``axis`` is a list/tuple of dimension(s), variance is calculated along all element(s) of ``axis`` . ``axis`` or element(s) of ``axis`` should be in range [-D, D), where D is the dimensions of ``x`` .
- If ``axis`` or element(s) of ``axis`` is less than 0, it works the same way as :math:`axis + D` .
- If ``axis`` is None, variance is calculated over all elements of ``x``. Default is None.
unbiased (bool, optional): Whether to use the unbiased estimation. If ``unbiased`` is True, the divisor used in the computation is :math:`N - 1`, where :math:`N` represents the number of elements along ``axis`` , otherwise the divisor is :math:`N`. Default is True.
keep_dim (bool, optional): Whether to reserve the reduced dimension in the output Tensor. The result tensor will have one fewer dimension than the input unless keep_dim is true. Default is False.
name (str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
correction (int|float, optional): Difference between the sample size and sample degrees of freedom.
Defaults to 1 (Bessel's correction). If unbiased is specified, this parameter is ignored.
out (Tensor|None, optional): Output tensor. Default is None.
Returns:
Tensor, results of variance along ``axis`` of ``x``, with the same data type as ``x``.
Examples:
.. code-block:: pycon
>>> import paddle
>>> x = paddle.to_tensor([[1.0, 2.0, 3.0], [1.0, 4.0, 5.0]])
>>> out1 = paddle.var(x)
>>> print(out1.numpy())
2.6666667
>>> out2 = paddle.var(x, axis=1)
>>> print(out2.numpy())
[1. 4.3333335]
"""
if unbiased is not None and correction != 1:
raise ValueError("Only one of unbiased and correction may be given")
if unbiased is not None:
actual_correction = 1.0 if unbiased else 0.0
else:
actual_correction = float(correction)
if paddle.is_compiled_with_cuda() and in_dynamic_or_pir_mode():
return _C_ops.var(
x,
axis if axis is not None else [],
keepdim,
unbiased,
actual_correction,
out=out,
)
if not in_dynamic_mode():
check_variable_and_dtype(
x, 'x', ['float16', 'float32', 'float64'], 'var'
)
u = mean(x, axis, True, name)
dtype = paddle.float32 if x.dtype == paddle.float16 else x.dtype
out_tensor = paddle.sum(
paddle.pow((x - u), 2), axis, keepdim=keepdim, name=name, dtype=dtype
)
n = paddle.cast(paddle.numel(x), "int64") / paddle.cast(
paddle.numel(out_tensor), "int64"
)
n = n.astype(dtype)
if actual_correction != 0:
corrected_n = n - actual_correction
corrected_n = paddle.maximum(
corrected_n, paddle.zeros_like(corrected_n)
)
if paddle.in_dynamic_mode() and paddle.any(corrected_n <= 0):
warnings.warn("Degrees of freedom is <= 0.", stacklevel=2)
else:
corrected_n = n
corrected_n.stop_gradient = True
out_tensor /= corrected_n
def _replace_nan(out):
indices = paddle.arange(out.numel(), dtype='int64')
out_nan = paddle.index_fill(
out.flatten(), indices, 0, float('nan')
).reshape(out.shape)
return out_nan
if 0 in x.shape:
out_tensor = _replace_nan(out_tensor)
if len(x.shape) == 0 and actual_correction == 0:
out_tensor = paddle.to_tensor(0, stop_gradient=out_tensor.stop_gradient)
if out_tensor.dtype != x.dtype:
result = out_tensor.astype(x.dtype)
else:
result = out_tensor
if out is not None:
paddle.assign(result, out)
return out
return result
@overload
def std(
x: Tensor,
axis: int | Sequence[int] | None = None,
unbiased: bool | None = None,
keepdim: bool = False,
name: str | None = None,
*,
correction: float = 1,
out: Tensor | None = None,
) -> Tensor: ...
@overload
def std(
input: Tensor,
dim: int | Sequence[int] | None = None,
*,
correction: float = 1,
keepdim: bool = False,
out: Tensor | None = None,
) -> Tensor: ...
@use_first_signature
def std(*args: Any, **kwargs: Any) -> Tensor:
"""
Computes the standard-deviation of ``x`` along ``axis`` .
Args:
x (Tensor): The input Tensor with data type float16, float32, float64.
Alias: ``input``.
axis (int|list|tuple|None, optional): The axis along which to perform
standard-deviation calculations. ``axis`` should be int, list(int)
or tuple(int). If ``axis`` is a list/tuple of dimension(s),
standard-deviation is calculated along all element(s) of ``axis`` .
``axis`` or element(s) of ``axis`` should be in range [-D, D),
where D is the dimensions of ``x`` . If ``axis`` or element(s) of
``axis`` is less than 0, it works the same way as :math:`axis + D` .
If ``axis`` is None, standard-deviation is calculated over all
elements of ``x``. Default is None.
Alias: ``dim``.
unbiased (bool, optional): Whether to use the unbiased estimation. If
``unbiased`` is True, the standard-deviation is calculated via the
unbiased estimator. If ``unbiased`` is True, the divisor used in
the computation is :math:`N - 1`, where :math:`N` represents the
number of elements along ``axis`` , otherwise the divisor is
:math:`N`. Default is True.
keepdim (bool, optional): Whether to reserve the reduced dimension(s)
in the output Tensor. If ``keepdim`` is True, the dimensions of
the output Tensor is the same as ``x`` except in the reduced
dimensions(it is of size 1 in this case). Otherwise, the shape of
the output Tensor is squeezed in ``axis`` . Default is False.
name (str|None, optional): Name for the operation (optional, default is None).
For more information, please refer to :ref:`api_guide_Name`.
correction (int|float, optional): Difference between the sample size and sample degrees of freedom.
Defaults to 1 (Bessel's correction). If unbiased is specified, this parameter is ignored.
out (Tensor|None, optional): Output tensor. Default is None.
Returns:
Tensor, results of standard-deviation along ``axis`` of ``x``, with the
same data type as ``x``.
Examples:
.. code-block:: pycon
>>> import paddle
>>> x = paddle.to_tensor([[1.0, 2.0, 3.0], [1.0, 4.0, 5.0]])
>>> out1 = paddle.std(x)
>>> print(out1.numpy())
1.6329932
>>> out2 = paddle.std(x, unbiased=False)
>>> print(out2.numpy())
1.490712
>>> out3 = paddle.std(x, axis=1)
>>> print(out3.numpy())
[1. 2.081666]
>>> out4 = paddle.std(x=x, keepdim=True, correction=1.5)
>>> print(out4.numpy())
[[1.721326]]
>>> out5 = paddle.std(input=x, dim=[0, 1])
>>> print(out5.numpy())
1.6329932
"""
if paddle.is_compiled_with_cuda() and in_dynamic_or_pir_mode():
x = args[0] if len(args) > 0 else kwargs.get('x', kwargs.get('input'))
axis = (
args[1]
if len(args) > 1
else kwargs.get('axis', kwargs.get('dim', None))
)
unbiased = args[2] if len(args) > 2 else kwargs.get('unbiased', None)
keepdim = args[3] if len(args) > 3 else kwargs.get('keepdim', False)
correction = kwargs.get('correction', 1.0)
out = kwargs.get('out', None)
axis = axis if axis is not None else []
if unbiased is not None:
correction = 1.0 if unbiased else 0.0
else:
correction = float(correction)
return _C_ops.std(x, axis, keepdim, unbiased, correction, out=out)
variance = var(*args, **kwargs)
if 'out' in kwargs:
return paddle.sqrt(variance, out=kwargs['out'])
return paddle.sqrt(variance)
def numel(x: Tensor, name: str | None = None) -> Tensor:
"""
Returns the number of elements for a tensor, which is a 0-D int64 Tensor with shape [].
Args:
x (Tensor): The input Tensor, it's data type can be bool, float16, float32, float64, uint8, int8, int32, int64, complex64, complex128.
name (str|None, optional): Name for the operation (optional, default is None).
For more information, please refer to :ref:`api_guide_Name`.
Returns:
Tensor: The number of elements for the input Tensor, whose shape is [].
Examples:
.. code-block:: pycon
>>> import paddle
>>> x = paddle.full(shape=[4, 5, 7], fill_value=0, dtype='int32')
>>> numel = paddle.numel(x)
>>> print(numel.numpy())
140
"""
if in_dynamic_or_pir_mode():
return _C_ops.numel(x)
else:
if not isinstance(x, Variable):
raise TypeError("x must be a Tensor in numel")
helper = LayerHelper('numel', **locals())
out = helper.create_variable_for_type_inference(
dtype=core.VarDesc.VarType.INT64
)
helper.append_op(type='size', inputs={'Input': x}, outputs={'Out': out})
return out
@overload
def nanmedian(
x: Tensor,
axis: int,
keepdim: bool = ...,
mode: Literal['min'] = ...,
name: str | None = ...,
) -> tuple[Tensor, Tensor]: ...
@overload
def nanmedian(
x: Tensor,
axis: int | Sequence[int] | None = ...,
keepdim: bool = ...,
mode: Literal['avg', 'min'] = ...,
name: str | None = ...,
) -> Tensor: ...
def nanmedian(
x,
axis=None,
keepdim=False,
mode='avg',
name=None,
):
r"""
Compute the median along the specified axis, while ignoring NaNs.
If the valid count of elements is a even number,
the average value of both elements in the middle is calculated as the median.
Args:
x (Tensor): The input Tensor, it's data type can be int32, int64, float16, bfloat16, float32, float64.
axis (None|int|list|tuple, optional):
The axis along which to perform median calculations ``axis`` should be int or list of int.
``axis`` should be in range [-D, D), where D is the dimensions of ``x`` .
If ``axis`` is less than 0, it works the same way as :math:`axis + D`.
If ``axis`` is None, median is calculated over all elements of ``x``. Default is None.
keepdim (bool, optional): Whether to reserve the reduced dimension(s)
in the output Tensor. If ``keepdim`` is True, the dimensions of
the output Tensor is the same as ``x`` except in the reduced
dimensions(it is of size 1 in this case). Otherwise, the shape of
the output Tensor is squeezed in ``axis`` . Default is False.
mode (str, optional): Whether to use mean or min operation to calculate
the nanmedian values when the input tensor has an even number of non-NaN elements
along the dimension ``axis``. Support 'avg' and 'min'. Default is 'avg'.
name (str|None, optional): Name for the operation (optional, default is None).
For more information, please refer to :ref:`api_guide_Name`.
Returns:
Tensor or tuple of Tensor. If ``mode`` == 'min' and ``axis`` is int, the result
will be a tuple of two tensors (nanmedian value and nanmedian index). Otherwise,
only nanmedian value will be returned.
Examples:
.. code-block:: pycon
>>> import paddle
>>> x = paddle.to_tensor(
... [
... [float('nan'), 2.0, 3.0],
... [0.0, 1.0, 2.0],
... ]
... )
>>> y1 = x.nanmedian()
>>> print(y1.numpy())
2.0
>>> y2 = x.nanmedian(0)
>>> print(y2.numpy())
[0. 1.5 2.5]
>>> y3 = x.nanmedian(0, keepdim=True)
>>> print(y3.numpy())
[[0. 1.5 2.5]]
>>> y4 = x.nanmedian((0, 1))
>>> print(y4.numpy())
2.0
>>> y5 = x.nanmedian(mode='min')
>>> print(y5.numpy())
2.0
>>> y6, y6_index = x.nanmedian(0, mode='min')
>>> print(y6.numpy())
[0. 1. 2.]
>>> print(y6_index.numpy())
[1 1 1]
>>> y7, y7_index = x.nanmedian(1, mode='min')
>>> print(y7.numpy())
[2. 1.]
>>> print(y7_index.numpy())
[1 1]
>>> y8 = x.nanmedian((0, 1), mode='min')
>>> print(y8.numpy())
2.0
"""
if not isinstance(x, (Variable, paddle.pir.Value)):
raise TypeError("In median, the input x should be a Tensor.")
if isinstance(axis, (list, tuple)) and len(axis) == 0:
raise ValueError("Axis list should not be empty.")
if mode not in ('avg', 'min'):
raise ValueError(f"Mode {mode} is not supported. Must be avg or min.")
need_index = (axis is not None) and (not isinstance(axis, (list, tuple)))
if axis is None:
axis = []
elif isinstance(axis, tuple):
axis = list(axis)
elif isinstance(axis, int):
axis = [axis]
if in_dynamic_or_pir_mode():
out, indices = _C_ops.nanmedian(x, axis, keepdim, mode)
indices.stop_gradient = True
else:
check_variable_and_dtype(
x,
'X',
['int32', 'int64', 'float16', 'float32', 'float64', 'uint16'],
'nanmedian',
)
helper = LayerHelper('nanmedian', **locals())
attrs = {'axis': axis, 'keepdim': keepdim, 'mode': mode}
out = helper.create_variable_for_type_inference(x.dtype)
indices = helper.create_variable_for_type_inference(paddle.int64)
helper.append_op(
type='nanmedian',
inputs={'X': x},
outputs={'Out': out, 'MedianIndex': indices},
attrs=attrs,
)
indices.stop_gradient = True
if mode == 'min' and need_index:
return out, indices
else:
return out
@overload
def median(
x: Tensor,
axis: int = ...,
keepdim: bool = ...,
mode: Literal['min'] = ...,
name: str | None = ...,
*,
out: tuple[Tensor, Tensor] | None = ...,
) -> tuple[Tensor, Tensor]: ...
@overload
def median(
x: Tensor,
axis: int | None = ...,
keepdim: bool = ...,
mode: Literal['avg', 'min'] = ...,
name: str | None = ...,
) -> Tensor: ...
@param_two_alias_one_default(["x", "input"], ["axis", "dim"], ["mode", 'min'])
def median(
x,
axis=None,
keepdim=False,
mode='avg',
name=None,
*,
out=None,
):
"""
Compute the median along the specified axis.
.. note::
Alias Support: The parameter name ``input`` can be used as an alias for ``x``, and ``dim`` can be used as an alias for ``axis``.
When an alias replacement occurs, the default parameter for mode setting is min instead of avg.
For example, ``median(input=tensor_x, dim=1, ...)`` is equivalent to ``median(x=tensor_x, axis=1, ...)``.
Args:
x (Tensor): The input Tensor, it's data type can be bfloat16, float16, float32, float64, int32, int64.
alias: ``input``.
axis (int|None, optional): The axis along which to perform median calculations ``axis`` should be int.
alias: ``dim``.
``axis`` should be in range [-D, D), where D is the dimensions of ``x`` .
If ``axis`` is less than 0, it works the same way as :math:`axis + D`.
If ``axis`` is None, median is calculated over all elements of ``x``. Default is None.
keepdim (bool, optional): Whether to reserve the reduced dimension(s)
in the output Tensor. If ``keepdim`` is True, the dimensions of
the output Tensor is the same as ``x`` except in the reduced
dimensions(it is of size 1 in this case). Otherwise, the shape of
the output Tensor is squeezed in ``axis`` . Default is False.
mode (str, optional): Whether to use mean or min operation to calculate
the median values when the input tensor has an even number of elements
in the dimension ``axis``. Support 'avg' and 'min'. Default is 'avg'.
When an alias replacement occurs, the default parameter for mode setting is min instead of avg.
name (str|None, optional): Name for the operation (optional, default is None).
For more information, please refer to :ref:`api_guide_Name`.
Returns:
Tensor or tuple of Tensor.
If ``mode`` == 'avg', the result will be the tensor of median values;
If ``mode`` == 'min' and ``axis`` is None, the result will be the tensor of median values;
If ``mode`` == 'min' and ``axis`` is not None, the result will be a tuple of two tensors
containing median values and their indices.
When ``mode`` == 'avg', if data type of ``x`` is float64, data type of median values will be float64,
otherwise data type of median values will be float32.
When ``mode`` == 'min', the data type of median values will be the same as ``x``. The data type of
indices will be int64.
Examples:
.. code-block:: pycon
>>> import paddle
>>> import numpy as np
>>> x = paddle.arange(12).reshape([3, 4])
>>> print(x)
Tensor(shape=[3, 4], dtype=int64, place=Place(cpu), stop_gradient=True,
[[0 , 1 , 2 , 3 ],
[4 , 5 , 6 , 7 ],
[8 , 9 , 10, 11]])
>>> y1 = paddle.median(x)
>>> print(y1)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
5.50000000)
>>> y2 = paddle.median(x, axis=0)
>>> print(y2)
Tensor(shape=[4], dtype=float32, place=Place(cpu), stop_gradient=True,
[4., 5., 6., 7.])
>>> y3 = paddle.median(x, axis=1)
>>> print(y3)
Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.50000000, 5.50000000, 9.50000000])
>>> y4 = paddle.median(x, axis=0, keepdim=True)
>>> print(y4)
Tensor(shape=[1, 4], dtype=float32, place=Place(cpu), stop_gradient=True,
[[4., 5., 6., 7.]])
>>> y5 = paddle.median(x, mode='min')
>>> print(y5)
Tensor(shape=[], dtype=int64, place=Place(cpu), stop_gradient=True,
5)
>>> median_value, median_indices = paddle.median(x, axis=1, mode='min')
>>> print(median_value)
Tensor(shape=[3], dtype=int64, place=Place(cpu), stop_gradient=True,
[1, 5, 9])
>>> print(median_indices)
Tensor(shape=[3], dtype=int64, place=Place(cpu), stop_gradient=True,
[1, 1, 1])
>>> # cases containing nan values
>>> x = paddle.to_tensor(
... np.array(
... [
... [1, float('nan'), 3, float('nan')],
... [1, 2, 3, 4],
... [float('nan'), 1, 2, 3],
... ]
... )
... )
>>> y6 = paddle.median(x, axis=-1, keepdim=True)
>>> print(y6)
Tensor(shape=[3, 1], dtype=float64, place=Place(cpu), stop_gradient=True,
[[nan ],
[2.50000000],
[nan ]])
>>> median_value, median_indices = paddle.median(x, axis=1, keepdim=True, mode='min')
>>> print(median_value)
Tensor(shape=[3, 1], dtype=float64, place=Place(cpu), stop_gradient=True,
[[nan],
[2. ],
[nan]])
>>> print(median_indices)
Tensor(shape=[3, 1], dtype=int64, place=Place(cpu), stop_gradient=True,
[[1],
[1],
[0]])
"""
if not isinstance(x, (Variable, paddle.pir.Value)):
raise TypeError("In median, the input x should be a Tensor.")
if isinstance(axis, (list, tuple)) and len(axis) == 0:
raise ValueError("Axis list should not be empty.")
dims = len(x.shape)
if dims == 0:
assert axis in [
-1,
0,
None,
], 'when input 0-D, axis can only be [-1, 0] or default None'
elif axis is not None:
if not isinstance(axis, int) or not (axis < dims and axis >= -dims):
raise ValueError(
"In median, axis should be none or an integer in range [-rank(x), rank(x))."
)
if mode not in ('avg', 'min'):
raise ValueError(f"Mode {mode} is not supported. Must be avg or min.")
need_idx = axis is not None
if axis is None:
is_flatten = True
if axis is None:
axis = []
elif isinstance(axis, int):
axis = [axis]
if mode == "avg" and not x.dtype == paddle.float64:
x = x.astype(paddle.float32)
values, indices = _C_ops.median(x, axis, keepdim, mode, out=out)
indices.stop_gradient = True
if mode == 'min' and need_idx:
return values, indices
else:
return values
def _compute_quantile(
x: Tensor,
q: float | Sequence[float] | Tensor | None,
axis: int | list[int] | None = None,
keepdim: bool = False,
interpolation: _Interpolation = "linear",
ignore_nan: bool = False,
out: Tensor | None = None,
) -> Tensor:
"""
Compute the quantile of the input along the specified axis.
Args:
x (Tensor): The input Tensor, it's data type can be float32, float64, int32, int64.
q (int|float|list|Tensor): The q for calculate quantile, which should be in range [0, 1]. If q is a list or
a 1-D Tensor, each element of q will be calculated and the first dimension of output is same to the number of ``q`` .
If q is a 0-D Tensor, it will be treated as an integer or float.
axis (int|list, optional): The axis along which to calculate quantile. ``axis`` should be int or list of int.
``axis`` should be in range [-D, D), where D is the dimensions of ``x`` .
If ``axis`` is less than 0, it works the same way as :math:`axis + D`.
If ``axis`` is a list, quantile is calculated over all elements of given axes.
If ``axis`` is None, quantile is calculated over all elements of ``x``. Default is None.
keepdim (bool, optional): Whether to reserve the reduced dimension(s)
in the output Tensor. If ``keepdim`` is True, the dimensions of
the output Tensor is the same as ``x`` except in the reduced
dimensions(it is of size 1 in this case). Otherwise, the shape of
the output Tensor is squeezed in ``axis`` . Default is False.
interpolation (str, optional): The interpolation method to use
when the desired quantile falls between two data points. Must be one of linear, higher,
lower, midpoint and nearest. Default is linear.
ignore_nan: (bool, optional): Whether to ignore NaN of input Tensor.
If ``ignore_nan`` is True, it will calculate nanquantile.
Otherwise it will calculate quantile. Default is False.
out (Tensor|None, optional): The output tensor. Default: None.
Returns:
Tensor, results of quantile along ``axis`` of ``x``.
In order to obtain higher precision, data type of results will be float64.
"""
# Validate x
if not isinstance(x, (Variable, paddle.pir.Value)):
raise TypeError("input x should be a Tensor.")
# Validate q
if isinstance(q, (int, float)):
q = [q]
elif isinstance(q, (list, tuple)):
if len(q) <= 0:
raise ValueError("q should not be empty")
elif isinstance(q, (Variable, paddle.pir.Value)):
if len(q.shape) > 1:
raise ValueError("q should be a 0-D tensor or a 1-D tensor")
if len(q.shape) == 0:
q = [q]
else:
raise TypeError(
"Type of q should be int, float, list or tuple, or tensor"
)
for q_num in q:
# we do not validate tensor q in static mode
if not in_dynamic_mode() and isinstance(
q_num, (Variable, paddle.pir.Value)
):
break
if q_num < 0 or q_num > 1:
raise ValueError("q should be in range [0, 1]")
if interpolation not in [
"linear",
"lower",
"higher",
"nearest",
"midpoint",
]:
raise ValueError(
f"interpolation must be one of 'linear', 'lower', 'higher', 'nearest' or 'midpoint', but got {interpolation}"
)
# Validate axis
dims = len(x.shape)
out_shape = list(x.shape)
if axis is None:
x = paddle.flatten(x)
axis = 0
out_shape = [1] * dims
else:
if isinstance(axis, list):
axis_src, axis_dst = [], []
for axis_single in axis:
if not isinstance(axis_single, int) or not (
axis_single < dims and axis_single >= -dims
):
raise ValueError(
"Axis should be None, int, or a list, element should in range [-rank(x), rank(x))."
)
if axis_single < 0:
axis_single = axis_single + dims
axis_src.append(axis_single)
out_shape[axis_single] = 1
axis_dst = list(range(-len(axis), 0))
x = paddle.moveaxis(x, axis_src, axis_dst)
if len(axis_dst) == 0:
x = paddle.flatten(x)
axis = 0
else:
x = paddle.flatten(x, axis_dst[0], axis_dst[-1])
axis = axis_dst[0]
else:
if not isinstance(axis, int) or not (axis < dims and axis >= -dims):
raise ValueError(
"Axis should be None, int, or a list, element should in range [-rank(x), rank(x))."
)
if axis < 0:
axis += dims
out_shape[axis] = 1
mask = x.isnan()
valid_counts = mask.logical_not().sum(axis=axis, keepdim=True)
indices = []
for q_num in q:
if in_dynamic_or_pir_mode():
q_num = paddle.to_tensor(q_num, dtype=x.dtype)
if ignore_nan:
indices.append(q_num * (valid_counts - 1))
else:
index = q_num * (valid_counts - 1)
last_index = x.shape[axis] - 1
nums = paddle.full_like(index, fill_value=last_index)
index = paddle.where(mask.any(axis=axis, keepdim=True), nums, index)
indices.append(index)
sorted_tensor = paddle.sort(x, axis)
def _compute_index(index):
if interpolation == "nearest":
idx = paddle.round(index).astype(paddle.int32)
return paddle.take_along_axis(sorted_tensor, idx, axis=axis)
indices_below = paddle.floor(index).astype(paddle.int32)
if interpolation != "higher":
# avoid unnecessary compute
tensor_below = paddle.take_along_axis(
sorted_tensor, indices_below, axis=axis
)
if interpolation == "lower":
return tensor_below
indices_upper = paddle.ceil(index).astype(paddle.int32)
tensor_upper = paddle.take_along_axis(
sorted_tensor, indices_upper, axis=axis
)
if interpolation == "higher":
return tensor_upper
if interpolation == "midpoint":
return (
tensor_upper.astype(x.dtype) + tensor_below.astype(x.dtype)
) / 2
weights = (index - indices_below.astype(index.dtype)).astype(x.dtype)
# "linear"
return paddle.lerp(
tensor_below.astype(x.dtype),
tensor_upper.astype(x.dtype),
weights,
)
outputs = []
# TODO(chenjianye): replace the for-loop to directly take elements.
for index in indices:
ret = _compute_index(index)
if not keepdim:
ret = paddle.squeeze(ret, axis=axis)
else:
ret = ret.reshape(out_shape)
outputs.append(ret)
if len(outputs) > 1:
outputs = paddle.stack(outputs, 0)
else:
outputs = outputs[0]
if out is not None:
paddle.assign(outputs, out)
return out
return outputs
@param_two_alias(["x", "input"], ["axis", "dim"])
def quantile(
x: Tensor,
q: float | Sequence[float] | Tensor,
axis: int | list[int] | None = None,
keepdim: bool = False,
interpolation: _Interpolation = "linear",
name: str | None = None,
*,
out: Tensor | None = None,
) -> Tensor:
"""
Compute the quantile of the input along the specified axis.
If any values in a reduced row are NaN, then the quantiles for that reduction will be NaN.
Args:
x (Tensor): The input Tensor, it's data type can be float32, float64, int32, int64.
q (int|float|list|Tensor): The q for calculate quantile, which should be in range [0, 1]. If q is a list or
a 1-D Tensor, each element of q will be calculated and the first dimension of output is same to the number of ``q`` .
If q is a 0-D Tensor, it will be treated as an integer or float.
axis (int|list, optional): The axis along which to calculate quantile. ``axis`` should be int or list of int.
``axis`` should be in range [-D, D), where D is the dimensions of ``x`` .
If ``axis`` is less than 0, it works the same way as :math:`axis + D`.
If ``axis`` is a list, quantile is calculated over all elements of given axes.
If ``axis`` is None, quantile is calculated over all elements of ``x``. Default is None.
keepdim (bool, optional): Whether to reserve the reduced dimension(s)
in the output Tensor. If ``keepdim`` is True, the dimensions of
the output Tensor is the same as ``x`` except in the reduced
dimensions(it is of size 1 in this case). Otherwise, the shape of
the output Tensor is squeezed in ``axis`` . Default is False.
interpolation (str, optional): The interpolation method to use
when the desired quantile falls between two data points. Must be one of linear, higher,
lower, midpoint and nearest. Default is linear.
name (str, optional): Name for the operation (optional, default is None).
For more information, please refer to :ref:`api_guide_Name`.
out (Tensor|None, optional): The output tensor. Default: None.
Returns:
Tensor, results of quantile along ``axis`` of ``x``.
Examples:
.. code-block:: pycon
>>> import paddle
>>> y = paddle.arange(0, 8, dtype="float32").reshape([4, 2])
>>> print(y)
Tensor(shape=[4, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0., 1.],
[2., 3.],
[4., 5.],
[6., 7.]])
>>> y1 = paddle.quantile(y, q=0.5, axis=[0, 1])
>>> print(y1)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
3.50000000)
>>> y2 = paddle.quantile(y, q=0.5, axis=1)
>>> print(y2)
Tensor(shape=[4], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.50000000, 2.50000000, 4.50000000, 6.50000000])
>>> y3 = paddle.quantile(y, q=[0.3, 0.5], axis=0)
>>> print(y3)
Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
[[1.80000007, 2.80000019],
[3. , 4. ]])
>>> y[0, 0] = float("nan")
>>> y4 = paddle.quantile(y, q=0.8, axis=1, keepdim=True)
>>> print(y4)
Tensor(shape=[4, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
[[nan ],
[2.79999995],
[4.80000019],
[6.80000019]])
"""
return _compute_quantile(
x,
q,
axis=axis,
keepdim=keepdim,
interpolation=interpolation,
ignore_nan=False,
out=out,
)
@param_two_alias(["x", "input"], ["axis", "dim"])
def nanquantile(
x: Tensor,
q: float | Sequence[float] | Tensor,
axis: list[int] | int | None = None,
keepdim: bool = False,
interpolation: _Interpolation = "linear",
*,
out: Tensor | None = None,
) -> Tensor:
"""
Compute the quantile of the input as if NaN values in input did not exist.
If all values in a reduced row are NaN, then the quantiles for that reduction will be NaN.
Args:
x (Tensor): The input Tensor, it's data type can be float32, float64, int32, int64.
Alias: ``input``.
q (int|float|list|Tensor): The q for calculate quantile, which should be in range [0, 1]. If q is a list or
a 1-D Tensor, each element of q will be calculated and the first dimension of output is same to the number of ``q`` .
If q is a 0-D Tensor, it will be treated as an integer or float.
axis (int|list, optional): The axis along which to calculate quantile. ``axis`` should be int or list of int.
``axis`` should be in range [-D, D), where D is the dimensions of ``x`` .
If ``axis`` is less than 0, it works the same way as :math:`axis + D`.
If ``axis`` is a list, quantile is calculated over all elements of given axes.
If ``axis`` is None, quantile is calculated over all elements of ``x``. Default is None.
Alias: ``dim``.
keepdim (bool, optional): Whether to reserve the reduced dimension(s)
in the output Tensor. If ``keepdim`` is True, the dimensions of
the output Tensor is the same as ``x`` except in the reduced
dimensions(it is of size 1 in this case). Otherwise, the shape of
the output Tensor is squeezed in ``axis`` . Default is False.
interpolation (str, optional): The interpolation method to use
when the desired quantile falls between two data points. Must be one of linear, higher,
lower, midpoint and nearest. Default is linear.
Keyword Args:
out (Tensor|None, optional): The output tensor. Default: None.
Returns:
Tensor, results of quantile along ``axis`` of ``x``.
Examples:
.. code-block:: pycon
>>> import paddle
>>> x = paddle.to_tensor(
... [
... [0, 1, 2, 3, 4],
... [5, 6, 7, 8, 9],
... ],
... dtype="float32",
... )
>>> x[0, 0] = float("nan")
>>> y1 = paddle.nanquantile(x, q=0.5, axis=[0, 1])
>>> print(y1)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
5.)
>>> y2 = paddle.nanquantile(x, q=0.5, axis=1)
>>> print(y2)
Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True,
[2.50000000, 7. ])
>>> y3 = paddle.nanquantile(x, q=[0.3, 0.5], axis=0)
>>> print(y3)
Tensor(shape=[2, 5], dtype=float32, place=Place(cpu), stop_gradient=True,
[[5. , 2.50000000, 3.50000000, 4.50000000, 5.50000000],
[5. , 3.50000000, 4.50000000, 5.50000000, 6.50000000]])
>>> y4 = paddle.nanquantile(x, q=0.8, axis=1, keepdim=True)
>>> print(y4)
Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
[[3.40000010],
[8.19999981]])
>>> nan = paddle.full(shape=[2, 3], fill_value=float("nan"))
>>> y5 = paddle.nanquantile(nan, q=0.8, axis=1, keepdim=True)
>>> print(y5)
Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
[[nan],
[nan]])
"""
return _compute_quantile(
x,
q,
axis=axis,
keepdim=keepdim,
interpolation=interpolation,
ignore_nan=True,
out=out,
)