1. 前言
上一篇我们已经进入了机器翻译这一部分,也搞清楚了几个关键点:
机器翻译是一个序列到序列任务
数据通常是平行语料
输入是源语言句子,输出是目标语言句子
两边通常要分别建词表,还要处理
<bos>、<eos>、<pad>等特殊符号
但新的问题立刻就来了:
既然输入序列和输出序列不是同一种语言,也不一定等长,那模型该怎么设计?
这就引出了序列到序列学习里最经典的基础框架:
编码器-解码器(Encoder-Decoder)架构
它的核心思想非常直观:
先把输入序列“读懂并压缩”成一个中间表示,再根据这个表示一步步生成输出序列。
这套思想不仅用于机器翻译,后面很多任务也都能看到它的影子。
2. 为什么机器翻译不能直接套普通语言模型
先回忆一下前面的语言模型。
语言模型做的是:
根据前文预测下一个 token
例如:
输入前几个字符/词
输出下一个字符/词
它的输入和输出,本质上都在同一个序列里。
但机器翻译不一样。
例如:
输入:
i love deep learning输出:
我 喜欢 深度 学习
这里有几个明显不同:
2.1 输入和输出属于不同语言
源句和目标句不是同一 token 空间。
2.2 输入输出长度不固定
一个英文句子翻成中文后,长度可能变长,也可能变短。
2.3 输出不是“输入右移一位”
语言模型标签可以直接由输入右移构造,
而机器翻译完全不是这个逻辑。
所以机器翻译必须有一种新的建模方式:
让模型先理解输入,再生成输出。
这正是编码器-解码器架构要解决的问题。
3. 什么是编码器-解码器架构
编码器-解码器架构可以简单理解为两部分:
编码器(Encoder)
解码器(Decoder)
它们的分工非常明确。
3.1 编码器
负责读取输入序列,并把它转换成一种内部表示。
例如在机器翻译里:
输入英文句子
编码器把整句英文“看完”
形成一个中间语义表示
3.2 解码器
负责根据这个中间表示,逐步生成目标序列。
例如:
根据编码器给出的表示
一个词一个词地生成中文翻译
所以这套架构本质上是在做:
输入序列理解 → 中间表示 → 输出序列生成
4. 为什么“编码”和“解码”这个说法很贴切
这个名字其实非常形象。
编码(Encode)
就是把原始输入序列的信息,压缩进模型内部的某种表示里。
有点像:
读完一句话后,脑子里形成了对这句话的理解
解码(Decode)
就是从这个内部表示出发,逐步还原出目标序列。
有点像:
你已经理解了英文句子的意思
再把它用中文表达出来
所以“编码器-解码器”这个名字并不是抽象术语,
而是非常符合任务直觉的。
5. 机器翻译中的编码器在做什么
在机器翻译里,编码器的任务可以概括成一句话:
把源语言句子变成一个对后续翻译有用的表示。
例如源句子是:
i love deep learning编码器会按顺序读入这些词,
并通过循环神经网络等结构,把它们的信息不断累积。
最终得到的可以是:
最后一个隐藏状态
一组中间隐藏状态
或更一般的上下文表示
在最基础的 Seq2Seq 思想里,常常先把这个结果看成:
源句子的语义摘要
6. 机器翻译中的解码器在做什么
解码器的任务则是:
根据编码器给出的表示,逐步生成目标语言序列。
例如目标句子应该是:
我 喜欢 深度 学习解码器通常不是一次把整个句子全吐出来,
而是按时间步生成:
第一个目标词
第二个目标词
第三个目标词
…
直到生成
<eos>
也就是说,解码器本质上也是一个序列模型,
只不过它生成序列时会依赖:
编码器提供的上下文
已经生成的前面目标词
7. 编码器-解码器最核心的思想是什么
如果只抓一句最重要的话,那就是:
把“读输入”和“写输出”分开。
这比把所有事情塞进一个网络里要清晰得多。
编码器负责“读懂输入”
输入序列可以长短不一,但最终会变成内部表示。
解码器负责“生成输出”
输出序列也可以长短不一,并且逐步生成。
这使得模型天然适合处理:
输入输出长度不同
输入输出模态不同
甚至输入输出语言不同
所以这套架构非常通用。
8. 编码器-解码器为什么特别适合序列到序列任务
因为序列到序列任务的本质就是:
给定一个输入序列,输出另一个序列。
例如:
机器翻译:英文句子 → 中文句子
文本摘要:长文本 → 短摘要
对话生成:用户输入 → 回复语句
语音识别:声学特征序列 → 文本序列
这些任务共同的问题是:
输入序列长度不固定
输出序列长度不固定
输入和输出不一定在同一个空间
而编码器-解码器正好天生适合这种映射。
9. 最基础的编码器通常怎么实现
在李沐这条线里,最基础的编码器通常可以先用:
RNN / GRU / LSTM
来实现。
例如:
把源句子 token 一个个输入编码器
编码器不断更新隐藏状态
最后把最终隐藏状态看作整个句子的表示
所以最初级的编码器其实可以理解成:
一个负责“读完整句”的循环网络
它读完以后,把“我已经理解了这句话”的结果交给解码器。
10. 最基础的解码器通常怎么实现
解码器通常也可以用:
RNN / GRU / LSTM
来实现。
不过和编码器不同的是,解码器在生成目标词时,通常要依赖三类信息:
10.1 编码器提供的上下文表示
这是源句子的“语义基础”。
10.2 上一时刻的隐藏状态
这是解码器自己的历史记忆。
10.3 上一时刻输出的目标 token
因为生成是逐步进行的,当前词通常依赖前面已经生成的词。
所以解码器本质上是一个:
带条件约束的生成模型
11. 为什么解码器生成时常常要用<bos>
目标句子生成不能凭空开始,所以通常要给解码器一个起始标志:
<bos>这表示:
现在开始生成目标序列。
例如机器翻译时:
第一步输入
<bos>解码器预测第一个目标词
第二步再把第一个词作为下一步输入
继续生成后续词
直到输出
<eos>
所以<bos>的作用就是:
生成的起点
12. 为什么解码器可以逐步生成句子
因为它本质上仍然是一个序列模型。
在时间步t,解码器会结合:
之前生成的内容
当前内部状态
编码器提供的上下文
来预测当前位置应该输出什么。
所以整个过程非常像语言模型,
只不过它不是“纯靠自己续写”,而是:
在源句语义的条件下生成目标句。
这也是为什么大家常说:
解码器像“有条件的语言模型”
13. 编码器-解码器最基础的 Seq2Seq 直觉
在最经典、最基础的 Seq2Seq 思想里,我们常常会这样理解:
编码器
把整个源句压缩成一个上下文向量。
解码器
只依赖这个上下文向量,再一步步生成目标句。
这种想法很优雅,也非常具有启发性,
因为它第一次把“序列到序列映射”系统化了。
但它也埋下了一个问题:
如果输入句子很长,只靠一个固定长度上下文向量,真的装得下全部信息吗?
这也是后面注意力机制会登场的原因。
不过在当前这一节,先把基础框架理解清楚最重要。
14. 编码器-解码器中的“上下文”是什么
“上下文(context)”在这里通常指:
编码器传递给解码器的中间语义表示
它可以是:
编码器最后时刻的隐藏状态
多层状态组合
更一般的编码结果
在最基础版本里,我们常把它简化理解成:
源句子的压缩语义摘要
解码器就是基于这个摘要去翻译。
15. 为什么说编码器-解码器是一种通用框架
因为它并不依赖“机器翻译”这一个任务本身。
它更像是一种任务组织方式:
先把输入序列编码,再把输出序列解码。
所以只要任务符合“输入序列 → 输出序列”的形式,
它就有可能用这套框架。
例如:
机器翻译
摘要生成
对话生成
语音到文本
图像描述(图像编码 + 文本解码)
所以编码器-解码器的意义,不只是某个具体模型,
而是一种非常有影响力的建模思想。
16. 李沐这一节最想让你理解什么
这一节的重点,不是立刻去推复杂公式,
而是帮你建立这样一条主线:
第一,机器翻译需要序列到序列建模
不是简单语言模型。
第二,最自然的办法就是把输入处理和输出生成分开
这就是编码器和解码器的分工。
第三,编码器负责“读懂”
解码器负责“表达”。
第四,这套框架非常通用
不只属于机器翻译。
所以这一节本质上是在帮你完成:
从“循环网络单元”到“完整序列到序列系统”的升级。
17. 编码器-解码器和前面内容怎么接起来
这条线其实是非常顺的。
前面学语言模型
知道了如何逐步生成 token。
前面学 RNN/GRU/LSTM
知道了如何建模序列和状态递推。
前面学机器翻译数据集
知道了任务是“源句子 → 目标句子”。
到这一节
终于把这些东西组装起来:
编码器读源句
解码器写目标句
所以编码器-解码器并不是横空出世的新概念,
而是前面一大串知识拼装后的自然结果。
18. 本节总结
这一节我们学习了编码器-解码器架构,核心内容可以总结为以下几点。
18.1 编码器-解码器是典型的序列到序列建模框架
特别适合机器翻译这类任务。
18.2 编码器负责把输入序列转成内部表示
可以理解为对源句子的语义编码。
18.3 解码器负责根据编码结果逐步生成输出序列
它通常是条件化的序列生成模型。
18.4 这套框架不只适用于机器翻译
很多输入序列到输出序列的任务都能套用。
18.5 这一节是在为后面的具体实现和注意力机制铺路
先有基础框架,后面才能继续增强。
19. 学习感悟
这一节特别关键,因为它让序列建模第一次真正从“单条序列理解”走向了“序列到序列转换”。
以前我们学的更多是:
预测下一个 token
处理一个序列本身
而编码器-解码器开始回答更复杂的问题:
如何把一种表达方式,转换成另一种表达方式。
这已经非常接近“真正的智能任务”了。
因为翻译、摘要、问答,本质上都不是单纯记忆,而是:
理解输入,再组织输出。
从这个角度看,编码器-解码器是序列建模里一个非常重要的里程碑。