Seq2seq (Sequence to Sequence) Modell med PyTorch

Innholdsfortegnelse:

Anonim

Hva er NLP?

NLP eller Natural Language Processing er en av de populære grenene av kunstig intelligens som hjelper datamaskiner å forstå, manipulere eller svare på et menneske på deres naturlige språk. NLP er motoren bak Google Translate som hjelper oss å forstå andre språk.

Hva er Seq2Seq?

Seq2Seq er en metode for koding -dekoderbasert maskinoversettelse og språkbehandling som tilordner en inngang av sekvens til en utgang av sekvens med en merkelapp og oppmerksomhetsverdi. Ideen er å bruke 2 RNN-er som vil fungere sammen med et spesielt token og prøve å forutsi neste tilstandssekvens fra forrige sekvens.

Trinn 1) Laster inn dataene våre

For datasettet vårt vil du bruke et datasett fra Tab-avgrenset tospråklig setningspar. Her vil jeg bruke engelsk til indonesisk datasett. Du kan velge hva du vil, men husk å endre filnavnet og katalogen i koden.

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

Trinn 2) Dataforberedelse

Du kan ikke bruke datasettet direkte. Du må dele setningene i ord og konvertere dem til One-Hot Vector. Hvert ord vil bli indeksert unikt i Lang-klassen for å lage en ordbok. Lang-klassen lagrer hver setning og deler den ord for ord med addSentence. Lag deretter en ordbok ved å indeksere hvert ukjente ord for Sekvens til sekvensmodeller.

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-klassen er en klasse som vil hjelpe oss å lage en ordbok. For hvert språk blir hver setning delt inn i ord og deretter lagt til beholderen. Hver container lagrer ordene i riktig indeks, teller ordet og legger til ordets indeks slik at vi kan bruke det til å finne indeksen til et ord eller finne et ord fra indeksen.

Fordi dataene våre er atskilt med TAB, må du bruke pandaer som vår datalaster. Pandas vil lese dataene våre som dataFrame og dele dem i vår kilde og målsetning. For hver setning du har,

  • du vil normalisere det til små bokstaver,
  • fjern alle ikke-tegn
  • konvertere til ASCII fra Unicode
  • del setningene, slik at du har hvert ord i det.
#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

En annen nyttig funksjon du vil bruke er konvertering av par til Tensor. Dette er veldig viktig fordi nettverket vårt bare leser data fra tensortypen. Det er også viktig fordi dette er den delen at det i hver ende av setningen vil være et token for å fortelle nettverket at inngangen er ferdig. For hvert ord i setningen får den indeksen fra det aktuelle ordet i ordboken og legger til et token på slutten av setningen.

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

Kilde: Seq2Seq

PyTorch Seq2seq-modellen er en slags modell som bruker PyTorch-koderdekoder på toppen av modellen. Koderen vil kode setningsordet med ord til et indeksert av ordforråd eller kjente ord med indeks, og dekoderen vil forutsi utgangen fra den kodede inngangen ved å dekode inngangen i rekkefølge og vil prøve å bruke den siste inngangen som neste inngang hvis det er mulig. Med denne metoden er det også mulig å forutsi neste inngang for å lage en setning. Hver setning tildeles et token for å markere slutten på sekvensen. På slutten av spådommen vil det også være et token for å markere slutten på utgangen. Så fra koderen vil den passere en tilstand til dekoderen for å forutsi utgangen.

Kilde: Seq2Seq Model

Koderen vil kode vår inngangssetning ord for ord i rekkefølge, og til slutt vil det være et token som markerer slutten på en setning. Koderen består av et innebygd lag og et GRU-lag. Embedding-laget er en oppslagstabell som lagrer innebyggingen av våre innspill i en ordliste med faste størrelser. Den vil bli overført til et GRU-lag. GRU-laget er en inngjerdet tilbakevendende enhet som består av flere lagstype RNN som vil beregne den sekvenserte inngangen. Dette laget vil beregne den skjulte tilstanden fra den forrige og oppdatere tilbakestillingen, oppdateringen og de nye portene.

Kilde: Seq2Seq

Dekoderen vil dekode inngangen fra koderen. Den vil prøve å forutsi neste utgang og prøve å bruke den som neste inngang hvis det er mulig. Dekoderen består av et innebygd lag, GRU-lag og et lineært lag. Det innebygde laget vil lage en oppslagstabell for utdataene og sendes til et GRU-lag for å beregne den forventede utgangstilstanden. Etter det vil et lineært lag hjelpe deg med å beregne aktiveringsfunksjonen for å bestemme den sanne verdien av den forutsagte utgangen.

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

Trinn 3) Trening av modellen

Treningsprosessen i Seq2seq-modeller startes med å konvertere hvert setningspar til Tensorer fra Lang-indeksen. Vår sekvens til sekvensmodell vil bruke SGD som optimaliserings- og NLLLoss-funksjon for å beregne tapene. Treningsprosessen begynner med å mate paret med en setning til modellen for å forutsi riktig utgang. Ved hvert trinn blir produksjonen fra modellen beregnet med de sanne ordene for å finne tapene og oppdatere parametrene. Så fordi du vil bruke 75000 iterasjoner, vil vår sekvens til sekvensmodell generere tilfeldige 75000 par fra datasettet vårt.

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

Trinn 4) Test modellen

Evalueringsprosessen til Seq2seq PyTorch er å sjekke modellutgangen. Hvert par Sekvens til sekvensmodeller mates inn i modellen og genererer de forutsagte ordene. Etter det vil du se den høyeste verdien ved hver utgang for å finne riktig indeks. Og til slutt vil du sammenligne for å se modellforutsigelsen vår med den sanne setningen

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

La oss nå starte treningen vår med Seq to Seq, med antall iterasjoner på 75000 og antall RNN-lag på 1 med den skjulte størrelsen på 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)

Som du kan se, samsvarer ikke vår forutsagte setning veldig bra, så for å få høyere nøyaktighet, må du trene med mye mer data og prøve å legge til flere iterasjoner og antall lag ved hjelp av sekvens til sekvenslæring.

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