Caffe2 - C++ API
A deep learning, cross platform ML framework
elementwise_linear_op.cc
1 #include "elementwise_linear_op.h"
2 
3 namespace caffe2 {
4 
5 template<>
6 bool ElementwiseLinearOp<float, CPUContext>::RunOnDevice(){
7  const auto& X = Input(0);
8  const auto& a = Input(1);
9  const auto& b = Input(2);
10 
11  const auto canonical_axis = X.canonical_axis_index(axis_);
12  const int N = X.size_to_dim(canonical_axis);
13  const int D = X.size_from_dim(canonical_axis);
14 
15  CAFFE_ENFORCE_EQ(a.dim(), 1, a.dim());
16  CAFFE_ENFORCE_EQ(a.size(0), D, a.dim());
17  CAFFE_ENFORCE_EQ(b.dim(), 1, b.dim());
18  CAFFE_ENFORCE_EQ(b.size(0), D, b.dim());
19 
20  auto* Y = Output(0, X.sizes(), at::dtype<float>());
21 
22  const float* X_data = X.data<float>();
23  const float* a_data = a.data<float>();
24  const float* b_data = b.data<float>();
25  float* Y_data = Y->template mutable_data<float>();
26 
27  int p = 0;
28  for (int n = 0; n < N; ++n) {
29  for (int d = 0; d < D; ++d) {
30  Y_data[p] = X_data[p] * a_data[d] + b_data[d];
31  p++;
32  }
33  }
34  return true;
35 }
36 
37 template<>
38 bool ElementwiseLinearGradientOp<float, CPUContext>::RunOnDevice(){
39  const auto& g_o = Input(0);
40  const auto& X = Input(1);
41  const auto& a = Input(2);
42 
43  const auto canonical_axis = X.canonical_axis_index(axis_);
44  const int N = X.size_to_dim(canonical_axis);
45  const int D = X.size_from_dim(canonical_axis);
46 
47  CAFFE_ENFORCE_EQ(a.dim(), 1, a.dim());
48  CAFFE_ENFORCE_EQ(a.size(0), D, a.dim());
49 
50  auto* g_X = Output(0, X.sizes(), at::dtype<float>());
51  auto* g_a = Output(1, a.sizes(), at::dtype<float>());
52  auto* g_b = Output(2, a.sizes(), at::dtype<float>());
53 
54  const float* g_o_data = g_o.data<float>();
55  const float* X_data = X.data<float>();
56  const float* a_data = a.data<float>();
57  float* g_X_data = g_X->template mutable_data<float>();
58  float* g_a_data = g_a->template mutable_data<float>();
59  float* g_b_data = g_b->template mutable_data<float>();
60 
61  math::Set<float, CPUContext>(g_a->numel(), 0.f, g_a_data, &context_);
62  math::Set<float, CPUContext>(g_b->numel(), 0.f, g_b_data, &context_);
63 
64  int p = 0;
65  for (int n = 0; n < N; ++n) {
66  for (int d = 0; d < D; ++d) {
67  g_X_data[p] = g_o_data[p] * a_data[d];
68  g_a_data[d] += g_o_data[p] * X_data[p];
69  g_b_data[d] += g_o_data[p];
70  p++;
71  }
72  }
73  return true;
74 }
75 
76 REGISTER_CPU_OPERATOR(
77  ElementwiseLinear,
78  ElementwiseLinearOp<float, CPUContext>);
79 REGISTER_CPU_OPERATOR(
80  ElementwiseLinearGradient,
81  ElementwiseLinearGradientOp<float, CPUContext>);
82 
83 OPERATOR_SCHEMA(ElementwiseLinear)
84  .NumInputs(3)
85  .NumOutputs(1)
86  .SetDoc(R"DOC(
87 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
88 
89 $$Y_{ij} = X_{ij}w_j + b_j \ for \ i\in{N}, j\in{D}$$
90 
91 Github Links:
92 - https://github.com/pytorch/pytorch/blob/master/caffe2/operators/elementwise_linear_op.h
93 - https://github.com/pytorch/pytorch/blob/master/caffe2/operators/elementwise_linear_op.cc
94 
95 <details>
96 
97 <summary> <b>Example</b> </summary>
98 
99 **Code**
100 
101 ```
102 
103 workspace.ResetWorkspace()
104 
105 op = core.CreateOperator(
106  "ElementwiseLinear",
107  ["X", "w", "b"],
108  ["Y"]
109 )
110 
111 // Create X
112 X = np.array([[1,2,3,4,5],[6,8,9,16,10]])
113 print("X:\n",X)
114 
115 // Create w
116 w = np.array([1,1/2.,1/3.,1/4.,1/5.])
117 print("w:\n",w)
118 
119 // Create b
120 b = np.array([1.,1.,1.,1.,1.])
121 print("b:\n",b)
122 
123 
124 // Feed X & w & b into workspace
125 workspace.FeedBlob("X", X.astype(np.float32))
126 workspace.FeedBlob("w", w.astype(np.float32))
127 workspace.FeedBlob("b", b.astype(np.float32))
128 
129 // Run op
130 workspace.RunOperatorOnce(op)
131 
132 // Collect Output
133 print("Y:\n", workspace.FetchBlob("Y"))
134 
135 ```
136 
137 **Result**
138 
139 ```
140 
141 X:
142  [[ 1 2 3 4 5]
143  [ 6 8 9 16 10]]
144 w:
145  [1. 0.5 0.33333333 0.25 0.2]
146 b:
147  [1. 1. 1. 1. 1.]
148 Y:
149  [[2. 2. 2. 2. 2.]
150  [7. 5. 4. 5. 3.]]
151 
152 ```
153 
154 </details>
155 
156  )DOC")
157  .Input(
158  0,
159  "X",
160  "2D input tensor of size $NxD$. This input represents the input data to be operated on.")
161  .Input(
162  1,
163  "w",
164  "1D scaling factors, or weights, of size $D$. This input contains the weights that will be multiplied by the data.")
165  .Input(
166  2,
167  "b",
168  "1D biases of size $D$. This input contains the biases that will be added to the products of the weights and data.")
169  .Output(
170  0,
171  "Y",
172  "2D output tensor of size $NxD$. Calculated as described above.")
173  .Arg(
174  "axis",
175  "*(type: int; default: 1)* Describes the axis of the inputs; defaults to one because the 0th axis most likely describes the batch size.")
176  .InheritOnnxSchema();
177 
178 OPERATOR_SCHEMA(ElementwiseLinearGradient)
179  .NumInputs(3)
180  .NumOutputs(3);
181 
183  using GradientMakerBase::GradientMakerBase;
184  vector<OperatorDef> GetGradientDefs() override {
185  return SingleGradientDef(
186  "ElementwiseLinearGradient",
187  "",
188  vector<string>{GO(0), I(0), I(1)},
189  vector<string>{GI(0), GI(1), GI(2)});
190  }
191 };
192 
193 REGISTER_GRADIENT(
194  ElementwiseLinear,
196 );
197 
198 } // namespace caffe2
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...
Definition: blob.h:13
static vector< OperatorDef > SingleGradientDef(const Args &...args)
a helper function to allow one to create one single operator def, which is usually the case for many ...
Definition: static.cpp:70