Detectron2 中重思 BatchNorm 的 "Batch" 语义:Rethinking-BatchNorm 项目配置与源码深度解析
【免费下载链接】detectron2Detectron2 is a platform for object detection, segmentation and other visual recognition tasks.项目地址: https://gitcode.com/GitHub_Trending/de/detectron2
导读:BatchNorm 的效果高度依赖 batch 的统计口径——这里的 "batch" 到底应该指单卡上的 mini-batch、跨卡聚合的全局 batch,还是某个特征域(feature domain)自身的统计?本文以 Detectron2 仓库中 Rethinking-BatchNorm 项目为骨架,逐条解析其 6 份可复现论文《Rethinking "Batch" in BatchNorm》实验的 LazyConfig 配置文件与评测脚本,并结合detectron2/layers/batch_norm.py、detectron2/modeling/meta_arch/retinanet.py等源码说明底层机制。读完你将掌握:在 Mask R-CNN / RetinaNet 的 head 中切换 BN 语义(单卡 BN、batch 统计量、跨卡 shuffle、SyncBN、共享 BN、域特定 BN)的完整配置方法,以及如何用域特定统计量脚本复现论文 Table 5 的高精度结果。
一、背景:BatchNorm 的 "Batch" 到底指什么
BatchNorm 在训练时用当前 batch 的均值/方差归一化激活,并在推理时切换为滑动平均得到的全局统计量。但在分布式训练、多尺度特征图等场景下,"当前 batch" 的边界是模糊的:
- 单卡 mini-batch:batch 只包含当前 GPU 上的样本,batch 较小时统计量噪声大;
- 跨卡全局 batch:SyncBatchNorm 把多卡统计量聚合后再归一化,等价于扩大了 batch;
- 特征域(domain)batch:同一层可能被多个输入域复用(如 RetinaNet 的 5 个特征金字塔层共享同一个 head),每个域应维护自己的测试期统计量。
论文 Rethinking "Batch" in BatchNorm 系统研究了这些问题,而本仓库 projects/Rethinking-BatchNorm 提供了一套 LazyConfig 实验配置,用于在 Detectron2 上复现论文中的 Mask R-CNN(Table 3、Figure 9、Table 6)与 RetinaNet(Table 5)检测实验。
二、项目结构与快速上手
2.1 目录构成
projects/Rethinking-BatchNorm/ ├── configs/ │ ├── mask_rcnn_BNhead.py # Mask R-CNN:head 中使用 BatchNorm │ ├── mask_rcnn_BNhead_batch_stats.py # 推理时改用 batch 统计量的 BN │ ├── mask_rcnn_BNhead_shuffle.py # 跨 GPU 打乱 head 输入 │ ├── mask_rcnn_SyncBNhead.py # head 中使用 SyncBN │ ├── retinanet_SyncBNhead.py # RetinaNet head 使用 SyncBN │ └── retinanet_SyncBNhead_SharedTraining.py # 5 个特征层共享归一化统计 ├── retinanet-eval-domain-specific.py # 重算域特定统计量的评测脚本 └── README.md2.2 训练命令
所有配置都可以直接通过 Detectron2 的 LazyConfig 训练入口启动(按 README 中命令,在projects/Rethinking-BatchNorm/目录下执行):
../../tools/lazyconfig_train_net.py --config-file configs/X.py --num-gpus 8其中X.py换成任意一个配置文件名。等价地,你也可以在仓库根目录下执行:
python tools/lazyconfig_train_net.py \ --config-file projects/Rethinking-BatchNorm/configs/mask_rcnn_BNhead.py \ --num-gpus 8注意两点:
- 必须使用
tools/lazyconfig_train_net.py而非tools/plain_train_net.py:这些配置是 Python 形式的 LazyConfig(通过LazyConfig与L()惰性实例化),与 YACS 风格的 yaml 配置不兼容; --num-gpus 8并非可选:SyncBN、batch shuffle 等实验依赖torch.distributed多卡通信,单卡运行会导致统计口径与论文不一致。--num-gpus会被 engine/launch.py 解析并启动 DDP 训练。
所有配置都通过get_config继承仓库根目录 configs/common 下的公共组件(详见下文"公共配置继承"小节),因此每个文件都非常精简——这正是 LazyConfig 设计的复用模式。
三、Mask R-CNN 系列:head 中的 BatchNorm 实验
3.1 mask_rcnn_BNhead.py — head 中加入 BatchNorm(对应论文 Table 3)
该配置是后续三个 Mask R-CNN 变体的"基类"配置,全文如下:
from detectron2.model_zoo import get_config model = get_config("common/models/mask_rcnn_fpn.py").model model.backbone.bottom_up.freeze_at = 2 model.roi_heads.box_head.conv_norm = model.roi_heads.mask_head.conv_norm = "BN" # 4conv1fc head model.roi_heads.box_head.conv_dims = [256, 256, 256, 256] model.roi_heads.box_head.fc_dims = [1024] dataloader = get_config("common/data/coco.py").dataloader lr_multiplier = get_config("common/coco_schedule.py").lr_multiplier_3x optimizer = get_config("common/optim.py").SGD train = get_config("common/train.py").train train.init_checkpoint = "detectron2://ImageNetPretrained/MSRA/R-50.pkl" train.max_iter = 270000 # 3x for batchsize = 16逐项解读:
| 配置项 | 取值 | 含义 |
|---|---|---|
model.backbone.bottom_up.freeze_at = 2 | 2 | 冻结 ResNet 前两个 stage(stem + res2)的参数,只更新后面层。这与 common/models/mask_rcnn_fpn.py 中的默认设置一致 |
box_head.conv_norm = "BN" | 字符串"BN" | 在 box head 的 4 个卷积层后插入 BatchNorm。该字符串会被 box_head.py 中的get_norm(conv_norm, conv_dim)解析为nn.BatchNorm2d |
mask_head.conv_norm = "BN" | 字符串"BN" | 同理,在 mask head 的卷积层后插入 BN,见 mask_head.py 中conv_norm参数 |
box_head.conv_dims = [256,256,256,256]、fc_dims = [1024] | — | 4 层 256 通道卷积 + 1 层 1024 维全连接,即注释中的 "4conv1fc" 检测头结构 |
train.init_checkpoint | R-50.pkl | 从 Detectron2 的 ImageNet 预训练权重(MSRA R-50)初始化 |
train.max_iter = 270000 | 270k | 3x 训练计划(约 37 epoch,COCO),对应 common/coco_schedule.py 中的lr_multiplier_3x;注释明确说明该数值基于 total batch size = 16 |
源码佐证:在FastRCNNConvFCHead的构造中,每个卷积层通过get_norm(conv_norm, conv_dim)生成归一化层,且当指定了 norm 时卷积层不设 bias(bias=not conv_norm),因为 BN 的 affine 变换会吸收 bias——见 box_head.py。这就是把 head 从"无归一化"改成"带 BN"时只有一行配置的原因:检测头的卷积层早已支持 norm 参数。
3.2 mask_rcnn_BNhead_batch_stats.py — 推理期使用 batch 统计量
from torch.nn import BatchNorm2d from torch.nn import functional as F class BatchNormBatchStat(BatchNorm2d): """ BN that uses batch stat in inference """ def forward(self, input): if self.training: return super().forward(input) return F.batch_norm(input, None, None, self.weight, self.bias, True, 1.0, self.eps) # After training with the base config, it's sufficient to load its model with # this config only for inference -- because the training-time behavior is identical. from .mask_rcnn_BNhead import model, dataloader, lr_multiplier, optimizer, train model.roi_heads.box_head.conv_norm = model.roi_heads.mask_head.conv_norm = BatchNormBatchStat关键设计:
- 它定义了一个继承
BatchNorm2d的BatchNormBatchStat,只在推理期改变行为:训练时走标准 BN(super().forward),推理时调用F.batch_norm(input, None, None, ...),传入的running_mean/running_var均为None,且training=True(F.batch_norm的第四个布尔参数),即强制用当前 batch 的统计量而非滑动平均; - 因此,训练阶段与基类配置完全一致。只需用基类配置(
mask_rcnn_BNhead.py)训练好的模型,再套上本配置做推理即可——无需重新训练,这是文件末尾注释强调的要点; - 在 COCO 评测场景下,若推理 batch 足够大,用 batch 统计量替代全局统计量能反映"batch 大小对 BN 的影响",对应论文 Table 3 的消融设计。
3.3 mask_rcnn_BNhead_shuffle.py — 跨 GPU 打乱 head 输入(对应论文 Figure 9 / Table 6)
这个配置在源码层面最复杂,它用运行时动态构造子类的方式,给 head 的输入做跨卡随机打乱再还原:
import math import torch import torch.distributed as dist from detectron2.modeling.roi_heads import FastRCNNConvFCHead, MaskRCNNConvUpsampleHead from detectron2.utils import comm from fvcore.nn.distributed import differentiable_all_gather def concat_all_gather(input): bs_int = input.shape[0] size_list = comm.all_gather(bs_int) max_size = max(size_list) max_shape = (max_size,) + input.shape[1:] padded_input = input.new_zeros(max_shape) padded_input[:bs_int] = input all_inputs = differentiable_all_gather(padded_input) inputs = [x[:sz] for sz, x in zip(size_list, all_inputs)] return inputs, size_list def batch_shuffle(x): # gather from all gpus batch_size_this = x.shape[0] all_xs, batch_size_all = concat_all_gather(x) all_xs_concat = torch.cat(all_xs, dim=0) total_bs = sum(batch_size_all) rank = dist.get_rank() assert batch_size_all[rank] == batch_size_this idx_range = (sum(batch_size_all[:rank]), sum(batch_size_all[: rank + 1])) # random shuffle index idx_shuffle = torch.randperm(total_bs, device=x.device) # broadcast to all gpus dist.broadcast(idx_shuffle, src=0) # index for restoring idx_unshuffle = torch.argsort(idx_shuffle) # shuffled index for this gpu splits = torch.split(idx_shuffle, math.ceil(total_bs / dist.get_world_size())) if len(splits) > rank: idx_this = splits[rank] else: idx_this = idx_shuffle.new_zeros([0]) return all_xs_concat[idx_this], idx_unshuffle[idx_range[0] : idx_range[1]] def batch_unshuffle(x, idx_unshuffle): all_x, _ = concat_all_gather(x) x_gather = torch.cat(all_x, dim=0) return x_gather[idx_unshuffle] def wrap_shuffle(module_type, method): def new_method(self, x): if self.training: x, idx = batch_shuffle(x) x = getattr(module_type, method)(self, x) if self.training: x = batch_unshuffle(x, idx) return x return type(module_type.__name__ + "WithShuffle", (module_type,), {method: new_method}) from .mask_rcnn_BNhead import model, dataloader, lr_multiplier, optimizer, train model.roi_heads.box_head._target_ = wrap_shuffle(FastRCNNConvFCHead, "forward") model.roi_heads.mask_head._target_ = wrap_shuffle(MaskRCNNConvUpsampleHead, "layers")实现原理分三步:
concat_all_gather:用fvcore.nn.distributed.differentiable_all_gather做可微的全量收集(区别于torch.distributed.all_gather的不可微版本),并对每个 GPU 上的输入先 padding 到统一形状再收集、收集后按各自真实 batch 截断——保证反向传播能正确回传梯度;batch_shuffle:把所有 GPU 的特征拼成一个大 batch,用torch.randperm生成全局随机索引,通过dist.broadcast(..., src=0)保证所有 rank 拿到同一份乱序索引,随后每个 GPU 领取自己那份打乱后的数据;同时返回idx_unshuffle(逆置换索引)用于后续还原;batch_unshuffle:head 计算完之后,再次跨卡收集结果,用之前保存的逆索引把样本放回原始位置,保证 loss 计算时每个样本的预测与标签对齐。
wrap_shuffle是点睛之笔:它不是手写一个新的 head 类,而是用type()在运行时基于FastRCNNConvFCHead/MaskRCNNConvUpsampleHead动态生成一个XXXWithShuffle子类,仅覆盖forward(box head)或layers(mask head)方法,在调用原始方法前后插入 shuffle / unshuffle。这样只需要改 LazyConfig 的_target_,就能让已实例化的 head 结构整体替换。
实验意义:打乱 head 输入等价于让"每个 BN 层看到的 batch"不再局限于本卡的样本,而近似于更大的、随机采样的跨卡 batch,用于隔离"batch 内样本相关性"对 BN 统计量的影响——对应论文 Figure 9 与 Table 6 的讨论。
3.4 mask_rcnn_SyncBNhead.py — head 中使用 SyncBN(对应论文 Table 6)
from .mask_rcnn_BNhead import model, dataloader, lr_multiplier, optimizer, train model.roi_heads.box_head.conv_norm = model.roi_heads.mask_head.conv_norm = "SyncBN"在基类配置之上仅改一行:把conv_norm从"BN"换成"SyncBN"。get_norm会将其解析为NaiveSyncBatchNorm(见 layers/batch_norm.py),其内部通过dist.all_reduce汇总各卡统计量后统一归一化。README 指出该配置可匹配论文 Table 6 的结果。
对比小结(三份配置的差异都在 head 的归一化策略上,backbone 与训练计划完全一致):
配置 head 归一化方式 训练期统计范围 mask_rcnn_BNhead.py单卡 BN 本卡 mini-batch mask_rcnn_BNhead_batch_stats.py单卡 BN(推理期用 batch 统计) 本卡 mini-batch mask_rcnn_BNhead_shuffle.py单卡 BN + 跨卡输入打乱 打乱后的跨卡混合 batch mask_rcnn_SyncBNhead.pySyncBN 跨卡全局 batch
四、RetinaNet 系列:特征金字塔多域下的 BN 实验
RetinaNet 与 Mask R-CNN 的关键差异在于:5 个金字塔特征层(p3–p7)共享同一个检测头。同一组 BN 层被 5 个"输入域"轮流调用,于是出现"BN 的 batch 统计到底按哪个域算"的问题——这正是论文 Table 5 的实验主题。
4.1 retinanet_SyncBNhead.py — head 中使用 SyncBN(对应论文 Table 5 row 3)
from detectron2.model_zoo import get_config from torch import nn model = get_config("common/models/retinanet.py").model model.backbone.bottom_up.freeze_at = 2 # The head will overwrite string "SyncBN" to use domain-specific BN, so we # provide a class here to use shared BN in training. model.head.norm = nn.SyncBatchNorm2d dataloader = get_config("common/data/coco.py").dataloader lr_multiplier = get_config("common/coco_schedule.py").lr_multiplier_3x optimizer = get_config("common/optim.py").SGD train = get_config("common/train.py").train optimizer.lr = 0.01 train.init_checkpoint = "detectron2://ImageNetPretrained/MSRA/R-50.pkl" train.max_iter = 270000 # 3x for batchsize = 16这里有一个容易踩坑的细节:注释明确指出——如果给model.head.norm传字符串"SyncBN",RetinaNetHead会"劫持"它并自动改成域特定(domain-specific)BN。看源码 retinanet.py:
if norm == "BN" or norm == "SyncBN": logger.info( f"Using domain-specific {norm} in RetinaNetHead with len={self._num_features}." ) bn_class = nn.BatchNorm2d if norm == "BN" else nn.SyncBatchNorm def norm(c): return CycleBatchNormList( length=self._num_features, bn_class=bn_class, num_features=c )也就是说,RetinaNetHead认为在共享 head 场景下使用普通(shared)BN 效果不佳(源码中遇到 shared BN 还会给出 warning:"Shared BatchNorm may not work well in RetinaNetHead"),因此字符串"BN"/"SyncBN"一律被替换为CycleBatchNormList(域特定 BN,见第六节)。要强行使用 shared BN,就必须像本配置这样直接传类对象nn.SyncBatchNorm2d(非字符串不会被字符串分支捕获,会走get_norm正常解析)。
其余要点:
- 与 Mask R-CNN 系列一样冻结 backbone 前两层(
freeze_at = 2)、使用 COCO 数据与 3x 计划、270k 迭代; optimizer.lr = 0.01单独指定了 SGD 初始学习率(RetinaNet 训练常用 0.01 配合 batch size 16);- 该配置是"straightforward"的 SyncBN-in-head 实现,README 说明其匹配论文 Table 5 的 row 3。
4.2 retinanet_SyncBNhead_SharedTraining.py — 5 个特征层共享归一化(对应论文 Table 5 row 1)
from typing import List import torch from torch import Tensor, nn from detectron2.modeling.meta_arch.retinanet import RetinaNetHead def apply_sequential(inputs, modules): for mod in modules: if isinstance(mod, (nn.BatchNorm2d, nn.SyncBatchNorm)): # for BN layer, normalize all inputs together shapes = [i.shape for i in inputs] spatial_sizes = [s[2] * s[3] for s in shapes] x = [i.flatten(2) for i in inputs] x = torch.cat(x, dim=2).unsqueeze(3) x = mod(x).split(spatial_sizes, dim=2) inputs = [i.view(s) for s, i in zip(shapes, x)] else: inputs = [mod(i) for i in inputs] return inputs class RetinaNetHead_SharedTrainingBN(RetinaNetHead): def forward(self, features: List[Tensor]): logits = apply_sequential(features, list(self.cls_subnet) + [self.cls_score]) bbox_reg = apply_sequential(features, list(self.bbox_subnet) + [self.bbox_pred]) return logits, bbox_reg from .retinanet_SyncBNhead import model, dataloader, lr_multiplier, optimizer, train model.head._target_ = RetinaNetHead_SharedTrainingBN核心是apply_sequential函数:它逐个遍历 head 的子模块,遇到 BN 层时,把 5 个特征层先各自flatten(2)展平、沿通道维torch.cat拼接成一个大矩阵,再统一送入同一个 BN 层归一化,最后按各层的空间尺寸split还原。这样,一个 BN 层的 batch 统计量同时聚合了 5 个金字塔层、所有空间位置的样本——即"共享归一化统计"的训练方式。非 BN 层(卷积、激活等)则按常规逐层逐个特征图计算。
RetinaNetHead_SharedTrainingBN继承RetinaNetHead并重写forward,把 cls 子网和 bbox 子网(含各自的最终输出层)都换成apply_sequential处理。配置中通过model.head._target_ = RetinaNetHead_SharedTrainingBN替换 LazyConfig 的惰性目标类即可(继承自上一节的retinanet_SyncBNhead.py的全部训练设置)。README 说明该配置匹配论文 Table 5 的 row 1。
两种 RetinaNet 变体的差异一句话总结:
retinanet_SyncBNhead.py:head 中每层 BN 的统计来自单个特征层(域特定 BN,跨卡 Sync + 域内统计),传nn.SyncBatchNorm2d类以绕过字符串劫持;retinanet_SyncBNhead_SharedTraining.py:head 中每层 BN 的统计来自全部 5 个特征层拼接后的大 batch(共享 BN)。
五、域特定统计量评测:retinanet-eval-domain-specific.py(对应论文 Table 5 row 4 / row 2)
训练完成后,论文还讨论了一个评测问题:RetinaNet 的 head 被 5 个特征域复用,推理期每个域应该用自己域的滑动统计量。脚本 retinanet-eval-domain-specific.py 在加载 checkpoint 后重新计算域特定统计量再评测:
./retinanet-eval-domain-specific.py checkpoint.pth脚本核心逻辑:
from fvcore.nn.precise_bn import update_bn_stats from detectron2.checkpoint import DetectionCheckpointer from detectron2.config import LazyConfig, instantiate from detectron2.evaluation import inference_on_dataset from detectron2.layers import CycleBatchNormList ... cfg = LazyConfig.load_rel("configs/retinanet_SyncBNhead.py") model = cfg.model model.head.norm = lambda c: CycleBatchNormList(len(model.head_in_features), num_features=c) model = instantiate(model) model.cuda() DetectionCheckpointer(model).load(checkpoint) cfg.dataloader.train.total_batch_size = 8 with EventStorage(), torch.no_grad(): update_bn_stats(model, instantiate(cfg.dataloader.train), 500) inference_on_dataset(model, ...)要点:
- 强制域特定 BN:即使训练用的是 shared BN 配置(上一节的两个变体),评测时统一把
model.head.norm覆盖为CycleBatchNormList(length=5, ...)——为 5 个金字塔层各维护一份 BN 统计;len(model.head_in_features)即 p3–p7 的数量 5; - 重算统计量:
fvcore.nn.precise_bn.update_bn_stats(model, dataloader, 500)在 500 个训练 batch 上以前向(无梯度)模式重新累计每层每个域的均值/方差,替代训练时的滑动平均,使统计量与推理 batch 口径一致; - 小 batch 重算:
total_batch_size = 8用于控制重算统计量时的 batch 规模。
README 说明:对上述两个 RetinaNet 配置训练出的模型运行该脚本,结果可分别匹配论文 Table 5 的 row 4 与 row 2——这正是"训练用共享/域特定 BN,评测统一用域特定统计量"的组合带来的精度提升。
六、底层机制:CycleBatchNormList 与 get_norm
6.1 CycleBatchNormList —— 域特定 BN 的实现
上文反复出现的CycleBatchNormList定义在 detectron2/layers/batch_norm.py,其文档字符串明确写着 "Implement domain-specific BatchNorm by cycling",并直接引用论文 Sec 5.2:
class CycleBatchNormList(nn.ModuleList): """ Implement domain-specific BatchNorm by cycling. When a BatchNorm layer is used for multiple input domains or input features, it might need to maintain a separate test-time statistics for each domain. See Sec 5.2 in :paper:`rethinking-batchnorm`. This module implements it by using N separate BN layers and it cycles through them every time a forward() is called. NOTE: The caller of this module MUST guarantee to always call this module by multiple of N times. Otherwise its test-time statistics will be incorrect. """ def __init__(self, length: int, bn_class=nn.BatchNorm2d, **kwargs): self._affine = kwargs.pop("affine", True) super().__init__([bn_class(**kwargs, affine=False) for k in range(length)]) if self._affine: # shared affine, domain-specific BN channels = self[0].num_features self.weight = nn.Parameter(torch.ones(channels)) self.bias = nn.Parameter(torch.zeros(channels)) self._pos = 0 def forward(self, x): ret = selfself._pos self._pos = (self._pos + 1) % len(self) if self._affine: w = self.weight.reshape(1, -1, 1, 1) b = self.bias.reshape(1, -1, 1, 1) ...设计要点:
- N 个独立 BN 子层 + 循环调度:第 k 次调用使用第
k % N个子层,从而为第 k 个输入域维护独立的 running_mean / running_var; - 共享 affine 参数:
weight/bias不放在子 BN 里(子层affine=False),而是由外层共享一个可学习参数——即 "shared affine, domain-specific BN":所有域共享缩放平移,但各自维护统计量; - 调用纪律:注释特别警告调用方必须保证调用次数是 N 的整数倍,否则测试期统计量会错位——在 RetinaNet 中,
RetinaNetHead每次 forward 恰好处理_num_features(5)个特征层,天然满足该约束。
6.2 head 中 norm 的解析链路
- Mask R-CNN:box / mask head 的
conv_norm参数传入get_norm(conv_norm, conv_dim)生成归一化层(见 box_head.py、mask_head.py)。get_norm定义于 layers/batch_norm.py,支持"BN"、"SyncBN"、"FrozenBN"、"GN"等字符串以及任意 callable; - RetinaNet:
RetinaNetHead在构造函数中特殊处理norm == "BN" or "SyncBN",拦截字符串并替换为CycleBatchNormList(见 retinanet.py),这就是"域特定 BN"自动化的位置;传类对象则绕过该分支。
6.3 公共配置继承
本项目的 6 份配置几乎都通过get_config("common/...")复用仓库根目录 configs/common 下的公共组件:
| 公共模块 | 提供的对象 | 说明 |
|---|---|---|
| common/models/mask_rcnn_fpn.py | model | Mask R-CNN + FPN + R-50 模型骨架 |
| common/models/retinanet.py | model | RetinaNet 模型骨架(head_in_features=["p3","p4","p5","p6","p7"]) |
| common/data/coco.py | dataloader | COCO 数据加载器 |
| common/coco_schedule.py | lr_multiplier_3x | 3x 学习率调度(270k 迭代) |
| common/optim.py | SGD | SGD 优化器(momentum、weight decay 默认值) |
| common/train.py | train | 训练超参(output_dir、checkpoint 周期、eval 周期、AMP/DDP 选项等) |
这种"配置组合(composition)"模式是 LazyConfig 相对 yaml 的核心优势:每个实验只需写与基线不同的差异行,并可用from .base import ...或get_config任意复用。
七、复现实验时的注意事项
- 多卡是硬前提:
mask_rcnn_BNhead_shuffle.py依赖dist.broadcast/differentiable_all_gather,mask_rcnn_SyncBNhead.py与retinanet_SyncBNhead.py依赖 SyncBN 的跨卡 all-reduce。单卡或未初始化 DDP 环境运行会报错或产生与论文不一致的统计口径; - batch size 对齐:
train.max_iter = 270000的注释强调 "3x for batchsize = 16",调整 total batch size 时需要同步调整迭代数与学习率(RetinaNet 配置中optimizer.lr = 0.01同样以 batch size 16 为前提); - 评测与训练分离:
mask_rcnn_BNhead_batch_stats.py无需重新训练(训练行为与基类一致),retinanet-eval-domain-specific.py接受任意 checkpoint 路径,重算域特定统计量后再评测;两处都是"训练配置 + 评测配置"解耦的典型用法; - 域特定 BN 的自动化陷阱:给
RetinaNetHead传字符串"BN"/"SyncBN"会被自动替换为CycleBatchNormList;需要真正的 shared BN 时务必传类对象(如nn.SyncBatchNorm2d),否则实验结果与预期不一致。
结语
从这份精简的 README 出发,可以看到 "batch" 一词在 BatchNorm 语境下具有多层含义:单卡 batch(mask_rcnn_BNhead)、跨卡 batch(SyncBN)、跨卡混合 batch(shuffle)、跨特征域 batch(shared training BN)以及域特定统计量(CycleBatchNormList)。本文涉及的 6 份 LazyConfig 与 1 个评测脚本完整覆盖了这些语义,其对应的论文实验结果分别为 Mask R-CNN 的 Table 3 / Figure 9 / Table 6 与 RetinaNet 的 Table 5(row 1–4)。如需在自有数据上复现,可对照 配置文件 逐行修改公共配置(数据、迭代数、学习率),并保持多卡训练与评测统计口径的一致性。
【免费下载链接】detectron2Detectron2 is a platform for object detection, segmentation and other visual recognition tasks.项目地址: https://gitcode.com/GitHub_Trending/de/detectron2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考