Seq2seq (Sequence to Sequence) Modelowanie za pomocą PyTorch

Spisie treści:

Anonim

Co to jest NLP?

NLP lub przetwarzanie języka naturalnego to jedna z popularnych gałęzi sztucznej inteligencji, która pomaga komputerom rozumieć człowieka, manipulować nim lub odpowiadać na niego w jego języku naturalnym. NLP to silnik stojący za Tłumaczem Google, który pomaga nam zrozumieć inne języki.

Co to jest Seq2Seq?

Seq2Seq jest metodą tłumaczenia maszynowego opartego na koderze-dekoderze i przetwarzania języka, która odwzorowuje wejście sekwencji na wyjście sekwencji ze znacznikiem i wartością uwagi. Chodzi o to, aby użyć 2 RNN, które będą współpracować ze specjalnym tokenem i spróbują przewidzieć następną sekwencję stanów z poprzedniej sekwencji.

Krok 1) Ładowanie naszych danych

W naszym zbiorze danych użyjesz zestawu danych z dwujęzycznych par zdań rozdzielonych tabulatorami. Tutaj użyję zbioru danych z angielskiego na indonezyjski. Możesz wybrać, co chcesz, ale pamiętaj, aby zmienić nazwę pliku i katalog w kodzie.

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")

Krok 2) Przygotowanie danych

Nie możesz bezpośrednio użyć zbioru danych. Musisz podzielić zdania na słowa i przekonwertować je na jeden gorący wektor. Każde słowo będzie unikalnie indeksowane w klasie Lang, aby utworzyć słownik. Klasa Lang zapisze każde zdanie i podzieli je słowo po słowie za pomocą addSentence. Następnie utwórz słownik, indeksując każde nieznane słowo w sekwencji do modeli sekwencyjnych.

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

Lang Class to klasa, która pomoże nam stworzyć słownik. Dla każdego języka każde zdanie zostanie podzielone na słowa, a następnie dodane do kontenera. Każdy kontener będzie przechowywać słowa w odpowiednim indeksie, policzyć słowo i dodać indeks słowa, abyśmy mogli go użyć do znalezienia indeksu słowa lub znalezienia słowa z jego indeksu.

Ponieważ nasze dane są oddzielone TAB, musisz użyć pand jako naszego modułu ładującego dane. Pandy odczytują nasze dane jako dataFrame i dzielą je na zdanie źródłowe i docelowe. Za każde zdanie, które masz,

  • znormalizujesz to na małe litery,
  • usuń wszystkie znaki niebędące znakami
  • przekonwertować na ASCII z Unicode
  • podziel zdania, aby zawierały każde słowo.
#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

Inną przydatną funkcją, której będziesz używać, jest zamiana par na Tensor. Jest to bardzo ważne, ponieważ nasza sieć odczytuje tylko dane typu tensor. Jest to również ważne, ponieważ jest to część, w której na każdym końcu zdania będzie token informujący sieć o zakończeniu wprowadzania danych. Dla każdego słowa w zdaniu pobierze indeks z odpowiedniego słowa w słowniku i doda token na końcu zdania.

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)

Model Seq2Seq

Źródło: Seq2Seq

Model PyTorch Seq2seq jest rodzajem modelu, który używa dekodera kodera PyTorch na górze modelu. Koder zakoduje słowo zdania po słowach w indeksowane słownictwo lub znane słowa z indeksem, a dekoder przewiduje wyjście zakodowanego wejścia przez dekodowanie wejścia w sekwencji i spróbuje użyć ostatniego wejścia jako następnego wejścia, jeśli to jest możliwe. Dzięki tej metodzie możliwe jest również przewidywanie następnego wejścia w celu utworzenia zdania. Do każdego zdania zostanie przypisany token oznaczający koniec sekwencji. Na końcu prognozy będzie również token oznaczający koniec wyniku. Tak więc z kodera przekaże stan do dekodera, aby przewidzieć wyjście.

Źródło: Seq2Seq Model

Enkoder zakoduje nasze wejściowe zdanie po kolei, a na końcu będzie token oznaczający koniec zdania. Koder składa się z warstwy osadzania i warstw GRU. Warstwa osadzania to tabela przeglądowa, która przechowuje osadzanie naszych danych wejściowych w słowniku słów o ustalonej wielkości. Zostanie przekazany do warstwy GRU. Warstwa GRU jest bramkowaną jednostką rekurencyjną, która składa się z wielu warstw typu RNN, które obliczają sekwencjonowane dane wejściowe. Ta warstwa obliczy stan ukryty z poprzedniej i zaktualizuje reset, aktualizację i nowe bramki.

Źródło: Seq2Seq

Dekoder zdekoduje wejście z wyjścia kodera. Spróbuje przewidzieć następne wyjście i spróbuje użyć go jako następnego wejścia, jeśli to możliwe. Dekoder składa się z warstwy osadzania, warstwy GRU i warstwy liniowej. Warstwa osadzająca utworzy tabelę przeglądową dla danych wyjściowych i przekaże je do warstwy GRU w celu obliczenia przewidywanego stanu wyjściowego. Następnie warstwa liniowa pomoże obliczyć funkcję aktywacji w celu określenia prawdziwej wartości przewidywanego wyjścia.

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

Krok 3) Trenowanie modelu

Proces uczenia modeli Seq2seq rozpoczyna się od konwersji każdej pary zdań na tensory z ich indeksu Lang. Nasz model sekwencji do sekwencji użyje SGD jako optymalizatora i funkcji NLLLoss do obliczenia strat. Proces uczenia rozpoczyna się od wprowadzenia pary zdań do modelu w celu przewidzenia prawidłowego wyniku. Na każdym etapie dane wyjściowe z modelu będą obliczane przy użyciu prawdziwych słów, aby znaleźć straty i zaktualizować parametry. Ponieważ będziesz używać 75000 iteracji, nasz model sekwencji do sekwencji wygeneruje losowe 75000 par z naszego zbioru danych.

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

Krok 4) Przetestuj model

Proces oceny Seq2seq PyTorch polega na sprawdzeniu wyników modelu. Każda para modeli Sequence to Sequence zostanie wprowadzona do modelu i wygeneruje przewidywane słowa. Następnie sprawdzisz najwyższą wartość na każdym wyjściu, aby znaleźć właściwy indeks. Na koniec porównaj, aby zobaczyć prognozę modelu z prawdziwym zdaniem

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))

Teraz zacznijmy nasze szkolenie od Seq to Seq, z liczbą iteracji 75000 i liczbą warstw RNN równą 1 z ukrytym rozmiarem 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)

Jak widać, nasze przewidywane zdanie nie jest dobrze dopasowane, więc aby uzyskać większą dokładność, musisz trenować z dużo większą ilością danych i spróbować dodać więcej iteracji i liczbę warstw, używając sekwencji do uczenia sekwencyjnego.

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