前阵子帮一个做自动驾驶感知的团队排查分割精度卡住的问题骨架从 ResNet 换到 EfficientNet 再到 SwinmIoU 就是卡在 79% 上下浮不动。后来我把注意力从 backbone 挪到分割头上发现他们一直在用单分支的普通卷积做分类缺少一个能同时感知“近处卡车”和“远处行人”的上下文模块。等我把空洞空间金字塔池化ASPP接上去mIoU 直接涨了 2 个多点。这篇文章就围绕 ASPP 展开我会把它拆到不能再拆它解决了什么问题、空洞卷积的账怎么算、标准结构里的每一条分支各自在做什么、PyTorch 怎么实现、工程落地会踩哪些坑最后聊聊 DenseASPP、LR-ASPP 这些变体以及现在 Transformer 分割模型大行其道的背景下ASPP 还有没有存在价值。想做语义分割、理解 DeepLabV3 源码、或者只是想给检测/分割模型加一个多尺度上下文模块的都可以把这篇当参考。1. 为什么分割网络绕不开“多尺度上下文”1.1 一个让所有分割模型头疼的问题语义分割任务的本职是给图像里的每个像素打上类别标签。听起来简单但真正跑起来你会发现最大的敌人不是分类不准而是同一个类别的目标在画面里的尺寸能差出几十倍。拿自动驾驶场景举例一辆近处的卡车可能占满屏幕的三分之一远处同一辆车可能只有几十个像素宽。分类网络里这种尺度差异还算好处理因为最后要做的是全局平均池化把整张图的信息浓缩成一个向量目标小一点大一点影响不大。但分割不一样它要在保留空间位置的前提下对每个像素做预测。固定感受野的卷积核对近处的卡车能覆盖完整轮廓到远处那辆小车上一个 3x3 卷积看到的就只是几个点很难判断它到底是车、是路障还是绿化带。这种问题在遥感图像里更夸张。一栋大楼和一栋别墅在图像上的尺度差异可能超过十倍道路更是横跨整张图。如果分割头没有多尺度上下文提取能力小物体漏检和大物体内部断裂几乎是必然的。我早期做分割项目时走过一个弯路模型效果不好就换 backbone从 ResNet50 换到 ResNet101再换到 HRNet指标涨得微乎其微。后来才反应过来backbone 提取的是“从像素到语义”的逐步抽象但到了分割头它需要的是“从单尺度语义到多尺度语义”的聚合。这一步没做好前面提的特征再强也发挥不出来。1.2 多尺度的常见解法图像金字塔与编码器-解码器在 ASPP 之前解决多尺度问题主要有两条路。第一条是图像金字塔。把输入图像缩放成多个尺度分别送进同一个网络做推理最后把多张概率图融合起来。优点很明显每个尺度都能利用预训练权重不需要重新设计结构。缺点更明显推理时间直接翻几倍训练时反向传播也要过多个尺度显存开销成倍增长。DeepLabV2 当年在测试阶段用过多尺度融合加 CRF 后处理效果确实好但部署时这个成本不是谁都能接受的。第二条是编码器-解码器结构。典型代表是 U-Net 和 FCN 的变体通过跳连把浅层高分辨率特征和深层语义特征拼接起来用解码器逐步恢复空间细节。这条路的优势是边缘细节恢复得好缺点是解码器关注的更多是“位置信息”而不是“上下文信息”。浅层特征虽然有高分辨率但语义抽象程度低对小目标周围环境的建模能力有限。ASPP 的思路和这两条路都不一样。它不动输入图像也不引入解码器而是在同一个特征图上并行跑多个不同空洞率的空洞卷积每个分支负责不同范围的上下文最后拼在一起。相当于只用一次前向就拿到了“看局部、看近邻、看远处、看全局”的多种视野计算量比图像金字塔小得多结构又比编码器-解码器更直接地针对上下文建模。这也是它在 DeepLabV3 里被当作核心模块的原因。2. 空洞卷积先算清“感受野”这笔账2.1 标准卷积为什么“看不远”空洞卷积是 ASPP 的砖块想理解 ASPP先把空洞卷积这笔账算清楚。标准 3x3 卷积的感受野就是 3x3。想扩大感受野最直接的办法是堆叠卷积层两层 3x3 卷积堆起来感受野变成 5x5三层变 7x7。但这种扩张是线性的每多一层只增加有限的感受野宽度网络深度的代价却不小。另一个常见的扩大感受野手段是池化。池化确实能把感受野撑大很多但代价是特征图分辨率降低。分割任务对空间位置极度敏感backbone 已经下采样 16 倍甚至 32 倍了再为了感受野丢分辨率小目标的边缘信息就彻底没了。所以分割模型需要一种“只扩大视野、不缩小分辨率”的卷积操作空洞卷积就是为这个场景设计的。2.2 空洞卷积插空的代价与收益空洞卷积的直观理解是在标准卷积核的像素之间插入空格让卷积核“撑大”但参与计算的采样点数量不变。比如一个 3x3 的卷积核空洞率 rate2等效核大小是 5x5但里面只有原来那 9 个位置有参数其余位置全是 0。放在特征图上滑的时候它每隔一个像素采一个点视野覆盖了 5x5 的区域实际计算量还是 9 次乘加。有效核大小的公式是k_eff dilation × (k - 1) 1rate1有效核是 3x3rate6有效核是 6×2113x13rate12有效核是 12×2125x25rate18有效核是 18×2137x37rate24有效核是 24×2149x49这个“免费”是关键。扩张到 49x49 的有效视野参数量还是 9 个。对于一个有 256 个输入通道、256 个输出通道的卷积层来说参数量始终是 256×256×9跟 rate 无关。计算量也基本不变因为每个输出位置还是做固定次数的乘加。代价是什么呢空洞卷积的采样点是稀疏的。rate18 时它在 37x37 的范围内只采 9 个点中间大量区域被跳过。这意味着它能“感知”到大范围内的信息但对这些信息的利用是离散的。所以空洞卷积不能无脑叠加太大 rate否则单个采样点之间的关联会弱到网络难以学习。2.3 一张表看清 rate 与感受野的关系下面是 3x3 卷积核在不同 rate 下的有效感受野和采样点密度这张表我在做 ASPP 参数选择时经常翻出来对照。rate有效核大小实际采样点数相对感受野扩张倍数特征图上连续覆盖能力13x391x连续25x59约 2.8x隔 1 采 1613x139约 18.8x隔 5 采 11225x259约 69.4x隔 11 采 11837x379约 152x隔 17 采 12449x499约 267x隔 23 采 1从这里也能看出 ASPP 为什么用一组不同 rate 而不是只用一个超大 rate。超大 rate 虽然看得远但采样点太稀疏对局部结构的感知能力下降得厉害。多个不同 rate 并行相当于用不同疏密程度的“渔网”去捞信息近处的细节用小网眼的网远处的轮廓用大网眼的网最后把捞上来的信息拼在一起。3. ASPP 的核心结构四条卷积支路加一条池化支路3.1 从 DeepLabV2 到 DeepLabV3ASPP 是怎么演变的ASPP 最早出现在 DeepLabV2当时的结构是四个并行的 3x3 空洞卷积rate 分别取 6、12、18、24。到了 DeepLabV3这个结构做了一次重要调整第一条分支从 3x3 空洞卷积换成了 1x1 普通卷积三个空洞卷积的 rate 变成 6、12、18另外增加了一条全局平均池化分支最后所有分支的输出拼接后过一个 1x1 卷积降维。这个调整背后有两个考量。第一backbone 到高层的特征图分辨率已经比较低过大的 rate比如 24在低分辨率特征图上的有效采样点非常稀疏可能大部分落在 padding 区域分支退化成“摆设”。第二全局平均池化能直接提供整张图的全局上下文比靠大 rate 硬撑出来的“伪全局”更可靠。所以标准 ASPPDeepLabV3 版本的分支组成是分支操作输出尺寸11x1 Conv256 通道与输入相同23x3 Convrate6256 通道与输入相同33x3 Convrate12256 通道与输入相同43x3 Convrate18256 通道与输入相同5全局平均池化 1x1 Conv256 通道再上采样与输入相同五个分支的输出在通道维度上拼接得到 5×2561280 通道的特征图再过 1x1 卷积降到 256 通道同时加了一个 Dropoutp0.5做正则。3.2 每条分支各管多远的上下文用语言描述每条分支的“职责”比记结构更有用。1x1 卷积分支负责的是“本位置”的信息。它不融合邻域只做通道间的线性组合相当于把高层特征重新映射了一遍。这一支保证了即使其他分支因为各种原因失效ASPP 的输出里至少还有当前像素自身的强语义特征。rate6 的分支有效感受野是 13x13对应的是“小邻域”。对于照片里十几像素宽的小目标这个范围基本能覆盖住目标本身加一点周围环境。小目标检测主要靠这一支。rate12 的分支有效感受野是 25x25算“中等上下文”。中等体量的目标比如行人、车辆加上它们周边的环境信息基本在这个范围。它帮助区分“路上的车”和“路边的车”这种区分在纯像素级别是看不出来的。rate18 的分支有效感受野是 37x37对应“大范围上下文”。大型目标的整体一致性靠这一支维持。比如说一个大卡车横跨整个特征图单靠小感受野分支可能把车头和车身判成两个东西大感受野分支能看到它们属于同一个连通区域输出自然更平滑。一条分支覆盖不开的由 1x1 卷积在拼接后自适应地分配权重。这也是 ASPP 结构“自适应”的来源网络自己学每一条分支的重要程度。3.3 全局平均池化分支为什么必须留很多人第一次看 ASPP 结构时会疑惑已经有三个大空洞率分支了全局平均池化分支不是重复吗实际差异非常大。空洞卷积的感受野再大也只是在局部区域里采样采样点覆盖不到整张图。假设特征图是 32x32rate18 的有效核是 37x37虽然已经超过特征图尺寸但实际采样中大量位置落在 padding 区域或特征图边界外真正能采到有效像素的点有限。全局平均池化则不同它把整张特征图所有位置取平均等效于感受野覆盖全图一锤子买卖拿到全局统计量。我在训练初期可视化过 ASPP 各分支输出感受很深三个空洞卷积分支输出的是带着明显局部纹理的模式图而全局池化分支的输出是一张非常平滑、只有大区域差异的响应图。后者对大目标内部一致性的贡献前面几个分支给不了。全局池化分支的操作细节也值得注意先用 AdaptiveAvgPool2d(1) 把特征图压成 1x1过 1x1 卷积升到 256 通道然后用双线性插值上采样回原始特征图尺寸最后参与拼接。这个上采样过程会引入一些“块状感”不过后面的 1x1 卷积和分割头可以学习修正它实际影响不大。3.4 ASPP、SPP 和 PPM三个容易混淆的模块ASPP 经常被拿来和空间金字塔池化SPP、PSPNet 里的金字塔池化模块PPM比较。很多初学者会把它们混为一谈这里做一次彻底区分。SPP 来自目标检测领域的 SPPNet。它的核心目的是让网络接受任意尺寸输入通过多级池化比如 1x1、2x2、4x4把特征图压成固定长度的向量再接全连接层。它改变的是特征图的空间尺寸输出不是密集预测所需要的“与输入同尺寸”。PPM 来自 PSPNet结构和 ASPP 有点类似多个不同尺寸的池化核1x1、2x2、3x3、6x6对特征图做池化各自过 1x1 卷积后再上采样回原尺寸最后拼接。它和 ASPP 的区别在于PPM 靠不同大小的池化窗口切分区域来获取不同尺度的上下文是“区域级”的ASPP 靠不同空洞率的卷积核做滑动采样是“像素邻域级”的。PPM 的池化结果天然带一种“分块统计”的语义ASPP 则更连续。三者的关系可以这样记SPP 解决输入尺寸不定PPM 用多级池化做上下文ASPP 用多空洞率卷积做上下文。ASPP 和 PPM 目标相近但实现机制完全不同在部署时选哪个要结合 backbone 和任务特点没有绝对的好坏。4. 手撕一个 ASPPPyTorch 实现与维度校验4.1 可以直接抄的 ASPP 代码直接给出一份可运行的 PyTorch 实现完整对应 DeepLabV3 标准结构。import torch import torch.nn as nn import torch.nn.functional as F class ASPPConvBlock(nn.Sequential): 3x3 空洞卷积 BN ReLU def __init__(self, in_channels, out_channels, dilation): super().__init__( nn.Conv2d( in_channels, out_channels, kernel_size3, paddingdilation, dilationdilation, biasFalse, ), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue), ) class ASPPPoolBranch(nn.Module): 全局平均池化 1x1 卷积 上采样 def __init__(self, in_channels, out_channels): super().__init__() self.pool nn.AdaptiveAvgPool2d(1) self.conv nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue), ) def forward(self, x): size x.shape[-2:] y self.pool(x) y self.conv(y) return F.interpolate(y, sizesize, modebilinear, align_cornersFalse) class ASPP(nn.Module): 空洞空间金字塔池化模块 def __init__(self, in_channels, out_channels256, rates(6, 12, 18)): super().__init__() self.branches nn.ModuleList( [ # 1x1 卷积分支 nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue), ), # 三个空洞卷积分支 ASPPConvBlock(in_channels, out_channels, rates[0]), ASPPConvBlock(in_channels, out_channels, rates[1]), ASPPConvBlock(in_channels, out_channels, rates[2]), # 全局池化分支 ASPPPoolBranch(in_channels, out_channels), ] ) self.project nn.Sequential( nn.Conv2d(out_channels * 5, out_channels, kernel_size1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue), nn.Dropout(0.5), ) def forward(self, x): features [branch(x) for branch in self.branches] x torch.cat(features, dim1) return self.project(x)测试一下维度if __name__ __main__: x torch.randn(2, 2048, 32, 32) aspp ASPP(in_channels2048, out_channels256) y aspp(x) print(y.shape) # 期望输出 torch.Size([2, 256, 32, 32])这份代码里有两个容易写错的地方。第一个是在全局池化分支里F.interpolate的目标尺寸必须用输入x的空间尺寸而不是写死某个值。很多模型里 ASPP 前面是一个固定输出尺寸的 backbone写死可能也能跑但一旦换输入分辨率就会报错。第二个是三个空洞卷积的padding必须等于dilation少写一个特征图尺寸就变了后面拼接就会对不上。4.2 padding 为什么取等于 rate公式推导很多人背住了“空洞卷积 padding 要等于 rate”但不理解为什么。这里用公式推一遍。3x3 卷积核空洞率为 d有效核大小为k_eff d × (3 - 1) 1 2d 1步长为 1 时输出尺寸公式为out (H 2×padding - k_eff) 1想让 out 保持等于 H只需要H 2×padding - (2d 1) 1 H 2×padding 2d padding d所以对于 3x3 空洞卷积padding 取 rate 值恰好保持特征图尺寸不变。如果是 5x5 空洞卷积则 padding 要取 2×rate。这个公式在接 ASPP 之前改空洞率时很有用别只记结论关键时刻能自己算。4.3 接入 ResNet 骨干与分割头的完整示例实际使用 ASPP 时前面要接 backbone后面要接分类层。以 ResNet50 为例一个完整的 DeepLabV3 风格分割头长这样import torchvision.models as models def get_backbone(pretrainedTrue): backbone models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1 if pretrained else None) backbone list(backbone.children())[:-2] # 去掉 avgpool 和 fc return nn.Sequential(*backbone) class SimpleSegHead(nn.Module): def __init__(self, in_channels2048, num_classes21): super().__init__() self.aspp ASPP(in_channelsin_channels, out_channels256) self.classifier nn.Sequential( nn.Conv2d(256, 256, kernel_size3, padding1, biasFalse), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue), nn.Conv2d(256, num_classes, kernel_size1), ) def forward(self, x): return self.classifier(self.aspp(x)) if __name__ __main__: backbone get_backbone(pretrainedFalse) seg_head SimpleSegHead(in_channels2048, num_classes19) x torch.randn(2, 3, 512, 512) with torch.no_grad(): f backbone(x) print(f.shape) # torch.Size([2, 2048, 16, 16]) out seg_head(f) print(out.shape) # torch.Size([2, 19, 16, 16])注意这里的 backbone 输出是输入分辨率的 1/32。如果希望保持更高分辨率需要把 ResNet 最后两个 stage 的下采样步长改成 1并用空洞卷积补偿感受野。torchvision 提供了现成参数backbone models.resnet50( weightsmodels.ResNet50_Weights.IMAGENET1K_V1, replace_stride_with_dilation[False, False, True], )第三个 True 表示最后一个 stage 不再下采样整体输出变成输入的 1/16。这样 ASPP 输入的 feature map 分辨率更高小目标的特征保留得更好代价是显存和计算量上升。4.4 output_stride 对 rate 的影响在使用 DeepLabV3 的语境下output_stride简称 OS决定了 backbone 最终输出的特征图相对原图的缩小倍数。标准推荐是 OS16 时ASPP 的 rate 取 (6, 12, 18)。如果用了 OS8特征图分辨率翻倍同样感受野对应的 rate 也要翻倍取 (12, 24, 36)。为什么因为 rate 是相对特征图像素距离定义的。特征图分辨率越高同样 rate 对应的原图距离越短如果不调整 rate感受野相比原来反而变小了。保持“在原图上的有效感受野”一致就需要按分辨率比例放大 rate。实操里我一般默认 OS16这是 DeepLabV3 论文验证过的最平衡配置。OS8 虽然精度略高但特征图面积是 OS16 的 4 倍ASPP 五个分支跑起来显存和耗时都会显著上涨移动端基本不用考虑。5. 实测中 ASPP 相关的坑与调参思路5.1 大空洞率在低分辨率特征图上会“失效”ASPP 不是随便往哪一层接都能提升效果。我在一个城市场景分割项目里试过把 ASPP 接到 ResNet 第二层输出上特征图是原图的 1/8 分辨率rate18 的有效核有 37x37 那么大而特征图本身可能只有 64x64卷积核撑开到比特征图还大采样时大量点落在 padding 区域分支实际采到的有效信息很少。效果自然不理想。判断分支有没有“失效”可以直接可视化每个分支的输出响应。如果某个分支的输出图和别的分支高度相似或者几乎全是靠近边界的亮斑大概率就是这个分支的 rate 对当前分辨率来说太大了。解决办法是降低 rate或者干脆减少分支数。对低分辨率特征图rate 取 2、4、6 往往比 6、12、18 更合适。反过来如果特征图分辨率很高比如原图的 1/4rate18 又显得太小看不了多远。ASPP 的 rate 应该随特征图分辨率变化不是一套参数打天下。5.2 全局池化分支对边缘信息的影响全局平均池化的本质是“用整图信息替换局部信息”它对大目标内部一致性贡献大但对边缘细节是负作用。如果把池化分支的输出可视化会发现它是一张非常平滑的图边缘信息几乎为零。好在 ASPP 有拼接结构202 层后 1x1 卷积可以学出“边缘位置少信池化分支、内部位置多信池化分支”的通道权重所以标准结构里这个副作用是可被学习修正的。真正危险的做法是在 ASPP 后面直接接一个 1x1 卷积分类没有中间修正层。此时边缘像素的预测会明显发糊。我习惯在 ASPP 后面加两层 3x3 卷积再接分类层相当于给“多尺度的粗结果”一个精修的机会。简单有效而且显存占用很小。这个细节在 torchvision 官方 DeepLabV3 实现里也有说明不是玄学。5.3 小 batch 训练时 BN 把上下文支路拖垮ASPP 每个分支都自带 BatchNorm这是标准结构但会产生一个实际工程问题batch size 太小的时候BN 的统计量不稳定。分割任务显存消耗大很多人单卡 batch 只能开到 2 甚至 1。此时每个分支的 BN 在训练中估计的均值方差噪声很大尤其全局池化分支的特征是经过池化后的全局向量统计量本身就和小 batch 不匹配。表现就是训练集上效果不错验证集上上下文融合出来的结果明显变差。解决方法有三个。一是加大 batch size哪怕用梯度累积也比在 BN 上硬撑强。二是在小 batch 场景下把 BN 换成 GroupNorm结构和代码几乎不用大改稳定性好很多。三是训练时固定 backbone 的 BN 参数只让 ASPP 里的 BN 更新但这个方案在预训练权重和目标任务分布差异大时不推荐。如果是移动端部署我一般直接用 GroupNorm省去 batch 大小带来的不确定性。5.4 关于“棋盘伪影”的常见误解经常有人把分割结果里出现的棋盘状纹理归因于空洞卷积说空洞卷积会导致网格伪影。这种说法不够准确。空洞卷积本身确实存在采样稀疏的问题但成熟训练出来的分割模型很少因为 ASPP 产生明显的棋盘状输出。棋盘伪影最常见来源是转置卷积或者反卷积上采样时核重叠不均匀和 ASPP 关系不大。不过有一种情况需要注意当你单独把 rate 很大的空洞卷积分支抽出来可视化时确实能看到离散的采样点痕迹但这只是中间特征不是最终预测。真正需要警惕的是在边界密集、目标密集的区域如果模型把大 rate 分支的权重学得太高可能会让相邻像素的预测趋于一致边界被“糊开”。这时候调小对应分支的 rate或者增加一个标准 3x3 卷积层混合信息通常能缓解。5.5 显存、速度与精度的取舍ASPP 五个分支并行显存占用不是五个普通卷积的简单相加因为每个分支都要保留一份完整特征图用于拼接。以 512x512 输入、OS16 为例ASPP 中间特征图是 32x32x256 的若干个副本多分支带来的显存增量大概是几十 MB 级别这个量级对显卡不算压力。但如果你把 ASPP 接在 OS8 的高分辨率特征上分支间特征图面积翻 4 倍显存增长就很明显了。速度上空洞卷积的计算量和普通 3x3 卷积基本一致ASPP 相对于一个普通 3x3 卷积分割头主要开销是多出来的四个分支和最后的拼接。实测在 GPU 上大约增加不到 10% 的推理延迟在 CPU 上可能增加 20% 以上。如果在意速度可以考虑砍分支保留 1x1、rate6、rate12去掉 rate18 和全局池化效果会掉一点但速度提升可观。6. ASPP 的变体以及它在 Transformer 时代的位置6.1 DenseASPP用密集连接补采样稀疏标准 ASPP 的五个分支是相互独立的每个分支的输入都是同一个特征图。DenseASPP 的思路是让不同 rate 的分支像 DenseNet 那样串联起来后面分支的输入不仅包含原始特征图还包含前面分支的输出。这样做的动机很直接大 rate 分支采样稀疏如果能把小 rate 分支提取的局部细节特征传给它大分支在做大范围上下文聚合时就不再是孤军奋战。实验显示 DenseASPP 在街景分割上比标准 ASPP 略优但代价是显存上升因为每个分支都要保留前面所有特征图用于拼接。工程上如果你的显存足够宽裕可以试试否则还是标准 ASPP 更稳。6.2 LR-ASPP移动端的轻量化改造LR-ASPP 出自 MobileNetV3 的搜索论文目标是在 EdgeTPU 上跑语义分割。它只保留两条核心路径一条低分辨率路径用全局平均池化获取全局上下文一条高分辨率路径直接从浅层拿细粒度特征。两条路径通过一个小型特征融合模块合并。对移动端应用来说标准 ASPP 五个分支的计算量还是偏大。LR-ASPP 把分支砍到两个速度上优势明显精度上也有取舍。如果你在做手机端或嵌入式端的语义分割可以先从 LR-ASPP 开始而不是直接上完整版 ASPP。MobileNetV3-Large 官方训练脚本里自带 LR-ASPP 的实现可以直接参考。6.3 可变形卷积能不能替代 ASPP可变形卷积的思路是给每个采样点学习一个偏移量让卷积核的形状自适应目标几何形变。理论上它能达到比固定 rate 更好的采样位置甚至替代 ASPP 的多分支结构。我在一个医学影像分割任务上试过用可变形卷积替代 ASPP效果确实不差但有两个问题。一是训练不稳定。可变形卷积需要额外学习偏移量数据量不够大时偏移量很容易过拟合训练曲线明显比固定 rate 的 ASPP 更抖。二是显存和速度都不占优势偏移量本身的存储和计算开销不小。所以综合考虑稳定性、确定性和部署友好度在大多数视觉任务里我仍然首选固定 rate 的 ASPP。可变形卷积适合做大模型、大数据的场景不太适合中小团队快速迭代。6.4 Transformer 分割模型还需要 ASPP 吗现在很多分割模型切到了 Transformer 架构比如 SegFormer、Mask2Former。这类模型的自注意力机制天然具备全局感受野看起来 ASPP 不再必要。实际观察下来Transformer 模型确实不再显式使用 ASPP但它们往往用其他模块实现类似功能。SegFormer 的 MLP decoder 本质上也是把不同层输出做多尺度融合Mask2Former 利用 masked attention 做可变形注意力采样Swin Transformer 的窗口注意力加跨窗口连接也是多尺度思路的变体。ASPP 退役了吗准确说是它的思想被吸收进了新的架构里。如果你还在用 CNN backboneASPP 依然是效果、复杂度、工程可维护性之间平衡最好的多尺度上下文模块之一。如果你已经切到 Transformer backbone不必硬塞一个 ASPP 进去但需要确认你的模型是否具备等价的多尺度上下文聚合能力。很多人在尝试把 Swin 和 ASPP 拼在一起时发现收益有限是因为 Transformer 本身已经覆盖了这部分功能再叠加属于冗余。我自己在实际项目中的习惯是CNN 系模型优先接 ASPPTransformer 系模型先把多尺度特征融合做扎实再考虑要不要补一个轻量上下文模块。结构是死的思路是活的搞清楚 ASPP 解决的“不同尺度上下文信息同时在位”这个核心问题远比赛一堆模型名字有用。
阅读完成 · 觉得有帮助?