MMSegmentation和Detectron2中几种Metrics的实现
本文记录MMSegmentation和Detectron2中Metircs模块的实现,关注模块设计,实现细节,边界情况,给出在库外使用该模块的方法;MMSegmentation中这部分的代码主要在此处,Detectron2中这部分的代码主要在此处。
常用Metrics定义
下列定义来自FCN。其中,\(K\) 是总类别数,\(i\) 是类别索引,\(n_{ij}\) 表示真实类别为 \(i\) 预测类别为 \(j\) 的总像素数,\(t_i = \sum_{j}n_{ij}\) 是第 \(i\) 类别所有像素数量。
pAcc
一个像素视为一个统计单位,计算准确率。
\[ \mathrm{pAcc} = \frac{\sum_{i}n_{ii}}{\sum_{i}t_i} \]
mAcc
mIoU
fwIoU
模块设计
1 | eval_metrics |
total_intersect_and_union
循环为多个(pred, gt)调用intersect_and_union
,并使用加法在数量维度上规约其结果;intersect_and_union
计算四个的直方图(横坐标为类,纵坐标为像素数量):交、并、pred,gt;total_area_to_metrics
使用总直方图信息计算- 诸如
mean_iou
,mean_dice
,mean_fscore
都是对eval_metrics
的简单封装(这三个也是目前MMSeg支持的,除了Acc, Precision, Recall之外的指标); pre_eval_to_metrics
将pre_eval
积累的所有输出的评估结果加法规约,之后再送入total_area_to_metrics
;
什么情况下触发
pre_eval
?为什么需要pre_eval
?
- 在使用
--eval
指定评估指标,并且不是在cityscapes的format结果上进行cityscape mIoU的评估时,都会开启pre_eval
;- 希望在将验证集上每个batch的输入(事实上在验证/测试时batch=1)通过模型后,就立刻与gt计算metrics,而不是在某处保存生成的结果,这样将来在reduce的时候也无需再次加载gt;
- 按照笔者曾经的经验来看,由于把每个batch的结果都添加到内存里,笔者曾经用一台内存比较小的机器跑ADE20K的推理,观察到内存占用逐渐增加,最后出现内存的OOM问题;为什么不边算便求和?
实现细节
这里主要关注 intersect_and_union
以及
total_area_to_metrics
的实现;
1 | def intersect_and_union(pred_label, |
在 intersect_and_union
使用了 torch.histc
实现,避免了逐个类的for循环;
值得注意的是,所有的metrics都是先对直方图信息求和,再计算,当然求和的位置确实会影响最终结果,笔者认为这样做可以避免频繁在个例中出现Nan;以第 \(c\) 类的 \(\mathrm{IoU}_c\) 为例:
\[ \mathrm{IoU}_c =\frac{ \sum_{i=1}^N P_c^{(i)}\cap Q_c^{(i)}}{ \sum_{i=1}^N P_c^{(i)}\cup Q_c^{(i)}} \]
边界情况:当某个类别从未在任何一张gt上出现时,以
total_area_label
为分母的metrics都是Nan,如果还满足从未在任何一张pred上出现,那么
total_area_union
为分母的IoU也是Nan;
在任何地方使用
笔者认为,使用 mean_xxx
这类接口就能满足大部分的需求,以下面一个随机demo为例,这个例子也验证了IoU的计算并不是逐对计算后平均;
1 | from mmseg.core.evaluation import mean_iou |
当然也可以有节约内存的写法,不必保存所有的(pred,
gt)对或者他们的面积信息,在迭代时即完成规约,与调用mean_iou
结果相同;
1 | from mmseg.core.evaluation.metrics import intersect_and_union, total_area_to_metrics |