2025-08-17 李沐深度学习18——循环神经网络基础
文章目录
- 1 序列模型
- 1.1 现实生活中的序列数据
- 1.2 建模方法
- 1.2.1 马尔可夫假设(Markov Assumption)
- 1.2.2 潜变量(Latent Variable)模型
- 2 文本预处理
- 2.1 载入和清洗文本数据
- 2.2 词元化
- 2.3 构建词汇表
- 2.4 将文本转换为数字序列
- 3 语言模型
- 3.1 N-gram 的基本思想
- 3.2 具体实现
- 3.3 N-gram 的优缺点
- 4 RNN
- 4.1 RNN 与自回归模型
- 4.2 更新公式
- 4.3 独热编码
- 4.4 困惑度
- 4.5 梯度剪裁
- 4.6 不同应用模式
1 序列模型
序列模型是处理具有**时序结构(temporal structure)或顺序依赖(sequential dependency)**的数据的模型。与处理独立同分布(i.i.d.)数据的模型(如用于图像分类的CNN)不同,序列模型的数据点之间是相互依赖的。例如,图像是一个空间信息,而文本、语音、视频等则包含时间信息。
在序列模型中,我们在不同时间点 T 观察到的 T 个数据点 X1,X2,...,XTX_1,X_2,...,X_TX1,X2,...,XT 不是独立的随机变量,而是相互关联的。
1.1 现实生活中的序列数据
- 电影评分: 电影评分不是固定的,而是随着时间变化的。
- 获奖效应: 电影获奖后,其评分会因公众认为专业评审很厉害而上升。
- 题材厌倦: 如果同一题材的经典电影太多,观众的期望会变高,新电影的评分可能不如预期。
- 季节性: 贺岁片或暑期档电影在特定时间段的观影体验和评分会有所不同。
- 演员/导演丑闻: 导演或演员的负面新闻会直接导致电影评分下降。

- 自然语言处理(NLP): 语言、文本、语音、视频等都是连续的序列。
- 例如,“狗咬人” 和 “人咬狗” 这两个标题的词语只是顺序不同,但表达的含义和新闻价值却截然不同。
- 自然灾害: 大地震后,通常会伴随多次小地震,小地震的发生概率远高于平时。
- 社交媒体: 微博上的互动、评论区的回复等都是一个连续的序列,前后文相互关联。
- 金融数据: 股票价格是典型的时序数据。明天的股价与今天相关,但也有大量的随机性。

1.2 建模方法
所有机器学习模型的本质都是对数据的联合概率分布 P(X) 进行建模。如果能够得到这个分布,我们就可以无限地生成数据,并从中提取任何信息。

对于序列数据,其联合概率分布 P(X1,...,XT)P(X_1,...,X_T)P(X1,...,XT) 可以通过条件概率链式法则展开:
P(X1,...,XT)=P(X1)⋅P(X2∣X1)⋅P(X3∣X1,X2)⋅...⋅P(XT∣X1,...,XT−1)P(X_1,...,X_T)=P(X_1)\cdot P(X_2|X_1)\cdot P(X_3|X_1,X_2)\cdot...\cdot P(X_T|X_1,...,X_{T-1}) P(X1,...,XT)=P(X1)⋅P(X2∣X1)⋅P(X3∣X1,X2)⋅...⋅P(XT∣X1,...,XT−1)
这个展开式表明,要计算时间 TTT 的数据 XTX_TXT 的概率,需要知道之前所有时间点 X1X_1X1 到 XT−1X_{T−1}XT−1 的数据。

1.2.1 马尔可夫假设(Markov Assumption)
-
核心思想: 假设当前数据 XTX_TXT 只与过去特定数量的 KKK 个数据点相关,而不是与所有过去的数据点相关。
-
公式简化:
P(XT∣X1,...,XT−1)P(X_T|X_1,...,X_{T-1}) P(XT∣X1,...,XT−1)
简化为
P(XT∣XT−K,...,XT−1)P(X_T|X_{T-K},...,X_{T-1}) P(XT∣XT−K,...,XT−1) -
优点:
- 模型变得更简单,因为每次只需处理固定长度的输入,避免了变长输入的问题。
- 我们可以使用一个简单的多层感知机 (MLP) 来进行建模。输入是 K 个数据点的特征向量,输出是下一个时间点的数据。
-
自回归模型(Auto-regressive Model): 这种用数据序列中过去的数据来预测现在的数据的模型被称为自回归模型。
-
适用场景:
- 当过去的信息与现在数据的相关性随着时间迅速衰减时,例如预测股票价格,可能只跟最近一个月的价格相关,与10年前的价格关系不大。
- 在文本处理中,可以假设当前词语只与前几个词语相关。

1.2.2 潜变量(Latent Variable)模型
-
核心思想: 引入一个潜变量 HTH_THT 来概括所有历史信息。这个潜变量捕捉了从时间 1 到时间 T−1 的所有信息。
-
公式简化:
P(XT∣X1,...,XT−1)P(X_T|X_1,...,X_{T-1}) P(XT∣X1,...,XT−1)
简化为
P(XT∣HT)P(X_T∣H_T) P(XT∣HT)
其中,潜变量 HTH_THT 是一个函数 HT=f(X1,...,XT−1)H_{T}=f(X_{1},...,X_{T-1})HT=f(X1,...,XT−1)。 -
递归更新: 潜变量 HTH_THT 的计算通常是递归的。
HT=f(X1,...,XT−1)H_{T}=f(X_{1},...,X_{T-1}) HT=f(X1,...,XT−1)
这表示新的潜变量 HTH_THT 是基于前一个时间点的潜变量 HT−1H_{T-1}HT−1 和前一个时间点的数据 XT−1X_{T−1}XT−1 计算得到的。 -
优点:
- 可以更好地处理长期依赖问题,因为潜变量能够将历史信息不断传递下去。
- 每次的计算都只依赖于前一步的状态,结构更紧凑。
-
模型结构: 这种模型通常包含两个部分:
- 更新潜变量: 一个模型用于根据当前观察到的数据 XT−1X_{T−1}XT−1 和前一个潜变量 HT−1H_{T-1}HT−1 来更新新的潜变量 HTH_THT。
- 预测: 另一个模型用于根据新的潜变量 HTH_THT 来预测下一个数据 XTX_TXT。
-
与马尔可夫模型的区别: 潜变量模型不假设历史信息的固定长度,潜变量 HTH_THT 理论上可以概括所有过去的信息,这与马尔可夫假设的固定窗口不同。
-
循环神经网络(RNN) 属于潜变量模型,它将在后续课程中详细介绍。

2 文本预处理
文本预处理的核心是将文本视为一个时序序列。在自然语言处理(NLP)中,我们把文本中的每一个字符、字或词当作一个变量或样本,这些样本之间具有时序信息,构成一个长序列。预处理的目标是将这些词汇转换为模型能够训练和处理的数字形式。
2.1 载入和清洗文本数据
使用一本名为《时间机器》(The Time Machine)的英文小说作为示例数据,将这本书的 .txt
文件载入,然后进行如下的暴力清洗操作:
- 转换和过滤: 将文本中所有非大小写英文字母的字符(如标点符号)替换为空格。这是一个有损操作,但能极大地简化数据。
- 统一格式: 去掉每行的回车符,并将所有大写字母转换为小写。
经过这些处理,整个文本最终只包含26个小写英文字母和一个空格,变得非常简单。这是一种最简单的暴力预处理方法,在实际应用中通常会保留更多的信息(如标点符号、大小写),但这里是为了教学方便。

2.2 词元化
**词元(Token)**是文本处理中的基本单位。词元化(Tokenization)是将一段文字转换为一个个词元的过程,这是NLP中最常见的操作。
- 英文文本: 常见的词元单位有两种:
- 词(Word): 优点是模型处理起来相对简单,但词汇量庞大(可能有几万个词)。
- 字符(Character): 优点是词汇量非常小(本例中只有27个:26个字母+1个空格),但模型需要学习如何用字符构成词语,这增加了模型学习的复杂度。
- 中文文本:
- 字符: 一个汉字可以作为一个词元。
- 词: 中文词语之间没有空格,因此需要专门的分词操作。中文分词是一项复杂的任务,历史上曾是中文NLP研究的重点。
本例按字符进行词元化(char
)。经过词元化后,一个文本行被表示为一个词元(token)的列表(e.g., ["t", "h", "e", " ", "t", "i", "m", "e", " ", "m", "a", "c", "h", "i", "n", "e"]
)。

2.3 构建词汇表
词汇表(Vocabulary)又称字典,是NLP中的另一个核心概念。它将每个独特的词元(如字符串)映射到一个从0开始的数字索引(Index)。
为什么需要词汇表?
- 模型训练都是基于张量(Tensor)的,张量操作需要数字下标。
- 字符串在CPU上处理较慢,在GPU上几乎无法直接处理。
- 因此,必须将每个词元映射到一个唯一的数字索引,以便后续转换为张量。
课程中详细介绍了一个用于实现词汇表(Vocab
)的类。其主要功能包括:
- 词频统计: 统计每个词元在整个文本中出现的次数。
- 过滤低频词: 设置一个最小词频(
min_freq
)阈值,如果一个词元的出现次数低于该阈值,就将其视为未知词元(Unknown Token),统一映射到一个特殊的索引。这样做可以避免模型为不常出现的词元分配过多的训练资源。 - 特殊词元: 词汇表可以预留一些特殊词元,如表示未知词元的
"<unk>"
,或表示句子开始和结束的"<bos>"
和"<eos>"
。 - 映射关系: 构建两个映射:
index_to_token
: 索引到词元的映射,用于将数字索引转换回原始词元。token_to_index
: 词元到索引的映射,用于将词元转换为数字索引。

2.4 将文本转换为数字序列
一旦词汇表构建完成,就可以将整个文本转换为一个数字序列。这个过程是:
- 获取整个文本中所有词元。
- 遍历这些词元,利用词汇表将每个词元映射为其对应的数字索引。
- 最终得到一个很长的整数序列,这个序列就是模型可以处理的时序序列。
本例中,因为是按字符分词元,词汇表的大小(vocab.len
)是28(26个字母 + 1个空格 + 1个未知词元 "<unk>"
),这个整数序列的长度约为17万。

3 语言模型
语言模型是NLP中最经典的模型之一,它的核心任务是估计一个文本序列出现的联合概率。给定一个文本序列 X1,X2,...,XTX_1,X_2,...,X_TX1,X2,...,XT,语言模型的目标是计算这个序列出现的概率 P(X1,X2,...,XT)P(X_1,X_2,...,X_T)P(X1,X2,...,XT)。
语言模型的核心思想是:通过学习大量的文本数据,来预测一个文本序列的出现概率,或给定上文来预测下一个词的概率。

语言模型的应用非常广泛:
- 预训练模型(Pre-trained Models):这是目前语言模型最主要的应用。以 GPT-3 这类大型语言模型为例,它们本质上就是语言模型。通过在海量未标注文本上进行训练,模型学会了语言的结构和模式。这些预训练好的模型可以作为基础,通过微调(fine-tuning)来适应各种下游任务(如情感分析、问答系统等),这类似于计算机视觉中在 ImageNet 上进行预训练。
- 文本生成(Text Generation):通过给定几个初始词,语言模型可以像我们之前讲过的序列模型一样,不断地采样并预测下一个词,从而生成完整的文本。这要求模型非常优秀,因为任何预测的误差都可能累积,导致后面的生成结果偏离。
- 序列排序和选择:语言模型可以判断哪个句子更符合语言习惯、更常见。例如,在语音识别中,语音模型可能会识别出两个发音相似但意义不同的句子,如 “to recognize a speech” 和 “to wreck a nice beach”。这时,一个好的语言模型可以计算出第一个句子出现的概率更高,从而做出正确的选择。在中文输入法中,语言模型也用于根据拼音来预测最可能的汉字序列,甚至进行纠错。
3.1 N-gram 的基本思想
在不使用深度学习模型的情况下,语言模型通常基于马尔可夫假设和 N-gram 语法来建模。
如果我们要计算一个序列 P(X1,X2,...,XT)P(X_1,X_2,...,X_T)P(X1,X2,...,XT),根据条件概率的乘法法则,可以展开为:
P(X1)×P(X2∣X1)×P(X3∣X1,X2)×...×P(XT∣X1,...,XT−1)P(X_1)\times P(X_2|X_1)\times P(X_3|X_1,X_2)\times...\times P(X_T|X_1,...,X_{T-1}) P(X1)×P(X2∣X1)×P(X3∣X1,X2)×...×P(XT∣X1,...,XT−1)
然而,当序列很长时,精确计算每个条件概率变得非常困难,因为历史依赖项太多。N-gram 语法通过马尔可夫假设来简化这个问题,即假设一个词的出现只依赖于它前面的 N-1 个词。
根据这个假设,我们可以将联合概率进行近似计算:
P(X1,...,XT)≈P(X1)×P(X2∣X1)×...×P(XT∣XT−(N−1),...,XT−1)P(X_1,...,X_T)\approx P(X_1)\times P(X_2|X_1)\times...\times P(X_T|X_{T-(N-1)},...,X_{T-1}) P(X1,...,XT)≈P(X1)×P(X2∣X1)×...×P(XT∣XT−(N−1),...,XT−1)
3.2 具体实现
N-gram 模型通过统计计数来实现:
-
一元语法(Unigram,N=1):假设每个词都是独立的。
P(X1,...,XT)≈∏i=1TP(Xi)P(X_1,...,X_T)\approx\prod_{i=1}^TP(X_i) P(X1,...,XT)≈i=1∏TP(Xi)
其中,P(Xi)P(X_i)P(Xi) 可以通过在语料库中统计 XiX_iXi 的出现次数来计算。这个模型完全忽略了词序信息。 -
二元语法(Bigram,N=2):假设一个词只依赖于它前面一个词。
P(X1,...,XT)≈P(X1)×∏i=2TP(Xi∣Xi−1)P(X_1,...,X_T)\approx P(X_1)\times\prod_{i=2}^TP(X_i|X_{i-1}) P(X1,...,XT)≈P(X1)×i=2∏TP(Xi∣Xi−1)
P(Xi∣Xi−1)P(X_i|X_{i-1})P(Xi∣Xi−1) 可以通过计算词对 (Xi−1,Xi)(X_{i−1},X_i)(Xi−1,Xi) 的出现次数除以 Xi−1X_{i−1}Xi−1 的出现次数来获得。 -
三元语法(Trigram,N=3):假设一个词只依赖于它前面两个词。
P(X1,...,XT)≈P(X1)×P(X2∣X1)×∏i=3TP(Xi∣Xi−2,Xi−1)P(X_{1},...,X_{T})\approx P(X_{1})\times P(X_{2}|X_{1})\times\prod_{i=3}^{T}P(X_{i}|X_{i-2},X_{i-1}) P(X1,...,XT)≈P(X1)×P(X2∣X1)×i=3∏TP(Xi∣Xi−2,Xi−1)
P(Xi∣Xi−2,Xi−1)P(X_{i}|X_{i-2},X_{i-1})P(Xi∣Xi−2,Xi−1) 可以通过计算词序列 (Xi−2,Xi−1,Xi)(X_{i−2},X_{i−1},X_i)(Xi−2,Xi−1,Xi) 的出现次数来获得。

3.3 N-gram 的优缺点
- 优点:
- 计算效率高:通过提前统计并存储所有 N-gram 的出现频率,对于任意长序列的概率查询可以在 O(T) 的时间复杂度内完成(T 是序列长度),这对于需要实时处理的应用(如输入法)非常关键。
- 易于实现:只需要简单的计数和查找操作。
- 缺点:
- 空间复杂度高:存储所有 N-gram 的计数会占用巨大的内存。当 N 增加时,可能的 N-gram 组合数量呈指数级增长。例如,一个包含1000个词的词汇表,二元语法的组合就有 10002=100 万种,三元语法就有 10003=10 亿种。这使得高阶的 N-gram 很难应用。
- 稀疏性问题:许多 N-gram 在训练语料库中从未出现,导致其概率为零。这使得模型无法处理未见过的序列。
- 无法捕获长距离依赖:由于马尔可夫假设,N-gram 模型无法捕捉超过 N-1 范围的词语之间的依赖关系,这限制了其对复杂语言结构的理解。
4 RNN
4.1 RNN 与自回归模型
RNN 可以看作是自回归模型的一种具体实现。在自回归模型中,当前时刻的观察(XTX_TXT)和隐变量(HTH_THT)都依赖于前一时刻的隐变量(HT−1H_{T-1}HT−1)和输入(XT−1X_{T−1}XT−1)。RNN 通过引入一个**隐状态(hidden state)**来具体化这种依赖关系,从而能够处理序列数据。
- 隐状态(HTH_THT):一个向量,它包含了模型到当前时刻为止的所有历史信息。
- 当前输出(OT):由当前时刻的隐状态(HTH_THT)生成。
- 下一个隐状态(HT+1H_{T+1}HT+1):由当前时刻的输入(XTX_TXT)和当前隐状态(HTH_THT)共同生成。
在语言模型中,这种关系的应用是:
- 输入:当前时刻的词元 XTX_TXT。
- 输出:用来预测下一个词元 XT+1X_{T+1}XT+1。在计算损失时,用 OTO_TOT 来和实际的 XT+1X_{T+1}XT+1 进行比较。
- 更新:根据 XTX_TXT 和 HT−1H_{T-1}HT−1 来更新隐状态 HTH_THT。

4.2 更新公式
RNN 本质上是一个带有时序连接的多层感知机(MLP)。它的核心在于隐状态的更新,这个更新包含了对过去信息的依赖。
RNN 的核心更新公式如下:
-
隐状态更新:
Ht=ϕ(WhhXt−1+WxhHt−1+bh)H_t=\phi(W_{hh}X_{t-1}+W_{xh}H_{t-1}+b_h) Ht=ϕ(WhhXt−1+WxhHt−1+bh)- ϕ\phiϕ 是激活函数(如 ReLU 或 Tanh)。
- WxhW_{xh}Wxh 是输入到隐状态的权重矩阵。
- WhhW_{hh}Whh 是隐状态到隐状态的权重矩阵。
- Xt−1X_{t-1}Xt−1 是前一时刻的输入。
- Ht−1H_{t-1}Ht−1 是前一时刻的隐状态。
- bhb_hbh 是偏置项。
-
输出更新:
Ot=ϕ(WhoHt+bo)O_t=\phi(W_{ho}H_t+b_o) Ot=ϕ(WhoHt+bo)- WhoW_{ho}Who 是隐状态到输出的权重矩阵。
- bob_obo 是偏置项。
核心思想:RNN 在传统的 MLP 基础上,增加了一个 WhhXt−1W_{hh}X_{t-1}WhhXt−1 项,使得当前时刻的隐状态不仅取决于当前输入,还取决于前一时刻的隐状态。这个 WhhW_{hh}Whh 矩阵就是用来存储和传递序列信息的关键。

4.3 独热编码
**独热编码(One-Hot Encoding)**将数字索引转换为模型可以处理的向量。向量的长度等于词汇表大小,对应索引位置为1,其余位置为0。
- 输入形状:小批量的输入
X
形状为(batch_size, num_steps)
。 - 输出形状:经过独热编码后,数据变为三维张量,形状为
(num_steps, batch_size, vocab_size)
,其中vocab_size
是词汇表的大小。

4.4 困惑度
虽然语言模型可以看作是一个分类任务,每次预测下一个词元,但 NLP 领域通常使用**困惑度(Perplexity, PPL)**作为衡量标准。
-
困惑度是平均交叉熵损失的指数。
Perplexity=exp(−1N∑n=1NlogP(xn∣x1,...,xn−1))Perplexity=\exp\left(-\frac1N\sum_{n=1}^N\log P(x_n|x_1,...,x_{n-1})\right) Perplexity=exp(−N1n=1∑NlogP(xn∣x1,...,xn−1))
这个公式中的 logP\log PlogP 部分就是交叉熵损失。 -
直观理解:
- 困惑度衡量的是模型对下一个词的不确定性。
- 如果困惑度为 K,可以理解为模型每次做预测时,平均有 K 个可能的候选词。
- 值越小越好。困惑度为1是最好情况,表示模型每次都能完全确定地预测下一个词。
- 在输入法或语音识别中,困惑度低意味着模型给出的候选词更少,预测更精准。

4.5 梯度剪裁
RNN 在训练过程中存在数值不稳定性问题,即梯度爆炸和梯度消失。这是因为反向传播时,需要对一系列矩阵进行连乘操作,导致梯度值变得非常大或非常小。
为了有效解决梯度爆炸问题,通常使用**梯度剪裁(Gradient Clipping)**技术。
- 工作原理:
- 设定一个阈值 θ\thetaθ。
- 如果所有梯度构成的向量 g 的 L2 范数(即向量长度)超过这个阈值 θ\thetaθ,就对梯度进行缩放。
- 缩放后的梯度 g′g^{\prime}g′ 满足 $|g^{\prime}|_2=\theta $。
- 缩放公式为:g′=min(1,θ∥g∥2)gg^{\prime}=\operatorname*{min}\displaystyle\left(1,\frac\theta{\|g\|_2}\right)gg′=min(1,∥g∥2θ)g。
- 效果:梯度剪裁能够将过大的梯度值拉回到一个合理的范围内,从而防止模型在训练过程中因为梯度爆炸而崩溃。

4.6 不同应用模式
RNN 具有多种应用模式,可以适应不同的序列任务:
- 一对一(One-to-One):最简单的模式,即 MLP,没有 RNN 的时序概念。
- 一对多(One-to-Many):
- 文本生成:给定一个初始输入,RNN 持续生成下一个词,直到完成。
- 多对一(Many-to-One):
- 文本分类:输入一个完整的句子,RNN 在每个词输入后更新隐状态,只在句子末尾的最后一个隐状态上进行一次输出,用来对整个句子进行分类。
- 多对多(Many-to-Many):
- 机器翻译:输入一个完整的句子,不立即输出,在句子输入完毕后,再开始输出翻译结果。
- 序列标注(Tagging):输入一个句子,在每个词输入后都进行一次输出,例如标注每个词的词性(名词、动词等)。
