Cos'è la PNL?
La PNL o elaborazione del linguaggio naturale è uno dei rami popolari dell'intelligenza artificiale che aiuta i computer a capire, manipolare o rispondere a un essere umano nel loro linguaggio naturale. La PNL è il motore di Google Translate che ci aiuta a capire altre lingue.
Cos'è Seq2Seq?
Seq2Seq è un metodo di traduzione automatica e di elaborazione del linguaggio basato su codificatore-decodificatore che mappa un input di sequenza su un output di sequenza con un tag e un valore di attenzione. L'idea è di utilizzare 2 RNN che lavoreranno insieme a un gettone speciale e proveranno a prevedere la sequenza di stati successiva dalla sequenza precedente.
Passaggio 1) Caricamento dei nostri dati
Per il nostro set di dati, utilizzerai un set di dati da coppie di frasi bilingue delimitate da tabulazioni. Qui userò il set di dati dall'inglese all'indonesiano. Puoi scegliere tutto ciò che ti piace, ma ricorda di cambiare il nome del file e la directory nel codice.
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")
Passaggio 2) Preparazione dei dati
Non è possibile utilizzare direttamente il set di dati. Devi dividere le frasi in parole e convertirle in One-Hot Vector. Ogni parola verrà indicizzata in modo univoco nella classe Lang per creare un dizionario. La classe Lang memorizzerà ogni frase e la dividerà parola per parola con addSentence. Quindi crea un dizionario indicizzando ogni parola sconosciuta per Sequence in modelli di sequenza.
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
La Lang Class è una classe che ci aiuterà a creare un dizionario. Per ogni lingua, ogni frase verrà suddivisa in parole e quindi aggiunta al contenitore. Ogni contenitore memorizzerà le parole nell'indice appropriato, conterà la parola e aggiungerà l'indice della parola in modo che possiamo usarlo per trovare l'indice di una parola o trovare una parola dal suo indice.
Poiché i nostri dati sono separati da TAB, è necessario utilizzare i panda come caricatore di dati. I panda leggeranno i nostri dati come dataFrame e li divideranno nella nostra frase di origine e di destinazione. Per ogni frase che hai
- lo normalizzerai in minuscolo,
- rimuovi tutti i non caratteri
- convertire in ASCII da Unicode
- dividi le frasi, in modo da contenere ogni parola.
#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
Un'altra funzione utile che utilizzerai è la conversione delle coppie in Tensor. Questo è molto importante perché la nostra rete legge solo dati di tipo tensore. È anche importante perché questa è la parte in cui ad ogni fine della frase ci sarà un token per dire alla rete che l'input è finito. Per ogni parola nella frase, otterrà l'indice dalla parola appropriata nel dizionario e aggiungerà un segno alla fine della frase.
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)
Modello Seq2Seq
Fonte: Seq2Seq
Il modello PyTorch Seq2seq è un tipo di modello che utilizza il decoder dell'encoder PyTorch sopra il modello. L'Encoder codificherà la frase parola per parola in un vocabolario indicizzato o parole note con indice, e il decodificatore predice l'output dell'ingresso codificato decodificando l'ingresso in sequenza e proverà a usare l'ultimo input come input successivo se è possibile. Con questo metodo è anche possibile prevedere l'input successivo per creare una frase. Ad ogni frase verrà assegnato un token per contrassegnare la fine della sequenza. Alla fine della previsione, ci sarà anche un token per contrassegnare la fine dell'output. Quindi, dal codificatore, passerà uno stato al decodificatore per prevedere l'uscita.
Fonte: Seq2Seq Model
L'Encoder codificherà la nostra frase in ingresso parola per parola in sequenza e alla fine ci sarà un token per contrassegnare la fine di una frase. Il codificatore è costituito da un livello di incorporamento e da un livello GRU. Il livello Embedding è una tabella di ricerca che memorizza l'incorporamento del nostro input in un dizionario di parole di dimensioni fisse. Verrà passato a un livello GRU. Il livello GRU è un'unità ricorrente controllata che consiste in un tipo di RNN a più livelli che calcolerà l'input in sequenza. Questo livello calcolerà lo stato nascosto dal precedente e aggiornerà il ripristino, l'aggiornamento e le nuove porte.
Fonte: Seq2Seq
Il decodificatore decodificherà l'ingresso dall'uscita dell'encoder. Proverà a prevedere l'output successivo e tenterà di usarlo come input successivo, se possibile. Il decodificatore è costituito da uno strato di incorporamento, uno strato GRU e uno strato lineare. Il livello di incorporamento creerà una tabella di ricerca per l'output e passerà a un livello GRU per calcolare lo stato di output previsto. Dopodiché, uno strato lineare aiuterà a calcolare la funzione di attivazione per determinare il valore reale dell'output previsto.
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
Passaggio 3) Addestramento del modello
Il processo di formazione nei modelli Seq2seq inizia con la conversione di ciascuna coppia di frasi in tensori dal loro indice Lang. Il nostro modello da sequenza a sequenza utilizzerà SGD come ottimizzatore e la funzione NLLLoss per calcolare le perdite. Il processo di addestramento inizia con l'alimentazione della coppia di una frase al modello per prevedere l'output corretto. Ad ogni passaggio, l'output del modello verrà calcolato con le parole vere per trovare le perdite e aggiornare i parametri. Quindi, poiché utilizzerai 75000 iterazioni, il nostro modello da sequenza a sequenza genererà 75000 coppie casuali dal nostro set di dati.
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
Passaggio 4) Testare il modello
Il processo di valutazione di Seq2seq PyTorch consiste nel controllare l'output del modello. Ogni coppia di modelli da sequenza a sequenza verrà inserita nel modello e genererà le parole previste. Dopodiché, guarderai il valore più alto ad ogni output per trovare l'indice corretto. E alla fine, confronterai per vedere la nostra previsione del modello con la frase vera
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))
Ora, iniziamo il nostro allenamento con Seq to Seq, con il numero di iterazioni di 75000 e il numero di layer RNN di 1 con la dimensione nascosta di 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)
Come puoi vedere, la nostra frase prevista non è abbinata molto bene, quindi per ottenere una maggiore precisione, devi allenarti con molti più dati e provare ad aggiungere più iterazioni e numero di livelli usando la sequenza per l'apprendimento in sequenza.
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