PyTorch1.0+中torch.nn.LSTM()的详解

先上结论:
output保存了最后一层,每个time step的输出h,如果是双向LSTM,每个time step的输出h = [h正向, h逆向] (同一个time step的正向和逆向的h连接起来)。
h_n保存了每一层,最后一个time step的输出h,如果是双向LSTM,单独保存前向和后向的最后一个time step的输出h。
c_n与h_n一致,只是它保存的是c的值。
import torch
import torch.nn as nn
lstm = nn.LSTM(10, 20, 2)
x = torch.randn(5, 3, 10)
h0 = torch.randn(2, 3, 20)
c0 = torch.randn(2, 3, 20)
output, (hn, cn)=lstm(x, (h0, c0))
##------->>
output.shape torch.Size([5, 3, 20])
hn.shape torch.Size([2, 3, 20])
cn.shape torch.Size([2, 3, 20])

举个例子:
对句子进行LSTM操作
假设有100个句子(sequence),每个句子里有7个词,batch_size=64,embedding_size=300
此时,各个参数为:
input_size=embedding_size=300
batch=batch_size=64
seq_len=7
另外设置hidden_size=100, num_layers=1
import torch
import torch.nn as nn
lstm = nn.LSTM(300, 100, 1)
x = torch.randn(7, 64, 300)
h0 = torch.randn(1, 64, 100)
c0 = torch.randn(1, 64, 100)
output, (hn, cn)=lstm(x, (h0, c0))
#>>
output.shape torch.Size([7, 64, 100])
hn.shape torch.Size([1, 64, 100])
cn.shape torch.Size([1, 64, 100])
下面单独分析三个输出:
- output是一个三维的张量,第一维表示序列长度,第二维表示一批的样本数(batch),第三维是 hidden_size(隐藏层大小) * num_directions ,这里是我遇到的第一个不理解的地方,hidden_sizes由我们自己定义,num_directions这是个什么鬼?翻看源码才明白,先贴出代码,从代码中可以发现num_directions根据是“否为双向”取值为1或2。因此,我们可以知道,output第三个维度的尺寸根据是否为双向而变化,如果不是双向,第三个维度等于我们定义的隐藏层大小;如果是双向的,第三个维度的大小等于2倍的隐藏层大小。为什么使用2倍的隐藏层大小?因为它把每个time step的前向和后向的输出连接起来了,后面会有一个实验,方便我们记忆。

2. h_n是一个三维的张量,第一维是num_layers*num_directions,num_layers是我们定义的神经网络的层数,num_directions在上面介绍过,取值为1或2,表示是否为双向LSTM。第二维表示一批的样本数量(batch)。第三维表示隐藏层的大小。第一个维度是h_n难理解的地方。首先我们定义当前的LSTM为单向LSTM,则第一维的大小是num_layers,该维度表示第n层最后一个time step的输出。如果是双向LSTM,则第一维的大小是2 * num_layers,此时,该维度依旧表示每一层最后一个time step的输出,同时前向和后向的运算时最后一个time step的输出用了一个该维度。
- 举个例子,我们定义一个num_layers=3的双向LSTM,h_n第一个维度的大小就等于 6 (2*3),h_n[0]表示第一层前向传播最后一个time
step的输出,h_n[1]表示第一层后向传播最后一个time step的输出,h_n[2]表示第二层前向传播最后一个time step的输出,h_n[3]表示第二层后向传播最后一个time step的输出,h_n[4]和h_n[5]分别表示第三层前向和后向传播时最后一个time step的输出。
3. c_n与h_n的结构一样,就不重复赘述了。
给出一个样例图(画工太差,如有错误请指正),对比前面的例子自己分析下

最后上一段代码结束战斗
import torch
import torch.nn as nn
定义一个两层双向的LSTM,input size为10,hidden size为20。
随机生成一个输入样本,sequence length为5,batch size为3,input size与定义的网络一致,为10。
手动初始化h0和c0,两个结构一致(num_layers * 2, batch, hidden_size) = (4, 3, 20)。
如果不初始化,PyTorch默认初始化为全零的张量。
bilstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2, bidirectional=True)
input = torch.randn(5, 3, 10)
h0 = torch.randn(4, 3, 20)
c0 = torch.randn(4, 3, 20)
output, (hn, cn) = bilstm(input, (h0, c0))
查看output,hn,cn的维度
print('output shape: ', output.shape)
print('hn shape: ', hn.shape)
print('cn shape: ', cn.shape)
输出:
output shape: torch.Size([5, 3, 40])
hn shape: torch.Size([4, 3, 20])
cn shape: torch.Size([4, 3, 20])
根据一开始结论,我们来验证下。
1.前向传播时,output中最后一个time step的前20个与hn最后一层前向传播的输出应该一致。
output[4, 0, :20] == hn[2, 0]
输出:
tensor([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1], dtype=torch.uint8)
2.后向传播时,output中最后一个time step的后20个与hn最后一层后向传播的输出应该一致。
output[0, 0, 20:] == hn[3, 0]
输出:
tensor([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1], dtype=torch.uint8)