La propagation avant
- Leçon 6
- 60 min
- Module 2 : Les tenseurs et PyTorch
Leçon 6 sur 23 · L'apprentissage profond et les transformeurs expliquésLa propagation avant
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
Transformez les billets de soutien de Larkfield en tenseurs qu’un réseau peut lire. Passez-en un lot dans un premier réseau non entraîné, puis vérifiez la forme après chaque couche. Vous pourrez ensuite lire les formes de n’importe quel réseau et corriger une erreur de forme à partir de son message.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter contient toutes les cellules de code de cette leçon, les tâches avec vérifications et les exercices du module. Ses cellules de préparation téléchargent les billets et ticketnet.py. Aucune installation locale n’est donc nécessaire. Environ 40 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
- Les formes, la dimension du lot, la diffusion et
float32, présentés dans Tenseurs et formes. - Une couche comme ensemble de neurones qui lisent les mêmes entrées, dans Neurones et couches, et ReLU, dans Pourquoi les couches ont besoin d’activations.
- La graine, présentée dans Installer PyTorch.
Ce qu’il vous faut sur votre ordinateur (uniquement pour l’option locale; le carnet n’en a pas besoin)
- Le dossier
ticket-routeravec son environnement.venvactif,ticketnet.pyetdata/, provenant de Installer PyTorch : option 2. Sipython first_run.pyn’affiche passum 2.4265(somme 2.4265), ou sidata/manque, suivez Réinitialiser la configuration.
Testé avec Python 3.12 et PyTorch 2.14.1 sur le processeur (macOS).
Un formulaire avec une case pour chaque mot
Section intitulée « Un formulaire avec une case pour chaque mot »Une équipe de sondage donne le même formulaire à chaque visiteur : une liste fixe d’éléments, chacun avec une case. Une réponse courte et une réponse longue remplissent le même formulaire. Chaque formulaire a donc les mêmes cases aux mêmes endroits. La personne qui additionne les résultats n’a jamais à deviner le sens d’une case.
Le problème : les billets ont des longueurs différentes
Section intitulée « Le problème : les billets ont des longueurs différentes »Une couche comme nn.Linear(2864, 64) lit exactement 2 864 nombres par billet. Mais les billets ont des longueurs différentes. Le billet médian de Larkfield contient 55 jetons, et le plus long en contient 232. Voici trois petits billets inventés, découpés en jetons (tokens) : mots en minuscules, nombres et signes de ponctuation individuels. re.findall() trouve chaque morceau de texte qui correspond au motif.
import reimport torch
def tokenize(text): """Lower-case words, numbers and single punctuation marks.""" return re.findall(r"[a-z]+|[0-9]+|[^\sa-z0-9]", text.lower())
tiny = ["Parcel not arrived.", "Card charged twice.", "Parcel arrived broken, broken lamp."]for text in tiny: print(len(tokenize(text)), tokenize(text))La sortie :
4 ['parcel', 'not', 'arrived', '.']4 ['card', 'charged', 'twice', '.']7 ['parcel', 'arrived', 'broken', ',', 'broken', 'lamp', '.']Transformez chaque jeton en sa position dans une liste triée, puis essayez de créer un seul tenseur :
tiny_vocab = sorted({token for text in tiny for token in tokenize(text)})ids = [[tiny_vocab.index(token) for token in tokenize(text)] for text in tiny]print(ids)print(torch.tensor(ids))La sortie :
[[8, 7, 2, 1], [4, 5, 9, 1], [8, 2, 3, 0, 3, 6, 1]]ValueError: expected sequence of length 4 at dim 1 (got 7)Un tenseur est une boîte : chaque ligne doit avoir la même longueur. Des lignes de 4, 4 et 7 nombres ne forment pas une boîte, donc PyTorch refuse. Le réseau exige un nombre fixe d’entrées pour chaque billet, quelle que soit sa longueur.
L’idée : un vocabulaire et un sac de mots
Section intitulée « L’idée : un vocabulaire et un sac de mots »Utilisez le formulaire du sondage. Fixez d’abord une liste de jetons, le vocabulaire. Donnez ensuite le même formulaire à chaque billet : une case par entrée du vocabulaire, contenant le nombre d’occurrences de ce jeton. Chaque billet reçoit un vecteur de même longueur, soit la taille du vocabulaire. C’est un sac de mots. Le nom décrit ce qui se passe : vous versez les mots d’un billet dans un sac et les comptez. Leur ordre est donc perdu.
Description textuelle du diagramme
Le diagramme présente trois étapes de haut en bas. L’étape 1 est le billet : « Parcel arrived broken, broken lamp. » (Colis arrivé endommagé, lampe brisée.) L’étape 2 montre ses 7 jetons dans l’ordre du texte : parcel, arrived, broken, une virgule, broken, lamp et un point. L’étape 3 montre les comptes, avec une case pour chacune des 10 entrées du vocabulaire, en ordre trié. Les comptes sont : virgule 1, point 1, arrived 1, broken 2, card 0, charged 0, lamp 1, not 0, parcel 1 et twice 0. Les cases dont le compte dépasse 0 sont ombrées. En dessous : forme (10,), la même longueur pour chaque billet, court ou long.
Dans le code, counts() remplit un formulaire pour un texte :
print(tiny_vocab)
def counts(text, vocab): """One number per vocabulary entry: how often it appears in the text.""" x = torch.zeros(len(vocab)) for token in tokenize(text): x[vocab.index(token)] += 1 return x
print(torch.stack([counts(text, tiny_vocab) for text in tiny]))La sortie :
[',', '.', 'arrived', 'broken', 'card', 'charged', 'lamp', 'not', 'parcel', 'twice']tensor([[0., 1., 1., 0., 0., 0., 0., 1., 1., 0.], [0., 1., 0., 0., 1., 1., 0., 0., 0., 1.], [1., 1., 1., 2., 0., 0., 1., 0., 1., 0.]])torch.stack() place les trois vecteurs l’un sous l’autre. Les trois billets deviennent donc un lot de forme (3, 10). La troisième ligne contient un 2 pour « broken » (brisé). torch.zeros() crée les comptes en float32 dès le départ, pour qu’ils soient compatibles avec les poids du réseau.
Les billets de Larkfield
Section intitulée « Les billets de Larkfield »Passons maintenant aux vrais billets. Un modèle de langage les a inventés pour ce cours à partir d’une liste de situations. Aucun vrai client ne les a écrits. La fonction load_split() du fichier ticketnet.py du cours lit un fichier CSV et produit une liste de lignes. Chaque billet devient un dictionnaire.
from collections import Counterfrom ticketnet import load_split
train_rows = load_split("data/train.csv")valid_rows = load_split("data/valid.csv")print(len(train_rows), "training tickets,", len(valid_rows), "validation tickets")La sortie :
4696 training tickets, 600 validation ticketsLe vocabulaire provient uniquement des billets d’entraînement, comme toute étape qui apprend à partir des données. Un jeton doit apparaître au moins deux fois pour obtenir une place. Un jeton vu une seule fois apporte peu au réseau et allonge chaque vecteur. Deux entrées spéciales viennent en premier. <unk> (inconnu), à la position 1, compte chaque jeton absent du vocabulaire, comme un numéro de commande. <pad> (remplissage), à la position 0, servira au module 5, où les billets deviennent des listes d’identifiants de jetons.
word_counts = Counter(token for row in train_rows for token in tokenize(row["text"]))itos = ["<pad>", "<unk>"] + sorted(t for t, c in word_counts.items() if c >= 2) # position -> tokenstoi = {token: i for i, token in enumerate(itos)} # token -> positionprint(len(word_counts), "different tokens in train,", len(itos), "in the vocabulary")print(itos[:4], itos[1000:1004])La sortie :
4964 different tokens in train, 2864 in the vocabulary['<pad>', '<unk>', '!', '$'] ['explain', 'explained', 'explaining', 'explains']itos (« integer to string », entier vers chaîne de caractères) transforme une position en jeton. Le dictionnaire stoi transforme un jeton en sa position. Maintenant, bag() remplit le formulaire d’un billet. Cette fonction garde les 120 premiers jetons, comme ticketnet.py; environ 95 % des billets d’entraînement sont plus courts.
def bag(text): """The counts of one ticket: shape (vocabulary size,).""" x = torch.zeros(len(itos)) for token in tokenize(text)[:120]: # the first 120 tokens, as in ticketnet.py x[stoi.get(token, 1)] += 1 # 1 is <unk>: a token not in the vocabulary return x
ticket = valid_rows[0]x = bag(ticket["text"])print(ticket["ticket_id"], ticket["team"], "|", len(tokenize(ticket["text"])), "tokens")print("shape", tuple(x.shape), "| non-zero entries", int((x > 0).sum()), "| sum", int(x.sum()))for i in x.argsort(descending=True)[:5].tolist(): print(f" {itos[i]!r:8} position {i:4} count {int(x[i])}")La sortie :
T-64697 payment | 92 tokensshape (2864,) | non-zero entries 70 | sum 92 'the' position 2540 count 7 '.' position 10 count 4 'i' position 1307 count 4 ',' position 8 count 4 'and' position 336 count 3Le billet T-64697 concerne un montant facturé sur une carte qui dépasse le prix affiché. Ses 92 jetons remplissent 70 des 2 864 cases. Ainsi, 2 794 cases, soit 97,6 %, contiennent 0. La somme est 92 : chaque jeton est compté une fois. Huit billets empilés forment le lot :
x8 = torch.stack([bag(row["text"]) for row in valid_rows[:8]])print(x8.shape)La sortie :
torch.Size([8, 2864])Le réseau
Section intitulée « Le réseau »Le réseau comporte trois étapes, les couches du module 1 :
nn.Linear(2864, 64)est une couche de 64 neurones. Chaque neurone lit les 2 864 comptes et calcule une somme pondérée plus un biais. La couche transforme donc 2 864 nombres en 64.nn.ReLU()remplace chaque nombre négatif par 0, comme dans Pourquoi les couches ont besoin d’activations.nn.Linear(64, 5)transforme les 64 nombres en 5 scores, un par équipe. Un score plus élevé signifie « cette équipe est plus probable ».
nn.Sequential enchaîne les couches : la sortie d’une couche devient l’entrée de la suivante. La graine rend les poids initiaux aléatoires identiques à ceux de cette page.
from torch import nn
TEAMS = ["delivery", "returns", "payment", "warranty", "account"]torch.manual_seed(0)model = nn.Sequential( nn.Linear(len(itos), 64), # 2,864 counts in, 64 numbers out nn.ReLU(), nn.Linear(64, len(TEAMS)), # 64 numbers in, 5 scores out)print(model)La sortie :
Sequential( (0): Linear(in_features=2864, out_features=64, bias=True) (1): ReLU() (2): Linear(in_features=64, out_features=5, bias=True))Les poids et les biais sont les paramètres du réseau : les nombres que l’entraînement modifiera. Comptez-les :
for name, p in model.named_parameters(): print(f"{name:<9} shape {str(tuple(p.shape)):<11} {p.numel():>7,} numbers")print("in all", f"{sum(p.numel() for p in model.parameters()):,}")La sortie :
0.weight shape (64, 2864) 183,296 numbers0.bias shape (64,) 64 numbers2.weight shape (5, 64) 320 numbers2.bias shape (5,) 5 numbersin all 183,685Vérifiez à la main : 2 864 × 64 + 64 = 183 360, et 64 × 5 + 5 = 325, soit 183 685 au total. Notez l’ordre de la forme des poids : PyTorch les stocke sous la forme (sortie, entrée), (64, 2864). Une couche calcule x @ weight.T + bias. Ainsi, (8, 2864) @ (2864, 64) donne (8, 64), et le biais de forme (64,) est diffusé à chaque billet.
Une autre étape vient d’abord. Un billet long répète les jetons courants : « the » (le, la ou les) apparaît 7 fois dans T-64697. Les grands comptes pousseraient les sommes pondérées trop loin. torch.log1p() calcule log(1 + x), ce qui atténue les grands comptes et laisse 0 à 0 :
print(torch.log1p(torch.tensor([0.0, 1.0, 2.0, 5.0, 20.0])))La sortie :
tensor([0.0000, 0.6931, 1.0986, 1.7918, 3.0445])La propagation avant, forme par forme
Section intitulée « La propagation avant, forme par forme »Faire passer les entrées dans les couches, de la première à la dernière, constitue la propagation avant. Le nom indique la direction : les nombres avancent, des comptes vers les scores. Le module 3 ajoute la propagation arrière, nécessaire à l’entraînement. Lisez d’abord les formes, puis exécutez chaque étape et affichez sa forme :
first, relu, last = modelh = torch.log1p(x8); print("input ", tuple(h.shape))h = first(h); print("Linear ", tuple(h.shape))h = relu(h); print("ReLU ", tuple(h.shape))scores = last(h); print("Linear ", tuple(scores.shape))La sortie :
input (8, 2864)Linear (8, 64)ReLU (8, 64)Linear (8, 5)Description textuelle du diagramme
Le diagramme est une chaîne de boîtes. Il commence par 8 billets sous forme de texte, une liste de 8. La boîte suivante, le sac de mots, donne 2 864 comptes par billet : la flèche indique (8, 2864). La boîte log1p atténue les grands comptes; sa sortie reste (8, 2864). La première couche Linear, de 2 864 à 64, a des poids de forme (64, 2864) et un biais de forme (64,) : 183 360 paramètres. Sa sortie est (8, 64). ReLU remplace les nombres négatifs par 0; sa sortie reste (8, 64). La deuxième couche Linear, de 64 à 5, a des poids de forme (5, 64) et un biais de forme (5,) : 325 paramètres. Sa sortie est (8, 5), soit 5 scores pour chacun des 8 billets. Une note indique : 183 685 paramètres au total, tous aléatoires avant l’entraînement. Une légende explique les nombres : 8 billets dans le lot, 2 864 entrées de vocabulaire, 64 nombres cachés, 5 équipes.
Chaque nombre de la forme de sortie (8, 5) a un sens. Le 8 représente le lot, avec une ligne par billet. Le 5 représente les équipes, avec un score pour chacune. La dimension du lot ne change jamais. Chaque couche modifie seulement la dernière dimension, de sa taille d’entrée à sa taille de sortie.
Ce que dit le réseau non entraîné
Section intitulée « Ce que dit le réseau non entraîné »Les poids sont encore aléatoires, donc les scores n’ont aucun sens pour le moment. Voici les 5 scores de T-64697 et l’équipe au score le plus élevé pour chacun des 8 billets. argmax(dim=1) trouve la position du score le plus élevé dans chaque ligne.
print(scores[0])pred = scores.argmax(dim=1)print([TEAMS[i] for i in pred.tolist()])print([row["team"] for row in valid_rows[:8]])La sortie :
tensor([-0.1288, 0.0228, 0.1072, 0.0242, 0.0647], grad_fn=<SelectBackward0>)['payment', 'account', 'account', 'account', 'account', 'account', 'account', 'account']['payment', 'delivery', 'delivery', 'delivery', 'payment', 'warranty', 'delivery', 'warranty']grad_fn (fonction de gradient) est une note que PyTorch conserve pour l’entraînement; le module 3 l’utilise. Les 5 scores sont proches, de −0,129 à 0,107. L’équipe de paiement a le score le plus élevé, 0,107, et T-64697 concerne bien un paiement. C’est de la chance. Le réseau répond « account » (compte) pour les 7 billets suivants, et aucun ne concerne un compte.
Pour les 600 billets de validation, torch.no_grad() demande à PyTorch de ne pas conserver ces notes, ce qui économise la mémoire :
x_valid = torch.stack([bag(row["text"]) for row in valid_rows])y_valid = torch.tensor([TEAMS.index(row["team"]) for row in valid_rows])with torch.no_grad(): pred = model(torch.log1p(x_valid)).argmax(dim=1)print(tuple(x_valid.shape), "->", tuple(pred.shape))print("accuracy", round((pred == y_valid).float().mean().item(), 3))print("predicted:", Counter(TEAMS[i] for i in pred.tolist()).most_common())La sortie :
(600, 2864) -> (600,)accuracy 0.182predicted: [('account', 438), ('payment', 156), ('returns', 4), ('warranty', 2)]Description textuelle du graphique
Un diagramme à colonnes avec une colonne par équipe, sur un axe de 0 à 500 billets. Le réseau non entraîné envoie 0 billet à la livraison, 4 aux retours, 156 au paiement, 2 à la garantie et 438 aux comptes. Les données de validation réelles contiennent 165 billets de livraison, 120 de retours, 126 de paiement, 101 de garantie et 88 de comptes.
Le réseau non entraîné a raison pour 18,2 % des billets. Il classe correctement 70 des 88 billets de comptes et 39 des 126 billets de paiement, car il y envoie la plupart des billets. C’est près de 20 %, la probabilité de deviner au hasard parmi 5 équipes. C’est moins que les 27,5 % obtenus en répondant toujours « delivery » (livraison), l’équipe la plus fréquente. Les poids aléatoires ne devinent pas uniformément : ils favorisent par hasard une ou deux équipes. Au module 3, l’entraînement modifie les 183 685 paramètres jusqu’à ce que les scores aient un sens. Le module 3 transforme aussi les scores en probabilités.
Le même code dans ticketnet.py
Section intitulée « Le même code dans ticketnet.py »Vous avez écrit chaque partie vous-même. Le fichier ticketnet.py du cours contient le même code, pour que toutes les leçons suivantes utilisent les mêmes nombres. Vocab construit le vocabulaire, bag_of_words() crée le lot, et BagOfWordsNet est le réseau, avec log1p à l’intérieur. Il contient une couche supplémentaire, le dropout (désactivation aléatoire de neurones), que le module 4 explique; ici, elle ne fait rien. Vérifiez que tout est identique :
import ticketnet
vocab = ticketnet.Vocab([row["text"] for row in train_rows])print(len(vocab), vocab.itos == itos)ticketnet.set_seed(0)net = ticketnet.BagOfWordsNet(len(vocab))x_same = ticketnet.bag_of_words(valid_rows[:8], vocab)print(torch.equal(x_same, x8), torch.allclose(net(x_same), scores))La sortie :
2864 TrueTrue TrueLe même vocabulaire, les mêmes comptes et les mêmes scores.
Les erreurs de forme dans le réseau
Section intitulée « Les erreurs de forme dans le réseau »Prédisez d’abord le résultat, puis essayez chaque erreur dans l’explorateur. Tous les messages sont réels.
Explorer
Les formes dans le réseau de billets
Prédisez d’abord : model attend 2 864 comptes par billet. Que se passe-t-il si vous lui donnez x8.T, de forme (2864, 8)? Et un seul billet de forme (2864,), sans dimension de lot?
- Forme de x8
- (8, 2864)
- Forme de model[0].weight
- (64, 2864)
- Résultat
- (8, 64)
Résultat : forme (8, 64). nn.Linear(2864, 64) lit 2 864 nombres par billet et en donne 64. Elle garde la dimension du lot : 8 billets en entrée, 8 lignes en sortie. Elle stocke ses poids sous la forme (out, in) = (64, 2864) et calcule x8 @ weight.T + bias.
x8
(8, 2864) float32
model[0].weight
(64, 2864) float32
result
(8, 64) float32
PyTorch affiche
>>> (model[0](torch.log1p(x8))).shape
torch.Size([8, 64])Description textuelle de l’explorateur
L’explorateur comporte une rangée de boutons. Première couche : x8 de forme (8, 2864), passé dans nn.Linear(2864, 64), dont les poids ont la forme (64, 2864), donne (8, 64). Réseau entier : (8, 2864) donne (8, 5). Un billet, sans lot : la forme (2864,) donne (5,), ce qui fonctionne, mais perd la dimension du lot. Lot transposé : la forme (2864, 8) provoque l’arrêt avec « mat1 and mat2 shapes cannot be multiplied (2864x8 and 2864x64) » (les formes de mat1 et mat2 ne permettent pas leur multiplication). Nouveau vocabulaire : les comptes d’un vocabulaire contenant chaque jeton, de forme (8, 4966), provoquent l’arrêt avec « mat1 and mat2 shapes cannot be multiplied (8x4966 and 2864x64) ». Deuxième couche trop petite : la deuxième couche est nn.Linear(32, 5), et le réseau s’arrête avec « mat1 and mat2 shapes cannot be multiplied (8x64 and 32x5) ».
Le cas d’un seul billet passe sans bruit. Le résultat (5,) fonctionne, mais la ligne suivante échoue. argmax(dim=1) sur un tenseur à une dimension s’arrête avec IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1) (dimension hors limites : attendue entre −1 et 0, mais 1 a été fourni). Rétablissez la dimension du lot avec .unsqueeze(0), ce qui donne (1, 2864), puis (1, 5).
Quand utiliser un sac de mots, et ses coûts
Section intitulée « Quand utiliser un sac de mots, et ses coûts »Un sac de mots est simple, rapide et de longueur fixe. Il fonctionne bien quand les mots déterminent l’équipe. Les caractéristiques TF-IDF du cours d’apprentissage automatique sont aussi un sac de mots, avec des poids. Elles acheminent très bien ces billets. Les coûts sont toutefois réels :
- L’ordre est perdu. Prenez deux billets inventés : « damaged on arrival, not after use » (endommagé à l’arrivée, pas après utilisation) et « damaged after use, not on arrival » (endommagé après utilisation, pas à l’arrivée). Ils contiennent les mêmes jetons, donc obtiennent les mêmes comptes et les mêmes scores. Le module 5 montre où cela échoue sur de vrais billets.
- Le vocabulaire est fixe. Un nouveau nom de produit absent des billets d’entraînement compte seulement comme
<unk>. - Les vecteurs contiennent surtout des zéros, soit 97,6 % pour T-64697, et la première couche contient 183 360 des 183 685 paramètres. Un vocabulaire plus grand agrandit la première couche.
Exercices guidés : la propagation avant dans le carnet
Section intitulée « Exercices guidés : la propagation avant dans le carnet »- Ouvrez le carnet depuis l’encadré Faites cette leçon dans un carnet Jupyter en haut de cette page, puis exécutez les cellules de préparation. La cellule de données se termine par
Ready: train 4696 tickets, valid 600, test 600.(prêt : 4696 billets d’entraînement, 600 de validation, 600 de test). - Dans la section 1, exécutez les petits billets. Vous voyez
ValueError(erreur de valeur), puis le tenseur de comptes (3, 10). - Dans la section 2, construisez le vocabulaire. Vous voyez
2864 in the vocabulary(2864 dans le vocabulaire). - Exécutez
bag()sur T-64697. Vous voyeznon-zero entries 70 | sum 92(70 entrées non nulles | somme 92). - Dans la section 3, construisez le réseau avec la graine 0 et affichez la forme après chaque couche. Vous voyez (8, 2864), (8, 64), (8, 64) et (8, 5).
- Dans la cellule Your turn (à vous), calculez
n_parameters, le nombre de paramètres demodel. Exécutez ensuite la cellule de vérification. Elle afficheCheck passed.(vérification réussie). - Calculez l’exactitude sur les billets de validation. Vous voyez
accuracy 0.182(exactitude 0.182).
Afficher un exemple de réponse à l’étape 6
n_parameters = sum(p.numel() for p in model.parameters())print(n_parameters)1836852 864 × 64 poids et 64 biais dans la première couche, 64 × 5 poids et 5 biais dans la deuxième.
À vous de jouer : une couche cachée plus large
Section intitulée « À vous de jouer : une couche cachée plus large »Changez une seule chose : la couche cachée passe de 64 à 128 neurones. Avant l’exécution, notez la forme de sortie de la première couche, celle du réseau et le nombre de paramètres.
Afficher la réponse
torch.manual_seed(0)wide = nn.Sequential(nn.Linear(len(itos), 128), nn.ReLU(), nn.Linear(128, len(TEAMS)))print(wide(torch.log1p(x8)).shape, wide[0](torch.log1p(x8)).shape)print(f"{sum(p.numel() for p in wide.parameters()):,}")torch.Size([8, 5]) torch.Size([8, 128])367,365La première couche donne maintenant (8, 128), mais le réseau donne toujours (8, 5). Cela représente une ligne par billet et un score par équipe. Le nombre de paramètres double presque : 2 864 × 128 + 128 = 366 720, et 128 × 5 + 5 = 645. La taille de la couche cachée change seulement l’intérieur du réseau et son coût.
Erreur courante
« PyTorch va trouver les formes »
Symptôme : Tomás veut que le réseau connaisse tous les mots, même les mots rares. Il construit le vocabulaire avec tous les jetons des billets d’entraînement, sans la règle « au moins deux fois ». Il calcule les fréquences de la même façon et les envoie au réseau :
itos_all = ["<pad>", "<unk>"] + sorted(word_counts) # every token, also the rare onesstoi_all = {token: i for i, token in enumerate(itos_all)}x8_all = torch.zeros(8, len(itos_all))for row_i, row in enumerate(valid_rows[:8]): for token in tokenize(row["text"])[:120]: x8_all[row_i, stoi_all.get(token, 1)] += 1print(x8_all.shape)print(model(torch.log1p(x8_all)).shape)torch.Size([8, 4966])RuntimeError: mat1 and mat2 shapes cannot be multiplied (8x4966 and 2864x64)« PyTorch va trouver les formes », dit-il. Ce n’est pas le cas.
Cause : lisez le message comme dans Tenseurs et formes. mat1 est l’entrée : 8 billets avec 4 966 fréquences chacun. mat2 est la matrice de poids transposée de la première couche : elle attend 2 864 valeurs. La taille du vocabulaire détermine la taille d’entrée de la première couche. Un nouveau vocabulaire produit une entrée différente, que l’ancien réseau ne peut pas lire. Même si les tailles correspondaient par hasard, la case 1 000 représenterait un autre jeton. Le réseau lirait alors des données sans sens.
Correction : construisez la première couche à partir du vocabulaire, jamais d’un nombre saisi à la main : nn.Linear(len(itos_all), 64). Le nouveau réseau peut alors lire les nouvelles fréquences :
torch.manual_seed(0)model_all = nn.Sequential(nn.Linear(len(itos_all), 64), nn.ReLU(), nn.Linear(64, len(TEAMS)))print(model_all(torch.log1p(x8_all)).shape)torch.Size([8, 5])Ce que cela signifie pour un vrai système d’IA : le vocabulaire fait partie du modèle. Le réseau doit toujours recevoir des fréquences tirées du vocabulaire utilisé pour sa construction et son entraînement. Amira doit enregistrer le vocabulaire avec le réseau entraîné, comme le montre le module 7. Un nouveau vocabulaire demande un nouveau réseau et un nouvel entraînement.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Pourquoi un réseau ne peut-il pas lire directement les listes de jetons des billets? Comment un sac de mots résout-il ce problème? Que perd-il?
Afficher la réponse
Une couche comme nn.Linear(2864, 64) exige le même nombre d’entrées pour chaque billet. Un tenseur doit aussi avoir des lignes de même longueur. Les billets ont des nombres de jetons différents (4, 4 et 7 dans le petit exemple). Leurs listes ne forment donc pas un tenseur. Un sac de mots fixe un vocabulaire et donne à chaque billet une fréquence pour chaque entrée. Chaque billet devient ainsi un vecteur de la taille du vocabulaire, soit 2 864 pour Larkfield. Il perd l’ordre des mots : deux billets contenant les mêmes mots dans un ordre différent reçoivent le même vecteur.
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
Avant l'entraînement, Omar voit que le réseau répond correctement pour 18,2 % des billets de validation. Il demande : est-ce un peu moins bon que de deviner? Expliquez ce que ce nombre signifie.
Afficher la réponse
Ce nombre signifie seulement que le réseau n’a encore rien appris. Ses poids sont aléatoires, donc ses scores le sont aussi. Un choix au hasard parmi 5 équipes est correct environ 20 % du temps. Un résultat de 18,2 % correspond donc au hasard. Les poids aléatoires ne répartissent pas les réponses également : ce réseau envoie 438 des 600 billets à account. Il répond donc correctement pour la plupart des billets account, mais pour aucun billet delivery. Son résultat est aussi inférieur à 27,5 %, la proportion de l’équipe la plus fréquente, delivery. L’entraînement du module 3 modifie les poids jusqu’à ce que les scores aient un sens.
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.
Exercice du module
Section intitulée « Exercice du module »Dans cet exercice, vous effectuez une propagation avant dans un tout petit réseau et examinez chaque forme intermédiaire. Suivez d’abord l’exemple détaillé, puis faites l’exercice vous-même.
Exemple détaillé : un tout petit réseau
Section intitulée « Exemple détaillé : un tout petit réseau »Les trois billets de Priya de Tenseurs et formes traversent un réseau à 4 entrées, 3 neurones cachés et 2 sorties. La boucle fait passer le lot dans une couche à la fois. Après chaque couche, elle affiche la forme et les nombres du premier billet.
batch = torch.tensor([[2.0, 2.0, 0.0, 1.0], [1.0, 0.0, 3.0, 0.0], [3.0, 1.0, 1.0, 1.0]])torch.manual_seed(0)tiny_net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 2))h = batchfor layer in tiny_net: h = layer(h) print(f"{layer.__class__.__name__:<6} {tuple(h.shape)} {h[0].detach().numpy().round(3)}")La sortie :
Linear (3, 3) [-0.317 -0.052 -0.129]ReLU (3, 3) [0. 0. 0.]Linear (3, 2) [ 0.479 -0.119]h[0].detach() retire la note d’entraînement (grad_fn) pour que .numpy() transforme les nombres du premier billet en tableau NumPy. Puis .round(3) conserve trois décimales. Lisez chaque ligne. La première couche transforme les 4 caractéristiques en 3 nombres par billet. Pour le premier billet, les 3 nombres sont négatifs, donc ReLU les remplace par 0. La dernière couche donne quand même 2 scores, soit 0,479 et −0,119. Comme elle ne reçoit que des zéros, ces scores sont ses 2 biais. La dimension du lot, 3, reste la même à chaque ligne.
Votre tâche
Section intitulée « Votre tâche »Créez un réseau à 4 entrées, 6 neurones cachés et 2 sorties, avec une graine de 0. Faites-y passer un lot de 5 billets, five = torch.ones(5, 4). Avant l’exécution, notez la forme après chaque couche et le nombre de paramètres. Puis exécutez le code et comparez.
Afficher le corrigé
torch.manual_seed(0)task_net = nn.Sequential(nn.Linear(4, 6), nn.ReLU(), nn.Linear(6, 2))five = torch.ones(5, 4)h = fivefor layer in task_net: h = layer(h) print(f"{layer.__class__.__name__:<6} {tuple(h.shape)}")print("parameters", sum(p.numel() for p in task_net.parameters()))Linear (5, 6)ReLU (5, 6)Linear (5, 2)parameters 44Le lot, 5, reste à la première position. La première couche fait passer la dernière dimension de 4 à 6, et ReLU conserve la forme. La dernière couche la fait passer de 6 à 2. La première couche compte 4 × 6 + 6 = 30 paramètres, et la deuxième, 6 × 2 + 2 = 14. Cela donne 44 paramètres en tout.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Rappel : qu'est-ce que la forme d'un tenseur, et quelle est la règle de diffusion? Donnez un exemple qui fonctionne et un autre qui échoue.
Afficher la réponse
La forme indique la taille de chaque dimension, en commençant par la dimension extérieure. Un lot de 3 billets avec 4 caractéristiques a la forme (3, 4). La diffusion compare deux formes à partir de la droite. Deux tailles sont compatibles si elles sont égales, si l’une vaut 1 ou si l’une est absente. Une taille de 1 ou une dimension absente est étirée. (3, 4) − (4,) fonctionne et donne (3, 4) : la ligne sert pour chaque billet. (3, 4) ÷ (3,) échoue : à droite, 4 rencontre 3; ces tailles diffèrent et aucune ne vaut 1.
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
Prédiction : un lot de 32 billets traverse le réseau à sac de mots avec une couche cachée de 64 neurones. Donnez la forme après le sac de mots, après chaque couche et celle des scores d'un billet.
Afficher la réponse
Après le sac de mots : (32, 2864), une fréquence pour chaque entrée du vocabulaire. Après log1p : (32, 2864). Après la première couche Linear : (32, 64). Après ReLU : (32, 64). Après la deuxième couche Linear : (32, 5). Les scores d’un billet, scores[0], ont la forme (5,), ou (1, 5) avec scores[0:1]. Seule la dimension du lot passe de 8 dans la leçon à 32; les couches ne changent pas.
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
Vérification finale : expliquez chaque dimension de la forme de sortie (8, 5). Trouvez et corrigez ensuite l'erreur dans ce réseau, qui s'arrête avec le message : mat1 and mat2 shapes cannot be multiplied (5x6 and 5x2) (les formes de mat1 et mat2 ne permettent pas leur multiplication). Le code est : broken = nn.Sequential(nn.Linear(4, 6), nn.ReLU(), nn.Linear(5, 2)); broken(five).
Afficher la réponse
La sortie (8, 5) : 8 est la dimension du lot, avec une ligne pour chacun des 8 billets. 5 est la dernière dimension, avec un score par équipe (delivery, returns, payment, warranty, account). scores[i, j] est le score du billet i pour l’équipe j.
L’erreur : mat1, (5 × 6), est ce qui arrive à la deuxième couche Linear. Ce sont 5 billets avec 6 nombres chacun, provenant de la première couche. mat2, (5 × 2), est la matrice de poids transposée de cette couche : elle attend 5 nombres par billet. La taille de sortie de la première couche, 6, diffère de la taille d’entrée de la deuxième, 5.
La correction : la taille d’entrée de chaque couche doit égaler la taille de sortie de la couche précédente. Écrivez nn.Linear(6, 2) pour la dernière couche :
fixed = nn.Sequential(nn.Linear(4, 6), nn.ReLU(), nn.Linear(6, 2))print(fixed(five).shape)torch.Size([5, 2])Une réponse complète nomme les deux dimensions de sortie et lit les deux formes du message. Elle indique aussi quelles tailles doivent être égales.
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
Dans ce module, vous avez appris à lire et à contrôler les formes d’un réseau :
- Un tenseur est une boîte de nombres avec une forme; les tenseurs d’un réseau ont généralement la forme (lot, caractéristiques). La diffusion étire une taille de 1 ou une dimension absente. Elle peut le faire par accident, sans erreur.
- Une première exécution au résultat connu, avec une graine, confirme votre installation :
torch.manual_seed(0)puistorch.rand(2, 3)donnent une somme de 2,4265. - Un sac de mots transforme chaque billet en vecteur de la taille du vocabulaire : 2 864 fréquences tirées des billets d’entraînement. L’ordre des mots est perdu.
- La propagation avant du réseau non entraîné : (8, 2864) → Linear → (8, 64) → ReLU → (8, 64) → Linear → (8, 5), avec 183 685 paramètres aléatoires. Il répond correctement pour 18,2 % des billets de validation : un résultat qui correspond au hasard.
- Une erreur de forme nomme deux formes : trouvez les tailles qui doivent être égales. Déterminez la taille de chaque couche à partir des données (
len(vocab)) et de la couche précédente.
Réinitialisation : dans le carnet Jupyter, sélectionnez Runtime › Disconnect and delete runtime (environnement d’exécution › déconnecter et supprimer l’environnement d’exécution) dans Colab. Puis exécutez toutes les cellules depuis le début. Sur votre ordinateur, exécutez python get_data.py --reset pour les données et ticketnet.py. Sinon, supprimez .venv et recréez-le comme dans Réinitialiser la configuration. Exécutez ensuite python first_run.py et vérifiez sum 2.4265 (somme de 2,4265).
Ensuite, dans le module 3, vous mesurerez à quel point ces scores sont erronés et commencerez à entraîner le réseau.
Leçon suivante : La perte : à quel point le réseau se trompe-t-il?