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 | torch.nn.RNN(input_size, hidden_size, num_layers=1, nonlinearity='tanh', 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 | rnn = RNN(input_size, hidden_size) |
RNN 的输出有两个,分别是输出值和当前的隐藏层。在 batch_first=True
的时候,当前的隐藏层的维度为 (batch, seq_len, num_directions*hidden_size)
,而前一个隐藏层的维度为 batch, num_layers*num_directions, hidden_size
。我们来看一个例子:我们首先创建一个接受维度为 (1, 5, 2)
(每批一个数据点,每个数据点有 5 个特征,两个隐藏层)的 RNN 层,其它参数使用默认参数:
1 | 1, 5, 2, batch_first=True) rnn = torch.nn.LSTM( |
然后创建一个两批、每批 3 个数据点、每个数据点一个特征的张量:
1 | 2, 3, 1) a = torch.rand( |
将这个张量喂给 RNN:
1 | out, h = rnn(a) |
这里我们没有给 RNN 网络是一个隐藏层的数值,所以 RNN 自动创建了一个权重全为 0 的隐藏层。我们看一下输出:
1 | print(out.size()) |
为什么会是这样呢?模型和输入张量的维度分别为:
1 | LSTM(input_size, hidden_size, num_layer) |
输出张量的维度为:
1 | out.shape: 2, 3, 5 |
是不是一目了然?这里要注意,RNN 在内部运算的时候,张量的维度是 (inpu_size, batch, hidden_size)
,虽然我们设置 batch_first=True
将输入和输出的张量的 batch 放到了第一维,输入和输出的 hidden 的 batch 仍然在第二维。
RNN 的改进版 LSTM 和 GRU 的原理可以看这里 1 2 3。
欢迎关注我的微信公众号“花解语 NLP”: