[PyTorch] Génération de phrases japonaises à l'aide de Transformer

introduction

Dans cet article, nous visons à créer un modèle qui génère automatiquement un roman en entrant un titre arbitraire à l'aide d'une paire de titre et de texte de roman.

Préparation préalable

Google Colaboratory est utilisé comme infrastructure d'analyse. Google Colaboratory est un environnement de bloc-notes cloud fourni par Google, et toute personne disposant d'un compte Google peut l'utiliser gratuitement. Non seulement la bibliothèque requise pour l'analyse des données est préparée à l'avance, mais elle peut également être utilisée avec le GPU, c'est pourquoi elle est fortement recommandée lorsque vous voulez vous sentir libre d'essayer quelque chose sur votre ordinateur portable. Pour plus de détails sur la configuration de Google Colaboratory, consultez cet article.

Après l'installation, ouvrez le notebook pour cette analyse et exécutez la commande suivante pour installer la bibliothèque qui n'est pas installée à l'avance.

!pip install PyDrive
!pip install janome
!pip install mojimoji

Obtenez des données

Les nouvelles données sont obtenues auprès de Github d'Aozora Bunko. Tout d'abord, copiez le référentiel cible sur votre propre Google Drive. Vous pouvez également exécuter les commandes suivantes à partir de Google Colaboratory.

!git clone --branch master --depth 1 https://github.com/aozorabunko/aozorabunko.git "drive/My Drive/Annuaire arbitraire"

Ensuite, extrayez les données utilisées pour la construction du modèle à partir des fichiers copiés et formatez-les. Cette fois, j'utiliserai un roman dont le texte comporte moins de 3 000 caractères. De plus, «id sur le lecteur» dans le code fait référence à la chaîne de caractères sous les dossiers / inclus dans l'URL du répertoire cible.

#----------------------
#Obtenir une liste de fichiers cibles
#----------------------
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials
import pandas as pd

#Autoriser l'accès à Google Drive
auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

#Obtenez le titre de toutes les œuvres et identifiant sur Drive
def get_list_file_recursively(parent_id, l=None):
    if l is None:
        l = []

    file_list = drive.ListFile({'q': '"{}" in parents and trashed = false'.format(parent_id)}).GetList()
    l += [f for f in file_list if f['mimeType'] != 'application/vnd.google-apps.folder']

    for f in file_list:
        if f['mimeType'] == 'application/vnd.google-apps.folder':
            get_list_file_recursively(f['id'], l)

    return l

listed = []
for f in get_list_file_recursively('L'identifiant sur le lecteur du répertoire de niveau supérieur du référentiel copié'):
  print(f['title'])
  if 'html' in f['title'] and 'card' not in f['title']:
    list = [f['title'], f['id']]
    listed.append(list)
listed = pd.DataFrame(listed)

#----------------------
#Acquisition de titre / texte
#----------------------
from bs4 import BeautifulSoup

#Lire le fichier html de la liste
Stories = []
for i in range(0, len(listed)):
    if i % 100 == 0:
        print('{} / {}'.format(i, len(listed)))

    #Identifier les fichiers à partir de l'identifiant de la liste
    file_data = drive.CreateFile({'id': listed.iloc[i, 2]})
    file_data.GetContentFile(listed.iloc[i, 1])
    with open(listed.iloc[i, 1], 'rb') as html:
        soup = BeautifulSoup(html, 'lxml')

    #Obtenir le titre / texte
    title = soup.find("h1", class_='title')
    main_text = soup.find("div", class_='main_text')

    #Si le titre ou le texte est manquant, ignorez-le
    if title == None or main_text == None:
        continue

    #Retirer le rubis
    for yomigana in main_text.find_all(["rp", "h4", "rt"]):
        yomigana.decompose()

    #Formater et convertir en chaîne
    title = [line.strip() for line in 
    title.text.strip().splitlines()]
    main_text = [line.strip() for line in 
    main_text.text.strip().splitlines()]
    title = ''.join(title)
    text=''.join(main_text)

    #Le texte est 3,Limitez-vous aux œuvres dans les 000 caractères
    if len(text) <= 3000:
        Stories.append([title, text])

#Enregistrer sous csv
Stories = pd.DataFrame(Stories)
Stories.to_csv('drive/My Drive/Stories.csv', index=False, header=False)

Enfin, enregistrez une répartition aléatoire de 80% des données pour la formation et 20% pour les tests.

#----------------------
#Répartition des données
#----------------------
from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=True, random_state=12345)
tr_idx, te_idx = list(kf.split(df))[0]

train = df.iloc[tr_idx, :]
test = df.iloc[te_idx, :]
train.to_csv('drive/My Drive/train.csv', index=False, header=False)
test.to_csv('drive/My Drive/test.csv', index=False, header=False)

Préparation des données d'entraînement

Utilisez le texte de la torche pour lire les données. Pour une explication de base de torchtext, voir cet article.

Définition du prétraitement

Tout d'abord, définissez la fonction de prétraitement à appliquer lors de la lecture de données avec torchtext. Utilisez janome pour l'analyse morphologique.

#----------------------
#Définition du prétraitement
#----------------------
from torchtext import data
from janome.tokenizer import Tokenizer
import re
import mojimoji

#Définition du traitement des chaînes
def preprocessing(text):
    #Supprimer les sauts de ligne, les espaces demi-largeur et les espaces pleine largeur
    text = re.sub('\r', '', text)
    text = re.sub('\n', '', text)
    text = re.sub(' ', '', text)
    text = re.sub(' ', '', text)
    #"0" uniforme pour les caractères numériques
    text = re.sub(r'[0-9 0-9]', '0', text) 
    #Angle complet
    text = mojimoji.han_to_zen(text)
    return text

#Définition de tokenizer
j_t = Tokenizer()
def tokenizer(text):
    return [tok for tok in j_t.tokenize(text, wakati=True)]

#Traitement des chaînes+ Tokenizer
def tokenizer_with_preprocessing(text):
    text = preprocessing(text)
    text = tokenizer(text)
    return text

paramètres de texte de la torche

Ensuite, définissez la méthode de lecture à l'aide de torchtext.

#----------------------
#Définition du champ
#----------------------
TEXT = data.Field(
    sequential=True, 
    init_token='<sos>', 
    eos_token='<eos>', 
    tokenize=tokenizer_with_preprocessing, 
    lower=True, 
    use_vocab=True, 
    batch_first=True
)

Lire les données

Lisez le fichier csv divisé pour l'apprentissage et le test, et créez un dictionnaire de vocabulaire.

#----------------------
#Lire les données
#----------------------
train_ds, test_ds = data.TabularDataset.splits(
    path='drive/My Drive',
    train='train.csv',
    test='test.csv',
    format='csv',
    skip_header=False,
    fields=[('title', TEXT), ('text', TEXT)]
)

#Vérification
train_ds[0].__dict__.keys()
test_ds[0].__dict__.keys()
for i in range(0, 10):
    print(vars(train_ds[i]))
    print(vars(test_ds[i]))

#Créer un dictionnaire
TEXT.build_vocab(train_ds, test_ds, min_freq=2)

#Nombre de mots
print(TEXT.vocab.freqs)
print('Nombre de vocabulaire:{}'.format(len(TEXT.vocab)))

#Créer un itérateur
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 
# -->Sélectionnez à l'avance le GPU dans "Modifier le type d'exécution".
train_iter = data.Iterator(train_ds, batch_size=16, shuffle=True, device=device)
test_iter = data.Iterator(test_ds, batch_size=16, shuffle=False, device=device)

#Vérification
batch = next(iter(train_iter))
print(batch.title)
print(batch.text)

batch = next(iter(test_iter))
print(batch.title)
print(batch.text)

Construction de modèles

Définition du réseau

Implémentez Transformer. Cet article n'explique pas comment fonctionne Transformer, mais en japonais c'est cet article et en anglais c'est [cet article](http: // jalammar). .github.io / illustré-transformer /) est très facile à comprendre. Aussi, pour l'implémentation, veuillez vous référer à ici. C'était. La signification de chaque processus est également expliquée en détail, veuillez donc la lire avant d'essayer.

Tout d'abord, définissez un encodeur qui vectorise le titre du roman.

import torch
from torch import nn

class Encoder(nn.Module):
    def __init__(self, 
                 input_dim, 
                 hid_dim, 
                 n_layers, 
                 n_heads, 
                 pf_dim,
                 dropout, 
                 device,
                 max_length=100):
        super().__init__()

        self.device = device
        
        self.tok_embedding = nn.Embedding(input_dim, hid_dim)
        self.pos_embedding = nn.Embedding(max_length, hid_dim)
        
        self.layers = nn.ModuleList([EncoderLayer(hid_dim, 
                                                  n_heads, 
                                                  pf_dim,
                                                  dropout, 
                                                  device) 
                                     for _ in range(n_layers)])
        
        self.dropout = nn.Dropout(dropout)
        
        self.scale = torch.sqrt(torch.FloatTensor([hid_dim])).to(device)
        
    def forward(self, src, src_mask):
        
        #src = [batch size, src len]
        #src_mask = [batch size, src len]
        
        batch_size = src.shape[0]
        src_len = src.shape[1]
        
        pos = torch.arange(0, src_len).unsqueeze(0).repeat(batch_size, 1).to(self.device)
        
        #pos = [batch size, src len]
        
        src = self.dropout((self.tok_embedding(src) * self.scale) + self.pos_embedding(pos))
        
        #src = [batch size, src len, hid dim]
        
        for layer in self.layers:
            src = layer(src, src_mask)
            
        #src = [batch size, src len, hid dim]
            
        return src


class EncoderLayer(nn.Module):
    def __init__(self, 
                 hid_dim, 
                 n_heads, 
                 pf_dim,  
                 dropout, 
                 device):
        super().__init__()
        
        self.layer_norm = nn.LayerNorm(hid_dim)
        self.self_attention = MultiHeadAttentionLayer(hid_dim, n_heads, dropout, device)
        self.positionwise_feedforward = PositionwiseFeedforwardLayer(hid_dim, pf_dim, dropout)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, src, src_mask):
        
        #src = [batch size, src len, hid dim]
        #src_mask = [batch size, src len]
                
        #self attention
        _src, _ = self.self_attention(src, src, src, src_mask)
        
        #dropout, residual connection and layer norm
        src = self.layer_norm(src + self.dropout(_src))
        
        #src = [batch size, src len, hid dim]
        
        #positionwise feedforward
        _src = self.positionwise_feedforward(src)
        
        #dropout, residual and layer norm
        src = self.layer_norm(src + self.dropout(_src))
        
        #src = [batch size, src len, hid dim]
        
        return src


class MultiHeadAttentionLayer(nn.Module):
    def __init__(self, hid_dim, n_heads, dropout, device):
        super().__init__()
        
        assert hid_dim % n_heads == 0
        
        self.hid_dim = hid_dim
        self.n_heads = n_heads
        self.head_dim = hid_dim // n_heads
        
        self.fc_q = nn.Linear(hid_dim, hid_dim)
        self.fc_k = nn.Linear(hid_dim, hid_dim)
        self.fc_v = nn.Linear(hid_dim, hid_dim)
        
        self.fc_o = nn.Linear(hid_dim, hid_dim)
        
        self.dropout = nn.Dropout(dropout)
        
        self.scale = torch.sqrt(torch.FloatTensor([self.head_dim])).to(device)
        
    def forward(self, query, key, value, mask = None):
        
        batch_size = query.shape[0]
        
        #query = [batch size, query len, hid dim]
        #key = [batch size, key len, hid dim]
        #value = [batch size, value len, hid dim]
                
        Q = self.fc_q(query)
        K = self.fc_k(key)
        V = self.fc_v(value)
        
        #Q = [batch size, query len, hid dim]
        #K = [batch size, key len, hid dim]
        #V = [batch size, value len, hid dim]
                
        Q = Q.view(batch_size, -1, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
        K = K.view(batch_size, -1, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
        V = V.view(batch_size, -1, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
        
        #Q = [batch size, n heads, query len, head dim]
        #K = [batch size, n heads, key len, head dim]
        #V = [batch size, n heads, value len, head dim]
                
        energy = torch.matmul(Q, K.permute(0, 1, 3, 2)) / self.scale
        
        #energy = [batch size, n heads, seq len, seq len]
        
        if mask is not None:
            energy = energy.masked_fill(mask == 0, -1e10)
        
        attention = torch.softmax(energy, dim = -1)
                
        #attention = [batch size, n heads, query len, key len]
        
        x = torch.matmul(self.dropout(attention), V)
        
        #x = [batch size, n heads, seq len, head dim]
        
        x = x.permute(0, 2, 1, 3).contiguous()
        
        #x = [batch size, seq len, n heads, head dim]
        
        x = x.view(batch_size, -1, self.hid_dim)
        
        #x = [batch size, seq len, hid dim]
        
        x = self.fc_o(x)
        
        #x = [batch size, seq len, hid dim]
        
        return x, attention


class PositionwiseFeedforwardLayer(nn.Module):
    def __init__(self, hid_dim, pf_dim, dropout):
        super().__init__()
        
        self.fc_1 = nn.Linear(hid_dim, pf_dim)
        self.fc_2 = nn.Linear(pf_dim, hid_dim)
        
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, x):
        
        #x = [batch size, seq len, hid dim]
        
        x = self.dropout(torch.relu(self.fc_1(x)))
        
        #x = [batch size, seq len, pf dim]
        
        x = self.fc_2(x)
        
        #x = [batch size, seq len, hid dim]
        
        return x

Ensuite, définissez un décodeur qui reçoit le vecteur titre et génère le corps du roman.

class Decoder(nn.Module):
    def __init__(self, 
                 output_dim, 
                 hid_dim, 
                 n_layers, 
                 n_heads, 
                 pf_dim, 
                 dropout, 
                 device,
                 max_length=1000):
        super().__init__()
        
        self.device = device
        
        self.tok_embedding = nn.Embedding(output_dim, hid_dim)
        self.pos_embedding = nn.Embedding(max_length, hid_dim)
        
        self.layers = nn.ModuleList([DecoderLayer(hid_dim, 
                                                  n_heads, 
                                                  pf_dim, 
                                                  dropout, 
                                                  device)
                                     for _ in range(n_layers)])
        
        self.fc_out = nn.Linear(hid_dim, output_dim)
        
        self.dropout = nn.Dropout(dropout)
        
        self.scale = torch.sqrt(torch.FloatTensor([hid_dim])).to(device)
        
    def forward(self, trg, enc_src, trg_mask, src_mask):
        
        #trg = [batch size, trg len]
        #enc_src = [batch size, src len, hid dim]
        #trg_mask = [batch size, trg len]
        #src_mask = [batch size, src len]
                
        batch_size = trg.shape[0]
        trg_len = trg.shape[1]
        
        pos = torch.arange(0, trg_len).unsqueeze(0).repeat(batch_size, 1).to(self.device)
                            
        #pos = [batch size, trg len]
            
        trg = self.dropout((self.tok_embedding(trg) * self.scale) + self.pos_embedding(pos))
                
        #trg = [batch size, trg len, hid dim]
        
        for layer in self.layers:
            trg, attention = layer(trg, enc_src, trg_mask, src_mask)
        
        #trg = [batch size, trg len, hid dim]
        #attention = [batch size, n heads, trg len, src len]
        
        output = self.fc_out(trg)
        
        #output = [batch size, trg len, output dim]
            
        return output, attention


class DecoderLayer(nn.Module):
    def __init__(self, 
                 hid_dim, 
                 n_heads, 
                 pf_dim, 
                 dropout, 
                 device):
        super().__init__()
        
        self.layer_norm = nn.LayerNorm(hid_dim)
        self.self_attention = MultiHeadAttentionLayer(hid_dim, n_heads, dropout, device)
        self.encoder_attention = MultiHeadAttentionLayer(hid_dim, n_heads, dropout, device)
        self.positionwise_feedforward = PositionwiseFeedforwardLayer(hid_dim, pf_dim, dropout)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, trg, enc_src, trg_mask, src_mask):
        
        #trg = [batch size, trg len, hid dim]
        #enc_src = [batch size, src len, hid dim]
        #trg_mask = [batch size, trg len]
        #src_mask = [batch size, src len]
        
        #self attention
        _trg, _ = self.self_attention(trg, trg, trg, trg_mask)
        
        #dropout, residual connection and layer norm
        trg = self.layer_norm(trg + self.dropout(_trg))
            
        #trg = [batch size, trg len, hid dim]
            
        #encoder attention
        _trg, attention = self.encoder_attention(trg, enc_src, enc_src, src_mask)
        
        #dropout, residual connection and layer norm
        trg = self.layer_norm(trg + self.dropout(_trg))
                    
        #trg = [batch size, trg len, hid dim]
        
        #positionwise feedforward
        _trg = self.positionwise_feedforward(trg)
        
        #dropout, residual and layer norm
        trg = self.layer_norm(trg + self.dropout(_trg))
        
        #trg = [batch size, trg len, hid dim]
        #attention = [batch size, n heads, trg len, src len]
        
        return trg, attention

Enfin, connectez l'encodeur et le décodeur pour terminer le transformateur.

class Seq2Seq(nn.Module):
    def __init__(self, 
                 encoder, 
                 decoder, 
                 src_pad_idx, 
                 trg_pad_idx, 
                 device):
        super().__init__()
        
        self.encoder = encoder
        self.decoder = decoder
        self.src_pad_idx = src_pad_idx
        self.trg_pad_idx = trg_pad_idx
        self.device = device
        
    def make_src_mask(self, src):
        
        #src = [batch size, src len]
        
        src_mask = (src != self.src_pad_idx).unsqueeze(1).unsqueeze(2)

        #src_mask = [batch size, 1, 1, src len]

        return src_mask
    
    def make_trg_mask(self, trg):
        
        #trg = [batch size, trg len]
        
        trg_pad_mask = (trg != self.trg_pad_idx).unsqueeze(1).unsqueeze(3)
        
        #trg_pad_mask = [batch size, 1, trg len, 1]
        
        trg_len = trg.shape[1]
        
        trg_sub_mask = torch.tril(torch.ones((trg_len, trg_len), device = self.device)).bool()
        
        #trg_sub_mask = [trg len, trg len]
            
        trg_mask = trg_pad_mask & trg_sub_mask
        
        #trg_mask = [batch size, 1, trg len, trg len]
        
        return trg_mask

    def forward(self, src, trg):
        
        #src = [batch size, src len]
        #trg = [batch size, trg len]
                
        src_mask = self.make_src_mask(src)
        trg_mask = self.make_trg_mask(trg)
        
        #src_mask = [batch size, 1, 1, src len]
        #trg_mask = [batch size, 1, trg len, trg len]
        
        enc_src = self.encoder(src, src_mask)
        
        #enc_src = [batch size, src len, hid dim]
                
        output, attention = self.decoder(trg, enc_src, trg_mask, src_mask)
        
        #output = [batch size, trg len, output dim]
        #attention = [batch size, n heads, trg len, src len]
        
        return output, attention

Bien que cela ne soit pas fait cette fois, il est possible de visualiser le poids d'attention après l'apprentissage en utilisant l'attention de la valeur de retour.

Apprentissage de modèle

Apprenez le modèle. Dans le script de référence, l'époque est arrêtée en examinant l'exactitude des données de validation, mais dans cet essai, l'exactitude des données de validation a diminué à mesure que l'apprentissage progressait à partir de la première époque, donc le surajustement a été ignoré et le modèle après l'époque finale Est adopté.

#----------------------
#Préparation à l'apprentissage
#----------------------
#Paramétrage
INPUT_DIM = len(TEXT.vocab)
OUTPUT_DIM = len(TEXT.vocab)
HID_DIM = 256
ENC_LAYERS = 3
DEC_LAYERS = 3
ENC_HEADS = 8
DEC_HEADS = 8
ENC_PF_DIM = 512
DEC_PF_DIM = 512
ENC_DROPOUT = 0.1
DEC_DROPOUT = 0.1

#Initialisation du codeur
enc = Encoder(INPUT_DIM, 
              HID_DIM, 
              ENC_LAYERS, 
              ENC_HEADS, 
              ENC_PF_DIM, 
              ENC_DROPOUT, 
              device)

#Initialisation du décodeur
dec = Decoder(OUTPUT_DIM, 
              HID_DIM, 
              DEC_LAYERS, 
              DEC_HEADS, 
              DEC_PF_DIM, 
              DEC_DROPOUT, 
              device)

#Spécification de l'ID pour le remplissage
PAD_IDX = TEXT.vocab.stoi[TEXT.pad_token]

#Initialisation du modèle
model = Seq2Seq(enc, dec, PAD_IDX, PAD_IDX, device).to(device)

#Initialisation du poids
def initialize_weights(m):
    if hasattr(m, 'weight') and m.weight.dim() > 1:
        nn.init.xavier_uniform_(m.weight.data)
model.apply(initialize_weights)

#Paramètres de l'optimiseur
LEARNING_RATE = 0.0005
optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)

#Paramètres de la fonction de perte
criterion = nn.CrossEntropyLoss(ignore_index=PAD_IDX)

#Définition de la fonction d'apprentissage
def train(model, iterator, optimizer, criterion, clip):
    
    model.train()
    
    epoch_loss = 0
    
    for i, batch in enumerate(iterator):
        
        src = batch.title
        trg = batch.text
        
        optimizer.zero_grad()
        
        output, _ = model(src, trg[:,:-1])
                
        #output = [batch size, trg len - 1, output dim]
        #trg = [batch size, trg len]
            
        output_dim = output.shape[-1]
            
        output = output.contiguous().view(-1, output_dim)
        trg = trg[:,1:].contiguous().view(-1)
                
        #output = [batch size * trg len - 1, output dim]
        #trg = [batch size * trg len - 1]
            
        loss = criterion(output, trg)
        
        loss.backward()
        
        torch.nn.utils.clip_grad_norm_(model.parameters(), clip)
        
        optimizer.step()
        
        epoch_loss += loss.item()
        
    return epoch_loss / len(iterator)

#Définition de la fonction d'évaluation
def evaluate(model, iterator, criterion):
    
    model.eval()
    
    epoch_loss = 0
    
    with torch.no_grad():
    
        for i, batch in enumerate(iterator):

            src = batch.title
            trg = batch.text

            output, _ = model(src, trg[:,:-1])
            
            #output = [batch size, trg len - 1, output dim]
            #trg = [batch size, trg len]
            
            output_dim = output.shape[-1]
            
            output = output.contiguous().view(-1, output_dim)
            trg = trg[:,1:].contiguous().view(-1)
            
            #output = [batch size * trg len - 1, output dim]
            #trg = [batch size * trg len - 1]
            
            loss = criterion(output, trg)

            epoch_loss += loss.item()
        
    return epoch_loss / len(iterator)

#Définition de la fonction de mesure du temps de traitement
def epoch_time(start_time, end_time):
    elapsed_time = end_time - start_time
    elapsed_mins = int(elapsed_time / 60)
    elapsed_secs = int(elapsed_time - (elapsed_mins * 60))
    return elapsed_mins, elapsed_secs

#Définition de la fonction de génération de phrases
def translate_sentence(sentence, src_field, trg_field, model, device, max_len=1000):
    
    model.eval()
        
    tokens = [token.lower() for token in sentence]

    tokens = [src_field.init_token] + tokens + [src_field.eos_token]
        
    src_indexes = [src_field.vocab.stoi[token] for token in tokens]

    src_tensor = torch.LongTensor(src_indexes).unsqueeze(0).to(device)
    
    src_mask = model.make_src_mask(src_tensor)
    
    with torch.no_grad():
        enc_src = model.encoder(src_tensor, src_mask)

    trg_indexes = [trg_field.vocab.stoi[trg_field.init_token]]

    for i in range(max_len):

        trg_tensor = torch.LongTensor(trg_indexes).unsqueeze(0).to(device)

        trg_mask = model.make_trg_mask(trg_tensor)
        
        with torch.no_grad():
            output, attention = model.decoder(trg_tensor, enc_src, trg_mask, src_mask)
        
        pred_token = output.argmax(2)[:,-1].item()
        
        trg_indexes.append(pred_token)

        if pred_token == trg_field.vocab.stoi[trg_field.eos_token]:
            break
    
    trg_tokens = [trg_field.vocab.itos[i] for i in trg_indexes]
    
    return trg_tokens[1:], attention


#----------------------
#Apprentissage de modèle
#----------------------
import time
import math

N_EPOCHS = 100
CLIP = 1

#Obtenez 1 exemple de travail
example_idx = 8
src_sample = vars(train_ds.examples[example_idx])['title']
trg_sample = vars(train_ds.examples[example_idx])['text']

#Afficher le titre et le corps
print(f'src = {src_sample}')
print(f'trg = {trg_sample}')

best_valid_loss = float('inf')

for epoch in range(N_EPOCHS):
    
    start_time = time.time()
    
    train_loss = train(model, train_iter, optimizer, criterion, CLIP)
    valid_loss = evaluate(model, test_iter, criterion)
    
    end_time = time.time()
    
    epoch_mins, epoch_secs = epoch_time(start_time, end_time)
    
    #if valid_loss < best_valid_loss:
    #    best_valid_loss = valid_loss
    #    torch.save(model.state_dict(), 'drive/My Drive/trained_model.pt')
    #Dans le script de référence, la précision des données de validation est utilisée comme indice, mais dans cet essai, la précision des données de validation diminuait au fur et à mesure que l'apprentissage progressait, le surajustement a donc été ignoré et le modèle après l'époque finale a été adopté (compte tenu de la fin du processus). Enregistrer après chaque époque)
    torch.save(model.state_dict(), 'drive/My Drive/trained_model.pt')

    #Afficher l'exactitude des données de formation / validation pour chaque époque
    print(f'Epoch: {epoch+1:02} | Time: {epoch_mins}m {epoch_secs}s')
    print(f'\tTrain Loss: {train_loss:.3f} | Train PPL: {math.exp(train_loss):7.3f}')
    print(f'\t Val. Loss: {valid_loss:.3f} |  Val. PPL: {math.exp(valid_loss):7.3f}')

    #Afficher le résultat de la génération du texte à partir du titre d'un exemple de travail toutes les 10 époques
    if epoch % 10 == 0:
      translation, attention = translate_sentence(src_sample, TEXT, TEXT, model, device)
      print(f'predicted trg = {translation}')

tester

Vous pouvez générer un roman en entrant votre titre préféré ci-dessous.

translation, attention = translate_sentence(['Tout titre'], TEXT, TEXT, model, device)
print(f'predicted trg = {translation}')

J'en ai essayé. Les deux premiers sont des titres inclus dans les données d'entraînement, et les deux derniers sont des titres non inclus (pour être exact, ils sont inclus dans le dictionnaire (= deux fois dans le titre ou le corps des données d'entraînement ou des données de test). Parmi les combinaisons de mots (qui apparaissent ci-dessus), celles qui ne sont pas les titres des données d'apprentissage).

Titre d'entrée Texte généré
souvenirs Quand j'avais la vingtaine, j'ai rencontré M. Ogai cinq ou six fois. Puis quelques fois du ministère de l'armée(unk)Je l'ai vu quand j'ai livré l'étalonnage à la station, mais c'était facile,(unk)Je n'ai pas assez de matériel pour raconter l'histoire. À cette époque, Hiroshi Yosano, Nagae Ikuta et Kafu Nagai étaient les élèves de M. Kogai, et j'étais comme le petit-fils du professeur. Pour cette raison, je n'ai aucune relation directe avec le professeur, mais je le respecte pour son travail littéraire. Il semble que l'enseignant ait toujours fait attention à ne pas que l'autre personne se sente à l'étroit, souffrant de ce qui semble être une personne difficile, mais c'était plutôt à l'étroit ici. En parlant de pensées, un jour, pour fêter la publication d'un magazine intitulé "Nous" ... (Omis ci-dessous)
Empreintes ずつと昔のこと一匹の狐が河岸の粘土層を走つていつたそれから何万年かたつたあとにその粘土層が化石となつてEmpreintesが残つたそのEmpreintesを見ると、むかし狐が何を考えて走つていつたのかがわかる
cheveux Ah, tu es triste et je suis mélancolique, Segawa(unk)Si vous regardez la lumière de Fufunabata, vous pouvez voir les yeux de la jeune fille de Yufugure.(unk), Kuchi(unk)
musiques (unk)Est(unk)Est(unk)太陽が落ちて太陽の世界が始つた[#「始つた」Est底本でEst「始まつた」](unk)Est戸袋(unk)Est(unk)Le soleil s'est levé et le monde nocturne a commencé(unk)Est妖怪下痢Est(unk)Higurashi a dessiné un diamètre et le monde de Dada a commencé (il(unk)Regardez-le et le Christ l'imprime

Pour les données de formation, il semble que le texte puisse être reproduit avec une grande précision à partir du titre. D'un autre côté, les titres qui ne sont pas inclus dans les données d'apprentissage sont devenus des phrases sans signification.

en conclusion

Il a été confirmé que le texte peut être généré à partir du titre du roman en utilisant Transformer. Cependant, je n'ai pas pu générer un titre approprié pour le titre qui ne figure pas dans les données d'entraînement, donc la prochaine fois, j'aimerais utiliser les données de validation que j'ai abandonnées cette fois et viser un modèle plus général.

référence

Recommended Posts

[PyTorch] Génération de phrases japonaises à l'aide de Transformer
[PyTorch] Introduction à la classification des documents japonais à l'aide de BERT
[Python3] Génération automatique de texte avec janome et markovify
07. Génération de déclaration par modèle
J'ai écrit le code pour la génération de phrases japonaises avec DeZero
Analyse morphologique japonaise avec Janome
Génération de phrases avec GRU (keras)
Tutoriel [PyTorch] (version japonaise) ② ~ AUTOGRAD ~
Remarques sur l'optimisation à l'aide de Pytorch
Génération de Pokémon la plus puissante utilisant LSTM