Tranformr架构及代码实现

1、Transformer

seq2seq模型,序列到序列,分为两部分,编码器和解码器

1、通过编码器对序列进行向量化(词向量)

2、把词向量输入到解码器,得到结果(生成单词)

Nx的意思是指连续的堆叠N个encoder,得到一个编码特征

Outputs(Shifted right)指的是将上一个encoder的输入直接拿过来

Self-Attention

## Self-Attention

import torch.nn as nn  
import torch   
  
class Self_Attention(nn.Module):  
	def __init__(self, dim, dk, dv):  
		super(Self_Attention, self).__init__()  
		self.scale = dk ** -0.5  
		self.q = nn.Linear(dim, dk)  
		self.k = nn.Linear(dim, dk)  
		self.v = nn.Linear(dim, dv)  
  
  
	def forward(self, x):  
		q = self.q(x)  
		k = self.k(x)  
		v = self.v(x)  
  
		attn = (q @ k.transpose(-2, -1)) * self.scale  
		attn = attn.softmax(dim=-1)  
		x = attn @ v  
		return x  
		
att = Self_Attention(dim=2, dk=2, dv=3)  
x = torch.rand((1, 4, 2))  
output = att(x)

  • a为嵌入后的特\征,q,k,v是a通过一个linear层映射实现的

  • q代表query,后续会去和每一个k进行匹配

  • k代表key,后续会被每个q匹配

  • v代表从a中提取得到的信息

  • 进行点乘后的数值很大,导致通过softmax后梯度变的很小

Masked(掩码) Self-Attention

对于生成模型来说,以一句话为例,I have a dream,生成的单词是一个一个生成的,所以没法再一开始就对所有的单词计算self-attention,所以需要用到mask

Multi-Head Attention

多头注意力是将q、k、v根据头数进行均分,然后对分头之后的进行self-attention的计算,最后再进行拼接回来,做一次线性变换,也就是乘以W得到最终的结果。代码的实现是直接transpose然后reshape会原来的维度

from math import sqrt
import torch
import torch.nn as nn


class MultiHeadSelfAttention(nn.Module):
    def __init__(self, dim_in, d_model, num_heads=3) -> None:
        super(MultiHeadSelfAttention, self).__init__()

        self.dim_in = dim_in
        self.d_model = d_model
        self.num_heads = num_heads

        assert d_model % num_heads == 0, "d_model must be multiple of num_heads"

        self.linear_q = nn.Linear(dim_in, d_model)
        self.linear_k = nn.Linear(dim_in, d_model)
        # v的层数可以不和q k相同
        self.linear_v = nn.Linear(dim_in, d_model)
        self.scale = 1 / sqrt(d_model // num_heads)

        self.fc = nn.Linear(d_model, d_model)

    def forward(self, x):
        batch, num_token, dim_in = x.shape
        assert dim_in == self.dim_in

        nh = self.num_heads
        dk = self.d_model // nh

        # multi heads attention
        q = self.linear_q(x).reshape(batch, num_token, nh, dk).transpose(1, 2)
        k = self.linear_k(x).reshape(batch, num_token, nh, dk).transpose(1, 2)
        v = self.linear_v(x).reshape(batch, num_token, nh, dk).transpose(1, 2)

        dist = torch.matmul(q, k.transpose(2, 3)) * self.scale
        dist = torch.softmax(dist, dim=-1)

        att = torch.matmul(dist, v)  # shape(batch nh num_token dv) (1,3,4,2)
        # multi heads fusion
        att = att.transpose(1, 2).reshape(batch, num_token, self.d_model) 
        # shape(batch num_token d_model)
        output = self.fc(att)
        return output

x = torch.rand((1, 4, 2))
multi_head_att = MultiHeadSelfAttention(x.shape[2], 6, 3)
output = multi_head_att(x) 

Positional Encoding

Positional Encoding的作用是让transformer引入相对位置的信息,使用如下公式进行位置编码:

pos为第几个词,i为词向量的第几个维度,例如,词向量的维度为512,则2i+1的最大值为512

为什么要使用这样两个三角函数来计算位置编码呢? 主要是利用到了三角函数的和差化积公式:

对于不同位置的编码,可以表示如下:

(3)式可以理解为,通过这样的和差化积公式,可以将两个不同位置的信息进行线性组合,从而使得他们之间有了一定的联系,蕴含了位置上的关联。

以一个例子来看: pos+k = 5; 其实就蕴含了1和4,2和3的情况,0的时候没意义。

2、Vision transformer

ViT模型是将transformer架构直接拿过来应用到视觉上的第一个模型,模型由三个模块组成:

  • Linear Projection of Flattened Patches (将patchs嵌入到和transformer要求一样的输入)

  • Transformer Encoder

  • MLP Head (用来分类的)

Embedding层

对于transformer,要求输入的是token,即二维矩阵[numtoken,tokendim]。在ViT中每个token向量长度为768。

对于ViT,输入图片的大小为(224x224),按照16x16大小的patch进行划分,将得到196个patchs。再通过映射得到长度为768的token。

class PatchEmbed(nn.Module):
    """
    2D Image to Patch Embedding
    """
    def __init__(self, img_size=224, patch_size=16, in_c=3, embed_dim=768, norm_layer=None):
        super().__init__()
        img_size = (img_size, img_size)
        patch_size = (patch_size, patch_size)
        self.img_size = img_size
        self.patch_size = patch_size
        self.grid_size = (img_size[0] // patch_size[0], img_size[1] // patch_size[1])
        self.num_patches = self.grid_size[0] * self.grid_size[1]

        self.proj = nn.Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size)
        self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()

    def forward(self, x):
        B, C, H, W = x.shape
        assert H == self.img_size[0] and W == self.img_size[1], \
            f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."

        # flatten: [B, C, H, W] -> [B, C, HW]
        # transpose: [B, C, HW] -> [B, HW, C]
        x = self.proj(x).flatten(2).transpose(1, 2)
        x = self.norm(x)
        return x

在代码中,是通过卷积直接实现的,将[224,224,3]的图像卷积为[14,14,768]的特征,然后再将前两个维度展平,就得到了[196,768]的二维矩阵,正是transformer想要的输入。

Position Encoding and [cls] token

  • [cls] token的作用是用来进行分类

  • Position Encoding的作用和transformer中一样,让向量蕴含位置信息

  • 整体的输入的维度就是 Cat([1,768],[196,768]) → [197,768]

ViT Encoder

  • DropPath是随机地禁用网络中的一些路径和连接,减少了网络的复杂性。

  • 对于SR任务,用的是经过Encoder之后的特征。

MLP Head

提取出[class] token生成的对应结果就行,即[197, 768]中抽取出[class ]token对应的[1, 768]。接着通过MLP Head得到最终的分类结果。MLP Head原论文中说在训练ImageNet21K时是由Linear+tanh激活函数+Linear组成。

3、SwinIR

使用了Swin transformer作为主体框架的图像超分辨率网络:

  • 22
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值