Le faire apprendre, puis le faire généraliser
Un réseau à six couches dont la perte reste à ln 2, corrigé mesure par mesure. Puis double descent : 5 000 paramètres pour 40 points.
Dans cet article
Le réseau du chapitre 5 fonctionne. Il possède neuf paramètres, apprend XOR, et ses gradients concordent avec ceux de PyTorch à seize décimales près.
Passez-le à six couches de profondeur et il cesse complètement d’apprendre. Pas lentement — complètement. Voici un réseau à six couches sur un problème de classification à deux spirales, entraîné pendant 5 000 étapes :
step 1: loss 0.693147
step 5000: loss 0.693147
accuracy: 50.0 %Ce nombre n’est pas arbitraire. est l’entropie croisée binaire d’un modèle qui sort une probabilité de pour tout, et 50 % correspond à un pile ou face sur un jeu de données équilibré. Après cinq mille étapes, le réseau n’a pas bougé d’un seul chiffre. Rien n’a planté, rien n’a émis d’avertissement, et les gradients sont toujours exactement justes.
Ce chapitre porte sur l’écart entre un réseau qui s’exécute et un réseau qui fonctionne. Il comporte deux moitiés qui ressemblent à des sujets différents mais relèvent du même travail : faire baisser la perte, puis la faire baisser sur des données que le modèle n’a jamais vues.
Pourquoi le réseau à six couches est mort
Lien vers la section : Pourquoi le réseau à six couches est mortCommencez par regarder, au lieu de deviner. Faites passer un batch d’entrées dans le réseau et affichez l’écart type des activations à chaque couche, puis l’écart type des gradients des poids :
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}")Trois initialisations, même architecture, six couches de :
| initialisation | écart type d’activation, couches 1→6 |
|---|---|
| normale, écart type | 0.0145 · 0.0016 · 0.0002 · 0.0000 · 0.0000 · 0.0000 |
| normale, écart type | 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 |
| initialisation | écart type du gradient, première couche → dernière |
|---|---|
| normale, écart type | 3.20e-06 · 4.97e-07 · … · 6.40e-06 |
| normale, écart type | 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 première ligne est le réseau ci-dessus, et il n’apprend pas lentement — il n’a plus aucun signal. À la quatrième couche, l’écart type des activations s’est effondré à zéro à quatre décimales près. Chaque entrée produit la même sortie, la sortie est une constante, et le gradient d’une constante ne vaut rien. Les poids ont été initialisés petits « par sécurité », et cette petitesse a été fatale.
La deuxième ligne est l’échec inverse, et elle mérite d’être comprise parce qu’elle est contre-intuitive. Les activations semblent saines — autour de 0,96 — mais c’est saturée, plaquée près de sa limite, exactement le régime que le chapitre 5 mesurait comme perdant un facteur de presque dix mille dans le gradient. Et pourtant les gradients sont énormes : 1 940 à la première couche. Les deux choses sont vraies en même temps. Chaque étape arrière multiplie par , et avec 128 entrées à variance unitaire ce facteur a un gain d’environ , qui submerge la contraction due à saturée. Les gradients croissent géométriquement en revenant en arrière. C’est l’exploding gradient, et cela produit des valeurs de perte de nan en quelques étapes dans n’importe quel véritable entraînement.
La troisième ligne est ce que vous voulez : des activations à peu près constantes en échelle sur la profondeur, des gradients à peu près constants en échelle sur la profondeur. Rien ne meurt, rien n’explose.
Normalisation, et celle qui a survécu
Lien vers la section : Normalisation, et celle qui a survécuBien initialiser corrige l’échelle à l’étape zéro. Cela ne la maintient pas fixe : les poids bougent, et à l’étape cinq mille, l’argument soigneux sur la variance ne s’applique plus.
Les couches de normalisation imposent l’échelle en continu. À partir d’un vecteur d’activations, soustrayez une moyenne, divisez par un écart type, puis appliquez une échelle apprise et un décalage afin que la couche puisse annuler la normalisation si c’est finalement ce qu’elle veut :
La seule vraie question est sur quoi vous faites la moyenne. Batch normalisation3 prend et sur la dimension du batch, une statistique par feature. Layer normalisation4 les prend sur les features, une statistique par exemple.
Ce choix a l’air mineur et décide presque tout en aval :
BatchNorm fait dépendre la sortie de chaque exemple des autres exemples qui se trouvent par hasard dans son batch. À l’entraînement, c’est un léger régularisateur. À l’inférence, il n’y a pas de batch, donc il faut conserver une moyenne glissante des statistiques collectées pendant l’entraînement — ce qui signifie que la couche se comporte différemment en mode entraînement et en mode évaluation, et oublier de changer de mode est l’un des bugs les plus courants du domaine. Elle se dégrade aussi avec de petits batchs, et elle est maladroite avec des séquences de longueur variable, parce que « la moyenne sur le batch à la position 40 » est calculée à partir du nombre de séquences qui se trouvent justement être aussi longues.
LayerNorm normalise chaque exemple isolément. Aucune dépendance au batch, aucune statistique glissante, comportement identique à l’entraînement et à l’inférence, indifférence à la taille du batch, indifférence à la longueur de séquence. Chacune de ces propriétés devient une exigence plutôt qu’un confort dès lors que vous générez un token à la fois pour un utilisateur, ce qui est le point d’arrivée du chapitre 13.
C’est pourquoi LayerNorm est celle que vous retrouverez au chapitre 9 inchangée : le bloc transformer l’utilise, et il l’utilise pour les raisons de la colonne de droite, pas parce qu’elle fonctionne mieux dans l’absolu.
Corriger une chose à la fois, ce qui est la vraie skill
Lien vers la section : Corriger une chose à la fois, ce qui est la vraie skillQuatre correctifs candidats pour le réseau mort : initialisation Xavier, LayerNorm, connexions résiduelles, et Adam au lieu de SGD. La tentation est d’appliquer les quatre et de passer à autre chose. Faites cela et vous ne saurez jamais lequel a compté ; la prochaine fois que cela arrivera, vous n’aurez pas de méthode — seulement un rituel.
Appliquez-les donc un par un. Même seed, mêmes données, même architecture, 800 étapes :
| ce qui a été ajouté | perte finale | précision |
|---|---|---|
| rien | 0.6931 | 50.0 % |
| initialisation Xavier | 0.5692 | 60.4 % |
| LayerNorm | 0.6230 | 61.5 % |
| connexions résiduelles | 0.6651 | 56.6 % |
| Adam | 0.6787 | 58.7 % |
| les quatre | 0.0000 | 100.0 % |
Lisez ce tableau comme vous le liriez à 2 h du matin, et la conclusion est : rien ne fonctionne seul, tout fonctionne ensemble, donc le deep learning est de l’alchimie. Cette conclusion est fausse, et comprendre pourquoi est ce qu’il y a de plus utile dans ce chapitre.
Donnez à chaque exécution six fois plus de budget — 5 000 étapes au lieu de 800 — et tout change :
| ce qui a été ajouté | perte finale @ 5 000 | précision |
|---|---|---|
| rien | 0.6931 | 50.0 % |
| initialisation Xavier | 0.0007 | 100.0 % |
| LayerNorm | 0.0002 | 100.0 % |
| connexions résiduelles | 0.6653 | 56.7 % |
| Adam | 0.6908 | 53.4 % |
| Xavier + Adam | 0.0000 | 100.0 % |
| Xavier + LayerNorm | 0.0001 | 100.0 % |
L’image est désormais nette, et c’est un diagnostic plutôt qu’un rituel.
L’initialisation seule le corrige. La normalisation seule le corrige. Chacune s’attaque à la vraie maladie — l’effondrement à zéro du signal avant — et l’une ou l’autre suffit. À 800 étapes, elles donnaient simplement l’impression de n’obtenir qu’un crédit partiel, parce qu’elles avaient résolu le problème mais étaient encore en train de remonter.
Les connexions résiduelles et Adam ne le corrigent pas, quel que soit le budget. Non pas parce qu’elles sont mauvaises, mais parce qu’elles traitent une autre maladie. Une connexion résiduelle donne au gradient un chemin qui contourne une couche bloquante ; c’est très précieux quand le gradient est le problème, et inutile quand le signal avant est déjà nul, parce qu’un raccourci autour d’une couche morte transporte encore une valeur morte. Adam remet à l’échelle le pas de chaque paramètre selon son propre historique de gradient ; cela aide quand les gradients ont des amplitudes très différentes, mais ne peut pas ressusciter un réseau dont la sortie ne dépend pas de son entrée.
Et « rien » vaut toujours exactement 0.6931 après cinq mille étapes. Pas 0.6929. Ce n’est pas lent ; c’est mort, et cette distinction est visible comme elle ne l’était pas avant, parce que vous avez la ligne montrant qu’un correctif fonctionne pour comparer.
Mériter PyTorch
Lien vers la section : Mériter PyTorchÀ partir d’ici, ce cours utilise PyTorch. Il faut le mériter plutôt que l’annoncer ; voici donc exactement ce qu’il fait que vous savez déjà faire.
Un optimiseur est une règle qui transforme des gradients en mises à jour de paramètres. Le gradient descent simple utilise le gradient. Momentum en utilise une moyenne glissante, ce qui lisse le bruit et prend de la vitesse dans les directions qui restent cohérentes :
v = beta * v + p.grad
p -= lr * v Adam5 conserve deux moyennes glissantes — du gradient et du gradient au carré — et divise l’une par la racine carrée de l’autre, de sorte que chaque paramètre reçoit un pas mis à l’échelle selon l’amplitude récente de son propre gradient :
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) Dix lignes. Exécutez les deux contre torch.optim sur le même problème pendant 50 étapes :
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-07Identique à la précision float32. torch.optim.Adam, ce sont ces cinq lignes, plus des décennies d’attention aux cas limites et un kernel C++. C’est l’échange que vous faites à partir d’ici : pas de la magie à la place de la compréhension, mais de la vitesse pour des lignes que vous avez déjà écrites.
Pourquoi Adam existe : la courbure
Lien vers la section : Pourquoi Adam existe : la courbureL’explication habituelle d’Adam est « des learning rates adaptatifs par paramètre », ce qui est une description plutôt qu’une raison. La raison est géométrique, et elle peut se mesurer.
Prenez une perte dont la courbure diffère selon les directions : raide dans l’une, plate dans l’autre. SGD dispose d’un unique learning rate global, il doit donc choisir une valeur assez petite pour rester stable dans la direction la plus raide — et cette valeur est alors beaucoup trop petite pour la direction plate, où la progression rampe. C’est ce qui provoque l’image classique du gradient descent qui zigzague dans une vallée étroite.
Deux ratios de courbure, trois optimiseurs, 300 étapes, et à chaque optimiseur le meilleur learning rate issu d’un balayage pour ne handicaper personne :
| ratio de courbure | SGD | SGD + momentum | Adam |
|---|---|---|---|
| 10 : 1 | erreur 0.000002 | erreur 0.000000 | erreur 0.000000 |
| 1000 : 1 | erreur 1.925485 | erreur 0.001432 | erreur 0.000000 |
| divergence à (1000:1) | 4 taux sur 8 | 4 taux sur 8 | 0 taux sur 6 |
À un ratio de dix, tout fonctionne et il n’y a rien à discuter. À mille, le SGD simple ne peut atteindre la réponse à aucun learning rate essayé — son meilleur résultat reste une erreur de 1,93 — et il diverge carrément à la moitié des taux. Adam arrive exactement sur la cible et ne diverge à aucun.
Cette dernière colonne est la raison pratique pour laquelle Adam est le choix par défaut. Ce n’est pas qu’Adam trouve de meilleures solutions ; sur des problèmes bien conditionnés, SGD bien réglé l’égale ou le bat souvent. C’est qu’Adam est beaucoup moins sensible au learning rate choisi, et les réseaux réels ont des ratios de courbure bien pires que mille sur leurs millions de paramètres.
Deux autres pièces appartiennent à cette section, et chacune tient en une ligne. Gradient clipping remet à l’échelle le vecteur de gradient chaque fois que sa norme dépasse un seuil, ce qui transforme la ligne « la perte saute soudain à une valeur énorme » du tableau de diagnostic en non-événement. Et les calendriers de learning rate : un court warmup depuis presque zéro sur les quelques centaines de premières étapes, parce que les estimations de variance d’Adam sont inutilisables tant qu’elles n’ont pas vu quelques gradients, et qu’un pas de taille complète pris sur des estimations inutilisables peut ruiner une initialisation ; puis une cosine decay vers zéro, parce que terminer une exécution avec la même taille de pas qu’au départ signifie trembler autour du minimum au lieu de s’y stabiliser.
La deuxième moitié : le modèle qui s’ajuste parfaitement et ne prédit rien
Lien vers la section : La deuxième moitié : le modèle qui s’ajuste parfaitement et ne prédit rienTout jusqu’ici visait à faire baisser la perte. Maintenant vient la moitié la plus difficile, parce que faire baisser la perte n’est pas l’objectif — c’est un proxy de l’objectif, et ce proxy échoue d’une manière précise et célèbre.
Douze points issus d’une fonction lisse avec un peu de bruit. Ajustez des polynômes de degré croissant :
| degré | RMSE entraînement | RMSE test |
|---|---|---|
| 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 |
Le degré 11 sur 12 points passe par chacun d’eux exactement — erreur d’entraînement nulle à six décimales près — et il est huit fois pire que le degré 5 sur des données qu’il n’a pas vues. Demandez au degré 3 et au degré 11 de prédire à , juste en dehors de la plage d’entraînement :
degree 3: predicts -1.053 (truth -0.012)
degree 11: predicts +61.224 (truth -0.012)Soixante et un, alors que la réponse vaut environ zéro. Le modèle n’a pas appris la fonction ; il a appris les douze points, et entre eux il fait ce que l’arithmétique exige.
C’est de l’overfitting, et son opposé — le degré 1, incapable de représenter la courbe et mauvais partout — est de l’underfitting. Le récit classique décompose l’erreur attendue d’un modèle en trois parties : le biais, l’erreur due au fait que le modèle est trop rigide pour représenter la vérité ; la variance, l’erreur due au fait que le modèle est si flexible qu’il poursuit le bruit de cet échantillon particulier ; et le bruit irréductible, que rien ne corrige. Les modèles simples sont biaisés, les modèles flexibles ont une variance élevée, et la prescription classique consiste à trouver le bon compromis au milieu — le degré 5 dans le tableau ci-dessus.
Les outils standards s’attaquent tous au terme de variance :
- Régularisation L2 (weight decay) ajoute à la perte, tirant les poids vers zéro et rendant la fonction plus lisse. Dans le tableau ci-dessus, le plus grand coefficient du degré 11 fait les dégâts ; pénaliser la taille le désamorce.
- L1 ajoute plutôt . La différence n’est pas cosmétique : le gradient de L2 est proportionnel au poids et diminue donc avec lui, s’approchant de zéro sans l’atteindre, tandis que le gradient de L1 est une constante qui continue de pousser jusqu’au bout. L1 produit donc des poids exactement nuls — elle sélectionne des features. L2 produit de petits poids. Utilisez L2 quand vous voulez de la douceur, L1 quand vous voulez de la parcimonie.
- Dropout7 met à zéro un sous-ensemble aléatoire d’activations à chaque étape d’entraînement, de sorte qu’aucune unité ne peut compter sur la présence d’une autre unité particulière.
- Arrêt anticipé surveille la perte de validation et s’arrête lorsqu’elle remonte.
- Augmentation de données fabrique davantage d’exemples d’entraînement à partir de ceux que vous avez, ce qui attaque le problème à la source : l’overfitting est autant un manque de données qu’un excès de paramètres.
- Validation croisée divise les données en parties et entraîne fois, ce qui permet d’obtenir une estimation fiable de l’erreur de test quand vous avez trop peu de données pour réserver un ensemble tenu à l’écart.
Double descent, ou pourquoi la section précédente n’est pas toute l’histoire
Lien vers la section : Double descent, ou pourquoi la section précédente n’est pas toute l’histoireMaintenant, le fait qui brise le tableau.
L’histoire biais-variance dit qu’au-delà du bon compromis, plus de paramètres signifie une généralisation moins bonne. Les modèles de langage modernes ont bien plus de paramètres que les règles classiques n’en autorisent pour les données qu’ils voient, et généralisent superbement. Ces deux affirmations sont vraies, et les réconcilier est la chose la plus utile de ce chapitre.
Quarante points d’entraînement, des entrées à vingt dimensions, des features ReLU aléatoires, et le nombre de features balayé de 2 à 5 000 — avec la solution à norme minimale choisie chaque fois qu’il en existe beaucoup qui s’ajustent :
| RMSE entraînement | RMSE test | |||
|---|---|---|---|---|
| 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 |
Lisez-le en trois parties. Jusqu’à , le récit classique tient exactement : l’erreur baisse, puis commence à remonter. À — le seuil d’interpolation, où le modèle possède exactement assez de paramètres pour passer par chaque point d’entraînement — l’erreur de test atteint un pic, à 5,81, cinq fois pire que le petit modèle. Ce pic est l’avertissement classique, et il est réel.
Puis elle redescend. Et elle continue de descendre, au-delà de , au-delà de , jusqu’à , où l’erreur de test de 0,5664 est meilleure que celle du meilleur modèle sous-paramétré jamais obtenue. Un modèle à 5 000 paramètres ajusté sur 40 points est le meilleur modèle du tableau.
C’est la double descent,89 et le mécanisme est visible dans la dernière colonne. Une fois , il existe une infinité de réglages de paramètres qui ajustent exactement les données d’entraînement, et celui que vous obtenez dépend de la façon dont vous choisissez. La solution à norme minimale prend la plus petite, et montre ce que cela signifie : elle culmine à 14,83 juste au seuil — là où il n’existe qu’une seule solution interpolante et vous êtes coincé avec elle, aussi extrême soit-elle — puis diminue monotonement à mesure que augmente, parce que plus de paramètres signifie plus de solutions interpolantes parmi lesquelles choisir, ce qui signifie que la plus petite disponible devient plus petite. À , la norme est de 0,18, quatre-vingts fois plus petite qu’au seuil.
Les paramètres supplémentaires n’ajoutent donc pas de complexité. Ils ajoutent du choix, et la règle de sélection dépense ce choix en simplicité. La régularisation n’est pas dans la fonction de perte ; elle est dans l’algorithme. Le gradient descent depuis une petite initialisation a un biais documenté vers des solutions à petite norme, ce qui explique pourquoi ce comportement apparaît dans de vrais réseaux entraînés de manière ordinaire, et pas seulement dans l’algèbre linéaire ci-dessus.
Conséquence pratique, dont dépend le chapitre 10 : « le modèle a plus de paramètres que de données, donc il va overfit » n’est pas un argument valide. C’était une bonne règle quand les modèles vivaient à gauche du seuil. Tout ce qui est intéressant vit maintenant loin à sa droite, là où la règle s’inverse.
Où cela mène ensuite
Lien vers la section : Où cela mène ensuiteLes outils de ce chapitre suffisent à entraîner un réseau qui fonctionne sur des données que vous pouvez mettre dans un tableau : des lignes de nombres, une colonne d’étiquettes.
Le langage n’est pas cela. Avant qu’un modèle puisse prédire le prochain mot, quelque chose doit décider ce qu’est même un « mot » — et la réponse n’est ni des lettres ni des mots, mais un vocabulaire que le modèle apprend à partir des octets bruts des données d’entraînement. Cette décision, prise une fois avant le début de l’entraînement, détermine combien de choses le modèle peut dire, combien coûte une requête, et pourquoi des modèles capables de réussir un examen de droit ne savent pas compter de manière fiable les lettres dans strawberry.
Le chapitre 7 construit un tokenizer.
Sources et méthode
Lien vers la section : Sources et méthodePour les connexions résiduelles utilisées ci-dessus, He et al., Deep Residual Learning for Image Recognition (arXiv:1512.03385). Building makemore Part 3: Activations & Gradients, BatchNorm d’Andrej Karpathy parcourt le diagnostic par histogrammes d’activation sur un vrai modèle et constitue le meilleur traitement pratique de la première moitié de ce chapitre. Les cours 8 et 11–13 de Learning From Data de Yaser Abu-Mostafa exposent correctement la théorie classique de la généralisation, y compris les parties que ce chapitre a condensées en un paragraphe.
Références
Lien vers la section : Références-
Glorot, X. et Bengio, Y. Understanding the difficulty of training deep feedforward neural networks. AISTATS (2010). L’argument de préservation de la variance reproduit dans l’encadré ci-dessus. ↩
-
He, K., Zhang, X., Ren, S. et Sun, J. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. arXiv:1502.01852 (2015). ↩
-
Ioffe, S. et Szegedy, C. Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. arXiv:1502.03167 (2015). Notez que l’explication par « internal covariate shift » présente dans le titre a depuis été largement contestée ; la couche fonctionne, mais l’explication originale du pourquoi est débattue. ↩
-
Ba, J. L., Kiros, J. R. et Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). ↩
-
Kingma, D. P. et Ba, J. Adam: A Method for Stochastic Optimization. arXiv:1412.6980 (2014). ↩
-
Loshchilov, I. et Hutter, F. Decoupled Weight Decay Regularization. arXiv:1711.05101 (2017). ↩
-
Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I. et Salakhutdinov, R. Dropout: A Simple Way to Prevent Neural Networks from Overfitting. JMLR 15, pp. 1929–1958 (2014). ↩
-
Belkin, M., Hsu, D., Ma, S. et Mandal, S. Reconciling modern machine-learning practice and the classical bias–variance trade-off. PNAS 116(32), pp. 15849–15854 (2019). L’article qui a nommé le phénomène. ↩
-
Nakkiran, P., Kaplun, G., Bansal, Y., Yang, T., Barak, B. et Sutskever, I. Deep Double Descent: Where Bigger Models and More Data Hurt. arXiv:1912.02292 (2019). Montre l’effet dans de vrais réseaux profonds, et sur l’axe du temps d’entraînement autant que sur l’axe de la taille du modèle. ↩