如何正确地为 PyTorch 中的嵌入、LSTM 和线性层提供输入?
Posted
技术标签:
【中文标题】如何正确地为 PyTorch 中的嵌入、LSTM 和线性层提供输入?【英文标题】:How to correctly give inputs to Embedding, LSTM and Linear layers in PyTorch? 【发布时间】:2018-09-03 03:32:47 【问题描述】:我需要了解如何使用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 的隐藏大小还是??)输出: batch_size
x output_size
如果我只需要LSTM
的last_hidden_state
,那么我可以将其作为输入提供给nn.Linear
。
但是,如果我想使用 Output(其中还包含所有中间隐藏状态),那么我需要将 nn.Linear
的输入大小更改为 seq_length * hidden_size
并将 Output 作为输入到 Linear
模块 I需要转置输出的轴1和轴2,然后我可以用Output_transposed(batch_size, -1)
查看。
我的理解正确吗?如何在张量(tensor.transpose(0, 1))
中进行这些转置操作?
【问题讨论】:
【参考方案1】:您对大多数概念的理解是准确的,但是,这里和那里都存在一些缺失点。
将嵌入接口连接到 LSTM(或任何其他循环单元)
您有(batch_size, seq_len, embedding_size)
形状的嵌入输出。现在,您可以通过多种方式将其传递给 LSTM。
* 如果LSTM
接受输入为batch_first
,您可以将其直接传递给LSTM
。因此,在创建您的 LSTM
时,传递参数 batch_first=True
。
* 或者,您可以以(seq_len, batch_size, embedding_size)
的形式传递输入。因此,要将嵌入输出转换为此形状,您需要使用torch.transpose(tensor_name, 0, 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 的输入大小)现在更改为@987654336 @
使用可变序列长度
批次中不同实例的输入大小不会始终相同。例如,您的句子中的一些可能是 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 的输出
现在,一旦您准备好您的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 连接到线性
现在,如果您只想使用 lstm 的输出,您可以直接将 h_t
输入到您的线性层,它会起作用。但是,如果您也想使用中间输出,那么您需要弄清楚,您将如何将其输入到线性层(通过一些注意力网络或一些池化)。您不想将完整的序列输入到线性层,因为不同的序列将具有不同的长度,并且您无法固定线性层的输入大小。是的,您需要转置 lstm 的输出以供进一步使用(同样,您不能在此处使用视图)。
结束说明:我特意留下了一些要点,例如使用双向循环单元,在展开时使用步长以及接口注意力,因为它们可能会变得非常麻烦并且超出了此答案的范围。
【讨论】:
感谢您花时间写出这么棒的答案!我只是想为我补充一点embeds = embeds.contiguous().view(embeds.size(0), embeds.size(1), -1)
是一个有效的方法,否则我得到 input is not contiguous 错误。
伟大而重要的文章。从view
更改为“转置”以准备 rnn 输入的嵌入输出刚刚救了我。我注意到任何大于 1 的批量大小都会显着恶化结果。应该有更多人支持该帖子,因此它的排名高于使用 view
的所有教程:)。
很好的解释。谢谢。有一件事对我来说仍然是可疑的。我们如何直接使用 h_t 到线性层,它将是暗淡的(num_layers*batch_size*lstm_size)
。我认为我们只需要使用h_t[-1]
来获取最后一层的输出。对吗?以上是关于如何正确地为 PyTorch 中的嵌入、LSTM 和线性层提供输入?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Pytorch 中的截断反向传播(闪电)在很长的序列上运行 LSTM?
如何在 PyTorch 中正确实现 Seq2Seq LSTM 的填充?