Caffe2 - C++ API
A deep learning, cross platform ML framework
filler_op.h
1 
17 #ifndef CAFFE2_OPERATORS_FILLER_OP_H_
18 #define CAFFE2_OPERATORS_FILLER_OP_H_
19 
20 #include "caffe2/core/context.h"
21 #include "caffe2/core/logging.h"
22 #include "caffe2/core/operator.h"
23 #include "caffe2/utils/math.h"
24 
25 namespace caffe2 {
26 
27 // FillerOp takes in either zero or one input.
28 //
29 // If the number of input is 1, the shape will be identical to that of the input
30 // at run time with optional additional dimensions appended at the end as
31 // specified by "extra_shape" argument. In that case the "shape" parameter
32 // should not be set.
33 //
34 // If the number of inputs is 0, the full shape must be provided via "shape"
35 // argument
36 template <class Context>
37 class FillerOp : public Operator<Context> {
38  public:
39  FillerOp(const OperatorDef& operator_def, Workspace* ws)
40  : Operator<Context>(operator_def, ws),
41  shape_(ToVectorTIndex(OperatorBase::GetRepeatedArgument<int>("shape"))),
42  extra_shape_(ToVectorTIndex(
43  OperatorBase::GetRepeatedArgument<int>("extra_shape"))),
44  input_as_shape_(
45  OperatorBase::GetSingleArgument<bool>("input_as_shape", false)) {
46  if (InputSize()) {
47  if (shape_.size() != 0) {
48  CAFFE_THROW(
49  "Cannot set the shape argument and pass in an input at "
50  "the same time");
51  }
52  } else {
53  if (!extra_shape_.empty()) {
54  CAFFE_THROW("Cannot set extra_shape when there is no input");
55  }
56  if (input_as_shape_) {
57  CAFFE_THROW("An input must be given if input_as_shape is true");
58  }
59  if (shape_.size() == 0 &&
60  OperatorBase::HasSingleArgumentOfType<int>("shape")) {
61  CAFFE_THROW("Fill 'shape' argument was a scalar, list expected");
62  }
63  }
64  }
65 
66  virtual ~FillerOp() {}
67  USE_OPERATOR_CONTEXT_FUNCTIONS;
68 
69  bool RunOnDevice() override {
70  auto* output = Operator<Context>::Output(0);
71  if (InputSize()) {
72  auto shape = vector<TIndex>{};
73  if (input_as_shape_) {
74  // Shape input must be in CPU context
75  auto& input = OperatorBase::Input<Tensor<CPUContext>>(0);
76  CAFFE_ENFORCE_EQ(
77  input.ndim(),
78  1,
79  "When input_as_shape is true, the input must be a 1D tensor of "
80  "data type TIndex");
81  auto* shape_data = input.template data<TIndex>();
82  shape.insert(shape.end(), shape_data, shape_data + input.dim32(0));
83  } else {
84  auto& input = Input(0);
85  shape.insert(shape.end(), input.dims().begin(), input.dims().end());
86  }
87  shape.insert(shape.end(), extra_shape_.begin(), extra_shape_.end());
88  output->Resize(shape);
89  } else {
90  output->Resize(shape_);
91  }
92  return Fill(output);
93  }
94 
95  virtual bool Fill(Tensor<Context>* output) = 0;
96 
97  protected:
98  vector<TIndex> shape_;
99  vector<TIndex> extra_shape_;
100  bool input_as_shape_;
101 };
102 
103 template <typename T, class Context>
104 class UniformFillOp final : public FillerOp<Context> {
105  public:
106  USE_OPERATOR_CONTEXT_FUNCTIONS;
107  UniformFillOp(const OperatorDef& operator_def, Workspace* ws)
108  : FillerOp<Context>(operator_def, ws),
109  min_(OperatorBase::template GetSingleArgument<T>("min", 0)),
110  max_(OperatorBase::template GetSingleArgument<T>("max", 1)) {
111  if (InputSize() == 3) {
112  CAFFE_ENFORCE(
113  !OperatorBase::HasSingleArgumentOfType<T>("min"),
114  "Cannot set both min arg and min input blob");
115  CAFFE_ENFORCE(
116  !OperatorBase::HasSingleArgumentOfType<T>("max"),
117  "Cannot set both max arg and max input blob");
118  } else {
119  CAFFE_ENFORCE_LT(
120  min_, max_, "Max value should be bigger than min value.");
121  }
122  }
123 
124  bool Fill(Tensor<Context>* output) override {
125  T min = min_;
126  T max = max_;
127  if (InputSize() == 3) {
128  CAFFE_ENFORCE_EQ(1, Input(1).size(), "min blob must be scalar");
129  CAFFE_ENFORCE_EQ(1, Input(2).size(), "max blob must be scalar");
130  min = *Input(1).template data<T>();
131  max = *Input(2).template data<T>();
132  if (min > max) {
133  auto shape = output->dims();
134  shape[0] = 0;
135  output->Resize(shape);
136  output->template mutable_data<T>();
137  return true;
138  }
139  }
140  math::RandUniform<T, Context>(
141  output->size(),
142  min,
143  max,
144  output->template mutable_data<T>(),
145  &context_);
146  return true;
147  }
148 
149  private:
150  T min_;
151  T max_;
152 };
153 
154 template <class Context>
155 class UniqueUniformFillOp final : public FillerOp<Context> {
156  public:
157  USE_OPERATOR_CONTEXT_FUNCTIONS;
158  UniqueUniformFillOp(const OperatorDef& operator_def, Workspace* ws)
159  : FillerOp<Context>(operator_def, ws) {
160  TensorProto_DataType dtype =
161  static_cast<TensorProto_DataType>(OperatorBase::GetSingleArgument<int>(
162  "dtype", TensorProto_DataType_INT32));
163 
164  switch (dtype) {
165  case TensorProto_DataType_INT32:
166  CheckRange<int>();
167  body_ = &UniqueUniformFillOp::FillWithType<int>;
168  break;
169  case TensorProto_DataType_INT64:
170  CheckRange<int64_t>();
171  body_ = &UniqueUniformFillOp::FillWithType<int64_t>;
172  break;
173  case TensorProto_DataType_UNDEFINED:
174  CAFFE_THROW(
175  "UniqueUniformFill op cannot have undefined 'dtype' argument");
176  // break;
177  default:
178  CAFFE_THROW("Unexpected 'dtype' argument value: ", dtype);
179  }
180  }
181 
182  bool Fill(Tensor<Context>* output) override {
183  return (this->*body_)(output);
184  }
185 
186  private:
187  template <typename T>
188  void CheckRange() {
189  CAFFE_ENFORCE(OperatorBase::HasSingleArgumentOfType<T>("min"));
190  CAFFE_ENFORCE(OperatorBase::HasSingleArgumentOfType<T>("max"));
191  CAFFE_ENFORCE_LT(
192  OperatorBase::GetSingleArgument<T>("min", 0),
193  OperatorBase::GetSingleArgument<T>("max", 0),
194  "Max value should be bigger than min value.");
195  }
196 
197  template <typename T>
198  bool FillWithType(Tensor<Context>* output) {
199  T min = OperatorBase::GetSingleArgument<T>("min", 0);
200  T max = OperatorBase::GetSingleArgument<T>("max", 0);
201 
202  const T* avoid_data = nullptr;
203  size_t avoid_size = 0;
204  if (InputSize() >= 2) {
205  auto& avoid = Input(1);
206  avoid_data = avoid.template data<T>();
207  avoid_size = avoid.size();
208  }
209  math::RandUniformUnique<T, Context>(
210  output->size(),
211  min,
212  max,
213  output->template mutable_data<T>(),
214  avoid_size,
215  avoid_data,
216  &context_);
217  return true;
218  }
219 
220  bool (UniqueUniformFillOp::*body_)(Tensor<Context>* output);
221 };
222 
223 template <class Context>
224 class ConstantFillOp final : public FillerOp<Context> {
225  public:
226  USE_OPERATOR_CONTEXT_FUNCTIONS;
227  ConstantFillOp(const OperatorDef& operator_def, Workspace* ws)
228  : FillerOp<Context>(operator_def, ws) {
229  TensorProto_DataType dtype =
230  static_cast<TensorProto_DataType>(OperatorBase::GetSingleArgument<int>(
231  "dtype", TensorProto_DataType_FLOAT));
232 
233  if (!OperatorBase::HasArgument("dtype") &&
234  OperatorBase::HasArgument("value")) {
235  // If 'dtype' is not provided, infer type based on the type of 'value'
236  // Currently, single argument contains either float, int64 or bytes
237  if (OperatorBase::HasSingleArgumentOfType<float>("value")) {
238  dtype = TensorProto_DataType_FLOAT;
239  } else if (OperatorBase::HasSingleArgumentOfType<int64_t>("value")) {
240  dtype = TensorProto_DataType_INT64;
241  } else {
242  CAFFE_THROW("Argument 'value' is of unexpected type");
243  }
244  VLOG(1) << "Argument 'dtype' is not provided. Assume the data type is "
245  << "the same as that of argument 'value': " << dtype;
246  }
247 
248  switch (dtype) {
249  case TensorProto_DataType_FLOAT:
250  body_ = &ConstantFillOp::FillWithType<float>;
251  break;
252  case TensorProto_DataType_DOUBLE:
253  body_ = &ConstantFillOp::FillWithType<double>;
254  break;
255  case TensorProto_DataType_BOOL:
256  body_ = &ConstantFillOp::FillWithType<bool>;
257  break;
258  case TensorProto_DataType_INT8:
259  body_ = &ConstantFillOp::FillWithType<int8_t>;
260  break;
261  case TensorProto_DataType_INT16:
262  body_ = &ConstantFillOp::FillWithType<int16_t>;
263  break;
264  case TensorProto_DataType_INT32:
265  body_ = &ConstantFillOp::FillWithType<int>;
266  break;
267  case TensorProto_DataType_INT64:
268  body_ = &ConstantFillOp::FillWithType<int64_t>;
269  break;
270  case TensorProto_DataType_UINT8:
271  body_ = &ConstantFillOp::FillWithType<uint8_t>;
272  break;
273  case TensorProto_DataType_UINT16:
274  body_ = &ConstantFillOp::FillWithType<uint16_t>;
275  break;
276  case TensorProto_DataType_STRING:
277  body_ = &ConstantFillOp::FillWithString;
278  break;
279  case TensorProto_DataType_UNDEFINED:
280  CAFFE_THROW("ConstantFill op cannot have undefined 'dtype' argument");
281  // break;
282  default:
283  CAFFE_THROW("Unexpected 'dtype' argument value: ", dtype);
284  }
285  }
286 
287  bool Fill(Tensor<Context>* output) override {
288  return (this->*body_)(output);
289  }
290 
291  template <typename T>
292  bool FillWithType(Tensor<Context>* output) {
293  T value = OperatorBase::GetSingleArgument<T>("value", 0);
294  auto* data = output->template mutable_data<T>();
295  if (output->size()) {
296  math::Set<T, Context>(output->size(), value, data, &context_);
297  }
298  return true;
299  }
300 
301  bool FillWithString(Tensor<Context>* output) {
302  auto value = OperatorBase::GetSingleArgument<std::string>("value", "");
303  auto* data = output->template mutable_data<std::string>();
304  for (int i = 0; i < output->size(); ++i) {
305  data[i] = value;
306  }
307  return true;
308  }
309 
310  private:
311  bool (ConstantFillOp::*body_)(Tensor<Context>* output);
312 };
313 
314 template <class Context>
315 class DiagonalFillOp final : public FillerOp<Context> {
316  public:
317  USE_OPERATOR_CONTEXT_FUNCTIONS;
318  DiagonalFillOp(const OperatorDef& operator_def, Workspace* ws)
319  : FillerOp<Context>(operator_def, ws) {
320  TensorProto_DataType dtype =
321  static_cast<TensorProto_DataType>(OperatorBase::GetSingleArgument<int>(
322  "dtype", TensorProto_DataType_FLOAT));
323 
324  if (!OperatorBase::HasArgument("dtype") &&
325  OperatorBase::HasArgument("value")) {
326  // If 'dtype' is not provided, infer type based on the type of 'value'
327  // Currently, single argument contains either float, int64 or bytes
328  if (OperatorBase::HasSingleArgumentOfType<float>("value")) {
329  dtype = TensorProto_DataType_FLOAT;
330  } else if (OperatorBase::HasSingleArgumentOfType<int64_t>("value")) {
331  dtype = TensorProto_DataType_INT64;
332  } else {
333  CAFFE_THROW("Argument 'value' is of unexpected type");
334  }
335  VLOG(1) << "Argument 'dtype' is not provided. Assume the data type is "
336  << "the same as that of argument 'value': " << dtype;
337  }
338 
339  switch (dtype) {
340  case TensorProto_DataType_FLOAT:
341  body_ = &DiagonalFillOp::FillWithType<float>;
342  break;
343  case TensorProto_DataType_DOUBLE:
344  body_ = &DiagonalFillOp::FillWithType<double>;
345  break;
346  case TensorProto_DataType_BOOL:
347  body_ = &DiagonalFillOp::FillWithType<bool>;
348  break;
349  case TensorProto_DataType_INT8:
350  body_ = &DiagonalFillOp::FillWithType<int8_t>;
351  break;
352  case TensorProto_DataType_INT16:
353  body_ = &DiagonalFillOp::FillWithType<int16_t>;
354  break;
355  case TensorProto_DataType_INT32:
356  body_ = &DiagonalFillOp::FillWithType<int>;
357  break;
358  case TensorProto_DataType_INT64:
359  body_ = &DiagonalFillOp::FillWithType<int64_t>;
360  break;
361  case TensorProto_DataType_UINT8:
362  body_ = &DiagonalFillOp::FillWithType<uint8_t>;
363  break;
364  case TensorProto_DataType_UINT16:
365  body_ = &DiagonalFillOp::FillWithType<uint16_t>;
366  break;
367  case TensorProto_DataType_UNDEFINED:
368  CAFFE_THROW("Cannot have undefined 'dtype' argument");
369  default:
370  CAFFE_THROW("Unexpected 'dtype' argument value: ", dtype);
371  }
372  }
373 
374  bool Fill(Tensor<Context>* output) override {
375  return (this->*body_)(output);
376  }
377 
378  template <typename T>
379  bool FillWithType(Tensor<Context>* output);
380 
381  private:
382  void VerifyOutputShape(Tensor<Context>* output) {
383  CAFFE_ENFORCE(output->ndim() >= 2, "Input shape must be >= 2D");
384  }
385 
386  TIndex GetStepSize(Tensor<Context>* output) {
387  TIndex step;
388  if (output->ndim() == 2) {
389  step = output->dim(1) + 1;
390  } else {
391  TIndex prev_i = output->dim(0);
392  for (auto i : output->dims()) {
393  if (i != prev_i) {
394  CAFFE_THROW("All dimensions of input must be of equal length");
395  }
396  }
397  vector<TIndex> cumprod(output->ndim());
398  auto dims = output->dims();
399  std::partial_sum(
400  dims.begin(),
401  dims.end() - 1,
402  cumprod.begin(),
403  std::multiplies<TIndex>());
404  step = 1 +
405  std::accumulate(
406  cumprod.begin(), cumprod.end(), static_cast<TIndex>(0));
407  VLOG(0) << step;
408  }
409  return step;
410  }
411 
412  bool (DiagonalFillOp::*body_)(Tensor<Context>* output);
413 };
414 
415 template <typename T, class Context>
416 class GaussianFillOp final : public FillerOp<Context> {
417  public:
418  USE_OPERATOR_CONTEXT_FUNCTIONS;
419  GaussianFillOp(const OperatorDef& operator_def, Workspace* ws)
420  : FillerOp<Context>(operator_def, ws),
421  mean_(OperatorBase::template GetSingleArgument<float>("mean", 0)),
422  std_(OperatorBase::template GetSingleArgument<float>("std", 1)) {
423  DCHECK_GT(std_, 0) << "Standard deviation should be nonnegative.";
424  }
425 
426  bool Fill(Tensor<Context>* output) override {
427  math::RandGaussian<T, Context>(
428  output->size(),
429  mean_,
430  std_,
431  output->template mutable_data<T>(),
432  &context_);
433  return true;
434  }
435 
436  private:
437  T mean_;
438  T std_;
439 };
440 
441 template <typename T, class Context>
442 class XavierFillOp final : public FillerOp<Context> {
443  public:
444  USE_OPERATOR_CONTEXT_FUNCTIONS;
445  XavierFillOp(const OperatorDef& operator_def, Workspace* ws)
446  : FillerOp<Context>(operator_def, ws) {}
447 
448  bool Fill(Tensor<Context>* output) override {
449  const int fan_in = output->size() / output->dim32(0);
450  T scale = std::sqrt(T(3) / fan_in);
451  math::RandUniform<T, Context>(
452  output->size(),
453  -scale,
454  scale,
455  output->template mutable_data<T>(),
456  &context_);
457  return true;
458  }
459 };
460 
461 template <typename T, class Context>
462 class MSRAFillOp final : public FillerOp<Context> {
463  public:
464  USE_OPERATOR_CONTEXT_FUNCTIONS;
465  MSRAFillOp(const OperatorDef& operator_def, Workspace* ws)
466  : FillerOp<Context>(operator_def, ws) {}
467 
468  bool Fill(Tensor<Context>* output) override {
469  const int fan_out = output->size() / output->dim32(1);
470  T scale = std::sqrt(T(2) / fan_out);
471  math::RandGaussian<T, Context>(
472  output->size(),
473  0.0,
474  scale,
475  output->template mutable_data<T>(),
476  &context_);
477  return true;
478  }
479 };
480 
481 // This is mostly used just as a debugging purpose stuff: it fills a tensor
482 // sequentially with values 0, 1, 2..., which can then be used to check e.g.
483 // reshape operations by allowing one to read the indices more easily.
484 template <typename T, class Context>
485 class RangeFillOp final : public FillerOp<Context> {
486  public:
487  USE_OPERATOR_CONTEXT_FUNCTIONS;
488  RangeFillOp(const OperatorDef& operator_def, Workspace* ws)
489  : FillerOp<Context>(operator_def, ws) {}
490 
491  bool Fill(Tensor<Context>* output) override;
492 };
493 
494 template <class Context>
495 class LengthsRangeFillOp : public Operator<Context> {
496  public:
497  USE_OPERATOR_CONTEXT_FUNCTIONS;
498  USE_SIMPLE_CTOR_DTOR(LengthsRangeFillOp);
499 
500  bool RunOnDevice() override {
501  auto& input = Input(0);
502  auto* output = Output(0);
503  auto* input_data = input.template data<int32_t>();
504 
505  CAFFE_ENFORCE_EQ(input.ndim(), 1, "Input must be a vector.");
506 
507  auto len_sum = std::accumulate(input_data, input_data + input.size(), 0);
508 
509  output->Resize(len_sum);
510  auto* output_data = output->template mutable_data<int32_t>();
511 
512  int32_t offset = 0;
513  for (int i = 0; i < input.size(); ++i) {
514  auto len = input_data[i];
515  auto start = output_data + offset;
516  std::iota(
517  start,
518  start + len,
519  0); // make the third argument the arg of this operator
520  offset += len;
521  }
522  return true;
523  }
524 };
525 
526 template <int VALUE_TYPE = TensorProto_DataType_FLOAT>
527 inline std::vector<TensorShape> FillerTensorInference(
528  const OperatorDef& def,
529  const vector<TensorShape>& in) {
530  vector<TensorShape> out(1);
531  ArgumentHelper helper(def);
532  out[0].set_data_type(static_cast<TensorProto_DataType>(
533  helper.GetSingleArgument<int>("dtype", VALUE_TYPE)));
534 
535  if (in.size()) {
536  // TODO
537  bool input_as_shape =
538  helper.GetSingleArgument<bool>("input_as_shape", false);
539  if (input_as_shape) {
540  out[0].set_unknown_shape(true);
541  return out;
542  }
543  for (int d : in[0].dims()) {
544  out[0].add_dims(d);
545  }
546  } else {
547  auto shape = helper.GetRepeatedArgument<int>("shape");
548  for (int d : shape) {
549  out[0].add_dims(d);
550  }
551  }
552  return out;
553 }
554 
555 } // namespace caffe2
556 
557 #endif // CAFFE2_OPERATORS_FILLER_OP_H_
TIndex dim(const int i) const
Returns the i-th dimension of the tensor.
Definition: tensor.h:687
Tensor is the basic class in Caffe2 that stores a contiguous memory with its shape information...
Definition: tensor.h:109
int dim32(const int i) const
Returns the i-th dimension of the tensor in int.
Definition: tensor.h:673
TIndex size() const
Returns the size (i.e.
Definition: tensor.h:609
A helper class to index into arguments.
Definition: proto_utils.h:198
const vector< TIndex > & dims() const
Returns the dimensions of the tensor as a vector.
Definition: tensor.h:627
Workspace is a class that holds all the related objects created during runtime: (1) all blobs...
Definition: workspace.h:63
void Resize(Ts...dim_source)
Resizes a tensor.
Definition: tensor.h:304
Copyright (c) 2016-present, Facebook, Inc.
bool HasArgument(const string &name) const
Checks if the operator has an argument of the given name.
Definition: operator.h:52
vector< TIndex > ToVectorTIndex(const std::vector< int > &src)
A utility function to convert vector<int> to vector<TIndex>.
Definition: tensor.h:49
int ndim() const
Returns the number of dimensions of the data.
Definition: tensor.h:605