0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

神经编码器-解码器模型的历史

深度学习自然语言处理 来源:深度学习自然语言处理 2023-06-20 15:42 次阅读

基于 transformer 的编码器-解码器模型是 表征学习 和 模型架构 这两个领域多年研究成果的结晶。本文简要介绍了神经编码器-解码器模型的历史,更多背景知识,建议读者阅读由 Sebastion Ruder 撰写的这篇精彩 博文。此外,建议读者对 自注意力 (self-attention) 架构 有一个基本了解,可以阅读 Jay Alammar 的 这篇博文 复习一下原始 transformer 模型。

本文分 4 个部分:

背景 - 简要回顾了神经编码器-解码器模型的历史,重点关注基于 RNN 的模型。

编码器-解码器 - 阐述基于 transformer 的编码器-解码器模型,并阐述如何使用该模型进行推理。

编码器 - 阐述模型的编码器部分。

解码器 - 阐述模型的解码器部分。

每个部分都建立在前一部分的基础上,但也可以单独阅读。这篇分享是最后一部分 解码器

解码器

如 编码器-解码器 部分所述, 基于 transformer 的解码器定义了给定上下文编码序列条件下目标序列的条件概率分布:

根据贝叶斯法则,在给定上下文编码序列和每个目标变量的所有前驱目标向量的条件下,可将上述分布分解为每个目标向量的条件分布的乘积:

我们首先了解一下基于 transformer 的解码器如何定义概率分布。基于 transformer 的解码器由很多 解码器模块 堆叠而成,最后再加一个线性层 (即 “LM 头”)。这些解码器模块的堆叠将上下文相关的编码序列 和每个目标向量的前驱输入 (这里 为 BOS) 映射为目标向量的编码序列 。然后,“LM 头”将目标向量的编码序列 映射到 logit 向量序列 , 而每个 logit 向量 的维度即为词表的词汇量。这样,对于每个 ,其在整个词汇表上的概率分布可以通过对 取 softmax 获得。公式如下:

“LM 头” 即为词嵌入矩阵的转置, 即 。直观上来讲,这意味着对于所有 “LM 头” 层会将 与词汇表 中的所有词嵌入一一比较,输出的 logit 向量 即表示 与每个词嵌入之间的相似度。Softmax 操作只是将相似度转换为概率分布。对于每个 ,以下等式成立:

总结一下,为了对目标向量序列 的条件分布建模,先在目标向量 前面加上特殊的 向量 ( 即 ),并将其与上下文相关的编码序列 一起映射到 logit 向量序列 。然后,使用 softmax 操作将每个 logit 目标向量 转换为目标向量 的条件概率分布。最后,将所有目标向量的条件概率 相乘得到完整目标向量序列的条件概率:

与基于 transformer 的编码器不同,在基于 transformer 的解码器中,其输出向量 应该能很好地表征 下一个 目标向量 (即 ),而不是输入向量本身 (即 )。此外,输出向量 应基于编码器的整个输出序列 。为了满足这些要求,每个解码器块都包含一个 单向自注意层,紧接着是一个 交叉注意层,最后是两个前馈层。单向自注意层将其每个输入向量 仅与其前驱输入向量 (其中 ,且 ) 相关联,来模拟下一个目标向量的概率分布。交叉注意层将其每个输入向量 与编码器输出的所有向量 相关联,来根据编码器输入预测下一个目标向量的概率分布。

好,我们仍以英语到德语翻译为例可视化一下 基于 transformer 的解码器。

22c0f430-0c1f-11ee-962d-dac502259ad0.png

我们可以看到解码器将 : “BOS”、“Ich”、“will”、“ein”、“Auto”、“kaufen” (图中以浅红色显示) 和 “I”、“want”、“to”、“buy”、“a”、“car”、“EOS” ( 即 (图中以深绿色显示)) 映射到 logit 向量 (图中以深红色显示)。

因此,对每个 使用 softmax 操作可以定义下列条件概率分布:

总条件概率如下:

其可表示为以下乘积形式:

图右侧的红框显示了前三个目标向量 、、 在一个解码器模块中的行为。下半部分说明了单向自注意机制,中间说明了交叉注意机制。我们首先关注单向自注意力。

与双向自注意一样,在单向自注意中, query 向量 (如下图紫色所示), key 向量 (如下图橙色所示),和 value 向量 (如下图蓝色所示) 均由输入向量 (如下图浅红色所示) 映射而来。然而,在单向自注意力中,每个 query 向量 仅 与当前及之前的 key 向量进行比较 (即 ) 并生成各自的 注意力权重 。这可以防止输出向量 (如下图深红色所示) 包含未来向量 (,其中 且 ) 的任何信息 。与双向自注意力的情况一样,得到的注意力权重会乘以它们各自的 value 向量并加权求和。

我们将单向自注意力总结如下:

请注意, key 和 value 向量的索引范围都是 而不是 , 是双向自注意力中 key 向量的索引范围。

下图显示了上例中输入向量 的单向自注意力。

2308af6e-0c1f-11ee-962d-dac502259ad0.png

可以看出 只依赖于 和 。因此,单词 “Ich” 的向量表征 ( 即 ) 仅与其自身及 “BOS” 目标向量 ( 即 ) 相关联,而 与 “will” 的向量表征 ( 即 ) 相关联。

那么,为什么解码器使用单向自注意力而不是双向自注意力这件事很重要呢?如前所述,基于 transformer 的解码器定义了从输入向量序列 到其 下一个 解码器输入的 logit 向量的映射,即 。举个例子,输入向量 = “Ich” 会映射到 logit 向量 ,并用于预测下一个输入向量 。因此,如果 可以获取后续输入向量 的信息,解码器将会简单地复制向量 “will” 的向量表征 ( 即 ) 作为其输出 ,并就这样一直传播到最后一层,所以最终的输出向量 基本上就只对应于 的向量表征,并没有起到预测的作用。

这显然是不对的,因为这样的话,基于 transformer 的解码器永远不会学到在给定所有前驱词的情况下预测下一个词,而只是对所有 ,通过网络将目标向量 复制到 。以下一个目标变量本身为条件去定义下一个目标向量,即从 中预测 , 显然是不对的。因此,单向自注意力架构允许我们定义一个 因果的 概率分布,这对有效建模下一个目标向量的条件分布而言是必要的。

太棒了!现在我们可以转到连接编码器和解码器的层 - 交叉注意力 机制!

交叉注意层将两个向量序列作为输入: 单向自注意层的输出 和编码器的输出 。与自注意力层一样, query 向量 是上一层输出向量 的投影。而 key 和 value 向量 、 是编码器输出向量 的投影。定义完 key 、value 和 query 向量后,将 query 向量 与 所有 key 向量进行比较,并用各自的得分对相应的 value 向量进行加权求和。这个过程与 双向 自注意力对所有 求 是一样的。交叉注意力可以概括如下:

注意,key 和 value 向量的索引范围是 ,对应于编码器输入向量的数目。

我们用上例中输入向量 来图解一下交叉注意力机制。

232ae52a-0c1f-11ee-962d-dac502259ad0.png

我们可以看到 query 向量 (紫色)源自 (红色),因此其依赖于单词 "Ich" 的向量表征。然后将 query 向量 与对应的 key 向量 (黄色)进行比较,这里的 key 向量对应于编码器对其输入 = "I want to buy a car EOS" 的上下文相关向量表征。这将 "Ich" 的向量表征与所有编码器输入向量直接关联起来。最后,将注意力权重乘以 value 向量 (青绿色)并加上输入向量 最终得到输出向量 (深红色)。

所以,直观而言,到底发生了什么?每个输出向量 是由所有从编码器来的 value 向量( )的加权和与输入向量本身 相加而得(参见上图所示的公式)。其关键思想是: 来自解码器的 的 query 投影与 来自编码器的 越相关,其对应的 对输出的影响越大。

酷!现在我们可以看到这种架构的每个输出向量 取决于其来自编码器的输入向量 及其自身的输入向量 。这里有一个重要的点,在该架构中,虽然输出向量 依赖来自编码器的输入向量 ,但其完全独立于该向量的数量 。所有生成 key 向量 和 value 向量 的投影矩阵 和 都是与 无关的,所有 共享同一个投影矩阵。且对每个 ,所有 value 向量 被加权求和至一个向量。至此,关于为什么基于 transformer 的解码器没有远程依赖问题而基于 RNN 的解码器有这一问题的答案已经很显然了。因为每个解码器 logit 向量 直接 依赖于每个编码后的输出向量,因此比较第一个编码输出向量和最后一个解码器 logit 向量只需一次操作,而不像 RNN 需要很多次。

总而言之,单向自注意力层负责基于当前及之前的所有解码器输入向量建模每个输出向量,而交叉注意力层则负责进一步基于编码器的所有输入向量建模每个输出向量。

为了验证我们对该理论的理解,我们继续上面编码器部分的代码,完成解码器部分。

词嵌入矩阵 为每个输入词提供唯一的 上下文无关 向量表示。这个矩阵通常也被用作 “LM 头”,此时 “LM 头”可以很好地完成“编码向量到 logit” 的映射。

与编码器部分一样,本文不会详细解释前馈层在基于 transformer 的模型中的作用。Yun 等 (2017) 的工作认为前馈层对于将每个上下文相关向量 映射到所需的输出空间至关重要,仅靠自注意力层无法完成。这里应该注意,每个输出词元 对应的前馈层是相同的。有关更多详细信息,建议读者阅读论文。

fromtransformersimportMarianMTModel,MarianTokenizer
importtorch

tokenizer=MarianTokenizer.from_pretrained("Helsinki-NLP/opus-mt-en-de")
model=MarianMTModel.from_pretrained("Helsinki-NLP/opus-mt-en-de")
embeddings=model.get_input_embeddings()

#createtokenidsforencoderinput
input_ids=tokenizer("Iwanttobuyacar",return_tensors="pt").input_ids

#passinputtokenidstoencoder
encoder_output_vectors=model.base_model.encoder(input_ids,return_dict=True).last_hidden_state

#createtokenidsfordecoderinput
decoder_input_ids=tokenizer("Ichwillein",return_tensors="pt",add_special_tokens=False).input_ids

#passdecoderinputidsandencodedinputvectorstodecoder
decoder_output_vectors=model.base_model.decoder(decoder_input_ids,encoder_hidden_states=encoder_output_vectors).last_hidden_state

#deriveembeddingsbymultiplyingdecoderoutputswithembeddingweights
lm_logits=torch.nn.functional.linear(decoder_output_vectors,embeddings.weight,bias=model.final_logits_bias)

#changethedecoderinputslightly
decoder_input_ids_perturbed=tokenizer("Ichwilldas",return_tensors="pt",add_special_tokens=False).input_ids
decoder_output_vectors_perturbed=model.base_model.decoder(decoder_input_ids_perturbed,encoder_hidden_states=encoder_output_vectors).last_hidden_state
lm_logits_perturbed=torch.nn.functional.linear(decoder_output_vectors_perturbed,embeddings.weight,bias=model.final_logits_bias)

#compareshapeandencodingoffirstvector
print(f"Shapeofdecoderinputvectors{embeddings(decoder_input_ids).shape}.Shapeofdecoderlogits{lm_logits.shape}")

#comparevaluesofwordembeddingof"I"forinput_idsandperturbedinput_ids
print("Isencodingfor`Ich`equaltoitsperturbedversion?:",torch.allclose(lm_logits[0,0],lm_logits_perturbed[0,0],atol=1e-3))

输出:

Shapeofdecoderinputvectorstorch.Size([1,5,512]).Shapeofdecoderlogitstorch.Size([1,5,58101])
Isencodingfor`Ich`equaltoitsperturbedversion?:True

我们首先比较解码器词嵌入层的输出维度 embeddings(decoder_input_ids) (对应于 ,这里 对应于 BOS 且 "Ich will das" 被分为 4 个词) 和 lm_logits (对应于 ) 的维度。此外,我们还通过解码器将单词序列 “ Ich will ein” 和其轻微改编版 “ Ich will das” 与 encoder_output_vectors 一起传递给解码器,以检查对应于 “Ich” 的第二个 lm_logit 在仅改变输入序列中的最后一个单词 (“ein” -> “das”) 时是否会有所不同。

正如预期的那样,解码器输入词嵌入和 lm_logits 的输出, 即 和 的最后一个维度不同。虽然序列长度相同 (=5),但解码器输入词嵌入的维度对应于 model.config.hidden_size,而 lm_logit 的维数对应于词汇表大小 model.config.vocab_size。其次,可以注意到,当将最后一个单词从 “ein” 变为 “das”, 的输出向量的值不变。鉴于我们已经理解了单向自注意力,这就不足为奇了。

最后一点, 自回归 模型,如 GPT2,与删除了交叉注意力层的 基于 transformer 的解码器模型架构是相同的,因为纯自回归模型不依赖任何编码器的输出。因此,自回归模型本质上与 自编码 模型相同,只是用单向注意力代替了双向注意力。这些模型还可以在大量开放域文本数据上进行预训练,以在自然语言生成 (NLG) 任务中表现出令人印象深刻的性能。在 Radford 等 (2019) 的工作中,作者表明预训练的 GPT2 模型无需太多微调即可在多种 NLG 任务上取得达到 SOTA 或接近 SOTA 的结果。你可以在 此处 获取所有 transformers 支持的 自回归 模型的信息。

好了!至此,你应该已经很好地理解了 基于 transforemr 的编码器-解码器模型以及如何在 transformers 库中使用它们。

非常感谢 Victor Sanh、Sasha Rush、Sam Shleifer、Oliver Åstrand、Ted Moskovitz 和 Kristian Kyvik 提供的宝贵反馈。

附录

如上所述,以下代码片段展示了如何为 基于 transformer 的编码器-解码器模型编写一个简单的生成方法。在这里,我们使用 torch.argmax 实现了一个简单的 贪心 解码法来对目标向量进行采样。

fromtransformersimportMarianMTModel,MarianTokenizer
importtorch

tokenizer=MarianTokenizer.from_pretrained("Helsinki-NLP/opus-mt-en-de")
model=MarianMTModel.from_pretrained("Helsinki-NLP/opus-mt-en-de")

#createidsofencodedinputvectors
input_ids=tokenizer("Iwanttobuyacar",return_tensors="pt").input_ids

#createBOStoken
decoder_input_ids=tokenizer("",add_special_tokens=False,return_tensors="pt").input_ids

assertdecoder_input_ids[0,0].item()==model.config.decoder_start_token_id,"`decoder_input_ids`shouldcorrespondto`model.config.decoder_start_token_id`"

#STEP1

#passinput_idstoencoderandtodecoderandpassBOStokentodecodertoretrievefirstlogit
outputs=model(input_ids,decoder_input_ids=decoder_input_ids,return_dict=True)

#getencodedsequence
encoded_sequence=(outputs.encoder_last_hidden_state,)
#getlogits
lm_logits=outputs.logits

#samplelasttokenwithhighestprob
next_decoder_input_ids=torch.argmax(lm_logits[:,-1:],axis=-1)

#concat
decoder_input_ids=torch.cat([decoder_input_ids,next_decoder_input_ids],axis=-1)

#STEP2

#reuseencoded_inputsandpassBOS+"Ich"todecodertosecondlogit
lm_logits=model(None,encoder_outputs=encoded_sequence,decoder_input_ids=decoder_input_ids,return_dict=True).logits

#samplelasttokenwithhighestprobagain
next_decoder_input_ids=torch.argmax(lm_logits[:,-1:],axis=-1)

#concatagain
decoder_input_ids=torch.cat([decoder_input_ids,next_decoder_input_ids],axis=-1)

#STEP3
lm_logits=model(None,encoder_outputs=encoded_sequence,decoder_input_ids=decoder_input_ids,return_dict=True).logits
next_decoder_input_ids=torch.argmax(lm_logits[:,-1:],axis=-1)
decoder_input_ids=torch.cat([decoder_input_ids,next_decoder_input_ids],axis=-1)

#let'sseewhatwehavegeneratedsofar!
print(f"Generatedsofar:{tokenizer.decode(decoder_input_ids[0],skip_special_tokens=True)}")

#Thiscanbewritteninaloopaswell.

输出:

Generatedsofar:Ichwillein

在这个示例代码中,我们准确地展示了正文中描述的内容。我们在输入 “I want to buy a car” 前面加上 ,然后一起传给编码器-解码器模型,并对第一个 logit (对应代码中第一次出现 lm_logits 的部分) 进行采样。这里,我们的采样策略很简单: 贪心地选择概率最高的词作为下一个解码器输入向量。然后,我们以自回归方式将采样得的解码器输入向量与先前的输入一起传递给编码器-解码器模型并再次采样。重复 3 次后,该模型生成了 “Ich will ein”。结果没问题,开了个好头。
责任编辑:彭菁

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编码器
    +关注

    关注

    41

    文章

    3360

    浏览量

    131537
  • 模型
    +关注

    关注

    1

    文章

    2704

    浏览量

    47686
  • rnn
    rnn
    +关注

    关注

    0

    文章

    67

    浏览量

    6803

原文标题:解码器 | 基于 Transformers 的编码器-解码器模型

文章出处:【微信号:zenRRan,微信公众号:深度学习自然语言处理】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    讨论一种用于TD-SCDMA (3GPP TDD LCR)技术的硬件Turbo解码器浮点模型

    TD-SCDMA Turbo解码器设计本文讨论一种用于TD-SCDMA (3GPP TDD LCR) 技术的硬件Turbo 解码器浮点模型,阐述了该模型的设计、架构和优化。这种浮点
    发表于 07-29 08:31

    如何使用PSoC Creator中的块构建NRZI解码器编码器

    您好!如何使用PSoC Creator中的块构建NRZI解码器编码器。当做
    发表于 09-10 09:26

    怎么理解真正的编码器解码器

      在进入关于编码器解码器的现实之前,让我们对复用进行简要的思考。通常我们会在需要将一些输入信号一次一个地加载到一个单独负载的应用程序中。选择输入信号中的一个输入信号的过程称为多路复用。这种操作
    发表于 09-01 17:48

    编码器解码器的区别是什么,编码器用软件还是硬件好

    编码器指的是对视频信号进行压缩,解码器主要是将压缩的视频信号进行解压缩。目前做直播的很多都是采用的编码器,客户端可以采用解码器或软件播放器解码
    发表于 08-02 17:23 3.4w次阅读

    基于结构感知的双编码器解码器模型

    ,在常见端到端翻译模型的基础上,利用代码抽象语法树将源代码的结构信息嵌入到编码器解码器翻译模型中,提出一种基于结构感知的双编码器
    发表于 05-26 15:38 5次下载

    PyTorch教程10.6之编码器-解码器架构

    电子发烧友网站提供《PyTorch教程10.6之编码器-解码器架构.pdf》资料免费下载
    发表于 06-05 18:12 0次下载
    PyTorch教程10.6之<b class='flag-5'>编码器</b>-<b class='flag-5'>解码器</b>架构

    PyTorch教程-10.6. 编码器-解码器架构

    -解码器架构(图 10.6.1),它由两个主要组件组成:一个 编码器,它以可变长度序列作为输入,以及一个 解码器,作为一个条件语言模型,接收编码
    的头像 发表于 06-05 15:44 575次阅读
    PyTorch教程-10.6. <b class='flag-5'>编码器</b>-<b class='flag-5'>解码器</b>架构

    基于transformer的编码器-解码器模型的工作原理

    与基于 RNN 的编码器-解码器模型类似,基于 transformer 的编码器-解码器模型由一
    发表于 06-11 14:17 1313次阅读
    基于transformer的<b class='flag-5'>编码器</b>-<b class='flag-5'>解码器</b><b class='flag-5'>模型</b>的工作原理

    基于 Transformers 的编码器-解码器模型

    基于 transformer 的编码器-解码器模型是 表征学习 和 模型架构 这两个领域多年研究成果的结晶。本文简要介绍了神经
    的头像 发表于 06-16 16:53 508次阅读
    基于 Transformers 的<b class='flag-5'>编码器</b>-<b class='flag-5'>解码器</b><b class='flag-5'>模型</b>

    详解编码器解码器电路

    编码器解码器是组合逻辑电路,在其中,主要借助布尔代数实现组合逻辑。今天就大家了解一下编码器解码器电路,分别从定义,工作原理,应用,真值表几个方面讲述一下。
    的头像 发表于 07-14 09:07 1783次阅读
    详解<b class='flag-5'>编码器</b>和<b class='flag-5'>解码器</b>电路

    视频编码器解码器的应用方案

    视频解码器和视频编码器在数字通讯、音视频压缩领域有着广泛的应用。视频编码器作为视频源的发送端,若接收端如果是 PC 机或显示设备就需要通过解码器进行
    的头像 发表于 08-14 14:38 898次阅读
    视频<b class='flag-5'>编码器</b>与<b class='flag-5'>解码器</b>的应用方案

    YXC丨视频编码器解码器的应用方案

    视频解码器和视频编码器是数字信号处理中常用的设备,它们在数据的传输和转换中发挥着重要作用。
    的头像 发表于 08-23 09:40 402次阅读
    YXC丨视频<b class='flag-5'>编码器</b>与<b class='flag-5'>解码器</b>的应用方案

    视频编码器解码器的应用方案

    视频解码器和视频编码器是数字信号处理中常用的设备,它们在数据的传输和转换中发挥着重要作用。
    的头像 发表于 08-28 11:31 352次阅读
    视频<b class='flag-5'>编码器</b>与<b class='flag-5'>解码器</b>的应用方案

    LDPC编码器解码器产品简介(v2.0)

    电子发烧友网站提供《LDPC编码器解码器产品简介(v2.0).pdf》资料免费下载
    发表于 09-13 15:21 2次下载
    LDPC<b class='flag-5'>编码器</b><b class='flag-5'>解码器</b>产品简介(v2.0)

    编码器好坏怎么判断,编码器原理

    (Autoencoder),它是一种无监督学习的神经网络模型。自动编码器由两部分组成:编码器解码器
    的头像 发表于 01-23 10:58 636次阅读