动手学深度学习——双向循环神经网络
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 让模型更接近“完整阅读”的方式。
更多推荐


所有评论(0)