图解循环神经网络(RNN)

一、概念速记

  RNN(循环神经网络)就是"带记忆的网络":让网络拥有一块"记忆",在处理当前数据时,能同时参考之前看过的内容;这块记忆通过同一组参数在时间上不断复用和更新。它不一次性看完全部输入,而是像人读书一样,一个字一个字地读,每读一个字就更新一次脑子里的记忆,然后用这份记忆去理解当前这个字。

  其最特别的地方在于:同一个"小脑细胞",在同一句话上被反复调用。不像传统网络那样为每个位置单独准备参数,而是时间上共享同一套规则------无论句子有 5 个字还是 50 个字,用的都是同一个单元。

二、概念引申

想象让一个失忆的人做这三件事:

任务 失忆者的表现 问题在哪
判断影评是好评还是差评:"剧情一般,但演员很精彩,不过结局太拖沓。" 他只能同时看几个词。看到"精彩"觉得是好评,看到"拖沓"觉得是差评,拼不起来 记不住前面说过什么
区分"苹果发布了新手机"和"我吃了个苹果" 两个句子里都有"苹果",他看不出区别 词的意思要靠上下文才能定
听写:"我喜欢吃____" 他不知道前面说了"喜欢吃",只能瞎猜 输出依赖历史

这三个困难的共同点:答案不在当前这一个字里,而在"前面那串东西"里。

RNN 的解法极其朴素:给它一张便签纸。每读一个字,就在便签上改写一笔。读到哪儿,便签就更新到哪儿。 这张便签,就是 RNN 的隐藏状态 hhh。

三、核心原理演示:一张会更新的便签

复制代码
初始便签 h₀ = [0, 0]  (空白)

读第1个字 → 结合 h₀ 和这个字 → 写出新便签 h₁
读第2个字 → 结合 h₁ 和这个字 → 写出新便签 h₂
读第3个字 → 结合 h₂ 和这个字 → 写出新便签 h₃
...

关键点只有两个:

  1. 新便签 = f(旧便签, 当前字)。每次只依赖"上一刻的记忆"和"眼前的字",不需要回头重读全文。
  2. 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 做两件事:

  1. 读入 当前输入 xtx_txt,结合上一时刻的记忆 ht−1h_{t-1}ht−1,算出新的记忆 hth_tht;
  2. 输出 一个结果 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 仍有四个不可替代的位置:
  1. 流式/实时场景:来一帧输出一帧,延迟极低、内存恒定。实时语音转写、在线手写识别还在大量用它。Transformer 则得等整段说完,还要存越来越长的缓存。
  2. 端侧超小模型:GRU 几万到几十万参数,单片机都能跑。
  3. 数据很少的时序任务:几百条样本时,RNN 比 Transformer 好训得多(归纳偏置强,相当于自带"时间是连续的"这个先验)。
  4. 强时序信号:如传感器数据、生理信号(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 应用减少的主要原因。
相关推荐
QXWZ_IA33 分钟前
测绘单位采购RTK:设备价格、账号年费、售后,用时间轴分权重
网络·人工智能·安全
kaixin_啊啊1 小时前
【零基础学AI】第 29 章——第一次调用大模型 API
人工智能
甲维斯1 小时前
Qwen4 被嘲笑了!国模被啥锁死了?
人工智能
夕阳_醉了1 小时前
Codex 客户端从装好到跑通:第三方 API 接入与配置避坑指南
人工智能·gpt·openai·codex·ai 编程
熟的生菜1 小时前
从零造一个腱绳驱动灵巧手——仿真器篇5:姿势串与手势工程——从文本协议到握拳、OK 与多姿势插值
人工智能
武子康2 小时前
攻击动作做对了,游戏规则就对了吗?H3-World 与三条控制路线的分工
大数据·人工智能·agent
探求虚无2 小时前
用 AI 在 Unity 里做一款 RTS:最难的不是生成代码,而是证明它真的能玩
人工智能·unity·游戏引擎
vivo互联网技术2 小时前
AI Agent 技术全景解析 —— 从提示词工程到高级智能体构建
人工智能·agent