Entrenar un modelo de deep learning para clasificación de texto es el paso donde todo cobra sentido. Después de definir hiperparámetros, funciones de procesamiento, optimizador y función de pérdida, llega el momento de poner el modelo a aprender y verificar si realmente puede generalizar a datos nuevos.
¿Cómo se estructura el loop de entrenamiento en PyTorch?
El proceso arranca inicializando una variable llamada best validation loss con un valor positivo infinito [01:00]. Esto funciona como referencia: cualquier pérdida real será menor que infinito, así que el primer modelo entrenado siempre se guardará como el mejor hasta ese momento.
El loop principal recorre cada época en un rango definido previamente. En cada iteración ocurren tres cosas fundamentales:
Se llama a la función entrena pasándole el dataloader de entrenamiento, lo que devuelve el accuracy promedio y la pérdida promedio de esa época.
Se llama a la función evalúa con el dataloader de validación, obteniendo también accuracy y pérdida de validación.
Se compara la pérdida de validación actual con la mejor registrada.
¿Cómo se guardan los mejores pesos del modelo?
Si la pérdida de validación actual es menor que la mejor pérdida almacenada, el modelo se considera superior y se actualiza la referencia [02:30]. En ese momento se ejecuta torch.save para guardar el state dict del modelo, que representa el estado de todos sus pesos. El archivo se guarda con extensión .pt, una convención estándar en PyTorch para almacenar pesos de modelos.
¿Qué hacer cuando aparece un error durante el entrenamiento?
Al ejecutar el entrenamiento por primera vez, apareció un error en la función evalúa [03:30]. El mensaje unsupported operand indicaba que faltaba especificar la dimensión cero al obtener el tamaño del tensor en la variable total_count. La corrección fue agregar .size(0) para extraer el entero correspondiente al tamaño del batch. Después de corregir, se usó la opción run after en el menú de runtime para reejecutar desde ese punto.
¿Qué resultados obtuvo el modelo entrenado?
El modelo alcanzó un accuracy de 0.79, lo que significa que acierta la clase correcta aproximadamente el 80% de las veces [04:15]. Un resultado sólido considerando los hiperparámetros iniciales.
Existe un detalle importante sobre el comportamiento del modelo: no desaprende. Si se vuelve a ejecutar la función de entrenamiento, el modelo continúa aprendiendo desde donde se quedó, no desde cero. Para reiniciar completamente el aprendizaje, sería necesario inicializar el modelo de nuevo [04:50].
¿Cómo ajustar hiperparámetros para mejorar el rendimiento?
Se recomienda experimentar con diferentes configuraciones:
Aumentar el learning rate por encima de 0.2, lo cual mostró mejores resultados en pruebas previas.
Modificar el número de épocas, tanto incrementándolas como reduciéndolas.
Variar el tamaño del batch para observar cómo impacta en la convergencia.
¿Qué tan bien generaliza el modelo con datos nuevos?
Hasta este punto se utilizaron dos particiones del dataset: entrenamiento y validación. Pero la prueba definitiva es evaluar con el conjunto de test, datos que el modelo nunca vio durante el entrenamiento [05:30].
Al pasar el test dataloader a la función evalúa, los resultados fueron reveladores:
El accuracy en testing fue prácticamente del 80%.
Incluso resultó ligeramente superior al obtenido en validación.
Esto confirma que el modelo aprendió patrones reales y no simplemente memorizó los datos de entrenamiento. La capacidad de generalización es la métrica que realmente importa cuando se construye un clasificador de texto.
El concepto de generalización es clave: un modelo que funciona bien solo con datos conocidos pero falla con datos nuevos sufre de overfitting. En este caso, el rendimiento consistente entre validación y test indica un entrenamiento saludable.
Si lograste resultados diferentes al modificar los hiperparámetros, comparte tu experiencia y los valores que utilizaste.
Gracias por compartir, muy buen resultado con los cambios que hiciste.
tengo una pregunta, se crea y se inicializa una variable major\_loss\_validation pero en ningun momento veo que el valor de esa variable se modifique, pero aun asi siempre se compara contra el valor de esa variable para guardar el mejor modelo:
if validacion_loss < major_loss_validation:
la pregunta es, no se deberia guardar el valor del mejor modelo en esa variable major\_loss\_validation?
yo creo lo mismo
Para guardar el valor del mejor modelo en la variable major_loss_validation, debes inicializarla con un valor positivo infinito al inicio del entrenamiento. Luego, durante cada época, compara la validation_loss actual con el valor almacenado en major_loss_validation. Si la validation_loss es menor, actualiza major_loss_validation con este nuevo valor y guarda el modelo actual utilizando torch.save().
El proceso se desglosa así:
Inicializar major_loss_validation como float('inf').
Durante el entrenamiento, si validation_loss < major_loss_validation, actualizar major_loss_validation y guardar el modelo.
Esto asegura que siempre tengas el mejor modelo con la menor pérdida de validación.
if validation_loss < major_loss_validation: major_loss_validation = validation_loss
torch.save(model.state_dict(),'mejores_pesos.pt')
Aquí tienes un ejemplo completo de cómo puedes realizar el **entrenamiento** y la **evaluación** de un modelo de clasificación de texto utilizando PyTorch y TorchText, asumiendo que ya tienes los datos preparados y tokenizados.
### Paso 1: Preparación de los datos
Para este ejemplo, asumiremos que ya has cargado el dataset (como **DBpedia**) y creado tu vocabulario. También vamos a usar un DataLoader para manejar los datos en mini-lotes.
### Paso 2: Definir el modelo
El modelo que vamos a usar es una simple red neuronal con una capa de embeddings, una capa recurrente (GRU), y una capa completamente conectada para la clasificación.
Para evaluar el modelo, no aplicamos gradientes, y calculamos la precisión en el conjunto de prueba.
defevaluate(dataloader, model, criterion):  model.eval()  total\_acc, total\_count = 0, 0  with torch.no\_grad():  for label, text, offsets in dataloader:  predicted\_label = model(text, offsets)  loss = criterion(predicted\_label, label)  total\_acc += (predicted\_label.argmax(1) == label).sum().item()  total\_count += label.size(0)  return total\_acc/total\_count
### Paso 5: Entrenar y evaluar
VOCAB\_SIZE =len(vocab)# Tamaño del vocabularioEMBED\_DIM =64# Dimensión de los embeddingsNUM\_CLASS =len(set(\[label for(label, text)in train\_iter]))# Número de clasesmodel = TextClassificationModel(VOCAB\_SIZE, EMBED\_DIM, NUM\_CLASS).to(device)optimizer = optim.SGD(model.parameters(), lr=0.001)criterion = nn.CrossEntropyLoss()
\# Entrenamiento
for epoch inrange(10):  train(train\_dataloader, model, criterion, optimizer)  acc = evaluate(test\_dataloader, model, criterion)  print(f'Epoch {epoch+1}: Test Accuracy: {acc:.4f}')
### Paso 6: Preparar los datos para DataLoader
Antes de entrenar el modelo, es necesario definir un DataLoader que maneje cómo se cargarán los datos en mini-lotes. Aquí te dejo un ejemplo de cómo preparar los datos:
from torch.utils.data import DataLoader
from torchtext.datasets import DBpedia
from torchtext.data.utils import get\_tokenizer
tokenizer = get\_tokenizer('basic\_english')defyield\_tokens(data\_iter):  for \_, text in data\_iter:  yield tokenizer(text)vocab = build\_vocab\_from\_iterator(yield\_tokens(train\_iter), specials=\["\<unk>"])vocab.set\_default\_index(vocab\["\<unk>"])def collate\_batch(batch):  label\_list, text\_list, offsets = \[], \[], \[0]  for (\_label, \_text) in batch:  label\_list.append(int(\_label) - 1)  processed\_text = torch.tensor(vocab(tokenizer(\_text)), dtype=torch.int64)  text\_list.append(processed\_text)  offsets.append(processed\_text.size(0))  label\_list = torch.tensor(label\_list, dtype=torch.int64)  text\_list = torch.cat(text\_list)  offsets = torch.tensor(offsets\[:-1]).cumsum(dim=0)  return label\_list, text\_list, offsetstrain\_iter = DBpedia(split='train')test\_iter = DBpedia(split='test')train\_dataloader = DataLoader(train\_iter, batch\_size=8, shuffle=True, collate\_fn=collate\_batch)test\_dataloader = DataLoader(test\_iter, batch\_size=8, shuffle=True, collate\_fn=collate\_batch)
### Conclusión
Este es un flujo básico de cómo entrenar y evaluar un modelo de clasificación de texto con PyTorch. El modelo usa embeddings simples y una capa de clasificación, pero puedes mejorarlo añadiendo capas más complejas como LSTM o CNN. También puedes probar diferentes optimizadores, tasas de aprendizaje y técnicas de regularización para obtener mejores resultados.
Hay un detalle que se me hizo raro en el código presentado
y es que creo que el mejor modelo no se guarda como tal , ya que major_loss_validation no se está actualizando y el sentido del condicional ahí equivale a revisar que la perdida promedio de validación en ese epoch sea menor a la antigua pero la antigua se guarda en una variable diferente a la que se usa para mirar la condición por ende realmente no se guarda la mejor sino se guarda la siguiente y la siguiente ya que esa condición siempre da TRUE
Codigo propuesto:
#Crear variable que devuelve la perdida de validacióon mas pequeña major_loss_validation =float('inf')#Entrenar for epoch inrange(1, EPOCHS +1):#Entrenar el modelo accuracy_mean_trian, mean_loss_train = training(model_t, dataloader_train)#Validación accuracy_mean_eval, mean_loss_eval = training(model_t, dataloader_val)#Guardar el modelo con mejores resultados if mean_loss_eval < major_loss_validation: major_loss_validation = mean_loss_eval
torch.save(model_t.state_dict(),"mejor_model.pt")
Si mi lógica está mal déjenmelo saber 🫰
¿Cuanto tiempo les tardo en entrenarse su modelo hasta llegar a las 4 Epochs? Mi entrenamiento está tardando demasiado. ¿Saben qué podría ser?
Aproximadamente 8 minutos en Kaggle con una "GPU T4 x2", 4 épocas con tamaño del lote de 128. Asegúrate de tener la GPU, sino esto puede tardar varias horas. También fíjate en la cantidad de épocas (entre más, más demorará) y en el tamaño del lote (entre más, más rápido, pero requiere más GPU).
Hola Juan
Aproximadamente 8min.
Lo estoy haciendo en local, creo que está configurado para 4 de RAM y usar CUDA de la targeta gráfica, pero no está a su máxima potencia.
Cambiando el optimizador a Adam solamente tuve un resultado prometedor
Durante la ejecución del Entrenamiento y la validación me apareció el siguiente mensaje de error:
Luego de eso, me salió el siguiente error en la evaluación:
El cual ocurre porque en el video, en el código de la clase, para la función de entrenamiento se utilizó:
total_count += label.size(0)
Pero en la función de validación no se le pasó ningún parámetro, ergo: la solución es ponerle el 0.
Comparto el modelo usando LSTM:from torch import nnimport torch.nn.functional as F
class ModeloClasificacionTexto(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_size, num_class): super(ModeloClasificacionTexto, self).__init__()
# Capa de incrustación (embedding) self.embedding = nn.EmbeddingBag(vocab_size, embed_dim)
# Capa LSTM self.lstm = nn.LSTM(embed_dim, hidden_size)
# Capa de transformación self.transformation = nn.Sequential( nn.Linear(hidden_size, hidden_size), nn.ReLU(), nn.Linear(hidden_size, hidden_size) )
# Capa de normalización por lotes (batch normalization) self.bn1 = nn.BatchNorm1d(embed_dim)
# Capa completamente conectada (fully connected) self.fc = nn.Linear(embed_dim, num_class)
def forward(self, text, offsets): # Incrustar el texto (embed the text) embedded = self.embedding(text, offsets)
# Pasar el texto incrustado a través de la capa LSTM (pass the embedded text through the LSTM layer) lstm_out, _ = self.lstm(embedded)
# Aplicar la transformación (apply the transformation) # transformed_out = self.transformation(lstm_out[:, -1, :]) transformed_out = self.transformation(lstm_out[-1, :])
# Aplicar la normalización por lotes (apply batch normalization) embedded_norm = self.bn1(embedded)
# Aplicar la función de activación ReLU (apply the ReLU activation function) embedded_activated = F.relu(embedded_norm)
# Devolver las probabilidades de clase (output the class probabilities) return self.fc(embedded_activated)
from torch import nn
import torch.nn.functional as F
classModeloClasificacionTexto(nn.Module):def__init__(self, vocab_size, embed_dim, hidden_size, num_class):super(ModeloClasificacionTexto, self).__init__()# Capa de incrustación (embedding) self.embedding = nn.EmbeddingBag(vocab_size, embed_dim)# Capa LSTM self.lstm = nn.LSTM(embed_dim, hidden_size)# Capa de transformación self.transformation = nn.Sequential( nn.Linear(hidden_size, hidden_size), nn.ReLU(), nn.Linear(hidden_size, hidden_size))# Capa de normalización por lotes (batch normalization) self.bn1 = nn.BatchNorm1d(embed_dim)# Capa completamente conectada (fully connected) self.fc = nn.Linear(embed_dim, num_class)defforward(self, text, offsets):# Incrustar el texto (embed the text) embedded = self.embedding(text, offsets)# Pasar el texto incrustado a través de la capa LSTM (pass the embedded text through the LSTM layer) lstm_out, _ = self.lstm(embedded)# Aplicar la transformación (apply the transformation)# transformed_out = self.transformation(lstm_out[:, -1, :]) transformed_out = self.transformation(lstm_out[-1,:])# Aplicar la normalización por lotes (apply batch normalization) embedded_norm = self.bn1(embedded)# Aplicar la función de activación ReLU (apply the ReLU activation function) embedded_activated = F.relu(embedded_norm)# Devolver las probabilidades de clase (output the class probabilities)return self.fc(embedded_activated)