| #include "caffe2/operators/transpose_op.h" |
| |
| namespace caffe2 { |
| |
| REGISTER_CPU_OPERATOR(Transpose, TransposeOp<CPUContext>); |
| |
| OPERATOR_SCHEMA(Transpose) |
| .NumInputs(1) |
| .NumOutputs(1) |
| .TensorInferenceFunction([](const OperatorDef& def, |
| const vector<TensorShape>& in) { |
| ArgumentHelper helper(def); |
| vector<int> axes = helper.GetRepeatedArgument<int>("axes"); |
| vector<TensorShape> out(1); |
| out[0].set_data_type(in[0].data_type()); |
| |
| if (axes.empty()) { |
| for (auto axis = in [0].dims().rbegin(); axis != in[0].dims().rend(); |
| ++axis) { |
| out[0].add_dims(*axis); |
| } |
| } else { |
| auto tensor_size = in[0].dims().size(); |
| auto valid_axes = |
| std::all_of(axes.begin(), axes.end(), [&tensor_size](int& axis) { |
| return axis >= 0 && axis < tensor_size; |
| }); |
| |
| CAFFE_ENFORCE(valid_axes, "Axes argument passed in had invalid values"); |
| CAFFE_ENFORCE( |
| // NOLINTNEXTLINE(clang-diagnostic-sign-compare) |
| axes.size() == tensor_size, |
| "Axes argument passed in had the incorrect size"); |
| |
| // NOLINTNEXTLINE(modernize-loop-convert) |
| for (auto axis = axes.begin(); axis != axes.end(); ++axis) { |
| out[0].add_dims(in[0].dims().Get(*axis)); |
| } |
| } |
| |
| return out; |
| }) |
| .SetDoc(R"DOC( |
| Transpose the input tensor by permuting the axes of the input according |
| to the `axes` argument. Similar to numpy's |
| [transpose](https://docs.scipy.org/doc/numpy/reference/generated/numpy.transpose.html) |
| function. |
| |
| For example, when axes=(1, 0, 2), given an input tensor of shape |
| (1, 2, 3), the output shape will be (2, 1, 3). |
| |
| Github Links: |
| |
| - https://github.com/pytorch/pytorch/blob/main/caffe2/operators/transpose_op.cc |
| |
| <details> |
| |
| <summary> <b>Example</b> </summary> |
| |
| **Code** |
| |
| ``` |
| workspace.ResetWorkspace() |
| |
| op = core.CreateOperator( |
| "Transpose", |
| ["X"], |
| ["Y"], |
| axes=(0,3,1,2) |
| ) |
| |
| x = np.random.rand(1,32,32,3) |
| workspace.FeedBlob("X", x) |
| print("X.shape (NHWC order):", workspace.FetchBlob("X").shape) |
| workspace.RunOperatorOnce(op) |
| print("Y.shape (NCHW order):", workspace.FetchBlob("Y").shape) |
| ``` |
| |
| **Result** |
| |
| ``` |
| X.shape (NHWC order): (1, 32, 32, 3) |
| Y.shape (NCHW order): (1, 3, 32, 32) |
| ``` |
| |
| </details> |
| |
| )DOC") |
| .Arg( |
| "axes", |
| "*(type: Tuple(int))* Order to permute axes of input tensor. Reverses " |
| "the dimensions by default.") |
| .Input(0, "X", "*(type: Tensor)* Input tensor.") |
| .Output(0, "Y", "*(type: Tensor)* Transposed output.") |
| .InheritOnnxSchema(); |
| |
| class GetTransposeGradient : public GradientMakerBase { |
| using GradientMakerBase::GradientMakerBase; |
| // We will create our own arguments. |
| bool CopyArguments() const override { |
| return false; |
| } |
| vector<OperatorDef> GetGradientDefs() override { |
| auto ops = SingleGradientDef( |
| "Transpose", "", vector<string>{GO(0)}, vector<string>{GI(0)}); |
| ops[0].mutable_arg()->CopyFrom(Def().arg()); |
| if (ArgumentHelper::HasArgument(Def(), "axes")) { |
| // If axes is specified, we will need to figure out the inverse index. |
| const Argument& old_axes = GetArgument(Def(), "axes"); |
| const int axes_size = old_axes.ints_size(); |
| Argument* new_arg = GetMutableArgument("axes", false, &ops[0]); |
| for (int i = 0; i < axes_size; ++i) { |
| new_arg->set_ints(old_axes.ints(i), i); |
| } |
| } |
| return ops; |
| } |
| }; |
| |
| REGISTER_GRADIENT(Transpose, GetTransposeGradient); |
| |
| } // namespace caffe2 |