【扒代码】CCFF跨尺度特征融合

news2024/9/21 16:42:51
import torch
import torch.nn as nn
import torch.nn.functional as F

class RepVggBlock(nn.Module):
    def __init__(self, ch_in, ch_out, act='relu'):
        super().__init__()
        self.ch_in = ch_in  # 输入通道数
        self.ch_out = ch_out  # 输出通道数

        # 第一个卷积层,使用 3x3 卷积核,填充为 1
        self.conv1 = ConvNormLayer(ch_in, ch_out, 3, 1, padding=1, act=None)
        # 第二个卷积层,使用 1x1 卷积核,无填充
        self.conv2 = ConvNormLayer(ch_in, ch_out, 1, 1, padding=0, act=None)

        # 初始化激活函数,如果未指定则使用恒等变换
        self.act = nn.Identity() if act is None else get_activation(act)

    def forward(self, x):
        # 前向传播,将输入 x 通过两个卷积层并相加
        y = self.conv1(x) + self.conv2(x)
        # 应用激活函数
        return self.act(y)

    def convert_to_deploy(self):
        # 转换为部署模式,将两个卷积层融合为一个
        if not hasattr(self, 'conv'):
            self.conv = nn.Conv2d(self.ch_in, self.ch_out, 3, 1, padding=1)

        # 获取等效的卷积核和偏置
        kernel, bias = self.get_equivalent_kernel_bias()
        # 更新融合后的卷积层的权重和偏置
        self.conv.weight.data = kernel
        self.conv.bias.data = bias
        # 注释:以下两行被注释掉了,它们原本用于删除 conv1 和 conv2 层
        # self.__delattr__('conv1')
        # self.__delattr__('conv2')

    def get_equivalent_kernel_bias(self):
        # 获取等效的卷积核和偏置
        kernel3x3, bias3x3 = self._fuse_bn_tensor(self.conv1)
        kernel1x1, bias1x1 = self._fuse_bn_tensor(self.conv2)
        # 将 1x1 卷积核填充为 3x3 大小
        return kernel3x3 + self._pad_1x1_to_3x3_tensor(kernel1x1), bias3x3 + bias1x1

    def _pad_1x1_to_3x3_tensor(self, kernel1x1):
        # 如果 1x1 卷积核不存在,则返回 0
        if kernel1x1 is None:
            return 0
        else:
            # 使用 F.pad 对 1x1 卷积核进行填充,使其成为 3x3 大小
            return F.pad(kernel1x1, [1, 1, 1, 1])

    def _fuse_bn_tensor(self, branch: ConvNormLayer):
        # 如果分支层不存在,则返回 0, 0
        if branch is None:
            return 0, 0
        # 获取卷积层的权重
        kernel = branch.conv.weight
        # 获取批量归一化层的参数
        running_mean = branch.norm.running_mean
        running_var = branch.norm.running_var
        gamma = branch.norm.weight
        beta = branch.norm.bias
        eps = branch.norm.eps
        # 计算并返回融合后的卷积核和偏置
        std = (running_var + eps).sqrt()
        t = (gamma / std).reshape(-1, 1, 1, 1)
        return kernel * t, beta - running_mean * gamma / std

功能解释

  • RepVggBlock 类实现了一个可部署的卷积块,它可以在训练时使用两个卷积层(一个 3x3 卷积和一个 1x1 卷积),并在部署时将它们融合为一个 3x3 卷积层,以减少模型的复杂性和提高推理速度。
  • forward 方法定义了模型的前向传播逻辑,它将输入 x 通过两个卷积层,并将它们的结果相加,然后应用激活函数。
  • convert_to_deploy 方法用于将模型转换为部署模式,通过融合两个卷积层为一个卷积层,并更新其权重和偏置。
  • get_equivalent_kernel_bias 方法用于获取融合后的卷积核和偏置,以便在部署模式下使用。
  • _pad_1x1_to_3x3_tensor 方法用于将 1x1 卷积核填充为 3x3 大小,以便与 3x3 卷积核相加。
  • _fuse_bn_tensor 方法用于融合 BatchNorm 层到卷积层中,计算融合后的卷积核和偏置。

整体而言,RepVggBlock 类提供了一种灵活的方式来构建和部署高效的卷积块,它通过融合卷积和归一化层来简化模型结构,同时保持性能。这种设计在实际应用中有助于提高模型的推理速度和减少内存占用。

import torch
from torch import nn

class CCFF(nn.Module):
    def __init__(self,
                 in_channels,
                 out_channels,
                 num_blocks=3,
                 expansion=1.0,
                 bias=None,
                 act="silu"):
        super(CCFF, self).__init__()  # 调用基类的初始化方法
        # 计算隐藏层的通道数,基于输出通道数和扩展比例
        hidden_channels = int(out_channels * expansion)
        # 第一个1x1卷积层,用于通道混合
        self.conv1 = ConvNormLayer(in_channels, hidden_channels, 1, 1, bias=bias, act=act)
        # 第二个1x1卷积层,用于跨通道特征融合
        self.conv2 = ConvNormLayer(in_channels, hidden_channels, 1, 1, bias=bias, act=act)
        # 构建瓶颈层序列,包含多个 RepVggBlock
        self.bottlenecks = nn.Sequential(*[
            RepVggBlock(hidden_channels, hidden_channels, act=act) for _ in range(num_blocks)
        ])
        # 如果隐藏层通道数不等于输出通道数,则添加第三个1x1卷积层进行通道映射
        if hidden_channels != out_channels:
            self.conv3 = ConvNormLayer(hidden_channels, out_channels, 1, 1, bias=bias, act=act)
        else:
            # 如果相等,则使用恒等变换
            self.conv3 = nn.Identity()

    def forward(self, x):
        # 前向传播方法
        # 通过第一个1x1卷积层和瓶颈层
        x_1 = self.conv1(x)
        x_1 = self.bottlenecks(x_1)
        # 通过第二个1x1卷积层
        x_2 = self.conv2(x)
        # 将两个分支的输出相加,然后通过第三个1x1卷积层或恒等变换
        return self.conv3(x_1 + x_2)

# 测试代码
if __name__ == '__main__':
    # 实例化模型对象,输入和输出通道数均为64
    model = CCFF(in_channels=64, out_channels=64)
    # 创建一个随机初始化的输入张量,大小为 (批量大小, 通道数, 高度, 宽度)
    input = torch.randn(1, 64, 32, 32)
    # 通过模型前向传播得到输出
    output = model(input)
    # 打印输入和输出张量的大小
    print('input_size:', input.size())
    print('output_size:', output.size())

功能解释

  • CCFF 类是一个深度学习模块,它使用两个1x1卷积层和一个瓶颈层序列来实现特征融合和通道混合。
  • in_channels 和 out_channels 分别指定了输入和输出的通道数。
  • num_blocks 指定了瓶颈层序列中 RepVggBlock 的数量。
  • expansion 用于计算隐藏层的通道数,如果设置为1.0,则隐藏层通道数与输出通道数相同。
  • bias 参数用于控制卷积层是否使用偏置项。
  • act 指定了激活函数的类型,默认为 "silu"。
  • ConvNormLayer 是一个自定义的卷积层,包含卷积和批量归一化操作。
  • RepVggBlock 是一个自定义的卷积块,它可以在训练和部署时进行优化。
  • forward 方法定义了数据通过 CCFF 模块的流程,包括通过卷积层、瓶颈层、相加操作和最后的卷积或恒等变换。

整体而言,CCFF 类实现了一个灵活的特征融合模块,可以用于深度学习模型中的各种任务,如特征提取、特征融合等。通过调整参数,可以适应不同的网络结构和应用需求。

完整代码

import torch
import torch.nn as nn
import torch.nn.functional as F
# https://arxiv.org/abs/2304.08069
'''
论文题目:DETR 在实时目标检测方面击败 YOLO    CVPR 2024
即插即用模块:CCFF跨尺度特征融合模块
CCFF基于跨尺度融合模块进行优化,该模块将多个由卷积层组成的融合块插入到融合路径中。
融合块的作用是将两个相邻的尺度特征融合成一个新的特征,其结构如图 5 所示。
融合块包含两个 1 × 1 卷积来调整通道数,由 RepConv[8]组成的 N 个 RepBlocks 用于特征融合,
两路径输出通过元素加法融合。

'''
# 激活层模块
def get_activation(act: str, inpace: bool = True):
    '''get activation
    '''
    # 将输入的激活函数名称转换为小写
    act = act.lower()

    if act == 'silu': m = nn.SiLU()

    elif act == 'relu': m = nn.ReLU()

    elif act == 'leaky_relu': m = nn.LeakyReLU()

    elif act == 'silu': m = nn.SiLU()

    elif act == 'gelu': m = nn.GELU()

    # 如果没有指定激活函数,使用恒等变换
    elif act is None: m = nn.Identity() 

    # 如果输入的是一个 nn.Module 对象,则直接使用该对象
    elif isinstance(act, nn.Module): m = act

    else: raise RuntimeError('')

    # 如果激活层模块有 inplace 属性,则设置该属性
    if hasattr(m, 'inplace'): m.inplace = inpace

    return m

# 类 ConvNormLayer 用于构建一个包含卷积、归一化和激活函数的卷积层。
class ConvNormLayer(nn.Module):
    def __init__(self, ch_in, ch_out, kernel_size, stride, padding=None, bias=False, act=None):
        super().__init__()
        self.conv = nn.Conv2d(
            ch_in,# 输入通道数
            ch_out,# 输出通道数
            kernel_size,# 卷积核大小
            stride,# 步长
            padding=(kernel_size - 1) // 2 if padding is None else padding,# 计算填充,以保持输出尺寸不变
            bias=bias)# 是否使用偏置项
        
        # 初始化批量归一化层
        self.norm = nn.BatchNorm2d(ch_out)
        # 根据提供的激活函数名称或对象,初始化激活层
        self.act = nn.Identity() if act is None else get_activation(act)

    def forward(self, x):# 前向传播方法,将输入 x 通过卷积层、归一化层和激活层
        return self.act(self.norm(self.conv(x)))


class RepVggBlock(nn.Module):
    def __init__(self, ch_in, ch_out, act='relu'):
        super().__init__()
        self.ch_in = ch_in  # 输入通道数
        self.ch_out = ch_out # 输出通道数

        # 第一个卷积层,使用 3x3 卷积核,填充为 1
        self.conv1 = ConvNormLayer(ch_in, ch_out, 3, 1, padding=1, act=None)
        # 第二个卷积层,使用 1x1 卷积核,无填充
        self.conv2 = ConvNormLayer(ch_in, ch_out, 1, 1, padding=0, act=None)
        # 初始化激活函数,如果未指定则使用恒等变换
        self.act = nn.Identity() if act is None else get_activation(act)

    def forward(self, x):
        # 前向传播,将输入 x 通过两个卷积层并相加
        if hasattr(self, 'conv'):           
            y = self.conv(x)
        else:
            y = self.conv1(x) + self.conv2(x)
        # 应用激活函数
        return self.act(y)

    def convert_to_deploy(self):
        # 此函数将模型转换为部署模式
        # 如果模型中还没有融合的卷积层,则创建一个
        # 转换为部署模式,将两个卷积层融合为一个
        if not hasattr(self, 'conv'):
            self.conv = nn.Conv2d(self.ch_in, self.ch_out, 3, 1, padding=1)
        
        # 获取融合后的卷积核和偏置
        kernel, bias = self.get_equivalent_kernel_bias()

        # 更新融合后的卷积层的权重和偏置
        self.conv.weight.data = kernel
        self.conv.bias.data = bias
        # self.__delattr__('conv1')
        # self.__delattr__('conv2')

    def get_equivalent_kernel_bias(self):
        # 此函数获取等效的卷积核和偏置,将两个卷积层融合为一个
        # 获取等效的卷积核和偏置
        kernel3x3, bias3x3 = self._fuse_bn_tensor(self.conv1)
        kernel1x1, bias1x1 = self._fuse_bn_tensor(self.conv2)
        # 将1x1卷积层的权重扩展为3x3大小,然后与3x3卷积层的权重相加
        return kernel3x3 + self._pad_1x1_to_3x3_tensor(kernel1x1), bias3x3 + bias1x1

    def _pad_1x1_to_3x3_tensor(self, kernel1x1):

        # 如果 1x1 卷积核不存在,则返回 0
        if kernel1x1 is None:
            return 0
        else:
            # 使用 F.pad 对 1x1 卷积核进行填充,使其成为 3x3 大小
            return F.pad(kernel1x1, [1, 1, 1, 1])

    def _fuse_bn_tensor(self, branch: ConvNormLayer):
        # 如果分支层不存在或为None,则返回0, 0
        if branch is None:
            return 0, 0
         # 融合卷积层和批量归一化层的权重和偏置
        kernel = branch.conv.weight
        # 获取批量归一化层的参数
        running_mean = branch.norm.running_mean
        running_var = branch.norm.running_var
        gamma = branch.norm.weight
        beta = branch.norm.bias
        eps = branch.norm.eps
        # 计算并返回融合后的卷积核和偏置
        std = (running_var + eps).sqrt()# 计算标准差
        t = (gamma / std).reshape(-1, 1, 1, 1)# 计算缩放因子
        return kernel * t, beta - running_mean * gamma / std# 返回融合后


class CCFF(nn.Module):
    def __init__(self,
                 in_channels,
                 out_channels,
                 num_blocks=3,
                 expansion=1.0,
                 bias=None,
                 act="silu"):
        super(CCFF, self).__init__()# 调用基类的初始化方法
        # 计算隐藏层的通道数,基于输出通道数和扩展比例
        hidden_channels = int(out_channels * expansion)
        # 第一个1x1卷积层,用于通道混合
        self.conv1 = ConvNormLayer(in_channels, hidden_channels, 1, 1, bias=bias, act=act)
        # 第二个1x1卷积层,用于跨通道特征融合
        self.conv2 = ConvNormLayer(in_channels, hidden_channels, 1, 1, bias=bias, act=act)
        # 构建瓶颈层序列,包含多个 RepVggBlock
        self.bottlenecks = nn.Sequential(*[
            RepVggBlock(hidden_channels, hidden_channels, act=act) for _ in range(num_blocks)
        ])
        # 如果隐藏层通道数不等于输出通道数,则添加第三个1x1卷积层进行通道映射
        if hidden_channels != out_channels:
            self.conv3 = ConvNormLayer(hidden_channels, out_channels, 1, 1, bias=bias, act=act)
        else:
            # 如果相等,则使用恒等变换
            self.conv3 = nn.Identity()

    def forward(self, x):
        # 前向传播方法
        # 通过第一个1x1卷积层和瓶颈层
        x_1 = self.conv1(x)
        x_1 = self.bottlenecks(x_1)
        # 通过第二个1x1卷积层
        x_2 = self.conv2(x)
        # 将两个分支的输出相加,然后通过第三个1x1卷积层或恒等变换
        return self.conv3(x_1 + x_2)

# 输入 N C H W,  输出 N C H W
if __name__ == '__main__':
    # 实例化模型对象
    model = CCFF(in_channels=64, out_channels=64)
    # 创建一个随机初始化的输入张量,大小为 (批量大小, 通道数, 高度, 宽度)
    input = torch.randn(1, 64, 32, 32)
    output = model(input)
    print('input_size:',input.size())
    print('output_size:',output.size())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1995298.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iOS ------ UIKit相关

UIView和CALayer UIView UIView表示屏幕上的一块矩形区域,它是基本上iOS中所有可视化控件的父类。UIView可以管理矩形区域里的内容,处理矩形区域的事件,包括子视图的管理以及动画的实现。 UIKit相关类的继承关系 UIView继承自UIResponde…

封装加载(raect18+antd)

该组件主要是anted的组件中自带的loading属性&#xff0c; 1、封装loading组件 import React from react;function WithLoading(WrappedComponent: React.ComponentType<any>) {return (props: any) > {const [isLoading, setIsLoading] React.useState(true);Reac…

基于JAVA的高考智能排考场系统设计与实现,源码、部署+讲解

绪 论 随着教育规模的不断扩大和技术的进步&#xff0c;传统的考试管理方式面临着诸多挑战&#xff0c;如考试安排的复杂性、作弊现象的频发以及考试过程中的监督和管理等问题。因此&#xff0c;针对这些挑战&#xff0c;智能排考系统应运而生。 智能排考系统利用先进的技术…

接口基础知识5:详解request headers(一篇讲完常见字段)

课程大纲 一、请求头的定义 HTTP请求头部&#xff08;HTTP Request Headers&#xff09;&#xff1a;HTTP协议中的一部分&#xff0c;用于在客户端和服务器之间传递附加信息。这些头部字段提供了关于请求、客户端环境、或请求的上下文的信息。 请求头是键值对的形式&#xff…

day04-套餐管理

完成套餐管理模块所有业务功能&#xff0c;包括&#xff1a; 新增套餐套餐分页查询删除套餐修改套餐起售停售套餐 要求&#xff1a; 根据产品原型进行需求分析&#xff0c;分析出业务规则设计接口梳理表之间的关系&#xff08;分类表、菜品表、套餐表、口味表、套餐菜品关系…

element plus el-select修改后缀图标

使用 element plus 提供的api 默认为&#xff1a; 修改后为&#xff1a; 方法&#xff1a; <el-select v-model"value" placeholder"Select" size"large" style"width: 120px;":teleported"false" :suffix-icon"…

图数据库Neo4j的调研

图数据库Neo4j的调研 一、neo4j基础概述 概述 neo4j作为当下最热门的图数据库之一&#xff0c;他的底层实现是java语言&#xff0c;所以安装的时候必须有jre环境。并且neo4j是根据计算机中图论理论来实现的。 neo4j图数据库主要有以下组成元素&#xff1a;&#xff08;具体…

Kibana,Docker Remote Api,Kubernetes Api Server我未授权访问漏洞(附带修复方法)

一.Kibana Kibana是⼀个开源的分析与可视化平台&#xff0c;设计出来⽤于和Elasticsearch⼀起使⽤的。你可以⽤kibana搜索、查看存放在Elasticsearch中的数据。Kibana与Elasticsearch的交互⽅式是以各种不同的图表、表格、地图等直观地展示数据&#xff0c;从⽽达到⾼级的数据分…

【数据链路层】ARP协议

文章目录 以太网以太网帧对的格式 MAC地址对比MAC地址和IP地址 MTU和MSSARP协议ARP协议的工作原理ARP欺骗 以太网 ”以太网" 不是一种具体的网络, 而是一种技术标准; 既包含了数据链路层的内容, 也包含了一些物理层的内容. 例如: 规定了网络拓扑结构, 访问控制方式, 传输…

数学思维曼哈顿距离

前言&#xff1a;刚刚看到这个题的时候模拟了一下&#xff0c;感觉就是一个曼哈顿距离的问题&#xff0c;我们计算当前位置和中心的曼哈顿距离&#xff0c;然后比较x 或 y 到中心距离的大小&#xff0c;如果有一个小于等于&#xff0c;那么就是ok的 #define _CRT_SECURE_NO_WAR…

正点原子imx6ull-mini-Linux驱动之Linux IIO 驱动实验

工业场合里面也有大量的模拟量和数字量之间的转换&#xff0c;也就是我们常说的 ADC 和 DAC。 而且随着手机、物联网、工业物联网和可穿戴设备的爆发&#xff0c;传感器的需求只持续增强。比如手 机或者手环里面的加速度计、光传感器、陀螺仪、气压计、磁力计等&#xff0c;这些…

【数据结构】关于栈你必须知道的内部原理!!!

前言&#xff1a; &#x1f31f;&#x1f31f;Hello家人们&#xff0c;小编这期将带来关于栈的相关知识&#xff0c;以及代码的实现&#xff0c;希望能够帮到屏幕前的你。 &#x1f4da;️上期双链表博客在这里哟&#xff1a;http://t.csdnimg.cn/xgjK1&#xff1b; &#x1f4…

预测云计算的未来

云计算的未来是否依旧未知&#xff1f; 直到最近&#xff0c;云计算还是软件编程界之外很少有人熟悉的一个概念。如今&#xff0c;云计算已成为一切的基础&#xff0c;从点播电视服务和在线游戏门户网站到电子邮件和社交媒体&#xff0c;这四大现代世界的基石。云计算将继续存在…

Electron 使用Electron-build 进行打包

看完下面两篇就可以完成&#xff01; 基于vue3vite的web项目改为Electron桌面应用&#xff08;一&#xff09;_vue3转electron-CSDN博客 将web项目打包成electron桌面端教程&#xff08;二&#xff09;vue3vitets_vue3 打包桌面端-CSDN博客 打包报错 1. 首先确定依赖包 npm …

合金钢旋转花键:高强度与高耐磨性的选择

旋转花键‌是一种利用装在花键外筒内的滚珠&#xff0c;‌在精密研磨的滚动沟槽中作平滑滚动&#xff0c;‌同时传递力矩的直线运动系统。需要在振动冲击负荷作用过大、‌定位精度要求高的地方&#xff0c;‌以及需要高速运动性能的地方发挥重要作用。 旋转花键的性能跟材质有直…

龙迅#LT86102SXE适用于HDMI转两路HDMI应用方案,分辨率高达4K60HZ,提供技术支持!

1.功能 ⚫HDMI1.4和DVI 1.0规范兼容 ⚫支持3D视频格式和4Kx2K扩展分辨率格式高达3.4Gbps数据速率和4Kx2K 60Hz YCbCr 4&#xff1a;2&#xff1a;0格式 ⚫自适应均衡和失重&#xff0c;以补偿长电缆损耗 ⚫ODTs和校准 ⚫集成HDCP中继器引擎符合HDCP 1.4规范 ⚫完全硬件控制或可…

CSS文本两端对齐

背景 如果我们要写了列表或表单类的样式&#xff0c;名称长短不一&#xff0c;但是想要两端对齐&#xff0c;如下面这样的&#xff1a; 你是怎么写的&#xff1f; 是这样的吗&#xff0c;在HTML里调整加空格&#xff1a; <ul><li>用户名</li><li>账 …

探索算法系列 - 前缀和算法

目录 一维前缀和&#xff08;原题链接&#xff09; 二维前缀和&#xff08;原题链接&#xff09; 寻找数组的中心下标&#xff08;原题链接&#xff09; 除自身以外数组的乘积&#xff08;原题链接&#xff09; 和为 K 的子数组&#xff08;原题链接&#xff09; 和可被 …

电脑开机后出现bootmgr is missing原因及解决方法

最近有网友问我为什么我电脑开机后出现bootmgr is missing&#xff0c;这个提示意思是:意思是启动管理器丢失&#xff0c;说明bootmgr损坏或者丢失&#xff0c;系统无法读取到这个必要的启动信息导致无法启动。原因有很多&#xff0c;比如我们采用的是uefi引导&#xff0c;而第…

【数学建模】简单的优化模型-6 血管分支

背景&#xff1a;机体提供能量维持血液在血管中的流动&#xff0c;给血管壁以营养&#xff1b;克服血液流动的阻力&#xff0c;能量消耗与血管的几何形状有关。在长期进化中动物血管的几何形状已经在能量消耗最小原则下达到最优化。 问题&#xff1a;在能量消耗最小原则下&…