全书导航
大模型之路:从图灵、感知机到 ChatGPT · 卷 3

第 20 章:循环网络:机器如何记住一句话

本章问题:模型如何处理序列?


CNN 特别适合图像——空间结构固定的输入。但语言是变长序列,每个词的理解依赖前面所有词。这一章引入循环连接,让网络拥有记忆。

20.1 网络也需要记忆

前两章的网络——多层感知机和 CNN——做的是同一件事:给定一个固定大小的输入,立刻产出输出。图片 → 类别。

但语言不是这样的。

"我今天早上去超市买了牛奶,但是忘记带会员卡了,所以——"

这句话的收尾取决于前面十几个字的完整语境。你不能只把最后一个字"以"喂进网络就让网络猜下一句。网络需要记住之前所有字的内容。

序列就是这样的:前一个、前两个、前十个时刻的信息对当前和未来的输出都可能是关键的。你读一个句子时,你的大脑自动地在维持一句话的"状态"——主语是谁?谓语在什么时候出现的?有没有出现过否定词?这个状态在每一个新词到来时被更新。

循环神经网络(RNN)的设计就是为了让网络能够做这件事。


20.2 一个最简单的 RNN

在最基本的 RNN 中,每个时刻的计算很简单:

h_t = tanh(W_xh × x_t + W_hh × h_{t-1} + b_h)

x_t 是当前时刻的输入(比如当前词的向量表示),h_{t-1} 是上一时刻的隐藏状态。两个矩阵——W_xh 处理"新输入"的贡献,W_hh 处理"过去状态"的贡献——分别加权后相加,通过 tanh 激活函数压到 -1 到 +1 之间。

这句公式在说什么?

新状态 = 当前输入(被处理过) + 过去状态(被处理过)——两者混合,形成新的"记忆"。

在最朴素的 RNN 里,每一步的隐藏状态 h 就是网络对"这个句子到目前为止的语义记忆"。读到第一个词时,h 包含了关于"我"的一些信息。读到"买了牛奶",h 里积累了主语+动词+宾语的一些摘要。读到"但是"——h 需要切换语义方向,表示后面有反转。

这个"状态-更新"机制让同一个 RNN 可以处理任意长度的序列——五字的句子、五十字的句子、五百字的段落——参数量不随序列长度增长。同一组权重在每个时刻重复使用——和 CNN 的"权重共享"有相似的数学优雅性。


20.3 训练一个字符级 RNN

一个理解 RNN 直觉的好办法就是训练一个字符级语言模型

任务很简单:给模型看大量英文文本(比如所有的莎士比亚剧本),每个字符作为刻度的输入。目标是在每一个时刻预测下一个字符。

比如:"Hello" 变成训练样本:

输入 H → 应该输出 e输入 e → 应该输出 l输入 l → 应该输出 l输入 l → 应该输出 o

RNN 每次读入一个字符,更新其隐藏状态。然后用隐藏状态去预测下一个字符。一开始它只能生成随机乱码。训练几百步后,它会生成看起来像"单词"的字符串。几千步后,开始生成"像句子"的序列。再长时间的训练后——开始出现语法碎片。

神奇的地方在于:RNN 的隐藏状态里没有任何显式的"主语""谓语""语法规则"存储——这些全是在预测任务中被隐式编码进 h 的。


20.4 消失的梯度:RNN 的致命伤

但 RNN 有一个被反复折磨的问题。

在一个 100 步的序列中,最早期的词(比如第 1 步的"我")需要对第 100 步的预测产生贡献。这意味着梯度必须从第 100 步往回传到第 1 步——经过 100 次链式法则的乘法。

如果每一步的梯度都小于 1,梯度在反向传播 100 步后会指数级衰减。这就是梯度消失——第 1 步的参数几乎收不到任何有效的训练信号。它不会学会去关注早期词语——因为早期词语的"贡献"信号已经衰减到接近零。

如果每一步的梯度都大于 1,梯度在反向传播 100 步后指数级爆炸——这虽然不会导致学习停滞(有梯度裁剪技术来应对),但会直接跳过最优参数区。

这个困难在 1990 年代初就被清楚地指出了。短 RNN 能学好句子的开头与结尾间的依赖关系,但面对 30-50 步的超长依赖——比如句子开头的主语在 50 个词之后才被需要的语义约束——朴素的 RNN 学不到。


20.5 LSTM:学会"忘记"和"记住"

1997 年,Hochreiter 和 Schmidhuber 发表了长短期记忆网络(LSTM)。LSTM 的核心创新是——给网络增加的不仅是一个隐藏状态,还有一个细胞状态。同时有三个"门"控制信息的流动。

遗忘门:基于当前输入和上一步的隐藏状态,决定细胞状态中的哪些旧信息应该被丢掉。"读到句号?主语信息可以适度衰减了。"

输入门:基于当前输入和上一步的隐藏状态,决定当前新信息中的哪些应该被写入细胞状态。"读到新的主语?把旧主语替换掉。"

输出门:基于细胞状态中的当前内容,决定这一时刻应该输出什么信息到隐藏状态中。

门的本质是一个 sigmoid 激活——输出在 0 到 1 之间——然后和要控制的信号逐元素相乘。sigmoid 输出接近 0 表示"把这段信息几乎全关掉";接近 1 表示"把这段信息完全通过"。

加上这些门之后,LSTM 能在一条长序列中维持一个相对稳定且可控的信息流。梯度在回传时不再被迫经过 100 次 tanh 乘法——它可以沿细胞状态的路径直通回去(没有激活函数的乘法恒等映射——在下面的 GRU 小节会更清晰地看到)。早期词语的信号不再指数级地消失。

在实际应用中,到 2010 年代中期,LSTM 已经主导了几乎所有序列问题——语音识别、机器翻译、语言建模、视频字幕生成。它是过去十年序列处理的绝对主力。


20.6 GRU:精简版的 LSTM

2014 年,Cho 等人提出了门控循环单元(GRU)。GRU 把 LSTM 简化了:它合并了隐藏状态和细胞状态(只有一个状态 h),把三个门精简为两个门——重置门(决定在产生新记忆时,应该抛弃多少旧状态)和更新门(决定旧状态保留多少、新状态采纳多少——本质上融合了遗忘门和输入门的功能)。

GRU 的参数量比 LSTM 少了约三分之一,在许多中小规模任务上效果相近。在 Transformer 出现之前,GRU 是机器翻译和语言建模中的常客。

LSTM 和 GRU 的共同思想都是在状态更新中引入可学习的门,让模型能自己决定——这条信息该存多久,该什么时候丢掉。这是从"固定记忆"到"可学习记忆"的关键一步。


20.7 本章小实验:人脑 RNN

读下面这句话,逐字遮住右边的部分:

"张三说他喜欢吃苹果,但是"

在读到"但是"之前,你的大脑里维持着什么信息?

再来一次,同样逐字遮住: "张三说他喜欢吃苹果,但是李四觉得橘子更好,所以王五就买了两种水果。后来张三"

在读到"后来张三"时,你的大脑维持的信息和第一次读到"但是"时有什么不同?

你刚才在脑子里进行的,就是一个 RNN 在更新其隐藏状态的过程。第一次——短句,"但是"之后你的大脑只维持了一个简单状态:主人公有反转意愿。第二次——长句、多个角色、多个转折——读到"后来张三"时你需要把李四、王五、张三三个人的主体和各自的行为从整个段落的记忆里调出来。

这就是序列长度的诅咒。大脑可以处理 50 个词内的依赖没有问题,RNN 却会在训练中遇到梯度消失。LSTM 能帮到 100-200 步,但面对 500 步的超长依赖——仍然吃力。

这就引出了一个问题:有没有方法让网络不需要"一步步滑动时间"就能同时看到所有词之间的关系?

有。它叫 Attention。那是 Transformer 的前奏——下一章我们从 Seq2Seq 开始介入这条线。


20.8 本章地图

text
问题:模型如何处理序列?方法:RNN 在每个时刻接收"当前输入 + 上一时刻的隐藏状态"来计算当前状态——相当于一个带记忆的循环单元。升级:LSTM(1997)引入细胞状态和三个门(遗忘/输入/输出),解决了朴素 RNN 在长序列上的梯度消失问题。GRU(2014)提供了一个更简洁的门控变体。局限:RNN/LSTM 的本质是逐时间步的顺序计算——在长序列上训练和推理都慢,且面对极长依赖时门控也无法完全解决。今天:序列处理的任务已很大程度上让位给 Transformer,但 LSTM 的基本思想——可学习的记忆门控——影响了很多后续模型的设计。

20.9 本章结语:时间是网络的第一根锁链

RNN 的核心洞见是给它一个状态——一个可以随时间步更新的记忆槽。这个想法优美而自然——语言、语音、视频的序列属性天然匹配这种设计。

但它也带着结构固有的一组弱点:训练时梯度消失,推理时无法并行。这些弱点在面临真正大规模的序列处理时变得不可忽视。

不过在 Transformer 出现之前,RNN 和 LSTM 还和另一个组件共同组成了当时的主流机器翻译架构。在下一章,我们会看到 Word2Vec——词是怎样从离散符号变成连续向量空间的居民——以及 Seq2Seq——编码器-解码器结构是怎样让语言模型把一个句子变形成另一个句子的。

那将是我们通向 Transformer 的最后几级台阶。

SECTION §02 · ENGAGE

Discussion

留言区 · GitHub-powered comments via Giscus