如图3-7所示,解码器和编码器的主要区别在于解码器有两个注意力子层:
掩码多头自注意力层
确保我们在每个时间步生成的词元只基于过去的输出和当前正在预测的词元。如果没有这样做,那么解码器将能够在训练时通过简单复制目标翻译来欺骗我们,导致训练失败。我们需要对输入进行掩码,以确保任务不是简单复制目标翻译。
编码器-解码器注意力层 1
对编码器栈的输出key和value向量执行多头注意力,并以解码器的中间表示作为query [7] 。在这种方式中,编码器-解码器注意力层学习如何关联来自两个不同序列(例如两种不同语言)的词元。在每个块中,解码器都可以访问编码器的key和value。
图3-7:Transformer解码器层放大图
我们来看一下需要进行哪些修改才能在自注意力层中添加掩码,然后将编码器-解码器注意力层的实现作为一道作业题留给你。掩码自注意力的技巧是引入一个掩码矩阵,该矩阵对角线下方的元素为1,上方的元素为0:
这里我们使用了PyTorch的tril()函数来创建下三角矩阵。一旦我们有了这个掩码矩阵,我们可以使用Tensor.masked fill()将所有的0替换为负无穷来防止每个注意力头查看后面的词元:
通过将矩阵对角线上方的值设为负无穷,可以保证当我们对分数进行softmax时,注意力权重都为0,因为e -∞ =0(回想一下softmax计算的是规范化指数)。我们只需要对本章早先实现的缩放点积注意力函数进行一点点修改,就能轻松地包含这种掩码行为:
这里的解码器层实现相当简单。如果你想了解更多,我们建议读者参考Andrej Karpathy创作的minGPT的优秀实现( https://oreil.ly/kwsOP )。
至此我们讲述了很多技术细节,希望你现在应该对Transformer架构每个部分的工作原理有了很好的理解。在开始构建比文本分类更高级的任务模型之前,我们稍微退后一步,看一看Transformer模型的各种分支,以及它们之间的关系。
这里我们用一个示例看看是否帮助你解开编码器-解码器注意力的神秘面纱。想象一下,你(解码器)正在班上,参加一场考试。你的任务是根据前面的单词(解码器输入)来预测下一个单词,听起来很简单,但实际上非常难(你自己试试,预测本书的某一段的下一个单词)。幸运的是,你旁边的同学(编码器)拥有整篇文章。不幸的是,他们是留学生,文章是用他们的母语写的。不过聪明如你,你还是想出了一种作弊的方式。你画了一幅小漫画,描述了你已经拥有的文章内容(query),交给了你的同学。然后他们会尝试找出哪一段文章与那个描述匹配(key),并画一幅漫画描述紧随该段文章之后的单词(value),然后把这个答案传给你。有了这种系统性的帮助,你轻松地通过了考试。