-Sommer der Studenten im 3. Jahr-
Ich bin ein Student im 3. Jahr, also suche ich einen Praktikanten ...
ES "** Was ist das Ergebnis? **"
Ich "Eh ..."
ES "** Qiita? Github? **"
Ich "Nein ..."
"** Hat verloren **"
Meister I "Ich habe meine Abschlussforschung abgeschlossen und möchte nur ein Ergebnis liefern ..."
Ich möchte etwas implementieren, das mich interessiert und über das ich etwas weiß.
Mein Hauptfach ist die Verarbeitung natürlicher Sprache, und in meiner Diplomarbeit habe ich Dokumente durch tiefes Lernen generiert. Nach dem Üben mit Pytorch generieren wir automatisch Dokumente.
Der Dokumentgenerator muss eine Einbettungsschicht, eine LSTM-Schicht und eine lineare Schicht aufweisen. Erstellen Sie Hyperparameter wie die Anzahl der Ebenen in LSTM, die über die Befehlszeile angegeben werden können.
Der für das Training verwendete Datensatz lautet "SNOW T15: Easy Japanese Corpus" vom Natural Language Processing Laboratory der Nagaoka University of Technology.
Nagaoka University of Technology Labor für die Verarbeitung natürlicher Sprache http://www.jnlp.org/SNOW/T15
Die zweisprachige japanisch-englische Übersetzung von 50.000 Sätzen + einfachem japanischem Parallelkorpus ist sehr praktisch. Da es im xlsx-Format bereitgestellt wird, konvertieren Sie es im Voraus in das csv-Format. Da es sich um eine automatische Satzgenerierung handelt, verwenden wir kein einfaches Japanisch und Englisch.
Die Verarbeitung natürlicher Sprache ist eine morphologische Analyse. Die morphologische Analyse unterteilt einen bestimmten Rohsatz in morphologische Elemente. OOV (Out Of Vocab) ist häufig ein Problem in der morphologischen Analyse. Die Ausgabedimension des tiefen Lernens hängt von der Größe des Korpus ab, der ausgegeben werden kann. Je größer er ist, desto mehr Druck wird auf das Gedächtnis ausgeübt. Daher registrieren viele Menschen niederfrequente Wörter im Korpus als UNK (unbekannt) und entwickeln verschiedene andere Dinge.
Hier wird das Satzstück verwendet.
https://github.com/google/sentencepiece
Satzstück ist ein sehr praktisches Werkzeug, das morphologische Analysen durchführt, so dass es in die angegebene Anzahl von Wörtern ohne OOV passt, indem es ohne Lehrer lernt. Detaillierte Spezifikationen finden Sie in der angegebenen URL. Es wird verwendet, um einen Datensatz im Bereich von 8000 Wörtern ohne OOV morphologisch zu analysieren.
Nun, es ist ein normales LSTM, also habe ich nichts zu besprechen. Ich würde mich freuen, wenn Sie auf Probleme hinweisen könnten.
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
Es ist normal.
Erstellen Sie als Nächstes den Trainingscode und den Datensatzlader. Index.model ist übrigens ein Modell für die morphologische Analyse, das durch Satzstücke erstellt wird. Ich habe das Gefühl, plötzlich nach dem Training ohne Überprüfung zu testen. Das Training lernt, einen bestimmten japanischen Satz einzugeben und genau denselben Satz auszugeben. Zum Zeitpunkt des Tests wird nur das erste Wort des Testsatzes eingegeben, und der Rest wird nach der gierigen Methode in chronologischer Reihenfolge ausgegeben. Vielleicht sollte dies automatisch das Dokument generieren ...
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]) #Schwierige japanische Sätze
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: #Endsymbol am Ende
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>") #Da dies problematisch ist, wird das beim Lernen des Satzstücks erzeugte Unk als Block verwendet.
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
Versuchen Sie vorerst, etwa 100 Epochen zu drehen. Ich wusste nicht, ob Ebenen oder Hyperparameter kleiner sein sollten, um ein Übertraining zu verhindern Es ist so eingestellt, dass es nicht zu groß ist.
Dies ist der Verlust beim Lernen.
Es fällt stetig, aber es ist subtil von der Mitte. Vielleicht sollte ich etwas gegen die Hyperparameter unternehmen.
Das Folgende ist also ein Beispiel für den tatsächlichen Ausgabetext.
(^ Ω ^) ... Es ist nicht gut ...
Erstens kann die Definition des Modells schlecht sein. Ist Seq2seq besser geeignet? Ich werde es versuchen, wenn ich eine Chance habe. Wie auch immer, ich habe noch nie konsequent mit dem LSTM-Modell nackt trainiert (obwohl es Embedding gibt) Es hat ziemlich viel Spaß gemacht, darüber nachzudenken, welche Aufgabe zu erledigen ist. In meiner Masterarbeit verwende ich grundsätzlich Transformatoren, daher möchte ich in Zukunft gelegentlich Implementierungen und Papiere vorstellen.
Recommended Posts