[PYTHON] Anfänger generieren automatisch Dokumente mit Pytorchs LSTM

Motivation

-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 ..."

Was ist diesmal zu tun?

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.

Datensätze für das Training und so weiter

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.

Morphologische Analyse

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.

Modelldefinition

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.

Training & Lader

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

Ergebnis

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. Figure_1.png

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 ...

Schlussfolgerungen und so weiter

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

Anfänger generieren automatisch Dokumente mit Pytorchs LSTM
Versuchen Sie, Python-Dokumente automatisch mit Sphinx zu generieren
Generieren Sie automatisch ein Modellbeziehungsdiagramm mit Django
Generieren Sie automatisch Objektspezifikationen mit Blue Prism
Lerne Zundokokiyoshi mit LSTM
Multivariates LSTM mit Keras
[Evangelion] Versuchen Sie, mit Deep Learning automatisch Asuka-ähnliche Linien zu erzeugen
Generieren Sie mit Python automatisch eine Häufigkeitsverteilungstabelle auf einmal
Messen und vergleichen Sie Temperaturen mit Raspberry Pi und generieren Sie automatisch Diagramme