
简介一份面向自动驾驶与计算机视觉研究者的街景图像语义分割方法技术文档。文档针对现有语义分割方法分割精度不高、参数量大等问题提出基于注意力机制的语义分割网络系统介绍残差网络特征提取、空间注意力模块与通道注意力模块并行细化特征图的原理并通过Cityscapes和CamVid数据集上的实验对比展示其轻量高效优势可帮助读者掌握高精度分割方案的设计思路。资源包共包含1个docx文件大小约367KB内容涵盖中英文摘要、关键词、引言、方法阐述与实验分析结构完整还讨论了该方法在自动驾驶感知、医学图像分析和地理遥感等领域的应用价值适合作为相关课题研究或论文写作的参考。目前已有240人学习值得自动驾驶、计算机视觉方向的学生和工程师查阅。1. 街景图像语义分割为什么绕不开注意力机制一个反直觉的结论先说在前面给语义分割模型加上注意力模块不等于一定涨点。加错位置、加错类型反而会让 mIoU 掉 12 个点这在街景图像上尤其常见。街景场景的特点是类别多、尺度跨度大、小目标密集——行人、交通标志、车道线、杆件挤在同一张图里再加上光照差异、遮挡和透视变形普通卷积的感受野根本覆盖不过来。注意力机制解决的就是这种“看得见但看不准”的问题让网络在融合特征时知道哪些通道、哪些位置、哪些长距离关系值得优先处理。这篇文章围绕“基于注意力机制的街景图像语义分割”展开覆盖三类注意力SE 通道注意力、CBAM 空间注意力、多头自注意力的选型理由、街景数据集制作、模型改造和训练避坑。适合两类人一是想复现注意力分割方案、却不知道从哪里改代码的算法工程师二是已经跑通 DeepLabV3 这类基线、想通过注意力机制继续提点的人。下面直接按落地顺序讲。2. 三类注意力在街景任务里的分工先想清楚再动手2.1 通道注意力与 SE 模块让网络知道“该看什么”SESqueeze-and-Excitation通道注意力机制是所有注意力方案里最轻量、也最容易上手的一种。它的思路很简单把每个特征图通道压缩成一个全局描述子再用两个全连接层学习通道之间的重要性最后用 Sigmoid 得到 01 的权重把原来的特征图逐通道加权。街景任务里 SE 为什么有效语义分割最终要输出像素级类别不同类别高度依赖不同的特征通道。以行车场景为例车道线靠边缘和方向特征天空靠低频颜色特征行人靠纹理和形状特征。如果没有通道注意力网络在融合高、中、低层特征时背景通道和目标通道会被一视同仁地叠加。加 SE 之后模型能学会“这一步优先放大哪些通道”的规则。PyTorch 里一个可用的 SE 模块实现如下import torch from torch import nn class SEBlock(nn.Module): def __init__(self, channels, reduction16): super().__init__() self.squeeze nn.AdaptiveAvgPool2d(1) self.excitation nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels), nn.Sigmoid() ) def forward(self, x): b, c, h, w x.shape y self.squeeze(x).view(b, c) y self.excitation(y).view(b, c, 1, 1) return x * y逻辑说明squeeze用全局平均池化把空间信息压缩成一个点excitation里的第一个Linear降维到channels // reduction以控制参数量第二个Linear升维回去最后 Sigmoid 输出每个通道的权重。代码里的reduction16是 SE 论文的默认值表示参数量压缩到原来的 1/16。实际操作中reduction在 8 到 16 之间比较合理太小参数量涨得快对显存不友好太大通道间的差异会被抹平。使用边界也要说清楚SE 只重标定通道不解决空间混淆。街景里两个不同类别若在相同通道上响应相近SE 帮不上什么忙。2.2 空间注意力与 CBAM在像素级别上做筛选CBAMConvolutional Block Attention Module在 SE 的基础上补齐了空间维度结构上把通道注意力和空间注意力串联起来先对特征图做通道重标定再用空间注意力找出“哪里值得看”。空间注意力用平均池化和最大池化各生成一张图拼在一起走一个 7×7 卷积得到空间权重图与特征图逐像素相乘。街景场景里空间注意力有价值的地方在于背景类道路、建筑、植被在图像里占大面积目标类行人、自行车、交通标志只占很小的像素比例。普通卷积在空间上是共享权重的小目标即使响应正确也会被后续池化和下采样稀释。CBAM 的空间注意力机制能生成一张与输入分辨率一致的权重图让模型在前向传播时对小目标位置的响应做显式放大。一个可插入到分割模型里的 CBAM 模块写法import torch from torch import nn class CBAM(nn.Module): def __init__(self, channels, reduction16, kernel_size7): super().__init__() # 通道注意力 self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.mlp nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels) ) # 空间注意力 self.conv nn.Conv2d(2, 1, kernel_sizekernel_size, paddingkernel_size // 2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): b, c, _, _ x.shape avg_out self.mlp(self.avg_pool(x).view(b, c)).view(b, c, 1, 1) max_out self.mlp(self.max_pool(x).view(b, c)).view(b, c, 1, 1) channel_weight self.sigmoid(avg_out max_out) x x * channel_weight # 空间注意力 avg_spatial torch.mean(x, dim1, keepdimTrue) max_spatial, _ torch.max(x, dim1, keepdimTrue) spatial_feat torch.cat([avg_spatial, max_spatial], dim1) spatial_weight self.sigmoid(self.conv(spatial_feat)) return x * spatial_weight逻辑说明通道注意力部分在 SE 的基础上多了一个最大池化分支最大池化捕捉的是“最强烈的通道响应”平均池化捕捉整体分布两者相加比单独使用平均池化更稳定。空间注意力部分dim1求均值得到通道维度的平均响应max得到通道维度的最大响应两张图拼成 2 通道后过一层7×7卷积。kernel_size默认 7因为这个卷积要覆盖比较大的空间邻域窗口太小空间权重图容易变成噪点图。注意CBAM 的空间注意力是在通道注意力之后计算的传入的是已经重标定过的特征图。把它放在 Backbone 每个 Stage 的输出位置比放在网络最后更有效最后那层特征图分辨率太低空间权重图没有足够细节。2.3 自注意力与多头注意力QKV 在长距离依赖里的代价自注意力机制和前面两类注意力有本质区别。SE 和 CBAM 是在已有特征图上计算权重而自注意力是把特征图的每个位置当成一个“查询”在整张图的范围里搜索与它相关的其他位置重新生成特征。这就是常说的 QKV 结构QQuery代表当前位置要查询什么KKey代表其他位置能提供什么VValue代表被聚合的内容。多头注意力机制是自注意力的扩展把 Q、K、V 切成多个头每个头在不同表示子空间里计算相关性最后拼回去。街景图像里最吃长距离依赖的是道路延伸和遮挡推理一条车道被车辆遮挡要继续判断它的走向需要跨越几十甚至上百像素建立联系一个行人被栏杆挡住一半要判断完整轮廓得从远处相似的纹理推测。这些场景中局部卷积核做不到自注意力可以做。但代价非常直接——计算复杂度是 O(N²)N 是位置的个数。在街景分割里输入通常是 1024×2048 级别即使在 1/8 下采样的特征图上也有 128×256 个位置全局自注意力一张图就能吃光显存。我一般不会在街景分割里直接做全局多头自注意力常见的可靠做法是两种替代只在高层的低分辨率特征图上做比如在 1/16 或 1/32 的特征图上用 256 个通道以内的多头自注意力或者用轻量级的坐标注意力Coordinate Attention来近似长距离依赖它把特征图按行、列方向分别做池化只建立“行内”和“列内”的长距离关系复杂度是 O(HW)。2.4 选型对照不同场景下注意力模块怎么选模块参数量增量解决的问题适合街景哪类痛点落地位置SE极小通道间重要性类别特征差异大Backbone 各 Stage 输出后CBAM较小通道 空间显著区域小目标易被稀释Backbone 各 Stage 后或 ASPP 前多头自注意力大全局长距离依赖车道延伸、遮挡推理高层 1/16 特征图坐标注意力小跨空间长距离依赖道路延伸兼顾效率替换自注意力插在 Backbone 后段选型顺序上不要一上来就上多头自注意力。我的顺序是先把基线跑通加 SE 看涨跌再加 CBAM 看涨跌最后才尝试自注意力。每加一个模块跑一次消融才能分辨提点是“注意力机制带来的”还是“参数量增加带来的”。3. 把街景图像做成语义分割数据集标签合并与增强3.1 从 Cityscapes 原始标签到训练类别映射脚本街景语义分割的首选公开数据集是 Cityscapes它的 gtFine 标签有 34 个类别但官方推荐的训练类别只有 19 类。原始标签里每个类别用 RGB 颜色表示比如道路是 (128, 64, 128)行人属于大类具体到 rider、person 等子类又会细分。训练分割模型之前必须把彩色标签图转换成单通道索引图每个像素存 018 的类别编号忽略区域存 255。转换脚本的核心是一个颜色到索引的映射字典import numpy as np from PIL import Image import os # Cityscapes 19 类对应的颜色RGB cityscapes_colors { 0: [128, 64, 128], # road 1: [244, 35, 232], # sidewalk 2: [70, 70, 70], # building 3: [102, 102, 156], # wall 4: [190, 153, 153], # fence 5: [153, 153, 153], # pole 6: [250, 170, 30], # traffic light 7: [220, 220, 0], # traffic sign 8: [107, 142, 35], # vegetation 9: [152, 251, 152], # terrain 10: [70, 130, 180], # sky 11: [220, 20, 60], # person 12: [255, 0, 0], # rider 13: [0, 0, 142], # car 14: [0, 0, 70], # truck 15: [0, 60, 100], # bus 16: [0, 80, 100], # train 17: [0, 0, 230], # motorcycle 18: [119, 11, 32], # bicycle } def convert_color_label_to_index(label_path, output_path): label_img np.array(Image.open(label_path).convert(RGB)) h, w, _ label_img.shape index_map np.full((h, w), 255, dtypenp.uint8) for idx, color in cityscapes_colors.items(): mask np.all(label_img np.array(color), axis-1) index_map[mask] idx # 忽略区域Cityscapes 提供的 ignore_label 一般是 255 Image.fromarray(index_map).save(output_path)逻辑说明遍历 19 个类别的颜色在标签图里逐像素判断 RGB 是否完全匹配匹配则写入对应的索引。index_map初始化为 255保证不属于 19 类的像素比如自行车拖车、道路边缘的杂项不参与训练。转换后要再检查一遍我见过有人把Image.open漏了convert(RGB)读到灰度模式导致所有 mask 匹配失败整个标签图全变成 ignore训练出来的模型效果和随机猜测差不多。更稳妥的方法是直接复用 Cityscapes 官方提供的labels.py里的trainId映射它对ignoreInEval和hasInstances这些属性处理得更完整。如果你用的是 LabelMe 或其他标注工具生成的数据本质思路一样建立一个“标注类别 → 训练索引”的映射再做逐像素转换。3.2 街景专属增强策略光照与尺度优先街景图像和普通自然图像最大的区别在于存在强烈的光照变化和透视规律。同一段路晴天、阴天、逆光、夜间拍出来的效果差异极大而卷积模型对光照非常敏感。常用的随机旋转、翻转和裁剪可以用但有两个街景专属的坑要注意第一不能随便做垂直翻转。垂直翻转后天空到了图像下方道路到了上方真实街景中这种分布几乎不会出现会让模型学习到错误的先验。我一般只做水平翻转概率设 0.5。第二随机缩放的比例不能太小。街景里小目标多如果缩放过狠行人可能变成几个像素标签也跟着被压缩出锯齿容易把类别边缘学坏。我一般把随机缩放控制在 0.52.0 倍配合随机裁剪保证长边对齐。用 Albumentations 实现一套街景增强管道import albumentations as A train_transform A.Compose([ A.RandomScale(scale_limit(0.5, 2.0), p0.8), A.PadIfNeeded(min_heightNone, min_widthNone, border_mode0), A.RandomCrop(height512, width1024, p1.0), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast( brightness_limit0.3, contrast_limit0.3, p0.8 ), A.HueSaturationValue(hue_shift_limit10, sat_shift_limit20, val_shift_limit30, p0.8), ])逻辑说明RandomScale和RandomCrop配合是为了让模型对小尺度目标和大尺度目标都有感知PadIfNeeded的border_mode0表示用常量填充防止缩放后边缘出现非整数对齐。RandomBrightnessContrast模拟晴天和阴天的光照差HueSaturationValue模拟不同时间段天空和植被的颜色偏移。参数设置上brightness_limit我建议调到 0.3太小对夜间场景几乎没有增强效果太大则会让车道线颜色失真干扰类别判断。HueSaturationValue里hue_shift_limit不要超过 15街景中红绿灯、交通标志对色相敏感过度扰动会让模型分不清红灯和绿灯。3.3 类别不平衡车道、行人和杆件不能一视同仁Cityscapes 19 类中道路和建筑占据 60% 以上的像素而行人和交通标志可能连 2% 都不到。直接用交叉熵损失模型会把所有像素预测成道路和建筑因为这么做 loss 也很低。计算每个类别的权重是解不平衡的基础手段def compute_class_weights(label_dir, num_classes19): counts np.zeros(num_classes, dtypenp.float64) for f in os.listdir(label_dir): label np.array(Image.open(os.path.join(label_dir, f)), dtypenp.uint8) for c in range(num_classes): counts[c] np.sum(label c) total counts.sum() weights total / (counts 1e-6) weights weights / weights.max() return torch.tensor(weights, dtypetorch.float32)逻辑说明统计每个类别像素出现的次数用总像素数除以类别像素数得到权重出现越少的类别权重越大。注意加1e-6防止某个类别在子集里完全没出现导致除零。weights / weights.max()是归一化把最大权重压到 1避免梯度爆炸。有了权重之后就把这个weights传给nn.CrossEntropyLoss(weightweights, ignore_index255)。我在实际项目里发现这一步做完行人、交通标志的 mIoU 通常能提升 58 个点比重堆模型结构还管用。如果你的数据里类别本身就不均衡这个脚本全套迁移过来即可。4. 在 DeepLabV3 上插注意力模型改法与训练超参4.1 基线选择ResNet50 还是 MobileNetV3街景语义分割的落地场景决定 backbone 怎么选。常见做法是如果是离线实验、以精度优先用 ResNet50 或 ResNet101如果要做成车载或嵌入式实时系统用 MobileNetV3。MobileNetV3 本身就是带 SE 通道注意力机制的它的 bottleneck 里内置了 SE 模块。这也意味着选 MobileNetV3 作为 backbone 时“加注意力”已经有一部分是模型自带的后续再叠加 CBAM 需要评估是否真的有增量。ResNet50 则比较“干净”没有自带注意力插入 SE 或 CBAM 的消融结果更可信。从街景分割的特点来看我一般推荐先从 ResNet50 开始。理由很现实Cityscapes 训练集只有 2975 张图分辨率又高ResNet101 很容易过拟合ResNet50 配合 ImageNet 预训练权重能快速验证注意力模块的效果。工程上ResNet50 的 ASPP 输入特征图是 2048 通道的即使插入 CBAM显存占用也在可控范围。4.2 注意力模块的插入位置Backbone 各阶段还是 ASPP 分支这是注意力分割最容易踩坑的地方。同样的 CBAM插在 ResNet 的 stage2 之后和插在 ASPP 的每个分支之后效果差别很大。我的经验是在 Backbone 的每个 stage 输出之后插入 SE 或 CBAM做的是“特征提取阶段的通道和空间筛选”在 ASPP 的多尺度分支后面插入做的是“多尺度特征融合后的筛选”。两者不冲突但优先级不同。显存有限时先插 Backbone 的 stage3、stage4再插 ASPP 最后一个融合层。修改 ResNet 的示例把 CBAM 加到每个 stage 输出之后from torchvision.models.resnet import ResNet, Bottleneck class AttentionResNet(ResNet): def forward(self, x): x self.conv1(x) x self.bn1(x) x self.relu(x) x self.maxpool(x) x self.layer1(x) x self.attention1(x) x self.layer2(x) x self.attention2(x) x self.layer3(x) x self.attention3(x) x self.layer4(x) x self.attention4(x) return x def build_attention_resnet50(attention_typecbam, channels[256, 512, 1024, 2048]): model AttentionResNet(Bottleneck, [3, 4, 6, 3]) for i, ch in enumerate(channels): if attention_type cbam: setattr(model, fattention{i 1}, CBAM(ch)) elif attention_type se: setattr(model, fattention{i 1}, SEBlock(ch)) return model逻辑说明继承torchvision.models.resnet.ResNet在四个 stage 的输出后插入注意力模块。channels参数对应 ResNet50 四个 stage 的输出通道数。CBAM 在 stage 的输出分辨率上计算空间注意力stage1 输出分辨率是输入图的 1/4空间注意力图还保留足够细节stage4 输出分辨率只有 1/32空间注意力基本退化成通道注意力这时候改插 SE 更划算。更激进的做法是把 CBAM 插到 ASPP 的每个分支里。ASPP 用 1×1 卷积和 3×3 空洞卷积并行捕捉不同感受野注意力模块可以放到每个分支最后。这个方案会增加约 10% 的显存但多尺度融合后的特征会被空间注意力重新校准对靠细长纹理区分的类别车道线、杆件有帮助。4.3 训练配置与关键超参街景语义分割训练的典型配置如下超参数推荐值说明输入分辨率512×1024在精度和显存之间平衡初始学习率0.02ResNet50配合 poly 策略不用 step 衰减学习率策略polypower0.9训练后期学习率平缓下降优化器SGDmomentum0.9weight_decay1e-4比 Adam 泛化性好Loss加权交叉熵 Lovász-Softmax加权 CE 保底Lovász 提 mIoUBatch size8单卡 V100 32G多卡时用同步 BN训练轮数120 epochCityscapes 2975 张图足够收敛混合精度开启 AMP显存减半训练加速poly 学习率策略的 PyTorch 写法def poly_lr(epoch, max_epoch, initial_lr, power0.9): return initial_lr * (1 - epoch / max_epoch) ** power逻辑说明poly 策略让学习率随着训练进度平滑衰减前 50 个 epoch 保持较高学习率后面越来越小。相比每 30 个 epoch 直接除以 10 的 step 策略poly 在分割任务里通常能多涨 12 个 mIoU。power0.9是 DeepLabV3 的常用默认值改到 1.0 会让学习率衰减更快适合训练轮数更少的场景。训练时还要注意一点ResNet backbone 的 batch normBN在迁移到不同分辨率时会不稳定。多卡训练时用同步 BNtorch.nn.SyncBatchNorm.convert_sync_batchnorm单卡时尽量用预训练权重附带的标准 BN。街景图像的 batch 里白天夜景混在同一个 batchBN 统计会被拉偏这也是很多人忽略的玄学问题。5. 注意力分割训练避坑清单5 次翻车换来的经验5.1 现象训练 loss 一路下降mIoU 却纹丝不动第一次在 ResNet50 DeepLabV3 上加 SE 就遇到这个问题训练了 30 个 epochCE loss 从 1.2 降到 0.5mIoU 停在 58% 左右。原因很快定位到我没有计算类别权重直接用了默认的交叉熵。街景里道路、建筑占大头模型把主要精力放在把背景类分的更“精细”上小目标学不动。另一个隐藏原因是我把标签映射写错了部分训练图的 ignore 区域占到了 30% 以上模型在“看不见”的像素上没有梯度。解决方法是两件事同时做按第 3 章的compute_class_weights计算类别权重并在训练前统计一次标签的 ignore 比例。我要求这个比例必须低于 10%如果超过说明映射脚本有 bug。改完之后同样的模型 mIoU 从 58% 涨到 64%。5.2 现象加上 CBAM 之后显存直接 OOMResNet50 ASPP 原本能塞进 batch size 8V100 32G在 stage3、stage4 插入 CBAM 后 batch size 降到 4 还偶尔 OOM。原因是 CBAM 的空间注意力需要保留原始分辨率的特征图来计算空间权重stage3 输出是 1/8 分辨率、1024 通道这个特征图本身就很大空间注意力又额外增加了一份显存开销。其次是 batch size 没降、输入分辨率没降两者叠加直接爆显存。解决先降输入到 384×768把 batch size 稳住再考虑把 stage1、stage2 的 CBAM 去掉只保留 stage3、stage4。我在多个项目里验证stage1、stage2 的注意力增益很小去掉后 mIoU 几乎不变显存省下将近 20%。如果还 OOM就把 stage4 的 CBAM 换成 SE让高层的“空间注意力”退化为“通道注意力”。5.3 现象行人、交通标志仍然漏检mIoU 上不去注意力模块加上了loss 也正常但行人、摩托车这些占像素少、形状多变的类别预测结果还是大面积漏检。原因有两个第一输入分辨率不够。512×1024 下一个行人可能只有 24×60 像素经过五次下采样后只剩 3×7 像素任何注意力机制都无法从 3×7 的响应里恢复轮廓。第二加权交叉熵权重设置过猛小目标权重太大模型开始把道路边缘误判成行人产生了大量假阳。解决输入分辨率提升到 768×1536batch size 相应降到 4并把类别权重的上限压到 10 以内避免少数类别过拟合。另外我在分割头里加了一个辅助深度监督在 stage3 输出位置加一个小的分类头把监督信号直接传到较低层小目标的梯度不再等 stage4 慢慢往回传。辅助头在推理时去掉不影响速度。5.4 现象夜间和逆光场景大面积误分类这个坑很邪门白天测试集 mIoU 72%放到夜间街景图上天空被预测成建筑阴影里的路面被预测成植被逆光下的行人轮廓基本是错的。原因大概率出在训练数据的色彩分布上。Cityscapes 基本是白天良好光照采集的模型学到的是“亮的是天空、暗的是道路”这种统计关系光照一变就翻车。注意力机制在这里不是没用但它只学习特征通道和位置的权重并没有解决图像整体光照差异。解决增强阶段加入光照扰动用RandomBrightnessContrast加上HueSaturationValue把亮度扰动范围调到 0.35色相扰动适度收紧到 10。如果项目里有少量夜间数据一定混合进训练集不要把夜间作为单独的评价集去看模型没见过的分布硬测没有参考价值。5.5 现象模型推理速度不达标多头自注意力成了瓶颈在 1/16 特征图上做多头自注意力后mIoU 确实涨了约 1.5 个点但推理耗时从 25ms 变成 48ms换到 Jetson 这类边缘设备上更惨。原因多头自注意力在 128×256 的特征图上计算 QKV 矩阵乘法的复杂度是 O(N²)N32768一张图要算上亿次乘法。参数量不大但浮点计算量非常大。解决我把它换成了坐标注意力或者窗口注意力。窗口注意力限制在 7×7 的局部窗口内相当于一个动态的、可学习的空间卷积速度跟 CBAM 差不多。如果必须保留全局自注意力就把特征图下采样到 1/32 再做并且只在 stage4 输出后做一次不要每个 stage 都做。6. 验证注意力机制有没有真起作用可视化和消融实验6.1 用 Grad-CAM 对比注意力图的差异注意力模块加完之后第一步不是看 mIoU而是看它输出的空间注意力图到底关注在哪里。用 Grad-CAM 对街景输入图生成类激活热力图和模型的预测图叠在一起看能快速发现注意力是不是学偏了。比如如果模型对行人类别的 Grad-CAM 高亮区域大部分集中在道路和建筑上说明注意力被背景带偏了。简短实现一个 Grad-CAM 流程def grad_cam(model, input_tensor, target_class): model.eval() features {} def hook_fn(name): def hook(module, input, output): features[name] output return hook target_layer model.backbone.layer4[-1] handle target_layer.register_forward_hook(hook_fn(feat)) output model(input_tensor) logits output[0, target_class] logits.backward() handle.remove() grads target_layer.weight.grad # 简化示意实际用 hook 存梯度 weights grads.mean(dim(2, 3), keepdimTrue) cam (weights * features[feat]).sum(dim1, keepdimTrue).relu() return torch.nn.functional.interpolate(cam, sizeinput_tensor.shape[-2:], modebilinear, align_cornersFalse)逻辑说明Grad-CAM 的核心是用目标类别的梯度去加权特征图权重大的通道说明对目标类别贡献大。代码里我用register_forward_hook取到 feature map实际工程中还需要注册一个 backward hook 来取梯度这里做了简化。比对时把 CAM 热力图和注意力模块输出的spatial_weight图叠加显示如果两者高亮区域一致说明空间注意力和类别判别区域对齐如果不一致说明注意力被别的东西吸引走了。6.2 消融实验的排布验证注意力增益的唯一可靠方法是消融实验而且顺序很重要。按这个表格逐项做模型配置mIoU参数量增量单张推理耗时DeepLabV3 ResNet50 基线———基线 SE———基线 CBAM———基线 SE CBAM———基线 多头自注意力高层———看结果的顺序也讲究先看参数量增量能不能带来对等的 mIoU 提升再看推理耗时涨了多少。街景分割如果是为了落地到量产耗时比参数量更重要。我通常要求一个注意力模块至少带来 0.8 个 mIoU 的提升才值得保留否则就是“为了加而加”。最后说一个我自己的习惯每一次改模型都保存一个独立的配置文件和评估结果哪怕只是改了reduction从 16 到 8。注意力模块的超参数非常敏感同一个模块在不同 backbone、不同分辨率下表现可能完全相反。把每一版结果记录在表格里翻车时才有后悔药可查。希望这篇记录能帮你在街景语义分割的注意力改造上少走几趟弯路。本文还有配套的精品资源点击获取