Модель Seq2seq (Послідовність до Послідовності) з PyTorch

Зміст:

Anonim

Що таке НЛП?

NLP або обробка природних мов - одна з популярних галузей штучного інтелекту, яка допомагає комп’ютерам розуміти, маніпулювати людиною чи реагувати на неї людиною її природною мовою. NLP - це двигун Google Translate, який допомагає нам розуміти інші мови.

Що таке Seq2Seq?

Seq2Seq - це метод машинного перекладу та обробки мови на основі кодера-декодера, який відображає вхід послідовності у вихід послідовності з тегом та значенням уваги. Ідея полягає у використанні 2 RNN, які працюватимуть разом із спеціальним маркером і намагатимуться передбачити наступну послідовність станів з попередньої послідовності.

Крок 1) Завантаження наших даних

Для нашого набору даних ви будете використовувати набір даних із розділених табуляцією двомовних пар речень. Тут я буду використовувати набір даних з англійської на індонезійську. Ви можете вибрати все, що вам подобається, але не забудьте змінити ім’я файлу та каталог у коді.

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

Крок 2) Підготовка даних

Ви не можете використовувати набір даних безпосередньо. Потрібно розділити речення на слова та перетворити їх на One-Hot Vector. Кожне слово буде унікально проіндексовано в класі Lang, щоб скласти словник. Клас Lang буде зберігати кожне речення та розділяти його слово за словом із addSentence. Потім створіть словник, індексуючи кожне невідоме слово Sequence до моделей послідовностей.

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 - це клас, який допоможе нам скласти словник. Для кожної мови кожне речення буде розділено на слова, а потім додано до контейнера. Кожен контейнер буде зберігати слова у відповідному індексі, підраховувати слово та додавати індекс слова, щоб ми могли використовувати його для пошуку індексу слова або пошуку слова з його індексу.

Оскільки наші дані розділені TAB, вам потрібно використовувати pandas як наш завантажувач даних. Pandas прочитає наші дані як dataFrame та розділить їх на наше вихідне та цільове речення. За кожне ваше речення,

  • ви нормалізуєте його до нижньої літери,
  • видалити всі несимвольні
  • перетворити на ASCII з Unicode
  • розділіть речення, щоб у вас було кожне слово.
#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

Ще однією корисною функцією, яку ви будете використовувати, є перетворення пар у тензор. Це дуже важливо, оскільки наша мережа зчитує лише дані типу тензора. Це також важливо, оскільки це частина, в якій у кожному кінці речення буде маркер, який повідомляє мережі, що введення закінчено. Для кожного слова в реченні він отримає індекс відповідного слова у словнику та додасть маркер в кінці речення.

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

Джерело: Seq2Seq

Модель PyTorch Seq2seq - це різновид моделі, яка використовує декодер кодера PyTorch поверх моделі. Кодер кодує слово речення за словами в індексований словниковий запас або відомі слова з індексом, а декодер прогнозуватиме вихід кодованого вводу шляхом декодування вводу послідовно і намагатиметься використовувати останній вхід як наступний вхід, якщо це можливо. За допомогою цього методу можна також передбачити наступний вхід для створення речення. Кожному реченню буде присвоєно маркер для позначення кінця послідовності. В кінці прогнозування також буде маркер для позначення кінця результату. Отже, з кодера він передає стан декодеру, щоб передбачити вихід.

Джерело: Seq2Seq Model

Кодер кодує наше вхідне речення слово за словом послідовно, і в кінці буде маркер для позначення кінця речення. Кодер складається з шару вбудовування та шарів GRU. Шар Вбудовування - це таблиця підстановки, яка зберігає вбудовування нашого вводу у словник слів фіксованого розміру. Він буде переданий шару GRU. Рівень GRU - це керований рекурентний блок, який складається з багаторівневого типу RNN, який обчислює послідовно введені дані. Цей шар обчислить прихований стан з попереднього та оновить скидання, оновлення та нові ворота.

Джерело: Seq2Seq

Декодер декодує вхідні дані з виходу кодера. Він спробує передбачити наступний результат і спробує використовувати його як наступний вхід, якщо це можливо. Декодер складається з шару вбудовування, шару GRU та лінійного шару. Шар для вбудовування створить таблицю підстановки для вихідних даних і перейде в рівень GRU для обчислення прогнозованого стану виводу. Після цього лінійний шар допоможе розрахувати функцію активації для визначення справжнього значення прогнозованого виходу.

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

Крок 3) Навчання моделі

Навчальний процес у моделях Seq2seq розпочинається з перетворення кожної пари речень у тензори з їхнього індексу Ланга. Наша модель послідовності до послідовності використовуватиме SGD як оптимізатор та функцію NLLLoss для обчислення втрат. Тренувальний процес починається з подачі пари речення моделі, щоб передбачити правильний результат. На кожному кроці вихідний результат моделі буде обчислюватися з істинними словами, щоб знайти втрати та оновити параметри. Отож, оскільки ви будете використовувати 75000 ітерацій, наша модель послідовності до послідовності генеруватиме випадкові 75000 пар з нашого набору даних.

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

Крок 4) Перевірте модель

Процес оцінки Seq2seq PyTorch полягає у перевірці вихідних даних моделі. Кожна пара послідовностей до моделей послідовностей буде подаватися в модель і генерувати передбачені слова. Після цього ви будете шукати найвище значення на кожному виході, щоб знайти правильний індекс. І зрештою, ви порівняєте, щоб побачити наш модельний прогноз із справжнім реченням

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

А зараз, давайте почнемо наше навчання з Seq в Seq, з кількістю ітерацій 75000 та кількістю шару RNN 1 із прихованим розміром 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)

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

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