Swin Transformer 是近年在计算机视觉(CV)圈子里绕不开的一个名字。它继承了 Transformer 处理长距离依赖的能力,又通过窗口化自注意力把计算复杂度从平方级拉回到线性级,同时还天然支持了图像任务最需要的多尺度特征。如果你做过目标检测、语义分割或者图像分类,一定体会过那种"想换 Transformer 骨干,但显存吃紧、小目标效果差"的尴尬。Swin Transformer 恰恰是冲着这两个痛点去的。
这篇文章我会从设计思路上拆解它的核心机制,分析窗口注意力、移位窗口、相对位置偏置到底解决了什么问题,再把网络结构和可复现的训练配置讲清楚,最后分享一些实际跑模型时踩过的坑和排查经验。无论你是刚接触 Vision Transformer 的新手,还是已经在用 ResNet 想换骨干的开发者,读完应该都能对 Swin Transformer 有个整体、可落地的理解。
1. 从全局到局部:Swin Transformer 的设计思路
1.1 Transformer 进入视觉时的核心矛盾
最早把 Transformer 搬到视觉任务的尝试,基本是把图像切成一个个 patch,然后像处理 NLP 序列一样直接做全局自注意力。这种方式在分类上确实能拿到不错的结果,但一旦碰到检测、分割这类需要稠密预测的任务,问题就出来了:全局自注意力的计算复杂度是随 token 数量平方增长的。一张 224x224 的图切成 16x16 patch,只有 196 个 token,还算能扛;可换到 512x512 的输入,token 数变成 1024,复杂度直接翻了几十倍。再叠加特征金字塔的多尺度需求,很多设备根本跑不动。
另一个矛盾是,全局注意力天然是"一视同仁"的。它会在整张图上搜索每个 patch 之间的相关性,却忽略了图像本身具有强烈的局部性——相邻像素之间关系最紧密,远处像素有相关性,但往往是在语义层面,而不是原始像素层面。CNN 通过卷积核天然地建模了局部邻域,而 Naive ViT 却把这种先验丢掉了,导致它需要更多数据、更大模型才能学回来。Swin Transformer 的设计者正是看准了这两点:既要 Transformer 的全局建模能力,又要像 CNN 一样有局部归纳偏置和多尺度特征。
1.2 分层特征金字塔与窗口化策略
如果你用过检测框架,会知道特征金字塔几乎是标配。行人检测需要大特征图的位置信息,大目标分类需要高层语义,所以多尺度是 CV 的"刚需"。Swin Transformer 借鉴了 CNN 的分层思想,把网络划分成四个 Stage,每个 Stage 内部特征图分辨率逐渐减半,通道数逐渐翻倍。这样输出的特征天然形成 C/2、C、2C、4C 这种金字塔结构,可以直接扔进 FPN、PAN 这类 neck 里用,不用像 ViT 那样额外设计反卷积或者上采样来适配下游任务。
那窗口化又是怎么回事?简单说,就是把自注意力限制在一个固定大小的局部窗口内。比如把 8x8 的窗口铺满整个特征图,每个窗口内部做自注意力,窗口和窗口之间互不通信。这样计算复杂度跟输入分辨率是线性关系,而且每个 token 只需要关注窗口内的邻居,刚好贴合图像的局部性假设。但问题来了,窗口之间彻底隔离,信息没法跨窗口流转,全局建模能力不是废了吗?这就需要移位窗口机制来补位。
2. 核心机制拆解:窗口自注意力与移位窗口
2.1 窗口划分与计算复杂度分析
先看标准的多头自注意力。假设输入有 N 个 token,每个 token 的维度是 d,那么计算所有 token 两两之间的注意力分数需要 N×N 的矩阵,复杂度是 O(N²·d)。图像越到深层,token 数 N 越大,这个平方项就会爆炸。
Swin Transformer 把特征图分成若干个不重叠的窗口,每个窗口固定包含 M×M 个 patch(通常是 7x7)。假设整张特征图有 h×w 个 patch,划分后大约有 N/M² 个窗口,窗口内自注意力的复杂度是每个窗口 M⁴·d,乘上窗口数量后得到:N × M² × d,也就是说,复杂度从 O(N²) 降到了 O(N)。这里的 M 是常数,所以整体是线性的。这就是为什么 Swin Transformer 能处理高分辨率输入的关键原因之一。
具体到代码里,窗口划分一般通过 reshpe 和 transpose 实现。比如特征图形状是 [B, H, W, C],先把它分块成 [B, H/M, M, W/M, M, C],然后 reshape 成 [B*num_windows, M, M, C],相当于把每个窗口内的 token 拉成一个序列。这样 self-attention 就和普通 Transformer 一模一样了。实际踩坑时要特别注意 reshape 的维度顺序,Windows 之间不能错位,不然注意力会学到错误的空间关系。
2.2 移位窗口的跨窗口信息交流
如果把自注意力一直限制在固定窗口内,深层 token 就只能感知到很小的一块区域,这会严重限制感受野。Swin Transformer 的解决办法是在相邻两层之间交替使用两种窗口划分方式:第一层用常规网格划分,第二层把窗口整体平移(M/2, M/2)个 patch,这样原本在不同窗口的 token 在下一层就会被分到同一个窗口里,信息就能跨窗口流动。多层堆叠后,每个 token 的等效感受野可以覆盖全图。
但移位窗口会带来一个实际问题:窗口数量会从规则的 (H/M) × (W/M) 变成 (H/M+1) × (W/M+1),多出来的边界区域凑不满一个完整的 M×M 窗口。直接补零或忽略都会引入大量无效计算。论文里用了一个非常巧妙的 trick——cyclic shift,就是循环移位:把特征图按对角线方向平移,让不完整的区块重新拼成规则窗口。这样窗口数量和计算量都不变,只需要在下一次移位时对齐回去即可。代码实现时,要在 attention 计算里额外添加一个 mask 矩阵,把原本不邻接的 token 之间的注意力分数设为 -100 这种极小值,防止模型学到虚假关系。这一块是源码中最容易看晕的地方,后面调试的时候我会再细说。
2.3 相对位置偏置为什么有效
窗口自注意力里还有一个容易被忽略但影响很大的组件:相对位置偏置(relative position bias)。普通的 Vision Transformer 会给每个 token 加上绝对位置编码,告诉模型"你现在在图像的哪里"。但 Swin Transformer 用的不是绝对位置,而是相对位置偏移表——attention 计算时,在相似度分数上加一个可学习的偏置,这个偏置取决于两个 token 之间的相对坐标差。
为什么相对位置比绝对位置更合适?因为图像中存在大量平移等变的结构,一个物体出现在左上角和右下角,它的局部模式应该是相似的。绝对位置编码会让模型过分关注位置本身,而相对位置编码天然具备平移等变性,更容易泛化到不同分辨率和不同位置的目标。此外,由于窗口划分是局部的,相对位置的范围是有限的(比如 -6 到 6),偏置表的大小也只是 13×13 级别,参数量非常小,却能带来稳定的性能提升。我自己实验里把相对位置偏置去掉后,在检测任务上掉了大约 0.5-1 个点,可见其重要性。
3. 网络结构与关键配置
3.1 整体架构与 Stage
Swin Transformer 的总体流程可以概括为四段式:先是一个 Patch Embedding 模块把图像映射成 patch 序列;然后依次经过四个 Stage,每个 Stage 由若干个 Swin Transformer Block 组成,相邻 Stage 之间通过 Patch Merging 降低分辨率、增加通道。它和 ViT 最大的差异在于,ViT 在所有层都保持同样的 token 数量和分辨率,而 Swin 是逐阶段降采样,像 CNN 一样构建层次特征。
每个 Swin Transformer Block 内部由两部分组成:基于窗口的自注意力(W-MSA)和基于移位窗口的自注意力(SW-MSA),两者交替出现。Block 里还包含 LayerNorm、MLP 和残差连接,结构上和标准 Transformer Block 基本一致,只是把全局注意力换成了窗口形式。论文在设计时把 attention 和 MLP 的 dim_ratio 都调成了 4,也就是 MLP 隐藏层维度是输入维度的 4 倍,通道数翻倍。
3.2 Patch Embedding 与 Patch Merging
Patch Embedding 的作用跟 CNN 里的 stem 类似。Swin 默认把输入图像切成 4x4 patch,所以 Patch Embedding 本质上是一个 kernel_size=4、stride=4 的卷积层,输出通道数设为 C。这样后续特征图的分辨率是 H/4 × W/4,和很多检测网络的下采样倍率对齐。如果你要处理 224x224 输入,第一步之后就是 56x56。
Patch Merging 则发生在 Stage 之间。它把特征图按 2x2 的邻域分成一组,每组四个 patch 在通道维度上拼接,得到 4C 维,再经过一个线性层压缩到 2C,同时分辨率减半。这个过程很像池化加通道扩展的变体,但保留了更多空间细节。要注意的是,Patch Merging 对特征图尺寸有要求,输入 H 和 W 必须是 2 的整数次幂,而且因为 Swin 做了 4x4 的 patch 化,整个输入尺寸最好能被 32 整除。我在实际项目中就遇到过输入尺寸没对齐导致后段维度报错的情况,后面会专门说。
3.3 不同规模的变体与配置表
Swin Transformer 提供了从 Tiny 到 Large 的多种变体,方便不同算力场景选择。核心配置参数有四个:embed_dim(也就是 C,第一层通道数)、每个 Stage 的 Block 数量、多头注意力的头数、以及窗口大小 M。一般窗口大小固定为 7,头数从 3 开始逐层翻倍,这样能保证每组的头维度一致。
常见的几个变体配置大致如下,我整理了一张表方便对照:
| 模型 | C | Block 数(4个Stage) | 头数 | 参数量(约) | 适用场景 |
|---|---|---|---|---|---|
| Swin-T | 96 | [2,2,6,2] | [3,6,12,24] | 28M | 轻量分类、检测、实时性要求高 |
| Swin-S | 96 | [2,2,18,2] | [3,6,12,24] | 50M | 平衡性能与算力 |
| Swin-B | 128 | [2,2,18,2] | [4,8,16,32] | 88M | 大规模数据集、高精度任务 |
| Swin-L | 192 | [2,2,18,2] | [6,12,24,48] | 197M | 顶级精度,训练成本较高 |
选择时不要只看参数量。Swin-S 和 Swin-B 在结构上相近,但 B 的通道更宽,同样的 Block 数量下感受野和容量都会更大。如果算力允许,Swin-L 在 ImageNet 和检测任务上确实能刷出很亮眼的数据,但部署时也要考虑推理延迟。
4. 从原理到复现:训练与实测要点
4.1 参数初始化与学习率策略
很多同学从分类模型迁移到检测或分割时,会直接加载官方预训练权重。Swin Transformer 的预训练模型是在 ImageNet-1K 或 ImageNet-22K 上训练的,迁移效果通常不错。但要注意,预训练权重的 shape 和输入分辨率绑定,如果你的输入分辨率从 224 改成 384,Position Bias 表也需要做插值处理。Swin 官方提供了一个 bicubic 插值脚本,但自己改代码时很容易因为维度不匹配报错。建议直接使用官方支持的window_size、img_size参数初始化模型,再加载权重,不要手动改太多。
训练 Swin 时,学习率策略和 CNN 骨干不太一样。因为 Transformer 类模型对学习率更敏感,通常初始学习率在 1e-4 到 3e-4 之间,配合 warmup 和 cosine decay。我常用的做法是:先 linear warmup 约 5 个 epoch,把学习率从 0 逐步升到目标值,再用 cosine 曲线衰减到接近 0。这个策略可以缓解早期训练不稳定,也能让模型在后期收敛得更平滑。如果 batch size 变大了,学习率也要按比例调整,一般遵循线性缩放规则,比如 batch size 从 128 倍增到 256,学习率也近似翻倍,但最好结合验证集观察。
4.2 数据增强与正则化选择
Swin 相对 ViT 来说,对数据增强的依赖没有那么极端,但该用的还是要用。分类任务里,基本的 RandomCrop、RandomHorizontalFlip 是标配;稍微强一点可以用 RandAugment、Mixup、CutMix。在检测和分割任务里,多尺度训练尤其有效,因为 Swin 的线性复杂度使它能承受更大的输入范围。我试过把输入短边从 480 到 960 随机变化,涨点比固定尺度明显不少。
正则化方面,Dropout 在 Swin 里用得不多,因为它的结构本身已经对过拟合比较稳定。但 Drop Path 一定要用,一般设置在 0.1 到 0.4 之间,模型越大值可以越大。Drop Path 相当于随机丢弃某个 Block 的残差分支,相当于一种深度上的 Dropout。如果你发现自己训练出来的模型在验证集上震荡得很厉害,很可能是 Drop Path 概率设得太高,或者 warmup 不够,可以先把它降到 0.1 试试。
4.3 显存优化与速度调优
Swin Transformer 虽然计算复杂度是线性的,但实际训练时显存占用并不低。原因在于窗口内仍然会生成 [B, num_windows, heads, MM, MM] 的注意力矩阵,如果 batch size 和窗口数很多,中间变量依然可观。我跑 Swin-L 在 8 卡 80G 上可以开到 batch size 16 左右,但换到 12G 的消费卡就得降到 2-4。显存不够时,有几个有效手段:
- 打开梯度检查点(gradient checkpointing),把某些中间激活值在反向传播时重新计算,用时间换显存。通常能省 40% 左右显存,但训练速度会下降。
- 混精度训练(AMP),PyTorch 的
torch.cuda.amp可以直接用,显存占用能减少 30%-50%,而且在 A100 上还能提速。 - 减小窗口大小,比如从 7x7 改到 5x5,会降低精度但显存和计算都明显减少。这个一般不建议,除非实在没办法。
速度调优的话,有个容易被忽视的点:Swin 的窗口化 reshape 操作涉及大量张量维度变换,在 CPU 和 GPU 之间切换会严重拖慢速度。写代码时尽量保证所有窗口操作都在 GPU 上完成,尽量避免把窗口循环写到 for 里。官方实现用了一个window_reverse来做逆向转换,这部分的 tensor 形状要仔细核对。另外,推理时如果输入尺寸固定,可以把窗口划分、偏置表等预先算好,缓存起来,能省不少动态计算时间。
5. 常见问题与调试技巧
5.1 显存不够怎么解决
这个问题几乎人人都遇到。除了上一节说的 checkpoint 和 AMP,还有一个容易被忽略的点:在检测框架里,Swin 作为 backbone 会输出四个阶段的特征图,中间层特征保存得非常多。如果你用 Detectron2 或 MMDetection 这类框架,可以检查一下是否默认开了find_unused_parameters=True,这个选项在 DDP 下会额外占用显存。如果不是每个 loss 都需要所有模型参数梯度,尽量别开。还有,把 backbone 的frozen_stages设置为 2 或 3,冻结前几个 Stage 的梯度,可以减少显存和计算,迁移训练时通常不损失太多精度。
5.2 为什么我的效果不如原论文
跑 Swin 时发现复现效果不如论文,是很多人的困惑。我自己的经验是,先别怀疑模型,先检查数据和学习率。Swin 在 ImageNet 上的训练是 300 epoch 起步,加上全套增强策略,很多人用 100 epoch 就想达到同样精度,这基本不可能。对于下游任务,预训练权重质量的影响非常大。如果用了官方权重,基本上效果是有保障的。如果是从零训练,那要保证训练轮次和数据增强足够充足。另一类问题是输入尺寸不匹配:Swin 的相对位置偏置表是针对 224x224 训练的,如果测试分辨率变成 384,需要先对位置偏置表做插值,否则性能会明显下降。你可以在加载模型后打印relative_position_bias_table的形状,确认它和num_patches是否对应。
5.3 与 CNN 或 ViT 如何选择
有些场景不一定非要上 Swin。如果你的任务输入分辨率不大、任务简单,用 ResNet 或 EfficientNet 反而更方便,部署生态成熟、推理快。如果你的任务依赖全局上下文,比如一些需要理解图像整体结构的任务,标准 ViT(保持固定分辨率)可能更直接。Swin 更适合那些既需要局部细节、又要多尺度特征的稠密预测任务,比如目标检测、实例分割、语义分割。在同等参数量下,Swin 在 COCO 和 ADE20K 这类基准上的表现通常明显优于同规模 CNN 和纯 ViT,这也是它一度成为检测分割任务默认骨干的原因。从工程角度讲,Swin 的另一个优势是它输出的多尺度特征可以直接替换 ResNet,无论 Detectron2、MMDetection 还是自定义 pipeline,很多框架都有现成 adapter,迁移成本很低。
5.4 移位窗口 Mask 的实现细节
这个是在源码实现时最难理解的部分,我特意拿出来单独讲。在做循环移位后,特征图的窗口并不是物理上真实相邻的,有些窗口包含了来自左上、右下不同区域的 patch。这些 patch 在计算注意力时不能互相看到,必须用 mask 把不合法位置遮住。具体做法是:为每个窗口计算一个 mask 矩阵,值为 0 的位置代表可保留,值为一个非常大的负数(比如 -100)的位置代表要屏蔽。然后把 mask 加到 attention 分数矩阵上,再经过 softmax,这些位置的权重就趋近于 0。注意这个 mask 是作用在 MM 的二维空间上的,shape 是 [num_windows, MM, MM] 或 [Bnum_windows, heads, MM, MM],很多人在这里会把维度搞混。调试时可以用一个很小的随机输入过一遍模型,检查输出 shape 是否一致。
5.5 一个实战案例:自制检测框架集成 Swin
我之前在某跨平台系统里做过一个目标检测模块,backbone 从 ResNet 换到 Swin-T 后,mAP 从 36.8 涨到了 39.1,同时推理帧率下降不到 10%。换成 Swin-S 之后 mAP 到 41.2,但帧率下降明显。实际落地时用了 Swin-T + FPN + 自定义检测头,输入分辨率 800x1333,在 1080Ti 上跑大约 18ms。集成过程中最大的坑是 batch norm 的同步问题:因为 Swin 的 LayerNorm 和 CNN 的 BN 行为不同,如果你的框架默认冻结了 BN,LayerNorm 不受影响,但如果自己加了 BN 层,就要注意 BN 统计量是否更新。训练阶段和推理阶段 BN 的状态切换要检查清楚,否则会出现训练精度高、推理掉点的情况。
Swin Transformer 这个架构,既保留了 Transformer 在长程建模上的优势,又通过窗口机制和层次设计拉近了它与 CNN 的距离,所以它能迅速成为视觉领域的主流骨干之一。我自己的经验是,理解 Shit 窗口和相对位置偏置是入门的关键,而真正让模型发挥价值的是把输入分辨率、预训练权重、学习率策略和数据增强这些细节打磨到位。如果你打算在自己的项目里引入 Swin,建议先在小数据集上从 Swin-T 开始跑通整个流程,把显存和速度摸清楚,再往更大模型迁移。这样踩坑的成本最低,也最快能见到效果。