← 全部章节

P38 · 第 38 讲

Recurrent Neural

49:00

本章目录(8 节)
  1. 1. 这一讲解决什么问题
  2. 2. 前向:同一组权重,重复使用
  3. 3. BPTT:把时间展开,然后普通反传
  4. 4. LSTM:把"乘"换成"加"
  5. 5. 伪代码
  6. 6. 常见误区
  7. 7. 自测
  8. 8. 30 秒速记

课程版本:李宏毅 ML Lecture 21-1,2016/17 学期(YouTube 上传 2017-01-25) 官方讲义:RNN (v2).pdf,89 页 — https://speech.ee.ntu.edu.tw/~tlkagk/courses/ML_2016/Lecture/RNN%20(v2).pdf 分P:BV1ou411N7X3?p=38,49:00 来源边界:这支视频是 "RNN (Part I)",同一份 89 页讲义还供 Part II 使用。页内标题的顺序是:p1–p37 讲 slot filling → RNN → LSTM → BPTT → 训练困难与技巧;p39 起标题变为 "RNN can do more than that"(many-to-one / many-to-many / seq2seq);p67 起是 attention-based model;p80 起是 deep & structured。metadata 只有总时长,无法确定视频停在第几页,因此本篇标注「讲义章节切分复习版」,并把范围限定在 p1–p37 的机制部分;p39 之后的结构与 seq2seq 只作为跨 P 连接提一句,不声称属于 P38 的视频内容。无字幕,不引用逐字台词。


1. 这一讲解决什么问题

槽位填充(slot filling):订票系统要从来电里抽出

"I would like to arrive Taipei on November 2nd."

Taipei → destination,November 2nd → time of arrival(p2)。

如果每个词单独送进前馈网络,Taipei 永远被标成 destination。但换成

"I would like to leave Taipei on November 2nd."

同一个 Taipei 应该标成 place of departure(p7、p13)。输入向量一模一样,输出标签却要变——信息不在这个词里,在它前面的词里。p7 因此写了一句 "Neural network needs memory!"。

这就是 RNN 的动机:给网络一个隐状态,让它把历史压在里面,随序列递推。注意这不是"记住所有词":隐状态是固定维度的向量,是一个瓶颈,它只能保留网络认为有用的部分。


2. 前向:同一组权重,重复使用

$$ a_t=\tanh\!\left(W_h a_{t-1}+W_i x_t+b_h\right),\qquad y_t=\operatorname{softmax}\!\left(W_o a_t+b_o\right) $$

shape(\(d\) 输入维、\(h\) 隐状态维、\(|V|\) 词表大小):

量 shape
\(x_t\) \(d\)
\(a_t\) \(h\)
\(W_i\) \(h\times d\)
\(W_h\) \(h\times h\)
\(W_o\) \(\lvert V\rvert\times h\)
\(y_t\) \(\lvert V\rvert\)

关键在 p12 的那句 "The same network is used again and again":\(W_i,W_h,W_o\) 在所有时间步共享,参数数量不随序列长度增长。这也是 RNN 与"把序列摊平接全连接"的根本区别。

玩具数值例(讲义 p9–p11 的演示,Python 复算)

讲义设定:"all the weights are 1, no bias, all activation functions are linear",输入序列 1, 1, 2,输出 4, 12, 32(p9–p11 三页逐步展开)。用一个显式递推复现同一串数:

$$ h_t=2h_{t-1}+2x_t,\qquad y_t=2h_t,\qquad h_0=0 $$

Python 计算:\(h=(2,6,16)\),\(y=(4,12,32)\),与讲义三页的数字一致。

这个例子的价值不在"权重全 1"这个特殊设定,而在两件事:

  1. 同一组权重反复乘,数值会随步数指数增长(这里每步约 ×2)。这正是后面梯度爆炸的直觉来源。
  2. 换顺序输出就变(p11 原话 "Changing the sequence order will change the output")。把 1,1,2 换成 2,1,1,递推结果不同——RNN 对顺序敏感,这是它相对于词袋模型的核心优势。

说明:\(h_t=2h_{t-1}+2x_t\) 是我为了复现讲义数字而写的重建式,不是讲义公布的原始权重矩阵;讲义只给了"权重全 1、线性"的设定和输入输出。不要把这条递推当成课堂原式。

p14 可以堆深(deep RNN);p15 区分 Elman(存隐层输出 \(a_t\))与 Jordan(存输出 \(y_t\));p16 的双向 RNN 同时跑正反两个方向,每个位置拼接两侧状态——代价是必须看到整句,不能用于在线生成。


3. BPTT:把时间展开,然后普通反传

损失是各步之和 \(L=\sum_t L_t\)。对某个参数 \(W\):

$$ \frac{\partial L}{\partial W} =\sum_t\sum_{k\le t} \frac{\partial L_t}{\partial a_t} \left(\prod_{i=k+1}^{t}\frac{\partial a_i}{\partial a_{i-1}}\right) \frac{\partial a_k}{\partial W} $$

对 tanh RNN,

$$ \frac{\partial a_i}{\partial a_{i-1}} =\operatorname{diag}\!\left(1-a_i^2\right)W_h $$

所以跨度 \(t-k\) 的梯度里,\(W_h\) 被乘了 \(t-k\) 次。BPTT 不是新算法:把序列按时间展开成一张很深的计算图,再调普通反向传播;"through time" 只是说这张图在时间上重复。

数值:同一个权重,乘 1000 次会怎样

讲义 p36 的玩具例:\(y_{1000}=w^{999}\)。

\(w\) \(w^{999}\)(Python) 讲义的写法
1.01 \(2.08\times10^{4}\) \(y_{1000}\approx20000\)
0.99 \(4.36\times10^{-5}\) \(y_{1000}\approx0\)
0.01 下溢为 0 \(y_{1000}\approx0\)

偏差 1% 的权重,连乘 999 次就是 2 万倍;偏差 -1% 就是 0。梯度里发生的是同一件事:

p34–p35 的实验曲线(总损失随 epoch 剧烈抖动)与"error surface is either very flat or very steep"就是这两种情形的宏观表现。p35 给的 clipping(梯度超过阈值就按比例缩回)只治爆炸,不治消失——因为消失的梯度已经小到没有方向信息。


4. LSTM:把"乘"换成"加"

p17 的结构:一个 LSTM 单元有 4 组输入、1 个输出——\(z\)(候选内容)、\(z_i\)(input gate)、\(z_f\)(forget gate)、\(z_o\)(output gate)。三个门都过 sigmoid,值在 \((0,1)\),用来"模拟开关"(p18)。

$$ c'=g(z)\odot f(z_i)+c\odot f(z_f),\qquad a=h(c')\odot f(z_o) $$

其中 \(f\) 是 sigmoid,\(g,h\) 通常是 tanh;\(c\) 是 cell state(记忆),\(a\) 是输出到下一层/下一时刻的隐状态。所有量都是 \(h\) 维向量。

为什么能缓解梯度消失(p37 的原话是 "Can deal with gradient vanishing (not gradient explode)"):

门控算术(Python 计算,讲义未给这组数)

取 \(g,h\) 为 identity,只看门的作用;\(c=7\):

情形 \(f(z_i)\) \(f(z_f)\) \(c'\) 读法
\(z=0\),两门开 0.9933 0.9933 6.953 旧记忆基本保留
\(z=0\),遗忘门关 0.9933 0.0067 0.047 旧记忆被清空
\(z=2\),输入门关 0.0067 0.9933 6.967 新信息进不来
\(z=2\),两门开 0.9933 0.9933 8.940 新旧相加

这张表说明 LSTM 的表达力主要在门:同样的 \(z\),开哪个门决定"写、留、清、读"。p19–p25 用一串具体数字演示了同一件事:\(x_2=1\) 时把 \(x_1\) 写进记忆,\(x_2=-1\) 时清空,\(x_3=1\) 时把记忆读出(p19 的注释就是这三句)。p27 提醒代价:把普通神经元换成 LSTM,参数量是 4 倍(四组 \(W,b\))。

p30 还提了 peephole(把 \(c\) 也接进门控);p31 说多层 LSTM "is quite standard now",工程上直接用框架的 LSTM/GRU 层即可。

GRU:只有一句,属于【补充】

讲义 p37 只在 LSTM 下面挂了一行 "Gated Recurrent Unit (GRU): simpler than LSTM" 并引 Cho EMNLP'14,没有展开公式。所以 GRU 不属于本讲的课堂内容。为了对照,这里给最小定义并明确标注【补充,讲义未展开】:

$$ r_t=\sigma(W_r[x_t;h_{t-1}]),\quad z_t=\sigma(W_z[x_t;h_{t-1}]),\quad \tilde h_t=\tanh(W[x_t;r_t\odot h_{t-1}]), $$
$$ h_t=(1-z_t)\odot h_{t-1}+z_t\odot\tilde h_t $$

GRU 把 LSTM 的 input/forget 两个门合成一个 update gate \(z_t\),并去掉独立的 cell state,参数约为 LSTM 的 \(3/4\)。它是不是"更好"没有定论,取决于任务与数据量;讲义没有比较,本文也不替它下结论。

p38 还列了几个 2014–15 年的替代方案(identity 初始化 + ReLU、Clockwise RNN、SCRN),都指向同一件事:让循环权重接近正交/单位阵,使连乘不衰减。这些属于延伸,不展开。


5. 伪代码

# 前向(单层)
h = zeros(h_dim)
for x in sequence:
    h = tanh(W_i @ x + W_h @ h + b_h)
    y = softmax(W_o @ h + b_o)

# BPTT 的实质
loss = sum(CE(y_t, target_t) for t in range(T))
loss.backward()          # 框架会把上面的循环展开成 T 步的计算图
clip_grad_norm_(params, max_norm=1.0)   # 只治爆炸

6. 常见误区

  1. "LSTM 解决了梯度消失"。它缓解的是沿 cell state 的消失,且以 forget gate 开着为前提;p37 明确写 "not gradient explode",爆炸仍在。
  2. "BPTT 是另一种反向传播"。它就是普通反传,只是计算图在时间上展开;截断 BPTT(只回传最近 \(k\) 步)是工程近似,不是新算法。
  3. "RNN 能记住整个序列"。隐状态是固定维向量,长序列必然丢信息;这正是后来 attention 的动机。
  4. "双向 RNN 总比单向好"。它需要未来的输入,不能用于实时/自回归生成;离线标注任务才适合。
  5. "梯度消失 = 参数不更新"。消失的是远距离贡献,近处梯度仍在,模型仍能学短期模式——所以 RNN 不是"训不动",而是"学不到长程"。
  6. "GRU 是讲义重点"。讲义只有一句引用;把 GRU 公式当成课堂内容会在复述时超出源范围。

7. 自测

Q1. 序列长度 100、隐状态 64 维、输入 32 维、词表 1000。RNN 有多少参数?序列长度变成 1000 时呢?

答案

\(W_i:64\times32\)、\(W_h:64\times64\)、\(b_h:64\)、\(W_o:1000\times64\)、\(b_o:1000\),合计 \(2048+4096+64+64000+1000=71208\)。序列变 1000 参数不变,因为权重在时间上共享;变的是计算图和显存。

Q2. 为什么 \(w=1.01\) 连乘 999 次约 2 万,而 \(w=0.99\) 连乘 999 次约 0?这和梯度消失/爆炸是什么关系?

答案

指数函数的放大/衰减:\(1.01^{999}=e^{999\ln1.01}\approx e^{9.94}\approx2.08\times10^4\);\(0.99^{999}\approx e^{-10.05}\approx4.4\times10^{-5}\)。BPTT 的梯度是雅可比矩阵的连乘,特征值略大于 1 就爆炸、略小于 1 就消失,与这个标量例子同构。

Q3. LSTM 的 forget gate 全开(\(f(z_f)=1\))和全关(\(f(z_f)=0\))分别意味着什么?如果输入门也全关,\(c'\) 是什么?

答案

全开:\(c'=c+\text{新内容}\),旧记忆无损保留,梯度可沿 \(c\) 长距离回传;全关:\(c'=\text{新内容}\)(若输入门也关则为 0),旧记忆被丢弃。输入门全关时 \(c'=c\odot f(z_f)\),即只保留(按遗忘门缩放的)旧记忆。

Q4. 为什么"把句子倒过来输入"有时能帮 RNN?它改变了什么、没改变什么?

答案

倒序缩短了某些依赖的间隔(例如句首词与句尾词的路径变短),从而减轻 BPTT 的衰减;它没有改变参数量、也没有改变 RNN 的串行本质,只是改变了信息在时间轴上的距离。这是数据层面的技巧,不是架构改进。


8. 30 秒速记

同一个词在不同上下文要有不同标签 → 需要 memory。\(a_t=\tanh(W_h a_{t-1}+W_i x_t+b)\),同一组权重反复用,所以参数不随长度长。BPTT = 时间展开 + 普通反传;梯度是雅可比连乘,\(1.01^{999}\approx2\times10^4\)、\(0.99^{999}\approx4\times10^{-5}\),于是爆炸/消失,clipping 只治前者。LSTM 用三个 sigmoid 门把记忆更新变成加法:\(c'=g(z)f(z_i)+c f(z_f)\),遗忘门开着时梯度不灭,但不解决爆炸;参数是普通神经元的 4 倍。GRU 讲义只有一句引用,公式属补充。

跨 P 连接:P36/P37 的 self-attention 正是为了去掉这里的串行与长程衰减:任意两位置一步直达、训练可并行,代价是 \(O(N^2)\)。P47/P48 的 Transformer 把"encoder 用 RNN"(seq2seq_v9 p17 明说 "You can use RNN or CNN")换成了 self-attention;而 p39 之后 many-to-many / seq2seq 的结构,就是 P47 要展开的 encoder-decoder 前身。


引用页(PDF 物理页,1 起)

p2–p7(slot filling 与"需要记忆")、p8–p13(RNN 结构、权重复用、4/12/32 演示、顺序敏感)、p14–p16(deep / Elman-Jordan / bidirectional)、p17–p25(LSTM 三门、\(c'\) 公式、门控数值例)、p26–p31(4 倍参数、多层 LSTM、peephole)、p32–p36(BPTT、误差曲面、\(w^{999}\))、p37–p38(LSTM 与梯度、GRU 一句引用及替代方案)。

来源与说明

基于对应官方讲义整理,未取到完整视频字幕;不冒充逐字稿。