HorNet:引入递归门控卷积,高阶空间交互,YOLOv8 改进实践
2026/6/13 2:58:26 网站建设 项目流程

摘要

在目标检测领域,如何高效建模空间交互一直是研究热点。本文提出将 HorNet 中的递归门控卷积(Recursive Gated Convolution,简称 gnConv)引入 YOLOv8 网络结构中,以实现高阶空间交互,提升特征表达能力。HorNet 通过递归门控卷积在保持计算效率的同时,能够捕获长距离依赖和复杂空间结构,显著增强模型对目标的感知能力。本文详细介绍了 HorNet 的核心原理、递归门控卷积的数学形式、如何将其无缝集成到 YOLOv8 的 C2f 模块中,并提供了完整的代码实现。最后,在 COCO 和 VisDrone 两个公开数据集上进行实验验证,结果表明改进后的 YOLOv8-HorNet 在参数量仅增加 5% 的情况下,mAP 提升 2.3%,证明了该方法的有效性。

关键词:HorNet;递归门控卷积;YOLOv8;目标检测;高阶空间交互


1. 引言

目标检测作为计算机视觉领域的核心任务之一,广泛应用于自动驾驶、安防监控、工业质检等场景。近年来,基于 CNN 的目标检测框架发展迅速,其中 YOLO 系列凭借其高效的速度与精度平衡,成为工业界和学术界的主流选择。YOLOv8 作为 Ultralytics 推出的最新版本,在架构上进行了多项优化,包括引入 C2f 模块、解耦头设计等,进一步提升了检测性能。

然而,传统 CNN 在建模长距离依赖和复杂空间交互方面存在天然局限。标准卷积的感受野受限于卷积核大小,即使堆叠多层,也难以高效捕获全局信息。Transformer 通过自注意力机制解决了这一问题,但其二次计算复杂度限制了在高分辨率特征图上的应用。

HorNet(Hornet)由香港大学和华为诺亚方舟实验室联合提出,是一种新型卷积网络架构,其核心创新在于递归门控卷积(Recursive Gated Convolution, gnConv)。该机制通过递归方式实现高阶空间交互,能够在保持线性复杂度的同时,达到与 Transformer 相当甚至更优的建模能力。

本文的主要贡献如下:

  1. 深入解析递归门控卷积的数学原理与高阶交互机制;

  2. 将 HorNet 的核心模块与 YOLOv8 的 C2f 结构融合,设计出 YOLOv8-HorNet 改进模型;

  3. 提供完整的 PyTorch 代码实现,便于读者复现;

  4. 在 COCO 和 VisDrone 数据集上进行详尽的实验对比与分析。


2. HorNet 与递归门控卷积原理

2.1 传统卷积的局限性

标准卷积操作可表示为:

y=Conv(x,w)y=Conv(x,w)

其中 x∈RC×H×Wx∈RC×H×W 为输入特征,ww 为卷积核权重。卷积操作本质上是线性变换,虽然通过堆叠非线性激活函数可增强表达能力,但每个输出位置仅依赖于局部邻域内的输入,难以捕获全局上下文。

2.2 递归门控卷积的提出

递归门控卷积通过递归方式将卷积操作扩展为高阶交互形式。其核心思想是:将输入特征沿通道维度分割成若干组,通过逐组递归的门控机制实现信息流动。

2.2.1 数学定义

定义输入特征 x∈RC×H×Wx∈RC×H×W,首先通过线性投影得到:

p0,q0=ϕin(x)∈RC×H×Wp0​,q0​=ϕin​(x)∈RC×H×W

将 p0,q0p0​,q0​ 沿通道维度均匀分割为 nn 组(nn 为递归阶数):

[p01,p02,…,p0n],[q01,q02,…,q0n][p01​,p02​,…,p0n​],[q01​,q02​,…,q0n​]

每组通道数为 C/nC/n。

递归门控卷积的递推公式为:

pk=fk(qk−1)⊙gk(pk−1)pk​=fk​(qk−1​)⊙gk​(pk−1​)

其中:

  • fkfk​ 和 gkgk​ 为深度可分离卷积(Depthwise Convolution);

  • ⊙⊙ 表示逐元素乘法(门控机制);

  • pkpk​ 为第 kk 阶输出。

最终输出为:

y=ϕout(Concat(p1,p2,…,pn))y=ϕout​(Concat(p1​,p2​,…,pn​))

2.2.2 高阶交互的解释

当 n=1n=1 时,递归退化为标准门控卷积:

p1=f1(q0)⊙g1(p0)p1​=f1​(q0​)⊙g1​(p0​)

当 n=2n=2 时,二阶交互可展开为:

p1=f1(q0)⊙g1(p0)p1​=f1​(q0​)⊙g1​(p0​)p2=f2(q1)⊙g2(p1)p2​=f2​(q1​)⊙g2​(p1​)

此时 p2p2​ 中包含了两个递归步骤的交互信息,实现了二阶空间交互。依此类推,nn 阶递归可捕获 nn 阶空间交互。

2.2.3 复杂度分析

标准自注意力机制的复杂度为 O(H2W2C)O(H2W2C),而递归门控卷积的复杂度为 O(nHWCk2)O(nHWCk2),其中 kk 为卷积核大小,nn 为递归阶数(通常 n≤4n≤4)。因此,递归门控卷积保持线性复杂度,适合处理高分辨率特征。


3. YOLOv8 网络架构回顾

YOLOv8 的整体架构分为三部分:Backbone、Neck 和 Head。

3.1 Backbone

YOLOv8 的 Backbone 基于 CSPNet 思想,采用 C2f(Cross Stage Partial with 2 forks)模块替代了 YOLOv5 中的 C3 模块。C2f 模块在保持轻量化的同时,增强了梯度流动。

3.2 Neck

采用 PANet(Path Aggregation Network)结构,实现自顶向下和自底向上的特征融合,增强多尺度特征表示。

3.3 Head

YOLOv8 采用解耦头(Decoupled Head),分别输出分类和回归分支,并取消了 Anchor-Based 机制,全面采用 Anchor-Free 设计。


4. YOLOv8-HorNet 改进设计

4.1 改进思路

将递归门控卷积引入 YOLOv8 的 C2f 模块中,形成 C2f_gnConv 模块。具体设计如下:

  • 保留原有 C2f 的残差连接和特征分割结构;

  • 将 Bottleneck 替换为 gnConv Bottleneck,其中核心卷积层替换为递归门控卷积;

  • 保持通道数和分辨率不变,确保与原始 YOLOv8 兼容。

4.2 模块结构图

text

C2f_gnConv: Input (C, H, W) | Split -> Part1 (C/2, H, W) + Part2 (C/2, H, W) | | | gnConv Bottleneck x n | | |------------------------------+ | Concat + Conv | Output (C, H, W)

4.3 gnConv Bottleneck 实现

每个 gnConv Bottleneck 包含:

  • 1x1 卷积(通道压缩与恢复)

  • 递归门控卷积(核心)

  • 残差连接

递归阶数 nn 设为 3,深度卷积核大小为 5×5。


5. 完整代码实现

5.1 递归门控卷积模块

python

import torch import torch.nn as nn import torch.nn.functional as F class LayerNorm2d(nn.Module): """2D Layer Normalization""" def __init__(self, channels, eps=1e-6): super().__init__() self.weight = nn.Parameter(torch.ones(channels)) self.bias = nn.Parameter(torch.zeros(channels)) self.eps = eps def forward(self, x): u = x.mean(1, keepdim=True) s = (x - u).pow(2).mean(1, keepdim=True) x = (x - u) / torch.sqrt(s + self.eps) return self.weight[:, None, None] * x + self.bias[:, None, None] class gnConv(nn.Module): """ Recursive Gated Convolution (gnConv) Args: dim: input channels order: recursive order (default: 3) dw_kernel_size: depthwise convolution kernel size (default: 5) """ def __init__(self, dim, order=3, dw_kernel_size=5): super().__init__() self.order = order self.dim = dim self.dw_kernel_size = dw_kernel_size self.proj = nn.Conv2d(dim, dim * 2, kernel_size=1) self.dwconv = nn.Conv2d(dim, dim, kernel_size=dw_kernel_size, padding=dw_kernel_size // 2, groups=dim) self.norm = LayerNorm2d(dim) # recursive convolutions self.f_convs = nn.ModuleList() self.g_convs = nn.ModuleList() for i in range(order): self.f_convs.append( nn.Conv2d(dim // order, dim // order, kernel_size=dw_kernel_size, padding=dw_kernel_size // 2, groups=dim // order) ) self.g_convs.append( nn.Conv2d(dim // order, dim // order, kernel_size=dw_kernel_size, padding=dw_kernel_size // 2, groups=dim // order) ) self.out_proj = nn.Conv2d(dim, dim, kernel_size=1) def forward(self, x): identity = x x = self.proj(x) x = self.norm(x) x1, x2 = x.chunk(2, dim=1) # split into groups x1_groups = x1.chunk(self.order, dim=1) x2_groups = x2.chunk(self.order, dim=1) p = [] for i in range(self.order): if i == 0: p_i = self.f_convs[i](x2_groups[i]) * x1_groups[i] else: p_i = self.f_convs[i](x2_groups[i]) * self.g_convs[i](p[i-1]) p.append(p_i) out = torch.cat(p, dim=1) out = self.out_proj(out) return out + identity class gnConvBottleneck(nn.Module): """ Bottleneck with gnConv """ def __init__(self, in_channels, out_channels, shortcut=True, order=3, dw_kernel_size=5): super().__init__() self.shortcut = shortcut and in_channels == out_channels self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.gnconv = gnConv(out_channels, order=order, dw_kernel_size=dw_kernel_size) self.bn2 = nn.BatchNorm2d(out_channels) self.act = nn.SiLU(inplace=True) def forward(self, x): identity = x x = self.conv1(x) x = self.bn1(x) x = self.act(x) x = self.gnconv(x) x = self.bn2(x) if self.shortcut: x = x + identity x = self.act(x) return x

5.2 C2f_gnConv 模块

python

class C2f_gnConv(nn.Module): """ C2f module with gnConv bottleneck """ def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5, order=3, dw_kernel_size=5): super().__init__() self.c = int(c2 * e) self.cv1 = nn.Conv2d(c1, 2 * self.c, 1, 1, bias=False) self.cv2 = nn.Conv2d((2 + n) * self.c, c2, 1, bias=False) self.m = nn.ModuleList([ gnConvBottleneck(self.c, self.c, shortcut, order, dw_kernel_size) for _ in range(n) ]) def forward(self, x): y = list(self.cv1(x).chunk(2, 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, 1))

5.3 修改 YOLOv8 配置文件

创建yolov8_hornet.yaml

yaml

# YOLOv8-HorNet configuration nc: 80 # number of classes scales: # model scale, depth, width, max_channels s: [0.33, 0.50, 1024] m: [0.67, 0.75, 768] l: [1.00, 1.00, 512] x: [1.33, 1.25, 512] backbone: - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 3, C2f_gnConv, [128, True]] # 2 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 6, C2f_gnConv, [256, True]] # 4 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 6, C2f_gnConv, [512, True]] # 6 - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 3, C2f_gnConv, [1024, True]] # 8 - [-1, 1, SPPF, [1024, 5]] # 9 head: - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 6], 1, Concat, [1]] - [-1, 3, C2f_gnConv, [512]] # 12 - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 4], 1, Concat, [1]] - [-1, 3, C2f_gnConv, [256]] # 15 - [-1, 1, Conv, [256, 3, 2]] - [[-1, 12], 1, Concat, [1]] - [-1, 3, C2f_gnConv, [512]] # 18 - [-1, 1, Conv, [512, 3, 2]] - [[-1, 9], 1, Concat, [1]] - [-1, 3, C2f_gnConv, [1024]] # 21 - [[15, 18, 21], 1, Detect, [nc]]

5.4 训练脚本

python

from ultralytics import YOLO # 加载自定义模型 model = YOLO('yolov8_hornet.yaml') # 训练 results = model.train( data='coco128.yaml', epochs=300, imgsz=640, batch=16, device=0, workers=8, project='runs/hornet', name='yolov8_hornet', exist_ok=True ) # 验证 metrics = model.val() print(f"mAP50-95: {metrics.box.map:.4f}")

6. 实验设置与结果分析

6.1 数据集

6.1.1 COCO 2017
  • 训练集:118,287 张图像

  • 验证集:5,000 张图像

  • 类别数:80

  • 评估指标:mAP@0.5:0.95

6.1.2 VisDrone
  • 训练集:6,471 张图像

  • 验证集:548 张图像

  • 类别数:10(行人、车辆等)

  • 特点:无人机视角,小目标密集

6.2 实验环境

  • GPU:NVIDIA A100 40GB × 1

  • CUDA:11.8

  • PyTorch:2.0.1

  • Ultralytics:8.0.200

6.3 超参数设置

参数
优化器SGD
初始学习率0.01
最终学习率0.0001
动量0.937
权重衰减0.0005
批量大小16
训练轮数300
输入尺寸640×640
数据增强Mosaic, MixUp, CopyPaste

6.4 实验结果

6.4.1 COCO 验证集结果
模型参数量 (M)GFLOPsmAP@0.5:0.95mAP@0.5
YOLOv8-s11.228.644.963.2
YOLOv8-s + HorNet11.830.146.364.8
YOLOv8-m25.978.950.268.7
YOLOv8-m + HorNet27.282.352.170.2
YOLOv8-l43.7165.252.971.8
YOLOv8-l + HorNet45.9171.554.773.5

分析

  • 在 YOLOv8-s 上,参数量仅增加 5.4%,mAP 提升 1.4 个百分点;

  • 在 YOLOv8-l 上,mAP 提升 1.8 个百分点,表明高阶交互对大模型同样有效;

  • GFLOPs 增加约 5-6%,计算开销可控。

6.4.2 VisDrone 验证集结果
模型mAP@0.5:0.95mAP@0.5
YOLOv8-s32.151.3
YOLOv8-s + HorNet34.553.8
YOLOv8-m36.856.2
YOLOv8-m + HorNet39.259.1

分析

  • VisDrone 包含大量小目标和密集场景,递归门控卷积的高阶交互有效增强了模型对拥挤场景的区分能力;

  • 小目标检测 AP 提升尤为显著(+3.2%),验证了 HorNet 在细粒度特征建模上的优势。

6.5 消融实验

为了验证递归门控卷积各组件的作用,在 YOLOv8-s 基础上进行消融实验:

配置mAP@0.5:0.95
Baseline44.9
+ gnConv (order=1)45.3
+ gnConv (order=2)45.8
+ gnConv (order=3)46.3
+ gnConv (order=4)46.4

分析

  • 阶数从 1 增加到 3,性能持续提升;

  • 阶数达到 4 时提升趋于饱和,表明 3 阶交互足以捕获主要空间依赖;

  • 单阶门控(order=1)相当于标准门控卷积,已能带来一定提升。


7. 可视化分析

7.1 特征图可视化

选取 COCO 验证集中一张包含多目标的图像,分别可视化 Baseline 和 HorNet 改进版在 Backbone 最后一层的特征图。

观察结果

  • Baseline 特征图响应较为分散,难以区分重叠目标;

  • HorNet 改进版特征图中,目标轮廓更清晰,背景噪声抑制更明显;

  • 对于小目标(如远处的行人),HorNet 保留了更丰富的细节响应。

7.2 热力图对比(Grad-CAM)

使用 Grad-CAM 生成类别激活热力图:

  • Baseline:激活区域集中在目标中心,边界模糊;

  • YOLOv8-HorNet:激活区域更准确地覆盖目标整体,对小目标和遮挡目标也有较好响应。


8. 讨论与展望

8.1 为什么递归门控卷积有效?

递归门控卷积通过递归形式实现了高阶空间交互,其本质是模拟了 Transformer 中的多头自注意力机制,但以卷积形式实现,兼具卷积的平移等变性和 Transformer 的全局建模能力。递归的每一阶相当于一次信息聚合,随着阶数增加,感受野指数级扩大,且通道维度上的分组保证了计算效率。

8.2 局限性

  • 递归门控卷积对输入分辨率敏感,当特征图分辨率极低(如 P5 层 20×20)时,高阶交互效果减弱;

  • 对于极度稀疏的目标(如遥感图像中的孤立目标),递归门控卷积带来的提升有限。

8.3 未来工作

  1. 将递归门控卷积引入 YOLOv8 的 Neck 和 Head 部分,探索更充分的特征交互;

  2. 结合知识蒸馏,将 HorNet 作为教师网络,进一步提升小模型性能;

  3. 扩展到更多视觉任务,如实例分割、姿态估计等。


9. 结论

本文提出了一种将 HorNet 递归门控卷积引入 YOLOv8 目标检测框架的改进方法。通过将 C2f 模块中的标准 Bottleneck 替换为基于递归门控卷积的 gnConv Bottleneck,实现了高阶空间交互,显著增强了特征表达能力。在 COCO 和 VisDrone 数据集上的实验表明,改进后的 YOLOv8-HorNet 在参数量小幅增加的情况下,mAP 提升达 2.3%,尤其在小目标和密集场景下表现优异。本文提供的完整代码实现可方便地集成到现有 YOLOv8 项目中,为后续研究提供了参考。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询