Phase 03 - Lesson 09
Agendamentos de Taxa de Aprendizado e Aquecimento
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
A taxa de aprendizado é o hiperparâmetro mais importante de todos. Não é a arquitetura. Não é o tamanho do dataset. Não é a função de ativação. É a taxa de aprendizado. Se você não ajustar mais nada, ajuste isto.
Tipo: Build Linguagens: Python Pré-requisitos: Lição 03.06 (Otimizadores), Lição 03.08 (Inicialização de Pesos) Tempo: ~90 minutos
Objetivos de Aprendizado
- Implementar do zero os agendamentos de taxa de aprendizado constante, step decay, cosine annealing, aquecimento + cosseno e 1cycle
- Demonstrar os três modos de falha na escolha da taxa de aprendizado: divergência (alta demais), estagnação (baixa demais) e oscilação (sem decaimento)
- Explicar por que o aquecimento é necessário para otimizadores baseados em Adam e como ele estabiliza o início do treinamento
- Comparar a velocidade de convergência entre os cinco agendamentos na mesma tarefa e escolher o mais adequado para um dado orçamento de treinamento
O Problema
Defina a taxa de aprendizado em 0.1. O treinamento diverge: a loss salta para o infinito em 3 passos. Defina em 0.0001. O treinamento se arrasta: depois de 100 épocas, o modelo mal saiu do estado aleatório. Defina em 0.01. O treinamento funciona por 50 épocas, e então a loss oscila em torno de um mínimo que ela nunca consegue alcançar, porque os passos são grandes demais.
A taxa de aprendizado ótima não é uma constante. Ela muda durante o treinamento. No início, você quer passos grandes para avançar rapidamente. No fim do treinamento, você quer passos minúsculos para assentar em um mínimo afiado. A diferença entre um modelo com 90% de acurácia e um com 95% de acurácia muitas vezes está justamente no agendamento.
Todo modelo importante publicado nos últimos três anos usa um agendamento de taxa de aprendizado. O Llama 3 usou lr de pico=3e-4 com 2000 passos de aquecimento e decaimento cosseno até 3e-5. O GPT-3 usou lr=6e-4 com aquecimento ao longo de 375 milhões de tokens. Essas não são escolhas arbitrárias. São o resultado de varreduras extensivas de hiperparâmetros que custaram milhões de dólares.
Você precisa entender agendamentos porque os padrões não vão funcionar para o seu problema. Quando você faz fine-tuning de um modelo pré-treinado, o agendamento certo é diferente do treinamento do zero. Quando você aumenta o tamanho do batch, o período de aquecimento precisa mudar. Quando o treinamento quebra no passo 10.000, você precisa saber se é um problema de agendamento ou outra coisa.
O Conceito
Taxa de Aprendizado Constante
A abordagem mais simples. Escolha um número e use-o em todos os passos.
lr(t) = lr_0
Raramente é ótima. Ou ela é alta demais para o fim do treinamento (oscilação em torno do mínimo) ou baixa demais para o começo (computação desperdiçada em passos minúsculos). Funciona bem para modelos pequenos e para depuração. Uma escolha terrível para qualquer coisa que treine por mais de uma hora.
Step Decay
A abordagem da velha guarda, da era da ResNet. Corte a taxa de aprendizado por um fator (geralmente 10x) em épocas fixas.
lr(t) = lr_0 * gamma^(floor(epoch / step_size))
Onde gamma = 0.1 e step_size = 30 significa: a lr cai por 10x a cada 30 épocas. A ResNet-50 usou isto: lr=0.1, com queda de 10x nas épocas 30, 60 e 90.
O problema: os pontos de decaimento ótimos dependem do dataset e da arquitetura. Mude para um problema diferente e você precisa reajustar quando cortar. As transições são abruptas: a loss pode disparar quando a taxa muda de repente.
Cosine Annealing
Decaimento suave da taxa de aprendizado máxima até a mínima, seguindo uma curva cosseno:
lr(t) = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * t / T))
Onde t é o passo atual e T é o número total de passos.
Em t=0, o termo cosseno vale 1, então lr = lr_max. Em t=T, o termo cosseno vale -1, então lr = lr_min. O decaimento é gentil no início, acelera no meio e volta a ser gentil perto do fim.
Este é o padrão para a maioria dos treinamentos modernos. Nenhum hiperparâmetro para ajustar além de lr_max e lr_min. O formato do cosseno coincide com a observação empírica de que a maior parte do aprendizado acontece no meio do treinamento: você quer passos de tamanho razoável durante esse período crítico.
Aquecimento: Por Que Você Começa Pequeno
O Adam e outros otimizadores adaptativos mantêm estimativas correntes da média e da variância do gradiente. No passo 0, essas estimativas são inicializadas em zero. As primeiras atualizações de gradiente são baseadas em estatísticas lixo. Se a sua taxa de aprendizado for grande durante esse período, o modelo dá passos enormes e mal direcionados.
O aquecimento corrige isso. Comece com uma taxa de aprendizado minúscula (muitas vezes lr_max / warmup_steps ou até zero) e aumente linearmente até lr_max ao longo dos primeiros N passos. Quando você atinge a taxa de aprendizado completa, as estatísticas do Adam já se estabilizaram.
lr(t) = lr_max * (t / warmup_steps) for t < warmup_steps
Aquecimento típico: 1-5% do total de passos de treinamento. O Llama 3 treinou por ~1,8 trilhão de tokens e aqueceu por 2000 passos. O GPT-3 aqueceu ao longo de 375 milhões de tokens.
Aquecimento Linear + Decaimento Cosseno
O padrão moderno. Aumente linearmente e depois decaia com cosseno:
if t < warmup_steps:
lr(t) = lr_max * (t / warmup_steps)
else:
progress = (t - warmup_steps) / (total_steps - warmup_steps)
lr(t) = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * progress))
É isto que o Llama, o GPT, o PaLM e a maioria dos transformers modernos usam. O aquecimento previne a instabilidade inicial. O decaimento cosseno assenta o modelo em um bom mínimo.
Política 1cycle
A descoberta de Leslie Smith (2018): aumente a taxa de aprendizado de um valor baixo para um valor alto na primeira metade do treinamento, e depois reduza-a de volta na segunda metade. Contraintuitivo: por que você aumentaria a taxa de aprendizado no meio do caminho?
A teoria: uma taxa de aprendizado alta age como regularização ao adicionar ruído à trajetória de otimização. O modelo explora mais do panorama da loss durante a fase de subida, encontrando melhores bacias. A fase de descida então refina dentro da melhor bacia encontrada.
Phase 1 (0 to T/2): lr ramps from lr_max/25 to lr_max
Phase 2 (T/2 to T): lr ramps from lr_max to lr_max/10000
A 1cycle frequentemente treina mais rápido que o cosine annealing para um orçamento de computação fixo. O trade-off: você precisa saber o número total de passos de antemão.
Formatos dos Agendamentos
graph LR
subgraph "Constante"
C1["lr"] --- C2["lr"] --- C3["lr"]
end
subgraph "Step Decay"
S1["0.1"] --- S2["0.1"] --- S3["0.01"] --- S4["0.001"]
end
subgraph "Cosine Annealing"
CS1["lr_max"] --> CS2["gradual"] --> CS3["acentuado"] --> CS4["lr_min"]
end
subgraph "Aquecimento + Cosseno"
WC1["0"] --> WC2["lr_max"] --> WC3["cosseno"] --> WC4["lr_min"]
end
Fluxograma de Decisão
flowchart TD
Start["Escolhendo um agendamento de LR"] --> Know{"Conhece o total<br/>de passos de treino?"}
Know -->|"Sim"| Budget{"Orçamento de computação?"}
Know -->|"Não"| Constant["Use LR constante<br/>com decaimento manual"]
Budget -->|"Grande (dias/semanas)"| WarmCos["Aquecimento + Decaimento Cosseno<br/>(padrão Llama/GPT)"]
Budget -->|"Pequeno (horas)"| OneCycle["Política 1cycle<br/>(convergência mais rápida)"]
Budget -->|"Moderado"| Cosine["Cosine Annealing<br/>(padrão seguro)"]
WarmCos --> Warmup["Aquecimento = 1-5% dos passos"]
OneCycle --> FindLR["Encontre lr_max com teste de faixa de LR"]
Cosine --> MinLR["Defina lr_min = lr_max / 10"]
Números Reais de Modelos Publicados
graph TD
subgraph "Configs de LR Publicadas"
L3["Llama 3 (405B)<br/>Pico: 3e-4<br/>Aquecimento: 2000 passos<br/>Agendamento: Cosseno até 3e-5"]
G3["GPT-3 (175B)<br/>Pico: 6e-4<br/>Aquecimento: 375M tokens<br/>Agendamento: Cosseno até 0"]
R50["ResNet-50<br/>Pico: 0.1<br/>Aquecimento: nenhum<br/>Agendamento: Step decay x0.1 em 30,60,90"]
B["BERT (340M)<br/>Pico: 1e-4<br/>Aquecimento: 10K passos<br/>Agendamento: Decaimento linear"]
end
Construa
Passo 1: Funções de Agendamento
Cada função recebe o passo atual e retorna a taxa de aprendizado naquele passo.
import math
def constant_schedule(step, lr=0.01, **kwargs):
return lr
def step_decay_schedule(step, lr=0.1, step_size=100, gamma=0.1, **kwargs):
return lr * (gamma ** (step // step_size))
def cosine_schedule(step, lr=0.01, total_steps=1000, lr_min=1e-5, **kwargs):
if step >= total_steps:
return lr_min
return lr_min + 0.5 * (lr - lr_min) * (1 + math.cos(math.pi * step / total_steps))
def warmup_cosine_schedule(step, lr=0.01, total_steps=1000, warmup_steps=100, lr_min=1e-5, **kwargs):
if total_steps <= warmup_steps:
return lr * (step / max(warmup_steps, 1))
if step < warmup_steps:
return lr * step / warmup_steps
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return lr_min + 0.5 * (lr - lr_min) * (1 + math.cos(math.pi * progress))
def one_cycle_schedule(step, lr=0.01, total_steps=1000, **kwargs):
mid = max(total_steps // 2, 1)
if step < mid:
return (lr / 25) + (lr - lr / 25) * step / mid
else:
progress = (step - mid) / max(total_steps - mid, 1)
return lr * (1 - progress) + (lr / 10000) * progress
Passo 2: Visualize Todos os Agendamentos
Imprima um gráfico baseado em texto mostrando como cada agendamento evolui ao longo do treinamento.
def visualize_schedule(name, schedule_fn, total_steps=500, **kwargs):
steps = list(range(0, total_steps, total_steps // 20))
if total_steps - 1 not in steps:
steps.append(total_steps - 1)
lrs = [schedule_fn(s, total_steps=total_steps, **kwargs) for s in steps]
max_lr = max(lrs) if max(lrs) > 0 else 1.0
print(f"\n{name}:")
for s, lr_val in zip(steps, lrs):
bar_len = int(lr_val / max_lr * 40)
bar = "#" * bar_len
print(f" Step {s:4d}: lr={lr_val:.6f} {bar}")
Passo 3: Rede de Treinamento
Uma rede simples de duas camadas no dataset do círculo, igual às lições anteriores, mas agora variamos o agendamento.
import random
def sigmoid(x):
x = max(-500, min(500, x))
return 1.0 / (1.0 + math.exp(-x))
def relu(x):
return max(0.0, x)
def relu_deriv(x):
return 1.0 if x > 0 else 0.0
def make_circle_data(n=200, seed=42):
random.seed(seed)
data = []
for _ in range(n):
x = random.uniform(-2, 2)
y = random.uniform(-2, 2)
label = 1.0 if x * x + y * y < 1.5 else 0.0
data.append(([x, y], label))
return data
def train_with_schedule(schedule_fn, schedule_name, data, epochs=300, base_lr=0.05, **kwargs):
random.seed(0)
hidden_size = 8
total_steps = epochs * len(data)
std = math.sqrt(2.0 / 2)
w1 = [[random.gauss(0, std) for _ in range(2)] for _ in range(hidden_size)]
b1 = [0.0] * hidden_size
w2 = [random.gauss(0, std) for _ in range(hidden_size)]
b2 = 0.0
step = 0
epoch_losses = []
for epoch in range(epochs):
total_loss = 0
correct = 0
for x, target in data:
lr = schedule_fn(step, lr=base_lr, total_steps=total_steps, **kwargs)
z1 = []
h = []
for i in range(hidden_size):
z = w1[i][0] * x[0] + w1[i][1] * x[1] + b1[i]
z1.append(z)
h.append(relu(z))
z2 = sum(w2[i] * h[i] for i in range(hidden_size)) + b2
out = sigmoid(z2)
error = out - target
d_out = error * out * (1 - out)
for i in range(hidden_size):
d_h = d_out * w2[i] * relu_deriv(z1[i])
w2[i] -= lr * d_out * h[i]
for j in range(2):
w1[i][j] -= lr * d_h * x[j]
b1[i] -= lr * d_h
b2 -= lr * d_out
total_loss += (out - target) ** 2
if (out >= 0.5) == (target >= 0.5):
correct += 1
step += 1
avg_loss = total_loss / len(data)
accuracy = correct / len(data) * 100
epoch_losses.append(avg_loss)
return epoch_losses
Passo 4: Compare Todos os Agendamentos
Treine a mesma rede com cada agendamento e compare a loss final e o comportamento de convergência.
def compare_schedules(data):
configs = [
("Constant", constant_schedule, {}),
("Step Decay", step_decay_schedule, {"step_size": 15000, "gamma": 0.1}),
("Cosine", cosine_schedule, {"lr_min": 1e-5}),
("Warmup+Cosine", warmup_cosine_schedule, {"warmup_steps": 3000, "lr_min": 1e-5}),
("1cycle", one_cycle_schedule, {}),
]
print(f"\n{'Schedule':<20} {'Start Loss':>12} {'Mid Loss':>12} {'End Loss':>12} {'Best Loss':>12}")
print("-" * 70)
for name, schedule_fn, extra_kwargs in configs:
losses = train_with_schedule(schedule_fn, name, data, epochs=300, base_lr=0.05, **extra_kwargs)
mid_idx = len(losses) // 2
best = min(losses)
print(f"{name:<20} {losses[0]:>12.6f} {losses[mid_idx]:>12.6f} {losses[-1]:>12.6f} {best:>12.6f}")
Passo 5: LR Alta Demais vs Baixa Demais
Demonstre os três modos de falha: alta demais (divergência), baixa demais (arrastando) e na medida certa.
def lr_sensitivity(data):
learning_rates = [1.0, 0.1, 0.01, 0.001, 0.0001]
print("\nLR Sensitivity (constant schedule, 100 epochs):")
print(f" {'LR':>10} {'Start Loss':>12} {'End Loss':>12} {'Status':>15}")
print(" " + "-" * 52)
for lr in learning_rates:
losses = train_with_schedule(constant_schedule, f"lr={lr}", data, epochs=100, base_lr=lr)
start = losses[0]
end = losses[-1]
if end > start or math.isnan(end) or end > 1.0:
status = "DIVERGED"
elif end > start * 0.9:
status = "BARELY MOVED"
elif end < 0.15:
status = "CONVERGED"
else:
status = "LEARNING"
end_str = f"{end:.6f}" if not math.isnan(end) else "NaN"
print(f" {lr:>10.4f} {start:>12.6f} {end_str:>12} {status:>15}")
Use
O PyTorch fornece agendadores em torch.optim.lr_scheduler:
import torch
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR, StepLR
model = nn.Sequential(nn.Linear(10, 64), nn.ReLU(), nn.Linear(64, 1))
optimizer = optim.Adam(model.parameters(), lr=3e-4)
scheduler = CosineAnnealingLR(optimizer, T_max=1000, eta_min=1e-5)
for step in range(1000):
loss = train_step(model, optimizer)
scheduler.step()
Para aquecimento + cosseno, use um agendador lambda ou o get_cosine_schedule_with_warmup da HuggingFace:
from transformers import get_cosine_schedule_with_warmup
scheduler = get_cosine_schedule_with_warmup(
optimizer,
num_warmup_steps=2000,
num_training_steps=100000,
)
A função da HuggingFace é o que a maioria dos scripts de fine-tuning de Llama e GPT usa. Na dúvida, use aquecimento + cosseno com aquecimento = 3-5% do total de passos. Funciona para quase tudo.
Entregue
Esta lição produz:
outputs/prompt-lr-schedule-advisor.md-- um prompt que recomenda o agendamento de taxa de aprendizado certo e os hiperparâmetros para a sua configuração de treinamento
Exercícios
Implemente o decaimento exponencial: lr(t) = lr_0 * gamma^t onde gamma = 0.999. Compare com o cosine annealing no dataset do círculo.
Implemente o teste de faixa de taxa de aprendizado (Leslie Smith): treine por algumas centenas de passos enquanto aumenta exponencialmente a LR de 1e-7 para 1. Plote a loss vs LR. A LR máxima ótima fica logo antes de a loss começar a aumentar.
Treine com aquecimento + cosseno mas varie a duração do aquecimento: 0%, 1%, 5%, 10%, 20% do total de passos. Encontre o ponto ideal onde o treinamento é mais estável.
Implemente o cosine annealing com warm restarts (SGDR): reinicie a taxa de aprendizado para lr_max a cada T passos e decaia de novo. Compare com o cosseno padrão em uma execução de treinamento mais longa.
Construa um "cirurgião de agendamento" que monitora a loss de treinamento e automaticamente troca de aquecimento para cosseno quando a loss se estabiliza, e reduz a lr se a loss estagnar por tempo demais.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Taxa de aprendizado | "Quão rápido o modelo aprende" | O escalar que multiplica o gradiente para determinar o tamanho da atualização do parâmetro |
| Agendamento | "Mudar a LR ao longo do tempo" | Uma função que mapeia o passo de treinamento para a taxa de aprendizado, projetada para otimizar a convergência |
| Aquecimento | "Comece com uma LR pequena" | Aumentar linearmente a LR de quase-zero até o valor alvo ao longo dos primeiros N passos para estabilizar as estatísticas do otimizador |
| Cosine annealing | "Decaimento suave da LR" | Diminuir a LR seguindo uma curva cosseno de lr_max até lr_min ao longo do treinamento |
| Step decay | "Cortar a LR em marcos" | Multiplicar a LR por um fator (geralmente 0.1) em intervalos fixos de épocas |
| Política 1cycle | "Subir e depois descer" | O método de Leslie Smith de subir e depois descer a LR em um único ciclo para convergência mais rápida |
| Teste de faixa de LR | "Encontrar a melhor taxa de aprendizado" | Treinar brevemente enquanto aumenta a LR para encontrar o valor onde a loss começa a divergir |
| Cosseno com warm restarts | "Reiniciar e repetir" | Reiniciar periodicamente a LR para lr_max e decair de novo (SGDR) |
| Eta min | "O piso da LR" | A taxa de aprendizado mínima até a qual o agendamento decai |
| Taxa de aprendizado de pico | "A LR máxima" | A maior LR alcançada durante o treinamento, tipicamente após o aquecimento |
Leitura Adicional
- Loshchilov & Hutter, "SGDR: Stochastic Gradient Descent with Warm Restarts" (2017) -- introduziu o cosine annealing e os warm restarts
- Smith, "Super-Convergence: Very Fast Training of Neural Networks Using Large Learning Rates" (2018) -- o artigo da política 1cycle
- Touvron et al., "Llama 2: Open Foundation and Fine-Tuned Chat Models" (2023) -- documenta o agendamento de aquecimento + cosseno usado em escala
- Goyal et al., "Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour" (2017) -- a regra de escalonamento linear e o aquecimento para treinamento com batch grande