本文介绍 EfficientDet训练数据集anchor设定教程101

EfficientDet训练数据集anchor设定教程101

本文由林大佬原创,转载请注明出处,来自腾讯、阿里等一线AI算法工程师组成的QQ交流群欢迎你的加入: 1037662480

上一篇文章我们传授了大家如何使用darknet训练自己的数据集,使用YoloV4的模型,不过最近很火的EfficientDet模型精度超越YoloV4(在D4+),尽管它使用了更大的输入尺寸。我们还是希望来尝试一下efficientdet吊炸天的检测能力。事实上,社区开源的efficientdet很多人无法复现原作者的coco检测结果,原因是lr比较难调。笔者自己尝试下来,发现lr确实很重要,但对于自己的数据集,anchor的设定才是重中之重。本文将从anchor这么一个角度,教大家如何来调efficientdet,同时看完本文,如果你是一个目标检测小白,我估计你应该也入门目标检测了。

本文将cover的内容包括:

  • 单阶段算法anchor的原理(白话图文版);
  • 如果debug你的anchor设定(尽量cover尽可能多的ground truth 框);
  • 在EfficientDet中通过调节anchor使得你的数据集检测能力提高一个等级。

可视化anchor

anchor是什么?很多人在这里会画一大堆的图,我不想这么做,尽管图片更加直观,我直接告诉你一个矩阵:

INFO 05.12 14:35:17 anchor_inspector.py:204: anchors: [[   2.88       2.88       5.12       5.12   ]
 [   2.88       1.76       5.12       6.24   ]
 [   1.76       2.88       6.24       5.12   ]
 ...
 [ 734.22217  734.22217 1185.7778  1185.7778 ]
 [ 734.22217  508.4443  1185.7778  1411.5557 ]
 [ 508.4443   734.22217 1411.5557  1185.7778 ]]

这是我log出来的一系列anchor,它每一行四个坐标代表的就是一个框(xywh), 它总共有:

anchors shape: (196416, 4), ann shape: (2, 4)

196416个anchor,如果把这些anchor全部画到图片上就长这样:

image-20200512144342706

什么?这怎么什么都么有?原因很简单,因为框实在是太多了,直接就把图片填满了。大家不要脑补这绿色背后是啥,它就是一张普通的图片。

接着我们接下来会怎么搞? 既然anchor这么多,我如何确定我需要的anchor呢?它就隐藏在这19w里面的一两个 (大家仔细看上图的白色框,实际上就是groud truth)这是一个极小目标检测的任务。如何找呢? 算法也很简单:

a. 我知道了anchor,我也知道了annotation,我接下来是不是要算一下他们之间的overlap;

b. 选择overlap最大的anchor,不就是我们需要的anchor了吗?

对应的算法也很简单,为了让大家耐心的看完这篇文章,我再最后的时候再放上所有的代码。那我们就来计算一下,看看能不能filterout我们需要的anchor,并把它画在图片上,和ground truth一起。

image-20200512145045227

看到上图,大家可以发现,淡绿色的是anchor(也就是我们的上面生成的19w的box里面的一个),我们找到一个和左边的groundtruth契合的框,而右边的没有找到。

这意味着什么?

至少意味着两点:

  • 我们生成的anchor不是最优的,它没有尽可能的囊括最多的ground truth;
  • 如果这样的anchor用于训练,会导致这个地方无任务背景,且浪费掉了大量的正例框,毫无疑问这样的网络无法学习到这么小物体的目标,不管你的检测器多强,都会漏检。

针对这样的问题,我们是不是需要想办法,对自己的数据集,去计算适合自己的anchor设定呢?

## 一劳永逸计算自己数据集的anchor

首先大家要明确自己当前要解决的问题是啥,anchor只和图片的shape有关,不同的图片同样的shape,则anchor是一样的,我们如何才能保证这些box能够尽可能的囊括所有的gt box呢?

能想到的第一个方法就是聚类,对box进行聚类,有同学会问了,你聚类之后呢?如何生成那19w个框呢?

这个疑问非常好,我们还没有告诉大家那19w个框到底是如何产生的。先给大家上anchor 生成的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
boxes_all = []
for stride in self.strides:
    boxes_level = []
    for scale, ratio in itertools.product(self.scales, self.ratios):
        if image_shape[1] % stride != 0:
            raise ValueError('input size must be divided by the stride.')
            base_anchor_size = self.anchor_scale * stride * scale

            anchor_size_x_2 = base_anchor_size * ratio[0] / 2.0
            anchor_size_y_2 = base_anchor_size * ratio[1] / 2.0

            x = np.arange(stride / 2, image_shape[1], stride)
            y = np.arange(stride / 2, image_shape[0], stride)
            xv, yv = np.meshgrid(x, y)
            xv = xv.reshape(-1)
            yv = yv.reshape(-1)

            # y1,x1,y2,x2
            boxes = np.vstack((yv - anchor_size_y_2, xv - anchor_size_x_2,
                               yv + anchor_size_y_2, xv + anchor_size_x_2))
            boxes = np.swapaxes(boxes, 0, 1)
            boxes_level.append(np.expand_dims(boxes, axis=1))
     # concat anchors on the same level to the reshape NxAx4
     boxes_level = np.concatenate(boxes_level, axis=1)
     boxes_all.append(boxes_level.reshape([-1, 4]))

anchor_boxes = np.vstack(boxes_all)
anchor_boxes = torch.from_numpy(anchor_boxes.astype(dtype)).to(device)
anchor_boxes = anchor_boxes.unsqueeze(0)

这部分代码取自keras-retinanet,完善于Yet-Another-EfficientDet,我们在这里做一个简单的讲解。

这里只有3个很重要的变量:

strides: [8, 16, 32, 64, 128]
scales: [0.1        0.25       1.25992105]
ratios: [(0.7, 0.7), (1.4, 0.7), (0.7, 1.4)]

其实最核心的是这行代码:

x = np.arange(stride / 2, image_shape[1], stride)
y = np.arange(stride / 2, image_shape[0], stride)
xv, yv = np.meshgrid(x, y)

知道imageshape, 也就知道了最里面的meshgrid之后的size有多大。

比如imageshape是(1024, 1024),那么stride是8 的时候最里面的box数目就是: 128x128=16384:

stride 8 -> 128*128 -> 128*128 * 3 * 3 = 147456
stride 16 -> 64*64 -> 64*64 *3*3       = 36864
stride 32 -> 32*32 -> 32*32 *3*3       = 9216
stride 64 -> 16*16 -> 16*16 *3*3       = 2304
stride 128 -> 8*8 -> 8*8 *3*3          = 576

所有总共的anchor数目应该是:

147456 + 36864 + 9216 + 2304 + 576 = 196416

这和我们上面直接输出的anchor完全吻合!

很多同学可能还没有看懂上面的具体演示和计算,如果你还没有搞懂具体计算方法,可以加入我们的社区,一起来交流:

http://t.manaai.cn

刚才说道如何计算我们的anchor?也就是需要聚类,那么这里面就有一个问题了,既然是聚类,我聚类成几类比较合适呢?这个类别和我的模型是否有关系呢?

聚类的列别数目,和我们需要的anchor有关,这个anchor的设计,实际上和网络的结构没有多大关系,唯一有关系的就是我们需要多少level的特征,就设置多少个不同比例的anchor。至于这背后的深刻思想内涵,谁知道呢?我们就按照大家都喜欢的方式来吧。

我们会把anchor可视化和聚类计算的代码都放在文章的最下面,大家可以跑一下,计算你数据集的最佳anchor。但有一句话必须要得说:

anchor的设计对于网络的影响非常大,但是只要你设置的合理,网络一般都能handle,一般直接聚类计算出来的anchor并不一定是最优的。你可能需要手动的去调节,比如小的物体没有匹配到anchor,我就把最小的那个scale减小。

又深深的上了一课,原来调参工程师并不简单没有对问题深刻的洞见无法解决我们的问题。

最后这里贴一张图,很多人训练efficientdet应该是没有办法很好的训练好的,除非他的数据集比较中等,也就是说没有太小的物体,也没有太大的物体。

那么这个时候你就需要考虑到anchor的设计啊,你想啊,你的anchor在训练的时候都匹配不大,你拿啥去训练,全给你当背景了。

image-20200513173815562

正常的anchor匹配。如果你能看到你的anchor,说明你匹配成功了。

当然这里的匹配不可能每个ground truth都能找到anchor,但是你得保证绝大部分是有的,其他的,随缘。

## 用新的anchor训练efficientdet

笔者在没有设计anchor之前,是懵逼的,因为coco的anchor直接拿过来根本没法用呀,豆大的目标无法检测,这还是霸榜多年的哪个efficientdet吗?

最后新的anchor训练出来的之后的检测效果,各位看官可以参考参考。

1832494085

当然,这也仅供参考,我们希望大家可以依照这个教程可以在自己的数据集上训练出一个比较好的结果。

最后分享几点在训练测试,调节efficientdet时几点经验, 以及这个检测器本身存在弊端:

  • 没有什么算法是万能的,这个highly hyper paramed 并且连lr都会严重影响训练效果的检测器非常的脆弱,一点某个地方不对,你就可能得到一个废掉的结果,最严重的就是本文提到的这个anchor;
  • 事实证明,不管你的anchor设计的多么合理,多么精妙,最终这个模型本身无法逃离它的致命缺陷:检测不稳。这里说的不稳,包括漏检和概率不稳,这对于工业应用是致命的。具体来说就是,前后两张几乎一样的图片(video的相邻两帧),第一针可能检测到了,而下一帧就漏,还有就是同一个物体,前面一帧的概率是90%,下一帧就可能是10%.

当然这也可能是由于achor还不是最优化导致的,但对于一个检测器来讲,你的数据以增加你就要重新计算anchor,甚至是重新训练模型,那还有什么意义呢?

代码

我们所有的代码托管在MANA AI,如果你看完本文觉得不错,一定记得三联啊老铁!!点赞,转发,评论走起!

本文中的数据集为非公开数据集,但anchor的配置可视化是公开的,大家可以点击链接去制作自己的anchor:

https://github.com/manaai-cn/anchor_computation_tool

我们的AI交流社区:

http://t.manaai.cn

MANA平台:

http://manaai.cn

广告时间

接下来是广告时间。如果你想学习人工智能,对前沿的AI技术比较感兴趣,可以加入我们的知识星球,获取第一时间资讯,前沿学术动态,业界新闻等等!你的支持将会鼓励我们更频繁的创作,我们也会帮助你开启更深入的深度学习之旅!

image-20200515153654923