RNN / LSTM
CNN 擅长"看空间",RNN 擅长"读时间"。凡是"顺序"很重要的数据——文字、语音、股价、心电图——早年都靠 RNN 来处理。它的核心思想很浪漫:让神经网络学会"记住刚才发生了什么"。
你读这句话:"今天早上出门忘带伞,结果被淋成落汤鸡。"
读到"淋成"时,你脑子里其实同时挂着前面的"忘带伞"——你知道正是这件事导致了后面的结果。理解语言,靠的就是"边往下读,边把前面读到的信息挂在心里"。
读音乐、看电影、听别人讲故事——你的大脑始终有一个"上下文缓存",一边读一边更新。RNN 就是把这个"记忆缓存"抄进神经网络里。
什么叫"序列数据",它为什么必须被特殊对待
序列数据(Sequential Data)这个词听着抽象,说白了就是"顺序一改,意思就变"的那种数据。判断一份数据是不是序列,只要做一个测试:把它打乱重排,含义还成立吗?不成立的,就是序列数据。
试几个例子。"我请你吃饭"和"你请我吃饭"——用的字一模一样,但谁掏钱这件事彻底反了。"狗咬人"和"人咬狗"——一个是日常,一个是新闻。心电图上的波形,你把前后两秒的数据对调,医生的诊断可能从"正常"变成"心律失常"。股价曲线,你把一年的日收盘价打乱顺序,K 线图立刻变成一团毫无意义的雪花。这些数据的信息不只藏在"有什么"里,更藏在"什么排在什么前面"里。
反过来,什么数据不是序列?一张照片里的像素基本上不是(虽然有空间结构,但没有"先后");一份体检报告上的各项指标不是——你把"血糖"和"血压"两行调换位置,报告的含义一点没变。
那么用之前学的网络处理序列,会碰上什么麻烦?两个死结。
第一个死结:长度不固定。全连接网络的输入必须是固定长度的——你搭了一个"接受 10 个数字"的网络,就只能喂 10 个数字。但一句话可能 5 个字,也可能 500 个字。生活场景:你去餐厅,服务员只有一种固定尺寸的托盘,客人点 2 个菜也用它、点 20 个菜也用它——点 2 个菜时托盘空一大半(浪费),点 20 个菜时装不下(截断)。你要么给每种长度定做一个托盘(不可能),要么想出一种"能反复往返端菜"的办法。RNN 选的就是后者。
第二个死结:顺序信息会被稀释掉。假设你硬把一句 50 个词的话拉平成一个大向量喂进全连接层,网络确实"看到"了所有词,但它不知道哪个词在前哪个词在后——它只是看到 50 个位置上各有一堆数字,每个位置的权重都是独立学的。这就带来一个荒谬的后果:模型在"第 3 个位置"学到的"不"字的否定含义,完全没法用在"第 30 个位置"的"不"字上——同一个字在不同位置要重新学一遍。这跟 CNN 面临的困境一模一样,只是这次错的维度是时间而不是空间。
RNN 给的答案,本质上是把 CNN 的"权值共享"这一招搬到时间轴上:用同一套权重,反复处理序列的每一个位置。这样既解决了长度不固定(想处理多长就调用多少次),又解决了权重浪费(一套权重通吃所有位置)。
RNN 的核心:一个自带"内存"的神经元
普通神经元每次只看当前输入 x,输出 y。RNN 神经元多了一个"心里的小本本"叫隐藏状态(hidden state)——每次它做计算时,除了看当前的 x,还会看上一步留下的隐藏状态 h。算完这一步,它又把新的 h 传给下一步。
展开来看:处理一个句子时,RNN 会像读书一样"一个词一个词"往前扫,扫到当前词时,它记着到目前为止读过的所有词的"精髓"(浓缩在隐藏状态里)。整个网络在时间维度上像一根链条一样滚动,所以叫循环神经网络。
RNN 像一位会记要点的秘书:会议上人们一句一句发言,秘书不能把所有原话都记下来,只能把"每句话的重点"更新到手里那张越来越浓缩的会议摘要上。等到最后一句话说完,她手里那份摘要就是整场会议的记忆。下次她做判断(比如决策"要不要发通知")时,就靠这份摘要——这正是 RNN 的隐藏状态。
隐藏状态到底是什么:一张越写越浓缩的便签
隐藏状态(hidden state)这个词是理解 RNN 的唯一门槛,翻过去就全通了。说白了它就是一小串数字,用来装"我到现在为止读明白了什么"。它有三个关键特性,我们一个一个说。
特性一:它的长度是固定的。不管你读了 3 个词还是 3000 个词,隐藏状态永远是那么几百个数字(比如 512 维)。这既是它的优点也是它的死穴——优点是无论多长的输入都能被压成同样大小的"摘要",方便后续处理;死穴是装的东西再多,容器也不会变大,早期的信息必然被后来的挤掉。
生活场景:一张只有名片大小的便签。你去听一场两小时的报告,手上只有一张名片大小的纸。开头听到"本季度营收 3.2 亿",你写上去。半小时后听到"华南区增长 40%",纸快满了,你得擦掉一点旧字才能写新的。两小时后,这张纸上剩下的是最后二十分钟的内容加上开头几个你觉得特别重要、坚持没擦掉的数字。这就是隐藏状态的全部工作方式——固定容量的滚动摘要。
特性二:它每一步都被完全重写一遍。RNN 的更新公式只有一行:
【RNN 的全部数学,就这一行】
h_t = tanh( W_x · x_t + W_h · h_(t-1) + b )
↑ ↑ ↑
激活函数 当前输入 上一步的记忆
然后如果这一步要输出:
y_t = W_y · h_t + c
【逐项翻译成大白话】
x_t = 这一步读到的新词
h_(t-1) = 我上一步记下的摘要
W_x = "新读到的词,我该记多少"的旋钮
W_h = "老摘要,我该保留多少"的旋钮
h_t = 更新后的新摘要
tanh = 把结果压到 -1 到 1 之间,防止数字越滚越大
【最关键的一点】
W_x、W_h、b 这三组旋钮,在整个序列的
每一步都是同一套 —— 从不换。
读第 1 个词用它,读第 500 个词还用它。
这就是"权值共享"在时间轴上的版本。
生活场景对应上面这行公式:一位会记要点的秘书。W_h · h_(t-1) 是她重读一遍手上那份旧摘要;W_x · x_t 是她听清了这句新发言;两者相加再过一遍 tanh,就是她把新旧信息揉在一起,重写出一份新摘要。注意"重写"这个词——她不是在旧摘要下面追加一行,而是把旧摘要和新信息一起看过,然后在一张新便签上写下融合后的版本。旧便签写完就撕了。
特性三:它同时承担两个角色。h_t 既是"传给下一步的记忆",又是"这一步用来做判断的依据"。这个双重身份是朴素 RNN 的一个隐患:要用来做当前判断,它就得随时更新以反映最新情况;要用来长期记忆,它就得保持稳定别被冲刷——两个要求互相矛盾。这个矛盾正是后来 LSTM 要解决的核心问题,LSTM 的办法就是把这两个角色分给两条不同的通道。
按时间展开:一条链子其实是一张深网
教材上画 RNN 时通常有两种画法,第一次看会以为是两个不同的东西,其实是同一个。
第一种画法:一个圈。画一个神经元,从它的输出画一根箭头绕回它自己的输入——这叫"循环"图,强调"它只有一套权重、反复调用自己"。
第二种画法:一条横着排开的链。把这个循环按时间摊平——第 1 步一个方块、第 2 步一个方块……第 T 步一个方块,横向连成一串。这叫按时间展开(unrolling / unfolding),说白了就是把"同一个人干了 T 遍"画成"T 个人排成一排各干一遍"。
【循环画法】 【展开画法】
┌──┐ x1 x2 x3 x4
│ ↓ ↓ ↓ ↓ ↓
x →[A]→ y [A] → [A] → [A] → [A]
↑ │ ↓ ↓ ↓ ↓
└──┘ y1 y2 y3 y4
注意:展开图里那 4 个 [A],
权重是完全同一套(不是四套)。
就像一位老师连上四节课 ——
画成课表上四格,但老师只有一个人。
展开这个动作有一个极重要的意义:展开之后你会发现,处理一句 100 个词的话,等于在训练一个 100 层深的网络。这句话请多读一遍——RNN 表面上只有一层,实际深度等于序列长度。所以上一节讲过的所有"深层网络的病",RNN 全都会得,而且得得更重:处理 100 个词,梯度就要连乘 100 次。这直接引出下面两节要讲的长依赖问题。
还有一个实用的推论:为什么长文本训练那么费显存。展开成 100 层之后,反向传播需要保留每一步的中间结果(前面说过训练必须缓存),所以显存占用跟序列长度成正比。这就是为什么早期 RNN 训练常要做"截断反向传播"——只往回追 20 步就停,更早的责任不算了。相当于查责任只查最近一个月的记录,一个月以前的翻不动,认了。
BPTT:把反向传播搬到时间轴上
BPTT(Backpropagation Through Time,时间反向传播——沿着时间轴倒着追责的反向传播),说白了就是把上一节那套"层层问责"的办法,从"层"换成"时刻"来用一遍。原理一点没变,只是这次追的不是"第几层出的错",而是"第几个时刻出的错"。
生活场景:一桌菜咸了,往回查是哪一步放的盐。菜端上来太咸(这是最终损失)。你怎么查?你会顺着做菜的时间线倒着问:最后勾芡时放盐了吗?放了半勺。之前炒的时候放了吗?放了一勺。腌肉的时候放了吗?放了一勺,还加了生抽。洗菜的时候……没放。这样一路倒推,你就知道了每一个时刻各贡献了多少咸味,也就知道下次该在哪一步减多少。BPTT 干的就是这件事,一模一样。
但这里有个 RNN 特有的复杂之处,必须讲清楚:因为每一步用的是同一套权重,所以每一步算出来的梯度要全部累加到同一套旋钮上。也就是说,如果这句话有 100 个词,那么 W_h 这个旋钮会收到 100 份责任报告,全部加起来才是它最终该调的量。
生活场景对应:一个身兼一百个职务的人。假设一家公司出了事故,复盘发现有一百个环节都有问题——而这一百个环节居然全是同一个人负责的(因为权值共享)。于是这一百份责任报告全部落到他一个人头上,累加起来。好处是这个人的改进意愿会非常强(信号强);坏处是如果这一百份报告的意见互相矛盾(有的说要往左调、有的说要往右调),最后加起来可能相互抵消,等于什么都没改。这正是朴素 RNN 训练不稳定的一个重要来源。
顺带把 RNN 的三大典型任务列一下,好有个落地的画面:
- 语言建模给模型一段前文,让它预测下一个词——输入法、机器翻译、文本生成的老祖宗都靠它。
- 语音识别把连续声波切成一小段一小段送进去,网络输出"这一段是哪个音素 / 字"。
- 时序预测股价、气温、心电、机器传感器读数——凡是"过去决定未来"的数据都能训。
RNN 的致命毛病:健忘 & 训练难
朴素 RNN 有个特别现实的问题——它记不住太久远的事。让它读一段 30 个词的短句还行;读一段 300 字的长文,前面的信息早就在反复"传话"中被稀释、覆盖,等到读到末尾时,它已经"忘了"开头讲的是什么。
数学上这叫"梯度消失 / 梯度爆炸":反向传播沿时间轴一层层回溯时,梯度要么衰减到 0(学不到远处的依赖),要么膨胀到爆炸(训练发散)。这个毛病在 90 年代一直阻碍着 RNN 走向实用。
长依赖问题:读到第 50 个词,已经忘了第 1 个词
上面说 RNN 健忘,这里把它讲得更具体。长依赖(long-range dependency)这个词说白了就是"要理解眼前这个词,必须回头用到很远之前的信息"。语言里这种情况多到不行,我们看几个例子。
【例子一:语法一致性】
"那个 在雨里 站了 整整 三个小时 等着 妈妈 来接 的
小男孩, 最后 终于 ____ 了。"
要填对这个空(应该是"哭"或"笑",而且是单个人的动作),
模型必须记住最前面的主语是"小男孩"(单数、人)。
中间隔了 20 多个词。
【例子二:指代】
"小李 把 车 停在 地下车库 B2 层 靠 电梯 的
那个 车位, 结果 晚上 下班 时 发现 ___ 被 刮了。"
这个"它"指的是车,不是车库、不是电梯、不是车位。
要判断对,得记住 20 个词之前的"车"。
【例子三:情感反转】
"这家店的 装修 很漂亮, 服务员 也 热情,
菜品 摆盘 精致, 价格 也 算 公道,
...(省略 40 个词的夸奖)...
但是 我 在 汤 里 发现 了 一根 头发。"
整段话前 50 个词全是好评,最后一句彻底反转。
模型必须能把"但是"之后那一句的权重
压过前面所有的夸奖 —— 这需要极强的长程建模能力。
朴素 RNN 在例子一上勉强能行(20 个词还在它的极限内),在例子三上基本无能为力。研究测出朴素 RNN 的有效记忆长度大约只有 10 到 20 步——超过这个距离,早期信息对当前输出的影响就衰减到可以忽略了。
生活场景:一个只能记住最近三句话的听众。你给他讲一个悬疑故事,开头埋了个伏笔"管家的左手有一道疤",结尾揭晓"凶手左手有疤"。这个听众听到结尾时,早就忘了开头那句话,所以他完全 get 不到这个包袱。你讲得再精彩,他也只是听懂了每一句,没听懂整个故事。这就是朴素 RNN 读长文本的真实状态。
梯度消失在时间维度上:数学原因只有一个乘法
为什么记忆就是撑不过 20 步?答案跟上一节 CNN 那边完全一样,还是连乘两个字。只不过这次乘的次数等于时间步数。
【责任链的样子】
第 50 步的损失,要追责到第 1 步的输入,
梯度必须穿过 49 次"从 h_t 到 h_(t-1)"的传递。
每穿一次,都要乘上两个东西:
① tanh 的导数(最大值 1,通常在 0.1~0.5 之间)
② 权重矩阵 W_h(它的最大奇异值决定放大还是缩小)
简化成一个数 λ,那么:
第 1 步收到的梯度 ≈ 第 50 步的梯度 × λ^49
【代入具体数字看】
λ = 0.5(很常见的情况)
0.5^49 = 0.0000000000000018
→ 1.8 × 10^-15,等于零。第 1 步学不到任何东西。
λ = 0.9(已经算很温和了)
0.9^49 = 0.0057
→ 只剩 0.57%,信号弱到淹没在噪声里。
λ = 1.1(稍微大于 1 就爆)
1.1^49 = 106
→ 放大 106 倍,还算能忍。
λ = 1.5
1.5^49 = 425 万倍
→ 训练直接崩,出现 NaN。
【残酷的结论】
λ 必须极其精确地等于 1,才能既不消失又不爆炸。
但 λ 是训练中不断变化的,你没法把它钉在 1 上。
所以朴素 RNN 处理长序列,
在数学上就是一件"走钢丝"的事。
生活场景:往复印机里塞一张复印件,再复印这张复印件,如此重复。第一遍还清楚,第五遍开始发灰,第二十遍就是一张灰纸,什么都看不见了——这是梯度消失。反过来,如果你每次复印都把对比度调高一点,第五遍开始出现噪点,第二十遍整张纸全黑——这是梯度爆炸。而"每次复印都完美还原"这个理想状态,需要机器的参数精确得不可能,这就是 λ=1 的难度。
这个问题最早在 1991 年被 Sepp Hochreiter 在他的毕业论文里严格分析出来,1994 年 Bengio 等人发表了更完整的论证。注意时间线:问题在 1991 年就被看清了,1997 年 LSTM 就给出了解法——但要等到 2013 年前后有了足够的算力和数据,LSTM 才真正大放异彩。这跟反向传播的命运一模一样:好点子躺在论文里等时代,等了十几年。
LSTM:给神经元装上"记忆开关"
1997 年,Hochreiter & Schmidhuber 提出LSTM(Long Short-Term Memory,长短期记忆网络——一种给神经网络装上"选择性遗忘"开关的设计),一举把 RNN 从"金鱼记忆"救成"能记长文"的选手。
LSTM 的巧思是给神经元加上三扇门:遗忘门(决定要不要忘掉旧信息)、输入门(决定要不要写入新信息)、输出门(决定要不要把当前记忆吐出来)。加上一个专门存记忆的"细胞状态"(cell state)——像一条贯穿始终的传送带,重要信息可以顺着传送带一直向后传,中间既能被抹掉,也能被叠加。这几个门本身也是神经网络学出来的,不是人设的规则。
LSTM 就像一间档案室:遗忘门是"销毁员"——他每次判断哪些旧档案该丢;输入门是"接收员"——他决定哪些新档案值得存进来;输出门是"查询员"——他决定这一刻要不要把某些档案交出去用。三道门都由训练数据教会该怎么开、怎么关。这就让 LSTM 在读长文时,能把开头的关键信息一路保留到结尾——短期和长期记忆兼顾。
三扇门逐个拆开:记事本上划掉一些、添上一些、念出哪些
上面的档案室类比给了整体画面,现在我们把每扇门单独拆开看,因为每扇门解决的是一个具体的问题。先要理解一个共同点:三扇门都不是"开/关"两档的机械开关,而是一个 0 到 1 之间的连续数值——0.0 是全关,1.0 是全开,0.3 是开三成。用什么算出这个数?Sigmoid 函数,因为它天生就把任何输入压到 0 到 1 之间,正好当"开度"用。
生活场景:水龙头,不是电灯开关。电灯只有开和关,水龙头可以拧到任意开度——三扇门都是水龙头。而且这些水龙头不是你手动拧的,是网络自己根据当前读到的词和上一步的记忆,实时算出该拧多大。
门一:遗忘门(Forget Gate)——决定旧记忆里哪些该划掉。它看一眼"当前读到的词 + 上一步的摘要",然后给记忆里的每一条内容打一个 0 到 1 的分:1 分表示"这条完整保留",0 分表示"这条彻底抹掉",0.5 表示"留一半"。
什么时候它会大力遗忘?举个例子:模型正在读"小明去了北京,他在那里待了三天。后来小红去了上海,她……"——读到"小红"这个新主语时,遗忘门就该把"小明、北京"这些旧信息的分数压低,因为话题换人了。生活场景:会议记录里画一道横线,主持人说"上一个议题就这样,我们进入下一个议题",秘书就在本子上画条线,前面的内容不再影响后面的记录。
门二:输入门(Input Gate)——决定新信息里哪些值得记。你读到的每个词都是新信息,但绝大多数不值得长期记住。"的"、"了"、"就是"这些词对理解整段话几乎没贡献,输入门会给它们很低的开度;而"但是"、"取消了"、"三千万元"这种词会得到很高的开度。
生活场景:听讲座时你的笔在什么时候动。讲师说"好的,那么我们来看下一页"——你的笔不动。讲师说"这里有一个关键结论,考试会考"——你的笔立刻动了。输入门就是你手里那支笔的开关,它决定什么值得往本子上写。
门三:输出门(Output Gate)——决定这一刻要不要把某些记忆说出来。这扇门最容易被忽略,但它解决的是前面提到的那个"双重身份矛盾":记在心里的东西,不一定这一刻就要用。你可能记着"这个句子的主语是复数",但只有在需要选动词形式时才把它拿出来用。
生活场景:一位知道很多内情但只在必要时开口的老员工。他心里存着"三年前那个项目就是因为供应商跑路才失败的",开会讨论天气时他不说,一旦有人提议用同一家供应商,他立刻开口。存着不等于随时往外倒,这就是输出门的价值。
【LSTM 完整的四行公式】看着长,其实每行都在干上面说的事
f_t = σ( W_f · [h_(t-1), x_t] + b_f ) ← 遗忘门开度
i_t = σ( W_i · [h_(t-1), x_t] + b_i ) ← 输入门开度
o_t = σ( W_o · [h_(t-1), x_t] + b_o ) ← 输出门开度
C~_t = tanh( W_C · [h_(t-1), x_t] + b_C ) ← 候选新记忆
(这次想写什么)
★ 更新记忆(这是全部魔法所在的一行):
C_t = f_t ⊙ C_(t-1) + i_t ⊙ C~_t
↑ ↑
旧记忆 × 保留比例 新内容 × 写入比例
★ 决定这一刻输出什么:
h_t = o_t ⊙ tanh(C_t)
(σ 是 Sigmoid,⊙ 是"逐个元素相乘")
【用大白话读那关键一行】
新记事本 =(旧记事本 划掉一部分)+(新内容 挑一部分写上)
注意这里是"加法"!不是"重写"!
朴素 RNN 是每步把摘要整个重写一遍,
LSTM 是在旧记忆上做加减法。
这一个加号,就是长期记忆能活下来的全部原因。
那个加号为什么这么关键?回想一下 CNN 那边的残差连接——也是一个加号,也是让梯度获得一条导数恒为 1 的通路。这里完全同理:C_t = f_t ⊙ C_(t-1) + ... 这个式子对 C_(t-1) 求导,得到的是 f_t 而不是一堆矩阵连乘。只要遗忘门保持接近 1(也就是"这条记忆一直别忘"),梯度就能几乎无损地穿过几百个时间步。LSTM 和 ResNet 是同一个思想在两个领域的独立发现,而 LSTM 早了 18 年。
细胞状态:一条贯穿全程的传送带
细胞状态(cell state,记作 C)是 LSTM 相比朴素 RNN 多出来的那条通道,也是它最核心的创新。说白了它就是一条只做加减、不做重写的记忆主干线。理解它和隐藏状态 h 的分工,LSTM 就通了。
把 LSTM 想象成一间长长的车间,正中间有一条从头贯穿到尾的传送带,这条传送带就是细胞状态 C。传送带上放着一个个箱子,装着"这篇文章的主角是女性"、"时态是过去式"、"话题是关于医疗的"这类需要长期携带的信息。
传送带旁边站着三种工人。第一种是"清货员"(遗忘门)——他会盯着经过面前的箱子,判断哪些已经过期了,把里面的东西倒掉一部分或全部。比如话题换了,"关于医疗的"这个箱子就可以清空了。第二种是"装货员"(输入门)——他手里拿着刚从车间外接进来的新货(当前读到的词),挑出值得长期携带的部分,放到传送带上的箱子里。第三种是"取货员"(输出门)——他从传送带上取出这一刻需要用到的东西,交给车间的下一道工序去做实际判断。
关键在于:传送带本身是匀速直行的,它不会因为经过了一百个工位就把货物磨损掉。货物少了只可能是清货员主动倒掉的,多了只可能是装货员主动放的。这就是"梯度不会自然衰减"的物理直觉——传送带不打折。
对比一下朴素 RNN 是什么样:它没有传送带,只有一百个工人排队,每个人把手上的东西看一眼,然后凭记忆在一张新纸上重写一遍再传给下一个人。一百次转抄之后,原始信息还剩多少?这就是为什么 LSTM 能记住五百个词之前的事,朴素 RNN 撑不过二十个。
最后强调一点:这三种工人的判断标准(也就是三扇门的权重)没有一条是人写的规则。全是靠反向传播、拿几百万句真实语料一点点训出来的。人做的唯一一件事,是给了它"传送带 + 三种工人"这个结构框架。
再把 C 和 h 的分工说明白,这是最后一个容易混的点。C(细胞状态)是"你心里知道的全部",h(隐藏状态)是"你这一刻说出来的部分"。C 装的东西多、变化慢、供长期携带;h 是 C 经过输出门筛选后的产物,用来做当前这一步的实际预测,也传给下一步当参考。生活场景:一个人的知识储备(C)和他这一刻说出的那句话(h)——储备很厚,说出来的只是其中最相关的一小部分。
RNN / LSTM 的历史地位
2014 到 2017 这几年,是 RNN / LSTM 的黄金时代——机器翻译、语音识别、图像描述生成、聊天机器人,几乎所有序列相关的 NLP 任务都靠它。谷歌翻译在 2016 年那次"神经机器翻译"重大升级,用的正是 LSTM 编码器 + LSTM 解码器结构。
但 2017 年 Transformer 论文《Attention Is All You Need》横空出世,彻底颠覆游戏规则:Transformer 用"自注意力"取代循环结构,可以并行训练(RNN 必须一个一个词串行)、能捕捉超长依赖,短短两年就把 RNN 从 NLP 主战场赶了下来。今天你几乎再也看不到新论文用 RNN 做主流 NLP 任务了——但在某些资源受限的边缘设备、实时流式音频等场景,RNN / LSTM 依然是好选择。
为什么依然值得学
Transformer 虽然占据了主流,但它借鉴了 RNN 的很多思想(比如"位置编码"就是为了弥补 Transformer 天生没有顺序感这一点)。理解 RNN 的"记忆状态"、"梯度沿时间传播"这些概念,会让你更容易看懂 Transformer 的注意力机制到底解决了什么问题。它是一把回顾深度学习进化史的钥匙——学过 RNN,你才知道 Transformer 为什么必须诞生。
Seq2Seq:翻译机器的原始骨架
2014 年 Sutskever 等人提出 Seq2Seq(序列到序列)框架,用两个 RNN 拼成一台通用翻译机:编码器 RNN 读完源语言句子,把整句压缩成一个"上下文向量";解码器 RNN 拿这个向量,一个词一个词吐出目标语言。这套结构让机器翻译效果一夜之间上一个台阶,也让"编码器-解码器"这种通用范式扎根在整个深度学习圈。
但 Seq2Seq 有个死穴——不管源句子多长,最后只能压缩到一个固定长度的向量里,长句子的细节全被挤没了。2015 年 Bahdanau 等人提出注意力机制(Attention):解码器每输出一个词时,都可以"回头看"编码器的每个词,动态挑重点。这一个补丁,直接埋下了 Transformer 的种子——3 年后 Google 的《Attention Is All You Need》干脆把 RNN 骨架砍掉,只留 Attention,从此改变了 NLP 的天。
GRU:LSTM 的极简版
2014 年,人们发现 LSTM 的三扇门其实有些冗余,于是搞出个精简款——GRU(Gated Recurrent Unit,门控循环单元——把 LSTM 的三扇门砍成两扇的简化版):只保留两扇门(更新门 + 重置门),参数更少、训练更快、效果和 LSTM 相当。工程上如果对速度敏感,很多人会优先选 GRU;如果任务复杂、追求上限,还是选 LSTM。
它到底简化了什么?第一,它把遗忘门和输入门合并成了一扇"更新门"。逻辑上很自然:既然记忆容量是固定的,那"忘掉多少旧的"和"写入多少新的"本来就该是一件事的两面——腾出三成空间,就正好写进三成新东西。生活场景:一个只能装十本书的书架,你要放三本新书,就必须拿走三本旧的,这两个动作根本不需要两个人分开决定。第二,它取消了独立的细胞状态,只保留一个 h 同时承担记忆和输出,省掉了输出门。
省下来的东西有多少?LSTM 每个单元有 4 组权重矩阵(三扇门 + 候选记忆),GRU 只有 3 组,参数量少约 25%。在同样的显卡上,GRU 训得快 20% 到 30%。那效果谁好?2014 年之后有大量实验做过对比,结论相当无趣但很实用:在大多数任务上两者几乎打平,差异小于随机种子带来的波动。经验法则是:数据量小、序列不太长、追求快,选 GRU;数据量大、序列很长、追求极限效果,选 LSTM。
编码器-解码器(Encoder-Decoder)这个结构听着专业,其实就是一位口译员干活的两个动作。
动作一(编码):听懂。对方用英语说了一长句话,口译员边听边在心里把意思归拢——他不是在心里记下每一个英文单词,而是在提炼"这句话到底想说什么"。等对方说完,他心里有了一个脱离了具体语言的"意思团"。这个意思团就是所谓的"上下文向量"(context vector),说白了就是一串数字,装着"这句话的意思"。
动作二(解码):说出来。他拿着这个意思团,开始用中文一个词一个词地往外讲。而且注意一个细节:他讲第二个词时,会参考自己刚讲出的第一个词——这样才能保证中文说得通顺。这就是解码器为什么也是一个 RNN:它要把自己上一步的输出当成下一步的输入。
这个类比也顺便解释了 Seq2Seq 的死穴在哪:如果对方一口气说了三分钟不停,这位口译员心里那个"意思团"就装不下了——开头讲的细节、具体数字、人名,全被后面的内容挤掉。他只能翻个大意,细节全丢。这就是"固定长度向量装不下长句子"的直观感受。
注意力机制的补丁是什么?相当于允许这位口译员在讲的时候,随时回头看一眼自己刚才记的速记本——他讲到"三千二百万"这个数字时,可以回去精确核对原话那一段;讲到人名时,回去核对拼写。他不再依赖"全凭脑子那一个意思团",而是可以按需回查原文的任意位置。这一个改动,让长句翻译质量立刻上了一个台阶,也直接催生了三年后的 Transformer。
为什么 Transformer 取代了 RNN
三条原因翻译成人话各是一句话。第一条:能不能同时开工。RNN 好比一个人挨着盖一百份章,第二份必须等第一份盖完;Transformer 好比雇一百个人同时盖,一秒钟全搞定。第二条:话要传几手。RNN 里第一个词的信息要转手九十九次才到句尾,好比基层的话经过五级开会转达,到高层早变形了;Transformer 里所有词在同一个群里直接对话,谁跟谁说话都是一步到位。第三条:钱砸下去有没有用。说白了就是Transformer 越砸越强,RNN 砸到一定程度就不涨了——于是所有的钱和显卡都涌向了唯一那个能把钱换成效果的架构。
它的代价也得换成大白话说清:Transformer 的算量随句子长度按平方涨。打个比方,那是一场规定"每个人必须和每个人都握一次手"的开会:10 个人握 45 次还行,1000 个人就是接近 50 万次握手,会开不下去了。而 RNN 是"每来一个人只跟旁边那位打个招呼",多一个人只多一次——句子一长,笨办法反而赢了。这就是 RNN 至今没死透的根本原因。
上一节讲了串行是 RNN 的天花板,这里把 Transformer 取代它的完整原因摊开讲,因为这是理解现代 AI 格局的关键一步。Transformer 赢在三件事上,缺一件都不够。
第一件:可以并行,训练速度差几十倍。RNN 读一句 100 个词的话,必须严格走 100 步——第 50 步不算完,第 51 步动不了。Transformer 用注意力机制,一次性把 100 个词全部塞进去同时算,一步到位。生活场景:一沓一百份的表格要盖章。RNN 是一个人挨着一份一份盖,盖第二份必须等第一份盖完。Transformer 是雇一百个人同时盖,一秒钟全搞定。在"算力越多越有优势"的时代,能不能吃满并行算力,直接决定了一个架构的生死。同样的数据和显卡,Transformer 可能 1 天训完,RNN 要 1 个月。
第二件:任意两个位置之间的距离都是 1。RNN 里第 1 个词的信息要传到第 100 个词,得经过 99 次转手,每次都损耗。Transformer 里,第 1 个词和第 100 个词是直接对话的,中间不经任何人转达。生活场景:从"层层转达的行政体系"变成"所有人在一个群里"。传统体系里基层的话要经过五级才能到高层,早就变形了;群里所有人平等发言,谁跟谁说话都是一步到位。这就彻底根治了长依赖问题——不是缓解,是根治,因为再也没有连乘了。
第三件:它的效果随规模稳定提升,而 RNN 会撞墙。这一点最要命。人们发现 Transformer 有一个漂亮的性质:参数加倍、数据加倍、算力加倍,效果就可预测地变好(这被总结成"缩放定律",Scaling Law)。而 RNN 堆到一定规模就不再变好了——它的串行结构和记忆瓶颈成了硬天花板。于是所有的钱和显卡都涌向了 Transformer,因为只有它能把"砸钱"变成"变强"。GPT-3 有 1750 亿参数,如果换成 RNN 架构,训练时间会长到无法接受,效果也不会更好。
代价当然也有,而且很实在:Transformer 的计算量跟序列长度的平方成正比。100 个词要算一万次两两配对,1000 个词要算一百万次,10000 个词要算一亿次。生活场景:一场必须让每个人跟每个人都握一次手的酒会——10 个人 45 次握手还行,1000 个人就是接近 50 万次,办不下去。而 RNN 是线性的:多一个词就多算一步,不管序列多长都一样。这个成本结构上的差异,正是 RNN 至今没有彻底消失的根本原因。
RNN 现在还有什么用:流式、超长序列、以及 Mamba 的回归
三个还活着的地方,换成大白话各是一个生活画面。第一,边来边办的活儿。RNN 好比餐厅里那位"客人报一样、他就记一样"的服务员——报到第三十样他的反应速度和报第一样时完全一样。而 Transformer 那种"必须等你把全部三十样报完才动笔"的做法,在实时字幕、同传、助听器降噪这些场合根本没法用。
第二,超长的活儿。好比读一本一千页的书:RNN 老老实实一页一页读、边读边记笔记,读一千页就是读一百页的十倍时间,很公平;Transformer 却要求"每一页都跟其余九百九十九页对照一遍",成本是一百页的一百倍。页数一多,笨办法赢。
第三,最有意思的:老思路带着新工具回来了。Mamba 这类模型本质上就是被现代数学重新武装过的 RNN。它解决了老 RNN 那个"只能一个人干"的死结:训练时能雇一百个人同时干,干完之后推理时又退回一个人值班的省钱模式。所以这一节该记住的不是"RNN 被淘汰了",而是——它输的是工程实现,不是核心思想。而工程问题,是后来人可以解决的。
说 RNN"死了"是个懒惰的判断。它在三个地方活得很好,而且第三个地方正在反攻。
第一,流式处理(streaming)。凡是"边来边处理、必须立刻给结果"的场景,RNN 结构性地占优。生活场景:实时字幕。你在看直播,字幕必须在讲者说完一句话后半秒内出来。RNN 的做法是:每收到一帧音频,更新一下隐藏状态,立刻可以输出——它的延迟恒定,跟已经讲了多久完全无关。Transformer 想做流式就麻烦得多:它要么等一整句说完再算(延迟高),要么每来一帧就把前面所有内容重算一遍(越到后面越慢)。所以助听器、实时降噪、同声传译、工业传感器异常报警这些场景,轻量 RNN 和 GRU 至今是首选。
第二,超长序列。回到那个平方成本的问题:处理一段 100 万个 token 的序列(比如一整本书、一段基因组、一年的传感器读数),Transformer 要算一万亿次配对,成本高到荒谬;RNN 只需要走 100 万步,成本是线性的。生活场景:从头到尾读一本一千页的书。RNN 的做法是老老实实一页一页读、边读边记笔记,读完一千页花的时间是读一百页的十倍——很公平。Transformer 的做法是要求"每一页都要和其余九百九十九页对照一遍",读完的成本是一百页的一百倍。序列一长,线性就赢了。
第三,也是最有意思的:状态空间模型的回归。2023 年底,卡内基梅隆和普林斯顿的研究者提出了 Mamba,属于一类叫状态空间模型(State Space Model,SSM——用一个持续更新的"状态"来概括历史信息的模型)的架构。你听这个定义就该觉得耳熟了——"用一个持续更新的状态概括历史",这不就是隐藏状态吗?没错,Mamba 骨子里就是一个被现代数学和工程重新武装过的 RNN。
它解决了老 RNN 的两个致命伤。第一,训练时能并行了。靠一种叫"并行扫描"的数学技巧,它在训练阶段可以像 Transformer 那样一次算完整个序列;而在推理阶段又退回 RNN 那种"一步一状态"的模式,享受线性成本和恒定延迟。相当于一个人白天雇一百个帮手干活(训练并行),晚上一个人值班就够(推理省钱)。第二,它的"门"是根据输入内容动态算出来的,让模型能有选择地记住重要信息、跳过无关内容——这在思想上正是 LSTM 那三扇门的现代版。
所以这一节最该记住的不是"RNN 被淘汰了",而是:"用一个固定大小的状态携带历史"这个思想,从 1986 年到今天从未过时。它输给 Transformer 输的是工程实现(不能并行),不是核心思想。而工程问题,是可以被后来人解决的。RNN 没死,它换了一副面孔,正从侧门走回来。
双向 RNN 与堆叠 RNN
双向 RNN(Bi-RNN,一条从头读到尾、另一条从尾读回头,两边的理解拼在一起)说白了就是看考试阅读题的正确姿势:很多人的习惯是先把题目扫一眼再回头读原文——因为知道了后面要问什么,前面读的时候才知道该留意哪句。只从前往后读一遍,很容易漏掉伏笔。
堆叠 RNN 换成大白话就是办公室里一份文件的三级流转:底层的人逐字挑错别字,中层的人看段落逻辑通不通,高层的人判断整份文件的立场对不对。每一级看的粒度完全不同,摞在一起才能既不出错字、又不跑偏立场。
标准 RNN 只从前往后扫,但语言里"后面的话"经常能帮我们理解"前面的话"——比如读"他把苹果咬了一口,然后皱起了眉头",你要读到"皱眉"才明白"这个苹果可能不好吃"。双向 RNN(Bi-RNN)用两条 RNN,一条从前往后、一条从后往前,把两个方向的隐藏状态拼起来——这样每个位置都能同时看到"前文和后文"。命名实体识别、词性标注这类任务,双向 RNN 一度是标配。
堆叠 RNN则是把几层 RNN 上下摞起来——底层学局部特征,高层学句法结构。谷歌 2016 年那次翻译升级用的就是"8 层堆叠 LSTM + 双向 + Attention"这套重型结构,训练一次要几周时间、动用几百块 GPU。
双向 RNN 有一个非常重要的限制必须知道,否则你会在实际项目里踩坑:它要求你能一次拿到完整的序列。因为那条"从后往前"的 RNN 得先看到最后一个词才能开始算。生活场景:翻译一份已经写完的合同 vs 给一场正在进行的演讲做同传。合同已经写完了,你可以先通读一遍再翻,这时候双向完全没问题。同传就不行——讲者还没说到句尾,你不可能"从后往前"读。所以凡是实时的、流式的场景,双向 RNN 一律用不了,只能用单向。这个限制后来也传给了 BERT——BERT 是双向的,所以它擅长理解已有文本,但不能像 GPT 那样一个词一个词往外生成。
至于堆叠,为什么要摞几层?直觉跟 CNN 一样:底层的 RNN 处理"词和词的搭配"这种局部关系,高层的 RNN 处理"句子和句子的逻辑"这种全局关系。生活场景:一份文件在公司里的三级流转。底层员工负责逐字校对错别字,中层主管负责看段落逻辑通不通,高层负责判断这份文件的整体立场对不对。每一级看的是不同粒度的东西,摞在一起才能既不出错字又不跑偏立场。不过堆叠也有代价:层数一多,前面说的那个"实际深度 = 层数 × 序列长度"就更恐怖了——8 层 LSTM 读 100 个词,等效深度是 800 层,这就是为什么谷歌那次训练要几周。
RNN 处理"时间"的独特优势
先把"同一组权重复用整段时间线"这件事翻译成人话:RNN 不是"为一百个词准备一百套零件",而是"一套零件用一百遍"。好比一家早点铺只有一口锅、一个师傅、一套调料,不管今天来五位客人还是五百位客人,他都是同一套家什反复用——来多少人就多炒几轮,压根不需要为人多的日子额外置办一整套厨具。
这个性质带来的好处特别实在:输入多长都不用改结构。而普通网络就不行了——它好比一家只有固定八人桌的餐厅:来五个人得空着三个位置,来十个人根本坐不下,非要为每种人数单独摆一种桌型。这就是为什么语音识别、实时字幕这类"长度事先根本不知道"的任务,天然就该用 RNN 这种结构。
为什么 CNN、全连接网络不能拿来做序列任务?其实也能,但会碰上两个死结:输入长度不固定——你输入的句子有的 5 个词、有的 50 个词,普通网络得为每种长度设计一套结构;时序信息容易丢——把整句话拉平成一个大向量喂进去,词的先后顺序信息在权重里被稀释掉。
RNN 天然为"变长序列"设计——不管你输入多长,它就把同一颗神经元反复调用多少次;同一组权重"复用"了整段时间线,就像同一位主持人一整晚串起所有节目。这一点在语音识别里尤其重要——一段录音有多少毫秒就得处理多少帧,RNN 天生适配。今天很多流式音频处理(比如同声传译、实时字幕)仍在用轻量 RNN 或 GRU,因为它逐帧处理、延迟极低,比 Transformer 那种"看完整句再输出"更适合实时场景。
为什么串行是 RNN 的天花板
这件事换成大白话就是一个非常朴素的产能问题。RNN 干活的方式,好比一间只有一位收银员的超市:不管你在店门口停了多少辆送货车、雇了多少临时工,收银这道工序永远只能一个人一件一件扫。你手上有八千张顶级显卡,在 RNN 面前和有一张的差别没那么大——因为它压根没法把活分给八千个人同时做。
而 Transformer 好比把收银台一口气开成一百个,八千张显卡全都能派上用场。在一个"谁能把钱换成算力、把算力换成效果"的游戏里,一个不能并行的架构等于自动退赛。这不是谁的算法更聪明的问题,是产能结构的问题——而产能结构,是砸钱砸不动的。
RNN 的致命工程限制在"串行"两个字上——第 t 步的输出必须等第 t-1 步算完,一整个序列必须一帧一帧顺次计算,GPU 那种大规模并行的算力优势派不上用场。你哪怕有 8000 张 H100,训练 RNN 也只能一个 token 一个 token 慢慢走。
Transformer 恰恰在这一点上把 RNN 打得毫无还手之力——它用自注意力一次性看完所有位置,天生可并行;同一批训练数据在 Transformer 上跑一遍的时间,可能只有 RNN 的几十分之一。在"算力越大越有效"的深度学习游戏里,RNN 被架构本身的串行性判了死刑——这不是聪明才智的问题,是工程规模的问题。
RNN 教会神经网络"记住刚才发生了什么",LSTM 又教会它"选择性地记 & 忘"。这条路线撑起了 2014-2017 年 NLP 的半壁江山,后来被 Transformer 取代——但"隐藏状态"、"门控机制"、"长期依赖"这些概念,已经融入了整个深度学习的思考方式。今天 Transformer 里的 KV 缓存、状态空间模型(Mamba)等新架构,都能看到 RNN 的影子。它没死,只是换了一副面孔继续活着。