Lecture 8:循环神经网络与LSTM
408 学习笔记:Lecture 8:循环神经网络与LSTM。保留原有知识内容、公式与图示;有对应原件时提供查看和下载入口。
本页目录
描述:本节从序列概率与语言模型出发,整理文本预处理、RNN 状态递推、困惑度、梯度裁剪和序列任务类型;随后完整推导 LSTM 的输入门、遗忘门、输出门、候选记忆、记忆单元和隐藏状态,并说明 LSTM-Attention 与编码器-解码器架构。
8.1 序列数据
8.1.1 定义与例子
序列数据中的样本按时间、位置或逻辑顺序排列,当前数据通常与前面观察到的数据相关,而不能简单视为相互独立。
课件给出的例子包括:
- 电影评分会随时间变化:获奖后可能上升,奖项影响淡化后又会变化。
- 连续看过很多好电影后,观众期望提高,后续评分标准可能改变。
- 贺岁档、暑期档等季节性因素会影响电影数据。
- 导演或演员的负面报道可能使评分下降。
- 音乐、文本和视频内部都具有连续结构。
- 大地震后出现若干较小余震,也体现时间依赖。
8.1.2 联合概率的链式展开
设依次观察到:
$$ X_1,X_2,\ldots,X_T, $$
它们的联合分布为:
$$ p(x_1,x_2,\ldots,x_T). $$
由条件概率:
$$ p(a,b)=p(a)p(b\mid a)=p(b)p(a\mid b), $$
可得链式法则:
$$ p(x_1,\ldots,x_T) =p(x_1) \prod_{t=2}^{T} p(x_t\mid x_1,\ldots,x_{t-1}). $$
该公式不要求变量独立,但完整条件历史会随 $t$ 增长,直接建模很困难。
8.1.3 自回归模型
自回归模型使用序列自身的过去值预测当前或未来值:
$$ p(x_t\mid x_1,\ldots,x_{t-1}). $$
可以把历史压缩成特征函数:
$$ p(x_t\mid x_1,\ldots,x_{t-1}) =p!\left(x_t\mid f(x_1,\ldots,x_{t-1})\right). $$
常见简化有两类:
- 有限阶马尔可夫模型:只使用最近 $\tau$ 个观测。
$$ p(x_t\mid x_1,\ldots,x_{t-1}) \approx p(x_t\mid x_{t-\tau},\ldots,x_{t-1}). $$
- 潜变量模型:用隐藏状态 $h_t$ 概括历史信息,再基于 $h_t$ 预测。
NOTE 自回归说明“用自身过去预测未来”;马尔可夫假设限制回看的窗口;潜变量模型则学习一个历史摘要。三者相关,但不是同一个概念。
8.2 文本预处理
课件使用《时间机器》(Time Machine)文本演示四个步骤:
- 下载并读取文本。
- 用正则表达式清理非字母字符并统一为小写。
- 按词或字符切分 token。
- 统计 token 频率并构造词表。
下面把课件中被分页和水印打断的代码整理为可读、可解析版本:
import collections
import re
from d2l import torch as d2l
d2l.DATA_HUB["time_machine"] = (
d2l.DATA_URL + "timemachine.txt",
"090b5e7e70c295757f55df93cb0a180b9691891a",
)
def read_time_machine():
"""读取并规范化《时间机器》文本。"""
with open(d2l.download("time_machine"), "r") as file:
lines = file.readlines()
return [
re.sub("[^A-Za-z]+", " ", line).strip().lower()
for line in lines
]
def tokenize(lines, token="word"):
"""按单词或字符切分文本行。"""
if token == "word":
return [line.split() for line in lines]
if token == "char":
return [list(line) for line in lines]
raise ValueError(f"未知令牌类型:{token}")
def count_corpus(tokens):
"""统计 token 频率。"""
if not tokens:
return collections.Counter()
if isinstance(tokens[0], list):
tokens = [token for line in tokens for token in line]
return collections.Counter(tokens)
class Vocab:
"""把 token 映射为整数下标。"""
def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
if tokens is None:
tokens = []
if reserved_tokens is None:
reserved_tokens = []
counter = count_corpus(tokens)
self.token_freqs = sorted(
counter.items(), key=lambda item: item[1], reverse=True
)
self.idx_to_token = ["<unk>"] + list(reserved_tokens)
self.token_to_idx = {
token: idx for idx, token in enumerate(self.idx_to_token)
}
for token, freq in self.token_freqs:
if freq < min_freq:
break
if token not in self.token_to_idx:
self.idx_to_token.append(token)
self.token_to_idx[token] = len(self.idx_to_token) - 1
def __len__(self):
return len(self.idx_to_token)
def __getitem__(self, tokens):
if not isinstance(tokens, (list, tuple)):
return self.token_to_idx.get(tokens, 0)
return [self[token] for token in tokens]
lines = read_time_machine()
tokens = tokenize(lines, token="word")
vocab = Vocab(tokens)
WARNING · 课件代码纠错 清洗表达式应为
[^A-Za-z]+;应先strip()再lower();分词判断变量是token而不是tolen;词频应从counter.items()排序。这些 OCR 或原代码拼写问题已在上面修正。
8.3 语言模型
8.3.1 目标
语言模型估计 token 序列的联合概率:
$$ p(x_1,x_2,\ldots,x_T)
\prod_{t=1}^{T} p(x_t\mid x_1,\ldots,x_{t-1}). $$
训练时常把序列错开一位:输入前缀,预测下一个 token。
8.3.2 基于计数的估计
设语料总 token 数为 $n$,$n(x)$ 是 token $x$ 的出现次数,$n(x,x')$ 是连续二元组 $(x,x')$ 的出现次数。
单个 token 的最大似然估计为:
$$ \hat p(x)=\frac{n(x)}{n}. $$
二元组概率可写为:
$$ \hat p(x,x') =\hat p(x)\hat p(x'\mid x) =\frac{n(x)}{n}\frac{n(x,x')}{n(x)}. $$
对于三元序列:
$$ \hat p(x,x',x'') =\frac{n(x)}{n} \frac{n(x,x')}{n(x)} \frac{n(x,x',x'')}{n(x,x')}. $$
8.3.3 $n$ 元语法与马尔可夫假设
若序列很长,完整历史条件概率难以可靠估计,可以采用有限阶马尔可夫假设。
**一元语法(unigram)**假设各 token 独立:
$$ p(x_1,x_2,x_3,x_4) \approx p(x_1)p(x_2)p(x_3)p(x_4). $$
计数形式为:
$$ \frac{n(x_1)}{n} \frac{n(x_2)}{n} \frac{n(x_3)}{n} \frac{n(x_4)}{n}. $$
**二元语法(bigram)**只依赖前一个 token:
$$ p(x_1,x_2,x_3,x_4) \approx p(x_1) p(x_2\mid x_1) p(x_3\mid x_2) p(x_4\mid x_3). $$
计数形式为:
$$ \frac{n(x_1)}{n} \frac{n(x_1,x_2)}{n(x_1)} \frac{n(x_2,x_3)}{n(x_2)} \frac{n(x_3,x_4)}{n(x_3)}. $$
更一般的 $n$ 元语法使用前 $n-1$ 个 token。阶数越高,条件越精细,但组合数量和稀疏性也越严重,实际还需要平滑或神经语言模型。
8.4 循环神经网络 RNN
8.4.1 潜变量自回归
RNN 用隐藏状态表示到当前为止的历史。课件以生成顺序表示为:
$$ p(h_t\mid h_{t-1},x_{t-1}), $$
以及:
$$ p(x_t\mid h_t,x_{t-1}). $$
标准确定性 RNN 把状态更新写为:
$$ \boldsymbol h_t =\phi!\left( W_{hh}\boldsymbol h_{t-1} +W_{xh}\boldsymbol x_t +\boldsymbol b_h \right). $$
输出分数为:
$$ \boldsymbol o_t =W_{ho}\boldsymbol h_t+\boldsymbol b_o. $$
再对 $\boldsymbol o_t$ 做 Softmax,可得到下一个 token 的概率分布。
8.4.2 与 MLP 的区别
如果去掉 $\boldsymbol h_{t-1}$,每个时间步只依赖当前输入,模型退化为逐位置共享参数的前馈网络。
RNN 的关键是:
- 当前状态同时依赖当前输入与前一状态。
- 同一组参数在所有时间步共享。
- 隐藏状态沿时间传播,充当历史摘要。
8.4.3 “你好,世界!”图的正确时序
课件图将观察序列写为:
你 好 , 世 界 !
对应目标应错开一个位置:
好 , 世 界 ! <结束>
即读入 $x_t$ 后,用 $h_t$ 预测 $x_{t+1}$。
WARNING · 课件表述纠错 图中文字“当前时刻的输出是为了预测当前时刻的输入”与图中的错位标签不一致。标准自回归语言模型使用当前及过去信息预测下一个 token;目标 token 在前向计算时不能作为当前输入泄漏给模型。
8.5 交叉熵与困惑度
8.5.1 平均交叉熵
设真实序列为 $x_1,\ldots,x_n$,平均负对数似然为:
$$ \mathcal L =-\frac{1}{n} \sum_{t=1}^{n} \log p(x_t\mid x_1,\ldots,x_{t-1}). $$
平均交叉熵越小,模型给真实 token 分配的概率越大。
8.5.2 困惑度
困惑度(Perplexity,PPL)定义为:
$$ \operatorname{PPL}=\exp(\mathcal L). $$
它可直观理解为模型在每一步面对的“有效平均候选数”:
- $\operatorname{PPL}=1$ 表示模型对每一步都把概率 1 分给真实 token,是理想情况。
- 在大小为 $V$ 的词表上,均匀随机预测的困惑度为 $V$。
- 困惑度越小通常越好,但只有在相同 token 化方式、词表和测试集上比较才有意义。
NOTE 困惑度没有固定的“最差有限值”。若模型给真实 token 的概率趋近 0,负对数似然与困惑度都可趋于无穷大。
8.6 时间反向传播与梯度裁剪
RNN 在 $T$ 个时间步上展开后,反向传播会形成长度为 $O(T)$ 的雅可比矩阵乘积链,容易产生梯度消失或梯度爆炸。
梯度裁剪主要限制梯度爆炸。若总梯度向量为 $\boldsymbol g$,阈值为 $\theta$,全局范数裁剪为:
$$ \boldsymbol g \leftarrow \min!\left(1,\frac{\theta}{\lVert\boldsymbol g\rVert}\right) \boldsymbol g. $$
因此:
- 当 $\lVert\boldsymbol g\rVert\le\theta$ 时,梯度不变。
- 当 $\lVert\boldsymbol g\rVert>\theta$ 时,方向不变,范数缩放为 $\theta$。
WARNING 梯度裁剪主要防止爆炸,不能从根本上解决梯度消失。
8.7 RNN 的输入输出形式
| 结构 | 输入/输出关系 | 典型任务 |
|---|---|---|
| one-to-one | 一个输入,一个输出 | 普通分类或回归 |
| one-to-many | 一个输入,生成多个输出 | 条件文本生成、图像描述 |
| many-to-one | 多个输入,一个输出 | 文本分类、序列分类 |
| many-to-many | 多个输入,多个输出 | 序列标注、问答、机器翻译 |
课件图中重点标出了:
- one-to-many:文本生成。
- many-to-one:文本分类。
- many-to-many:问答、机器翻译。
many-to-many 还可分为输入输出同步对齐和编码完整输入后再逐步解码两种情况。
8.8 LSTM 基础
长短期记忆网络(Long Short-Term Memory,LSTM)是一种带门控记忆单元的 RNN,用于改善普通 RNN 对长程依赖的学习能力。
每个时间步接收:
- 当前输入 $X_t$。
- 前一隐藏状态 $H_{t-1}$。
- 前一记忆单元状态 $C_{t-1}$。
每个时间步输出:
- 当前隐藏状态 $H_t$。
- 当前记忆单元状态 $C_t$。
LSTM 的四条控制支路是:
- 遗忘门 $F_t$。
- 输入门 $I_t$。
- 候选记忆 $\widetilde C_t$。
- 输出门 $O_t$。
课件图中的 “Special Neuron: 4 inputs, 1 output” 强调了一个 LSTM 单元内部由多路输入和门控共同决定输出,而不是单一的普通神经元变换。
8.9 LSTM 三个门
下面沿用课件的行向量记号。$\sigma$ 是 Sigmoid,输出范围为 $(0,1)$;$\odot$ 表示逐元素乘法。
8.9.1 遗忘门
遗忘门决定旧记忆 $C_{t-1}$ 保留多少:
$$ F_t =\sigma!\left( X_tW_{xf}+H_{t-1}W_{hf}+b_f \right). $$
- $F_t$ 接近 1:尽量保留对应旧记忆。
- $F_t$ 接近 0:尽量遗忘对应旧记忆。
8.9.2 输入门
输入门决定候选新信息写入多少:
$$ I_t =\sigma!\left( X_tW_{xi}+H_{t-1}W_{hi}+b_i \right). $$
- $I_t$ 接近 1:更多写入候选记忆。
- $I_t$ 接近 0:较少写入候选记忆。
8.9.3 输出门
输出门决定当前记忆暴露给隐藏状态多少:
$$ O_t =\sigma!\left( X_tW_{xo}+H_{t-1}W_{ho}+b_o \right). $$
- $O_t$ 接近 1:更多输出。
- $O_t$ 接近 0:更少输出。
WARNING · 课件符号纠错 课件第 6-8 页部分文字把 $I_t$ 叫作遗忘门、把 $F_t$ 叫作输入门,与其权重下标和最终总公式冲突。标准记号是:$F_t$ 为 forget gate,$I_t$ 为 input gate;本笔记统一采用标准记号。
8.10 候选记忆
候选记忆表示当前时间步可能写入的新信息:
$$ \widetilde C_t =\tanh!\left( X_tW_{xc}+H_{t-1}W_{hc}+b_c \right). $$
它通常位于 $(-1,1)$,但不会全部直接写入记忆单元,而是受输入门 $I_t$ 调节。
课件中的含义可以整理为:
- $\widetilde C_t$:当前输入和历史状态共同生成的候选新内容。
- $I_t\odot\widetilde C_t$:实际准备写入的内容。
- 候选内容可能大量采用,也可能几乎不用。
8.11 记忆单元更新
LSTM 的核心更新公式为:
$$ C_t =F_t\odot C_{t-1} +I_t\odot\widetilde C_t. $$
两项分别表示:
- $F_t\odot C_{t-1}$:保留的旧记忆。
- $I_t\odot\widetilde C_t$:写入的新记忆。
极端情况:
- $F_t=1,I_t=0$:只保留旧记忆。
- $F_t=0,I_t=1$:丢弃旧记忆,主要采用新候选。
- $F_t=0,I_t=0$:旧记忆和新候选都不保留。
- 两门均取中间值:按维度混合旧记忆与新信息。
8.11.1 梯度为什么更稳定
记忆单元采用加性更新:
$$ C_t=F_t\odot C_{t-1}+\cdots, $$
沿记忆通路有:
$$ \frac{\partial C_t}{\partial C_{t-1}}=F_t. $$
若需要长期保留信息,模型可学习令有关维度的 $F_t$ 接近 1,使梯度沿近似线性的记忆通路传播,减轻普通 RNN 反复矩阵乘法造成的快速衰减。
门控中的 Sigmoid 与候选记忆中的 $\tanh$ 也把数值限制在合理范围。
NOTE LSTM 是“缓解”长序列中的梯度消失和爆炸,不保证在任意序列、初始化和训练设置下彻底消除问题;实际仍常配合梯度裁剪。
8.12 隐藏状态
当前隐藏状态为:
$$ H_t=O_t\odot\tanh(C_t). $$
其中:
- $\tanh(C_t)$ 把记忆内容压到 $(-1,1)$。
- $O_t$ 控制哪些记忆维度向外输出。
- $H_t$ 既传给下一时间步,也可供当前预测头使用。
记忆单元 $C_t$ 与隐藏状态 $H_t$ 的区别:
| 状态 | 主要作用 | 更新方式 |
|---|---|---|
| $C_t$ | 长期记忆通路 | 遗忘旧记忆并加上候选新记忆 |
| $H_t$ | 当前可见表示/短期输出 | 对 $C_t$ 做 $\tanh$ 后受输出门控制 |
8.13 LSTM 总公式
$$ \begin{aligned} I_t&=\sigma(X_tW_{xi}+H_{t-1}W_{hi}+b_i),\ F_t&=\sigma(X_tW_{xf}+H_{t-1}W_{hf}+b_f),\ O_t&=\sigma(X_tW_{xo}+H_{t-1}W_{ho}+b_o),\ \widetilde C_t&=\tanh(X_tW_{xc}+H_{t-1}W_{hc}+b_c),\ C_t&=F_t\odot C_{t-1}+I_t\odot\widetilde C_t,\ H_t&=O_t\odot\tanh(C_t). \end{aligned} $$
记忆方法:
忘多少旧信息:F_t
写多少新信息:I_t
新信息是什么:C~_t
当前记忆是什么:C_t
向外显示多少:O_t
当前隐藏输出:H_t
8.14 LSTM 代码实现
下面保留课件“同时计算四个门”的结构,修正分页造成的拼写和缩进错误,并采用与公式一致的门顺序:输入门、遗忘门、候选记忆、输出门。
import torch
import torch.nn as nn
class LSTMCell(nn.Module):
"""单个 LSTM 时间步。"""
def __init__(self, input_size, hidden_size):
super().__init__()
self.input_size = input_size
self.hidden_size = hidden_size
# 右乘约定:
# x @ weight_ih: [batch, input] @ [input, 4 * hidden]
# h @ weight_hh: [batch, hidden] @ [hidden, 4 * hidden]
self.weight_ih = nn.Parameter(
torch.empty(input_size, 4 * hidden_size)
)
self.weight_hh = nn.Parameter(
torch.empty(hidden_size, 4 * hidden_size)
)
self.bias = nn.Parameter(torch.empty(4 * hidden_size))
self.reset_parameters()
def reset_parameters(self):
nn.init.xavier_uniform_(self.weight_ih)
nn.init.xavier_uniform_(self.weight_hh)
nn.init.zeros_(self.bias)
def forward(self, x, state):
"""
x: [batch_size, input_size]
state: (h_prev, c_prev),均为 [batch_size, hidden_size]
"""
h_prev, c_prev = state
gates = x @ self.weight_ih + h_prev @ self.weight_hh + self.bias
# 顺序:输入门、遗忘门、候选记忆、输出门。
i_gate, f_gate, g_gate, o_gate = gates.chunk(4, dim=1)
i_gate = torch.sigmoid(i_gate)
f_gate = torch.sigmoid(f_gate)
g_gate = torch.tanh(g_gate)
o_gate = torch.sigmoid(o_gate)
c_next = f_gate * c_prev + i_gate * g_gate
h_next = o_gate * torch.tanh(c_next)
return h_next, c_next
class LSTM(nn.Module):
"""用自定义 LSTMCell 处理完整序列。"""
def __init__(self, input_size, hidden_size):
super().__init__()
self.lstm_cell = LSTMCell(input_size, hidden_size)
self.hidden_size = hidden_size
def forward(self, x, initial_state=None):
"""
x: [seq_len, batch_size, input_size]
initial_state: (h0, c0)
返回:
outputs: [seq_len, batch_size, hidden_size]
(h_last, c_last): 最终状态
"""
seq_len, batch_size, _ = x.shape
outputs = []
if initial_state is None:
h = x.new_zeros(batch_size, self.hidden_size)
c = x.new_zeros(batch_size, self.hidden_size)
else:
h, c = initial_state
for t in range(seq_len):
h, c = self.lstm_cell(x[t], (h, c))
outputs.append(h.unsqueeze(0))
outputs = torch.cat(outputs, dim=0)
return outputs, (h, c)
代码中的形状关系:
$$ [B,D_x]@[D_x,4D_h] +[B,D_h]@[D_h,4D_h] \rightarrow[B,4D_h]. $$
再把最后一维等分成四个 $[B,D_h]$ 张量。
8.15 LSTM 与 Attention
8.15.1 动机
LSTM 可以把历史压入隐藏状态,但很长序列中的早期关键信息仍可能被最终状态弱化。
注意力机制不要求所有时间步贡献相同,而是:
- 保存每个时间步的隐藏状态 $h_t$。
- 计算每个 $h_t$ 对当前任务的重要性分数。
- 用 Softmax 把分数转换为归一化权重。
- 对隐藏状态加权求和,得到上下文向量。
它广泛用于时间序列预测、自然语言处理和编码器-解码器模型。
8.15.2 相似度/重要性分数
课件给出一种简化加性打分:
$$ e_t =v^{\mathrm T}\tanh(W_hh_t). $$
若需要让注意力依赖当前查询 $q$,更一般地可写为:
$$ e_t =v^{\mathrm T}\tanh(W_hh_t+W_qq+b). $$
8.15.3 Softmax 权重
$$ \alpha_t =\frac{\exp(e_t)} {\sum_{k=1}^{T}\exp(e_k)}. $$
因此:
$$ \alpha_t>0, \qquad \sum_{t=1}^{T}\alpha_t=1. $$
8.15.4 上下文向量
$$ h_{\mathrm{attention}} =\sum_{t=1}^{T}\alpha_th_t. $$
网络结构为:
输入时间序列
↓
LSTM:生成 h_1, h_2, ..., h_T
↓
Attention:打分并归一化为 α_1, ..., α_T
↓
上下文向量 h_attention
↓
输出层:生成最终预测
8.16 LSTM-Attention 数值例子
8.16.1 数据与隐藏状态
课件给出的前五个时间步数据为:
| 时间步 | 数据值 | 假设隐藏状态 |
|---|---|---|
| 1 | 100 | 0.5 |
| 2 | 102 | 0.7 |
| 3 | 98 | 0.4 |
| 4 | 105 | 0.8 |
| 5 | 103 | 0.6 |
假设注意力分数为:
$$ (e_1,e_2,e_3,e_4,e_5) =(0.1,0.3,0.2,0.4,0.2). $$
8.16.2 课件的示意权重
课件把权重近似写为:
$$ (\alpha_1,\alpha_2,\alpha_3,\alpha_4,\alpha_5) =(0.1,0.2,0.2,0.3,0.2). $$
按这组示意权重计算:
$$ \begin{aligned} h_{\mathrm{attention}} &=0.1\times0.5 +0.2\times0.7 +0.2\times0.4\ &\quad+0.3\times0.8 +0.2\times0.6\ &=0.63. \end{aligned} $$
8.16.3 按公式得到的正确 Softmax
对上述分数实际执行 Softmax,约为:
$$ (0.173,0.211,0.191,0.233,0.191). $$
相应上下文向量约为:
$$ h_{\mathrm{attention}} \approx0.612. $$
WARNING · 例题纠错 课件的 $(0.1,0.2,0.2,0.3,0.2)$ 虽然和为 1,但不是分数 $(0.1,0.3,0.2,0.4,0.2)$ 的真实 Softmax;所以 $0.63$ 只能视为使用课件手工示意权重得到的结果。
课件随后假设模型预测第 6 个时间步为 104。这个数还需要输出层参数才能从上下文向量得到,不能只凭 $h_{\mathrm{attention}}=0.63$ 直接推出;它是示意性的最终预测。
8.17 编码器与解码器
8.17.1 基本职责
一个模型可以分成两部分:
- 编码器(Encoder):读取输入并生成中间表示、特征或状态。
- 解码器(Decoder):读取编码状态以及可选的解码器输入,生成目标输出。
课件的通用流程图是:
输入 → 编码器 → 状态 → 解码器 → 输出
↑
解码器输入
WARNING · 课件文字纠错 “编码器处理输出,解码器生成输出”中的前一个“输出”应为“输入”。
8.17.2 CNN 中的对应关系
课件用图像分类网络作类比:
- 前面的卷积层负责特征提取,可看作编码器。
- 最后的输出层把特征映射为类别,可看作简单解码头。
在更复杂的生成任务中,解码器通常不只是一个 Softmax 层,而会逐步产生结构化输出。
8.17.3 RNN 中的对应关系
课件的 this movie is great 图表示:
- Embedding 把每个词转换为向量。
- LSTM 编码完整输入序列。
- 最后隐藏状态概括文本。
- Dense 层根据该向量输出结果。
这是 many-to-one 分类结构。对于机器翻译等 sequence-to-sequence 任务,解码器会接收编码器状态,并结合前一目标 token 逐步生成输出序列。
8.18 编码器-解码器代码骨架
课件使用一个通用包装器连接编码器和解码器:
import torch.nn as nn
class EncoderDecoder(nn.Module):
"""编码器-解码器架构的基类。"""
def __init__(self, encoder, decoder, **kwargs):
super().__init__(**kwargs)
self.encoder = encoder
self.decoder = decoder
def forward(self, enc_x, dec_x, *args):
enc_outputs = self.encoder(enc_x, *args)
dec_state = self.decoder.init_state(enc_outputs, *args)
return self.decoder(dec_x, dec_state)
数据流为:
encoder(enc_x)生成编码器输出。decoder.init_state(...)把编码器输出转换为解码初始状态。decoder(dec_x, dec_state)根据解码器输入和状态生成结果。
注意力模型中,enc_outputs 往往保留所有时间步,而不只保留最后一个向量;解码器可在每一步对这些状态计算注意力。
8.19 易错点
- 序列联合概率必须按条件概率链式展开,不能在没有独立假设时直接写成边缘概率乘积。
- 自回归模型使用过去值预测当前或未来,马尔可夫模型只是把依赖限制为有限阶。
- 一元语法假设 token 独立;二元语法只依赖前一个 token;阶数提高会加重稀疏性。
- 语言模型输入和标签错开一位,当前状态预测下一个 token,不应读取要预测的 token。
- 困惑度是平均交叉熵的指数;均匀随机基线等于词表大小,且比较时必须统一 token 化与数据集。
- 梯度裁剪防止梯度爆炸,不会自动解决梯度消失。
- 普通 RNN 的隐藏状态包含循环依赖;去掉前一隐藏状态后不再具有时序记忆。
- LSTM 中 $F_t$ 是遗忘门,$I_t$ 是输入门;课件部分文字交换了两者。
- 候选记忆 $\widetilde C_t$ 不是最终记忆,必须经过输入门后才能写入 $C_t$。
- $C_t$ 是长期记忆通路,$H_t$ 是受输出门控制的当前可见状态,两者不能混用。
- LSTM 缓解而非绝对消除长序列梯度问题。
- 注意力权重必须由 Softmax 正规化;课件数值例子的手工权重不是其给定分数的真实 Softmax。
- 上下文向量只是输出层输入,若没有输出层参数,不能由它直接推出最终预测值 104。
- 编码器处理输入,解码器生成输出;序列解码器通常还需要自己的输入 token。
相关知识
8.20 本节复盘
- 序列数据具有顺序依赖,联合概率可按条件概率链式展开。
- 自回归模型用历史预测未来;马尔可夫模型限制历史窗口;潜变量模型用状态概括历史。
- 文本预处理包括清洗、按词/字符切分、计数与词表映射。
- $n$ 元语法用有限历史估计语言概率,神经语言模型则学习连续隐藏状态。
- RNN 用 $h_t=\phi(W_{hh}h_{t-1}+W_{xh}x_t+b_h)$ 递推历史表示,并预测下一 token。
- 困惑度等于平均负对数似然的指数,越低通常越好;梯度裁剪限制梯度全局范数。
- LSTM 用遗忘门保留旧记忆、输入门写入候选记忆、输出门控制隐藏状态。
- LSTM 核心公式是 $C_t=F_t\odot C_{t-1}+I_t\odot\widetilde C_t$ 与 $H_t=O_t\odot\tanh(C_t)$。
- LSTM 的加性记忆通路改善长程梯度传播,但仍可能需要裁剪和合理训练设置。
- Attention 对各时间步隐藏状态打分、Softmax 归一化并加权求和,使模型动态选择重要历史。
- 编码器把输入变为状态,解码器结合该状态与自己的输入生成输出;注意力可让解码器访问全部编码状态。