Mô hình Seq2seq (Sequence to Sequence) với PyTorch

Mục lục:

Anonim

NLP là gì?

NLP hay Xử lý ngôn ngữ tự nhiên là một trong những nhánh phổ biến của Trí tuệ nhân tạo giúp máy tính hiểu, thao tác hoặc phản hồi con người bằng ngôn ngữ tự nhiên của họ. NLP là công cụ đằng sau Google Dịch giúp chúng ta hiểu các ngôn ngữ khác.

Seq2Seq là gì?

Seq2Seq là một phương pháp xử lý ngôn ngữ và dịch máy dựa trên bộ mã hóa-giải mã, ánh xạ đầu vào của chuỗi thành đầu ra của chuỗi có thẻ và giá trị chú ý. Ý tưởng là sử dụng 2 RNN sẽ hoạt động cùng với một mã thông báo đặc biệt và cố gắng dự đoán trình tự trạng thái tiếp theo từ trình tự trước đó.

Bước 1) Tải dữ liệu của chúng tôi

Đối với tập dữ liệu của chúng tôi, bạn sẽ sử dụng tập dữ liệu từ các Cặp câu song ngữ được phân tách bằng tab. Ở đây tôi sẽ sử dụng tập dữ liệu từ tiếng Anh sang tiếng Indonesia. Bạn có thể chọn bất cứ thứ gì bạn thích nhưng hãy nhớ thay đổi tên tệp và thư mục trong mã.

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

Bước 2) Chuẩn bị dữ liệu

Bạn không thể sử dụng tập dữ liệu trực tiếp. Bạn cần tách các câu thành các từ và chuyển nó thành One-Hot Vector. Mỗi từ sẽ được lập chỉ mục duy nhất trong lớp Lang để làm từ điển. Lớp Lang sẽ lưu trữ từng câu và tách nó ra từng từ bằng addSentence. Sau đó, tạo một từ điển bằng cách lập chỉ mục mọi từ chưa biết cho các mô hình Trình tự để trình tự.

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

Lớp Lang là lớp sẽ giúp chúng ta làm từ điển. Đối với mỗi ngôn ngữ, mỗi câu sẽ được tách thành các từ và sau đó được thêm vào vùng chứa. Mỗi vùng chứa sẽ lưu trữ các từ trong chỉ mục thích hợp, đếm từ và thêm chỉ mục của từ để chúng ta có thể sử dụng nó để tìm chỉ mục của một từ hoặc tìm một từ từ chỉ mục của nó.

Vì dữ liệu của chúng tôi được phân tách bằng TAB nên bạn cần sử dụng gấu trúc làm trình tải dữ liệu của chúng tôi. Gấu trúc sẽ đọc dữ liệu của chúng ta dưới dạng dataFrame và chia nó thành nguồn và câu đích của chúng ta. Đối với mỗi câu bạn có,

  • bạn sẽ bình thường hóa nó thành chữ thường,
  • loại bỏ tất cả các ký tự không phải
  • chuyển đổi sang ASCII từ Unicode
  • tách các câu để bạn có từng từ trong đó.
#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

Một chức năng hữu ích khác mà bạn sẽ sử dụng là chuyển đổi các cặp thành Tensor. Điều này rất quan trọng vì mạng của chúng tôi chỉ đọc dữ liệu kiểu tensor. Nó cũng rất quan trọng vì đây là phần mà ở mỗi cuối câu sẽ có một mã thông báo cho mạng biết rằng quá trình nhập liệu đã hoàn tất. Với mỗi từ trong câu, nó sẽ lấy chỉ mục từ từ thích hợp trong từ điển và thêm mã thông báo vào cuối câu.

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)

Mô hình Seq2Seq

Nguồn: Seq2Seq

Mô hình PyTorch Seq2seq là một loại mô hình sử dụng bộ giải mã bộ mã hóa PyTorch trên đầu mô hình. Bộ mã hóa sẽ mã hóa từng câu từng từ thành một từ vựng được lập chỉ mục hoặc các từ đã biết có chỉ mục và bộ giải mã sẽ dự đoán đầu ra của đầu vào được mã hóa bằng cách giải mã đầu vào theo trình tự và sẽ cố gắng sử dụng đầu vào cuối cùng làm đầu vào tiếp theo nếu nó có thể. Với phương pháp này, cũng có thể dự đoán đầu vào tiếp theo để tạo câu. Mỗi câu sẽ được gán một mã thông báo để đánh dấu sự kết thúc của chuỗi. Khi kết thúc dự đoán, cũng sẽ có một mã thông báo để đánh dấu sự kết thúc của đầu ra. Vì vậy, từ bộ mã hóa, nó sẽ chuyển một trạng thái để bộ giải mã dự đoán đầu ra.

Nguồn: Seq2Seq Model

Bộ mã hóa sẽ mã hóa từng câu đầu vào của chúng ta theo trình tự và cuối cùng sẽ có một mã thông báo để đánh dấu phần cuối của một câu. Bộ mã hóa bao gồm một lớp Nhúng và một lớp GRU. Lớp Nhúng là một bảng tra cứu lưu trữ việc nhúng đầu vào của chúng ta vào một từ điển các từ có kích thước cố định. Nó sẽ được chuyển đến một lớp GRU. Lớp GRU là một Đơn vị lặp lại được xác định bao gồm nhiều loại RNN sẽ tính toán đầu vào theo trình tự. Lớp này sẽ tính toán trạng thái ẩn từ lớp trước đó và cập nhật các cổng đặt lại, cập nhật và mới.

Nguồn: Seq2Seq

Bộ giải mã sẽ giải mã đầu vào từ đầu ra bộ mã hóa. Nó sẽ cố gắng dự đoán đầu ra tiếp theo và cố gắng sử dụng nó làm đầu vào tiếp theo nếu có thể. Bộ giải mã bao gồm lớp Nhúng, lớp GRU và lớp Tuyến tính. Lớp nhúng sẽ tạo một bảng tra cứu đầu ra và được chuyển vào lớp GRU để tính toán trạng thái đầu ra dự đoán. Sau đó, một lớp Tuyến tính sẽ giúp tính toán hàm kích hoạt để xác định giá trị thực của đầu ra dự đoán.

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

Bước 3) Đào tạo người mẫu

Quá trình đào tạo trong các mô hình Seq2seq được bắt đầu với việc chuyển đổi từng cặp câu thành Tensors từ chỉ mục Lang của chúng. Mô hình tuần tự của chúng tôi sẽ sử dụng SGD làm trình tối ưu hóa và hàm NLLLoss để tính toán tổn thất. Quá trình huấn luyện bắt đầu bằng việc đưa cặp câu vào mô hình để dự đoán đầu ra chính xác. Tại mỗi bước, kết quả đầu ra từ mô hình sẽ được tính toán với các từ true để tìm ra các tổn thất và cập nhật các thông số. Vì vậy, bởi vì bạn sẽ sử dụng 75000 lần lặp, nên mô hình trình tự nối tiếp của chúng tôi sẽ tạo ra 75000 cặp ngẫu nhiên từ tập dữ liệu của chúng tôi.

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

Bước 4) Kiểm tra mô hình

Quá trình đánh giá Seq2seq PyTorch là kiểm tra đầu ra của mô hình. Mỗi cặp mô hình Trình tự đến trình tự sẽ được đưa vào mô hình và tạo ra các từ được dự đoán. Sau đó, bạn sẽ nhìn vào giá trị cao nhất ở mỗi đầu ra để tìm chỉ số chính xác. Và cuối cùng, bạn sẽ so sánh để xem dự đoán mô hình của chúng tôi với câu đúng

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

Bây giờ, hãy bắt đầu đào tạo của chúng ta với Seq to Seq, với số lần lặp là 75000 và số lớp RNN là 1 với kích thước ẩn là 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)

Như bạn có thể thấy, câu dự đoán của chúng tôi không khớp lắm, vì vậy để có độ chính xác cao hơn, bạn cần phải huấn luyện với nhiều dữ liệu hơn và cố gắng thêm nhiều lần lặp và số lớp hơn bằng cách sử dụng Trình tự để học trình tự.

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