Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【PaddlePaddle Hackathon 3 No.15】为 Paddle 新增 count_nonzero #44169

Merged
merged 5 commits into from Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
112 changes: 112 additions & 0 deletions python/paddle/fluid/tests/unittests/test_count_nonzero_api.py
@@ -0,0 +1,112 @@
# 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)

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

def test_case(x, axis=None, keepdim=False):
if isinstance(axis, list):
axis = list(axis)
if len(axis) == 0:
axis = None
x_tensor = paddle.to_tensor(x, stop_gradient=False)
y = paddle.count_nonzero(x_tensor, axis, keepdim)
dx = paddle.grad(y, x_tensor)[0].numpy()
self.assertEqual(
np.allclose(np.sum(np.ones_like(x, dtype=np.int64)), dx.sum()),
True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the gradient of x obtained by this code correct in math? or x should have gradient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reminding me, Mr. Gao. Here this count operator was mistaken for sum operator by me. According to gradient law, x is not supposed to have gradient. @jeff41404


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()


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 @@ -1316,6 +1316,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