Aller au contenu
English

Lots, époques et validation

  • Leçon 11
  • 60 min
  • Module 3 : Comment les réseaux de neurones apprennent

Ce que vous saurez faire

Entraînez le modèle d’acheminement des billets de soutien comme on entraîne les vrais réseaux : beaucoup de petits pas sur des lots mélangés, passage après passage. Après chaque passage, mesurez les résultats sur les billets de validation. Puis lisez les deux courbes pour choisir le meilleur moment pour arrêter.

Carnet JupyterFaites cette leçon dans un carnet JupyterColab · Kaggle · GitHub · en anglais

Le carnet (notebook) Jupyter contient chaque cellule de code, tâche et vérification de cette leçon, ainsi que les exercices du module. Il couvre les lots, la boucle avec validation, les billets triés sans mélange et l’entraînement complet de 20 époques avec ses courbes. Il couvre aussi le dropout (désactivation aléatoire de neurones) laissé actif. Chaque exécution prend quelques secondes sur un processeur. Environ 35 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 mainEnviron 60 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur

Testé avec Python 3.12 et PyTorch 2.14.1 sur un processeur.

Une cuisinière prépare une grande marmite de soupe. Pour vérifier le sel, elle ne mange pas toute la marmite. Elle remue la soupe, prend une cuillerée et la goûte. Une cuillerée d’une soupe bien remuée lui en dit presque autant que toute la marmite. Elle peut goûter, ajouter un peu de sel, remuer et goûter encore, plusieurs fois. Si elle oubliait de remuer, chaque cuillerée viendrait du dessus. Elle salerait alors la soupe selon la mauvaise couche.

Avant de la servir, elle demande à une amie qui ne l’a pas préparée de goûter. L’amie goûte seulement. Elle n’ajoute pas de sel.

Le problème : les lots complets apprennent lentement et prennent beaucoup de mémoire

Section intitulée « Le problème : les lots complets apprennent lentement et prennent beaucoup de mémoire »

La boucle de la dernière leçon utilisait les 4 696 billets d’entraînement à chaque pas : un entraînement par lots complets. Chaque pas lit tous les billets une fois et effectue une mise à jour. Priya a mesuré les résultats sur les billets de validation après un, deux, cinq et vingt passages sur les données :

Passages sur les billets d’entraînement 1 2 5 20
Lot complet (un pas par passage) : exactitude de validation 33,7 % 47,5 % 67,5 % 88,0 %
Lots de 64 (74 pas par passage) : exactitude de validation 97,2 % 98,2 % 98,5 % 97,3 %

Pour une même lecture des données, les petits lots donnent 74 mises à jour plutôt qu’une seule. Le réseau atteint 97,2 % après le premier passage. Les lots complets exigent aussi de garder tous les billets en mémoire en même temps. C’est possible pour 4 696 billets courts, mais impossible pour des millions d’images.

L’idée : des mini-lots mélangés, des époques et un passage de validation

Section intitulée « L’idée : des mini-lots mélangés, des époques et un passage de validation »

Un mini-lot, ou lot, est un petit groupe d’exemples d’entraînement, ici 64 billets. Chaque pas d’entraînement utilise un lot : les cinq étapes sur 64 billets, puis le lot suivant. La perte d’un lot n’est pas exactement celle de tous les billets. Chaque pas est donc un peu irrégulier, comme une cuillerée ne représente pas exactement la marmite. Mais il va en moyenne vers une perte plus faible, et les pas sont beaucoup plus nombreux.

Une époque est un passage sur tous les lots d’entraînement, donc sur chaque billet d’entraînement une fois. Avec des lots de 64, une époque de 4 696 billets compte 74 pas d’entraînement : 73 lots complets et un dernier lot de 24. Le mélange place les billets dans un nouvel ordre aléatoire au début de chaque époque : la cuisinière remue la soupe.

Les 4 696 billets d'entraînement passent par le mélange, avec un nouvel ordre à chaque époque, puis forment des lots de 64 billets : lot 1, lot 2, et ainsi de suite jusqu'au lot 74, qui contient 24 billets. Chaque lot donne 1 mise à jour. Ensuite : 1 époque = 74 mises à jour, puis mesure sur les billets de validation, sans mise à jour.
Une époque : mélangez les 4 696 billets d'entraînement, puis effectuez un pas d'entraînement (une mise à jour) par lot de 64. Après l'époque, mesurez les résultats sur les billets de validation sans aucune mise à jour.
Description textuelle du diagramme

Les 4 696 billets d’entraînement sont placés dans un nouvel ordre aléatoire au début de chaque époque. Ils sont ensuite divisés en lots de 64 billets : lot 1, lot 2, et ainsi de suite jusqu’au lot 74. Ce dernier contient les 24 billets restants. Chaque lot donne une mise à jour des poids. Une époque compte donc 74 mises à jour. Après l’époque, on mesure les résultats du réseau sur les billets de validation, sans effectuer de mise à jour.

Pourquoi le mélange compte-t-il? Priya a trié les billets d’entraînement par équipe et a entraîné le réseau pendant une époque sans les mélanger. Chaque lot contenait donc une seule équipe. Chaque série de lots d’une même équipe orientait le réseau vers cette équipe, puis l’équipe suivante l’en éloignait. Après une époque, l’exactitude de validation était de 40,5 %, contre 97,0 % pour les mêmes billets triés puis mélangés.

Après chaque époque, on mesure les résultats du réseau sur les billets de validation : l’amie goûte, mais n’ajoute pas de sel. Ce passage d’évaluation ne doit pas modifier le réseau. Il doit aussi utiliser le réseau comme il sera utilisé plus tard. Deux appels PyTorch assurent cela :

  • model.eval() fait passer le réseau en mode évaluation. Certaines couches se comportent différemment pendant l’entraînement. Une couche de dropout, par exemple, désactive une partie aléatoire des neurones à chaque pas d’entraînement. La leçon Limiter le surapprentissage du module 4 explique pourquoi cela aide. En mode évaluation, le dropout est désactivé, et le même billet reçoit toujours la même réponse. model.train() rétablit le mode entraînement.
  • with torch.no_grad(): demande à PyTorch de ne rien enregistrer pour les gradients dans le bloc. Cela économise de la mémoire et du temps. Sans cet enregistrement, aucun backward() ni aucune mise à jour accidentelle ne sont possibles.
Deux colonnes. Pas d'entraînement, une mise à jour : model.train(), optimizer.zero_grad(), scores = model(batch), loss = loss_fn(scores, labels), loss.backward() donne les gradients, optimizer.step() modifie les poids. Passage d'évaluation, sans mise à jour : model.eval() désactive le dropout, with torch.no_grad() ne conserve aucun gradient, scores = model(valid), loss = loss_fn(scores, labels), la perte et l'exactitude sont des nombres à lire, sans rétropropagation ni pas d'optimisation, donc les poids restent identiques.
Un pas d'entraînement modifie les poids; un passage d'évaluation mesure seulement les résultats. La différence tient au mode, à l'enregistrement et aux deux dernières étapes.
Description textuelle du diagramme

À gauche, un pas d’entraînement, qui effectue une mise à jour : model.train(), optimizer.zero_grad(), scores = model(batch), loss = loss_fn(scores, labels), puis loss.backward(), qui calcule les gradients, et optimizer.step(), qui modifie les poids. À droite, un passage d’évaluation, sans mise à jour : model.eval() désactive le dropout, with torch.no_grad(): ne conserve aucun gradient, scores = model(valid), loss = loss_fn(scores, labels), et la perte et l’exactitude sont des nombres à lire. Il n’y a ni propagation arrière ni pas d’optimisation, donc les poids restent identiques.

Priya ajoute les deux changements à la boucle d’entraînement : une boucle interne sur les lots mélangés et un passage d’évaluation après chaque époque.

train_batches.py
set_seed(0)
model = BagOfWordsNet(len(vocab))
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
batch_size = 64
for epoch in range(1, 4):
model.train() # training mode
order = torch.randperm(len(x_train)) # shuffle: a new order every epoch
for start in range(0, len(order), batch_size):
batch = order[start:start + batch_size] # the row numbers of one batch
optimizer.zero_grad()
loss = loss_fn(model(x_train[batch]), y_train[batch])
loss.backward()
optimizer.step()
model.eval() # evaluation mode: dropout off
with torch.no_grad(): # no record, no gradients
scores = model(x_valid)
valid_loss = loss_fn(scores, y_valid).item()
valid_acc = (scores.argmax(dim=1) == y_valid).float().mean().item()
print(f"epoch {epoch} validation loss {valid_loss:.4f} accuracy {valid_acc:.4f}")

La sortie :

epoch 1 validation loss 0.5486 accuracy 0.9717
epoch 2 validation loss 0.2065 accuracy 0.9867
epoch 3 validation loss 0.1374 accuracy 0.9850

torch.randperm(n) donne les nombres de 0 à n − 1 dans un ordre aléatoire. x_train[batch] sélectionne ces lignes : forme (64, 2864) pour un lot complet. argmax(dim=1) choisit l’équipe qui a le score le plus élevé pour chaque billet. Après une époque de 74 petits pas, le réseau atteint 97,2 %. La boucle par lots complets avait besoin de 100 pas pour atteindre 97,8 %.

Le fichier ticketnet.py du cours contient la même boucle dans train() : les cinq étapes par lot, avec les mêmes commentaires. Après chaque époque, il exécute evaluate() sur les billets d’entraînement et de validation. Il renvoie une ligne par époque. Priya entraîne le réseau pendant 20 époques :

train_batches.py
set_seed(0)
model = BagOfWordsNet(len(vocab))
history = train(model, x_train, y_train, x_valid, y_valid, epochs=20, log=None)
best = min(history, key=lambda row: row["valid_loss"])
print(history[0])
print(best)
print(history[-1])

La sortie :

{'epoch': 1, 'train_loss': 0.5345, 'train_acc': 0.9593, 'valid_loss': 0.5527, 'valid_acc': 0.9717}
{'epoch': 12, 'train_loss': 0.0179, 'train_acc': 0.9977, 'valid_loss': 0.0633, 'valid_acc': 0.9833}
{'epoch': 20, 'train_loss': 0.0059, 'train_acc': 0.9996, 'valid_loss': 0.0723, 'valid_acc': 0.9733}

Les nombres de la première époque diffèrent un peu de ceux de la boucle ci-dessus. En effet, train() fixe de nouveau la graine aléatoire avant le mélange, ce qui donne des lots différents. Le carnet affiche les 20 lignes.

  • Perte d'entraînement
  • Perte de validation
Perte d'entraînement et de validation après chaque époque de l'exécution du cours : réseau à sac de mots, Adam, taux d'apprentissage de 0,001, lots de 64, graine aléatoire 0. La perte de validation atteint son minimum à l'époque 12.
Description textuelle du graphique

Les deux pertes commencent près de 0,55 après l’époque 1 et baissent rapidement : environ 0,21 après l’époque 2 et 0,10 après l’époque 4. La perte d’entraînement continue de baisser à chaque époque, jusqu’à 0,018 à l’époque 12 et 0,006 à l’époque 20. La perte de validation baisse plus lentement après l’époque 6. Elle atteint son minimum, 0,063, à l’époque 12, puis remonte un peu, jusqu’à 0,072 à l’époque 20.

Les deux pertes baissent ensemble au début. Ce que le réseau apprend des billets d’entraînement fonctionne aussi sur les billets de validation. Après environ six époques, elles se séparent. La perte d’entraînement continue de baisser vers zéro. La perte de validation cesse de baisser, atteint son minimum à l’époque 12 (0,063), puis remonte.

  • Exactitude d'entraînement
  • Exactitude de validation
Exactitude d'entraînement et de validation de la même exécution. L'exactitude d'entraînement monte à 99,96 %; l'exactitude de validation atteint tôt son maximum, puis baisse progressivement.
Description textuelle du graphique

L’exactitude d’entraînement passe de 95,9 % après l’époque 1 à 99,8 % à l’époque 12. Elle atteint 99,96 % à partir de l’époque 16 : presque tous les billets d’entraînement. L’exactitude de validation est de 97,2 % après l’époque 1. Elle atteint son maximum à l’époque 4 (99,2 %), puis 98,3 % à l’époque 12 et 97,3 % à l’époque 20.

À l’époque 20, le réseau classe correctement 99,96 % des billets d’entraînement. Mais 70 billets d’entraînement ont volontairement une mauvaise équipe, soit environ 1,5 %. Un réseau qui comprendrait la tâche ne pourrait donc pas atteindre ce résultat. Il apprend par cœur les 70 mauvaises étiquettes. C’est le surapprentissage, le problème vu dans le cours d’apprentissage automatique. Le réseau apprend des détails de ses données d’entraînement qui ne s’appliquent pas aux nouveaux billets. La perte de validation le révèle en premier; l’exactitude de validation varie seulement un peu.

Deux habitudes en découlent :

  • Gardez la meilleure époque, choisie selon la perte de validation : ici l’époque 12, avec une perte de validation de 0,063 et une exactitude de 98,3 %. Arrêter à la meilleure époque s’appelle l’arrêt anticipé. La leçon Limiter le surapprentissage en fait une méthode, avec le dropout et la décroissance des poids.
  • Ne choisissez jamais selon les données de test. Les billets de validation servent à choisir l’époque, donc leur résultat est maintenant un peu optimiste. Les 600 billets de test restent réservés jusqu’à la fin du cours.

Entraînez toujours avec des mini-lots mélangés. Les tailles de 16 à 512 sont courantes; de 32 à 128 est un bon point de départ sur un processeur. Mesurez les résultats sur les données de validation après chaque époque, en mode évaluation.

Leur coût : chaque pas ne voit qu’une partie des données, donc la perte d’entraînement de chaque lot monte et descend. Le cours Mathématiques et statistiques essentielles pour l’IA explique pourquoi; c’est la tendance qui compte. La taille du lot est un réglage de plus à choisir. Les petits lots donnent plus de pas, mais plus irréguliers; les grands lots exigent plus de mémoire. Chaque passage d’évaluation prend aussi du temps. Ce temps est court ici (600 billets) et en vaut la peine.

  1. Calculez combien de lots de 64 forment une époque de 4 696 billets : divisez et arrondissez vers le haut. Vous devriez obtenir 74.
  2. Calculez la taille du dernier lot : 4 696 − 73 × 64. Vous devriez obtenir 24.
  3. Dans le carnet, exécutez la cellule de la section 1. Vous devriez voir batches per epoch: 74 (lots par époque : 74) et les formes (64, 2864) et (24, 2864).
  4. Exécutez la boucle de la section 2. Vous devriez voir 97,17 % après l’époque 1.
  5. Comptez les mises à jour : combien de pas d’entraînement les trois époques ont-elles effectués?
Afficher la réponse

4 696 ÷ 64 = 73,4, donc 74 lots : 73 lots complets (4 672 billets) et un dernier lot de 24. Trois époques donnent 3 × 74 = 222 pas d’entraînement et trois passages d’évaluation, sans mise à jour. La boucle par lots complets de la dernière leçon effectuait 100 pas, mais lisait les données d’entraînement 100 fois. Cette boucle les a lues 3 fois.

Utilisez les 20 lignes de l’exécution du cours (le carnet les affiche). Omar demande : « Pourquoi ne pas simplement arrêter à l’époque 4? Elle a la meilleure exactitude de validation, 99,2 %. » Avant de répondre, regardez la perte de validation à l’époque 4 et à l’époque 12.

Afficher la réponse

À l’époque 4, la perte de validation est de 0,105; à l’époque 12, elle est de 0,063. L’époque 4 classe correctement 595 des 600 billets de validation, et l’époque 12 en classe correctement 590. C’est une différence de cinq billets, assez petite pour être due au hasard sur 600 billets. La perte tient compte de chaque billet et du degré de certitude du réseau. C’est donc un guide plus stable. Voilà pourquoi le cours choisit l’époque selon la perte de validation. Les deux choix se défendent si vous expliquez votre méthode et choisissez avant de regarder les données de test.

Erreur courante

Mesurer avec le dropout actif

Symptôme : Tomás entraîne un réseau avec une couche de dropout (dropout de 0,5). Il lui soumet ensuite trois fois le même billet de validation, un billet de paiement. Il oublie model.eval(). Les trois probabilités de paiement diffèrent : 0,996, 1,000 et 0,983. Il exécute deux fois le réseau sur toutes les données de validation et obtient deux exactitudes différentes. Des réponses qui changent entre les exécutions sont le symptôme.

Cause : le réseau est encore en mode entraînement. Le dropout désactive donc une moitié aléatoire différente des neurones cachés à chaque passage. Chaque passage utilise un réseau légèrement différent. En mode évaluation, le même billet obtient 0,999 chaque fois.

Correction : appelez model.eval() avant de mesurer les résultats ou d’utiliser un réseau, et model.train() avant la prochaine époque d’entraînement. Utilisez aussi with torch.no_grad(): pour la mesure. La fonction evaluate() du cours fait les deux.

Ce que cela signifie pour un vrai service d’IA : un comportement réservé à l’entraînement laissé actif donne aux clients des réponses qui changent d’une requête à l’autre. Personne ne peut non plus reproduire son exactitude mesurée. Vérifiez le mode partout où un modèle est chargé pour faire des prédictions.

Vérifiez votre compréhension

Que sont un lot et une époque? Avec des lots de 32, combien de pas d'entraînement forment une époque de 4 696 billets?

Afficher la réponse

Un lot est un petit groupe d’exemples d’entraînement qu’utilise un pas d’entraînement. Une époque est un passage sur tous les exemples d’entraînement, lot par lot. Avec des lots de 32 : 4 696 ÷ 32 = 146,75, donc 147 pas par époque. Cela donne 146 lots complets et un dernier lot de 24 billets.

Vérifiez votre compréhension

Prédisez : Priya entraîne le réseau pendant 40 époques plutôt que 20, sans rien changer d'autre. Que feront les pertes d'entraînement et de validation après l'époque 20, et quelle époque devrait-elle garder?

Afficher la réponse

La perte d’entraînement continuera de baisser vers zéro. Le réseau continue en effet d’apprendre par cœur les billets d’entraînement, y compris les mauvaises étiquettes. La perte de validation continuera très probablement de monter lentement, comme elle l’a fait de l’époque 12 à l’époque 20. Priya devrait toujours garder l’époque qui a la plus faible perte de validation. Ce sera l’époque 12 ou une époque proche, pas la dernière. Ajouter des époques n’aide plus une fois que la perte de validation augmente.

Exercice guidé → tâche autonome. Vous avez vu Priya entraîner le réseau de 64 neurones pour les billets, en consignant les pertes et les mesures. Maintenant, entraînez un réseau plus petit et consignez les résultats de la même façon.

  1. Dans la section 6 du carnet Jupyter, créez BagOfWordsNet(len(vocab), hidden=32) après set_seed(0), puis affichez son nombre de paramètres.
  2. Entraînez-le pendant 10 époques avec train(), puis conservez les 10 lignes retournées dans practice.
  3. Affichez les lignes sous forme de journal : époque, perte d’entraînement, perte de validation, exactitude de validation.
  4. Trouvez l’époque ayant la plus faible perte de validation, placez sa ligne dans practice_best, puis exécutez la cellule de vérification.
  5. Écrivez trois phrases : la meilleure époque et ses chiffres; la comparaison avec le réseau de 64 neurones à cette époque; et l’utilité possible d’époques supplémentaires.
Afficher une réponse modèle

Le réseau compte 91 845 paramètres, soit la moitié des 183 685 paramètres du réseau de 64 neurones. Son journal :

Époque 1 2 4 6 8 10
Perte d’entraînement 0.813 0.316 0.146 0.092 0.064 0.045
Perte de validation 0.837 0.327 0.144 0.105 0.091 0.077
Exactitude de validation 95.5 % 98.0 % 98.5 % 98.5 % 98.3 % 98.7 %

« La meilleure époque est la dernière, l’époque 10 : perte de validation de 0,077, exactitude de 98,7 %. À l’époque 10, le réseau de 64 neurones avait une perte de validation de 0,066 et une exactitude de 98,5 %. Le réseau plus petit apprend donc un peu plus lentement, mais atteint à peu près la même exactitude. Sa perte de validation diminuait encore à l’époque 10, donc quelques époques supplémentaires pourraient aider. Je l’entraînerais jusqu’à l’époque 20 et garderais l’époque ayant la plus faible perte de validation. »

Vérifiez votre compréhension

Rappel : nommez dans l'ordre les cinq étapes d'une étape d'entraînement, puis les deux appels qui sécurisent la mesure sur les billets de validation.

Afficher la réponse

Les cinq étapes : optimizer.zero_grad(), la propagation avant model(x), la perte loss_fn(scores, y), loss.backward() et optimizer.step(). Pour une mesure : model.eval(), qui désactive les comportements réservés à l’entraînement, comme le dropout (désactivation aléatoire de neurones), et with torch.no_grad():, qui ne garde aucune trace pour calculer les gradients.

Vérifiez votre compréhension

Expliquez ou prédisez : le réseau non entraîné pour les billets a une perte de validation de 1,624. Pourquoi ce nombre est-il proche de ln 5? Que signifierait une perte de 0,063 pour un billet de validation typique?

Afficher la réponse

Le réseau non entraîné attribue environ 0,2 à chacune des cinq équipes. La perte d’un billet est −ln p pour la bonne équipe, soit environ −ln 0,2 = ln 5 = 1,609. Une perte moyenne de 0,063 correspond à une probabilité d’environ e à la puissance −0,063 = 0,94 pour la bonne équipe sur un billet typique. Le réseau est sûr de ses réponses et a généralement raison. (La moyenne masque la dispersion : la plupart des billets ont une perte proche de 0, et quelques erreurs ont de grandes pertes.)

Vérifiez votre compréhension

Vérification de fin de module : voici deux extraits de code qui exécutent tous deux le réseau sur des billets. Pour chacun, dites s'il met à jour les paramètres ou effectue une propagation d'évaluation seulement, et quelles lignes le déterminent. (A) model.train(); optimizer.zero_grad(); loss = loss_fn(model(x_batch), y_batch); loss.backward(); optimizer.step(). (B) model.eval(); with torch.no_grad() : loss = loss_fn(model(x_valid), y_valid).

Afficher la réponse

(A) met à jour les paramètres : c’est une étape d’entraînement. loss.backward() calcule un gradient pour chaque paramètre et optimizer.step() modifie les poids. model.train() garde actifs les comportements réservés à l’entraînement, comme le dropout, et zero_grad() efface les anciens gradients. (B) effectue une propagation d’évaluation seulement. Il calcule une perte à consulter, mais sans backward() ni step(), donc aucun poids ne change. torch.no_grad() ne garde aucune trace, donc une propagation arrière n’est même pas possible. model.eval() désactive le dropout : les réponses sont donc celles que le modèle donnera en utilisation réelle. Une propagation avant seule ne modifie jamais un réseau : seul step() le fait.

Récapitulatif

Cette leçon : entraînez le réseau avec des mini-lots mélangés (74 étapes de 64 billets par époque). Mesurez les résultats après chaque époque en mode évaluation (model.eval() et torch.no_grad()), puis gardez l’époque ayant la plus faible perte de validation. L’exécution du cours : 97,2 % après une époque, meilleur résultat à l’époque 12 (perte de validation de 0,063, exactitude de 98,3 %). Puis vient le surapprentissage : exactitude d’entraînement de 99,96 %, avec les 70 mauvaises étiquettes apprises par cœur.

Ce module : un réseau apprend en répétant un cycle. La perte (entropie croisée, −ln p pour la bonne équipe) indique à quel point il se trompe. La rétropropagation fournit un gradient pour chaque poids en une seule propagation arrière. La descente de gradient déplace chaque poids dans le sens opposé à son gradient, selon le taux d’apprentissage. La boucle d’entraînement répète les cinq étapes, lot après lot. La propagation de validation vérifie le résultat sans le modifier. Vous avez entraîné le routeur de billets, faisant passer son exactitude de 18,2 % à 98,3 %.

Procédure de réinitialisation : si un carnet Jupyter donne d’autres chiffres, redémarrez le noyau et exécutez toutes les cellules depuis le début. Chaque exécution commence par set_seed(0), donc les chiffres se répètent. Si la cellule de configuration signale une mauvaise somme de contrôle, supprimez le dossier ticket-router et réexécutez cette cellule. Sur votre ordinateur, vérifiez que l’environnement virtuel de la leçon Installer PyTorch est actif. Vérifiez aussi que python -c "import torch; print(torch.__version__)" affiche une version 2.x.

À venir : le module 4 sert à diagnostiquer les problèmes d’entraînement, en commençant par le taux d’apprentissage.

Leçon suivante : Taux d'apprentissage et optimiseurs