一、概念速记
RNN(循环神经网络)就是"带记忆的网络":让网络拥有一块"记忆",在处理当前数据时,能同时参考之前看过的内容;这块记忆通过同一组参数在时间上不断复用和更新。它不一次性看完全部输入,而是像人读书一样,一个字一个字地读,每读一个字就更新一次脑子里的记忆,然后用这份记忆去理解当前这个字。
其最特别的地方在于:同一个"小脑细胞",在同一句话上被反复调用。不像传统网络那样为每个位置单独准备参数,而是时间上共享同一套规则------无论句子有 5 个字还是 50 个字,用的都是同一个单元。

二、概念引申
想象让一个失忆的人做这三件事:
| 任务 | 失忆者的表现 | 问题在哪 |
|---|---|---|
| 判断影评是好评还是差评:"剧情一般,但演员很精彩,不过结局太拖沓。" | 他只能同时看几个词。看到"精彩"觉得是好评,看到"拖沓"觉得是差评,拼不起来 | 记不住前面说过什么 |
| 区分"苹果发布了新手机"和"我吃了个苹果" | 两个句子里都有"苹果",他看不出区别 | 词的意思要靠上下文才能定 |
| 听写:"我喜欢吃____" | 他不知道前面说了"喜欢吃",只能瞎猜 | 输出依赖历史 |
这三个困难的共同点:答案不在当前这一个字里,而在"前面那串东西"里。
RNN 的解法极其朴素:给它一张便签纸。每读一个字,就在便签上改写一笔。读到哪儿,便签就更新到哪儿。 这张便签,就是 RNN 的隐藏状态 hhh。
三、核心原理演示:一张会更新的便签
初始便签 h₀ = [0, 0] (空白)
读第1个字 → 结合 h₀ 和这个字 → 写出新便签 h₁
读第2个字 → 结合 h₁ 和这个字 → 写出新便签 h₂
读第3个字 → 结合 h₂ 和这个字 → 写出新便签 h₃
...
关键点只有两个:
- 新便签 = f(旧便签, 当前字)。每次只依赖"上一刻的记忆"和"眼前的字",不需要回头重读全文。
- f 是同一个函数、同一组参数,在第 1 步和第 100 步用的是完全相同的那一套。这就是"循环"二字的含义------不是网络里有环,而是同一个模块在时间上被循环调用。
为了直观,假设便签只有一个数字,代表"目前为止的正面情绪分数",规则简化为:
ht=ht−1+当前字的感情分h_t = h_{t-1} + \text{当前字的感情分}ht=ht−1+当前字的感情分
句子:"这 电影 真 好看 但是 太 贵 了"
| 读到 | 当前字感情分 | 便签 hth_tht | 此刻的判断 |
|---|---|---|---|
| 这 | 0 | 0 | 还不知道 |
| 电影 | 0 | 0 | 还不知道 |
| 真 | +0.5 | 0.5 | 有点正面 |
| 好看 | +1 | 1.5 | 挺正面的 |
| 但是 | −0.3 | 1.2 | (转折来了) |
| 太 | −0.5 | 0.7 | 开始回落 |
| 贵 | −1 | −0.3 | 变负面了 |
| 了 | 0 | −0.3 | 最终:负面评价 |
读到"贵"的时候,网络并没有重新去查"好看"这个词------但"好看"的影响已经融在了便签里(那个 1.5)。历史被压缩进了当前这一个数。
这也点出了 RNN 的本质:它用固定长度的向量,去装任意长度的历史。 能装下,是它的本事;装不下,是它的死穴。
四、核心原理剖析
基本结构

RNN 在每个时间步 ttt 做两件事:
- 读入 当前输入 xtx_txt,结合上一时刻的记忆 ht−1h_{t-1}ht−1,算出新的记忆 hth_tht;
- 输出 一个结果 yty_tyt(可以每步都输出,也可以最后才输出)。
ht=tanh(Uxt+Wht−1+b) \boxed{h_t = \tanh\left(\mathbf{U}x_t + \mathbf{W}h_{t-1} + \mathbf{b}\right)} ht=tanh(Uxt+Wht−1+b)
yt=Vht+c \boxed{y_t = \mathbf{V}h_t + \mathbf{c}} yt=Vht+c
若为分类任务,输出层再套一层 softmax:y^t=softmax(Vht+c)\hat y_t = \text{softmax}(\mathbf{V}h_t + \mathbf{c})y^t=softmax(Vht+c)。
参数共享的含义不变 :U,W,V\mathbf{U}, \mathbf{W}, \mathbf{V}U,W,V 在 t=1,2,...,Tt=1,2,\dots,Tt=1,2,...,T 所有时间步上完全共用。因此,无论序列多长,总参数量恒为:
∣θ∣=dh(din+dh+1)+dout(dh+1) |\theta| = d_h(d_{in}+d_h+1) + d_{out}(d_h+1) ∣θ∣=dh(din+dh+1)+dout(dh+1)
与序列长度 TTT 无关------这正是 RNN 能处理变长序列的数学原因。
两个关键点
① 参数共享(Parameter Sharing)
UUU、VVV、WWW 这三组参数在所有时间步完全共用。无论句子有 5 个词还是 500 个词,参数量都不变。
这一设计的意义在于:
- 模型可以在任意位置识别相同的模式。例如无论在句首还是句尾,"不"字都表示否定,它们共享同一套权重。
- 避免了为每个时间步单独设置参数而导致样本不足的问题。
这也是 RNN 与"把序列展开成一个巨大的全连接网"的本质区别。
② 按时间展开(Unrolling)
虽然结构上存在一个循环箭头,但在训练时,我们会将其沿时间轴展开为一条链式网络:
h0 → [RNN单元] → h1 → [RNN单元] → h2 → [RNN单元] → h3
↑ ↑ ↑
x1 x2 x3
↓ ↓ ↓
y1 y2 y3
展开后,它就变成了一个极深的网络,其深度等于序列长度。这也是 RNN 为何容易遭遇梯度消失问题的原因。
三种输入输出形态
| 形态 | 说明 | 典型任务 |
|---|---|---|
| 多对一(Many-to-One) | 读完整个序列才输出一次 | 情感分类、文本分类 |
| 一对一/多对多(同步) | 每步都输出 | 词性标注、命名实体识别、逐帧视频分割 |
| 多对多(异步,Encoder-Decoder) | 先编码完再解码输出 | 机器翻译、文本摘要、问答 |
- 多对一 (情感分类):ht=tanh(Uxt+Wht−1+b)h_t=\tanh(\mathbf{U}x_t+\mathbf{W}h_{t-1}+b)ht=tanh(Uxt+Wht−1+b),仅取 y=VhT+cy=\mathbf{V}h_T+cy=VhT+c
- 多对多同步 (NER/逐帧分割):yt=Vht+cy_t=\mathbf{V}h_t+cyt=Vht+c,每步都算 loss
- Encoder-Decoder (翻译):Encoder 用 (Uenc,Wenc)(\mathbf{U}^{enc},\mathbf{W}^{enc})(Uenc,Wenc),Decoder 用 (Udec,Wdec,Vdec)(\mathbf{U}^{dec},\mathbf{W}^{dec},\mathbf{V}^{dec})(Udec,Wdec,Vdec),两套参数不共享 ,靠上下文向量 ccc 桥接
手算案例:用 RNN 预测明日气温
用一个单隐藏单元的极简 RNN 进行完整的前向传播演算。
(1)设定
- 目标:根据连续三天的"归一化气温"预测第四天
- 输入序列:x1=1.0, x2=2.0, x3=3.0x_1=1.0,\ x_2=2.0,\ x_3=3.0x1=1.0, x2=2.0, x3=3.0
- 初始记忆:h0=0h_0 = 0h0=0
- 参数(假设已训练好):W=0.5,U=0.8,bh=0,V=1.2W=0.5,\quad U=0.8,\quad b_h=0,\quad V=1.2W=0.5,U=0.8,bh=0,V=1.2
- 预测结果:yyy
(2) 逐步计算
- t=1 :z1=0.8(1.0)+0.5(0)=0.8⇒h1=tanh(0.8)=0.664z_1 = 0.8(1.0)+0.5(0)=0.8 \Rightarrow h_1=\tanh(0.8)=0.664z1=0.8(1.0)+0.5(0)=0.8⇒h1=tanh(0.8)=0.664,y1=1.2(0.664)=0.797y_1=1.2(0.664)=0.797y1=1.2(0.664)=0.797
- t=2 :z2=0.8(2.0)+0.5(0.664)=1.932⇒h2=tanh(1.932)=0.959z_2 = 0.8(2.0)+0.5(0.664)=1.932 \Rightarrow h_2=\tanh(1.932)=0.959z2=0.8(2.0)+0.5(0.664)=1.932⇒h2=tanh(1.932)=0.959,y2=1.151y_2=1.151y2=1.151
- t=3 :z3=0.8(3.0)+0.5(0.959)=2.880⇒h3=tanh(2.880)=0.994z_3 = 0.8(3.0)+0.5(0.959)=2.880 \Rightarrow h_3=\tanh(2.880)=0.994z3=0.8(3.0)+0.5(0.959)=2.880⇒h3=tanh(2.880)=0.994,y3=1.192y_3=1.192y3=1.192
结论:记忆累加、越靠后的输入影响越大、tanh\tanhtanh 饱和导致细节丢失。
(3)BPTT (随时间反向传播)与梯度消失问题
总损失 L=∑tLtL=\sum_{t}L_tL=∑tLt,对三组参数的梯度都要跨时间步累加:
∂L∂U=∑t∂Lt∂U,∂L∂W=∑t∂Lt∂W,∂L∂V=∑t∂Lt∂V \frac{\partial L}{\partial \mathbf{U}}=\sum_t\frac{\partial L_t}{\partial \mathbf{U}},\qquad \frac{\partial L}{\partial \mathbf{W}}=\sum_t\frac{\partial L_t}{\partial \mathbf{W}},\qquad \frac{\partial L}{\partial \mathbf{V}}=\sum_t\frac{\partial L_t}{\partial \mathbf{V}} ∂U∂L=t∑∂U∂Lt,∂W∂L=t∑∂W∂Lt,∂V∂L=t∑∂V∂Lt
其中关键的连乘项只涉及 W\mathbf{W}W:
∂ht∂ht−k=∏i=t−k+1ttanh′ (Uxi+Whi−1+b)⋅W \frac{\partial h_t}{\partial h_{t-k}}=\prod_{i=t-k+1}^{t}\tanh'\!\left(\mathbf{U}x_i+\mathbf{W}h_{i-1}+\mathbf{b}\right)\cdot \mathbf{W} ∂ht−k∂ht=i=t−k+1∏ttanh′(Uxi+Whi−1+b)⋅W
代入数值:tanh′(0.8)=1−0.6642=0.559\tanh'(0.8)=1-0.664^2=0.559tanh′(0.8)=1−0.6642=0.559,每步衰减因子 0.559×0.5≈0.280.559\times 0.5\approx 0.280.559×0.5≈0.28,跨 5 步后剩 0.285≈1.7×10−30.28^5\approx1.7\times10^{-3}0.285≈1.7×10−3。
由此看出一个重要的不对称性:
- W\mathbf{W}W 同时出现在连乘项和求和项中 → 最容易消失或爆炸,是调参的主战场;
- U\mathbf{U}U 只受一次连乘影响 → 相对稳定;
- V\mathbf{V}V 不进入循环连乘 → 几乎不会梯度消失,所以输出层通常不用特殊初始化。
五、RNN优势与缺陷
- 优势
NLP 主力建模已被 Transformer 接管(并行快、一层就看到全局、能靠堆数据堆出智能)。但 RNN 仍有四个不可替代的位置:
- 流式/实时场景:来一帧输出一帧,延迟极低、内存恒定。实时语音转写、在线手写识别还在大量用它。Transformer 则得等整段说完,还要存越来越长的缓存。
- 端侧超小模型:GRU 几万到几十万参数,单片机都能跑。
- 数据很少的时序任务:几百条样本时,RNN 比 Transformer 好训得多(归纳偏置强,相当于自带"时间是连续的"这个先验)。
- 强时序信号:如传感器数据、生理信号(ECG/EEG)和金融高频数据,其局部依赖特征明显,RNN 足以胜任。
-
缺陷:便签太小,且容易被覆盖(这也是后来被 Transformer 取代的原因。)
1、长距离记不住
看这句:"我小时候住在巴黎......(中间隔了 40 个字)......所以我会说一口流利的法语。"
要判断"法语"合不合理,得记住 40 个字前的"巴黎"。但便签一直在被改写,新信息不断挤占旧信息,传到第 40 步时,"巴黎"的痕迹已经淡得几乎没了。
即梯度消失:训练时,错误信号要从第 40 步一路乘回第 1 步,几十个小于 1 的数连乘,结果趋近于 0 → 前面的词根本学不到。实际效果就是:普通 RNN 的有效记忆大概只有 5~10 步。
2、必须按顺序,不能并行
h2h_2h2 要等 h1h_1h1,h3h_3h3 要等 h2h_2h2......GPU 再强也只能一步步串行跑。一部长句或一段长音频,训练起来极慢。而 Transformer 可以一次性把整句话全算完,速度差出一个量级。
六、 RNN应用场景
案例 1:影评情感分类(多对一)
任务:判断"这部电影剧情拖沓,但特效惊艳"是正面还是负面评价。
流程:
分词 → 词嵌入(300维) → Bi-LSTM(256) → 取最后状态 → 全连接 → Sigmoid → 0.73(正面)
要点分析:
- 使用 Bi-LSTM(双向)非常必要。因为"但"之后的内容往往决定了整句话的情感倾向,单向 LSTM 读到句末时,可能已经淡忘了开头的负面评价,而双向结构能从正反两个方向捕捉语义。
- 实践中常将最后一步的隐藏状态与平均池化结果拼接,效果通常优于仅使用最后一步状态。
案例 2:小说续写 / 代码补全(多对多,同步)
任务:给定上文,逐词生成下一个词。
流程:每一步的输出经过 softmax 转化为词表上的概率分布,随后采样或取 argmax 作为下一步的输入(自回归)。
要点分析:
- 训练时使用 真实标签作为下一步输入(teacher forcing),而在推理时则使用模型自身的输出,两者存在差异,容易导致错误累积。缓解策略包括 scheduled sampling 或采用束搜索(beam search)。
- 输出端引入temperature 参数调节随机性:temperature 较低时生成结果保守重复,较高时则更具创意但也更易偏离逻辑。
- 评估指标不应仅依赖 accuracy,而应关注 perplexity(困惑度)以及人工评测结果。
案例 3:机器翻译(Encoder-Decoder + Attention)
任务:将 "How are you?" 翻译为 "你好吗?"
流程:
Encoder(LSTM) 读完整句 → 得到上下文向量 c
Decoder(LSTM) 以 c 为初始状态,逐个生成目标词
要点分析:
- 基础 Encoder-Decoder 的瓶颈在于:所有源句信息必须压缩进一个固定长度的向量 c。句子越长,翻译质量下降越明显。
- Attention 机制解决了该问题:解码每一步都回头查看编码器所有时间步的隐藏状态,并为它们分配不同的权重。例如在翻译"苹果"时,模型会将注意力集中在原文的 "apple" 上。
- Attention 带来了两大好处:一是显著提升了长句翻译质量,二是具备可解释性,可以通过可视化观察模型的对齐情况。
- 补充说明:Transformer 正是在此思路上彻底去除了循环结构,全面改用 Attention,这也是当前 RNN 应用减少的主要原因。