Генерация текста LSTM с помощью Pytorch

#nlp #pytorch #lstm

Вопрос:

В настоящее время я пытаюсь генерировать цитаты (на уровне символов) с помощью LSTMs с помощью Pytorch. В настоящее время я сталкиваюсь с некоторыми проблемами, связанными с пониманием того, как именно скрытое состояние реализовано в Pytorch.

Некоторые подробности:

У меня есть список цитат из персонажа телесериала. Я преобразую их в последовательность целых чисел, каждый символ которых соответствует определенному целому числу, используя словарь char2idx . У меня также есть обратная ситуация idx2char , когда отображение перевернуто.

После этого я использую скользящее окно, скажем, размера window_size , и шаг размера step для подготовки данных.

В качестве примера предположим, что последовательность [1, 2, 3, 4, 5, 0] , где 0 обозначает символ EOS. Затем , используя window_size = 3 и step = 2 , я получаю последовательность для x и y в виде:

 x1 = [1, 2, 3], y1 = [2, 3, 4]
x2 = [3, 4, 5], y1 = [4, 5, 0]

x = [x1, x2], y = [y1, y2]
 

Следующим шагом является обучение модели. Я приложил код, который я использую для обучения модели.

ПРИМЕЧАНИЕ: Я не передаю скрытые состояния из одной партии в другую, поскольку i-я последовательность (j 1) — й партии, вероятно, не является следующим шагом к i-й последовательности из j-й партии. (Вот почему я использую раздвижное окно, чтобы помочь модели запомнить). Есть ли лучший способ сделать это?

Мой главный вопрос возникает во время тестирования. Есть два метода, с помощью которых я тестирую.

Метод 1: Я беру начальную начальную строку, передаю ее в модель и получаю следующий символ в качестве предсказания. Теперь я добавляю это в начальную строку и передаю всю эту последовательность в модель, не передавая скрытое состояние. То есть я ввожу в модель всю последовательность, при этом LSTM имеет начальное скрытое состояние 0, получаю вывод, добавляю вывод в последовательность и повторяю, пока не столкнусь с символом EOS.

Метод 2: Я беру начальную начальную строку, передаю ее в модель и получаю следующий символ в качестве предсказания. Теперь я просто передаю символ и предыдущее скрытое состояние в качестве следующего ввода и продолжаю делать это до тех пор, пока не будет обнаружен символ EOS.

Вопрос

  1. According to my current understanding, the outputs of both methods should be the same because the same thing should be happening in both.
  2. What’s actually happening is that both methods are giving completely different results. Why is this happening?
  3. The second one gets stuck in an infinite loop for most inputs (e.g. it gives «back to back to back to ….») and on some inputs, the first one also gets stuck. How to prevent and avoid this?
  4. Is this related in some way to the training?

I have tried multiple different ways (using bidirectional LSTMs, using one hot encoding (instead of embedding), changing the batch sizes, not using a sliding window approach (using padding and feeding the whole quote at once).

I cannot figure out how to solve this issue. Any help would be greatly appreciated.

CODE

Code for the Model Class:

 class RNN(nn.Module):
    def __init__(self, vocab_size, hidden_size, num_layers, dropout=0.15):
        super(RNN, self).__init__()
        self.vocab_size = vocab_size
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        
        self.embedding = nn.Embedding(vocab_size, hidden_size)
        self.lstm = nn.LSTM(hidden_size, hidden_size, num_layers, dropout=dropout, batch_first=True)
        self.dense1 = nn.Linear(hidden_size, hidden_size*4)
        self.dense2 = nn.Linear(hidden_size*4, hidden_size*2)
        self.dense3 = nn.Linear(hidden_size*2, vocab_size)
        self.drop = nn.Dropout(dropout)
        
    def forward(self, X, h=None, c=None):
        if h is None:
            h, c = self.init_hidden(X.size(0))
        out = self.embedding(X)
        out, (h, c) = self.lstm(out, (h, c))
        out = self.drop(out)
        out = self.dense1(out.reshape(-1, self.hidden_size)) # Reshaping it into (batch_size*seq_len, hidden_size)
        out = self.dense2(out)
        out = self.dense3(out)
        return out, h, c
        
    def init_hidden(self, batch_size):
        num_l = self.num_layers
        hidden = torch.zeros(num_l, batch_size, self.hidden_size).to(DEVICE)
        cell = torch.zeros(num_l, batch_size, self.hidden_size).to(DEVICE)
        return hidden, cell
 

Code for training:

 rnn = RNN(VOCAB_SIZE, HIDDEN_SIZE, NUM_LAYERS).to(DEVICE)
optimizer = torch.optim.Adam(rnn.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()

rnn.train()
history = {}
best_loss = 100

for epoch in range(EPOCHS): #EPOCH LOOP
    counter = 0
    epoch_loss = 0
    
    for x, y in train_loader: #BATCH LOOP
        optimizer.zero_grad()
        counter  = 1

        o, h, c = rnn(x)
        loss = criterion(o, y.reshape(-1))   
        epoch_loss  = loss.item()
        
        loss.backward()
        nn.utils.clip_grad_norm_(rnn.parameters(), 5) # Clipping Gradients
        optimizer.step()

        if counter%print_every == 0:
            print(f"[INFO] EPOCH: {epoch 1}, BATCH: {counter}, TRAINING LOSS: {loss.item()}")
    
    epoch_loss = epoch_loss/counter       
    history["train_loss"] = history.get("train_loss", [])   [epoch_loss]
    print(f"nEPOCH: {epoch 1} COMPLETED!nTRAINING LOSS: {epoch_loss}n")     

 

Method 1 Code:

 with torch.no_grad():
    w = None
    start_str = "Hey, "
    x1 = quote2seq(start_str)[:-1]

    while w != EOS_TOKEN:
        x1 = torch.tensor(x1, device=DEVICE).unsqueeze(0)
        o1, h1, c1 = rnn(x1)
        p1 = F.softmax(o1, dim=1).detach()
        q1 = np.argmax(p1.cpu(), axis=1)[-1].item()
        w = idx2char[q1]
        start_str  = w
        x1 = x1.tolist()[0]  [q1]
    
quote = start_str.replace("<EOS>", "")
quote
 

Method 2 Code:

 with torch.no_grad():
    w = None
    start_str = "Are we back"
    x1 = quote2seq(start_str)[:-1]
    h1, c1 = rnn.init_hidden(1)

    while w != EOS_TOKEN:
        x1 = torch.tensor(x1, device=DEVICE).unsqueeze(0)
        h1, c1 = h1.data, c1.data
        o1, h1, c1 = rnn(x1, h1, c1)
        p1 = F.softmax(o1, dim=1).detach()
        q1 = np.argmax(p1.cpu(), axis=1)[-1].item()
        w = idx2char[q1]
        start_str  = w
        x1 = [q1]
    
quote = start_str.replace("<EOS>", "")
quote