【论文+代码】1706.Transformer简易学习笔记

Transformer 论文: 1706.attention is all you need!
唐宇迪解读transformer:transformer2021年前,从NLP活到CV的过程
综述:2110.Transformers in Vision: A Survey
代码讲解1: Transformer 模型详解及代码实现 - 进击的程序猿 - 知乎
代码讲解2:: Transformer代码解读(Pytorch) - 神洛的文章 - 知乎
在这里插入图片描述在这里插入图片描述

一、 结构概述

图1 原论文总框图

输入:词向量(embedding)
位置编码模块:给输入的词的在全局的位置信息
多头注意力层:它是有多个单层注意力层构成
正则化层:
masked-head attention
在这里插入图片描述

图2 attention的计算方式(向量表示方法)

在这里插入图片描述
在这里插入图片描述

其他论文对其结构解读

2110.Transformers in Vision: A Survey

图2 自注意层如何使用在图像特征提取中

在这里插入图片描述

图3:Transformer模型的架构概述

在这里插入图片描述
该模型最初是为语言翻译任务开发的,其中一种语言的输入序列需要转换成另一种语言的输出序列。
Transformer编码器(中间行)对输入语言序列进行操作,并在将其传递给编码器块之前将其转换为嵌入。Transformer解码器(下一行)操作先前用翻译语言生成的输出和中间分支的编码输入序列,以输出输出序列中的下一个单词。先前的输出序列(用作解码器的输入)是通过将输出语句向右移动一个位置并在开始时附加句子开始标记来获得的。这种移位避免了模型学习简单地将解码器输入复制到输出。训练模型的基本事实是输出语言序列(没有任何右移)附加一个句子结束标记。由多头注意(顶行)和前馈层组成的块在编码器和解码器中重复N次。

图6 视觉transformer的架构

在这里插入图片描述

self-attention 代码实现

from: Transformer 模型详解及代码实现 - 进击的程序猿 - 知乎

单层attention的实现

输入序列单词的 Embedding Vector 经过线性变换(Linear 层)得到 Q、K、V 三个向量,并将它们作为 Self-Attention 层的输入。假设输入序列的长度为 seq_len,则 Q、K 和 V 的形状为(seq_len,d_k),其中,dk 表示每个词或向量的维度,也是 矩阵的列数。在论文中,输入给 Self-Attention 层的 Q、K、V 的向量维度是 64,
Embedding Vector 和 Encoder-Decoder 模块输入输出的维度都是 512。
在这里插入图片描述

class ScaleDotProductAttention(nn.Module):
    def __init__(self, ):
        super(ScaleDotProductAttention, self).__init__()
        self.softmax = nn.Softmax(dim = -1)

    def forward(self, Q, K, V, mask=None):
        K_T = K.transpose(-1, -2) # 计算矩阵 K 的转置  
        d_k = Q.size(-1)
        # 1, 计算 Q, K^T 矩阵的点积,再除以 sqrt(d_k) 得到注意力分数矩阵
        scores = torch.matmul(Q, K_T) / math.sqrt(d_k)
        # 2, 如果有掩码,则将注意力分数矩阵中对应掩码位置的值设为负无穷大
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)
        # 3, 对注意力分数矩阵按照最后一个维度进行 softmax 操作,得到注意力权重矩阵,值范围为 [0, 1]
        attn_weights = self.softmax(scores)
        # 4, 将注意力权重矩阵乘以 V,得到最终的输出矩阵
        output = torch.matmul(attn_weights, V)

        return output, attn_weights

# 创建 Q、K、V 三个张量
Q = torch.randn(5, 10, 64)  # (batch_size, sequence_length, d_k)
K = torch.randn(5, 10, 64)  # (batch_size, sequence_length, d_k)
V = torch.randn(5, 10, 64)  # (batch_size, sequence_length, d_k)

# 创建 ScaleDotProductAttention 层
attention = ScaleDotProductAttention()

# 将 Q、K、V 三个张量传递给 ScaleDotProductAttention 层进行计算
output, attn_weights = attention(Q, K, V)

# 打印输出矩阵和注意力权重矩阵的形状
print(f"ScaleDotProductAttention output shape: {
      
      output.shape}") # torch.Size([5, 10, 64])
print(f"attn_weights shape: {
      
      attn_weights.shape}") # torch.Size([5, 10, 10])

唐宇迪的self-attention的计算讲解

构建3个矩阵来查询,当前词和其他词的关系,特征向量表示
在这里插入图片描述

Q K V是由可训练的矩阵计算得到

在这里插入图片描述

向量对应位点乘

在这里插入图片描述

最终的输出结果 scale softmax

在这里插入图片描述

在这里插入图片描述

整体计算流程总结

即 K Q 计算是加权权重值
在这里插入图片描述

多头注意力

相当于卷积里的多层
在这里插入图片描述
多头表达多个特征
在这里插入图片描述
具体的计算
在这里插入图片描述

多头注意力代码

from: Transformer 模型详解及代码实现 - 进击的程序猿 - 知乎

class MultiHeadAttention(nn.Module):
    """Multi-Head Attention Layer
    Args:
        d_model: Dimensions of the input embedding vector, equal to input and output dimensions of each head
        n_head: number of heads, which is also the number of parallel attention layers
    """
    def __init__(self, d_model, n_head):
        super(MultiHeadAttention, self).__init__()
        self.n_head = n_head
        self.attention = ScaleDotProductAttention()
        self.w_q = nn.Linear(d_model, d_model)  # Q 线性变换层
        self.w_k = nn.Linear(d_model, d_model)  # K 线性变换层
        self.w_v = nn.Linear(d_model, d_model)  # V 线性变换层
        self.fc = nn.Linear(d_model, d_model)   # 输出线性变换层

    def forward(self, q, k, v, mask=None):
        # 1. dot product with weight matrices
        q, k, v = self.w_q(q), self.w_k(k), self.w_v(v) # size is [batch_size, seq_len, d_model]
        # 2, split by number of heads(n_head) # size is [batch_size, n_head, seq_len, d_model//n_head]
        q, k, v = self.split(q), self.split(k), self.split(v)
        # 3, compute attention
        sa_output, attn_weights = self.attention(q, k, v, mask)
        # 4, concat attention and linear transformation
        concat_tensor = self.concat(sa_output)
        mha_output = self.fc(concat_tensor)

        return mha_output

    def split(self, tensor):
        """
        split tensor by number of head(n_head)

        :param tensor: [batch_size, seq_len, d_model]
        :return: [batch_size, n_head, seq_len, d_model//n_head], 输出矩阵是四维的,第二个维度是 head 维度

        # 将 Q、K、V 通过 reshape 函数拆分为 n_head 个头
        batch_size, seq_len, _ = q.shape
        q = q.reshape(batch_size, seq_len, n_head, d_model // n_head)
        k = k.reshape(batch_size, seq_len, n_head, d_model // n_head)
        v = v.reshape(batch_size, seq_len, n_head, d_model // n_head)
        """

        batch_size, seq_len, d_model = tensor.size()
        d_tensor = d_model // self.n_head
        split_tensor = tensor.view(batch_size, seq_len, self.n_head, d_tensor).transpose(1, 2)
        # it is similar with group convolution (split by number of heads)

        return split_tensor

    def concat(self, sa_output):
        """ merge multiple heads back together

        Args:
            sa_output: [batch_size, n_head, seq_len, d_tensor]
            return: [batch_size, seq_len, d_model]
        """
        batch_size, n_head, seq_len, d_tensor = sa_output.size()
        d_model = n_head * d_tensor
        concat_tensor = sa_output.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)

        return concat_tensor

位置编码

实际的特征向量=token的词向量+位置编码
在这里插入图片描述

位置编码的代码

from : Transformer代码解读(Pytorch) - 神洛的文章 - 知乎

词嵌入之后紧接着就是位置编码,位置编码用以区分不同词以及同词不同特征之间的关系。代码中需要注意:X_只是初始化的矩阵,并不是输入进来的;完成位置编码之后会加一个dropout。另外,位置编码是最后加上去的,因此输入输出形状不变。

代码

Tensor = torch.Tensor
def positional_encoding(X, num_features, dropout_p=0.1, max_len=512) -> Tensor:
    r'''
        给输入加入位置编码
    参数:
        - num_features: 输入进来的维度
        - dropout_p: dropout的概率,当其为非零时执行dropout
        - max_len: 句子的最大长度,默认512
    
    形状:
        - 输入: [batch_size, seq_length, num_features]
        - 输出: [batch_size, seq_length, num_features]

    例子:
        >>> X = torch.randn((2,4,10))
        >>> X = positional_encoding(X, 10)
        >>> print(X.shape)
        >>> torch.Size([2, 4, 10])
    '''

    dropout = nn.Dropout(dropout_p)
    P = torch.zeros((1,max_len,num_features))
    X_ = torch.arange(max_len,dtype=torch.float32).reshape(-1,1) / torch.pow(
        10000,
        torch.arange(0,num_features,2,dtype=torch.float32) /num_features)
    P[:,:,0::2] = torch.sin(X_)
    P[:,:,1::2] = torch.cos(X_)
    X = X + P[:,:X.shape[1],:].to(X.device)
    return dropout(X)

# 位置编码例子
X = torch.randn((2,4,10))
X = positional_encoding(X, 10)
print(X.shape)

编码器其他细节

在这里插入图片描述

解码器

整体架构汇总
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/imwaters/article/details/132714262