From 8b2436a776e5cb5d0ebc8bc06f9624ecd87c9189 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Wed, 4 Nov 2020 10:43:33 +0800 Subject: [PATCH] Add broadcast_shape api (#28257) * add broadcast_shape api * add ut * follow comments * add example code, test=dodument_fix * update example code, test=document_fix --- .../operators/common_infer_shape_functions.cc | 27 +++++++++----- .../operators/common_infer_shape_functions.h | 5 ++- paddle/fluid/pybind/pybind.cc | 7 ++++ python/paddle/__init__.py | 4 ++- .../tests/unittests/test_broadcast_shape.py | 35 +++++++++++++++++++ python/paddle/tensor/__init__.py | 4 ++- python/paddle/tensor/math.py | 30 +++++++++++++++- 7 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 python/paddle/fluid/tests/unittests/test_broadcast_shape.py diff --git a/paddle/fluid/operators/common_infer_shape_functions.cc b/paddle/fluid/operators/common_infer_shape_functions.cc index ce622d7501f..c10bba74ce7 100644 --- a/paddle/fluid/operators/common_infer_shape_functions.cc +++ b/paddle/fluid/operators/common_infer_shape_functions.cc @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "paddle/fluid/operators/common_infer_shape_functions.h" + #include #include @@ -28,6 +29,7 @@ class InferShapeContext; namespace paddle { namespace operators { namespace details { + inline void GetBroadcastDimsArrays(const framework::DDim &x_dims, const framework::DDim &y_dims, int *x_dims_array, int *y_dims_array, @@ -76,6 +78,20 @@ inline void GetBroadcastDimsArrays(const framework::DDim &x_dims, } } } + +framework::DDim BroadcastTwoDims(const framework::DDim &x_dims, + const framework::DDim &y_dims, int axis) { + int max_dim = std::max(x_dims.size(), y_dims.size()); + axis = (axis == -1 ? std::abs(x_dims.size() - y_dims.size()) : axis); + std::vector x_dims_array(max_dim); + std::vector y_dims_array(max_dim); + std::vector out_dims_array(max_dim); + GetBroadcastDimsArrays(x_dims, y_dims, x_dims_array.data(), + y_dims_array.data(), out_dims_array.data(), max_dim, + axis); + return framework::make_ddim(out_dims_array); +} + } // namespace details // shape input(0) -> output(0) without change. @@ -153,16 +169,9 @@ void BinaryOpBroadcastInferShape(framework::InferShapeContext *ctx) { ctx->ShareDim(x_name, /*->*/ out_name); ctx->ShareLoD(x_name, /*->*/ out_name); } else { - int max_dim = std::max(x_dims.size(), y_dims.size()); int axis = ctx->Attrs().Get("axis"); - axis = (axis == -1 ? std::abs(x_dims.size() - y_dims.size()) : axis); - std::vector x_dims_array(max_dim); - std::vector y_dims_array(max_dim); - std::vector out_dims_array(max_dim); - details::GetBroadcastDimsArrays(x_dims, y_dims, x_dims_array.data(), - y_dims_array.data(), out_dims_array.data(), - max_dim, axis); - ctx->SetOutputDim(out_name, framework::make_ddim(out_dims_array)); + auto out_dims = details::BroadcastTwoDims(x_dims, y_dims, axis); + ctx->SetOutputDim(out_name, out_dims); ctx->ShareLoD(x_name, /*->*/ out_name); } } diff --git a/paddle/fluid/operators/common_infer_shape_functions.h b/paddle/fluid/operators/common_infer_shape_functions.h index 922d5262abc..2c28db4324e 100644 --- a/paddle/fluid/operators/common_infer_shape_functions.h +++ b/paddle/fluid/operators/common_infer_shape_functions.h @@ -28,7 +28,10 @@ class InferShapeContext; namespace paddle { namespace operators { - +namespace details { +framework::DDim BroadcastTwoDims(const framework::DDim& x_dims, + const framework::DDim& y_dims, int axis = -1); +} // shape input(0) -> output(0) without change. void UnaryOpUnchangedInferShape(framework::InferShapeContext* ctx); // shape input(0) -> output(0) without change, check if axis in range [-Rank(x), diff --git a/paddle/fluid/pybind/pybind.cc b/paddle/fluid/pybind/pybind.cc index 736669fa4ef..a7e3cd82d26 100644 --- a/paddle/fluid/pybind/pybind.cc +++ b/paddle/fluid/pybind/pybind.cc @@ -54,6 +54,7 @@ limitations under the License. */ #include "paddle/fluid/memory/allocation/allocator_strategy.h" #include "paddle/fluid/memory/allocation/mmap_allocator.h" #include "paddle/fluid/operators/activation_op.h" +#include "paddle/fluid/operators/common_infer_shape_functions.h" #include "paddle/fluid/operators/py_func_op.h" #include "paddle/fluid/platform/cpu_helper.h" #include "paddle/fluid/platform/cpu_info.h" @@ -467,6 +468,12 @@ PYBIND11_MODULE(core_noavx, m) { << ", sci_mode=" << print_opt.sci_mode; }); + m.def("broadcast_shape", [](const std::vector &x_dim, + const std::vector &y_dim) { + return vectorize(operators::details::BroadcastTwoDims( + make_ddim(x_dim), make_ddim(y_dim), -1)); + }); + m.def( "_append_python_callable_object_and_return_id", [](py::object py_obj) -> size_t { diff --git a/python/paddle/__init__.py b/python/paddle/__init__.py index c8e0d830f4e..50c1142c7bf 100755 --- a/python/paddle/__init__.py +++ b/python/paddle/__init__.py @@ -200,6 +200,8 @@ from .tensor.math import isfinite #DEFINE_ALIAS from .tensor.math import isinf #DEFINE_ALIAS from .tensor.math import isnan #DEFINE_ALIAS from .tensor.math import prod #DEFINE_ALIAS +from .tensor.math import broadcast_shape #DEFINE_ALIAS + from .tensor.random import multinomial #DEFINE_ALIAS from .tensor.random import standard_normal from .tensor.random import normal @@ -220,7 +222,7 @@ from .tensor.search import index_select #DEFINE_ALIAS from .tensor.search import nonzero #DEFINE_ALIAS from .tensor.search import sort #DEFINE_ALIAS -from .tensor.to_string import set_printoptions +from .tensor.to_string import set_printoptions #DEFINE_ALIAS from .framework.random import seed #DEFINE_ALIAS from .framework.random import get_cuda_rng_state #DEFINE_ALIAS diff --git a/python/paddle/fluid/tests/unittests/test_broadcast_shape.py b/python/paddle/fluid/tests/unittests/test_broadcast_shape.py new file mode 100644 index 00000000000..b4ac096a696 --- /dev/null +++ b/python/paddle/fluid/tests/unittests/test_broadcast_shape.py @@ -0,0 +1,35 @@ +# 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. + +import unittest +import numpy as np +import paddle + + +class TestBroadcastShape(unittest.TestCase): + def test_result(self): + shape = paddle.broadcast_shape([2, 1, 3], [1, 3, 1]) + self.assertEqual(shape, [2, 3, 3]) + + shape = paddle.broadcast_shape( + [-1, 1, 3], [1, 3, 1]) #support compile time infershape + self.assertEqual(shape, [-1, 3, 3]) + + def test_error(self): + self.assertRaises(ValueError, paddle.broadcast_shape, [2, 1, 3], + [3, 3, 1]) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/paddle/tensor/__init__.py b/python/paddle/tensor/__init__.py index 43e6c9654c4..2a9820d4a90 100755 --- a/python/paddle/tensor/__init__.py +++ b/python/paddle/tensor/__init__.py @@ -164,6 +164,8 @@ from .math import isnan #DEFINE_ALIAS from .math import prod #DEFINE_ALIAS from .math import all #DEFINE_ALIAS from .math import any #DEFINE_ALIAS +from .math import broadcast_shape #DEFINE_ALIAS + from .random import multinomial #DEFINE_ALIAS from .random import standard_normal from .random import normal @@ -194,4 +196,4 @@ from .stat import median #DEFINE_ALIAS # from .tensor import Tensor #DEFINE_ALIAS # from .tensor import LoDTensor #DEFINE_ALIAS # from .tensor import LoDTensorArray #DEFINE_ALIAS -from .to_string import set_printoptions +from .to_string import set_printoptions #DEFINE_ALIAS diff --git a/python/paddle/tensor/math.py b/python/paddle/tensor/math.py index 36793e07696..c83e788538e 100755 --- a/python/paddle/tensor/math.py +++ b/python/paddle/tensor/math.py @@ -121,7 +121,8 @@ __all__ = [ 'kron', 'isfinite', 'isinf', - 'isnan' + 'isnan', + 'broadcast_shape' ] # yapf: enable. @@ -2133,3 +2134,30 @@ def any(x, axis=None, keepdim=False, name=None): outputs={'Out': out}, attrs=attrs) return out + +def broadcast_shape(x_shape, y_shape): + """ + The function returns the shape of doing operation with broadcasting on tensors of x_shape and y_shape, please refer to :ref:`user_guide_broadcasting` for more details. + + Args: + x_shape (list[int]|tuple[int]): A shape of tensor. + y_shape (list[int]|tuple[int]): A shape of tensor. + + + Returns: + list[int], the result shape. + + Examples: + .. code-block:: python + + import paddle + + shape = paddle.broadcast_shape([2, 1, 3], [1, 3, 1]) + # [2, 3, 3] + + # shape = paddle.broadcast_shape([2, 1, 3], [3, 3, 1]) + # ValueError (terminated with error message). + + """ + + return core.broadcast_shape(x_shape, y_shape) -- GitLab