Press "Enter" to skip to content

Swin transformer(一) – self_attention介绍

 

前段时间用了swin transformer,是微软提出的,也在GitHub上开源了,可以作为视觉里的backbone

 

https://github.com/microsoft/Swin-Transformer

 

如果只是想尝试用一下SwinTransformer,那可以直接去把model文件夹down下来,在__init__.py加一行

 

from .swin_transformer import SwinTransformer

 

然后就可以当一个快乐的调包侠啦

 

from models import SwinTransformer
model = SwinTransformer(img_size=224, in_chans=3, num_classes=10)

 

如果不想改模型里面的参数,那就改上面这几个就可以啦,或者去看一下模型里面的具体的参数进行修改

 

import torch
from models import SwinTransformer
model = SwinTransformer(img_size=224, in_chans=3, num_classes=10)
x = torch.zeros(tuple([8, 3, 224, 224]))
print(x.shape)
y = model(x)
print(y.shape)

 

torch.Size([8, 3, 224, 224])
torch.Size([8, 10])

 

有木有看清楚,是不是非常清晰,这里假设batchsize是8,图像的大小为(3, 224, 224)
最后输出的是(1,10),这样就做的是十分类

 

模型的接口有了,相信大家也是都有自己的一套训练策略的,直接接入进去就好啦,当然你也可以就用这里面提供的训练策略

 

不过这篇文章不讲这个,我前段时间其实只是用到了self-attention,是从这里面剥离出来的,去掉了mask,也去掉了了相对位置,只留了一个self-attention

 

现在都说attention是未来嘛,那也是得学一学的

 

我感觉之前的卷积,全连接层,也不能算就没有attention,只是可能以前那种是隐性的attention,现在这个是显性的使用了attention,不知道这样想对不对啊,欢迎各位大佬指导

 

Attention block

 

这里面一个Block的构成是这样的

 

 

MLP

 

MLP就不用说了,大家一定很熟悉了,不熟悉的可以去看我之前写的一个MLP推导和实现

 

import torch.nn as nn
class Mlp(nn.Module):
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
        super().__init__()
        out_features = out_features or in_features
        hidden_features = hidden_features or in_features
        self.fc1 = nn.Linear(in_features, hidden_features)
        self.act = act_layer()
        self.fc2 = nn.Linear(hidden_features, out_features)
        self.drop = nn.Dropout(drop)
def forward(self, x):
        x = self.fc1(x)
        x = self.act(x)
        x = self.drop(x)
        x = self.fc2(x)
        x = self.drop(x)
return x
dim = 4
mlp_ratio = 4
mlp_hidden_dim = int(dim * mlp_ratio)
mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=nn.GELU, drop=0)
print(mlp)
import torch
x = torch.zeros(tuple([8, 20, 4]))
print(x.shape)
y = mlp(x)
print(y.shape)

 

Mlp(
  (fc1): Linear(in_features=4, out_features=16, bias=True)
  (act): GELU()
  (fc2): Linear(in_features=16, out_features=4, bias=True)
  (drop): Dropout(p=0, inplace=False)
)
torch.Size([8, 20, 4])
torch.Size([8, 20, 4])

 

这里面有个参数mlp_ratio,这个其实就是隐藏层是输入层节点的几倍哈,比如这里是4,输入为4,隐藏层的节点数就是16

 

Self_Attention

 

然后就是Self_Attention了

 

class Self_Attention(nn.Module):
def __init__(self, dim, num_heads=2, qkv_bias=True, qk_scale=None, attn_drop=0., proj_drop=0.):
        super().__init__()
        self.dim = dim
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = qk_scale or head_dim ** -0.5
        self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
        self.attn_drop = nn.Dropout(attn_drop)
        self.proj = nn.Linear(dim, dim)
        self.proj_drop = nn.Dropout(proj_drop)
        self.softmax = nn.Softmax(dim=-1)
def forward(self, x):
        B_, N, C = x.shape
        qkv = self.qkv(x).reshape(B_, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
        q, k, v = qkv[0], qkv[1], qkv[2]  # make torchscript happy (cannot use tensor as tuple)
        q = q * self.scale
        attn = (q @ k.transpose(-2, -1))
        attn = self.softmax(attn)
        attn = self.attn_drop(attn)
        x = (attn @ v).transpose(1, 2).reshape(B_, N, C)
        x = self.proj(x)
        x = self.proj_drop(x)
return x
dim = 4
self_attn = Self_Attention(dim, num_heads=2, qkv_bias=True, qk_scale=None, attn_drop=0., proj_drop=0.)
print(self_attn)
import torch
x = torch.zeros(tuple([8, 20, 4]))
print(x.shape)
y = self_attn(x)
print(y.shape)

 

Self_Attention(
  (qkv): Linear(in_features=4, out_features=12, bias=True)
  (attn_drop): Dropout(p=0.0, inplace=False)
  (proj): Linear(in_features=4, out_features=4, bias=True)
  (proj_drop): Dropout(p=0.0, inplace=False)
  (softmax): Softmax(dim=-1)
)
torch.Size([8, 20, 4])
torch.Size([8, 20, 4])

 

@代表的是矩阵乘法

 

从上面的输入输出的维度,我们可以看到经过self-attention,tensor的维度大小没有变

 

我当时使用的是一个时序序列,每一个时间点有四个特征,那我们就假设是一个20*4的输入,20是对应的时间,4是特征或者叫embedding向量吧

 

此时设置的输入维度为20*4,dim=4就是embedding的大小, num_heads=2

 

它的计算过程看下面的图,其实表达的非常的清楚了,先是输入分别做一个线性变换,得到Q,K,V

 

因为num_heads=2,所以特征维度的4,分开为两个,变为两个,,,也是一样

 

对于每一个,,的计算,那就是,和的每一行都是一个时间点的特征,这样相乘就是算两个向量的相似度嘛,得到一个20*20的矩阵,每一行都代表了这个时间点特征和其他时间点特征的相关性,用softmax使得每一行的和加起来为1,最后再乘上

 

那此时的结果里每一行时间点此时的信息是包含了其他时间点的信息的,是加权和

 

对于,都是这样分别于对应的和计算,最后得到的结果进行reshape就OK了,出来的维度和进来的一样

 

self.scale就是计算的, 除以是因为当比较大的时候,两个向量点积后的值会很大或者很小,相对的差距就会变大,会导致softmax出来的值更靠近1,或者更加靠近0,此时的值就会向两端靠拢

 

加了后就可以使得softmax归一化后的结果更加稳定,在反向传播时获取平衡的梯度

 

对于本次的样例num_heads=2,dim=4, 对于每一个的只有2,那除不除影响不大

 

 

LN

 

这个就是Layer Normalization了,这个Normalization是对一个样本进行的,不是以往对batch里一列特征进行的那种

 

残差连接

 

这里面也是用了残差连接,因为维度都是没有变的,因此可以直接相加,这样训练的时候也是能把网络叠的更深避免出现梯度消失

 

Block

 

OK,最后组合一下,就是一个 block啦

 

这个Block的输入输出维度一致

 

需要注意下print的结构是初始化那边的参数,和实际网络结构可能不同,具体还是以代码实现的结构为准

 

接下来,不管想怎幺魔改网络都可以把这个Block直接塞进去啦

 

class Block(nn.Module):
def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0., drop_path=0.,
                 act_layer=nn.GELU, norm_layer=nn.LayerNorm):
        super().__init__()
        self.dim = dim
        self.num_heads = num_heads
        self.mlp_ratio = mlp_ratio
        self.norm1 = norm_layer(dim)
        self.attn = Self_Attention(dim, num_heads=num_heads, qkv_bias=qkv_bias,
                                    qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop)
        self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
        self.norm2 = norm_layer(dim)
        mlp_hidden_dim = int(dim * mlp_ratio)
        self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
def forward(self, x):
        shortcut = x
        x = self.norm1(x)
        x = self.attn(x)
        x = shortcut + self.drop_path(x)
        x = x + self.drop_path(self.mlp(self.norm2(x)))
return x
Block = Block(dim=4, num_heads=2)
print(Block)
import torch
x = torch.zeros(tuple([8, 20, 4]))
print(x.shape)
y = Block(x)
print(y.shape)

 

Block(
  (norm1): LayerNorm((4,), eps=1e-05, elementwise_affine=True)
  (attn): Self_Attention(
    (qkv): Linear(in_features=4, out_features=12, bias=False)
    (attn_drop): Dropout(p=0.0, inplace=False)
    (proj): Linear(in_features=4, out_features=4, bias=True)
    (proj_drop): Dropout(p=0.0, inplace=False)
    (softmax): Softmax(dim=-1)
  )
  (drop_path): Identity()
  (norm2): LayerNorm((4,), eps=1e-05, elementwise_affine=True)
  (mlp): Mlp(
    (fc1): Linear(in_features=4, out_features=16, bias=True)
    (act): GELU()
    (fc2): Linear(in_features=16, out_features=4, bias=True)
    (drop): Dropout(p=0.0, inplace=False)
  )
)
torch.Size([8, 20, 4])
torch.Size([8, 20, 4])

 

ref

 

https://github.com/aespresso/a_journey_into_math_of_ml/blob/master/03_transformer_tutorial_1st_part/transformer_1.ipynb

 

https://search.bilibili.com/all?keyword=%E6%9D%8E%E6%B2%90&from_source=webtop_search&spm_id_from=333.851

 

Liu, Ze, et al. “Swin transformer: Hierarchical vision transformer using shifted windows.”arXiv preprint arXiv:2103.14030
(2021).

 

本文仅供参考, 若有错误, 欢迎指出, 共同学习

 

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注