La boucle d'entraînement
- Leçon 10
- 50 min
- Module 3 : Comment les réseaux de neurones apprennent
Leçon 10 sur 23 · L'apprentissage profond et les transformeurs expliquésLa boucle d'entraînement
1. Ce qu'est un réseau de neurones
2. Les tenseurs et PyTorch
3. Comment les réseaux de neurones apprennent
4. Diagnostiquer les problèmes d'entraînement
5. Représenter les séquences et le sens
6. Assembler un transformeur
7. Utiliser des modèles préentraînés
8. Sous le capot : les mathématiquesFacultatif
Ce que vous saurez faire
Entraînez réellement le routeur de billets de soutien : répétez cinq étapes, dans un ordre fixe, jusqu’à obtenir une faible perte. Gardez un journal de cette perte. Tout entraînement dans PyTorch suit cette boucle. Une étape manquante ne produit généralement aucun message d’erreur, seulement un réseau qui apprend mal ou pas du tout.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter contient toutes les cellules de code, les tâches et les vérifications de cette leçon. Il exécute la boucle d’entraînement en lot complet sur les billets et mesure le résultat. Ensuite, il omet chaque étape à tour de rôle et montre les gradients qui s’accumulent sans zero_grad(). L’entraînement prend quelques secondes sur un processeur. Environ 25 minutes.
Le carnet est en anglais. Colab et Kaggle ouvrent votre propre copie. Il faut un compte Google ou Kaggle gratuit. Vous êtes bloqué ? Consultez le carnet de solution.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- La perte et
nn.CrossEntropyLoss(), dans La perte : à quel point le réseau se trompe-t-il? - La règle de mise à jour et le taux d’apprentissage, dans La descente de gradient.
loss.backward(),.gradet les gradients qui s’additionnent, dans La rétropropagation et autograd.
Ce qu’il vous faut sur votre ordinateur
- Rien pour travailler dans le carnet.
- Pour travailler localement : PyTorch et le dossier
ticket-router, de la leçon Installer PyTorch. Ajoutez aussi les lignes de configuration de Les billets sous forme de nombres au début de chaque fichier de code.
Testé avec Python 3.12 et PyTorch 2.14.1 sur un processeur.
S’exercer au lancer franc
Section intitulée « S’exercer au lancer franc »Une joueuse de basketball s’exerce aux lancers francs. Chaque lancer suit les mêmes étapes. Elle oublie la dernière correction. Elle lance. Elle observe à quelle distance le ballon a manqué la cible. Elle détermine la correction : un peu plus de force, un peu vers la gauche. Ensuite, elle modifie son lancer et recommence.
L’ordre compte. Si elle modifie son lancer avant de voir où le ballon atterrit, elle corrige sans raison. Et si elle n’oublie pas les corrections précédentes, chacune s’ajoute à la nouvelle. Après quelques lancers, elle corrige beaucoup trop.
Le problème : exécuter les éléments dans l’ordre, des milliers de fois
Section intitulée « Le problème : exécuter les éléments dans l’ordre, des milliers de fois »Les trois dernières leçons vous ont donné les éléments nécessaires. La perte indique à quel point le réseau se trompe. La descente de gradient modifie les poids, et la rétropropagation fournit les gradients. Pour entraîner le routeur de billets, il faut exécuter ces éléments des centaines ou des milliers de fois. Toujours dans le même ordre.
Chaque élément occupe une ligne de code. Une ligne manquante ou mal placée peut facilement passer inaperçue. Pire encore, PyTorch ne signale aucune erreur pour la plupart de ces problèmes. Le réseau s’entraîne, la perte s’affiche, mais le résultat est simplement mauvais. Il vous faut donc une structure d’entraînement fixe, que vous pouvez vérifier d’un coup d’œil.
L’idée : la boucle d’entraînement en cinq étapes
Section intitulée « L’idée : la boucle d’entraînement en cinq étapes »La boucle d’entraînement répète cinq étapes. Un cycle de ces cinq étapes constitue un pas d’entraînement : une mise à jour des poids.
Description textuelle du diagramme
Cinq cases dans un cycle. Étape 1, remettre les gradients à zéro : optimizer.zero_grad(). Étape 2, la propagation avant : scores = model(x). Étape 3, la perte : loss = loss_fn(scores, y). Étape 4, la rétropropagation : loss.backward(). Étape 5, mettre à jour les poids : optimizer.step(). Une flèche portant l’étiquette « lot suivant » revient de l’étape 5 à l’étape 1.
- Remettre les gradients à zéro.
backward()s’ajoute aux gradients déjà présents. Effacez-les donc d’abord : la joueuse oublie les corrections précédentes. - Propagation avant. Le réseau calcule ses scores pour les billets d’entraînement : le lancer.
- Perte. Comparez les scores aux bonnes équipes : la distance entre le ballon et la cible.
- Rétropropagation. Un gradient pour chaque paramètre : la correction.
- Mise à jour. Déplacez chaque paramètre dans le sens opposé à son gradient : modifier le lancer.
Les étapes 1 et 5 relèvent de l’optimiseur : l’objet qui conserve la liste des paramètres et les modifie. torch.optim.SGD(model.parameters(), lr=0.5) applique exactement la règle de mise à jour de la leçon sur la descente de gradient. Chaque paramètre moins 0,5 fois son gradient. Le cours utilise torch.optim.Adam avec un taux d’apprentissage de 0,001. Adam utilise aussi la descente de gradient, mais adapte la taille du pas pour chaque paramètre. Taux d’apprentissage et optimiseurs compare les deux. Pour l’instant, voyez Adam comme une « descente de gradient avec laquelle il est plus difficile de se tromper ».
Exemple détaillé : Priya entraîne le routeur
Section intitulée « Exemple détaillé : Priya entraîne le routeur »Priya commence par la version la plus simple. Chaque pas utilise les 4 696 billets d’entraînement en même temps. C’est ce qu’on appelle l’entraînement en lot complet. Elle crée l’optimiseur une seule fois, avant la boucle, et consigne la perte tous les 10 pas.
set_seed(0)model = BagOfWordsNet(len(vocab))optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for step in range(1, 101): optimizer.zero_grad() # 1. forget the last step's gradients scores = model(x_train) # 2. forward pass: all 4,696 training tickets loss = loss_fn(scores, y_train) # 3. how wrong? loss.backward() # 4. backward pass: a gradient for every parameter optimizer.step() # 5. update every parameter if step % 10 == 0: print(f"step {step:3} loss {loss.item():.4f}")La sortie :
step 10 loss 1.4113step 20 loss 1.1342step 30 loss 0.8550step 40 loss 0.6181step 50 loss 0.4454step 60 loss 0.3313step 70 loss 0.2573step 80 loss 0.2076step 90 loss 0.1724step 100 loss 0.1460model.parameters() fournit à l’optimiseur les 183 685 paramètres du réseau. step % 10 == 0 est vrai tous les dix pas, ce qui garde le journal court. L’exécution complète prend environ 1,5 seconde sur un processeur. Le graphique montre la perte à chaque pas, pas seulement tous les dix pas.
Description textuelle du graphique
La perte commence à 1,624, la perte d’une prédiction qui donne la même probabilité à chaque équipe. Elle baisse lentement aux premiers pas, puis rapidement entre les pas 10 et 50 (de 1,41 à 0,45). Ensuite, elle baisse plus lentement encore : 0,26 au pas 70 et 0,146 au pas 100. Elle baisse à chaque pas; la courbe ne présente aucun saut.
La perte baisse à chaque pas, sans saut, contrairement à la descente de gradient simple de la dernière leçon. Priya évalue ensuite le réseau entraîné sur les 600 billets de validation, qu’il n’a jamais utilisés pour s’entraîner :
valid_loss, valid_acc = evaluate(model, x_valid, y_valid)print(f"validation loss {valid_loss:.4f} accuracy {valid_acc:.4f}")La sortie :
validation loss 0.1985 accuracy 0.9783De 18,2 % à 97,8 % en 100 pas. Omar demande ce que cela signifie pour l’équipe de Grace. Environ 98 billets sur 100 arrivent maintenant à la bonne équipe sans qu’une personne les lise. Ce résultat ne dépasse pas encore celui du modèle TF-IDF du cours d’apprentissage automatique. Ce modèle atteint 98,8 % sur les mêmes billets; le cours les compare équitablement à la fin.
Ce qui arrive quand une étape manque
Section intitulée « Ce qui arrive quand une étape manque »Priya effectue quatre entraînements de 20 pas. Une fois avec les cinq étapes, puis une fois sans chacune des étapes 1, 4 et 5. Voici la perte au pas 20 de chaque entraînement, tirée du carnet :
| Élément omis | Perte au pas 20 | Message d’erreur? | Ce qui s’est passé |
|---|---|---|---|
| rien | 1.1342 | non | Le réseau apprend. |
zero_grad() |
1.1745 | non | Il apprend, mais moins bien : les anciens gradients s’accumulent. |
backward() |
1.6236 | non | Aucun gradient, donc step() n’a rien à utiliser. Le réseau reste inchangé. |
step() |
1.6236 | non | Les gradients sont calculés, puis ignorés. Le réseau reste inchangé. |
Aucun des quatre entraînements ne s’arrête avec une erreur. Une perte qui reste à sa valeur initiale (1,6236 ici) indique que les poids ne changent jamais. Cherchez un appel manquant à backward() ou à step(). Omettre l’étape 2 ou 3 n’est pas possible : sans elles, aucune perte ne permet d’appeler backward().
Quand utiliser la boucle et ce qu’elle coûte
Section intitulée « Quand utiliser la boucle et ce qu’elle coûte »Chaque réseau que vous entraînez dans PyTorch utilise cette boucle. Les bibliothèques de plus haut niveau la cachent dans une fonction fit() ou train(), mais les cinq étapes restent présentes. Le fichier ticketnet.py du cours en contient une, train(), avec les mêmes cinq lignes et les mêmes commentaires. Dès la prochaine leçon, vous pourrez l’utiliser.
L’entraînement en lot complet a deux coûts. D’abord, la mémoire : chaque pas conserve tous les billets d’entraînement et les données de leur propagation avant. Les fréquences des mots seules représentent 4 696 × 2 864 nombres, soit environ 54 Mo. Les jeux de données réels comptent des millions de lignes. Ensuite, la vitesse d’apprentissage : chaque pas exige un passage complet sur les données. Ainsi, 100 pas lisent chaque billet 100 fois. La prochaine leçon règle ces deux problèmes avec de petits lots.
Exercice guidé : la descente de gradient simple
Section intitulée « Exercice guidé : la descente de gradient simple »- Dans le carnet, allez à la section 4.
- Créez un nouveau réseau avec
set_seed(0)etBagOfWordsNet(len(vocab)). - Créez l’optimiseur :
torch.optim.SGD(model.parameters(), lr=0.5). - Écrivez les cinq étapes dans une boucle de 10 pas, et ajoutez chaque
loss.item()àlosses. - Exécutez la cellule de vérification. Vous devriez voir
Check passed(vérification réussie). Les dix pertes commencent à 1,6236 et finissent à 1,4191.
Afficher un exemple de réponse
losses = []set_seed(0)model = BagOfWordsNet(len(vocab))optimizer = torch.optim.SGD(model.parameters(), lr=0.5)for step in range(10): optimizer.zero_grad() loss = loss_fn(model(x_train), y_train) loss.backward() optimizer.step() losses.append(loss.item())Ce sont les mêmes dix pas que la descente de gradient simple sur le réseau de billets dans la dernière leçon. Les pertes sont : 1,624, 1,599, 1,578 … 1,419. torch.optim.SGD applique la règle de mise à jour pour vous. Adam a atteint 1,411 après les mêmes dix pas, une valeur un peu plus faible.
À vous de jouer : un taux d’apprentissage 10 fois plus grand
Section intitulée « À vous de jouer : un taux d’apprentissage 10 fois plus grand »Faites passer le taux d’apprentissage d’Adam de 0,001 à 0,01. Entraînez de nouveau le réseau pendant 100 pas en lot complet. Avant l’exécution, faites une prédiction : la perte d’entraînement après 100 pas sera-t-elle plus faible ou plus élevée? Et l’exactitude sur les données de validation?
Afficher la réponse
Résultat de Priya : la perte d’entraînement baisse beaucoup plus vite, jusqu’à 0,303 au pas 10 et 0,0008 au pas 100. Elle est presque nulle. Mais le résultat de validation n’est pas meilleur : la perte est de 0,138 et l’exactitude de 96,7 %. Avec 0,001, elles étaient de 0,199 et de 97,8 %. Une perte d’entraînement proche de zéro signifie que le réseau a commencé à mémoriser les billets d’entraînement. La perte d’entraînement seule ne permet pas de savoir comment le réseau se comporte sur de nouveaux billets. La prochaine leçon mesure les deux, après chaque passage.
Erreur courante
Oublier zero_grad()
Symptôme : Tomás supprime optimizer.zero_grad() « parce que le code fonctionne quand même ». C’est vrai : aucune erreur, et la perte s’affiche. Mais le réseau apprend de moins en moins bien. Priya a exécuté sa version avec les petits lots habituels de 64 billets du cours, expliqués dans la prochaine leçon. Après chacun des trois passages sur les données, l’exactitude de validation est de 91,5 %, 89,5 % et 90,0 %. La perte de validation augmente : 0,349, 0,417, 0,489. Avec zero_grad(), le même entraînement donne 97,2 %, 98,7 % et 98,5 %.
Cause : backward() ajoute chaque nouveau gradient aux anciens. Sans zero_grad(), la somme de tous les gradients précédents détermine chaque pas. Ces gradients ne correspondent plus aux poids actuels. Priya a mesuré la taille du gradient de la première couche après chaque passage. Elle passe de 10,7 à 15,7, puis à 25,6. Avec zero_grad(), elle reste petite : 0,66, 0,39, 0,39.
- Avec zero_grad()
- Sans zero_grad()
Description textuelle du graphique
Avec zero_grad(), la taille du gradient est de 0,66 après le premier passage et de 0,39 après les deuxième et troisième. Sans zero_grad(), elle est de 10,7, 15,7 et 25,6. Elle augmente à chaque passage, car les anciens gradients s’accumulent.
Correction : appelez optimizer.zero_grad() au début de chaque pas d’entraînement, avant la propagation avant. Pour vérifier une boucle, affichez la taille du gradient d’une couche, model.layers[0].weight.grad.norm(), pendant quelques pas. Elle ne devrait pas augmenter constamment.
Ce que cela signifie pour un vrai service d’IA : une erreur d’entraînement provoque rarement un arrêt. Elle produit un modèle un peu moins bon. Personne ne le remarque à moins de vérifier le journal d’entraînement et les résultats de validation de chaque exécution.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Énumérez dans l'ordre les cinq étapes d'un pas d'entraînement. Expliquez en quelques mots ce que fait chacune.
Afficher la réponse
1, optimizer.zero_grad() : remettre les gradients à zéro, car backward() s’y ajoute. 2, la propagation avant, model(x) : calculer les scores. 3, la perte, loss_fn(scores, y) : mesurer à quel point les scores sont incorrects. 4, loss.backward() : la rétropropagation, un gradient pour chaque paramètre. 5, optimizer.step() : déplacer chaque paramètre dans le sens opposé à son gradient.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Faites une prédiction : un journal d'entraînement montre la même perte, 1,6236, à chaque pas. Le code s'exécute sans erreur. Quelles étapes pourraient manquer, et comment le vérifier?
Afficher la réponse
Une perte qui ne bouge jamais signifie que les poids ne changent jamais. Il manque donc loss.backward() ou optimizer.step(). L’optimiseur pourrait aussi avoir été créé avec les mauvais paramètres, par exemple ceux d’un autre modèle. Vérifiez que les deux lignes sont dans la boucle. Ensuite, affichez un paramètre et son .grad avant et après un pas. Le gradient ne devrait pas être None (aucune valeur), et le paramètre devrait changer.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Tomás déplace optimizer.zero_grad() juste après loss.backward(), avant optimizer.step(). Le code s'exécute. Que se passe-t-il, et pourquoi?
Afficher la réponse
Le réseau cesse d’apprendre. backward() calcule les gradients, zero_grad() les efface aussitôt, puis step() ne voit aucun gradient (zéro ou None). Les poids ne changent donc pas. La perte reste à sa valeur initiale. zero_grad() doit précéder la propagation avant et la rétropropagation d’un pas, et non se placer entre backward() et step().
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Un pas d’entraînement comporte cinq étapes, dans cet ordre :
zero_grad(), propagation avant, perte,backward(),step(). La boucle d’entraînement les répète. - L’optimiseur conserve les paramètres et les modifie.
torch.optim.SGDapplique la règle de mise à jour de la descente de gradient;torch.optim.Adamadapte le pas de chaque paramètre. - Avec Adam, 100 pas en lot complet ont fait passer la perte sur les billets d’entraînement de 1,624 à 0,146. L’exactitude de validation est passée de 18,2 % à 97,8 %.
- Une étape manquante produit rarement une erreur. Une perte qui ne bouge jamais indique un appel manquant à
backward()ou àstep(). Sanszero_grad(), les gradients s’accumulent (10,7 → 15,7 → 25,6). - L’entraînement en lot complet lit chaque billet à chaque pas, ce qui coûte de la mémoire et du temps.
Ensuite : entraînez le réseau en petits lots sur plusieurs passages. Évaluez-le sur les billets de validation après chaque passage.
Leçon suivante : Lots, époques et validation