序列到序列与编码器-解码器
两个循环网络如何串起来,把一个序列变成另一个序列——神经网络机器翻译背后的架构,以及那个引出了注意力的瓶颈。
给一个句子分类,得到的是一个标签。但翻译、摘要和问答要难得多:输入一个序列,输出一个不同的序列——长度不同、语言也可能不同。2014 年提出的序列到序列(sequence-to-sequence)架构,就是让神经网络做成这件事的设计,它同时引入了「编码器-解码器」这套拆分,直到今天的模型里还在用。
两个网络,一次交接
编码器一次一个 token 地读完整条输入序列,边读边更新自己的隐藏状态。读到最后时,它最终的隐藏状态就是一个固定大小的向量,用来概括整段输入。这个向量叫上下文向量,也叫思想向量。
解码器是第二个 RNN,用那个上下文向量来初始化。它一次一个 token 地生成输出序列,每产出一个词就把它作为下一步的输入喂回去,直到输出一个特殊的序列结束 token。
把 "the cat sat" 翻成法语:
- 编码器依次读
the、cat、sat,把这三个词压成一个上下文向量。 - 解码器拿过这个向量,先输出
le,再输出chat、s'est、assis,然后停止。
两个网络端到端一起训练。编码器学会把含义打包进上下文向量;解码器学会把它拆开、变成通顺的输出。
编码器-解码器的拆分为什么重要
把「理解输入」和「产出输出」分开,是一个确实很强的分解方式,也正是这套模式比它诞生时的 RNN 活得更久的原因。
它能自然地处理长度可变、输入输出不等长的情况。一个 5 个词的英文句子可以变成 8 个词的法语;解码器只管一直生成,直到自己决定停下。词袋模型和普通分类器永远做不到这一点。
它把两种语言解耦。编码器只需理解源语言,解码器只需生成目标语言。换掉解码器,就能在复用同一套理解的前提下切换到新的输出语言。
这种拆分后来到处重现。Transformer 时代的编码器-解码器架构在翻译任务上保留的正是这个结构。就连 GPT 这类 decoder-only 模型,某种意义上也就是把解码器那一半单独拿了出来。
瓶颈
再回头看那次交接。编码器对输入理解到的一切,都必须穿过一个固定大小的上下文向量。整句话——每一个名词、每一个从句、每一层依赖关系——都被挤进比如说 512 个数里。
短句子没问题。长句子就是灾难。一个 40 词的句子和一个 4 词的句子必须压进同一个向量,细节会被压碎。研究者观察到的正是这一点:短输入时翻译质量还不错,句子一长就急剧下滑。这个唯一的上下文向量是一个所有东西都得钻过去的漏斗,而它太窄了。
还有一处明显的浪费。解码器要生成「cat」对应的法语词时,能直接看编码器对「cat」的表示会很有帮助。但它拿不到编码器的中间状态——只有最后那个被挤压过的摘要。编码器算出来的那些按词的信息,在最后一步之后全都被丢掉了。
改变了一切的那个解法
2014 年提出的解法是:别再逼着所有东西都挤进一个向量。与其只把最终那个上下文向量交给解码器,不如让它回头看编码器的全部隐藏状态,并在每一个输出步骤上决定该关注哪些输入词。
要生成「chat」?就关注编码器对「cat」的那个状态。要生成动词?就关注那个动词。解码器每产出一个词,都会重新算一次编码器状态的加权混合。
这套机制就是注意力,它让解码器能直接、有选择地访问整段输入,从而拆掉了瓶颈。它是这一整层里影响最深远的一个想法,在注意力的起源里有专门的一篇。
要点回顾
- Seq2seq 把两个 RNN 串起来:编码器读完输入、压成上下文向量,解码器据此生成输出。
- 它能处理长度可变、输入输出不等长的情况,普通分类器做不到。
- 编码器-解码器的拆分——先理解、再生成——是一套比 RNN 活得更久的模式。
- 缺陷在于:整段输入被挤进一个固定大小的上下文向量,长句子会被压碎。
- 解法是注意力:让解码器回头看编码器的全部状态,每产出一个词就聚焦到相关的那些上。
下一篇: 注意力的起源——让解码器回头看,如何拆掉了瓶颈,并为 transformer 铺好了路。