|  | #include "caffe2/operators/lpnorm_op.h" | 
|  |  | 
|  | #include "caffe2/core/operator.h" | 
|  | #include "caffe2/core/types.h" | 
|  | #include "caffe2/utils/eigen_utils.h" | 
|  |  | 
|  | namespace caffe2 { | 
|  |  | 
|  | template <> | 
|  | bool LpNormOp<float, CPUContext>::RunOnDevice() { | 
|  | const auto& X = Input(0); | 
|  |  | 
|  | auto* norm = Output(0, {1}, at::dtype<float>()); | 
|  | const float* X_data = X.data<float>(); | 
|  | const float size = average_ ? (float)X.numel() : 1.0f; | 
|  | if (p_ == 1) { | 
|  | *(norm->template mutable_data<float>()) = | 
|  | (ConstEigenVectorMap<float>(X_data, X.numel()).array()).abs().sum() / | 
|  | size; | 
|  | // L1(x) = sum(|x|), L1_average(x) = sum(\x\) / x.size() | 
|  | } else if (p_ == 2) { | 
|  | *(norm->template mutable_data<float>()) = | 
|  | (ConstEigenVectorMap<float>(X_data, X.numel()).array()).square().sum() / | 
|  | size; | 
|  | // L2(x) = (sum(|x|^2)), L2_average(x) = sum(|x|^2) / x.size() | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template <> | 
|  | bool LpNormGradientOp<float, CPUContext>::RunOnDevice() { | 
|  | const auto& X = Input(0); | 
|  | const auto& dnorm = Input(1); | 
|  |  | 
|  | CAFFE_ENFORCE_EQ(dnorm.dim(), 1); | 
|  | CAFFE_ENFORCE_EQ(dnorm.dim32(0), 1); | 
|  | auto* dX = Output(0, X.sizes(), at::dtype<float>()); | 
|  | const float size = average_ ? (float)X.numel() : 1.0f; | 
|  | if (p_ == 1) { | 
|  | EigenVectorMap<float>(dX->template mutable_data<float>(), X.numel()) | 
|  | .array() = ConstEigenVectorMap<float>(X.data<float>(), X.numel()) | 
|  | .array() | 
|  | .unaryExpr([](float x) { | 
|  | const float kEps = 1e-12f; | 
|  | if (x < -kEps) { | 
|  | return -1.0f; | 
|  | } else if (x > kEps) { | 
|  | return 1.0f; | 
|  | } else { | 
|  | return 0.0f; | 
|  | } | 
|  | }) * | 
|  | ((dnorm.data<float>())[0] / size); | 
|  | } else if (p_ == 2) { | 
|  | EigenVectorMap<float>(dX->template mutable_data<float>(), X.numel()) | 
|  | .array() = | 
|  | ConstEigenVectorMap<float>(X.data<float>(), X.numel()).array() * 2.0f * | 
|  | ((dnorm.data<float>())[0] / size); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // LpNorm | 
|  | REGISTER_CPU_OPERATOR(LpNorm, LpNormOp<float, CPUContext>); | 
|  | REGISTER_CPU_OPERATOR(LpNormGradient, LpNormGradientOp<float, CPUContext>); | 
|  |  | 
|  | OPERATOR_SCHEMA(LpNorm) | 
|  | .NumInputs(1) | 
|  | .NumOutputs(1) | 
|  | .SetDoc(R"DOC( | 
|  | This op computes the $L_p$ norm of the one dimensional input tensor $X$, and outputs a one dimensional output tensor $Y$. Here, the $L_p$ norm is calculated as | 
|  |  | 
|  | $$L_p(\mathbf{x}) = \sum_i x_i^p$$ | 
|  |  | 
|  | This op supports $p$ values of 1 or 2. If the average argument is set, the norm is calculated as Lp_averaged_norm(x) is defined as Lp_averaged_norm(x) = LpNorm(x) / size(x). | 
|  |  | 
|  | Github Links: | 
|  |  | 
|  | - https://github.com/pytorch/pytorch/blob/main/caffe2/operators/lpnorm_op.h | 
|  | - https://github.com/pytorch/pytorch/blob/main/caffe2/operators/lpnorm_op.cc | 
|  |  | 
|  |  | 
|  | <details> | 
|  |  | 
|  | <summary> <b>Example</b> </summary> | 
|  |  | 
|  | **Code** | 
|  |  | 
|  | ``` | 
|  | workspace.ResetWorkspace() | 
|  |  | 
|  | op = core.CreateOperator( | 
|  | "LpNorm", | 
|  | ["X"], | 
|  | ["Y"], | 
|  | p=2 | 
|  | ) | 
|  | X = np.array([5., 2.]) | 
|  | print("X:\n",X) | 
|  |  | 
|  | // Feed X into workspace | 
|  | workspace.FeedBlob("X", X.astype(np.float32)) | 
|  |  | 
|  | workspace.RunOperatorOnce(op) | 
|  | print("Y:\n", workspace.FetchBlob("Y")) | 
|  |  | 
|  | ``` | 
|  |  | 
|  | **Result** | 
|  |  | 
|  | ``` | 
|  |  | 
|  | X: | 
|  | [5. 2.] | 
|  | Y: | 
|  | [29.] | 
|  |  | 
|  | ``` | 
|  |  | 
|  | </details> | 
|  |  | 
|  | )DOC") | 
|  | .Input(0, "X", "1D Input tensor of data to be operated on.") | 
|  | .Output(0, "Z", "1D output tensor") | 
|  | .Arg( | 
|  | "p", | 
|  | "*(type: int; default: 2, possible values: {1,2})* Order of the norm in p-norm.") | 
|  | .Arg( | 
|  | "average", | 
|  | "*(type: bool; default: False)* Whether we calculate norm or averaged_norm.The Lp_averaged_norm(x) is defined as Lp_averaged_norm(x) = LpNorm(x) / size(x)") | 
|  | .TensorInferenceFunction([](const OperatorDef& /* unused */, | 
|  | const vector<TensorShape>& in) { | 
|  | std::vector<int64_t> output_dims(1); | 
|  | output_dims[0] = 1; // 1 | 
|  | return vector<TensorShape>{ | 
|  | CreateTensorShape(vector<int64_t>{output_dims}, in[0].data_type())}; | 
|  | }); | 
|  |  | 
|  | OPERATOR_SCHEMA(LpNormGradient) | 
|  | .NumInputs(2) | 
|  | .NumOutputs(1) | 
|  | .SetDoc(R"DOC( | 
|  | Given one input float tensor X, derivative dout, and produces one output | 
|  | float tensor dX. dX is the derivative of the Lp norm of tensor X, computed as | 
|  | dx = d(sum over |x^p|)/dx, in which p is either 1 or 2(currently only | 
|  | supports l1 and l2 norm) determined by the argument p. | 
|  | )DOC") | 
|  | .Input(0, "X", "1D input tensor") | 
|  | .Input(1, "dout", "1D input tensor") | 
|  | .Output(0, "dx", "1D output tensor") | 
|  | .Arg("p", "Order of the norm in p-norm") | 
|  | .Arg( | 
|  | "average", | 
|  | "whether we calculate norm or averaged_norm." | 
|  | "The Lp_averaged_norm(x) is defined as" | 
|  | "Lp_averaged_normgradient(x) = LpNormGradient(x) / size(x)"); | 
|  |  | 
|  | class GetLpNormGradient : public GradientMakerBase { | 
|  | using GradientMakerBase::GradientMakerBase; | 
|  | vector<OperatorDef> GetGradientDefs() override { | 
|  | return SingleGradientDef( | 
|  | "LpNormGradient", | 
|  | "", | 
|  | vector<string>{I(0), GO(0)}, | 
|  | vector<string>{GI(0)}); | 
|  | } | 
|  | }; | 
|  |  | 
|  | REGISTER_GRADIENT(LpNorm, GetLpNormGradient); | 
|  |  | 
|  | } // namespace caffe2 |