YOLOv13改进策略【卷积层篇】| RFAConv 感受野注意力卷积,每个位置都值得被单独加权

本文基于 YOLOv13 官方仓库(iMoonLab/yolov13,ultralytics 8.3.63 fork) 实测整理,Windows/CPU 全程可跑。标准卷积有个隐含假设:3×3 感受野里的 9 个位置同等重要 。RFAConv(Receptive-Field Attention Convolution,2023)推翻了这个假设------先给感受野里每个位置打分,加权之后再卷积。本文把它替换进 v13n 的第 1 层下采样卷积,实测 +4,000 参数。

前言

卷积层改进的"替换式"玩法:本文的候选是 RFAConv,出自论文《RFAConv: Innovating Spatital Attention and Standard Convolution》(2023),官方源码 github.com/Liuchen1997/RFAConv。它是"空间注意力 + 标准卷积"的融合体:注意力不再作用在特征图上,而是作用在卷积核的感受野内部 。替换点选在 v13n backbone 的层 1(输入 16 通道 @320×320 → 输出 32 通道 @160×160),与普通 stride-2 卷积的输入输出完全对齐。

专栏目录 :YOLOv13改进目录一览

专栏地址 :YOLOv13改进专栏------持续更新各方向即插即用改进

一、替换点分析

yaml 复制代码
backbone:
  - [-1, 1, Conv,  [64, 3, 2]]          # 0-P1/2
  - [-1, 1, Conv,  [128, 3, 2, 1, 2]]   # 1-P2/4  ← 本文替换这一层
  ...

层数不变,无顺移。层 1 的输入 16 通道、输出 32 通道、stride 2------RFAConv 官方实现支持 stride 参数(k=3, s=2 时输出恰为输入的一半,本文已数值验证),可直接对齐。

二、RFAConv 原理

  • 动机 :空间注意力(CBAM 的空间分支等)回答"特征图上哪个位置重要",但卷积核滑窗内部------即感受野的 9 个位置------始终被同等对待。感受野内部的差异信息被池化/下采样给抹掉了;
  • RFAConv 的三步 :
    1. 打分:AvgPool 采样 + 分组 1×1 卷积,为每个感受野生成 k² 个权重,softmax 归一化------"这 9 个位置谁重要";
    2. 提特征:分组 3×3 卷积把每个感受野展开成 k² 个特征片,乘上权重;
    3. 重排 + 卷积 :把加权后的特征片按空间顺序重排回大图(等价于官方的 rearrange('b c (n1 n2) h w -> b c (h n1) (w n2)')),再做一次标准卷积输出;
  • 效果:感受野内部的位置信息被显式保留和加权,而标准卷积把 9 个位置压成一组共享权重。

三、实现代码

由官方 Liuchen1997/RFAConv 等价适配 :官方用 einops.rearrange 做空间重排,本文以 view/permute 等价替换,已做同权重输出逐位比对(320×320 输入下 max diff = 0):

python 复制代码
import torch
import torch.nn as nn


class RFAConv(nn.Module):
    """RFAConv 感受野注意力卷积 (Liu et al., 2023),官方等价适配版。

    输出空间尺寸 = 输入 / stride(k=3, s=2 时与普通 stride-2 卷积对齐,
    可直接替换下采样卷积)。
    """

    def __init__(self, c1, c2, kernel_size=3, stride=1):
        super().__init__()
        self.kernel_size = kernel_size
        self.get_weight = nn.Sequential(
            nn.AvgPool2d(kernel_size=kernel_size, padding=kernel_size // 2, stride=stride),
            nn.Conv2d(c1, c1 * (kernel_size ** 2), kernel_size=1, groups=c1, bias=False))
        self.generate_feature = nn.Sequential(
            nn.Conv2d(c1, c1 * (kernel_size ** 2), kernel_size=kernel_size,
                      padding=kernel_size // 2, stride=stride, groups=c1, bias=False),
            nn.BatchNorm2d(c1 * (kernel_size ** 2)),
            nn.ReLU())
        self.conv = nn.Sequential(
            nn.Conv2d(c1, c2, kernel_size=kernel_size, stride=kernel_size))

    def forward(self, x):
        b, c = x.shape[0:2]
        weight = self.get_weight(x)
        h, w = weight.shape[2:]
        weighted = weight.view(b, c, self.kernel_size ** 2, h, w).softmax(2)
        feature = self.generate_feature(x).view(b, c, self.kernel_size ** 2, h, w)
        weighted_data = feature * weighted
        # 等价于官方 rearrange('b c (n1 n2) h w -> b c (h n1) (w n2)')
        conv_data = weighted_data.view(b, c, self.kernel_size, self.kernel_size, h, w)
        conv_data = conv_data.permute(0, 1, 4, 2, 5, 3).reshape(
            b, c, h * self.kernel_size, w * self.kernel_size)
        return self.conv(conv_data)

注意签名与 Conv 对齐(c1 自动传入,c2/k/s 来自 yaml),注册用卷积类分支(c2 宽度缩放)。

四、添加步骤(v13 版)

1. 修改 ultralytics/nn/modules/conv.py

RFAConv 类粘贴到 conv.py 末尾。

2. 修改 ultralytics/nn/modules/__init__.py

from .conv import (...) 里追加 RFAConv,;__all__ 里给最后一项 "DSConv" 补逗号后追加 "RFAConv"。

3. 修改 ultralytics/nn/tasks.py

顶部导入块加 RFAConv;parse_model() 里新增卷积类分支(c2 宽度缩放):

python 复制代码
        elif m is RFAConv:  # 卷积类:c1 自动传入,c2 按宽度系数缩放
            c1 = ch[f]
            c2 = args[0]
            if c2 != nc:
                c2 = make_divisible(min(c2, max_channels) * width, 8)
            args = [c1, c2, *args[1:]]

插入位置:elif m is FullPAD_Tunnel: 之后、else: 之前。

五、yaml 模型文件

只改第 1 层,其余与官方 yolov13.yaml 逐行一致:

yaml 复制代码
# Ultralytics YOLOv13n + RFAConv 感受野注意力卷积 🚀
nc: 80
scales:
  n: [0.50, 0.25, 1024]
  s: [0.50, 0.50, 1024]
  l: [1.00, 1.00, 512]
  x: [1.00, 1.50, 512]

backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv,  [64, 3, 2]]          # 0-P1/2
  - [-1, 1, RFAConv, [128, 3, 2]]       # 1-P2/4 ★RFAConv(替换stride-2卷积,层数不变)
  - [-1, 2, DSC3k2,  [256, False, 0.25]]# 2
  - [-1, 1, Conv,  [256, 3, 2, 1, 4]]   # 3-P3/8
  - [-1, 2, DSC3k2,  [512, False, 0.25]]# 4
  - [-1, 1, DSConv,  [512, 3, 2]]       # 5-P4/16
  - [-1, 4, A2C2f, [512, True, 4]]      # 6
  - [-1, 1, DSConv,  [1024, 3, 2]]      # 7-P5/32
  - [-1, 4, A2C2f, [1024, True, 1]]     # 8

head:
  - [[4, 6, 8], 2, HyperACE, [512, 8, True, True, 0.5, 1, "both"]] # 9
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]  # 10
  - [ 9, 1, DownsampleConv, []]         # 11
  - [[6, 9], 1, FullPAD_Tunnel, []]     # 12
  - [[4, 10], 1, FullPAD_Tunnel, []]    # 13
  - [[8, 11], 1, FullPAD_Tunnel, []]    # 14
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]  # 15
  - [[-1, 12], 1, Concat, [1]]          # 16 cat backbone P4
  - [-1, 2, DSC3k2, [512, True]]        # 17
  - [[-1, 9], 1, FullPAD_Tunnel, []]    # 18
  - [17, 1, nn.Upsample, [None, 2, "nearest"]]  # 19
  - [[-1, 13], 1, Concat, [1]]          # 20 cat backbone P3
  - [-1, 2, DSC3k2, [256, True]]        # 21
  - [10, 1, Conv, [256, 1, 1]]          # 22
  - [[21, 22], 1, FullPAD_Tunnel, []]   # 23
  - [-1, 1, Conv, [256, 3, 2]]          # 24
  - [[-1, 18], 1, Concat, [1]]          # 25 cat head P4
  - [-1, 2, DSC3k2, [512, True]]        # 26
  - [[-1, 9], 1, FullPAD_Tunnel, []]    # 27
  - [26, 1, Conv, [512, 3, 2]]          # 28
  - [[-1, 14], 1, Concat, [1]]          # 29 cat head P5
  - [-1, 2, DSC3k2, [1024,True]]        # 30
  - [[-1, 11], 1, FullPAD_Tunnel, []]   # 31
  - [[23, 27, 31], 1, Detect, [nc]]     # 32 Detect(P3, P4, P5)

六、成功运行结果

bash 复制代码
yolo detect train model=yolov13n-RFAConv.yaml data=mydata.yaml epochs=100 imgsz=640 batch=16

Python 方式:

python 复制代码
from ultralytics import YOLO

model = YOLO("yolov13n-RFAConv.yaml").load("yolov13n.pt")
model.train(data="mydata.yaml", epochs=100, imgsz=640, batch=16)

本文实测(YOLOv13 官方仓库,Windows CPU,v13n 结构,替换层 1):

复制代码
基线 yolov13n:  2,494,151 parameters, 6.5 GFLOPs(层1为 2,368 参数)
+RFAConv:       2,498,151 parameters, 6.7 GFLOPs(层1为 6,368 参数)
3 epochs completed.  Results saved to runs\detect\v13_rfaconv_smoke

✅ 断言全部通过:① 适配版与官方 RFAConv 同权重输出逐位一致(max diff = 0,einops 重排已等价替换);② 第 1 层为 RFAConv,总参数量恰为基线 +4,000;③ 层数不变,整网 640 前向正常,3 轮冒烟训练正常收敛。

参数量分解(c1=16, c2=32, k=3, s=2):

部件 参数量 说明
get_weight 分组 1×1 144 感受野打分(16 组,每组 9 分)
generate_feature 分组 3×3 + BN 1,584 感受野特征展开
输出卷积 3×3 4,640 重排后的标准卷积
合计 6,368(+4,000)
模型 Params GFLOPs 层1参数变化 说明
YOLOv13n 基线 2,494,151 6.5 2,368 官方 stride-2 分组卷积
+RFAConv 2,498,151 6.7 6,368(+4,000) 感受野内逐位置加权

七、总结

RFAConv 的三点记忆锚:注意力作用在感受野内部 (k² 个位置打分,而非整张特征图)、空间重排是关键一步 (把加权后的特征片拼回大图再卷积)、stride 由输出卷积的 stride=kernel_size 实现(k=3, s=2 恰好对齐下采样)。它与 CoordConv 同位替换层 1,两者互为对照:CoordConv 补"绝对位置感",RFAConv 补"感受野内相对重要性"。

觉得有帮助的话,点赞收藏关注三连支持一下,评论区欢迎交流你的实验结果~

专栏目录:YOLOv13改进目录一览 专栏地址:YOLOv13改进专栏------持续更新各方向即插即用改进

相关推荐
Web3&Basketball1 小时前
给Agent上线前加一道自动化稳定性闸门
深度学习·大模型·ai技术
yi0112 小时前
Leetcode 49 用 “身份证‘‘巧解
人工智能·笔记·python·算法·leetcode·哈希表
liuchangng2 小时前
类Jev项目Kev从入门到实战(7):如何选型:决策树与 checklist
算法·决策树·机器学习
yi0112 小时前
DAY22: LeetCode 733:图像渲染——从二维网格开始理解 DFS 和 BFS
数据结构·笔记·python·算法·leetcode·深度优先·宽度优先
这张生成的图像能检测吗2 小时前
(论文速读)一种用于图像超分辨率的有效扩散变换结构
人工智能·计算机视觉·扩散模型·超分辨率
Pniubi2 小时前
力扣41缺失的第一个正数(哈希表法)
算法·leetcode·职场和发展
指针向南2 小时前
视频截图发黑先检查透明区域
图像处理·人工智能·计算机视觉·音视频
地平线开发者2 小时前
MQBench QAT量化实践指南
人工智能·算法·自动驾驶
今夜有雨.2 小时前
图像运算、掩膜/ROI 与绘制交互
c语言·数据结构·c++·qt·算法·计算机视觉