Transformer视觉系列结构:从原理到实战避坑指南

1次阅读
没有评论

共计 3966 个字符,预计需要花费 10 分钟才能阅读完成。

image.webp

背景痛点

传统 CNN 通过局部感受野和层次化结构取得了显著成效,但在处理长距离依赖关系时存在天然局限。比如在医学图像分割中,病灶区域可能分散在图像各处,CNN 需要通过多次下采样和上采样才能建立这种全局关联,导致信息损失。另一方面,原始 Transformer 将图像视为像素序列(如 224×224 图像会生成 50176 长度的序列),其自注意力层的 O(n²)复杂度直接导致显存爆炸。

主流架构对比

1. Vision Transformer (ViT)

  • 核心创新:将图像划分为 16×16 的 patch(序列长度降至 196),直接套用 NLP Transformer
  • 优势:全局注意力建模能力突出,在大型数据集(如 JFT-300M)表现优异
  • 局限:固定尺度的 patch 缺乏多粒度特征,且计算量随图像尺寸平方增长

2. Swin Transformer

Transformer 视觉系列结构:从原理到实战避坑指南

  • 核心创新
  • 层级式金字塔结构(4/8/16/32 倍下采样)
  • 滑动窗口注意力(Window-MSA)降低计算复杂度至线性
  • 跨窗口连接(Shifted Window)解决窗口间信息隔离
  • 计算效率:在 ImageNet-1K 上达到 85.4% 准确率时仅需 4.5G FLOPs

3. Pyramid Vision Transformer (PVT)

  • 特色设计
  • 渐进式收缩金字塔保持空间分辨率层级
  • 空间缩减注意力(SRA)降低 key/value 分辨率
  • 相比 Swin 无需复杂窗口划分,更适合密集预测任务

关键代码实现

可学习 2D 位置编码

class Learnable2DPositionEmbedding(nn.Module):
    def __init__(self, dim, grid_size):
        super().__init__()
        self.pos_emb = nn.Parameter(torch.randn(1, dim, grid_size, grid_size) * 0.02)

    def forward(self, x):
        # x: [B, C, H, W]
        return x + F.interpolate(self.pos_emb, size=x.shape[2:], mode='bilinear')

数学原理:与传统 sin/cos 编码不同,可学习参数通过梯度下降自动适应图像空间关系,公式表示为:

$$\text{Output} = \text{Input} + \text{Interp}(E_{pos}), \quad E_{pos} \in \mathbb{R}^{1×C×S×S}$$

窗口多头注意力实现

def window_partition(x, window_size):
    B, H, W, C = x.shape
    x = x.view(B, H//window_size, window_size, W//window_size, window_size, C)
    windows = x.permute(0,1,3,2,4,5).contiguous().view(-1, window_size, window_size, C)
    return windows

class WindowAttention(nn.Module):
    def __init__(self, dim, num_heads, window_size, qkv_bias=True):
        super().__init__()
        self.dim = dim
        self.num_heads = num_heads
        self.scale = (dim // num_heads) ** -0.5

        # 相对位置偏置表 [(2M-1) × (2M-1) ]
        self.relative_position_bias_table = nn.Parameter(torch.zeros((2*window_size-1)**2, num_heads))

        # 生成相对位置索引
        coords = torch.stack(torch.meshgrid([torch.arange(window_size), torch.arange(window_size)]))
        coords_flatten = torch.flatten(coords, 1)  # [2, M²]
        relative_coords = coords_flatten[:,:,None] - coords_flatten[:,None,:]
        relative_coords += window_size - 1  # 转换为正数
        self.register_buffer("relative_position_index", 
            relative_coords[0] * (2*window_size-1) + relative_coords[1])

        self.qkv = nn.Linear(dim, dim*3, bias=qkv_bias)
        self.proj = nn.Linear(dim, dim)

    def forward(self, x, mask=None):
        B_, N, C = x.shape
        qkv = self.qkv(x).reshape(B_, N, 3, self.num_heads, C//self.num_heads)
        q, k, v = qkv.permute(2,0,3,1,4).unbind(0)

        attn = (q @ k.transpose(-2,-1)) * self.scale

        # 添加相对位置偏置
        relative_position_bias = self.relative_position_bias_table[self.relative_position_index.view(-1)].view(N, N, -1)
        attn = attn + relative_position_bias.permute(2,0,1).unsqueeze(0)

        if mask is not None:
            attn = attn.view(-1, self.num_heads, N, N)
            attn += mask.unsqueeze(1)
            attn = attn.view(-1, self.num_heads, N, N)

        attn = attn.softmax(dim=-1)
        x = (attn @ v).transpose(1,2).reshape(B_, N, C)
        return self.proj(x)

Patch Merging 下采样层

class PatchMerging(nn.Module):
    """2×2 降采样,同时通道数翻倍"""
    def __init__(self, dim):
        super().__init__()
        self.norm = nn.LayerNorm(4*dim)
        self.reduction = nn.Linear(4*dim, 2*dim, bias=False)

    def forward(self, x):
        B, H, W, C = x.shape
        x = x.view(B, H//2, 2, W//2, 2, C)
        x = x.permute(0,1,3,2,4,5).reshape(B, -1, 4*C)
        x = self.norm(x)
        return self.reduction(x)

性能优化策略

混合精度训练要点

  1. 在 PyTorch 中使用 torch.cuda.amp.autocast 上下文管理器
  2. 对 LayerNorm 和 Softmax 保持 FP32 计算:
    with torch.cuda.amp.autocast(dtype=torch.float16):
        x = layer_norm(x.float())  # 显式转换
        attn = attn_weights.float().softmax(dim=-1)
  3. 梯度缩放防止下溢出:搭配 GradScaler 使用

大图像分块处理

  • 策略一:重叠切片(Overlap-tile)
  • 将 5120×5120 图像切分为 640×640 的带重叠(如 128 像素)区块
  • 预测时拼接结果并融合重叠区域
  • 策略二:使用 PVT 的空间缩减注意力
  • 在 key/value 路径上应用 stride 卷积降低分辨率
  • 计算量从 O(H²W²)降至 O(HW(H/p)(W/p)),p 为缩减比例

生产环境避坑指南

问题 1:位置编码微调灾难性遗忘

现象:下游任务微调时,预训练的位置编码参数剧烈变化导致性能下降

解决方案
– 初始几层位置编码学习率设为 base_lr × 0.1
– 采用分层学习率策略:

param_groups = [{'params': model.pos_embed.parameters(), 'lr': lr*0.1},
    {'params': model.blocks.parameters()}
]
optimizer = AdamW(param_groups, lr=lr)

问题 2:小样本过拟合

应对措施
– 使用 DeiT 的蒸馏策略:在 ViT 中加入蒸馏 token

self.distill_token = nn.Parameter(torch.zeros(1, 1, dim))
x = torch.cat((cls_token, distill_token, patch_embeddings), dim=1)

– 强数据增强:MixUp + CutMix 组合
– 早停法(Early Stopping)监控验证集损失

问题 3:显存不足

优化方案
– 激活检查点技术(Gradient Checkpointing):

from torch.utils.checkpoint import checkpoint_sequential

def forward(self, x):
    return checkpoint_sequential(self.blocks, chunks=4, input=x)

– 采用 Swin 的窗口注意力替代全局注意力
– 使用梯度积累(Gradient Accumulation)模拟更大 batch

延伸思考

  1. 视频 Transformer 如何平衡时空注意力计算效率?是否需要分离时间 / 空间注意力层?
  2. 在边缘设备部署时,如何设计硬件友好的稀疏注意力模式?
  3. 视觉 Transformer 能否与神经辐射场(NeRF)结合,实现 3D 场景理解?
正文完
 0
评论(没有评论)