Transformer 超详细教程 1: 结构解析

本文我们一起梳理一下Transformer的架构,希望能让认真阅读的人看得更明白。

整体架构

首先上一幅非常常见的图:

相信大家已经见过这幅图无数次了,这张图对了解Transformer的整体架构是很有用的,因此我们还是得讲一下. 当然,这张图的信息太多,我们接下来拆开来从宏观到微观来讲。

首先,整体来看,一个Transformer有三个部分,一个encoder一个decoder还有最后一个全连接层.

Encoder

  1. 输入序列 src, 它的输入shape是 [BatchSize, seqLen]. 在图中的例子里,BatchSize = 32, seqLen = 8. 而且这个序列是经过tokenize之后的 (不知道tokenize的看Remark 1)。
  2. 输入mask: src_mask, 用来标记哪些是人为pad的字符, 它的维度为 [BatchSize, seqLen, seqLen]。不理解为啥需要mask的参见Remark 2. 我们这里主要讨论一下src_mask的维度。对于一个输入长度为8的句子,它词跟词之间的相关性矩阵是 8 × 8 8\times 8 8×8。因此我们对应需要一个 8 × 8 8\times 8 8×8 的mask矩阵,这也是为啥src_mask是[BatchSize, seqLen, seqLen]的原因。但是,注意看图,其中的维度是 [32, 1, 8] 而不是 [32, 8, 8]. 这说明,我们为这个batch中的每一个句子都分别准备了一个 [1,8] (而非[8,8]) 的mask. 换句话说,在真正mask的时候,我们会把[1,8]复制8行组成一个 8 × 8 8\times 8 8×8的矩阵对相关性矩阵操作。假如某个句子最后一个词是pad的,那最后我们只会对 8 × 8 8\times 8 8×8相关性矩阵的最后一列操作。
  3. 输出encOut, 维度是 [BatchSizee, seqLen, embedSize], 这个暂且不表,等看完Encoder的内部结构自然明了。

Remark 1 (tokenize): 标签化的意思就是对文字进行标记,比如说一种语言中有 1 0 4 10^4 104 个单词,那我们可以构建一个词汇表vocabulary然后把这 1 0 4 10^4 104 个单词标记为 1 ∼ 1 0 4 1\sim10^4 1104。除去这个词汇表之外我们还需要一些特殊的token,比如说句子开始标记 (sos), 句子结束标记 (eos), padding 标记。

Remark 2 (mask): 简单来说,Encoder和Decoder都是基于attention机制的,所谓的attention机制,其核心就是算输入句子中每个词之间的相关性。但是,有些词是人为预处理时候pad上去的,或者说这些词本身其实是不可见的(因为casuality),所以有时我们需要mask掉一些词,而src_mask的作用就是标记出哪些是需要mask的。

Decoder

  1. 输入序列 trg 实际上就是label。我们可以把整个Transformer想象成一个sequence to sequence 模型,所以training data 的label也是sequence。那么,在训练的时候我们确实可以把trg输入进decoder,那么test的时候这个trg从哪里来尼?这里的奥秘有两部分, 一方面,在test的时候,每次我们只输入当前已经decode出来的句子。更具体地说,test一个句子时,我们首先只输入一个开始token “sos” 进 decoder,当decoder decode出来第一个字时,我们再把 “sos+第一个字” 输入decoder来译码第二个字,紧接着再输入 “sos+第一个字+第二个字”。也就是说,decoder的输入在test的时候是不断变化的。这里如果还没理解,可以在后面详述model test的时候再理解。但至少,training的时候,整个trg sequence我们都是知道的,所以可以直接输入进 Decoder。另一部分的奥秘在于trg_mask.
  2. 输入mask: trg_mask. 刚刚已经说了,虽然training的时候我们知道整个句子,但是test的时候我们可不知道。所以,在训练的时候我们就要刻意要求decoder的译码过程和某个单词之后的单词都无关,即casuality。显然,这又可以用msk的来实现。如图中所示,trg_mask的维度是[BatchSize, seqLen, seqLen],这里第二个不能置1是因为 8*8的相关性矩阵每一行我们需要mask掉的词都不一样了,如果输出序列没有padding,那trg_mask就是一个下三角矩阵,所有上三角元素都要mask掉因为我们不希望译码结果跟他们有关。

Linear

最后一个Linear layer的作用就是改变一下维度而已,这是因为我们最终想要的是 32 和句子,每个句子长度是10,每个词都是一个 1 0 4 10^4 104 维的向量 (假设输出单词表大小也是 1 0 4 10^4 104).

需要注意的是,从 [32, 10, 512] 到 [32, 10, 1 0 4 10^4 104] 这个全连接层整个只有 512 × 1 0 4 512\times 10^4 512×104 个weights 和 1 0 4 10^4 104 个bias. 也就是说,每个batch中的每个词用的都是同样的weights和bias来变换。

Encoder的内部结构

好,接下来我们来看一下 Encoder 的内部结构:

在上一节中我们已经说过,encoder的输入src是一个batch的sequence,其中每个seq已经被pad并tokenize了(而且通常sequence的最后一位是end token)。所以说,图中的src实际上就是32 * 8 的矩阵,每个元素都是整数token。

Word embedding

第一步,我们要做embedding. 实际上就是把每个token对应成一个512维的向量。这一步可以通过nn.Embedding来实现 (一般情况下,随机给vecor也表现很好)。embed之后维度被张成了 [32, 8, 512] 因为每个token都变成了一个512维的vector,而每个序列都被表示成了[8, 512]维的矩阵。

Position encoding

第二步,我们要做position encoding。这个意思就是说我们要手动标记出每个序列对应的 [8, 512]维的矩阵中每个元素的位置。position encoding具体的实现方式有很多,最原始 paper 使用以下公式来标记 [8, 512] 这个矩阵的每个位置

PE ( pos , 2 i ) = sin ⁡ ( pos 1000 0 2 i / d model ) PE ( pos , 2 i + 1 ) = cos ⁡ ( pos 1000 0 2 i / d model ) \text{PE}(\text{pos}, 2i)= \sin\left(\frac{\text{pos}}{10000^{2i/d_\text{model}}} \right) \\\text{PE}(\text{pos}, 2i+1)= \cos\left(\frac{\text{pos}}{10000^{2i/d_\text{model}}} \right) PE(pos,2i)=sin(100002i/dmodelpos)PE(pos,2i+1<

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值