Шта је НЛП?
НЛП или обрада природног језика једна је од популарних грана вештачке интелигенције која помаже рачунарима да разумеју, манипулишу или одговоре на човека на његовом природном језику. НЛП је покретач Гоогле Преводиоца који нам помаже да разумемо друге језике.
Шта је Сек2Сек?
Сек2Сек је метода машинског превођења и обраде језика заснована на енкодер-декодеру која пресликава улаз секвенце на излаз секвенце са ознаком и вредношћу пажње. Идеја је да се користе 2 РНН-а који ће радити заједно са посебним токеном и покушати да предвиде следећу секвенцу стања из претходне секвенце.
Корак 1) Учитавање наших података
За наш скуп података користићете скуп података из двојезичних парова реченица раздвојених табулаторима. Овде ћу користити скуп података са енглеског на индонежански. Можете да изаберете шта год желите, али не заборавите да промените име датотеке и директоријум у коду.
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")
Корак 2) Припрема података
Скуп података не можете користити директно. Реченице морате поделити на речи и претворити их у један врући вектор. Свака реч ће бити јединствено индексирана у класи Ланг да би се направио речник. Ланг класа ће сачувати сваку реченицу и поделити је реч по реч са аддСентенце. Затим креирајте речник индексирањем сваке непознате речи за Секуенце на моделе секвенци.
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
Разред језика је час који ће нам помоћи да направимо речник. За сваки језик, свака реченица ће се поделити на речи и затим додати у контејнер. Сваки контејнер ће речи сместити у одговарајући индекс, пребројати реч и додати индекс речи како бисмо је могли користити за проналажење индекса речи или проналажење речи из њеног индекса.
Будући да су наши подаци одвојени ТАБ-ом, морате да користите панде као наш учитач података. Пандас ће прочитати наше податке као датаФраме и подијелити их на нашу изворну и циљану реченицу. За сваку реченицу коју имате,
- нормализоваћете на мала слова,
- уклони све не-карактере
- претворити у АСЦИИ из Уницоде-а
- поделите реченице, тако да имате сваку реч у себи.
#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
Још једна корисна функција коју ћете користити је претварање парова у Тенсор. Ово је веома важно јер наша мрежа чита само податке типа тензора. Такође је важно јер је ово део на којем ће на сваком крају реченице бити токен који ће рећи мрежи да је унос завршен. За сваку реч у реченици добиће индекс одговарајуће речи у речнику и додаће жетон на крају реченице.
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)
Сек2Сек Модел
Извор: Сек2Сек
ПиТорцх Сек2сек модел је врста модела који користи ПиТорцх декодер кодера на врху модела. Давач ће кодирати реченицу по речима у индексирани речник или познате речи са индексом, а декодер ће предвидети излаз кодираног улаза декодирањем уноса у секвенци и покушаће да користи последњи улаз као следећи улаз ако то је могуће. Овом методом је такође могуће предвидети следећи улаз за стварање реченице. Свакој реченици ће бити додељен знак који означава крај низа. На крају предвиђања биће и жетон који означава крај резултата. Дакле, из кодера ће прећи у декодер да предвиди излаз.
Извор: Сек2Сек Модел
Давач ће кодирати нашу улазну реченицу реч по реч у низу и на крају ће бити жетон који означава крај реченице. Кодирање се састоји од слоја за уграђивање и слоја ГРУ. Слој за уграђивање је табела за претрагу која чува уграђивање нашег уноса у речник речи фиксне величине. Биће прослеђен ГРУ слоју. ГРУ слој је затворена понављајућа јединица која се састоји од више нивоа слоја РНН који ће израчунати секвенцирани улаз. Овај слој ће израчунати скривено стање из претходног и ажурирати ресетовање, ажурирање и нове капије.
Извор: Сек2Сек
Декодер ће декодирати улаз из излаза кодера. Покушаће да предвиди следећи излаз и покушаће да га користи као следећи улаз ако је то могуће. Декодер се састоји од слоја за уграђивање, слоја ГРУ и линеарног слоја. Слој за уграђивање ће направити табелу за претраживање излаза и пренијети у ГРУ слој за израчунавање предвиђеног излазног стања. Након тога, Линеарни слој ће помоћи да се израчуна функција активирања за одређивање праве вредности предвиђеног излаза.
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
Корак 3) Обука модела
Процес обуке у моделима Сек2сек започиње претварањем сваког пара реченица у Тензоре из њиховог Ланговог индекса. Наш модел секвенце до секвенце користиће СГД као оптимизатор и функцију НЛЛЛосс за израчунавање губитака. Процес обуке започиње убацивањем пара реченице у модел како би се предвидео тачан резултат. У сваком кораку ће се израчунати излаз из модела са истинитим речима да би се пронашли губици и ажурирали параметри. Дакле, зато што ћете користити 75000 итерација, наш модел секвенце до секвенце ће генерисати случајних 75000 парова из нашег скупа података.
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
Корак 4) Тестирајте модел
Процес евалуације Сек2сек ПиТорцх је да провери излаз модела. Сваки пар Секвенце у секвенцијалне моделе улази у модел и генерише предвиђене речи. Након тога ћете на сваком излазу гледати највишу вредност како бисте пронашли тачан индекс. И на крају ћете упоредити да бисте видели наше предвиђање модела са истинитом реченицом
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))
Сада, започнимо нашу обуку од Сек до Сек, са бројем итерација 75000 и бројем РНН слоја 1 са скривеном величином 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)
Као што видите, наша предвиђена реченица се не подудара баш најбоље, па да бисте постигли већу прецизност, потребно је да тренирате са много више података и покушате да додате више итерација и број слојева помоћу Секуенце за учење секвенци.
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