diff --git a/python/paddle/__init__.py b/python/paddle/__init__.py index 7d0ec4981b8257bdf9dc24854d21d10150ac7de3..a10481db95857cb6415ac1ac44cbd2676472d803 100755 --- a/python/paddle/__init__.py +++ b/python/paddle/__init__.py @@ -197,6 +197,7 @@ from .tensor.math import sqrt # noqa: F401 from .tensor.math import square # noqa: F401 from .tensor.math import stanh # noqa: F401 from .tensor.math import sum # noqa: F401 +from .tensor.math import nansum # noqa: F401 from .tensor.math import tanh # noqa: F401 from .tensor.math import tanh_ # noqa: F401 from .tensor.math import add_n # noqa: F401 @@ -524,6 +525,7 @@ __all__ = [ # noqa 'ones', 'not_equal', 'sum', + 'nansum', 'tile', 'greater_equal', 'isfinite', diff --git a/python/paddle/fluid/tests/unittests/test_nansum_api.py b/python/paddle/fluid/tests/unittests/test_nansum_api.py new file mode 100644 index 0000000000000000000000000000000000000000..a9fc285d2d9d0494348c40e1ecc30480eac95f17 --- /dev/null +++ b/python/paddle/fluid/tests/unittests/test_nansum_api.py @@ -0,0 +1,101 @@ +# Copyright (c) 2018 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 print_function + +import unittest +import numpy as np +import paddle +import paddle.fluid as fluid +import paddle.fluid.core as core +from paddle.fluid import Program, program_guard + + +class API_Test_Nansum(unittest.TestCase): + def test_static_graph(self): + paddle.enable_static() + startup_program = fluid.Program() + train_program = fluid.Program() + with fluid.program_guard(train_program, startup_program): + input = fluid.data(name='input', dtype='float32', shape=[2, 4]) + out1 = paddle.nansum(input) + out2 = paddle.nansum(input, axis=0) + out3 = paddle.nansum(input, axis=-1) + out4 = paddle.nansum(input, axis=1, keepdim=True) + place = fluid.CPUPlace() + if fluid.core.is_compiled_with_cuda(): + place = fluid.CUDAPlace(0) + exe = fluid.Executor(place) + exe.run(startup_program) + + x = np.array([[float('nan'), 3, 5, 9], + [1, 2, float('-nan'), 7]]).astype(np.float32) + res = exe.run(train_program, + feed={'input': x}, + fetch_list=[out1, out2, out3, out4]) + + out1_np = np.array(res[0]) + out2_np = np.array(res[1]) + out3_np = np.array(res[2]) + out4_np = np.array(res[3]) + out1_ref = np.array([27]).astype(np.float32) + out2_ref = np.array([1, 5, 5, 16]).astype(np.float32) + out3_ref = np.array([17, 10]).astype(np.float32) + out4_ref = np.array([[17], [10]]).astype(np.float32) + + self.assertTrue( + (out1_np == out1_ref).all(), + msg='nansum output is wrong, out =' + str(out1_np)) + self.assertTrue( + (out2_np == out2_ref).all(), + msg='nansum output is wrong, out =' + str(out2_np)) + self.assertTrue( + (out3_np == out3_ref).all(), + msg='nansum output is wrong, out =' + str(out3_np)) + self.assertTrue( + (out4_np == out4_ref).all(), + msg='nansum output is wrong, out =' + str(out4_np)) + + def test_error_api(self): + paddle.enable_static() + + ## input dtype error + def run1(): + input = fluid.data(name='input', dtype='float16', shape=[2, 3]) + output = paddle.nansum(input) + + self.assertRaises(TypeError, run1) + + ## axis type error + def run2(): + input = fluid.data(name='input', dtype='float16', shape=[2, 3]) + output = paddle.nansum(input, axis=1.2) + + self.assertRaises(TypeError, run2) + + def test_dygraph(self): + x = np.array([[float('nan'), 3, 5, 9], + [1, 2, float('-nan'), 7]]).astype(np.float32) + with fluid.dygraph.guard(): + inputs = fluid.dygraph.to_variable(x) + out = paddle.nansum(inputs) + out_ref = np.array([27]).astype(np.float32) + + self.assertTrue( + (out.numpy() == out_ref).all(), + msg='nansum output is wrong, out =' + str(out.numpy())) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/paddle/tensor/__init__.py b/python/paddle/tensor/__init__.py index d0cd9201903142a310d8df62b90f09c758eaea6a..ce6c3e5350f6abb2c9cefda76928a27f582e774d 100755 --- a/python/paddle/tensor/__init__.py +++ b/python/paddle/tensor/__init__.py @@ -157,6 +157,7 @@ from .math import sqrt_ # noqa: F401 from .math import square # noqa: F401 from .math import stanh # noqa: F401 from .math import sum # noqa: F401 +from .math import nansum # noqa: F401 from .math import tanh # noqa: F401 from .math import tanh_ # noqa: F401 from .math import add_n # noqa: F401 @@ -315,6 +316,7 @@ tensor_method_func = [ #noqa 'square', 'stanh', 'sum', + 'nansum', 'tanh', 'tanh_', 'add_n', diff --git a/python/paddle/tensor/math.py b/python/paddle/tensor/math.py index dcc241b583055f0e60c35055989a2e3304314757..2e2d443e5c852748333e67caf97a502e2cbd9fae 100755 --- a/python/paddle/tensor/math.py +++ b/python/paddle/tensor/math.py @@ -905,6 +905,66 @@ def sum(x, axis=None, dtype=None, keepdim=False, name=None): return out +def nansum(x, axis=None, dtype=None, keepdim=False, name=None): + """ + Computes the sum of tensor elements over the given axis, treating Not a Numbers (NaNs) as zero. + + Args: + x (Tensor): An N-D Tensor, the data type is float32, float64, int32 or int64. + axis (int|list|tuple, optional): The dimensions along which the nansum is performed. If + :attr:`None`, nansum all elements of :attr:`x` and return a + Tensor with a single element, otherwise must be in the + range :math:`[-rank(x), rank(x))`. If :math:`axis[i] < 0`, + the dimension to reduce is :math:`rank + axis[i]`. + dtype (str, optional): The dtype of output Tensor. The default value is None, the dtype + of output is the same as input Tensor `x`. + keepdim (bool, optional): Whether to reserve the reduced dimension in the + output Tensor. The result Tensor will have one fewer dimension + than the :attr:`x` unless :attr:`keepdim` is true, default + value is False. + name (str, optional): The default value is None. Normally there is no need for + user to set this property. For more information, please refer to :ref:`api_guide_Name` + + Returns: + Tensor: Results of summation operation on the specified axis of input Tensor `x`, + + Examples: + .. code-block:: python + + import paddle + import numpy as np + + # x is a Tensor with following elements: + # [[nan, 0.3, 0.5, 0.9] + # [0.1, 0.2, -nan, 0.7]] + # Each example is followed by the corresponding output tensor. + x = np.array([[float('nan'), 0.3, 0.5, 0.9], + [0.1, 0.2, float('-nan'), 0.7]]).astype(np.float32) + x = paddle.to_tensor(x) + out1 = paddle.nansum(x) # [2.7] + out2 = paddle.nansum(x, axis=0) # [0.1, 0.5, 0.5, 1.6] + out3 = paddle.nansum(x, axis=-1) # [1.7, 1.0] + out4 = paddle.nansum(x, axis=1, keepdim=True) # [[1.7], [1.0]] + + # y is a Tensor with shape [2, 2, 2] and elements as below: + # [[[1, nan], [3, 4]], + # [[5, 6], [-nan, 8]]] + # Each example is followed by the corresponding output tensor. + y = np.array([[[1, float('nan')], [3, 4]], + [[5, 6], [float('-nan'), 8]]]) + y = paddle.to_tensor(y) + out5 = paddle.nansum(y, axis=[1, 2]) # [8, 19] + out6 = paddle.nansum(y, axis=[0, 1]) # [9, 18] + """ + check_variable_and_dtype( + x, 'x', ['float32', 'float64', 'int32', 'int64'], 'nansum') + check_type(axis, 'axis', (int, list, tuple, type(None)), 'nansum') + + zero_tensor = paddle.zeros_like(x) + tmp_tensor = paddle.where(isnan(x), zero_tensor, x) + return sum(tmp_tensor, axis, dtype, keepdim, name) + + @templatedoc(op_type="sum") def add_n(inputs, name=None): """