Post

mmdet based faster rcnn解析

mmdet based faster rcnn解析

Model 组件

Backbone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
model = dict(
    type='FasterRCNN',
    pretrained='torchvision://resnet50',  ## 使用 pytorch 提供的在 imagenet 上面训练过的权重作为预训练权重
    # 骨架网络类名
    backbone=dict(
        # 表示使用 ResNet50
        type='ResNet',
        depth=50,
        # ResNet 系列包括 stem+ 4个 stage 输出
        num_stages=4,
        # 表示本模块输出的特征图索引,(0, 1, 2, 3),表示4个 stage 输出都需要,
        # 其 stride 为 (4,8,16,32),channel 为 (256, 512, 1024, 2048)
        out_indices=(0, 1, 2, 3),
        # 表示固定 stem 加上第一个 stage 的权重,不进行训练
        frozen_stages=1,  # 具体参见文章后面备注
        # 所有的 BN 层的可学习参数都不需要梯度,也就不会进行参数更新
        norm_cfg=dict(type='BN', requires_grad=True),
        # backbone 所有的 BN 层的均值和方差都直接采用全局预训练值,不进行更新,控制整个backbone归一化算子是否需要编程eval模式
        norm_eval=True,
        # 默认采用 pytorch 模式
        style='pytorch'),

[!NOTE] 虽然resnet常规来说是有5层的,但是一般第一层,也就是第一个卷积是没人用的

关于style说明

style='caffe' 和 style='pytorch' 的差别就在 Bottleneck 模块中

Bottleneck 是标准的 1x1-3x3-1x1 结构,考虑 stride=2 下采样的场景,caffe 模式下,stride 参数放置在第一个 1x1 卷积上,而 Pyorch 模式下,stride 放在第二个 3x3 卷积上:

出现两种模式的原因是因为 ResNet 本身就有不同的实现,torchvision 的 resnet 和早期 release 的 resnet 版本不一样,使得目标检测框架在使用 Backbone 的时候有两种不同的配置,不过目前新网络都是采用 PyTorch 模式

FPN

1
2
3
4
5
6
7
8
9
10
neck=dict(
        type='FPN',
        # ResNet 模块输出的4个尺度特征图通道数
        in_channels=[256, 512, 1024, 2048],
        # FPN 输出的每个尺度输出特征图通道
        out_channels=256,
        # FPN 输出特征图个数
        num_outs=5),

详细流程是:

  • 将c2 c3 c4 c5 4 个特征图全部经过各自 1x1 卷积进行通道变换变成 m2~m5,输出通道统一为 256
  • 从 m5 开始,先进行 2 倍最近邻上采样,然后和 m4 进行 add 操作,得到新的 m4
  • 将新 m4 进行 2 倍最近邻上采样,然后和 m3 进行 add 操作,得到新的 m3
  • 将新 m3 进行 2 倍最近邻上采样,然后和 m2 进行 add 操作,得到新的 m2
  • 对 m5 和新的融合后的 m4 ~ m2,都进行各自的 3x3 卷积,得到 4 个尺度的最终输出 p5 ~ p2
  • 将 c5 进行 3x3 且 stride=2 的卷积操作,得到 p6,目的是提供一个感受野非常大的特征图,有利于检测超大物体

故 FPN 模块实现了c2 ~ c5 4 个特征图输入,p2 ~ p6 5个特征图输出,其 strides = (4,8,16,32,64)。

[!NOTE] 最后一个层通常没有indice,叫做pooling

RPN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rpn_head=dict(
        type='RPNHead',  # RPN网络类型
        in_channels=256,  # RPN网络的输入通道数,也是FPN 层输出特征图通道数
        feat_channels=256,  # 中间特征图通道数
        anchor_scales=[8],  # 生成的anchor的baselen,baselen = sqrt(w*h),w和h为anchor的宽和高
        anchor_ratios=[0.5, 1.0, 2.0],  # 每个特征图有 3 个高宽比例
        anchor_strides=[4, 8, 16, 32, 64],  # 在每个特征层上的anchor的步长(对应于原图)
        target_means=[.0, .0, .0, .0],     # 往下看
        target_stds=[1.0, 1.0, 1.0, 1.0],
        loss_cls=dict(
            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
        loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0)),

模型可训练结构比较简单 一个卷积进行特征通道变换,加上两个输出分支即可。models/anchor_heads/rpn_head.py 具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
def _init_layers(self):
        # 特征通道变换
        self.rpn_conv = nn.Conv2d(
            self.in_channels, self.feat_channels, 3, padding=1)
        # 分类分支,类别固定是2,表示前后景分类
        # 并且由于 cls loss 是 bce,故实际上 self.cls_out_channels=1
        self.rpn_cls = nn.Conv2d(self.feat_channels,
                                 self.num_anchors * self.cls_out_channels, 1)
        # 回归分支,固定输出4个数值,表示基于 anchor 的变换值
        self.rpn_reg = nn.Conv2d(self.feat_channels, self.num_anchors * 4, 1)

BBox Assigner

相比不包括 FPN 的 Faster R-CNN 算法,由于其 RPN Head 是多尺度特征图,为了适应这种变化,anchor 设置进行了适当修改,FPN 输出的多尺度信息可以帮助区分不同大小物体识别问题,每一层就不再需要不包括 FPN 的 Faster R-CNN 算法那么多 anchor 了。

因此,在train cfg中设置了一系列的操作
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
rpn=dict(
            assigner=dict(
                type='MaxIoUAssigner', # RPN网络的正负样本划分
                pos_iou_thr=0.7,       # 正样本的iou阈值
                neg_iou_thr=0.3,       # 负样本的iou阈值
                min_pos_iou=0.3,       
                match_low_quality=True, # 忽略bbox的阈值,当ground truth中包含需要忽略的bbox时使用,-1表示不忽略
                ignore_iof_thr=-1),
            sampler=dict(
	            # 随机采样
                type='RandomSampler',
                # 正负样本数量
                num=256,
                # 正样本比例
                pos_fraction=0.5,
                # 正负样本比例,负样本采样上限
                neg_pos_ub=-1,
                # 把gt也作为proposal
                add_gt_as_proposals=False),
            # 允许bbox向外扩一定像素
            allowed_border=-1,
            # 正样本权重,-1表示不改变
            pos_weight=-1,
            debug=False),

核心参数的具体含义是:

  • num = 256 表示采样后每张图片的样本总数,pos_fraction 表示其中的正样本比例,具体是正样本采样 128 个,那么理论上负样本采样也是 128 个
  • neg_pos_ub 表示负和正样本比例上限,用于确定负样本采样个数上界,例如打算采样 1000 个样本,正样本打算采样 500 个,但是可能正样本才 200 个,那么正样本实际上只能采样 200 个,如果设置 neg_pos_ub=-1 那么就会对负样本采样 800 个,用于凑足 1000 个,但是如果设置了 neg_pos_ub 比例,例如 1.5,那么负样本最多采样 200x1.5=300 个,最终返回的样本实际上不够 1000 个,默认情况 neg_pos_ub=-1
  • add_gt_as_proposals=True 是防止高质量正样本太少而加入的,可以保证前期收敛更快、更稳定,属于训练技巧,在 RPN 模块设置为 False,主要用于 R-CNN,因为前期 RPN 提供的正样本不够,可能会导致训练不稳定或者前期收敛慢的问题。

其实现过程比较简单,如下所示:

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
30
if self.add_gt_as_proposals and len(gt_bboxes) > 0:
    # 增加 gt 作为 proposals
    bboxes = torch.cat([gt_bboxes, bboxes], dim=0)
    assign_result.add_gt_(gt_labels)

# 计算正样本个数
num_expected_pos = int(self.num * self.pos_fraction)
# 正样本随机采样
pos_inds = self.pos_sampler._sample_pos(
    assign_result, num_expected_pos, bboxes=bboxes, **kwargs)
# 去重
pos_inds = pos_inds.unique()

# 计算负样本数
num_sampled_pos = pos_inds.numel()
num_expected_neg = self.num - num_sampled_pos
if self.neg_pos_ub >= 0:
   # 计算负样本个数上限
    _pos = max(1, num_sampled_pos)
    neg_upper_bound = int(self.neg_pos_ub * _pos)
    if num_expected_neg > neg_upper_bound:
        num_expected_neg = neg_upper_bound

# 负样本随机采样
neg_inds = self.neg_sampler._sample_neg(
    assign_result, num_expected_neg, bboxes=bboxes, **kwargs)
# 去重  
neg_inds = neg_inds.unique()

而具体的随机采样函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 随机采样正样本
def _sample_pos(self, assign_result, num_expected, **kwargs):
    """Randomly sample some positive samples."""
    pos_inds = torch.nonzero(assign_result.gt_inds > 0, as_tuple=False)
    if pos_inds.numel() != 0:
        pos_inds = pos_inds.squeeze(1)
    if pos_inds.numel() <= num_expected:
        return pos_inds
    else:
        return self.random_choice(pos_inds, num_expected)

# 随机采样负样本
def _sample_neg(self, assign_result, num_expected, **kwargs):
    """Randomly sample some negative samples."""
    neg_inds = torch.nonzero(assign_result.gt_inds == 0, as_tuple=False)
    if neg_inds.numel() != 0:
        neg_inds = neg_inds.squeeze(1)
    if len(neg_inds) <= num_expected:
        return neg_inds
    else:
        return self.random_choice(neg_inds, num_expected)

经过随机采样函数后,可以有效控制 RPN 网络计算 loss 时正负样本平衡问题。

bbox_coder

在 anchor-based 算法中,为了利用 anchor 信息进行更快更好的收敛,一般会对 head 输出的 bbox 分支 4 个值进行编解码操作,作用有两个:

  1. 更好的平衡分类和回归分支 loss,以及平衡 bbox 四个预测值的 loss
  2. 训练过程中引入 anchor 信息,加快收敛

RetinaNet 采用的编解码函数是主流的 DeltaXYWHBBoxCoder,其配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rpn_head=dict(
        type='RPNHead',
        in_channels=256,
        feat_channels=256,
        anchor_generator=dict(
            type='AnchorGenerator',
            scales=[8],
            ratios=[0.5, 1.0, 2.0],
            strides=[4, 8, 16, 32, 64]),
        bbox_coder=dict(
            type='DeltaXYWHBBoxCoder',
            target_means=[.0, .0, .0, .0],
            target_stds=[1.0, 1.0, 1.0, 1.0]),
        loss_cls=dict(
            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
        loss_bbox=dict(type='L1Loss', loss_weight=1.0)),

target_means 和 target_stds 相当于对 bbox 回归的 4 个 $t_x\(t_y\)t_w\(t_h$ 进行变换。在不考虑 target_means 和 target_stds 情况下,其编码公式如下:\) t_x^* = \frac{x-x_a}{w_a}, t_y^* = \frac{y-y_a}{h_a};
t_w^* = \log{\left(\frac{w}{w_a} \right)}, t_h^*=\log \left(\frac{h}{ h_a}\right) $$

编码过程

1
2
3
4
5
6
7
8
9
10
11
dx = (gx - px) / pw
dy = (gy - py) / ph
dw = torch.log(gw / pw)
dh = torch.log(gh / ph)
deltas = torch.stack([dx, dy, dw, dh], dim=-1)
# 最后减掉均值,处于标准差
means = deltas.new_tensor(means).unsqueeze(0)
stds = deltas.new_tensor(stds).unsqueeze(0)
deltas = deltas.sub_(means).div_(stds)

解码过程是编码过程的反向,比较容易理解,其核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 先乘上 std,加上 mean
means = deltas.new_tensor(means).view(1, -1).repeat(1, deltas.size(1) // 4)
stds = deltas.new_tensor(stds).view(1, -1).repeat(1, deltas.size(1) // 4)
denorm_deltas = deltas * stds + means
dx = denorm_deltas[:, 0::4]
dy = denorm_deltas[:, 1::4]
dw = denorm_deltas[:, 2::4]
dh = denorm_deltas[:, 3::4]
# wh 解码
gw = pw * dw.exp()
gh = ph * dh.exp()
# 中心点 xy 解码
gx = px + pw * dx
gy = py + ph * dy
# 得到 x1y1x2y2 的 gt bbox 预测坐标
x1 = gx - gw * 0.5
y1 = gy - gh * 0.5
x2 = gx + gw * 0.5
y2 = gy + gh * 0.5

Loss

1
2
3
4
5
loss_cls=dict(
    type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
loss_bbox=dict(type='L1Loss', loss_weight=1.0)),

RPN 采用的 loss 是常用的 ce loss 和 l1 loss,不需要详细描述。

ROIHead

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
bbox_roi_extractor=dict(
        type='SingleRoIExtractor',                                   # RoIExtractor类型
        # ROI具体参数:ROI类型为ROIalign,输出尺寸为7,sample数为2
        roi_layer=dict(type='RoIAlign', out_size=7, sample_num=2),  
        out_channels=256,                                            # 输出通道数
        featmap_strides=[4, 8, 16, 32]),                             # 特征图的步长
    bbox_head=dict(
        # 2 个共享 FC 模块
        type='SharedFCBBoxHead',
        num_fcs=2,
        # 输入通道数,相等于 FPN 输出通道
        in_channels=256,
       # 输出通道数
        fc_out_channels=1024,.
        # RoIAlign 或 RoIPool 输出的特征图大小
        roi_feat_size=7,
        # COCO数据集类别个数,这里是因为版本问题,加了一个背景,在2.0不在+1
        num_classes=80,
        target_means=[0., 0., 0., 0.],
        target_stds=[0.1, 0.1, 0.2, 0.2],
        # 是否采用class_agnostic的方式来预测,class_agnostic表示输出bbox时只考虑其是否为前景,后续分类的时候再根据该bbox在网络中的类别得分来分类,也就是说一个框可以对应多个类别
        # 影响 bbox 分支的通道数,True 表示 4 通道输出,False 表示 4×num_classes 通道输出
        reg_class_agnostic=False,
        #  RPN 采用的 loss 是常用的分类 ce loss 和回归 l1 loss
        loss_cls=dict(
            type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
        loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0)))

这里多了一个ROI extractor

  1. RPN 层输出每张图片最多 nms_post 个候选框,故 R-CNN 输入 shape 为 (batch, nms_post, 4),4 表示 RoI 坐标。
  2. 利用 RoI 重映射规则,将 nms_post 个候选框映射到 FPN 输出的不同特征图上,提取对应的特征图,然后利用插值思想将其变成指定的固定大小输出,输出 shape 为 (batch, nms_post, 256, roi_feat_size, roi_feat_size),其中 256 是 FPN 层输出特征图通道大小,roi_feat_size 一般取 7。上述步骤即为 RoIAlign 或者 RoIPool 计算过程。
  3. 将 (batch, nms_post, 256, roi_feat_size, roi_feat_size) 数据拉伸为 (batch*nms_post, 256*roi_feat_size*roi_feat_size),转化为 FC 可以支持的格式, 然后应用两次共享卷积,输出 shape 为 (batch*nms_post, 1024)
  4. 将 (batch*nms_post, 1024) 分成分类和回归分支,分类分支输出 (batch*nms_post, num_classes+1), 回归分支输出 (batch*nms_post, 4*num_class)

\(k=\lfloor k_0 + log_2(\sqrt{wh}/224)\rfloor\) 上述公式中 $k_0$=4,通过公式可以算出 pk,具体是:

  • wh>=448x448,则分配给 p5
  • wh<448x448 并且 wh>=224x224,则分配给 p4
  • wh<224x224 并且 wh>=112x112,则分配给 p3
  • 其余分配给 p2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def map_roi_levels(self, rois, num_levels):
    """Map rois to corresponding feature levels by scales.

    - scale < finest_scale * 2: level 0
    - finest_scale * 2 <= scale < finest_scale * 4: level 1
    - finest_scale * 4 <= scale < finest_scale * 8: level 2
     - scale >= finest_scale * 8: level 3
    """
    scale = torch.sqrt(
        (rois[:, 3] - rois[:, 1]) * (rois[:, 4] - rois[:, 2]))
    target_lvls = torch.floor(torch.log2(scale / self.finest_scale + 1e-6))
    target_lvls = target_lvls.clamp(min=0, max=num_levels - 1).long()
    return target_lvls

其中 finest_scale=56,num_level=5

然后经过两层分类和回归共享全连接层 FC,最后是各自的输出头,其 forward 逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if self.num_shared_fcs > 0:
    x = x.flatten(1)
    # 两层共享 FC
    for fc in self.shared_fcs:
        x = self.relu(fc(x))

x_cls = x
x_reg = x

# 不共享的分类和回归分支输出
cls_score = self.fc_cls(x_cls) if self.with_cls else None
bbox_pred = self.fc_reg(x_reg) if self.with_reg else None
return cls_score, bbox_pred

最终输出分类和回归预测结果。相比于目前主流的全卷积模型,Faster R-CNN 的 R-CNN 模块依然采用的是全连接模式。

训练逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
rcnn=dict(
    assigner=dict(
        # 和 RPN 一样,正负样本定义参数不同
        type='MaxIoUAssigner',
        pos_iou_thr=0.5,
        neg_iou_thr=0.5,
        min_pos_iou=0.5,
        match_low_quality=False,
        ignore_iof_thr=-1),
    sampler=dict(
        # 和 RPN 一样,随机采样参数不同
        type='RandomSampler',
        num=512,
        pos_fraction=0.25,
        neg_pos_ub=-1,
        # True,RPN 中为 False
        add_gt_as_proposals=True)

理论上,BBox Assigner 和 BBox Sampler 逻辑可以放置在 (1) 公共部分 后面,因为其任务是输入每张图片的 nms_post 个候选框以及标注的 gt bbox 信息,然后计算每个候选框样本的正负样本属性,最后再进行随机采样尽量保证样本平衡。R-CNN的候选框对应了 RPN 阶段的 anchor,只不过 RPN 中的 anchor 是预设密集的,而 R-CNN 面对的 anchor 是动态稀疏的,RPN 阶段基于 anchor 进行分类回归对应于 R-CNN 阶段基于候选框进行分类回归,思想是完全一致的,故 Faster R-CNN 类算法叫做 two-stage,因此可以简化为 one-stage + RoI 区域特征提取 + one-stage

配置参数和 RPN 不同:

  • match_low_quality=False。为了避免出现低质量匹配情况(因为 two-stage 算法性能核心在于 R-CNN,RPN 主要保证高召回率,R-CNN 保证高精度),R-CNN 阶段禁用了允许低质量匹配设置
  • 3 个 iou_thr 设置都是 0.5,不存在忽略样本,这个参数在 Cascade R-CNN 论文中有详细说明,影响较大
  • add_gt_as_proposals=True。主要是克服刚开始 R-CNN 训练不稳定情况

整体逻辑

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
30
31
32
33
34
35
36
37
38
if self.with_bbox or self.with_mask:
    num_imgs = len(img_metas)
    sampling_results = []
    # 遍历每张图片,单独计算 BBox Assigner 和 BBox Sampler 
    for i in range(num_imgs):

        # proposal_list 是 RPN test 输出的候选框
        assign_result = self.bbox_assigner.assign(
            proposal_list[i], gt_bboxes[i], gt_bboxes_ignore[i],
            gt_labels[i])

        # 随机采样
        sampling_result = self.bbox_sampler.sample(
            assign_result,
            proposal_list[i],
            gt_bboxes[i],
            gt_labels[i],
            feats=[lvl_feat[i][None] for lvl_feat in x])
        sampling_results.append(sampling_result)

# 特征重映射+ RoI 区域特征提取+ 网络 forward + Loss 计算
losses = dict()
# bbox head forward and loss
if self.with_bbox:
    bbox_results = self._bbox_forward_train(x, sampling_results,
                                            gt_bboxes, gt_labels,
                                            img_metas)
    losses.update(bbox_results['loss_bbox'])

# mask head forward and loss
if self.with_mask:
    mask_results = self._mask_forward_train(x, sampling_results,
                                            bbox_results['bbox_feats'],
                                            gt_masks, img_metas)
    losses.update(mask_results['loss_mask'])
return losses

_bbox_forward_train 逻辑和 RPN 非常类似,只不过多了额外的 RoI 区域特征提取步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def _bbox_forward_train(self, x, sampling_results, gt_bboxes, gt_labels,
                        img_metas):
    rois = bbox2roi([res.bboxes for res in sampling_results])

    # 特征重映射+ RoI 特征提取+ 网络 forward
    bbox_results = self._bbox_forward(x, rois)

    # 计算每个样本对应的 target, bbox encoder 在内部进行
    bbox_targets = self.bbox_head.get_targets(sampling_results, gt_bboxes,
                                              gt_labels, self.train_cfg)

    # 计算 loss
    loss_bbox = self.bbox_head.loss(bbox_results['cls_score'],
                                    bbox_results['bbox_pred'], rois,
                                    *bbox_targets)
    bbox_results.update(loss_bbox=loss_bbox)
    return bbox_results

_bbox_forward 逻辑是 R-CNN 的重点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def _bbox_forward(self, x, rois):
    # 特征重映射+ RoI 区域特征提取,仅仅考虑前 num_inputs 个特征图
    bbox_feats = self.bbox_roi_extractor(
        x[:self.bbox_roi_extractor.num_inputs], rois)

    # 共享模块
    if self.with_shared_head:
        bbox_feats = self.shared_head(bbox_feats)

    # 独立分类和回归 head
    cls_score, bbox_pred = self.bbox_head(bbox_feats)

    bbox_results = dict(
        cls_score=cls_score, bbox_pred=bbox_pred, bbox_feats=bbox_feats)
    return bbox_results

测试逻辑

1
2
3
4
5
6
rcnn=dict(
    score_thr=0.05,
    nms=dict(type='nms', iou_threshold=0.5),
    max_per_img=100)

测试逻辑核心逻辑如下:

  • 公共逻辑部分输出 batch * nms_post 个候选框的分类和回归预测结果
  • 将所有预测结果按照 batch 维度进行切分,然后依据单张图片进行后处理,后处理逻辑为:先解码并还原为原图尺度;然后利用 score_thr 去除低分值预测;然后进行 NMS;最后保留最多 max_per_img 个结果

Dataset组件

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# dataset settings
dataset_type = 'CocoDataset'                # 数据集类型
data_root = 'data/coco/'                    # 数据集根目录
img_norm_cfg = dict(
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)   # 输入图像初始化,减去均值mean并处以方差std,to_rgb表示将bgr转为rgb
data = dict(
    imgs_per_gpu=2,                # 每个gpu计算的图像数量
    workers_per_gpu=2,             # 每个gpu分配的线程数
    train=dict(
        type=dataset_type,                                                 # 数据集类型
        ann_file=data_root + 'annotations/instances_train2017.json',       # 数据集annotation路径
        img_prefix=data_root + 'train2017/',                               # 数据集的图片路径
        img_scale=(1333, 800),                                             # 输入图像尺寸,最大边1333,最小边800
        img_norm_cfg=img_norm_cfg,                                         # 图像初始化参数
        size_divisor=32,                                                   # 对图像进行resize时的最小单位,32表示所有的图像都会被resize成32的倍数
        flip_ratio=0.5,                                                    # 图像的随机左右翻转的概率
        with_mask=False,                                                   # 训练时附带mask
        with_crowd=True,                                                   # 训练时附带difficult的样本
        with_label=True),                                                  # 训练时附带label
    val=dict(
        type=dataset_type,           
        ann_file=data_root + 'annotations/instances_val2017.json',         
        img_prefix=data_root + 'val2017/',   
        img_scale=(1333, 800),               
        img_norm_cfg=img_norm_cfg,           
        size_divisor=32,                     
        flip_ratio=0,                        
        with_mask=False,                     
        with_crowd=True,                     
        with_label=True),                    
    test=dict(
        type=dataset_type,                   
        ann_file=data_root + 'annotations/instances_val2017.json',         
        img_prefix=data_root + 'val2017/',   
        img_scale=(1333, 800),               
        img_norm_cfg=img_norm_cfg,          
        size_divisor=32,                    
        flip_ratio=0,                       
        with_mask=False,                    
        with_label=False,                   
        test_mode=True)) 

Optimizer

1
2
3
4
5
6
7
8
9
10
11
12
13
# optimizer
optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)   # 优化参数,lr为学习率,momentum为动量因子,weight_decay为权重衰减因子
optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))          # 梯度均衡参数
# learning policy
lr_config = dict(
    policy='step',                        # 优化策略
    warmup='linear',                      # 初始的学习率增加的策略,linear为线性增加
    warmup_iters=500,                     # 在初始的500次迭代中学习率逐渐增加
    warmup_ratio=1.0 / 3,                 # 起始的学习率
    step=[8, 11])                         # 在第8和11个epoch时降低学习率
checkpoint_config = dict(interval=1)      # 每1个epoch存储一次模型

Log控制器

1
2
3
4
5
6
7
8
9
10
# yapf:disable
log_config = dict(
    interval=50,                          # 每50个batch输出一次信息
    hooks=[
        dict(type='TextLoggerHook'),      # 控制台输出信息的风格
        # dict(type='TensorboardLoggerHook')
    ])
# yapf:enable

运行时环境(runtime)设置

1
2
3
4
5
6
7
8
9
10
# runtime settings
total_epochs = 12                               # 最大epoch数
dist_params = dict(backend='nccl')              # 分布式参数
log_level = 'INFO'                              # 输出信息的完整度级别
work_dir = './work_dirs/faster_rcnn_r50_fpn_1x' # log文件和模型文件存储路径
load_from = None                                # 加载模型的路径,None表示从预训练模型加载
resume_from = None                              # 恢复训练模型的路径
workflow = [('train', 1)]                       # 当前工作区名称

This post is licensed under CC BY 4.0 by the author.