mmdetection最小复刻版(一):整体概览——#mmdetection学习

举报
lutianfei 发表于 2021/09/02 11:54:23 2021/09/02
【摘要】 本文转载自https://www.zybuluo.com/huanghaian/note/1742545github: https://github.com/hhaAndroid/mmdetection-mini欢迎star部分内容有删改本文是整个框架介绍的第一篇,主要包括框架说明、结构说明、Resize、Registry、FileClient、GroupSampler、collate等部分...

本文转载自
https://www.zybuluo.com/huanghaian/note/1742545
github: https://github.com/hhaAndroid/mmdetection-mini
欢迎star

部分内容有删改


本文是整个框架介绍的第一篇,主要包括框架说明结构说明ResizeRegistryFileClientGroupSamplercollate等部分。

1 Registry

mmdetection的一个非常大的特色是注册器机制。要理解mmdetection,第一步就是理解Registry,其有两种用法:

  • 方式一:定义一个类,然后在上方采用@xx…register_module()的方式注册
backbones = Registry('backbone')
@backbones.register_module()
 class ResNet:
      pass
  • 方式二:直接注册自己实现或者任何地方已经实现的类到注册器中
ACTIVATION_LAYERS.register_module(module=nn.ReLU)

一旦注册进去了,那么在配置里面就可以通过dict(type='类名',类参数)方式实例化指定类(具体是通过build_from_cfg函数解析并且实例化)。

这种方式的好处是:扩展性非常强,解耦性也很好。

其核心原理就是简单的装饰器。Registry类把python装饰器功能封装为了类,原因是类可以存储实例对象。真正起作用的还是装饰器函数register_module,其返回一个装饰器函数。

2 FileClient作用

fileClient也叫作文件后端,主要目的是对文件进行加速缓存读取,尽可能减少io读取耗时,特别是机械硬盘上会显著影响。以常规的LmdbBackend为例,

和我们息息相关的,mmdetection实现的主要是:

HardDiskBackend

这个是默认使用的,其实就是啥也没有做的,没有缓存,每次都是从硬盘里面把图片字节码读取处理即可。

MemcachedBackend

采用了python的第三方库memcached 实现对文件名和图片字节进行实时存储,内存自动管理。这个库比较庞大,功能非常强,需要提前开启缓存服务器,然后在客户端运行(服务器程序和客户端可以在同一个机器),可能在一些复杂场景会用到

LmdbBackend

lmdb是一个Lightning Memory-Mapped Database 快如闪电的内存映射数据库。lmdb库的使用需要将数据集先制作成lmdb的格式,然后就可以采用lmdb快速索引,图片的读取就可以省略了。使用lmdb会遍历图片,然后采用文件名作为key,图片字节码作为value高效存储,保存为一个文件,有点类似tfrecord。在后续get读取时候就不需要再频繁io读取了,只需要从生成的文件中读取一次,然后再进行图片字节解码即可。

参考: 如何生成lmdb

3 Resize

由于这个类写的功能比较多,需要总结下用法。

  • 第一种用法:
transform = dict(type='Resize', img_scale=(1333, 800), keep_ratio=True)

将图片保持比例的resize到图片长短边都在指定的img_scale范围。不同大小的输入图片,输出的图片size是不一样的,如果不保持比例,则说明img_scale是目标图片的w,h,直接对图片resize到指定的img_scale即可,输出图片大小都是一样的。

  • 第二种用法:
transform = dict(type='Resize', img_scale=[(1333, 800), (1333, 600)], keep_ratio=True)

如果img_scale是list,说明是多尺度resize,内部有两种做法,通过multiscale_mode参数来控制,默认是range。如果是’range’,则表示从多尺度里面随机插值一个新尺度,例如上述,其会组成短边[600,800]和长边[1333,1333],然后基于这两个list插值出新的scale;如果是‘value’模式,则是仅仅随机从多尺度列表里面选择其中一个。

  • 第三种用法:
transform = dict(
        type='Resize',
        img_scale=(1333, 800),
        ratio_range=(0.9, 1.1),
        keep_ratio=True)

如果指定了ratio_range,则img_scale必须是单尺度,表示对指定的img_scale进行在指定范围内的随机,得到新scale。

如果img_scale是一个数,那么就直接表示缩放系数了。

可以看出Resize函数实现了非常多的功能,包括随机版本和非随机版本。

4. GroupSampler用途和缺点

分组采样的作用是将长宽比大于1和小于1的图片分成两组,在组成batch的时候将同一组的图片组成batch返回,好处是后面的pad操作不会引入比较多的黑边(由于其datalayer的输出是不定shape的图片,所以需要pad)。

分组采样需要dataset里面有每个样本的flag标志。但是其实有个小问题:flag是在图片读取的时候确定的,但是如果中间的数据增强比较剧烈,导致长宽比变化了,那么可能flag表示是错误的,但是框架没有处理,也就是默认不会出现这种情况,属于一个坑。目前的mmdetection采用的数据增强很少,不会出现这种情况,但是如果我自己想试试其他模型,并且引入了比较强的数据增强的话,虽然这个类不会报错,但是用途也很少了。

image.png

正确做法应该是对于长宽比改变的数据增强操作,其对应图片的flag也要跟着改变。这个问题或许mmdetection后面会解决。

5. collate分析

  • 为啥要有samples_per_gpu参数?
    原因:为了分布式多卡训练而设置。单卡训练模式没有意义
    假设一共4张卡,每张卡8个样本,故dataloader吐出来的batch=32,但是分组采样时候是对单batch而言的,也就是说这里收集到的32个batch其实分成了4组,4组里面可能存在flag不一样的组,如果这里不对每组进行单独操作,那么其实前面写的分组采样GroupSampler功能就没多大用途了。本函数写法会出现4个组输出的shape不一样,但是由于是分配到4块卡上训练,所以大小不一样也没有关系,保持单张卡内shape一样就行。

所以对于单卡训练场景,len(batch)=samples_per_gpu,这里的for循环没有意义,可以删掉。但是需要注意的是在batch 测试模式下,最后一个batch可能不会相等,但是不影响测试。

6. MMDataParallel

这个类是继承自DataParallel,本来应该也是支持多卡训练的,但是由于mmdetection里面强制将多卡训练设置为分布式,故这个类只能用于单卡训练。

  • 作用: 将dataloader吐出来的含有dc格式的数据去掉,变成网络能够吃的数据。

注意:train/val和test的运行逻辑有区别:

  • 在train或者val模式下,运行逻辑是:
1. 首先在runner里面
	outputs = self.model.train_step(data_batch, self.optimizer, **kwargs)
    
2. 其首先调用MMDataParallel.train_step,这个函数的核心功能就是把dc格式的数据去掉,变成tensor
 	inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)
    
3. 然后调用model本身的train_step,位于base.py里面
 	losses = self(**data) # 调用forward
 	loss, log_vars = self._parse_losses(losses)

在test模式下,运行逻辑是:

1.没有runner了,其首先调用MMDataParallel.forward,其在gpu模式下,啥也不干,直接
	return super().forward(*inputs, **kwargs)  # 调用DataParallel.forward
    
2. dataparallel.forward在单卡下,核心函数是:
	inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)
由于MMDataParallel复写了这个函数,故实际调用依然是MMDataParallel自己的scatter,作用也就是把带dc格式的数据去掉,变成tensor

3. 然后调用model本身的forward,位于base.py里面
  if return_loss:
      return self.forward_train(img, img_metas, **kwargs)
   else:
      return self.forward_test(img, img_metas, **kwargs)

7. anchor分析

见知乎文章:基于mmdetection框架算法可视化分析随笔(上)

本框架也集成了anchor分析,并且更加完善。具体是tools/anchor_analyze.py

8. MaxIoUAssigner匹配规则

见知乎文章:目标检测正负样本区分策略和平衡策略总结(一)

MaxIoUAssigner的操作包括4个步骤:

  1. 首先初始化时候假设每个anchor的mask都是-1,表示都是忽略anchor
  2. 将每个anchor和所有gt的iou的最大Iou小于neg_iou_thr的anchor的mask设置为0,表示是负样本(背景样本)
  3. 对于每个anchor,计算其和所有gt的iou,选取最大的iou对应的gt位置,如果其最大iou大于等于pos_iou_thr,则设置该anchor的mask设置为1,表示该anchor负责预测该gt bbox,是高质量anchor
  4. 第3步的设置可能会出现某些gt没有分配到对应的anchor(由于iou低于pos_iou_thr),故下一步对于每个gt还需要找出和最大iou的anchor位置,如果其iou大于min_pos_iou,将该anchor的mask设置为1,表示该anchor负责预测对应的gt。通过本步骤,可以最大程度保证每个gt都有anchor负责预测,如果还是小于min_pos_iou,那就没办法了,只能当做忽略样本了。从这一步可以看出,3和4有部分anchor重复分配了,即当某个gt和anchor的最大iou大于等于pos_iou_thr,那肯定大于min_pos_iou,此时3和4步骤分配的同一个anchor。

从上面4步分析,可以发现每个gt可能和多个anchor进行匹配,每个anchor不可能存在和多个gt匹配的场景。在第4步中,每个gt最多只会和某一个anchor匹配,但是实际操作时候为了多增加一些正样本,通过参数gt_max_assign_all可以实现某个gt和多个anchor匹配场景。通常第4步引入的都是低质量anchor,网络训练有时候还会带来噪声,可能还会起反作用。

简单总结来说就是:如果anchor和gt的iou低于neg_iou_thr的,那就是负样本,其应该包括大量数目;如果某个anchor和其中一个gt的最大iou大于pos_iou_thr,那么该anchor就负责对应的gt;如果某个gt和所有anchor的iou中最大的iou会小于pos_iou_thr,但是大于min_pos_iou,则依然将该anchor负责对应的gt;其余的anchor全部当做忽略区域,不计算梯度。该最大分配策略,可以尽最大程度的保证每个gt都有合适的高质量anchor进行负责预测,

9. tensorflow的same模式在pytorch中的实现

一定要注意:tf的same模式并不是左右两边都补0的,
如果你以为:pytorch中,如果kernel已知,然后pading设置为(kernel-1)//2,实现的输入和输出相等的操作,虽然实现了和tf的same相同的功能,但是其实效果是不一样的。pytorch中设置的pad参数,是只能实现左右两边同时填充的,而tf的same可能并不是。例如
输入是512x512,stride=2,kernel=3,按照tf的same模型,输出是256x256的,但是其补充的0其实是只是在右边和下面补充了1个像素,先pad成513x513的,然后conv后向上取整变成了256x256的输出;如果采用pytorch实现,pad=1,会先pad成514x514的输入,然后conv变成256x256的输出,可以看出如果按照上面实现,虽然输出一样了,但是pytorch的实现其实偏了两个像素。
也就是说如果你想直接用tf的权重,然后迁移到pytorch中,那么一定要注意same的实现,必须要手动先算出pad参数,利用F.pad函数先实现same功能,然后在进行conv,这样复现的结果才是完全一致的。

具体可以参考:https://github.com/zylo117/Yet-Another-EfficientDet-Pytorch/blob/master/efficientnet/utils_extra.py

mmcv也有实现: https://github.com/hhaAndroid/mmdetection-mini/mmdet/cv_core/cnn/bricks/conv2d_adaptive_padding.py

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。