YOLO目标检测创新改进与实战案例专栏
专栏目录: YOLO有效改进系列及项目实战目录 包含卷积,主干 注意力,检测头等创新机制 以及 各种目标检测分割项目实战案例
专栏链接: YOLO基础解析+创新改进+实战案例
介绍
摘要
自从引入视觉变换器(Vision Transformers)以来,许多计算机视觉任务(如语义分割)的格局发生了显著的变革,这些任务曾经被CNN压倒性地主导。然而,计算成本和内存需求使得这些方法在移动设备上,尤其是在高分辨率逐像素语义分割任务中显得不适用。在本文中,我们介绍了一种新的方法——压缩增强轴向变换器(SeaFormer)用于移动端语义分割。具体来说,我们设计了一个通用的注意力模块,其特点是压缩轴向和细节增强的结合。它可以进一步用于创建一系列具有优越性价比的骨干架构。结合一个轻量级的分割头,我们在ADE20K和Cityscapes数据集上的ARM架构移动设备上实现了分割精度和延迟之间的最佳平衡。关键是,我们在没有任何花哨技巧的情况下,以更好的性能和更低的延迟击败了移动友好型的竞争对手和基于变换器的对手。除了语义分割之外,我们还将提出的SeaFormer架构应用于图像分类问题,展示了其作为通用移动友好型骨干的潜力。我们的代码和模型已在https://github.com/fudan-zvg/SeaFormer公开发布。
文章链接
论文地址:论文地址
代码地址:代码地址
基本原理
在文章中提出了一种名为"Squeeze-enhanced Axial Attention"的注意力模块,结合了全局语义提取和局部细节增强,旨在提高计算效率并同时聚合全局信息。以下是Squeeze-enhanced Axial Attention的技术原理:
全局语义提取:
- 首先,从输入特征图 $x$ 中通过线性投影得到查询向量 $q$、键向量 $k$ 和数值向量 $v$。
- 通过水平和垂直方向的压缩操作,将查询、键、数值向量分别压缩为 $q(h)$、$k(h)$、$v(h)$ 和 $q(v)$、$k(v)$、$v(v)$。
- 使用 softmax 函数计算每个位置的注意力权重,并将权重与对应数值向量相乘,得到全局语义提取的输出。
局部细节增强:
- 在全局语义提取的基础上,引入了一个卷积操作的细节增强核,用于增强空间细节。
- 通过另一组线性投影得到新的查询、键、数值向量,并将它们与全局语义提取的输出进行通道拼接。
- 将通道拼接后的特征传递给一个由3x3深度卷积和批量归一化组成的块,以增强局部细节信息。
时间复杂度:
- 压缩操作使得 Squeeze-enhanced Axial Attention 的时间复杂度降低到 $O(HW)$,有效减少了计算成本。
- 通过将全局语义提取和局部细节增强相结合,实现了全局信息聚合和局部细节增强的平衡,提高了特征提取的效率和性能。
以下是该注意力模块的公式:
Squeeze-enhanced Axial Attention的公式:
全局语义提取部分:
$$ q(h) = \frac{1}{W} \left( q \rightarrow (C_{qk}, H, W) \frac{1}{W} \right) \rightarrow (H, C_{qk}) $$$$ q(v) = \frac{1}{H} \left( q \rightarrow (C_{qk}, W, H) \frac{1}{H} \right) \rightarrow (W, C_{qk}) $$
局部细节增强部分:
$$ y_o = \sum_{p \in N^{m \times m}(o)} \text{softmax}_p(q > o \cdot k_p) \cdot v_p $$$$ y_o = \sum_{p \in N^{1 \times W}(o)} \text{softmax}_p(q > o \cdot k_p) \cdot v_p + \sum_{p \in N^{H \times 1}(o)} \text{softmax}_p(q > o \cdot k_p) \cdot v_p $$
在这些公式中,$q(h)$ 和 $q(v)$ 分别表示全局语义提取部分的查询向量,$y_o$ 表示输出向量,$k_p$ 和 $v_p$ 分别表示键向量和数值向量,$N^{m \times m}(o)$、$N^{1 \times W}(o)$ 和 $N^{H \times 1}(o)$ 分别表示局部邻域的位置集合。
核心代码
class Sea_Attention(torch.nn.Module):
def __init__(self, dim, key_dim=16, num_heads=4,
attn_ratio=2,
activation=None,
norm_cfg=dict(type='BN', requires_grad=True), ):
super().__init__()
self.num_heads = num_heads
self.scale = key_dim ** -0.5
self.key_dim = key_dim
self.nh_kd = nh_kd = key_dim * num_heads # num_head key_dim
self.d = int(attn_ratio * key_dim)
self.dh = int(attn_ratio * key_dim) * num_heads
self.attn_ratio = attn_ratio
self.to_q = Conv2d_BN(dim, nh_kd, 1, norm_cfg=norm_cfg)
self.to_k = Conv2d_BN(dim, nh_kd, 1, norm_cfg=norm_cfg)
self.to_v = Conv2d_BN(dim, self.dh, 1, norm_cfg=norm_cfg)
self.proj = torch.nn.Sequential(activation(), Conv2d_BN(
self.dh, dim, bn_weight_init=0, norm_cfg=norm_cfg))
self.proj_encode_row = torch.nn.Sequential(activation(), Conv2d_BN(
self.dh, self.dh, bn_weight_init=0, norm_cfg=norm_cfg))
self.pos_emb_rowq = SqueezeAxialPositionalEmbedding(nh_kd, 16)
self.pos_emb_rowk = SqueezeAxialPositionalEmbedding(nh_kd, 16)
self.proj_encode_column = torch.nn.Sequential(activation(), Conv2d_BN(
self.dh, self.dh, bn_weight_init=0, norm_cfg=norm_cfg))
self.pos_emb_columnq = SqueezeAxialPositionalEmbedding(nh_kd, 16)
self.pos_emb_columnk = SqueezeAxialPositionalEmbedding(nh_kd, 16)
self.dwconv = Conv2d_BN(self.dh + 2 * self.nh_kd, 2 * self.nh_kd + self.dh, ks=3, stride=1, pad=1, dilation=1,
groups=2 * self.nh_kd + self.dh, norm_cfg=norm_cfg)
self.act = activation()
self.pwconv = Conv2d_BN(2 * self.nh_kd + self.dh, dim, ks=1, norm_cfg=norm_cfg)
self.sigmoid = h_sigmoid()
task与yaml配置
详见:https://blog.csdn.net/shangyanaf/article/details/139652325