| #include "elementwise_linear_op.h" |
| |
| namespace caffe2 { |
| |
| template<> |
| bool ElementwiseLinearOp<float, CPUContext>::RunOnDevice(){ |
| const auto& X = Input(0); |
| const auto& a = Input(1); |
| const auto& b = Input(2); |
| |
| const auto canonical_axis = X.canonical_axis_index(axis_); |
| const int N = X.size_to_dim(canonical_axis); |
| const int D = X.size_from_dim(canonical_axis); |
| |
| CAFFE_ENFORCE_EQ(a.dim(), 1, a.dim()); |
| CAFFE_ENFORCE_EQ(a.size(0), D, a.dim()); |
| CAFFE_ENFORCE_EQ(b.dim(), 1, b.dim()); |
| CAFFE_ENFORCE_EQ(b.size(0), D, b.dim()); |
| |
| auto* Y = Output(0, X.sizes(), at::dtype<float>()); |
| |
| const float* X_data = X.data<float>(); |
| const float* a_data = a.data<float>(); |
| const float* b_data = b.data<float>(); |
| float* Y_data = Y->template mutable_data<float>(); |
| |
| int p = 0; |
| for (int n = 0; n < N; ++n) { |
| for (int d = 0; d < D; ++d) { |
| Y_data[p] = X_data[p] * a_data[d] + b_data[d]; |
| p++; |
| } |
| } |
| return true; |
| } |
| |
| template<> |
| bool ElementwiseLinearGradientOp<float, CPUContext>::RunOnDevice(){ |
| const auto& g_o = Input(0); |
| const auto& X = Input(1); |
| const auto& a = Input(2); |
| |
| const auto canonical_axis = X.canonical_axis_index(axis_); |
| const int N = X.size_to_dim(canonical_axis); |
| const int D = X.size_from_dim(canonical_axis); |
| |
| CAFFE_ENFORCE_EQ(a.dim(), 1, a.dim()); |
| CAFFE_ENFORCE_EQ(a.size(0), D, a.dim()); |
| |
| auto* g_X = Output(0, X.sizes(), at::dtype<float>()); |
| auto* g_a = Output(1, a.sizes(), at::dtype<float>()); |
| auto* g_b = Output(2, a.sizes(), at::dtype<float>()); |
| |
| const float* g_o_data = g_o.data<float>(); |
| const float* X_data = X.data<float>(); |
| const float* a_data = a.data<float>(); |
| float* g_X_data = g_X->template mutable_data<float>(); |
| float* g_a_data = g_a->template mutable_data<float>(); |
| float* g_b_data = g_b->template mutable_data<float>(); |
| |
| math::Set<float, CPUContext>(g_a->numel(), 0.f, g_a_data, &context_); |
| math::Set<float, CPUContext>(g_b->numel(), 0.f, g_b_data, &context_); |
| |
| int p = 0; |
| for (int n = 0; n < N; ++n) { |
| for (int d = 0; d < D; ++d) { |
| g_X_data[p] = g_o_data[p] * a_data[d]; |
| g_a_data[d] += g_o_data[p] * X_data[p]; |
| g_b_data[d] += g_o_data[p]; |
| p++; |
| } |
| } |
| return true; |
| } |
| |
| REGISTER_CPU_OPERATOR( |
| ElementwiseLinear, |
| ElementwiseLinearOp<float, CPUContext>); |
| REGISTER_CPU_OPERATOR( |
| ElementwiseLinearGradient, |
| ElementwiseLinearGradientOp<float, CPUContext>); |
| |
| OPERATOR_SCHEMA(ElementwiseLinear) |
| .NumInputs(3) |
| .NumOutputs(1) |
| .SetDoc(R"DOC( |
| This op computes the elementwise linear combination of a batch of input vectors with a weight vector and bias vector. As input, the op takes an input tensor $X$ of shape $NxD$, a weight vector $w$ of length $D$, and a bias vector $b$ of length $D$. Here, $N$ represents the batch size and $D$ represents the length of the feature vectors. The output, $Y$, is a tensor of shape $NxD$ and is calculated as |
| |
| $$Y_{ij} = X_{ij}w_j + b_j \ for \ i\in{N}, j\in{D}$$ |
| |
| Github Links: |
| - https://github.com/pytorch/pytorch/blob/master/caffe2/operators/elementwise_linear_op.h |
| - https://github.com/pytorch/pytorch/blob/master/caffe2/operators/elementwise_linear_op.cc |
| |
| <details> |
| |
| <summary> <b>Example</b> </summary> |
| |
| **Code** |
| |
| ``` |
| |
| workspace.ResetWorkspace() |
| |
| op = core.CreateOperator( |
| "ElementwiseLinear", |
| ["X", "w", "b"], |
| ["Y"] |
| ) |
| |
| // Create X |
| X = np.array([[1,2,3,4,5],[6,8,9,16,10]]) |
| print("X:\n",X) |
| |
| // Create w |
| w = np.array([1,1/2.,1/3.,1/4.,1/5.]) |
| print("w:\n",w) |
| |
| // Create b |
| b = np.array([1.,1.,1.,1.,1.]) |
| print("b:\n",b) |
| |
| |
| // Feed X & w & b into workspace |
| workspace.FeedBlob("X", X.astype(np.float32)) |
| workspace.FeedBlob("w", w.astype(np.float32)) |
| workspace.FeedBlob("b", b.astype(np.float32)) |
| |
| // Run op |
| workspace.RunOperatorOnce(op) |
| |
| // Collect Output |
| print("Y:\n", workspace.FetchBlob("Y")) |
| |
| ``` |
| |
| **Result** |
| |
| ``` |
| |
| X: |
| [[ 1 2 3 4 5] |
| [ 6 8 9 16 10]] |
| w: |
| [1. 0.5 0.33333333 0.25 0.2] |
| b: |
| [1. 1. 1. 1. 1.] |
| Y: |
| [[2. 2. 2. 2. 2.] |
| [7. 5. 4. 5. 3.]] |
| |
| ``` |
| |
| </details> |
| |
| )DOC") |
| .Input( |
| 0, |
| "X", |
| "2D input tensor of size $NxD$. This input represents the input data to be operated on.") |
| .Input( |
| 1, |
| "w", |
| "1D scaling factors, or weights, of size $D$. This input contains the weights that will be multiplied by the data.") |
| .Input( |
| 2, |
| "b", |
| "1D biases of size $D$. This input contains the biases that will be added to the products of the weights and data.") |
| .Output( |
| 0, |
| "Y", |
| "2D output tensor of size $NxD$. Calculated as described above.") |
| .Arg( |
| "axis", |
| "*(type: int; default: 1)* Describes the axis of the inputs; defaults to one because the 0th axis most likely describes the batch size.") |
| .InheritOnnxSchema(); |
| |
| OPERATOR_SCHEMA(ElementwiseLinearGradient) |
| .NumInputs(3) |
| .NumOutputs(3); |
| |
| struct GetElementwiseLinearGradient : public GradientMakerBase { |
| using GradientMakerBase::GradientMakerBase; |
| vector<OperatorDef> GetGradientDefs() override { |
| return SingleGradientDef( |
| "ElementwiseLinearGradient", |
| "", |
| vector<string>{GO(0), I(0), I(1)}, |
| vector<string>{GI(0), GI(1), GI(2)}); |
| } |
| }; |
| |
| REGISTER_GRADIENT( |
| ElementwiseLinear, |
| GetElementwiseLinearGradient |
| ); |
| |
| } // namespace caffe2 |