如何理解Transformer中的Masked Multi-Head Attention

29

我目前正在学习Transformer的代码,但是我不理解解码器的掩码多头是什么意思。论文说这是为了防止你看到生成的单词,但是如果生成单词后面的单词还没有被生成,那么它们怎么能被看到呢?

我尝试阅读Transformer的代码(链接:https://github.com/Kyubyong/transformer)。下面展示了代码实现的掩码。它使用下三角矩阵进行掩码处理,但我不理解为什么要这样做。

padding_num = -2 ** 32 + 1
diag_vals = tf.ones_like(inputs[0, :, :])  # (T_q, T_k)
tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense()  # (T_q, T_k)
masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(inputs)[0], 1, 1])  # (N, T_q, T_k)
paddings = tf.ones_like(masks) * padding_num
outputs = tf.where(tf.equal(masks, 0), paddings, inputs)
3个回答

57
阅读完Transformer论文后,我也有同样的问题。在互联网上找不到完整详细的答案,所以我会尝试解释一下我对遮蔽多头注意力的理解。
简短回答是——我们需要屏蔽来使训练并行化。并行化是好的,因为它可以加快模型的训练速度。
这里有一个说明这个想法的例子。假设我们正在训练将“I love you”翻译成德语。编码器以并行模式工作-它可以在固定步骤数内生成输入序列(“I love you”的向量表示)。(即步骤的数量不取决于输入序列的长度)。
假设编码器生成的数字为11、12、13作为输入序列的向量表示。实际上,这些向量会更长,但为了简单起见,我们使用短的向量。同时出于简单起见,我们忽略了服务令牌,比如 - 序列开始、序列结束等。
在训练过程中,我们知道翻译应该是“Ich liebe dich”(我们在训练期间总是知道预期输出)。假设“Ich liebe dich”单词的期望向量表示为21、22、23。如果我们以顺序模式进行解码器训练,它会类似于循环神经网络的训练。将执行以下顺序步骤:
- 顺序操作#1。输入:11、12、13。 - 尝试预测21。 - 预测输出不会完全是 21,假设它将是21.1。 - 顺序操作#2。输入:11、12、13,以及前一个输出为21.1。 - 尝试预测22。 - 预测输出不会完全是 22,假设它将是22.3。 - 顺序操作#3。输入:11、12、13,以及前一个输出为22.3。 - 尝试预测23。 - 预测输出不会完全是 23,假设它将是23.5
这意味着我们需要进行3个顺序操作(在一般情况下-每个输入一个顺序操作)。此外,每次迭代都会累积误差。另外,由于我们只查看单个先前的输出,因此我们不使用注意力。
由于我们实际上知道预期的输出,所以可以调整该过程并使其并行。无需等待上一步的输出。
- 并行操作#A。输入: 11、12、13。 - 尝试预测21
  • 并行操作#B。输入:11,12,1321
    • 试图预测22
  • 并行操作#C。输入:11,12,1321,22
    • 试图预测23
  • 这个算法可以并行执行,也不会累积误差。该算法使用attention(即查看所有先前的输入),因此在进行预测时需要考虑更多上下文信息。

    这就是我们需要掩码的地方。训练算法知道整个期望输出(21,22,23)。对于每个并行操作,它隐藏(掩码)已知输出序列的一部分。

    • 当执行#A时-隐藏(掩码)整个输出。
    • 当执行#B时-隐藏第二个和第三个输出。
    • 当执行#C时-隐藏第三个输出。

    掩码本身的实现如下所示(来自原始论文):

    我们通过在softmax的输入中屏蔽(设置为-∞)与非法连接相对应的所有值来在缩放的点积attention内部实现这一点。

    注意:在推理阶段(而非训练阶段),解码器以顺序模式运行(而非并行),因为它最初不知道输出序列。但这与RNN方法不同,因为Transformer推理仍然使用自注意力并查看所有先前的输出(而不仅仅是前一个)。
    注意2:我在一些材料中看到遮罩可以用于非翻译应用。例如,对于语言建模,遮罩可以用于隐藏输入句子中的某些单词,模型将尝试使用其他未被遮罩的单词(即学习理解上下文)在训练过程中预测它们。

    5
    我推荐这篇文章,结合你的解释,它可以帮助你更好地理解。 - user7075574
    +1 对这个非常有帮助的例子表示赞赏。通过这个例子,我对src_mark很清楚了。但是,src_key_padding_mask对我来说还有点模糊。它是用来忽略单个序列末尾的一些填充标记吗? - eroot163pi
    我理解你所解释的内容。但是如果解码器正在处理目标序列,那么如何进行自回归解码呢?我认为自回归的定义是使用过去的预测来预测未来。 - Soumyadip Sarkar
    嗨,在推理模式下,解码器需要面具吗? - dingx
    很好的回答!还想补充一下,在你培训中提到的那个过程被称为“教师强制”(https://en.wikipedia.org/wiki/Teacher_forcing),最初是在RNN环境中进行的,目的是为了避免错误传播(正如你所提到的)。 - undefined
    显示剩余2条评论

    2

    解码器是一个自回归模型,无法看到未来的单词。

    1. 转换器中的编码器是一个自回归模型;
    2. 这意味着它将根据之前的单词预测下一个单词;
    3. 因此输入的x无法看到未来的单词;
    4. 我们使用掩码多头注意力来完成此任务。

    这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - BDL
    我认为这是一个简单的问题,可以给出一个简短的答案。 - zhangjq

    0
    假设我们训练的文本是"one two three four five"。
    这是自监督训练,我们只需要训练模型来预测序列中的下一个单词。与编码器-解码器模型不同,我们将使用GPT风格的Transformer(有时称为“仅解码器”,因为它是“因果”的,有时称为“仅编码器”,因为没有交叉注意力)。
    如果我们正在进行生成式预训练,我们将使用以下方式训练模型: input_tokens = [one, two, three, four] 和 output_tokens [two, three, four, five] 我们将移动标记,以便模型始终预测下一个标记。
    现在,对于输出的"two",我们只想考虑输入的"one"。当学习生成输出的"three"时,我们只想考虑输入的"one"和"two"。依此类推。
    现在,在预训练之后,我们将输入"Mary had a little"到模型中,我们期望得到输出"had a little lamb"。输出会向后移动一个标记。
    训练输出整个句子可能看起来有些浪费。你可能会问自己,为什么不只训练模型预测下一个标记呢?为什么要预测输入中已经存在的单词,从而需要这种因果掩码呢?嗯,参数是共享的。当模型通过关注"one"来学习"two"时,它正在改变帮助生成"four"并关注"five"的模型参数。较长的序列长度最终等同于更大的批次大小,因此这种看似多余的训练方式实际上非常高效利用数据。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接