From e45c3fa57d5d1dcc98b3744bcdbcd215bf0dccff Mon Sep 17 00:00:00 2001 From: chentianyu03 Date: Thu, 8 Apr 2021 17:47:50 +0800 Subject: [PATCH] Add LayerDict class (#31951) * add layerdict class * add docs and test cases for LayerDict class * remove the arguments type in function define * add update inputs type check --- .../test_imperative_container_layerdict.py | 105 +++++++ python/paddle/nn/__init__.py | 2 + python/paddle/nn/layer/__init__.py | 2 + python/paddle/nn/layer/container.py | 294 ++++++++++++++++++ 4 files changed, 403 insertions(+) create mode 100644 python/paddle/fluid/tests/unittests/test_imperative_container_layerdict.py create mode 100644 python/paddle/nn/layer/container.py diff --git a/python/paddle/fluid/tests/unittests/test_imperative_container_layerdict.py b/python/paddle/fluid/tests/unittests/test_imperative_container_layerdict.py new file mode 100644 index 00000000000..9cd3c6a8fb5 --- /dev/null +++ b/python/paddle/fluid/tests/unittests/test_imperative_container_layerdict.py @@ -0,0 +1,105 @@ +# Copyright (c) 2021 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 +from collections import OrderedDict + + +class TestLayerDict(unittest.TestCase): + def test_layer_dict(self): + layers = OrderedDict([ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ]) + + layers_dicts = paddle.nn.LayerDict(sublayers=layers) + + def check_layer_dict(): + self.assertEqual(len(layers), len(layers_dicts)) + + for k1, k2 in zip(layers, layers_dicts): + self.assertIs(layers[k1], layers_dicts[k2]) + + for k, v in zip(layers, layers_dicts.children()): + self.assertIs(layers[k], v) + + for k in layers_dicts: + self.assertIs(layers[k], layers_dicts[k]) + + for k in layers.keys(): + self.assertTrue(k in layers_dicts) + + for k1, k2 in zip(layers.keys(), layers_dicts.keys()): + self.assertEqual(k1, k2) + + for k, v in layers_dicts.items(): + self.assertIs(layers[k], v) + + for v1, v2 in zip(layers.values(), layers_dicts.values()): + self.assertIs(v1, v2) + + check_layer_dict() + + layers['linear'] = paddle.nn.Linear(2, 4) + layers_dicts['linear'] = layers['linear'] + check_layer_dict() + + sublayer = OrderedDict([ + ('sigmod', paddle.nn.Sigmoid()), + ('relu', paddle.nn.ReLU()), + ]) + layers.update(sublayer) + layers_dicts.update(sublayer) + check_layer_dict() + + del layers['conv1d'] + del layers_dicts['conv1d'] + check_layer_dict() + + l = layers_dicts.pop('linear') + self.assertIs(layers['linear'], l) + layers.pop('linear') + check_layer_dict() + + layers_dicts.clear() + self.assertEqual(0, len(layers_dicts)) + layers.clear() + check_layer_dict() + + list_format_layers = [ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ] + layers = OrderedDict(list_format_layers) + layers_dicts.update(list_format_layers) + check_layer_dict() + + def test_layer_dict_error_inputs(self): + layers = [ + ('conv1d', paddle.nn.Conv1D(3, 2, 3), "conv1d"), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ] + + layers_dicts = paddle.nn.LayerDict() + self.assertRaises(ValueError, layers_dicts.update, layers) + + self.assertRaises(AssertionError, layers_dicts.update, 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/paddle/nn/__init__.py b/python/paddle/nn/__init__.py index 3a552d588be..79f21aadae6 100644 --- a/python/paddle/nn/__init__.py +++ b/python/paddle/nn/__init__.py @@ -151,6 +151,8 @@ from .layer.distance import PairwiseDistance #DEFINE_ALIAS from .layer.vision import PixelShuffle +from .layer.container import LayerDict #DEFINE_ALIAS + from .layer import loss #DEFINE_ALIAS from .layer import conv #DEFINE_ALIAS from .layer import vision #DEFINE_ALIAS diff --git a/python/paddle/nn/layer/__init__.py b/python/paddle/nn/layer/__init__.py index 13fdde07087..17c4ca5c5d1 100644 --- a/python/paddle/nn/layer/__init__.py +++ b/python/paddle/nn/layer/__init__.py @@ -23,6 +23,7 @@ from . import rnn from . import vision from . import distance from . import transformer +from . import container from .activation import * from .loss import * @@ -99,3 +100,4 @@ from .norm import LocalResponseNorm #DEFINE_ALIAS from .vision import PixelShuffle #DEFINE_ALIAS from .distance import PairwiseDistance #DEFINE_ALIAS +from .container import LayerDict #DEFINE_ALIAS diff --git a/python/paddle/nn/layer/container.py b/python/paddle/nn/layer/container.py new file mode 100644 index 00000000000..db317839ae8 --- /dev/null +++ b/python/paddle/nn/layer/container.py @@ -0,0 +1,294 @@ +# Copyright (c) 2021 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 collections import OrderedDict +from ...fluid.dygraph.layers import Layer +from six.moves import collections_abc + +__all__ = ['LayerDict', ] + + +class LayerDict(Layer): + """ + LayerDict holds sublayers in the ordered dictionary, and sublayers it contains are properly registered. + Holded sublayers can be accessed like a regular ordered python dictionary. + + Parameters: + sublayers (LayerDict|OrderedDict|list[(key,Layer)...], optional): iterable of key/value pairs, the type of value is 'paddle.nn.Layer' . + + Examplex: + .. code-block:: python + + import paddle + import numpy as np + from collections import OrderedDict + + sublayers = OrderedDict([ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ('conv3d', paddle.nn.Conv3D(4, 6, (3, 3, 3))), + ]) + + layers_dict = paddle.nn.LayerDict(sublayers=sublayers) + + l = layers_dict['conv1d'] + + for k in layers_dict: + l = layers_dict[k] + + len(layers_dict) + #3 + + del layers_dict['conv2d'] + len(layers_dict) + #2 + + conv1d = layers_dict.pop('conv1d') + len(layers_dict) + #1 + + layers_dict.clear() + len(layers_dict) + #0 + + """ + + def __init__(self, sublayers=None): + super(LayerDict, self).__init__() + if sublayers is not None: + self.update(sublayers) + + def __getitem__(self, key): + return self._sub_layers[key] + + def __setitem__(self, key, sublayer): + return self.add_sublayer(key, sublayer) + + def __delitem__(self, key): + del self._sub_layers[key] + + def __len__(self): + return len(self._sub_layers) + + def __iter__(self): + return iter(self._sub_layers) + + def __contains__(self, key): + return key in self._sub_layers + + def clear(self): + """ + Clear all the sublayers in the LayerDict. + + Parameters: + None. + + Examplex: + .. code-block:: python + + import paddle + from collections import OrderedDict + + sublayers = OrderedDict([ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ('conv3d', paddle.nn.Conv3D(4, 6, (3, 3, 3))), + ]) + + layer_dict = paddle.nn.LayerDict(sublayers=sublayers) + len(layer_dict) + #3 + + layer_dict.clear() + len(layer_dict) + #0 + + """ + self._sub_layers.clear() + + def pop(self, key): + """ + Remove the key from the LayerDict and return the layer of the key. + + Parameters: + key (str): the key to be removed. + + Examples: + .. code-block:: python + + import paddle + from collections import OrderedDict + + sublayers = OrderedDict([ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ('conv3d', paddle.nn.Conv3D(4, 6, (3, 3, 3))), + ]) + + layer_dict = paddle.nn.LayerDict(sublayers=sublayers) + len(layer_dict) + #3 + + layer_dict.pop('conv2d') + len(layer_dict) + #2 + + """ + v = self[key] + del self[key] + return v + + def keys(self): + """ + Return the iterable of the keys in LayerDict. + + Parameters: + None. + + Examples: + .. code-block:: python + + import paddle + from collections import OrderedDict + + sublayers = OrderedDict([ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ('conv3d', paddle.nn.Conv3D(4, 6, (3, 3, 3))), + ]) + + layer_dict = paddle.nn.LayerDict(sublayers=sublayers) + for k in layer_dict.keys(): + print(k) + + #conv1d + #conv2d + #conv3d + + """ + return self._sub_layers.keys() + + def items(self): + """ + Return the iterable of the key/value pairs in LayerDict. + + Parameters: + None. + + Examples: + .. code-block:: python + + import paddle + from collections import OrderedDict + + sublayers = OrderedDict([ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ('conv3d', paddle.nn.Conv3D(4, 6, (3, 3, 3))), + ]) + + layer_dict = paddle.nn.LayerDict(sublayers=sublayers) + for k, v in layer_dict.items(): + print(k, ":", v) + + #conv1d : Conv1D(3, 2, kernel_size=[3], data_format=NCL) + #conv2d : Conv2D(3, 2, kernel_size=[3, 3], data_format=NCHW) + #conv3d : Conv3D(4, 6, kernel_size=[3, 3, 3], data_format=NCDHW) + + """ + return self._sub_layers.items() + + def values(self): + """ + Return the iterable of the values in LayerDict. + + Parameters: + None. + + Examples: + .. code-block:: python + + import paddle + from collections import OrderedDict + + sublayers = OrderedDict([ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ('conv3d', paddle.nn.Conv3D(4, 6, (3, 3, 3))), + ]) + + layer_dict = paddle.nn.LayerDict(sublayers=sublayers) + for v in layer_dict.values(): + print(v) + + #Conv1D(3, 2, kernel_size=[3], data_format=NCL) + #Conv2D(3, 2, kernel_size=[3, 3], data_format=NCHW) + #Conv3D(4, 6, kernel_size=[3, 3, 3], data_format=NCDHW) + + """ + return self._sub_layers.values() + + def update(self, sublayers): + """ + Update the key/values pairs in sublayers to the LayerDict, overwriting the existing keys. + + Parameters: + sublayers (LayerDict|OrderedDict|list[(key,Layer)...]): iterable of key/value pairs, the type of value is 'paddle.nn.Layer' . + + Examples: + .. code-block:: python + + import paddle + from collections import OrderedDict + + sublayers = OrderedDict([ + ('conv1d', paddle.nn.Conv1D(3, 2, 3)), + ('conv2d', paddle.nn.Conv2D(3, 2, 3)), + ('conv3d', paddle.nn.Conv3D(4, 6, (3, 3, 3))), + ]) + + new_sublayers = OrderedDict([ + ('relu', paddle.nn.ReLU()), + ('conv2d', paddle.nn.Conv2D(4, 2, 4)), + ]) + layer_dict = paddle.nn.LayerDict(sublayers=sublayers) + + layer_dict.update(new_sublayers) + + for k, v in layer_dict.items(): + print(k, ":", v) + #conv1d : Conv1D(3, 2, kernel_size=[3], data_format=NCL) + #conv2d : Conv2D(4, 2, kernel_size=[4, 4], data_format=NCHW) + #conv3d : Conv3D(4, 6, kernel_size=[3, 3, 3], data_format=NCDHW) + #relu : ReLU() + + """ + + assert isinstance( + sublayers, collections_abc.Iterable + ), "The type of sublayers is not iterable of key/value pairs, the type of sublayers is " + type( + sublayers).__name__ + + if isinstance(sublayers, + (OrderedDict, LayerDict, collections_abc.Mapping)): + for key, layer in sublayers.items(): + self.add_sublayer(key, layer) + else: + # handle this format [(key1, layer1), (key2, layer2)...] + for i, kv in enumerate(sublayers): + if len(kv) != 2: + raise ValueError("The length of the " + str(i) + + "'s element in sublayers is " + str( + len(kv)) + ", which must be 2.") + self.add_sublayer(kv[0], kv[1]) -- GitLab