Qu'est-ce que la PNL?
La PNL ou traitement du langage naturel est l'une des branches populaires de l'intelligence artificielle qui aide les ordinateurs à comprendre, manipuler ou répondre à un humain dans son langage naturel. La PNL est le moteur de Google Translate qui nous aide à comprendre d'autres langues.
Qu'est-ce que Seq2Seq?
Seq2Seq est une méthode de traduction automatique et de traitement du langage basée sur un codeur-décodeur qui mappe une entrée de séquence à une sortie de séquence avec une étiquette et une valeur d'attention. L'idée est d'utiliser 2 RNN qui fonctionneront avec un jeton spécial et d'essayer de prédire la séquence d'états suivante à partir de la séquence précédente.
Étape 1) Chargement de nos données
Pour notre ensemble de données, vous utiliserez un ensemble de données de paires de phrases bilingues délimitées par des tabulations. Ici, j'utiliserai l'ensemble de données anglais-indonésien. Vous pouvez choisir ce que vous voulez mais n'oubliez pas de changer le nom du fichier et le répertoire dans le code.
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")
Étape 2) Préparation des données
Vous ne pouvez pas utiliser le jeu de données directement. Vous devez diviser les phrases en mots et les convertir en One-Hot Vector. Chaque mot sera indexé de manière unique dans la classe Lang pour créer un dictionnaire. La classe Lang stockera chaque phrase et la divisera mot par mot avec le addSentence. Créez ensuite un dictionnaire en indexant chaque mot inconnu de la séquence aux modèles de séquence.
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 classe Lang est une classe qui nous aidera à créer un dictionnaire. Pour chaque langue, chaque phrase sera divisée en mots puis ajoutée au conteneur. Chaque conteneur stockera les mots dans l'index approprié, comptera le mot et ajoutera l'index du mot afin que nous puissions l'utiliser pour trouver l'index d'un mot ou trouver un mot à partir de son index.
Parce que nos données sont séparées par TAB, vous devez utiliser des pandas comme chargeur de données. Les pandas liront nos données en tant que dataFrame et les diviseront en phrases source et cible. Pour chaque phrase que vous avez,
- vous le normaliserez en minuscules,
- supprimer tout non-caractère
- convertir en ASCII à partir d'Unicode
- divisez les phrases pour avoir chaque mot dedans.
#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
Une autre fonction utile que vous utiliserez est la conversion des paires en Tensor. Ceci est très important car notre réseau ne lit que les données de type tensoriel. C'est également important parce que c'est la partie qui à chaque fin de la phrase, il y aura un jeton pour dire au réseau que l'entrée est terminée. Pour chaque mot de la phrase, il obtiendra l'index du mot approprié dans le dictionnaire et ajoutera un jeton à la fin de la phrase.
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)
Modèle Seq2Seq
Source: Seq2Seq
Le modèle PyTorch Seq2seq est une sorte de modèle qui utilise le décodeur d'encodeur PyTorch au-dessus du modèle. Le codeur encodera le mot de la phrase par mots dans un vocabulaire indexé ou des mots connus avec index, et le décodeur prédira la sortie de l'entrée codée en décodant l'entrée en séquence et essaiera d'utiliser la dernière entrée comme entrée suivante si c'est possible. Avec cette méthode, il est également possible de prédire la prochaine entrée pour créer une phrase. Chaque phrase se verra attribuer un jeton pour marquer la fin de la séquence. À la fin de la prédiction, il y aura également un jeton pour marquer la fin de la sortie. Ainsi, à partir du codeur, il passera un état au décodeur pour prédire la sortie.
Source: modèle Seq2Seq
L'encodeur encodera notre phrase d'entrée mot par mot dans l'ordre et à la fin il y aura un jeton pour marquer la fin d'une phrase. L'encodeur se compose d'une couche Embedding et d'une couche GRU. La couche Embedding est une table de recherche qui stocke l'incorporation de notre entrée dans un dictionnaire de mots de taille fixe. Il sera transmis à une couche GRU. La couche GRU est une unité récurrente fermée qui se compose d'un type de couche multiple de RNN qui calculera l'entrée séquencée. Cette couche calculera l'état caché à partir du précédent et mettra à jour la réinitialisation, la mise à jour et les nouvelles portes.
Source: Seq2Seq
Le décodeur décodera l'entrée de la sortie du codeur. Il essaiera de prédire la sortie suivante et de l'utiliser comme entrée suivante si cela est possible. Le décodeur se compose d'une couche d'incorporation, d'une couche GRU et d'une couche linéaire. La couche d'intégration créera une table de recherche pour la sortie et passera dans une couche GRU pour calculer l'état de sortie prévu. Après cela, une couche linéaire aidera à calculer la fonction d'activation pour déterminer la valeur réelle de la sortie prévue.
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
Étape 3) Formation du modèle
Le processus de formation dans les modèles Seq2seq commence par la conversion de chaque paire de phrases en Tensors à partir de leur index Lang. Notre modèle de séquence à séquence utilisera SGD comme optimiseur et la fonction NLLLoss pour calculer les pertes. Le processus d'apprentissage commence par alimenter la paire de phrases dans le modèle pour prédire la sortie correcte. A chaque étape, la sortie du modèle sera calculée avec les vrais mots pour trouver les pertes et mettre à jour les paramètres. Donc, comme vous utiliserez 75000 itérations, notre modèle de séquence à séquence générera 75000 paires aléatoires à partir de notre ensemble de données.
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
Étape 4) Testez le modèle
Le processus d'évaluation de Seq2seq PyTorch consiste à vérifier la sortie du modèle. Chaque paire de modèles de séquence à séquence sera introduite dans le modèle et générera les mots prédits. Après cela, vous regarderez la valeur la plus élevée à chaque sortie pour trouver l'index correct. Et à la fin, vous comparerez pour voir notre modèle de prédiction avec la vraie phrase
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))
Maintenant, commençons notre entraînement avec Seq to Seq, avec le nombre d'itérations de 75000 et le nombre de couche RNN de 1 avec la taille cachée de 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)
Comme vous pouvez le voir, notre phrase prédite ne correspond pas très bien, donc pour obtenir une plus grande précision, vous devez vous entraîner avec beaucoup plus de données et essayer d'ajouter plus d'itérations et de nombre de couches en utilisant la séquence pour l'apprentissage de séquences.
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