1. 程式人生 > >What in the hell is the AP and mAP?

What in the hell is the AP and mAP?

Detection都做了兩個月了,對AP(average precision)和mAP(mean average precision)的理解還是似懂非懂。

其實這個東西,適用面很廣,在text retrieval, classification等問題中都有應用,或者說,只要是廣義的分類問題都可以用AP來做metric。

具體操作就是,將某一個類的detect到的目標,按照score降序排列,從recall為0開始(也就是第一個目標開始),依次往後加入目標,這樣recall肯定是一個遞增的趨勢(但不一定是嚴格遞增),而precision的話,過程中有增有減,但是總的趨勢還是降低的,這樣就可以畫出這個類的PR曲線了,比如:

recall = 當前score之前的TP/所有的TP
precision = TP/(TP+FN)

這裡寫圖片描述

嚴格的AP就是這個PR曲線下的面積,mAP就是所有類AP的算術平均。

但是一般都是用逼近的方法去估計這個面積,比如
approximated precision的方法:每個recall point都approximate,計算每個矩形面積求和(下圖紅色虛線)
Interpolated Precision的方法:從每個recall point往後看,用最大的precision作為插值來,計算每個矩形面積求和(下圖藍色虛線)
這裡寫圖片描述
從PASCAL VOC 2007開始,就是用的類似Interpolated Precision的方法,不過稍有不同的是,VOC使用的是在固定的11個recall([.0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1.])的地方取precision然後來近似AP,所以又叫11-point interpolated average precision

而從PASCAL VOC 2010開始,又擯棄了11-point interpolated average precision的計算方法,取而代之的是用所有的(recall, precision)資料點(只要是recall有改變的地方)來計算AP。具體而言就是,取所有recall改變的資料點及其後的最大的precision作為當前recall的precion(這樣就能得到一條單調遞減的(recall, precision)曲線)來計算矩形面積,然後累加所有小矩形,即得AP。
(下圖來自VOC2012的devkit文件)

用上述方法分別算出各個類的AP,然後取平均,就得到mAP了。AP的計算可以直接統計該類別下的TP,FP和postitive number的總數,然後就可以一次性算出AP了。得到了各類的AP,mAP就是各類別AP的算術平均!mAP的好處是可以防止AP bias到某一個數量較多的類別上去。

================================================================

這裡擷取一些SSD原始碼中,對應計算AP的程式碼片段:

caffe.proto

  // 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.
  optional string ap_version = 42 [default = "Integral"];

bbox_util.cpp

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) {
  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;
    }
  } 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;
  }
}

================================================================

從上面可以看出,AP的計算方法還是有幾種不同方法的,所以在比較演算法的時候,一定要確保使用的是相同的AP計算方法,否則對比就不嚴格。比如說使用的是VOC2007那樣的interpolated AP,那麼就可能會存在高估演算法效能的可能。