第6周学习笔记:Vision Transformer & Swin Transformer学习
Vision Transformer模型详解
该模型采用了Transformer架构直接应用于图像处理。具体而言,则是将一张图像划分为多个Patch单元,并将其视为NLP中的Token(单词)。随后会对每个Patch单元进行一系列Linear Embedding操作,并将其作为Transformer模型的输入。
Vision Transformer 模型由三个模块组成:
- 线性投影于展平后的片段(嵌入层)
- 基于变换体的编码器(基于变换体的编码器)
- 多层感知机头部(负责分类的任务模块)

Linear Projection of Flattened Patches(Embedding层)
针对标准Transformer模块设计其输入为token序列(向量形式),即表示为[num_token, token_dim]维度的矩阵如图所示其中token0至token9均为相应的向量例如在ViT-B/16模型中每个token向量长度为768

左边展示了一系列被精确分割的图像片段。假设输入图像数据维度为H×W×C,则我们采用块切割技术将图像划分为大小为S1×S2的小块区域。这种划分策略能够有效减少后续特征提取所需的计算量。具体而言,在这种划分下总共有N = (H/S1) × (W/S2)个小块区域需要处理。随后我们将每个分割后的图像片段展平为一维向量序列(Patch),每个Patch向量长度为S1×S2×C(Patch_dim)。通过这种操作我们能够构建一个完整的N×Patch_dim输入矩阵以供后续算法处理。
Transformer Encoder(Transformer 层)
该编码器包含以下四个关键组件:层归一化、多头注意力机制、残差连接以及线性变换。
- 通过给定输入编码矩阵X ,随后对输入编码矩阵执行layer normalization处理以获得X';
- 通过矩阵W_Q对序列进行线性变换得到查询向量Q;再通过矩阵W_K对序列进行线性变换得到键向量K;最后将查询向量Q fed into Multi-Head Attention模块以生成中间结果I_1
- 经过第二次层归一化处理后获得I_2;然后将原始输入序列x_0经过全连接神经网络进行进一步变换以获取特征表示h$
- 最后对特征表示h_1和h_2执行残差操作以生成该Block的最终输出h
- 最终一个Encoder模块能够整合多个Block并通过堆叠的方式完成特征提取

其中Multi-Head Attention机制能够让模型从全方位的角度去捕捉并整合信息,在一张图片中不同的人可能会注意到不同的部分;而这些多头正是通过整合这些差异来完成学习过程。
MLP Head(最终用于分类的层)
当完成Transformer Encoder模块后

Swin Transformer 网络详解
目前Transformer应用到图像领域主要有两大挑战:
- 视觉实体呈现多样性,在多样化的应用场景中视觉Transformer的表现不一定理想
- 图像的分辨率较高意味着像素数量大,在这种情况下使用基于全局自注意力机制的Transformer模型会导致较高的计算复杂度
针对这两个关键问题,在开发出一种新型算法框架的基础上构建了Swin Transformer模型架构
网络整体架构
采用与CNN类似的多层次架构进行图像处理后,则能实现模型对不同尺寸图像的有效适应。

接下来,简单看下原论文中给出的关于Swin Transformer(Swin-T)网络的架构图,可以看出整个框架的基本流程如下:

该系统采用分阶段设计,并包含四个不同的阶段。各个阶段都会逐步降低输入特征图的空间分辨率。与传统CNN不同的是,在此模型中
在输入阶段开始时采用了Patch Embedding技术,在图像中划分出独立的图块区域后将其嵌入到Embedding空间中进行后续处理。
每个Stage内部包含了Patch Merging操作以及多个Block结构。
其中Patch Merging模块主要在每个Stage的起始部分负责降低图片分辨率以适应后续处理需求。
Block的具体架构主要由LayerNorm、MLP、Window Attention以及Shifted Window Attention这四个核心组件构成。
class SwinTransformer(nn.Module):
def __init__(...):
super().__init__()
...
# absolute position embedding
if self.ape:
self.absolute_pos_embed = nn.Parameter(torch.zeros(1, num_patches, embed_dim))
self.pos_drop = nn.Dropout(p=drop_rate)
# build layers
self.layers = nn.ModuleList()
for i_layer in range(self.num_layers):
layer = BasicLayer(...)
self.layers.append(layer)
self.norm = norm_layer(self.num_features)
self.avgpool = nn.AdaptiveAvgPool1d(1)
self.head = nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity()
def forward_features(self, x):
x = self.patch_embed(x)
if self.ape:
x = x + self.absolute_pos_embed
x = self.pos_drop(x)
for layer in self.layers:
x = layer(x)
x = self.norm(x) # B L C
x = self.avgpool(x.transpose(1, 2)) # B C 1
x = torch.flatten(x, 1)
return x
def forward(self, x):
x = self.forward_features(x)
x = self.head(x)
return x
Patch Embedding详解
在输入进入Block之前,在图像处理过程中需要先将图片分割成多个patch块,并对其进行相应的向量化处理。具体来说,在处理过程中需要先对原始图片按照指定的patch_size参数切分出一系列尺寸为patch_size × patch_size的小块窗口区域,并对这些区域执行嵌入操作以生成特征表示。随后,在二维卷积层中配置 stride 和 kernelsize 参数设置为与patch_size相同的值,并根据设定的输出通道数来确定最终生成的一维嵌入向量的空间维度。最后,在处理完上述步骤后会将高度(H)和宽度(W)两个维度展平并将其展平后与原始数据在同一纬度上结合以完成整体特征映射。
Patch Merging详解
随后紧接着的是一个Patch Partition, 后面紧跟的是一个Linear Embedding层, 两者结合在一起实际上相当于一个Patch Merging层.
class PatchMerging(nn.Module):
def __init__(self, input_resolution, dim, norm_layer=nn.LayerNorm):
super().__init__()
self.input_resolution = input_resolution
self.dim = dim
self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False)
self.norm = norm_layer(4 * dim)
def forward(self, x):
"""
x: B, H*W, C
"""
H, W = self.input_resolution
B, L, C = x.shape
assert L == H * W, "input feature has wrong size"
assert H % 2 == 0 and W % 2 == 0, f"x size ({H}*{W}) are not even."
x = x.view(B, H, W, C)
x0 = x[:, 0::2, 0::2, :] # B H/2 W/2 C
x1 = x[:, 1::2, 0::2, :] # B H/2 W/2 C
x2 = x[:, 0::2, 1::2, :] # B H/2 W/2 C
x3 = x[:, 1::2, 1::2, :] # B H/2 W/2 C
x = torch.cat([x0, x1, x2, x3], -1) # B H/2 W/2 4*C
x = x.view(B, -1, 4 * C) # B H/2*W/2 4*C
x = self.norm(x)
x = self.reduction(x)
return x
SW-MSA详解
当采用W-MSA模块时

