EfficientDet论文复现踩坑记

Posted by Lucifer443 on April 29, 2020

前言

​ EfficientDet论文自去年放到arXiv上就备受人们关注,但一直没人能复现它的结果,直到3月份release代码,才让人们知道其中实现的细节。我在复现EfficientDet的过程中可以说是踩坑无数,看了官方release的代码,才发现里面充满了炼丹的技巧…

​ EfficientDet的结构与RetinaNet类似,都分为backbone、neck和head三部分。EfficientDet的backbone为EfficientNet系列,neck为论文中提出的bifpn,head与RetinaNet一样(此处有坑…)。根据网络规模的不同,衍生出D0~D7的8种不同精度的结构,其中D7达到了SOTA的水平。下面就说下我在复现过程中遇到的坑。

踩过的坑

Backbone

  • 论文中从EfficientNet中取了P3~P7的5个scale的feature map作为BiFPN的输入,但是EfficientNet只有1/8~1/32的三个scale。github上最早的EfficientDet复现代码采用了魔改EfficientNet的方式,将其中两个block的stride改为了2。这么做显然很不靠谱,我只好采用了RetinaNet中的方式,通过Downsample来得到1/64和1/128的feature map。后来证明官方确实也是这么做的,只是可能是为了减少计算量,官方中用的max_pooling做的降采样,而我当初用的是3x3conv。
  • BN是否更新的问题。在检测任务中,有时会freeze backbone中BN,而我实验时发现EfficientDet不固定BN比固定BN要高2个点。后来看官方代码也是这么做的。

BiFPN

BiFPN

​ 上图就是BiFPN的最小结构,看起来不是很复杂,实际上充满了细节。

  • 没有lateral_conv。一般在FPN中会通过lateral_conv来对齐backbone输出与fpn输入的channel数,但EfficientDet中没这样做。
  • 在第一层BiFPN中P5和P4节点的两个输出为不同tensor(图中根本看不出来~)。在官方代码中,上图中的彩色节点的输入都会经过Resample的过程,而Resample会在输入channel数与输出channel数不等时添加1x1conv,所以就导致了第一层BiFPN中P5和P4节点的两个输出为不同tensor。
  • 在第一版论文中只提到了上图的彩色节点由separable conv BN与activation组成,没有说三者的顺序以及是哪种activation。这点在最新论文中有提及,每个彩色节点由swish-BN-sepconv组成。

​ head中的坑并不多,主要是它把正常RetinaHead中的conv都换成了separable conv,以及在不同level间只共享conv的参数,不共享BN的参数。

训练过程

  • 在第一版论文中有一个关键的训练参数没有提及,就是epoch。当初复现时,通过论文图片猜测他训了120epoch左右,结果它足足训了300epoch…
  • 另一个坑是关于数据预处理的,第一版论文中没有提及使用了random crop,使用random crop会比不使用提升3个点左右。

复现结果

​ 最后贴上我的复现结果,在参考了官方release的代码后,EfficientDet-D0终于复现到接近论文的精度了,不容易啊~

  mAP(val2017) Params FLOPs mAP(val2017) in paper Params in paper Flops in paper
D0 32.02 3.87M 2.55B 33.5 3.9M 2.5B

​ 代码在https://github.com/lucifer443/EfficientDet-Pytorch,欢迎star。