Caffe2 - C++ API
A deep learning, cross platform ML framework
rank_loss_op.cc
1 #include "caffe2/operators/rank_loss_op.h"
2 
3 namespace caffe2 {
4 
5 namespace {
6 
7 // Computes log(1 + exp(y)) in a way that avoids early over-/under-flow
8 template <class T>
9 inline T logLogit(T x) {
10  static const auto kMinLogDiff = std::log(std::numeric_limits<T>::epsilon());
11 
12  if (x < kMinLogDiff) {
13  return 0;
14  }
15  if (x > -kMinLogDiff) {
16  return x;
17  }
18  return std::log(std::exp(x) + 1);
19 }
20 }
21 
22 template <typename T, class Context>
23 bool PairWiseLossOp<T, Context>::RunOnDevice() {
24  auto& X = Input(XVALUE);
25  auto& label = Input(LABEL);
26  auto* Y = Output(YVALUE);
27 
28  int N = X.ndim() > 0 ? X.dim32(0) : 0;
29  if (N == 0) {
30  Y->Resize(0);
31  Y->template mutable_data<T>();
32  return true;
33  }
34 
35  const int32_t* lengths_vec;
36  int len_size = 1;
37  if (InputSize() > LENGTHS) {
38  auto& lengths = Input(LENGTHS);
39  CAFFE_ENFORCE_EQ(lengths.ndim(), 1);
40  len_size = lengths.size();
41  lengths_vec = lengths.template data<int32_t>();
42  int len_sum = 0;
43  if (len_size > 0) {
44  math::Sum<int, Context>(len_size, lengths_vec, &len_sum, &context_);
45  }
46  CAFFE_ENFORCE_EQ(len_sum, N);
47  } else {
48  lengths_vec = &N;
49  }
50 
51  // a total of len_size sessions
52  Y->Resize(len_size);
53  auto* Ydata = Y->template mutable_data<T>();
54 
55  int D = X.size() / N;
56  CAFFE_ENFORCE(
57  (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == 1));
58  CAFFE_ENFORCE_EQ(label.dim32(0), N);
59  CAFFE_ENFORCE_EQ(1, D); // only support one class at the moment
60 
61  const auto* Xdata = X.template data<T>();
62  const auto* labelData = label.template data<T>();
63  int offset = 0;
64  for (int idx = 0; idx < len_size; ++idx) {
65  Ydata[idx] = 0;
66  int numPairs = 0;
67  for (int i = offset; i < offset + lengths_vec[idx]; ++i) {
68  for (int j = offset; j < i; ++j) {
69  if (std::abs(labelData[i] - labelData[j]) <
70  std::numeric_limits<T>::epsilon()) {
71  continue;
72  }
73  ++numPairs;
74  // only use sigmoid loss function at the moment
75  auto sign = labelData[i] > labelData[j] ? 1 : -1;
76  Ydata[idx] += logLogit(sign * (Xdata[j] - Xdata[i]));
77  }
78  }
79  if (numPairs > 0) {
80  Ydata[idx] /= numPairs;
81  }
82  offset += lengths_vec[idx];
83  }
84  return true;
85 }
86 
87 template <class T, class Context>
88 bool PairWiseLossGradientOp<T, Context>::RunOnDevice() {
89  auto& X = Input(XVALUE);
90  auto& label = Input(LABEL);
91  auto& dY = Input(DYVALUE);
92  auto* dX = Output(DXVALUE);
93  int N = X.ndim() > 0 ? X.dim32(0) : 0;
94  CAFFE_ENFORCE_EQ(N, X.size());
95  CAFFE_ENFORCE(
96  (label.ndim() == 1) || (label.ndim() == 2 && label.dim32(1) == 1));
97  CAFFE_ENFORCE_EQ(label.dim32(0), N);
98  dX->ResizeLike(X);
99  math::Set<T, CPUContext>(
100  dX->size(), 0.f, dX->template mutable_data<T>(), &context_);
101 
102  if (N == 0) {
103  return true;
104  }
105 
106  const int32_t* lengths_vec;
107  int len_size = 1;
108  if (InputSize() > LENGTHS) {
109  auto& lengths = Input(LENGTHS);
110  CAFFE_ENFORCE_EQ(lengths.ndim(), 1);
111  len_size = lengths.size();
112  lengths_vec = lengths.template data<int32_t>();
113  int len_sum = 0;
114  if (len_size > 0) {
115  math::Sum<int, Context>(len_size, lengths_vec, &len_sum, &context_);
116  }
117  CAFFE_ENFORCE_EQ(len_sum, N);
118  } else {
119  lengths_vec = &N;
120  }
121 
122  CAFFE_ENFORCE_EQ(dY.ndim(), 1);
123  CAFFE_ENFORCE_EQ(dY.dim32(0), len_size);
124 
125  const T* Xdata = X.template data<T>();
126  const T* dYdata = dY.template data<T>();
127  const T* labelData = label.template data<T>();
128  T* dXdata = dX->template mutable_data<T>();
129  int offset = 0;
130  for (int idx = 0; idx < len_size; ++idx) {
131  int numPairs = 0;
132  for (int i = offset; i < offset + lengths_vec[idx]; ++i) {
133  for (int j = offset; j < i; ++j) {
134  if (std::abs(labelData[i] - labelData[j]) <
135  std::numeric_limits<T>::epsilon()) {
136  continue;
137  }
138  ++numPairs;
139  // only use sigmoid loss function at the moment
140  auto sign = labelData[i] > labelData[j] ? 1 : -1;
141  auto grad =
142  sign * dYdata[idx] / (1 + exp(-sign * (Xdata[j] - Xdata[i])));
143  dXdata[i] -= grad;
144  dXdata[j] += grad;
145  }
146  }
147  if (numPairs > 0) {
148  for (int i = offset; i < offset + lengths_vec[idx]; ++i) {
149  dXdata[i] /= numPairs;
150  }
151  }
152  offset += lengths_vec[idx];
153  }
154  return true;
155 }
156 
157 namespace {
158 REGISTER_CPU_OPERATOR(PairWiseLoss, PairWiseLossOp<float, CPUContext>);
159 REGISTER_CPU_OPERATOR(
160  PairWiseLossGradient,
161  PairWiseLossGradientOp<float, CPUContext>);
162 
163 OPERATOR_SCHEMA(PairWiseLoss)
164  .NumInputs(2, 3)
165  .NumOutputs(1)
166  .SetDoc(R"DOC(
167 Operator computes the pair wise loss between all pairs within a batch
168  using the logit loss function on the difference in scores between pairs
169 )DOC")
170  .Input(
171  0,
172  "X",
173  "Input blob from the previous layer, which is almost always "
174  "the result of a softmax operation; X is a 2D array of size N x 1"
175  "where N is the batch size. For more info: "
176  "D. Sculley, Large Scale Learning to Rank. "
177  "https://www.eecs.tufts.edu/~dsculley/papers/large-scale-rank.pdf")
178  .Input(1, "label", "Blob containing the labels used to compare the input")
179  .Input(
180  2,
181  "lengths",
182  "Optional input blob that contains the lengths"
183  "of multiple sessions. The summation of this blob must be equal"
184  "to the size of blob X. If lengths blob is provided, the output"
185  "blob has the same size as lengths blob, and the cross entropy"
186  "is computed within each session.")
187  .Output(0, "Y", "Output blob after the cross entropy computation");
188 OPERATOR_SCHEMA(PairWiseLossGradient).NumInputs(3, 4).NumOutputs(1);
189 
190 class GetPairWiseLossGradient : public GradientMakerBase {
191  using GradientMakerBase::GradientMakerBase;
192  vector<OperatorDef> GetGradientDefs() override {
193  vector<string> blob_names{I(0), I(1), GO(0)};
194 
195  // Add lengths blob if given
196  if (def_.input_size() == 3) {
197  blob_names.push_back(I(2));
198  }
199  return SingleGradientDef(
200  "PairWiseLossGradient", "", blob_names, vector<string>{GI(0)});
201  }
202 };
203 REGISTER_GRADIENT(PairWiseLoss, GetPairWiseLossGradient);
204 
205 } // namespace
206 } // namespace caffe2
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...