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 đó

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 Seq2seq
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.

Mô hình Seq2seq
Mô hình Seq2Seq

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.

Mô hình Seq2seq

Seq2Seq

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>

Tóm tắt bài viết này với: