0%

[DL] PyTorch 折桂 13:RNN

RNN(recurrent neural network)擅长处理序列内容,因此在 NLP 中应用较多。然而 RNN 的拓扑结构与 MLP、CNN 完全不同,因此学习起来会有很大的困扰。本文是介绍如何用锤子敲钉子的,而不是如何造锤子或者为什么要敲的。所以 RNN 的原理与使用场景在这里从略。然而了解 RNN 的工作原理对正确使用 RNN 大有裨益,所以在此附上参考资料 ,供读者参考。

RNN 主要有三个实现:原始 RNN 和 RNN 的改进版 LSTM 和 GRU。一个循环神经网络主要由输入层、隐藏层(RNN 层)、输出层构成,两层之间由激活函数相连。不像 MLP、CNN 那样多个隐藏层必须显式地写出来,RNN 的隐藏层可以以一个 RNN 的参数表示。所以 RNN 网络的格式是:
$$y=\alpha(RNN(x))$$
而 RNN、LSTM 和 GRU 的类也是大同小异:

1
2
3
torch.nn.RNN(input_size, hidden_size, num_layers=1, nonlinearity='tanh', bias=True, batch_first=False, dropout=0, bidirectional=False)
torch.nn.LSTM(input_size, hidden_size, num_layers=1, bias=True, batch_first=False, dropout=0, bidirectional=False)
torch.nn.GRU(input_size, hidden_size, num_layers=1, bias=True, batch_first=False, dropout=0, bidirectional=False)

可以看到,torch.nn.RNN 比其它两个类就多了一个参数 nonlinearity,这是因为 RNN 里的激活函数可以是 tanh 也可以说 relu,而另外两个类的激活函数已经定义好了。下面逐一说明一下:

  • input_size:输入 x 中的特征数;
  • hidden_size:隐藏层的特征数;
  • num_layers:隐藏层的数量;
  • bias:是否有偏置项;
  • batch_first:数据维度中批是否在第一项;
  • dropout:是否有 dropout;
  • bidirectional:RNN 是单向还是双向。

RNN 实例接受的参数有两个:一个张量和上一次的隐藏层:

1
2
>>> rnn = RNN(input_size, hidden_size)
>>> output, hidden_current = rnn(input, hidden_previous)

RNN 的输出有两个,分别是输出值和当前的隐藏层。在 batch_first=True 的时候,当前的隐藏层的维度为 (batch, seq_len, num_directions*hidden_size),而前一个隐藏层的维度为 batch, num_layers*num_directions, hidden_size。我们来看一个例子:我们首先创建一个接受维度为 (1, 5, 2)(每批一个数据点,每个数据点有 5 个特征,两个隐藏层)的 RNN 层,其它参数使用默认参数:

1
>>> rnn = torch.nn.LSTM(1, 5, 2, batch_first=True)

然后创建一个两批、每批 3 个数据点、每个数据点一个特征的张量:

1
2
3
4
5
6
7
8
9
>>> a = torch.rand(2, 3, 1)
>>> print(a)
tensor([[[0.9472],
[0.1003],
[0.7684]],

[[0.8318],
[0.7707],
[0.2214]]])

将这个张量喂给 RNN:

1
>>> out, h = rnn(a)

这里我们没有给 RNN 网络是一个隐藏层的数值,所以 RNN 自动创建了一个权重全为 0 的隐藏层。我们看一下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>>> print(out.size())
torch.Size([2, 3, 5])
>>> print(out)
tensor([[[ 0.0620, 0.0790, -0.0028, -0.1094, 0.1258],
[ 0.0840, 0.0963, -0.0315, -0.1287, 0.1837],
[ 0.0983, 0.1190, -0.0491, -0.1257, 0.2184]],

[[ 0.0612, 0.0764, -0.0047, -0.1101, 0.1244],
[ 0.0865, 0.1130, -0.0228, -0.1283, 0.1899],
[ 0.0992, 0.1151, -0.0485, -0.1235, 0.2183]]],
grad_fn=<TransposeBackward0>)

>>> print(h[0].size())
torch.Size([2, 2, 5])
>>> print(h)
(tensor([[[-0.0562, -0.0368, -0.1863, -0.2322, 0.0921],
[-0.0424, -0.0347, -0.1600, -0.1809, 0.1258]],

[[ 0.0983, 0.1190, -0.0491, -0.1257, 0.2184],
[ 0.0992, 0.1151, -0.0485, -0.1235, 0.2183]]],
grad_fn=<StackBackward>),
tensor([[[-0.1437, -0.0643, -0.3578, -0.3889, 0.1648],
[-0.1044, -0.0650, -0.3243, -0.3031, 0.2357]],

[[ 0.1939, 0.1787, -0.0983, -0.2349, 0.3685],
[ 0.1932, 0.1733, -0.0973, -0.2295, 0.3687]]],
grad_fn=<StackBackward>))

为什么会是这样呢?模型和输入张量的维度分别为:

1
2
3
4
LSTM(input_size,                         hidden_size,     num_layer)
1 5 2
trnsor(batch (if 'batch_first=True'), seq_len, input_size)
2 3 1

输出张量的维度为:

1
2
3
4
out.shape:      2,                             3,               5
batch, seq_len, num_directions*hidden_size
hidden.shape: 2, 2, 5
num_layers*num_directions, batch, hidden_zie

是不是一目了然?这里要注意,RNN 在内部运算的时候,张量的维度是 (inpu_size, batch, hidden_size),虽然我们设置 batch_first=True 将输入和输出的张量的 batch 放到了第一维,输入和输出的 hidden 的 batch 仍然在第二维。

RNN 的改进版 LSTM 和 GRU 的原理可以看这里 1 2 3

欢迎关注我的微信公众号“花解语 NLP”:

欢迎关注我的其它发布渠道