评估所有检测为positive的结果中,有多少是真的positive。
P r e c i s i o n = T P T o t a l _ P r e d i c t e d _ P o s i t i v e = T P T P + F P Precision = \frac{TP}{Total\_Predicted\_Positive} = \frac{TP}{TP + FP} Precision=Total_Predicted_PositiveTP=TP+FPTP
评估所有真的positive中,有多少positive被准确检测出。
R e c a l l = T P T o t a l _ A c t u a l _ P o s i t i v e = T P T P + F N Recall = \frac{TP}{Total\_Actual\_Positive} = \frac{TP}{TP + FN} Recall=Total_Actual_PositiveTP=TP+FNTP
设置不同的score阈值,可以得到不同的Precison和Recall,然后以Precision为纵坐标,Recall为横坐标,即可绘制出P-R曲线。
设置一组阈值为[0, 0.1, 0.2, …, 0.9, 1], 对于recall大于阈值组中的某个阈值(比如0.1),可以得到一个对应的最大precision,这样就可以计算出11个precision,然后对这11个precision取平均值,得到AP值。
// VOC2007 style for computing AP.
vector max_precs(11, 0.);
int start_idx = num - 1; // num = tp.size()
for (int j = 10; j >= 0; --j) {
for (int i = start_idx; i >= 0 ; --i) {
if ((*rec)[i] < j / 10.) {
start_idx = i;
if (j > 0) {
max_precs[j-1] = max_precs[j];
}
break;
} else {
if (max_precs[j] < (*prec)[i]) {
max_precs[j] = (*prec)[i];
}
}
}
}
for (int j = 10; j >= 0; --j) {
*ap += max_precs[j] / 11;
}
根据积分的定义,计算P-R曲线下的面积。公式如下:
∑ k = 1 N P ( k ) ∗ Δ r ( k ) \sum_{k=1}^{N}P(k) * \Delta r(k) k=1∑NP(k)∗Δr(k)
// Natural integral.
float prev_rec = 0.;
for (int i = 0; i < num; ++i) {
if (fabs((*rec)[i] - prev_rec) > eps) {
*ap += (*prec)[i] * fabs((*rec)[i] - prev_rec);
}
prev_rec = (*rec)[i];
}
将Integral方法中的 P ( k ) P(k) P(k)改为 m a x k ~ ≥ k P ( k ~ ) max_{\tilde k \geq k}P(\tilde k) maxk~≥kP(k~):
∑ k = 1 N m a x k ~ ≥ k P ( k ~ ) ∗ Δ r ( k ) \sum_{k=1}^{N} max_{\tilde k \geq k}P(\tilde k) * \Delta r(k) k=1∑Nmaxk~≥kP(k~)∗Δr(k)
假设N个样本中有M个正样本,则得到M个recall和precision,对于每个recall值r,计算大于r时的最大precision,然后计算 ∑ k = 1 N m a x k ~ ≥ k P ( k ~ ) ∗ Δ r ( k ) \sum_{k=1}^{N} max_{\tilde k \geq k}P(\tilde k) * \Delta r(k) ∑k=1Nmaxk~≥kP(k~)∗Δr(k),得到AP值。
// VOC2012 or ILSVRC style for computing AP.
float cur_rec = rec->back();
float cur_prec = prec->back();
for (int i = num - 2; i >= 0; --i) {
cur_prec = std::max((*prec)[i], cur_prec);
if (fabs(cur_rec - (*rec)[i]) > eps) {
*ap += cur_prec * fabs(cur_rec - (*rec)[i]);
}
cur_rec = (*rec)[i];
}
*ap += cur_rec * cur_prec;
相关文章:https://sanchom.wordpress.com/tag/average-precision/(中文翻译:https://blog.csdn.net/hysteric314/article/details/54093734)
来自caffe.ssd/src/caffe/util/bbox_util.cpp
// Compute average precision given true positive and false positive vectors.
// tp: contains pairs of scores and true positive.
// num_pos: number of positives.
// fp: contains pairs of scores and false positive.
// ap_version: different ways of computing Average Precision.
// Check https://sanchom.wordpress.com/tag/average-precision/ for details.
// 11point: the 11-point interpolated average precision. Used in VOC2007.
// MaxIntegral: maximally interpolated AP. Used in VOC2012/ILSVRC.
// Integral: the natural integral of the precision-recall curve.
// prec: stores the computed precisions.
// rec: stores the computed recalls.
// ap: the computed Average Precision.
void ComputeAP(const vector >& tp, const int num_pos,
const vector >& fp, const string ap_version,
vector* prec, vector* rec, float* ap) {
const float eps = 1e-6;
// tp.size() = fp.size()
CHECK_EQ(tp.size(), fp.size()) << "tp must have same size as fp.";
const int num = tp.size();
// Make sure that tp and fp have complement value.
for (int i = 0; i < num; ++i) {
// |tp[i].first - fp[i].first| < eps
CHECK_LE(fabs(tp[i].first - fp[i].first), eps);
// tp[i].second = 1 - fp[i].second
CHECK_EQ(tp[i].second, 1 - fp[i].second);
}
prec->clear();
rec->clear();
*ap = 0;
if (tp.size() == 0 || num_pos == 0) {
return;
}
// Compute cumsum of tp.
vector tp_cumsum;
CumSum(tp, &tp_cumsum); // 按分数从高到底进行排序,然后对数量进行按行/列累加。比如,[1, 2, 1],执行cumsum操作后,结果为[1, 3, 4]
CHECK_EQ(tp_cumsum.size(), num);
// Compute cumsum of fp.
vector fp_cumsum;
CumSum(fp, &fp_cumsum);
CHECK_EQ(fp_cumsum.size(), num);
// Compute precision. // prec和tp的size相等
for (int i = 0; i < num; ++i) {
prec->push_back(static_cast(tp_cumsum[i]) /
(tp_cumsum[i] + fp_cumsum[i]));
}
// Compute recall. // rec和tp的size相等
for (int i = 0; i < num; ++i) {
CHECK_LE(tp_cumsum[i], num_pos);
rec->push_back(static_cast(tp_cumsum[i]) / num_pos); // num_pos为label类别的实际pos数量
}
if (ap_version == "11point") {
// VOC2007 style for computing AP.
vector max_precs(11, 0.);
int start_idx = num - 1; // num = tp.size()
for (int j = 10; j >= 0; --j) {
for (int i = start_idx; i >= 0 ; --i) {
if ((*rec)[i] < j / 10.) {
start_idx = i;
if (j > 0) {
max_precs[j-1] = max_precs[j];
}
break;
} else {
if (max_precs[j] < (*prec)[i]) {
max_precs[j] = (*prec)[i];
}
}
}
}
for (int j = 10; j >= 0; --j) {
*ap += max_precs[j] / 11;
}
} else if (ap_version == "MaxIntegral") {
// VOC2012 or ILSVRC style for computing AP.
float cur_rec = rec->back();
float cur_prec = prec->back();
for (int i = num - 2; i >= 0; --i) {
cur_prec = std::max((*prec)[i], cur_prec);
if (fabs(cur_rec - (*rec)[i]) > eps) {
*ap += cur_prec * fabs(cur_rec - (*rec)[i]);
}
cur_rec = (*rec)[i];
}
*ap += cur_rec * cur_prec;
} else if (ap_version == "Integral") {
// Natural integral.
float prev_rec = 0.;
for (int i = 0; i < num; ++i) {
if (fabs((*rec)[i] - prev_rec) > eps) {
*ap += (*prec)[i] * fabs((*rec)[i] - prev_rec);
}
prev_rec = (*rec)[i];
}
} else {
LOG(FATAL) << "Unknown ap_version: " << ap_version;
}
}
CumSum
cumsum是matlab中一个函数,通常用于计算一个数组各行的累加值,函数用法是B = cumsum(A,dim),或B = cumsum(A)。—百度百科
在这里,CumSum先对pairs的第一个值(分数)按降序排序,然后计算向量的pairs的第二个值的累加值,并保存到cumsum向量中。eg: A = [1, 3, 5], cumsum(A) = [1, 1 + 3, 1 + 3 + 5]
template
bool SortScorePairDescend(const pair& pair1,
const pair& pair2) {
return pair1.first > pair2.first;
}
void CumSum(const vector >& pairs, vector* cumsum) {
// Sort the pairs based on first item of the pair.
vector > sort_pairs = pairs;
std::stable_sort(sort_pairs.begin(), sort_pairs.end(),
SortScorePairDescend);
cumsum->clear();
for (int i = 0; i < sort_pairs.size(); ++i) {
if (i == 0) {
cumsum->push_back(sort_pairs[i].second);
} else {
cumsum->push_back(cumsum->back() + sort_pairs[i].second);
}
}
}
对所有类别的APs求和取平均值,得到mAP。
map APs;
float mAP = 0.;
for label in labels:
ComputeAP(label_true_pos, label_num_pos, label_false_pos,
param_.ap_version(), &prec, &rec, &(APs[label]));
mAP += APs[label];
mAP /= num_pos.size();
完整代码:
caffe.ssd/src/caffe/solver.cpp
template
void Solver::TestDetection(const int test_net_id) {
CHECK(Caffe::root_solver());
LOG(INFO) << "Iteration " << iter_
<< ", Testing net (#" << test_net_id << ")";
CHECK_NOTNULL(test_nets_[test_net_id].get())->
ShareTrainedLayersWith(net_.get());
map > > > all_true_pos;
map > > > all_false_pos;
map > all_num_pos;
const shared_ptr >& test_net = test_nets_[test_net_id];
Dtype loss = 0;
for (int i = 0; i < param_.test_iter(test_net_id); ++i) {
SolverAction::Enum request = GetRequestedAction();
// Check to see if stoppage of testing/training has been requested.
while (request != SolverAction::NONE) {
if (SolverAction::SNAPSHOT == request) {
Snapshot();
} else if (SolverAction::STOP == request) {
requested_early_exit_ = true;
}
request = GetRequestedAction();
}
if (requested_early_exit_) {
// break out of test loop.
break;
}
Dtype iter_loss;
const vector*>& result = test_net->Forward(&iter_loss);
if (param_.test_compute_loss()) {
loss += iter_loss;
}
for (int j = 0; j < result.size(); ++j) {
CHECK_EQ(result[j]->width(), 5);
const Dtype* result_vec = result[j]->cpu_data();
int num_det = result[j]->height();
for (int k = 0; k < num_det; ++k) {
int item_id = static_cast(result_vec[k * 5]);
int label = static_cast(result_vec[k * 5 + 1]);
if (item_id == -1) {
// Special row of storing number of positives for a label.
if (all_num_pos[j].find(label) == all_num_pos[j].end()) {
all_num_pos[j][label] = static_cast(result_vec[k * 5 + 2]);
} else {
all_num_pos[j][label] += static_cast(result_vec[k * 5 + 2]);
}
} else {
// Normal row storing detection status.
float score = result_vec[k * 5 + 2];
int tp = static_cast(result_vec[k * 5 + 3]);
int fp = static_cast(result_vec[k * 5 + 4]);
if (tp == 0 && fp == 0) {
// Ignore such case. It happens when a detection bbox is matched to
// a difficult gt bbox and we don't evaluate on difficult gt bbox.
continue;
}
all_true_pos[j][label].push_back(std::make_pair(score, tp));
all_false_pos[j][label].push_back(std::make_pair(score, fp));
}
}
}
}
if (requested_early_exit_) {
LOG(INFO) << "Test interrupted.";
return;
}
if (param_.test_compute_loss()) {
loss /= param_.test_iter(test_net_id);
LOG(INFO) << "Test loss: " << loss;
}
for (int i = 0; i < all_true_pos.size(); ++i) {
if (all_true_pos.find(i) == all_true_pos.end()) {
LOG(FATAL) << "Missing output_blob true_pos: " << i;
}
const map > >& true_pos =
all_true_pos.find(i)->second;
if (all_false_pos.find(i) == all_false_pos.end()) {
LOG(FATAL) << "Missing output_blob false_pos: " << i;
}
const map > >& false_pos =
all_false_pos.find(i)->second;
if (all_num_pos.find(i) == all_num_pos.end()) {
LOG(FATAL) << "Missing output_blob num_pos: " << i;
}
// mAP计算
// num_pos包含某个label和某个label_num_pos
const map& num_pos = all_num_pos.find(i)->second;
map APs;
float mAP = 0.;
// Sort true_pos and false_pos with descend scores.
for (map::const_iterator it = num_pos.begin();
it != num_pos.end(); ++it) {
int label = it->first;
int label_num_pos = it->second;
if (true_pos.find(label) == true_pos.end()) {
LOG(WARNING) << "Missing true_pos for label: " << label;
continue;
}
const vector >& label_true_pos =
true_pos.find(label)->second;
if (false_pos.find(label) == false_pos.end()) {
LOG(WARNING) << "Missing false_pos for label: " << label;
continue;
}
const vector >& label_false_pos =
false_pos.find(label)->second;
vector prec, rec;
ComputeAP(label_true_pos, label_num_pos, label_false_pos,
param_.ap_version(), &prec, &rec, &(APs[label]));
mAP += APs[label];
if (param_.show_per_class_result()) {
LOG(INFO) << "class" << label << ": " << APs[label];
}
}
mAP /= num_pos.size();
const int output_blob_index = test_net->output_blob_indices()[i];
const string& output_name = test_net->blob_names()[output_blob_index];
LOG(INFO) << " Test net output #" << i << ": " << output_name << " = "
<< mAP;
}
}
参考:
https://blog.csdn.net/qq_41994006/article/details/81051150