如何实现Transformer的解码器及输出?
摘要:我们在《Transformer的结构拆解》那篇文章中介绍过,Transformer 可以分为四个部分:输入、输出、编码器、解码器。上篇文章介绍了编码器的实现,这篇文章介绍一下解码器的实现。 我们回顾一下 Transformer 的解码器的构
我们在《Transformer的结构拆解》那篇文章中介绍过,Transformer 可以分为四个部分:输入、输出、编码器、解码器。上篇文章介绍了编码器的实现,这篇文章介绍一下解码器的实现。
我们回顾一下 Transformer 的解码器的构成。它由 N 个解码器层堆叠而成,每个解码器层由三个子层连接结构组成。
第一个子层连接结构包括一个掩码多头自注意力子层和层归一化以及一个残差连接;
第二个子层连接结构包括一个多头交叉注意力子层和层归一化以及一个残差连接;
第三个子层连接结构包括一个前馈网络子层和层归一化以及一个残差连接。
1 解码器层
Transformer 解码器层是实现序列生成的基础单元,每层由掩码多头自注意力、编码器 - 解码器交叉注意力与前馈网络三个子层构成,且每个子层均通过残差连接与层归一化封装为子层连接结构。其中掩码机制保证了自回归生成的合法性,交叉注意力实现了源序列与目标序列的语义对齐,共同完成翻译、文本生成等生成式任务。
1.1 解码器层的代码实现
class DecoderLayer(nn.Module):
def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
"""
size: 模型的特征维度(通常与词嵌入维度 d_model 相同),用来确定层归一化的输入维度
self_attn: 解码器的自注意力机制实例
src_attn: 编码器-解码器交叉注意力机制实例
feed_forward: 位置前馈神经网络实例
dropout: dropout 置零概率,用于正则化
"""
super(DecoderLayer, self).__init__()
self.size = size
self.self_attn = self_attn
self.src_attn = src_attn
self.feed_forward = feed_forward
self.sublayer = clones(SublayerConnection(size, dropout), 3)
def forward(self, x, memory, src_mask, tgt_mask):
"""
x: 目标序列嵌入
memory: 编码器输出
src_mask: 源掩码
tgt_mask: 目标掩码
"""
m = memory
# 自注意力子层
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))
# 编码器-解码器交叉注意力子层
x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))
# 前馈层
return self.sublayer[2](x, self.feed_forward)
自注意力子层是把目标序列嵌入当作 query、key、value 计算自注意力。编码器-解码器交叉注意力子层是把自注意力子层的输出作为 query,将编码器的输出同时作为 key 和 value。解码器通过交叉注意力机制,能够关注编码器输出中与当前解码位置相关的信息。这种设计使得解码器能够利用源序列的信息来指导目标序列的生成。
(1)为什么有两个掩码tgt_mask和src_mask?
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))在解码器的自注意力计算中,会通过前瞻掩码(Look-ahead Mask,包含在tgt_mask中)对目标序列进行遮掩,其核心目的是实现自回归生成,保证模型在生成第 \(i\) 个位置的输出时,只能依赖前 \(i-1\) 个位置的信息,绝对无法访问未来位置的信息。
讲得再通俗一点,Transformer 在训练时并非逐词生成,而是采用Teacher Forcing机制直接传入完整的目标序列来快速计算损失。但我们绝不允许模型在生成当前词元时“偷看”后面的真实结果,这相当于直接抄答案,违背了生成逻辑。因此必须通过掩码将未来信息全部遮蔽。
