Att få den att träna och att få den att generalisera
Ett sexlagersnätverk vars loss fastnar på ln 2, fixat en mätning i taget. Sedan double descent: 5 000 parametrar på 40 punkter.
På den här sidan
Nätverket från kapitel 5 fungerar. Det har nio parametrar, det lär sig XOR, och dess gradienter stämmer med PyTorch ned till sexton decimaler.
Gör det sex lager djupt och det slutar lära sig helt. Inte långsamt — helt. Här är ett sexlagersnätverk på ett tvåspirals-klassificeringsproblem, tränat i 5000 steg:
step 1: loss 0.693147
step 5000: loss 0.693147
accuracy: 50.0 %Den siffran är inte godtycklig. är den binära korsentropin för en modell som ger sannolikheten för allt, och 50 % är en slantsingling på ett balanserat dataset. Efter fem tusen steg har nätverket inte rört sig en enda siffra. Inget kraschade, inget varnade, och gradienterna är fortfarande exakt rätt.
Det här kapitlet handlar om gapet mellan ett nätverk som körs och ett nätverk som fungerar. Det har två halvor som ser ut som olika ämnen men är samma jobb: att få loss att gå nedåt, och att få den att gå nedåt på data som modellen aldrig har sett.
Varför sexlagersnätverket är dött
Länk till avsnittet: Varför sexlagersnätverket är döttBörja med att titta, i stället för att gissa. Skicka en batch med inputs genom nätverket och skriv ut standardavvikelsen för aktiveringarna vid varje lager, och sedan standardavvikelsen för viktgradienterna:
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 initieringar, samma arkitektur, sex lager av :
| initiering | aktiverings-std, lager 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 |
| initiering | gradient-std, första lagret → sista |
|---|---|
| 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 |
Den första raden är nätverket ovan, och det lär sig inte långsamt — det har ingen signal kvar. Vid lager fyra har aktiveringarnas standardavvikelse underflödat till noll med fyra decimalers precision. Varje input producerar samma output, outputen är en konstant, och gradienten för en konstant är ingenting. Vikterna initierades små ”för säkerhets skull”, och smått var dödligt.
Den andra raden är det motsatta felet och den är värd att förstå eftersom den är kontraintuitiv. Aktiveringarna ser friska ut — omkring 0.96 — men det är mättad, fastnålad nära sin gräns, exakt det regime som kapitel 5 mätte som att förlora nästan tiotusen gånger i gradient. Och ändå är gradienterna enorma: 1940 vid första lagret. Båda sakerna är sanna samtidigt. Varje backward-steg multiplicerar med , och med 128 inputs med enhetsvarians har den faktorn en förstärkning på ungefär , vilket överväldigar krympningen från den mättade . Gradienterna växer geometriskt på vägen tillbaka. Det här är exploding gradient, och det producerar loss-värden på nan inom några få steg i vilken verklig träningskörning som helst.
Den tredje raden är det du vill ha: aktiveringar med ungefär konstant skala genom djupet, gradienter med ungefär konstant skala genom djupet. Inget dör, inget exploderar.
Normalisering, och vilken som överlevde
Länk till avsnittet: Normalisering, och vilken som överlevdeBra initiering fixar skalan vid steg noll. Den håller den inte fixerad: vikterna rör sig, och vid steg fem tusen gäller inte det noggranna variansargumentet längre.
Normaliseringslager upprätthåller skalan kontinuerligt. Givet en vektor av aktiveringar, subtrahera ett medelvärde, dividera med en standardavvikelse, och applicera sedan en inlärd skala och förskjutning så att lagret kan ångra normaliseringen om det visar sig vara vad det vill:
Den enda verkliga frågan är vad du tar medelvärde över. Batch normalisation3 tar och över batch-dimensionen, en statistik per feature. Layer normalisation4 tar dem över features, en statistik per exempel.
Det valet ser litet ut och avgör nästan allt nedströms:
BatchNorm gör att varje exempels output beror på de andra exemplen som råkade finnas i dess batch. Under träning är det en mild regulariserare. Vid inferens finns ingen batch, så den måste behålla ett löpande genomsnitt av statistiken som samlades in under träningen — vilket innebär att lagret beter sig olika i tränings- och utvärderingsläge, och att glömma att växla läge är en av de vanligaste buggarna i fältet. Det försämras också med små batcher, och det är klumpigt med sekvenser av varierande längd, eftersom ”medelvärdet över batchen vid position 40” beräknas från hur många sekvenser som råkar vara så långa.
LayerNorm normaliserar varje exempel för sig. Inget batchberoende, ingen löpande statistik, identiskt beteende i träning och inferens, oberört av batchstorlek, oberört av sekvenslängd. Var och en av de egenskaperna är ett krav snarare än en bekvämlighet när du genererar en token i taget för en användare, vilket är där kapitel 13 hamnar.
Det är därför LayerNorm är den du kommer att möta igen i kapitel 9 oförändrad: transformer-blocket använder den, och det använder den av skälen i högerspalten, inte för att den fungerar bättre i abstrakt mening.
Att fixa en sak i taget, vilket är den faktiska skill
Länk till avsnittet: Att fixa en sak i taget, vilket är den faktiska skillFyra möjliga fixar för det döda nätverket: Xavier-initiering, LayerNorm, residual connections och Adam i stället för SGD. Frestelsen är att applicera alla fyra och gå vidare. Gör det och du kommer aldrig att veta vilken som spelade roll, och nästa gång det händer har du ingen metod — bara en ritual.
Så applicera dem en i taget. Samma seed, samma data, samma arkitektur, 800 steg:
| vad som lades till | slutlig loss | accuracy |
|---|---|---|
| inget | 0.6931 | 50.0 % |
| Xavier-initiering | 0.5692 | 60.4 % |
| LayerNorm | 0.6230 | 61.5 % |
| residual connections | 0.6651 | 56.6 % |
| Adam | 0.6787 | 58.7 % |
| alla fyra | 0.0000 | 100.0 % |
Läs den tabellen som du skulle läsa den klockan 02.00 och slutsatsen blir: inget fungerar ensamt, allt fungerar tillsammans, alltså är deep learning alkemi. Den slutsatsen är fel, och att ta reda på varför är det mest användbara i det här kapitlet.
Ge varje körning sex gånger budgeten — 5000 steg i stället för 800 — så förändras allt:
| vad som lades till | slutlig loss @ 5000 | accuracy |
|---|---|---|
| inget | 0.6931 | 50.0 % |
| Xavier-initiering | 0.0007 | 100.0 % |
| LayerNorm | 0.0002 | 100.0 % |
| residual connections | 0.6653 | 56.7 % |
| Adam | 0.6908 | 53.4 % |
| Xavier + Adam | 0.0000 | 100.0 % |
| Xavier + LayerNorm | 0.0001 | 100.0 % |
Nu är bilden skarp, och den är en diagnos snarare än en ritual.
Initiering ensam fixar det. Normalisering ensam fixar det. Var och en angriper den faktiska sjukdomen — att forward-signalen kollapsar till noll — och endera räcker. Vid 800 steg såg de bara ut som delpoäng, eftersom de hade löst problemet och fortfarande var på väg upp ur gropen.
Residual connections och Adam fixar det inte, oavsett budget. Inte för att de är dåliga, utan för att de behandlar en annan sjukdom. En residual connection ger gradienten en väg runt ett blockerande lager; det är mycket värt när gradienten är problemet, och inget värt när forward-signalen redan är noll, eftersom en genväg runt ett dött lager fortfarande bär ett dött värde. Adam skalar om varje parameters steg med dess egen gradienthistorik; det hjälper när gradienter har vilt olika magnituder, och kan inte återuppliva ett nätverk vars output inte beror på dess input.
Och ”inget” är fortfarande exakt 0.6931 efter fem tusen steg. Inte 0.6929. Det är inte långsamt; det är dött, och den distinktionen är synlig på ett sätt den inte var tidigare, eftersom du har raden som säger att en fix fungerar att jämföra med.
Att förtjäna PyTorch
Länk till avsnittet: Att förtjäna PyTorchHärifrån använder kursen PyTorch. Det bör förtjänas snarare än tillkännages, så här är exakt vad det gör som du redan vet hur man gör.
En optimerare är en regel för att omvandla gradienter till parameteruppdateringar. Vanlig gradient descent använder gradienten. Momentum använder ett löpande genomsnitt av den, vilket jämnar ut bruset och bygger upp fart längs riktningar som förblir konsekventa:
v = beta * v + p.grad
p -= lr * v Adam5 behåller två löpande genomsnitt — av gradienten och av gradienten i kvadrat — och dividerar det ena med kvadratroten av det andra, så att varje parameter får ett steg skalat till sin egen senaste gradientmagnitud:
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) Tio rader. Kör båda mot torch.optim på samma problem i 50 steg:
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-07Identiskt med float32-precision. torch.optim.Adam är de där fem raderna, plus decennier av omsorg om kantfall och en C++-kärna. Det är bytet du gör härifrån: inte magi mot förståelse, utan hastighet mot rader du redan har skrivit.
Varför Adam finns: krökning
Länk till avsnittet: Varför Adam finns: krökningDen vanliga förklaringen av Adam är ”adaptiva learning rates per parameter”, vilket är en beskrivning snarare än en orsak. Orsaken är geometri, och den kan mätas.
Ta en loss vars krökning skiljer sig mellan riktningar: brant i en, grund i en annan. SGD har en global learning rate, så den måste välja ett värde som är tillräckligt litet för att vara stabilt i den brantaste riktningen — och det värdet är då alldeles för litet för den grunda, där framstegen kryper. Det är detta som orsakar den klassiska bilden av gradient descent som sicksackar nedför en smal dal.
Två krökningskvoter, tre optimerare, 300 steg, och varje optimerare får den bästa learning rate från en sweep så att ingen missgynnas:
| krökningskvot | SGD | SGD + momentum | Adam |
|---|---|---|---|
| 10 : 1 | fel 0.000002 | fel 0.000000 | fel 0.000000 |
| 1000 : 1 | fel 1.925485 | fel 0.001432 | fel 0.000000 |
| divergerade vid (1000:1) | 4 av 8 nivåer | 4 av 8 nivåer | 0 av 6 nivåer |
Vid kvoten tio fungerar allt och det finns inget att diskutera. Vid tusen kan vanlig SGD inte nå svaret vid någon learning rate som testades — dess bästa resultat är fortfarande ett fel på 1.93 — och den divergerar direkt vid hälften av nivåerna. Adam landar exakt på målet och divergerar inte vid någon av dem.
Den sista kolumnen är det praktiska skälet till att Adam är standard. Det är inte att Adam hittar bättre lösningar; på välkonditionerade problem matchar eller slår ofta trimmad SGD den. Det är att Adam är mycket mindre känslig för den learning rate du valde, och verkliga nätverk har krökningskvoter som är mycket värre än tusen över sina miljoner parametrar.
Två bitar till hör hemma här och båda är en rad. Gradient clipping skalar om gradientvektorn när dess norm överskrider en tröskel, vilket gör raden ”loss hoppar plötsligt till ett enormt värde” i diagnostabellen till en icke-händelse. Och learning rate schedules: en kort warmup från nära noll under de första några hundra stegen, eftersom Adams variansestimat är skräp tills de har sett några gradienter och ett fullstort steg taget på skräp kan förstöra en initiering; sedan cosine decay mot noll, eftersom att avsluta en körning med samma stegstorlek som du började med innebär att man skakar runt minimum i stället för att lägga sig till ro i det.
Den andra halvan: modellen som passar perfekt och inte förutsäger något
Länk till avsnittet: Den andra halvan: modellen som passar perfekt och inte förutsäger någotAllt hittills handlade om att få loss nedåt. Nu den svårare halvan, eftersom att loss går ned inte är målet — det är en proxy för målet, och proxyn misslyckas på ett specifikt och berömt sätt.
Tolv punkter från en jämn funktion med lite brus. Anpassa polynom av ökande grad:
| grad | 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 |
Grad 11 genom 12 punkter passerar genom varenda en exakt — train-fel noll till sex decimaler — och är åtta gånger sämre än grad 5 på data den inte har sett. Be grad 3 och grad 11 att förutsäga vid , precis utanför träningsintervallet:
degree 3: predicts -1.053 (truth -0.012)
degree 11: predicts +61.224 (truth -0.012)Sextioett, där svaret är ungefär noll. Modellen lärde sig inte funktionen; den lärde sig de tolv punkterna, och mellan dem gör den vad aritmetiken kräver.
Det här är overfitting, och dess motsats — grad 1, som inte alls kan representera kurvan och är dålig överallt — är underfitting. Den klassiska redogörelsen delar upp en modells förväntade fel i tre delar: bias, felet från att modellen är för stel för att representera sanningen; varians, felet från att modellen är så flexibel att den jagar bruset i just detta sampel; och irreducerbart brus, som inget fixar. Enkla modeller är bias-drivna, flexibla modeller har hög varians, och det klassiska receptet är att hitta den gyllene medelvägen — grad 5 i tabellen ovan.
Standardverktygen angriper alla variansledet:
- L2-regularisering (weight decay) lägger till till loss, drar vikter mot noll och gör funktionen jämnare. I tabellen ovan är det grad 11:s största koefficient som gör skadan; att straffa storlek desarmerar den.
- L1 lägger till i stället. Skillnaden är inte kosmetisk: L2:s gradient är proportionell mot vikten och krymper därför när vikten gör det, närmar sig noll utan att nå fram, medan L1:s gradient är en konstant som fortsätter trycka hela vägen. L1 producerar därför vikter som är exakt noll — den väljer features. L2 producerar små vikter. Använd L2 när du vill ha jämnhet, L1 när du vill ha gleshet.
- Dropout7 nollar en slumpmässig delmängd av aktiveringarna vid varje träningssteg, så att ingen enhet kan lita på att någon särskild annan enhet finns där.
- Early stopping bevakar validation loss och stoppar när den vänder uppåt.
- Data augmentation tillverkar fler träningsexempel från dem du har, vilket angriper problemet vid källan: overfitting är brist på data lika mycket som ett överskott av parametrar.
- Cross-validation delar upp data på sätt och tränar gånger, vilket köper en tillförlitlig uppskattning av testfel när du har för lite data för att avvara en separat uppsättning.
Double descent, eller varför föregående avsnitt inte är hela historien
Länk till avsnittet: Double descent, eller varför föregående avsnitt inte är hela historienNu faktumet som bryter bilden.
Bias-variance-berättelsen säger att efter den gyllene medelvägen betyder fler parametrar sämre generalisering. Moderna språkmodeller har långt fler parametrar än de klassiska reglerna tillåter för datan de ser, och generaliserar utmärkt. Båda påståendena är sanna, och att förena dem är det mest användbara i det här kapitlet.
Fyrtio träningspunkter, tjugodimensionella inputs, slumpmässiga ReLU-features, och antalet features svept från 2 till 5000 — med minimum-norm-lösningen vald när det finns många som passar:
| 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 |
Läs den i tre delar. Upp till gäller den klassiska berättelsen exakt: felet faller och börjar sedan stiga. Vid — interpolationströskeln, där modellen har exakt nog många parametrar för att passera genom varje träningspunkt — toppar testfelet, vid 5.81, fem gånger värre än den lilla modellen. Den toppen är den klassiska varningen, och den är verklig.
Sedan sjunker den igen. Och den fortsätter sjunka, förbi , förbi , hela vägen till , där testfelet 0.5664 är bättre än den bästa underparametriserade modellen någonsin uppnådde. En modell med 5000 parametrar anpassad till 40 punkter är den bästa modellen i tabellen.
Det här är double descent,89 och mekanismen syns i den sista kolumnen. När finns oändligt många parameterinställningar som passar träningsdatan exakt, och vilken du får beror på hur du väljer. Minimum-norm-lösningen väljer den minsta, och visar vad det betyder: den toppar vid 14.83 precis vid tröskeln — där det finns exakt en interpolerande lösning och du sitter fast med den, hur extrem den än är — och faller monotont när växer, eftersom fler parametrar betyder fler interpolerande lösningar att välja mellan, vilket betyder att den minsta tillgängliga blir mindre. Vid är normen 0.18, åttio gånger mindre än vid tröskeln.
Så de extra parametrarna lägger inte till komplexitet. De lägger till valmöjligheter, och urvalsregeln spenderar den valmöjligheten på enkelhet. Regulariseringen finns inte i loss-funktionen; den finns i algoritmen. Gradient descent från en liten initiering har en dokumenterad bias mot lösningar med liten norm, vilket är varför detta beteende dyker upp i verkliga nätverk tränade på vanligt sätt och inte bara i den linjära algebran ovan.
Den praktiska konsekvensen, som kapitel 10 beror på: ”modellen har fler parametrar än data, så den kommer att overfit” är inte ett giltigt argument. Det var en bra regel när modeller levde till vänster om tröskeln. Allt intressant nu lever långt till höger om den, där regeln vänds.
Vart detta leder härnäst
Länk till avsnittet: Vart detta leder härnästVerktygen i det här kapitlet räcker för att träna ett nätverk som fungerar på data du kan lägga i en tabell: rader av tal, en kolumn med labels.
Språk är inte det. Innan en modell kan förutsäga nästa ord måste något avgöra vad ett ”ord” ens är — och svaret är varken bokstäver eller ord, utan ett vokabulär som modellen lär sig från träningsdatans råa bytes. Det beslutet, fattat en gång innan träningen börjar, avgör hur många saker modellen kan säga, hur mycket en begäran kostar, och varför modeller som kan klara en juridiktenta inte tillförlitligt kan räkna bokstäverna i strawberry.
Kapitel 7 bygger en tokenizer.
Källor och metod
Länk till avsnittet: Källor och metodFör residual connections som användes ovan, He et al., Deep Residual Learning for Image Recognition (arXiv:1512.03385). Andrej Karpathys Building makemore Part 3: Activations & Gradients, BatchNorm går igenom aktiveringshistogram-diagnostiken på en verklig modell och är den bästa praktiska behandlingen av den första halvan av det här kapitlet. Yaser Abu-Mostafas Learning From Data-föreläsningar 8 och 11–13 ger den klassiska generaliseringsteorin ordentligt, inklusive de delar som det här kapitlet komprimerade till ett stycke.
Referenser
Länk till avsnittet: Referenser-
Glorot, X. and Bengio, Y. Understanding the difficulty of training deep feedforward neural networks. AISTATS (2010). Variansbevarande-argumentet återges i rutan ovan. ↩
-
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). Notera att förklaringen ”internal covariate shift” i titeln sedan dess har ifrågasatts kraftigt; lagret fungerar, den ursprungliga förklaringen till varför är omstridd. ↩
-
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). Artikeln som namngav fenomenet. ↩
-
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). Visar effekten i verkliga djupa nätverk, och längs axeln träningstid såväl som axeln modellstorlek. ↩