Conv2Former:一种transformer风格的卷积特征提取方式

一、前言

昨天读到了一篇有意思的文章,文章提出通过利用卷积调制操作来简化self-attention。还证明了这种简单的方法可以更好地利用卷积层中嵌套的大核(≥7 × 7)。我们都知道ViTs推动了设计识别模型的发展,近几年使用的也相当的多,通常就是CNN网络引入注意力机制,往往可以获得不错的性能,因为相比较与卷积cnn,self-attention能够模拟全局成对依赖关系,这是一种更有效的空间信息编码方式。

自注意机制与所提出的卷积调制运算的比较 

作者注意到有研究者通过对标准ResNet进行现代化改进,采用与Transformers相似的设计和训练方法,ConvNets的性能在某些情况下甚至能够超越一些热门的ViTs。由此,引入了一种名为Conv2Former的新型网络结构,旨在更有效地利用空间卷积。与以往工作不同,Conv2Former采用了一种称为卷积调制的操作,通过将大内核卷积的输出与值表示进行Hadamard乘积,模拟了自注意力的输出计算过程。与传统的ViTs使用自注意力不同,Conv2Former完全是一个卷积网络,其计算复杂度随分辨率线性增加,而不是像Transformers那样呈二次增加。

二、Conv2Former的网络架构

在每两个阶段间,引入了一个Patch Embed来降低分辨率,通常是一个2 × 2的卷积,步幅为2。不同的阶段有不同数量的卷积块。

构建了五个Conv2Former变体,分别是Conv2Former- n、Conv2Former- t、Conv2Former- s、Conv2Former- b、Conv2Former- l。参数由下图所示:

残差块、自注意块和所提出的调制块之间的对比图

与自注意相比,(d)方法利用卷积来建立关系,这比自注意更节省内存,特别是在处理高分辨率图像时。与经典的残差块方法(a)相比,(d)还可以通过调制操作来适应输入内容。

在VGG,Resnet等网络提出后,3x3卷积几乎就是构建卷积网络的标准选择,但深度可分离卷积的出现改变了这一格局。ConvNeXt的研究显示,将卷积核从3扩大到7可以提升性能,然而,进一步增大卷积核几乎不再带来性能提升,反而增加了计算负担。

作者认为ConvNeXt未能从大于7×7的更大卷积核中获得更大性能提升的原因在于对空间卷积的使用方式。相比之下,Conv2Former通过观察发现,在卷积核大小从5×5增加到21×21的范围内,性能持续提升。这一现象在不同规模的Conv2Former模型中均得到验证,为更好地利用空间卷积提供了重要的设计指导。

考虑到模型的效率,作者选择将卷积核大小默认设置为11×11。这一研究发现对于ConvNet设计,更大的卷积核可以通过正确的使用方式获得持续的性能提升,为未来的视觉识别模型提供了有益的启示。

在Conv2Former的标准化和激活层方面,标准化层使用Layer Normalization,而不是常用的批标准化,激活层采用GELU。作者发现这种Layer Normalization和GELU的组合能够带来0.1%-0.2%的性能提升。

三、卷积调制操作

卷积核大小影响性能

实验证明,在Conv2Former中,增加深度卷积核大小(从5×5到21×21)可以显著提升模型性能,与传统结论不同,这表明使用卷积特征作为权重能更有效地利用大核,相较于传统方法无法带来明显性能提升。

Hadamard乘积优于求和

对比Hadamard乘积和元素求和两种融合策略,实验证明在Conv2Former中,使用深度卷积特征通过Hadamard乘积调制权重表现更好,尤其对于小型模型。这突显了卷积调制在编码空间信息方面的高效性。

权重策略分析

通过尝试不同的特征图融合方式,包括添加Sigmoid函数、应用L1标准化以及线性归一化等,结果显示Hadamard乘积依然是最优选择。有趣的是,将A的值调整为正值反而导致性能下降,与传统注意机制的做法有所不同,为未来研究提供了新的问题。

四、论文复现

作者也是给出了代码地址:HVision-NKU/Conv2Former

但我看了一下,里面只是给出了一些核心的代码,所以复现还是要靠咱自己。

"""
Copyright (c) 2023, Auorui.
All rights reserved.

reference <https://arxiv.org/pdf/2211.11943.pdf> (Conv2Former: A Simple Transformer-Style ConvNet for Visual Recognition)
Time:2023.12.31, Complete before the end of 2023.
"""
import torch
import torch.nn as nn

from pyzjr.Models import DropPath

C = {'n': [64, 128, 256, 512],
     't': [72, 144, 288, 576],
     's': [72, 144, 288, 576],
     'b': [96, 192, 384, 768],
     'l': [128, 256, 512, 1024],
     } #  reference <https://arxiv.org/pdf/2211.11943.pdf> Table 1
L = {'n': [2, 2, 8, 2],
     't': [3, 3, 12, 3],
     's': [4, 4, 32, 4],
     'b': [4, 4, 34, 4],
     'l': [4, 4, 48, 4],
     } #  reference <https://arxiv.org/pdf/2211.11943.pdf> Table 1

class MLP(nn.Module):
    def __init__(self, dim, mlp_ratio=4.):
        super().__init__()

        self.norm = nn.LayerNorm(dim, eps=1e-6)
        self.fc1 = nn.Conv2d(dim, dim * mlp_ratio, 1)
        self.pos = nn.Conv2d(dim * mlp_ratio, dim * mlp_ratio, 3, padding=1, groups=dim * mlp_ratio)
        self.fc2 = nn.Conv2d(dim * mlp_ratio, dim, 1)
        self.act = nn.GELU()

    def forward(self, x):
        x = self.norm(x.permute(0, 2, 3, 1)).permute(0, 3, 1, 2)
        x = self.fc1(x)
        x = self.act(x)
        x = x + self.act(self.pos(x))
        x = self.fc2(x)
        return x


class ConvMod(nn.Module):
    def __init__(self, dim):
        super().__init__()

        self.norm = nn.LayerNorm(dim, eps=1e-6)
        self.a = nn.Sequential(
            nn.Conv2d(dim, dim, 1),
            nn.GELU(),
            nn.Conv2d(dim, dim, 11, padding=5, groups=dim)
        )
        self.v = nn.Conv2d(dim, dim, 1)
        self.proj = nn.Conv2d(dim, dim, 1)

    def forward(self, x):
        x = self.norm(x.permute(0, 2, 3, 1)).permute(0, 3, 1, 2)
        a = self.a(x)
        x = a * self.v(x)
        x = self.proj(x)
        return x



class Block(nn.Module):
    def __init__(self, dim, mlp_ratio=4.0, drop_path=0.):
        super().__init__()

        self.attn = ConvMod(dim)
        self.mlp = MLP(dim, mlp_ratio)
        layer_scale_init_value = 1e-6
        self.layer_scale_1 = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True)
        self.layer_scale_2 = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True)
        self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()

    def forward(self, x):
        x = x + self.drop_path(self.layer_scale_1.unsqueeze(-1).unsqueeze(-1) * self.attn(x))
        x = x + self.drop_path(self.layer_scale_2.unsqueeze(-1).unsqueeze(-1) * self.mlp(x))
        return x

class BaseLayer(nn.Module):
    def __init__(self, dim, depth, mlp_ratio=4., drop_path=None, downsample=True):
        super().__init__()
        self.dim = dim
        self.drop_path = drop_path

        self.blocks = nn.ModuleList([
            Block(dim=self.dim,mlp_ratio=mlp_ratio,drop_path=drop_path[i],)
            for i in range(depth)
        ])

        # patch merging layer
        if downsample:
            self.downsample = nn.Sequential(
                nn.GroupNorm(num_groups=1, num_channels=dim),
                nn.Conv2d(dim, dim * 2, kernel_size=2, stride=2,bias=False)
            )
        else:
            self.downsample = None

    def forward(self, x):
        for blk in self.blocks:
            x = blk(x)
        if self.downsample is not None:
            x = self.downsample(x)
        return x

class Conv2Former(nn.Module):
    def __init__(self, num_classes=10, depths=(2,2,8,2), dim=(64,128,256,512), mlp_ratio=2.,drop_rate=0.,
                 drop_path_rate=0.15, **kwargs):
        super().__init__()

        norm_layer = nn.LayerNorm
        self.num_classes = num_classes
        self.num_layers = len(depths)
        self.dim = dim
        self.mlp_ratio = mlp_ratio
        self.pos_drop = nn.Dropout(p=drop_rate)

        # stochastic depth decay rule
        dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))]

        # build layers
        self.layers = nn.ModuleList()
        for i_layer in range(self.num_layers):
            layer = BaseLayer(dim[i_layer],
                              depth=depths[i_layer],
                              mlp_ratio=self.mlp_ratio,
                              drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])],
                              downsample=(i_layer < self.num_layers - 1),
                              )
            self.layers.append(layer)
        self.fc1 = nn.Conv2d(3, dim[0], 1)
        self.norm = norm_layer(dim[-1], eps=1e-6,)
        self.avgpool = nn.AdaptiveAvgPool2d(1)
        self.head = nn.Linear(dim[-1], num_classes) \
            if num_classes > 0 else nn.Identity()

        self.apply(self._init_weights)

    def _init_weights(self, m):
        if isinstance(m, nn.Linear):
            nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            if m.bias is not None:
                nn.init.constant_(m.bias, 0.)
        elif isinstance(m, (nn.Conv1d, nn.Conv2d)):
            nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            if m.bias is not None:
                nn.init.constant_(m.bias, 0.)
        elif isinstance(m, (nn.LayerNorm, nn.GroupNorm)):
            nn.init.constant_(m.bias, 0.)
            nn.init.constant_(m.weight, 1.)

    def forward_features(self, x):
        x = self.fc1(x)
        x = self.pos_drop(x)

        for layer in self.layers:
            x = layer(x)

        x = self.norm(x.permute(0, 2, 3, 1)).permute(0, 3, 1, 2)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        return x

    def forward(self, x):
        x = self.forward_features(x)
        x = self.head(x)
        return x



if __name__ == '__main__':
    model = Conv2Former(num_classes=10, depths=L["b"], dim=C["b"], mlp_ratio=2, drop_path_rate=0.1)

    input_tensor = torch.randn(1, 3, 224, 224)

    output = model(input_tensor)
    print("Output shape:", output.shape)




暂时还未进行测试,如果有空再进行实验, 你可以从这里找到我的源码:pyzjr/pyzjr/Models/backbone/Conv2Former_2.py at main · Auorui/pyzjr (github.com)

总结

Conv2Former,这是一种新型的卷积神经网络架构,其核心是卷积调制操作,通过卷积和Hadamard乘积简化了自注意力机制。在ImageNet分类、目标检测和语义分割任务中,Conv2Former相对于先前的CNN和Transformer模型表现更优。作者强调了对大内核卷积的更有效利用,如何将提出的卷积调制块与Transformer结合起来,这是将来的研究方向。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C/C++面向对象(OOP)编程-回调函数详解(回调函数、C/C++异步回调、函数指针)

本文主要介绍回调函数的使用&#xff0c;包括函数指针、异步回调编程、主要通过详细的例子来指导在异步编程和事件编程中如何使用回调函数来实现。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;C/C精进之路 &…

ROS学习记录:使用RViz观测激光雷达传感器数据

一、使用CtrlAltT打开终端 二、输入 cd ~/catkin_ws1/ 进入工作空间 三、输入source ./devel/setup.bash 四、输入&#xff1a; roslaunch wpr_simulation wpb_simple.launch 打开机器人仿真环境 五、这是机器人仿真环境&#xff0c;里面机器人和书柜 六、再开一个终端&#…

开始使用MEVN技术栈开发01 概述

开始使用MEVN技术栈开发01 概述 简介 Welcome to Beginning MEVN Stack! This book focuses on the key tasks and concepts to get you started to learn and build MEVN stack applications in a faster pace. 欢迎阅读《MEVN堆栈入门》&#xff01;本书重点介绍关键任务…

echarts 二分图布局_力向导图_关系图

Echarts 常用各类图表模板配置 注意&#xff1a; 这里主要就是基于各类图表&#xff0c;更多的使用 Echarts 的各类配置项&#xff1b; 以下代码都可以复制到 Echarts 官网&#xff0c;直接预览&#xff1b; 图标模板目录 Echarts 常用各类图表模板配置一、力向导图(二分图布局…

【力扣题解】P98-验证二叉搜索树-Java题解

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【力扣题解】 文章目录 【力扣题解】P98-验证二叉搜索树-Java题解&#x1f30f;题目描述&#x1f4a1;题解&#x1f30f;总…

分割数组的最大差值 - 华为OD统一考试

分割数组的最大差值 - 华为OD统一考试 OD统一考试 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 给定一个由若干整数组成的数组nums &#xff0c;可以在数组内的任意位置进行分割&#xff0c;将该数组分割成两个非空子数组(即左数组和右数组)&#xf…

【2024最新版】neo4j安装配置

neo4j安装 写在最前面下载配置环境&#xff08;还是不行&#xff1f;&#xff09;启动neo4jpython中调用 写在最前面 之前我安装过&#xff0c;还写了一篇笔记 结果意外发现没有了&#xff0c;而且和之前安装的步骤不一样了&#xff0c;因此再次记录安装过程 下载 https://ne…

OpenGL FXAA抗锯齿算法(Qt)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 之前已经提供了使用VCG读取Mesh的方式,接下来就需要针对读取的网格数据进行一些渲染操作了。在绘制Mesh数据时总会遇到图形的抗锯齿问题,OpenGL本身已经为我们提供了一种MSAA技术,但该技术对于一些实时渲染性能有…

【数据结构】栈和队列(栈的基本操作和基础知识)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;《数据结构》https://blog.csdn.net/qinjh_/category_12536791.html?spm1001.2014.3001.5482 目录 前言 栈 栈的概念和结构 栈的实现 ​…

Rust使用gRPC

需要先安装protoc&#xff08;Protocol Buffers Compiler&#xff09;&#xff0c;可据此Protobuf Compiler Installation下载 第一步&#xff1a;创建项目 创建两个新的Rust项目&#xff0c;分别作为服务端与客户端&#xff1a; cargo new rust_grpc_servercargo new rust_grp…

elasticsearch系列九:异地容灾-CCR跨集群复制

概述 起初只在部分业务中采用es存储数据&#xff0c;在主中心搭建了个集群&#xff0c;随着es在我们系统中的地位越来越重要&#xff0c;数据也越来越多&#xff0c;针对它的安全性问题也越发重要&#xff0c;那如何对es做异地容灾呢&#xff1f; 今天咱们就一起看下官方提供的…

Redis(上)

1、redis Redis是一个完全开源免费的高性能&#xff08;NOSQL&#xff09;的key-value数据库。它遵守BSD协议&#xff0c;使用ANSI C语言编写&#xff0c;并支持网络和持久化。Redis拥有极高的性能&#xff0c;每秒可以进行11万次的读取操作和8.1万次的写入操作。它支持丰富的数…

步进电机为什么叫步进电机,内部结构是什么,工作原理是什么,有什么特点,什么用途。

问题描述&#xff1a;步进电机为什么叫步进电机&#xff0c;内部结构是什么&#xff0c;工作原理是什么&#xff0c;有什么特点&#xff0c;什么用途。 问题解答&#xff1a; "步进"一词表示电机按照固定的步进角度运动。步进电机以控制脉冲信号来驱动转子按照一定的…

Vue2中使用echarts,并从后端获取数据同步

一、安装echarts npm install echarts -S 二、导入echarts 在script中导入&#xff0c;比如&#xff1a; import * as echarts from "echarts"; 三、查找要用的示例 比如柱状图 四、初始化并挂载 <template><div id"total-orders-chart" s…

三天吃透Java基础面试八股文

给大家分享我整理的Java高频面试题&#xff0c;有小伙伴靠他拿到字节offer了。 Java基础面试题 Java的特点Java 与 C 的区别JDK/JRE/JVM三者的关系Java程序是编译执行还是解释执行&#xff1f;面向对象和面向过程的区别&#xff1f;面向对象有哪些特性&#xff1f;数组到底是…

适用于电脑的 8 款文件/软件迁移软件 – 快速安全地更换电脑!

将文件/软件从一台设备传输到另一台设备已成为我们日常生活的重要组成部分&#xff0c;无论是出于个人目的还是出于职业目的。在当今快节奏的世界中&#xff0c;我们经常需要在不同设备之间传输大文件&#xff0c;例如视频、照片、文档等。虽然云服务提供了一种共享文件的好方法…

关于Python里xlwings库对Excel表格的操作(二十四)

这篇小笔记主要记录如何【如何使用xlwings库中的“api”类设置单元格边界线型、粗细、颜色】。前面的小笔记已整理成目录&#xff0c;可点链接去目录寻找所需更方便。 【目录部分内容如下】【点击此处可进入目录】 &#xff08;1&#xff09;如何安装导入xlwings库&#xff1b;…

Android--Jetpack--Paging详解

不尝世间醋与墨&#xff0c;怎知人间酸与苦。 择一业谋食养命&#xff0c;等一运扭转乾坤。 你见过哪些令你膛目结舌的代码技巧&#xff1f; 文章目录 不尝世间醋与墨&#xff0c;怎知人间酸与苦。择一业谋食养命&#xff0c;等一运扭转乾坤。你见过哪些令你膛目结舌的代码技…

【GoLang】Go语言几种标准库介绍(三)

文章目录 前言几种库debug 库 (各种调试文件格式访问及调试功能)相关的包和工具&#xff1a;示例 encoding (常见算法如 JSON、XML、Base64 等)常用的子包和其主要功能&#xff1a;示例 flag(命令行解析)关键概念&#xff1a;示例示例执行 总结专栏集锦写在最后 前言 上一篇&a…

【ArcGIS微课1000例】0085:甘肃省白银市平川区4.9级地震震中位置图件制作

据中国地震台网正式测定,12月31日22时27分在甘肃白银市平川区发生4.9级地震,震源深度10公里,震中位于北纬36.74度,东经105.00度。 文章目录 一、白银市行政区划图1. 县级行政区2. 乡镇行政区二、4.9级地震图件制作1. 震中位置2. 影像图3. 震中三维地形一、白银市行政区划图…