Cuando la entrada de cada paso del decodificador proviene de la salida del paso anterior, estamos hablando de un modelo Seq2Seq con realimentación (feedback). Esto es especialmente común en tareas como la generación de texto.
Inicio de la Secuencia: Se inicia la generación con un token especial, como <SOS>
(Start of Sequence).
Generación Paso a Paso:
<SOS>
y el estado del codificador como entrada.<EOS>
, End of Sequence) o hasta alcanzar un límite máximo de longitud.Ventajas y Desventajas:
Imagina que tienes un modelo seq2seq entrenado para traducir inglés a español. Para la frase "How are you?", el proceso sería algo así:
<SOS>
.<EOS>
para indicar el final de la secuencia.Este modelo de generación permite que el decodificador tenga en cuenta no solo el contexto proporcionado por el codificador, sino también la estructura de la secuencia que está generando, paso a paso.
Teacher forcing es una técnica utilizada en el entrenamiento de modelos seq2seq en la que, en un porcentaje de las veces, se utiliza la salida real (etiqueta) de un paso de tiempo como entrada para el siguiente paso, en lugar de la salida predicha por el modelo. Esta técnica puede ayudar a acelerar la convergencia y mejorar el rendimiento del modelo, especialmente en las etapas iniciales del entrenamiento.
Durante el Entrenamiento: En cada paso de tiempo y con una cierta probabilidad de que suceda, en lugar de pasar la predicción del modelo del paso anterior como entrada al siguiente paso, se pasa la palabra real de la secuencia objetivo. Esto proporciona al modelo información directa y clara sobre cómo debería haber respondido en el paso anterior, independientemente de si la predicción fue correcta o no.
Durante la Evaluación/Predicción: El modelo debe generar secuencias por sí mismo, utilizando sus propias predicciones del paso anterior para el siguiente paso. Durante esta fase, no se utiliza "teacher forcing".
Aprendizaje más Rápido: Al proporcionar al modelo la respuesta correcta en cada paso, se reduce la propagación de errores a través de la secuencia, lo que puede llevar a un aprendizaje más rápido.
Mejor Rendimiento: Puede resultar en un mejor rendimiento del modelo, especialmente en las primeras etapas del entrenamiento.
El conjunto de datos Europarl contiene las transcripciones de los procedimientos del Parlamento Europeo, proporcionando una valiosa fuente de textos paralelos en 21 idiomas europeos. Las oraciones están alineadas entre los idiomas, lo que lo hace especialmente útil para tareas de traducción automática.
Descargamos el dataset Europarl para español-inglés. Una vez descargado tendremos dos ficheros de texto, uno para cada idioma con las frases alineadas. El código siguiente muestra las primeras frases de cada fichero.
def read_translation(archivo_ingles, archivo_espanol):
with open(archivo_ingles, 'r', encoding='utf-8') as f_ingles, open(archivo_espanol, 'r', encoding='utf-8') as f_espanol:
for oracion_ingles, oracion_espanol in zip(f_ingles, f_espanol):
yield oracion_ingles.strip(), oracion_espanol.strip()
archivo_ingles = 'data/europarl/europarl-v7.es-en.en'
archivo_espanol = 'data/europarl/europarl-v7.es-en.es'
# Leer el conjunto de datos
for i, (ingles, espanol) in enumerate(read_translation(archivo_ingles, archivo_espanol)):
print('Inglés:', ingles)
print('Español:', espanol)
print('---')
if i == 15:
break
Inglés: Resumption of the session Español: Reanudación del período de sesiones --- Inglés: I declare resumed the session of the European Parliament adjourned on Friday 17 December 1999, and I would like once again to wish you a happy new year in the hope that you enjoyed a pleasant festive period. Español: Declaro reanudado el período de sesiones del Parlamento Europeo, interrumpido el viernes 17 de diciembre pasado, y reitero a Sus Señorías mi deseo de que hayan tenido unas buenas vacaciones. --- Inglés: Although, as you will have seen, the dreaded 'millennium bug' failed to materialise, still the people in a number of countries suffered a series of natural disasters that truly were dreadful. Español: Como todos han podido comprobar, el gran "efecto del año 2000" no se ha producido. En cambio, los ciudadanos de varios de nuestros países han sido víctimas de catástrofes naturales verdaderamente terribles. --- Inglés: You have requested a debate on this subject in the course of the next few days, during this part-session. Español: Sus Señorías han solicitado un debate sobre el tema para los próximos días, en el curso de este período de sesiones. --- Inglés: In the meantime, I should like to observe a minute' s silence, as a number of Members have requested, on behalf of all the victims concerned, particularly those of the terrible storms, in the various countries of the European Union. Español: A la espera de que se produzca, de acuerdo con muchos colegas que me lo han pedido, pido que hagamos un minuto de silencio en memoria de todas las víctimas de las tormentas, en los distintos países de la Unión Europea afectados. --- Inglés: Please rise, then, for this minute' s silence. Español: Invito a todos a que nos pongamos de pie para guardar un minuto de silencio. --- Inglés: (The House rose and observed a minute' s silence) Español: (El Parlamento, de pie, guarda un minuto de silencio) --- Inglés: Madam President, on a point of order. Español: Señora Presidenta, una cuestión de procedimiento. --- Inglés: You will be aware from the press and television that there have been a number of bomb explosions and killings in Sri Lanka. Español: Sabrá usted por la prensa y la televisión que se han producido una serie de explosiones y asesinatos en Sri Lanka. --- Inglés: One of the people assassinated very recently in Sri Lanka was Mr Kumar Ponnambalam, who had visited the European Parliament just a few months ago. Español: Una de las personas que recientemente han asesinado en Sri Lanka ha sido al Sr. Kumar Ponnambalam, quien hace pocos meses visitó el Parlamento Europeo. --- Inglés: Would it be appropriate for you, Madam President, to write a letter to the Sri Lankan President expressing Parliament's regret at his and the other violent deaths in Sri Lanka and urging her to do everything she possibly can to seek a peaceful reconciliation to a very difficult situation? Español: ¿Sería apropiado que usted, Señora Presidenta, escribiese una carta al Presidente de Sri Lanka expresando las condolencias del Parlamento por esa y otras muertes violentas, pidiéndole que haga todo lo posible para encontrar una reconciliación pacífica ante la extremadamente difícil situación que está viviendo su país? --- Inglés: Yes, Mr Evans, I feel an initiative of the type you have just suggested would be entirely appropriate. Español: Sí, señor Evans, pienso que una iniciativa como la que usted acaba de sugerir sería muy adecuada. --- Inglés: If the House agrees, I shall do as Mr Evans has suggested. Español: Si la Asamblea está de acuerdo, haré lo que el señor Evans acaba de sugerir. --- Inglés: Madam President, on a point of order. Español: Señora Presidenta, una cuestión de procedimiento. --- Inglés: I would like your advice about Rule 143 concerning inadmissibility. Español: Me gustaría que me asesorara sobre el Artículo 143 concerniente a la inadmisibilidad. --- Inglés: My question relates to something that will come up on Thursday and which I will then raise again. Español: Mi pregunta se refiere a un asunto del que se hablará el jueves, día que en volveré a plantearla. ---
from torchtext.data.utils import get_tokenizer
from torch.utils.data import Dataset
from torch.nn.utils.rnn import pad_sequence
import torchtext
import torch
from collections import defaultdict
class Translation(Dataset):
def __init__(self, source_file, target_file):
self.ingles = []
self.espanol = []
self.tokenizer_es = get_tokenizer("spacy", language="es_core_news_md")
self.tokenizer_en = get_tokenizer("spacy", language="en_core_web_md")
self.vocab_es = torchtext.vocab.FastText(language='es', unk_init=torch.Tensor.normal_) # <-- Mirar esto para ver si añadir el token <unk> al vocabulario
self.vocab_en = torchtext.vocab.FastText(language='en', unk_init=torch.Tensor.normal_)
self.vocab_en = self.add_sos_eos_unk_pad(self.vocab_en)
self.vocab_es = self.add_sos_eos_unk_pad(self.vocab_es)
self.archivo_ingles = source_file
self.archivo_espanol = target_file
# Leer el conjunto de datos
for ingles, espanol in self.read_translation():
self.ingles.append(ingles)
self.espanol.append(espanol)
def add_sos_eos_unk_pad(self, vocabulary):
words = vocabulary.itos
vocab = vocabulary.stoi
embedding_matrix = vocabulary.vectors
# Tokens especiales
sos_token = '<sos>'
eos_token = '<eos>'
pad_token = '<pad>'
unk_token = '<unk>'
# Inicializamos los vectores para los tokens especiales, por ejemplo, con ceros
sos_vector = torch.full((1, embedding_matrix.shape[1]), 1.)
eos_vector = torch.full((1, embedding_matrix.shape[1]), 2.)
pad_vector = torch.zeros((1, embedding_matrix.shape[1]))
unk_vector = torch.full((1, embedding_matrix.shape[1]), 3.)
# Añade los vectores al final de la matriz de embeddings
embedding_matrix = torch.cat((embedding_matrix, sos_vector, eos_vector, unk_vector, pad_vector), 0)
# Añade los tokens especiales al vocabulario
vocab[sos_token] = len(vocab)
vocab[eos_token] = len(vocab)
vocab[pad_token] = len(vocab)
vocab[unk_token] = len(vocab)
words.append(sos_token)
words.append(eos_token)
words.append(pad_token)
words.append(unk_token)
vocabulary.itos = words
vocabulary.stoi = vocab
vocabulary.vectors = embedding_matrix
default_stoi = defaultdict(lambda : len(vocabulary)-1, vocabulary.stoi)
vocabulary.stoi = default_stoi
return vocabulary
def read_translation(self):
with open(self.archivo_ingles, 'r', encoding='utf-8') as f_ingles, open(self.archivo_espanol, 'r', encoding='utf-8') as f_espanol:
for oracion_ingles, oracion_espanol in zip(f_ingles, f_espanol):
yield oracion_ingles.strip().lower(), oracion_espanol.strip().lower()
def __len__(self):
return len(self.ingles)
def __getitem__(self, idx):
item = self.ingles[idx], self.espanol[idx]
tokens_ingles = self.tokenizer_en(item[0])
tokens_espanol = self.tokenizer_es(item[1])
tokens_ingles = tokens_ingles + ['<eos>']
tokens_espanol = ['<sos>'] + tokens_espanol + ['<eos>']
if not tokens_ingles or not tokens_espanol:
return torch.zeros(1, 300), torch.zeros(1, 300)
# raise RuntimeError("Una de las muestras está vacía.")
tensor_ingles = self.vocab_en.get_vecs_by_tokens(tokens_ingles)
tensor_espanol = self.vocab_es.get_vecs_by_tokens(tokens_espanol)
indices_ingles = [self.vocab_en.stoi[token] for token in tokens_ingles] + [self.vocab_en.stoi['<pad>']]
indices_espanol = [self.vocab_es.stoi[token] for token in tokens_espanol] + [self.vocab_es.stoi['<pad>']]
return tensor_ingles, tensor_espanol, indices_ingles, indices_espanol
def collate_fn(batch):
ingles_batch, espanol_batch, ingles_seqs, espanol_seqs = zip(*batch)
ingles_batch = pad_sequence(ingles_batch, batch_first=True, padding_value=0)
espanol_batch = pad_sequence(espanol_batch, batch_first=True, padding_value=0)
# Calcular la longitud máxima de la lista de listas de índices
pad = espanol_seqs[0][-1] # token <pad>
max_len = max([len(l) for l in espanol_seqs])
for seq in espanol_seqs:
seq += [pad]*(max_len-len(seq))
return ingles_batch, espanol_batch, ingles_seqs, espanol_seqs
# archivo_ingles = 'data/europarl/europarl-v7.es-en.en'
# archivo_espanol = 'data/europarl/europarl-v7.es-en.es'
archivo_ingles = 'data/mock/mock.en'
archivo_espanol = 'data/mock/mock.es'
translation = Translation(archivo_ingles, archivo_espanol)
import torch
import torch.nn as nn
import torch.optim as optim
class Encoder(nn.Module):
def __init__(self, input_dim, hidden_dim, num_layers):
super().__init__()
self.rnn = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True)
def forward(self, x):
output, (hidden, cell) = self.rnn(x)
return output, (hidden, cell)
class Decoder(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim, num_layers):
super().__init__()
self.rnn = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True) # TO DO: Añadir dropout
self.fc_out = nn.Linear(hidden_dim, output_dim)
def forward(self, x, hidden, cell):
output, (hidden, cell) = self.rnn(x, (hidden, cell))
output = self.fc_out(output)
return output, (hidden, cell)
import random
class Seq2Seq(nn.Module):
def __init__(self, encoder, decoder):
super().__init__()
self.encoder = encoder
self.decoder = decoder
self.es_embeddings = torchtext.vocab.FastText(language='es')
self.M = self.es_embeddings.vectors
self.M = torch.cat((self.M, torch.zeros((4, self.M.shape[1]))), 0)
def forward(self, source, target, teacher_forcing_ratio=0.5):
target_len = target.shape[1]
batch_size = target.shape[0]
# Tensor para almacenar las salidas del decoder
outputs = torch.zeros(batch_size, target_len, 985671)
# Primero, la fuente es procesada por el encoder
_, (hidden, cell) = self.encoder(source)
# La primera entrada al decoder es el vector <sos>
x = target[:, 0, :]
for t in range(1, target_len):
output, (hidden, cell) = self.decoder(x.unsqueeze(1), hidden, cell)
outputs[:, t, :] = output.squeeze(1)
teacher_force = random.random() < teacher_forcing_ratio
if teacher_force:
x = target[:, t, :]
else:
x = torch.matmul(output.squeeze(1), self.M)
return outputs
# Parámetros
input_dim = 300
output_dim = translation.vocab_es.vectors.shape[0]
hidden_dim = 512
num_layers = 2
learning_rate = 0.001
num_epochs = 30
batch_size = 8
num_workers = 0
shuffle = True
# Inicializa el modelo, el optimizador y la función de pérdida
encoder = Encoder(input_dim, hidden_dim, num_layers)
decoder = Decoder(input_dim, hidden_dim, output_dim, num_layers)
model = Seq2Seq(encoder, decoder)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
criterion = nn.CrossEntropyLoss()
# DataLoader
from torch.utils.data import DataLoader
dataloader = DataLoader(translation, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers, collate_fn=collate_fn)
# Bucle de entrenamiento
for epoch in range(num_epochs):
model.train()
total_loss = 0
for batch_idx, (src, tgt, src_indices, tgt_indices) in enumerate(dataloader):
optimizer.zero_grad()
output = model(src, tgt)
tgt_indices = torch.tensor(tgt_indices, dtype=torch.long)
loss = 0
for t in range(1, tgt.shape[1]):
loss += criterion(output[:, t, :], tgt_indices[:, t])
# loss = criterion(output, torch.tensor(tgt_indices, dtype=torch.long))
loss.backward()
optimizer.step()
total_loss += loss.item()
if batch_idx % 5 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Step [{batch_idx+1}/{len(dataloader)}], Loss: {loss.item():.4f}')
print(f'Epoch [{epoch+1}/{num_epochs}], Average Loss: {total_loss / len(dataloader):.4f}')
--------------------------------------------------------------------------- KeyboardInterrupt Traceback (most recent call last) Cell In[11], line 12 10 loss = 0 11 for t in range(1, tgt.shape[1]): ---> 12 loss += criterion(output[:, t, :], tgt_indices[:, t]) 13 # loss = criterion(output, torch.tensor(tgt_indices, dtype=torch.long)) 15 loss.backward() Cell In[11], line 12 10 loss = 0 11 for t in range(1, tgt.shape[1]): ---> 12 loss += criterion(output[:, t, :], tgt_indices[:, t]) 13 # loss = criterion(output, torch.tensor(tgt_indices, dtype=torch.long)) 15 loss.backward() File ~/Propio/Notebooks/Machine Learning/RL/env/lib/python3.10/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py:988, in PyDBFrame.trace_dispatch(self, frame, event, arg) 986 # if thread has a suspend flag, we suspend with a busy wait 987 if info.pydev_state == STATE_SUSPEND: --> 988 self.do_wait_suspend(thread, frame, event, arg) 989 return self.trace_dispatch 990 else: File ~/Propio/Notebooks/Machine Learning/RL/env/lib/python3.10/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py:165, in PyDBFrame.do_wait_suspend(self, *args, **kwargs) 164 def do_wait_suspend(self, *args, **kwargs): --> 165 self._args[0].do_wait_suspend(*args, **kwargs) File ~/Propio/Notebooks/Machine Learning/RL/env/lib/python3.10/site-packages/debugpy/_vendored/pydevd/pydevd.py:2070, in PyDB.do_wait_suspend(self, thread, frame, event, arg, exception_type) 2067 from_this_thread.append(frame_custom_thread_id) 2069 with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, thread, stop_reason): -> 2070 keep_suspended = self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker) 2072 frames_list = None 2074 if keep_suspended: 2075 # This means that we should pause again after a set next statement. File ~/Propio/Notebooks/Machine Learning/RL/env/lib/python3.10/site-packages/debugpy/_vendored/pydevd/pydevd.py:2106, in PyDB._do_wait_suspend(self, thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker) 2103 self._call_input_hook() 2105 self.process_internal_commands() -> 2106 time.sleep(0.01) 2108 self.cancel_async_evaluation(get_current_thread_id(thread), str(id(frame))) 2110 # process any stepping instructions KeyboardInterrupt:
# Test the model with input sentences
model.eval()
sentence = "tiger"
# Convertir a vectores
tokens = translation.tokenizer_en(sentence)
tokens = tokens + ['<eos>']
text_tensor = translation.vocab_en.get_vecs_by_tokens(tokens)
text_tensor = text_tensor.unsqueeze(0)
with torch.no_grad():
encoder_outputs, (hidden, cell) = model.encoder(text_tensor)
outputs = []
input_token = torch.tensor(translation.vocab_es.stoi['<sos>']).unsqueeze(0)
input_token = translation.vocab_es.vectors[input_token].unsqueeze(0)
for _ in range(5):
with torch.no_grad():
output, (hidden, cell) = model.decoder(input_token, hidden, cell) # teacher_forcing_ratio=0.0
# Obtener el token con la probabilidad más alta
best_guess = output.argmax(2).squeeze(0)
outputs.append(best_guess.item())
# Si el token es <eos>, terminar la traducción
if best_guess == translation.vocab_es.stoi['<eos>']:
break
# Utilizar la palabra predicha como la siguiente entrada al decoder
input_token = translation.vocab_es.vectors[best_guess].unsqueeze(0)
# Convertir los índices de salida a palabras
translated_sentence = [translation.vocab_es.itos[idx] for idx in outputs]
result = ' '.join(translated_sentence)
print(result)
gato <eos>
# Guardar el modelo
torch.save(model.state_dict(), 'seq2seq.pth')
# Load model from file
model.load_state_dict(torch.load('seq2seq.pth'))
<All keys matched successfully>