我需要明确如何使用torch.nn
模块的不同组件正确准备批量训练的输入。具体来说,我正在寻找为seq2seq模型创建编码器 - 解码器网络。
假设我有一个包含这三层的模块,按顺序:
nn.Embedding
nn.LSTM
nn.Linear
nn.Embedding
输入: batch_size * seq_length
输出 batch_size * seq_length * embedding_dimension
我在这里没有任何问题,我只想明确输入和输出的预期形状。
nn.LSTM
输入: seq_length * batch_size * input_size
(在这种情况下为embedding_dimension
)
输出 seq_length * batch_size * hidden_size
last_hidden_state: batch_size * hidden_size
last_cell_state: batch_size * hidden_size
要使用Embedding
图层的输出作为LSTM
图层的输入,我需要转置轴1和2。
我在网上发现的很多例子都有类似x = embeds.view(len(sentence), self.batch_size , -1)
的内容,但这让我很困惑。该视图如何确保同一批次的元素保留在同一批次中?如果len(sentence)
和self.batch
尺寸相同,会发生什么?
nn.Linear
输入: batch_size
x input_size
(在这种情况下LSTM的hidden_size或??)
输出 batch_size
x output_size
如果我只需要last_hidden_state
的{{1}},那么我可以将其作为LSTM
的输入。
但如果我想使用Output(其中也包含所有中间隐藏状态),那么我需要将nn.Linear
的输入大小更改为nn.Linear
并使用输出为输入到seq_length * hidden_size
模块我需要转置输出的第1轴和第2轴,然后我可以使用Linear
进行查看。
我的理解是否正确?如何在张量Output_transposed(batch_size, -1)
?
答案 0 :(得分:24)
您对大多数概念的理解是准确的,但是,这里和那里都有一些缺失点。
您已将(batch_size, seq_len, embedding_size)
形状的输出嵌入。现在,您可以通过多种方式将其传递给LSTM
*如果LSTM
接受输入LSTM
,您可以直接将其传递给batch_first
。因此,在创建LSTM
传递参数batch_first=True
时
*或者,您可以以(seq_len, batch_size, embedding_size)
的形式传递输入。因此,要将嵌入输出转换为此形状,您需要使用torch.transpose(tensor_name, 0, 1)
转置第一维和第二维,就像您提到的那样。
Q值。我在网上看到很多像x = embeds.view(len(sentence),self.batch_size,-1)这样的例子让我很困惑。
答:这是错误的。它将混合批次,你将尝试学习一个绝望的学习任务。无论你在哪里看到这个,你都可以告诉作者改变这个陈述并改为使用转置。
有一种观点支持不使用batch_first
,它声明Nvidia CUDA提供的底层API使用批处理作为辅助运行速度要快得多。
您正在直接将嵌入输出提供给LSTM,这会将LSTM的输入大小固定为上下文大小1.这意味着如果您的输入是LSTM的单词,您将始终一次给它一个单词。但是,这不是我们一直想要的。因此,您需要扩展上下文大小。这可以按照以下方式完成 -
# Assuming that embeds is the embedding output and context_size is a defined variable
embeds = embeds.unfold(1, context_size, 1) # Keeping the step size to be 1
embeds = embeds.view(embeds.size(0), embeds.size(1), -1)
Unfold documentation
现在,您可以按上述方式继续将其提供给LSTM
,只记得seq_len
现已更改为seq_len - context_size + 1
和embedding_size
(这是输入的大小) LSTM)现已更改为context_size * embedding_size
批处理中不同实例的输入大小始终不同。例如,你的一些句子可能长10个字,有些可能是15个,有些可能是1000个。所以,你肯定希望可变长度序列输入到你的经常性单位。为此,在将输入提供给网络之前,需要执行一些其他步骤。您可以按照以下步骤进行操作 -
1.将批次从最大序列分类到最小序列
2.创建一个seq_lengths
数组,用于定义批处理中每个序列的长度。 (这可以是一个简单的python列表)
3.将所有序列填充到与最大序列相等的长度
4.创建此批次的LongTensor变量
5.现在,在通过嵌入和创建适当的上下文大小输入传递上述变量之后,您需要按如下方式打包您的序列 -
# Assuming embeds to be the proper input to the LSTM
lstm_input = nn.utils.rnn.pack_padded_sequence(embeds, [x - context_size + 1 for x in seq_lengths], batch_first=False)
现在,一旦你准备了lstm_input
acc。根据您的需要,您可以将lstm称为
lstm_outs, (h_t, h_c) = lstm(lstm_input, (h_t, h_c))
这里,(h_t, h_c)
需要作为初始隐藏状态提供,它将输出最终的隐藏状态。您可以看到,为什么需要包装可变长度序列,否则LSTM也将运行非必需的填充单词。
现在,lstm_outs
将是一个打包序列,它是每一步的lstm输出,(h_t, h_c)
分别是最终输出和最终单元状态。 h_t
和h_c
的形状为(batch_size, lstm_size)
。您可以直接使用它们进行进一步输入,但如果您想使用中间输出,则需要首先解压缩lstm_outs
,如下所示
lstm_outs, _ = nn.utils.rnn.pad_packed_sequence(lstm_outs)
现在,您的lstm_outs
将成型(max_seq_len - context_size + 1, batch_size, lstm_size)
。现在,您可以根据需要提取lstm的中间输出。
请记住,解压缩的输出在每个批处理的大小之后将具有0,这只是填充以匹配最大序列的长度(始终是第一个序列,因为我们将输入从最大值排序到最小值)。
另请注意,h_t将始终等于每个批输出的最后一个元素。
现在,如果你只想使用lstm的输出,你可以直接将h_t
提供给你的线性层,它会起作用。但是,如果你想使用中间输出,那么,你需要弄清楚,你将如何将它输入线性层(通过一些注意网络或一些池)。您不希望将完整序列输入到线性图层,因为不同的序列将具有不同的长度,并且您无法修复线性图层的输入大小。是的,你需要转换lstm的输出以便进一步使用(再次你不能在这里使用视图)。
结束注意:我故意留下一些点,例如使用双向重复单元格,在展开时使用步长,以及连接注意力,因为它们会变得相当麻烦并且超出了这个答案的范围。