From 3b52f68e9127f3945e97eed71d999ee6d6e7c0d5 Mon Sep 17 00:00:00 2001 From: Aurelius84 Date: Wed, 1 Sep 2021 10:23:01 +0800 Subject: [PATCH] [Dy2Stat]Support append method and initialized value for List in ControlFlow (#35212) * Support append method and initialized value for List in ControlFlow * polish error msg and en doc * fix code style --- .../dygraph_to_static/list_transformer.py | 9 ++-- python/paddle/fluid/layers/control_flow.py | 28 ++++++++++-- python/paddle/fluid/layers/math_op_patch.py | 22 ++++++++- .../unittests/dygraph_to_static/test_list.py | 15 +++++-- .../tests/unittests/test_lod_tensor_array.py | 45 +++++++++++++++++++ python/paddle/tensor/array.py | 6 ++- 6 files changed, 113 insertions(+), 12 deletions(-) diff --git a/python/paddle/fluid/dygraph/dygraph_to_static/list_transformer.py b/python/paddle/fluid/dygraph/dygraph_to_static/list_transformer.py index e041fe7c9ac..e62def897d2 100644 --- a/python/paddle/fluid/dygraph/dygraph_to_static/list_transformer.py +++ b/python/paddle/fluid/dygraph/dygraph_to_static/list_transformer.py @@ -93,7 +93,8 @@ class ListTransformer(gast.NodeTransformer): for child_node in gast.walk(node): if isinstance(child_node, gast.Assign): if self._need_to_create_tensor_array(child_node): - child_node.value = self._create_tensor_array() + child_node.value = self._create_tensor_array( + child_node.value) def _transform_list_append_in_control_flow(self, node): for child_node in gast.walk(node): @@ -189,9 +190,11 @@ class ListTransformer(gast.NodeTransformer): return True return False - def _create_tensor_array(self): + def _create_tensor_array(self, value_node): # Although `dtype='float32'`, other types such as `int32` can also be supported - func_code = "paddle.tensor.create_array(dtype='float32')" + init_value = ast_to_source_code(value_node).strip() + func_code = "paddle.tensor.create_array('float32', {})".format( + init_value) func_node = gast.parse(func_code).body[0].value return func_node diff --git a/python/paddle/fluid/layers/control_flow.py b/python/paddle/fluid/layers/control_flow.py index a0a2a329ba6..f444b5e9c0e 100755 --- a/python/paddle/fluid/layers/control_flow.py +++ b/python/paddle/fluid/layers/control_flow.py @@ -1538,7 +1538,7 @@ def array_write(x, i, array=None): return array -def create_array(dtype): +def create_array(dtype, initialized_list=None): """ This OP creates an LOD_TENSOR_ARRAY. It is used as the input of :ref:`api_fluid_layers_array_read` and @@ -1548,6 +1548,8 @@ def create_array(dtype): Args: dtype (str): The data type of the elements in the lod_tensor_array. Support data type: float32, float64, int32, int64. + initialized_list(list): Used to initialize as default value for created array. + All values in initialized list should be a Tensor. Returns: Variable: The empty lod_tensor_array. The data type of elements in Tensor is ``dtype``. @@ -1559,15 +1561,35 @@ def create_array(dtype): data = fluid.layers.create_array(dtype='float32') # Create a float32 LoDTensorArray. """ + array = [] + if initialized_list is not None: + if not isinstance(initialized_list, (list, tuple)): + raise TypeError( + "Require type(initialized_list) should be list/tuple, but received {}". + format(type(initialized_list))) + array = list(initialized_list) + + # NOTE: Only support plain list like [x, y,...], not support nested list in static mode. + for val in array: + if not isinstance(val, Variable): + raise TypeError( + "All values in `initialized_list` should be Variable, but recevied {}.". + format(type(val))) + if in_dygraph_mode(): - return [] + return array helper = LayerHelper("array", **locals()) - return helper.create_variable( + tensor_array = helper.create_variable( name="{0}.out".format(helper.name), type=core.VarDesc.VarType.LOD_TENSOR_ARRAY, dtype=dtype) + for val in array: + array_write(x=val, i=array_length(tensor_array), array=tensor_array) + + return tensor_array + @templatedoc() def less_than(x, y, force_cpu=None, cond=None, name=None): diff --git a/python/paddle/fluid/layers/math_op_patch.py b/python/paddle/fluid/layers/math_op_patch.py index feb723d9c8b..e1b61393498 100644 --- a/python/paddle/fluid/layers/math_op_patch.py +++ b/python/paddle/fluid/layers/math_op_patch.py @@ -18,8 +18,9 @@ import warnings import inspect from .. import core -from ..framework import Variable, unique_name +from ..framework import Variable, unique_name, static_only from .layer_function_generator import OpProtoHolder +from .control_flow import array_write, array_length _supported_int_dtype_ = [ core.VarDesc.VarType.BOOL, @@ -182,6 +183,24 @@ def monkey_patch_variable(): out.stop_gradient = self.stop_gradient return out + @static_only + def append(self, var): + """ + **Notes**: + **The type variable must be LoD Tensor Array. + + """ + if not isinstance(var, Variable): + raise TypeError( + "Required input var should be Variable, but received {}".format( + type(var))) + if self.type != core.VarDesc.VarType.LOD_TENSOR_ARRAY: + raise TypeError( + "Only Variable with VarType.LOD_TENSOR_ARRAY support `append` method, but received type: {}". + format(self.type)) + + array_write(x=var, i=array_length(self), array=self) + def _scalar_op_(var, scale, bias): block = current_block(var) out = create_new_tmp_var(block, var.dtype) @@ -344,6 +363,7 @@ def monkey_patch_variable(): # b=-a ('__neg__', _neg_), ('astype', astype), + ('append', append), ('dim', lambda x: len(x.shape)), ('ndimension', lambda x: len(x.shape)), ('ndim', _ndim_), diff --git a/python/paddle/fluid/tests/unittests/dygraph_to_static/test_list.py b/python/paddle/fluid/tests/unittests/dygraph_to_static/test_list.py index 8da4e200cfc..57386bd00c9 100644 --- a/python/paddle/fluid/tests/unittests/dygraph_to_static/test_list.py +++ b/python/paddle/fluid/tests/unittests/dygraph_to_static/test_list.py @@ -147,14 +147,17 @@ def test_list_pop_without_control_flow_2(x): def test_list_pop_in_if(x): x = fluid.dygraph.to_variable(x) a = [] + b = [x * 2 + (x + 1)] if x.numpy()[0] > 0: a.append(x) + b.append(x + 1) a.append(fluid.layers.fill_constant(shape=[1], value=1, dtype="int64")) else: a.append(x + 1) + b.append(x - 1) a.append(fluid.layers.fill_constant(shape=[2], value=2, dtype="int64")) item1 = a.pop(1) - return item1 + return item1, b[-1] def test_list_pop_in_for_loop(x, iter_num): @@ -165,14 +168,16 @@ def test_list_pop_in_for_loop(x, iter_num): ) # TODO(liym27): Delete it if the type of parameter iter_num can be resolved a = [] + b = [x - 1, x + 1] for i in range(iter_num): a.append(x + i) + b.append(x * 2) one = fluid.layers.ones(shape=[1], dtype="int32") for i in range(one.numpy()[0]): item = a.pop() - return a[0], item + return a[0], item, b[1] def test_list_pop_in_while_loop(x, iter_num): @@ -180,14 +185,18 @@ def test_list_pop_in_while_loop(x, iter_num): iter_num = fluid.layers.fill_constant( shape=[1], value=iter_num, dtype="int32") a = [] + b = [x] + b.append(x) + b.pop() i = 0 while i < iter_num: a.append(x + i) + b.append(x - i) i += 1 if i % 2 == 1: a.pop() - return a[0] + return a[0], b[2] class TestListWithoutControlFlow(unittest.TestCase): diff --git a/python/paddle/fluid/tests/unittests/test_lod_tensor_array.py b/python/paddle/fluid/tests/unittests/test_lod_tensor_array.py index 6ad27de9a0e..678e9e21197 100644 --- a/python/paddle/fluid/tests/unittests/test_lod_tensor_array.py +++ b/python/paddle/fluid/tests/unittests/test_lod_tensor_array.py @@ -15,6 +15,7 @@ from __future__ import print_function import unittest +import paddle import paddle.fluid.core as core import numpy @@ -50,5 +51,49 @@ class TestLoDTensorArray(unittest.TestCase): self.assertEqual([[1]], t.recursive_sequence_lengths()) +class TestCreateArray(unittest.TestCase): + def setUp(self): + self.place = paddle.CPUPlace() + self.shapes = [[10, 4], [8, 12], [1]] + + def test_initialized_list_and_error(self): + paddle.disable_static() + init_data = [ + numpy.random.random(shape).astype('float32') + for shape in self.shapes + ] + array = paddle.tensor.create_array( + 'float32', [paddle.to_tensor(x) for x in init_data]) + for res, gt in zip(array, init_data): + self.assertTrue(numpy.array_equal(res, gt)) + + # test for None + array = paddle.tensor.create_array('float32') + self.assertTrue(isinstance(array, list)) + self.assertEqual(len(array), 0) + + # test error + with self.assertRaises(TypeError): + paddle.tensor.create_array('float32', 'str') + + def test_static(self): + paddle.enable_static() + init_data = [paddle.ones(shape, dtype='int32') for shape in self.shapes] + array = paddle.tensor.create_array('float32', init_data) + for res, gt in zip(array, init_data): + self.assertTrue(res.shape, gt.shape) + + # test error with nest list + with self.assertRaises(TypeError): + paddle.tensor.create_array('float32', + [init_data[0], [init_data[1]]]) + + # test error with not variable + with self.assertRaises(TypeError): + paddle.tensor.create_array('float32', ("str")) + + paddle.enable_static() + + if __name__ == '__main__': unittest.main() diff --git a/python/paddle/tensor/array.py b/python/paddle/tensor/array.py index 6c3d5c577e7..49678443f1f 100644 --- a/python/paddle/tensor/array.py +++ b/python/paddle/tensor/array.py @@ -122,13 +122,15 @@ def array_write(x, i, array=None): return layers.array_write(x, i, array) -def create_array(dtype): +def create_array(dtype, initialized_list=None): """ This OP creates an array. It is used as the input of :ref:`api_paddle_tensor_array_array_read` and :ref:`api_paddle_tensor_array_array_write`. Args: dtype (str): The data type of the elements in the array. Support data type: float32, float64, int32, int64 and bool. + initialized_list(list): Used to initialize as default value for created array. + All values in initialized list should be a Tensor. Returns: list|Tensor: An empty array. In dynamic mode, ``array`` is a Python list. But in static mode, array is a Tensor @@ -149,4 +151,4 @@ def create_array(dtype): print(item) # [[5., 5., 5.]] """ - return layers.create_array(dtype) + return layers.create_array(dtype, initialized_list) -- GitLab