Skip to content

Commit

Permalink
【PaddlePaddle Hackathon 3 No.15】为 Paddle 新增 count_nonzero (#44169)
Browse files Browse the repository at this point in the history
* add count_nonzero api

* remove grad test
  • Loading branch information
thunder95 committed Jul 29, 2022
1 parent 0551566 commit a6c50a6
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 0 deletions.
2 changes: 2 additions & 0 deletions python/paddle/__init__.py
Expand Up @@ -220,6 +220,7 @@
from .tensor.math import sum # noqa: F401
from .tensor.math import nansum # noqa: F401
from .tensor.math import nanmean # noqa: F401
from .tensor.math import count_nonzero # noqa: F401
from .tensor.math import tanh # noqa: F401
from .tensor.math import tanh_ # noqa: F401
from .tensor.math import add_n # noqa: F401
Expand Down Expand Up @@ -560,6 +561,7 @@
'sum',
'nansum',
'nanmean',
'count_nonzero',
'tile',
'greater_equal',
'isfinite',
Expand Down
86 changes: 86 additions & 0 deletions python/paddle/fluid/tests/unittests/test_count_nonzero_api.py
@@ -0,0 +1,86 @@
# 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 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

np.random.seed(10)


class TestCountNonzeroAPI(unittest.TestCase):
# test paddle.tensor.math.count_nonzero

def setUp(self):
self.x_shape = [2, 3, 4, 5]
self.x = np.random.uniform(-1, 1, self.x_shape).astype(np.float32)
self.place = paddle.CUDAPlace(0) if core.is_compiled_with_cuda() \
else paddle.CPUPlace()

def test_api_static(self):
paddle.enable_static()
with paddle.static.program_guard(paddle.static.Program()):
x = paddle.fluid.data('X', self.x_shape)
out1 = paddle.count_nonzero(x)
out2 = paddle.tensor.count_nonzero(x)
out3 = paddle.tensor.math.count_nonzero(x)
axis = np.arange(len(self.x_shape)).tolist()
out4 = paddle.count_nonzero(x, axis)
out5 = paddle.count_nonzero(x, tuple(axis))
exe = paddle.static.Executor(self.place)
res = exe.run(feed={'X': self.x},
fetch_list=[out1, out2, out3, out4, out5])
out_ref = np.count_nonzero(self.x)
for out in res:
self.assertEqual(np.allclose(out, out_ref), True)

def test_api_dygraph(self):
paddle.disable_static(self.place)

def test_case(x, axis=None, keepdim=False):
x_tensor = paddle.to_tensor(x)
out = paddle.count_nonzero(x_tensor, axis=axis, keepdim=keepdim)
if isinstance(axis, list):
axis = tuple(axis)
if len(axis) == 0:
axis = None

out_ref = np.count_nonzero(x, axis, keepdims=keepdim)
self.assertEqual(np.allclose(out.numpy(), out_ref), True)

test_case(self.x)
test_case(self.x, None)
test_case(self.x, -1)
test_case(self.x, keepdim=True)
test_case(self.x, 2, keepdim=True)
test_case(self.x, [0, 2])
test_case(self.x, (0, 2))
test_case(self.x, (0, 1, 3))
test_case(self.x, [0, 1, 2, 3])
paddle.enable_static()

def test_errors(self):
paddle.enable_static()
with paddle.static.program_guard(paddle.static.Program()):
x = paddle.fluid.data('X', [10, 12], 'int32')
self.assertRaises(ValueError, paddle.count_nonzero, x, axis=10)


if __name__ == "__main__":
unittest.main()
2 changes: 2 additions & 0 deletions python/paddle/tensor/__init__.py
Expand Up @@ -168,6 +168,7 @@
from .math import sum # noqa: F401
from .math import nansum # noqa: F401
from .math import nanmean # noqa: F401
from .math import count_nonzero # noqa: F401
from .math import tanh # noqa: F401
from .math import tanh_ # noqa: F401
from .math import add_n # noqa: F401
Expand Down Expand Up @@ -343,6 +344,7 @@
'sum',
'nansum',
'nanmean',
'count_nonzero',
'tanh',
'tanh_',
'add_n',
Expand Down
66 changes: 66 additions & 0 deletions python/paddle/tensor/math.py
Expand Up @@ -1315,6 +1315,72 @@ def nanmean(x, axis=None, keepdim=False, name=None):
return paddle.divide(paddle.nansum(x, axis=axis, keepdim=keepdim, name=name), cnt.astype(x.dtype))


def count_nonzero(x, axis=None, keepdim=False, name=None):
r"""
Counts the number of non-zero values in the tensor x along the specified axis.
Args:
x (Tensor): An N-D Tensor, the data type is bool, float16, float32, float64, int32 or int64.
axis (int|list|tuple, optional): The dimensions along which the sum is performed. If
:attr:`None`, sum 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]`.
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): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
Returns:
Tensor: Results of count operation on the specified axis of input Tensor `x`, it's data type is `'int64'`.
Examples:
.. code-block:: python
:name: count_nonzero-example
import paddle
# x is a 2-D Tensor:
x = paddle.to_tensor([[0., 1.1, 1.2], [0., 0., 1.3], [0., 0., 0.]])
out1 = paddle.count_nonzero(x)
# [3]
out2 = paddle.count_nonzero(x, axis=0)
# [0, 1, 2]
out3 = paddle.count_nonzero(x, axis=0, keepdim=True)
# [[0, 1, 2]]
out4 = paddle.count_nonzero(x, axis=1)
# [2, 1, 0]
out5 = paddle.count_nonzero(x, axis=1, keepdim=True)
#[[2],
# [1],
# [0]]
# y is a 3-D Tensor:
y = paddle.to_tensor([[[0., 1.1, 1.2], [0., 0., 1.3], [0., 0., 0.]],
[[0., 2.5, 2.6], [0., 0., 2.4], [2.1, 2.2, 2.3]]])
out6 = paddle.count_nonzero(y, axis=[1, 2])
# [3, 6]
out7 = paddle.count_nonzero(y, axis=[0, 1])
# [1, 3, 5]
"""


if axis is not None:
if isinstance(axis, int):
axis = [axis]
dims = len(x.shape)
for i in range(len(axis)):
if not isinstance(axis[i], int) or not (axis[i] < dims and axis[i] >= -dims):
raise ValueError(
"Axis should be None, int, or a list, element should in range [-rank(x), rank(x))."
)

bool_tensor = paddle.cast(x, 'bool')
int_tensor = paddle.cast(bool_tensor, 'int64')
return paddle.sum(int_tensor, axis=axis, keepdim=keepdim, name=name)


@templatedoc(op_type="sum")
def add_n(inputs, name=None):
"""
Expand Down

0 comments on commit a6c50a6

Please sign in to comment.