【Swin Transformer原理和源码解析】Hierarchical Vision Transformer using Shifted Windows

news2024/11/16 13:25:54

目录

  • 前言
  • 一、动机和改进点
  • 二、整体架构:SwinTransformer
  • 三、输入设置:PatchEmbed
  • 四、4个重复的Stage:BasicLayer
    • 4.1、SwinTransformerBlock
      • 4.1.1、创建mask
      • 4.1.2、shift特征
      • 4.1.3、为shift后的特征划分窗口
      • 4.1.4、W-MSA VS SW-MSA
    • 4.2、PatchMerging
  • 五、总结
  • 六、一些问题
    • 6.1.为什么要W-MSA和SW-MSA混合使用?
  • Reference

前言

ViT让Transformer第一次在视觉任务中暂露头角,而Swin Transfomer直接让Transformer在视觉任务中大放光彩,直接打败了当时的所有的CNN网络,一出来就直接是当时的Sota。现在的很多厉害的Transfomer变体都是Swin改进的,而且Swin Transformer这个网络在很多比赛上都会用它,分类、分割、检测基本上用它都不会差,我打的一个分类比赛就是用的它: 【记第一次kaggle比赛】PetFinder.my - Pawpularity Contest 宠物预测。当时打的时候是掉包的,两句话就创建了Model了,知其然不知所以然,这怎么行,所以今天有必要学习一下。

论文地址: https://arxiv.org/pdf/2103.14030.pdf

源码地址: https://github.com/microsoft/Swin-Transformer

这里我用的是b站大佬 霹雳吧啦Wz 改编后(相对源码作了微小改动,增加了多尺度训练)的代码:
WZMIAOMIAO

注释版本代码也同样分享到了我的Github:https://github.com/HuKai97/Classification

一、动机和改进点

VIT为了让图像可以像词向量那样输入Encoder中,而且计算量还不能太大,就直接将图像切分成一个个小的Patch,再把每个Patch当成一个词向量,把所有Patch拼接起来送入Encoder,这样当然可以降低参数量和计算量,但是当图像变大,Patch数目变多,复杂度太大。还有没有更好的输入方式了呢?

VIT主要是改变了一下图片的输入,让Transformer的Encoder可以适用于图像任务中,但是对于整个模型的架构(之前讲LN提前了),VIT是没有做什么改进的,用的还是原始的Transformer中的Encoder(整个Encoder内部各个encoder变换,但是特征的shape是不变的)。那么原始的Transformer的Encoder模块真的就适用于图像任务吗,还有没有更好的Encoder结构?

所以总结下,ViT有两个问题:

  1. 尺度问题,数据集物体大大小小,但是整个Encoder过程特征尺度是不变的,效果肯定不好;
  2. 划分patch,再把整张图片的所有patch都输入Encoder中,计算量太大;

所以,Swin Transformer针对这两点做出了改进:

  1. Encode呈现金字塔形状。每过一个Encode图片shape变小,感受野在不停的增大,解决了尺度问题。
  2. 注意力机制放在一个窗口内部。不再把整张图片的所有patch都输入Encoder,而是将各个Patch单独的输入Encoder,解决了计算量太大的问题。

二、整体架构:SwinTransformer

在这里插入图片描述

  1. Patch Embeded:对输入图片 [bs,3,H_,W_] 进行处理。第一步:先经过Patch Partition,将图像划分为一个个的patch,每个patch是4x4x3大小(4x4Conv实现)得到一个 [bs,48,H_/4, W_/4] 大小的特征图;第二步:经过一个Linear Embedding层,进行Linear线性变换,得到 [bs, H_/4 * W_/4, C=96];(但是实际代码是通过一个4x4Conv s=4实现的,其实本质还是在学习参数,一样的)
  2. 经过4个stage:每个stage是若干个Swin Transformer Block + Patch Merging。前者计算相关性,后者进行采样,实现多尺度;最终经过4个stage后,特征下采样为 [bs,H_/32 * W_/32,8C=768];
  3. 分类:经过一个avgpool+flatten+Linear进行分类预测,最终得到 [bs,num_classes];

源码:

class SwinTransformer(nn.Module):
    r""" Swin Transformer
        A PyTorch impl of : `Swin Transformer: Hierarchical Vision Transformer using Shifted Windows`  -
          https://arxiv.org/pdf/2103.14030
    """
    def __init__(self, patch_size=4, in_chans=3, num_classes=1000,
                 embed_dim=96, depths=(2, 2, 6, 2), num_heads=(3, 6, 12, 24),
                 window_size=7, mlp_ratio=4., qkv_bias=True,
                 drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1,
                 norm_layer=nn.LayerNorm, patch_norm=True,
                 use_checkpoint=False, **kwargs):
        """
        patch_size: 每个patch的大小  4x4
        in_chans: 输入图像的通道数 3
        num_classes: 分类类别数 默认1000
        embed_dim: 通过Linear Embedding后映射得到的通道数 也就是图片中的C 默认96
        depths: 每个stage中重复swin-transformer block的次数 默认(2, 2, 6, 2)
        num_heads: 每个stage中swin-transformer block的muti-head的个数 默认(3, 6, 12, 24)
        window_size: 滑动窗口的大小 默认7x7
        mlp_ratio: MLP中第一个全连接层Linear会将channel翻多少倍 默认4倍
        qkv_bias: 在muti-head self-attention中是否使用偏置 默认使用True
        drop_rate:
        attn_drop_rate: 在muti-head self-attention中使用的drop rate
        drop_path_rate: 在每个swin-transformer block中使用的drop rate  从0慢慢增加到0.1
        norm_layer: LN
        patch_norm:
        use_checkpoint: 使用可以节省内存 默认不使用
        """
        super().__init__()

        self.num_classes = num_classes   # 5
        self.num_layers = len(depths)    # 4
        self.embed_dim = embed_dim       # C = 96
        self.patch_norm = patch_norm     # True
        # stage4输出特征矩阵的channels
        self.num_features = int(embed_dim * 2 ** (self.num_layers - 1))   # 768 = 8C
        self.mlp_ratio = mlp_ratio       # 4.0

        # split image into non-overlapping patches
        self.patch_embed = PatchEmbed(
            patch_size=patch_size, in_c=in_chans, embed_dim=embed_dim,
            norm_layer=norm_layer if self.patch_norm else None)
        self.pos_drop = nn.Dropout(p=drop_rate)  # p=0

        # stochastic depth
        # [0.0, 0.00909090880304575, 0.0181818176060915, 0.027272727340459824, 0.036363635212183, 0.045454543083906174, 0.054545458406209946, 0.06363636255264282, 0.0727272778749466, 0.08181818574666977, 0.09090909361839294, 0.10000000149011612]
        dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))]  # stochastic depth decay rule

        # build layers/stages   4个
        self.layers = nn.ModuleList()
        for i_layer in range(self.num_layers):
            # 注意这里构建的stage和论文图中有些差异
            # 这里的stage不包含该stage的patch_merging层,包含的是下个stage的
            # stage1-3: Swin Transformer Block + Patch Merging
            # Stage4: Swin Transformer Block
            layers = BasicLayer(dim=int(embed_dim * 2 ** i_layer),
                                depth=depths[i_layer],
                                num_heads=num_heads[i_layer],
                                window_size=window_size,
                                mlp_ratio=self.mlp_ratio,
                                qkv_bias=qkv_bias,
                                drop=drop_rate,
                                attn_drop=attn_drop_rate,
                                drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])],
                                norm_layer=norm_layer,
                                downsample=PatchMerging if (i_layer < self.num_layers - 1) else None,
                                use_checkpoint=use_checkpoint)
            self.layers.append(layers)

        self.norm = norm_layer(self.num_features)   # LN(768)
        self.avgpool = nn.AdaptiveAvgPool1d(1)
        self.head = nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity()  # 分类头  768 -> 5

        self.apply(self._init_weights)   # 初始化

    def _init_weights(self, m):
        if isinstance(m, nn.Linear):
            nn.init.trunc_normal_(m.weight, std=.02)
            if isinstance(m, nn.Linear) and m.bias is not None:
                nn.init.constant_(m.bias, 0)
        elif isinstance(m, nn.LayerNorm):
            nn.init.constant_(m.bias, 0)
            nn.init.constant_(m.weight, 1.0)

    def forward(self, x):
        """
        x: [bs, 3, H_, W_]
        """
        # 1、Patch Partition + Linear Embedding
        # [bs, 3, H_, W_] -> [bs, H_/4 * W_/4, C] -> [bs, H_/4 * W_/4, C]   C=96
        x, H, W = self.patch_embed(x)  # H = H_/4  W = W_/4
        x = self.pos_drop(x)

        # 2、4 stage = 4 x (Swin Transformer Block x n + Patch Merging)
        # x: [bs, H_/4 * W_/4, C] -> [bs, H_/8 * W_/8, 2C] -> [bs, H_/16 * W_/16, 4C] -> [bs, H_/32 * W_/32, 8C]
        for layer in self.layers:
            x, H, W = layer(x, H, W)
        
        # 3、分类
        x = self.norm(x)  # LN(8C=768)
        x = self.avgpool(x.transpose(1, 2))  # [bs, H_/32 * W_/32, 8C] -> [bs, 8C, H_/32 * W_/32] -> [bs, 8C, 1]
        x = torch.flatten(x, 1)  # [bs, 8C, 1] -> [bs, 8C]
        x = self.head(x)  # [bs, num_classes]
        return x

三、输入设置:PatchEmbed

源码和论文有出入,这里直接使用一个4x4Conv s=4,实现下采样的过程。对输入图片 [bs,3,H_,W_]进行初步处理,得到一个[bs, H_/4 * W_/4, C=96]大小的特征图。源码如下:

class PatchEmbed(nn.Module):
    """
    2D Image to Patch Embedding  [bs, 3, H_, W_] -> [B, H_/4 * W_/4, C=96]
    """
    def __init__(self, patch_size=4, in_c=3, embed_dim=96, norm_layer=None):
        """
        patch_size: 每个patch的大小 4x4
        in_c: 输入图像的channel 3
        embed_dim: 96 = C
        norm_layer: LN
        """
        super().__init__()
        patch_size = (patch_size, patch_size)
        self.patch_size = patch_size
        self.in_chans = in_c
        self.embed_dim = embed_dim
        self.proj = nn.Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size)  # 4x4Conv 下采样4倍 c:3->96
        self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()

    def forward(self, x):
        # x: [bs, 3, H_, W_]
        _, _, H, W = x.shape

        # padding
        # 如果输入图片的H,W不是patch_size的整数倍,需要进行padding
        pad_input = (H % self.patch_size[0] != 0) or (W % self.patch_size[1] != 0)  # False
        if pad_input:
            # to pad the last 3 dimensions,
            # (W_left, W_right, H_top,H_bottom, C_front, C_back)
            x = F.pad(x, (0, self.patch_size[1] - W % self.patch_size[1],
                          0, self.patch_size[0] - H % self.patch_size[0],
                          0, 0))

        # 1、Patch Partition
        # 下采样patch_size倍  [bs, 3, H_, W_] -> [bs, C=96, H_/4, W_/4]
        x = self.proj(x)
        _, _, H, W = x.shape  # H=H_/4  W=W_/4
        # flatten: [B, C, H_/4, W_/4] -> [B, C, H_/4 * W_/4]
        # transpose: [B, C, H_/4 * W_/4] -> [B, H_/4 * W_/4, C]
        x = x.flatten(2).transpose(1, 2)
        x = self.norm(x)
        return x, H, W

四、4个重复的Stage:BasicLayer

每个stage都由若干个Swin Transformer Block 和 1个Patch Merging组成。

class BasicLayer(nn.Module):
    """A basic Swin Transformer layer for one stage."""
    def __init__(self, dim, depth, num_heads, window_size,
                 mlp_ratio=4., qkv_bias=True, drop=0., attn_drop=0.,
                 drop_path=0., norm_layer=nn.LayerNorm, downsample=None, use_checkpoint=False):
        """
        dim: C = 96
        depth: 重叠的Swin Transformer Block个数
        num_heads: muti-head self-transformer的头数
        window_size: 窗口大小7x7
        mlp_ratio: MLP中第一个全连接层Linear会将channel翻多少倍 默认4倍
        qkv_bias: 在muti-head self-attention中是否使用偏置 默认使用True
        drop: patch_embed之后一般要接一个Dropout 但是默认是 0.0
        attn_drop: 在muti-head self-attention中使用的drop rate  0.0
        drop_path: list: depth  存放这个stage中depth个transformer block的drop rate
        norm_layer: LN
        downsample: Pathc Merging进行下采样
        use_checkpoint: Whether to use checkpointing to save memory. Default: False
        """
        super().__init__()
        self.dim = dim
        self.depth = depth
        self.window_size = window_size
        self.use_checkpoint = use_checkpoint
        self.shift_size = window_size // 2  # 3

        # 调用depth个swin transformer block
        self.blocks = nn.ModuleList([
            SwinTransformerBlock(
                dim=dim,
                num_heads=num_heads,
                window_size=window_size,
                shift_size=0 if (i % 2 == 0) else self.shift_size,
                mlp_ratio=mlp_ratio,
                qkv_bias=qkv_bias,
                drop=drop,
                attn_drop=attn_drop,
                drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path,
                norm_layer=norm_layer)
            for i in range(depth)])

        # patch merging layer
        if downsample is not None:
            self.downsample = downsample(dim=dim, norm_layer=norm_layer)
        else:
            self.downsample = None

    def create_mask(self, x, H, W):
        ...

    def forward(self, x, H, W):
        # 1、depth个swin transformer block
        # 因为每个stage中的特征图大小是不变的,所以每个block的mask大小是相同的 所以只需要创建一次即可
        # [64,49,49]  64个网格  49x49每个网格中的每个位置(49个位置)对该网格中所有位置(49个位置)的注意力蒙版
        attn_mask = self.create_mask(x, H, W)  # [nW, Mh*Mw, Mh*Mw]
        for blk in self.blocks:
            blk.H, blk.W = H, W
            if not torch.jit.is_scripting() and self.use_checkpoint:
                x = checkpoint.checkpoint(blk, x, attn_mask)
            else:
                # 默认执行 调用swin transformer block
                x = blk(x, attn_mask)

        # 2、下采样 Patch Merging
        # 最后一个stage是None 不执行下采样
        if self.downsample is not None:
            x = self.downsample(x, H, W)
            H, W = (H + 1) // 2, (W + 1) // 2  # 下采样 重新计算H W

        return x, H, W

值得注意的是创建attention mask(create_mask)的步骤,这一步是下面SW-MSA和W-MSA的关键点,下面再详细讲解。

4.1、SwinTransformerBlock

4.1.1、创建mask

在SwinTransformerBlock中,主要是负责创建attention mask,只在shift windows muti-head attention中使用,主要是告诉我们当前位置和哪些其他位置是同属于一个windows的(因为之前有一步shift window的操作),同属于一个windows的位置的mask=0,不同属于一个位置的mask=-100。

这样到后面计算出attention之后,同一个windows位置的attention + mask再softmax值是不变的,但是不同windows位置的attention + mask(-100),再softmax值就趋近于0了。

class BasicLayer(nn.Module):
    """A basic Swin Transformer layer for one stage."""
    ...
    def create_mask(self, x, H, W):
        """calculate attention mask for SW-MSA(shift window muti-head self-attention)
        以第一个stage为例
        x: [bs, 56x56, 96]
        H: 56
        W: 56
        返回attn_mask: [64,49,49] 64个网格  49x49每个网格中的每个位置(49个位置)对该网格中所有位置(49个位置)的注意力蒙版
                     记录每个位置需要在哪些位置计算attention
        """
        # 保证Hp和Wp是window_size的整数倍
        Hp = int(np.ceil(H / self.window_size)) * self.window_size   # 56
        Wp = int(np.ceil(W / self.window_size)) * self.window_size   # 56
        # 拥有和feature map一样的通道排列顺序,方便后续window_partition
        img_mask = torch.zeros((1, Hp, Wp, 1), device=x.device)  # [1, 56, 56, 1]
        # 对h和w先进行切片 划分为3个区域  0=(0,-7) (-7,-3) (-3,-1)
        h_slices = (slice(0, -self.window_size),
                    slice(-self.window_size, -self.shift_size),
                    slice(-self.shift_size, None))
        w_slices = (slice(0, -self.window_size),
                    slice(-self.window_size, -self.shift_size),
                    slice(-self.shift_size, None))
        # 对3x3=9个区域进行划分 编号 0-8
        cnt = 0
        for h in h_slices:
            for w in w_slices:
                img_mask[:, h, w, :] = cnt
                cnt += 1

        # 将img_mask划分为一个个的窗口   64个7x7大小的窗口
        # [1,56,56,1] -> [64,7,7,1] -> [64,7,7]
        mask_windows = window_partition(img_mask, self.window_size)  # [nW, Mh, Mw, 1]
        mask_windows = mask_windows.view(-1, self.window_size * self.window_size)  # [nW, Mh*Mw]
        # [nW, 1, Mh*Mw] - [nW, Mh*Mw, 1] -> [nW, Mh*Mw, Mh*Mw]=[64,49,49]
        # 数字相同的位置代表是同一个区域  我们就是要计算同一个区域的attention  相减之后为0的区域就是我们需要计算attention的地方
        # 64个网格  49x49每个网格中的每个位置(49个位置)对该网格中所有位置(49个位置)的注意力蒙版
        attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2)
        # 对于非零区域填上-100  这些区域是不需要计算attention的  所以在之后的softmax后就会为0
        attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill(attn_mask == 0, float(0.0))
        return attn_mask

这里涉及到划分窗口的操作:

def window_partition(x, window_size: int):
    """
    将feature map按照window_size划分成一个个没有重叠的window
    Args:
        x: (B, H, W, C)
        window_size (int): window size(M)

    Returns:
        windows: (num_windows*B, window_size, window_size, C)
    """
    B, H, W, C = x.shape   # 1  56  56  1
    x = x.view(B, H // window_size, window_size, W // window_size, window_size, C)  # [1,56,56,1] -> [1,8,7,8,7,1]
    # permute: [B, H//Mh, Mh, W//Mw, Mw, C] -> [B, H//Mh, W//Mh, Mw, Mw, C]
    # view: [B, H//Mh, W//Mw, Mh, Mw, C] -> [B*num_windows, Mh, Mw, C]
    windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C)  # [1,8,7,8,7,1] -> [1,8,8,7,7,1] -> [64,7,7,1]
    return windows

4.1.2、shift特征

class SwinTransformerBlock(nn.Module):
	def forward(self, x, attn_mask):
		# cyclic shift
        if self.shift_size > 0:  # SW-MSA
            # 对x特征进行移动  0-shift_size列移动到最右侧   0-shift_size行移动到最下面
            # -的就是从上往下 从左往右  +的就是从下往上 从右往左了
            # 对应的attn_mask就是传入的attn_mask
            shifted_x = torch.roll(x, shifts=(-self.shift_size, -self.shift_size), dims=(1, 2))
        else:  # W-MSA  不需要移动
            shifted_x = x
            attn_mask = None

最后计算完SW-MSA后需要将shift过的特征进行还原:

		# 之前shift过windows 再还原  从下往上 从右往左 +
        if self.shift_size > 0:
            x = torch.roll(shifted_x, shifts=(self.shift_size, self.shift_size), dims=(1, 2))
        else:
            x = shifted_x

4.1.3、为shift后的特征划分窗口

		# 为shifted_x划分窗口  与attn_mask划分的窗口对应  [bs,56,56,96] -> [512,7,7,96]  8x8xbs个7x7的窗口 x 96个通道
        x_windows = window_partition(shifted_x, self.window_size)  # [nW*B, Mh, Mw, C]
        x_windows = x_windows.view(-1, self.window_size * self.window_size, C)  # [nW*B, Mh*Mw, C]=[512,49,96]

这里的划分窗口和上面mask的划分窗口一样,就不赘述。

4.1.4、W-MSA VS SW-MSA

class WindowAttention(nn.Module):
    r"""W-MSA/SW-MSA
    Window based multi-head self attention (W-MSA) module with relative position bias.
    It supports both of shifted and non-shifted window.
    """
    def __init__(self, dim, window_size, num_heads, qkv_bias=True, attn_drop=0., proj_drop=0.):
        """
        dim: C = 96
        window_size: 窗口大小7x7
        num_heads: muti-head self-transformer的头数
        qkv_bias: 在muti-head self-attention中是否使用偏置 默认使用True
        proj_drop: 在muti-head self-attention中使用的drop rate  0.0
        """
        super().__init__()
        self.dim = dim
        self.window_size = window_size  # [7, 7]
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = head_dim ** -0.5

        # 初始化relative_position_bias_table
        self.relative_position_bias_table = nn.Parameter(
            torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads))  # [2*7-1 * 2*7-1, num_heads]

        # 1、生成绝对位置坐标索引
        coords_h = torch.arange(self.window_size[0])  # tensor([0, 1, 2, 3, 4, 5, 6])
        coords_w = torch.arange(self.window_size[1])  # tensor([0, 1, 2, 3, 4, 5, 6])
        # coords = torch.stack(torch.meshgrid([coords_h, coords_w], indexing="ij"))
        # [2, 7, 7]  7x7窗口的xy坐标
        coords = torch.stack(torch.meshgrid([coords_h, coords_w]))
        # [2, 7, 7] -> [2, 49]  第一个是所有位置的行坐标  第二个是所有位置的列坐标
        coords_flatten = torch.flatten(coords, 1)

        # 2、生成相对位置坐标索引
        # [2, Mh*Mw, 1] - [2, 1, Mh*Mw] -> [2, Mh*Mw, Mh*Mw]
        relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :]

        # [2, Mh*Mw, Mh*Mw] -> [Mh*Mw, Mh*Mw, 2]
        relative_coords = relative_coords.permute(1, 2, 0).contiguous()

        # 3、将二元相对位置坐标索引转变成一元相对位置坐标索引
        # 原始相对位置行/列标 = -6~6 + (window_size-1) -> 0~12
        # 行标 + (2 * window_size - 1) -> 13~25
        # 这时直接把行标 + 列标 直接把2D索引转换为1D索引 就不会出现(-1,0) (0,-1) 相加都是-1 无法区分的情况了
        relative_coords[:, :, 0] += self.window_size[0] - 1      # 行标 + (window_size-1)
        relative_coords[:, :, 1] += self.window_size[1] - 1      # 列标 + (window_size-1)
        relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1  # 行标 + (2 * window_size - 1)
        # [Mh*Mw, Mh*Mw, 2] -> [Mh*Mw, Mh*Mw]   行标 + 列标   直接转换为1元索引  与relative_position_bias_table一一对应
        relative_position_index = relative_coords.sum(-1)

        # 把relative_position_index放到缓存中  因为relative_position_index是固定值  不会变的  不需要修改
        # 我们网络训练的其实是relative_position_bias_table中的参数  我们每次循环都从relative_position_bias_table中拿对应idx的值即可
        self.register_buffer("relative_position_index", relative_position_index)

        self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)   # 生成qkv  3倍dim = q+k+v
        self.attn_drop = nn.Dropout(attn_drop)             # p=0.0
        self.proj = nn.Linear(dim, dim)                    # linear
        self.proj_drop = nn.Dropout(proj_drop)             # linear dropout p=0

        nn.init.trunc_normal_(self.relative_position_bias_table, std=.02)  # 初始化relative_position_bias_table参数

        self.softmax = nn.Softmax(dim=-1)   # softmax层

    def forward(self, x, mask: Optional[torch.Tensor] = None):
        """
        x: [bsx8x8, 49, 96]  bsx  8x8个7x7大小的window size  x96channel
        mask: W-MSA和SW-MSA交替出现 None/[8x8,49,49]  记录8x8个7x7大小的window size  中 每个位置需要和哪些位置计算attention
              =0的位置表示是需要计算attention的
        Attention(Q,K,V) = SoftMax(Q*K的转置/scale + B)*V
        """
        B_, N, C = x.shape  # batch_size*num_windows=bsx8x8, Mh*Mw=7x7, total_embed_dim=96

        # 生成qkv 和vit中的一样  和原始的transformer有区别  但是本质都是相同的 都是通过学习参数把输入的x映射到3个空间上
        # qkv(): -> [batch_size*num_windows, Mh*Mw, 3 * total_embed_dim]
        # reshape: -> [batch_size*num_windows, Mh*Mw, 3, num_heads, embed_dim_per_head]
        # permute: -> [3, batch_size*num_windows, num_heads, Mh*Mw, embed_dim_per_head] = [3,bsx8x8,3,7x7,32]
        qkv = self.qkv(x).reshape(B_, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)

        # 分别获得q k v
        # [batch_size*num_windows, num_heads, Mh*Mw, embed_dim_per_head] = [bsx8x8,3,7x7,32]
        q, k, v = qkv.unbind(0)  # make torchscript happy (cannot use tensor as tuple)

        # 这里是先缩放再乘以k的转置  其实是一样的
        # transpose: -> [batch_size*num_windows, num_heads, embed_dim_per_head, Mh*Mw]
        # @: multiply -> [batch_size*num_windows, num_heads, Mh*Mw, Mh*Mw]
        q = q * self.scale
        attn = (q @ k.transpose(-2, -1))

        # relative_position_bias_table.view: [Mh*Mw*Mh*Mw,nH] -> [Mh*Mw,Mh*Mw,nH]
        # 生成相对位置偏置:生成相对位置index + 去relative_position_bias_table中去取相应的可学习的bias参数
        relative_position_bias = self.relative_position_bias_table[self.relative_position_index.view(-1)].view(
            self.window_size[0] * self.window_size[1], self.window_size[0] * self.window_size[1], -1)
        relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous()  # [nH, Mh*Mw, Mh*Mw]

        # att + B
        attn = attn + relative_position_bias.unsqueeze(0)

        # softmax处理
        if mask is not None:
            # SW-MSA
            # mask: [nW, Mh*Mw, Mh*Mw]=[8x8,49,49]  记录8x8个7x7大小的window中每个位置需要和哪些位置计算attention
            #       =0的位置表示是需要计算attention的   不相同的区域位置是接近-100表示的
            nW = mask.shape[0]  # num_windows
            # attn.view: [batch_size, num_windows, num_heads, Mh*Mw, Mh*Mw]
            # mask.unsqueeze: [1, nW, 1, Mh*Mw, Mh*Mw]
            # 相同区域位置attn+0没有影响   不同区域位置attn+(-100)  再进行softmax   这个位置的attn就->0
            attn = attn.view(B_ // nW, nW, self.num_heads, N, N) + mask.unsqueeze(1).unsqueeze(0)
            attn = attn.view(-1, self.num_heads, N, N)
            attn = self.softmax(attn)
        else:
            # W-MSA
            attn = self.softmax(attn)

        attn = self.attn_drop(attn)

        # attn * v
        # @: multiply -> [batch_size*num_windows, num_heads, Mh*Mw, embed_dim_per_head]
        # transpose: -> [batch_size*num_windows, Mh*Mw, num_heads, embed_dim_per_head]
        # reshape: -> [batch_size*num_windows, Mh*Mw, total_embed_dim]
        x = (attn @ v).transpose(1, 2).reshape(B_, N, C)
        x = self.proj(x)
        x = self.proj_drop(x)
        return x

这个步骤和ViT中的其实差不多,只不过ViT是计算每个位置和所有位置的attention,而WindowAttention是按照窗口来计算每个位置和当前windows内所有位置的attention,计算量更小。

4.2、PatchMerging

这部分主要功能就是进行下采样,操作:每个一个元素取一个像素,有点类似YOLOv5中的Focus层。最后将4个特征拼接起来,再接一个Linear缩放通道。

在这里插入图片描述

class PatchMerging(nn.Module):
    r""" Patch Merging Layer. 下采样
    输入[bs, H_/4 * W/4, C=96]  ->  输出[bs, H_/8 * W/8, 2C] 
    """
    def __init__(self, dim, norm_layer=nn.LayerNorm):
        super().__init__()
        self.dim = dim  # 输入特征的channel = 96/192/384
        self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False)
        self.norm = norm_layer(4 * dim)  # LN

    def forward(self, x, H, W):
        """
        x: [bs, H_/4 * W/4, C=96]
        """
        B, L, C = x.shape   # B=8 C=96 L= H_/4*W/4
        assert L == H * W, "input feature has wrong size"

        x = x.view(B, H, W, C)  # [bs, H_/4 * W/4, C=96] -> [bs, H_/4, W_/4, C=96]

        # padding
        # 如果输入feature map的H,W不是2的整数倍,需要进行padding
        pad_input = (H % 2 == 1) or (W % 2 == 1)  # False
        if pad_input:  # 跳过
            # to pad the last 3 dimensions, starting from the last dimension and moving forward.
            # (C_front, C_back, W_left, W_right, H_top, H_bottom)
            # 注意这里的Tensor通道是[B, H, W, C],所以会和官方文档有些不同
            x = F.pad(x, (0, 0, 0, W % 2, 0, H % 2))

        # 每隔一个像素取一个元素 有点像yolov5的focus层 最后一个特征 -> 4个下采样的特征
        # [bs, H_/4, W_/4, C=96]  ->  4 x [bs, H_/8, W_/8, C=96]
        x0 = x[:, 0::2, 0::2, :]  
        x1 = x[:, 1::2, 0::2, :] 
        x2 = x[:, 0::2, 1::2, :]  
        x3 = x[:, 1::2, 1::2, :]  
        
        # 4 x [bs, H_/8, W_/8, 96] -> [bs, H_/8, W_/8, 96*4] -> [bs, H_/8 * W_/8, 4*C]
        x = torch.cat([x0, x1, x2, x3], -1)  
        x = x.view(B, -1, 4 * C)  

        x = self.norm(x)  # LN
        
        # Linear 将通道从4C -> 2C  [bs, H_/8 * W_/8, C*4] -> [bs, H_/8 * W_/8, 2*C]
        x = self.reduction(x)  

        return x

五、总结

为了解决ViT存在的问题:

  • 尺度问题:数据集物体大大小小,但是整个Encoder过程特征尺度是不变的,效果肯定不好;
  • 计算量大:划分patch,再把整张图片的所有patch都输入Encoder中,计算量太大;

改进点:

  • Encode呈现金字塔形状。每过一个Stage对特征进行一次下采样,感受野在不停的增大,解决了尺度问题。所以Swin-Transformer不进适合分类任务,在下游检测、分割任务可以充分利用这种多尺度信息,检测效果很好;
  • 注意力机制放在一个窗口内部。不再把整张图片的所有patch都输入Encoder,而是将各个Patch单独的输入Encoder,解决了计算量太大的问题。

关于第二点改进点还有很多的细节:

  • 提出Window Muti-head Self-Attention(W-MSA):把输入特征划分为一个个的windows窗口,只计算每个位置和当前windows窗口的所有位置的相关性Attention,其他窗口的不关心,这样就大大减少了计算量了;
  • W-MSA有一个问题,不同窗口完全不相关了,那不同窗口的位置之间不就没法交互了,所以作者又提出了Shift-Window Muti-head Self-Attention(SW-MSA)。
  • 特征图Shift操作其实很简单,就是特征某些行列平移,但是Shift了之后就会产生更多的窗口,计算量还是增加了,作者为了解决这个问题,引入了Mask,仍然是使用原先的窗口划分方式,但是用mask记录每个位置属于哪个窗口,相同窗口的位置mask=0,不同窗口的位置mask=-100,那么最后再用计算好的attention + mask,再softmax。于是,相同窗口的attention不变,不同窗口的attention=0,完美解决所有问题;
  • 作者还在WindowAttention中引入了relative_position_bias,使用Attention(Q,K,V) = SoftMax(Q*K的转置/scale + B)*V计算公式;

六、一些问题

6.1.为什么要W-MSA和SW-MSA混合使用?

我的理解:单独的W-MSA和单独的SW-MSA其实都是固定的位置窗口(SW-MSA是对固定的区域进行shift,但是如果单独只使用SW-MSA,那么不还是固定的窗口),这样使用还是会有不同窗口无法信息交互的问题,但是混合起来使用,才能真正的起到交互作用。

Reference

b站: Swin Transformer论文精读【论文精读】

b站: 12.1 Swin-Transformer网络结构详解

b站: 12.2 使用Pytorch搭建Swin-Transformer网络

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/56456.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android APP 停止运行报错弹窗

一个APP经常性的报错,然后就会弹出一个"很抱歉,xxx已停止运行"这样的弹窗,在Android系统层临时将这个弹窗屏蔽.弹窗如下: 在没做过此类修改之前,不知到如何下手的情况下,请做如下几步: 在Android目录下全局搜索关键字"很抱歉",然后会有一个路径frameworks…

ICS计算系统概论实验3—LC3汇编代码实现最长重复子字符串Longest-duplicate-substring

Lab03 Longest-duplicate-substring Purpose 子字符串是字符串中至少出现一次的连续字符序列。重复子字符串是一种由相同字符组成的子字符串。例如&#xff0c;“aabbbc”的重复子字符串是“aa”&#xff0c;“bbb”和“c”。 给定一个字符串及其长度&#xff0c;计算出它最长…

全球DTC品牌纷纷奔走线下,价值岂止于用户体验和品牌形象

走向线下&#xff0c;开设新零售门店/旗舰店/体验店/快闪店&#xff0c;已成为很多全球品牌的共同做法&#xff1a;从海外巨头亚马逊的Amazon go、国内的盒马O2O&#xff0c;到DTC经典品牌的Warby Parker、Allbirds们遍地开花的线下实体店&#xff0c;是什么让全球DTC品牌纷纷走…

【Matplotlib绘制图像大全】(二十三):Matplotlib保存图像

前言 大家好,我是阿光。 本专栏整理了《Matplotlib绘制图像大全》,内包含了各种常见的绘图方法,以及Matplotlib各种内置函数的使用方法,帮助我们快速便捷的绘制出数据图像。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmMatp…

Sqli-Libs 速通

Sqli-Libs持续更新...目标Less-1Less-2Less-3Less-4Less-5Less-6Less-7Less-8Less-9Less-10Less-11Less-12Less-13Less-14Less-15Less-16Less-17Less-18Less-19 bp处理Less-20目标 直接写payload&#xff0c;sql语句非预期执行就算成功 表&#xff1a;emails,referers,uagent…

scratch绘制旋转六边形 电子学会图形化编程scratch等级考试二级真题和答案解析2022年9月

目录 scratch绘制旋转六边形 一、题目要求 1、准备工作 2、功能实现

12月2日:thinkphp中数据库完结

数据库的查询 聚合查询 聚合查询的几种方法其中将count作为重点来说&#xff0c;由图所示&#xff0c;即为使用count()方法中需要注意的点 count(*)的使用count()中字段名为具体值的使用方法时间查询 官方文档中列举的是使用wheretime()进行查询的方法&#xff0c;但是在日常的…

如何在Windows上安装并启动MySql

如何在Windows上安装并启动MySql一、MySql 安装包下载二、MySql 初始化三、启动MySql服务四、登录MySql五、修改MySql的root密码六、关于远程登录七、设置环境变量一、MySql 安装包下载 首先进入以下网址&#xff0c;选择合适的版本进行下载即可。 https://dev.mysql.com/dow…

Bootstrap5 安装使用

我们可以通过以下两种方式来安装 Bootstrap5&#xff1a; 使用 Bootstrap5 CDN。 从官网 getbootstrap.com 下载 Bootstrap 5。 Bootstrap 5 CDN 国内推荐使用 Staticfile CDN 上的库&#xff1a; Bootstrap5 CDN <!-- 新 Bootstrap5 核心 CSS 文件 --> <link r…

基于vue-simple-uploader的断点续传

方案&#xff1a; 分片上传&#xff0c;再次上传时&#xff0c;查询已上传分片&#xff0c;继续上传剩余分片 实现效果&#xff1a; 1. 安装uploader和spark-md5的依赖 npm install --save vue-simple-uploader npm install --save spark-md5 2.mainjs导入uploader impo…

日期格式化 YYYY-MM-DD 出现时间偏移量

在js中&#xff0c;很多时候需要把日期字符串转换为一个 Date 对象。 如果得到的日期字符串有时间还好办&#xff0c;如果没有时间&#xff0c;只有日期的格式&#xff0c;例如 2022-12-01 这样的字符串呢&#xff1f; 大部分人可能什么都没想&#xff0c;直接就调用了 new D…

MyBatis-Plus分页查询(快速上手运用)

系列文章目录 Mybatis-Plus知识点[MyBatisMyBatis-Plus的基础运用]_心态还需努力呀的博客-CSDN博客 Mybatis-PlusSpringBoot结合运用_心态还需努力呀的博客-CSDN博客MyBaits-Plus中TableField和TableId用法_心态还需努力呀的博客-CSDN博客 MyBatis-Plus中的更新操作&#…

【实操篇】Linux实用指令总结

目录 1.运行级别类 ●运行级别 ●指定运行级别 2.帮助指令类 ●帮助指令 1.man获得帮助信息 2.help指令 3.文件目录类 ●pwd指令 ●ls指令 ●cd指令 ●mkdir指令 ●rmdir指令 ●touch指令 ●cp指令 ●rm指令 ●mv指令 ●cat指令 ●more指令 ●less指令 ●>指令和>>…

如此简单的K8S,来玩下pv和pvc,利用nfs来实现持久化存储(内网环境,非常详细)

如此简单的K8S,来玩下pv和pvc&#xff0c;利用nfs来实现持久化存储(内网环境&#xff0c;非常详细) k8s很简单&#xff0c;怎么个简单法呢&#xff0c;来给小编一起再来复习一边吧。今天主要来了解下pv和pvc的概念&#xff0c;小编也是当过多次的面试官&#xff0c;小编悄悄的告…

低代码助力制造型企业管理:项目管理系统

制造业企业经过近两个世纪的发展&#xff0c;已经成为世界各国经济发展的支柱产业&#xff0c;要增强一个国家的综合国力&#xff0c;就必须首先建设一个强大的制造业。因此&#xff0c;在一些率先进入知识经济的工业发达国家&#xff0c;汽车、航空、装备等制造业依然保持支柱…

QT QSpinBox 整数计数器控件 使用详解

本文详细的介绍了QSpinBox控件的各种操作&#xff0c;例如&#xff1a;获取数值、设置前后缀、设置最大/小值、进制转换、关联信号槽、优化信号、QSS优化、文件源码、样式表 、效果&#xff1a;可以设置背景、边框、向上按钮、向下按钮 等等操作。 本文作者原创&#xff0c;转载…

安装dolphinscheduler

四种安装方式&#xff0c;机器有限&#xff0c;最后选的伪集群安装&#xff0c;所有都装在一台机器上。 安装手册 需要依次安装 JDK&#xff1a;下载JDK (1.8)&#xff0c;安装并配置 JAVA_HOME 环境变量&#xff0c;并将其下的 bin 目录追加到 PATH环境变量中。如果你的环境…

[附源码]Python计算机毕业设计SSM居民个人健康服务平台(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

能耗物联网的入口—计量监测解决方案价值解析

能耗物联网的入口—计量监测解决方案价值解析 在双碳政策支持下&#xff0c;有着众多头部企业的引领&#xff0c;能源物联网呈现蓬勃发展势头。 然而作为该领域内占有重要位置的海量用电设备&#xff0c;其运行状态、设备利用率均是未知的&#xff0c;绝大多数用电设备仍未联网…

CTFHUB-web-文件上传

无验证 上传一句话木马 <?php eval($_POST[attack]) ?>蚁剑添加数据&#xff0c;密码为attack 成功连接 找到flag flag&#xff1a; ctfhub{5d24093c73fddbba912490b6} 前端验证 还是传那个一句话木马&#xff0c;不允许上传 只能上传指定后缀的文件&#xff0c;所以可…