Hvad er NLP?
NLP eller Natural Language Processing er en af de populære grene af kunstig intelligens, der hjælper computere med at forstå, manipulere eller reagere på et menneske på deres naturlige sprog. NLP er motoren bag Google Translate, der hjælper os med at forstå andre sprog.
Hvad er Seq2Seq?
Seq2Seq er en metode til encoder-dekoderbaseret maskinoversættelse og sprogbehandling, der kortlægger et input af sekvens til et output af sekvens med et mærke og opmærksomhedsværdi. Ideen er at bruge 2 RNN'er, der vil arbejde sammen med et specielt token og forsøge at forudsige den næste tilstandssekvens fra den foregående sekvens.
Trin 1) Indlæsning af vores data
I vores datasæt bruger du et datasæt fra tabuleret tosprogede sætningspar. Her vil jeg bruge det engelske til indonesiske datasæt. Du kan vælge hvad du vil, men husk at ændre filnavnet og kataloget 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")
Trin 2) Dataforberedelse
Du kan ikke bruge datasættet direkte. Du skal opdele sætningerne i ord og konvertere den til One-Hot Vector. Hvert ord indekseres entydigt i Lang-klassen for at lave en ordbog. Lang-klassen gemmer hver sætning og deler den ord for ord med addSentence. Opret derefter en ordbog ved at indeksere hvert ukendt 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, der hjælper os med at lave en ordbog. For hvert sprog opdeles hver sætning i ord og føjes derefter til containeren. Hver container gemmer ordene i det relevante indeks, tæller ordet og tilføjer ordets indeks, så vi kan bruge det til at finde indekset for et ord eller finde et ord fra dets indeks.
Da vores data er adskilt af TAB, skal du bruge pandaer som vores datalæsser. Pandas læser vores data som dataFrame og deler dem i vores kilde og målsætning. For hver sætning, du har,
- du normaliserer det til små bogstaver,
- fjern alt ikke-tegn
- konvertere til ASCII fra Unicode
- del sætningerne, så 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 anden nyttig funktion, som du vil bruge, er konvertering af par til Tensor. Dette er meget vigtigt, fordi vores netværk kun læser tensordata. Det er også vigtigt, fordi dette er den del, at der i hver ende af sætningen vil være et token til at fortælle netværket, at input er færdig. For hvert ord i sætningen får det indekset fra det relevante ord i ordbogen og tilføjer et token i slutningen af sætningen.
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 Model
Kilde: Seq2Seq
PyTorch Seq2seq-modellen er en slags model, der bruger PyTorch-koderdekoder oven på modellen. Encoderen koder sætningsordet med ord i et indekseret ordforråd eller kendte ord med indeks, og dekoderen forudsiger output fra det kodede input ved at afkode indgangen i rækkefølge og vil forsøge at bruge den sidste indgang som den næste input, hvis er det muligt. Med denne metode er det også muligt at forudsige det næste input for at oprette en sætning. Hver sætning tildeles et token for at markere slutningen af sekvensen. I slutningen af forudsigelsen vil der også være et token for at markere slutningen af output. Så fra koderen vil den passere en tilstand til dekoderen for at forudsige output.
Kilde: Seq2Seq Model
Encoderen koder vores input sætning ord for ord i rækkefølge, og til sidst vil der være et token for at markere slutningen af en sætning. Koderen består af et indlejringslag og et GRU-lag. Embedding-laget er en opslagstabel, der gemmer indlejringen af vores input i en ordbog med fast størrelse af ord. Det sendes til et GRU-lag. GRU-laget er en lukket tilbagevendende enhed, der består af flere lagstyper af RNN, der beregner den sekventerede input. Dette lag beregner den skjulte tilstand fra den forrige og opdaterer nulstillingen, opdateringen og de nye porte.
Kilde: Seq2Seq
Dekoderen afkoder indgangen fra encoderudgangen. Det vil forsøge at forudsige det næste output og forsøge at bruge det som det næste input, hvis det er muligt. Dekoderen består af et indlejringslag, GRU-lag og et lineært lag. Det indlejrede lag udgør en opslagstabel for output og sendes til et GRU-lag for at beregne den forudsagte outputtilstand. Derefter hjælper et lineært lag med at beregne aktiveringsfunktionen for at bestemme den sande værdi af det forudsagte output.
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
Trin 3) Træning af modellen
Træningsprocessen i Seq2seq-modeller startes med at konvertere hvert sæt sæt til Tensorer fra deres Lang-indeks. Vores sekvens til sekvensmodel bruger SGD som optimerings- og NLLLoss-funktion til at beregne tabene. Træningsprocessen begynder med at fodre parret med en sætning til modellen for at forudsige den korrekte output. Ved hvert trin beregnes output fra modellen med de sande ord for at finde tabene og opdatere parametrene. Så fordi du bruger 75000 iterationer, genererer vores sekvens til sekvensmodel tilfældige 75000 par fra vores datasæt.
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
Trin 4) Test modellen
Evalueringsprocessen for Seq2seq PyTorch er at kontrollere modeloutput. Hvert par Sekvens til sekvensmodeller indføres i modellen og genererer de forudsagte ord. Derefter ser du den højeste værdi ved hvert output for at finde det rigtige indeks. Og i sidste ende vil du sammenligne for at se vores model forudsigelse med den sande sætning
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))
Lad os nu starte vores træning med Seq to Seq, med antallet af iterationer på 75000 og antal RNN-lag på 1 med den skjulte størrelse 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, matches vores forudsagte sætning ikke særlig godt, så for at få højere nøjagtighed er du nødt til at træne med meget flere data og forsøge at tilføje flere iterationer og antal lag ved hjælp af sekvens til sekvensindlæ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