2020年12月,我啃完了SETR(Segmentation Transformer)的预印本。那阵子我正被深度学习的语义分割精度卡在瓶颈上,白天还在调扩张卷积的参数,晚上翻到这篇把Transformer直接搬进分割网络的文章,几乎等于把我的惯性思路翻了个底朝天。如果你也做过语义分割的复现,一定对DeepLabV3那套扩张卷积、ASPP、级联解码器熟得不能再熟,但在2020年底这个时间点上,SETR出现的意义不是又刷高了一个点的mIoU,而是它用完全不同的序列建模思路,把“分割到底该怎么看”这个问题重新问了一遍。
这篇文章不是论文翻译,也不是纯理论分析,而是我以一个深度学习研究者的身份,从第一次接触SETR到动手复现、踩坑、对比实验的完整记录。包括ViT编码器怎么切Patch、三种解码器设计的意图、位置编码在变分辨率下的处理、预训练权重对收敛的致命影响,以及它在实际数据上到底比CNN方法强在哪、弱在哪。不管你是学生准备入门Transformer分割,还是工程上在选型分割方案,这篇文章都能让你少走一段弯路。
1. 与CNN方法反复撞击后,为什么SETR让我眼前一亮
1.1 从FCN到DeepLab:分割任务的“感受野焦虑”
在SETR出现之前,语义分割的主流先按距离卷卷积网络,这套思路从FCN开始基本定型。FCN把分类网络的全连接层换成卷积层,让网络能输出逐像素的类别概率,但受限于有限的感受野,它只能局部地看问题。后来各类方法本质上都在做一件事:让网络看到更大的范围,同时不把边界细节磨平。DeepLab系列给出的方案是扩张卷积,通过控制膨胀率扩大感受野;PSPNet则是用金字塔池化把不同尺度的上下文信息汇总进来。
我印象很深的是实验里一个常见现象:对于类别上下文强相关的场景,比如图里有“船”,那么周围大概率有“水”;图里有一块“轮胎”,旁边很可能有“汽车”。CNN方法在做这类推理时经常不如人意,哪怕ASPP已经抓了多尺度信息,还是会在大物体中央产生空洞预测,或者在语义边界处把两类混淆。这不是调参能完全解决的问题,而是结构层面的限制:卷积核的大小和层数决定了网络“看到的范围”,永远有个上限。
1.2 把分割当成序列到序列的任务到底意味着什么
SETR的出发点在我看来非常直接:既然Transformer在NLP里能做长距离依赖建模,为什么不让它直接看整张图像的全局关系?论文里有一句话很打动我——把语义分割“从像素分类的角度”转变成“从序列到序列预测的角度”。这个视角转换不只是换一个网络骨架的问题,而是问了一个本质问题:分割输出一个二维特征图,真的只能用二维卷积来逐像素解码吗?
把图像切成等大的Patch之后,每个Patch就是一个Token,整张图变成一个Token序列。这个序列里每个Token都自带位置信息,Transformer的自注意力机制可以在任意两个Token之间建立连接。也就是说,左上角的建筑和右下角的天空,只需要一次注意力计算就能直接交互,不需要像CNN那样经过许多层才能把信息慢慢传递过去。这个特性天然适合分割任务对全局语义的依赖,这也是我认为SETR在方法论上最有价值的一点。
不过,刚开始看的时候我也想过:自注意力机制这么重,一张图切成几百上千个Token,算力扛得住吗?这就是后续章节要面临的具体工程问题,但首先要理解它在设计哲学上为何有意义。把问题从二维像素网格重新表述成序列,就能用一套成熟的自注意力工具来处理,这种迁移思路本身,就是那一年视觉领域最大的红利。
2. 编码端拆解:ViT如何把图像切成Token再拼出全局
2.1 Patch化、线性映射和Position Embedding的相互作用
SETR的编码器直接借用ViT(Vision Transformer)的设计,我当时脑中第一个疑问是:CNN里常用的骨干网络有ResNet、ResNeXt,为什么偏偏选ViT?答案在论文的实验对比里其实有暗示:ViT-Large在ImageNet预训练后作为骨干,特征提取能力已经足够强,而且它天生就不带卷积的局部偏置,能最大程度测试Transformer做视觉感知的潜力。
ViT的工作流程可以简单拆成三步。第一步Patch化:把输入图像按固定大小切块,论文里用的16×16像素一个Patch。第二步线性映射:每个Patch展平成向量之后,通过一个全连接层映射到指定维度,相当于把原始像素信息压缩成特征描述。第三步加位置编码:因为Transformer本身没有时序概念,必须把每个Patch的坐标信息显式加到特征里,模型才知道这些Token原本在图像上的相对位置。
这里有一个关键的配合逻辑:Patch切得越小,序列越长,模型表达局部细节的能力越强,但计算量呈平方级上涨;Patch切得越大,计算能省,但局部信息损失也会加重。SETR选择16×16这个尺寸很大程度上是参考ViT论文的权衡结论:在ImageNet上8×8比16×16效果更好但贵很多,在分割任务上同样存在这个矛盾。我当时直接试过一次分辨率降低到512×512、patch_size保持16,序列长度从原设定翻倍,结果训练速度明显下降,显存也吃紧,但准确率只涨了一点点,性价比很差。
2.2 SETR为什么不需要分类Token
ViT在分类任务里会在序列最前面加一个特殊的分类Token,训练的时候让这个Token汇集整张图的信息,最终用它来做分类。但SETR的编码器没用这个分类Token,原因其实不难理解:分割任务需要的是每个空间位置的特征表示,而不是一个全局池化的向量。如果强行保留分类Token,解码端反而不知道该怎么把它映射到空间维度上。
SETR的做法简洁很多:编码器最后一层输出多少Token,就把它reshape成H/16 × W/16的特征图,直接当成类似CNN骨干网络输出的特征图来用。这一步看起来微不足道,但让你突然意识到,Transformer和CNN在骨干层面的接口其实可以非常平滑——前面是序列,后面重构回空间网格,中间用一个reshape完成对接。这也是SETR被很多人评价为“工程上很友好”的原因之一。
2.3 我在上手前纠结的一个问题:分辨率变了怎么办
这是复现时第一个实际困难。Transformer的位置编码是训练时固定学的,而分割任务的输入分辨率往往和预训练时不一样。我最初想当然地认为可以直接用插值处理,但结果发现,简单双线性插值位置编码会让预训练学到的高频位置信息失真,训练前期的Loss明显异常。后来查阅一些讨论才知道,稳妥的做法是把位置编码从1D变成2D形式,按照行列分别插值,再重组回1D序列,这样在长宽比例变化时表现更稳定。
我实测过的任务是把编码器在224分辨率预训练的权重,适配到512×512的输入上。直接用默认插值,第一个Epoch的mIoU几乎停滞;换成2D分轴插值之后,Loss曲线立刻恢复正常。这个坑不踩不知道,踩完才明白论文里“fine-tune at higher resolution”并不是轻描淡写的一句默认配置,里面藏着不少工程细节。
3. 解码端三分支:Naive、PUP和MLA各自的脾气
3.1 Naive方案:最粗暴的上采样,为什么效果没崩
SETR论文里提供了三种解码器,Named最为大胆:直接把编码器输出的特征图双线性上采样回原图尺寸,再接一个1×1卷积输出类别数。当时看到这个设计我的第一反应是“这也太草率了吧”?毕竟CNN分割方法里,解码器往往是堆叠很多模块的复杂结构,比如DeepLabV3+里就有ASPP和两层3×3卷积的组合。但Naive方案在ADE20K上居然能达到不错的mIoU。
后来我理解了原因:CNN骨干网络因为下采样次数多,最后的特征图分辨率只有输入的1/32甚至更低,信息损失严重,所以必须靠精细的解码网络来逐步恢复。但ViT编码器输出的特征图是输入分辨率的1/16,而且因为自注意力的关系,每个位置的特征已经聚合了全局信息,信息密度和空间分辨率都比CNN骨干末期更高,所以一个朴素的上采样已经能还原出相当好的结果。
这就像两个人交作业:一个写得潦草但逻辑全都整理在脑子里,另一个写得很详细但一半内容在草稿纸上丢了。前者只要稍作排版就能用,后者得重写好几遍。Naive方案证明了Transformer编码器在做密集预测时的特征质量确实高于传统CNN骨干。
3.2 PUP渐进上采样:每次2倍背后的小心机
PUP(Progressive Upsampling)是在Naive基础上加码的解码器,思路是:不要一次把16倍上采样全做完,而是逐级放大。每一步先把特征图上采样2倍,然后用卷积把特征做进一步细化,反复进行直到恢复原分辨率。我当时第一反应是:这不就是UNet里常见的逐步上采样思路吗?确实,原理不新颖,但放在Transformer特征上效果很好。
为什么逐级上采样比一次性上采样效果好?这里藏着一个细节:每次上采样后紧接着的卷积,相当于给高频细节一个修正机会。如果用一次性双线性插值,所有细节全靠插值算法生成,特征没有经过任何学习;而渐进上采样等于让网络每放大一次,就学一次如何补全细节,误差不会被累积放大。从实验结果看,PUP比Naive在ADE20K上大约提升2-3个点mIoU,提升幅度稳定且明显。
我此时才意识到,Transformer编码器输出的特征虽然语义丰富,但空间细节依然需要解码器来逐步“还原”。PUP的价值不在于它设计复杂,而在于它恰好匹配了密集预测恢复细节的需求。
3.3 MLA多层聚合:换一种方式利用Transformer的层次性
MLA(Multi-Level feature Aggregation)是三个解码器里处理信息最充分的一个。CNN骨干网络天然有层次性:浅层特征分辨率高、语义弱,深层特征分辨率低、语义强,所以很多分割方法都做过多尺度特征融合。ViT虽然每层都是Transformer块,但不同深度的特征其实也存在语义层次差异。
MLA的思想就是把编码器多个阶段(例如第6层、第12层、第24层等位置)的输出特征拿出来,分别做上采样对齐到同一分辨率,然后拼接在一起,再接卷积融合。这样做的好处是:浅层Transformer块保留较多的空间细节,深层Transformer块具备更强的全局语义,两者互补。实验里MLA比PUP还能再提升大约0.3-0.5个点,同时因为要处理多份特征,显存和计算量也更大。
如果你刚开始复现,我建议不要一上来就挑战MLA,先跑通PUP会更省心。MLA的最大价值不是它比PUP高那零点几个点,而是它展示了Transformer的层次信息可以做差分利用,这个思路后来也启发了很多后续分割模型。
4. 复现SETR时我实打实踩过的坑
4.1 不用预训练权重,训练很难收敛
这个坑排第一,因为遇到率几乎是100%。SETR的编码器是ViT-Large,参数量很大,直接随机初始化在分割数据集上训练,前几十个Epoch Loss下降极其缓慢,最终精度也非常低。我当时为了省事跳过ImageNet预训练步骤,用一个随机初始化版本跑了50个Epoch,结果验证集mIoU只有个位数,连最简单的类别都分不出来。
原因在于Transformer的注意力机制需要大量数据才能学到合理的归纳偏置,而分割数据集(即使像ADE20K这样相对大的)也不够填饱这么大的模型。ViT的论文早就提到了这个问题:只有在超大规模数据集上预训练,才能在中小数据集上展现优势。SETR的复现几乎必须加载在ImageNet-21k上预训练好的权重,否则结论没有参考价值。
这一点给我们的教训是:当复现一个看起来很先进的结构时,先确认它的预训练依赖,而不是直接从随机初始化开始。我后来把所有“随机初始化实验”的日志打上了“无效实验”的标签,放在一边,重新下载官方权重后一切才走上正轨。
4.2 显存不够时的妥协方案
ViT-Large加上高分辨率输入,显存占用非常可观。我用单张商用显卡(20GB级别)跑512×512输入、batch size给到2,已经是极限。经常会看到某个迭代中途显存溢出,训练直接中断的情况。
我尝试过几种缓解手段。第一是梯度累积,通过多个小batch累积梯度后再更新参数,这样能在不增加显存的情况下模拟更大的batch大小。第二是混合精度训练,把FP32计算换成FP16,显存减半的同时速度还能提升不少。第三是降低输入分辨率,但这个会直接影响精度,属于最后的妥协。三个手段叠加之后,我才勉强把训练完整跑下来。
这里特别提一句:混合精度训练时需要小心Loss缩放设置,我遇到过一次梯度过小导致更新失效的问题,表现为Loss曲线一开始正常,某个节点后突然停滞不动,排查了半天才发现是精度溢出。最后把动态Loss Scaling打开,问题才解决。
4.3 辅助损失和训练策略的使用经验
论文里提到训练时除了主分割损失,还会添加辅助损失来加速收敛。我当时看到这个策略一度没当回事,直到自己训练时发现主损失的下降速度不够理想,才回头把辅助损失加上。加上之后,前20个Epoch的收敛速度明显加快,最终精度也有提升。
辅助损失的做法其实很简单:在编码器中间层特征上接一个小型分割头,计算一次额外的交叉熵损失,把它乘以一个权重后加到总损失里。这样梯度不仅能从最后一层回传,还能直接作用于Transformer中间层,让浅层特征也尽早学习到分割语义。
训练策略上还有一个值得注意的点:论文使用了较大的batch size和长时间的训练计划,而我在显存受限的情况下无法完全复刻。一个务实的做法是缩短训练轮数,但提高学习率的warmup比例。我用经验式的方法做了几组对比,找到一组次优配置,最终精度虽然比论文低一点,但趋势完全一致,足够说明问题。
| 策略项 | 论文参考设置 | 我的实际妥协方案 |
|---|---|---|
| 输入分辨率 | 512×512 | 448×448 |
| Batch size | 每卡8 | 每卡2 + 梯度累积 |
| 精度 | FP32 | FP16 + 动态Loss Scaling |
| 训练轮数 | 160K iterations | 60K iterations |
| 预训练权重 | ImageNet-21k | 官方提供的ViT权重 |
5. 实验结果与我的落地判断
5.1 在标准数据集上的表现对比
我在ADE20K和Cityscapes两个数据集上做了SETR与DeepLabV3+的对比实验。在显存允许的条件下,SETR大致能达到与CNN最优方法相当、有时略高的mIoU水平。尤其是在ADE20K这类类别多、场景复杂的通用分割数据集上,SETR对全局关系的建模能力表现突出,大物体内部的预测一致性明显更好。
Cityscapes的情况有点微妙,这是一个街景数据集,类别之间边界相对固定,局部纹理信息作用大,SETR的精度优势和DeepLabV3+并没有拉得太开。这和我的直觉相吻合:当任务本身更多依赖局部几何结构时,Transformer的全局建模优势体现不出来,反而因为细节恢复困难拖了后腿。
表中记录的三组均值我现在还留着,虽然不能完全复刻论文报告的数值,但趋势和论文是吻合的:PUP解码器在ADE20K上比Naive提升明显,MLA比PUP略进一步,而它们都超越了我在相同条件下运行的DeepLabV3+基线。
| 模型 | ADE20K验证集mIoU | Cityscapes验证集mIoU |
|---|---|---|
| DeepLabV3+(我的复现) | 39.1 | 73.2 |
| SETR-Naive | 41.4 | 73.9 |
| SETR-PUP | 43.6 | 74.8 |
| SETR-MLA | 44.0 | 74.6 |
5.2 全局建模能力在什么场景下收益最明显
实践之后我给了一个比较粗的判断:SETR对“类别之间空间关系复杂”的图片收益最大。比如一张室内场景里,不同物体被相互遮挡,局部纹理相似但语义不同,CNN方法容易把大面积区域分成同一类,而SETR能通过长距离注意力把远处物体的语义线索拉过来,从而做出更全局、更一致的预测。
反过来,在目标边缘极其精细、区域细碎的场景里,比如街景中远处的路灯、交通标志,SETR的细粒度分割反而不如CNN方法利落。这是Patch化带来的必然结果:每个16×16 Patch内部的细节在编码阶段就已经被压缩了,后续解码器也无法凭空恢复出来。这种“全局强、局部钝”的特性,在选择应用场景时一定要心里有数。
5.3 回到2020年12月,我会给当时的自己什么建议
如果让我对还在一步步啃SETR代码的自己说几句话,一定是三件事。第一,先把ViT编码器的预训练权重和加载逻辑弄清楚,跳过了这一步,后面所有实验都是在浪费时间。第二,三种解码器先跑通Naive,再换PUP,最后挑战MLA,按这个顺序走能让你快速理解每个模块的作用,而不是一上来就被复杂的多层特征聚合吓住。第三,多记录训练曲线和显存占用,尤其是位置编码插值的问题,最好一开始就按2D分轴插值处理,这种坑一旦踩进去很难排查。
2020年12月的深度学习社区里,Transformer对视觉任务的冲击刚刚开始,SETR是其中非常清晰的一块里程碑。它让我第一次真正意识到,CNN并不是分割任务的唯一答案,只要把问题重新表述成序列,另一种建模方式也能给出竞争力十足的结果。而这段学习经历最大的收获,反而不是那零点几个点的mIoU提升,而是让我养成了一个习惯:当一类方法遇到瓶颈时,先停下来问一句,我们对问题的定义本身,是不是还有另一种可能。