После достаточного количества (200) эпох LSTM LM возвращает только , , ‘токен с потерей Nan

#pytorch #lstm #recurrent-neural-network

#pytorch #lstm #рекуррентная нейронная сеть

Вопрос:

Я построил языковую модель Bi-LSTM с помощью pytorch и обнаружил, что после 200 эпох модель внезапно вернула только бессмысленные токены с потерей Nan, в то время как раньше она возвращала разумные токены.

Пожалуйста, обратитесь к коду модели ниже:

 # optimizer = torch.optim.Adam(model.parameters(), lr=0.009,  amsgrad=False)

class BiLSTM(nn.Module):
    
    def __init__(self, voc_size, hidn_size, emb_size=300):
        super().__init__()     
        self.voc_size = voc_size
        self.emb_size = emb_size
        self.hidn_size = hidn_size
        self.emb = nn.Embedding(num_embeddings=voc_size, embedding_dim=emb_size) 
        self.lstm = nn.LSTM(input_size=emb_size, hidden_size=hidn_size, bidirectional=True) 
        self.lm_out = nn.Linear(hidn_size*2, voc_size) 
        self.dropout = nn.Dropout(p=0.3)
        
    def forward(self, x, prev_state):
        state_h, state_c = prev_state
        bs = len(x)
                
        emb = self.emb(x)
        emb = emb.permute(1,0,-1)
        out, (state_h, state_c) = self.lstm(emb, (state_h[:,:bs,:].contiguous(), state_c[:,:bs,:].contiguous()))
        
        forward_out = out[:, :, :self.hidn_size] 
        backward_out = out[:, :, self.hidn_size:]
        concat_h = torch.cat([forward_out[:-2], backward_out[2:]], dim=2) 
        
        final_out = self.lm_out(self.dropout(concat_h.permute(1,0,2)))  
        
        return final_out.view(final_out.size()[0]*final_out.size()[1], final_out.size()[-1]), (state_h, state_c)
  

Ответ №1:

Проблема была вызвана exploding gradient . Я нашел это, проверив градиент и вес слоя:

 model.lm_out.weight
>>>
tensor([[nan, nan, nan,  ..., nan, nan, nan],
        [nan, nan, nan,  ..., nan, nan, nan],
        [nan, nan, nan,  ..., nan, nan, nan],
        ...,
        [nan, nan, nan,  ..., nan, nan, nan],
        [nan, nan, nan,  ..., nan, nan, nan],
        [nan, nan, nan,  ..., nan, nan, nan]], 
       device='cuda:0', requires_grad=True)

model.lm_out.weight.grad
>>>
tensor([[nan, nan, nan,  ..., nan, nan, nan],
        [nan, nan, nan,  ..., nan, nan, nan],
        [nan, nan, nan,  ..., nan, nan, nan],
        ...,
        [nan, nan, nan,  ..., nan, nan, nan],
        [nan, nan, nan,  ..., nan, nan, nan],
        [nan, nan, nan,  ..., nan, nan, nan]], 
       device='cuda:0', requires_grad=True)

  

Поэтому я редактирую свой код следующим образом:

 loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1)
optimizer.step()
  

Это решило мою проблему.

Комментарии:

1. Вы уверены, что хотите, чтобы ваш LM действительно генерировал pad токены? Если это допустимый вывод, вы усложняете обучение сети, учитывая, что у вас, вероятно, много этих токенов и что они, скорее всего, будут предсказаны. Обычно вы просто выполняете выборку слов в цикле до end токена и избегаете создания фактических пэдов.

2. @runDOSrun Спасибо за ваш совет. Я не рассматривал pad , и ваш комментарий заставляет меня понять, что неправильно этого не делать. Я ввожу один полный обзор IMDB (набор предложений), в этом случае, я думаю, мне следовало бы: 1) добавить токен <eos> в конце каждого ввода данных обзора, 2) не рассчитанные потери сгенерированных токенов после <eos> , 3) и, кроме того, я должен был выбрать некоторыетокены из сгенерированного обзора для расчета потерь, это правильно?

3. Да, это, безусловно, было бы правильно для вашего принятого подхода! Есть и другие очень разные способы, которыми можно это сделать, но это зависит от того, какой тип текста вы хотите сгенерировать и его структуры. Например, для обычного LM вам не нужна структура предложений в пакетах. Если вы прогнозируете слово за словом, ввод также может быть на уровне слова, так что вам не нужны дополнения (например, путем ввода всего документа или его случайных фрагментов). Затем позиция <eos> (конец документа / отправлено) может использоваться для указания структуры вывода.

4. @runDOSrun Спасибо за ваше руководство. Теперь я могу понять, как правильно кодировать LM 🙂