MMsegmentation 中的 DANet:双注意力机制场景分割算法解析与源码级实践指南
2026/9/16 8:31:33 网站建设 项目流程

MMsegmentation 中的 DANet:双注意力机制场景分割算法解析与源码级实践指南

【免费下载链接】mmsegmentationOpenMMLab Semantic Segmentation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmsegmentation

导读

DANet(Dual Attention Network)是面向场景分割的经典注意力算法,通过在传统空洞卷积 FCN 之上叠加位置注意力(PAM)与通道注意力(CAM)两个模块,分别建模空间与通道维度的全局依赖,从而显著提升分割精度。本文以 MMsegmentation 仓库中 configs/danet/README.md 为主线,结合 da_head.py、self_attention_block.py 的源码实现与 configs/danet 目录下的完整配置文件,系统讲解 DANet 的算法原理、模块级实现、三路监督损失机制、配置参数含义以及训练/测试/推理的完整实操流程。读完本文,你将掌握在 MMsegmentation 中复现 DANet、改造双注意力解码头并迁移到新数据集的方法。

一、算法背景:从多尺度融合到注意力建模

在 DANet 提出之前,场景分割任务主要依赖两种思路捕获上下文:一是扩大感受野(空洞卷积),二是多尺度特征融合(如 PSPNet 的金字塔池化、DeepLab 的 ASPP)。但这些方法都基于局部感受野的堆叠,难以显式建模远距离像素之间的语义关联。

DANet 的核心主张(见 configs/danet/README.md 的 Abstract)是:利用自注意力机制,自适应地将局部特征与其全局依赖相融合。具体做法是在传统空洞 FCN 之上并联两个注意力分支:

  • 位置注意力模块(Position Attention Module, PAM):在空间维度上,对任意位置的特征,用所有位置特征的加权和来增强——"相似的特征无论相距多远都会被关联起来",解决同一类别因尺度、视角差异导致的特征不一致问题;
  • 通道注意力模块(Channel Attention Module, CAM):在通道维度上,通过整合所有通道图之间的关联来强调相互依赖的通道映射,使网络自动聚焦于对判别最有利的语义通道。

两个分支的输出相加,进一步增强特征表示,从而获得更精细的分割结果。原始论文在 Cityscapes、PASCAL Context 与 COCO Stuff 三个挑战性数据集上取得了当时的领先精度(Cityscapes 测试集在未使用粗标注数据的情况下达到 81.5% Mean IoU)。本文档对应的实现属于 MMsegmentation 官方算法集合(metafile.yaml 中标注 License 为 Apache License 2.0,Framework 为 PyTorch)。

二、源码级实现解析:PAM、CAM 与 DAHead

DANet 在 MMsegmentation 中的完整实现集中在 mmseg/models/decode_heads/da_head.py,共包含三个关键类:PAMCAM与注册为DAHead的解码头。此外,PAM复用了通用的自注意力基类 mmseg/models/utils/self_attention_block.py 中的SelfAttentionBlock

2.1 SelfAttentionBlock:通用的非局部/自注意力基类

SelfAttentionBlock(self_attention_block.py)实现了标准的 key/query/value 自注意力流程,其构造参数高度可配置:

  • key_in_channels/query_in_channels:key 与 query 投影的输入通道数;
  • channels:key/query 投影后的输出通道数;
  • out_channels:最终输出通道数;
  • share_key_query:key 与 query 是否共享投影权重;
  • query_downsample/key_downsample:对 query/key 特征的下采样模块;
  • key_query_num_convs/value_out_num_convs:投影使用的卷积层数;
  • matmul_norm:注意力图是否除以通道数的平方根做归一化;
  • with_out:是否使用输出投影层。

forward(self_attention_block.py)流程为:分别对 query/key/value 做 1×1 卷积投影 → 展平为向量序列 → 计算相似度矩阵sim_map = query @ key^T→ softmax 归一化 → 与 value 相乘得到聚合后的 context 特征。

2.2 PAM:位置注意力模块

PAM(da_head.py)继承自SelfAttentionBlock,在实例化时做了针对性配置:

class PAM(_SelfAttentionBlock): def __init__(self, in_channels, channels): super().__init__( key_in_channels=in_channels, query_in_channels=in_channels, channels=channels, out_channels=in_channels, share_key_query=False, query_downsample=None, key_downsample=None, key_query_num_convs=1, key_query_norm=False, value_out_num_convs=1, value_out_norm=False, matmul_norm=False, with_out=False, conv_cfg=None, norm_cfg=None, act_cfg=None) self.gamma = Scale(0)

关键点在于:

  • 注意力图不做matmul_norm缩放,也不使用输出投影(with_out=False),结构与经典 non-local 块一致;
  • 引入可学习的缩放标量self.gamma = Scale(0),初始化为 0,保证训练初期注意力分支输出为 0、网络退化为普通 FCN,从而保持训练稳定;
  • 前向计算out = self.gamma(attn_out) + x,即残差式融合——每个位置的特征等于自身特征加上所有位置特征的加权和,权重即 softmax 后的空间相似度。

2.3 CAM:通道注意力模块

CAM(da_head.py)是独立实现的轻量模块,直接在原始特征上计算通道间的相似度:

class CAM(nn.Module): def __init__(self): super().__init__() self.gamma = Scale(0) def forward(self, x): batch_size, channels, height, width = x.size() proj_query = x.view(batch_size, channels, -1) proj_key = x.view(batch_size, channels, -1).permute(0, 2, 1) energy = torch.bmm(proj_query, proj_key) energy_new = torch.max( energy, -1, keepdim=True)[0].expand_as(energy) - energy attention = F.softmax(energy_new, dim=-1) proj_value = x.view(batch_size, channels, -1) out = torch.bmm(attention, proj_value) out = out.view(batch_size, channels, height, width) out = self.gamma(out) + x return out

实现要点:

  • 将特征展平为C × (H·W)矩阵,通过torch.bmm计算C × C的通道相似度矩阵;
  • 采用max(energy) - energy最大项消减技巧再做 softmax,而非直接对energy做 softmax,这一做法与原文一致,可以改善数值稳定性并改变注意力的分布形态;
  • 同样使用Scale(0)初始化的gamma做残差融合:out = gamma * attn + x

2.4 DAHead:双注意力解码头整体流程

DAHead(da_head.py)通过@MODELS.register_module()注册,可直接在配置中通过type='DAHead'引用。其构造参数除继承自BaseDecodeHeadin_channelschannelsnum_classesdropout_rationorm_cfgalign_cornersloss_decode等外,独有参数为:

  • pam_channels (int):PAM 中 key/query 投影的中间通道数(默认配置为 64)。

DAHead内部构建了两条并行分支:

  • PAM 分支pam_in_conv(3×3 ConvModule)→PAMpam_out_convpam_conv_seg(1×1 分类卷积);
  • CAM 分支cam_in_conv(3×3 ConvModule)→CAMcam_out_convcam_conv_seg(1×1 分类卷积)。

forward(da_head.py)返回一个三元组:

feat_sum = pam_feat + cam_feat pam_cam_out = self.cls_seg(feat_sum) return pam_cam_out, pam_out, cam_out

即三个 logits:融合分支pam_cam、PAM 独立分支pam、CAM 独立分支cam。这三个 logits 在训练时全部参与监督(详见第四节),而推理时仅使用融合分支:

def predict(self, inputs, batch_img_metas, test_cfg, **kwargs): """Forward function for testing, only ``pam_cam`` is used.""" seg_logits = self.forward(inputs)[0] return self.predict_by_feat(seg_logits, batch_img_metas, **kwargs)

三、配置文件深度解析

3.1 模型基类:danet_r50-d8.py

DANet 的 R50-D8 模型骨架定义在 configs/base/models/danet_r50-d8.py,这是所有 DANet 配置共享的基础,核心字段如下:

norm_cfg = dict(type='SyncBN', requires_grad=True) data_preprocessor = dict( type='SegDataPreProcessor', mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], bgr_to_rgb=True, pad_val=0, seg_pad_val=255) model = dict( type='EncoderDecoder', data_preprocessor=data_preprocessor, pretrained='open-mmlab://resnet50_v1c', backbone=dict( type='ResNetV1c', depth=50, num_stages=4, out_indices=(0, 1, 2, 3), dilations=(1, 1, 2, 4), # 即 "D8" 的含义:第3、4阶段空洞率为2、4 strides=(1, 2, 1, 1), norm_cfg=norm_cfg, norm_eval=False, style='pytorch', contract_dilation=True), decode_head=dict( type='DAHead', in_channels=2048, # 主干第4阶段输出通道数 in_index=3, # 取 backbone 第4个输出特征 channels=512, # PAM/CAM 分支内部工作通道数 pam_channels=64, # PAM 中 key/query 投影通道数 dropout_ratio=0.1, num_classes=19, # Cityscapes 类别数 norm_cfg=norm_cfg, align_corners=False, loss_decode=dict( type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), auxiliary_head=dict( type='FCNHead', in_channels=1024, in_index=2, channels=256, num_convs=1, concat_input=False, dropout_ratio=0.1, num_classes=19, norm_cfg=norm_cfg, align_corners=False, loss_decode=dict( type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), train_cfg=dict(), test_cfg=dict(mode='whole'))

各字段含义与作用:

  • backboneResNetV1c(带 stem 处 7×7 卷积替换为 3 个 3×3 卷积的变体),dilations=(1, 1, 2, 4)表示第 3、4 阶段分别使用空洞率 2 和 4,保持输出分辨率不下降,这是 "D8"(dilation 8)的由来;
  • decode_head:即DAHeadin_channels=2048对应 backbone 最高层特征,channels=512决定 PAM/CAM 分支内部的计算量,pam_channels=64控制 PAM 的 key/query 投影维度(越小越省显存与计算);dropout_ratio=0.1在分类卷积前施加 dropout 抑制过拟合;
  • auxiliary_head:在 backbone 第 3 阶段(in_index=2,1024 通道)额外挂一个 FCN 辅助头做中间层深度监督loss_weight=0.4控制辅助损失占比,这也是 DANet 精度的重要来源之一;
  • test_cfg=dict(mode='whole'):推理时整图一次前向,不做滑窗切块(切换为mode='slide'可配合crop_size进行滑窗推理以降低显存)。

3.2 各数据集入口配置的继承关系

configs/danet 目录下的 16 个配置文件全部通过_base_继承上述基类,按数据集分为三类:

Cityscapes(4xb2,crop 512x1024 或 769x769),以 danet_r50-d8_4xb2-40k_cityscapes-512x1024.py 为例:

_base_ = [ '../_base_/models/danet_r50-d8.py', '../_base_/datasets/cityscapes.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' ] crop_size = (512, 1024) data_preprocessor = dict(size=crop_size) model = dict(data_preprocessor=data_preprocessor)

配置只覆盖了输入裁剪尺寸(512×1024),类别数 19、训练调度 40k 均继承自基类。切换danet_r101-d8_...文件时,仅需改两处:pretrained='open-mmlab://resnet101_v1c'backbone=dict(depth=101)(见 danet_r101-d8_4xb4-160k_ade20k-512x512.py 的写法)。

ADE20K(4xb4,crop 512x512),以 danet_r50-d8_4xb4-160k_ade20k-512x512.py 为例:

_base_ = [ '../_base_/models/danet_r50-d8.py', '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' ] crop_size = (512, 512) data_preprocessor = dict(size=crop_size) model = dict( data_preprocessor=data_preprocessor, decode_head=dict(num_classes=150), # ADE20K 有 150 类 auxiliary_head=dict(num_classes=150))

Pascal VOC 2012 + Aug(4xb4,crop 512x512):以 danet_r50-d8_4xb4-20k_voc12aug-512x512.py 为代表,数据基类换为voc12aug.py,训练迭代数随调度基类(20k/40k)变化,类别数相应覆盖为 21。

可以看出,从 Cityscapes 迁移到新数据集只需要:替换数据集基类、修改num_classes、调整crop_size与调度即可,DANet 解码头本身无需改动。

3.3 训练调度配置

40k 调度的完整定义见 configs/base/schedules/schedule_40k.py:

optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) param_scheduler = [ dict( type='PolyLR', eta_min=1e-4, power=0.9, begin=0, end=40000, by_epoch=False) ] train_cfg = dict(type='IterBasedTrainLoop', max_iters=40000, val_interval=4000) val_cfg = dict(type='ValLoop') test_cfg = dict(type='TestLoop') default_hooks = dict( timer=dict(type='IterTimerHook'), logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), param_scheduler=dict(type='ParamSchedulerHook'), checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), sampler_seed=dict(type='DistSamplerSeedHook'), visualization=dict(type='SegVisualizationHook'))

要点:

  • 优化器:SGD,初始学习率 0.01,momentum 0.9,weight_decay 5e-4(多卡 4xb2 时的标准配置;若改单卡可参考其他配置按线性缩放学习率);
  • 学习率策略PolyLRpower=0.9,最小学习率eta_min=1e-4,按迭代(by_epoch=False)衰减;
  • 训练循环:迭代制训练(IterBasedTrainLoop),每 4000 迭代验证一次并保存一次 checkpoint(CheckpointHook interval=4000),共 40000 迭代。80k/160k 配置只需把endmax_iters等比放大。

四、三路监督损失机制

DANet 的与众不同之处在于训练时同时优化三个分割输出loss_by_feat(da_head.py)将前向得到的(pam_cam, pam, cam)三元组分别计算交叉熵损失,并用add_prefix加上pam_cam_pam_cam_前缀写入日志:

def loss_by_feat(self, seg_logit, batch_data_samples, **kwargs): pam_cam_seg_logit, pam_seg_logit, cam_seg_logit = seg_logit loss = dict() loss.update(add_prefix( super().loss_by_feat(pam_cam_seg_logit, batch_data_samples), 'pam_cam')) loss.update(add_prefix( super().loss_by_feat(pam_seg_logit, batch_data_samples), 'pam')) loss.update(add_prefix( super().loss_by_feat(cam_seg_logit, batch_data_samples), 'cam')) return loss

配合基类配置中的辅助头(auxiliary_head,FCNHead,loss_weight=0.4),一次迭代总共包含 4 项损失:融合分支(权重 1.0)、PAM 分支(权重 1.0)、CAM 分支(权重 1.0)与辅助头(权重 0.4)。这种"三路解码监督 + 中间层深度监督"的复合监督策略,迫使 PAM、CAM 各自学到有判别力的空间/通道注意力,再在融合分支中互补。推理时则只走融合分支(见前文predict实现),因此推理成本与单分支解码头相当。

五、训练、测试与推理实操

以下命令均在仓库根目录执行(多卡命令通过 tools/dist_train.sh 与 tools/dist_test.sh 分发)。

5.1 单卡训练

python tools/train.py configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py \ --work-dir work_dirs/danet_r50-d8_4xb2-40k_cityscapes-512x1024

5.2 多卡分布式训练

bash tools/dist_train.sh configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py 8

8为 GPU 卡数,与配置中4xb2(4 卡 × batch 2)的总 batch size 8 对应;实际卡数请按可用资源调整。

5.3 测试与指标评估

python tools/test.py configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py \ /path/to/checkpoint_file --eval mIoU

多卡版本:

bash tools/dist_test.sh configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py \ /path/to/checkpoint_file 8 --eval mIoU

--eval mIoU使用 mIoU 评估器 计算 mIoU 与 mAcc 等指标;测试时test_cfg=dict(mode='whole')整图前向。若需多尺度 + 翻转(对应结果表中的 mIoU(ms+flip) 指标),可配合工具脚本进行,实际数值可见 metafile.yaml 中各模型的mIoU(ms+flip)记录。

5.4 单张图片推理可视化

使用 demo/image_demo.py 即可快速体验 DANet 的分割效果:

python demo/image_demo.py demo/demo.png \ configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py \ /path/to/checkpoint_file --device cuda:0 --out-file result.png

预训练权重与训练日志的下载地址统一记录在 configs/danet/metafile.yaml 的WeightsTraining log字段中,可按模型名(如danet_r50-d8_4xb2-40k_cityscapes-512x1024)查找对应下载链接。

六、基准测试结果

以下三张结果表完整继承自 configs/danet/README.md,其中 mIoU、内存与推理耗时均为官方在 V100 上的实测记录;config 列已转换为仓库内相对路径,模型与日志下载地址见 metafile.yaml。

6.1 Cityscapes

MethodBackboneCrop SizeLr schdMem (GB)Inf time (fps)DevicemIoUmIoU(ms+flip)config
DANetR-50-D8512x1024400007.42.66V10078.74-config
DANetR-101-D8512x10244000010.91.99V10080.52-config
DANetR-50-D8769x769400008.81.56V10078.8880.62config
DANetR-101-D8769x7694000012.81.07V10079.8881.47config
DANetR-50-D8512x102480000--V10079.34-config
DANetR-101-D8512x102480000--V10080.41-config
DANetR-50-D8769x76980000--V10079.2780.96config
DANetR-101-D8769x76980000--V10080.4782.02config

6.2 ADE20K

MethodBackboneCrop SizeLr schdMem (GB)Inf time (fps)DevicemIoUmIoU(ms+flip)config
DANetR-50-D8512x5128000011.521.20V10041.6642.90config
DANetR-101-D8512x512800001514.18V10043.6445.19config
DANetR-50-D8512x512160000--V10042.4543.25config
DANetR-101-D8512x512160000--V10044.1745.02config

6.3 Pascal VOC 2012 + Aug

MethodBackboneCrop SizeLr schdMem (GB)Inf time (fps)DevicemIoUmIoU(ms+flip)config
DANetR-50-D8512x512200006.520.94V10074.4575.69config
DANetR-101-D8512x512200009.913.76V10076.0277.23config
DANetR-50-D8512x51240000--V10076.3777.29config
DANetR-101-D8512x51240000--V10076.5177.32config

从结果可以观察到的规律(基于上述官方记录):在 Cityscapes 上 512x1024 输入下 R101 比 R50 提升约 1.8 个点;同一 backbone 下 769x769 比 512x1024 输入带来 0.1~0.3 个点的提升;ms+flip 多尺度测试普遍比单尺度高 1~1.6 个点;ADE20K 与 VOC 场景下将训练迭代从 80k 延长到 160k(或 20k 到 40k)同样能带来稳定增益。

七、复现与扩展建议

  1. 快速复现:下载 metafile.yaml 中对应模型的权重,用第五节命令直接测试,即可对照上表指标;
  2. 迁移新数据集:复制 danet_r50-d8_4xb4-160k_ade20k-512x512.py 式入口配置,替换_base_中的数据集基类、修改num_classescrop_size;类别数变化后pam_channelschannels无需调整;
  3. 显存受限场景:可降低channels(512→256)或pam_channels(64→32),或将test_cfg.mode改为'slide'crop_size滑窗推理;
  4. 模块复用PAMCAM均可在自己的解码头中直接from mmseg.models.decode_heads.da_head import PAM, CAM导入复用,PAM底层依赖的 SelfAttentionBlock 也是实现其他 non-local/自注意力模块的通用积木。

附录:引用规范

若在研究中引用 DANet 算法,官方推荐引用格式如下(来自 configs/danet/README.md):

@article{fu2018dual, title={Dual Attention Network for Scene Segmentation}, author={Jun Fu, Jing Liu, Haijie Tian, Yong Li, Yongjun Bao, Zhiwei Fang,and Hanqing Lu}, booktitle={The IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2019} }

【免费下载链接】mmsegmentationOpenMMLab Semantic Segmentation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmsegmentation

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询