Far sì che si alleni, e che generalizzi
Una rete a sei strati con loss fermo a ln 2, corretta una misura alla volta. Poi double descent: 5.000 parametri su 40 punti.
In questa pagina
La rete del Capitolo 5 funziona. Ha nove parametri, impara XOR e i suoi gradienti coincidono con PyTorch fino alla sedicesima cifra decimale.
Rendila profonda sei strati e smette del tutto di imparare. Non lentamente — del tutto. Ecco una rete a sei strati su un problema di classificazione a due spirali, addestrata per 5000 step:
step 1: loss 0.693147
step 5000: loss 0.693147
accuracy: 50.0 %Quel numero non è arbitrario. è la binary cross-entropy di un modello che restituisce probabilità per tutto, e 50% è un lancio di moneta su un dataset bilanciato. Dopo cinquemila step la rete non si è mossa di una singola cifra. Nulla è andato in crash, nulla ha generato warning, e i gradienti sono ancora esattamente corretti.
Questo capitolo parla del divario tra una rete che gira e una rete che funziona. Ha due metà che sembrano argomenti diversi e sono lo stesso lavoro: far scendere la loss, e farla scendere su dati che il modello non ha mai visto.
Perché la rete a sei strati è morta
Link alla sezione: Perché la rete a sei strati è mortaInizia guardando, invece di tirare a indovinare. Fai passare un batch di input e stampa la deviazione standard delle attivazioni a ogni strato, poi la deviazione standard dei gradienti dei pesi:
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}")Tre inizializzazioni, stessa architettura, sei strati di :
| inizializzazione | std attivazione, strati 1→6 |
|---|---|
| normale, std | 0.0145 · 0.0016 · 0.0002 · 0.0000 · 0.0000 · 0.0000 |
| normale, 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 |
| inizializzazione | std gradiente, primo strato → ultimo |
|---|---|
| normale, std | 3.20e-06 · 4.97e-07 · … · 6.40e-06 |
| normale, 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 |
La prima riga è la rete di prima, e non sta imparando lentamente — non le è rimasto alcun segnale. Al quarto strato la deviazione standard delle attivazioni è andata in underflow a zero nelle quattro cifre decimali mostrate. Ogni input produce lo stesso output, l'output è una costante, e il gradiente di una costante è nulla. I pesi erano stati inizializzati piccoli "per sicurezza", e piccolo è stato fatale.
La seconda riga è il fallimento opposto e vale la pena capirlo perché è controintuitivo. Le attivazioni sembrano sane — intorno a 0.96 — ma quello è saturo, inchiodato vicino al suo limite, esattamente il regime che il Capitolo 5 ha misurato come una perdita di quasi diecimila volte nel gradiente. Eppure i gradienti sono enormi: 1940 al primo strato. Le due cose sono vere contemporaneamente. Ogni step backward moltiplica per , e con 128 input a varianza unitaria quel fattore ha un guadagno di circa , che sovrasta la contrazione dovuta al saturo. I gradienti crescono geometricamente tornando indietro. Questo è l'exploding gradient, e in una vera esecuzione di training produce valori di loss pari a nan in pochi step.
La terza riga è ciò che vuoi: attivazioni con scala più o meno costante attraverso la profondità, gradienti con scala più o meno costante attraverso la profondità. Nulla muore, nulla esplode.
Normalizzazione, e quale è sopravvissuta
Link alla sezione: Normalizzazione, e quale è sopravvissutaInizializzare bene corregge la scala allo step zero. Non la mantiene corretta: i pesi si muovono, e allo step cinquemila l'argomento accurato sulla varianza non vale più.
Gli strati di normalizzazione impongono la scala in modo continuo. Dato un vettore di attivazioni, sottrai una media, dividi per una deviazione standard, poi applica una scala appresa e uno shift , così lo strato può annullare la normalizzazione se scopre che è quello che vuole:
L'unica vera domanda è su cosa fai la media. La batch normalisation3 calcola e lungo la dimensione del batch, una statistica per feature. La layer normalisation4 li calcola attraverso le feature, una statistica per esempio.
Quella scelta sembra minore e decide quasi tutto ciò che viene dopo:
BatchNorm fa dipendere l'output di ogni esempio dagli altri esempi che sono capitati nel suo batch. Durante il training è un leggero regolarizzatore. In inference non c'è batch, quindi deve mantenere una media mobile delle statistiche raccolte durante il training — il che significa che lo strato si comporta diversamente in modalità training e evaluation, e dimenticare di cambiare modalità è uno dei bug più comuni sul campo. Inoltre peggiora con batch piccoli, ed è scomoda con sequenze di lunghezza variabile, perché "la media sul batch alla posizione 40" viene calcolata su qualunque numero di sequenze capiti che sia così lungo.
LayerNorm normalizza ogni esempio da solo. Nessuna dipendenza dal batch, nessuna statistica mobile, comportamento identico in training e inference, indifferente alla dimensione del batch, indifferente alla lunghezza della sequenza. Ognuna di queste proprietà diventa un requisito, non una raffinatezza, quando generi un token alla volta per un utente, che è dove si arriva nel Capitolo 13.
Ecco perché LayerNorm è quella che incontrerai di nuovo nel Capitolo 9 immutata: il blocco transformer la usa, e la usa per le ragioni nella colonna di destra, non perché funzioni meglio in astratto.
Correggere una cosa alla volta, che è la vera skill
Link alla sezione: Correggere una cosa alla volta, che è la vera skillQuattro possibili correzioni per la rete morta: inizializzazione Xavier, LayerNorm, connessioni residuali e Adam invece di SGD. La tentazione è applicarle tutte e quattro e andare avanti. Fallo e non saprai mai quale contava, e la prossima volta che succede non avrai un metodo — solo un rituale.
Quindi applicale una alla volta. Stesso seed, stessi dati, stessa architettura, 800 step:
| cosa è stato aggiunto | loss finale | accuracy |
|---|---|---|
| nulla | 0.6931 | 50.0% |
| inizializzazione Xavier | 0.5692 | 60.4% |
| LayerNorm | 0.6230 | 61.5% |
| connessioni residuali | 0.6651 | 56.6% |
| Adam | 0.6787 | 58.7% |
| tutte e quattro | 0.0000 | 100.0% |
Leggi quella tabella come la leggeresti alle 2 di notte e la conclusione è: nulla funziona da solo, tutto funziona insieme, quindi il deep learning è alchimia. Quella conclusione è sbagliata, e capire perché è la cosa più utile di questo capitolo.
Dai a ogni esecuzione sei volte il budget — 5000 step invece di 800 — e cambia tutto:
| cosa è stato aggiunto | loss finale @ 5000 | accuracy |
|---|---|---|
| nulla | 0.6931 | 50.0% |
| inizializzazione Xavier | 0.0007 | 100.0% |
| LayerNorm | 0.0002 | 100.0% |
| connessioni residuali | 0.6653 | 56.7% |
| Adam | 0.6908 | 53.4% |
| Xavier + Adam | 0.0000 | 100.0% |
| Xavier + LayerNorm | 0.0001 | 100.0% |
Ora il quadro è nitido, ed è una diagnosi invece di un rituale.
L'inizializzazione da sola la corregge. La normalizzazione da sola la corregge. Ognuna affronta la vera malattia — il segnale forward che collassa a zero — e una qualsiasi delle due è sufficiente. A 800 step sembravano solo un credito parziale, perché avevano risolto il problema ma stavano ancora risalendo.
Connessioni residuali e Adam non la correggono, a nessun budget. Non perché siano cattivi, ma perché trattano un'altra malattia. Una connessione residuale dà al gradiente un percorso attorno a uno strato bloccante; vale moltissimo quando il problema è il gradiente, e non vale nulla quando il segnale forward è già zero, perché una scorciatoia attorno a uno strato morto trasporta comunque un valore morto. Adam riscalibra lo step di ogni parametro in base alla sua storia di gradienti; aiuta quando i gradienti hanno magnitudini molto diverse, e non può resuscitare una rete il cui output non dipende dall'input.
E "nulla" è ancora esattamente 0.6931 dopo cinquemila step. Non 0.6929. Non è lenta; è morta, e quella distinzione ora è visibile in un modo che prima non lo era, perché hai la riga che dice che una correzione funziona con cui confrontarla.
Guadagnarsi PyTorch
Link alla sezione: Guadagnarsi PyTorchDa qui in avanti questo corso usa PyTorch. Dovrebbe essere guadagnato più che annunciato, quindi ecco esattamente cosa fa che tu sai già fare.
Un ottimizzatore è una regola per trasformare gradienti in aggiornamenti dei parametri. Il plain gradient descent usa il gradiente. Momentum ne usa una media mobile, che smussa il rumore e accumula velocità lungo direzioni che restano coerenti:
v = beta * v + p.grad
p -= lr * v Adam5 mantiene due medie mobili — del gradiente e del gradiente al quadrato — e divide una per la radice quadrata dell'altra, così ogni parametro riceve uno step scalato alla propria magnitudine recente del 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) Dieci righe. Esegui entrambi contro torch.optim sullo stesso problema per 50 step:
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-07Identici fino alla precisione float32. torch.optim.Adam sono quelle cinque righe, più decenni di cura per i casi limite e un kernel C++. Questo è lo scambio che fai da qui in poi: non magia al posto della comprensione, ma velocità in cambio di righe che hai già scritto.
Perché esiste Adam: la curvatura
Link alla sezione: Perché esiste Adam: la curvaturaLa spiegazione abituale di Adam è "learning rate adattivi per parametro", che è una descrizione più che una ragione. La ragione è geometria, e si può misurare.
Prendi una loss la cui curvatura differisce tra direzioni: ripida in una, piatta in un'altra. SGD ha un solo learning rate globale, quindi deve scegliere un valore abbastanza piccolo da essere stabile nella direzione più ripida — e quel valore è poi troppo piccolo per quella piatta, dove il progresso striscia. Questo è ciò che causa la classica immagine del gradient descent che zigzaga giù per una valle stretta.
Due rapporti di curvatura, tre ottimizzatori, 300 step, e a ogni ottimizzatore viene dato il miglior learning rate da una sweep, così nessuno è svantaggiato:
| rapporto di curvatura | SGD | SGD + momentum | Adam |
|---|---|---|---|
| 10 : 1 | errore 0.000002 | errore 0.000000 | errore 0.000000 |
| 1000 : 1 | errore 1.925485 | errore 0.001432 | errore 0.000000 |
| divergenza a (1000:1) | 4 su 8 rate | 4 su 8 rate | 0 su 6 rate |
A un rapporto di dieci, tutto funziona e non c'è nulla da discutere. A mille, SGD semplice non riesce a raggiungere la risposta con nessun learning rate provato — il suo risultato migliore è ancora un errore di 1.93 — e diverge del tutto a metà dei rate. Adam atterra esattamente sul target e non diverge con nessuno.
Quell'ultima colonna è la ragione pratica per cui Adam è il default. Non è che Adam trovi soluzioni migliori; su problemi ben condizionati, SGD ben tarato spesso lo eguaglia o lo batte. È che Adam è molto meno sensibile al learning rate che hai scelto, e le reti reali hanno rapporti di curvatura molto peggiori di mille attraverso i loro milioni di parametri.
Qui appartengono altri due pezzi, entrambi in una riga. Gradient clipping riscalibra il vettore del gradiente ogni volta che la sua norma supera una soglia, trasformando la riga "la loss salta improvvisamente a un valore enorme" della tabella diagnostica in un non-evento. E learning rate schedules: un breve warmup da quasi zero nei primi centinaia di step, perché le stime di varianza di Adam sono spazzatura finché non hanno visto alcuni gradienti e uno step a grandezza piena fatto sulla spazzatura può rovinare un'inizializzazione; poi cosine decay verso zero, perché finire una run con la stessa step size con cui hai iniziato significa tremare attorno al minimo invece di assestarsi.
La seconda metà: il modello che fitta perfettamente e non predice nulla
Link alla sezione: La seconda metà: il modello che fitta perfettamente e non predice nullaFinora tutto riguardava far scendere la loss. Ora arriva la metà più difficile, perché far scendere la loss non è l'obiettivo — è una proxy dell'obiettivo, e la proxy fallisce in un modo specifico e famoso.
Dodici punti da una funzione liscia con un po' di rumore. Fitta polinomi di grado crescente:
| grado | train RMSE | test RMSE |
|---|---|---|
| 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 |
Il grado 11 su 12 punti passa per ognuno esattamente — errore di training zero a sei cifre decimali — ed è otto volte peggiore del grado 5 su dati che non ha visto. Chiedi al grado 3 e al grado 11 di predire a , appena fuori dall'intervallo di training:
degree 3: predicts -1.053 (truth -0.012)
degree 11: predicts +61.224 (truth -0.012)Sessantuno, quando la risposta è approssimativamente zero. Il modello non ha imparato la funzione; ha imparato i dodici punti, e tra di essi fa qualunque cosa l'aritmetica richieda.
Questo è overfitting, e il suo opposto — grado 1, che non può rappresentare affatto la curva ed è cattivo ovunque — è underfitting. Il resoconto classico divide l'errore atteso di un modello in tre parti: bias, l'errore dovuto al modello troppo rigido per rappresentare la verità; variance, l'errore dovuto al modello così flessibile da inseguire il rumore in questo particolare campione; e rumore irriducibile, che nulla può correggere. I modelli semplici sono biased, i modelli flessibili sono ad alta variance, e la prescrizione classica è trovare il punto giusto nel mezzo — il grado 5 nella tabella sopra.
Gli strumenti standard attaccano tutti il termine di variance:
- Regolarizzazione L2 (weight decay) aggiunge alla loss, tirando i pesi verso zero e rendendo la funzione più liscia. Nella tabella sopra, il coefficiente più grande del grado 11 fa il danno; penalizzare la dimensione lo disinnesca.
- L1 aggiunge invece . La differenza non è cosmetica: il gradiente di L2 è proporzionale al peso e quindi si riduce mentre il peso si riduce, avvicinandosi a zero senza arrivarci, mentre il gradiente di L1 è una costante che continua a spingere fino in fondo. L1 quindi produce pesi esattamente zero — seleziona feature. L2 produce pesi piccoli. Usa L2 quando vuoi levigatezza, L1 quando vuoi sparsità.
- Dropout7 azzera un sottoinsieme casuale di attivazioni a ogni step di training, così nessuna unità può contare sulla presenza di un'altra unità particolare.
- Early stopping osserva la validation loss e si ferma quando gira verso l'alto.
- Data augmentation fabbrica più esempi di training da quelli che hai, attaccando il problema alla radice: l'overfitting è una carenza di dati tanto quanto un eccesso di parametri.
- Cross-validation divide i dati in parti e addestra volte, comprando una stima affidabile dell'errore di test quando hai troppo pochi dati per permetterti un held-out set.
Double descent, o perché la sezione precedente non è tutta la storia
Link alla sezione: Double descent, o perché la sezione precedente non è tutta la storiaOra il fatto che rompe il quadro.
La storia bias-variance dice che, oltre il punto giusto, più parametri significano peggiore generalizzazione. I modelli linguistici moderni hanno molti più parametri di quanto le regole classiche consentirebbero per i dati che vedono, e generalizzano superbamente. Entrambe le affermazioni sono vere, e riconciliarle è la cosa più utile di questo capitolo.
Quaranta punti di training, input a venti dimensioni, feature ReLU casuali, e il numero di feature variato da 2 a 5000 — scegliendo la soluzione a norma minima ogni volta che ce ne sono molte che fittano:
| train RMSE | test RMSE | |||
|---|---|---|---|---|
| 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 |
Leggila in tre parti. Fino a la storia classica regge esattamente: l'errore scende, poi inizia a salire. A — la soglia di interpolazione, dove il modello ha esattamente abbastanza parametri per passare attraverso ogni punto di training — l'errore di test raggiunge il picco, a 5.81, cinque volte peggiore del modello piccolo. Quel picco è l'avvertimento classico, ed è reale.
Poi scende di nuovo. E continua a scendere, oltre , oltre , fino a , dove l'errore di test di 0.5664 è migliore di quanto abbia mai ottenuto il miglior modello sotto-parametrizzato. Un modello con 5000 parametri fittato su 40 punti è il miglior modello della tabella.
Questo è double descent,89 e il meccanismo è visibile nell'ultima colonna. Una volta che esistono infinite configurazioni di parametri che fittano esattamente i dati di training, e quale ottieni dipende da come scegli. La soluzione a norma minima sceglie la più piccola, e mostra cosa significa: raggiunge il picco a 14.83 proprio alla soglia — dove esiste esattamente una soluzione interpolante e sei bloccato con quella, per quanto estrema — e poi scende monotonamente mentre cresce, perché più parametri significano più soluzioni interpolanti tra cui scegliere, il che significa che la più piccola disponibile diventa più piccola. A la norma è 0.18, ottanta volte più piccola che alla soglia.
Quindi i parametri extra non aggiungono complessità. Aggiungono scelta, e la regola di selezione spende quella scelta in semplicità. La regolarizzazione non è nella funzione di loss; è nell'algoritmo. Gradient descent da una piccola inizializzazione ha un bias documentato verso soluzioni a norma piccola, ed è per questo che questo comportamento appare nelle reti reali addestrate nel modo ordinario e non solo nell'algebra lineare sopra.
La conseguenza pratica, da cui dipende il Capitolo 10: "il modello ha più parametri che dati, quindi farà overfit" non è un argomento valido. Era una buona regola quando i modelli vivevano a sinistra della soglia. Tutto ciò che è interessante ora vive molto a destra, dove la regola si inverte.
Dove si va ora
Link alla sezione: Dove si va oraGli strumenti di questo capitolo bastano per addestrare una rete che funziona su dati che puoi mettere in una tabella: righe di numeri, una colonna di etichette.
Il linguaggio non è così. Prima che un modello possa predire la prossima parola, qualcosa deve decidere che cosa sia una "parola" — e la risposta non sono né lettere né parole, ma un vocabolario che il modello impara dai byte grezzi dei dati di training. Quella decisione, presa una volta prima che il training inizi, determina quante cose può dire il modello, quanto costa una richiesta, e perché modelli che possono superare un esame di legge non riescono a contare in modo affidabile le lettere in strawberry.
Il Capitolo 7 costruisce un tokenizer.
Fonti e metodo
Link alla sezione: Fonti e metodoPer le connessioni residuali usate sopra, He et al., Deep Residual Learning for Image Recognition (arXiv:1512.03385). Building makemore Part 3: Activations & Gradients, BatchNorm di Andrej Karpathy percorre la diagnostica degli istogrammi di attivazione su un modello reale ed è il miglior trattamento pratico della prima metà di questo capitolo. Le lezioni 8 e 11–13 di Learning From Data di Yaser Abu-Mostafa presentano correttamente la teoria classica della generalizzazione, incluse le parti che questo capitolo ha compresso in un paragrafo.
Riferimenti
Link alla sezione: Riferimenti-
Glorot, X. e Bengio, Y. Understanding the difficulty of training deep feedforward neural networks. AISTATS (2010). L'argomento di preservazione della varianza riprodotto nel riquadro sopra. ↩
-
He, K., Zhang, X., Ren, S. e Sun, J. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. arXiv:1502.01852 (2015). ↩
-
Ioffe, S. e Szegedy, C. Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. arXiv:1502.03167 (2015). Nota che la spiegazione dell'"internal covariate shift" nel titolo è stata da allora sostanzialmente contestata; lo strato funziona, la spiegazione originale del perché è controversa. ↩
-
Ba, J. L., Kiros, J. R. e Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). ↩
-
Kingma, D. P. e Ba, J. Adam: A Method for Stochastic Optimization. arXiv:1412.6980 (2014). ↩
-
Loshchilov, I. e Hutter, F. Decoupled Weight Decay Regularization. arXiv:1711.05101 (2017). ↩
-
Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I. e Salakhutdinov, R. Dropout: A Simple Way to Prevent Neural Networks from Overfitting. JMLR 15, pp. 1929–1958 (2014). ↩
-
Belkin, M., Hsu, D., Ma, S. e Mandal, S. Reconciling modern machine-learning practice and the classical bias–variance trade-off. PNAS 116(32), pp. 15849–15854 (2019). L'articolo che ha dato il nome al fenomeno. ↩
-
Nakkiran, P., Kaplun, G., Bansal, Y., Yang, T., Barak, B. e Sutskever, I. Deep Double Descent: Where Bigger Models and More Data Hurt. arXiv:1912.02292 (2019). Mostra l'effetto in vere reti profonde, e lungo l'asse del tempo di training oltre che lungo l'asse della dimensione del modello. ↩