Seq2seq (Sequence to Sequence) -malli PyTorchilla

Sisällysluettelo:

Anonim

Mikä on NLP?

NLP tai luonnollinen kielenkäsittely on yksi keinotekoisen älykkyyden suosituimmista haaroista, joka auttaa tietokoneita ymmärtämään, manipuloimaan ihmistä tai vastaamaan ihmiseen heidän luonnollisella kielellään. NLP on Google Kääntäjän takana oleva moottori, joka auttaa meitä ymmärtämään muita kieliä.

Mikä on Seq2Seq?

Seq2Seq on menetelmä kooderi-dekooderipohjaiseen konekääntämiseen ja kielenkäsittelyyn, joka kartoittaa sekvenssin syötteen sekvenssin tulokseen, jossa on tunniste ja huomioarvo. Ajatuksena on käyttää 2 RNN: ää, jotka toimivat yhdessä erityisen tunnuksen kanssa ja yrittää ennustaa seuraava tilasekvenssi edellisestä sekvenssistä.

Vaihe 1) Ladataan tietomme

Aineistossamme käytetään sarkaimilla erotettujen kaksikielisten lauseparien tietojoukkoa. Tässä käytän englannista indonesiaan liittyvää aineistoa. Voit valita mitä haluat, mutta muista muuttaa tiedoston nimi ja hakemisto koodissa.

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

Vaihe 2) Tietojen valmistelu

Et voi käyttää tietojoukkoa suoraan. Sinun on jaettava lauseet sanoiksi ja muunnettava se One-Hot Vektoriksi. Jokainen sana indeksoidaan ainutlaatuisesti Lang-luokassa sanakirjan luomiseksi. Lang-luokka tallentaa jokaisen lauseen ja jakaa sen sana sanalta addSentence-sanalla. Luo sitten sanakirja indeksoimalla kaikki sekvenssin tuntemattomat sanat sekvenssimalleihin.

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-luokka on luokka, joka auttaa meitä tekemään sanakirjaa. Jokaiselle kielelle jokainen lause jaetaan sanoiksi ja lisätään sitten säilöön. Jokainen säilö tallentaa sanat sopivaan hakemistoon, laskee sanan ja lisää sanan hakemiston, jotta voimme käyttää sitä sanan hakemiston löytämiseen tai sanan löytämiseen hakemistosta.

Koska tietomme on erotettu sarkaimella, sinun on käytettävä pandoja datakuormaajana. Pandas lukee datamme dataFrame-kehyksenä ja jakaa sen lähde- ja kohdelauseemme. Jokaisesta lauseestasi,

  • normalisoit sen pienillä kirjaimilla,
  • poista kaikki ei-merkit
  • muuntaa ASCII: ksi Unicodesta
  • jaa lauseet, niin sinulla on jokainen sana siinä.
#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

Toinen hyödyllinen toiminto, jota käytät, on parien muuntaminen Tensoriksi. Tämä on erittäin tärkeää, koska verkkomme lukee vain tensorityyppisiä tietoja. Se on myös tärkeä, koska tämä on osa, jonka lauseen jokaisessa päässä on tunnus, joka kertoo verkolle, että syöte on valmis. Jokaisesta lauseen sanasta se saa hakemiston sanakirjan sopivasta sanasta ja lisää merkin lauseen loppuun.

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

Lähde: Seq2Seq

PyTorch Seq2seq -malli on eräänlainen malli, joka käyttää PyTorch-kooderidekooderia mallin päällä. Kooderi koodaa lauseen sanan sanoin indeksoituun sanastoon tai tunnettuja sanoja sisältävään hakemistoon, ja dekooderi ennustaa koodatun syötteen lähdön dekoodaamalla syötteen peräkkäin ja yrittää käyttää viimeistä syötettä seuraavana syötteenä, jos se on mahdollista. Tällä menetelmällä on myös mahdollista ennustaa seuraava syöttö lauseen luomiseksi. Jokaiselle lauseelle annetaan tunnus merkitsevän jakson loppua. Ennusteen lopussa on myös merkki, joka merkitsee lähdön loppua. Joten kooderista se siirtää tilan dekooderille ennustamaan lähdön.

Lähde: Seq2Seq-malli

Kooderi koodaa syöttölauseemme sanasta sanaan peräkkäin, ja lopulta on merkki, joka merkitsee lauseen loppua. Kooderi koostuu upotuskerroksesta ja GRU-kerroksista. Upottava kerros on hakutaulukko, joka tallentaa syötteemme upottamisen kiinteän kokoiseen sanasanastoon. Se välitetään GRU-kerrokselle. GRU-kerros on Aidattu toistuva yksikkö, joka koostuu monikerroksisesta RNN-tyypistä, joka laskee sekvensoidun tulon. Tämä kerros laskee piilotetun tilan edellisestä ja päivittää nollaus-, päivitys- ja uudet portit.

Lähde: Seq2Seq

Dekooderi purkaa kooderin lähdön tulon. Se yrittää ennustaa seuraavan tuloksen ja yrittää käyttää sitä seuraavana tulona, ​​jos se on mahdollista. Dekooderi koostuu upotuskerroksesta, GRU-kerroksesta ja lineaarisesta kerroksesta. Upotuskerros tekee hakutaulukon tulosteelle ja siirretään GRU-kerrokseen ennustetun lähtötilan laskemiseksi. Sen jälkeen lineaarinen kerros auttaa laskemaan aktivointitoiminnon ennustetun ulostulon todellisen arvon määrittämiseksi.

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

Vaihe 3) Kouluta mallia

Koulutusprosessi Seq2seq-malleissa aloitetaan muuntamalla jokaiset lauseparit tensoreiksi niiden Lang-indeksistä. Sarjasarjamallimme käyttää SGD: tä optimoijana ja NLLLoss-funktiota tappioiden laskemiseen. Harjoitusprosessi alkaa syöttämällä lauseparin malliin oikean tuloksen ennustamiseksi. Kussakin vaiheessa mallin tuotos lasketaan oikeilla sanoilla häviöiden löytämiseksi ja parametrien päivittämiseksi. Joten koska käytät 75000 iteraatiota, sekvenssimallimme tuottaa satunnaiset 75000 paria tietojoukostamme.

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

Vaihe 4) Testaa malli

Seq2seq PyTorchin arviointiprosessina on tarkistaa mallin tuotos. Jokainen sekvenssimallimalli syötetään malliin ja luo ennustetut sanat. Sen jälkeen näytät kunkin tuloksen suurimman arvon oikean indeksin löytämiseksi. Ja lopulta verrataan, jotta näet mallin ennustuksemme todellisen lauseen kanssa

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

Aloitetaan nyt Harjoituksemme sekvenssistä sekvensseihin, toistojen lukumäärällä 75000 ja RNN-kerroksen 1 lukumäärällä piilotettu koko 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)

Kuten näette, ennustettu lauseemme ei sovi yhteen kovin hyvin, joten saadaksesi paremman tarkkuuden, sinun on harjoiteltava paljon enemmän tietoja ja yritettävä lisätä lisää iteraatioita ja kerrosten lukumäärää sekvenssin avulla sekvenssin oppimiseen.

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