¿Por qué mi modelo no aprende? Guía amigable para diagnosticar callejones sin salida en el deep learning
La última vez, Lina logró que su modelo de predicción de compras de BookSight entrenara de manera estable con normalización por lotes y dropout. Ahora, su modelo más reciente, construido de la misma manera, se niega a aprender en absoluto, y está a punto de actuar como mecánica para averiguar por qué.
Has pasado horas configurando tu entorno. Codificaste tus capas, elegiste una función de pérdida y presionaste Ejecutar. Esperas a que ocurra la magia. Entonces lo ves: la curva de pérdida es plana. No disminuye. Ni siquiera oscila. Esa es exactamente la situación en la que se encuentra Lina con su modelo más reciente de BookSight.
Parece un fracaso personal. Pero incluso los investigadores sénior de OpenAI y Google lidian con modelos que se niegan a aprender. Piensa en tu modelo como un motor de auto. Si no arranca, no compras un auto nuevo: revisas la chispa y el combustible, una pieza a la vez. En esta parte de la serie, seremos los mecánicos de tu red neuronal.
Antes de tocar su arquitectura completa, Lina construye el sustituto más simple posible para su modelo estancado: una sola capa lineal que debería aprender a multiplicar por 2. Si este pequeño sustituto no puede aprender, su modelo real no tiene ninguna posibilidad.
import torch
import torch.nn as nn
# A simple model that should learn to multiply by 2
model = nn.Linear(1, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
criterion = nn.MSELoss()
# Dummy data: Input is 1.0, Target is 2.0
x = torch.tensor([[1.0]])
y = torch.tensor([[2.0]])
print("Starting training...")
for epoch in range(5):
optimizer.zero_grad()
output = model(x)
loss = criterion(output, y)
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
import torch,import torch.nn as nn— Las importaciones estándar de PyTorch que se usan en toda esta serie: la biblioteca principal de tensores y la API del módulo de redes neuronales.nn.Linear(1, 1)— Una capa lineal simple con una característica de entrada y una de salida. Este es el modelo más simple posible que debería aprender el mapeo y = 2x — un sustituto simplificado del modelo de predicción de compras estancado de Lina.torch.optim.SGD(model.parameters(), lr=0.001)— Descenso de gradiente estocástico con una tasa de aprendizaje de 0.001. Esto es intencionalmente bajo — lo suficientemente lento como para que la pérdida apenas se mueva en 5 épocas, produciendo la curva “plana como una tabla” que Lina está viendo.nn.MSELoss()— Error cuadrático medio: penaliza la diferencia al cuadrado entre la predicción del modelo y el objetivo. Simple e interpretable para un modelo de juguete de depuración.torch.tensor([[1.0]])/torch.tensor([[2.0]])— Un solo ejemplo de entrenamiento: entrada 1.0, objetivo 2.0. En el modelo real de Lina, la entrada sería un vector de características de la sesión de navegación y el objetivo una etiqueta de compra/no-compra.- El bucle de entrenamiento:
optimizer.zero_grad()limpia los gradientes obsoletos,model(x)hace el paso hacia adelante,criterion(output, y)calcula la pérdida,loss.backward()ejecuta la retropropagación para llenar los gradientes,optimizer.step()aplica la actualización de los pesos, y la instrucciónprintmuestra el valor de la pérdida después de cada época. - Con
lr=0.001, la pérdida apenas se moverá — este es el bucle de entrenamiento “muerto” que Lina está intentando diagnosticar.
Si ejecutas esto y la pérdida se mantiene en 1.2345 en cada época, algo está roto. Más datos no ayudarán aquí. Si el motor no genera chispa, agregar combustible solo inunda el sistema. Necesitamos encontrar el bloqueo específico en las tuberías.
Paso 1: El truco de ‘sobreajustar un lote’
Tu diagnóstico más útil. Antes de entrenar con un millón de imágenes, prueba con cinco. Si tu modelo no puede memorizar 5 filas de datos, la lógica está fundamentalmente rota.
Lo difícil es admitir que el código podría tener un error, y no simplemente “datos malos”. Lo que realmente estás verificando es si la retropropagación de la Parte 2 está realmente conectada a los pesos.
# The 'Overfit One Batch' Strategy
# Take a tiny slice of your data
tiny_x = x[:5]
tiny_y = y[:5]
# Crank the learning rate up and train for 100 iterations
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
for i in range(100):
optimizer.zero_grad()
pred = model(tiny_x)
loss = criterion(pred, tiny_y)
loss.backward()
optimizer.step()
print(f"Final tiny-batch loss: {loss.item():.8f}")
x[:5]/y[:5]— Extrae las primeras 5 filas de los tensores de entrada y objetivo. Con los datos de juguete de un solo ejemplo de arriba, esto produce el mismo tensor de 1 fila; en el modelo real de Lina, esto serían 5 sesiones de navegación extraídas de su conjunto de datos completo.torch.optim.SGD(model.parameters(), lr=0.1)— Recrea el optimizador con la tasa de aprendizaje aumentada 100x (de 0.001 a 0.1). La idea es forzar una convergencia rápida en el lote diminuto: si el modelo puede aprender, debería aprender de forma agresiva aquí.for i in range(100):— Ejecuta 100 iteraciones de entrenamiento (no épocas) en las mismas 5 filas. Esto es memorización por fuerza bruta: si la pérdida no se acerca a cero después de ver los mismos 5 ejemplos 100 veces, la lógica de entrenamiento en sí está rota.loss.item():.8f— Imprime la pérdida final con 8 decimales. Un resultado saludable sería algo como0.00000001; un resultado como2.34567890significa que tienes un “error silencioso”: la plomería está desconectada en algún lugar.- Los errores silenciosos más comunes que detecta esta prueba: olvidar
optimizer.step(), calcular la pérdida en la variable equivocada o tener un grafo de computación desconectado (por ejemplo, separar un tensor con.detach()en el punto equivocado).
Si la pérdida no se acerca a cero aquí, probablemente tengas un error silencioso. Quizás olvidaste optimizer.step(), o estás calculando la pérdida en la variable equivocada. Si sí llega a cero, tu plomería funciona. El problema reside en otro lado.
El arte de desaparecer: cuando los gradientes tienden a cero
¿Recuerdas el “Juego del Teléfono” de la Parte 2? En las redes profundas, la “culpa” (el gradiente) tiene que viajar desde la última capa hasta la primera. Si la señal se va debilitando en cada paso, las primeras capas nunca escuchan las instrucciones. Esto es el Gradiente Desvanecido.
Para detectarlo, observamos la magnitud de tus pesos. Gradientes minúsculos significan que los pesos no se mueven. Esto es lo que aparece cuando imprimimos la “norma del gradiente”: una medida de la fuerza total de la señal.
# Let's check the 'pulse' of our layers
for name, param in model.named_parameters():
if param.grad is not None:
grad_norm = param.grad.norm().item()
print(f"Layer: {name} | Gradient Strength: {grad_norm:.10f}")
model.named_parameters()— Itera sobre cada parámetro aprendible del modelo, produciendo tuplas(name, tensor). Para elnn.Linear(1, 1)individual de Lina, esto devuelve dos entradas:weightybias.param.grad— El tensor del gradiente para este parámetro, que es llenado porloss.backward(). Sibackward()aún no ha sido llamado (o si el parámetro no forma parte del gráfico de cálculo),gradseráNone— el guardiaif param.grad is not Noneomite esos casos.param.grad.norm().item()— Calcula la norma L2 (longitud Euclidiana) del vector del gradiente y lo convierte a un valor float de Python. Este número único te indica la “fuerza” total de la señal de aprendizaje que llega a este parámetro.- Una norma del gradiente de
0.0000000000significa que la señal murió antes de llegar a esta capa — los pesos de la capa nunca se actualizarán, sin importar cuántas épocas entrenes. Esta es la marca distintiva de un gradiente desvanecido. - En el modelo más profundo de predicción de compras de Lina, ella revisaría la norma del gradiente de cada capa; si las primeras capas muestran
0.0000000000mientras que las últimas capas muestran valores saludables, la señal se está muriendo en tránsito.
Interpretación: Si ese número es 0.0000000000, tu modelo es efectivamente sordo. La señal murió antes de llegar a esa capa. Esto suele ocurrir con funciones de activación equivocadas, o cuando la red es demasiado profunda sin los “atajos” que veremos en los capítulos de Atención.
La zona de Ricitos de Oro: tasas de aprendizaje e inicialización
A veces el modelo está bien. La velocidad simplemente es incorrecta. La tasa de aprendizaje es como el ritmo de un excursionista: demasiado rápido y te pasas del valle, demasiado lento y nunca sales de casa.
La inicialización de los pesos también importa. Inicia todos los pesos exactamente en cero y cada neurona hará lo mismo. Piensa en un coro donde todos cantan la misma nota: no es posible ninguna armonía. El modelo necesita algo de ruido aleatorio al principio para que las neuronas puedan empezar a distinguir las características.
# Too high vs Too low learning rate
for lr in [10.0, 0.0000001]:
# Reset model
m = nn.Linear(1, 1)
opt = torch.optim.SGD(m.parameters(), lr=lr)
# ... training loop ...
print(f"Testing LR {lr}: Resulting loss was either exploding or stagnant.")
for lr in [10.0, 0.0000001]:— Prueba dos tasas de aprendizaje extremas: una absurdamente alta (10.0) y otra absurdamente baja (0.0000001). El objetivo es mostrar los dos modos de falla en los extremos opuestos del espectro.m = nn.Linear(1, 1)— Crea un modelo nuevo para cada prueba de tasa de aprendizaje. Si reutilizaras el mismo modelo, los pesos de la primera prueba contaminarían la segunda.torch.optim.SGD(m.parameters(), lr=lr)— Aplica la tasa de aprendizaje probada al optimizador. Conlr=10.0, las actualizaciones de los pesos son tan grandes que la pérdida supera el mínimo y diverge aNaN; conlr=0.0000001, las actualizaciones son tan minúsculas que la pérdida parece completamente plana.- El comentario
# ... training loop ...es ilustrativo: en la práctica, rellenarías la misma secuencia dezero_grad/ forward /loss/backward/stepque se utiliza en todo este artículo. - El diagnóstico real de Lina probaría un rango como
[0.1, 0.01, 0.001]: uno de esos normalmente produce un aprendizaje visible, que es como sabría el valor aproximado correcto para su modelo.
Establece la tasa de aprendizaje en 10.0 y tu pérdida se convierte en NaN (Not a Number) — los pesos se disparan hacia el infinito. Redúcela a 0.0000001 y la pérdida parecerá plana. Los pesos apenas se mueven.
¿Tus datos te están mintiendo?
Si el modelo y el optimizador están bien, el problema es el “combustible”. A las redes neuronales no les gustan los números grandes. Quieren que todo esté entre 0 y 1. Si introduces 1000.0 con un objetivo de 0.001, las matemáticas se vuelven inestables.
Tu código puede ejecutarse perfectamente y aún así aprender basura de datos desordenados. Revisa tus tensores antes de que entren al modelo. La característica de tiempo en la página de Lina, por ejemplo, podría variar de 0 a 3600 segundos si olvida normalizarla.
def check_data(tensor):
print(f"Mean: {tensor.mean():.2f}")
print(f"Max: {tensor.max():.2f}")
print(f"Min: {tensor.min():.2f}")
check_data(x)
def check_data(tensor):— Una función de diagnóstico simple que imprime tres estadísticas de resumen para cualquier tensor. Este es el tipo de verificación rápida que Lina realiza en cada característica antes de pasarla a su modelo.tensor.mean()— El valor promedio de todos los elementos. Si este es500.0en lugar de cerca de0.0, el optimizador tendrá dificultades; la superficie de pérdida se convierte en un barranco largo y estrecho que es difícil de navegar.tensor.max()/tensor.min()— El rango de valores en el tensor. Si el máximo es3600y el mínimo es0(tiempo en la página en segundos sin normalizar), los gradientes estarán dominados por las características de gran magnitud y las pequeñas serán ignoradas.check_data(x)— Se llama en el tensor de entradax. En el modelo real de Lina, llamaría a esta función en cada columna de características de sus datos de sesión de navegación — tiempo en la página, artículos en el carrito, profundidad de desplazamiento, etc. — antes de concatenarlos en el vector de entrada.- La regla general: si tus datos no están aproximadamente en el rango [-1, 1] (o [0, 1]), resta la media y divide por la desviación estándar antes de pasarlos al modelo.
En la práctica: si tu media es 500.0, escala los datos. Resta el promedio y divide por la dispersión para que la media quede en 0.0. La superficie de pérdida se vuelve más suave para que el optimizador pueda navegarla.
Tu lista de verificación para diagnóstico
Cuando tu modelo deja de aprender, no entres en pánico. Recorre la lista de comprobación:
- Sobreajusta un lote pequeño: ¿Puede aprender 5 filas? Si no, revisa la lógica de tu código.
- Revisa tus gradientes: ¿Los números se mueven o son ceros?
- Escala tus datos: ¿Está todo aproximadamente entre -1 y 1?
- Revisa la tasa de aprendizaje: Prueba con 0.1, 0.01 y 0.001. Uno de ellos suele marcar la diferencia.
¿Qué técnica de depuración usar primero?
Las cuatro técnicas de este artículo abordan cada una un modo de fallo diferente. Aquí se indica cuál usar dependiendo de lo que estés observando:
| Síntoma | Primera técnica | Lo que detecta | Cuándo usarla | Compromiso |
|---|---|---|---|---|
| La pérdida es completamente plana desde la época 1 | Sobreajustar un lote | Lógica de entrenamiento rota — falta optimizer.step(), grafo de cálculo desconectado, variable de pérdida incorrecta. | Siempre empieza aquí. Si el modelo no puede memorizar 5 filas, ningún ajuste de hiperparámetros lo salvará. | No diagnostica problemas de datos o hiperparámetros — solo descarta errores de código. |
| La pérdida cae y luego se estanca abruptamente en una red profunda | Revisar las normas de gradiente | Desvanecimiento de gradientes — la señal del “teléfono descompuesto” muriendo antes de alcanzar las primeras capas. | Después de que pase la prueba de sobreajustar un lote, pero las capas profundas aún no aprendan. Busca normas de gradiente cercanas a cero específicamente en las primeras capas. | Solo diagnostica el flujo del gradiente, no la calidad de los datos ni la tasa de aprendizaje. |
La pérdida es NaN o explota al infinito | Barrido de la tasa de aprendizaje | Tasa de aprendizaje demasiado alta — los pesos se pasan del objetivo y divergen. | Inmediatamente cuando veas NaN. Reduce la tasa de aprendizaje por 10x y vuelve a intentarlo. | Una tasa de aprendizaje demasiado baja (el otro extremo) produce la pérdida plana con la que empezaste — el barrido encuentra el punto óptimo intermedio. |
| La pérdida disminuye pero el modelo se desempeña mal con datos nuevos | Revisión del escalado de datos | Entradas sin escalar que hacen que el paisaje de pérdida sea irregular — p. ej., tiempo en la página en segundos (0–3600) en lugar de normalizado (0–1). | Después de que el modelo entrena pero la precisión se queda corta. Comprueba la media, el máximo y el mínimo de cada característica de entrada. | El escalado por sí solo no arreglará un bucle de entrenamiento roto ni un desvanecimiento de gradiente — es un problema de combustible, no del motor. |
| La pérdida oscila pero nunca se asienta | Barrido de la tasa de aprendizaje (más fino) | Tasa de aprendizaje ligeramente demasiado alta — rebotando alrededor del mínimo sin converger. | Después de que pasen otras revisiones. Prueba tasas separadas por un orden de magnitud: 0.1, 0.01, 0.001. | Consume mucho tiempo — cada tasa requiere una ejecución completa de entrenamiento para su evaluación. |
El recorrido de diagnóstico de Lina: Su pérdida se mantuvo plana desde la época 1, así que comenzó con la prueba de sobreajuste de un solo lote —la cual falló, revelando que su optimizador no estaba llamando realmente a optimizer.step(). Una sola línea faltante era la culpable, no su arquitectura ni sus datos. La lista de comprobación la salvó de horas de conjeturas.
Y con esto, el viaje de Lina cierra el círculo. Comenzó con una sola neurona decidiendo si un visitante compraría un libro. Ahora ejecuta un modelo completo basado en transformers para BookSight. Está construido sobre retropropagación, mantenido entrenable al resolver el desvanecimiento de gradientes, ampliado para procesar imágenes para la verificación de portadas, dotado de memoria para sesiones largas mediante RNNs y luego LSTMs, acelerado y afinado por atención y atención multicabeza, ensamblado en un mini-transformer funcional, hecho práctico mediante transfer learning, estabilizado por batch norm y dropout, y cuando falló, depurado sistemáticamente en lugar de adivinar. Cada pieza fue construida desde cero, un concepto a la vez.
Comprueba tu comprensión
Las preguntas a continuación avanzan desde el simple recuerdo hasta el diseño de preguntas abiertas, siguiendo aproximadamente la Taxonomía de Bloom.
Recordar ¿Cuál es el truco de “Overfit One Batch” (sobreajustar un lote) y qué te indica si la pérdida (loss) no se acerca a cero?
Comprender Con tus propias palabras, explica por qué inicializar todos los pesos de una red exactamente a cero evita que el modelo aprenda, utilizando la analogía del “coro” del artículo.
Aplicar
El artículo prueba tasas de aprendizaje de 10.0 y 0.0000001 y dice que una hace que la pérdida explote a NaN mientras que la otra se ve “plana”. Dada la lista de verificación de diagnóstico del artículo, si vieras una curva de pérdida plana durante 100 epochs, ¿qué dos elementos de la lista (de los cuatro enumerados) probarías primero y en qué orden, y por qué ese orden?
Analizar
El artículo dice que comprobar las normas del gradiente puede revelar un Vanishing Gradient (gradiente desvanecido), donde un valor impreso de 0.0000000000 significa que “tu modelo es efectivamente sordo”. Explica paso a paso por qué una norma de gradiente cercana a cero en la primera capa específicamente (y no en la última capa) apunta a un problema de gradiente desvanecido en lugar de, por ejemplo, una mala tasa de aprendizaje.
Evaluar La lista de verificación de diagnóstico del artículo es una lista fija y ordenada: sobreajustar un lote pequeño, comprobar los gradientes, escalar los datos, comprobar la tasa de aprendizaje. Critica este orden: ¿es “sobreajustar un lote pequeño” realmente siempre el primer paso correcto, o se te ocurre algún síntoma (algo en la curva de pérdida o en el código) en el que querrías comprobar la tasa de aprendizaje o la escala de los datos antes de intentar sobreajustar un lote pequeño?
Crear Diseña una “lista de verificación previa al vuelo” que un científico de datos pueda ejecutar antes de comenzar una ejecución de entrenamiento completa (no después de que falle) y que detecte al menos dos de los cuatro modos de falla de este artículo con anticipación. Nombra las comprobaciones específicas y qué señal de alarma buscaría cada una.
Artículos relacionados
- Normalización por lotes y Dropout: Los trucos de regularización que hacen que el aprendizaje profundo realmente funcione
- Redes neuronales sin cálculo: ¿Qué ocurre realmente dentro de una única neurona?
Referencias y lecturas adicionales
- Karpathy, A. (2019). “Una receta para entrenar redes neuronales.” — La guía práctica definitiva para la depuración y el entrenamiento de redes neuronales, escrita por Andrej Karpathy (exdirector de IA en Tesla). Codifica la misma filosofía de diagnóstico que Lina sigue aquí: comenzar con la prueba más simple posible, verificar tus datos antes de culpar a tu modelo, sobreajustar un lote antes de escalar y nunca asumir que tu código es correcto hasta que se demuestre lo contrario. Disponible en karpathy.github.io.
- Documentación de PyTorch: Recetas para la solución de problemas y optimización del rendimiento — Recetas oficiales de PyTorch para escenarios comunes de depuración, que incluyen la verificación de gradientes, la programación de la tasa de aprendizaje y la normalización de datos — las mismas técnicas que Lina utilizó para diagnosticar su estancado modelo de BookSight.
Esta traducción fue generada automáticamente y puede contener errores. Si el idioma inglés es tu preferencia, puedes leer el artículo original en inglés .
«Aplica lo que aprendiste» es para suscriptores Supporter e Insider.
Suscríbete para desbloquear los ejercicios de este artículo.
Ver planesArtículos relacionados
- Aprendizaje Profundo En revisión
Referencia: Optimizadores
Una referencia que cubre los optimizadores de redes neuronales desde GD hasta AdamW, con programación de la tasa de aprendizaje, árboles de decisión y guía práctica para cada arquitectura.
- Aprendizaje Profundo En revisión
Por qué las computadoras ven mejor con las CNN: una guía intuitiva para el reconocimiento de imágenes
Aprende por qué las CNN superan a las redes densas en el reconocimiento de imágenes utilizando filtros deslizantes, pesos compartidos y pooling para detectar patrones espaciales de manera eficiente.
- Aprendizaje Profundo En revisión
Atención de múltiples cabezas y codificación posicional: Dándole al Transformer un sentido de dirección
Descubre cómo la codificación posicional le otorga a los Transformers noción del orden de las palabras y la atención de múltiples cabezas rastrea múltiples patrones a la vez en esta guía práctica.
- Aprendizaje Profundo En revisión
El mecanismo de atención, finalmente explicado (sin álgebra matricial)
Comprende el mecanismo de atención que impulsa todos los modelos de lenguaje modernos: consultas, claves, valores y softmax explicados con analogías y código Python.
¿Buscas otra cosa?
Busca en todos los artículos por título, resumen o tema.