Vad är NLP?
NLP eller Natural Language Processing är en av de populära grenarna av artificiell intelligens som hjälper datorer att förstå, manipulera eller svara på en människa i deras naturliga språk. NLP är motorn bakom Google Translate som hjälper oss att förstå andra språk.
Vad är Seq2Seq?
Seq2Seq är en metod för kodar-avkodare-baserad maskinöversättning och språkbehandling som mappar en sekvensingång till en sekvensutgång med en tagg och uppmärksamhetsvärde. Tanken är att använda två RNN som fungerar tillsammans med en speciell token och försöker förutsäga nästa tillståndssekvens från föregående sekvens.
Steg 1) Ladda vår data
För vår dataset använder du en dataset från Tab-avgränsade tvåspråkiga meningspar. Här kommer jag att använda engelska till indonesiska dataset. Du kan välja vad du vill men kom ihåg att ändra filnamn och katalog 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")
Steg 2) Dataförberedelse
Du kan inte använda dataset direkt. Du måste dela upp meningarna i ord och konvertera den till One-Hot Vector. Varje ord indexeras unikt i Lang-klassen för att göra en ordbok. Lang Class lagrar varje mening och delar upp ord för ord med addSentence. Skapa sedan en ordlista genom att indexera alla okända ord för Sekvens till 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 är en klass som hjälper oss att skapa en ordbok. För varje språk delas varje mening i ord och läggs sedan till i behållaren. Varje behållare lagrar orden i lämpligt index, räknar ordet och lägger till ordets index så att vi kan använda det för att hitta index för ett ord eller hitta ett ord från dess index.
Eftersom våra data är åtskilda av TAB måste du använda pandor som vår dataladdare. Pandas kommer att läsa våra data som dataFrame och dela upp dem i vår källa och målsättning. För varje mening du har,
- du kommer att normalisera det till gemener,
- ta bort alla icke-karaktärer
- konvertera till ASCII från Unicode
- dela upp meningarna så att du har varje 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 annan användbar funktion som du kommer att använda är att konvertera par till Tensor. Detta är mycket viktigt eftersom vårt nätverk bara läser tensortypsdata. Det är också viktigt eftersom det är den delen som i varje ände av meningen kommer att finnas en symbol för att berätta för nätverket att ingången är klar. För varje ord i meningen kommer det att få indexet från lämpligt ord i ordboken och lägga till en symbol i slutet av meningen.
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
Källa: Seq2Seq
PyTorch Seq2seq-modellen är en slags modell som använder PyTorch-kodaravkodare ovanpå modellen. Kodaren kodar meningsordet med ord till ett indexerat ordförråd eller kända ord med index, och avkodaren förutsäger utmatningen från den kodade ingången genom att avkoda ingången i sekvens och försöker använda den sista ingången som nästa ingång om det är möjligt. Med denna metod är det också möjligt att förutsäga nästa ingång för att skapa en mening. Varje mening tilldelas en token för att markera slutet på sekvensen. I slutet av förutsägelsen kommer det också att finnas en token för att markera slutet på utdata. Så från kodaren kommer det att överföra ett tillstånd till avkodaren för att förutsäga utsignalen.
Källa: Seq2Seq Model
Encodern kommer att koda vår inmatade mening ord för ord i ordning och i slutändan kommer det att finnas en token för att markera slutet på en mening. Kodaren består av ett inbäddningsskikt och ett GRU-lager. Inbäddningsskiktet är en uppslagstabell som lagrar inbäddningen av våra inmatningar i en ordlista med fast storlek. Den skickas till ett GRU-lager. GRU-lager är en Gated Recurrent Unit som består av flera lager av RNN som beräknar den sekvenserade ingången. Detta lager beräknar det dolda tillståndet från det tidigare och uppdaterar återställningen, uppdateringen och de nya grindarna.
Källa: Seq2Seq
Avkodaren avkodar ingången från kodarutgången. Det kommer att försöka förutsäga nästa utdata och försöka använda den som nästa ingång om det är möjligt. Avkodaren består av ett inbäddningsskikt, GRU-lager och ett linjärt lager. Det inbäddade lagret gör en uppslagstabell för utdata och skickas till ett GRU-lager för att beräkna det förutsagda utgångstillståndet. Därefter hjälper ett linjärt lager till att beräkna aktiveringsfunktionen för att bestämma det verkliga värdet på den förutsagda utgången.
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
Steg 3) Utbildning av modellen
Träningsprocessen i Seq2seq-modeller startas med att konvertera varje par av meningar till Tensorer från deras Lang-index. Vår sekvens till sekvensmodell använder SGD som optimerings- och NLLLoss-funktion för att beräkna förlusterna. Träningsprocessen börjar med att mata ett par av en mening till modellen för att förutsäga rätt resultat. Vid varje steg kommer produktionen från modellen att beräknas med de sanna orden för att hitta förlusterna och uppdatera parametrarna. Så eftersom du kommer att använda 75000 iterationer kommer vår sekvens till sekvensmodell att generera slumpmässiga 75000 par från vår dataset.
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
Steg 4) Testa modellen
Utvärderingsprocessen för Seq2seq PyTorch är att kontrollera modellutdata. Varje par Sekvens till sekvensmodeller matas in i modellen och genererar de förutsagda orden. Därefter ser du det högsta värdet vid varje utgång för att hitta rätt index. Och i slutändan kommer du att jämföra för att se vår modellförutsägelse med den sanna meningen
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))
Låt oss nu börja vår träning med Seq to Seq, med antalet iterationer på 75000 och antal RNN-lager på 1 med den dolda storleken 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 matchas vår förutsagda mening inte så bra, så för att få högre noggrannhet måste du träna med mycket mer data och försöka lägga till fler iterationer och antal lager med sekvens för sekvensinlärning.
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