1. 前言

前面我们已经学习了:

  • RNN

  • GRU

  • LSTM

  • 深层循环神经网络

到这里,循环神经网络这条线已经很清楚了:

模型沿着时间从前往后处理序列,并把前面的信息传到后面。

这种方式当然很自然,因为很多任务本来就是按时间向前推进的。
但新的问题也很快出现了:

如果当前时刻的理解,不仅需要前面的信息,也需要后面的信息怎么办?

例如一句话里,一个词的含义有时要结合前后文一起看才更准确。
这就引出了这一节的主题:

双向循环神经网络(Bidirectional RNN, BiRNN)

它的核心思想非常直接:

不仅从左到右看序列,也从右到左看序列。


2. 为什么单向 RNN 不够

普通 RNN、GRU、LSTM 默认都是单向的。
也就是说,在时间步 t 上,模型通常只能利用:

  • 当前输入

  • 以及前面时刻的信息

这意味着它对未来信息是“看不到”的。

但很多任务里,仅靠左侧上下文并不总是够用。

2.1 文本理解中的例子

例如一句话:

他拿着一个很轻的苹果

如果这里只看到“苹果”前面,模型可能还不确定这是:

  • 水果

  • 还是苹果公司相关产品

如果再结合后文,例如:

  • “吃了一口”

  • “发布会”

  • “手机”

含义就更清楚了。

这说明:

理解当前位置,有时需要前后文一起参与。


3. 什么是双向循环神经网络

双向循环神经网络可以简单理解为:

在同一个序列上,建立两条循环链:一条从前往后,一条从后往前。

也就是说,对一个序列:

x1, x2, x3, ..., xT

会同时有:

前向 RNN

x1 走到 xT

后向 RNN

xT 走到 x1

然后在每个时间步,把这两个方向得到的隐藏表示合起来,
共同作为当前位置的表示。

所以双向 RNN 的本质是:

让每个位置同时拥有左上下文和右上下文信息。


4. 双向 RNN 的结构怎么理解

假设我们有一个序列:

x1, x2, x3, x4

那么双向 RNN 会有两套隐藏状态。

前向隐藏状态

H_1^f, H_2^f, H_3^f, H_4^f

它们从左到右递推。

后向隐藏状态

H_1^b, H_2^b, H_3^b, H_4^b

它们从右到左递推。

在位置 t,最终表示通常会把两者拼接起来:

H_t = [H_t^f ; H_t^b]

这表示:

  • H_t^f 提供左侧上下文信息

  • H_t^b 提供右侧上下文信息

于是当前位置的表示更完整了。


5. 为什么双向 RNN 更适合理解任务

对于很多“理解类任务”,双向结构非常有优势。

因为这类任务的目标通常是:

对整个输入序列做分析

例如:

  • 命名实体识别

  • 词性标注

  • 序列标注

  • 文本分类中的句子编码

  • 语音识别中的帧标注

在这些任务里,当前位置的判断通常可以合法地依赖未来信息。
因为整个序列在推理时本来就已经完整给出。

所以双向 RNN 特别适合:

输入完整已知,目标是更充分理解当前位置或整体序列。


6. 双向 RNN 不适合什么任务

这点一定要讲清楚。

双向 RNN 虽然强,但它不是万能的。
它不适合那些需要“实时生成”的任务。

例如:

6.1 语言模型

语言模型要做的是:

根据前文预测下一个词

这时候你根本不可能提前看到“后文”,
否则就等于作弊了。

6.2 实时在线预测

如果任务要求模型边读边输出,而未来数据尚未到来,
双向结构也无法使用。

所以双向 RNN 的前提是:

整个序列在处理时已经完整可见。

这也是它和普通单向语言模型的最大使用场景差异。


7. 双向 RNN 的核心公式怎么理解

如果用更数学一点的形式表示,双向 RNN 通常可以写成:

前向递推

H_t^f = f(X_t, H_{t-1}^f)

后向递推

H_t^b = f(X_t, H_{t+1}^b)

然后当前时刻的输出或表示可以写成:

H_t = [H_t^f ; H_t^b]

或者再接一个线性层映射成最终输出。

这里最关键的是:

  • 前向链依赖前一个状态

  • 后向链依赖后一个状态

  • 最后把两个方向合并

这样当前位置就拥有了双向上下文。


8. 为什么通常是“拼接”而不是“相加”

在双向 RNN 中,最常见做法是把两个方向的隐藏状态:

concat

也就是拼接起来。

例如:

  • 前向隐藏状态是 256 维

  • 后向隐藏状态也是 256 维

拼接后就得到:

512 维

为什么更常用拼接?

因为拼接能最大程度保留两个方向各自的信息。
如果直接相加,会把两部分混在一起,信息区分度没那么强。

所以一般默认理解双向 RNN 输出时:

隐藏维度会翻倍。


9. 双向 RNN 和深层 RNN 有什么区别

这两个概念非常容易混,一定要区分。

深层 RNN

强调的是:

层数增加

也就是同一时间步上,多层循环单元堆叠。

双向 RNN

强调的是:

方向增加

也就是同一层里,既从左往右看,也从右往左看。

所以它们是两个完全不同的维度:

  • 深层:沿“深度”加层

  • 双向:沿“方向”加链

而且它们还能组合起来,形成:

多层双向 LSTM / 多层双向 GRU

这在实际任务里非常常见。


10. 双向 RNN 在代码层面怎么实现

PyTorch 对双向循环网络也做了很方便的封装。
核心参数就是:

bidirectional=True

例如:

lstm_layer = nn.LSTM(
    input_size=vocab_size,
    hidden_size=num_hiddens,
    bidirectional=True
)

这一句就表示:

  • 用 LSTM 作为循环单元

  • 同时建立前向和后向两套链

这和前面深层网络里改 num_layers 的感觉很像,
也是一个非常直接的接口。


11. bidirectional=True 后最明显的变化是什么

最明显的变化有两个。

第一,输出隐藏维度翻倍

如果单向 LSTM 的输出是:

hidden_size

那么双向后就会变成:

2 * hidden_size

因为前向和后向隐藏状态拼接了。

第二,状态第一维会乘上方向数

原来的状态形状一般是:

(num_layers, batch_size, hidden_size)

双向后就会变成:

(num_layers * 2, batch_size, hidden_size)

这里多出来的 2 就是两个方向。


12. 为什么状态第一维变成 num_layers * 2

因为每一层现在不再只有一个方向的状态,
而是:

  • 前向一份

  • 后向一份

例如:

单层单向 LSTM

(1, batch_size, hidden_size)

单层双向 LSTM

(2, batch_size, hidden_size)

两层双向 LSTM

(4, batch_size, hidden_size)

因为:

2 层 × 2 方向 = 4

这点在代码里非常重要,后面初始化状态和理解输出都要用到。


13. 双向 RNN 的输出为什么更强

因为每个时间步的表示更完整。

单向 RNN 在位置 t 的表示大致只能概括:

我到当前位置之前看到了什么

而双向 RNN 在位置 t 的表示变成:

我到当前位置之前看到了什么
加上
我从后往前看到当前位置之后还有什么

这让模型更容易 disambiguate(消歧义)和理解上下文。

所以双向结构本质上是在做:

更充分的上下文编码


14. 双向 RNN 在 NLP 中典型适合哪些任务

特别适合下面这些任务:

14.1 序列标注

例如:

  • 词性标注

  • 命名实体识别

  • 分词

因为这些任务对当前位置的判断通常需要前后文共同决定。

14.2 编码器

比如机器翻译中的编码器,经常可以用双向 RNN 来更充分地编码源句子。

14.3 句子表示学习

如果整个句子已经给定,双向 RNN 能更完整提取句子级表示。

所以双向 RNN 往往是:

理解型模块
而不是
生成型模块


15. 为什么双向 RNN 不适合做标准语言模型

这一点一定要再强调一遍。

语言模型的任务是:

根据前文预测下一个 token

如果你用了双向 RNN,那么当前位置的表示已经看到了右侧未来信息。
这时候再去预测“下一个词”,就不公平了。

换句话说:

双向 RNN 会偷看到答案后面的内容。

所以它不适合标准自回归语言模型。
这也是为什么 GPT 这类模型本质上都是单向/因果遮罩的。


16. 李沐这一节最想让你理解什么

这一节的重点,不是公式多复杂,而是场景意识:

第一,双向 RNN 的价值在于前后文同时利用

这会显著增强序列理解能力。

第二,双向和深层是两个不同维度

不要混淆。

第三,双向特别适合理解任务

但不适合标准自回归生成任务。

第四,代码层面通常只需要加 bidirectional=True

这使得双向循环网络很容易落地。

所以这一节本质上是在告诉你:

循环网络不仅能“记过去”,也可以“看未来”。

前提是任务允许这样做。


17. 双向 RNN 和前面内容怎么接上

回头看这条主线会很顺。

先有单向 RNN

学会沿时间从前往后建模。

再有 GRU / LSTM

提升单元的记忆管理能力。

再有深层循环网络

提升层次表示能力。

最后引入双向循环网络

补上“未来上下文”这一侧信息。

所以这一节其实是在循环网络这条线里,再打开一个全新维度:

方向维


18. 本节总结

这一节我们学习了双向循环神经网络,核心内容可以总结为以下几点。

18.1 双向 RNN 会同时建立前向和后向两条循环链

从而让每个位置同时拥有左右上下文信息。

18.2 双向结构特别适合理解类任务

例如序列标注、句子编码、编码器建模等。

18.3 双向和深层是两个不同维度

  • 深层:加层数

  • 双向:加方向

18.4 在代码中通常通过 bidirectional=True 开启

这是最直接的实现方式。

18.5 双向 RNN 不适合标准自回归语言模型

因为它会使用未来信息。


19. 学习感悟

这一节很有意思,因为它让“序列建模”第一次摆脱了“只能往前看”的限制。

以前我们总觉得,序列模型就是:

一步一步从前往后走

但双向 RNN 提醒我们:

如果任务目标不是实时生成,而是充分理解,那么未来信息同样值得利用。

这其实非常符合人类阅读的直觉。
很多时候,我们理解一句话里的某个词,也会结合前后文一起判断,而不是只看左边。

从这个角度看,双向 RNN 让模型更接近“完整阅读”的方式。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐