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

Add local_response_norm in nn.functional and nn.layer #27725

Merged
merged 5 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions python/paddle/fluid/tests/unittests/test_lrn_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from __future__ import print_function

import paddle
import unittest
import numpy as np
import paddle.fluid as fluid
Expand Down Expand Up @@ -154,5 +155,196 @@ def test_errors(self):
self.assertRaises(TypeError, fluid.layers.lrn, in_w)


class TestLocalResponseNormFAPI(unittest.TestCase):
def setUp(self):
np.random.seed(123)
self.places = [fluid.CPUPlace()]
if core.is_compiled_with_cuda():
self.places.append(fluid.CUDAPlace(0))

def check_static_3d_input(self, place):
with fluid.program_guard(fluid.Program(), fluid.Program()):
in_np1 = np.random.random([3, 40, 40]).astype("float32")
in_np2 = np.transpose(in_np1, (0, 2, 1))

input1 = fluid.data(
name="input1", shape=[3, 40, 40], dtype="float32")
input2 = fluid.data(
Copy link
Member

Choose a reason for hiding this comment

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

input1/2 shape should be [3, 3, 40] and [3, 40, 3]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

[3, 40, 40] also works

name="input2", shape=[3, 40, 40], dtype="float32")
res1 = paddle.nn.functional.local_response_norm(
x=input1, size=5, data_format='NCL')
res2 = paddle.nn.functional.local_response_norm(
x=input2, size=5, data_format='NLC')
exe = fluid.Executor(place)
fetches = exe.run(fluid.default_main_program(),
feed={"input1": in_np1,
"input2": in_np2},
fetch_list=[res1, res2])

fetches1_tran = np.transpose(fetches[1], (0, 2, 1))
self.assertTrue(np.allclose(fetches[0], fetches1_tran))

def check_static_4d_input(self, place):
with fluid.program_guard(fluid.Program(), fluid.Program()):
input1 = fluid.data(
name="input1", shape=[3, 3, 40, 40], dtype="float32")
input2 = fluid.data(
name="input2", shape=[3, 40, 40, 3], dtype="float32")

res1 = paddle.nn.functional.local_response_norm(
x=input1, size=5, data_format='NCHW')
res2 = paddle.nn.functional.local_response_norm(
x=input2, size=5, data_format='NHWC')

in_np1 = np.random.random([3, 3, 40, 40]).astype("float32")
in_np2 = np.transpose(in_np1, (0, 2, 3, 1))

exe = fluid.Executor(place)
fetches = exe.run(fluid.default_main_program(),
feed={"input1": in_np1,
"input2": in_np2},
fetch_list=[res1, res2])

fetches1_tran = np.transpose(fetches[1], (0, 3, 1, 2))
self.assertTrue(np.allclose(fetches[0], fetches1_tran))

def check_static_5d_input(self, place):
with fluid.program_guard(fluid.Program(), fluid.Program()):
input1 = fluid.data(
name="input1", shape=[3, 3, 3, 40, 40], dtype="float32")
input2 = fluid.data(
name="input2", shape=[3, 3, 40, 40, 3], dtype="float32")
res1 = paddle.nn.functional.local_response_norm(
x=input1, size=5, data_format='NCDHW')
res2 = paddle.nn.functional.local_response_norm(
x=input2, size=5, data_format='NDHWC')

in_np1 = np.random.random([3, 3, 3, 40, 40]).astype("float32")
in_np2 = np.transpose(in_np1, (0, 2, 3, 4, 1))

exe = fluid.Executor(place)
fetches = exe.run(fluid.default_main_program(),
feed={"input1": in_np1,
"input2": in_np2},
fetch_list=[res1, res2])

fetches1_tran = np.transpose(fetches[1], (0, 4, 1, 2, 3))
self.assertTrue(np.allclose(fetches[0], fetches1_tran))

def test_static(self):
for place in self.places:
self.check_static_3d_input(place=place)
self.check_static_4d_input(place=place)
self.check_static_5d_input(place=place)

def check_dygraph_3d_input(self, place):
Copy link
Member

Choose a reason for hiding this comment

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

so it only check the data_format?

Copy link
Contributor Author

@huangjun12 huangjun12 Oct 6, 2020

Choose a reason for hiding this comment

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

Yeah, Acc is aligned with torch implementation for simplicity, here we just test data_format

with fluid.dygraph.guard(place):
in_np1 = np.random.random([3, 40, 40]).astype("float32")
in_np2 = np.transpose(in_np1, (0, 2, 1))

in1 = paddle.to_tensor(in_np1)
in2 = paddle.to_tensor(in_np2)

res1 = paddle.nn.functional.local_response_norm(
x=in1, size=5, data_format='NCL')
res2 = paddle.nn.functional.local_response_norm(
x=in2, size=5, data_format='NLC')

res2_tran = np.transpose(res2.numpy(), (0, 2, 1))
self.assertTrue(np.allclose(res1.numpy(), res2_tran))

def check_dygraph_4d_input(self, place):
with fluid.dygraph.guard(place):
in_np1 = np.random.random([3, 3, 40, 40]).astype("float32")
in_np2 = np.transpose(in_np1, (0, 2, 3, 1))

in1 = paddle.to_tensor(in_np1)
in2 = paddle.to_tensor(in_np2)

res1 = paddle.nn.functional.local_response_norm(
x=in1, size=5, data_format='NCHW')
res2 = paddle.nn.functional.local_response_norm(
x=in2, size=5, data_format='NHWC')

res2_tran = np.transpose(res2.numpy(), (0, 3, 1, 2))
self.assertTrue(np.allclose(res1.numpy(), res2_tran))

def check_dygraph_5d_input(self, place):
with fluid.dygraph.guard(place):
in_np1 = np.random.random([3, 3, 3, 40, 40]).astype("float32")
in_np2 = np.transpose(in_np1, (0, 2, 3, 4, 1))

in1 = paddle.to_tensor(in_np1)
in2 = paddle.to_tensor(in_np2)

res1 = paddle.nn.functional.local_response_norm(
x=in1, size=5, data_format='NCDHW')
res2 = paddle.nn.functional.local_response_norm(
x=in2, size=5, data_format='NDHWC')

res2_tran = np.transpose(res2.numpy(), (0, 4, 1, 2, 3))
self.assertTrue(np.allclose(res1.numpy(), res2_tran))

def test_dygraph(self):
for place in self.places:
self.check_dygraph_3d_input(place)
self.check_dygraph_4d_input(place)
self.check_dygraph_5d_input(place)


class TestLocalResponseNormFAPIError(unittest.TestCase):
def test_errors(self):
with program_guard(Program(), Program()):

def test_Variable():
# the input of lrn must be Variable.
x1 = fluid.create_lod_tensor(
np.array([-1, 3, 5, 5]), [[1, 1, 1, 1]], fluid.CPUPlace())
paddle.nn.functional.local_response_norm(x1, size=5)

self.assertRaises(TypeError, test_Variable)

def test_datatype():
x = fluid.data(name='x', shape=[3, 4, 5, 6], dtype="int32")
paddle.nn.functional.local_response_norm(x, size=5)

self.assertRaises(TypeError, test_datatype)

def test_dataformat():
x = fluid.data(name='x', shape=[3, 4, 5, 6], dtype="float32")
paddle.nn.functional.local_response_norm(
x, size=5, data_format="NCTHW")

self.assertRaises(ValueError, test_dataformat)

def test_dim():
x = fluid.data(name='x', shape=[3, 4], dtype="float32")
paddle.nn.functional.local_response_norm(x, size=5)

self.assertRaises(ValueError, test_dim)


class TestLocalResponseNormCAPI(unittest.TestCase):
def setUp(self):
np.random.seed(123)
self.places = [fluid.CPUPlace()]
if core.is_compiled_with_cuda():
self.places.append(fluid.CUDAPlace(0))

def test_dygraph(self):
for place in self.places:
Copy link
Member

Choose a reason for hiding this comment

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

why don't use the new api to test dygraph?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

with fluid.dygraph.guard(place):
in1 = paddle.rand(shape=(3, 3, 40, 40), dtype="float32")
in2 = paddle.transpose(x, [0, 2, 3, 1])

m = paddle.nn.LocalResponseNorm(size=5)

res1 = m(in1, data_format='NCHW')
res2 = m(in2, data_format='NHWC')

res2_tran = np.transpose(res2.numpy(), (0, 3, 1, 2))
self.assertTrue(np.allclose(res1.numpy(), res2_tran))


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions python/paddle/nn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
from .layer.norm import BatchNorm1d #DEFINE_ALIAS
from .layer.norm import BatchNorm2d #DEFINE_ALIAS
from .layer.norm import BatchNorm3d #DEFINE_ALIAS
from .layer.norm import LocalResponseNorm #DEFINE_ALIAS

from .layer.rnn import RNNCellBase #DEFINE_ALIAS
from .layer.rnn import SimpleRNNCell #DEFINE_ALIAS
Expand Down
2 changes: 1 addition & 1 deletion python/paddle/nn/functional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
from .norm import batch_norm #DEFINE_ALIAS
from .norm import instance_norm #DEFINE_ALIAS
from .norm import layer_norm #DEFINE_ALIAS
from .norm import lrn #DEFINE_ALIAS
from .norm import local_response_norm #DEFINE_ALIAS
from .norm import normalize #DEFINE_ALIAS
# from .norm import spectral_norm #DEFINE_ALIAS
from .pooling import pool2d #DEFINE_ALIAS
Expand Down
109 changes: 107 additions & 2 deletions python/paddle/nn/functional/norm.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from ...fluid.layer_helper import LayerHelper
from ...fluid.framework import in_dygraph_mode, core
from ...framework import create_parameter
from ...fluid.layers import lrn #DEFINE_ALIAS
from ...fluid.initializer import Constant
from ...fluid.param_attr import ParamAttr
from ...fluid import core, dygraph_utils
Expand All @@ -29,7 +28,7 @@
# 'data_norm',
'instance_norm',
'layer_norm',
'lrn',
'local_response_norm',
'normalize',
# 'spectral_norm'
]
Expand Down Expand Up @@ -403,3 +402,109 @@ def instance_norm(x,
helper.append_op(
type="instance_norm", inputs=inputs, outputs=outputs, attrs=attrs)
return instance_norm_out


def local_response_norm(x,
size,
alpha=1e-4,
beta=0.75,
k=1.,
data_format="NCHW",
name=None):
"""
Local Response Normalization performs a type of "lateral inhibition" by normalizing over local input regions.
Copy link
Member

Choose a reason for hiding this comment

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

refer to its own class doc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, functional is referred to class

For more information, please refer to `ImageNet Classification with Deep Convolutional Neural Networks <https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf>`_

The formula is as follows:

.. math::

Output(i, x, y) = Input(i, x, y) / \\left(k + \\alpha \\sum\\limits^{\\min(C-1, i + size/2)}_{j = \\max(0, i - size/2)}(Input(j, x, y))^2\\right)^{\\beta}

In the above equation:

- :math:`size` : The number of channels to sum over.
- :math:`k` : The offset (avoid being divided by 0).
- :math:`\\alpha` : The scaling parameter.
- :math:`\\beta` : The exponent parameter.


Args:
x (Tensor): The input 3-D/4-D/5-D tensor. The data type is float32.
size (int): The number of channels to sum over.
alpha (float, optional): The scaling parameter, positive. Default:1e-4
beta (float, optional): The exponent, positive. Default:0.75
k (float, optional): An offset, positive. Default: 1.0
data_format (str, optional): Specify the data format of the input, and the data format of the output
will be consistent with that of the input. An optional string from:
If x is 3-D Tensor, the string could be `"NCL"` or `"NLC"` . When it is `"NCL"`,
the data is stored in the order of: `[batch_size, input_channels, feature_length]`.
If x is 4-D Tensor, the string could be `"NCHW"`, `"NHWC"`. When it is `"NCHW"`,
the data is stored in the order of: `[batch_size, input_channels, input_height, input_width]`.
If x is 5-D Tensor, the string could be `"NCDHW"`, `"NDHWC"` . When it is `"NCDHW"`,
the data is stored in the order of: `[batch_size, input_channels, input_depth, input_height, input_width]`.
name (str, optional): Name for the operation (optional, default is None). For more information,
please refer to :ref:`api_guide_Name`.

Returns:
A tensor storing the transformation result with the same shape and data type as input.


Examples:

.. code-block:: python

import paddle

x = paddle.rand(shape=(3, 3, 112, 112), dtype="float32")
y = paddle.nn.functional.local_response_norm(x, size=5)
print(y.shape) # [3, 3, 112, 112]
"""
if not in_dygraph_mode():
check_variable_and_dtype(x, 'x', ['float32'], 'local_response_norm')
if data_format not in ['NCL', 'NLC', 'NCHW', 'NHWC', 'NCDHW', 'NDHWC']:
raise ValueError(
"data_format should be in one of [NCL, NCHW, NCDHW, NLC, NHWC, NDHWC], " \
"but got {}".format(data_format))

sizes = x.shape
dim = len(sizes)
if dim < 3:
raise ValueError(
'Expected 3D or higher dimensionality input, but got {} dimensions'.
format(dim))

channel_last = True if data_format[-1] == "C" else False

div = paddle.unsqueeze(paddle.multiply(x, x), axis=1)
if not channel_last:
pad4d_shape = [0, 0, size // 2, (size - 1) // 2]
pool2d_shape = (size, 1)
reshape_shape = [sizes[0], 1, sizes[1], sizes[2], -1]
pad5d_shape = [0, 0, 0, 0, size // 2, (size - 1) // 2]
pool3d_shape = (size, 1, 1)
else:
pad4d_shape = [size // 2, (size - 1) // 2, 0, 0]
pool2d_shape = (1, size)
reshape_shape = [sizes[0], 1, sizes[1], -1, sizes[-1]]
pad5d_shape = [size // 2, (size - 1) // 2, 0, 0, 0, 0]
pool3d_shape = (1, 1, size)

if dim == 3:
div = paddle.nn.functional.pad(div, pad=pad4d_shape)
div = paddle.nn.functional.avg_pool2d(
div, kernel_size=pool2d_shape, stride=1)
div = paddle.squeeze(div, axis=1)
else:
div = paddle.reshape(div, shape=reshape_shape)
div = paddle.nn.functional.pad(div,
pad=pad5d_shape,
data_format='NCDHW')
div = paddle.nn.functional.avg_pool3d(
div, kernel_size=pool3d_shape, stride=1)
div = paddle.reshape(paddle.squeeze(div, axis=1), sizes)

div = paddle.scale(div, scale=alpha, bias=k)
div = paddle.pow(div, beta)
res = paddle.divide(x, div, name=name)
return res
1 change: 1 addition & 0 deletions python/paddle/nn/layer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
from .norm import LayerNorm #DEFINE_ALIAS
from .norm import SpectralNorm #DEFINE_ALIAS
from .norm import InstanceNorm #DEFINE_ALIAS
from .norm import LocalResponseNorm #DEFINE_ALIAS
# from .rnn import RNNCell #DEFINE_ALIAS
# from .rnn import GRUCell #DEFINE_ALIAS
# from .rnn import LSTMCell #DEFINE_ALIAS
Expand Down
Loading