首页 文章

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

提问于
浏览
8

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

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

  • nn.Embedding

  • nn.LSTM

  • nn.Linear

nn.Embedding

Input: batch_size * seq_length
Output: batch_size * seq_length * embedding_dimension

我在这里没有任何问题,我只想明确输入和输出的预期形状 .

nn.LSTM

Input: seq_length * batch_size * input_size (在这种情况下为 embedding_dimension
Output: seq_length * batch_size * hidden_size
last_hidden_state: batch_size * hidden_size
last_cell_state: batch_size * hidden_size

要使用 Embedding 图层的输出作为 LSTM 图层的输入,我需要转置轴1和2 .

我在网上找到的许多例子都有类似_843239的内容,但这让我很困惑 . 该视图如何确保同一批次的元素保留在同一批次中?当 len(sentence)self.batch 尺寸相同时会发生什么?

nn.Linear

Input: batch_size x input_size (在这种情况下LSTM的hidden_size或??)
Output: batch_size x output_size

如果我只需要 last_hidden_stateLSTM ,那么我可以将它作为 nn.Linear 的输入 .

但是,如果我想使用输出(其中包含所有中间隐藏状态),那么我需要将 nn.Linear 的输入大小更改为 seq_length * hidden_size 并使用输出作为 Linear 模块的输入我需要转置输出的轴1和2然后我可以用 Output_transposed(batch_size, -1) 查看 .

我的理解在这里是否正确?如何在张量 (tensor.transpose(0, 1)) 中执行这些转置操作?

1 回答

  • 16

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

    接口嵌入到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) 转置第一维和第二维,就像您提到的那样 .

    问:我在网上看到很多像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的输出才能进一步使用(再次你不能在这里使用视图) .

    结束注意:我有目的地留下了一些点,例如使用双向循环单元格,在展开时使用步长,以及接口注意,因为它们可能变得非常麻烦并且将超出此答案的范围 .

相关问题