0. 写作目的
好记性不如烂笔头。
1. 计算AP(Average Precision)的理论知识
对于每一类目标检测给出的置信度(Confidence),首先按照Confidence降序排列,然后将交并比(IOU)>0.5的当做正确预测的结果,否则为错误预测的结果。然后依据预测结果的正确与否计算TP(True Positive), FP(False Positive), FN(False Negative),TN(True Negative),依据TP, FP, FN, TN来计算召回率(Recall, 查全率)和精确度(Precision, 查准率)。
我们平时所说的Accuracy的计算公式为:
然后依据Confidence排序后的顺序依次计算出Precision和Recall,理论上AP的计算为,由Precision和Recall构成的折线图的面积,但在实际计算时,主要有三种计算方法。
三种AP的计算方法:
1) VOC2007的11Point方法
2) VOC2012以及ILSVRC的 MAXIntegral方法
3) Integral方法(Natural Integral方法)
这三种计算AP的方法只是在积分上有不同,计算方法是类似的。11Point方法是在MaxIntegral方法中找到11个最大的Precision,然后结合Recall = [0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],绘制出P-R曲线,并通过如下公式计算出AP:
对于MaxIntegral的方法是:计算所有的MaxPrecision的值,然后进行相加求平均值。
对于Integral的方法是:直接对曲线下的值进行积分(当然实际计算是也是采用相加来积分的)。
另外还有COCO数据中计算AP的方法,采用的是Confidence在[0.5 : 0.05 : 0.95]计算10次AP,然后求均值的方法计算AP。
主要介绍11Point方法[3,4]。
在11Point中具体如何查找这11点的MaxPrecision,可以参考ssd(caffe版本)的代码[1](在bbox_util.cpp中的ComputeAP函数中),这里直接给出代码(以下代码中vector<float>* temp是博主添加的[2],用于输出每个类的MaxPrecision,然后绘制P-R曲线):
void ComputeAP(const vector<pair<float, int> >& tp, const int num_pos,
const vector<pair<float, int> >& fp, const string ap_version,
vector<float>* prec, vector<float>* rec, float* ap, vector<float> *temp) {
const float eps = 1e-6;
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) {
CHECK_LE(fabs(tp[i].first - fp[i].first), eps);
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<int> tp_cumsum;
CumSum(tp, &tp_cumsum);
CHECK_EQ(tp_cumsum.size(), num);
// Compute cumsum of fp.
vector<int> fp_cumsum;
CumSum(fp, &fp_cumsum);
CHECK_EQ(fp_cumsum.size(), num);
// Compute precision.
for (int i = 0; i < num; ++i) {
prec->push_back(static_cast<float>(tp_cumsum[i]) /
(tp_cumsum[i] + fp_cumsum[i]));
}
// Compute recall.
for (int i = 0; i < num; ++i) {
CHECK_LE(tp_cumsum[i], num_pos);
rec->push_back(static_cast<float>(tp_cumsum[i]) / num_pos);
}
if (ap_version == "11point") {
// VOC2007 style for computing AP.
vector<float> max_precs(11, 0.);
int start_idx = num - 1;
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;
temp->push_back(max_precs[j]); // save max_prec
}
} 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<float>((*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;
}
}
2. 修改ssd(caffe)代码来输出AP和绘制P-R曲线
2.1 修改代码输出AP
对于输出每一类的AP值,只要修改caffe中的配置文件即可,在ssd中修改的文件为: caffe/exampels/ssd/score_ssd_pascal.py中的 solver_param={}中的‘show_per_class_result’参数,该参数为True是,输出每一类的AP和所有类的mAP,当该参数为False时,只输出mAP。
2.2 修改代码绘制每一类的P-R曲线
需要修改的代码文件有: caffe/src/caffe/util/bbox_util.cpp 以及bbox_util.hpp 以及 solver.cpp 和caffe.proto 以及 caffe/exampels/ssd/score_ssd_pascal.py。
i)在bbox_util.cpp中修改的内容主要是,保存每一类在计算过程中的max_precision(11Point)。
修改代码为(主要有两部分,一个是函数定义时:加入了vector<float>* temp, 另一个是在11Point模式下,加入了保存max_precision的代码, temp->bushback(max_precs[j])):
void ComputeAP(const vector<pair<float, int> >& tp, const int num_pos,
const vector<pair<float, int> >& fp, const string ap_version,
vector<float>* prec, vector<float>* rec, float* ap, vector<float> *temp) {
const float eps = 1e-6;
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) {
CHECK_LE(fabs(tp[i].first - fp[i].first), eps);
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<int> tp_cumsum;
CumSum(tp, &tp_cumsum);
CHECK_EQ(tp_cumsum.size(), num);
// Compute cumsum of fp.
vector<int> fp_cumsum;
CumSum(fp, &fp_cumsum);
CHECK_EQ(fp_cumsum.size(), num);
// Compute precision.
for (int i = 0; i < num; ++i) {
prec->push_back(static_cast<float>(tp_cumsum[i]) /
(tp_cumsum[i] + fp_cumsum[i]));
}
// Compute recall.
for (int i = 0; i < num; ++i) {
CHECK_LE(tp_cumsum[i], num_pos);
rec->push_back(static_cast<float>(tp_cumsum[i]) / num_pos);
}
if (ap_version == "11point") {
// VOC2007 style for computing AP.
vector<float> max_precs(11, 0.);
int start_idx = num - 1;
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;
temp->push_back(max_precs[j]); // save max_prec
}
} 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<float>((*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;
}
}
ii) 在bbox_util.hpp中修改的主要是ComputeAP函数的声明参数。
iii) 在solver.cpp 中声明的主要是:打印输出max_precison的情况
vector<float> prec, rec, p_r; // add p_r vector
ComputeAP(label_true_pos, label_num_pos, label_false_pos,
param_.ap_version(), &prec, &rec, &(APs[label]), &p_r); // add &p_r parameter
mAP += APs[label];
if (param_.show_per_class_result()) {
LOG(INFO) << "class" << label << ": " << APs[label];
if(param_.show_pr_value()){ // add output MAX_Precision if given a Recall value
for(int i=0; i < p_r.size(); i++){
LOG(INFO)<<"for plot P-R for each class: "<<p_r[i];
// LOG(INFO)<<"for plot P-R for each class Precision: "<<prec[i]; // add output line
// LOG(INFO)<<"for plot P-R for each class Recall: "<<rec[i];
}
}
}
iv)在caffe.proto 修改的是: 添加一个控制是否输出每一类的max_precision(11Point)的变量。
// If true, display pr value of per class
optional bool show_pr_value = 45 [default = false]; // add line to show MAX_precision given a Recall value
v) 在caffe/examples/ssd/score_ssd_pascal.py中修改配置文件,选择是否输出 max_precision(11Point)
solver_param = {
# Train parameters
'base_lr': base_lr,
'weight_decay': 0.0005,
'lr_policy': "multistep",
'stepvalue': [80000, 100000, 120000],
'gamma': 0.1,
'momentum': 0.9,
'iter_size': iter_size,
'max_iter': 0,
'snapshot': 0,
'display': 10,
'average_loss': 10,
'type': "SGD",
'solver_mode': solver_mode,
'device_id': device_id,
'debug_info': False,
'snapshot_after_train': False,
# Test parameters
'test_iter': [test_iter],
'test_interval': 10000,
'eval_type': "detection",
'show_per_class_result': True, ## whether output AP for each class
'show_pr_value': True, ## whether output max_precision for each class
'ap_version': "11point", ## the way of computing AP
'test_initialization': True,
}
2.3 绘制P-R曲线
如何在caffe/examples/ssd/score_ssd_pascal.py中绘制P-R曲线,请参考我的博客——python-matplotlib 绘制折线图(同时解决XShell远程访问Ubuntu使用matplotlib绘制图时,出现的问题)
[Reference]
[1] ssd(caffe)代码:https://github.com/weiliu89/caffe/tree/ssd
[2] 修改输出AP的代码:https://blog.csdn.net/xunan003/article/details/79252162
[3] 11Point AP计算的理解:https://medium.com/@jonathan_hui/map-mean-average-precision-for-object-detection-45c121a31173
[4] 11Point AP计算的理解:https://github.com/Cartucho/mAP
[5] ssd(caffe)源码解读:https://blog.csdn.net/xunan003/article/details/79089280
[6] mAP计算的理解: http://forums.fast.ai/t/mean-average-precision-map/14345