YOLOv5的Tricks | 【Trick4】参数重结构化(融合Conv+BatchNorm2d)

简介: 这篇文章是想要记录yolov5在模型搭建过程中的一个融合模块,就是把卷积与批归一化的参数进行融合,想卷积带有批归一化的性质,使得推理过程中可以加快模型推理速度,简化整个模型结构,实现训练与推理两个阶段的解耦。

1. 参数融合概念介绍


我最早接触参数重结构化这个词是看见了大佬丁霄汉发表的几篇论文:RepVGG,RepMLP,RepLKNet,这些构建新backbone的论文无一例外的全部使用了参数重结构化的思想。


RepVGG将3x3,1x1,identity分支的残差结果利用数学计算方法等价为一个3x3的卷积结构,实现训练与推断过程的解耦;RepMLP将局部的CNN先验信息加进了全连接层,使得其与MLP相结合等等。这里需要注意,重结构化层MLP结构也不是说变成Linear层,而是简化为1x1的卷积。(后续有机会把这几篇文章介绍一下,或者直接看大佬的知乎:https://www.zhihu.com/people/ding-xiao-yi-93/posts


BN(批归一化)层常用于在卷积层之后,对feature maps进行归一化,从而加速网络学习,也具有一定的正则化效果。训练时,BN需要学习一个minibatch数据的均值、方差,然后利用这些信息进行归一化。而在推理过程,通常为了加速,都会把BN融入到其上层卷积中,这样就将两步运算变成了一步,也就达到了加速目的。


那么这里yolov5所实现的,是参数重结构化的一个小内容,就是把卷积与批归一化进行融合,变成一个新的卷积,但是包含BN层的特性。所以相比之下,算是参数重结构化系列的一个小小idea,可以稍微的加快推理速度。因为使用的是csp结构,所以没有涉及多并联分支的卷积模块(所以这一点其实也可以魔改下yolov5试试)。


2. 参数融合详细推导


在yolov5的注释中给了一个推导的参考资料:Fusing batch normalization and convolution in runtime,代码也是基于这篇文章来稍微修改的。


其实现的主要思想就是将bn层转化为一个1x1的卷积:

image.png


然后就变成了两个卷积层的迭代处理,公式为:

image.png

其中:

image.png

在pytorch实现中,每个BN层都有以下几个:

image.png

但是在批归一化转换为1x1的卷积那里其实没有给出太多解释,后来我看了另一篇博客介绍:卷积层与BN层的融合方式,其实我是没有完全弄到batchnormalization的过程。为了搞清楚如何融合卷积和BN,需要先搞懂卷积和BN的过程。

image.png

image.png

image.png

至此完成了卷积层和BN层的融合,可以和代码的参考文档一一对上了。


3. 参数融合代码实现


这里的融合代码集成了模型中,然后再另外的调用其他打码,yolov5代码如下所示:


class Model(nn.Module):
    def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None):  # model, input channels, number of classes
        super().__init__()
  ...
        # 如果配置文件中有中文,打开时要加encoding参数
        with open(cfg, errors='ignore') as f:
            self.yaml = yaml.safe_load(f)  # model dict
        # 创建网络模型
        # self.model: 初始化的整个网络模型(包括Detect层结构)
        # self.save: 所有层结构中from不等于-1的序号,并排好序  [4, 6, 10, 14, 17, 20, 23]
        self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch])  # model, savelist
        ...
    def forward(self, x, augment=False, profile=False, visualize=False):  # debug同样需要第三次才能正常跳进来
        if augment:     # use Test Time Augmentation(TTA), 如果打开会对图片进行scale和flip
            return self._forward_augment(x)  # augmented inference, None
        return self._forward_once(x, profile, visualize)  # single-scale inference, train
  # 参数重结构化: 融合conv2d + batchnorm2d (推理的时候用, 可以加快模型的推理速度)
    def fuse(self):  # fuse model Conv2d() + BatchNorm2d() layers
        LOGGER.info('Fusing layers... ')
        for m in self.model.modules():
            # 对Conv与DWConv(继承Conv)的结构进行卷积与bn的融合
            if isinstance(m, (Conv, DWConv)) and hasattr(m, 'bn'):
                # 融合Conv模块中的conv与bn层(不包含激活函数), 返回的是参数融合后的卷积
                m.conv = fuse_conv_and_bn(m.conv, m.bn)  # update conv
                # 融合后conv的参数就包含了bn的用途, 所以可以删除bn层
                delattr(m, 'bn')  # remove batchnorm
                # 由于不需要bn层, 所以forward函数需要改写:
                # self.act(self.bn(self.conv(x))) -> self.act(self.conv(x))
                m.forward = m.forward_fuse  # update forward
        self.info()
        return self
  ...


融合Conv+BatchNorm2d的具体实现代码如下所示:


def fuse_conv_and_bn(conv, bn):
    # Fuse convolution and batchnorm layers https://tehnokv.com/posts/fusing-batchnorm-and-conv/
    # 设置为no grad(推理不需要反向传播), 指定在相同的设备上
    fusedconv = nn.Conv2d(conv.in_channels,
                          conv.out_channels,
                          kernel_size=conv.kernel_size,
                          stride=conv.stride,
                          padding=conv.padding,
                          groups=conv.groups,
                          bias=True).requires_grad_(False).to(conv.weight.device)
    # prepare filters: W = W_BN * W_conv
    # (32,3,6,6) -> (32,3*6*6) -> (32,108)
    w_conv = conv.weight.clone().view(conv.out_channels, -1)
    w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps + bn.running_var)))
    # (32,32)*(32,108) -> (32,108) -> (32,3,6,6)
    fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.shape))
    # prepare spatial bias: b = W_BN * b_conv + b_BN
    b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) if conv.bias is None else conv.bias
    b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps))
    # (32,32)*(32,1) -> (32,1) -> (32)
    fusedconv.bias.copy_(torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn)
    return fusedconv


同时可以注意到,在fuse模块代码中还改变了模型模块的前向传播函数:m.forward = m.forward_fuse,这里是因为在Conv中是卷积+bn连续使用的,其作为整个模型的一个基础卷积模块,使用只需要改变其前向传播过程就可以融合卷积+BN层,在推理的时候可以加快速度。其代码如下:


class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
    def forward(self, x):
        return self.act(self.bn(self.conv(x)))
    def forward_fuse(self, x):
        return self.act(self.conv(x))
class DWConv(Conv):
    # Depth-wise convolution class
    def __init__(self, c1, c2, k=1, s=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        # math.gcd最大公约数
        super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), act=act)


后续:如果还想了解其他不同卷积和之间的融合或者与全连接的融合可以了解重参数化系列文章:RepVGG,RepMLP等


参考资料:

1. Fusing batch normalization and convolution in runtime

2. 卷积层与BN层的融合方式


目录
打赏
0
0
0
0
21
分享
相关文章
RT-DETR改进策略【Conv和Transformer】| ICCV-2023 iRMB 倒置残差移动块 轻量化的注意力模块
RT-DETR改进策略【Conv和Transformer】| ICCV-2023 iRMB 倒置残差移动块 轻量化的注意力模块
71 14
RT-DETR改进策略【Conv和Transformer】| ICCV-2023 iRMB 倒置残差移动块 轻量化的注意力模块
RT-DETR改进策略【卷积层】| CVPR-2020 Strip Pooling 空间池化模块 处理不规则形状的对象 含二次创新
RT-DETR改进策略【卷积层】| CVPR-2020 Strip Pooling 空间池化模块 处理不规则形状的对象 含二次创新
37 5
RT-DETR改进策略【卷积层】| CVPR-2020 Strip Pooling 空间池化模块 处理不规则形状的对象 含二次创新
RT-DETR改进策略【RT-DETR和Mamba】| MLLA:Mamba-Like Linear Attention,融合Mamba设计优势的注意力机制
RT-DETR改进策略【RT-DETR和Mamba】| MLLA:Mamba-Like Linear Attention,融合Mamba设计优势的注意力机制
54 1
RT-DETR改进策略【RT-DETR和Mamba】| MLLA:Mamba-Like Linear Attention,融合Mamba设计优势的注意力机制
RT-DETR改进策略【Conv和Transformer】| 2024 AssemFormer 结合卷积与 Transformer 优势,弥补传统方法不足
RT-DETR改进策略【Conv和Transformer】| 2024 AssemFormer 结合卷积与 Transformer 优势,弥补传统方法不足
40 1
RT-DETR改进策略【Conv和Transformer】| 2024 AssemFormer 结合卷积与 Transformer 优势,弥补传统方法不足
RT-DETR改进策略【Conv和Transformer】| ACmix 卷积和自注意力的结合,充分发挥两者优势
RT-DETR改进策略【Conv和Transformer】| ACmix 卷积和自注意力的结合,充分发挥两者优势
73 14
RT-DETR改进策略【Conv和Transformer】| CVPR-2021 Bottleneck Transformers 简单且高效的自注意力模块
RT-DETR改进策略【Conv和Transformer】| CVPR-2021 Bottleneck Transformers 简单且高效的自注意力模块
23 4
YOLOv11改进策略【YOLO和Mamba】| MLLA:Mamba-Like Linear Attention,融合Mamba设计优势的注意力机制
YOLOv11改进策略【YOLO和Mamba】| MLLA:Mamba-Like Linear Attention,融合Mamba设计优势的注意力机制
149 9
YOLOv11改进策略【卷积层】| CVPR-2020 Strip Pooling 空间池化模块 处理不规则形状的对象 含二次创新
YOLOv11改进策略【卷积层】| CVPR-2020 Strip Pooling 空间池化模块 处理不规则形状的对象 含二次创新
45 0
YOLOv11改进策略【卷积层】| CVPR-2020 Strip Pooling 空间池化模块 处理不规则形状的对象 含二次创新
【YOLOv11改进 - 注意力机制】EMA(Efficient Multi-Scale Attention):基于跨空间学习的高效多尺度注意力
【YOLOv11改进 - 注意力机制】EMA(Efficient Multi-Scale Attention):基于跨空间学习的高效多尺度注意力.EMA(Efficient Multi-Scale Attention)模块是一种高效多尺度注意力机制,旨在提高计算机视觉任务中的特征表示效果。该模块通过结合通道和空间信息、采用多尺度并行子网络结构以及优化坐标注意力机制,实现了更高效和有效的特征表示。EMA模块在图像分类和目标检测任务中表现出色,使用CIFAR-100、ImageNet-1k、MS COCO和VisDrone2019等数据集进行了广泛测试。
【YOLOv11改进 - 注意力机制】EMA(Efficient Multi-Scale Attention):基于跨空间学习的高效多尺度注意力
【YOLOv8改进-卷积Conv】 OREPA(Online Convolutional Re-parameterization):在线卷积重参数化
**OREPA**是在线卷积重参数化的缩写,它提出了一种两阶段流程来减少深度模型训练的开销。该方法通过线性缩放层优化复杂训练块,并在训练完成后将其压缩为单个卷积层,降低内存使用和提高训练速度。与现有技术相比,OREPA能减少约70%的训练内存开销,提升2倍训练速度,并在ImageNet上提高最多0.6%的准确性。此外,它还在目标检测和语义分割任务中表现出色。论文和代码可在提供的链接中找到。