Fazer a rede treinar — e fazê-la generalizar
Uma rede de seis camadas com loss presa em ln 2, corrigida medição a medição. Depois, double descent: 5.000 parâmetros em 40 pontos.
Nesta página
A rede do Capítulo 5 funciona. Tem nove parâmetros, aprende XOR, e os seus gradientes coincidem com os do PyTorch até à décima sexta casa decimal.
Torne-a profunda, com seis camadas, e deixa de aprender por completo. Não devagar — por completo. Eis uma rede de seis camadas num problema de classificação com duas espirais, treinada durante 5000 passos:
step 1: loss 0.693147
step 5000: loss 0.693147
accuracy: 50.0 %Esse número não é arbitrário. é a entropia cruzada binária de um modelo que devolve probabilidade para tudo, e 50 % é um lançamento de moeda num dataset equilibrado. Depois de cinco mil passos, a rede não se mexeu um único dígito. Nada falhou, nada avisou, e os gradientes continuam exatamente certos.
Este capítulo é sobre a distância entre uma rede que corre e uma rede que funciona. Tem duas metades que parecem assuntos diferentes e são a mesma tarefa: fazer a loss descer, e fazê-la descer em dados que o modelo nunca viu.
Porque é que a rede de seis camadas está morta
Ligação para a secção: Porque é que a rede de seis camadas está mortaComece por observar, em vez de adivinhar. Passe um batch de inputs pela rede e imprima o desvio-padrão das ativações em cada camada, e depois o desvio-padrão dos gradientes dos pesos:
def profile(model, x):
h = x
for layer in model:
h = layer(h)
if isinstance(layer, (nn.Tanh, nn.ReLU)):
print(f"activation std: {h.std().item():.4f}")
model(x).sum().backward()
for p in model.parameters():
if p.dim() == 2:
print(f"gradient std: {p.grad.std().item():.2e}")Três inicializações, a mesma arquitetura, seis camadas de :
| inicialização | desvio-padrão das ativações, camadas 1→6 |
|---|---|
| normal, std | 0.0145 · 0.0016 · 0.0002 · 0.0000 · 0.0000 · 0.0000 |
| normal, std | 0.6573 · 0.9296 · 0.9585 · 0.9634 · 0.9637 · 0.9625 |
| Xavier | 0.1579 · 0.1493 · 0.1353 · 0.1333 · 0.1325 · 0.1403 |
| inicialização | desvio-padrão do gradiente, primeira camada → última |
|---|---|
| normal, std | 3.20e-06 · 4.97e-07 · … · 6.40e-06 |
| normal, std | 1.94e+03 · 2.28e+02 · 1.22e+02 · 4.43e+01 · 1.85e+01 · 7.30e+00 |
| Xavier | 2.31e+00 · 4.50e-01 · 4.26e-01 · 3.89e-01 · 4.39e-01 · 4.73e-01 |
A primeira linha é a rede acima, e não está a aprender devagar — já não tem sinal. À quarta camada, o desvio-padrão das ativações sofreu underflow para zero em quatro casas decimais. Todos os inputs produzem o mesmo output, o output é uma constante, e o gradiente de uma constante não é nada. Os pesos foram inicializados pequenos «por segurança», e pequeno foi fatal.
A segunda linha é a falha oposta, e vale a pena compreendê-la porque é contraintuitiva. As ativações parecem saudáveis — por volta de 0.96 — mas isso é saturado, preso perto do seu limite, exatamente o regime que o Capítulo 5 mediu como perdendo um fator de quase dez mil no gradiente. E, no entanto, os gradientes são enormes: 1940 na primeira camada. As duas coisas são verdadeiras ao mesmo tempo. Cada passo para trás multiplica por , e com 128 inputs de variância unitária esse fator tem um ganho de cerca de , que domina o encolhimento provocado pelo saturado. Os gradientes crescem geometricamente no caminho de volta. Isto é o exploding gradient, e produz valores de loss de nan em poucos passos em qualquer treino real.
A terceira linha é o que se quer: ativações com escala aproximadamente constante ao longo da profundidade, gradientes com escala aproximadamente constante ao longo da profundidade. Nada morre, nada explode.
Normalização, e qual sobreviveu
Ligação para a secção: Normalização, e qual sobreviveuInicializar bem corrige a escala no passo zero. Não a mantém fixa: os pesos mexem-se, e ao passo cinco mil o argumento cuidadoso da variância já não se aplica.
As camadas de normalização impõem a escala continuamente. Dado um vetor de ativações, subtrai-se uma média, divide-se por um desvio-padrão, e depois aplica-se uma escala aprendida e um deslocamento para que a camada possa desfazer a normalização se for isso que acabar por querer:
A única pergunta real é sobre o que se calcula a média. A normalização por batch3 calcula e ao longo da dimensão do batch, uma estatística por feature. A normalização por camada4 calcula-as ao longo das features, uma estatística por exemplo.
Essa escolha parece menor e decide quase tudo a jusante:
BatchNorm faz com que o output de cada exemplo dependa dos outros exemplos que calharam estar no seu batch. Durante o treino, isso é um regularizador suave. Em inferência, não há batch, por isso tem de manter uma média móvel das estatísticas recolhidas durante o treino — o que significa que a camada se comporta de forma diferente em modo de treino e de avaliação, e esquecer-se de trocar de modo é um dos bugs mais comuns na área. Também degrada com batches pequenos, e é incómoda com sequências de comprimento variável, porque «a média sobre o batch na posição 40» é calculada a partir de quantas sequências acontecerem ter esse comprimento.
LayerNorm normaliza cada exemplo por si só. Sem dependência do batch, sem estatísticas móveis, comportamento idêntico em treino e inferência, indiferente ao tamanho do batch, indiferente ao comprimento da sequência. Cada uma dessas propriedades deixa de ser uma comodidade e passa a ser um requisito quando se está a gerar um token de cada vez para um utilizador, que é onde o Capítulo 13 vai parar.
É por isso que LayerNorm é a que voltará a encontrar no Capítulo 9 inalterada: o bloco transformer usa-a, e usa-a pelas razões na coluna da direita, não porque funcione melhor em abstrato.
Corrigir uma coisa de cada vez, que é a verdadeira skill
Ligação para a secção: Corrigir uma coisa de cada vez, que é a verdadeira skillQuatro correções candidatas para a rede morta: inicialização Xavier, LayerNorm, ligações residuais e Adam em vez de SGD. A tentação é aplicar as quatro e avançar. Faça isso e nunca saberá qual delas importou, e da próxima vez que acontecer não terá método — apenas um ritual.
Por isso, aplique-as uma de cada vez. A mesma seed, os mesmos dados, a mesma arquitetura, 800 passos:
| o que foi acrescentado | loss final | accuracy |
|---|---|---|
| nada | 0.6931 | 50.0 % |
| inicialização Xavier | 0.5692 | 60.4 % |
| LayerNorm | 0.6230 | 61.5 % |
| ligações residuais | 0.6651 | 56.6 % |
| Adam | 0.6787 | 58.7 % |
| as quatro | 0.0000 | 100.0 % |
Leia essa tabela como a leria às 2 da manhã e a conclusão é: nada funciona sozinho, tudo funciona em conjunto, portanto deep learning é alquimia. Essa conclusão está errada, e descobrir porquê é a coisa mais útil deste capítulo.
Dê a cada execução seis vezes mais orçamento — 5000 passos em vez de 800 — e o cenário muda por completo:
| o que foi acrescentado | loss final @ 5000 | accuracy |
|---|---|---|
| nada | 0.6931 | 50.0 % |
| inicialização Xavier | 0.0007 | 100.0 % |
| LayerNorm | 0.0002 | 100.0 % |
| ligações residuais | 0.6653 | 56.7 % |
| Adam | 0.6908 | 53.4 % |
| Xavier + Adam | 0.0000 | 100.0 % |
| Xavier + LayerNorm | 0.0001 | 100.0 % |
Agora a imagem fica nítida, e é um diagnóstico em vez de um ritual.
A inicialização por si só corrige o problema. A normalização por si só corrige o problema. Cada uma ataca a doença real — o sinal para a frente a colapsar para zero — e qualquer uma é suficiente. Aos 800 passos apenas pareciam crédito parcial, porque já tinham resolvido o problema e ainda estavam a sair do buraco.
As ligações residuais e Adam não corrigem o problema, com nenhum orçamento. Não porque sejam maus, mas porque tratam uma doença diferente. Uma ligação residual dá ao gradiente um caminho à volta de uma camada bloqueante; isso vale muito quando o problema é o gradiente, e não vale nada quando o sinal para a frente já é zero, porque um atalho à volta de uma camada morta continua a transportar um valor morto. Adam reescala o passo de cada parâmetro pelo seu próprio histórico de gradientes; isso ajuda quando os gradientes têm magnitudes muito diferentes, e não consegue ressuscitar uma rede cujo output não depende do input.
E «nada» continua exatamente 0.6931 depois de cinco mil passos. Não 0.6929. Não é lenta; está morta, e essa distinção é visível de uma forma que antes não era, porque tem a linha que diz que uma correção funciona para comparar.
Merecer o PyTorch
Ligação para a secção: Merecer o PyTorchDaqui em diante, este curso usa PyTorch. Isso deve ser merecido em vez de anunciado, por isso aqui está exatamente o que ele faz e que já sabe fazer.
Um otimizador é uma regra para transformar gradientes em atualizações de parâmetros. O gradient descent simples usa o gradiente. Momentum usa uma média móvel dele, que suaviza o ruído e ganha velocidade em direções que permanecem consistentes:
v = beta * v + p.grad
p -= lr * v Adam5 mantém duas médias móveis — do gradiente e do gradiente ao quadrado — e divide uma pela raiz quadrada da outra, para que cada parâmetro receba um passo escalado para a sua própria magnitude recente de gradiente:
m = b1 * m + (1 - b1) * g # mean of the gradient
v = b2 * v + (1 - b2) * g * g # mean of the squared gradient
m_hat = m / (1 - b1 ** t) # bias correction: both averages start at zero
v_hat = v / (1 - b2 ** t)
p -= lr * m_hat / (v_hat.sqrt() + eps) Dez linhas. Execute ambas contra torch.optim no mesmo problema durante 50 passos:
SGD+momentum by hand [2.7781870365142822, -1.0304985046386719]
torch [2.7781870365142822, -1.0304983854293823] max |diff| = 1.19e-07
Adam by hand [0.4893140196800232, -0.46317872405052185]
torch [0.48931416869163513, -0.46317875385284424] max |diff| = 1.49e-07Idêntico até à precisão float32. torch.optim.Adam são essas cinco linhas, mais décadas de cuidado com casos-limite e um kernel C++. Esta é a troca que fará daqui para a frente: não magia em vez de compreensão, mas velocidade em troca de linhas que já escreveu.
Porque existe Adam: curvatura
Ligação para a secção: Porque existe Adam: curvaturaA explicação habitual de Adam é «learning rates adaptativas por parâmetro», o que é uma descrição e não uma razão. A razão é geometria, e pode ser medida.
Pegue numa loss cuja curvatura difere entre direções: íngreme numa, suave noutra. SGD tem uma learning rate global, por isso tem de escolher um valor pequeno o suficiente para ser estável na direção mais íngreme — e esse valor é então demasiado pequeno para a direção suave, onde o progresso se arrasta. É isto que causa a imagem clássica de gradient descent a fazer ziguezague por um vale estreito.
Dois rácios de curvatura, três otimizadores, 300 passos, e a cada otimizador foi dada a melhor learning rate de uma sweep para que ninguém ficasse em desvantagem:
| rácio de curvatura | SGD | SGD + momentum | Adam |
|---|---|---|---|
| 10 : 1 | erro 0.000002 | erro 0.000000 | erro 0.000000 |
| 1000 : 1 | erro 1.925485 | erro 0.001432 | erro 0.000000 |
| divergiu em (1000:1) | 4 de 8 taxas | 4 de 8 taxas | 0 de 6 taxas |
Com um rácio de dez, tudo funciona e não há nada a discutir. Com mil, SGD simples não consegue chegar à resposta em nenhuma learning rate testada — o seu melhor resultado ainda é um erro de 1.93 — e diverge por completo em metade das taxas. Adam aterra exatamente no alvo e não diverge em nenhuma.
Essa última coluna é a razão prática pela qual Adam é o padrão. Não é que Adam encontre soluções melhores; em problemas bem condicionados, SGD bem ajustado muitas vezes iguala-o ou supera-o. É que Adam é muito menos sensível à learning rate que escolheu, e redes reais têm rácios de curvatura muito piores do que mil ao longo dos seus milhões de parâmetros.
Cabem aqui mais duas peças, ambas de uma linha. Gradient clipping reescala o vetor de gradiente sempre que a sua norma excede um limiar, o que transforma a linha «a loss salta de repente para um valor enorme» da tabela de diagnóstico num não-evento. E calendários de learning rate: um warmup curto a partir de quase zero ao longo das primeiras centenas de passos, porque as estimativas de variância de Adam são lixo até terem visto alguns gradientes e um passo de tamanho completo dado sobre lixo pode arruinar uma inicialização; depois cosine decay em direção a zero, porque terminar uma execução com o mesmo tamanho de passo com que se começou significa oscilar à volta do mínimo em vez de assentar nele.
A segunda metade: o modelo que ajusta perfeitamente e não prevê nada
Ligação para a secção: A segunda metade: o modelo que ajusta perfeitamente e não prevê nadaAté aqui, tudo foi sobre fazer a loss descer. Agora vem a metade mais difícil, porque a loss descer não é o objetivo — é um proxy do objetivo, e o proxy falha de uma forma específica e famosa.
Doze pontos de uma função suave com um pouco de ruído. Ajuste polinómios de grau crescente:
| grau | RMSE treino | RMSE teste |
|---|---|---|
| 1 | 0.764499 | 0.6985 |
| 3 | 0.252605 | 0.3031 |
| 5 | 0.164437 | 0.1568 |
| 9 | 0.088960 | 0.2347 |
| 11 | 0.000000 | 1.2094 |
Grau 11 através de 12 pontos passa por cada um deles exatamente — erro de treino zero a seis casas decimais — e é oito vezes pior do que grau 5 em dados que não viu. Peça ao grau 3 e ao grau 11 para preverem em , mesmo fora do intervalo de treino:
degree 3: predicts -1.053 (truth -0.012)
degree 11: predicts +61.224 (truth -0.012)Sessenta e um, quando a resposta é aproximadamente zero. O modelo não aprendeu a função; aprendeu os doze pontos, e entre eles faz o que a aritmética exigir.
Isto é overfitting, e o seu oposto — grau 1, que não consegue representar a curva de todo e é mau em todo o lado — é underfitting. A explicação clássica divide o erro esperado de um modelo em três partes: bias, o erro causado pelo modelo ser rígido demais para representar a verdade; variance, o erro causado pelo modelo ser tão flexível que persegue o ruído desta amostra em particular; e ruído irredutível, que nada resolve. Modelos simples têm bias, modelos flexíveis têm variance alta, e a prescrição clássica é encontrar o ponto ideal no meio — grau 5 na tabela acima.
As ferramentas padrão atacam todas o termo variance:
- Regularização L2 (weight decay) acrescenta à loss, puxando os pesos para zero e tornando a função mais suave. Na tabela acima, o maior coeficiente do grau 11 é o que causa o estrago; penalizar o tamanho neutraliza-o.
- L1 acrescenta em vez disso. A diferença não é cosmética: o gradiente de L2 é proporcional ao peso e, por isso, encolhe à medida que o peso encolhe, aproximando-se de zero sem lá chegar, enquanto o gradiente de L1 é uma constante que continua a empurrar até ao fim. Portanto, L1 produz pesos que são exatamente zero — seleciona features. L2 produz pesos pequenos. Use L2 quando quiser suavidade, L1 quando quiser esparsidade.
- Dropout7 zera um subconjunto aleatório de ativações em cada passo de treino, para que nenhuma unidade possa depender de outra unidade específica estar presente.
- Early stopping observa a loss de validação e para quando ela começa a subir.
- Data augmentation fabrica mais exemplos de treino a partir dos que já tem, atacando o problema na origem: overfitting é tanto falta de dados como excesso de parâmetros.
- Cross-validation divide os dados de formas e treina vezes, o que compra uma estimativa fiável do erro de teste quando há poucos dados para reservar um conjunto held-out.
Double descent, ou porque a secção anterior não é a história toda
Ligação para a secção: Double descent, ou porque a secção anterior não é a história todaAgora, o facto que quebra a imagem.
A história bias-variance diz que, depois do ponto ideal, mais parâmetros significam pior generalização. Os modelos de linguagem modernos têm muito mais parâmetros do que as regras clássicas permitem para os dados que veem, e generalizam de forma soberba. Ambas as afirmações são verdadeiras, e reconciliá-las é a coisa mais útil deste capítulo.
Quarenta pontos de treino, inputs de vinte dimensões, features ReLU aleatórias, e o número de features varrido de 2 a 5000 — com a solução de norma mínima escolhida sempre que há muitas que ajustam os dados:
| RMSE treino | RMSE teste | |||
|---|---|---|---|---|
| 10 | 0.25 | 0.8822 | 1.2520 | 1.89 |
| 20 | 0.50 | 0.5962 | 1.1634 | 2.59 |
| 30 | 0.75 | 0.3896 | 1.5323 | 4.15 |
| 38 | 0.95 | 0.1769 | 3.7163 | 10.25 |
| 40 | 1.00 | 0.0000 | 5.8140 | 14.83 |
| 42 | 1.05 | 0.0000 | 3.1623 | 9.35 |
| 60 | 1.50 | 0.0000 | 1.1058 | 2.78 |
| 200 | 5.00 | 0.0000 | 0.6638 | 0.98 |
| 1500 | 37.50 | 0.0000 | 0.5859 | 0.33 |
| 5000 | 125.00 | 0.0000 | 0.5664 | 0.18 |
Leia-a em três partes. Até , a história clássica verifica-se exatamente: o erro desce, depois começa a subir. Em — o limiar de interpolação, onde o modelo tem exatamente parâmetros suficientes para passar por todos os pontos de treino — o erro de teste atinge o pico, em 5.81, cinco vezes pior do que o modelo pequeno. Esse pico é o aviso clássico, e é real.
Depois volta a descer. E continua a descer, para lá de , para lá de , até , onde o erro de teste de 0.5664 é melhor do que o melhor modelo subparametrizado alguma vez conseguiu. Um modelo com 5000 parâmetros ajustado a 40 pontos é o melhor modelo da tabela.
Isto é double descent,89 e o mecanismo é visível na última coluna. Assim que , há infinitas configurações de parâmetros que ajustam exatamente os dados de treino, e a que se obtém depende de como se escolhe. A solução de norma mínima escolhe a menor, e mostra o que isso significa: atinge o pico de 14.83 mesmo no limiar — onde há exatamente uma solução interpoladora e ficamos presos a ela, por mais extrema que seja — e depois desce monotonicamente à medida que cresce, porque mais parâmetros significam mais soluções interpoladoras por onde escolher, o que significa que a menor solução disponível fica menor. Em , a norma é 0.18, oitenta vezes menor do que no limiar.
Portanto, os parâmetros extra não estão a acrescentar complexidade. Estão a acrescentar escolha, e a regra de seleção gasta essa escolha em simplicidade. A regularização não está na função de loss; está no algoritmo. Gradient descent a partir de uma inicialização pequena tem um bias documentado para soluções de norma pequena, razão pela qual este comportamento aparece em redes reais treinadas da forma normal e não apenas na álgebra linear acima.
A consequência prática, da qual o Capítulo 10 depende: «o modelo tem mais parâmetros do que dados, portanto vai sofrer overfit» não é um argumento válido. Era uma boa regra quando os modelos viviam à esquerda do limiar. Agora, tudo o que é interessante vive muito à direita dele, onde a regra se inverte.
Para onde isto vai a seguir
Ligação para a secção: Para onde isto vai a seguirAs ferramentas deste capítulo são suficientes para treinar uma rede que funciona em dados que se podem pôr numa tabela: linhas de números, uma coluna de rótulos.
A linguagem não é isso. Antes de um modelo conseguir prever a palavra seguinte, algo tem de decidir o que é sequer uma «palavra» — e a resposta não são nem letras nem palavras, mas um vocabulário que o modelo aprende a partir dos bytes brutos dos dados de treino. Essa decisão, tomada uma vez antes do início do treino, determina quantas coisas o modelo consegue dizer, quanto custa um pedido, e porque é que modelos capazes de passar num exame de Direito não conseguem contar de forma fiável as letras em strawberry.
O Capítulo 7 constrói um tokenizer.
Fontes e método
Ligação para a secção: Fontes e métodoPara as ligações residuais usadas acima, He et al., Deep Residual Learning for Image Recognition (arXiv:1512.03385). Building makemore Part 3: Activations & Gradients, BatchNorm, de Andrej Karpathy, percorre o diagnóstico por histograma de ativações num modelo real e é o melhor tratamento prático da primeira metade deste capítulo. As aulas 8 e 11–13 de Learning From Data, de Yaser Abu-Mostafa, apresentam devidamente a teoria clássica da generalização, incluindo as partes que este capítulo comprimiu num parágrafo.
Referências
Ligação para a secção: Referências-
Glorot, X. and Bengio, Y. Understanding the difficulty of training deep feedforward neural networks. AISTATS (2010). O argumento de preservação da variância reproduzido na caixa acima. ↩
-
He, K., Zhang, X., Ren, S. and Sun, J. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. arXiv:1502.01852 (2015). ↩
-
Ioffe, S. and Szegedy, C. Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. arXiv:1502.03167 (2015). Note-se que a explicação de «internal covariate shift» no título foi entretanto substancialmente contestada; a camada funciona, mas a explicação original do porquê é disputada. ↩
-
Ba, J. L., Kiros, J. R. and Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). ↩
-
Kingma, D. P. and Ba, J. Adam: A Method for Stochastic Optimization. arXiv:1412.6980 (2014). ↩
-
Loshchilov, I. and Hutter, F. Decoupled Weight Decay Regularization. arXiv:1711.05101 (2017). ↩
-
Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I. and Salakhutdinov, R. Dropout: A Simple Way to Prevent Neural Networks from Overfitting. JMLR 15, pp. 1929–1958 (2014). ↩
-
Belkin, M., Hsu, D., Ma, S. and Mandal, S. Reconciling modern machine-learning practice and the classical bias–variance trade-off. PNAS 116(32), pp. 15849–15854 (2019). O artigo que deu nome ao fenómeno. ↩
-
Nakkiran, P., Kaplun, G., Bansal, Y., Yang, T., Barak, B. and Sutskever, I. Deep Double Descent: Where Bigger Models and More Data Hurt. arXiv:1912.02292 (2019). Mostra o efeito em redes profundas reais, e ao longo do eixo do tempo de treino, além do eixo do tamanho do modelo. ↩