Seq2seq (reeks tot reeks) model met PyTorch

Wat is NLP?

NLP of Natural Language Processing is een van de populaire takken van kunstmatige intelligentie die computers helpt een mens in zijn natuurlijke taal te begrijpen, manipuleren of erop te reageren. NLP is de motor achter Google Translate die ons helpt andere talen te begrijpen.

Wat is Seq2Seq?

Seq2Seq is een methode van op een encoder-decoder gebaseerde machinevertaling en taalverwerking die een invoer van een reeks toewijst aan een uitvoer van een reeks met een tag en attentiewaarde. Het idee is om twee RNN's te gebruiken die samenwerken met een speciaal token en proberen de volgende toestandsreeks uit de vorige reeks te voorspellen.

Hoe u een reeks kunt voorspellen op basis van de vorige reeks

Voorspel de reeks op basis van de vorige reeks

Following zijn stappen voor het voorspellen van de reeks uit de vorige reeks met PyTorch.

Stap 1) Onze gegevens laden

Voor onze dataset gebruikt u een dataset van Door tabs gescheiden tweetalige zinsparen. Hier zal ik de Engels-Indonesische dataset gebruiken. U kunt alles kiezen wat u maar wilt, maar vergeet niet de bestandsnaam en map in de code te wijzigen.

from __future__ import unicode_literals, print_function, division
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

import numpy as np
import pandas as pd

import os
import re
import random

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

Stap 2) Gegevensvoorbereiding

U kunt de gegevensset niet rechtstreeks gebruiken. Je moet de zinnen in woorden splitsen en deze omzetten in One-Hot Vector. Elk woord wordt uniek geïndexeerd in de Lang-klasse om een ​​woordenboek te maken. De Lang Class slaat elke zin op en splitst deze woord voor woord met de addSentence. Maak vervolgens een woordenboek door elk onbekend woord voor Reeks te indexeren naar reeksmodellen.

SOS_token = 0
EOS_token = 1
MAX_LENGTH = 20

#initialize Lang Class
class Lang:
   def __init__(self):
       #initialize containers to hold the words and corresponding index
       self.word2index = {}
       self.word2count = {}
       self.index2word = {0: "SOS", 1: "EOS"}
       self.n_words = 2  # Count SOS and EOS

#split a sentence into words and add it to the container
   def addSentence(self, sentence):
       for word in sentence.split(' '):
           self.addWord(word)

#If the word is not in the container, the word will be added to it, 
#else, update the word counter
   def addWord(self, word):
       if word not in self.word2index:
           self.word2index[word] = self.n_words
           self.word2count[word] = 1
           self.index2word[self.n_words] = word
           self.n_words += 1
       else:
           self.word2count[word] += 1

De Lang Class is een klasse die ons helpt een woordenboek te maken. Voor elke taal wordt elke zin in woorden opgesplitst en vervolgens aan de container toegevoegd. Elke container slaat de woorden op in de juiste index, telt het woord en voegt de index van het woord toe, zodat we deze kunnen gebruiken om de index van een woord te vinden of een woord uit de index te vinden.

Omdat onze gegevens gescheiden zijn door TAB, moet u gebruik maken van panda's als onze datalader. Panda's lezen onze gegevens als dataFrame en splitsen deze op in onze bron- en doelzin. Voor elke zin die je hebt,

  • je normaliseert het naar kleine letters,
  • verwijder al het niet-karakter
  • converteren naar ASCII van Unicode
  • splits de zinnen, zodat je elk woord erin hebt.
#Normalize every sentence
def normalize_sentence(df, lang):
   sentence = df[lang].str.lower()
   sentence = sentence.str.replace('[^A-Za-z\s]+', '')
   sentence = sentence.str.normalize('NFD')
   sentence = sentence.str.encode('ascii', errors='ignore').str.decode('utf-8')
   return sentence

def read_sentence(df, lang1, lang2):
   sentence1 = normalize_sentence(df, lang1)
   sentence2 = normalize_sentence(df, lang2)
   return sentence1, sentence2

def read_file(loc, lang1, lang2):
   df = pd.read_csv(loc, delimiter='\t', header=None, names=[lang1, lang2])
   return df

def process_data(lang1,lang2):
   df = read_file('text/%s-%s.txt' % (lang1, lang2), lang1, lang2)
   print("Read %s sentence pairs" % len(df))
   sentence1, sentence2 = read_sentence(df, lang1, lang2)

   source = Lang()
   target = Lang()
   pairs = []
   for i in range(len(df)):
       if len(sentence1[i].split(' ')) < MAX_LENGTH and len(sentence2[i].split(' ')) < MAX_LENGTH:
           full = [sentence1[i], sentence2[i]]
           source.addSentence(sentence1[i])
           target.addSentence(sentence2[i])
           pairs.append(full)

   return source, target, pairs

Een andere handige functie die u gaat gebruiken is het omzetten van paren naar Tensor. Dit is erg belangrijk omdat ons netwerk alleen gegevens van het tensortype leest. Het is ook belangrijk omdat dit het gedeelte is dat er aan elk einde van de zin een token staat om het netwerk te vertellen dat de invoer is voltooid. Voor elk woord in de zin haalt het de index van het juiste woord in het woordenboek en voegt het een token toe aan het einde van de zin.

def indexesFromSentence(lang, sentence):
   return [lang.word2index[word] for word in sentence.split(' ')]

def tensorFromSentence(lang, sentence):
   indexes = indexesFromSentence(lang, sentence)
   indexes.append(EOS_token)
   return torch.tensor(indexes, dtype=torch.long, device=device).view(-1, 1)

def tensorsFromPair(input_lang, output_lang, pair):
   input_tensor = tensorFromSentence(input_lang, pair[0])
   target_tensor = tensorFromSentence(output_lang, pair[1])
   return (input_tensor, target_tensor)

Seq2Seq-model

Seq2seq-model
Seq2Seq

Het PyTorch Seq2seq-model is een soort model dat een PyTorch-encoderdecoder bovenop het model gebruikt. De encoder codeert de zin woord voor woord in een geïndexeerde woordenschat of bekende woorden met index, en de decoder voorspelt de uitvoer van de gecodeerde invoer door de invoer op volgorde te decoderen en zal proberen de laatste invoer als volgende invoer te gebruiken als het is mogelijk. Met deze methode is het ook mogelijk om de volgende invoer te voorspellen om een ​​zin te creëren. Aan elke zin wordt een token toegewezen om het einde van de reeks aan te geven. Aan het einde van de voorspelling zal er ook een token zijn om het einde van de uitvoer aan te geven. Vanuit de encoder zal deze dus een status doorgeven aan de decoder om de uitvoer te voorspellen.

Seq2seq-model
Seq2Seq-model

De Encoder codeert onze ingevoerde zin woord voor woord op volgorde en uiteindelijk zal er een token zijn om het einde van een zin aan te geven. De encoder bestaat uit een Embedding-laag en een GRU-laag. De Embedding-laag is een opzoektabel waarin de inbedding van onze invoer wordt opgeslagen in een woordenlijst met een vaste grootte. Het wordt doorgegeven aan een GRU-laag. GRU-laag is een Gated Recurrent Unit die bestaat uit meerdere lagen RNN waarmee de opeenvolgende invoer wordt berekend. Deze laag berekent de verborgen status van de vorige en werkt de reset-, update- en nieuwe poorten bij.

Seq2seq-model

Seq2Seq

De decoder decodeert de invoer van de encoderuitvoer. Het zal proberen de volgende uitvoer te voorspellen en deze indien mogelijk als volgende invoer te gebruiken. De decoder bestaat uit een inbeddingslaag, een GRU-laag en een lineaire laag. De inbeddingslaag maakt een opzoektabel voor de uitvoer en geeft deze door aan een GRU-laag om de voorspelde uitvoerstatus te berekenen. Daarna zal een lineaire laag helpen bij het berekenen van de activeringsfunctie om de werkelijke waarde van de voorspelde output te bepalen.

class Encoder(nn.Module):
   def __init__(self, input_dim, hidden_dim, embbed_dim, num_layers):
       super(Encoder, self).__init__()
      
       #set the encoder input dimesion , embbed dimesion, hidden dimesion, and number of layers 
       self.input_dim = input_dim
       self.embbed_dim = embbed_dim
       self.hidden_dim = hidden_dim
       self.num_layers = num_layers

       #initialize the embedding layer with input and embbed dimention
       self.embedding = nn.Embedding(input_dim, self.embbed_dim)
       #intialize the GRU to take the input dimetion of embbed, and output dimention of hidden and
       #set the number of gru layers
       self.gru = nn.GRU(self.embbed_dim, self.hidden_dim, num_layers=self.num_layers)
              
   def forward(self, src):
      
       embedded = self.embedding(src).view(1,1,-1)
       outputs, hidden = self.gru(embedded)
       return outputs, hidden

class Decoder(nn.Module):
   def __init__(self, output_dim, hidden_dim, embbed_dim, num_layers):
       super(Decoder, self).__init__()

#set the encoder output dimension, embed dimension, hidden dimension, and number of layers 
       self.embbed_dim = embbed_dim
       self.hidden_dim = hidden_dim
       self.output_dim = output_dim
       self.num_layers = num_layers

# initialize every layer with the appropriate dimension. For the decoder layer, it will consist of an embedding, GRU, a Linear layer and a Log softmax activation function.
       self.embedding = nn.Embedding(output_dim, self.embbed_dim)
       self.gru = nn.GRU(self.embbed_dim, self.hidden_dim, num_layers=self.num_layers)
       self.out = nn.Linear(self.hidden_dim, output_dim)
       self.softmax = nn.LogSoftmax(dim=1)
      
   def forward(self, input, hidden):

# reshape the input to (1, batch_size)
       input = input.view(1, -1)
       embedded = F.relu(self.embedding(input))
       output, hidden = self.gru(embedded, hidden)       
       prediction = self.softmax(self.out(output[0]))
      
       return prediction, hidden

class Seq2Seq(nn.Module):
   def __init__(self, encoder, decoder, device, MAX_LENGTH=MAX_LENGTH):
       super().__init__()
      
#initialize the encoder and decoder
       self.encoder = encoder
       self.decoder = decoder
       self.device = device
     
   def forward(self, source, target, teacher_forcing_ratio=0.5):

       input_length = source.size(0) #get the input length (number of words in sentence)
       batch_size = target.shape[1] 
       target_length = target.shape[0]
       vocab_size = self.decoder.output_dim
      
#initialize a variable to hold the predicted outputs
       outputs = torch.zeros(target_length, batch_size, vocab_size).to(self.device)

#encode every word in a sentence
       for i in range(input_length):
           encoder_output, encoder_hidden = self.encoder(source[i])

#use the encoder’s hidden layer as the decoder hidden
       decoder_hidden = encoder_hidden.to(device)
  
#add a token before the first predicted word
       decoder_input = torch.tensor([SOS_token], device=device)  # SOS

#topk is used to get the top K value over a list
#predict the output word from the current target word. If we enable the teaching force,  then the #next decoder input is the next word, else, use the decoder output highest value. 

       for t in range(target_length):   
           decoder_output, decoder_hidden = self.decoder(decoder_input, decoder_hidden)
           outputs[t] = decoder_output
           teacher_force = random.random() < teacher_forcing_ratio
           topv, topi = decoder_output.topk(1)
           input = (target[t] if teacher_force else topi)
           if(teacher_force == False and input.item() == EOS_token):
               break

       return outputs

Stap 3) Het model trainen

Het trainingsproces in Seq2seq-modellen begint met het omzetten van elk paar zinnen in Tensors op basis van hun Lang-index. Ons sequentie-tot-sequentiemodel gebruikt SGD als optimalisatie en NLLLoss-functie om de verliezen te berekenen. Het trainingsproces begint met het invoeren van een zinspaar aan het model om de juiste uitvoer te voorspellen. Bij elke stap wordt de uitvoer van het model berekend met de echte woorden om de verliezen te vinden en de parameters bij te werken. Omdat u dus 75000 iteraties gaat gebruiken, genereert ons sequentie-tot-sequentiemodel willekeurige 75000 paren uit onze dataset.

teacher_forcing_ratio = 0.5

def clacModel(model, input_tensor, target_tensor, model_optimizer, criterion):
   model_optimizer.zero_grad()

   input_length = input_tensor.size(0)
   loss = 0
   epoch_loss = 0
   # print(input_tensor.shape)

   output = model(input_tensor, target_tensor)

   num_iter = output.size(0)
   print(num_iter)

#calculate the loss from a predicted sentence with the expected result
   for ot in range(num_iter):
       loss += criterion(output[ot], target_tensor[ot])

   loss.backward()
   model_optimizer.step()
   epoch_loss = loss.item() / num_iter

   return epoch_loss

def trainModel(model, source, target, pairs, num_iteration=20000):
   model.train()

   optimizer = optim.SGD(model.parameters(), lr=0.01)
   criterion = nn.NLLLoss()
   total_loss_iterations = 0

   training_pairs = [tensorsFromPair(source, target, random.choice(pairs))
                     for i in range(num_iteration)]
  
   for iter in range(1, num_iteration+1):
       training_pair = training_pairs[iter - 1]
       input_tensor = training_pair[0]
       target_tensor = training_pair[1]

       loss = clacModel(model, input_tensor, target_tensor, optimizer, criterion)

       total_loss_iterations += loss

       if iter % 5000 == 0:
           avarage_loss= total_loss_iterations / 5000
           total_loss_iterations = 0
           print('%d %.4f' % (iter, avarage_loss))
          
   torch.save(model.state_dict(), 'mytraining.pt')
   return model

Stap 4) Test het model

Het evaluatieproces van Seq2seq PyTorch bestaat uit het controleren van de modeluitvoer. Elk paar Sequence to Sequence-modellen wordt in het model ingevoerd en genereert de voorspelde woorden. Daarna kijkt u bij elke uitgang naar de hoogste waarde om de juiste index te vinden. En uiteindelijk zul je onze modelvoorspelling vergelijken met de ware zin

def evaluate(model, input_lang, output_lang, sentences, max_length=MAX_LENGTH):
   with torch.no_grad():
       input_tensor = tensorFromSentence(input_lang, sentences[0])
       output_tensor = tensorFromSentence(output_lang, sentences[1])
  
       decoded_words = []
  
       output = model(input_tensor, output_tensor)
       # print(output_tensor)
  
       for ot in range(output.size(0)):
           topv, topi = output[ot].topk(1)
           # print(topi)

           if topi[0].item() == EOS_token:
               decoded_words.append('<EOS>')
               break
           else:
               decoded_words.append(output_lang.index2word[topi[0].item()])
   return decoded_words

def evaluateRandomly(model, source, target, pairs, n=10):
   for i in range(n):
       pair = random.choice(pairs)
       print(‘source {}’.format(pair[0]))
       print(‘target {}’.format(pair[1]))
       output_words = evaluate(model, source, target, pair)
       output_sentence = ' '.join(output_words)
       print(‘predicted {}’.format(output_sentence))

Laten we nu onze training beginnen met Seq to Seq, met het aantal iteraties van 75000 en het aantal RNN-lagen van 1 met de verborgen grootte van 512.

lang1 = 'eng'
lang2 = 'ind'
source, target, pairs = process_data(lang1, lang2)

randomize = random.choice(pairs)
print('random sentence {}'.format(randomize))

#print number of words
input_size = source.n_words
output_size = target.n_words
print('Input : {} Output : {}'.format(input_size, output_size))

embed_size = 256
hidden_size = 512
num_layers = 1
num_iteration = 100000

#create encoder-decoder model
encoder = Encoder(input_size, hidden_size, embed_size, num_layers)
decoder = Decoder(output_size, hidden_size, embed_size, num_layers)

model = Seq2Seq(encoder, decoder, device).to(device)

#print model 
print(encoder)
print(decoder)

model = trainModel(model, source, target, pairs, num_iteration)
evaluateRandomly(model, source, target, pairs)

Zoals u kunt zien, komt onze voorspelde zin niet erg goed overeen, dus om een ​​hogere nauwkeurigheid te krijgen, moet u met veel meer gegevens trainen en proberen meer iteraties en een aantal lagen toe te voegen met behulp van Sequence to Sequence Learning.

random sentence ['tom is finishing his work', 'tom sedang menyelesaikan pekerjaannya']
Input : 3551 Output : 4253
Encoder(
  (embedding): Embedding(3551, 256)
  (gru): GRU(256, 512)
)
Decoder(
  (embedding): Embedding(4253, 256)
  (gru): GRU(256, 512)
  (out): Linear(in_features=512, out_features=4253, bias=True)
  (softmax): LogSoftmax()
)
Seq2Seq(
  (encoder): Encoder(
    (embedding): Embedding(3551, 256)
    (gru): GRU(256, 512)
  )
  (decoder): Decoder(
    (embedding): Embedding(4253, 256)
    (gru): GRU(256, 512)
    (out): Linear(in_features=512, out_features=4253, bias=True)
    (softmax): LogSoftmax()
  )
)

5000 4.0906
10000 3.9129
15000 3.8171
20000 3.8369
25000 3.8199
30000 3.7957
35000 3.8037
40000 3.8098
45000 3.7530
50000 3.7119
55000 3.7263
60000 3.6933
65000 3.6840
70000 3.7058
75000 3.7044

> this is worth one million yen
= ini senilai satu juta yen
< tom sangat satu juta yen <EOS>

> she got good grades in english
= dia mendapatkan nilai bagus dalam bahasa inggris
< tom meminta nilai bagus dalam bahasa inggris <EOS>

> put in a little more sugar
= tambahkan sedikit gula
< tom tidak <EOS>

> are you a japanese student
= apakah kamu siswa dari jepang
< tom kamu memiliki yang jepang <EOS>

> i apologize for having to leave
= saya meminta maaf karena harus pergi
< tom tidak maaf karena harus pergi ke

> he isnt here is he
= dia tidak ada di sini kan
< tom tidak <EOS>

> speaking about trips have you ever been to kobe
= berbicara tentang wisata apa kau pernah ke kobe
< tom tidak <EOS>

> tom bought me roses
= tom membelikanku bunga mawar
< tom tidak bunga mawar <EOS>

> no one was more surprised than tom
= tidak ada seorangpun yang lebih terkejut dari tom
< tom ada orang yang lebih terkejut <EOS>

> i thought it was true
= aku kira itu benar adanya
< tom tidak <EOS>