本节摘要:机器翻译这类"长短不一的输入换长短不一的输出"任务,需要把两个 RNN 拼起来:编码器把源序列压成一个上下文向量,解码器再由它逐词吐出新序列。本节讲清编解码分工、解码时的逐词生成,以及注意力为何应运而生,并落到情感分析等真实 NLP 场景。
4.1 到 4.3 都在讲"一个序列怎么被理解"。本节把镜头拉到更高一层:有些任务不是理解,而是从一段输入生成另一段输出——翻译、摘要、对话皆然。这会牵出一个更通用的骨架:序列到序列(Seq2Seq)。
分类一个句子是正面还是负面,输出只有一个标签;可翻译一个句子,输出是另一串长短不定的词。这类任务用单个 RNN 办不到,于是人们把两个 RNN 焊成机关:编码器按顺序读过源序列,每次更新隐藏状态,把整句的语义慢慢"压"进一个收尾的上下文向量;解码器拿着这个上下文向量当起点,一次吐一个词,并把刚吐出的词再喂回自己,一路滚到句尾标记为止。
编码器读、解码器写,两端通过上下文向量交接。它有个朴素假设:整句的要点都能装进那一个向量里。句子一长,这个"单点收纳"就成了瓶颈——开头的重要信息传到解码器时往往淡了。解决它,正是第5章注意力的出场动机:解码时不再只盯那一个向量,而是回头看一眼源序列的"每个位置",按需取用。
按时间把编解码展开,流向一目了然:
这张图浓缩了两件事:左边是"压缩",右边是"展开"。注意解码器有一条自己喂自己的回环——这正是它"逐词生成"的来源,也是线上推理时最费算力的环节,因为必须等上一个词出来才能算下一个,无法并行。
不追求工程完善,把结构用框架直译出来,重点是看清"编码器输出如何喂给解码器":
import tensorflow as tf # 编码器:读源序列,产出最终状态 enc_in = tf.keras.Input(shape=(None,)) enc = tf.keras.layers.Embedding(src_vocab, 128)(enc_in) enc = tf.keras.layers.LSTM(256, return_state=True) enc_out, h, c = enc(enc) # 解码器:用编码器终态初始化自己的 LSTM 状态 dec_in = tf.keras.Input(shape=(None,)) dec = tf.keras.layers.Embedding(tgt_vocab, 128)(dec_in) dec = tf.keras.layers.LSTM(256, return_sequences=True, initial_state=[h, c]) dec = tf.keras.layers.Dense(tgt_vocab, activation="softmax")(dec) model = tf.keras.Model([enc_in, dec_in], dec) model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
initial_state=[h, c] 那一行就是"上下文携带"的落点:编码器学到的整句压缩,被解码器拿来当起点。骨架秒懂后,你需要补上的只是把源/目标序列 pad 到等长、做掩码这些工程细节。
这一整套结构从机器翻译成长起来,却远远不止翻译:
机器翻译:上文骨架的原型任务,源句长、目标句长,还牵扯词序颠倒,是考验长程建模的硬仗。
文本生成 / 摘要:解码器连句首与句尾做的是"接骨",先进的生成模型对每一步既考虑语言感也考虑主题感的取舍。
情感分析:相对简单——多数是分类(还要看是句子级还是方面级,比如"服务好但价格高"就得分两面看)。用分类的判别力,用 RNN 或直接卷到注意力上。
问答与对话:本质是"上下文 + 问题 → 答案序列"的 Seq2Seq 变体,近距离的下一站就是第5章注意力与 Transformer 的主场。
列这张清单时你会发现一个规律:越是任务复杂、上下文长,RNN 越吃力,注意力越成为刚需。这不只是本节的事,也是整本主线走到第5章的必然。
解码器逐词生成时有个技巧:喂给解码器的"上一步输出",训练阶段用真实的上一词(对教学开局有利),推理阶段才用模型自己刚生成的词。这被称为教师强迫。好处是训练时模型不会因早期一步错而步步错、梯度更稳;坏处是训练与推理的输入分布有差异,学术界称之为"曝光偏差"。工程上的通用做法是训练时以一定概率混入模型自己的输出(计划采样),让两头逐渐对齐,只是要留一手防止训练因此崩溃。
如果说翻译是最典型的 Seq2Seq,那么摘要把"压缩输入"这一面放大了:一段长文压成一个简短摘要,本质上要求模型先"读进全貌"再"择要吐述"。原文越长,越考验长程建模,也越凸显单上下文向量的吃力——注意力在下文登场的一切动机,在摘要这类任务上尤其直观。做情感分析常是"判别式"的轻活,做摘要则是实打实"生成式"的硬仗,两者的差距,正是一扇观察"注意力为何必要"的好窗口。
Seq2Seq 里"长短不一"会在三处出现:输入序列、输出序列、以及 batch 内互相不等长。相应地要处理 pad 与掩码——pad 补零后要让编码器对这些位置不更新、解码器对补上的结束符不惩罚。把这些掩码理清楚,编解码模型才算真正在变长数据上跑得动。这一步看似琐碎,却是从"跑通 toy"到"喂真语料"之间最常卡人的一环。