From 2a47cc5fc4b0484faa4315008a8e370acab1d533 Mon Sep 17 00:00:00 2001 From: songyouwei Date: Mon, 6 Jan 2020 14:26:05 +0800 Subject: [PATCH] dygraph LayerList container (#21734) * add dygraph LayerList * add unittest test=develop * add newline test=develop * typo fix test=develop --- python/paddle/fluid/dygraph/container.py | 69 +++++++++++++++++++ .../unittests/test_imperative_layerlist.py | 66 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 python/paddle/fluid/tests/unittests/test_imperative_layerlist.py diff --git a/python/paddle/fluid/dygraph/container.py b/python/paddle/fluid/dygraph/container.py index 07667f548ca..0ca32abfbf4 100644 --- a/python/paddle/fluid/dygraph/container.py +++ b/python/paddle/fluid/dygraph/container.py @@ -12,12 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from collections import OrderedDict from ..framework import Parameter from .layers import Layer __all__ = [ 'Sequential', 'ParameterList', + 'LayerList', ] @@ -169,3 +171,70 @@ class ParameterList(Layer): idx = len(self._parameters) self.add_parameter(str(idx), parameter) return self + + +class LayerList(Layer): + """ + LayerList holds sublayers, and sublayers it contains are properly registered. + Holded sublayers can be indexed like a regular python list. + + Parameters: + sublayers (iterable of Layer, optional): sublayers to hold + + Examples: + .. code-block:: python + import paddle.fluid as fluid + import numpy as np + + class MyLayer(fluid.Layer): + def __init__(self): + super(MyLayer, self).__init__() + self.linears = fluid.dygraph.LayerList( + [fluid.dygraph.Linear(10, 10) for i in range(10)]) + + def forward(self, x): + # LayerList can act as an iterable, or be indexed using ints + for i, l in enumerate(self.linears): + x = self.linears[i // 2](x) + l(x) + return x + """ + + def __init__(self, sublayers=None): + super(LayerList, self).__init__() + for idx, layer in enumerate(sublayers): + self.add_sublayer(str(idx), layer) + + def __getitem__(self, idx): + if isinstance(idx, slice): + return self.__class__(list(self._sub_layers.values())[idx]) + else: + return self._sub_layers[str(idx)] + + def __setitem__(self, idx, sublayer): + return setattr(self, str(idx), sublayer) + + def __delitem__(self, idx): + if isinstance(idx, slice): + for k in range(len(self._sub_layers))[idx]: + delattr(self, str(k)) + else: + delattr(self, str(idx)) + str_indices = [str(i) for i in range(len(self._sub_layers))] + self._sub_layers = OrderedDict( + list(zip(str_indices, self._sub_layers.values()))) + + def __len__(self): + return len(self._sub_layers) + + def __iter__(self): + return iter(self._sub_layers.values()) + + def append(self, sublayer): + """ + Appends a sublayer to the end of the list. + + Parameters: + sublayer (Layer): sublayer to append + """ + self.add_sublayer(str(len(self)), sublayer) + return self diff --git a/python/paddle/fluid/tests/unittests/test_imperative_layerlist.py b/python/paddle/fluid/tests/unittests/test_imperative_layerlist.py new file mode 100644 index 00000000000..57509692fc3 --- /dev/null +++ b/python/paddle/fluid/tests/unittests/test_imperative_layerlist.py @@ -0,0 +1,66 @@ +# Copyright (c) 2019 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 paddle.fluid as fluid +import numpy as np + + +class MyLayer(fluid.Layer): + def __init__(self, layerlist): + super(MyLayer, self).__init__() + self.layerlist = layerlist + + def forward(self, x): + for l in self.layerlist: + x = l(x) + return x + + +class TestImperativeContainerParameterList(unittest.TestCase): + def test_paramter_list(self): + data_np = np.random.uniform(-1, 1, [5, 1]).astype('float32') + with fluid.dygraph.guard(): + x = fluid.dygraph.to_variable(data_np) + layerlist = fluid.dygraph.LayerList( + [fluid.dygraph.Linear(2**i, 2**(i + 1)) for i in range(6)]) + size = len(layerlist) + + model = MyLayer(layerlist) + res1 = model(x) + self.assertListEqual(res1.shape, [5, 2**size]) + model.layerlist[size - 1] = fluid.dygraph.Linear(2**(size - 1), 5) + res2 = model(x) + self.assertListEqual(res2.shape, [5, 5]) + del model.layerlist[size - 1] + res3 = model(x) + self.assertListEqual(res3.shape, [5, 2**(size - 1)]) + model.layerlist.append(fluid.dygraph.Linear(2**(size - 1), 3)) + res4 = model(x) + self.assertListEqual(res4.shape, [5, 3]) + res4.backward() + + model2 = MyLayer(layerlist[:-1]) + res5 = model2(x) + self.assertListEqual(res5.shape, [5, 2**(size - 1)]) + del model2.layerlist[1:] + res6 = model2(x) + self.assertListEqual(res6.shape, [5, 2**(0 + 1)]) + res6.backward() + + +if __name__ == '__main__': + unittest.main() -- GitLab