专栏首页GiantPandaCV【MMDetection 超全专栏】一,结构设计和官方文档2.0伪译

【MMDetection 超全专栏】一,结构设计和官方文档2.0伪译

目录

  • 第一节 结构设计
    • 0.1.1 总体逻辑
  • 第二节 官方文档2.0伪译
    • 0.2.1 配置系统
    • 0.2.2 使用预训练模型
    • 0.2.3 增加新数据类
    • 0.2.4 自定义数据管道
    • 0.2.5 增加新模块
    • 0.2.6 1.x模型升级到2.0
    • 0.2.7 2.0和1.x的不同之处
    • 0.2.8 版本变化补充

第一节 结构设计

19年7月,Kai Chen等人写了一篇文章MMDetection(https://arxiv.org/pdf/1906.07155.pdf),介绍了他们在mmdetection(https://github.com/open-mmlab/mmdetection)上的一些工作。包括mmdetection的设计逻辑,已实现的算法等。猜:KaiChen在不知道经历了一些什么之后,觉得对各种实现迥异的检测算法抽象一些公共的组件出来也许是一件不错的事。这里尝试对代码做一些简单的解析,见下。

组件设计:

  • BackBone:特征提取骨架网络,ResNet,ResneXt,ssd_vgg, hrnet等。
  • Neck: 连接骨架和头部.多层级特征融合,FPN,BFP,PAFPN等。
  • DenseHead:处理特征图上的密集框部分, 主要分AnchorHead。AnchorFreeHead两大类,分别有RPNHead, SSDHead,RetinaHead和FCOSHead等。
  • RoIExtractor:对特征图上的预选框做pool得到大小统一的roi。
  • RoIHead (BBoxHead/MaskHead):在特征图上对roi做类别分类或位置回归等(1.x)。
  • ROIHead:bbox或mask的roi_extractor+head(2.0,合并了extractor和head)
  • OneStage: BackBone + Neck + DenseHead
  • TwoStage: BackBone + Neck + DenseHead + RoIExtractor + RoIHead : 1.x
  • TwoStage: BackBone + Neck + DenseHead + RoIHead(2.0)

代码结构:

configs: 网络组件结构等配置信息

tools: 训练和测试的最终包装和一些实用脚本

mmdet:

  • apis: 分布式环境设定(1.x,2.0移植到mmcv),推断,测试,训练基础代码
  • core: anchor生成,bbox,mask编解码,变换,标签锚定,采样等,模型评估,加速,优化器,后处理等
  • datasets: coco,voc等数据类,数据pipelines的统一格式,数据增强,数据采样
  • models: 模型组件(backbone,head,loss,neck),采用注册和组合构建的形式完成模型搭建
  • ops: 优化加速代码,包括nms,roialign,dcn,masked_conv,focal_loss等

Figure1: Framework

Figure2: Trainning pipline

0.1.1 总体逻辑

从tools/train.py中能看到整体可分如下4个步骤:

    1. mmcv.Config.fromfile从配置文件解析配置信息,并做适当更新,包括环境搜集,预加载模型文件,分布式设置,日志记录等
    • 2.1 build系列函数调用build_from_cfg函数,按type关键字从注册表中获取相应的对象,对象的具名参数在注册文件中赋值。
    • 2.2 registr.py放置了模型的组件注册器。其中注册器的register_module成员函数是一个装饰器功能函数,在具体的类对象
    A

    头上装饰@X.register_module,并同时在

    A

    对象所在包的初始化文件中调用

    A

    ,即可将

    A

    保存到registry.module_dict中,完成注册。

    • 2.3 目前包含BACKBONES,NECKS,ROI_EXTRACTORS,SHARED_HEADS,HEADS,LOSSES,DETECTORS七个模型相关注册器,另外还有数据类,优化器等注册器。
    1. mmdet.models中的build_detector根据配置信息构造模型
    • 3.1 coco,cityscapes,voc,wider_face等数据(数据类扩展见后续例子)。
    1. build_dataset根据配置信息获取数据类
  • 4.train_detector模型训练流程
    • 4.1 数据loader化,模型分布式化,优化器选取
    • 4.2 进入runner训练流程(来自mmcv库,采用hook方式,整合了pytorch训练流程)
    • 4.3 训练pipelines可见上面的第2点,具体细节见后续展开。

后续说说配置文件,注册机制和训练逻辑。

第二节 官方文档2.0伪译

2.0相比1.x,就代码组织上,在模块化这方面有了更好的贯彻。能拆分的就拆分,比如配置文件,数据的信息整合、变换、采样迭代, 以前版本命名不严格的一律改掉, 比如anchor_heads。

0.2.1 配置系统

1.x版本是将所有配置信息放到一个x.py配置文件中,2.0增加了配置文件的模块化和继承能力,这样在实验中能提高组合不同部分的效率。执行python tools/print_config.py /PATH/TO/CONFIG 能看到配置信息。--options xxx.yyy=zzz可看到更新信息。

基础配置文件在config/_base_中,有dataset, model, schedule,default_runtime四个部分,对应1.x版本单个配置文件的 不同部分。

0.2.2 使用预训练模型

将coco数据训练的模型作为CitySpace等数据预训练模型,需要做以下五处改动

继承基础配置文件

基础模型继承自mask_rcnn_r50_fpn,数据继承自cityscapes风格,训练schedules继承自默认的default_runtime,在 配置文件顶部增加如下代码:

 _base_ = [
  '../_base_/models/mask_rcnn_r50_fpn.py',
  '../_base_/datasets/cityscapes_instance.py', '../_base_/default_runtime.py'
 ]
 

更改头部

如果新旧模型的类别不同,则需要改一下类别数目。

 model = dict(
    pretrained=None,
    roi_head=dict(
        bbox_head=dict(
            type='Shared2FCBBoxHead',
            in_channels=256,
            fc_out_channels=1024,
            roi_feat_size=7,
            num_classes=8,    # new num_classes
            target_means=[0., 0., 0., 0.],
            target_stds=[0.1, 0.1, 0.2, 0.2],
            reg_class_agnostic=False,
            loss_cls=dict(
                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
            loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)),
        mask_head=dict(
            type='FCNMaskHead',
            num_convs=4,
            in_channels=256,
            conv_out_channels=256,
            num_classes=8,
            loss_mask=dict(
                type='CrossEntropyLoss', use_mask=True, loss_weight=1.0)))
 

预训练的模型权重除最后的预测层不会加载预用模型权值,其他均会被加载。

更改数据

仿照VOC, WIDER FACE, COCO and Cityscapes 数据类重写自己的数据整合方式。改一下数据类的名字即可。具体改写细节见下小节。

改写训练schedule

优化器,训练超参数等的修改。

 # optimizer
 # lr is set for a batch size of 8
 optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)
 optimizer_config = dict(grad_clip=None)
 # learning policy
 lr_config = dict(
  policy='step',
  warmup='linear',
  warmup_iters=500,
  warmup_ratio=0.001,
  # [7] yields higher performance than [6]
  step=[7])
 total_epochs = 8  # actual epoch = 8 * 8 = 64
 log_config = dict(interval=100)

使用预训练模型

load_from = 'https://s3.ap-northeast-2.amazonaws.com/open-mmlab/mmdetection/models/mask_rcnn_r50_fpn_2x_20181010-41d35c05.pth' 

0.2.3 增加新数据类

转成公用格式

最简单的方式就是将自己的数据脚本转换成coco或者voc格式。然后更改配置文件中的数据信息。比如coco格式, 在configs/my_custom_config. py中有:

...
 # dataset settings
 dataset_type = 'CocoDataset'
 classes = ('a', 'b', 'c', 'd', 'e')    # 自己的五类名字
 ...
 data = dict(
  samples_per_gpu=2,
  workers_per_gpu=2,
  train=dict(
   type=dataset_type,
   classes=classes,
   ann_file='path/to/your/train/data',
   ...),
  val=dict(
   type=dataset_type,
   classes=classes,
   ann_file='path/to/your/val/data',
   ...),
  test=dict(
   type=dataset_type,
   classes=classes,
   ann_file='path/to/your/test/data',
   ...))
 ...
 

中间格式

mmdet提供了和coco,voc等兼容的中间格式:

[
  {
   'filename': 'a.jpg',
   'width': 1280,
   'height': 720,
   'ann': {
    'bboxes': <np.ndarray, float32> (n, 4),
    'labels': <np.ndarray, int64> (n, ),
    'bboxes_ignore': <np.ndarray, float32> (k, 4),
    'labels_ignore': <np.ndarray, int64> (k, ) (optional field)
   }
  },
  ...
 ]
 

使用方式:

  • 在线转换: 写一个继承自CustomDataset的类,并重写load_annotations(self, ann_file) 和 get_ann_info(self,idx)两个方法。参考CocoDataset,VOCDataset。
  • 离线转换: 将标注文件转成中间格式,保存成pickle或json文件,参看pascal_voc.py,然后调用CustomDataset即可。

一个例子, 假设标注文件格式如下

#
000001.jpg
1280 720
2
10 20 40 60 1
20 40 50 60 2
#
000002.jpg
1280 720
3
50 20 40 60 2
20 40 30 45 2
30 40 50 60 3

我们可以写一个继承自CustomDataset的新类如下:

import mmcv
import numpy as np

from .builder import DATASETS
from .custom import CustomDataset

@DATASETS.register_module()
class MyDataset(CustomDataset):

 CLASSES = ('person', 'bicycle', 'car', 'motorcycle')

 def load_annotations(self, ann_file):
  ann_list = mmcv.list_from_file(ann_file)

  data_infos = []
  for i, ann_line in enumerate(ann_list):
   if ann_line != '#':
    continue

   img_shape = ann_list[i + 2].split(' ')
   width = int(img_shape[0])
   height = int(img_shape[1])
   bbox_number = int(ann_list[i + 3])

   anns = ann_line.split(' ')
   bboxes = []
   labels = []
   for anns in ann_list[i + 4:i + 4 + bbox_number]:
    bboxes.append([float(ann) for ann in anns[:4]])
    labels.append(int(anns[4]))

   data_infos.append(
    dict(
     filename=ann_list[i + 1],
     width=width,
     height=height,
     ann=dict(
      bboxes=np.array(bboxes).astype(np.float32),
      labels=np.array(labels).astype(np.int64))
    ))

  return data_infos

 def get_ann_info(self, idx):
  return self.data_infos[idx]['ann']

# 配置文件做如下更改:

dataset_A_train = dict(
    type='MyDataset',
    ann_file = 'image_list.txt',
    pipeline=train_pipeline
)

数据合并,Repeat或Concatemate,顾名思义,将同一种数据重复多次,或不同数据concate成一个更大的数据。

如果你只想训练某数据的指定类别,只需要做如下改动:

classes = ('person', 'bicycle', 'car')
# classes = 'path/to/classes.txt'  # 或者类别从文件中读取
data = dict(
 train=dict(classes=classes),
 val=dict(classes=classes),
 test=dict(classes=classes))

0.2.4 自定义数据管道

经典的数据管道如下:

Figure 3: data pipeline

蓝色块为管道算子,一个算子为一个数据增强算法,从左到右,依次字典进,字典出。关于数据结构,可参考第二节数据处理。这里绿色为算子作用后新增的keys,橙色为算子作用与已有keys的values后的更新标记。

扩展pipelines

# 1.实现新增强函数 my\_pipeline.py
# 和pytorch原始transforms中的增强方式一样,实现__call__方法的类即可
from mmdet.datasets import PIPELINES

@PIPELINES.register_module()
class MyTransform:

    def __call__(self, results):    # 输入的是mmdet设定的字典格式
        results['dummy'] = True
  return results
  
# 2. 导入新类.
from .my_pipeline import MyTransform

# 3. 配置文件调用

img_norm_cfg = dict(
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
train_pipeline = [
    dict(type='LoadImageFromFile'),
    dict(type='LoadAnnotations', with_bbox=True),
    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
    dict(type='RandomFlip', flip_ratio=0.5),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='Pad', size_divisor=32),
    dict(type='MyTransform'),
    dict(type='DefaultFormatBundle'),
    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),
]

0.2.5 增加新模块

每一个组件都如上小节所给的三部曲,实现,导入,修改配置文件。每一步,mmdet中都有一些范例,分别看一下,就能实现改写。

优化器

一些模型可能需要对某部分参数做特殊优化处理,比如批归一化层的权重衰减。我们可以通过自定义优化器构造函数来进行细粒度参数调优。

from mmcv.utils import build_from_cfg

from mmdet.core.optimizer import OPTIMIZER_BUILDERS, OPTIMIZERS
from mmdet.utils import get_root_logger
from .cocktail_optimizer import CocktailOptimizer

@OPTIMIZER_BUILDERS.register_module
class CocktailOptimizerConstructor(object):

 def __init__(self, optimizer_cfg, paramwise_cfg=None):

 def __call__(self, model):

  return my_optimizer
 

开发新组件

  • backbone,参考MobilenetV2
  • neck, 参考PAFPN
  • head 参考Retinaface
  • roi extractor,参考 DCN ROIAlign

关于head相关组件,核心的点我认为在于数据的流向。基础变换层由forward函数得到变换的结果,怎么放到Loss中去,其中所涉及到的数据细节操作,是比较关键的。mmdet中会在head模块中实现对应的loss函数,最终被汇集到检测模型的loss中去。分别调用各自的loss.step,进行权重更新。

这里贴一个新增loss样例:

# 1.在 mmdet/models/losses/my_loss.py实现新的box回归函数
import torch
import torch.nn as nn

from ..builder import LOSSES
from .utils import weighted_loss

@weighted_loss    # 加权损失函数,可参考损失函数章节
def my_loss(pred, target):
 assert pred.size() == target.size() and target.numel() > 0
 loss = torch.abs(pred - target)
 return loss

@LOSSES.register_module
class MyLoss(nn.Module):

 def __init__(self, reduction='mean', loss_weight=1.0):
  super(MyLoss, self).__init__()
  self.reduction = reduction
  self.loss_weight = loss_weight

 def forward(self,
    pred,
    target,
    weight=None,
    avg_factor=None,
    reduction_override=None):
  assert reduction_override in (None, 'none', 'mean', 'sum')
  reduction = (
   reduction_override if reduction_override else self.reduction)
  loss_bbox = self.loss_weight * my_loss(
   pred, target, weight, reduction=reduction, avg_factor=avg_factor)

# 2.然后在mmdet/models/losses/__init__.py.中注册
from .my_loss import MyLoss, my_loss

# 3. 配置文件使用
loss_bbox=dict(type='MyLoss', loss_weight=1.0))

0.2.6 1.x模型升级到2.0

执行脚本 tools/upgrade_model_version.py。可能有小于 1%的绝对AP减小,具体可参见configs/legacy。

0.2.7 2.0和1.x的不同之处

主要有四点不同:坐标系,基础代码约定,训练超参数,模块设计。

坐标系

新坐标系与detectron2(https://github.com/facebookresearch/detectron2/)一致. treats the center of the most left-top pixel as (0, 0) rather than the left-top corner of that pixel. 这句话的意思是将

bbox = [x1, y1, x1 + w - 1, y1 + h - 1]

改为

bbox = [x1, y1, x1 + w, y1 + h]

,这样更加自然和精确(假设长或宽为1,则box退缩为点或线,这是有问题的),同理xyxy2xywh的长宽就不在+1了,生成的anhor的中心偏移也不在是0.5而是0了。与此相关的改动有Box的编解码,与iou计算相关的nms,assinger。另外,现在的坐标为float,1.xx为int,与此相关的有ahchor与特征网格的中心对齐问题,这对anchor-based的方法在性能上有一定影响(变好),ROIAlign也能更好的对齐,mask cropping and pasting更精准, 利用新的RoIAlign 去crop masktargets,会得到更好的结果,因为没有取整等误差了,而且在训练上也有 0.1s/iter的速度提升(少了取整操作)。

Codebase Conventions

  • 类别设定,1.x中0为背景,
[1, k]

k

类对象,2.0中

k

为背景,

[0, k-1]

k

类对象。

  • bbox分配方案就低质量分配上得到了改进。支持了更多长宽尺度输入,以上均有微弱性能提升。
  • 配置名称约定改动为
[model]\_(model setting)\_[backbone]\_[neck]\_(normsetting)\_(misc)\_(gpu x batch)\_[schedule]\_[dataset].py

训练超参数

一些训练参数的优化

  • nms后的rpn的proposals从2000改为1000,(nms_post=1000, max_ num=1000),mask, bbox AP有 0.2%的提升。
  • Mask, Faster R-CNN框回归损失函数Smooth L1 改为L1带来 0.6%的提升,Cascade R-CNN and HTC保持原样。
  • RoIAlign layer 采样数设置为0,0.2%提升。
  • 默认设置不在使用梯度截断,这样训练更快,但RepPoints保持是为了训练稳定以及更好的结果。
  • 默认的warmup ratio 从
1/3

改为

0.001

,这样更平滑,同时也是因为去掉了梯度截断。

0.2.8 版本变化补充

2.0相对于1.x版本,从代码上主要有这些改变:

  • core内部做了一些改写,比如anchor中的tagret移走了,增加了分割数据结构,抽象了bbox的编解码,新增iou_calculators,看起来evaluation中的bbox_overlaps不是最优的。
  • 数据类做了更彻底的分解,将1.x版本的loader中的sample分离成和pipeline同级别,data_loader放到数据类中的builder,结构更清晰。
  • models中anchor_heads改为dense_heads;bbox_heads, mask_heads等two stage 相关的合并到roi_heads中(和2思想一致)
  • 所有registry.py均合并到builder.py中,更为简洁。
  • necks增加了pafpn, detectors增加了fsaf,core/mask
  • 配置文件采用了组合继承的方式。
  • 优化了训练,测试流程,速度提升明显,版本支持pytorch1.5,1.2以下均不在支持,定制化模块做了优化。

本文分享自微信公众号 - GiantPandaCV(BBuf233),作者:Sisyphes

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 目标检测算法之AAAI2019 Oral论文GHM Loss

    这篇论文仍然是瞄准了One-Stage目标检测算法中的正负样本不均衡问题,上周我们介绍He Kaiming等人提出的Focal Loss,推文地址如下:http...

    BBuf
  • 基于Kaggle DeepFake比赛的代码实战

    本文使用Kaggle的Deepfake比赛数据集,使用CNN+LSTM架构,对视频帧做二分类,该项目部署在百度的aistudio上进行训练。

    BBuf
  • OpenCV图像处理专栏十八 | 手动构造Sobel算子完成边缘检测

    众所周知,在传统的图像边缘检测算法中,最常用的一种算法是利用Sobel算子完成的。Sobel算子一共有个,一个是检测水平边缘的算子,另一个是检测垂直边缘的算子。

    BBuf
  • AXI协议

    AXI(Advanced eXtensible Interface)是一种总协议,该协议是ARM公司提出的AMBA(Advanced Microcontrol...

    anytao
  • 社会工程学攻击名词解释

         社会工程学(Social Engineering),一种通过对受害者心理弱点、本能反应、好奇心、信任、贪婪等心理陷阱进行诸如欺骗、伤害等危害手段,取得...

    周俊辉
  • 超越数据湖和数据仓库的新范式:LakeHouse

    在Databricks的过去几年中,我们看到了一种新的数据管理范式,该范式出现在许多客户和案例中:LakeHouse。在这篇文章中,我们将描述这种新范式及其相对...

    大数据技术架构
  • python函数式编程-装饰器

    yaohong
  • 十个mysql语句的优化方法

    三哥
  • 使用SolrJ客户端管理SolrCloud(Solr集群)

    注意:集群中使用的配置文件是zookeeper统一管理的配置文件。如果修改了schema.xml配置文件,使用zkCli.sh脚本命令重新上传conf目录即可,...

    别先生
  • 二进制中1的个数

    解析:如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边...

    用户3003813

扫码关注云+社区

领取腾讯云代金券