-Été des étudiants de 3e année de premier cycle-
Je suis étudiant en 3e année, donc je recherche un stagiaire ...
ES "** Quel est le livrable? **"
Je "Eh ..."
ES "** Qiita? Github? **"
Je "Non ..."
"** Perdu **"
Master I "Je viens de terminer mes recherches de fin d'études et je veux faire un livrable ..."
J'aimerais mettre en œuvre quelque chose qui m'intéresse et dont j'ai quelques connaissances.
Ma spécialité est le traitement du langage naturel, et dans mon mémoire de premier cycle, j'avais l'habitude de générer des documents grâce à l'apprentissage en profondeur. Après avoir pratiqué avec Pytorch, nous générerons automatiquement des documents.
Le générateur de document doit avoir une couche d'intégration, une couche LSTM et une couche linéaire. Créez des hyper-paramètres tels que le nombre de couches dans LSTM qui peuvent être spécifiées à partir de la ligne de commande.
L'ensemble de données utilisé pour la formation sera "SNOW T15: Easy Japanese Corpus" du Laboratoire de traitement du langage naturel de l'Université de technologie de Nagaoka.
Laboratoire de traitement du langage naturel de l'Université de technologie de Nagaoka http://www.jnlp.org/SNOW/T15
Traduction bilingue japonais-anglais de 50 000 phrases + corpus parallèle japonais facile est super pratique. Comme il est fourni au format xlsx, convertissez-le à l'avance au format csv. De plus, puisqu'il s'agit d'une génération de phrases automatique, nous n'utilisons pas de japonais et d'anglais faciles.
Le traitement du langage naturel est une analyse morphologique. L'analyse morphologique divise une phrase brute donnée en éléments morphologiques. OOV (Out Of Vocab) est souvent un problème dans l'analyse morphologique. La dimension de sortie de l'apprentissage profond dépend de la taille du corpus qui peut être produit, et plus elle est grande, plus elle met la pression sur la mémoire. Par conséquent, de nombreuses personnes enregistrent les mots à basse fréquence dans le corpus comme UNK (inconnu) et conçoivent diverses autres choses.
Ici, le morceau de phrase est utilisé.
https://github.com/google/sentencepiece
Phrasepiece est un outil super pratique qui effectue une analyse morphologique afin qu'il tienne dans le nombre spécifié de mots sans OOV en apprenant sans professeur. Veuillez vous référer à l'URL citée pour les spécifications détaillées. Il est utilisé pour analyser morphologiquement un ensemble de données dans la plage de 8000 mots sans OOV.
Eh bien, c'est un LSTM normal, donc je n'ai rien à dire. Je serais ravi si vous pouviez signaler tout problème.
LSTM.py
import torch
import torch.nn as nn
import torch.nn.functional as F
class LSTM(nn.Module):
def __init__(self, source_size, hidden_size, batch_size, embedding_size, num_layers):
super(LSTM, self).__init__()
self.hidden_size = hidden_size
self.source_size = source_size
self.batch_size = batch_size
self.num_layers = num_layers
self.embed_source = nn.Embedding(source_size, embedding_size, padding_idx=0)
self.embed_source.weight.data.normal_(0, 1 / embedding_size**0.5)
self.lstm_source = nn.LSTM(self.hidden_size, self.hidden_size, num_layers=self.num_layers,
bidirectional=True, batch_first=True)
self.linear = nn.Linear(self.hidden_size*2, self.source_size)
def forward(self, sentence_words, hx, cx):
source_k = self.embed_source(sentence_words)
self.lstm_source.flatten_parameters()
encoder_output, (hx, cx) = self.lstm_source(source_k, (hx, cx))
prob = F.log_softmax(self.linear(encoder_output), dim=1)
_, output = torch.max(prob, dim = -1)
return prob, output, (hx, cx)
def init_hidden(self, bc):
hx = torch.zeros(self.num_layers*2, bc, self.hidden_size)
cx = torch.zeros(self.num_layers*2, bc, self.hidden_size)
return hx, cx
C'est normal.
Ensuite, créez le code d'entraînement et le chargeur de l'ensemble de données. À propos, index.model est un modèle d'analyse morphologique créé par morceau de phrase. J'ai envie de tester soudainement après l'entraînement sans vérification. La formation apprend à saisir une certaine phrase japonaise et à produire exactement la même phrase. Au moment du test, entrez uniquement le premier mot de la phrase de test et affichez le reste dans la méthode cupidité dans l'ordre chronologique. Peut-être que cela devrait générer automatiquement le document ...
train.py
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import argparse
from loader import Dataset
import torch.optim as optim
import sentencepiece as spm
from utils import seq_to_string, to_np, trim_seqs
import matplotlib.pyplot as plt
from torchviz import make_dot
from model.LSTM import LSTM
def make_model(source_size, hidden_size, batch_size, embedding_size=256, num_layers=1):
model = LSTM(source_size, hidden_size, batch_size, embedding_size, num_layers)
criterion = nn.NLLLoss(reduction="sum")
model_opt = optim.Adam(model.parameters(), lr=0.0001)
return model, criterion, model_opt
def data_load(maxlen, source_size, batch_size):
data_set = Dataset(maxlen=maxlen)
data_num = len(data_set)
train_ratio = int(data_num*0.8)
test_ratio = int(data_num*0.2)
res = int(data_num - (train_ratio + test_ratio))
train_ratio += res
ratio=[train_ratio, test_ratio]
train_dataset, test_dataset = torch.utils.data.random_split(data_set, ratio)
dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=True)
del(train_dataset)
del(data_set)
return dataloader, test_dataloader
def run_epoch(data_iter, model, criterion, model_opt, epoch):
model, criterion = model.cuda(), criterion.cuda()
model.train()
total_loss = 0
for i, data in enumerate(data_iter):
model_opt.zero_grad()
src = data[:,:-1]
trg = data[:,1:]
src, trg = src.cuda(), trg.cuda()
hx, cx = model.init_hidden(src.size(0))
hx, cx = hx.cuda(), cx.cuda()
output_log_probs, output_seqs, _ = model(src, hx, cx)
flattened_log_probs = output_log_probs.view(src.size(0) * src.size(1), -1)
loss = criterion(flattened_log_probs, trg.contiguous().view(-1))
loss /= (src.size(0) * src.size(1))
loss.backward()
model_opt.step()
total_loss += loss
if i % 50 == 1:
print("Step: %d Loss: %f " %
(i, loss))
mean_loss = total_loss / len(data_iter)
torch.save({
'model': model.state_dict()
}, "./model_log/model.pt")
dot = make_dot(output_log_probs ,params=dict(model.named_parameters()))
dot.format = 'png'
dot.render('image')
return model, mean_loss
def depict_graph(mean_losses, epochs):
epoch = [i+1 for i in range(epochs)]
plt.xlim(0, epochs)
plt.ylim(1, mean_losses[0])
plt.plot(epoch, mean_losses)
plt.title("loss")
plt.xlabel("epoch")
plt.ylabel("loss")
plt.show()
def test(model, data_loader):
model.eval()
all_output_seqs = []
all_target_seqs = []
with torch.no_grad():
for data in data_loader:
src = Variable(data[:,:-1])
src = src.cuda()
del(data)
input_data = src[:,:2]
hx, cx = model.init_hidden(input_data.size(0))
for i in range(18):
hx, cx = hx.cuda(), cx.cuda()
output_log_probs, output_seqs, hidden = model(input_data, hx, cx)
hx, cx = hidden[0], hidden[1]
input_data = torch.cat((input_data, output_seqs[:,-1:]), 1)
all_output_seqs.extend(trim_seqs(input_data))
out_set = (all_target_seqs, all_output_seqs)
return out_set
if __name__ == "__main__":
sp = spm.SentencePieceProcessor()
sp.load("./index.model")
source_size = sp.GetPieceSize()
parser = argparse.ArgumentParser(description='Parse training parameters')
parser.add_argument('--do_train', type=str, default='False')
parser.add_argument('--batch_size', type=int, default=256)
parser.add_argument('--maxlen', type=int, default=20)
parser.add_argument('--epochs', type=int, default=50)
parser.add_argument('--hidden_size', type=int, default=128)
parser.add_argument('--embedding_size', type=int, default=128)
parser.add_argument('--num_layers', type=int, default=1)
args = parser.parse_args()
model, criterion, model_opt = make_model(source_size, args.hidden_size, args.batch_size, args.embedding_size, args.num_layers)
data_iter, test_data_iter = data_load(args.maxlen, source_size, args.batch_size)
mean_losses = []
if args.do_train == "True":
for epoch in range(args.epochs):
print(epoch+1)
model, mean_loss = run_epoch(data_iter, model, criterion, model_opt, epoch)
mean_losses.append(mean_loss.item())
depict_graph(mean_losses, args.epochs)
else:
model.load_state_dict(torch.load("./model_log/model.pt")["model"])
out_set = test(model, data_iter)
true_txt = out_set[0]
out_txt = out_set[1]
with open("true.txt", "w", encoding="utf-8") as f:
for i in true_txt:
for j in i:
f.write(sp.IdToPiece(int(j)))
f.write("\n")
with open("out.txt", "w", encoding="utf-8") as f:
for i in out_txt:
for j in i:
f.write(sp.IdToPiece(int(j)))
f.write("\n")
loader.py
import torch
import numpy as np
import csv
import sentencepiece as spm
class Dataset(torch.utils.data.Dataset):
def __init__(self, maxlen):
self.sp = spm.SentencePieceProcessor()
self.sp.load("./index.model")
self.maxlen = maxlen
with open('./data/parallel_data.csv', mode='r', newline='', encoding='utf-8') as f:
csv_file = csv.reader(f)
read_data = [row for row in csv_file]
self.data_num = len(read_data) - 1
jp_data = []
for i in range(1, self.data_num):
jp_data.append(read_data[i][1:2]) #Phrases japonaises difficiles
self.en_data_idx = np.zeros((len(jp_data), maxlen+1))
for i,sentence in enumerate(jp_data):
self.en_data_idx[i][0] = self.sp.PieceToId("<s>")
for j,idx in enumerate(self.sp.EncodeAsIds(sentence[0])[:]):
self.en_data_idx[i][j+1] = idx
if j+1 == maxlen-1: #Symbole de fin à la fin
self.en_data_idx[i][j+1] = self.sp.PieceToId("</s>")
break
if j+2 <= maxlen-1:
self.en_data_idx[i][j+2] = self.sp.PieceToId("</s>")
if j+3 < maxlen-1:
self.en_data_idx[i][j+3:] = self.sp.PieceToId("<unk>") #Parce que c'est gênant, le unk généré lors de l'apprentissage du morceau de phrase est utilisé comme un pad.
else:
self.en_data_idx[i][j+1] = self.sp.PieceToId("</s>")
if j+2 < maxlen-1:
self.en_data_idx[i][j+2:] = self.sp.PieceToId("<unk>")
def __len__(self):
return self.data_num
def __getitem__(self, idx):
en_data = torch.tensor(self.en_data_idx[idx-1][:], dtype=torch.long)
return en_data
Pour le moment, essayez de tourner environ 100 époques. Je ne savais pas si les couches ou les hyper paramètres devaient être plus petits pour éviter le surentraînement, alors Il est réglé de manière à ne pas être trop grand.
C'est la perte pendant l'apprentissage.
Il tombe régulièrement, mais c'est subtil du milieu. Peut-être que je devrais faire quelque chose au sujet des hyper paramètres.
Ainsi, ce qui suit est un exemple du texte de sortie réel.
(^ Ω ^) ... Ce n'est pas bien ...
Tout d'abord, la définition du modèle peut être mauvaise. Seq2seq est-il plus approprié? J'essaierai si j'ai une chance. Quoi qu'il en soit, je ne me suis jamais entraîné avec le modèle LSTM nu de manière cohérente (bien qu'il y ait Embedding), C'était assez amusant de réfléchir à la tâche à accomplir. Dans ma thèse de maîtrise, j'utilise essentiellement des Transformers, donc à l'avenir, j'aimerais parfois présenter des implémentations et des articles.
Recommended Posts