如何正确地为PyTorch中的嵌入,LSTM和线性图层提供输入?

时间:2018-03-24 16:07:12

标签: lstm pytorch

我需要明确如何使用torch.nn模块的不同组件正确准备批量训练的输入。具体来说,我正在寻找为seq2seq模型创建编码器 - 解码器网络。

假设我有一个包含这三层的模块,按顺序:

  1. nn.Embedding
  2. nn.LSTM
  3. nn.Linear
  4. 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)

    中执行这些转置操作

1 个答案:

答案 0 :(得分:24)

您对大多数概念的理解是准确的,但是,这里和那里都有一些缺失点。

接口嵌入LSTM(或任何其他经常性单位)

您已将(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 + 1embedding_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的输出

现在,一旦你准备了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_th_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的输出以便进一步使用(再次你不能在这里使用视图)。

  

结束注意:我故意留下一些点,例如使用双向重复单元格,在展开时使用步长,以及连接注意力,因为它们会变得相当麻烦并且超出了这个答案的范围。