|  | #include "caffe2/operators/conv_op.h" | 
|  | #include "caffe2/operators/conv_op_impl.h" | 
|  | #include "caffe2/operators/conv_pool_op_base.h" | 
|  |  | 
|  | namespace caffe2 { | 
|  |  | 
|  | std::vector<TensorShape> TensorInferenceForConvGradient( | 
|  | const OperatorDef& def, | 
|  | const std::vector<TensorShape>& in) { | 
|  | CAFFE_ENFORCE_EQ(in.size(), 3, "ConvGradient requires 3 inputs"); | 
|  |  | 
|  | if (in[0].unknown_shape()) { | 
|  | std::vector<TensorShape> out(1); | 
|  | out[0].set_unknown_shape(true); | 
|  | return out; | 
|  | } | 
|  | ArgumentHelper helper(def); | 
|  | const auto no_bias = helper.GetSingleArgument<int>("no_bias", 0); | 
|  | const auto n_outputs = def.output_size(); | 
|  | vector<TensorShape> out(n_outputs); | 
|  |  | 
|  | // FILTER_GRAD has the same shape as FILTER | 
|  | out[0] = in[1]; | 
|  | if (!no_bias) { | 
|  | vector<int64_t> bias_shape = {in[1].dims(0)}; | 
|  | out[1] = CreateTensorShape(bias_shape, in[1].data_type()); | 
|  | } | 
|  |  | 
|  | if (n_outputs == 3 || (no_bias && n_outputs == 2)) { | 
|  | // INPUT_GRAD has the same shape as INPUT | 
|  | out[out.size() - 1] = in[0]; | 
|  | } | 
|  |  | 
|  | return out; | 
|  | } | 
|  |  | 
|  | OpSchema::Cost CostInferenceForConvGradient( | 
|  | const OperatorDef& def, | 
|  | const vector<TensorShape>& inputs) { | 
|  | CAFFE_ENFORCE_EQ(inputs.size(), 3, "ConvGradient requires 3 inputs"); | 
|  | ArgumentHelper helper(def); | 
|  | const auto order = | 
|  | StringToStorageOrder(helper.GetSingleArgument<string>("order", "NCHW")); | 
|  | const auto no_bias = helper.GetSingleArgument<int>("no_bias", 0); | 
|  | const auto n_outputs = def.output_size(); | 
|  |  | 
|  | const auto& outputs = TensorInferenceForConvGradient(def, inputs); | 
|  | const auto& X = inputs[0]; | 
|  | const auto& filter = inputs[1]; | 
|  | const auto& dY = inputs[2]; | 
|  | const auto N = X.dims(0); | 
|  | const auto M = filter.dims(0); | 
|  | const auto C = | 
|  | (order == StorageOrder::NCHW ? X.dims(1) : X.dims(X.dims_size() - 1)); | 
|  | const auto output_image_size = | 
|  | (order == StorageOrder::NCHW | 
|  | ? nElemFromDim(dY, 2) | 
|  | : nElemBetweenDim(dY, 1, dY.dims_size() - 1)); | 
|  | auto kernel_elem = | 
|  | (order == StorageOrder::NCHW | 
|  | ? nElemFromDim(filter, 2) | 
|  | : nElemBetweenDim(filter, 1, filter.dims_size() - 1)); | 
|  |  | 
|  | struct OpSchema::Cost c; | 
|  | c.flops = N * 2 * M * kernel_elem * C * output_image_size; | 
|  | if (!no_bias) { | 
|  | c.flops += N * (M * output_image_size); | 
|  | } | 
|  | if (n_outputs == 3 || (no_bias && n_outputs == 2)) { | 
|  | c.flops += N * 2 * M * kernel_elem * C * output_image_size; | 
|  | } | 
|  |  | 
|  | c.bytes_read = (nElemFromDim(X) + nElemFromDim(filter) + nElemFromDim(dY)) * | 
|  | sizeof(float); | 
|  |  | 
|  | for (auto i = 0; i < n_outputs; i++) { | 
|  | c.bytes_written += nElemFromDim(outputs[i]) * sizeof(float); | 
|  | } | 
|  | c.params_bytes = nElemFromDim(filter) * sizeof(float); | 
|  |  | 
|  | return c; | 
|  | } | 
|  |  | 
|  | REGISTER_CPU_OPERATOR(ConvGradient, ConvGradientOp<float, CPUContext>); | 
|  | OPERATOR_SCHEMA(ConvGradient) | 
|  | .NumInputs(2, 3) | 
|  | .NumOutputs(1, 3) | 
|  | .TensorInferenceFunction(TensorInferenceForConvGradient) | 
|  | .CostInferenceFunction(CostInferenceForConvGradient); | 
|  |  | 
|  | REGISTER_CPU_OPERATOR(Conv1DGradient, ConvGradientOp<float, CPUContext>); | 
|  | OPERATOR_SCHEMA(Conv1DGradient).NumInputs(2, 3).NumOutputs(1, 3); | 
|  |  | 
|  | REGISTER_CPU_OPERATOR(Conv2DGradient, ConvGradientOp<float, CPUContext>); | 
|  | OPERATOR_SCHEMA(Conv2DGradient).NumInputs(2, 3).NumOutputs(1, 3); | 
|  |  | 
|  | REGISTER_CPU_OPERATOR(Conv3DGradient, ConvGradientOp<float, CPUContext>); | 
|  | OPERATOR_SCHEMA(Conv3DGradient).NumInputs(2, 3).NumOutputs(1, 3); | 
|  |  | 
|  | class GetConvGradient : public GradientMakerBase { | 
|  | using GradientMakerBase::GradientMakerBase; | 
|  | vector<OperatorDef> GetGradientDefs() override { | 
|  | CAFFE_ENFORCE(def_.input_size() == 3 || def_.input_size() == 2); | 
|  |  | 
|  | ArgumentHelper argsHelper(def_); | 
|  |  | 
|  | auto compute_dX = !argsHelper.GetSingleArgument<bool>("no_gradient_to_input", 0); | 
|  |  | 
|  | if (def_.input_size() == 3) { | 
|  | if (compute_dX) { | 
|  | return SingleGradientDef( | 
|  | def_.type() + "Gradient", | 
|  | "", | 
|  | vector<string>{I(0), I(1), GO(0)}, | 
|  | vector<string>{GI(1), GI(2), GI(0)}); | 
|  | } else { | 
|  | return SingleGradientDef( | 
|  | def_.type() + "Gradient", | 
|  | "", | 
|  | vector<string>{I(0), I(1), GO(0)}, | 
|  | vector<string>{GI(1), GI(2)}); | 
|  | } | 
|  | } else { | 
|  | if (compute_dX) { | 
|  | return SingleGradientDef( | 
|  | def_.type() + "Gradient", | 
|  | "", | 
|  | vector<string>{I(0), I(1), GO(0)}, | 
|  | vector<string>{GI(1), GI(0)}, | 
|  | vector<Argument>{MakeArgument<int>("no_bias", 1)}); | 
|  | } else { | 
|  | return SingleGradientDef( | 
|  | def_.type() + "Gradient", | 
|  | "", | 
|  | vector<string>{I(0), I(1), GO(0)}, | 
|  | vector<string>{GI(1)}, | 
|  | vector<Argument>{MakeArgument<int>("no_bias", 1)}); | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  | REGISTER_GRADIENT(Conv, GetConvGradient); | 
|  | REGISTER_GRADIENT(Conv1D, GetConvGradient); | 
|  | REGISTER_GRADIENT(Conv2D, GetConvGradient); | 
|  | REGISTER_GRADIENT(Conv3D, GetConvGradient); | 
|  |  | 
|  | } // namespace caffe2 |