约 30 分钟 · 需要矩阵乘法、链式法则与激活函数

读完你能: 写出标准 RNN 的前向递推公式与各矩阵维度;区分序列到单值、单值到序列、同步序列标注与编码器—解码器结构;手算一步 LSTM 与 GRU 的状态更新;判断双向序列模型在哪些任务中适用、哪些任务中会造成未来信息泄漏。

1. 为什么词袋模型处理不好语序与长距离依赖

在词袋表示里,the service is not good 与 the service is good, not bad 只要词频接近,向量就很相似;普通线性分类器或前馈网络既没有记忆历史状态的结构,也难以自然处理长度可变的句子。

循环神经网络(RNN)按时间步 t=1,2,…,Tt=1,2,\dots,T 依次读入词向量 xtx_t,并用隐藏状态 hth_t 保存截至当前位置的历史摘要。最基本的 Vanilla RNN 在每个时间步共享同一组参数:

ht=tanh⁡(Wxxt+Whht−1+bh),pt=softmax⁡(Woht+bo).h_t=\tanh(W_xx_t+W_hh_{t-1}+b_h),\qquad p_t=\operatorname{softmax}(W_oh_t+b_o).

设输入词向量维度为 dd、隐藏状态维度为 hh、输出类别数为 CC,各量的尺寸如下:

符号 含义 维度
xt, htx_t,\ h_t 第 tt 步输入向量、隐藏状态 d×1d\times 1、h×1h\times 1
Wx, Wh, bhW_x,\ W_h,\ b_h 输入权重、循环权重、隐藏偏置 h×dh\times d、h×hh\times h、h×1h\times 1
Wo, boW_o,\ b_o 输出层权重、输出层偏置 C×hC\times h、C×1C\times 1

无论句子有 5 个词还是 50 个词,复用的都是同一套 (Wx,Wh,bh)(W_x,W_h,b_h)。词嵌入表负责把离散词元编号映射成稠密向量 xtx_t,而 RNN 负责沿着时间轴传递和更新状态 hth_t。

2. 手算两步标量 RNN

令所有维度都取 1,参数设为 Wx=0.5, Wh=0.5, bh=0W_x=0.5,\ W_h=0.5,\ b_h=0,初始状态 h0=0.2h_0=0.2,依次输入 x1=1x_1=1、x2=0x_2=0:

h1=tanh⁡(0.5×1+0.5×0.2+0)=tanh⁡(0.6)≈0.5370,h_1=\tanh(0.5\times1+0.5\times0.2+0)=\tanh(0.6)\approx0.5370,
h2=tanh⁡(0.5×0+0.5×0.5370+0)=tanh⁡(0.2685)≈0.2623.h_2=\tanh(0.5\times0+0.5\times0.5370+0)=\tanh(0.2685)\approx0.2623.

可以看到,尽管第二步输入 x2=0x_2=0,状态 h2h_2 依然非零,因为它从 h1h_1 继承了第一步的信息;同时由于乘了循环权重 0.50.5 并经过 tanh⁡\tanh 压缩,信号强度也比第一步有所衰减。

3. 同一套循环递推,四种常见输入输出结构

根据任务对输入与输出形态的要求,RNN 可以组装成四种常见结构:

  • 序列到单值(Sequence-to-One):读完整句后只输出一个类别,例如情感分类。通常取最后一步隐藏状态 hTh_T(或所有时间步状态的池化)送入分类层。
  • 单值到序列(One-to-Sequence):由一个初始向量出发逐步生成序列,例如根据图像特征生成一句图片描述。
  • 同步序列到序列(Sequence-to-Sequence Tagging):每个输入词元 xtx_t 对应输出一个标签 yty_t,输入与输出长度严格相等,例如词性标注(POS)和命名实体识别(NER)。
  • 编码器—解码器(Encoder–Decoder):先用编码器读完源序列浓缩成上下文表示,再用解码器逐步生成目标序列,适合机器翻译、摘要等输入输出长度不等的任务。

在自回归文本生成中,第 tt 步只能看到 x≤tx_{\le t} 来预测下一个词。训练时常直接把真实历史前缀喂给模型(teacher forcing),而测试生成时每一步只能把模型自己刚生成的词作为下一步输入;训练与推断的输入分布差异需要留意。此外,工程批处理(batching)会把长短不一的句子补齐(padding)到相同长度,计算损失、池化或取末态时必须用掩码(mask)屏蔽补齐位置。

4. 激活函数与梯度消失、梯度爆炸

序列模型里最常成对出现的两个非线性函数是 sigmoid 与 tanh:

  • Sigmoid:σ(x)=11+e−x∈(0,1)\sigma(x)=\dfrac{1}{1+e^{-x}}\in(0,1),导数为 σ′(x)=σ(x)(1−σ(x))≤0.25\sigma'(x)=\sigma(x)(1-\sigma(x))\le 0.25。因为输出落在 (0,1)(0,1) 之间,非常适合用作控制信息开闭比例的“门”;但它的输出恒为正(非零中心),且当 ∣x∣\lvert x\rvert 较大时导数趋近 0。
  • Tanh:tanh⁡(x)=ex−e−xex+e−x=2σ(2x)−1∈(−1,1)\tanh(x)=\dfrac{e^x-e^{-x}}{e^x+e^{-x}}=2\sigma(2x)-1\in(-1,1),导数为 tanh⁡′(x)=1−tanh⁡2(x)≤1\tanh'(x)=1-\tanh^2(x)\le 1。它的输出以 0 为中心,适合表示正负兼有的状态和候选内容,但饱和区同样存在导数趋近 0 的问题。

当沿时间反向传播(BPTT)计算早期状态 hkh_k 对末步状态 hTh_T 的梯度时,链式法则会产生一连串雅可比矩阵的乘积:

∂hT∂hk=∏t=k+1T∂ht∂ht−1.\frac{\partial h_T}{\partial h_k}=\prod_{t=k+1}^T\frac{\partial h_t}{\partial h_{t-1}}.

如果这些雅可比矩阵的主特征值持续小于 1(再加上 tanh⁡′≤1\tanh'\le 1 的反复收缩),相隔几十步之后梯度就会呈指数级衰减到接近 0,导致模型学不到远距离依赖,即梯度消失;反之,若循环权重的主特征值大于 1,连乘会让梯度急剧放大,导致参数更新失控,即梯度爆炸。

梯度裁剪(见第10章)可以有效抑制梯度爆炸,但无法凭空恢复已经衰减殆尽的远距离梯度;要让长距离信号顺畅传递,需要从网络结构上改造状态更新方式。

5. LSTM:加法细胞通道与三道门

长短期记忆网络(LSTM)在隐藏状态 hth_t 之外,专门维护一条承载长期记忆的细胞状态(cell state) ctc_t,并用遗忘门、输入门和输出门调控信息读写。记当前步拼接向量为 at=[ht−1;xt]a_t=[h_{t-1};x_t](等价于分别乘输入矩阵 UU 和循环矩阵 WW 后相加):

ft=σ(Wfat+bf),it=σ(Wiat+bi),gt=tanh⁡(Wgat+bg),ot=σ(Woat+bo),\begin{aligned} f_t&=\sigma(W_fa_t+b_f),&\quad i_t&=\sigma(W_ia_t+b_i),\\ g_t&=\tanh(W_ga_t+b_g),&\quad o_t&=\sigma(W_oa_t+b_o), \end{aligned}
ct=ft⊙ct−1+it⊙gt,ht=ot⊙tanh⁡(ct).c_t=f_t\odot c_{t-1}+i_t\odot g_t,\qquad h_t=o_t\odot\tanh(c_t).

其中 ⊙\odot 表示逐元素相乘,候选状态 gtg_t 在许多文献中也记作 c~t\widetilde c_t。四个分量的分工非常清晰:

分量 取值范围 作用
遗忘门 ftf_t (0,1)(0,1) 决定旧细胞状态 ct−1c_{t-1} 保留多少比例
输入门 iti_t (0,1)(0,1) 决定当前候选信息 gtg_t 写入多少比例
候选状态 gtg_t(或 c~t\widetilde c_t) (−1,1)(-1,1) 由当前输入与前一隐藏状态提炼出的新候选内容
输出门 oto_t (0,1)(0,1) 决定更新后的细胞状态 tanh⁡(ct)\tanh(c_t) 对外暴露多少给 hth_t

为什么 LSTM 能缓解梯度消失?关键在于 ct=ft⊙ct−1+it⊙gtc_t=f_t\odot c_{t-1}+i_t\odot g_t 是线性加权相加,而不是普通 RNN 那样每一步都把旧状态套进 tanh⁡(Whht−1+… )\tanh(W_hh_{t-1}+\dots)。当某个维度上的遗忘门 ftf_t 接近 1、输入门 iti_t 接近 0 时,早期信息几乎可以无损地沿细胞通道传到很多步之后,对应方向的局部偏导也接近 1。

例1: 对一维 LSTM,已知旧细胞状态 ct−1=0.5c_{t-1}=0.5,当前步门值与候选为 ft=0.8, it=0.25, gt=0.4, ot=0.6f_t=0.8,\ i_t=0.25,\ g_t=0.4,\ o_t=0.6,则:

ct=0.8×0.5+0.25×0.4=0.4+0.1=0.5,c_t=0.8\times0.5+0.25\times0.4=0.4+0.1=0.5,
ht=0.6×tanh⁡(0.5)≈0.6×0.4621=0.2773.h_t=0.6\times\tanh(0.5)\approx0.6\times0.4621=0.2773.

注意 ctc_t 与 hth_t 的区别:ctc_t 在内部通道上保持了 0.50.5,而对外输出的隐藏状态 hth_t 经过了 tanh⁡\tanh 压缩和输出门 ot=0.6o_t=0.6 的缩放。

6. GRU:合并细胞状态,只留重置门与更新门

门控循环单元(GRU)将细胞状态与隐藏状态合二为一,只维护单一状态向量 hth_t,并用两道门控制更新,参数量比同维度的 LSTM 更少。

本章将更新门 ztz_t 定义为写入新候选状态的比例:

rt=σ(Wrxt+Urht−1+br),zt=σ(Wzxt+Uzht−1+bz),r_t=\sigma(W_rx_t+U_rh_{t-1}+b_r),\qquad z_t=\sigma(W_zx_t+U_zh_{t-1}+b_z),
h~t=tanh⁡(Whxt+Uh(rt⊙ht−1)+bh),ht=(1−zt)⊙ht−1+zt⊙h~t.\widetilde h_t=\tanh\big(W_hx_t+U_h(r_t\odot h_{t-1})+b_h\big),\qquad h_t=(1-z_t)\odot h_{t-1}+z_t\odot\widetilde h_t.
  • **重置门 rtr_t**:在构造当前步候选状态 h~t\widetilde h_t 时,控制保留多少过去状态 ht−1h_{t-1};若 rt→0r_t\to 0,候选状态就近似只看当前输入 xtx_t,相当于“重置”历史。
  • **更新门 ztz_t**:在旧状态 ht−1h_{t-1} 与新候选 h~t\widetilde h_t 之间做凸组合,同时扮演了 LSTM 中遗忘门(1−zt1-z_t)和输入门(ztz_t)的角色。

例2: 设单维状态 ht−1=0.5h_{t-1}=0.5,算得候选状态 h~t=0.1\widetilde h_t=0.1,更新门 zt=0.3z_t=0.3,则新状态为

ht=(1−0.3)×0.5+0.3×0.1=0.35+0.03=0.38.h_t=(1-0.3)\times0.5+0.3\times0.1=0.35+0.03=0.38.

对照论文与框架代码时先核对更新门方向

深度学习框架(如 PyTorch 的 nn.GRU)和部分教材会把最后一步写成 ht=zt⊙ht−1+(1−zt)⊙h~th_t=z_t\odot h_{t-1}+(1-z_t)\odot\widetilde h_t,也就是把 ztz_t 定义为“保留旧状态的比例”;同时重置门 rtr_t 作用在乘 UhU_h 之前还是之后也有细微差别。两种写法表达能力等价,但手算或迁移权重时务必先看清公式约定。

7. 双向 RNN:什么时候能看右侧上下文

阅读整句话做词性标注或实体识别时,一个词的意思往往同时取决于它前面和后面的词。双向循环网络(Bi-RNN / Bi-LSTM / Bi-GRU)设置两个独立的循环分支:

  • 前向分支从左到右按 t=1→Tt=1\to T 计算 h→t\overrightarrow{h}_t;
  • 反向分支从右到左按 t=T→1t=T\to 1 计算 h←t\overleftarrow{h}_t;
  • 在每个位置 tt 将两者拼接为 ht=[h→t;h←t]h_t=[\overrightarrow{h}_t;\overleftarrow{h}_t]。

若单向隐藏维度为 hh,拼接后的表示维度变为 2h2h,下游全连接层的输入维度也相应翻倍。

双向结构适用的前提是推断时整段输入序列已经完整可见(如句子分类、整句 POS/NER、编码器端编码)。如果任务是因果语言建模或实时流式预测(根据 x1,…,xtx_1,\dots,x_t 预测尚未发生的 xt+1x_{t+1}),反向分支会把包含 xt+1,…,xTx_{t+1},\dots,x_T 的未来信息直接传给位置 tt,造成严重的信息泄漏。

自测

1. 在 LSTM 中,如果某一步遗忘门 f_t=1、输入门 i_t=0,细胞状态 c_t 和隐藏输出 h_t 会发生什么变化?

细胞状态完全保留上一步的值:ct=1⋅ct−1+0⋅gt=ct−1c_t=1\cdot c_{t-1}+0\cdot g_t=c_{t-1}。但隐藏状态 ht=ot⊙tanh⁡(ct)h_t=o_t\odot\tanh(c_t) 还受当前步输出门 oto_t 的控制;只要 ot≠ot−1o_t\ne o_{t-1},对外输出的 hth_t 仍会改变。

2. 按本章第 6 节的 GRU 公式,z_t=0 和 z_t=1 分别对应什么行为?

在本章 ht=(1−zt)⊙ht−1+zt⊙h~th_t=(1-z_t)\odot h_{t-1}+z_t\odot\widetilde h_t 的约定下:zt=0z_t=0 时 ht=ht−1h_t=h_{t-1},完全保留旧状态、忽略新候选;zt=1z_t=1 时 ht=h~th_t=\widetilde h_t,完全用新候选覆盖旧状态。