首页 > 解决方案 > 解码器总是预测相同的标记

问题描述

我有以下用于机器翻译的解码器,经过几个步骤后仅预测 EOS 令牌。因此,在一个虚拟的、微小的数据集上过度拟合是不可能的,因此代码中似乎存在很大的错误。

Decoder(
  (embedding): Embeddings(
    (word_embeddings): Embedding(30002, 768, padding_idx=3)
    (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
    (dropout): Dropout(p=0.5, inplace=False)
  )
  (ffn1): FFN(
    (dense): Linear(in_features=768, out_features=512, bias=False)
    (layernorm): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
    (dropout): Dropout(p=0.5, inplace=False)
    (activation): GELU()
  )
  (rnn): GRU(512, 512, batch_first=True, bidirectional=True)
  (ffn2): FFN(
    (dense): Linear(in_features=1024, out_features=512, bias=False)
    (layernorm): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
    (dropout): Dropout(p=0.5, inplace=False)
    (activation): GELU()
  )
  (selector): Sequential(
    (0): Linear(in_features=512, out_features=30002, bias=True)
    (1): LogSoftmax(dim=-1)
  )
)

前向相对简单(看看我在那里做了什么?):将 input_ids 传递给嵌入和 FFN,然后在 RNN 中使用该表示,并将给定sembedding作为初始隐藏状态。将输出通过另一个 FFN 并进行 softmax。返回 RNN 的 logits 和最后的隐藏状态。在下一步中,使用这些隐藏状态作为新的隐藏状态,并将最高预测的标记作为新的输入。

def forward(self, input_ids, sembedding):
    embedded = self.embedding(input_ids)
    output = self.ffn1(embedded)
    output, hidden = self.rnn(output, sembedding)
    output = self.ffn2(output)
    logits = self.selector(output)

    return logits, hidden

sembedding是 RNN 的初始 hidden_​​state。这类似于编码器-解码器架构,只是在这里我们不训练编码器,但我们确实可以访问预训练的编码器表示

在我的训练循环中,我从每个批次开始使用一个 SOS 令牌,并将每个顶部预测的令牌提供给下一步,直到target_len达到。我也在老师强制训练之间随机交换。

def step(self, batch, teacher_forcing_ratio=0.5):
    batch_size, target_len = batch["input_ids"].size()[:2]
    # Init first decoder input woth SOS (BOS) token
    decoder_input = torch.tensor([[self.tokenizer.bos_token_id]] * batch_size).to(self.device)
    batch["input_ids"] = batch["input_ids"].to(self.device)

    # Init first decoder hidden_state: one zero'd second embedding in case the RNN is bidirectional
    decoder_hidden = torch.stack((batch["sembedding"],
                                  torch.zeros(*batch["sembedding"].size()))
                                 ).to(self.device) if self.model.num_directions == 2 \
        else batch["sembedding"].unsqueeze(0).to(self.device)

    loss = torch.tensor([0.]).to(self.device)

    use_teacher_forcing = random.random() < teacher_forcing_ratio
    # contains tuples of predicted and correct words
    tokens = []
    for i in range(target_len):
        # overwrite previous decoder_hidden
        output, decoder_hidden = self.model(decoder_input, decoder_hidden)
        batch_correct_ids = batch["input_ids"][:, i]

        # NLLLoss compute loss between predicted classes (bs x classes) and correct classes for _this word_
        # set to ignore the padding index
        loss += self.criterion(output[:, 0, :], batch_correct_ids)

        batch_predicted_ids = output.topk(1).indices.squeeze(1).detach()

        # if use teacher training: use current correct word for next prediction
        # else do NOT use teacher training: us current predction for next prediction
        decoder_input = batch_correct_ids.unsqueeze(1) if use_teacher_forcing else batch_predicted_ids

    return loss, loss.item() / target_len

我还在每一步之后剪辑渐变:

clip_grad_norm_(self.model.parameters(), 1.0)

起初,随后的预测已经相对相同,但经过几次迭代后,会有更多的变化。但是相对较快,所有预测都变成了其他词(但总是相同的),最终变成了 EOS 令牌(编辑:将激活更改为 ReLU 后,总是预测另一个令牌 - 它似乎是一个总是重复的随机令牌)。请注意,这已经发生在 80 步之后(batch_size 128)。

我发现RNN返回的隐藏状态包含很多零。我不确定这是否是问题,但似乎它可能是相关的。

tensor([[[  3.9874e-02,  -6.7757e-06,   2.6094e-04,  ...,  -1.2708e-17,
            4.1839e-02,   7.8125e-03],
         [ -7.8125e-03,  -2.5341e-02,   7.8125e-03,  ...,  -7.8125e-03,
           -7.8125e-03,  -7.8125e-03],
         [ -0.0000e+00, -1.0610e-314,   0.0000e+00,  ...,   0.0000e+00,
            0.0000e+00,   0.0000e+00],
         [  0.0000e+00,   0.0000e+00,   0.0000e+00,  ...,   0.0000e+00,
           -0.0000e+00,  1.0610e-314]]], device='cuda:0', dtype=torch.float64,
       grad_fn=<CudnnRnnBackward>)

我不知道可能出了什么问题,尽管我怀疑问题出在我身上而step不是模型上。我已经尝试过使用学习率,禁用一些层(LayerNorm,dropout,ffn2),使用预训练嵌入并冻结或解冻它们,并禁用教师强制,使用双向与单向 GRU。最终的结果总是一样的。

如果您有任何指示,那将非常有帮助。我用谷歌搜索了很多关于神经网络总是预测同一个项目的东西,我已经尝试了所有我能找到的建议。欢迎任何新的,无论多么疯狂!

标签: pytorchrecurrent-neural-networkencoder-decoder

解决方案


在我的情况下,问题似乎是dtype初始隐藏状态是双精度的,输入是浮点数。我不太明白为什么这是一个问题,但是将隐藏状态转换为浮点数解决了这个问题。如果您对为什么这可能是 PyTorch 的问题有任何直觉,请在评论中告诉我,或者更好的是,在PyTorch 官方论坛上告诉我。

编辑:正如该主题所示,这是 PyTorch 1.6 中的一个错误,已在 1.7 中解决,在 1.7 中,您将收到一条错误消息,希望能帮您省去调试所有代码的麻烦,而不是找到导致奇怪行为的原因。


推荐阅读