原文链接:mp.weixin.qq.com/s/D3ztMx5He…
欢迎关注公zh: AI-Frontiers
transformer往期文章推荐
这是Transformer系列文章的第二篇。在第一篇中 transformer进阶之路:#1 整体概述 中,我们了解了 Transformer 的功能、使用方法、高级架构及其优势。
本篇将深入剖析其工作原理,详细研究数据在 Transformer 中的流动的机制,比如数据的矩阵表示和形状,以理解每个阶段具体逻辑。
架构概述
Transformer 架构的主要组成部分有:
编码器 和 解码器 的数据输入包括:
-
嵌入层
-
位置编码层
编码器堆栈包含多个编码器。每个编码器包含:
-
多头注意力层
-
前馈层
解码器栈包含多个解码器。每个解码器包含:
-
两个多头注意力层
-
前馈层
输出(右上角)——生成最终输出,包含:
-
线性层
-
Softmax层。
为了理解每个组件的作用,我们以训练一个翻译任务为例,一步步走过 Transformer 的工作流程。我们只用一个训练样本:输入序列是英文 "You are welcome",目标序列是西班牙语 "De nada"。
嵌入和位置编码
和任何自然语言处理模型一样,Transformer 需要知道每个单词的两件事:词义及其在序列里的位置。
- 嵌入层: 负责编码单词的含义。
- 位置编码层: 负责表示单词的位置。
Transformer 把这两部分编码直接相加,合二为一。
嵌入(Embedding)
Transformer 有两个嵌入层。输入序列被喂给第一个嵌入层,叫做 「输入嵌入」。
目标序列则被喂给第二个嵌入层,不过在喂之前,需要先把目标序列向右移动一个位置,并在第一个位置插入一个「起始」标记。
注意,在推理阶段我们没有目标序列,只能像第一篇 transformer进阶之路:#1 整体概述 讲的那样,通过循环把输出序列喂给这第二个嵌入层。所以它也被称为「输出嵌入」。
文本序列通过我们的词汇表映射到数字词ID。然后,嵌入层将每个输入词映射到一个嵌入向量,该向量更丰富地表示了该词的含义。
位置编码(Position Encoding)
RNN 本质上是一个循环:一个词接一个词地输入。因此它天然知道每个单词的位置。
但 Transformer 不用 RNN,序列里的所有单词是并行输入的。这虽然是它相比 RNN 的一大优势,但也意味着位置信息会丢失,必须单独把它加回来。
和两个嵌入层对应,Transformer 也有两个位置编码层。位置编码是独立于输入序列计算出来的。它们是一些固定的值,只取决于序列的最大长度。举个例子:
-
第一个位置的编码是一个常数,表示这是第一个位置
-
第二个位置的编码是另一个常数,表示这是第二个位置
-
以此类推。
这些常数是用下面这个公式算出来的。
其中:
- pos 是单词在序列中的位置
- 是编码向量的长度(和嵌入向量的长度一样)
- 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。
- 同样,"nada" 应该在第二个单词位置上概率为 1,"END" 在第三个单词位置上概率为 1。
跟往常一样,这个损失会被用来计算梯度,通过反向传播来训练 Transformer。
小结
希望这篇文章能让读者明白,训练期间 Transformer 内部到底发生了什么。正如我们在前一篇文章中所讨论的,推理阶段它会在一个循环中运行,但大部分处理过程是一样的。
多头注意力模块,正是 Transformer 强大能力的来源。在下一篇文章中,我们将继续深入一步,真正理解注意力计算的细节。
感谢阅读文章,如果觉得不错,欢迎点赞、转发!