Saltar ao contido
6/30Capítulo 6 de 30

Facer que adestre e facer que xeneralice

Unha rede de seis capas coa perda fixa en ln 2, arranxada medición a medición. Logo double descent: 5.000 parámetros en 40 puntos.

Nesta páxina

A rede do capítulo 5 funciona. Ten nove parámetros, aprende XOR e os seus gradientes coinciden cos de PyTorch ata dezaseis decimais.

Faina de seis capas de profundidade e deixa de aprender por completo. Non aos poucos: por completo. Aquí tes unha rede de seis capas nun problema de clasificación de dúas espirais, adestrada durante 5000 pasos:

TEXT
step    1: loss 0.693147
step 5000: loss 0.693147
accuracy: 50.0 %

Ese número non é arbitrario. ln⁡2=0.693147\ln 2 = 0.693147 é a entropía cruzada binaria dun modelo que devolve probabilidade 0.50.5 para todo, e 50 % é lanzar unha moeda nun conxunto de datos equilibrado. Despois de cinco mil pasos, a rede non moveu nin unha soa cifra. Nada fallou, nada avisou, e os gradientes seguen sendo exactamente correctos.

Este capítulo trata da fenda entre unha rede que se executa e unha rede que funciona. Ten dúas metades que parecen temas distintos e son o mesmo traballo: conseguir que a perda baixe, e conseguir que baixe en datos que o modelo nunca viu.

Comeza mirando, non adiviñando. Pasa un batch de entradas pola rede e imprime a desviación estándar das activacións en cada capa, e logo a desviación estándar dos gradientes dos pesos:

profile.pyPYTHON
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}")

Tres inicializacións, mesma arquitectura, seis capas de tanh⁡\tanh:

inicializacióndesviación estándar da activación, capas 1→6
normal, std 0.010.010.0145 · 0.0016 · 0.0002 · 0.0000 · 0.0000 · 0.0000
normal, std 110.6573 · 0.9296 · 0.9585 · 0.9634 · 0.9637 · 0.9625
Xavier0.1579 · 0.1493 · 0.1353 · 0.1333 · 0.1325 · 0.1403
inicializacióndesviación estándar do gradiente, primeira capa → última
normal, std 0.010.013.20e-06 · 4.97e-07 · … · 6.40e-06
normal, std 111.94e+03 · 2.28e+02 · 1.22e+02 · 4.43e+01 · 1.85e+01 · 7.30e+00
Xavier2.31e+00 · 4.50e-01 · 4.26e-01 · 3.89e-01 · 4.39e-01 · 4.73e-01

A primeira fila é a rede de arriba, e non está aprendendo lentamente: xa non lle queda sinal. Na capa catro, a desviación estándar da activación xa subdesbordou ata cero con catro decimais. Cada entrada produce a mesma saída, a saída é unha constante, e o gradiente dunha constante non é nada. Os pesos inicializáronse pequenos «por seguridade», e pequeno foi fatal.

A segunda fila é o fallo contrario e paga a pena entendelo porque é contraintuitivo. As activacións parecen sas — arredor de 0.96 — pero iso é tanh⁡\tanh saturado, fixado preto do seu límite, exactamente o réxime que no capítulo 5 medimos como unha perda dun factor de case dez mil no gradiente. E, con todo, os gradientes son enormes: 1940 na primeira capa. Ambas cousas son certas á vez. Cada paso cara atrás multiplica por W⊤W^\top, e con 128 entradas con varianza unitaria ese factor ten unha ganancia duns 128≈11\sqrt{128} \approx 11, que asolaga a contracción do tanh⁡\tanh saturado. Os gradientes medran xeometricamente ao volver cara atrás. Este é o gradiente explosivo, e produce valores de perda de nan en poucos pasos en calquera adestramento real.

A terceira fila é o que queres: activacións aproximadamente constantes en escala a través da profundidade, gradientes aproximadamente constantes en escala a través da profundidade. Nada morre, nada explota.

Inicializar ben arranxa a escala no paso cero. Non a mantén fixa: os pesos móvense, e no paso cinco mil o coidadoso argumento da varianza xa non se aplica.

As capas de normalización impoñen a escala de forma continua. Dado un vector de activacións, réstalle unha media, divídeo por unha desviación estándar e logo aplica unha escala aprendida γ\gamma e un desprazamento β\beta para que a capa poida desfacer a normalización se resulta que iso é o que quere:

h^=h−μσ2+ϵ,y=γh^+β\hat{h} = \frac{h - \mu}{\sqrt{\sigma^2 + \epsilon}}, \qquad y = \gamma\hat{h} + \beta

A única pregunta real é sobre que fas a media. Batch normalisation3 toma μ\mu e σ\sigma ao longo da dimensión do batch, unha estatística por feature. Layer normalisation4 tómaas ao longo das features, unha estatística por exemplo.

Esa escolla parece pequena e decide case todo o que vén despois:

BatchNorm fai que a saída de cada exemplo dependa dos outros exemplos que cadraron no seu batch. Durante o adestramento iso é un regularizador suave. En inferencia non hai batch, así que ten que manter unha media móbil das estatísticas recollidas durante o adestramento, o que significa que a capa se comporta de xeito distinto en modo de adestramento e en modo de avaliación, e esquecer cambiar de modo é un dos bugs máis comúns no campo. Tamén se degrada con batches pequenos, e é incómoda con secuencias de lonxitude variable, porque «a media do batch na posición 40» calcúlase a partir de cantas secuencias resulten ser tan longas.

LayerNorm normaliza cada exemplo por si mesmo. Sen dependencia do batch, sen estatísticas móbiles, comportamento idéntico en adestramento e inferencia, indiferente ao tamaño do batch, indiferente á lonxitude da secuencia. Cada unha desas propiedades é un requisito máis ca unha comodidade cando estás xerando un token de cada vez para unha persoa usuaria, que é onde acaba o capítulo 13.

Por iso LayerNorm é a que volverás atopar no capítulo 9 sen cambios: o bloque transformer úsaa, e úsaa polos motivos da columna dereita, non porque funcione mellor en abstracto.

Recibe novas publicacións na túa caixa de entrada

Novas de IA, guías e actualizacións do produto — un correo breve cando publiquemos algo que pague a pena.

Arranxar unha cousa de cada vez, que é a verdadeira skill

Ligazón á sección: Arranxar unha cousa de cada vez, que é a verdadeira skill

Catro posibles arranxos para a rede morta: inicialización Xavier, LayerNorm, conexións residuais e Adam en vez de SGD. A tentación é aplicar os catro e seguir. Fai iso e nunca saberás cal importou, e a próxima vez que pase non terás método: só un ritual.

Así que aplícaos un a un. Mesma semente, mesmos datos, mesma arquitectura, 800 pasos:

que se engadiuperda finalprecisión
nada0.693150.0 %
inicialización Xavier0.569260.4 %
LayerNorm0.623061.5 %
conexións residuais0.665156.6 %
Adam0.678758.7 %
os catro0.0000100.0 %

Le esa táboa como a lerías ás 2 da mañá e a conclusión é: nada funciona só, todo funciona xunto, polo tanto o deep learning é alquimia. Esa conclusión é incorrecta, e descubrir por que é o máis útil deste capítulo.

Dálle a cada execución seis veces máis orzamento — 5000 pasos en vez de 800 — e cambia por completo:

que se engadiuperda final @ 5000precisión
nada0.693150.0 %
inicialización Xavier0.0007100.0 %
LayerNorm0.0002100.0 %
conexións residuais0.665356.7 %
Adam0.690853.4 %
Xavier + Adam0.0000100.0 %
Xavier + LayerNorm0.0001100.0 %

Agora a imaxe é nítida, e é un diagnóstico máis ca un ritual.

A inicialización soa arránxao. A normalización soa arránxao. Cada unha aborda a enfermidade real — o sinal cara adiante que colapsa a cero — e calquera das dúas é suficiente. Aos 800 pasos só parecían crédito parcial, porque xa resolveran o problema e aínda estaban saíndo do burato.

As conexións residuais e Adam non o arranxan, con ningún orzamento. Non porque sexan malas, senón porque tratan outra enfermidade. Unha conexión residual dálle ao gradiente un camiño arredor dunha capa bloqueante; iso vale moitísimo cando o problema é o gradiente, e non vale nada cando o sinal cara adiante xa é cero, porque un atallo arredor dunha capa morta segue transportando un valor morto. Adam reescala o paso de cada parámetro segundo o seu propio historial de gradientes; iso axuda cando os gradientes teñen magnitudes moi distintas, e non pode resucitar unha rede cuxa saída non depende da súa entrada.

E «nada» segue exactamente en 0.6931 despois de cinco mil pasos. Non 0.6929. Non é lenta; está morta, e esa distinción vese dun xeito que antes non se vía, porque tes a fila que di que un arranxo funciona para comparala.

A partir de aquí este curso usa PyTorch. Iso debería gañarse, non anunciarse, así que aquí tes exactamente o que fai que xa sabes facer.

Un optimizador é unha regra para converter gradientes en actualizacións de parámetros. O gradient descent simple usa o gradiente. Momentum usa unha media móbil del, que suaviza o ruído e colle velocidade nas direccións que se manteñen consistentes:

optim_by_hand.pyPYTHON
v = beta * v + p.grad          
p -= lr * v                    

Adam5 mantén dúas medias móbiles — do gradiente e do gradiente ao cadrado — e divide unha pola raíz cadrada da outra, de modo que cada parámetro recibe un paso escalado á súa propia magnitude recente de gradiente:

optim_by_hand.pyPYTHON
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 liñas. Executa ambos contra torch.optim no mesmo problema durante 50 pasos:

TEXT
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-07

Idénticos ata a precisión de float32. torch.optim.Adam son esas cinco liñas, máis décadas de coidado cos casos límite e un kernel en C++. Ese é o intercambio que fas a partir de aquí: non maxia por comprensión, senón velocidade por liñas que xa escribiches.

A explicación habitual de Adam é «learning rates adaptativos por parámetro», que é unha descrición máis ca unha razón. A razón é a xeometría, e pódese medir.

Toma unha perda cuxa curvatura difire entre direccións: empinada nunha, suave noutra. SGD ten un único learning rate global, así que debe escoller un valor o bastante pequeno para ser estable na dirección máis empinada, e ese valor é entón demasiado pequeno para a suave, onde o progreso vai a paso de caracol. Isto é o que causa a imaxe clásica do gradient descent facendo zigzag por un val estreito.

Dúas razóns de curvatura, tres optimizadores, 300 pasos, e a cada optimizador dáselle o mellor learning rate dun varrido para que ninguén teña desvantaxe:

razón de curvaturaSGDSGD + momentumAdam
10 : 1erro 0.000002erro 0.000000erro 0.000000
1000 : 1erro 1.925485erro 0.001432erro 0.000000
diverxeu en (1000:1)4 de 8 taxas4 de 8 taxas0 de 6 taxas

Cunha razón de dez, todo funciona e non hai nada que discutir. Con mil, SGD simple non pode chegar á resposta con ningún learning rate probado — o seu mellor resultado segue sendo un erro de 1.93 — e diverxe directamente coa metade das taxas. Adam aterra exactamente no obxectivo e non diverxe con ningunha.

Esa última columna é a razón práctica pola que Adam é o predeterminado. Non é que Adam atope solucións mellores; en problemas ben condicionados, SGD afinado adoita igualalo ou superalo. É que Adam é moito menos sensible ao learning rate que escolliches, e as redes reais teñen razóns de curvatura moito peores ca mil nos seus millóns de parámetros.

Aquí pertencen dúas pezas máis e ambas son dunha liña. Gradient clipping reescala o vector de gradiente sempre que a súa norma supera un limiar, o que converte a fila «a perda salta de súpeto a un valor enorme» da táboa de diagnóstico nun non-evento. E learning rate schedules: un warmup curto desde case cero durante os primeiros centos de pasos, porque as estimacións de varianza de Adam son lixo ata que viron algúns gradientes e un paso de tamaño completo tomado sobre lixo pode estragar unha inicialización; logo cosine decay cara a cero, porque rematar unha execución co mesmo tamaño de paso co que comezaches significa tremer arredor do mínimo en vez de asentarse nel.

A segunda metade: o modelo que encaixa perfectamente e non predí nada

Ligazón á sección: A segunda metade: o modelo que encaixa perfectamente e non predí nada

Todo ata agora trataba de facer que a perda baixase. Agora vén a metade máis difícil, porque que a perda baixe non é o obxectivo: é un proxy do obxectivo, e o proxy falla dun xeito concreto e famoso.

Doce puntos dunha función suave cun pouco de ruído. Axusta polinomios de grao crecente:

graoRMSE trainRMSE test
10.7644990.6985
30.2526050.3031
50.1644370.1568
90.0889600.2347
110.0000001.2094

O grao 11 a través de 12 puntos pasa por cada un deles exactamente — erro de train cero ata seis decimais — e é oito veces peor ca o grao 5 en datos que non viu. Pídelles ao grao 3 e ao grao 11 que predigan en x=3.25x = 3.25, xusto fóra do rango de adestramento:

TEXT
degree  3: predicts   -1.053   (truth -0.012)
degree 11: predicts  +61.224   (truth -0.012)

Sesenta e un, cando a resposta é aproximadamente cero. O modelo non aprendeu a función; aprendeu os doce puntos, e entre eles fai o que a aritmética esixe.

Isto é overfitting, e o seu oposto — grao 1, que non pode representar a curva en absoluto e é malo en todas partes — é underfitting. A explicación clásica divide o erro esperado dun modelo en tres partes: bias, o erro de que o modelo sexa demasiado ríxido para representar a verdade; variance, o erro de que o modelo sexa tan flexible que persiga o ruído desta mostra concreta; e ruído irreducible, que nada arranxa. Os modelos simples teñen bias, os modelos flexibles teñen alta variance, e a prescrición clásica é atopar o punto doce no medio: grao 5 na táboa anterior.

As ferramentas estándar atacan todas o termo de variance:

  • Regularización L2 (weight decay) engade λ∥w∥2\lambda \lVert w \rVert^2 á perda, tirando dos pesos cara a cero e facendo a función máis suave. Na táboa anterior, o coeficiente máis grande do grao 11 fai o dano; penalizar o tamaño desactívao.
  • L1 engade λ∑∣wi∣\lambda \sum |w_i| no seu lugar. A diferenza non é cosmética: o gradiente de L2 é proporcional ao peso e por iso encolle a medida que o fai o peso, achegándose a cero sen chegar, mentres que o gradiente de L1 é unha constante ±λ\pm\lambda que segue empurrando ata o final. Polo tanto, L1 produce pesos que son exactamente cero: selecciona features. L2 produce pesos pequenos. Usa L2 cando queiras suavidade, L1 cando queiras esparsidade.
  • Dropout7 pon a cero un subconxunto aleatorio de activacións en cada paso de adestramento, así que ningunha unidade pode depender de que outra unidade concreta estea presente.
  • Early stopping observa a perda de validación e para cando empeza a subir.
  • Data augmentation fabrica máis exemplos de adestramento a partir dos que tes, o que ataca o problema na súa orixe: o overfitting é unha escaseza de datos tanto como un exceso de parámetros.
  • Cross-validation divide os datos en kk partes e adestra kk veces, o que compra unha estimación fiable do erro de test cando tes poucos datos para reservar un conxunto separado.

Double descent, ou por que a sección anterior non é toda a historia

Ligazón á sección: Double descent, ou por que a sección anterior non é toda a historia

Agora o feito que rompe a imaxe.

A historia bias-variance di que, pasado o punto doce, máis parámetros significan peor xeneralización. Os modelos de linguaxe modernos teñen moitos máis parámetros dos que as regras clásicas permitirían para os datos que ven, e xeneralizan de marabilla. Ambas afirmacións son certas, e concilialas é o máis útil deste capítulo.

Corenta puntos de adestramento, entradas de vinte dimensións, features ReLU aleatorias, e o número de features PP varrido de 2 a 5000, coa solución de norma mínima escollida sempre que hai moitas que encaixan:

PPP/nP/nRMSE trainRMSE test∥w∥\lVert w \rVert
100.250.88221.25201.89
200.500.59621.16342.59
300.750.38961.53234.15
380.950.17693.716310.25
401.000.00005.814014.83
421.050.00003.16239.35
601.500.00001.10582.78
2005.000.00000.66380.98
150037.500.00000.58590.33
5000125.000.00000.56640.18

Léao en tres partes. Ata P/n=0.5P/n = 0.5 a historia clásica cúmprese exactamente: o erro cae, logo comeza a subir. En P=n=40P = n = 40 — o limiar de interpolación, onde o modelo ten exactamente parámetros dabondo para pasar por cada punto de adestramento — o erro de test acada o seu pico, en 5.81, cinco veces peor ca o modelo pequeno. Ese pico é a advertencia clásica, e é real.

Logo descende outra vez. E segue descendendo, pasado P=5nP = 5n, pasado P=37nP = 37n, ata P=125nP = 125n, onde o erro de test de 0.5664 é mellor ca o mellor modelo infraparametrizado que se acadou nunca. Un modelo con 5000 parámetros axustado a 40 puntos é o mellor modelo da táboa.

Isto é double descent,89 e o mecanismo vese na última columna. Unha vez que P>nP > n hai infinitas configuracións de parámetros que encaixan exactamente cos datos de adestramento, e cal obteñas depende de como escollas. A solución de norma mínima escolle a máis pequena, e ∥w∥\lVert w \rVert mostra o que iso significa: acada un pico de 14.83 xusto no limiar — onde hai exactamente unha solución interpolante e estás atrapado con ela, por extrema que sexa — e logo cae monotonamente a medida que PP medra, porque máis parámetros significan máis solucións interpolantes entre as que escoller, o que significa que a máis pequena dispoñible faise máis pequena. En P=5000P = 5000 a norma é 0.18, oitenta veces menor ca no limiar.

Así que os parámetros extra non engaden complexidade. Engaden escolla, e a regra de selección gasta esa escolla en simplicidade. A regularización non está na función de perda; está no algoritmo. Gradient descent desde unha inicialización pequena ten un sesgo documentado cara a solucións de norma pequena, que é por iso que este comportamento aparece en redes reais adestradas do xeito ordinario e non só na álxebra linear de arriba.

A consecuencia práctica, da que depende o capítulo 10: «o modelo ten máis parámetros ca datos, así que vai facer overfit» non é un argumento válido. Era unha boa regra cando os modelos vivían á esquerda do limiar. Agora todo o interesante vive moi á dereita del, onde a regra se inverte.

As ferramentas deste capítulo abondan para adestrar unha rede que funcione con datos que podes poñer nunha táboa: filas de números, unha columna de etiquetas.

A linguaxe non é iso. Antes de que un modelo poida predicir a seguinte palabra, algo ten que decidir que é sequera unha «palabra», e a resposta non son nin letras nin palabras, senón un vocabulario que o modelo aprende dos bytes crus dos datos de adestramento. Esa decisión, tomada unha vez antes de que comece o adestramento, determina cantas cousas pode dicir o modelo, canto custa unha solicitude, e por que modelos que poden aprobar un exame de dereito non poden contar de forma fiable as letras en strawberry.

O capítulo 7 constrúe un tokenizer.


Para as conexións residuais usadas arriba, 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 histogramas de activación nun modelo real e é o mellor tratamento práctico da primeira metade deste capítulo. As leccións 8 e 11–13 de Learning From Data, de Yaser Abu-Mostafa, dan a teoría clásica da xeneralización como é debido, incluídas as partes que este capítulo comprimiu nun parágrafo.

  1. Glorot, X. and Bengio, Y. Understanding the difficulty of training deep feedforward neural networks. AISTATS (2010). O argumento de preservación da varianza reproducido na caixa anterior. ↩

  2. He, K., Zhang, X., Ren, S. and Sun, J. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. arXiv:1502.01852 (2015). ↩

  3. Ioffe, S. and Szegedy, C. Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. arXiv:1502.03167 (2015). Ten en conta que a explicación de «internal covariate shift» no título foi desde entón moi cuestionada; a capa funciona, pero a explicación orixinal de por que funciona está disputada. ↩

  4. Ba, J. L., Kiros, J. R. and Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). ↩

  5. Kingma, D. P. and Ba, J. Adam: A Method for Stochastic Optimization. arXiv:1412.6980 (2014). ↩

  6. Loshchilov, I. and Hutter, F. Decoupled Weight Decay Regularization. arXiv:1711.05101 (2017). ↩

  7. 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). ↩

  8. 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 lle deu nome ao fenómeno. ↩

  9. 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 efecto en redes profundas reais, e tamén ao longo do eixo do tempo de adestramento, ademais do eixo do tamaño do modelo. ↩

Este capítulo en vídeo

Vídeos breves que explican ideas deste capítulo con linguaxe clara.

Ver todos os vídeos

Pon a túa IA a traballar hoxe

Todos os modelos e todas as ferramentas nun só lugar. Comeza gratis, sen tarxeta.