7.5 BiRNN网络#

经过前面几节内容的介绍,我们对于循环神经网络这一类模型已经有了清晰的认识。可以发现,不管是原始的RNN还是后续改进的LSTM和GRU等模型,它们在对输入序列进行特征编码时都是从左往右依次进行,也即在对当前时刻的信息进行编码时只能依赖于当前时刻之前的历史信息。然而,在实际情况中当前时刻之后的信息对于当前时刻的输出结果可能同样有用,例如在类似命名体识别这样的任务中。在这样的背景下,Mike Schuster等人[1]于1997年提出双向循环神经网络结构(Bidirectional Recurrent Neural Networks, BRNN),也简称为BiRNN,来解决这一问题。

7.5.1 BiRNN动机#

传统的RNN模型在处理文本序列时都只能利用当前时刻之前的上下文信息而无法获得其后的上下文信息;相反,如果将文本序列反向输入到RNN模型中则又只能利用到当前位置之后的上下文信息而无法获取其之前的上下文信息。为了能够充分利用文本序列正反两个方向上的语义信息,BiRNN模型通过同时使用正向和反向RNN对输入序列进行特征提取,以便能够同时获得这两个方向上的上下文信息。

由于BiRNN可以分别从正反两个方向上获得文本序列的上下文语义,因此它能够更好地理解序列中的上下文依赖关系,将全局语义信息传递到序列中的每一个位置从而提供更加准确的特征表示;并且由于BiRNN这种特有的正反结构,它还能够更好地处理序列中的长期依赖关系,对长文本序列的处理也有着较好的效果。不过也正是因为BiRNN需要从正反两个方向来依次计算每个时刻的输出结果,这导致其计算速度非常缓慢,因此它也只是常用于一些特定的场景中,如命名实体识别和语言翻译这类需要依靠全局信息的任务[2]。

7.5.2 BiRNN结构#

总的来说BiRNN的模型结构并不复杂,它本质上就是由两个RNN所构成,只是分别将原始序列的正反两个方向作为各自的输入,如图7-13所示。

图 7-13 BiRNN网络结构图

如图7-13所示便是一个单层的BiRNN网络结构,下面的RNN1从正向(Forward)对输入序列进行特征提取,上面的RNN2从反向(Backward)对输入序列进行特征提取,对于两者每个时刻的输出结果既可以再次输入到下一层BiRNN中也可以进行组合直接作为特征使用,而对于正反两个方向的计算过程均与式(7-1)相同。同时,图7-13中的RNN记忆单元也可以替换成LSTM或GRU等。

7.5.3 BiRNN实现#

在清楚双向循环神经网络模型的相关原理之后,我们再来看如何借助PyTorch快速实现模型,示例代码如下所示:

1 def test_BiLSTM():
2     batch_size,time_step = 3, 5
3     input_size,hidden_size = 6, 4
4     x = torch.rand([batch_size, time_step, input_size])
5     lstm = nn.LSTM(input_size, hidden_size, num_layers=1,
6                    batch_first=True, bidirectional=True)
7     output, (hn, cn) = lstm(x)

从上述代码可以看出,PyTorch中双向循环神经网络的使用方式和7.1.6节中介绍的RNN的使用方式一致,只需要将bidirectional设置为True即可(默认为False),因此这里我们就不再赘述。这里需要仔细介绍一下最后输出outputhn的形状。在上述第7行代码中,output的形状为[3,5,8],即[batch_size, time_step, 2*hidden_size],其中第3个维度的前面4列为正向LSTM提取得到的输出结果,后4列为反向LSTM提取得到的输出结果。

如下便是第1个样本的输出结果:

1 tensor([[[ 0.0322, -0.1162, -0.0091, -0.0761, -0.1878, -0.0995,  0.4181, -0.1844],
2          [ 0.0509, -0.1594, -0.0122, -0.1520, -0.1246, -0.0768,  0.3270, -0.0915],
3          [ 0.0054, -0.1520, -0.0167, -0.2036, -0.1360, -0.0494,  0.3670, -0.1104],
4          [-0.0456, -0.1496, -0.0269, -0.2669, -0.0534, -0.0301,  0.2054, -0.1105],
5          [ 0.0211, -0.1875, -0.0312, -0.2397, -0.0353, -0.0387,  0.1633, -0.1326]]])

在上述结果中,左边的5行4列便是正向LSTM各个时刻的输出结果,且第5行为最后一个时刻的输出结果;右边的5行4列便是反向LSTM各个时刻的输出结果,且第1行才是最后一个时刻的输出结果。

进一步,hncn的输出形状均为[2,3,4],即[num_layers*2, batch_size, hidden_size],因为是双向所以第1个维度乘以了2,其中第1个维度的每两个切片均表示图7-13所示的最后一个时刻正反两个方向的输出结果。

如下便是上述代码中hn的输出结果:

1 tensor([[[ 0.0211, -0.1875, -0.0312, -0.2397],
2          [-0.0113, -0.1713, -0.0314, -0.2340],
3          [ 0.0190, -0.1552, -0.0424, -0.2485]],
4 
5         [[-0.1878, -0.0995,  0.4181, -0.1844],
6          [-0.1409, -0.0749,  0.3836, -0.1914],
7          [-0.1214, -0.0129,  0.3164, -0.1672]]])

在上述结果中,第1~3行表示正向LSTM最后一个时刻的输出结果。第5~7行表示反向LSTM最后一个时刻的输出结果,各位读者可以将其与上面output的输出结果进行对比。以上完整示例代码可以参见Code/Chapter07/C06_BiLSTM/main.py内容。

7.5.4 小结#

在本节内容中,我们首先介绍了RNN模型的缺点、BiRNN模型所提出的动机以及其所适用的场景;然后介绍了BiRNN模型的相关原理;最后详细介绍了在PyTorch中BiLSTM的使用方法并详细分析了其输出结果的维度信息。在写一节内容中,我们将会详细介绍基于循环神经网络的序列生成任务。

引用#

[1] Schuster M, Paliwal K K. Bidirectional recurrent neural networks[J]. IEEE transactions on Signal Processing, 1997, 45(11): 2673-2681.

[2] 阿斯顿·张、李沐、扎卡里 C. 立顿等,动手学深度学习[M],2版. 北京:人民邮电出版社, 2019.