深入理解Transformer结构与实现

引言

在上一篇博客 深入理解Attention机制中我们学习了《Attention Is All Your Need》论文中的前半部分–注意力机制,并通过手动计算来了解Attention机制底层的计算原理。今天这篇文章来深入学习论文的下半部分–Transformer结构

一、Transformer模型概述

  Transformer模型是一种以注意力机制为核心,用于处理序列数据的神经网络架构。它和Attention机制一起在论文《Attention is All You Need》中被提出,并迅速成为自然语言处理(NLP)领域的基石技术。Transformer模型的主要优势在于其能够并行处理序列数据,从而大幅度提高训练效率,同时在多个NLP任务中取得了当时的最先进成果。
  Transformer模型采用了编码器-解码器(Encoder-Decoder)的架构,如下图。这种结构通常用于将输入序列转换成输出序列,例如在机器翻译任务中,将一种语言的文本序列转换为另一种语言的文本序列。
在这里插入图片描述

1.1 编码器-解码器结构

  • 编码器
    编码器由N个相同的编码单元堆叠而成,每一层都包含两个主要的子层:一个多头自注意力机制(Multi-Head Self-Attention)和一个简单的全连接前馈网络(Feed-Forward Network)。这两个子层之间都用到了残差连接,并且每个子层之后都有一个norm层(Layer Normalization)。

  • 解码器
    解码器由N个相同的解码单元堆叠而成,但是每层有三个子层。除了编码器中的两个子层之外,解码器的每一层还插入了一个特殊的多头注意力机制(Masked Multi-Head Attention),这个特殊的结构用来对编码器的输出做注意力计算。在计算注意力的时候会Mask掉当前token之后的输入,以防止位置后面的位置影响当前位置。这里先要纠结为什么要加mask,等后面讲清楚mask的实现原理之后会给出引入mask的原因。

1.2 Transformer中的主要组件

  1. 输入嵌入(Input Embedding): 输入序列首先被转换成固定维度的嵌入向量,这里的embedding是可训的

  2. 位置编码(Positional Encoding): 由于Transformer不像循环神经网络(RNN)那样自然地处理序列的顺序信息,所以需要添加位置编码以保持序列中单词的位置信息,在Transformer中位置编码不是可训的,是根据位置直接计算的

  3. 多头自注意力机制(Multi-Head Self-Attention): 允许模型在处理每个序列元素时,同时考虑序列中的所有其他元素,这是通过注意力权重实现的,其中更重要的元素将获得更高的权重

  4. 前馈网络(Feed-Forward Network): attention模块后接着是一个前馈网络,该网络对每个位置应用相同的全连接层

  5. 残差连接(Residual Connection)和归一化(Normalization): 在每个子层的输出上,都会进行残差连接,然后在做蹭归一化(Layer-Norm)

  6. 解码器的遮掩注意力: 防止解码器在生成输出序列时提前“看到”正确答案(后面结合mask原理解释

  7. 线性层和Softmax: 解码器的最后输出通过一个线性层和Softmax层,将解码器输出转换为预测的下一个词的概率分布。

以上是Transformer模型各组件介绍,在模型结构中这些组件按照特定顺序相互结合处理并生成序列数据。接下来,接下来我们深入探讨每个组件的细节和实现方式。

二、Transformer详细结构

2.1 输入表示

  在Transformer模型中,文本输入需要转换成embedding向量,这样才是模型能够处理的形式。这涉及到两个关键的组件:词嵌入和位置编码。

2.1.1 词嵌入

  词嵌入是将自然语言中的词汇转换为固定长度的向量表示。每个单词或标记被映射到一个高维空间,其中语义相似的单词的向量表示高维空间中的几何度量更接近。

  在Transformer中,输入句子首先被分割成词汇或子词单元(subword units),然后每个单元被转换成一个高维空间的密集向量。这种表示方法捕捉了单词的语义信息,并且为模型提供了一个可微分的方式来学习单词之间的关系。

  通常情况下,词嵌入通过一个可训练的矩阵实现,这个矩阵称为词嵌入矩阵。给定一个词汇表大小为V,和一个嵌入维度为D,则词嵌入矩阵的大小为V x D。对于词汇表中的每个词,我们查找对应的向量表示作为模型的输入。

# 例子:词嵌入矩阵的查找过程
emb_matrix = ... # 假设这是一个 V x D 的矩阵
word_index = ... # 单词在词汇表中的索引
word_embedding = emb_matrix[word_index] # 获取词向量

  在Transformer中,这个embedding矩阵是可训的,随着模型的训练不断的优化。下面是torch框架下,embedding的一种实现,直接使用了torch.nn.Embedding这个底层实现。

import torch.nn as nn

class TokenEmbedding(nn.Module):
    def __init__(self, vocab_size: int, emb_size):
        super(TokenEmbedding, self).__init__()
        self.embedding = nn.Embedding(vocab_size, emb_size)
        self.emb_size = emb_size

    """
        :param tokens: shape : [len, batch_size]
        :return: shape: [len, batch_size, emb_size]
        """

    def forward(self, tokens):
        return self.embedding(tokens.long()) * math.sqrt(self.emb_size)
2.1.2 位置编码

  由于Transformer模型的自注意力机制不具有处理序列顺序的能力,因此位置编码被引入以给模型提供关于单词在序列中位置的信息。

  位置编码是一个与词嵌入具有相同维度D的向量,它按照一定的规则生成,以确保每个位置的编码是唯一的。Transformer的原始论文中提出了一种基于正弦和余弦函数的位置编码方法。

位置编码的计算公式如下:

# 位置编码的计算公式
PE(pos, 2i) = sin(pos / 10000^(2i/D))
PE(pos, 2i+1) = cos(pos / 10000^(2i/D))

其中pos是位置索引,i是维度索引,而D是嵌入维度。通过这种方式,不同位置的编码在多维空间中会有独特的位置,从而允许模型学习到序列中单词的顺序关系。

位置编码通常与词嵌入向量直接相加,以形成最终的输入表示。

# 输入表示的组合
final_input_representation = word_embedding + position_encoding

位置编码可以是静态的(如原始论文中所提出的),也可以是通过模型学习得到的动态编码。无论采用哪种方式,位置编码都是Transformer模型捕捉序列位置信息的关键。

综上所述,每个单词或标记的最终输入表示是词嵌入和位置编码的组合。这种表示不仅包含了单词本身的语义信息,也包含了它在句子中的位置信息,为自注意力机制的运作提供了必要的基础。

为什么要设计这么复杂的位置编码,直接按照把pos加上去不可以吗?
回答:
不可以,首先我们为什么要引入位置编码,是为了让模型可以区分不同位置的词语。如果直接把位置index加上去,由于index是1~n的蒸熟,而我们的文本embedding一般是0~1之间的数,所以如果这样直接把index加上去会让位置信息直接遮蔽语义信息。


通过上面的回答可以看出,我们需要的位置编码不是仅仅是位置信息加上去就行,而是有一些约束条件的。

  • 位置编码需要体现一个单词在不同位置的差异
  • 位置编码需要有顺序关系,且位置编码不依赖于序列长度
  • 位置编码需要被体现在一定的区间内。
  • 词语之间的间隔对于不同长度的句子来说,含义应该是一致的
  • 能够随意延伸到任意长度的句子

关于位置编码选择的详细解释可以参考:一文教你彻底理解Transformer中Positional Encoding

2.2 编码器

  Transformer的编码器部分由N个相同的编码层堆叠而成,每一层包含两个主要的子模块:自注意力层和前馈全连接层,它们之间通过残差连接和层归一化进行连接。下面分别介绍这些组件:

2.2.1 自注意力层

  自注意力(Self-Attention)已经在上一章中做了详细介绍,它可以关联输入序列中的不同位置以计算序列的表示。在自注意力层中,一个序列中的所有单词对于计算该序列中任何位置的单词的注意力分数是可见的。

  • 注意力分数计算
    自注意力层的关键是计算注意力分数,这是通过以下步骤完成的:

    1. 将输入的序列转换成三个不同的向量集合,即查询(Query, Q)、键(Key, K)和值(Value, V),通常这是通过训练得到的线性转换实现的。
    2. 为了获得位置i的注意力分数,计算查询向量Q[i]与所有键向量K的点积。
    3. 点积后,通常会除以一个缩放因子(通常是键向量维度的平方根),以避免过大的点积值导致梯度消失或爆炸。
    4. 应用softmax函数,将其转换为概率分布,即得到注意力权重。
    

    具体的计算公式如下:
    Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V Attention(Q,K,V)=softmax(dk QKT)V
    其中, d k d_k dk 是键向量的维度。

  • 多头注意力机制
    为了使模型能够同时关注来自不同位置的不同信息,Transformer采用了多头注意力机制。在这种机制下,每个头会独立学习到attention的不同方面。具体步骤如下:

    1. 将Q、K和V向量分别线性投射h次(h即为头的数量)。
    2. 每次投射后,执行上面的注意力分数计算。
    3. 将所有头的输出向量拼接起来。
    4. 将拼接后的向量再次进行线性投射,以产生最终的输出。
    

    多头注意力允许模型在不同的表示子空间上学习信息,从而可以提高模型的表示能力。

  • 注意力权重与值的乘积
    得到注意力权重后,将它们与值V相乘,得到加权的值向量,这些向量再相加就构成了该位置的输出。

2.2.2 前馈全连接层

  每个编码器层中的自注意力层后面跟随着一个前馈全连接层,这个层包含两个线性变换,并且它们之间有一个ReLU激活函数:
FFN ( x ) = ReLU ( x W 1 + b 1 ) W 2 + b 2 \text{FFN}(x) = \text{ReLU}(xW_1 + b_1)W_2 + b_2 FFN(x)=ReLU(xW1+b1)W2+b2

2.2.3 残差连接与层归一化

  为了促进深度网络的训练,每个子层(自注意力层和前馈全连接层)都采用残差连接,即将子层的输入以旁路加到其输出上:
Output = Sublayer ( x ) + x \text{Output} = \text{Sublayer}(x) + x Output=Sublayer(x)+x
  残差连接后通常接一个层归一化(Layer-Norm)操作,层归一化是对每一个样本的所有特征进行归一化,使得输出的均值接近0,方差接近1,这有助于稳定训练过程。

2.2.4 编码器的代码实现
class MyTransformerEncoderLayer(nn.Module):
    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1):
        super(MyTransformerEncoderLayer, self).__init__()
        """
        :param d_model:         d_k = d_v = d_model/nhead = 64, 模型中向量的维度,论文默认值为 512
        :param nhead:           多头注意力机制中多头的数量,论文默认为值 8
        :param dim_feedforward: 全连接中向量的维度,论文默认值为 2048
        :param dropout:         丢弃率,论文中的默认值为 0.1    
        
        
        """
        ####这里使用多头注意力机制
        self.self_attn = MyMultiheadAttention(d_model, nhead, dropout=dropout)

        # attention后的残差连接与layerNorm
        self.dropout1 = nn.Dropout(dropout)
        self.norm1 = nn.LayerNorm(d_model)

		#####两层前馈神经网络MLP,激活函数是ReLU
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)
        self.activation = F.relu

		#####MLP后接的残差连接与layerNorm
        self.dropout2 = nn.Dropout(dropout)
        self.norm2 = nn.LayerNorm(d_model)

    def forward(self, src, src_mask=None, src_key_padding_mask=None):
        """
        :param src: 编码部分的输入,形状为 [src_len,batch_size, embed_dim]
        :param src_mask:  编码部分输入的padding情况,形状为 [batch_size, src_len]
        :return:
        """
        #####计算多头注意力
        src2 = self.self_attn(src, src, src, attn_mask=src_mask,
                              key_padding_mask=src_key_padding_mask, )[0]

		#####残差连接与Norm
        src = src + self.dropout1(src2)
        src = self.norm1(src)

		#####前馈全连接MLP
        src2 = self.activation(self.linear1(src))
        src2 = self.linear2(self.dropout(src2))

		#####残差连接与Norm
        src = src + self.dropout2(src2)
        src = self.norm2(src)
        return src

2.3 解码器

  Transformer模型中的解码器会根据编码器的输出以及之前已生成的输出序列来生成下一个输出。解码器的架构与编码器类似,但它包含一个额外的子层来进行编码器-解码器注意力操作。同时解码器和编码器一样,解码器通常由多个相同的解码层堆叠而成。

2.3.1 Masked self-Attention

  这个子层是解码器中第一个子层,它类似于编码器中的自注意力机制,但有一个关键的区别:为了防止位置 i i i 的解码器在预测时可以看到待预测的token,造成信息泄露,即在预测位置 i i i 的输出时不应该使用位置 i i i 之后的输出信息,因此需要在自注意力计算中引入一个掩码(mask),用来遮掩当前时刻之后的token信息。

  • 输入:解码器的输入是一个序列,它可能是目标序列的一部分(训练时),也可能是已经生成的序列(推理时)

  • 掩码机制:为了实现这种掩蔽,可以使用一个上三角矩阵,其中上三角部分(包括对角线)的值为负无穷或一个非常大的负数,而下三角部分的值为0。这个矩阵被加到自注意力机制中的logits上,经过softmax之前,这样在softmax步骤中,被掩蔽的位置会接近于0,不会对最终的注意力分数产生影响。

  • 自注意力计算:解码器的自注意力层使用掩码的方式确保位置 i i i 只能关注到位置 i i i 以前的位置。
    具体来说,就是在正常的注意力权重上加一个mask矩阵,把当前位置之后的信息mask掉。公式如下:
    Attention ( Q , K , V ) = softmax ( Q K T d k + M a s k ) V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} + Mask\right)V Attention(Q,K,V)=softmax(dk QKT+Mask)V

    M a s k Mask Mask是一个上对角阵,上三角部分全部用 − i n f -inf inf填充,这样就把当前token对自身之后token的注意力权重置零,也就做到了上面说的屏蔽效果。 M a s k Mask Mask矩阵一般如下所示:

    在这里插入图片描述

2.3.2 编码器-解码器注意力

  编码器-解码器注意力层允许解码器的每个位置都能关注编码器的整个输出序列,编码器的输出连接到解码器每个解码层的encoder-decoder-Attention模块上,作为该模块的K、V。前面的Masked self-Attention的输出作为该Attention模块的Q。

- 功能:这个子层通过注意编码器的输出来帮助解码器集中于输入序列的相关部分,从而生成正确的下一个输出。
- 输入:编码器-解码器注意力层接收编码器的输出作为其键(K)和值(V),同时接收解码器的掩码自注意力子层的输出作为其查询(Q)。
- 注意力计算:该层对编码器的输出和解码器的当前状态进行交互,计算得到的注意力分数表明了在生成下一个输出时,解码器应该给予编码器输出序列中每个位置多少关注。
2.3.3 前馈全连接层

解码器中的前馈全连接层与编码器中的完全相同,它由两个线性变换组成,中间通过一个ReLU激活函数。

- 结构:前馈网络由两个线性变换组成,公式可以表示为:$FFN(x) = \max(0, xW_1 + b_1)W_2 + b_2$。
- 激活函数:这里使用的激活函数是ReLU(Rectified Linear Unit),它对输入进行非线性变换。
- 目的:前馈全连接层可以增加模型的表达能力,不仅仅限于注意力机制处理的序列关系。
2.3.4 残差连接与层归一化

为了便于优化和能够训练更深的模型,残差连接和层归一化在每个子层的输出上都被使用。

- 残差连接:每个子层(自注意力层、编码器-解码器注意力层、前馈全连接层)的输出都会加上该子层的输入,也就是 $LayerNorm(x + Sublayer(x))$,其中 $Sublayer(x)$ 是子层自己的操作。
- 层归一化:残差连接后通常接一个层归一化(Layer Normalization),它是对每个样本的所有特征进行归一化,以稳定训练过程。
2.3.5 解码器的代码实现

最终的解码器是由N个这样的层堆叠而成。

- 层数:在原始的Transformer模型中,通常使用6个解码器层。
- 结构:每个层都包含了上述的掩码自注意力、编码器-解码器注意力和前馈全连接层,每个子层周围都有残差连接,然后是层归一化。
- 输出:解码器的顶层输出将被用于最终的输出序列的生成,通常是通过一个线性层和一个softmax层进行预测。

代码实现如下:

class MyTransformerDecoderLayer(nn.Module):
    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1):
        super(MyTransformerDecoderLayer, self).__init__()
        """
        :param d_model:         d_k = d_v = d_model/nhead = 64, 模型中向量的维度,论文默认值为 512
        :param nhead:           多头注意力机制中多头的数量,论文默认为值 8
        :param dim_feedforward: 全连接中向量的维度,论文默认值为 2048
        :param dropout:         丢弃率,论文中的默认值为 0.1    
        """
        # 解码部分输入序列之间的多头注意力(也就是论文结构图中的Masked Multi-head attention)
        self.masked_attn = MyMultiheadAttention(embed_dim=d_model, num_heads=nhead, dropout=dropout)
        
        # 编码器输出(memory)和解码器mask-attention之间的多头注意力机制。
        self.encoder_decoder_attn = MyMultiheadAttention(embed_dim=d_model, num_heads=nhead, dropout=dropout)
        
        # 前馈全连接MLP
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)

		# 层归一化和dropout初始化
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.dropout3 = nn.Dropout(dropout)

		# 激活函数
        self.activation = F.relu

    def forward(self, tgt, memory, tgt_mask=None, memory_mask=None, tgt_key_padding_mask=None,
                memory_key_padding_mask=None):
        """
        :param tgt:  解码部分的输入,形状为 [tgt_len,batch_size, embed_dim]
        :param memory: 编码部分的输出(memory), [src_len,batch_size,embed_dim]
        :param tgt_mask: 注意力Mask输入,用于掩盖当前position之后的信息, [tgt_len, tgt_len]
        :param memory_mask: 编码器-解码器交互时的注意力掩码,一般为None
        :param tgt_key_padding_mask: 解码部分输入的padding情况,形状为 [batch_size, tgt_len]
        :param memory_key_padding_mask: 编码部分输入的padding情况,形状为 [batch_size, src_len]
        :return:
        """
        # 解码部分输入序列之间'的多头注意力(也就是论文结构图中的Masked Multi-head attention)
        tgt2 = self.self_attn(tgt, tgt, tgt,  # [tgt_len,batch_size, embed_dim]
                              attn_mask=tgt_mask,
                              key_padding_mask=tgt_key_padding_mask)[0]
		
		# 接着是残差连接与归一化
        tgt = tgt + self.dropout1(tgt2)  
        tgt = self.norm1(tgt)

		# 解码部分的输入经过多头注意力后同编码部分的输出(memory)通过多头注意力机制进行交互
        tgt2 = self.multihead_attn(tgt, memory, memory,
                                   attn_mask=memory_mask,
                                   key_padding_mask=memory_key_padding_mask)[0]

        # 接着是残差连接与归一化
        tgt = tgt + self.dropout2(tgt2)
        tgt = self.norm2(tgt)

		# 最后的两层全连接
        tgt2 = self.activation(self.linear1(tgt))
        tgt2 = self.linear2(self.dropout(tgt2))
        
        # 输出层
        tgt = tgt + self.dropout3(tgt2)
        tgt = self.norm3(tgt)
        return tgt

接下来,我们回答一个问题为什么需要mask机制?以下是我的理解:

解码器中的Mask机制核心是为了做训练和预测之间的aligment。如果没有mask机制,那么在训练的过程中Decoder在预测当前Token时可以看到(感知)当前位置后面的信息(文本),这样训练时可以快速降低loss,提高训练时模型的表现。但是在预测时,由于当前Token之后的信息尚未生成,所以模型无法获得后面部分信息,这样会造成模型预测时性能的骤降。因此增加mask是为了对其模型训练与预测时的性能,并且这样也符合真实的应用情况,预测当前Token就是因为不知道当前Token是什么,如果已经知道也就没必要预测了。

三、Transformer的训练

3.1 损失函数

  在自然语言处理的任务中,尤其是分类任务(如语言模型预测下一个单词),交叉熵损失(Cross-Entropy Loss)是最常用的损失函数之一。该损失函数计算模型预测的概率分布与真实标签的概率分布之间的差异。

交叉熵损失的数学表示如下:
L = − ∑ i = 1 C y i log ⁡ ( p i ) L = -\sum_{i=1}^{C} y_i \log(p_i) L=i=1Cyilog(pi)

其中, C C C是类别的数量, y i y_i yi是one-hot的标签向量, p i p_i pi是模型预测的概率分布。

在Transformer中,交叉熵损失用于计算每个时间步输出的概率分布与实际单词的分布之间的差异。

3.2 优化器

  Transformer一般使用的是Adam优化器,这是一种广泛应用于深度学习模型的优化算法,它结合了动量(Momentum)和RMSprop的概念。Adam优化器不仅考虑了过去梯度的指数衰减平均,而且还考虑了过去梯度的平方的指数衰减平均。

Adam优化器的参数更新规则如下:

  • m t = β 1 ⋅ m t − 1 + ( 1 − β 1 ) ⋅ g t m_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot g_t mt=β1mt1+(1β1)gt
  • v t = β 2 ⋅ v t − 1 + ( 1 − β 2 ) ⋅ g t 2 v_t =\beta_2 \cdot v_{t-1} + (1 - \beta_2) \cdot g_t^2 vt=β2vt1+(1β2)gt2
  • m ^ t = m t / ( 1 − β 1 t ) \hat{m}_t = m_t / (1 - \beta_1^t) m^t=mt/(1β1t)
  • v ^ t = v t / ( 1 − β 2 t ) \hat{v}_t = v_t / (1 - \beta_2^t) v^t=vt/(1β2t)
  • θ t + 1 = θ t − η ⋅ m ^ t / ( v ^ t + ϵ ) \theta_{t+1} =\theta_t - \eta \cdot \hat{m}_t / (\sqrt{\hat{v}_t} + \epsilon) θt+1=θtηm^t/(v^t +ϵ)

其中, m t 和 v t m_t和v_t mtvt分别是梯度的一阶矩估计和二阶矩估计, g t g_t gt是当前梯度, β 1 和 β 2 β1和β2 β1β2是衰减率参数,通常取值为0.9和0.999, θ θ θ是模型参数, η η η是学习率, ε ε ε是一个避免除零的小常数。

四、Transformer的变种与扩展

Transformer 架构自2017年提出以来,已经催生了一系列强大且多样化的变种。以下是一些著名的Transformer变体,它们在自然语言处理领域产生了巨大影响。

BERT (Bidirectional Encoder Representations from Transformers)

  BERT是由Google在2018年提出的一个用于自然语言处理预训练的模型。BERT的主要创新在于其双向训练的特点,它使用了“Masked Language Model”(MLM)的概念,即在输入序列中随机遮蔽一些单词,然后让模型预测这些遮蔽的单词。这允许模型从两个方向学习上下文信息,从而更好地理解语言。BERT还使用了“Next Sentence Prediction”(NSP)任务来改善对句子关系的理解。BERT采用的是Encoder-only架构,舍弃掉了Transformer架构中的decoder部分。

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

GPT (Generative Pre-trained Transformer)

  GPT是OpenAI在2018年提出的一个自回归语言模型。与BERT不同的是,GPT采用的Decoder-only架构,即它只从左到右预测下一个单词。GPT的训练分为两个阶段:无监督的预训练阶段和有监督的微调阶段。在预训练阶段,模型在大量文本上学习语言模型,然后在微调阶段,模型在特定任务的数据集上进行微调以进行特定任务。GPT通过这种方式展示了将预训练模型转移到多种任务的强大能力。2022.11月chatGPT横空出世让decode-only架构大放异彩,目前decoder-only在大语言模型领域大杀四方,关于这一块后面我们专门展开讲。

论文地址:https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf

T5 (Text-to-Text Transfer Transformer)

  T5由Google于2019年提出,其核心理念是将所有文本相关的任务视为一个“文本到文本”的问题,即将任何输入转换为文本输出。例如,翻译任务被视为将一种语言的文本转换为另一种语言的文本;而分类任务可以被视为将输入文本转换为类别名称的文本。T5在一个统一的架构下处理各种不同类型的NLP任务,并在预训练阶段引入了一种名为“C4”(Colossal Clean Crawled Corpus)的新型大型数据集。

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

RoBERTa (A Robustly Optimized BERT Pretraining Approach)

  RoBERTa是Facebook在2019年提出的,对BERT的训练过程进行了改进。通过在更大的数据集上训练,去除了BERT中的NSP任务,使用更长的训练时间、更大的批量尺寸和更细致的超参数调整,RoBERTa在多个NLP基准测试中取得了更好的性能。

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

Transformer-XL (Extra Long)

  Transformer-XL是在2019年提出的,用于解决传统Transformer在处理长序列时性能下降的问题。它通过引入一个可以学习跨越固定长度序列的依赖关系的机制,即“循环机制”,来捕捉长距离上下文信息。这使得Transformer-XL在长文本处理任务上表现更好,同时保持了较快的训练速度。

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

ELECTRA (Efficiently Learning an Encoder that Classifies Token Replacements Accurately)

  ELECTRA是Google在2020年提出的,它是一个更高效的预训练方法。与BERT不同,ELECTRA使用了“Replaced Token Detection”而不是MLM。在这个任务中,模型需要判断一个token是否被一个同分布的生成器模型替换过。这种方法允许模型在每次前向传播中学习所有的token,从而提高训练效率。

  这些变种和扩展表明,Transformer架构具有极大的灵活性和扩展性,能够适应不同的应用场景和需求,是当今自然语言处理领域的核心技术之一。

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

五、实现Transformer的技术挑战

  在实现Transformer模型时,开发者可能会遇到许多技术上的挑战。Transformer模型自2017年提出以来,因其在处理序列数据方面的高效性和效果而变得非常流行。然而,实现这一架构需要解决以下几个主要的技术问题:

1. 理解自注意力机制

  自注意力机制(Self-Attention)是Transformer的核心部分,理解其细节对于实现模型至关重要。自注意力允许输入的每个位置都能够注意到其他所有位置,从而捕捉到全局依赖关系。然而,这个机制包含了一些相对复杂的操作,如点积、缩放、掩码和Softmax,随着模型增大,模型计算量指数增加。关于Attention计算效率的优化后面我们会有专门的文章来进行讲解。

2. 处理长序列的挑战

  Transformer模型在处理长序列时面临着显著的挑战,尤其是因为自注意力机制的计算复杂度和内存需求随着序列长度的增加而二次方增长。为了有效地处理长序列,可能需要采用例如稀疏注意力机制、分块注意力或者自注意力的近似方法,这些技术本身就是一个研究领域。

六、总结

  可以说Attention和Transformer架构开启了NLP领域新的时代,自然语言处理任务开始从百花齐放走向了大一统,此后几年基于Attention机制和Transformer架构的创新进入了一个大爆发,其中最为出彩的是BERT为代表的Encoder-only架构和GPT为代表的Decoder-only架构。其中BERT引领了预训练+领域微调的范式,而GPT则以一己之力开启了大语言模型的时代,让AGI开起来触手可及。以上这两种架构我们都会做详细的拆解。

参考文献

GPT4 本文有些大纲和一些概念性的内容是有GPT4模型生成,再由我修改
https://arxiv.org/pdf/1706.03762.pdf

这个大纲提供了一个全面的视角来解析Transformer模型,从基础知识到模型的详细实现,再到实际的训练和挑战,最后是对模型未来的展望。每个部分都可以根据需要进一步扩展,以提供更多的细节和示例。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值