pytorch/caffe2/operators/apmeter_op.cc
Will Constable 4f34cd6d1e Replace all CHECK_ and DCHECK_ with TORCH_* macros (#82032)
Avoid exposing defines that conflict with google logging, since this blocks external usage of libtorch in certain cases.

All the 'interesting' changes should be in these two files, and the rest should just be mechanical changes via sed.
c10/util/logging_is_not_google_glog.h
c10/util/logging_is_google_glog.h

Fixes https://github.com/pytorch/pytorch/issues/81415

cc @miladm @malfet
Pull Request resolved: https://github.com/pytorch/pytorch/pull/82032
Approved by: https://github.com/soumith, https://github.com/miladm
2022-07-26 01:20:44 +00:00

132 lines
3.7 KiB
C++

#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