欢迎光临
我们一直在努力

【AAAI 2026即插即用模块】LWGA轻量级分组注意力,面向遥感图像的轻量化异构分组注意力机制,适合语义分割、图像分类、目标检测、实例分割、变化检测等任务!CV任务通用,涨点起飞

一、论文信息

本文目录

一、论文信息

二、论文摘要概况

三、LWGA轻量级分组注意力模块结构图

四、LWGA模块的作用

五、LWGA模块的原理

六、LWGA模块的优势

七、即插即用模块代码


论文题目:LWGANet: Addressing Spatial and Channel Redundancy in Remote Sensing Visual Tasks with Light-Weight Grouped Attention

中文题目:LWGANet:利用轻量级分组注意力机制解决遥感视觉任务中的空间与通道冗余问题

论文链接:https://arxiv.org/abs/2501.10040

所属单位:安徽大学计算机科学与技术学院

二、论文摘要概况

用于遥感(RS)视觉分析的轻量级神经网络必须克服两种固有的冗余问题:来自广阔均匀背景的空间冗余,以及通道冗余——极端尺度变化会导致单一特征空间效率低下。现有的模型通常针对自然图像设计,难以应对遥感场景中的这一双重挑战。为弥补这一缺陷,我们提出了LWGANet——一种专为遥感特性设计的轻量级骨干网络。LWGANet引入两项核心创新:Top-K全局特征交互(TGFI)模块通过将计算重点聚焦于显著区域来缓解空间冗余;轻量级分组注意力(LWGA)模块则通过将通道划分为专门的、尺度特定路径来解决通道冗余问题。通过协同解决这些核心不足,LWGANet在特征表示质量与计算成本之间实现了更优的平衡。我们在涵盖场景分类、方向性目标检测、语义分割和变化检测四大遥感任务的12个多样化数据集上进行的广泛实验表明,LWGANet在准确性和效率方面均持续超越当前最先进的轻量级骨干网络。我们的研究为遥感图像的高效视觉分析建立了新的、稳健的基准模型。

在UAVid测试集(Lyu等人,2020)上进行的可视化对比显示:使用UnetFormer(Wang等人,2022a)作为解码器时效果最佳;基于卷积网络的FasterNet(Chen等人,2023)虽能精准捕捉建筑细节,但在处理移动车辆分割所需的全局上下文信息时表现欠佳;而利用全局注意力机制的EfficientFormer V2(Li等人,2023b)虽能有效分割车辆,却无法保留精细的建筑结构特征。我们的LWGANet通过同时建模局部细节与长距离依赖关系,实现了更优的平衡效果。

三、LWGA轻量级分组注意力模块结构图

四、LWGA模块的作用

1)解决通道冗余问题

LWGA(Light-Weight Grouped Attention)模块是LWGANet的核心模块,旨在解决遥感图像中的通道冗余(Channel Redundancy)问题。由于遥感图像中存在极大的尺度变化,同一特征空间难以同时高效表达小目标和大目标信息,导致部分通道利用率较低。LWGA通过将通道划分为多个专门化分支,使不同通道负责不同尺度特征的建模,从而提高特征利用效率和表达能力。

(2)增强多尺度特征表达能力

遥感场景中常同时包含车辆、船舶等小目标以及机场、港口等大型目标。LWGA通过多分支结构分别学习细粒度信息、局部纹理信息、中尺度结构信息和全局语义信息,实现多尺度特征协同建模,从而增强网络对复杂遥感场景的感知能力。

(3)实现局部与全局信息融合

LWGA能够同时建模局部细节和长距离依赖关系。通过卷积分支提取局部纹理,通过注意力分支捕获中尺度和全局语义信息,使网络既能保留目标边缘和纹理,又能理解整体场景结构,实现局部与全局信息的有效融合。

(4)提升遥感场景适应能力

针对遥感图像背景占比大、目标分布稀疏以及尺度跨度大的特点,LWGA采用专门设计的多尺度特征提取策略,使网络能够更加准确地处理复杂地物和多尺度目标,提高模型在遥感视觉任务中的泛化性能。

在 DOTA 1.0测试集上的定性比较:所有骨干网络均与定向R-CNN检测器集成。LWGANet在多尺度目标检测方面表现卓越,能准确识别大型港口和小型船舶;而其他方法则存在漏检或定位不准确的问题。

五、LWGA模块的原理

(1)异构分组机制

LWGA首先将输入特征按照通道维度均匀划分为四组,不同于传统分组卷积对所有组采用相同运算方式,LWGA为每组特征设计不同的信息处理路径,使各组专门学习不同尺度的信息,从而提升特征表示效率。

(2)GPA分支(Gate Point Attention)

功能定位:细粒度特征建模

GPA分支负责提取点级细节信息,通过门控注意力机制增强关键像素点响应,重点关注小目标、边缘结构和高频纹理信息,提高网络对车辆、行人等微小目标的识别能力。

(3)RLA分支(Regular Local Attention)

功能定位:局部纹理建模

RLA分支采用标准卷积结构进行局部特征提取,利用卷积的局部感受野优势学习建筑物纹理、道路边缘和局部空间模式,为后续高级语义建模提供稳定的局部特征基础。

(4)SMA分支(Sparse Medium-range Attention)

功能定位:中尺度结构建模

SMA分支结合TGFI模块进行稀疏采样,通过建立中距离空间依赖关系获取区域上下文信息,重点学习河流、道路网络和农田等中尺度结构特征,扩大网络感受野并增强区域理解能力。

(5)SGA分支(Sparse Global Attention)

功能定位:全局语义建模

SGA分支负责捕获长距离依赖关系和全局场景信息,通过稀疏全局注意力机制建立场景级语义关联,实现对机场、港口以及城市布局等大尺度目标和整体场景结构的理解。

(6)多尺度特征融合

四个分支完成特征提取后,将输出结果沿通道维度进行拼接融合,形成同时包含细节信息、局部纹理、中尺度结构和全局语义的综合特征表示,实现统一的多尺度特征建模。

六、LWGA模块的优势

1)降低通道冗余

通过将不同通道分配给不同尺度特征的学习任务,LWGA避免了传统统一特征空间中的重复建模问题,提高了通道利用率,减少无效计算,实现更加高效的特征表达。

(2)强化多尺度建模能力

四条专门化路径分别对应不同尺度范围的信息提取,使网络能够同时学习细节特征、中尺度结构和全局语义信息,有效解决遥感图像中目标尺度跨度大的问题。

(3)兼顾局部细节与全局语义

GPA和RLA保证局部细节和纹理信息不丢失,SMA和SGA负责捕获更大范围的上下文信息和场景语义,实现细节保留与全局理解的统一,提高整体特征表达质量。

(4)计算效率高

LWGA结合TGFI模块进行稀疏特征筛选,仅对关键区域进行复杂注意力计算,大幅降低了计算复杂度和参数规模,在保证精度提升的同时保持较高推理速度。

(5)适合遥感图像特征特点

遥感图像具有背景区域广、目标分布稀疏以及尺度变化剧烈等特点。LWGA针对这些特性设计了细节、局部、中尺度和全局四层信息建模机制,因此比传统轻量化网络更适合遥感场景分析任务。

(6)具有良好的任务泛化能力

实验结果表明,LWGA不仅能够提升场景分类性能,同时在目标检测、语义分割和变化检测等多个遥感视觉任务中均表现出稳定的性能增益,说明其学习到的特征具有较强的通用性和迁移能力。

在使用UnetFormer作为分割头的LoveDA测试集上获得的定性结果表明:LWGANet生成的分割图更清晰,道路与建筑物的边界更为精确,充分展现了其相较于其他骨干网络在捕捉精细细节方面的卓越能力。

七、即插即用模块代码

import torch
import torch.nn as nn
from timm.models.layers import DropPath
from typing import List
from torch import Tensor
import antialiased_cnns
import torch.nn.functional as F

class PA(nn.Module):
def __init__(self, dim, norm_layer, act_layer):
super().__init__()
self.p_conv = nn.Sequential(
nn.Conv2d(dim, dim*4, 1, bias=False),
norm_layer(dim*4),
act_layer(),
nn.Conv2d(dim*4, dim, 1, bias=False)
)
self.gate_fn = nn.Sigmoid()

def forward(self, x):
att = self.p_conv(x)
x = x * self.gate_fn(att)

return x

class LA(nn.Module):
def __init__(self, dim, norm_layer, act_layer):
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(dim, dim, 3, 1, 1, bias=False),
norm_layer(dim),
act_layer()
)

def forward(self, x):
x = self.conv(x)
return x

class MRA(nn.Module):
def __init__(self, channel, att_kernel, norm_layer):
super().__init__()
att_padding = att_kernel // 2
self.gate_fn = nn.Sigmoid()
self.channel = channel
self.max_m1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.max_m2 = antialiased_cnns.BlurPool(channel, stride=3)
self.H_att1 = nn.Conv2d(channel, channel, (att_kernel, 3), 1, (att_padding, 1), groups=channel, bias=False)
self.V_att1 = nn.Conv2d(channel, channel, (3, att_kernel), 1, (1, att_padding), groups=channel, bias=False)
self.H_att2 = nn.Conv2d(channel, channel, (att_kernel, 3), 1, (att_padding, 1), groups=channel, bias=False)
self.V_att2 = nn.Conv2d(channel, channel, (3, att_kernel), 1, (1, att_padding), groups=channel, bias=False)
self.norm = norm_layer(channel)

def forward(self, x):
x_tem = self.max_m1(x)
x_tem = self.max_m2(x_tem)
x_h1 = self.H_att1(x_tem)
x_w1 = self.V_att1(x_tem)
x_h2 = self.inv_h_transform(self.H_att2(self.h_transform(x_tem)))
x_w2 = self.inv_v_transform(self.V_att2(self.v_transform(x_tem)))

att = self.norm(x_h1 + x_w1 + x_h2 + x_w2)

out = x[:, :self.channel, :, :] * F.interpolate(self.gate_fn(att),
size=(x.shape[-2], x.shape[-1]),
mode='nearest')
return out

def h_transform(self, x):
shape = x.size()
x = torch.nn.functional.pad(x, (0, shape[-1]))
x = x.reshape(shape[0], shape[1], -1)[…, :-shape[-1]]
x = x.reshape(shape[0], shape[1], shape[2], 2*shape[3]-1)
return x

def inv_h_transform(self, x):
shape = x.size()
x = x.reshape(shape[0], shape[1], -1).contiguous()
x = torch.nn.functional.pad(x, (0, shape[-2]))
x = x.reshape(shape[0], shape[1], shape[-2], 2*shape[-2])
x = x[…, 0: shape[-2]]
return x

def v_transform(self, x):
x = x.permute(0, 1, 3, 2)
shape = x.size()
x = torch.nn.functional.pad(x, (0, shape[-1]))
x = x.reshape(shape[0], shape[1], -1)[…, :-shape[-1]]
x = x.reshape(shape[0], shape[1], shape[2], 2*shape[3]-1)
return x.permute(0, 1, 3, 2)

def inv_v_transform(self, x):
x = x.permute(0, 1, 3, 2)
shape = x.size()
x = x.reshape(shape[0], shape[1], -1)
x = torch.nn.functional.pad(x, (0, shape[-2]))
x = x.reshape(shape[0], shape[1], shape[-2], 2*shape[-2])
x = x[…, 0: shape[-2]]
return x.permute(0, 1, 3, 2)

class GA12(nn.Module):
def __init__(self, dim, act_layer):
super().__init__()
self.downpool = nn.MaxPool2d(kernel_size=2, stride=2, return_indices=True)
self.uppool = nn.MaxUnpool2d((2, 2), 2, padding=0)
self.proj_1 = nn.Conv2d(dim, dim, 1)
self.activation = act_layer()
self.conv0 = nn.Conv2d(dim, dim, 5, padding=2, groups=dim)
self.conv_spatial = nn.Conv2d(dim, dim, 7, stride=1, padding=9, groups=dim, dilation=3)
self.conv1 = nn.Conv2d(dim, dim // 2, 1)
self.conv2 = nn.Conv2d(dim, dim // 2, 1)
self.conv_squeeze = nn.Conv2d(2, 2, 7, padding=3)
self.conv = nn.Conv2d(dim // 2, dim, 1)
self.proj_2 = nn.Conv2d(dim, dim, 1)

def forward(self, x):
x_, idx = self.downpool(x)
x_ = self.proj_1(x_)
x_ = self.activation(x_)
attn1 = self.conv0(x_)
attn2 = self.conv_spatial(attn1)

attn1 = self.conv1(attn1)
attn2 = self.conv2(attn2)

attn = torch.cat([attn1, attn2], dim=1)
avg_attn = torch.mean(attn, dim=1, keepdim=True)
max_attn, _ = torch.max(attn, dim=1, keepdim=True)
agg = torch.cat([avg_attn, max_attn], dim=1)
sig = self.conv_squeeze(agg).sigmoid()
attn = attn1 * sig[:, 0, :, :].unsqueeze(1) + attn2 * sig[:, 1, :, :].unsqueeze(1)
attn = self.conv(attn)
x_ = x_ * attn
x_ = self.proj_2(x_)
x = self.uppool(x_, indices=idx)
return x

class D_GA(nn.Module):

def __init__(self, dim, norm_layer):
super().__init__()
self.norm = norm_layer(dim)
self.attn = GA(dim)
self.downpool = nn.MaxPool2d(kernel_size=2, stride=2, return_indices=True)
self.uppool = nn.MaxUnpool2d((2, 2), 2, padding=0)

def forward(self, x):
x_, idx = self.downpool(x)
x = self.norm(self.attn(x_))
x = self.uppool(x, indices=idx)

return x

class GA(nn.Module):
def __init__(self, dim, head_dim=4, num_heads=None, qkv_bias=False,
attn_drop=0., proj_drop=0., proj_bias=False, **kwargs):
super().__init__()

self.head_dim = head_dim
self.scale = head_dim ** -0.5

self.num_heads = num_heads if num_heads else dim // head_dim
if self.num_heads == 0:
self.num_heads = 1

self.attention_dim = self.num_heads * self.head_dim
self.qkv = nn.Linear(dim, self.attention_dim * 3, bias=qkv_bias)
self.attn_drop = nn.Dropout(attn_drop)
self.proj = nn.Linear(self.attention_dim, dim, bias=proj_bias)
self.proj_drop = nn.Dropout(proj_drop)

def forward(self, x):
B, C, H, W = x.shape
x = x.permute(0, 2, 3, 1)
N = H * W
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)
q, k, v = qkv.unbind(0) # make torchscript happy (cannot use tensor as tuple)

attn = (q @ k.transpose(-2, -1)) * self.scale
attn = attn.softmax(dim=-1)
attn = self.attn_drop(attn)

x = (attn @ v).transpose(1, 2).reshape(B, H, W, self.attention_dim)
x = self.proj(x)
x = self.proj_drop(x)
x = x.permute(0, 3, 1, 2)
return x

class LWGA_Block(nn.Module):
def __init__(self,
dim,
stage,
att_kernel,
mlp_ratio,
drop_path,
act_layer,
norm_layer
):
super().__init__()
self.stage = stage
self.dim_split = dim // 4
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()

mlp_hidden_dim = int(dim * mlp_ratio)

mlp_layer: List[nn.Module] = [
nn.Conv2d(dim, mlp_hidden_dim, 1, bias=False),
norm_layer(mlp_hidden_dim),
act_layer(),
nn.Conv2d(mlp_hidden_dim, dim, 1, bias=False)
]

self.mlp = nn.Sequential(*mlp_layer)

self.PA = PA(self.dim_split, norm_layer, act_layer) # PA is point attention
self.LA = LA(self.dim_split, norm_layer, act_layer) # LA is local attention
self.MRA = MRA(self.dim_split, att_kernel, norm_layer) # MRA is medium-range attention
if stage == 2:
self.GA3 = D_GA(self.dim_split, norm_layer) # GA3 is global attention (stage of 3)
elif stage == 3:
self.GA4 = GA(self.dim_split) # GA4 is global attention (stage of 4)
self.norm = norm_layer(self.dim_split)
else:
self.GA12 = GA12(self.dim_split, act_layer) # GA12 is global attention (stages of 1 and 2)
self.norm = norm_layer(self.dim_split)
self.norm1 = norm_layer(dim)
self.drop_path = DropPath(drop_path)

def forward(self, x: Tensor) -> Tensor:
# for training/inference
shortcut = x.clone()
x1, x2, x3, x4 = torch.split(x, [self.dim_split, self.dim_split, self.dim_split, self.dim_split], dim=1)
x1 = x1 + self.PA(x1)
x2 = self.LA(x2)
x3 = self.MRA(x3)
if self.stage == 2:
x4 = x4 + self.GA3(x4)
elif self.stage == 3:
x4 = self.norm(x4 + self.GA4(x4))
else:
x4 = self.norm(x4 + self.GA12(x4))
x_att = torch.cat((x1, x2, x3, x4), 1)

x = shortcut + self.norm1(self.drop_path(self.mlp(x_att)))

return x

if __name__ == '__main__':
input = torch.rand(1, 64, 32, 32)
model = LWGA_Block(
dim=64,
stage=2,
att_kernel=3,
mlp_ratio=4.0,
drop_path=0.1,
act_layer=nn.GELU,
norm_layer=nn.BatchNorm2d
)
print(model)
print("CSDN:AI魔改大王")
output = model(input)
print('LWGA input_size:', input.size())
print('LWGA output_size:', output.size())

赞(0)
未经允许不得转载:171主机测评 » 【AAAI 2026即插即用模块】LWGA轻量级分组注意力,面向遥感图像的轻量化异构分组注意力机制,适合语义分割、图像分类、目标检测、实例分割、变化检测等任务!CV任务通用,涨点起飞
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址