Seq2seq (Sequence to Sequence) -model met PyTorch

Inhoudsopgave:

Anonim

Wat is NLP?

NLP of Natural Language Processing is een van de populaire takken van kunstmatige intelligentie die computers helpt mensen in hun natuurlijke taal te begrijpen, te 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 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 2 RNN's te gebruiken die samenwerken met een speciaal token en proberen de volgende toestandssequentie uit de vorige reeks te voorspellen.

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 om de bestandsnaam en map in de code te wijzigen.

from __future__ import unicode_literals, print_function, divisionimport torchimport torch.nn as nnimport torch.optim as optimimport torch.nn.functional as Fimport numpy as npimport pandas as pdimport osimport reimport randomdevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")

Stap 2) Voorbereiding van gegevens

U kunt de dataset niet rechtstreeks gebruiken. U moet de zinnen in woorden splitsen en deze omzetten in One-Hot Vector. Elk woord wordt uniek geïndexeerd in de klasse Lang om een ​​woordenboek te maken. De Lang Class zal elke zin opslaan en woord voor woord splitsen met de addSentence. Maak vervolgens een woordenboek door elk onbekend woord te indexeren voor Sequence-to-sequence-modellen.

SOS_token = 0EOS_token = 1MAX_LENGTH = 20#initialize Lang Classclass Lang:def __init__(self):#initialize containers to hold the words and corresponding indexself.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 containerdef 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 counterdef addWord(self, word):if word not in self.word2index:self.word2index[word] = self.n_wordsself.word2count[word] = 1self.index2word[self.n_words] = wordself.n_words += 1else:self.word2count[word] += 1

De Lang Class is een les die ons zal helpen bij het maken van een woordenboek. Voor elke taal wordt elke zin opgesplitst in woorden en vervolgens toegevoegd aan de container. 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 om een ​​woord uit de index te vinden.

Omdat onze gegevens worden gescheiden door TAB, moet u panda's gebruiken als onze gegevenslader. 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 alle niet-tekens
  • converteren naar ASCII vanuit Unicode
  • Splits de zinnen, zodat je elk woord erin hebt.
#Normalize every sentencedef 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 sentencedef read_sentence(df, lang1, lang2):sentence1 = normalize_sentence(df, lang1)sentence2 = normalize_sentence(df, lang2)return sentence1, sentence2def read_file(loc, lang1, lang2):df = pd.read_csv(loc, delimiter='\t', header=None, names=[lang1, lang2])return dfdef 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, zijn het omzetten van paren naar Tensor. Dit is erg belangrijk omdat ons netwerk alleen gegevens van het tensor-type leest. Het is ook belangrijk omdat dit het deel is dat aan elk einde van de zin een token zal zijn 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

Bron: Seq2Seq

PyTorch Seq2seq-model is een soort model dat PyTorch-encoderdecoder bovenop het model gebruikt. De encoder codeert de zin woord voor woord in een geïndexeerd vocabulaire of bekende woorden met index, en de decoder voorspelt de uitvoer van de gecodeerde invoer door de invoer achtereenvolgens te decoderen en zal proberen de laatste invoer als de 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 maken. Elke zin krijgt een token toegewezen om het einde van de reeks te markeren. Aan het einde van de voorspelling zal er ook een token zijn om het einde van de uitvoer te markeren. Dus, van de encoder, zal het een status doorgeven aan de decoder om de output te voorspellen.

Bron: Seq2Seq-model

De Encoder zal onze ingevoerde zin woord voor woord achter elkaar coderen en aan het einde 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 insluitingslaag is een opzoektabel die de inbedding van onze invoer in een woordenwoordenboek met een vaste grootte opslaat. Het wordt doorgegeven aan een GRU-laag. GRU-laag is een Gated Recurrent Unit die bestaat uit een type RNN met meerdere lagen dat de opeenvolgende invoer zal berekenen. Deze laag berekent de verborgen status van de vorige en werkt de reset-, update- en nieuwe poorten bij.

Bron: Seq2Seq

De decoder decodeert de invoer van de encoderuitgang. Het zal proberen de volgende uitvoer te voorspellen en proberen deze als de volgende invoer te gebruiken, indien mogelijk. De decoder bestaat uit een inbeddingslaag, GRU-laag en een lineaire laag. De inbeddingslaag maakt een opzoektabel voor de uitvoer en wordt doorgegeven aan een GRU-laag om de voorspelde uitvoerstatus te berekenen. Daarna zal een lineaire laag helpen om de activeringsfunctie te berekenen 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 layersself.input_dim = input_dimself.embbed_dim = embbed_dimself.hidden_dim = hidden_dimself.num_layers = num_layers#initialize the embedding layer with input and embbed dimentionself.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 layersself.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, hiddenclass 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 layersself.embbed_dim = embbed_dimself.hidden_dim = hidden_dimself.output_dim = output_dimself.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, hiddenclass Seq2Seq(nn.Module):def __init__(self, encoder, decoder, device, MAX_LENGTH=MAX_LENGTH):super().__init__()#initialize the encoder and decoderself.encoder = encoderself.decoder = decoderself.device = devicedef 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 outputsoutputs = torch.zeros(target_length, batch_size, vocab_size).to(self.device)#encode every word in a sentencefor i in range(input_length):encoder_output, encoder_hidden = self.encoder(source[i])#use the encoder’s hidden layer as the decoder hiddendecoder_hidden = encoder_hidden.to(device)#add a token before the first predicted worddecoder_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_outputteacher_force = random.random() < teacher_forcing_ratiotopv, topi = decoder_output.topk(1)input = (target[t] if teacher_force else topi)if(teacher_force == False and input.item() == EOS_token):breakreturn outputs

Stap 3) Training van het model

Het trainingsproces in Seq2seq-modellen wordt gestart met het omzetten van elk paar zinnen in Tensoren uit hun Lang-index. Ons sequentie-naar-sequentiemodel zal SGD gebruiken als de optimizer en NLLLoss-functie om de verliezen te berekenen. Het trainingsproces begint met het invoeren van een paar zinnen aan het model om de juiste output te voorspellen. Bij elke stap wordt de uitvoer van het model berekend met de ware woorden om de verliezen te vinden en de parameters bij te werken. Dus omdat u 75.000 iteraties gaat gebruiken, genereert ons sequentie-tot-sequentie-model 75.000 willekeurige paren uit onze dataset.

teacher_forcing_ratio = 0.5def clacModel(model, input_tensor, target_tensor, model_optimizer, criterion):model_optimizer.zero_grad()input_length = input_tensor.size(0)loss = 0epoch_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 resultfor ot in range(num_iter):loss += criterion(output[ot], target_tensor[ot])loss.backward()model_optimizer.step()epoch_loss = loss.item() / num_iterreturn epoch_lossdef trainModel(model, source, target, pairs, num_iteration=20000):model.train()optimizer = optim.SGD(model.parameters(), lr=0.01)criterion = nn.NLLLoss()total_loss_iterations = 0training_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 += lossif iter % 5000 == 0:avarage_loss= total_loss_iterations / 5000total_loss_iterations = 0print('%d %.4f' % (iter, avarage_loss))torch.save(model.state_dict(), 'mytraining.pt')return model

Stap 4) Test het model

Het evaluatieproces van Seq2seq PyTorch is om de modeluitvoer te controleren. Elk paar Sequence to sequence-modellen zal in het model worden ingevoerd en de voorspelde woorden genereren. Daarna kijk je bij elke output naar de hoogste waarde om de juiste index te vinden. En uiteindelijk zult u 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('')breakelse:decoded_words.append(output_lang.index2word[topi[0].item()])return decoded_wordsdef 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 tot Seq, met het aantal iteraties van 75000 en het aantal RNN-laag 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 wordsinput_size = source.n_wordsoutput_size = target.n_wordsprint('Input : {} Output : {}'.format(input_size, output_size))embed_size = 256hidden_size = 512num_layers = 1num_iteration = 100000#create encoder-decoder modelencoder = 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 modelprint(encoder)print(decoder)model = trainModel(model, source, target, pairs, num_iteration)evaluateRandomly(model, source, target, pairs)

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

random sentence ['tom is finishing his work', 'tom sedang menyelesaikan pekerjaannya']Input : 3551 Output : 4253Encoder((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.090610000 3.912915000 3.817120000 3.836925000 3.819930000 3.795735000 3.803740000 3.809845000 3.753050000 3.711955000 3.726360000 3.693365000 3.684070000 3.705875000 3.7044> this is worth one million yen= ini senilai satu juta yen< tom sangat satu juta yen > she got good grades in english= dia mendapatkan nilai bagus dalam bahasa inggris< tom meminta nilai bagus dalam bahasa inggris > put in a little more sugar= tambahkan sedikit gula< tom tidak > are you a japanese student= apakah kamu siswa dari jepang< tom kamu memiliki yang jepang > 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 > speaking about trips have you ever been to kobe= berbicara tentang wisata apa kau pernah ke kobe< tom tidak > tom bought me roses= tom membelikanku bunga mawar< tom tidak bunga mawar > no one was more surprised than tom= tidak ada seorangpun yang lebih terkejut dari tom< tom ada orang yang lebih terkejut > i thought it was true= aku kira itu benar adanya< tom tidak