当前位置:   article > 正文

Tranformer模型详解及源码阅读_transformer源码解析

transformer源码解析

1.概述

Transformer是一种仅使用attention机制、encoder-decoder架构的神经网络,最初应用于NLP领域的机器翻译,后逐渐在语音、CV、时间序列分析等多个领域成为主流深度模型。
在这里插入图片描述

2.模型结构

2.1 整体结构:Encoder-Decoder

  • Input:input Embedding + Position Encoding
  • Encoder:由N层自注意力块叠加而成,每个模块由多头自注意力模块 mutli-head self-attention、残差模块、Layer Normalization以及前馈神经网络组成。
  • Decoder:由N层自注意力块叠加而成,每个模块由掩码多头注意力模块 masked multi-head attention、残差模块、Layer Normalization、交叉注意力 cross attention以及前馈神经网络组成。
    在这里插入图片描述

2.2 位置编码 Position Encoding

Attention机制不像CNN/RNN一样对输入顺序敏感,Attention是顺序不敏感的。为了使Attention能够感受到顺序的变化,Transformer引入了Position Encoding。

思考两个问题

  1. Position Encoding为什么是正余弦函数的形式?如何对位置进行编码?
  2. Position Encoding和Position Embedding的区别?

Position Encoding为什么是正余弦函数的形式?如何对位置进行编码?

最简单最能想到的方式:

  1. 计数。0,1,2,3,4,5…T-1,存在的问题:序列没有上界,如果 T=500 那最后一个位置的编码就会比第一个位置的编码大太多,字嵌入合并以后会出现特征在数值上的倾斜和干扰字嵌入。
  2. 对计数归一化:序列有界了,但是仍有问题:不同序列长度的编码步长是不同的,比如“上海是直辖市”、“上海是中国的直辖市”两句话,“上”和“海”两个字在不同句子里的位置距离分别是1/6和1/9。编码位置信息的核心是相对位置关系,如果使用这种方式,长文本的相对次序关系就会被稀释

因此位置编码需要满足以下条件:

  1. 顺序敏感:需要体现同一单词在不同位置的区别。
  2. 有界:需要有值域的范围限制。
  3. 编码步长相同:需要体现一定的先后次序,并且在一定范围内的编码差异不应该依赖于文本的长度,具有一定的不变性。

满足以上三个条件的函数:有界周期函数,即三角函数。在不同维度使用不同周期的三角函数进行调控,周期最长为10000,可支持长序列输入,多个维度编码类似二进制编码,丰富了位置信息。

Position Encoding和Position Embedding的区别?

Transformers使用的是position encoding,而BERT使用的是position embedding,那么其本质区别是什么?

  • Position Encoding由于是三角函数,PE_t和PE_t+k、PE_t和PE_t-k的位置距离是一样的,因此,Position Encoding不具备方向性,Position Embedding由模型自己训练对应的Embedding表征,具备学习方向性的潜力,表达位置信息更丰富(需要大量数据);
  • Position Encoding是参数固定的,由正余弦函数组成,无需学习;而Position Embedding是需要参数学习的。因此Position Encoding有外推能力,可以输入比训练集所有序列更长的序列(周期函数的无限扩展性),Position Embedding如果输入更长的序列,找不到训练过的Embedding,因此不具备外推能力。

2.3 Transformer中的各种Attention

2.3.1 Scaled dot-product Attention

Attention机制可以描述为查表的过程,将各个时间步的输入映射为Query、Key、Value,在每个时间步计算当前时间步Query和所有时间步Key的相似度,根据相似度softmax之后的结果对所有时间步Value进行加权平均。

计算Attention方式:

  1. Dot-product Attention,求Q和K的内积

  2. Additive Attention:score = MLP(Q|K)

Transformer中Dot-product Attention还需乘以缩放因子1/sqrt(d_k),其目的是为了避免softmax函数落入梯度饱和区。

为什么不乘以缩放因子容易落入梯度饱和区?
Dot-product Attention公式如下,表示为不同q,k对内积的softmax归一化概率乘以对应的value,其中softmax归一化概率为注意力分数。
在这里插入图片描述

假设q,k分别为正态分布的随机变量,当q,k的维度增大时,其内积的方差也会增大,再经过softmax之后最大值概率趋于1,最小值概率趋于0,类似sigmoid。
代码验证:
随机生成10个维度为30的q和k(以下示例,1代表batch_size, 10代表n_steps,30代表hidden_dim),分别计算每个q,k对的注意力分数,发现在不乘以缩放因子时注意力分数非常集中,不利于梯度反向传播,而经过缩放因子后,注意力分布较为平滑,更容易获得有效的梯度。
在这里插入图片描述
在这里插入图片描述

2.3.2 Multi-head Attention

Multi-head Attention将query、key、value映射到多个不同的子空间内,在多个子空间进行attention后,最后拼接起来。

映射参数在这里插入图片描述
需要学习得到。
在这里插入图片描述
在这里插入图片描述

2.3.3 Masked Attention

在 Transformer中,解码器的self-Attention和编码器中的不同,解码器采用了Masked Attention。Masked Attention保证了解码器只能注意到当前位置之前的信息,保证了自回归性。

编码器是对已知输入序列进行编码,因此没有采用masked attention,可以注意到当前位置前后各个位置的信息。
带mask的简单操作就是把qk计算的注意力分数置为负无穷,此后再进行softmax得到的注意力分数区域0,即不再关注到被mask的token。
在这里插入图片描述

2.3.4 Cross Attention

Self-Attention输入是一个单一的嵌入序列,源序列;

Cross-Attention将两个相同维度的独立嵌入序列组合在一起,即源序列和目标序列,目标序列用作查询输入,源序列作为键和值输入。

Transformer解码器从完整的输入序列开始,但解码序列为空。cross attention将信息从输入序列传入解码器,以便它可以预测下一个输出序列标记。然后,解码器将预测值添加到输出序列中,并重复此自回归过程。
在这里插入图片描述

2.4 Position-wise Feed Forward Network

两层全连接层,独立应用在每个位置上,参数在每个位置共享。类似kernel大小为1的一维卷积。

2.5 其他网络结构

残差块:跳跃连接,缓解梯度消失,防止网络退化

Layer Normalization:层归一化,使数据满足独立同分布,提高训练稳定性

3. 多头注意力代码实现

class ScaledDotProductAttention(nn.Module):
    """Scaled dot-product attention"""
 
    def __init__(self, temperature: float, attn_dropout: float = 0.1):
        super().__init__()
        self.temperature = temperature # 缩放因子
        self.dropout = nn.Dropout(attn_dropout)
 
    def forward(
        self,
        q: torch.Tensor,
        k: torch.Tensor,
        v: torch.Tensor,
        attn_mask: torch.Tensor = None,
    ) -> Tuple[torch.Tensor, torch.Tensor]:
        attn = torch.matmul(q / self.temperature, k.transpose(2, 3)) # q:batch*n_head*T_step*F_dim   k.transpose(2,3): batch*n_head*F_dim*T_step  attn: batch*n_head*T_step*T_step
        if attn_mask is not None:
            attn = attn.masked_fill(attn_mask == 1, -1e9)    # mask注意力分数填充为-∞
        attn = self.dropout(F.softmax(attn, dim=-1))
        output = torch.matmul(attn, v)     # 注意力分数加权平均融合value
        return output, attn
       
       
class MultiHeadAttention(nn.Module):
    """original Transformer multi-head attention"""
 
    def __init__(
        self,
        n_head: int,
        d_model: int,
        d_k: int,
        d_v: int,
        attn_dropout: float,
    ):
        super().__init__()
 
        self.n_head = n_head
        self.d_k = d_k
        self.d_v = d_v
         
        # 分别将q、k、v映射到不同的子空间, n_head:注意力头数,这里映射到空间大小是n_head*d_k,维度切分后即得到n_head个映射
        self.w_qs = nn.Linear(d_model, n_head * d_k, bias=False)
        self.w_ks = nn.Linear(d_model, n_head * d_k, bias=False)
        self.w_vs = nn.Linear(d_model, n_head * d_v, bias=False)
 
        # 上面Scaled dot-product,缩放因子为d_k**0.5
        self.attention = ScaledDotProductAttention(d_k**0.5, attn_dropout)
        # concat多头注意力结果并进行线性变换
        self.fc = nn.Linear(n_head * d_v, d_model, bias=False)
 
    def forward(
        self,
        q: torch.Tensor,
        k: torch.Tensor,
        v: torch.Tensor,
        attn_mask: torch.Tensor = None,
    ) -> Tuple[torch.Tensor, torch.Tensor]:
        d_k, d_v, n_head = self.d_k, self.d_v, self.n_head
        sz_b, len_q, len_k, len_v = q.size(0), q.size(1), k.size(1), v.size(1)
 
        # Pass through the pre-attention projection: b x lq x (n*dv)
        # Separate different heads: b x lq x n x dv
        q = self.w_qs(q).view(sz_b, len_q, n_head, d_k)
        k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)
        v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)
 
        # Transpose for attention dot product: b x n x lq x dv
        q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
 
        if attn_mask is not None:
            # this mask is imputation mask, which is not generated from each batch, so needs broadcasting on batch dim
            attn_mask = attn_mask.unsqueeze(0).unsqueeze(
                1
            )  # For batch and head axis broadcasting.
 
        v, attn_weights = self.attention(q, k, v, attn_mask)
 
        # Transpose to move the head dimension back: b x lq x n x dv
        # Combine the last two dimensions to concatenate all the heads together: b x lq x (n*dv)
        v = v.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
        v = self.fc(v)
        return v, attn_weights
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

4. 应用

  1. Transformer编码器:任务P(X_i | X_0, X_1…X_i-1, X_i+1, X_i+2…X_T)。采用上下文还原被mask的token,适合表示学习,如BERT就采用Masked Language Model作为预训练任务之一,充分学习到词的表征。

  2. Transformer解码器:任务:P(X_i | X_0, X_1…X_i-1),采用上文预测下文,自回归任务,适合问答、预测等任务。如GPT采用Auto Regression结构学习Language Model,在问答领域表现优秀。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/852315
推荐阅读
相关标签
  

闽ICP备14008679号