构概述Transformer 架构的主要组成部分有编码器和解码器的数据输入包括嵌入层位置编码层编码器堆栈包含多个编码器。每个编码器包含多头注意力层前馈层解码器栈包含多个解码器。每个解码器包含两个多头注意力层前馈层输出右上角——生成最终输出包含线性层Softmax层。为了理解每个组件的作用我们以训练一个翻译任务为例一步步走过 Transformer 的工作流程。我们只用一个训练样本输入序列是英文 You are welcome目标序列是西班牙语 De nada。嵌入和位置编码和任何自然语言处理模型一样Transformer 需要知道每个单词的两件事词义及其在序列里的位置。嵌入层 负责编码单词的含义。位置编码层 负责表示单词的位置。Transformer 把这两部分编码直接相加合二为一。嵌入EmbeddingTransformer 有两个嵌入层。输入序列被喂给第一个嵌入层叫做 「输入嵌入」。目标序列则被喂给第二个嵌入层不过在喂之前需要先把目标序列向右移动一个位置并在第一个位置插入一个「起始」标记。注意在推理阶段我们没有目标序列只能像第一篇 transformer进阶之路#1 整体概述 讲的那样通过循环把输出序列喂给这第二个嵌入层。所以它也被称为「输出嵌入」。文本序列通过我们的词汇表映射到数字词ID。然后嵌入层将每个输入词映射到一个嵌入向量该向量更丰富地表示了该词的含义。位置编码Position EncodingRNN 本质上是一个循环一个词接一个词地输入。因此它天然知道每个单词的位置。但 Transformer 不用 RNN序列里的所有单词是并行输入的。这虽然是它相比 RNN 的一大优势但也意味着位置信息会丢失必须单独把它加回来。和两个嵌入层对应Transformer 也有两个位置编码层。位置编码是独立于输入序列计算出来的。它们是一些固定的值只取决于序列的最大长度。举个例子第一个位置的编码是一个常数表示这是第一个位置第二个位置的编码是另一个常数表示这是第二个位置以此类推。这些常数是用下面这个公式算出来的。其中pos是单词在序列中的位置dmodel​ 是编码向量的长度和嵌入向量的长度一样i是这个向量里的索引值。换句话说它把正弦曲线和余弦曲线交错在一起所有偶数索引用正弦值奇数索引用余弦值。举个例子假如我们编码一个长度为 40 个词的序列下图展示了其中几个单词位置编码索引组合对应的编码值。蓝色曲线展示了第 0 个索引对于全部 40 个单词位置的编码值橙色曲线是第 1 个索引的。剩下的索引值也会有类似的曲线。矩阵维度我们知道深度学习模型一次处理一批训练样本。嵌入层和位置编码层操作的对象是代表一批序列样本的矩阵。嵌入层接收一个形状为 (样本数, 序列长度) 的矩阵里面是单词 ID。它把每个单词 ID 编码成一个词向量词向量的长度就是嵌入尺寸embedding size。输出的矩阵形状就变成了(样本数, 序列长度, 嵌入尺寸)。位置编码使用的编码尺寸等于嵌入尺寸。所以它能生成一个形状完全一样的矩阵直接加到嵌入矩阵上。这个由嵌入层和位置编码层生成的 (样本数, 序列长度, 嵌入尺寸) 形状会贯穿整个 Transformer数据流过编码器堆栈和解码器堆栈时都保持不变直到最后的输出层才会改变形状。这里对 Transformer 里的 3D 矩阵维度有了个概念。为了让图示更简单接下来我们会丢掉「样本数」这个维度只针对单个样本使用 2D 表示。输入嵌入把输出送给编码器。同样输出嵌入把输出送给解码器。编码器编码器堆栈和解码器堆栈分别由若干个通常是 6 个编码器和解码器依次串联而成。堆栈里的第一个编码器接收来自嵌入层和位置编码层的输入。堆栈里的其他编码器则接收上一个编码器的输出。编码器把它的输入传给一个多头自注意力层。自注意力的输出再传给一个前馈层然后前馈层把输出向上送给下一个编码器。自注意力和前馈这两个子层周围都有一圈残差连接紧接着是一个层归一化。最后一个编码器的输出会按下面要讲的方式被喂给解码器堆栈里的每一个解码器。解码器解码器的结构和编码器非常相似但有两处不同。和编码器一样堆栈里的第一个解码器接收来自输出嵌入和位置编码的输入。堆栈里的其他解码器则接收上一个解码器的输出。解码器把它的输入传给一个多头自注意力层。但与编码器中的自注意力稍有不同它只允许关注到序列中当前位置之前的位置。这是通过掩码masking未来位置实现的我们稍后会详细讨论。与编码器不同的是解码器还有第二个多头注意力层叫做「编码器-解码器注意力层」。这个层的工作方式类似自注意力但它会结合两个来源的输入一个是它下面的自注意力层的输出另一个是编码器堆栈的输出。自注意力的输出会传给前馈层然后前馈层把输出向上送给下一个解码器。这些子层自注意力、编码器-解码器注意力和前馈层周围都有一个残差连接紧接着是一个层归一化。注意力机制在上一篇中我们聊了为什么注意力在处理序列时如此重要。在 Transformer 里注意力出现在三个地方编码器中的自注意力输入序列关注自己解码器中的自注意力目标序列关注自己解码器中的编码器-解码器注意力目标序列关注输入序列注意力层接收三个参数形式的输入分别叫做 Query查询、Key键和Value值。在编码器的自注意力中编码器的输入同时传给 Query、Key 和 Value 这三个参数。在解码器的自注意力中解码器的输入同时传给 Query、Key 和 Value 这三个参数。在解码器的编码器-解码器注意力中编码器堆栈最后一个编码器的输出被传给 Value 和 Key 参数而它下面那个自注意力和层归一化模块的输出被传给 Query 参数。多头注意力Transformer 把每一个注意力处理器称为一个注意力头然后并行地把这个过程重复多次。这就是多头注意力名字的由来。通过组合几个并行的、计算方式相似的注意力它给了自己的注意力更强的辨别能力。Query、Key 和 Value 会分别各自通过一个独立的线性层每个层有自己的权重产生三个结果Q、K 和 V。然后如下图所示它们会通过注意力公式组合在一起生成注意力分数。这里需要认识到一个关键点Q、K 和 V 携带的是序列中每个词的编码表示。注意力计算会把序列中的每一个词和其他每一个词都结合起来这样最终算出的注意力分数里就编码了序列中每个词的一个分数。刚才讨论解码器时我们简单提到了掩码。上面的注意力图示里也画出了Mask。我们来看看它是怎么工作的。注意力掩码在计算注意力分数时注意力模块会执行一个掩码步骤。掩码有两个目的a) 在编码器自注意力机制和编码器-解码器注意力机制中掩码用于将输入句子中存在填充的部分的注意力输出置零以确保填充不会影响自注意力。注由于输入序列的长度可能不同因此像大多数自然语言处理应用一样它们会使用填充标记进行扩展以便将固定长度的向量输入到Transformer中。编码器-解码器注意力机制也类似。b) 在解码器中自我注意力掩码作用是防止解码器在预测下一个单词时偷看目标句子的其余部分。解码器处理源序列中的单词并利用这些单词预测目标序列中的单词。在训练过程中这一过程通过教师强制法实现即将完整的目标序列作为解码器的输入。因此在预测某个位置的单词时解码器可以利用该单词之前和之后的目标单词。这使得解码器能够作弊利用未来时间步的目标单词进行预测。举个例子当预测 “Word 3” 的时候解码器应该只能参考目标序列里的前 3 个输入词而不能看到第 4 个词 “Ketan”。因此解码器会屏蔽序列中稍后出现的输入词。在计算注意力得分时参见前面展示计算过程的图片在调用 Softmax 函数之前会对分子应用掩码。被掩码掉的元素白色方块被设置为负无穷大以便 Softmax 函数将这些值转换为零。生成输出解码器堆栈中的最后一个解码器把它的输出传给输出组件由它转换成最终的输出句子。线性层把解码器的向量投射成单词分数对于句子中的每个位置都会为目标词汇表里的每一个独特的词生成一个分数值。举个例子假设我们最终的输出句子有 7 个词而目标西班牙语词汇表有 10000 个独特词汇。那么我们会为这 7 个词里的每一个都生成 10000 个分数值。这些分数值表示在该句子位置上词汇表中每个词出现的可能性大小。然后Softmax 层把这些分数转换成概率所有概率加起来等于 1.0。在每个位置上我们找到概率最高的那个词所对应的索引然后把这个索引映射回词汇表里真正的词。这些词就构成了 Transformer 的输出序列。训练与损失函数训练期间我们会用一个损失函数比如交叉熵损失来比较生成的输出概率分布和目标序列。概率分布给出了每个词出现在该位置上的概率。假设我们的目标词汇表只有四个词。我们的目标是产出一个概率分布让它匹配我们期望的目标序列De nada END。这意味着对于第一个单词位置概率分布应该在 De 这个词上概率为 1而词汇表里所有其他词的概率为 0。