04. NLP & RNN

返回目录

1. 为什么使用 RNN?

RNN 的核心价值是记忆。它用共享参数递归处理序列:

\[ h_t=\phi(W_{xh}x_t+W_{hh}h_{t-1}+b_h) \]

\[ o_t=W_{hy}h_t+b_y \]

直观理解这条递推:新记忆 \(h_t\) 由当前输入 \(x_t\) 和上一步的旧记忆 \(h_{t-1}\) 共同决定;\(W_{xh},W_{hh}\) 决定”新输入 vs 旧记忆”各占多重;\(\phi\)(通常是 \(\tanh\))把数值压到 \([-1,1]\),防止逐步累加发散。示意图里那一长排 node 其实是同一套单元在不同时间步的”展开(unroll)”(同一台机器在重播),并不是多个不同单元;每步的输入 \(x_t\) 通常是词向量(embedding)。

逐步走一遍:以 “我 / 今天 / 吃了 / 苹果” 为例。第 1 步只有输入 “我”,产生初始记忆 \(h_1\);第 2 步输入 “今天”,与旧记忆 \(h_1\) 按权重相加再经 \(\phi\) 揉合,得到同时含 “我” 和 “今天” 的 \(h_2\);第 3 步输入 “吃了” 与 \(h_2\) 合成 \(h_3\)……像接力赛,每一棒手里的接力棒都带着前面所有棒的痕迹,因此只要链条不断,理论上第 1 步的信息能一路传到最后一步。

因此 RNN 有三点优势:

  • 带记忆理解上下文:把历史信息压进 hidden state \(h_t\) 带到当前步,当前预测能看到之前发生了什么。
  • 天然处理变长序列:逐步处理,来一个 token 算一步,序列多长都能吃。
  • 参数共享:无论序列多长都复用同一套权重,参数量不随序列长度增长,模型不会因序列变长而变大。

适合变长序列、因果预测和流式处理。

典型用途

  • Language modeling。
  • 序列分类和标注。
  • 时间序列。
  • 语音、传感器和在线流式推理。

Transformer 成为主流后,RNN 仍可用于低延迟流式、小模型和严格内存场景。


2. Vanilla RNN 的局限

RNN 训练用 BPTT(Backpropagation Through Time):把展开的 \(n\) 步当成一个 \(n\) 层的深网络,前向传到最后算 Loss,再让误差沿时间反向逐步传回、更新那套共享的 \(W\)。由此带来两大痛点:

  • 梯度消失/爆炸:因为共享 \(W\),反向传播每经过一个时间步就连乘一次 \(W\)。\(W\) 的量级 \(<1\),连乘后梯度指数衰减到 \(0\),远端节点几乎收不到梯度、”摆烂”学不到长程关系;\(W\) 的量级 \(>1\),连乘后梯度爆炸。
    • 具体数字感受连乘的威力:若每步回传的量级约 \(0.9\),回传 100 步就是 \(0.9^{100}\approx2.6\times10^{-5}\),梯度传到最前面几乎归零,早期时间步”学不动”;若量级约 \(1.1\),\(1.1^{100}\approx1.4\times10^{4}\),梯度爆炸。
  • 信息瓶颈:固定大小的 hidden state 装不下很长的历史,新信息会不断冲刷、覆盖旧信息。
    • 直觉:用固定大小的 hidden state 装长历史,像用一句话总结一整本书,后面的内容会不断把前面的冲刷掉。

其他局限:

  • 时间步必须串行,训练难以并行。
  • 长程依赖的信息路径长度为 \(O(n)\)。
  • 隐状态容量和记忆时间缺少显式控制。

从数学上说,若 recurrent Jacobian 的谱范数持续小于 1,梯度随时间指数衰减;持续大于 1 则可能爆炸。


3. LSTM 公式

LSTM 的核心是 门控 + 一条独立的细胞状态 \(c_t\)「传送带」:\(c_t\) 默认只做加法、信息近乎无损地一路向前流,门则决定往这条传送带上删什么、加什么、读什么。

先记住两个通用符号:

  • \(\sigma\)(Sigmoid)= 门控开关,输出 \(0\sim1\)(\(0\) 全关/全忘,\(1\) 全开/全留)。
  • \(\tanh\) = 内容调节器,输出 \(-1\sim1\)(可增可减,正=强化、负=抵消,避免只加不减导致数值爆炸)。

给定输入 \(x_t\)、上一时刻 hidden state \(h_{t-1}\) 和 cell state \(c_{t-1}\):

\[ f_t =\sigma(W_f[x_t;h_{t-1}]+b_f) \]

\[ i_t =\sigma(W_i[x_t;h_{t-1}]+b_i) \]

\[ \tilde c_t =\tanh(W_c[x_t;h_{t-1}]+b_c) \]

\[ c_t =f_t\odot c_{t-1} +i_t\odot\tilde c_t \]

\[ o_t =\sigma(W_o[x_t;h_{t-1}]+b_o) \]

\[ h_t=o_t\odot\tanh(c_t) \]

读公式的套路:凡是看到 \([x_t;h_{t-1}]\),都是”把眼前输入和上一步的短期记忆拼起来综合考虑”;再配上上面 \(\sigma\)=开关、\(\tanh\)=内容两条,六个公式其实只有四步:

  • ① 决定忘掉多少:\(f_t\) 给旧记忆打一个保留比例。
  • ② 准备新内容:\(\tilde c_t\) 是模型打的新草稿,\(i_t\) 决定这份草稿写进去多少。
  • ③ 更新长期记事本:\(c_t=f_t\odot c_{t-1}+i_t\odot\tilde c_t\),旧的擦一部分 + 新的写一部分,是加法。
  • ④ 决定这一步往外说多少:\(o_t\) 过滤 \(\tanh(c_t)\) 得到 \(h_t\)。

三个门各管一件事:

  • 遗忘门 \(f_t\):旧记忆 \(c_{t-1}\) 留多少。
  • 输入门 \(i_t\):新候选内容 \(\tilde c_t\) 记多少。
  • 输出门 \(o_t\):当前这一步从记忆里吐出多少给 \(h_t\)。

关键在细胞更新 \(c_t=f_t\odot c_{t-1}+i_t\odot\tilde c_t\) 是加法——旧记忆按 \(f_t\) 保留、新内容按 \(i_t\) 写入,两路相加放回传送带,而不是像 Vanilla RNN 那样每步把状态整体非线性重写。

实际实现通常把四个 affine transformation 合并成一次大矩阵乘法,提高效率。


4. LSTM 为什么比 Vanilla RNN 更适合长依赖?

Vanilla RNN 每一步都对 hidden state 做完整非线性变换。LSTM 的 cell state 使用加法更新,并由 gate 控制:

\[ \frac{\partial c_t}{\partial c_{t-1}} \approx f_t \]

关键区别在于:cell state 的更新是加法而非像 Vanilla RNN 那样连乘 \(W\)。因此只要遗忘门 \(f_t\approx1\),\(\partial c_t/\partial c_{t-1}\approx1\),梯度就能沿这条”传送带高速公路”几乎无损地一路回传到很早的时间步,缓解梯度消失,从而学到长程记忆;gate 还允许模型自主选择写入、保留和输出哪些信息。

LSTM 只是缓解,不是彻底解决:

  • 很长序列中 gate 乘积仍可能衰减。
  • 仍然串行。
  • 每步有四组门,计算和参数多于 vanilla RNN。
  • 所有历史仍压缩在有限状态中。

GRU 用更少 gate 做相似折中,参数更少但表达取舍依任务而异。


4.1 LSTM 参数量

LSTM 有输入门、遗忘门、输出门和候选状态四组变换。输入维度为 \(d\),隐藏维度为 \(h\) 时,每组包含:

  • 输入权重:\(h\times d\)。
  • 循环权重:\(h\times h\)。
  • 偏置:\(h\)。

因此单层、单向 LSTM 参数量为:

\[ 4(hd+h^2+h) \]

也可把输入和隐藏状态拼接,写成:

\[ 4h(d+h)+4h \]

工程细节

不同框架的偏置实现可能不同。例如 PyTorch LSTM 通常为输入侧和隐藏侧分别保存一组 bias,因此参数量会变为:

\[ 4(hd+h^2+2h) \]

双向 LSTM 还要乘以 2;堆叠层中,后续层输入维度取决于前一层方向数和 hidden size。面试计算前应先说明实现约定。


4.2 GRU

GRU(Gated Recurrent Unit)是 LSTM 的轻量版,做了两处精简:

  • 状态合并:把 LSTM 的 \(c_t\) 和 \(h_t\) 合并成单一隐状态 \(h_t\),不再单独维护细胞状态。
  • 门三变二:只保留两个门。更新门 \(z_t\) 相当于把 LSTM 的遗忘门 + 输入门合成一个”跷跷板”——按下方公式,写入 \(z_t\) 比例的新内容、保留 \(1-z_t\) 比例的旧记忆,二者互补(一个变大另一个必然变小);重置门 \(r_t\) 在算新候选记忆前决定用多少旧记忆,可清零以”切断上下文”。

\[ z_t=\sigma(W_z[x_t;h_{t-1}]) \]

\[ r_t=\sigma(W_r[x_t;h_{t-1}]) \]

\[ \tilde h_t=\tanh(W_h[x_t;r_t\odot h_{t-1}]) \]

\[ h_t=(1-z_t)\odot h_{t-1}+z_t\odot\tilde h_t \]

优点:参数比 LSTM 少约 \(1/4\)、训练更快、小数据上更不易过拟合,多数任务效果与 LSTM 相当;具体哪个更好依任务而异。


5. 如何缓解 RNN 梯度消失或爆炸?

梯度消失

  • 使用 LSTM/GRU 的 gated additive memory。
  • 合理初始化 recurrent matrix,例如 orthogonal initialization。
  • 使用 residual/skip connection。
  • LayerNorm 等归一化方法。
  • 缩短依赖路径,例如引入 Attention。
  • 对任务重新设计,使监督信号更密集。

梯度爆炸

  • Gradient clipping,常按 global norm:

\[ g\leftarrow g\cdot\min\left(1,\frac{\tau}{\|g\|}\right) \]

  • 降低学习率。
  • 稳定初始化和归一化。
  • 检查异常序列与 loss 数值。

Gradient clipping 主要处理爆炸,不能恢复已经消失的梯度。Truncated BPTT 节省计算和显存,但同时截断更远的信用分配,也不是长依赖的根本解法。


6. 什么是 Attention,为什么需要它?

Attention 根据 query 与一组 key 的相关性,对 value 做动态加权:

\[ \alpha_i =\operatorname{softmax}_i(\operatorname{score}(q,k_i)) \]

\[ c=\sum_i\alpha_i v_i \]

Seq2Seq 中的动机

早期 Encoder-Decoder RNN 把整个输入压缩为最后一个固定长度向量,长句容易形成信息瓶颈(正是 §2 里 RNN 固定大小 hidden state 的老问题)。Attention 直接绕开瓶颈:让 Decoder 的每一步都能回看全部输入,按需软对齐(soft alignment) 到相关位置,而不是只依赖一个被压扁的向量。

  • 动态选择当前输出所需的输入位置。
  • 改善长序列信息传递。
  • 学习翻译等任务中的软对齐。

至于 Q/K/V 的完整机制(多头、缩放、位置编码等),在 Transformer 相关章节展开,这里不重复。

常见 Score

  • Dot product:\(q^\top k\)。
  • Scaled dot product:\(q^\top k/\sqrt d\)。
  • Additive/Bahdanau:

\[ v^\top\tanh(W_qq+W_kk) \]

Self-Attention 的 Q/K/V 来自同一序列;Cross-Attention 的 Q 与 K/V 来自不同序列。Transformer 进一步用 Attention 取代主要 recurrent computation。


7. Language Model 的原理

Language Model 对 token 序列分配概率。根据 chain rule:

\[ p(w_{1:T}) =\prod_{t=1}^{T} p(w_t\mid w_{<t}) \]

自回归神经语言模型学习 next-token distribution,训练时最小化负对数似然:

\[ \mathcal L =-\sum_{t=1}^{T} \log p_\theta(w_t\mid w_{<t}) \]

语言模型可用于生成,也可用 log-likelihood 或 perplexity 评价模型对文本的预测能力。


8. N-Gram Language Model

N-Gram 使用 \((n-1)\) 阶 Markov 假设:

\[ p(w_t\mid w_{<t}) \approx p(w_t\mid w_{t-n+1:t-1}) \]

Maximum Likelihood count estimate:

\[ p(w_t\mid h) =\frac{\operatorname{count}(h,w_t)} {\operatorname{count}(h)} \]

优点

  • 简单、可解释、训练和查询快。
  • 小数据和受限领域中可作为强 baseline。

局限

  • 数据稀疏,未见 N-Gram 概率为 0。
  • 上下文长度固定,无法泛化到语义相似历史。
  • 词表和 \(n\) 增大时存储迅速增长。

Smoothing

  • Add-k/Laplace。
  • Backoff 与 interpolation。
  • Good-Turing。
  • Kneser-Ney,利用词在多少种上下文中出现来估计 continuation probability。

9. Word2Vec

Word2Vec 是学习静态词向量的一组浅层神经方法,主要包括 CBOW 和 Skip-Gram。它利用 distributional hypothesis:出现在相似上下文中的词具有相似语义。

CBOW

根据周围上下文预测中心词:

\[ \max_\theta \sum_t \log p(w_t\mid w_{t-c:t-1},w_{t+1:t+c}) \]

通常将上下文 embedding 求和或平均。CBOW 训练快,对高频词表示通常较稳定。

Skip-Gram

根据中心词预测窗口内上下文:

\[ \max_\theta \sum_t\sum_{\substack{-c\le j\le c\\j\ne0}} \log p(w_{t+j}\mid w_t) \]

Skip-Gram 为每个中心词产生多个训练 pair,对低频词通常更有利,但训练成本更高。

Full Softmax

给定中心词 \(w\),预测上下文词 \(c\):

\[ p(c\mid w) =\frac{\exp(u_c^\top v_w)} {\sum_{c'\in V}\exp(u_{c'}^\top v_w)} \]

分母遍历整个词表,词表很大时昂贵。


10. Negative Sampling

Negative Sampling 把多分类预测近似为区分真实 word-context pair 与噪声 pair 的多个二分类任务。

对正样本 \((w,c)\) 和 \(K\) 个负样本 \(n_k\):

\[ \log\sigma(u_c^\top v_w) +\sum_{k=1}^{K} \log\sigma(-u_{n_k}^\top v_w) \]

训练最大化该目标,或最小化其负值。

负样本如何采?

经典 Word2Vec 使用平滑后的 unigram distribution:

\[ P_{\text{neg}}(w) \propto \operatorname{count}(w)^{3/4} \]

\(3/4\) 次幂降低极高频词的支配,同时仍比均匀采样更多抽到常见词。

为什么有效?

  • 每个训练 pair 只更新一个正词和少量负词,从 \(O(|V|)\) 降到约 \(O(K)\)。
  • 迫使真实共现 pair 的点积增大,噪声 pair 的点积减小。

Negative Sampling 的目标不是精确计算 normalized language-model probability,而是高效学习有用 embedding。若任务需要准确概率,应使用 full softmax、hierarchical softmax 或其他概率模型。

其他 Word2Vec 技巧

  • 高频词 subsampling,减少大量低信息 stop words。
  • Dynamic context window。
  • 一个词通常有 input embedding 和 output embedding 两套参数;下游可选择其一或组合。
  • Word2Vec 是 static embedding,同一词在不同上下文中表示相同,无法处理一词多义;contextual embedding 可缓解。

11. Stemming vs Lemmatization

两者都是把词变成更基础的形式,减少词形变化带来的稀疏性。

Stemming

Stemming 用规则粗暴截断词缀:

playing -> play
studies -> studi

优点是快、简单;缺点是结果可能不是合法单词。

Lemmatization

Lemmatization 结合词典和词性,把词还原成 lemma:

better -> good
running -> run

优点是更准确、可读;缺点是需要词性分析和语言资源,速度较慢。

什么时候用?

  • 搜索、传统文本分类、关键词匹配:可以用 stemming 或 lemmatization。
  • 现代 Transformer / LLM:通常直接使用 tokenizer,不一定需要手工 stemming。

面试一句话:

Stemming 是按规则砍词尾,快但粗;lemmatization 是按语言学还原原词,准但慢。

目录

章节
00. 机器学习核心概念
01. 基础与神经网络机制
02. 模型评估与指标
04. 经典机器学习
05. NLP、RNN 与词向量
06. LLM 基础
07. 训练与系统
08. 对齐与 RLHF
09. 推理与部署
11. ML Coding 基础
12. ML Coding 专题
参考资料