0.1 流程
使用Transformer来进行文本生成其实就是用模型来预测下一个词,完整流程包括多个阶段,如分词、向量化、计算注意力和采样,具体运作流程如下:
- 分词(tokenize)。把用户的输入文本(此处假设是“Data visualization empowers users to”)拆解为若干独立的词汇单元,即token。
- 编码。借助词表把token映射为数字,每个token由一个唯一的数字表示。
- embedding(嵌入)化。embedding模块将token代表的数字转换为embedding向量,即将词映射到一个向量空间,这样LLM才能处理。此时还会加上位置编码信息,因为理解语言不仅关乎单词,还关乎单词的顺序。位置编码可以确保单词的顺序不会丢失。所有嵌入向量组合在一起形成嵌入矩阵。
- 注意力计算。这是语境化操作,若干堆叠的Transformer Block通过注意力机制将这些Embedding向量转换成若干特征向量,构建词和词之间的关系。在注意力计算过程中,每个token可以了解自己与其它token的相关性。最终每个token流经Transformer最后一层之后得到的是一个代表语义的特征向量。
- 计算概率。将最后一个token(”to“)对应的特征向量映射为下一个待预测词的概率分布(logits)。具体操作是通过一个线性层把特征向量升维到词表维度(即把解码器的输出转化为与词典大小相同的向量),并且通过softmax进行归一化,最终输出一个概率分布。该分布表示对词表中每个词匹配这个特征向量的概率。
- 采样。依据这些概率,按照一定的采样规则来采样下一个token,比如选取概率最高的”visualize“作为最有可能出现的下一个单词。
- 再次使用分词表将”visualize“对应的整数转换回原始的词汇,形成推理结果句子。
- 不断重复上述过程。直到LLM输出结束流(EOS)标记表示解码结束或者已经生成所需数量的token。
下图将上述流程的核心部分作了可视化,也是本篇讲解的基础,后续将对模型结构和执行流程进行逐步细化。
0.2 说明
本系列主要以下面几项为基础:
- Transformer论文:Attention Is All You Need https://arxiv.org/abs/1706.03762v7。
- 其它相关经典论文和精彩博客,参考将在各个篇幅的具体部分中给出。
- “The Annotated Transformer” 博客以及其源码(后续简称为哈佛源码)。“The Annotated Transformer” 是Transformer论文的读书笔记,而且博客作者用代码实现了论文的模型,并且结合实现的模型对原始论文做了详细解读。与互联网上可以获取的其他Transformer的模型实现相比较,“The Annotated Transformer” 更适合学习和解读。其地址为:
- 2025年-2月4日拉取其代码。
另外,本篇以文本翻译功能为例来进行说明。
0x01 总体架构
1.1 设计动机
Transformer的新颖之处在于它是一个完全基于注意力机制实现的序列转换架构,我们对Transformer的主要设计动机分析如下:
- 解决长距离依赖关系。论文希望解决RNN在序列长距离上的限制,而注意力机制可以将序列中的任意两个位置之间的距离是缩小为一个常量,从而在长文本分析时可以捕获更多的语义关联关系。
- 提升训练并行度。论文希望克服RNN不能并行的缺点,而注意力机制可以无视序列的先后顺序来捕捉序列间的关系,因此具有更好的并行性,符合现有的GPU框架,能够进行分布式训练,提升模型训练效率。
因此,Jakob Uszkoreit(Transformer作者之一)提出了用自注意力机制来替换RNN对序列的编解码过程。而Noam Shazeer(Transformer作者之一)在此基础上提出了scaled dot-product attention、多头注意力和位置表示。
1.2 模型结构
首先我们来看看原始论文里面的架构图,接下来就以它为源头进行分析。
主体模块
从网络结构来分析,Transformer 包括了四个主体模块。
https://m.56.com/view/id-MTk0NTI4NzIz.html
https://m.56.com/view/id-MTk0NTI4NzEx.html
https://www.56.com/u70/v_MTk0NTI4NzIz.html
https://www.56.com/u58/v_MTk0NTI4NzEx.html
https://i.56.com/u/shunm_56125228199/videos/
https://i.56.com/u/shunm_56125228199/
- 输入模块,对应下图的绿色圈。
- 编码器(Encoder),对应下图的蓝色圈。
- 解码器(Decoder),对应下图的红色圈。编码器和解码器都有自己的输入和输出,编码器的输出会作为解码器输入的一部分(位于解码器的中间的橙色圈)。
- 输出模块,对应下图的紫色圈。
确切的说,蓝色圈是编码器层(Encoder layer),红色圈是解码器层(Decoder layer)。图中的 N×�× 代表把若干具有相同结构的层堆叠起来,这种将同一结构重复多次的分层机制就是栈。为了避免混淆,我们后续把单个层称为编码器层或解码器层,把堆叠的结果称为编码器或解码器。在Transformer论文中,Transformer使用了6层堆叠来进行学习。
多层
在Transformer中,第一层的输入是嵌入矩阵。第一层的输出随后被用作第二层的输入,依此类推。每一层都生成了一组嵌入,但这些嵌入不再直接与单个词元相关,而是与某种更复杂的词元关系的理解相关联。比如下图给出了一个模型中的第6层和第7层之间的关系,该模型每层有12个注意头。
https://m.56.com/view/id-MTk0NDI5NTcz.html
https://m.56.com/view/id-MTk0NDQyNzU0.html
https://m.56.com/view/id-MTk0NDQ0MDE5.html
https://www.56.com/u22/v_MTk0NDQ0MDE5.html
https://www.56.com/u77/v_MTk0NDQyNzU0.html
https://www.56.com/u96/v_MTk0NDI5NTcz.html
https://i.56.com/u/shunm_56125228199/videos/
https://i.56.com/u/shunm_56125228199/
对于多层的作用,目前也有不同的解释。比较常见的解释是分层的本质是由下往上从不同上下文中逐步构建不同层次的特征。比如底层学习单词特征,中间层学习句法特征,高层学习语义特征等,每一层做各自的事情,不会相互影响。
输入文本对应的embedding在Transformer内部各层流通时会不断演变,这个过程类似于逐层“精炼”和“抽象”输入的信息。每一层都会对输入进行不同级别的变换和抽象,都会在其输入的基础之上吸收更多上下文信息来丰富自己的表示,逐层提取出更高层次的特征,从而在综合多层之后就会获得更加强大的表达能力。随着深度学习模型进行训练,这些网络层会逐渐学习到各种范畴之间的关系和相似性,从而在推理和回答问题时能够利用这些知识。当embedding到达最后一层时,其不仅仅代表对应token独立的含义,而是具备深刻的语境信息,反应了该token与序列中其它token的综合关系。我们可以把多层加工理解为工厂的流水线,假定要生产一件瓷器,我们要先通过印坯和修坯来确定器物形状,然后通过刻花来在已经干了的坯体上刻画出各种精美的花纹或者图案。接下来进行施釉,在成型的陶瓷坯体表面施以釉浆;最后将瓷坯装入匣钵,高温入窑烧造。最终才能得到一件精美的瓷器。
针对分层中的每一层可能都会起到不同的作用这点,研究人员做了深入的研究。
论文“What Does BERT Learn about the Structure of Language?”剖析了 BERT 所理解的英语结构的复杂性。他们的研究发现,BERT 的短语表示主要在神经网络的较低层捕捉短语级别的信息,并在中间层中编码了语言要素的复杂层次结构。这个层次结构以表层特征作为基础,中间层提取语法特征,最上层呈现语义特征。
论文"Analyzing Memorization in Large Language Models through the Lens of Model Attribution"指出:
https://www.lofter.com/shareblog/mmmf5dfg/
https://m.56.com/view/id-MTkyODM5ODY2.html
https://www.56.com/u21/v_MTkyODM5ODY2.html
https://m.56.com/view/id-MTkzMDM5NzE4.html
https://www.56.com/u25/v_MTkzMDM5NzE4.html
https://i.56.com/u/shunm_56124919816/
https://i.56.com/u/shunm_56124948477/
- 较深层的注意力模块(最后25%的层)主要负责记忆。
- 较浅层的注意力模块对模型的泛化和推理能力至关重要。
- 在深层注意力模块应用短路(short-circuit)干预可以显著降低记忆所需内存,同时保持模型性能。
论文”Interpreting Key Mechanisms of Factual Recall in Transformer-Based Language Models"则发现。语言模型存在一种普遍机制:防止过度自信(anti-overconfidence):在模型的最后若干层,语言模型总是在抑制正确答案的输出。这种抑制具体又分为两种:
- 通过注意力头将输入起始位置的信息复制到了最末位置,我们发现起始位置的信息似乎包含了很多高频的token,模型可以通过这种方法来让高频token稀释残差流中的正确回答,降低回答的自信度。
- 末层的MLP似乎在将残差流引导向一个“平均”token的方向(平均token是基于训练数据的词频,对token embedding加权平均得到的结果)。
另外,模型的效果往往和模型的参数量成正比,Transformer就是通过增加模型的层数来加大模型可学习的参数量,让更多的参数来承载文本中深层次的信息。
1.3 注意力模块
注意力机制是Transformer模型的心脏,它赋予模型洞察句子中每个单词与其它单词间错综复杂关系的超能力。
分类
在Transformer中有三种注意力结构:全局自注意力,掩码自注意力和交叉注意力,具体如下图所示。交叉注意力主要用于处理两个不同序列之间的关系;全局自注意力主要用于处理单个序列内元素之间的关系;掩码自注意力(也被称做因果自注意力)通过掩码来控制模型在计算注意力分数时的关注范围,从而确保在解码时不会受到未来信息的影响。
位置
三种注意力模块在Transformer网络对应位置如下图所示。
作用
Transformer实际上是通过三重注意力机制建立起了序列内部以及序列之间的全局联系。论文中对这三种注意力作用的解释如下图所示。
我们具体分析下这三种注意力。
全局自注意力层
全局自注意力层(Global self attention layer)位于编码器中,它负责处理整个输入序列。在全局自注意力机制中,序列中的每个元素都可以直接访问序列中的其它元素,从而与序列中的其他元素建立动态的关联,这样可以使模型更好地捕捉序列中的重要信息。自注意力的意思就是关注于序列内部关系的注意力机制,那么是如何实现让模型关注序列内部之间的关系呢?自注意力将query、key、value设置成相同的东西,都是输入的序列,就是让注意力机制在序列的本身中寻找关系,注意到不同部分之间的相关性。
对于全局自注意力来说,Q、K、V有如下可能:
- Q、K、V都是输入序列。
- Q、K、V都来自编码器中前一层的输出。编码器中的每个位置都可以关注编码器前一层输出的所有位置。
再细化来说,Q是序列中当前位置的词向量,K和V是序列中的所有位置的词向量。
掩码自注意力
掩码自注意力层或者说因果自注意力层(Causal attention layer)可以在解码阶段捕获当前词与已经解码的词之间的关联。它是对解码器的输入序列执行类似全局自注意力层的工作,但是又有不同之处。
Transformer是自回归模型,它逐个生成文本,然后将当前输出文本附加到之前输入上变成新的输入,后续的输出依赖于前面的输出词,具备因果关系。这种串行操作会极大影响训练模型的时间。为了并行提速,人们引入了掩码,这样在计算注意力时,通过掩码可以确保后面的词不会参与前面词的计算。
对于掩码自注意力来说,Q、K、V有如下可能:
- Q、K、V都是解码器的输入序列。
- Q、K、V都来自解码器中前一层的输出。解码器中的每个位置都可以关注解码器前一层的所有位置。
再细化来说,Q是序列中当前位置的词向量,K和V是序列中的所有位置的词向量。
交叉注意力层
交叉注意力层(Cross attention layer)其实就是传统的注意力机制。交叉注意力层位于解码器中,但是其连接了编码器和解码器,这样可以刻画输入序列和输出序列之间的全局依赖关系,完成输入和输出序列之间的对齐。因此它需要将目标序列作为Q,将上下文序列作为K和V。
对于交叉注意力来说,Q、K、V来自如下:
- Q来自前一个解码器层,是因果注意力层的输出向量。
- K和V来自编码器输出的注意力向量。
这使得解码器中的每个位置都能关注输入序列中的所有位置。另外,编码器并非只传递最后一步的隐状态,而是把所有时刻(对应每个位置)产生的所有隐状态都传给解码器,这就解决了中间语义编码上下文的长度是固定的问题。
或者从另一个角度来理解,交叉注意力是序列到序列模式;双向自注意力是自编码模式;单向自注意力是自回归模式。
1.4 执行流程
我们再来结合模型结构图来简述推理阶段的计算流程,具体如下图所示。
假设我们进行机器翻译工作,把中文”我吃了一个苹果“翻译成英文”I ate an apple“,在假设模型只有一层,执行步骤如下:
- 处理输入。用户输入自然语言句子”我吃了一个苹果“;tokenizer先把序列转换成token序列;然后Input Embedding层对每个token进行embedding编码,再加入Positional Encoding(位置编码),最终形成带有位置信息的embedding编码矩阵。编码矩阵用 Xn∗d��∗� 表示, n 是句子中单词个数,d 是表示向量的维度(论文中 d=512)。注:原论文图上的输入是token,本篇为了更好的说明,把输入设置为自然语言句子。
- 编码器进行编码。编码矩阵首先进入MHA(Multi-Head Attention,多头注意力)模块,在这里每个token会依据一定权重把自己的信息和其它token的信息进行交换融合;融合结果会进入FFN(Feed Forward Network)模块做进一步处理,最终得到整个句子的数学表示,句子中每个字都会带上其它字的信息。整个句子的数学表示就是Encoder的输出。
- 通过输入翻译开始符来启动解码器。
- 解码器进行解码。解码器首先进入Masked Multi-Head Attention模块,在这里解码器的输入序列会进行内部信息交换;然后在Multi-Head Attention模块中,解码器把自己的输入序列和编码器的输出进行融合转换,最终输出一个概率分布,表示词表中每个单词作为下一个输出单词的概率;最终依据某种策略输出一个最可能的单词。这里会预测出第一个单词”I“。
- 把预测出的第一个单词”I“和一起作为解码器的输入,进行再次解码。
- 解码器预测出第二个单词”ate“。
针对本例,解码器的每一步输入和输出具体如下表所示。
1.6 小结
Transformer总体架构是一个有机整体,难以分割。组合的意义不在于构成它的基本单元,而在于这些单元之间形成的复杂关系和涌现的行为。比如集体智能来自个体的组合,却产生了所有个体都不具备的高阶能力。
有些工作就将焦点转移到 transformer 模块的高级架构上,并认为其完整结构,而不仅仅是标记混合注意力操作,对Transformer实现具有竞争力的性能至关重要。
论文"Attention is not all you need"指出如果没有skip connection(residual connection-残差链接)和MLP,自注意力网络的输出会朝着一个rank-1的矩阵收缩。即,skip connection和MLP可以很好地阻止自注意力网络的这种”秩坍塌(秩坍塌)退化“。这揭示了skip connection,MLP对self-attention的不可或缺的作用;
论文”MetaFormer is Actually What You Need for Vision“则描述了一种通用架构,在该结构中,输入首先经过embedding,得到 𝑋。然后embedding送入重复的blocks中,第一个block主要包含了token mixer,使得不同的token能够相互信息通信(Y = TokenMixer(Norm(X)) + X,);第二个block包含两层MLP。该架构通过指定token mixer的具体设计,可以获得不同的模型。如果将token mixer指定为注意力或spatial MLP,则MetaFormer将分别成为一个transformer或类似MLP的模型。
0x02 构建
我们接下来结合哈佛源码进行分析和学习。哈佛代码中的make_model()函数是Transformer模型的构建函数。
2.1 参数
make_model()函数的参数有如下7个:
- src_vocab:源语言词表中单词数目,即源词典的大小。
- tgt_vocab:目标语言词表中单词数目,即目标词典的大小。
- N=6:编码器和解码器堆叠数,即编码器层数和解码器层数。
- d_model=512:模型所处理数据的维度,即词向量(word embedding)的大小。
- d_ff=2048:FFN(前馈全连接层)中变换矩阵的维度,即隐层神经元的数量。
- head=8:多头注意力层中的注意力头数。
- dropout=0.1:防止过拟合。
2.2 构建逻辑
make_model()函数的主要思路就是用从小到大搭建积木的方式来构建Transformer。我们先脱离代码来构思下,看看架构图上的哪些模块可以作为积木。
- 输入模块:Input Embedding和Positional Encoding分别可以作为单独的积木块,它们结合在一起又可以作为一个新的积木块。
- 编码器层和解码器层可以作为两个单独的大积木块。其内部的Masked Multi-Head Attention、Multi-Head Attention、Feed Forward和Add & Norm也都可以作为单独的小积木块。
- 输出模块:Linear和Softmax分别可以作为单独的积木块,它们结合在一起又可以作为一个新的积木块。
有了这些积木块,我们就可以很容易的构建起Tranformer了。当然,在make_model()函数中做了一定的抽象,有些小积木块被用某些类进行了封装(比如Linear和Softmax被封装在Generator类中,细节没有在make_model()函数中展示出来)。我们把这些模块和代码中一一对应起来看,下图中的数字代表代码中某模块出现的顺序,这些数字在下面代码的注释中也有标明。
具体的代码逻辑如下。
- 把copy.deepcopy()这个深度拷贝函数重新命名为c,这样后续代码会比较简洁。deepcopy()函数会开辟一个新内存并将源实例完全复制过来,复制过来的对象和源对象没有任何关联。后续各种类的构造函数中会调用copy.deepcopy()来重新生成一个对应实例,比如上面图中的标号1,2,3在解码器端就分别被做了深度拷贝。这样,两个标号1对应的实例彼此之间相互独立,不受干扰。
- 构建 MultiHeadedAttention,PositionwiseFeedForward 和 PositionalEncoding 对象。
- 构造 EncoderDecoder 对象,这是Transformer主体类,其参数是Encoder、Decoder、src-embed、tgt-embed和 Generator。我们先分析后面三个参数。
- src-embed是nn.Sequential(Embeddings(d_model, src_vocab), c(position))的返回结果,其意义是输入编码,对应上图的编号7(由编码3和6组成)。nn.Sequential()函数构建了顺序容器,容器内模块的顺序就是模型处理数据的顺序;
- tgt-embed是nn.Sequential(Embeddings(d_model, tgt_vocab), c(position))的返回结果,其意义是输入编码,对应上图的编号9(由编码3和8组成)。
- Generator对应上图的编号10,其包括了Linear和Softmax。Generator会把Decoder的输出变成输出词的概率。
- Encoder和Decoder两个类很像,我们以Encoder为例来说明。Encoder由N个EncoderLayer构成,EncoderLayer的参数是d_model, c(attn), c(ff), dropout,即word embedding维度、多头注意力、FFN层和Dropout。可以看到,Encoder和Decoder类中的注意力都是MultiHeadedAttention的实例,只是因为传递参数的不同,才决定某个注意力是交叉注意力还是掩码多头注意力。
- 初始化模型参数。Xavier初始化可以参考论文"Understanding the difficulty of training deep feedforward neural networks"。
具体代码如下。
def make_model(src_vocab, tgt_vocab, N=6, d_model=512, d_ff=2048, h=8, dropout=0.1):
"Helper: Construct a model from hyperparameters."
# copy.deepcopy是深度拷贝函数,即重新生成一个新实例。重新命名可以让后续代码比较简洁
c = copy.deepcopy
# 构建多头注意力层的实例,对应上图的数字标号1
attn = MultiHeadedAttention(h, d_model)
# 构建前馈神经网络层的实例,对应上图的数字标号2
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# 构建位置编码模块的实例,对应上图的数字标号3
position = PositionalEncoding(d_model, dropout)
# 总的Transformer模型
model = EncoderDecoder(
# EncoderLayer只包含一个Attention层,对应上图的数字标号4。Encoder则包括外面的N
Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),
# DecoderLayer包含两个Attention层,对应上图的数字标号5,Decoder则包括外面的N
Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout), N),
# 输入的Embedding和位置编码,Embeddings对应上图的数字标号6,Sequential就是两个编码合并的结果,对应上图的数字标号7
nn.Sequential(Embeddings(d_model, src_vocab), c(position)),
# 输出的Embedding和位置编码,Embeddings对应上图的数字标号8,Sequential就是两个编码合并的结果,对应上图的数字标号9
nn.Sequential(Embeddings(d_model, tgt_vocab), c(position)),
# Generator类包括Linear层和Softmax层,对应上图的数字标号10,负责依据Decoder的输出来预测下一个token
Generator(d_model, tgt_vocab),
)
# This was important from their code.
# Initialize parameters with Glorot / fan_avg.
# 初始化模型参数,这里采用xavier初始化,即如果参数的维度大于1,则将其初始化成一个服从均匀分布的矩阵
for p in model.parameters():
if p.dim() > 1:
nn.init.xavier_uniform_(p)
return model
2.3 主体类
EncoderDecoder类就是基于Transformer架构的编码器-解码器实现,其成员变量如下:
- encoder:Encoder类实例,这是编码器的实现。
- decoder:Decoder类实例,这是解码器的实现。
- src_embed:源语言的word embedding生成模块,是一个nn.Sequential对象,包括Embebddings和PositionalEncoding。src_embed将对输入进行Embedding和位置编码
- tgt_embed:目标语言的word embedding生成模块,是一个nn.Sequential对象,包括Embebddings和PositionalEncoding。tgt_embed将对再传入的输出进行Embedding和位置编码
- generator:Generator类的对象,包括Linear层和Softmax层,负责对Decoder的输出做预测,即依据Decoder的隐状态输出来预测当前时刻的词。隐状态会输入到全连接层(全连接层的输出大小是词典的大小),全连接层会接上一个softmax得到预测词的概率。
EncoderDecoder类的forward()函数完成了编码和解码的工作,它接受四个函数:
- src:源序列,其内容是token在词表对应的编号。src的形状是[batch_size, seq_len],举例是[[ 0, 2, 4, 8, 1, 2, 2 ]] ,即批量大小为1,句子长度是7,其中0为bos,1为eos,2为pad。
- tgt:目标序列,具体含义类似src。
- src_mask:源序列掩码,具体作用是对填充符号进行掩码。以[[ 0, 2, 4, 8, 1, 2, 2 ]]为例,其掩码是[[True,True,True,True,True,False,False]],即对两个填充的pad进行掩码。
- tgt_mask:目标序列掩码,其作用有两种:不让注意力计算看到未来的单词;对填充符号进行掩码。其形状是[batch_size, seq_len, seq_len],上面对应的掩码如下:
[True,False,False,False,False,False,False],
[True,True,False,False,False,False,False],
[True,True,True,False,False,False,False],
[True,True,True,True,False,False,False],
[True,True,True,True,True,False,False], # 例句是5个正式token,后面两个pad被置为False
[True,True,True,True,True,False,False],
[True,True,True,True,True,False,False],
EncoderDecoder代码具体如下:
# 继承nn.Module
class EncoderDecoder(nn.Module):
"""
A standard Encoder-Decoder architecture. Base for this and many other models.
标准的编码器-解码器架构,这是很多模型的基础。
"""
def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
"""
初始化函数有5个参数,从外部传入参数的目的是更加灵活,可以更换组件
"""
super(EncoderDecoder, self).__init__()
self.encoder = encoder # 编码器对象
self.decoder = decoder # 解码器对象
# 源语言input embedding和position embedding的组合
self.src_embed = src_embed
# 目标语言output embedding和position embedding的组合
self.tgt_embed = tgt_embed
self.generator = generator # 类别生成器对象
def forward(self, src, tgt, src_mask, tgt_mask):
# 前向传播函数有四个参数:源序列,目标序列,源序列掩码,目标序列掩码
"Take in and process masked src and target sequences."
# 1. 将source, source_mask传入编码函数encode(),让编码器对源序列进行编码,得到编码结果memory
# 2. 将memory,source_mask,target,target_mask一同传给解码函数decode()进行解码
return self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)
# 编码函数,接受参数是源序列和掩码
def encode(self, src, src_mask):
# 1. 对src编码,得到input embedding
# 2. 计算位置编码,将input embedding和位置编码相加,得到word embedding
# 3. 使用编码器encoder进行编码,编码结果记作memory
return self.encoder(self.src_embed(src), src_mask)
# 解码函数,参数为:编码器输出(memory)、源序列掩码、目标序列和目标序列掩码
def decode(self, memory, src_mask, tgt, tgt_mask):
# 1. 对tgt编码,得到得到input embedding
# 2. 计算位置编码,将input embedding和位置编码相加,得到word embedding
# 3. 使用编码器decoder进行解码,解码器输出可以使用self.generator进行最后的预测
return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)
2.4 如何调用
模型调用的方式如下面代码所示,forward()函数负责把输入编码成隐状态,然后把隐状态解码称输出logits(对数几率)。其参数都从batch类的实例中获取,具体如下:
- src:源句子列表。形状是[batch size, max sequence length]。每个句子是从数据集提取出来,经过词典处理过的,举例为:[ 0, 5, 12,..., 1, 2, 2]。其中0为bos,1为eos,2为pad。
- tgt:目标句子列表。具体意义同上。
- src_mask:注意力层要用的掩码(后面章节会详细分析)。
- tgt_mask:解码器的掩码自注意力层要用的掩码(后面章节会详细分析)。
out = model.forward(batch.src, batch.tgt, batch.src_mask, batch.tgt_mask)