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层的融合方式


目录
相关文章
|
机器学习/深度学习 开发框架 .NET
YOLOv5的Tricks | 【Trick6】学习率调整策略(One Cycle Policy、余弦退火等)
YOLOv5的Tricks | 【Trick6】学习率调整策略(One Cycle Policy、余弦退火等)
3924 0
YOLOv5的Tricks | 【Trick6】学习率调整策略(One Cycle Policy、余弦退火等)
|
存储 数据采集 传感器
一文多图搞懂KITTI数据集下载及解析
一文多图搞懂KITTI数据集下载及解析
14808 3
一文多图搞懂KITTI数据集下载及解析
|
机器学习/深度学习 计算机视觉
YOLOv5改进 | Conv篇 | 在线重参数化卷积OREPA助力二次创新(提高推理速度 + FPS)
YOLOv5改进 | Conv篇 | 在线重参数化卷积OREPA助力二次创新(提高推理速度 + FPS)
297 2
|
机器学习/深度学习 存储 缓存
YOLOv5的Tricks | 【Trick9】模型剪枝处理与Pytorch实现的剪枝策略
在yolov5项目中的torch_utils.py文件下,有prune这个函数,用来实现模型的剪枝处理。对模型裁剪,模型剪枝这方面之前没有接触到,这里用这篇笔记来学习记录一下这方面内容。
2646 0
YOLOv5的Tricks | 【Trick9】模型剪枝处理与Pytorch实现的剪枝策略
|
机器学习/深度学习 自动驾驶 计算机视觉
YOLOv5改进 | Neck篇 | Slim-Neck替换特征融合层实现超级涨点 (又轻量又超级涨点)
YOLOv5改进 | Neck篇 | Slim-Neck替换特征融合层实现超级涨点 (又轻量又超级涨点)
986 0
YOLOv5改进 | Neck篇 | Slim-Neck替换特征融合层实现超级涨点 (又轻量又超级涨点)
|
机器学习/深度学习 算法 计算机视觉
YOLOv5改进 | 损失函数 | EIoU、SIoU、WIoU、DIoU、FocusIoU等多种损失函数
💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡
|
人工智能 PyTorch 算法框架/工具
AI计算机视觉笔记二:基于YOLOV5的CPU版本部署openvino
本文档详细记录了YOLOv5模型在CPU环境下的部署流程及性能优化方法。首先,通过设置Python虚拟环境并安装PyTorch等依赖库,在CPU环境下成功运行YOLOv5模型的示例程序。随后,介绍了如何将PyTorch模型转换为ONNX格式,并进一步利用OpenVINO工具包进行优化,最终实现模型在CPU上的高效运行。通过OpenVINO的加速,即使是在没有GPU支持的情况下,模型的推理速度也从约20帧每秒提高到了50多帧每秒,显著提升了性能。此文档对希望在资源受限设备上部署高性能计算机视觉模型的研究人员和工程师具有较高的参考价值。
1042 0
|
PyTorch Serverless 算法框架/工具
YOLOv5源码逐行超详细注释与解读(6)——网络结构(1)yolo.py
YOLOv5源码逐行超详细注释与解读(6)——网络结构(1)yolo.py
4018 0
YOLOv5源码逐行超详细注释与解读(6)——网络结构(1)yolo.py
|
机器学习/深度学习 编解码 计算机视觉
【YOLOv10改进-注意力机制】Polarized Self-Attention: 极化自注意力,双重注意力机制
YOLOv10引入了极化自注意(PSA)块,结合通道和空间注意力,降低信息损失。PSA通过极化过滤保持高分辨率,并用增强处理非线性分布。在2D姿态估计和分割任务上提升1-2点精度,相比基线提升2-4点。代码示例展示了PSA模块如何集成到模型中。更多配置详情见相关链接。
|
机器学习/深度学习 计算机视觉
【YOLOv8改进】MPDIoU:有效和准确的边界框损失回归函数 (论文笔记+引入代码)
YOLO目标检测专栏介绍了YOLO的有效改进和实战案例,包括卷积、主干网络、注意力机制和检测头的创新。提出了一种新的边界框回归损失函数MPDIoU,它基于最小点距离,能更好地处理不同宽高比的预测框,包含重叠、中心点距离和尺寸偏差的全面考虑。MPDIoU损失函数在YOLACT和YOLOv7等模型上的实验显示了优于现有损失函数的性能。此外,还介绍了WIoU_Scale类用于计算加权IoU,以及bbox_iou函数实现不同IoU变体的计算。详细实现和配置可在相应链接中查阅。