PyTorch を使用した Seq2seq (シーケンスツーシーケンス) モデル

NLPとは何ですか?

NLP または自然言語処理は、コンピューターが自然言語で人間を理解し、操作し、応答するのに役立つ人工知能の一般的な分野の 1 つです。 NLP は背後にあるエンジンです Google Translate それは他の言語を理解するのに役立ちます。

Seq2Seqとは何ですか?

シーケンス 2 シーケンス これは、タグとアテンション値を使用してシーケンスの入力をシーケンスの出力にマッピングする、エンコーダー/デコーダー ベースの機械翻訳および言語処理の方法です。 このアイデアは、特別なトークンと連携して動作する 2 つの RNN を使用し、前のシーケンスから次の状態シーケンスを予測しようとすることです。

前のシーケンスからシーケンスを予測する方法

前のシーケンスからシーケンスを予測する

以下は、PyTorch を使用して前のシーケンスからシーケンスを予測する手順です。

ステップ 1) データをロードする

データセットには、次のデータセットを使用します。 タブ区切りの対訳文ペア。 ここでは英語からインドネシア語へのデータセットを使用します。 好きなものを選択できますが、コード内のファイル名とディレクトリを変更することを忘れないでください。

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

ステップ 2) データの準備

データセットを直接使用することはできません。 文章を単語に分割し、One-Hot Vector に変換する必要があります。 すべての単語は、辞書を作成するために Lang クラス内で一意にインデックス付けされます。 Lang クラスはすべての文を保存し、addSentence を使用して単語ごとに分割します。 次に、シーケンスモデルのすべての未知の単語にインデックスを付けて辞書を作成します。

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

Lang クラスは、辞書の作成に役立つクラスです。 言語ごとに、すべての文が単語に分割されてコンテナに追加されます。 各コンテナは、単語を適切なインデックスに保存し、単語をカウントし、単語のインデックスを追加します。これにより、単語のインデックスを検索したり、インデックスから単語を検索したりするために使用できるようになります。

データは TAB で区切られているため、次を使用する必要があります。 パンダ データローダーとして。 Pandas はデータを dataFrame として読み取り、ソース文とターゲット文に分割します。 あなたが持っているすべての文について、

  • 小文字に正規化します。
  • 文字以外をすべて削除する
  • Unicode から ASCII に変換する
  • 文を分割して、その中に各単語が含まれるようにします。
#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

使用するもう XNUMX つの便利な関数は、ペアを 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 モデル
シーケンス 2 シーケンス

PyTorch Seq2seq モデルは、モデルの上に PyTorch エンコーダ デコーダを使用する一種のモデルです。 エンコーダは文を単語ごとにインデックス付きの語彙またはインデックス付きの既知の単語にエンコードします。デコーダは入力を順番にデコードすることでコード化された入力の出力を予測し、次の場合は最後の入力を次の入力として使用しようとします。それが可能だ。 この方法では、次の入力を予測して文章を作成することも可能です。 各文にはシーケンスの終わりを示すトークンが割り当てられます。 予測の最後には、出力の終了をマークするトークンも表示されます。 したがって、エンコーダーからデコーダーに状態を渡し、出力を予測します。

Seq2seq モデル
Seq2Seq モデル

エンコーダーは入力文を単語ごとに順番にエンコードし、最後に文の終わりをマークするトークンが作成されます。 エンコーダーは、Embedding レイヤーと GRU レイヤーで構成されます。 埋め込みレイヤーは、固定サイズの単語辞書への入力の埋め込みを保存するルックアップ テーブルです。 これは GRU 層に渡されます。 GRU レイヤーは、複数のレイヤー タイプで構成されるゲート付きリカレント ユニットです。 Rnn これにより、シーケンスされた入力が計算されます。 このレイヤーは、前のレイヤーから隠れた状態を計算し、リセット、更新、および新しいゲートを更新します。

Seq2seq モデル

シーケンス 2 シーケンス

デコーダはエンコーダ出力から入力をデコードします。 次の出力を予測し、可能であればそれを次の入力として使用しようとします。 Decoder は、Embedding 層、GRU 層、および Linear 層で構成されます。 埋め込み層は出力のルックアップ テーブルを作成し、それを 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 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

ステップ 3) モデルのトレーニング

Seq2seq モデルのトレーニング プロセスは、文の各ペアをラング インデックスからテンソルに変換することから始まります。 シーケンス間モデルは、オプティマイザーとして SGD を使用し、損失を計算するために NLLLoss 関数を使用します。 トレーニング プロセスは、正しい出力を予測するために、文のペアをモデルに供給することから始まります。 各ステップで、モデルからの出力が真実の言葉で計算され、損失が検出され、パラメーターが更新されます。 したがって、75000 回の反復を使用するため、シーケンス間モデルはデータセットからランダムな 75000 ペアを生成します。

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

ステップ 4) モデルをテストする

Seq2seq PyTorch の評価プロセスは、モデルの出力を確認することです。 Sequence to sequence モデルの各ペアがモデルにフィードされ、予測単語が生成されます。 その後、各出力の最高値を調べて正しいインデックスを見つけます。 そして最後に、モデルの予測と実際の文を比較して確認します。

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

ここで、反復数 75000、RNN 層の数 1、隠れサイズ 512 で、Seq to Seq でトレーニングを開始しましょう。

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)

ご覧のとおり、予測された文はあまりよく一致していないため、より高い精度を得るには、より多くのデータでトレーニングし、シーケンス間学習を使用して反復回数とレイヤー数を追加する必要があります。

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>