Otimizadores
O que os otimizadores fazem (e por que eles importam)
Em aprendizado profundo (deep learning), um otimizador (optimizer) é o algoritmo que atualiza os parâmetros do modelo (\theta) para minimizar uma função de perda (loss function) (L(\theta)). O treinamento normalmente se parece com:
- Executar uma passagem direta (forward pass) para calcular as predições.
- Calcular a perda (veja Ativações e Perdas).
- Calcular os gradientes (gradients) (\nabla_\theta L) via diferenciação automática (automatic differentiation) e retropropagação (backpropagation) (veja Autodiferenciação e Retropropagação).
- Usar um otimizador para transformar gradientes em uma atualização de parâmetros (\Delta \theta).
O otimizador é onde vivem muitas decisões de “dinâmica de treinamento (training dynamics)”: tamanho do passo (taxa de aprendizado), uso de momentum (momentum), tratamento de gradientes ruidosos, escalonamento por parâmetro, estabilidade numérica e como o decaimento de peso (weight decay) é aplicado (veja Regularização). Duas execuções com o mesmo modelo e os mesmos dados podem se comportar de forma muito diferente dependendo da escolha do otimizador.
Matematicamente, o problema de otimização é:
[ \min_{\theta} \ \mathbb{E}{(x,y)\sim \mathcal{D}}[\ell(f\theta(x), y)]. ]
Como o conjunto de dados é grande, usamos minilotes (minibatches) para estimar gradientes, levando à otimização estocástica (stochastic optimization).
Da descida do gradiente (gradient descent) à descida estocástica do gradiente (stochastic gradient descent, SGD)
A descida do gradiente em lote completo (full-batch gradient descent) atualiza parâmetros usando gradientes calculados sobre todo o conjunto de dados:
[ \theta_{t+1} = \theta_t - \eta \nabla_\theta L(\theta_t), ]
onde (\eta) é a taxa de aprendizado (learning rate).
Em aprendizado profundo, quase sempre usamos SGD com minilotes (minibatch SGD):
[ g_t = \nabla_\theta L_{\mathcal{B}t}(\theta_t), \quad \theta{t+1} = \theta_t - \eta g_t, ]
onde (\mathcal{B}_t) é um minilote. O gradiente é ruidoso, mas esse ruído frequentemente é útil: ele pode melhorar a exploração e, em muitos cenários, a generalização (generalization).
Essa é a ideia central por trás de Descida do Gradiente. Os otimizadores diferem principalmente em como transformam (g_t) em uma atualização.
Os blocos de construção dos otimizadores modernos
A maioria dos otimizadores amplamente usados pode ser entendida como combinações de:
- Taxa de aprendizado ((\eta)): Tamanho do passo global. Este costuma ser o hiperparâmetro (hyperparameter) mais importante.
- Momentum: Média exponencial de gradientes para suavizar ruído e acelerar em direções consistentes.
- Escalonamento adaptativo (adaptive scaling): Tamanhos de passo por parâmetro com base em estatísticas do gradiente (por exemplo, segundos momentos (second moments)).
- Decaimento de peso / regularização L2 (L2 regularization): Penaliza pesos grandes; interage de forma sutil com métodos adaptativos.
- Truques de estabilidade (stability tricks): termos (\epsilon), correção de viés (bias correction), clipping de gradiente (gradient clipping) etc. (veja Problemas de Gradiente).
A escolha do otimizador e o escalonamento da taxa de aprendizado (learning rate scheduling) são fortemente acoplados; a maioria das “receitas” de treinamento assume um cronograma específico (veja Cronogramas de Taxa de Aprendizado).
SGD e suas variantes com momentum
SGD simples
O SGD simples (vanilla SGD) é o mais básico: atualizar no sentido oposto ao gradiente do minilote.
- Prós: simples, leve em memória, frequentemente com forte generalização com o cronograma certo.
- Contras: pode ser lento para convergir; sensível à taxa de aprendizado; sofre com curvatura mal condicionada (ill-conditioned curvature).
Na prática, “SGD” geralmente significa “SGD + momentum”.
SGD com momentum
O SGD com momentum mantém um vetor de velocidade (velocity vector) (v_t) que acumula gradientes:
[ v_{t+1} = \mu v_t + g_t,\quad \theta_{t+1} = \theta_t - \eta v_{t+1}, ]
onde (\mu \in [0,1)) é o coeficiente de momentum (comumente 0.9).
Intuição:
- Se os gradientes continuam apontando em uma direção similar, o momentum aumenta o tamanho de passo efetivo.
- Se os gradientes oscilam (comum em direções íngremes), o momentum suaviza as atualizações.
O momentum frequentemente torna o treinamento mais rápido e menos ruidoso na mesma taxa de aprendizado.
Momentum de Nesterov
O gradiente acelerado de Nesterov (Nesterov accelerated gradient, NAG) é um pequeno ajuste: calcular o gradiente em uma posição de “olhar à frente”. Muitos frameworks o implementam como uma variante do SGD com momentum.
Ele pode melhorar a responsividade (menos ultrapassagem) e é popular em receitas clássicas de treinamento em visão computacional.
Comportamento prático do SGD
O SGD (com momentum) frequentemente tem estas características:
- Precisa de um cronograma: Uma taxa de aprendizado constante raramente é ótima. Receitas clássicas usam decaimento em degraus (step decay); as modernas frequentemente usam decaimento cossenoidal (cosine decay) e aquecimento (warmup) (veja Cronogramas de Taxa de Aprendizado).
- Pode generalizar muito bem: Empiricamente, métodos do tipo SGD muitas vezes são competitivos ou melhores em algumas tarefas supervisionadas, especialmente com ajuste cuidadoso.
- Sensível ao tamanho do lote (batch size): Lotes maiores reduzem o ruído do gradiente e frequentemente exigem escalonamento da taxa de aprendizado e/ou aquecimento.
Um padrão comum é:
- Usar uma taxa de aprendizado relativamente alta no início (possivelmente com aquecimento),
- Depois reduzi-la para refinar a solução.
Otimizadores adaptativos: Adagrad, RMSProp, Adam
Métodos adaptativos reescalam atualizações por parâmetro, tipicamente usando segundos momentos do gradiente. Eles frequentemente convergem mais rápido “sem ajustes” (out of the box), especialmente quando os gradientes são esparsos (sparse) ou estão em escalas diferentes entre parâmetros.
Adagrad (histórico, mas instrutivo)
O Adagrad acumula gradientes ao quadrado:
[ s_{t+1} = s_t + g_t^2,\quad \theta_{t+1} = \theta_t - \eta \frac{g_t}{\sqrt{s_{t+1}} + \epsilon}. ]
Ele funciona bem para atributos esparsos, mas o acúmulo faz as taxas de aprendizado encolherem continuamente, às vezes de forma agressiva demais para redes profundas.
RMSProp
O RMSProp corrige o acumulador sempre crescente do Adagrad usando uma média móvel exponencial:
[ s_{t+1} = \beta s_t + (1-\beta) g_t^2,\quad \theta_{t+1} = \theta_t - \eta \frac{g_t}{\sqrt{s_{t+1}} + \epsilon}. ]
Isso é comum em redes recorrentes (recurrent networks) e em alguns cenários de aprendizado por reforço (reinforcement learning, RL).
Adam
O Adam combina momentum (primeiro momento) e escalonamento do tipo RMSProp (segundo momento), com correção de viés:
[ m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t ] [ v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2 ]
Estimativas com correção de viés:
[ \hat{m}_t = \frac{m_t}{1-\beta_1^t},\quad \hat{v}_t = \frac{v_t}{1-\beta_2^t} ]
Atualização:
[ \theta_{t+1} = \theta_t - \eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}. ]
Padrões típicos:
- (\beta_1 = 0.9)
- (\beta_2 = 0.999)
- (\epsilon = 10^{-8}) (mas algumas implementações usam valores diferentes; (\epsilon) pode importar para estabilidade)
Por que o Adam é popular
- Frequentemente treina bem com ajuste mínimo.
- Lida bem com escalas diferentes entre parâmetros.
- Funciona bem com gradientes esparsos e arquiteturas complexas (Transformers (Transformers) comumente usam AdamW).
AdamW (Adam com decaimento de peso desacoplado)
Um detalhe prático crucial: decaimento de peso não é a mesma coisa que regularização L2 no Adam quando implementado de forma ingênua.
- No SGD, adicionar uma penalidade L2 (\lambda |\theta|^2) é equivalente ao decaimento de peso na atualização.
- No Adam, como os gradientes são reescalados por parâmetro, acoplar a penalidade ao gradiente muda como o decaimento se comporta.
O AdamW desacopla o decaimento de peso da atualização baseada em gradiente:
[ \theta_{t+1} = \theta_t - \eta \left(\frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} + \lambda \theta_t \right) ]
(na prática, implementações aplicam o decaimento como um passo separado de encolhimento multiplicativo).
O AdamW é o otimizador padrão em muitas receitas modernas de aprendizado profundo, especialmente para Transformers, porque faz o decaimento de peso se comportar de maneira mais previsível (veja Regularização).
Comportamento de otimizadores na prática
Velocidade de otimização vs generalização
Uma observação empírica comum:
- Otimizadores adaptativos (Adam/AdamW) frequentemente reduzem a perda de treinamento mais rápido no início.
- SGD com momentum pode igualar ou superar o desempenho final em alguns cenários supervisionados quando ajustado com cuidado.
Isso não é uma lei universal — arquitetura, tamanho do conjunto de dados, aumentação (augmentation), tamanho do lote e regularização interagem — mas é um bom modelo mental ao escolher baselines.
Sensibilidade à taxa de aprendizado
- O SGD tipicamente tem uma faixa “boa” de taxa de aprendizado mais estreita, mas, uma vez ajustado, pode ser excelente.
- Adam/AdamW tende a ser mais tolerante, mas ainda pode divergir se (\eta) for grande demais (especialmente com precisão mista (mixed precision) ou sem aquecimento).
Cronogramas de taxa de aprendizado importam pelo menos tanto quanto a escolha do otimizador (veja Cronogramas de Taxa de Aprendizado).
Decaimento de peso e “o que não decair”
Na prática, muitas receitas:
- Aplicam decaimento de peso a matrizes de “pesos”,
- Excluem termos de viés (bias) e parâmetros de normalização (escala e viés do BatchNorm/LayerNorm), porque decair esses parâmetros pode prejudicar a otimização.
Isso se conecta a Inicialização e Normalização: camadas de normalização afetam fortemente a estabilidade do treinamento, e seus parâmetros frequentemente exigem tratamento especial.
Interações com o tamanho do lote
O tamanho do lote muda a escala do ruído do gradiente:
- Lotes maiores frequentemente permitem taxas de aprendizado maiores (até certo ponto), mas podem precisar de aquecimento.
- Lotes menores adicionam ruído que pode ajudar na exploração, mas também podem desestabilizar o treinamento se a taxa de aprendizado for alta demais.
Se você mudar o tamanho do lote, não presuma que a mesma taxa de aprendizado continuará ótima.
Modos de falha a reconhecer
“Sintomas” comuns de otimizador:
- A perda explode / NaNs: taxa de aprendizado alta demais, escalonamento instável de precisão mista, falta de clipping de gradiente, inicialização ruim, ou problemas numéricos com softmax/logs (veja Problemas de Gradiente).
- A perda diminui extremamente devagar: taxa de aprendizado baixa demais, decaimento de peso excessivo, cronograma incorreto, camadas congeladas.
- A perda de treinamento diminui, mas a validação estagna: regularização fraca/forte demais, problemas nos dados, ou simplesmente overfitting; o otimizador pode contribuir via viés implícito.
Exemplos práticos (no estilo PyTorch)
Exemplo 1: SGD + momentum com um cronograma em degraus ou cosseno
import torch
from torch import nn
model = ...
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.1,
momentum=0.9,
weight_decay=1e-4, # classic vision recipe
nesterov=True
)
# Either a step schedule...
scheduler = torch.optim.lr_scheduler.MultiStepLR(
optimizer, milestones=[60, 80], gamma=0.1
)
# ...or cosine decay (often better with modern training)
# scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)
for epoch in range(100):
model.train()
for x, y in train_loader:
optimizer.zero_grad(set_to_none=True)
logits = model(x)
loss = criterion(logits, y)
loss.backward()
# Optional: clip gradients for stability
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
scheduler.step()
Notas:
- O SGD comumente combina com uma taxa de aprendizado inicial maior do que o Adam.
- Muitas receitas com SGD dependem fortemente do cronograma para alcançar boa acurácia final.
Exemplo 2: AdamW com grupos de parâmetros (parameter groups) (não decair normalizações/viés)
def split_decay_params(model):
decay, no_decay = [], []
for name, p in model.named_parameters():
if not p.requires_grad:
continue
# Common heuristic: no weight decay on biases and norm parameters
if name.endswith(".bias") or "norm" in name.lower():
no_decay.append(p)
else:
decay.append(p)
return decay, no_decay
decay, no_decay = split_decay_params(model)
optimizer = torch.optim.AdamW(
[{"params": decay, "weight_decay": 0.01},
{"params": no_decay, "weight_decay": 0.0}],
lr=3e-4,
betas=(0.9, 0.999),
eps=1e-8
)
# Pair AdamW with warmup + cosine decay (common for Transformers).
# You'd typically implement warmup via a custom scheduler or library helper.
Notas:
- O AdamW frequentemente usa taxas de aprendizado como (1\text{e-}4) a (3\text{e-}4) como pontos de partida comuns para modelos do tipo Transformer, mas isso depende da tarefa.
- O aquecimento pode ser crítico ao usar lotes grandes ou ao treinar arquiteturas instáveis.
Como escolher entre SGD e Adam/AdamW
Estes são pontos de partida pragmáticos, não regras rígidas:
Use Adam/AdamW quando:
- Você quer um baseline forte com ajuste mínimo.
- Você está treinando Transformers ou outras arquiteturas com forte uso de atenção (attention) (AdamW é o padrão comum).
- Os gradientes são esparsos ou variam muito em escala entre parâmetros.
- Você está fazendo ajuste fino (fine-tuning) de modelos pré-treinados (pretrained models) (AdamW é frequentemente usado, embora SGD também possa funcionar).
Use SGD (+ momentum) quando:
- Você está treinando muitos modelos convolucionais ou supervisionados “clássicos” do zero e tem bons cronogramas/aumentações.
- Você se importa em extrair o desempenho final máximo e pode arcar com ajustes.
- A memória é limitada (Adam armazena buffers adicionais de momentos, ~2× de sobrecarga de memória em relação aos parâmetros).
Um bom fluxo de trabalho:
- Comece com uma receita amplamente usada para sua família de arquitetura.
- Obtenha uma execução estável (sem NaNs, curva de aprendizado sensata).
- Ajuste taxa de aprendizado e cronograma antes de trocar a família de otimizadores.
- Ajuste decaimento de peso e tamanho do lote conforme necessário.
Hiperparâmetros que mais importam
Taxa de aprendizado
O parâmetro mais importante.
- Se o treinamento diverge cedo: diminua a taxa de aprendizado, adicione aquecimento, adicione clipping, verifique normalização e escalonamento de perda.
- Se o treinamento é lento: aumente a taxa de aprendizado ou ajuste o cronograma.
Decaimento de peso
Afeta fortemente a generalização e às vezes a otimização.
- AdamW comumente usa (0.01) como baseline para Transformers, mas varia.
- Receitas de visão com SGD frequentemente usam (1\text{e-}4) (com aumentação pesada), mas isso não é universal.
Momentum / betas
- Momentum do SGD: (\mu = 0.9) é um padrão comum; 0.95 às vezes é usado.
- (\beta_1, \beta_2) do Adam: padrões ((0.9, 0.999)) funcionam bem de forma ampla; algumas tarefas se beneficiam de ajustar (\beta_2) (afeta a escala de tempo da adaptatividade).
Epsilon (\(\epsilon\))
Frequentemente ignorado, mas pode afetar a estabilidade em baixa precisão ou quando os gradientes são minúsculos. Algumas implementações usam (\epsilon=1\text{e-}8); outras usam valores maiores (por exemplo, (1\text{e-}6)) por estabilidade.
Clipping de gradiente
Útil quando gradientes ocasionalmente disparam (comum em redes neurais recorrentes (recurrent neural networks, RNNs), Transformers, RL):
- Clipping da norma em 1.0 é um ponto de partida comum.
- Clipping muda a otimização efetiva; use-o de forma consistente uma vez habilitado.
Considerações práticas avançadas (mas comuns)
Precisão mista e escalonamento de perda
Com treinamento em FP16/bfloat16, gradientes podem sofrer underflow/overflow com mais facilidade. Escalonamento dinâmico de perda (dynamic loss scaling) (comum em frameworks) ajuda a evitar NaNs e interage com a estabilidade do otimizador. Se você vê NaNs intermitentes, investigue as configurações de precisão mista antes de culpar o otimizador.
Média móvel exponencial (EMA) dos pesos
Muitos pipelines de treinamento mantêm uma média móvel exponencial (exponential moving average, EMA) dos parâmetros: [ \theta^{EMA}{t} = \alpha \theta^{EMA}{t-1} + (1-\alpha)\theta_t ] e avaliam usando os pesos de EMA. Isso pode melhorar métricas de validação sem mudar a atualização do otimizador em si.
Treinamento distribuído e otimizadores para lotes grandes
Em tamanhos de lote muito grandes, métodos especializados como LARS ou LAMB podem ajudar ao adaptar taxas de aprendizado por camada (layer-wise). Isso é mais relevante em pré-treinamento em larga escala e pode não ajudar em configurações menores.
Métodos de segunda ordem (raros em aprendizado profundo em escala)
Métodos como L-BFGS podem funcionar bem para modelos pequenos ou casos especiais, mas são incomuns para grandes redes neurais devido a memória/custo computacional e aos gradientes ruidosos de minilotes.
Principais conclusões
- SGD (geralmente com momentum) é simples, eficiente em memória e frequentemente excelente quando combinado com um bom cronograma de taxa de aprendizado.
- Adam adiciona adaptatividade e tipicamente converge mais rápido com menos ajustes; AdamW é a variante preferida ao usar decaimento de peso.
- Na prática, taxa de aprendizado + cronograma + decaimento de peso frequentemente importam tanto quanto (ou mais do que) a família do otimizador.
- Observe o comportamento no mundo real: divergência, treinamento lento e lacunas de generalização muitas vezes podem ser diagnosticados ajustando taxa de aprendizado, cronograma, clipping e decaimento — antes de reescrever seu modelo.
Para mais contexto sobre o restante da pilha de treinamento, veja Cronogramas de Taxa de Aprendizado, Regularização e Problemas de Gradiente.