diff --git a/paddle/fluid/operators/roll_op.cc b/paddle/fluid/operators/roll_op.cc index d28feb257ef14..f470f41f1eb5c 100644 --- a/paddle/fluid/operators/roll_op.cc +++ b/paddle/fluid/operators/roll_op.cc @@ -33,7 +33,7 @@ class RollOp : public framework::OperatorWithKernel { platform::errors::InvalidArgument( "Output(Out) of RollOp should not be null.")); - auto dims = ctx->Attrs().Get>("dims"); + auto dims = ctx->Attrs().Get>("axis"); auto shifts = ctx->Attrs().Get>("shifts"); PADDLE_ENFORCE_EQ(dims.size(), shifts.size(), @@ -92,7 +92,7 @@ class RollOpMaker : public framework::OpProtoAndCheckerMaker { "of the tensor are shifted.") .SetDefault({}); AddAttr>( - "dims", + "axis", "Axis along which to roll. It must have the same size " "with shifts.") .SetDefault({}); diff --git a/paddle/fluid/operators/roll_op.h b/paddle/fluid/operators/roll_op.h index fbc277433fc56..74dd37ed8388f 100644 --- a/paddle/fluid/operators/roll_op.h +++ b/paddle/fluid/operators/roll_op.h @@ -82,7 +82,7 @@ class RollKernel : public framework::OpKernel { auto& input = input_var->Get(); auto* output = output_var->GetMutable(); std::vector shifts = context.Attr>("shifts"); - std::vector dims = context.Attr>("dims"); + std::vector dims = context.Attr>("axis"); std::vector out_vec; TensorToVector(input, context.device_context(), &out_vec); @@ -94,8 +94,8 @@ class RollKernel : public framework::OpKernel { PADDLE_ENFORCE_EQ( dims[i] < input_dim.size() && dims[i] >= (0 - input_dim.size()), true, platform::errors::OutOfRange( - "Attr(dims[%d]) is out of range, It's expected " - "to be in range of [-%d, %d]. But received Attr(dims[%d]) = %d.", + "Attr(axis[%d]) is out of range, It's expected " + "to be in range of [-%d, %d]. But received Attr(axis[%d]) = %d.", i, input_dim.size(), input_dim.size() - 1, i, dims[i])); shift_along_dim(out_vec.data(), input_dim, dims[i], shifts[i]); } @@ -114,7 +114,7 @@ class RollGradKernel : public framework::OpKernel { auto& input = input_var->Get(); auto* output = output_var->GetMutable(); std::vector shifts = context.Attr>("shifts"); - std::vector dims = context.Attr>("dims"); + std::vector dims = context.Attr>("axis"); std::vector out_vec; TensorToVector(input, context.device_context(), &out_vec); diff --git a/python/paddle/fluid/tests/unittests/test_roll_op.py b/python/paddle/fluid/tests/unittests/test_roll_op.py index 79162dbd9116d..b20293adf4c40 100644 --- a/python/paddle/fluid/tests/unittests/test_roll_op.py +++ b/python/paddle/fluid/tests/unittests/test_roll_op.py @@ -28,17 +28,17 @@ def setUp(self): self.op_type = "roll" self.init_dtype_type() self.inputs = {'X': np.random.random(self.x_shape).astype(self.dtype)} - self.attrs = {'shifts': self.shifts, 'dims': self.dims} + self.attrs = {'shifts': self.shifts, 'axis': self.axis} self.outputs = { 'Out': np.roll(self.inputs['X'], self.attrs['shifts'], - self.attrs['dims']) + self.attrs['axis']) } def init_dtype_type(self): self.dtype = np.float64 self.x_shape = (100, 4, 5) self.shifts = [101, -1] - self.dims = [0, -2] + self.axis = [0, -2] def test_check_output(self): self.check_output() @@ -52,7 +52,7 @@ def init_dtype_type(self): self.dtype = np.float32 self.x_shape = (100, 10, 5) self.shifts = [8, -1] - self.dims = [-1, -2] + self.axis = [-1, -2] class TestRollAPI(unittest.TestCase): @@ -78,7 +78,7 @@ def test_roll_op_api(self): # case 2: with program_guard(Program(), Program()): x = fluid.layers.data(name='x', shape=[-1, 3]) - z = paddle.roll(x, shifts=1, dims=0) + z = paddle.roll(x, shifts=1, axis=0) exe = fluid.Executor(fluid.CPUPlace()) res, = exe.run(feed={'x': self.data_x}, fetch_list=[z.name], @@ -101,12 +101,26 @@ def test_dygraph_api(self): # case 2: with fluid.dygraph.guard(): x = fluid.dygraph.to_variable(self.data_x) - z = paddle.roll(x, shifts=1, dims=0) + z = paddle.roll(x, shifts=1, axis=0) np_z = z.numpy() expect_out = np.array([[7.0, 8.0, 9.0], [1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) self.assertTrue(np.allclose(expect_out, np_z)) + def test_roll_op_false(self): + self.input_data() + + def test_axis_out_range(): + with program_guard(Program(), Program()): + x = fluid.layers.data(name='x', shape=[-1, 3]) + z = paddle.roll(x, shifts=1, axis=10) + exe = fluid.Executor(fluid.CPUPlace()) + res, = exe.run(feed={'x': self.data_x}, + fetch_list=[z.name], + return_numpy=False) + + self.assertRaises(ValueError, test_axis_out_range) + if __name__ == "__main__": unittest.main() diff --git a/python/paddle/tensor/manipulation.py b/python/paddle/tensor/manipulation.py index a888b78ea9146..f1fc40e3c2b9b 100644 --- a/python/paddle/tensor/manipulation.py +++ b/python/paddle/tensor/manipulation.py @@ -104,23 +104,24 @@ def flip(input, dims, name=None): return out -def roll(input, shifts, dims=None): +def roll(x, shifts, axis=None, name=None): """ :alias_main: paddle.roll :alias: paddle.roll,paddle.tensor.roll,paddle.tensor.manipulation.roll - Roll the `input` tensor along the given dimension(s). Elements that are shifted beyond - the last position are re-introduced at the first position. If a dimension is not specified, + Roll the `x` tensor along the given axis(axes). With specific 'shifts', Elements that + roll beyond the last position are re-introduced at the first according to 'shifts'. + If a axis is not specified, the tensor will be flattened before rolling and then restored to the original shape. Args: - input (Variable): The input tensor variable. + x (Variable): The x tensor variable as input. shifts (int|list|tuple): The number of places by which the elements - of the `input` tensor are shifted. - dims (int|list|tuple|None): Dimentions along which to roll. + of the `x` tensor are shifted. + axis (int|list|tuple|None): axis(axes) along which to roll. Returns: - Variable: A Tensor with same data type as `input`. + Variable: A Tensor with same data type as `x`. Examples: .. code-block:: python @@ -131,48 +132,56 @@ def roll(input, shifts, dims=None): data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) - with fluid.dygraph.guard(): - x = fluid.dygraph.to_variable(data) - out_z1 = paddle.roll(x, shifts=1) - print(out_z1.numpy()) - #[[9. 1. 2.] - # [3. 4. 5.] - # [6. 7. 8.]] - out_z2 = paddle.roll(x, shifts=1, dims=0) - print(out_z2.numpy()) - #[[7. 8. 9.] - # [1. 2. 3.] - # [4. 5. 6.]] + paddle.enable_imperative() + x = paddle.imperative.to_variable(data) + out_z1 = paddle.roll(x, shifts=1) + print(out_z1.numpy()) + #[[9. 1. 2.] + # [3. 4. 5.] + # [6. 7. 8.]] + out_z2 = paddle.roll(x, shifts=1, axis=0) + print(out_z2.numpy()) + #[[7. 8. 9.] + # [1. 2. 3.] + # [4. 5. 6.]] """ helper = LayerHelper("roll", **locals()) - origin_shape = input.shape + origin_shape = x.shape if type(shifts) == int: shifts = [shifts] - if type(dims) == int: - dims = [dims] - - if dims: - check_type(dims, 'dims', (list, tuple), 'roll') + if type(axis) == int: + axis = [axis] + + len_origin_shape = len(origin_shape) + if axis: + for i in range(len(axis)): + if axis[i] >= len_origin_shape or axis[i] < -len_origin_shape: + raise ValueError( + "axis is out of range, it should be in range [{}, {}), but received {}". + format(-len_origin_shape, len_origin_shape, axis)) + + if axis: + check_type(axis, 'axis', (list, tuple), 'roll') check_type(shifts, 'shifts', (list, tuple), 'roll') if in_dygraph_mode(): - if dims is None: - input = core.ops.reshape(input, 'shape', [-1, 1]) - dims = [0] - out = core.ops.roll(input, 'dims', dims, 'shifts', shifts) + if axis is None: + x = core.ops.reshape(x, 'shape', [-1, 1]) + axis = [0] + out = core.ops.roll(x, 'axis', axis, 'shifts', shifts) return core.ops.reshape(out, 'shape', origin_shape) - out = helper.create_variable_for_type_inference(input.dtype) + out = helper.create_variable_for_type_inference(x.dtype) - if dims is None: - input = reshape(input, shape=[-1, 1]) - dims = [0] + if axis is None: + x = reshape(x, shape=[-1, 1]) + axis = [0] helper.append_op( type='roll', - inputs={'X': input}, + inputs={'X': x}, outputs={'Out': out}, - attrs={'dims': dims, + attrs={'axis': axis, 'shifts': shifts}) out = reshape(out, shape=origin_shape, inplace=True) return out