Position et masquage
- Leçon 19
- 50 min
- Module 6 : Assembler un transformeur
Leçon 19 sur 23 · L'apprentissage profond et les transformeurs expliquésPosition et masquage
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
Donnez à un transformeur l’ordre des mots et contrôlez ce que chaque jeton (token) peut lire. Vous pourrez ensuite expliquer les deux masques utilisés dans les transformeurs. Vous comprendrez aussi la différence entre les modèles qui lisent un texte entier et ceux qui en écrivent un.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter exécute le test de mélange et les deux masques sur de vrais tenseurs. Il entraîne d’abord son propre petit transformeur pendant 8 époques (une à trois minutes). Ses scores diffèrent donc un peu de ceux de cette page. 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 main
Ce que vous devez savoir
- Le bloc du transformeur et
TinyTransformer, présentés dans Les blocs du transformeur. - Les poids d’attention sous forme de tableau, avec une ligne par jeton lecteur, dans L’attention étape par étape.
- Pourquoi l’ordre des mots compte pour certains billets de soutien, dans Pourquoi l’ordre des mots et le contexte comptent.
Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local)
- Le dossier
ticket-routeravec son.venv, de Installer PyTorch : parcours 2, et le fichiertiny_transformer.ptenregistré dans Les blocs du transformeur : entraînez-le. Si le fichier manque, refaites l’entraînement avec la graine 0 et enregistrez-le.
Testé avec Python 3.12 et PyTorch 2.14.1 sur le processeur (macOS).
Une recette sur des fiches séparées
Section intitulée « Une recette sur des fiches séparées »Écrivez chaque étape d’une recette sur une fiche, puis laissez tomber les fiches. La personne qui les ramasse a toutes les étapes, mais pas leur ordre. « Cuire pendant 20 minutes » peut maintenant précéder « mélanger la farine ». Tous les mots sont là, mais le sens dépend de la place de chaque fiche.
Le problème : l’attention ne voit pas l’ordre
Section intitulée « Le problème : l’attention ne voit pas l’ordre »Pourquoi l’ordre des mots et le contexte comptent a montré un problème du sac de mots. « Endommagé à l’arrivée, pas après utilisation » et « endommagé après utilisation, pas à l’arrivée » reçoivent la même entrée. L’attention fait-elle mieux? Exécutez le test avec deux phrases inventées contenant les mêmes 7 jetons dans un ordre différent. Une couche d’attention lit chaque phrase. La moyenne de ses sorties forme un résumé.
import torchfrom torch import nn
words = ["damaged", "on", "arrival", ",", "not", "after", "use"]a = [0, 1, 2, 3, 4, 5, 6] # damaged on arrival , not after useb = [0, 5, 6, 3, 4, 1, 2] # damaged after use , not on arrivaltorch.manual_seed(0)table = nn.Embedding(len(words), 4) # one vector of 4 numbers per wordattention = nn.MultiheadAttention(4, 1, batch_first=True)
def read(order): x = table(torch.tensor([order])) # (1, 7, 4) out, _ = attention(x, x, x) # (1, 7, 4) return out.mean(dim=1).detach() # one summary per sentence: (1, 4)
print(read(a))print(read(b))print(torch.allclose(read(a), read(b), atol=1e-6))La sortie :
tensor([[-0.1657, -0.0738, 0.3466, -0.1725]])tensor([[-0.1657, -0.0738, 0.3466, -0.1725]])TrueLes deux phrases opposées obtiennent le même résumé. Regardez à l’intérieur, la sortie pour « use » (utilisation), à la position 6 dans la phrase a et 2 dans b :
x_a, x_b = table(torch.tensor([a])), table(torch.tensor([b]))out_a = attention(x_a, x_a, x_a)[0].detach()out_b = attention(x_b, x_b, x_b)[0].detach()print("'use' in a, position 6:", out_a[0, 6])print("'use' in b, position 2:", out_b[0, 2])La sortie :
'use' in a, position 6: tensor([-0.0194, -0.0040, 0.2147, -0.0818])'use' in b, position 2: tensor([-0.0194, -0.0040, 0.2147, -0.0818])Les mêmes nombres. L’attention compare chaque requête à chaque clé, peu importe leur place. « Use » lit donc le même ensemble de mots dans les deux phrases. Il obtient les mêmes poids et donne la même sortie. Mélangez l’entrée, et l’attention mélange simplement la sortie de la même façon. L’ordre n’entre jamais dans le calcul.
L’idée : un vecteur pour chaque position
Section intitulée « L’idée : un vecteur pour chaque position »Donnez à chaque place dans le texte son propre motif de nombres, puis ajoutez-le au vecteur du jeton. « Use » à la position 6 et « use » à la position 2 deviennent alors des entrées différentes. C’est un plongement de position : une table apprise avec une ligne par position. Elle s’utilise exactement comme la table de plongements (embeddings) des jetons. Le nom général est encodage positionnel : toute méthode qui intègre la position aux vecteurs.
torch.manual_seed(1)position = nn.Embedding(7, 4) # one vector of 4 numbers per positionsteps = torch.arange(7) # positions 0, 1, ..., 6
def read_in_order(order): x = table(torch.tensor([order])) + position(steps) # word vector + position vector out, _ = attention(x, x, x) return out.mean(dim=1).detach()
print(read_in_order(a))print(read_in_order(b))print(torch.allclose(read_in_order(a), read_in_order(b), atol=1e-6))La sortie :
tensor([[-0.2461, -0.0334, 0.7288, -0.4205]])tensor([[-0.1802, 0.0059, 0.7413, -0.4107]])FalseLes deux phrases diffèrent maintenant. Ces vecteurs de position sont encore aléatoires, donc la différence ne signifie encore rien. L’entraînement enseigne à la table ce que chaque position doit ajouter.
Les positions dans le petit transformeur
Section intitulée « Les positions dans le petit transformeur »TinyTransformer contient la table self.position = nn.Embedding(120, 64) : 120 positions, car MAX_TOKENS limite les billets à 120 jetons. Chargez le modèle entraîné dans Les blocs du transformeur. Regardez ses 4 premiers nombres pour « parcel » (colis) à deux endroits :
from ticketnet import (TinyTransformer, Vocab, labels, load_split, token_ids, evaluate, tokenize, TEAMS, PAD)
train_rows, valid_rows = load_split("data/train.csv"), load_split("data/valid.csv")vocab = Vocab([row["text"] for row in train_rows])model = TinyTransformer(len(vocab))model.load_state_dict(torch.load("tiny_transformer.pt"))model.eval()print(tuple(model.position.weight.shape), model.position.weight.numel(), "numbers")
parcel = vocab.stoi["parcel"]print("token 'parcel' ", model.embed.weight[parcel, :4].detach().numpy().round(2))print("position 0 ", model.position.weight[0, :4].detach().numpy().round(2))print("position 5 ", model.position.weight[5, :4].detach().numpy().round(2))print("'parcel' at 0 ", (model.embed.weight[parcel, :4] + model.position.weight[0, :4]).detach().numpy().round(2))print("'parcel' at 5 ", (model.embed.weight[parcel, :4] + model.position.weight[5, :4]).detach().numpy().round(2))La sortie :
(120, 64) 7680 numberstoken 'parcel' [-1.35 -0.68 -1.01 -0.75]position 0 [ 0.13 -0.41 -0.04 1.04]position 5 [ 1.76 1.14 0.25 -0.61]'parcel' at 0 [-1.21 -1.09 -1.06 0.29]'parcel' at 5 [ 0.42 0.46 -0.77 -1.36]torch.load() lit les nombres enregistrés, et load_state_dict() les place dans un nouveau modèle de même forme. Vérifiez le premier nombre à la main : −1,35 + 0,13 = −1,22. Il s’affiche comme −1,21, car la page arrondit chaque nombre séparément. « Parcel » à la position 0 et à la position 5 entrent maintenant dans les blocs sous forme de vecteurs différents.
Le test de mélange sur un vrai billet
Section intitulée « Le test de mélange sur un vrai billet »Mélangez les 92 jetons du billet T-64697, un billet de paiement, puis comparez les 5 scores du modèle entraîné :
import random
ticket = valid_rows[0]["text"]tokens = tokenize(ticket)random.seed(0)shuffled = random.sample(tokens, len(tokens))print(" ".join(tokens[:10]), "...")print(" ".join(shuffled[:10]), "...")
def scores(text): ids = torch.tensor([vocab.encode(text)]) with torch.no_grad(): return [round(v, 3) for v in model(ids)[0].tolist()]
print("in order:", scores(ticket))print("shuffled:", scores(" ".join(shuffled)))La sortie :
i am writing regarding order lk - 714258 . the ...i refund lk delivery process i receiving week so as ...in order: [-3.104, -3.135, 10.019, -1.814, -1.48]shuffled: [-3.283, -1.909, 10.309, -2.007, -2.905]Les scores changent, donc le modèle remarque l’ordre. Mettez maintenant la table de positions à zéro pour que le modèle n’ait plus de positions :
saved = model.position.weight.data.clone()model.position.weight.data.zero_() # remove the positionsprint("in order:", scores(ticket))print("shuffled:", scores(" ".join(shuffled)))model.position.weight.data = saved # put them backLa sortie :
in order: [-3.571, -2.937, 10.644, -2.797, 0.005]shuffled: [-3.571, -2.937, 10.644, -2.797, 0.005]Sans positions, le transformeur entraîné entier donne exactement les mêmes scores pour le billet et pour les mots mélangés. Les positions sont le seul endroit où l’ordre intervient.
À quel point le modèle entraîné utilise-t-il l’ordre? Quand tous les billets de validation sont mélangés, son exactitude est de 93.3%, contre 94.3% dans l’ordre : un point de moins. Le réseau à sac de mots obtient 98.0% sur les billets mélangés, car il ne peut pas remarquer le changement. Pour ces billets, les mots déterminent l’équipe, donc le transformeur utilise peu l’ordre. Il paie quand même tout le coût de son apprentissage.
D’autres façons d’encoder les positions
Section intitulée « D’autres façons d’encoder les positions »Une table apprise est la méthode la plus simple, mais elle a une limite. Elle contient seulement 120 lignes de positions. Les positions rares pendant l’entraînement restent presque aléatoires. Deux autres méthodes sont courantes :
- Positions sinusoïdales, utilisées dans le premier transformeur de 2017 : un motif fixe d’ondes de différentes longueurs pour chaque position. Une formule le calcule, donc rien n’est appris.
- Positions rotatives (RoPE) : au lieu d’ajouter un vecteur, elles tournent chaque requête et chaque clé selon un angle qui augmente avec la position. L’attention voit ainsi la distance entre deux jetons. SmolLM2-135M, au module 7, utilise des positions rotatives.
Le masque de remplissage : ne lisez pas les lignes vides
Section intitulée « Le masque de remplissage : ne lisez pas les lignes vides »La situation familière. Un formulaire contient dix lignes de réponse, et une réponse courte en laisse sept vides. Personne ne lit les lignes vides comme une partie de la réponse.
Le problème. Un lot forme un seul tenseur, donc tous ses billets doivent avoir la même longueur. token_ids() complète les billets courts avec <pad> (remplissage), d’identifiant 0, jusqu’à la longueur du plus long billet du lot. Prenez un court billet de livraison et placez-le dans un lot avec le plus long billet de validation :
short = next(row for row in valid_rows if row["style"] == "short")long = max(valid_rows, key=lambda row: len(vocab.encode(row["text"])))print(short["ticket_id"], short["team"], "|", short["text"])pair = token_ids([short, long], vocab)print(tuple(pair.shape))print(pair[0, :12].tolist(), "...", pair[0, -4:].tolist())print("padding in the short ticket:", int((pair[0] == PAD).sum()))La sortie :
T-64715 delivery | Ugh, need to change trampoline delivery slot. Order LK-640933(2, 120)[2653, 8, 1664, 2591, 618, 2620, 832, 2338, 10, 1749, 1486, 9] ... [0, 0, 0, 0]padding in the short ticket: 107Le billet court contient 13 vrais jetons et 107 jetons de remplissage. Sans masque, l’attention lirait le remplissage comme des mots. Exécutez les étapes du modèle à la main, une fois avec le masque et une fois sans :
def team_scores(ids, use_mask): with torch.no_grad(): h = model.embed(ids) + model.position(torch.arange(ids.shape[1])) mask = ids == PAD if use_mask else None h = model.blocks(h, src_key_padding_mask=mask) keep = (ids != PAD).unsqueeze(-1).float() mean = (model.norm(h) * keep).sum(1) / keep.sum(1) return [round(v, 3) for v in model.out(mean)[0].tolist()]
alone = token_ids([short], vocab)print("alone, no padding: ", team_scores(alone, use_mask=True))print("padded, with mask: ", team_scores(pair, use_mask=True))print("padded, without mask:", team_scores(pair, use_mask=False))La sortie :
alone, no padding: [5.223, -1.598, -3.307, -4.811, 4.613]padded, with mask: [5.223, -1.598, -3.307, -4.811, 4.613]padded, without mask: [1.351, -6.228, -1.896, 0.008, 8.922]Les équipes sont dans l’ordre : livraison, retours, paiement, garantie, compte. Seul, le billet va à la livraison (5,223), ce qui est correct. Avec remplissage, sans masque, il va à l’équipe compte (8,922) : 107 lignes vides ont changé la réponse.
L’idée. Indiquez à l’attention quelles clés ignorer. src_key_padding_mask a la forme (lot, jetons) et vaut True (vrai) lorsqu’un jeton doit être masqué : ids == PAD. Les clés masquées ne reçoivent aucun poids, donc les vrais jetons ne lisent jamais le remplissage. Avec le masque, le billet rempli obtient exactement les mêmes scores que lorsqu’il est seul. C’est le masque de remplissage. TinyTransformer l’utilise à chaque propagation avant, et sa moyenne ignore aussi les lignes de remplissage.
Le masque causal : cachez les réponses en dessous
Section intitulée « Le masque causal : cachez les réponses en dessous »La situation familière. Dans un examen, les réponses sont imprimées sur la même feuille, sous chaque question. Un élève honnête couvre tout ce qui se trouve sous la ligne actuelle avec une feuille de papier.
Le problème. La prochaine leçon entraîne un modèle à prédire le jeton suivant à chaque position. Si la position 3 peut lire la position 4, elle lit la réponse au lieu de la prédire. Le modèle échoue alors dès qu’il doit écrire un nouveau texte, où le prochain jeton n’existe pas encore.
L’idée. Pour chaque jeton, masquez tous les jetons qui le suivent. Dans le tableau d’attention, cela correspond à toutes les cellules à droite de la diagonale : un triangle.
Description textuelle du diagramme
Le schéma est une grille de 6 lignes et 6 colonnes pour les jetons « my » (mon), « parcel », « arrived » (arrivé), « broken » (brisé), une virgule et « please » (s’il vous plaît). Chaque ligne représente un jeton qui lit, et chaque colonne un jeton qu’il peut lire. Les cellules sur la diagonale et en dessous sont bleues : un jeton peut se lire lui-même et lire tous les jetons précédents. Les cellules au-dessus de la diagonale sont grises et portent la mention « hidden » (masqué). Ce sont les jetons suivants, les réponses que le modèle doit prédire. Une note indique que la ligne « broken » peut lire « my », « parcel », « arrived » et « broken », mais pas la virgule ni « please ». 15 des 36 cellules sont masquées.
C’est le masque causal. « Causal », car le passé peut influencer un jeton, mais pas le futur. PyTorch le crée pour vous :
causal = nn.Transformer.generate_square_subsequent_mask(6)print(causal)La sortie :
tensor([[0., -inf, -inf, -inf, -inf, -inf], [0., 0., -inf, -inf, -inf, -inf], [0., 0., 0., -inf, -inf, -inf], [0., 0., 0., 0., -inf, -inf], [0., 0., 0., 0., 0., -inf], [0., 0., 0., 0., 0., 0.]])Le masque s’ajoute aux scores avant la fonction softmax (normalisation en poids dont la somme vaut 1). Un score de moins l’infini devient un poids d’exactement 0, car e à la puissance moins l’infini vaut 0. Le voici sur une tête d’attention, avec 6 vecteurs de jetons aléatoires :
torch.manual_seed(0)x = torch.randn(1, 6, 8) # 6 tokens, 8 numbers eachhead = nn.MultiheadAttention(8, 1, batch_first=True)_, weights = head(x, x, x, attn_mask=causal)print(weights[0].detach().numpy().round(2))La sortie :
[[1. 0. 0. 0. 0. 0. ] [0.6 0.4 0. 0. 0. 0. ] [0.38 0.2 0.42 0. 0. 0. ] [0.29 0.23 0.22 0.26 0. 0. ] [0.22 0.17 0.21 0.18 0.22 0. ] [0.12 0.16 0.26 0.15 0.15 0.16]]Le triangle supérieur ne contient que des 0. Le premier jeton peut seulement se lire lui-même, donc son poids vaut 1. La somme de chaque ligne vaut toujours 1, répartie entre les jetons qu’elle peut lire.
Essayez les deux masques sur le petit transformeur entraîné. Faites d’abord une prédiction, puis choisissez un masque.
Explorer
Quelles cellules chaque masque cache-t-il?
Prédisez d’abord : L'exemple contient 4 vrais jetons et 2 jetons de remplissage. Combien des 36 cellules le masque de remplissage cache-t-il? Combien le masque causal en cache-t-il? Quelle ligne ne lit qu'un seul jeton?
- Cellules masquées
- 0 sur 36
- Cellules visibles
- 36
- « my » lit
- 6 jetons
Aucune cellule n’est masquée. Chaque jeton lit tous les jetons, y compris les 2 jetons de remplissage : « late » donne 0,37 de son attention au remplissage. 0 cellule sur 36 est masquée; les poids visibles de chaque ligne totalisent 1.
| my | parcel | is | late | pad | pad | |
|---|---|---|---|---|---|---|
| my | 0,10 | 0,13 | 0,26 | 0,14 | 0,23 | 0,14 |
| parcel | 0,20 | 0,11 | 0,28 | 0,15 | 0,14 | 0,11 |
| is | 0,04 | 0,46 | 0,08 | 0,17 | 0,11 | 0,13 |
| late | 0,13 | 0,24 | 0,09 | 0,16 | 0,32 | 0,05 |
| pad | 0,24 | 0,04 | 0,23 | 0,12 | 0,15 | 0,22 |
| pad | 0,21 | 0,02 | 0,14 | 0,14 | 0,13 | 0,36 |
Description textuelle de l’explorateur
L’explorateur montre un tableau de 6 lignes et 6 colonnes pour les jetons « my », « parcel », « is » (est), « late » (en retard) et deux jetons de remplissage. Les lignes représentent le jeton qui lit; les colonnes représentent celui qu’il lit. Une rangée de boutons permet de choisir le masque. Sans masque : aucune cellule n’est masquée, et « late » accorde 0,37 de son attention aux deux jetons de remplissage. Masque de remplissage : les deux colonnes de remplissage sont masquées, soit 12 cellules sur 36. Les poids des vrais jetons augmentent pour combler l’écart, et « late » accorde maintenant 0,46 à « parcel ». Masque causal : les 15 cellules au-dessus de la diagonale sont masquées. « My » ne lit que lui-même, avec un poids de 1,00. « Parcel » accorde 0,51 à « my » et 0,49 à lui-même. Les deux masques : 18 cellules sont masquées. Les vrais jetons lisent comme avec le masque causal, et seules les lignes de remplissage changent. La somme des poids visibles de chaque ligne vaut 1.
Deux détails méritent votre attention. Les lignes de remplissage reçoivent encore des poids : le modèle les calcule. TinyTransformer les élimine lorsqu’il calcule la moyenne des vrais jetons. Avec le remplissage à la fin, le masque causal cache déjà le remplissage aux vrais jetons. L’option « les deux » ne change donc que les lignes de remplissage.
Encodeurs et décodeurs
Section intitulée « Encodeurs et décodeurs »Les masques déterminent le type de modèle. Un modèle qui lit un texte entier à la fois, avec seulement un masque de remplissage, est un encodeur. Un modèle avec un masque causal, où chaque jeton ne lit que le passé, est un décodeur. Il peut écrire un texte un jeton à la fois.
| Encodeur (de type BERT) | Décodeur (de type GPT) | |
|---|---|---|
| Chaque jeton lit | tous les jetons, avant et après | lui-même et les jetons précédents |
| Masques | remplissage | causal (et remplissage dans les lots) |
| Utile pour | lire : classer, chercher, trouver des noms | écrire : continuer un texte, répondre, traduire |
| Dans ce cours | TinyTransformer (nn.TransformerEncoder) |
SmolLM2-135M au module 7 |
BERT (2018) est l’encodeur classique, et les modèles GPT sont les décodeurs classiques. Pour acheminer les billets, un encodeur convient : le billet entier est disponible. « Not » (pas) à la fin peut changer le sens de « delivery » (livraison) au début. Pour écrire une réponse, un modèle doit fonctionner comme un décodeur, car le prochain mot de la réponse n’existe pas encore.
Quand utiliser chaque élément, et à quel coût
Section intitulée « Quand utiliser chaque élément, et à quel coût »- Les positions sont nécessaires dès que l’ordre peut changer le sens. Une table apprise est simple, mais s’arrête à sa longueur maximale; celle de Larkfield s’arrête à 120 jetons. Les positions rotatives gèrent mieux les textes longs.
- Le masque de remplissage est nécessaire dès qu’un lot contient des textes de longueurs différentes. C’est presque toujours le cas. Son calcul ne coûte rien; l’oublier change les réponses sans provoquer d’erreur.
- Le masque causal est nécessaire dans tout modèle qui apprend à prédire le prochain jeton. Il limite aussi le modèle : un jeton ne peut jamais utiliser ce qui suit, même si le texte entier est disponible.
Exercice guidé : des masques sur de vrais tenseurs
Section intitulée « Exercice guidé : des masques sur de vrais tenseurs »- Ouvrez le carnet dans l’encadré en haut de cette page et exécutez les cellules de préparation. La cellule d’entraînement termine après 8 époques avec une exactitude de validation d’environ 92 %.
- Dans la section 1, exécutez le test de mélange avec une couche d’attention. Vous voyez
True: le même résumé pour les deux phrases. - Ajoutez la table de positions et exécutez le test à nouveau. Vous voyez
False(faux). - Dans la section 2, mettez à zéro la table de positions du modèle entraîné. Comparez les scores de T-64697 dans l’ordre et mélangé. Vous voyez les mêmes 5 scores deux fois.
- Dans la section 3, exécutez le billet court seul, avec remplissage et masque, puis avec remplissage sans masque. Les deux premières lignes sont égales.
- Dans la cellule Your turn (à vous de jouer), créez
causalpour 6 jetons et comptez ses cellules masquées danshidden_cells. Exécutez la cellule de vérification. Elle afficheCheck passed.(vérification réussie).
Afficher un exemple de réponse à l’étape 6
causal = nn.Transformer.generate_square_subsequent_mask(6)hidden_cells = int(torch.isinf(causal).sum())print(hidden_cells)15torch.isinf() vaut True lorsqu’une valeur est infinie. Le triangle supérieur d’un tableau de 6 × 6 contient 5 + 4 + 3 + 2 + 1 = 15 cellules.
À vous de jouer : quatre jetons
Section intitulée « À vous de jouer : quatre jetons »Changez une condition : un texte de 4 jetons. Avant de l’exécuter, dessinez le masque causal sur papier et comptez ses cellules masquées.
Afficher la réponse
four = nn.Transformer.generate_square_subsequent_mask(4)print(four)print("hidden cells:", int(torch.isinf(four).sum()))tensor([[0., -inf, -inf, -inf], [0., 0., -inf, -inf], [0., 0., 0., -inf], [0., 0., 0., 0.]])hidden cells: 63 + 2 + 1 = 6 cellules, le triangle au-dessus de la diagonale. Pour n jetons, le masque causal cache n × (n − 1) ÷ 2 cellules, soit presque la moitié du tableau.
Erreur courante
Un masque de remplissage inversé
Symptôme : Tomás écrit le masque comme « les jetons à garder », ids != PAD. Rien ne s’arrête avec une erreur, mais l’exactitude de validation baisse. Certains billets obtiennent nan (pas un nombre) comme scores :
ids_valid = token_ids(valid_rows, vocab)y_valid = labels(valid_rows)
def scores_with(mask): with torch.no_grad(): h = model.embed(ids_valid) + model.position(torch.arange(ids_valid.shape[1])) h = model.blocks(h, src_key_padding_mask=mask) keep = (ids_valid != PAD).unsqueeze(-1).float() return model.out((model.norm(h) * keep).sum(1) / keep.sum(1))
wrong = scores_with(ids_valid != PAD) # True on the real tokens: they are hiddenright = scores_with(ids_valid == PAD) # True on the padding: it is hiddenprint("wrong mask: accuracy", round((wrong.argmax(1) == y_valid).float().mean().item(), 4), "| tickets with nan scores:", int(torch.isnan(wrong).any(1).sum()))print("right mask: accuracy", round((right.argmax(1) == y_valid).float().mean().item(), 4))first = int(torch.isnan(wrong).any(1).nonzero()[0])print(valid_rows[first]["ticket_id"], len(vocab.encode(valid_rows[first]["text"])), "tokens:", wrong[first])wrong mask: accuracy 0.155 | tickets with nan scores: 23right mask: accuracy 0.9433T-64707 120 tokens: tensor([nan, nan, nan, nan, nan])Cause : dans PyTorch, True dans un masque signifie masquer. Le masque de Tomás cache les vrais mots, donc chaque billet ne lit que son remplissage : 15,5 %, pire que le hasard. Un billet de 120 jetons n’a aucun remplissage, donc toutes les clés sont masquées. Softmax divise alors 0 par 0, ce qui donne nan.
Solution : écrivez le masque comme les jetons à cacher, ids == PAD, comme le fait ticketnet.py. Testez-le avec un billet seul et le même billet avec remplissage : les scores doivent être égaux.
Ce que cela signifie pour un vrai système d’IA : chaque bibliothèque a sa propre règle pour les masques. Dans transformers de Hugging Face, attention_mask fonctionne à l’inverse : 1 signifie garder. Lisez la documentation de chaque masque. Gardez un test qui compare un billet seul au même billet dans un lot avec remplissage.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Sans vecteurs de position, pourquoi l'attention donne-t-elle le même résumé pour deux phrases contenant les mêmes mots dans un ordre différent? Que changent les vecteurs de position?
Afficher la réponse
L’attention compare la requête de chaque jeton à chaque clé et fait la moyenne des valeurs. Rien dans ce calcul ne dépend de la position d’un jeton. Chaque mot reçoit donc la même sortie dans les deux phrases, mais à une autre position. La moyenne des jetons est alors exactement la même (True, ou vrai, dans la leçon). Le tout petit transformeur entraîné, avec sa table de positions mise à zéro, a donné des scores identiques pour un billet et ses mots mélangés. Les vecteurs de position ajoutent un motif appris différent pour chaque position. Le même mot à deux positions devient donc deux entrées différentes, et l’ordre peut influencer le résultat.
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 court billet reçoit 107 jetons de remplissage dans un lot, sans masque de remplissage. Sa réponse peut-elle changer? Que fait le masque? Quelle est la forme de src_key_padding_mask pour un lot de 2 billets de 120 jetons?
Afficher la réponse
Oui. Sans masque, l’attention lit les jetons de remplissage comme des mots, et ils changent la sortie de chaque vrai jeton. Dans la leçon, le court billet de livraison T-64715 était classé dans la catégorie compte (8,922) avec remplissage, sans masque. Seul, il était classé dans la catégorie livraison (5,223). Le masque de remplissage vaut True (vrai) aux positions des jetons de remplissage. Aucun jeton ne leur donne donc de poids. Avec le masque, le billet rempli reçoit exactement les mêmes scores que lorsqu’il est seul. Le masque contient une valeur pour chaque jeton de chaque billet : sa forme est (2, 120).
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
Larkfield veut deux modèles : l'un dirige un billet complet, l'autre écrit les premiers mots d'une réponse. Lequel devrait être un encodeur et lequel un décodeur? De quels masques chacun a-t-il besoin?
Afficher la réponse
Le modèle qui dirige les billets devrait être un encodeur, comme TinyTransformer. Le billet entier est disponible. Chaque jeton peut donc lire tous les autres jetons, avant et après lui. Il a seulement besoin du masque de remplissage. Le modèle qui rédige les réponses doit être un décodeur. Il écrit un jeton à la fois, et chaque jeton peut dépendre seulement des jetons qui le précèdent. Il a besoin du masque causal, ainsi que du masque de remplissage lorsqu’il s’entraîne sur des lots avec remplissage.
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
- L’attention seule ignore l’ordre des mots : des mots mélangés donnent une sortie mélangée et le même résumé.
- Un plongement de position ajoute un vecteur appris pour chaque position.
TinyTransformeren a 120; les positions sinusoïdales et rotatives sont d’autres méthodes. - Le masque de remplissage cache les jetons de remplissage. Sans lui, la réponse pour un court billet peut changer selon son lot.
- Le masque causal cache tous les jetons suivants, soit le triangle au-dessus de la diagonale. Le modèle ne peut donc pas lire la réponse qu’il doit prédire.
- Un encodeur (de type BERT) lit tout; un décodeur (de type GPT) lit seulement le passé et peut écrire du texte.
À suivre : comment un décodeur apprend à partir de texte brut sans étiquettes, et pourquoi Larkfield ne peut pas le faire à grande échelle.
Leçon suivante : Les objectifs d'entraînement