理解Pytorch中LSTM的输入输出参数含义

  • 1、举个栗子

在介绍LSTM各种参数含义之前我们还是需要先用一个例子(参考LSTM神经网络输入输出究竟是怎样的?Scofield的回答)来理解LSTM。

Recurrent NNs,一般看的最多的图是这个:

理解Pytorch中LSTM的输入输出参数含义

rnn但是这个图对初学者相当不太友好。个人认为,目前所有的关于描述RecurrentNNs的图都画得不好,不够明确,里面的细节丢失了。(事实上里面一个"A"仅仅表示了一层的变换,具体如下图所示。)

理解Pytorch中LSTM的输入输出参数含义

非常清楚,这是很多初学者不能理解RecurrentNNs的根本原因,即在于Recurrent NNs是在time_step上的拓展的这一特性。MLP好理解,CNN也好理解,但Recurrent NNs,就是无法搞清楚里面的拓扑结构,跟MLP联系不上。

先看看MLP,很好理解,就是一张网络清楚地显示了张量流向。

general MLP是这样的拓扑:

理解Pytorch中LSTM的输入输出参数含义

mlp然后CNN也好理解,跟MLP无差若干,只是权重运算由\(*\)变为\(\otimes\)。CNN是这样的拓扑:

理解Pytorch中LSTM的输入输出参数含义

但RecurrentNNs的拓扑发生了一个很大的改动,即一个MLP会在time_step这个维度上进行延伸,每个时序都会有input。

所以RecurrentNNs的结构图应该这样画,在理解上才会更清晰些,对比MLP,也一目了然。(为了简约,只画了4个time-steps )……

理解Pytorch中LSTM的输入输出参数含义

如上图所示,

  • 每个时序\(t\) 的输入\(T_i^t\),也就是说一次time_step输入一个input tensor。
  • 隐状态\(h_i^t\)也就代表了一张MLP的hidden layer的一个cell,可以看到中间黄色圈圈就表示隐藏层.
  • 输出\(O_i^t\)理解无异,可以看到每个时序的输出节点数是等于隐藏节点数的。注意,红色的箭头指向仅仅表示数据流动方向,并不是表示隐藏层之间相连。

再结合一个操作实例说明。如果我们有一条长文本,我们给句子事先分割好句子,并且进行tokenize, dictionarize,接着再由look up table 查找到embedding,将token由embedding表示,再对应到上图的输入。流程如下:

  • step1, raw text (语料库如下):
    接触LSTM模型不久,简单看了一些相关的论文,还没有动手实现过。然而至今仍然想不通LSTM神经网络究竟是怎么工作的。……
  • step2, tokenize (中文得分词):
    • sentence1:  接触 LSTM 模型 不久 ,简单 看了 一些 相关的 论文 , 还 没有 动手 实现过 。
    • sentence2: 然而 至今 仍然 想不通 LSTM 神经网络 究竟是 怎么 工作的。
    • ……
  • step3, dictionarize:
    • sentence1: 1 34 21 98 10 23 9 23
    • sentence2: 17 12 21 12 8 10 13 79 31 44 9 23
    • ……
  • step4, padding every sentence to fixed length:
    • sentence1: 1 34 21 98 10 23 9 23 0 0 0 0 0
    • sentence2: 17 12 21 12 8 10 13 79 31 44 9 23  0
    • ……
  • step5, mapping token to an embeddings:
    • sentence1:\(\left[\begin{array}{cccc}{0.341} & {0.133} & {0.011} & {\cdots} \\ {0.435} & {0.081} & {0.501} & {\cdots} \\ {0.013} & {0.958} & {0.121} & {\ldots} \\ {\cdots} & {\cdots} & {\cdots} & {\cdots}\end{array}\right]\),每一列代表一个词向量,词向量维度自行确定(假设一个单词由长度为100的向量表示);矩阵列数固定为time_step length。
    • sentence2:  ...
    • ……
  • step6, feed into RNNs as input:
    假设 一个RNN的time_step 确定为\(l\),则padded sentence length(step5中矩阵列数)固定为  \(l\)。一次RNNs的run只处理一条sentence。每个sentence的每个token的embedding对应了每个时序  的输入  。一次RNNs的run,连续地将整个sentence处理完。简单理解就是每次传入RNN的句子长度为\(l\),换句话就是RNN横向长度为\(l\)
  • step7, get output:
    看图,每个time_step都是可以输出当前时序\(t\)的隐状态\(h_i^t\);但整体RNN的输出\(O_i^t\)是在最后一个time_step\(t=l\)时获取,才是完整的最终结果。
  • step8, further processing with the output:
    我们可以将output根据分类任务或回归拟合任务的不同,分别进一步处理。比如,传给cross_entropy&softmax进行分类……或者获取每个time_step对应的隐状态\(h_i^t\),做seq2seq 网络……或者搞创新……

2、Pytorch源代码参数理解

2.1 LSTM模型参数含义

通过源代码中可以看到nn.LSTM继承自nn.RNNBase,其初始化函数定义如下

class RNNBase(Module):
	...
    def __init__(self, mode, input_size, hidden_size,
                 num_layers=1, bias=True, batch_first=False,
                 dropout=0., bidirectional=False):

我们需要关注的参数以及其含义解释如下:

  • input_size – 输入数据的大小,也就是前面例子中每个单词向量的长度
  • hidden_size – 隐藏层的大小(即隐藏层节点数量),输出向量的维度等于隐藏节点数
  • num_layers – recurrent layer的数量,默认等于1。
  • bias – If False, then the layer does not use bias weights b_ih and b_hh. Default: True
  • batch_first – 默认为False,也就是说官方不推荐我们把batch放在第一维,这个CNN有点不同,此时输入输出的各个维度含义为 (seq_length,batch,feature)。当然如果你想和CNN一样把batch放在第一维,可将该参数设置为True。
  • dropout – 如果非0,就在除了最后一层的其它层都插入Dropout层,默认为0。
  • bidirectional – If True, becomes a bidirectional LSTM. Default: False

2.2 输入数据

下面介绍一下输入数据的维度要求(batch_first=False):

输入数据需要按如下形式传入 input, (h_0,c_0)

  • input: 输入数据,即上面例子中的一个句子(或者一个batch的句子),其维度形状为 (seq_len, batch, input_size)
    • seq_len: 句子长度,即单词数量,这个是需要固定的。当然假如你的一个句子中只有2个单词,但是要求输入10个单词,这个时候可以用torch.nn.utils.rnn.pack_padded_sequence()或者torch.nn.utils.rnn.pack_sequence()来对句子进行填充或者截断。
    • batch:就是你一次传入的句子的数量
    • input_size: 每个单词向量的长度,这个必须和你前面定义的网络结构保持一致
  • h_0:维度形状为 (num_layers * num_directions, batch, hidden_size):
    • 结合下图应该比较好理解第一个参数的含义num_layers * num_directions, 即LSTM的层数乘以方向数量。这个方向数量是由前面介绍的bidirectional决定,如果为False,则等于1;反之等于2。
    • batch:同上
    • hidden_size: 隐藏层节点数
  • c_0: 维度形状为 (num_layers * num_directions, batch, hidden_size),各参数含义和h_0类似。

当然,如果你没有传入(h_0, c_0),那么这两个参数会默认设置为0。

2.3 输出数据

  • output: 维度和输入数据类似,只不过最后的feature部分会有点不同,即 (seq_len, batch, num_directions * hidden_size)
    • 这个输出tensor包含了LSTM模型最后一层每个time step的输出特征,比如说LSTM有两层,那么最后输出的是\([h^1_0,h^1_1,...,h^1_l]\),表示第二层LSTM每个time step对应的输出。
    • 另外如果前面你对输入数据使用了torch.nn.utils.rnn.PackedSequence,那么输出也会做同样的操作编程packed sequence。
    • 对于unpacked情况,我们可以对输出做如下处理来对方向作分离output.view(seq_len, batch, num_directions, hidden_size), 其中前向和后向分别用0和1表示Similarly, the directions can be separated in the packed case.
  • h_n:(num_layers * num_directions, batch, hidden_size),
    • 只会输出最后个time step的隐状态结果(如下图所示)。
    • Like output, the layers can be separated using h_n.view(num_layers, num_directions, batch, hidden_size) and similarly for c_n.
  • c_n :(num_layers * num_directions, batch, hidden_size),只会输出最后个time step的cell状态结果(如下图所示)。

理解Pytorch中LSTM的输入输出参数含义

3、 代码示例

rnn = nn.LSTM(10, 20, 2) # 一个单词向量长度为10,隐藏层节点数为20,LSTM有2层
input = torch.randn(5, 3, 10) # 输入数据由3个句子组成,每个句子由5个单词组成,单词向量长度为10
h0 = torch.randn(2, 3, 20) # 2:LSTM层数*方向 3:batch 20: 隐藏层节点数
c0 = torch.randn(2, 3, 20) # 同上
output, (hn, cn) = rnn(input, (h0, c0))

print(output.shape, hn.shape, cn.shape)

>>> torch.Size([5, 3, 20]) torch.Size([2, 3, 20]) torch.Size([2, 3, 20])


上一篇:Pytorch-基础入门之LSTM


下一篇:基于SARIMA-LSTM的门诊量预测研究