diff options
Diffstat (limited to 'caffe2')
-rw-r--r-- | caffe2/operators/utility_ops.cc | 38 | ||||
-rw-r--r-- | caffe2/operators/utility_ops.h | 12 | ||||
-rw-r--r-- | caffe2/python/operator_test/flatten_op_test.py | 38 | ||||
-rw-r--r-- | caffe2/python/operator_test/shape_inference_test.py | 16 |
4 files changed, 86 insertions, 18 deletions
diff --git a/caffe2/operators/utility_ops.cc b/caffe2/operators/utility_ops.cc index 5763e64a35..9a2e82e09f 100644 --- a/caffe2/operators/utility_ops.cc +++ b/caffe2/operators/utility_ops.cc @@ -135,34 +135,44 @@ OPERATOR_SCHEMA(LengthsToShape).NumInputs(1).NumOutputs(1); OPERATOR_SCHEMA(Flatten) .NumInputs(1) .NumOutputs(1) - .TensorInferenceFunction([](const OperatorDef&, + .TensorInferenceFunction([](const OperatorDef& def, const vector<TensorShape>& in) { + ArgumentHelper helper(def); + const int axis = helper.GetSingleArgument<int>("axis", 1); vector<TensorShape> out(1); - int total = 1; + TIndex outer = 1; + TIndex inner = 1; std::size_t index = 0; for (auto d : in[0].dims()) { - // skip the first element - if (index++ == 0) { - continue; + if (index < axis) { + outer *= d; + } else { + inner *= d; } - total *= d; + ++index; } out[0].set_data_type(in[0].data_type()); - out[0].add_dims(in[0].dims(0)); - out[0].add_dims(total); + out[0].add_dims(outer); + out[0].add_dims(inner); return out; }) .SetDoc(R"DOC( -Flattens the input tensor into a 2D matrix, keeping the first dimension -unchanged. +Flattens the input tensor into a 2D matrix. If input tensor has shape +(d_0, d_1, ... d_n) then the output will have shape +(d_0 X d_1 ... d_(axis-1), d_axis X d_(axis+1) ... X dn) )DOC") - .Input(0, "input", "A tensor of rank >= 2.") + .Input(0, "input", "A tensor of rank >= axis.") .Output( 0, "output", - "A tensor of rank 2 with the contents of the input tensor, " - "with first dimension equal first dimension of input, and remaining " - "input dimensions flattened into the inner dimension of the output."); + "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.") + .Arg( + "axis", + "(Default to 1) Indicate up to which input dimensions " + "(exclusive) should be flattened to the outer dimension of the output"); OPERATOR_SCHEMA(FlattenToVec) .NumInputs(1) diff --git a/caffe2/operators/utility_ops.h b/caffe2/operators/utility_ops.h index da080a6cfc..0b130d9655 100644 --- a/caffe2/operators/utility_ops.h +++ b/caffe2/operators/utility_ops.h @@ -222,14 +222,17 @@ template <class Context> class FlattenOp : public Operator<Context> { public: USE_OPERATOR_CONTEXT_FUNCTIONS; - USE_SIMPLE_CTOR_DTOR(FlattenOp); + + FlattenOp(const OperatorDef& operator_def, Workspace* ws) + : Operator<Context>(operator_def, ws), + axis_(OperatorBase::GetSingleArgument<int>("axis", 1)) {} bool RunOnDevice() override { auto& input = Input(0); auto* output = Output(0); CAFFE_ENFORCE_GE( - input.dims().size(), 2, "The rank of the tensor must be >= 2."); - output->Resize(input.dim(0), input.size_from_dim(1)); + input.dims().size(), axis_, "The rank of the tensor must be >= axis."); + output->Resize(input.size_to_dim(axis_), input.size_from_dim(axis_)); context_.template CopyItems<Context, Context>( input.meta(), input.size(), @@ -237,6 +240,9 @@ class FlattenOp : public Operator<Context> { output->raw_mutable_data(input.meta())); return true; } + + private: + int axis_; }; template <class Context> diff --git a/caffe2/python/operator_test/flatten_op_test.py b/caffe2/python/operator_test/flatten_op_test.py new file mode 100644 index 0000000000..19d204e0bd --- /dev/null +++ b/caffe2/python/operator_test/flatten_op_test.py @@ -0,0 +1,38 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from hypothesis import given +import numpy as np + +from caffe2.python import core +import caffe2.python.hypothesis_test_util as hu + + +class TestFlatten(hu.HypothesisTestCase): + @given(X=hu.tensor(min_dim=2, max_dim=4), + **hu.gcs) + def test_flatten(self, X, gc, dc): + for axis in range(X.ndim + 1): + op = core.CreateOperator( + "Flatten", + ["X"], + ["Y"], + axis=axis) + + def flatten_ref(X): + shape = X.shape + outer = np.prod(shape[:axis]).astype(int) + inner = np.prod(shape[axis:]).astype(int) + return np.copy(X).reshape(outer, inner), + + self.assertReferenceChecks(gc, op, [X], flatten_ref) + + # Check over multiple devices + self.assertDeviceChecks(dc, op, [X], [0]) + + +if __name__ == "__main__": + import unittest + unittest.main() diff --git a/caffe2/python/operator_test/shape_inference_test.py b/caffe2/python/operator_test/shape_inference_test.py index db7520a179..e903e8ef2e 100644 --- a/caffe2/python/operator_test/shape_inference_test.py +++ b/caffe2/python/operator_test/shape_inference_test.py @@ -347,7 +347,7 @@ class TestShapeInference(test_util.TestCase): self.InferTensorRunAndCompare(model) - + # test Flatten with default axis (=1) model = model_helper.ModelHelper(name="test_model") model.Flatten("X", "Flat") model.Flatten("empty", "EmptyFlat") @@ -356,6 +356,20 @@ class TestShapeInference(test_util.TestCase): self.InferTensorRunAndCompare(model) + # test Flatten with axis + model = model_helper.ModelHelper(name="test_model") + x = np.random.randn(17, 5, 13) + for axis in range(x.ndim + 1): + model.Flatten("x", "Flat", axis=axis) + workspace.FeedBlob("x", x) + self.InferTensorRunAndCompare(model) + + empty = np.random.randn(0, 5, 13) + for axis in range(empty.ndim + 1): + model.Flatten("empty", "Flat", axis=axis) + workspace.FeedBlob("empty", empty) + self.InferTensorRunAndCompare(model) + def testShapeInferenceReshape(self): model = model_helper.ModelHelper(name="test_model") model.Reshape("X", ["Reshaped", "Old_Shape"], shape=[8, 0, -1, 2]) |