解码器总是预测相同的令牌

问题描述

我有以下用于机器翻译的解码器,仅需几步便可以预测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,inplace=False)
    (activation): GELU()
  )
  (rnn): GRU(512,512,batch_first=True,bidirectional=True)
  (ffn2): FFN(
    (dense): Linear(in_features=1024,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的登录信息和最后隐藏状态。下一步,将这些隐藏状态用作新的隐藏状态,并将预测的最高令牌用作新的输入。

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[:,:],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],[ -0.0000e+00,-1.0610e-314,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,辍学,ffn2),使用预训练的嵌入并冻结或解冻它们,并使用双向和单向GRU禁用教师强迫。最终结果始终相同。

如果您有任何指针,那将非常有帮助。我用谷歌搜索了很多关于神经网络的东西,它们总是预测相同的项目,并且我尝试了所有可以找到的建议。任何新事物,无论多么疯狂,都欢迎!

解决方法

在我的情况下,问题似乎是初始隐藏状态的dtype是双精度型,而输入是浮点型。我不太了解为什么这是一个问题,但是将隐藏状态转换为float可以解决此问题。如果您对为什么PyTorch可能会遇到问题有任何直觉,请在评论中,或者最好在the official PyTorch forums上告诉我。

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