En el tema anterior aprendimos a construir una red neuronal simple en PyTorch. Definimos su arquitectura, sus capas, su función forward y vimos cómo producir una salida a partir de una entrada.
Pero hasta ese momento el modelo todavía no había aprendido. Solo habíamos creado su estructura y lo habíamos hecho funcionar.
En este tema vamos a estudiar el paso que realmente convierte a una red en un modelo útil: el entrenamiento. Aquí es donde la red ajusta sus parámetros para reducir el error y mejorar sus predicciones.
Entrenar un modelo significa mostrarle ejemplos de datos y permitir que ajuste sus parámetros internos para producir mejores resultados.
En términos prácticos, entrenar consiste en repetir un ciclo como este:
Ese ciclo se repite muchas veces hasta que el modelo mejora lo suficiente.
Todo lo que ocurre durante el entrenamiento ya lo habíamos visto conceptualmente en temas anteriores.
Por ejemplo:
La diferencia es que ahora veremos cómo todo eso se implementa concretamente en PyTorch.
Para entrenar un modelo en PyTorch, en general necesitamos cuatro elementos básicos:
Sin estos componentes no podemos cerrar el ciclo de aprendizaje.
El modelo produce predicciones, la pérdida mide el error y el optimizador usa ese error para ajustar parámetros.
Para aprender, conviene trabajar con un caso muy simple. Imaginemos que queremos que una red aprenda una relación entre entradas y salidas numéricas.
Podríamos tener datos de este tipo:
Este ejemplo representa un problema de regresión, no de clasificación. Decimos que es regresión porque la salida esperada es un valor numérico continuo y no una categoría como “sí” o “no”.
Si observamos los datos, vemos además una relación muy clara:
Por ejemplo, cuando la entrada es 3.0, la salida esperada es 6.0; cuando la entrada es 4.0, la salida esperada es 8.0. Entonces, lo que queremos que la red aprenda es esa regla numérica sencilla que conecta cada entrada con su salida.
No buscamos todavía un problema realista, sino un ejemplo que permita entender el mecanismo.
Como estamos trabajando en PyTorch, los datos deben representarse con tensores.
Aquí:
X contiene las entradas.y contiene las salidas correctas.Usamos X en mayúscula porque representa el conjunto completo de datos de entrada, es decir, varias observaciones juntas. En muchos contextos se reserva la minúscula x para una sola observación o un solo dato de entrada.
X = lo que le damos al modelo.
y = lo que queremos que aprenda a predecir.
Observa que ambos tensores tienen forma de lote: varias filas y una sola característica o salida por fila.
Para este ejemplo podemos usar una red muy simple con una sola capa lineal.
Este modelo recibe una entrada de tamaño 1 y produce una salida de tamaño 1.
Es un caso muy pequeño, pero perfecto para estudiar el entrenamiento sin distraernos con una arquitectura complicada.
Una vez definida la clase, debemos crear el modelo:
Desde ese momento, el modelo ya tiene parámetros iniciales. Esos parámetros todavía no están bien ajustados, pero ya existen y pueden modificarse durante el entrenamiento.
Para entrenar, necesitamos medir qué tan equivocada es la red. Para eso usamos una función de pérdida.
En problemas sencillos de regresión, una elección muy común es el error cuadrático medio, representado en PyTorch por nn.MSELoss().
Esta función compara la salida predicha con la salida real y produce un valor numérico que resume el error.
Después de medir el error, necesitamos un mecanismo para ajustar los parámetros. Ese mecanismo es el optimizador.
Una opción clásica y muy didáctica es SGD (descenso del gradiente estocástico).
Aquí le estamos diciendo a PyTorch:
0.01.La tasa de aprendizaje, o learning rate, indica qué tan grande será cada ajuste de parámetros.
Si es demasiado grande, el modelo puede volverse inestable. Si es demasiado pequeña, el entrenamiento puede volverse muy lento.
Por eso, elegirla bien es importante. En ejemplos sencillos suele usarse un valor pequeño como punto de partida.
Con todos los elementos listos, el entrenamiento sigue casi siempre una secuencia fundamental:
backward().step().Este es el corazón del entrenamiento en PyTorch.
El primer paso del ciclo es hacer que el modelo procese las entradas y produzca una salida:
Aquí el modelo toma el tensor de entrada X y genera una salida.
Esta etapa corresponde a la propagación hacia adelante que ya habíamos estudiado en teoría.
Una vez obtenida la predicción, debemos medir qué tan lejos está del valor correcto:
El resultado será un tensor escalar que resume el error total según la función elegida.
Cuanto más pequeña sea la pérdida, mejor estará funcionando el modelo sobre esos datos.
Antes de calcular nuevos gradientes, en PyTorch normalmente debemos limpiar los gradientes anteriores:
Esto es necesario porque PyTorch acumula gradientes por defecto. Si no los reiniciamos, los nuevos gradientes se sumarían a los anteriores y obtendríamos actualizaciones incorrectas.
Este paso es muy importante y suele olvidarse al principio.
Después de calcular la pérdida, le pedimos a PyTorch que obtenga automáticamente los gradientes:
Esta llamada activa el sistema de autograd y calcula cómo afecta cada parámetro al valor final de la pérdida.
En otras palabras, aquí ocurre la parte práctica del backpropagation.
Una vez que los gradientes están disponibles, el optimizador puede usarlos para cambiar los parámetros:
Este paso modifica pesos y bias intentando reducir la pérdida en la siguiente iteración.
Si repetimos este proceso muchas veces, el modelo debería ir mejorando gradualmente.
Si juntamos todo, el ciclo básico queda así:
Estas pocas líneas representan el núcleo del entrenamiento de muchísimos modelos en PyTorch.
Un modelo no aprende con una sola actualización. Normalmente necesitamos repetir el ciclo muchas veces.
Cada vez que el modelo recorre el conjunto de datos y realiza un paso completo de ajuste, hablamos de una época.
Por eso es habitual envolver el ciclo de entrenamiento en un bucle:
La cantidad de épocas dependerá del problema, del modelo y de la velocidad de aprendizaje.
Veamos un ejemplo completo y sencillo:
Este ejemplo ya entrena de verdad un modelo simple.
Para entender si el modelo está mejorando, suele ser útil imprimir la pérdida cada cierta cantidad de épocas.
De esta forma podemos observar si la pérdida baja con el tiempo.
La variable perdida es un tensor. Si queremos obtener su valor numérico como un dato de Python, usamos item().
Esto resulta muy útil para mostrar el error en pantalla o guardarlo en una lista para luego graficarlo.
En un caso bien planteado, la pérdida debería tender a bajar a medida que pasan las épocas.
No necesariamente disminuirá de forma perfecta en cada iteración, pero en general esperamos una tendencia descendente.
Si eso ocurre, es una señal de que el modelo está aprendiendo a ajustar sus parámetros en la dirección correcta.
Si la pérdida no disminuye, puede deberse a varias causas:
Por eso, observar la pérdida no solo sirve para medir progreso, sino también para detectar problemas.
Otro punto importante es no confundir el entrenamiento con la evaluación del modelo.
Durante el entrenamiento, el modelo ajusta sus parámetros. Durante la evaluación, en cambio, verificamos cómo se comporta una vez entrenado.
En este tema nos enfocamos principalmente en el proceso de aprendizaje. Más adelante veremos con más detalle cómo evaluar correctamente el desempeño del modelo.
En ejemplos pequeños podemos usar todo el conjunto de datos de una sola vez. Sin embargo, en problemas reales es habitual dividir los datos en lotes o batches.
Eso significa que en cada iteración el modelo ve solo una parte del conjunto total.
Aunque todavía no profundizaremos en DataLoader, conviene saber desde ahora que entrenar por lotes es una práctica muy común.
En PyTorch existe un modo de entrenamiento que se activa con:
En redes muy simples esto puede no cambiar demasiado, pero en modelos que incluyen capas como Dropout o Batch Normalization es muy importante.
Por eso es una buena costumbre activar explícitamente el modo entrenamiento antes de comenzar el ciclo.
Una vez terminado el entrenamiento, podemos probar el modelo con una entrada y observar la salida:
Si el modelo aprendió bien la relación de los datos, debería producir un valor razonable para esa nueva entrada.
Esto ayuda a ver que el entrenamiento produjo un cambio real en el comportamiento del modelo.
Al comenzar, no es necesario memorizar cada línea del ciclo de entrenamiento de manera mecánica. Lo verdaderamente importante es comprender el sentido de cada paso.
Un estudiante debería poder responder preguntas como estas:
backward()?Si esas ideas están claras, el código se vuelve mucho más fácil de recordar y usar correctamente.
Algunos errores muy frecuentes al empezar son:
zero_grad().Todos estos errores son normales al principio. Lo importante es detectarlos y entender por qué afectan al entrenamiento.
Si estás empezando a entrenar modelos, estas recomendaciones suelen ser muy útiles:
Estas prácticas ayudan a construir una base sólida y evitar frustraciones innecesarias.
Podemos resumir el entrenamiento de un modelo en PyTorch así:
Ese es el mecanismo fundamental por el cual una red neuronal aprende.
El entrenamiento de un modelo en PyTorch es el momento en que la red deja de ser una estructura estática y comienza realmente a aprender a partir de datos. Todo lo que estudiamos antes converge aquí: arquitectura, forward propagation, pérdida, gradientes y optimización.
Dominar este tema significa haber dado un paso enorme en la parte práctica del Deep Learning. A partir de aquí, ya es posible construir modelos sencillos y entrenarlos de verdad.
En el próximo tema veremos cómo realizar la evaluación del modelo, es decir, cómo medir su desempeño una vez entrenado.