blob: b515157c266d1e9fbd22fe8067cc2246cbd615ea [file] [log] [blame]
#include "caffe2/operators/apmeter_op.h"
namespace caffe2 {
template <>
void APMeterOp<float, CPUContext>::BufferPredictions(
const float* XData,
const int* labelData,
int N,
int D) {
if (buffers_.empty()) {
// Initialize the buffer
buffers_.resize(D, std::vector<BufferDataType>(buffer_size_));
}
TORCH_DCHECK_EQ(buffers_.size(), D);
// Fill atmose buffer_size_ data at a time, so truncate the input if needed
if (N > buffer_size_) {
XData = XData + (N - buffer_size_) * D;
labelData = labelData + (N - buffer_size_) * D;
N = buffer_size_;
}
// Reclaim space if not enough space in the buffer to hold new data
int space_to_reclaim = buffer_used_ + N - buffer_size_;
if (space_to_reclaim > 0) {
for (auto& buffer : buffers_) {
std::rotate(
buffer.begin(), buffer.begin() + space_to_reclaim, buffer.end());
}
buffer_used_ -= space_to_reclaim;
}
// Fill the buffer
for (int i = 0; i < D; i++) {
for (int j = 0; j < N; j++) {
buffers_[i][buffer_used_ + j].first = XData[j * D + i];
buffers_[i][buffer_used_ + j].second = labelData[j * D + i];
}
}
buffer_used_ += N;
}
template <>
bool APMeterOp<float, CPUContext>::RunOnDevice() {
auto& X = Input(PREDICTION);
auto& label = Input(LABEL);
// Check dimensions
TORCH_DCHECK_EQ(X.dim(), 2);
int N = X.dim32(0);
int D = X.dim32(1);
TORCH_DCHECK_EQ(label.dim(), 2);
TORCH_DCHECK_EQ(label.dim32(0), N);
TORCH_DCHECK_EQ(label.dim32(1), D);
auto* Y = Output(0, {D}, at::dtype<float>());
const auto* Xdata = X.data<float>();
const auto* labelData = label.data<int>();
auto* Ydata = Y->template mutable_data<float>();
BufferPredictions(Xdata, labelData, N, D);
// Calculate AP for each class
for (int i = 0; i < D; i++) {
auto& buffer = buffers_[i];
// Sort predictions by score
std::stable_sort(
buffer.begin(),
buffer.begin() + buffer_used_,
[](const BufferDataType& p1, const BufferDataType& p2) {
return p1.first > p2.first;
});
// Calculate cumulative precision for each sample
float tp_sum = 0.0;
float precision_sum = 0.0;
int ntruth = 0;
for (int j = 0; j < buffer_used_; j++) {
tp_sum += buffer[j].second;
if (buffer[j].second == 1) {
ntruth += 1;
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions,bugprone-narrowing-conversions)
precision_sum += tp_sum / (j + 1);
}
}
// Calculate AP
Ydata[i] = precision_sum / std::max(1, ntruth);
}
return true;
}
namespace {
REGISTER_CPU_OPERATOR(APMeter, APMeterOp<float, CPUContext>);
OPERATOR_SCHEMA(APMeter)
.NumInputs(2)
.NumOutputs(1)
.ScalarType(TensorProto::FLOAT)
.SetDoc(R"DOC(
APMeter computes Average Precision for binary or multi-class classification.
It takes two inputs: prediction scores P of size (n_samples x n_classes), and
true labels Y of size (n_samples x n_classes). It returns a single float number
per class for the average precision of that class.
)DOC")
.Arg(
"buffer_size",
"(int32_t) indicates how many predictions should the op buffer. "
"defaults to 1000")
.Input(
0,
"predictions",
"2-D tensor (Tensor<float>) of size (num_samples x"
"num_classes) containing prediction scores")
.Input(
1,
"labels",
"2-D tensor (Tensor<float>) of size (num_samples) "
"containing true labels for each sample")
.Output(
0,
"AP",
"1-D tensor (Tensor<float>) of size num_classes containing "
"average precision for each class");
SHOULD_NOT_DO_GRADIENT(APMeter);
} // namespace
} // namespace caffe2