课程版本:李宏毅 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. 前向:同一组权重,重复使用
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 三页逐步展开)。用一个显式递推复现同一串数:
Python 计算:\(h=(2,6,16)\),\(y=(4,12,32)\),与讲义三页的数字一致。
这个例子的价值不在"权重全 1"这个特殊设定,而在两件事:
- 同一组权重反复乘,数值会随步数指数增长(这里每步约 ×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\):
对 tanh RNN,
所以跨度 \(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。梯度里发生的是同一件事:
- \(0.9^{50}=0.00515\):50 步后梯度只剩千分之五 → vanishing;
- \(1.1^{50}=117.39\):50 步后放大一百多倍 → exploding。
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)。
其中 \(f\) 是 sigmoid,\(g,h\) 通常是 tanh;\(c\) 是 cell state(记忆),\(a\) 是输出到下一层/下一时刻的隐状态。所有量都是 \(h\) 维向量。
为什么能缓解梯度消失(p37 的原话是 "Can deal with gradient vanishing (not gradient explode)"):
- 记忆的更新是加法:\(c'=c\odot f(z_f)+\text{新内容}\)。沿 \(c\) 反传时,梯度主要乘的是 \(f(z_f)\),而不是 \(W_h\) 的连乘。
- 只要 forget gate 开着(\(f(z_f)\approx1\)),"the influence never disappears unless forget gate is closed"(p37)。
- 但注意括号:不解决梯度爆炸。加法路径不消除 \(W\) 的谱半径问题,爆炸仍要靠 clipping。
门控算术(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 不属于本讲的课堂内容。为了对照,这里给最小定义并明确标注【补充,讲义未展开】:
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. 常见误区
- "LSTM 解决了梯度消失"。它缓解的是沿 cell state 的消失,且以 forget gate 开着为前提;p37 明确写 "not gradient explode",爆炸仍在。
- "BPTT 是另一种反向传播"。它就是普通反传,只是计算图在时间上展开;截断 BPTT(只回传最近 \(k\) 步)是工程近似,不是新算法。
- "RNN 能记住整个序列"。隐状态是固定维向量,长序列必然丢信息;这正是后来 attention 的动机。
- "双向 RNN 总比单向好"。它需要未来的输入,不能用于实时/自回归生成;离线标注任务才适合。
- "梯度消失 = 参数不更新"。消失的是远距离贡献,近处梯度仍在,模型仍能学短期模式——所以 RNN 不是"训不动",而是"学不到长程"。
- "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 一句引用及替代方案)。