Módulo 5 · Redes neuronales
Loop de entrenamiento
En esta páginaEl problema
El problema
Tienes 200 puntos generados con make_moons(n_samples=200, noise=0.2, random_state=0). Cada punto tiene dos características y una clase, 0 o 1. Estas son las primeras cuatro filas:
| punto | x1 | x2 | clase |
|---|---|---|---|
| P-1 | 0.74 | 0.46 | 0 |
| P-2 | 1.66 | −0.63 | 1 |
| P-3 | −0.16 | 0.26 | 1 |
| P-4 | −1.09 | −0.40 | 0 |
Separas 150 puntos para entrenamiento y 50 para validación. El modelo es un MLP 2 → 16 → 2 con ReLU, y la pérdida es CrossEntropyLoss. Con los pesos iniciales, los logits no tienen relación con las clases.
En Tensores y autograd calculaste un gradiente y cambiaste un peso una vez. Ahora ese cálculo se repite cientos de veces, sobre grupos de puntos, para todos los parámetros del MLP. Necesitas un loop que lo haga y una forma de comprobar que el modelo aprende, tanto en los 150 puntos de entrenamiento como en los 50 que nunca usa para actualizar pesos.
Batches con DataLoader
Un batch es un grupo de ejemplos que el modelo procesa junto. TensorDataset empareja tensores fila por fila: el elemento i es la tupla (X[i], y[i]). DataLoader recorre ese dataset y entrega un batch en cada iteración. Un recorrido completo por el dataset es una época.
Con shuffle=True, el DataLoader baraja los ejemplos. El argumento generator recibe un generador de números aleatorios con semilla fija.
La celda plegada define recorrer, que recorre el cargador una vez, es decir, una época, y devuelve cada batch como una lista de números.
Mostrar códigoOcultar código
def recorrer(cargador):
return [batch[0].tolist() for batch in cargador]
Pregunta. Un dataset de 10 números se recorre con batch_size=4. ¿Cuántos batches hay por época y de qué tamaño es el último? ¿La segunda época repite el orden de la primera? ¿Un segundo DataLoader con la misma semilla repite el orden del primero?
import torch
from torch.utils.data import DataLoader, TensorDataset
numeros = TensorDataset(torch.arange(10))
cargador_a = DataLoader(
numeros, batch_size=4, shuffle=True, generator=torch.Generator().manual_seed(0)
)
epoca_1 = recorrer(cargador_a)
epoca_2 = recorrer(cargador_a)
print(epoca_1)
print(epoca_2)
cargador_b = DataLoader(
numeros, batch_size=4, shuffle=True, generator=torch.Generator().manual_seed(0)
)
print(recorrer(cargador_b) == epoca_1)
La salida esperada está oculta.
Ahora el dataset real. La celda plegada genera los 200 puntos con make_moons y los separa con train_test_split: 50 para validación y el resto para entrenamiento, con la misma proporción de clases en las dos partes gracias a stratify=y.
Mostrar códigoOcultar código
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
X, y = make_moons(n_samples=200, noise=0.2, random_state=0)
X_ent, X_val, y_ent, y_val = train_test_split(
X, y, test_size=50, random_state=0, stratify=y
)
X_ent y X_val son arreglos de NumPy con dos coordenadas por punto. y_ent y y_val guardan la clase de cada punto, 0 o 1. X_ent tiene forma (150, 2) y X_val, forma (50, 2). La media de y_ent y la de y_val valen 0.5: la mitad de los puntos de cada parte es de clase 1.
torch.tensor convierte los arreglos en tensores. Las características pasan a float32 con dtype=torch.float32, el tipo de los pesos de nn.Linear. Las etiquetas quedan como enteros, el tipo que pide CrossEntropyLoss. crear_cargador devuelve un DataLoader nuevo con su propio generador semillado, así que cada llamada recorre los batches en el mismo orden.
X_entrenamiento = torch.tensor(X_ent, dtype=torch.float32)
y_entrenamiento = torch.tensor(y_ent)
X_validacion = torch.tensor(X_val, dtype=torch.float32)
y_validacion = torch.tensor(y_val)
datos_entrenamiento = TensorDataset(X_entrenamiento, y_entrenamiento)
def crear_cargador(batch_size=32, semilla=0):
generador = torch.Generator().manual_seed(semilla)
return DataLoader(
datos_entrenamiento, batch_size=batch_size, shuffle=True, generator=generador
)
next(iter(cargador)) toma el primer batch sin recorrer la época completa.
Pregunta. Con 150 ejemplos y batch_size=32, escribe el número de batches por época, el tamaño de cada uno y la forma y el tipo de xb y yb en el primer batch.
cargador = crear_cargador()
xb, yb = next(iter(cargador))
print(len(datos_entrenamiento), len(cargador))
print(xb.shape, xb.dtype)
print(yb.shape, yb.dtype)
print([len(yb) for _, yb in cargador])
La salida esperada está oculta.
Pasos del loop
Un optimizador recibe los parámetros del modelo con modelo.parameters() y sabe cómo cambiarlos. Para cada batch, el loop repite cinco pasos:
optimizador.zero_grad()borra los gradientes guardadosmodelo(xb)calcula los logits (forward)perdida_fn(logits, yb)calcula la pérdida del batchperdida.backward()suma a.gradel gradiente de cada parámetrooptimizador.step()cambia cada parámetro con su gradiente
torch.optim.SGD recibe además un número lr, el learning rate, que fija cuánto pesa el gradiente en cada cambio. crear_modelo fija la semilla antes de crear las capas, así que cada llamada devuelve los mismos pesos iniciales.
from torch import nn
def crear_modelo():
torch.manual_seed(0)
return nn.Sequential(nn.Linear(2, 16), nn.ReLU(), nn.Linear(16, 2))
perdida_fn = nn.CrossEntropyLoss()
modelo = crear_modelo()
optimizador = torch.optim.SGD(modelo.parameters(), lr=0.1)
xb, yb = next(iter(crear_cargador()))
La celda siguiente aplica los cinco pasos a ese batch. Antes de step() guarda una copia del peso de la primera capa con .detach().clone() y una del gradiente con .clone(). Después recalcula la pérdida del mismo batch dentro de torch.no_grad().
Pregunta. Después de un solo step(), ¿la pérdida del mismo batch sube o baja? ¿El peso nuevo de la primera capa es igual a peso_antes − 0.1 · gradiente?
optimizador.zero_grad()
logits = modelo(xb)
perdida = perdida_fn(logits, yb)
perdida.backward()
peso_antes = modelo[0].weight.detach().clone() # copia que step() no modifica
gradiente = modelo[0].weight.grad.clone()
optimizador.step()
with torch.no_grad():
perdida_despues = perdida_fn(modelo(xb), yb)
print(round(perdida.item(), 4), round(perdida_despues.item(), 4))
print(torch.allclose(modelo[0].weight, peso_antes - 0.1 * gradiente))
La salida esperada está oculta.
Una época repite esos pasos para cada batch. La función entrenar_epoca devuelve la pérdida media de la época. Cada pérdida de batch se multiplica por el tamaño del batch, así el batch de 22 ejemplos pesa menos que uno de 32. perdida.item() extrae un número de Python y no conserva el registro de operaciones.
def entrenar_epoca(modelo, cargador, optimizador):
suma = 0.0
for xb, yb in cargador:
optimizador.zero_grad()
perdida = perdida_fn(modelo(xb), yb)
perdida.backward()
optimizador.step()
suma += perdida.item() * len(yb)
return suma / len(cargador.dataset) # entre 150 ejemplos, no entre 5 batches
La segunda función es idéntica, pero omite optimizador.zero_grad().
def entrenar_epoca_sin_limpiar(modelo, cargador, optimizador):
suma = 0.0
for xb, yb in cargador:
perdida = perdida_fn(modelo(xb), yb) # sin zero_grad() antes
perdida.backward()
optimizador.step()
suma += perdida.item() * len(yb)
return suma / len(cargador.dataset)
Cada función entrena 10 épocas con SGD y lr=0.1, desde el mismo modelo inicial y el mismo cargador.
Pregunta. Durante 10 épocas, ¿con cuál de las dos funciones baja la pérdida de forma sostenida? ¿Qué gradiente usa step() en la segunda función a partir del segundo batch?
Ver resultado
Cada fila muestra la pérdida media de entrenamiento de una época. La columna entrenar_epoca corresponde a la función que llama a zero_grad() en cada batch, y la columna entrenar_epoca_sin_limpiar a la que lo omite. Por ejemplo, la fila 6 dice que en la sexta época la pérdida es 0.503 con zero_grad() y 0.430 sin él.
| época | entrenar_epoca | entrenar_epoca_sin_limpiar |
|---|---|---|
| 1 | 0.711 | 0.679 |
| 2 | 0.634 | 0.557 |
| 3 | 0.588 | 0.435 |
| 4 | 0.556 | 0.394 |
| 5 | 0.527 | 0.389 |
| 6 | 0.503 | 0.430 |
| 7 | 0.481 | 0.440 |
| 8 | 0.461 | 0.484 |
| 9 | 0.442 | 0.456 |
| 10 | 0.425 | 0.511 |
zero_grad(), la pérdida baja en cada época. Sin él, backward() suma cada gradiente nuevo a todos los anteriores, y step() usa esa suma. En las primeras épocas, la suma produce pasos más grandes y la pérdida baja más rápido. Después la suma arrastra gradientes de pesos que ya cambiaron y los pasos quedan demasiado grandes: desde la época 6 la pérdida sube y oscila. Una pérdida que baja al principio no demuestra que el loop esté bien.Sobreajuste de un batch
Un MLP con 82 parámetros puede memorizar las etiquetas de 8 ejemplos. Si el loop es correcto, entrenar muchas veces sobre ese único batch debe llevar su pérdida cerca de cero. Si no ocurre, el defecto está en el loop o en el modelo, no en la dificultad de los datos. La prueba tarda segundos y se hace antes de entrenar con el dataset completo.
sobreajustar repite los cinco pasos sobre los mismos 8 ejemplos. Al final cuenta los aciertos con argmax(dim=1), que elige en cada fila la clase con el logit mayor.
Pregunta. El código entrena 500 pasos con SGD y lr=0.5 sobre los mismos 8 ejemplos e imprime la pérdida en los pasos 1, 50, 100, 200 y 500. ¿En cuál de esos pasos queda la pérdida por primera vez por debajo de 0.1?
x_mini, y_mini = X_entrenamiento[:8], y_entrenamiento[:8]
def sobreajustar(modelo, optimizador, pasos=500):
for paso in range(1, pasos + 1):
optimizador.zero_grad()
perdida = perdida_fn(modelo(x_mini), y_mini)
perdida.backward()
optimizador.step()
if paso in (1, 50, 100, 200, 500):
print(paso, round(perdida.item(), 4))
with torch.no_grad():
aciertos = (modelo(x_mini).argmax(dim=1) == y_mini).sum().item()
print("aciertos:", aciertos, "de 8")
modelo = crear_modelo()
sobreajustar(modelo, torch.optim.SGD(modelo.parameters(), lr=0.5))
La salida esperada está oculta.
Un error frecuente es crear el optimizador con los parámetros de otro modelo, por ejemplo tras volver a llamar a crear_modelo().
Pregunta. El optimizador recibe los parámetros de otro_modelo, pero el loop calcula la pérdida con modelo. ¿Qué pasa con la pérdida en 500 pasos?
modelo = crear_modelo()
otro_modelo = crear_modelo()
sobreajustar(modelo, torch.optim.SGD(otro_modelo.parameters(), lr=0.5))
La salida esperada está oculta.
Optimizador y learning rate
En SGD, el learning rate multiplica el gradiente en cada paso, así que fija el tamaño del cambio de los pesos.
La función entrenar_varias crea el mismo modelo inicial y el mismo cargador para cada configuración. Así, lo único que cambia entre corridas es el optimizador. Recibe una función que crea el optimizador a partir de los parámetros del modelo nuevo, aquí una lambda. resumen toma la pérdida de cuatro épocas. Las listas muestran la pérdida de entrenamiento en las épocas 1, 10, 20 y 30.
Pregunta. Ordena lr = 0.01, 0.1, 1.0 y 10.0 según la pérdida en la época 30. ¿Con cuál sube la pérdida en la primera época en lugar de bajar?
def entrenar_varias(crear_optimizador, epocas=30):
modelo = crear_modelo()
optimizador = crear_optimizador(modelo.parameters())
cargador = crear_cargador()
return [entrenar_epoca(modelo, cargador, optimizador) for _ in range(epocas)]
def resumen(perdidas):
return [round(perdidas[i], 3) for i in (0, 9, 19, 29)]
for lr in (0.01, 0.1, 1.0, 10.0):
perdidas = entrenar_varias(lambda p: torch.optim.SGD(p, lr=lr))
print(f"SGD lr={lr}", resumen(perdidas))
La salida esperada está oculta.
torch.optim.Adam es otro optimizador. Guarda, para cada parámetro, un promedio móvil de sus gradientes y otro de sus cuadrados. Divide el primero entre la raíz del segundo, así que cada parámetro recibe un tamaño de paso propio. El learning rate sigue fijando la escala general. Su valor por defecto es 0.001.
Pregunta. Con el mismo modelo inicial y el mismo cargador, ¿Adam con lr=0.01 termina por encima o por debajo de SGD con lr=0.1? ¿Qué pasa con Adam si el learning rate es 0.001?
for lr in (0.001, 0.01):
perdidas = entrenar_varias(lambda p: torch.optim.Adam(p, lr=lr))
print(f"Adam lr={lr}", resumen(perdidas))
La salida esperada está oculta.
Schedule del learning rate
Hasta aquí, el learning rate queda fijo durante todo el entrenamiento. Un scheduler lo cambia según un calendario. En PyTorch, los schedulers están en torch.optim.lr_scheduler y reciben el optimizador al crearse. Cada llamada a scheduler.step() avanza el calendario un paso y escribe el learning rate nuevo en el optimizador. scheduler.get_last_lr() devuelve el learning rate actual en una lista, con un valor por grupo de parámetros. Los optimizadores de esta lección tienen un solo grupo, así que se lee el elemento [0].
Los calendarios de esta sección cuentan épocas. Por eso scheduler.step() va una vez por época, al terminar la época de entrenamiento, después de todas las llamadas a optimizador.step() de esa época.
StepLR(optimizador, step_size=10, gamma=0.5)multiplica el learning rate porgammacadastep_sizeépocasCosineAnnealingLR(optimizador, T_max=30)baja el learning rate siguiendo medio ciclo de coseno, desde el valor inicial hasta 0 después deT_maxllamadas astep()
La función entrenar_con_scheduler usa SGD con el mismo modelo inicial y el mismo cargador que entrenar_varias. Antes de entrenar cada época, guarda el learning rate que esa época va a usar.
from torch.optim.lr_scheduler import CosineAnnealingLR, StepLR
def entrenar_con_scheduler(crear_scheduler, lr, epocas=30):
modelo = crear_modelo()
optimizador = torch.optim.SGD(modelo.parameters(), lr=lr)
scheduler = crear_scheduler(optimizador)
cargador = crear_cargador()
lrs, perdidas = [], []
for _ in range(epocas):
lrs.append(scheduler.get_last_lr()[0])
perdidas.append(entrenar_epoca(modelo, cargador, optimizador))
scheduler.step() # una vez por época, fuera del loop de batches
return lrs, perdidas
El parámetro crear_scheduler recibe una función que toma el optimizador y devuelve el scheduler. El diccionario schedulers guarda una de esas funciones por calendario.
schedulers = {
"StepLR": lambda opt: StepLR(opt, step_size=10, gamma=0.5),
"coseno": lambda opt: CosineAnnealingLR(opt, T_max=30),
}
Las dos corridas usan SGD con lr=1.0 durante 30 épocas, una con cada scheduler de schedulers.
Pregunta. Con lr=1.0, ¿qué learning rate usa cada scheduler en las épocas 11, 21 y 30? ¿En qué época llega el coseno a 0.5?
Ver resultado
Cada fila muestra el learning rate que usa una época, leído con scheduler.get_last_lr()[0] antes de entrenarla. La columna StepLR corresponde a StepLR(step_size=10, gamma=0.5) y la columna coseno a CosineAnnealingLR(T_max=30). Por ejemplo, la fila 11 dice que la época 11 entrena con 0.500 bajo StepLR y con 0.750 bajo el coseno.
| época | StepLR | coseno |
|---|---|---|
| 1 | 1.000 | 1.000 |
| 10 | 1.000 | 0.794 |
| 11 | 0.500 | 0.750 |
| 16 | 0.500 | 0.500 |
| 20 | 0.500 | 0.297 |
| 21 | 0.250 | 0.250 |
| 30 | 0.250 | 0.003 |
scheduler.step() se llama al terminar cada época. StepLR mantiene 1.0 en las épocas 1 a 10, baja a 0.5 en la 11 y a 0.25 en la 21: cambia en escalones. El coseno cambia en cada época: 0.794 en la época 10, 0.5 en la 16, a la mitad del calendario, y 0.003 en la 30.La comparación usa dos learning rates iniciales: 1.0, el mejor de la sección anterior, y 3.0. La corrida sin scheduler es entrenar_varias con SGD, así que parte del mismo modelo y del mismo cargador.
Pregunta. ¿Con cuál de los dos learning rates baja la pérdida de la época 30 al agregar un scheduler? ¿Con cuál sube?
Ver resultado
Cada fila muestra una corrida de 30 épocas. La columna lr es el learning rate inicial, scheduler es el calendario (ninguno, StepLR o coseno), época 30 es la pérdida de entrenamiento en la última época y máx. 21–30 es la pérdida más alta entre las épocas 21 y 30. Por ejemplo, la fila 3.0, ninguno, 0.233, 0.402 dice que sin scheduler y con lr=3.0 la época 30 termina en 0.233 y alguna de las últimas diez épocas llega a 0.402.
| lr | scheduler | época 30 | máx. 21–30 |
|---|---|---|---|
| 1.0 | ninguno | 0.142 | 0.257 |
| 1.0 | StepLR | 0.194 | 0.216 |
| 1.0 | coseno | 0.205 | 0.216 |
| 3.0 | ninguno | 0.233 | 0.402 |
| 3.0 | StepLR | 0.189 | 0.226 |
| 3.0 | coseno | 0.183 | 0.203 |
lr=3.0, los dos schedulers bajan la pérdida de la época 30: de 0.233 a 0.189 con StepLR y a 0.183 con el coseno. Sin scheduler, los pasos siguen siendo grandes al final y la pérdida sube hasta 0.402 en alguna de las épocas 21 a 30. Con lr=1.0 pasa lo contrario: sin scheduler la época 30 queda en 0.142, por debajo de 0.194 y 0.205, pero esa misma corrida sube hasta 0.257 en las épocas 21 a 30. Esa oscilación es mayor que la diferencia con las corridas con scheduler, así que una sola época no basta para decidir cuál termina mejor. Con los dos learning rates, el scheduler baja la pérdida más alta de las épocas 21 a 30. Un scheduler no sustituye la elección del learning rate inicial: la combinación se elige comparando corridas.Validación por época
modelo.train() y modelo.eval() cambian el modo del modelo. El modo se guarda en el atributo training de cada submódulo. Algunas capas calculan distinto según ese atributo. El loop pone modelo.train() antes de entrenar y modelo.eval() antes de evaluar para que el mismo código sirva con esas capas.
Pregunta. Después de modelo.eval(), ¿la salida del modelo sigue registrando operaciones para backward()? ¿Y dentro de torch.no_grad()?
modelo = crear_modelo()
print(modelo.training)
modelo.eval()
print(modelo.training, modelo[0].training)
print(modelo(X_validacion).requires_grad)
with torch.no_grad():
print(modelo(X_validacion).requires_grad)
modelo.train()
print(modelo.training)
La salida esperada está oculta.
La función evaluar aplica modelo.eval() y torch.no_grad() sobre los 50 puntos de validación y devuelve la pérdida y la exactitud. .float() convierte cada acierto en 1 y cada fallo en 0, y .mean() los promedia.
def evaluar(modelo):
modelo.eval()
with torch.no_grad():
logits = modelo(X_validacion)
perdida = perdida_fn(logits, y_validacion).item()
exactitud = (logits.argmax(dim=1) == y_validacion).float().mean().item()
return perdida, exactitud
El loop completo alterna una época de entrenamiento con una evaluación sobre los 50 puntos de validación. Cada época se guarda en historial. La validación nunca llama a backward() ni a step(), así que no cambia los pesos.
Pregunta. En la época 1, ¿la pérdida de validación queda por encima o por debajo de la de entrenamiento? Piensa en qué momento de la época se mide cada una.
modelo = crear_modelo()
optimizador = torch.optim.Adam(modelo.parameters(), lr=0.01)
cargador = crear_cargador()
historial = []
for epoca in range(1, 101):
modelo.train()
perdida_ent = entrenar_epoca(modelo, cargador, optimizador)
perdida_val, exactitud_val = evaluar(modelo)
historial.append((epoca, perdida_ent, perdida_val, exactitud_val))
print("época ent. val. exactitud val.")
for epoca, p_ent, p_val, exac in historial:
if epoca in (1, 5, 10, 20, 40, 60, 80, 100):
print(f"{epoca:>5} {p_ent:.3f} {p_val:.3f} {exac:.2f}")
La salida esperada está oculta.
Entrenamiento de un MLP
Reproduce el entrenamiento y observa cómo la frontera se curva mientras bajan las pérdidas de entrenamiento y de validación por época.
Qué probar
Si subes el learning rate a 10, la pérdida de entrenamiento se vuelve inestable y sube y baja de una época a otra.
120 puntos de entrenamiento y 60 de validación. Cada cambio vuelve a la época 0 con la misma semilla.
Práctica
Traza guiada
Antes de procesar, escribe el número de batches por época, la forma de xb en cada batch y cuántas veces llamaría a step() un loop de 3 épocas con este cargador. Decide también si cada fila de xb sigue junto a su etiqueta después de reordenar.
datos_traza = TensorDataset(
torch.arange(12, dtype=torch.float32).reshape(6, 2),
torch.tensor([0, 1, 0, 1, 1, 0]),
)
cargador_traza = DataLoader(
datos_traza, batch_size=4, shuffle=True, generator=torch.Generator().manual_seed(1)
)
Comprueba con print(xb, yb) dentro de un for. La fila [2., 3.] debe aparecer siempre con la etiqueta 1. Si cuentas mal los batches, revisa qué pasa con los ejemplos que no completan un batch.
Tamaño de batch
Añade un parámetro batch_size a entrenar_varias y pásalo a crear_cargador. Repite la corrida de Adam con lr=0.01 y batch_size igual a 8 y a 150. Antes de procesar, predice cuántas llamadas a step() hace cada configuración por época y cuál llega a una pérdida más baja en la época 30. Comprueba el número de batches con assert len(cargador) == …. Relaciona la diferencia con el número de llamadas a step() en 30 épocas.
Loop de tres clases
Genera datos con make_blobs(n_samples=300, centers=3, random_state=0) y sepáralos en 240 de entrenamiento y 60 de validación con stratify. Escribe un cálculo que:
- convierta los datos a tensores
float32y etiquetas enteras - cree un MLP
2 → 16 → 3contorch.manual_seed(0)y unDataLoadercon generador semillado - sobreajuste 8 ejemplos y compruebe con
assertque la pérdida final queda por debajo de 0.05 - entrene un modelo nuevo durante 50 épocas y guarde en
historialla pérdida de entrenamiento, la de validación y la exactitud de validación - compruebe con
assert len(historial) == 50y escriba la época con menor pérdida de validación
Reto opcional
Entrena el modelo de «Validación por época» con semillas 0, 1 y 2 en crear_cargador y el mismo modelo inicial. Después añade un parámetro de semilla a crear_modelo, fija el cargador con semilla 0 y cambia solo la semilla del modelo. Compara el rango de la pérdida de validación final en los dos casos y explica cuál de las dos fuentes de azar cambia más el resultado.
Errores que revisar
- Si la pérdida de la primera época queda por encima de la pérdida inicial, el learning rate es demasiado grande. Divídelo entre 10 y compara.
- Si la pérdida del batch diminuto no baja, revisa que el optimizador recibió
modelo.parameters()del mismo modelo que calcula la pérdida y questep()va después debackward(). - Si la pérdida baja rápido al principio y luego sube con un learning rate que antes funcionaba, falta
optimizador.zero_grad()al inicio de cada batch. - Si dos corridas del mismo código dan pérdidas distintas, fija
torch.manual_seedantes de crear el modelo y pasa ungeneratorsemillado alDataLoader. - Si la memoria crece época tras época, revisa que acumulas
perdida.item()y no el tensorperdida, que conserva su registro de operaciones. - Si el learning rate baja mucho antes de lo previsto, o el coseno vuelve a subir a mitad del entrenamiento, revisa que
scheduler.step()esté fuera del loop de batches. Dentro de él avanza una vez por batch: con 5 batches por época,StepLR(step_size=10, gamma=0.5)divide el learning rate entre 2 cada 2 épocas yCosineAnnealingLR(T_max=30)llega a 0 en la época 7 y vuelve a subir.
Cierre
Explica qué comprueba el sobreajuste de un batch de 8 ejemplos y qué información añade la pérdida de validación registrada por época.
Lista de comprobación
- ¿Cuántas veces se llama
optimizador.step()en una época con 150 ejemplos ybatch_size=32? - ¿Qué gradiente usa
step()si se omiteoptimizador.zero_grad()? - ¿Por qué un learning rate demasiado grande hace que la pérdida suba en lugar de bajar?
- ¿Qué cambia
modelo.eval()y qué cambiatorch.no_grad()? - ¿Por qué la pérdida de entrenamiento de una época puede quedar por encima de la de validación?
- ¿Qué learning rate usa la época 21 con
StepLR(step_size=10, gamma=0.5)ylr=1.0, y qué cambia sischeduler.step()se llama en cada batch?
Documentación oficial
- Datasets & DataLoaders: datasets, batches y recorrido con
DataLoader. - Optimizing Model Parameters: loop de entrenamiento y validación con
zero_grad(),backward()ystep(). - torch.utils.data:
TensorDataset,DataLoader,shuffleygenerator. - torch.optim: uso de optimizadores, SGD, Adam y learning rate.
- torch.nn.Module:
parameters(),train(),eval()y el atributotraining.