Mô hình Seq2seq (Trình tự theo trình tự) với PyTorch
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à động cơ đằng sau Google Translate điều đó giúp chúng ta hiểu được 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 bằng 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 chuỗi trạng thái tiếp theo từ chuỗi trước đó.
Cách dự đoán trình tự từ trình tự trước đó
Sau đây là các bước để dự đoán trình tự từ trình tự trước đó bằng PyTorch.
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ặp câu song ngữ được phân cách bằng tab. Ở đây tôi sẽ sử dụng tập dữ liệu tiếng Anh sang tiếng Indonesia. Bạn có thể chọn bất cứ thứ gì mình thích nhưng hãy nhớ thay đổi tên file và thư mục trong mã.
from __future__ import unicode_literals, print_function, division
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import numpy as np
import pandas as pd
import os
import re
import random
device = 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 đánh chỉ mục duy nhất trong lớp Lang để tạo thành từ điển. Lớp Lang sẽ lưu trữ từng câu và chia nhỏ từng chữ 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ự theo trình tự.
SOS_token = 0
EOS_token = 1
MAX_LENGTH = 20
#initialize Lang Class
class Lang:
def __init__(self):
#initialize containers to hold the words and corresponding index
self.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 container
def 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 counter
def addWord(self, word):
if word not in self.word2index:
self.word2index[word] = self.n_words
self.word2count[word] = 1
self.index2word[self.n_words] = word
self.n_words += 1
else:
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 chia 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ừ trong chỉ mục của nó.
Vì dữ liệu của chúng ta đượ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. Pandas sẽ đọc dữ liệu của chúng tôi dưới dạng dataFrame và chia nó thành câu nguồn và câu đích. Đối với mỗi câu mà 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
- chuyển đổi sang ASCII từ Unicode
- chia các câu để bạn có mỗi từ trong đó.
#Normalize every sentence
def 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 sentence
def read_sentence(df, lang1, lang2):
sentence1 = normalize_sentence(df, lang1)
sentence2 = normalize_sentence(df, lang2)
return sentence1, sentence2
def read_file(loc, lang1, lang2):
df = pd.read_csv(loc, delimiter='\t', header=None, names=[lang1, lang2])
return df
def 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 loại tensor. Điều này cũng quan trọng vì đây là phần mà ở mỗi cuối câu sẽ có một mã thông báo để báo cho mạng biết rằng quá trình nhập đã 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 token 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

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 cùng của mô hình. Bộ mã hóa sẽ mã hóa từng từ trong câu thành một chỉ mục từ vựng 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òn có thể dự đoán được đầu vào tiếp theo để tạo thành 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ó token để đánh dấu sự kết thúc của đầu ra. Vì vậy, từ bộ mã hóa sẽ chuyển một trạng thái đến bộ giải mã để dự đoán đầu ra.

Bộ mã hóa sẽ mã hóa câu đầu vào của chúng ta từng từ một theo trình tự và cuối cùng sẽ có một token để đánh dấu sự kết thúc của câu. Bộ mã hóa bao gồm lớp Nhúng và lớp GRU. Lớp Nhúng là một bảng tra cứu lưu trữ phần 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 lớp GRU. Lớp GRU là Đơn vị lặp lại có cổng bao gồm nhiều loại lớp 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à cổng mới.

Bộ giải mã sẽ giải mã đầu vào từ đầu ra của 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à chuyển nó vào lớp GRU để tính toán trạng thái đầu ra dự đoán. Sau đó, 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 layers
self.input_dim = input_dim
self.embbed_dim = embbed_dim
self.hidden_dim = hidden_dim
self.num_layers = num_layers
#initialize the embedding layer with input and embbed dimention
self.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 layers
self.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, hidden
class 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 layers
self.embbed_dim = embbed_dim
self.hidden_dim = hidden_dim
self.output_dim = output_dim
self.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, hidden
class Seq2Seq(nn.Module):
def __init__(self, encoder, decoder, device, MAX_LENGTH=MAX_LENGTH):
super().__init__()
#initialize the encoder and decoder
self.encoder = encoder
self.decoder = decoder
self.device = device
def 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 outputs
outputs = torch.zeros(target_length, batch_size, vocab_size).to(self.device)
#encode every word in a sentence
for i in range(input_length):
encoder_output, encoder_hidden = self.encoder(source[i])
#use the encoder’s hidden layer as the decoder hidden
decoder_hidden = encoder_hidden.to(device)
#add a token before the first predicted word
decoder_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_output
teacher_force = random.random() < teacher_forcing_ratio
topv, topi = decoder_output.topk(1)
input = (target[t] if teacher_force else topi)
if(teacher_force == False and input.item() == EOS_token):
break
return outputs
Bước 3) Đào tạo người mẫu
Quá trình đào tạo trong các mô hình Seq2seq bắt đầu bằng việc chuyển đổi từng cặp câu thành Tensors từ chỉ số Lang của chúng. Mô hình trình tự đến trình 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 kết quả đầu ra chính xác. Ở mỗi bước, đầu ra từ mô hình sẽ được tính toán với các từ đúng để tìm ra tổn thất và cập nhật các tham số. Vì vậy, vì bạn sẽ sử dụng 75000 lần lặp, mô hình trình tự này 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.5
def clacModel(model, input_tensor, target_tensor, model_optimizer, criterion):
model_optimizer.zero_grad()
input_length = input_tensor.size(0)
loss = 0
epoch_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 result
for ot in range(num_iter):
loss += criterion(output[ot], target_tensor[ot])
loss.backward()
model_optimizer.step()
epoch_loss = loss.item() / num_iter
return epoch_loss
def trainModel(model, source, target, pairs, num_iteration=20000):
model.train()
optimizer = optim.SGD(model.parameters(), lr=0.01)
criterion = nn.NLLLoss()
total_loss_iterations = 0
training_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 += loss
if iter % 5000 == 0:
avarage_loss= total_loss_iterations / 5000
total_loss_iterations = 0
print('%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á của Seq2seq PyTorch là kiểm tra đầu ra của mô hình. Mỗi cặp Trình tự đến các mô hình 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ẽ xem giá trị cao nhất ở mỗi đầu ra để tìm chỉ mục chính xác. Và cuối cùng các bạn sẽ so sánh để xem dự đoán mẫu 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('<EOS>')
break
else:
decoded_words.append(output_lang.index2word[topi[0].item()])
return decoded_words
def 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ờ, chúng ta hãy bắt đầu đào tạo 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 words
input_size = source.n_words
output_size = target.n_words
print('Input : {} Output : {}'.format(input_size, output_size))
embed_size = 256
hidden_size = 512
num_layers = 1
num_iteration = 100000
#create encoder-decoder model
encoder = 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 model
print(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 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 cũng như số lớp bằng cách sử dụng Trình tự để học theo trình tự.
random sentence ['tom is finishing his work', 'tom sedang menyelesaikan pekerjaannya']
Input : 3551 Output : 4253
Encoder(
(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.0906
10000 3.9129
15000 3.8171
20000 3.8369
25000 3.8199
30000 3.7957
35000 3.8037
40000 3.8098
45000 3.7530
50000 3.7119
55000 3.7263
60000 3.6933
65000 3.6840
70000 3.7058
75000 3.7044
> this is worth one million yen
= ini senilai satu juta yen
< tom sangat satu juta yen <EOS>
> she got good grades in english
= dia mendapatkan nilai bagus dalam bahasa inggris
< tom meminta nilai bagus dalam bahasa inggris <EOS>
> put in a little more sugar
= tambahkan sedikit gula
< tom tidak <EOS>
> are you a japanese student
= apakah kamu siswa dari jepang
< tom kamu memiliki yang jepang <EOS>
> 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 <EOS>
> speaking about trips have you ever been to kobe
= berbicara tentang wisata apa kau pernah ke kobe
< tom tidak <EOS>
> tom bought me roses
= tom membelikanku bunga mawar
< tom tidak bunga mawar <EOS>
> no one was more surprised than tom
= tidak ada seorangpun yang lebih terkejut dari tom
< tom ada orang yang lebih terkejut <EOS>
> i thought it was true
= aku kira itu benar adanya
< tom tidak <EOS>

