Stochastic Gradient Descent (SGD)
SGD treina um modelo com muitas atualizações de pesos de baixo custo, calculadas em pequenos lotes aleatórios de dados. Aborda a regra de atualização, o momentum, o tamanho do lote e o código.
A descida estocástica do gradiente (SGD) é um algoritmo de otimização que treina um modelo atualizando repetidamente seus pesos com o gradiente da perda calculado em uma pequena amostra aleatória dos dados de treinamento, em vez de usar todo o conjunto de dados. O gradiente de cada amostra é uma estimativa ruidosa, mas não tendenciosa, do gradiente do conjunto completo, então o SGD realiza muitas atualizações baratas no tempo em que a descida do gradiente em lote completo faria uma atualização cara. O método descende do método de aproximação estocástica de Robbins e Monro, de 1951, e, com momentum, continua sendo um otimizador padrão para redes neurais convolucionais (CNNs).
Como funciona o SGD#
Em cada etapa do SGD, o algoritmo usa um minibatch B, calcula o gradiente da perda nele com retropropagação e atualiza cada peso com um pequeno passo na direção oposta à do gradiente: w ← w − η·∇L_B(w), em que η é a taxa de aprendizado. Tecnicamente, o SGD usa um exemplo por etapa, mas os frameworks de aprendizado profundo o aplicam a minibatches de dezenas a milhares de exemplos, então, na prática, “SGD” e “descida do gradiente em minibatches” são sinônimos. Um tamanho de lote maior produz um gradiente menos ruidoso e melhor utilização do hardware, mas reduz o número de atualizações por época.
O treinamento embaralha os dados no início de cada época, percorre-os em minibatches e dá um passo por lote. Ajustar uma reta a 1.000 pontos ruidosos demonstra todo o procedimento:
import torch
torch.manual_seed(0)
# 1,000 noisy samples of y = 3x + 2
x = torch.randn(1000, 1)
y = 3 * x + 2 + 0.1 * torch.randn(1000, 1)
model = torch.nn.Linear(1, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.05, momentum=0.9)
for epoch in range(5):
# Reshuffle every epoch, then update the weights once per 32-sample mini-batch
for idx in torch.randperm(1000).split(32):
loss = torch.nn.functional.mse_loss(model(x[idx]), y[idx])
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"epoch {epoch}: w = {model.weight.item():.3f}, b = {model.bias.item():.3f}, loss = {loss.item():.4f}")Após duas épocas e 64 atualizações no total, o peso chega a 3.081 e o viés, a 2.012. Nas três épocas seguintes, o peso continua variando entre 2.967 e 3.003, em vez de se estabilizar, porque o gradiente de cada minibatch contém ruído de amostragem; a perda do último lote fica perto de 0.01, a variância do ruído adicionado a y.
Taxa de aprendizagem, ruído e tamanho do lote#
Com uma taxa de aprendizagem constante, o SGD oscila numa região em torno do mínimo, cujo tamanho aumenta com a taxa de aprendizagem e o ruído do gradiente. A teoria da aproximação estocástica garante convergência apenas quando a taxa de aprendizagem diminui até zero; por isso, na prática, ela é reduzida ao longo do tempo com decaimento em etapas ou agendas cossenoidais, geralmente após um breve aquecimento. O ruído do gradiente de lotes pequenos também conduz o SGD a mínimos planos, que generalizam melhor do que os mínimos acentuados que o treino com lotes grandes tende a encontrar, uma forma implícita de regularização.
O tamanho do lote e a taxa de aprendizagem interagem. Aumentar a taxa de aprendizagem na mesma proporção que o tamanho do lote, com um aquecimento de cinco épocas, permitiu ao ResNet-50 treinar com o ImageNet em uma hora, usando minilotes de 8.192 imagens e sem perda de precisão.
Momentum e momentum de Nesterov#
Num vale longo e estreito da superfície de perda, o gradiente aponta sobretudo através do vale, por isso o SGD simples ziguezagueia entre as paredes enquanto avança lentamente pelo fundo. O momentum mantém uma velocidade acumulada, v ← μ·v + g, e avança nessa direção, w ← w − η·v, de modo que direções consistentes do gradiente ganham velocidade e direções oscilantes se anulam. O valor padrão habitual do coeficiente é μ = 0.9. O momentum de Nesterov avalia o gradiente após um passo de antecipação ao longo da velocidade, o que reduz ainda mais a ultrapassagem. O SGD do PyTorch implementa ambos e aplica weight_decay adicionando λ·w ao gradiente, o que, para SGD, equivale a uma penalização L2 dos pesos.
SGD vs. outros otimizadores#
- Descida do gradiente em lote: Calcula o gradiente exato em todo o conjunto de dados para cada atualização, pelo que cada época produz um único passo e as necessidades de memória aumentam com o conjunto de dados.
- Adam: Atribui a cada parâmetro o seu próprio tamanho de passo com base nas médias móveis dos gradientes e dos gradientes ao quadrado, e armazena dois valores de estado por parâmetro, em comparação com um no SGD com momentum. O Adam avança mais rapidamente no início e é menos sensível à taxa de aprendizagem, enquanto o SGD com momentum bem ajustado muitas vezes iguala ou supera a sua precisão final em CNNs. O AdamW, a sua variante com decaimento de pesos desacoplado, é o otimizador padrão para Transformers.
- Muon: Ortogonaliza a atualização do momentum de cada matriz de pesos como um todo e foi escalado para o treino de modelos de linguagem de grande dimensão.
O treinamento do Ultralytics YOLO cria o SGD do PyTorch com momentum de Nesterov quando optimizer="SGD" está definido, usando lr0=0.01 e momentum=0.937 por padrão. O optimizer="auto" padrão ignora lr0 e escolhe o MuSGD, uma combinação de atualizações SGD e Muon, para execuções com mais de 10.000 iterações, e AdamW para execuções mais curtas.
Onde se usa o SGD#
O SGD com momentum treinou a maioria das CNNs marcantes do ImageNet. Os modelos ResNet originais usavam momentum de 0.9, decaimento de pesos de 0.0001 e minilotes de 256 imagens, com uma taxa de aprendizagem que começava em 0.1 e era dividida por 10 sempre que o erro estabilizava. Fora da aprendizagem profunda, o SGDClassifier e o SGDRegressor do scikit-learn ajustam modelos lineares, como a regressão logística e as máquinas de vetores de suporte lineares, com SGD; o método partial_fit treina em conjuntos de dados demasiado grandes para caber na memória, um bloco de cada vez.










