Модель Seq2seq (от последовательности к последовательности) с PyTorch

Содержание:

Anonim

Что такое НЛП?

НЛП или обработка естественного языка - одна из популярных ветвей искусственного интеллекта, которая помогает компьютерам понимать человека, манипулировать им или реагировать на него на их естественном языке. 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. Затем создайте словарь, индексируя каждое неизвестное слово для последовательности для моделей последовательности.

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

Еще одна полезная функция, которую вы будете использовать, - это преобразование пар в Tensor. Это очень важно, потому что наша сеть считывает только данные тензорного типа. Это также важно, потому что это часть, в которой на каждом конце предложения будет токен, сообщающий сети, что ввод завершен. Для каждого слова в предложении он получит индекс из соответствующего слова в словаре и добавит токен в конец предложения.

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

Кодировщик будет кодировать наше входное предложение слово за словом в последовательности, и в конце будет маркер, чтобы отметить конец предложения. Кодировщик состоит из слоя встраивания и слоев 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 начинается с преобразования каждой пары предложений в тензоры из их индекса Lang. Наша модель от последовательности к последовательности будет использовать 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 to 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)

Как видите, наше предсказанное предложение не очень хорошо совпадает, поэтому для получения более высокой точности вам нужно тренироваться с гораздо большим количеством данных и попытаться добавить больше итераций и количества слоев, используя Последовательность для последовательного обучения.

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