Scheduler de tasa de aprendizaje de PyTorch

La tasa de aprendizaje (Learning Rate) es uno de los hiperparámetros más importantes en el entrenamiento de redes neuronales. Si es demasiado grande, el entrenamiento oscila o incluso diverge; si es demasiado pequeña, la convergencia es muy lenta y es fácil caer en óptimos locales.

Scheduler de tasa de aprendizaje (LR Scheduler)Al ajustar dinámicamente la tasa de aprendizaje durante el entrenamiento, se equilibra la convergencia rápida de las etapas iniciales con el ajuste fino de las etapas finales.


1. Conceptos básicos y modos de uso

Flujo de uso estándar

Ejemplo

import torch
import torch.nn as nn
import torch.optim as optim

model     = nn.Linear(10, 1)
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 1. Crear el scheduler, pasándole el optimizer # 2. Entrenar # 3. Después de cada epoch, llamar a scheduler.step() # 4. Ver la tasa de aprendizaje actual
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

for epoch in range(100):
    Momento de llamada de step()
    train(model, optimizer)

    Ejemplo
    scheduler.step()

    # Correcto: optimizer.step() primero, scheduler.step() después # Incorrecto: scheduler.step() antes de optimizer.step() # Después de PyTorch 1.1.0 se genera una advertencia, y algunos schedulers se comportan de forma anómala
    current_lr = scheduler.get_last_lr()[0]
    print(f"Epoch {epoch+1}, LR: {current_lr:.6f}")

Momento de llamada de step()

Ejemplo

llamarse antes.
optimizer.step()
scheduler.step()

Después de PyTorch 1.1.0 se generará una advertencia, y algunos schedulers se comportarán de manera anómala.
# Obtener la tasa de aprendizaje actual
scheduler.step()
optimizer.step()

# LR después del último step()optimizer.step()# Guardar checkpoint (también debes guardar el estado del scheduler)scheduler.step()# No olvides esto

Ver y guardar el estado de la tasa de aprendizaje

Ejemplo

2.1 StepLR: Decaimiento por pasos
current_lr = optimizer.param_groups[0]['lr']
current_lr = scheduler.get_last_lr()[0]   Cada intervalo fijo de

epochs, multiplica la tasa de aprendizaje por
torch.save({
    'epoch':          epoch,
    'model':          model.state_dict(),
    'optimizer':      optimizer.state_dict(),
    'scheduler':      scheduler.state_dict(),   . Es uno de los schedulers más simples y más utilizados.
}, 'checkpoint.pth')

Fórmula:
ckpt = torch.load('checkpoint.pth')
model.load_state_dict(ckpt['model'])
optimizer.load_state_dict(ckpt['optimizer'])
scheduler.load_state_dict(ckpt['scheduler'])

2. Scheduler de decaimiento fijo

2.1 StepLR: Decaimiento por pasos

Escenarios de aplicación:step_sizeTareas con ritmo de entrenamiento fijo y etapas claras, como el entrenamiento de ResNet en ImageNet (90 epochs, con decaimiento en los epochs 30 y 60).gamma2.2 MultiStepLR: Decaimiento multi-hito

Decae la tasa de aprendizaje en varios epochs especificados (hitos), es más flexible que StepLR. lr = lr_base * gamma^(floor(step / step_size))

Ejemplo

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(
    optimizer,
    step_size=30,   # Decae en los epochs 30, 60 y 80 # Cambio de LR:
    gamma=0.1       Escenarios de aplicación:
)

Cuando se sabe en qué epochs el modelo necesita un ajuste fino, como en las etapas de convergencia fina intermedias y finales de un modelo de clasificación.
# Epoch  0-29:  0.1
# Epoch 30-59:  0.01
# Epoch 60-89:  0.001
# Epoch 90+:    0.0001

2.3 ExponentialLR: Decaimiento exponencialDecae en cada epoch, la tasa de aprendizaje disminuye continuamente de forma exponencial, con un decaimiento más suave.


2.2 MultiStepLR: Decaimiento multi-hito

Ejemplo

Ejemplo

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.MultiStepLR(
    optimizer,
    milestones=[30, 60, 80],   Si se establece demasiado pequeño (por ejemplo, 0.5), la tasa de aprendizaje se acercará rápidamente a cero; normalmente se establece entre 0.9 y 0.99.
    gamma=0.1
)

3. Scheduler adaptativo
# Epoch  0-29:  0.1
# Epoch 30-59:  0.01
# Epoch 60-79:  0.001
# Epoch 80+:    0.0001

3.1 ReduceLROnPlateau: Decaimiento basado en métricas monitoreadasUno de los schedulers más inteligentes


2.3 ExponentialLR: Decaimiento exponencial

Ejemplo

# 'min': cuanto menor sea el valor monitoreado, mejor (loss); 'max': cuanto mayor, mejor (accuracy) # Al activarse, lr = lr × factor # Número de epochs permitidos de estancamiento de la métrica; si se supera, se decae # Si la mejora es menor que este valor, se considera que no hay mejora # Límite inferior de la tasa de aprendizaje, no bajará de este valor # Imprime la información del decaimiento # A diferencia de otros schedulers, aquí se pasa la métrica monitoreada lr = lr_base * gamma^epoch

Ejemplo

optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.ExponentialLR(
    optimizer,
    gamma=0.95   Ejemplo
)

# Cuanto mayor sea el accuracy, mejor
# Epoch 1: 0.0100
# Epoch 2: 0.0095
# Epoch 3: 0.0090
# Epoch 4: 0.0086
# Epoch 5: 0.0081

gammaEscenarios de aplicación:


3. Scheduler adaptativo

3.1 ReduceLROnPlateau: Decaimiento basado en métricas monitoreadas

La tasa de aprendizaje sigueuna curva coseno

Ejemplo

optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='min',       ), evitando los cambios bruscos del decaimiento escalonado.
    factor=0.1,       Fórmula:
    patience=10,      Ejemplo
    threshold=1e-4,   # Longitud de medio período (normalmente se establece como el número total de epochs) # Valor mínimo de la tasa de aprendizaje (por defecto 0) # Trayectoria de cambio de LR (ejemplo con T_max=10): # (descenso suave en curva coseno)
    min_lr=1e-6,      Escenarios de aplicación:
    verbose=True      Escenarios con un número fijo de epochs de entrenamiento; se usa ampliamente en artículos como Vision Transformer, ResNet, etc., con buena calidad de convergencia.
)

for epoch in range(100):
    train_loss = train(model, optimizer)
    val_loss   = evaluate(model)

    Versión mejorada del recocido por coseno, admite
    scheduler.step(val_loss)

reinicios periódicos

Ejemplo

scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='max',       Ejemplo
    factor=0.5,
    patience=5,
)

scheduler.step(val_accuracy)

# Longitud del primer ciclo (número de epochs) # Multiplicador de la longitud del ciclo después de cada reinicio (1=igual, 2=se alarga gradualmente) # Longitudes de ciclo con T_mult=2: 10 -> 20 -> 40 -> 80 ... # Cambio de LR (T_0=10, T_mult=1): # 0.1 -> ... -> 0 -> 0.1 -> ... -> 0 -> 0.1 (se reinicia cada 10 epochs)Escenarios de aplicación:


3.2 CosineAnnealingLR: Recocido por coseno

4. Scheduler de precalentamiento (warmup)Warmup (precalentamiento)se refiere a que, durante los primeros pasos del entrenamiento, la tasa de aprendizaje aumenta gradualmente desde un valor muy pequeño hasta el valor objetivo. El entrenamiento con un tamaño de batch grande y los modelos tipo Transformer casi siempre requieren precalentamiento; de lo contrario, las actualizaciones de gradiente iniciales son demasiado fuertes y el modelo difícilmente se estabiliza.eta_minEjemplo

# Se usa junto con el siguiente scheduler # Tasa de aprendizaje inicial = factor inicial × LR inicial # Crece linealmente desde el valor final del scheduler anterior lr_t = eta_min + 0.5 * (eta_max - eta_min) * (1 + cos(t * pi / T_max)

Ejemplo

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.CosineAnnealingLR(
    optimizer,
    T_max=100,     Ejemplo
    eta_min=1e-6   # Factor inicial (LR inicial = start_factor × LR original) # Factor final (se alcanza después de total_iters pasos) # Aumenta linealmente de start_factor a end_factor # Se actualiza en cada paso
)

4.2 ConstantLR: Fase constante
# 0.1 -> 0.095 -> 0.079 -> 0.055 -> 0.026 -> 0.001
Ejemplo

# Factor inicial (LR inicial = factor × LR original) # Se mantiene constante4.3 SequentialLR: Programación combinada


3.3 CosineAnnealingWarmRestarts

Ejemplo# Lista de schedulers (aplicados en orden) # Hito: límite de epoch para cambiar al siguiente scheduler # Se pueden encadenar varios schedulers5. Scheduler cíclico

Ejemplo

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(
    optimizer,
    T_0=10,       Ejemplo
    T_mult=2,     # base_lr: límite inferior de cada ciclo # max_lr: límite superior de cada ciclo # Modos de ciclo: triangular, triangular2, exp_range # Pasos por ciclo = 2 × step_size_up # Por defecto base_lr -> max_lr -> base_lr
    eta_min=1e-6
)

5.2 OneCycleLR: Estrategia de un solo ciclo
Ejemplo
# max_lr: tasa de aprendizaje máxima (pico) # Número total de pasos (número de epochs × pasos por epoch) # Tres fases: precalentamiento, recocido y ajuste fino final # pct_start: proporción del precalentamiento en el entrenamiento total # anneal_strategy: cos (coseno) o linear

6. Scheduler personalizado6.1 LambdaLR: Personalización funcional


4. Scheduler de precalentamiento (warmup)

# Función lambda personalizada, recibe epoch y devuelve un factor multiplicador # Aumenta linealmente durante los primeros 5 epochs y luego se mantiene constante # Nota: el factor es relativo a la LR inicial6.2 Heredar de LRScheduler

4.1 LinearLR: Programación lineal

Cambia la tasa de aprendizaje linealmente dentro de un número determinado de épocas (útil para precalentamiento lineal o decaimiento lineal).

Ejemplo

optimizer = optim.Adam(model.parameters(), lr=0.01)

# Precalentamiento lineal: en las primeras 5 épocas, lr aumenta linealmente de 0.01×0.1=0.001 a 0.01
warmup_scheduler = optim.lr_scheduler.LinearLR(
    optimizer,
    start_factor=0.1,   # lr inicial = base_lr × start_factor
    end_factor=1.0,     # lr final = base_lr × end_factor
    total_iters=5       # Se completa después de 5 épocas
)

# Cambio de LR: 0.001 -> 0.003 -> 0.005 -> 0.007 -> 0.009 -> 0.01

4.2 ConstantLR: Fase constante

Dentro de un número determinado de épocas, fija la tasa de aprendizaje enbase_lr × factor, y después restaura el valor original.

Ejemplo

# En las primeras 5 épocas usa base_lr × 0.5, luego vuelve a la normalidad
scheduler = optim.lr_scheduler.ConstantLR(
    optimizer,
    factor=0.5,
    total_iters=5
)

4.3 SequentialLR: Programación combinada

Concatenar varios programadoresen ordenes la práctica estándar para implementar la estrategia combinada de "precalentamiento + decaimiento".

Ejemplo

optimizer = optim.Adam(model.parameters(), lr=0.01)

# Fase uno: precalentamiento (primeras 5 épocas, LR aumenta linealmente de 0.001 a 0.01)
warmup = optim.lr_scheduler.LinearLR(
    optimizer, start_factor=0.1, end_factor=1.0, total_iters=5
)

# Fase dos: decaimiento por coseno (restantes 95 épocas)
cosine = optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=95, eta_min=1e-6
)

# Combinación: primero se ejecuta el warmup, después de la época 5 se cambia a cosine
scheduler = optim.lr_scheduler.SequentialLR(
    optimizer,
    schedulers=[warmup, cosine],
    milestones=[5]          # Cambio en la época 5
)

# Se usa exactamente igual que un programador normal
for epoch in range(100):
    train(...)
    scheduler.step()

Escenarios de aplicación:Estándar en el entrenamiento de Transformers (precalentamiento + decaimiento por coseno); el preentrenamiento de BERT, GPT y ViT utiliza esta estrategia.


5. Scheduler cíclico

5.1 CyclicLR: Tasa de aprendizaje cíclica

La tasa de aprendizajebase_lrymax_lrentrecicla periódicamente, lo que puede ayudar al modelo a explorar un espacio de parámetros más amplio y salir de puntos de silla.

Ejemplo

optimizer = optim.SGD(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.CyclicLR(
    optimizer,
    base_lr=0.001,          # Límite inferior de la tasa de aprendizaje
    max_lr=0.01,            # Límite superior de la tasa de aprendizaje
    step_size_up=2000,      # Número de pasos de iteración desde base_lr hasta max_lr
    step_size_down=2000,    # Número de pasos de iteración desde max_lr hasta base_lr (por defecto igual a step_size_up)
    mode='triangular',      # Ciclo triangular (amplitud constante)
    # mode='triangular2' # Cada ciclo reduce la amplitud a la mitad
    # mode='exp_range' # Amplitud con decaimiento exponencial
)

# CyclicLR se llama por step (batch), no por epoch
for epoch in range(num_epochs):
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        optimizer.step()
        scheduler.step()    # Se llama después de cada batch

Comparación de los tres modos:

modeVariación de amplitudCaracterística
triangularConstanteExploración estable, adecuado para las etapas iniciales
triangular2Se reduce a la mitad cada cicloPrimero explora, luego converge
exp_rangeDecaimiento exponencialConvergencia estable al final

5.2 OneCycleLR: Estrategia de un solo ciclo

Uno de los programadores con mejor rendimiento, la política 1-Cycle propuesta por fastai. Todo el entrenamiento tiene un solo ciclo: la tasa de aprendizaje primero sube y luego baja, y el momento (momentum) varía en sentido inverso.

El entrenamiento es más rápido; normalmente solo se necesitan1/5~1/10de las épocas del entrenamiento tradicional para alcanzar la misma precisión.

Ejemplo

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

steps_per_epoch = len(train_loader)

scheduler = optim.lr_scheduler.OneCycleLR(
    optimizer,
    max_lr=0.1,                              # Punto máximo de la tasa de aprendizaje
    steps_per_epoch=steps_per_epoch,         # Número de pasos por epoch
    epochs=10,                               # Número total de épocas
    pct_start=0.3,                           # El 30% inicial se usa para el precalentamiento ascendente
    anneal_strategy='cos',                   # Estrategia de decaimiento ('cos' o 'linear')
    div_factor=25,                           # lr inicial = max_lr / div_factor
    final_div_factor=1e4                     # lr final = max_lr / final_div_factor
)

# lr inicial = 0.1 / 25 = 0.004
# lr máximo = 0.1 (en el 30%)
# lr final = 0.1 / 10000 = 0.00001

# También se llama por batch
for epoch in range(10):
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        optimizer.step()
        scheduler.step()    # Se llama después de cada batch

Escenarios de aplicación:Es la primera opción cuando los recursos computacionales son limitados y se necesita validar ideas rápidamente;max_lrSe recomienda usar la herramienta LR Finder para determinar el valor óptimo.


6. Scheduler personalizado

6.1 LambdaLR: Personalización funcional

Se personaliza completamente la estrategia de programación pasando una función Lambda (recibe el número de epoch y devuelve un multiplicador de la tasa de aprendizaje).

Ejemplo

optimizer = optim.Adam(model.parameters(), lr=0.01)

# Ejemplo 1: estrategia clásica de precalentamiento de Transformer
# lr ∝ min(step^-0.5, step × warmup_steps^-1.5)
def transformer_lr(epoch, warmup_epochs=10, d_model=512):
    if epoch == 0:
        return 1e-7 / 0.01          # Evitar división por cero
    step = epoch + 1
    warmup = warmup_epochs
    return (d_model ** -0.5) * min(step ** -0.5, step * warmup ** -1.5) / 0.01

scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=transformer_lr)


# Ejemplo 2: decaimiento polinómico (lr disminuye de forma lineal/polinómica desde el valor inicial hasta 0)
def polynomial_decay(epoch, total_epochs=100, power=1.0):
    return max((1 - epoch / total_epochs) ** power, 0.0)

scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=polynomial_decay)


# Ejemplo 3: tasa de aprendizaje por grupos (diferentes estrategias para diferentes grupos de parámetros)
optimizer = optim.Adam([
    {'params': model.backbone.parameters(), 'lr': 1e-4},
    {'params': model.head.parameters(),     'lr': 1e-3},
])

# Una función lambda para cada grupo de parámetros
scheduler = optim.lr_scheduler.LambdaLR(
    optimizer,
    lr_lambda=[
        lambda epoch: 0.95 ** epoch,    # backbone: decaimiento lento
        lambda epoch: 0.85 ** epoch,    # head: decaimiento rápido
    ]
)

6.2 Heredar de LRScheduler

Cuando se necesita una lógica más compleja, heredeLRScheduler(PyTorch >= 2.0, en versiones anteriores era_LRScheduler) para implementar una personalización completa:

Ejemplo

from torch.optim.lr_scheduler import LRScheduler
import math

class WarmupCosineScheduler(LRScheduler):
    """
Programador combinado de precalentamiento + decaimiento por coseno (versión implementada manualmente)
- Precalentamiento lineal durante las primeras warmup_epochs épocas
- Después, decaimiento por coseno hasta min_lr
    """

    def __init__(self, optimizer, warmup_epochs, total_epochs,
                 min_lr=1e-6, last_epoch=-1):
        self.warmup_epochs = warmup_epochs
        self.total_epochs  = total_epochs
        self.min_lr        = min_lr
        super().__init__(optimizer, last_epoch)

    def get_lr(self):
        epoch = self.last_epoch

        # Fase de precalentamiento: aumento lineal
        if epoch < self.warmup_epochs:
            warmup_factor = (epoch + 1) / self.warmup_epochs
            return [base_lr * warmup_factor for base_lr in self.base_lrs]

        # Fase de decaimiento por coseno
        progress = (epoch - self.warmup_epochs) / (
            self.total_epochs - self.warmup_epochs
        )
        cosine_factor = 0.5 * (1 + math.cos(math.pi * progress))
        return [
            self.min_lr + (base_lr - self.min_lr) * cosine_factor
            for base_lr in self.base_lrs
        ]


optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = WarmupCosineScheduler(
    optimizer,
    warmup_epochs=10,
    total_epochs=100,
    min_lr=1e-6
)

7. Comparación visual de schedulers

El siguiente código puede dibujar las curvas de cambio de LR de cada programador para una comparación visual directa:

Ejemplo

import torch
import torch.optim as optim
import matplotlib.pyplot as plt

def simulate_lr(scheduler_fn, epochs=100, steps_per_epoch=None):
    """Simula y registra los cambios de la tasa de aprendizaje del programador"""
    model     = torch.nn.Linear(1, 1)
    optimizer = optim.SGD(model.parameters(), lr=0.1)
    scheduler = scheduler_fn(optimizer)
    lrs = []

    if steps_per_epoch:
        # Programador que se llama por step
        for _ in range(epochs):
            for _ in range(steps_per_epoch):
                optimizer.step()
                scheduler.step()
                lrs.append(optimizer.param_groups[0]['lr'])
    else:
        # Programador que se llama por epoch
        for _ in range(epochs):
            optimizer.step()
            scheduler.step()
            lrs.append(optimizer.param_groups[0]['lr'])
    return lrs

schedulers = {
    'StepLR(step=30, gamma=0.1)':
        lambda opt: optim.lr_scheduler.StepLR(opt, 30, 0.1),
    'CosineAnnealingLR':
        lambda opt: optim.lr_scheduler.CosineAnnealingLR(opt, T_max=100),
    'ExponentialLR(gamma=0.95)':
        lambda opt: optim.lr_scheduler.ExponentialLR(opt, 0.95),
    'CosineWarmRestarts(T0=25)':
        lambda opt: optim.lr_scheduler.CosineAnnealingWarmRestarts(opt, T_0=25),
}

plt.figure(figsize=(12, 5))
for name, fn in schedulers.items():
    plt.plot(simulate_lr(fn), label=name)

plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('PyTorch LR Scheduler Comparison')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lr_schedulers.png', dpi=150)
plt.show()

8. Plantilla de entrenamiento completa

Plantilla de entrenamiento de nivel producción que integra precalentamiento + decaimiento por coseno, guardado y restauración de modelos:

Ejemplo

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import SequentialLR, LinearLR, CosineAnnealingLR

# Hiperparámetros
EPOCHS       = 100
WARMUP_EPOCHS = 5
BASE_LR      = 1e-3
MIN_LR       = 1e-6
SAVE_PATH    = 'best_model.pth'

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# Modelo / optimizador / programador
model     = MyModel().to(device)
optimizer = optim.AdamW(model.parameters(), lr=BASE_LR, weight_decay=1e-4)
criterion = nn.CrossEntropyLoss(label_smoothing=0.1)

# Precalentamiento 5 épocas + decaimiento por coseno 95 épocas
warmup_sched = LinearLR(optimizer, start_factor=0.1, total_iters=WARMUP_EPOCHS)
cosine_sched = CosineAnnealingLR(optimizer, T_max=EPOCHS - WARMUP_EPOCHS, eta_min=MIN_LR)
scheduler    = SequentialLR(optimizer, [warmup_sched, cosine_sched], milestones=[WARMUP_EPOCHS])

# Restaurar punto de control
start_epoch = 0
best_acc    = 0.0

try:
    ckpt = torch.load(SAVE_PATH, map_location=device)
    model.load_state_dict(ckpt['model'])
    optimizer.load_state_dict(ckpt['optimizer'])
    scheduler.load_state_dict(ckpt['scheduler'])
    start_epoch = ckpt['epoch'] + 1
    best_acc    = ckpt['best_acc']
    print(f"Restaurado desde Epoch {start_epoch}, mejor precisión {best_acc:.4f}")
except FileNotFoundError:
    print("Entrenar desde cero")

# Bucle de entrenamiento
history = {'train_loss': [], 'val_acc': [], 'lr': []}

for epoch in range(start_epoch, EPOCHS):
    # Entrenamiento
    model.train()
    total_loss = 0.0
    for inputs, labels in train_loader:
        inputs, labels = inputs.to(device), labels.to(device)
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # Recorte de gradientes
        optimizer.step()
        total_loss += loss.item()

    # Validación
    model.eval()
    correct = 0
    with torch.no_grad():
        for inputs, labels in val_loader:
            inputs, labels = inputs.to(device), labels.to(device)
            correct += (model(inputs).argmax(1) == labels).sum().item()

    avg_loss = total_loss / len(train_loader)
    val_acc  = correct / len(val_loader.dataset)
    cur_lr   = scheduler.get_last_lr()[0]

    # Registrar e imprimir
    history['train_loss'].append(avg_loss)
    history['val_acc'].append(val_acc)
    history['lr'].append(cur_lr)
    print(f"Epoch {epoch+1:3d}/{EPOCHS} | "
          f"Loss: {avg_loss:.4f} | Acc: {val_acc:.4f} | LR: {cur_lr:.2e}")

    # Actualizar el programador
    scheduler.step()

    # Guardar el mejor modelo
    if val_acc > best_acc:
        best_acc = val_acc
        torch.save({
            'epoch':      epoch,
            'model':      model.state_dict(),
            'optimizer':  optimizer.state_dict(),
            'scheduler':  scheduler.state_dict(),
            'best_acc':   best_acc,
        }, SAVE_PATH)
        print(f" Guardando el mejor modelo, Acc: {best_acc:.4f}")

print(f"\nEntrenamiento completado, mejor precisión de validación: {best_acc:.4f}")

9. Guía para elegir un scheduler

Recomendaciones por tipo de tarea

EscenarioProgramador recomendadoMotivo
Experimentación rápida / validación de prototiposReduceLROnPlateauNo requiere ajuste de hiperparámetros, decaimiento adaptativo
Clasificación de imágenes (épocas fijas)CosineAnnealingLRDecaimiento suave, buena calidad de convergencia
Transformer / BERTLinearLR + CosineAnnealingLREl precalentamiento es indispensable
Recursos limitados, entrenamiento rápidoOneCycleLRAlcanza la misma precisión en 1/5 del tiempo
Ajuste fino de modelos grandes (fine-tuning)LinearLR(precalentamiento) +ConstantLRajuste fino estable con tasa de aprendizaje baja
Número de épocas inciertoReduceLROnPlateauRespuesta automática, no depende de un ritmo fijo
Detección de objetos (YOLO/Faster-RCNN)MultiStepLR / OneCycleLRTiene puntos de decaimiento claros o busca velocidad
Se desea escapar de óptimos localesCosineAnnealingWarmRestartsReinicio periódico para explorar el espacio de parámetros

Combinaciones de configuración comunes

Ejemplo

# Combinación 1: Warmup + decaimiento por coseno (estándar en Transformers)
SequentialLR([LinearLR(5 epochs), CosineAnnealingLR(95 epochs)])

# Combinación 2: monitoreo del conjunto de validación + decaimiento automático (opción general preferida)
ReduceLROnPlateau(mode='min', patience=5, factor=0.5)

# Combinación 3: OneCycleLR (opción preferida para entrenamiento rápido)
OneCycleLR(max_lr=0.1, total_steps=total_steps, pct_start=0.3)

# Combinación 4: decaimiento por escalones tradicional (clásico en visión por computadora)
MultiStepLR(milestones=[30, 60, 90], gamma=0.1)

Referencia rápida de precauciones

ProblemaSolución
El programador no tiene efectoVerifiquescheduler.step()si se llamaoptimizer.step() despuésLlamada
CyclicLR / OneCycleLR no cambianEstos dos sebatchinvocan, no por epoch
ReduceLROnPlateau no se activaCompruebamodesi está configurado correctamente (para loss use'min', para accuracy use'max')
Después de reanudar el entrenamiento, el LR no es correctoComprueba si se guardó y cargóscheduler.state_dict()
Configuración de LR para múltiples grupos de parámetrosUsaLambdaLRpasa una lista, un lambda para cada grupo de parámetros
Otras extensiones