The Residuals
我们需要提到的编码器架构中的一个细节是,每个编码器中的每个子层(self-attention,,ffnn)周围都有一个残余连接,然后是 layer-normalization 步骤。
如果我们要可视化向量和与 self attention 相关的 layer-norm 运算,它看起来是这样的
这也适用于解码器的子层。如果我们考虑一个由2个堆叠的编码器和解码器组成的Transformer,它看起来就像这样
The Decoder Side
现在我们已经涵盖了编码器方面的大部分概念,我们基本上也知道了解码器的组件是如何工作的。但是让我们看看它们是如何协同工作的。
编码器首先处理输入序列。然后,顶部编码器的输出被转换成一组注意向量K和v 。这些将被每个解码器在其编码器-解码器注意层中使用,这有助于解码器专注于输入序列中的适当位置
以下步骤重复该过程,直到达到一个特殊符号,表明 transformer 解码器已完成其输出。每一步的输出在下一个时间步中被馈送到底部的解码器,解码器像编码器一样将它们的解码结果气泡化。就像我们对编码器输入所做的那样,我们在这些解码器输入中嵌入并添加位置编码来指示每个单词的位置。
解码器中的 self attention 的运行方式与编码器中的略有不同:
在解码器中, self attention 层只允许关注输出序列中的早期位置。这是通过在 self attention 计算的 softmax 步骤之前 mask 未来位置(将其设置为-inf)来完成的。
"Encoder-Decoder Attention"层的工作原理就像多头 self attention 一样,只是它从下面的层创建查询矩阵,并从编码器堆栈的输出中获取键和值矩阵。
The Final Linear and Softmax Layer
解码器堆栈输出一个浮点数向量。我们怎么把它变成一个词?这是最后一个线性层的工作,然后是一个Softmax层。
线性层是一个简单的全连接神经网络,它将解码器堆栈产生的向量投影成一个更大的向量,称为logits向量。
假设我们的模型知道从训练数据集中学习到的1万个唯一的英语单词(我们模型的输出词汇表)。这将使logits向量有10,000个单元格宽,每个单元格对应一个唯一单词的分数。这就是我们如何解释线性层之后的模型输出。
然后softmax层将这些分数转换为概率(所有分数都是正的,加起来都是1.0)。选择概率最高的单元格,并生成与之关联的单词作为此时间步骤的输出。
Recap Of Training
既然我们已经介绍了经过训练的Transformer的整个前向传递过程,那么了解一下训练模型的直觉将会很有用。
在训练期间,未经训练的模型将经历完全相同的向前传递。但由于我们是在一个标记的训练数据集上训练它,我们可以将它的输出与实际的正确输出进行比较。
为了可视化这一点,假设我们的输出词汇表只包含六个单词(a、am、i、thanks、student和(句子结束的缩写))。
一旦定义了输出词汇表,就可以使用相同宽度的向量来表示词汇表中的每个单词。这也被称为 one-hot 编码。例如,我们可以用下面的向量表示单词am
在这个概述之后,让我们讨论模型的损失函数------我们在训练阶段优化的度量,以得到一个训练好的、希望非常准确的模型。
The Loss Function
假设我们正在训练我们的模型。假设这是我们训练阶段的第一步,我们正在用一个简单的例子来训练它,把"merci" 翻译成 "thanks" 。
这意味着,我们希望输出是一个表示单词thanks的概率分布。但是由于这个模型还没有经过训练,这种情况还不太可能发生。
如何比较两个概率分布?我们简单地用一个减去另一个。要了解更多细节,请查看交叉熵和 Kullback--Leibler divergence。
但请注意,这是一个过于简化的例子。更现实地说,我们将使用一个多于一个单词的句子。例如输入:我是一名学生,期望输出:我是一名学生。这真正的意思是,我们希望我们的模型连续输出概率分布,其中:
- 每个概率分布都由一个宽度词汇大小的向量表示(在我们的示例中是6,但更现实的数字是30,000或50,000)。
- 第一个概率分布在与单词i相关的单元格上的概率最高
- 第二个概率分布在与单词am相关的单元格上的概率最高
- 以此类推,直到第五个输出分布表示符号,它也有一个与它相关联的来自10,000个元素词汇表的单元格。
在足够大的数据集上训练模型足够长的时间后,我们希望生成的概率分布看起来像这样
现在,由于模型一次产生一个输出,我们可以假设模型从该概率分布中选择概率最高的单词,然后扔掉其余的。这是一种方法(称为贪婪解码)。另一种方法是抓住,比如说,前两个单词(例如'I' 和 'a'),然后在下一步中,运行模型两次:一次假设第一个输出位置是单词 'I' ,另一次假设第一个输出位置是单词"a",考虑到保留位置#1和#2,哪个版本产生更少的错误。我们对#2和#3等位置重复此操作。这种方法被称为"beam search",在我们的示例中,beam_size是两个(意味着在任何时候,两个部分假设(未完成的翻译)都保存在内存中),top_beams也是两个(意味着我们将返回两个翻译)。这些都是你可以尝试的超参数。