From 9333a627927c63eda79c1e3d59a1d8d54affb65c Mon Sep 17 00:00:00 2001 From: Bai Yifan Date: Wed, 15 Aug 2018 14:55:00 +0800 Subject: [PATCH] Add flatten op interface and enhance APIs about detection to support variable-length image. (#12422) * add flatten api&enhance detection api * unify shape_op data type * update API.spec --- paddle/fluid/API.spec | 1 + paddle/fluid/operators/.flatten_op.cc.swp | Bin 16384 -> 0 bytes paddle/fluid/operators/shape_op.cc | 4 +- paddle/fluid/operators/shape_op.cu | 2 +- paddle/fluid/operators/shape_op.h | 2 +- python/paddle/fluid/layers/detection.py | 50 ++++++++----- python/paddle/fluid/layers/nn.py | 68 ++++++++++++++++++ .../fluid/tests/unittests/test_layers.py | 11 +++ 8 files changed, 115 insertions(+), 23 deletions(-) delete mode 100644 paddle/fluid/operators/.flatten_op.cc.swp diff --git a/paddle/fluid/API.spec b/paddle/fluid/API.spec index 46e56981ea..6635aedbce 100644 --- a/paddle/fluid/API.spec +++ b/paddle/fluid/API.spec @@ -159,6 +159,7 @@ paddle.fluid.layers.relu ArgSpec(args=['x'], varargs=None, keywords=None, defaul paddle.fluid.layers.log ArgSpec(args=['x'], varargs=None, keywords=None, defaults=None) paddle.fluid.layers.crop ArgSpec(args=['x', 'shape', 'offsets', 'name'], varargs=None, keywords=None, defaults=(None, None, None)) paddle.fluid.layers.rank_loss ArgSpec(args=['label', 'left', 'right', 'name'], varargs=None, keywords=None, defaults=(None,)) +paddle.fluid.layers.flatten ArgSpec(args=['x', 'axis', 'name'], varargs=None, keywords=None, defaults=(1, None)) paddle.fluid.layers.data ArgSpec(args=['name', 'shape', 'append_batch_size', 'dtype', 'lod_level', 'type', 'stop_gradient'], varargs=None, keywords=None, defaults=(True, 'float32', 0, VarType.LOD_TENSOR, True)) paddle.fluid.layers.open_recordio_file ArgSpec(args=['filename', 'shapes', 'lod_levels', 'dtypes', 'pass_num', 'for_parallel'], varargs=None, keywords=None, defaults=(1, True)) paddle.fluid.layers.open_files ArgSpec(args=['filenames', 'shapes', 'lod_levels', 'dtypes', 'thread_num', 'buffer_size', 'pass_num', 'is_test'], varargs=None, keywords=None, defaults=(None, None, 1, None)) diff --git a/paddle/fluid/operators/.flatten_op.cc.swp b/paddle/fluid/operators/.flatten_op.cc.swp deleted file mode 100644 index 3395b6074b6a4c684a97674af702ca8b91dc85e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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, ops::ShapeKernel, +REGISTER_OP_CPU_KERNEL(shape, ops::ShapeKernel, ops::ShapeKernel, ops::ShapeKernel, ops::ShapeKernel); diff --git a/paddle/fluid/operators/shape_op.cu b/paddle/fluid/operators/shape_op.cu index 7736a2a1e1..d8fa9515ab 100644 --- a/paddle/fluid/operators/shape_op.cu +++ b/paddle/fluid/operators/shape_op.cu @@ -15,6 +15,6 @@ limitations under the License. */ #include "paddle/fluid/operators/shape_op.h" REGISTER_OP_CUDA_KERNEL(shape, paddle::operators::ShapeKernel, - paddle::operators::ShapeKernel, + paddle::operators::ShapeKernel, paddle::operators::ShapeKernel, paddle::operators::ShapeKernel); diff --git a/paddle/fluid/operators/shape_op.h b/paddle/fluid/operators/shape_op.h index 3be86b66a5..0d510a5055 100644 --- a/paddle/fluid/operators/shape_op.h +++ b/paddle/fluid/operators/shape_op.h @@ -27,7 +27,7 @@ class ShapeKernel : public framework::OpKernel { void Compute(const framework::ExecutionContext& ctx) const override { auto* in_t = ctx.Input("Input"); auto* out_t = ctx.Output("Out"); - auto out_data = out_t->mutable_data(platform::CPUPlace()); + auto out_data = out_t->mutable_data(platform::CPUPlace()); auto in_dims = in_t->dims(); for (int i = 0; i < in_dims.size(); ++i) { out_data[i] = in_dims[i]; diff --git a/python/paddle/fluid/layers/detection.py b/python/paddle/fluid/layers/detection.py index 0800c02d9e..b996c83688 100644 --- a/python/paddle/fluid/layers/detection.py +++ b/python/paddle/fluid/layers/detection.py @@ -20,7 +20,9 @@ from .layer_function_generator import autodoc, templatedoc from ..layer_helper import LayerHelper from . import tensor from . import nn +from . import ops import math +import numpy from functools import reduce __all__ = [ @@ -264,10 +266,11 @@ def detection_output(loc, prior_box_var=prior_box_var, target_box=loc, code_type='decode_center_size') - old_shape = scores.shape - scores = nn.reshape(x=scores, shape=(-1, old_shape[-1])) + compile_shape = scores.shape + run_shape = ops.shape(scores) + scores = nn.flatten(x=scores, axis=2) scores = nn.softmax(input=scores) - scores = nn.reshape(x=scores, shape=old_shape) + scores = nn.reshape(x=scores, shape=compile_shape, actual_shape=run_shape) scores = nn.transpose(scores, perm=[0, 2, 1]) scores.stop_gradient = True nmsed_outs = helper.create_tmp_variable(dtype=decoded_box.dtype) @@ -677,9 +680,10 @@ def ssd_loss(location, raise ValueError("Only support mining_type == max_negative now.") num, num_prior, num_class = confidence.shape + conf_shape = ops.shape(confidence) def __reshape_to_2d(var): - return nn.reshape(x=var, shape=[-1, var.shape[-1]]) + return nn.flatten(x=var, axis=2) # 1. Find matched boundding box by prior box. # 1.1 Compute IOU similarity between ground-truth boxes and prior boxes. @@ -690,7 +694,8 @@ def ssd_loss(location, # 2. Compute confidence for mining hard examples # 2.1. Get the target label based on matched indices - gt_label = nn.reshape(x=gt_label, shape=gt_label.shape + (1, )) + gt_label = nn.reshape( + x=gt_label, shape=(len(gt_label.shape) - 1) * (0, ) + (-1, 1)) gt_label.stop_gradient = True target_label, _ = target_assign( gt_label, matched_indices, mismatch_value=background_label) @@ -701,9 +706,12 @@ def ssd_loss(location, target_label = __reshape_to_2d(target_label) target_label.stop_gradient = True conf_loss = nn.softmax_with_cross_entropy(confidence, target_label) - # 3. Mining hard examples - conf_loss = nn.reshape(x=conf_loss, shape=(num, num_prior)) + conf_loss = nn.reshape( + x=conf_loss, + shape=(num, num_prior), + actual_shape=ops.slice( + conf_shape, axes=[0], starts=[0], ends=[2])) conf_loss.stop_gradient = True neg_indices = helper.create_tmp_variable(dtype='int32') dtype = matched_indices.dtype @@ -772,7 +780,11 @@ def ssd_loss(location, # 5.3 Compute overall weighted loss. loss = conf_loss_weight * conf_loss + loc_loss_weight * loc_loss # reshape to [N, Np], N is the batch size and Np is the prior box number. - loss = nn.reshape(x=loss, shape=[-1, num_prior]) + loss = nn.reshape( + x=loss, + shape=(num, num_prior), + actual_shape=ops.slice( + conf_shape, axes=[0], starts=[0], ends=[2])) loss = nn.reduce_sum(loss, dim=1, keep_dim=True) if normalize: normalizer = nn.reduce_sum(target_loc_weight) @@ -1005,13 +1017,7 @@ def multi_box_head(inputs, """ def _reshape_with_axis_(input, axis=1): - if not (axis > 0 and axis < len(input.shape)): - raise ValueError("The axis should be smaller than " - "the arity of input and bigger than 0.") - new_shape = [ - -1, reduce(lambda x, y: x * y, input.shape[axis:len(input.shape)]) - ] - out = nn.reshape(x=input, shape=new_shape) + out = nn.flatten(x=input, axis=axis) return out def _is_list_or_tuple_(data): @@ -1101,11 +1107,13 @@ def multi_box_head(inputs, stride=stride) mbox_loc = nn.transpose(mbox_loc, perm=[0, 2, 3, 1]) - new_shape = [ + compile_shape = [ mbox_loc.shape[0], mbox_loc.shape[1] * mbox_loc.shape[2] * mbox_loc.shape[3] / 4, 4 ] - mbox_loc_flatten = nn.reshape(mbox_loc, shape=new_shape) + run_shape = tensor.assign(numpy.array([0, -1, 4]).astype("int32")) + mbox_loc_flatten = nn.reshape( + mbox_loc, shape=compile_shape, actual_shape=run_shape) mbox_locs.append(mbox_loc_flatten) # get conf @@ -1117,11 +1125,15 @@ def multi_box_head(inputs, padding=pad, stride=stride) conf_loc = nn.transpose(conf_loc, perm=[0, 2, 3, 1]) - new_shape = [ + new_shape = [0, -1, num_classes] + compile_shape = [ conf_loc.shape[0], conf_loc.shape[1] * conf_loc.shape[2] * conf_loc.shape[3] / num_classes, num_classes ] - conf_loc_flatten = nn.reshape(conf_loc, shape=new_shape) + run_shape = tensor.assign( + numpy.array([0, -1, num_classes]).astype("int32")) + conf_loc_flatten = nn.reshape( + conf_loc, shape=compile_shape, actual_shape=run_shape) mbox_confs.append(conf_loc_flatten) if len(box_results) == 1: diff --git a/python/paddle/fluid/layers/nn.py b/python/paddle/fluid/layers/nn.py index 0960b54123..c75e7eeb43 100644 --- a/python/paddle/fluid/layers/nn.py +++ b/python/paddle/fluid/layers/nn.py @@ -112,6 +112,7 @@ __all__ = [ 'log', 'crop', 'rank_loss', + 'flatten', ] @@ -5361,3 +5362,70 @@ def rank_loss(label, left, right, name=None): "Right": right}, outputs={'Out': out}) return out + + +def flatten(x, axis=1, name=None): + """ + **Flatten layer** + Flattens the input tensor into a 2D matrix. + + Examples: + Case 1: + Given + X.shape = (3, 100, 100, 4) + and + axis = 2 + We get: + Out.shape = (3 * 100, 4 * 100) + + Case 2: + Given + X.shape = (3, 100, 100, 4) + and + axis = 0 + We get: + Out.shape = (1, 3 * 100 * 100 * 4) + + Args: + x (Variable): A tensor of rank >= axis. + axis (int): Indicate up to which input dimensions (exclusive) should + be flattened to the outer dimension of the output. + The value for axis must be in the range [0, R], where R + is the rank of the input tensor. When axis = 0, the shape + of the output tensor is (1, (d_0 X d_1 ... d_n), where the + shape of the input tensor is (d_0, d_1, ... d_n). + name(str|None): A name for this layer(optional). If set None, the layer + will be named automatically. + + Returns: + Variable: A 2D tensor with the contents of the input tensor, with input + dimensions up to axis flattened to the outer dimension of + the output and remaining input dimensions flattened into the + inner dimension of the output. + + Raises: + ValueError: If x is not a variable. + ValueError: If axis is not in range [0, rank(x)]. + + Examples: + + .. code-block:: python + + x = fluid.layers.data(name="x", shape=[4, 4, 3], dtype="float32") + out = fluid.layers.flatten(x=x, axis=2) + """ + helper = LayerHelper('flatten', **locals()) + + if not (isinstance(x, Variable)): + raise ValueError("The input x should be a Variable") + + if not (isinstance(axis, int)) or axis > len(x.shape) or axis < 0: + raise ValueError("The axis should be a int, and in range [0, rank(x)]") + + out = helper.create_tmp_variable(x.dtype) + helper.append_op( + type='flatten', + inputs={"X": x}, + outputs={'Out': out}, + attrs={"axis": axis}) + return out diff --git a/python/paddle/fluid/tests/unittests/test_layers.py b/python/paddle/fluid/tests/unittests/test_layers.py index 8f2dac786d..38a138a8fa 100644 --- a/python/paddle/fluid/tests/unittests/test_layers.py +++ b/python/paddle/fluid/tests/unittests/test_layers.py @@ -465,6 +465,17 @@ class TestBook(unittest.TestCase): self.assertIsNotNone(out) print(str(program)) + def test_flatten(self): + program = Program() + with program_guard(program): + x = layers.data( + name='x', + append_batch_size=False, + shape=[4, 4, 3], + dtype="float32") + out = layers.flatten(x, axis=1, name="flatten") + self.assertIsNotNone(out) + def test_shape(self): program = Program() with program_guard(program): -- GitLab