目标检测中如何计算AP和mAP以及绘制P-R曲线以及SSD中如何修改代码来输出AP和绘制P-R曲线

版权声明:原创博客未经允许请勿转载! https://blog.csdn.net/holmes_MX/article/details/82466537

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, 查准率)。

                                                                  \large Precision= \frac{TP}{TP+FP }

                                                                      \large Recall= \frac{TP}{TP+FN}

我们平时所说的Accuracy的计算公式为:

                                                               \large Acc==\frac{TP + TN}{TP+TN+FP+FN}

然后依据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:

                                                          \large \bg_black \large AP_{11point}=\frac{1}{11}\ast \left (\sum x \right ) \left ( x\in MaxPrecision \right )

对于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

展开阅读全文

没有更多推荐了,返回首页