Les objectifs d'entraînement
- Leçon 20
- 45 min
- Module 6 : Assembler un transformeur
Leçon 20 sur 23 · L'apprentissage profond et les transformeurs expliquésLes objectifs 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
Voyez comment un modèle de langage apprend à partir de texte brut que personne n’a étiqueté. Il utilise l’un de deux objectifs d’entraînement : prédire le prochain jeton ou remplir un jeton caché. Vous pourrez ensuite expliquer pourquoi l’entraînement exige un masque causal et pourquoi Larkfield ne devrait pas préentraîner son propre grand modèle.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter construit les exemples d’entraînement des deux objectifs. Il entraîne un petit modèle de prédiction du prochain mot, avec et sans masque causal, et contient les exercices du module. Il ne télécharge pas SmolLM2. 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
- La perte d’entropie croisée comme « la surprise devant la bonne réponse », −log p, dans La perte : à quel point le réseau se trompe-t-il?.
- Le masque causal, le masque de remplissage, les encodeurs et les décodeurs, dans Position et masquage.
TinyTransformeret ses formes, dans Les blocs du transformeur.
Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local)
- Le dossier
ticket-routeravec son.venv, dans Installer PyTorch : parcours 2. L’exemple SmolLM2 exige aussipip install "transformers>=5,<6"et télécharge environ 270 Mo. Il est facultatif, car cette page présente sa sortie.
Testé avec Python 3.12, PyTorch 2.14.1 et transformers 5.19.0 sur le processeur (macOS).
Remplir les espaces vides
Section intitulée « Remplir les espaces vides »Dans un cours de langue, le professeur vous donne un texte à trous : « Mon colis est arrivé ____, veuillez envoyer une nouvelle lampe. » Vous remplissez chaque trou à partir des mots qui l’entourent. Personne n’a dû rédiger de corrigé : le texte original est le corrigé. Un bon élève améliore sa maîtrise de la langue simplement en faisant beaucoup de ces exercices.
Le problème : les étiquettes coûtent cher, et la langue exige beaucoup de texte
Section intitulée « Le problème : les étiquettes coûtent cher, et la langue exige beaucoup de texte »Jusqu’ici, chaque réseau de ce cours a appris à partir d’exemples étiquetés. Chaque billet de soutien de Larkfield indiquait l’équipe désignée par la politique de Grace. Quelqu’un a dû choisir chaque étiquette, et le tout petit transformeur n’en a vu que 4 696. Dans Les blocs du transformeur, c’était bien trop peu pour apprendre le sens des mots et le rôle de leur ordre.
Pour apprendre une langue, un modèle a besoin de milliards de phrases. Personne ne peut étiqueter des milliards de phrases à la main.
L’idée : le texte est son propre corrigé
Section intitulée « L’idée : le texte est son propre corrigé »Créez les questions à partir du texte lui-même. Cachez une partie d’une phrase et demandez au modèle de la prédire. La partie cachée est la bonne réponse. Ainsi, chaque phrase jamais écrite devient une donnée d’entraînement. C’est l’apprentissage autosupervisé : supervisé, car chaque question a une réponse connue; « auto », car le texte fournit la réponse. Deux façons de cacher sont courantes.
Prédiction du prochain jeton. Lisez le texte à partir de la gauche et, à chaque position, prédisez le prochain jeton (token). Une phrase de Larkfield donne un exemple pour chaque jeton après le premier :
from ticketnet import tokenize
sentence = "My parcel arrived broken, please send a new lamp."tokens = tokenize(sentence)print(len(tokens), tokens)for i in range(1, len(tokens)): print(" ".join(tokens[:i]), "->", tokens[i])La sortie :
11 ['my', 'parcel', 'arrived', 'broken', ',', 'please', 'send', 'a', 'new', 'lamp', '.']my -> parcelmy parcel -> arrivedmy parcel arrived -> brokenmy parcel arrived broken -> ,my parcel arrived broken , -> pleasemy parcel arrived broken , please -> sendmy parcel arrived broken , please send -> amy parcel arrived broken , please send a -> newmy parcel arrived broken , please send a new -> lampmy parcel arrived broken , please send a new lamp -> .11 jetons donnent 10 exemples. Un décodeur ne les traite pas un par un. Il lit la phrase entière une seule fois. La position i prédit le jeton i + 1, à toutes les positions en même temps. Le masque causal de la dernière leçon rend l’exercice équitable : chaque position ne voit que sa propre ligne de cette liste.
Prédiction des jetons masqués. Cachez des jetons n’importe où dans le texte et prédisez-les à partir des deux côtés :
import random
random.seed(3)hidden = sorted(random.sample(range(len(tokens)), 2)) # hide 2 of the 11 tokensmasked = ["[MASK]" if i in hidden else t for i, t in enumerate(tokens)]print(" ".join(masked))print("answers:", {i: tokens[i] for i in hidden})La sortie :
my parcel arrived [MASK] , please send a new [MASK] .answers: {3: 'broken', 9: 'lamp'}[MASK] (masque) est un jeton spécial qui signifie « il y avait un mot ici ». BERT, l’encodeur classique, cache environ 15 % des jetons de chaque texte. Chaque jeton caché est prédit à partir de tous les mots avant et après lui, selon la lecture d’un encodeur.
Description textuelle du diagramme
Le schéma présente deux panneaux pour la phrase « My parcel arrived broken, please send a new lamp. » (Mon colis est arrivé cassé, veuillez envoyer une nouvelle lampe.) Panneau 1, prédiction du prochain jeton (de type GPT) : une rangée de jetons « my », « parcel », « arrived », « broken », la virgule, « please » et « send ». Au-dessus de chacun, une flèche pointe vers sa réponse, le prochain jeton : « parcel », « arrived », « broken », la virgule, « please », « send » et « a ». Une note indique que chaque position prédit le prochain jeton et lit seulement les jetons à sa gauche. Une phrase de 11 jetons donne donc 10 exemples. Panneau 2, prédiction des jetons masqués (de type BERT) : la rangée « my parcel arrived [MASK] , please send a new [MASK] . », avec les deux masques ombragés. Sous chaque masque, une flèche pointe vers sa réponse, « broken » et « lamp ». Une note indique qu’environ 15 % des jetons sont cachés. Le modèle prédit chacun à partir des mots des deux côtés, et le texte contient toutes les réponses.
Combien d’exemples se cachent dans les billets d’entraînement de Larkfield, sans étiquette fournie par une personne?
from ticketnet import load_split
train_rows = load_split("data/train.csv")n_tokens = sum(len(tokenize(row["text"])) for row in train_rows)print(f"{n_tokens:,} tokens in {len(train_rows):,} training tickets")print(f"{n_tokens - len(train_rows):,} next-token examples, with no label written by a person")La sortie :
269,231 tokens in 4,696 training tickets264,535 next-token examples, with no label written by a personPlus de 50 fois plus d’exemples que de billets, et personne n’a rédigé une seule de leurs réponses. Pourtant, comme vous le verrez ci-dessous, c’est une quantité minuscule de texte pour apprendre une langue.
L’objectif sur un vrai modèle
Section intitulée « L’objectif sur un vrai modèle »SmolLM2-135M, que le module 7 explore, a été préentraîné avec la prédiction du prochain jeton. Sa perte d’entraînement est l’entropie croisée que vous connaissez : à chaque position, la surprise devant le vrai prochain jeton, −log p. Demandez-lui la probabilité de chaque vrai prochain jeton de la phrase :
import torchfrom transformers import AutoModelForCausalLM, AutoTokenizer
name = "HuggingFaceTB/SmolLM2-135M"tokenizer = AutoTokenizer.from_pretrained(name)model = AutoModelForCausalLM.from_pretrained(name, dtype=torch.float32).eval()
ids = tokenizer("My parcel arrived broken, please send a new lamp.", return_tensors="pt").input_idswith torch.no_grad(): scores = model(ids).logits # (1, tokens, 49152)probs = scores[0, :-1].softmax(-1) # the prediction after each tokenanswers = ids[0, 1:] # the real next tokensp = probs[torch.arange(len(answers)), answers]for token, chance in zip(tokenizer.convert_ids_to_tokens(answers), p.tolist()): print(f"{token!r:>10} {chance:.4f} 1 in {1 / chance:,.0f}")print("loss", round(float(-p.log().mean()), 3))La sortie :
'Ġparcel' 0.0000 1 in 215,056'Ġarrived' 0.0036 1 in 277 'Ġbroken' 0.0000 1 in 35,351 ',' 0.1655 1 in 6 'Ġplease' 0.0001 1 in 9,503 'Ġsend' 0.0486 1 in 21 'Ġa' 0.0601 1 in 17 'Ġnew' 0.0148 1 in 68 'Ġlamp' 0.0000 1 in 251,563 '.' 0.1967 1 in 5loss 6.345Ġ marque une espace avant le jeton. Le modèle donne un score pour chacun de ses 49 152 jetons à chaque position. Le vrai prochain jeton est la réponse. « broken » (cassé) après « My parcel arrived » (mon colis est arrivé) le surprend : 1 chance sur 35 351. La virgule après « broken » ne le surprend pas : 1 sur 6. La perte, 6,345, est la surprise moyenne. model(ids, labels=ids).loss donne aussi 6,345, car Hugging Face calcule cet objectif pour vous. Le préentraînement modifie les poids, sur des milliers de milliards de jetons, pour réduire ce nombre.
Pourquoi le masque causal compte pendant l’entraînement
Section intitulée « Pourquoi le masque causal compte pendant l’entraînement »Que se passe-t-il si un modèle apprend la prédiction du prochain jeton sans masque causal? Priya fait l’essai sur les billets. NextWordModel est un petit décodeur : vecteurs de jetons et de positions, 2 blocs à 4 têtes, et un score pour chaque mot du vocabulaire. La seule différence entre les deux exécutions est le masque :
import torchfrom torch import nnfrom ticketnet import Vocab, PAD, set_seed
vocab = Vocab([row["text"] for row in train_rows])valid_rows = load_split("data/valid.csv")
def sequences(rows, length=48): """Each ticket's first 48 token IDs, padded with 0.""" ids = [vocab.encode(row["text"])[:length] for row in rows] return torch.tensor([s + [PAD] * (length - len(s)) for s in ids])
seq_train, seq_valid = sequences(train_rows), sequences(valid_rows)
class NextWordModel(nn.Module): """Token + position vectors -> 2 transformer blocks -> a score for every word of the vocabulary."""
def __init__(self, vocab_size, dim=64, length=48): super().__init__() self.embed = nn.Embedding(vocab_size, dim) self.position = nn.Embedding(length, dim) block = nn.TransformerEncoderLayer(dim, 4, 4 * dim, dropout=0.0, batch_first=True, norm_first=True) self.blocks = nn.TransformerEncoder(block, 2, enable_nested_tensor=False) self.out = nn.Linear(dim, vocab_size)
def forward(self, ids, causal=True): # ids: (batch, tokens) n = ids.shape[1] h = self.embed(ids) + self.position(torch.arange(n)) # (batch, tokens, dim) mask = nn.Transformer.generate_square_subsequent_mask(n) if causal else None h = self.blocks(h, mask=mask, is_causal=causal) # (batch, tokens, dim) return self.out(h) # (batch, tokens, vocab)
loss_fn = nn.CrossEntropyLoss(ignore_index=PAD) # padding is not an answer
def next_word_loss(model, ids, causal=True): scores = model(ids[:, :-1], causal=causal) # read tokens 0 .. n-2 targets = ids[:, 1:] # the answers: tokens 1 .. n-1 return loss_fn(scores.reshape(-1, scores.shape[-1]), targets.reshape(-1))Les réponses sont les mêmes jetons, décalés d’une place vers la gauche : ids[:, 1:]. ignore_index=PAD indique à la perte d’ignorer le remplissage. nn.TransformerEncoderLayer avec un masque causal fonctionne comme un bloc de décodeur. Son nom indique seulement que PyTorch l’a d’abord conçu pour les encodeurs. Entraînez les deux modèles pendant 3 époques avec les cinq étapes :
def train_next_word(causal, epochs=3): set_seed(0) model = NextWordModel(len(vocab)) opt = torch.optim.Adam(model.parameters(), lr=1e-3) curve = [] for epoch in range(epochs): model.train() order = torch.randperm(len(seq_train)) for i in range(0, len(order), 32): opt.zero_grad() loss = next_word_loss(model, seq_train[order[i:i + 32]], causal=causal) loss.backward() opt.step() curve.append(round(loss.item(), 4)) return model, curve
honest, honest_curve = train_next_word(causal=True)cheater, cheater_curve = train_next_word(causal=False)print("steps", len(honest_curve))print("training loss, last step: with causal mask", honest_curve[-1], "| without mask", cheater_curve[-1])La sortie :
steps 441training loss, last step: with causal mask 3.4118 | without mask 0.5356- Avec le masque causal
- Sans masque
Description textuelle du graphique
Un graphique en courbes avec l’étape d’entraînement, de 1 à 441, sur l’axe horizontal. La perte d’entraînement, de 0 à 8, est sur l’axe vertical. Les deux courbes commencent près de 8, la perte d’une réponse au hasard parmi 2 864 mots. Avec le masque causal, la perte descend à environ 3,5 après 441 étapes. Sans masque, elle descend beaucoup plus vite et plus bas, jusqu’à environ 0,6.
Sans masque, la perte d’entraînement semble six fois meilleure. Mesurez maintenant les deux modèles sur les billets de validation, dans leur situation d’utilisation : l’écriture, où le futur n’existe pas encore.
honest.eval()cheater.eval()with torch.no_grad(): print("with mask, valid loss ", round(next_word_loss(honest, seq_valid).item(), 3)) print("without mask, future open ", round(next_word_loss(cheater, seq_valid, causal=False).item(), 3)) print("without mask, future hidden", round(next_word_loss(cheater, seq_valid, causal=True).item(), 3))
def continue_text(model, text, words=8): ids = vocab.encode(text) with torch.no_grad(): for _ in range(words): scores = model(torch.tensor([ids]), causal=True) # at writing time, the future does not exist ids.append(int(scores[0, -1].argmax())) return " ".join(vocab.itos[i] for i in ids)
print("with mask: ", continue_text(honest, "my parcel"))print("without mask:", continue_text(cheater, "my parcel"))La sortie :
with mask, valid loss 3.559without mask, future open 0.54without mask, future hidden 11.345with mask: my parcel was charged twice for the garden bench ,without mask: my parcel my problem problem problem problem problem problem problemcontinue_text() est la boucle de génération : prédire un mot, l’ajouter, prédire à nouveau. Lisez les trois pertes. Avec le futur visible, le modèle sans masque obtient 0,54, car il copie le prochain mot de l’entrée. Quand le futur est caché, sa perte grimpe à 11,345. C’est pire qu’une réponse au hasard parmi 2 864 mots (ln 2 864 ≈ 7,96). Il a appris à copier, pas à prédire. Le modèle honnête, à 3,559, écrit une phrase qui ressemble à un billet, même si le colis et les frais ne concordent pas. Voilà ce que trois époques sur 4 696 billets peuvent enseigner.
Jeton masqué ou prochain jeton : lequel utiliser
Section intitulée « Jeton masqué ou prochain jeton : lequel utiliser »- La prédiction du prochain jeton entraîne un décodeur, puis la même boucle écrit du texte. C’est l’objectif des modèles de type GPT comme SmolLM2. Chaque jeton apprend seulement à partir de son côté gauche.
- La prédiction des jetons masqués entraîne un encodeur, qui lit les deux côtés de chaque mot. Elle convient aux tâches de lecture, comme la classification des billets. Elle n’écrit pas de texte un jeton après l’autre.
- Les deux n’exigent aucune étiquette, mais exigent d’énormes quantités de texte. Voilà le coût.
Tomás : « Entraînons notre propre LLM »
Section intitulée « Tomás : « Entraînons notre propre LLM » »Tomás a vu le résultat honnête du tout petit transformeur. « La solution est évidente : on préentraîne notre propre grand modèle de langage sur du texte de soutien. » Priya chiffre cette proposition.
SmolLM2-135M est un petit modèle : 30 blocs et 134 515 008 paramètres. Sa fiche sur Hugging Face indique : « Pretraining tokens : 2T » (jetons de préentraînement : 2 000 milliards), « GPUs : 64 H100 » (processeurs graphiques : 64 H100), « Precision : bfloat16 » (précision : bfloat16). Sa version plus grande, SmolLM2-1.7B, a été « trained on 11 trillion tokens » (entraînée sur 11 000 milliards de jetons) sur 256 processeurs graphiques H100. Une H100 est une carte graphique de centre de données conçue pour l’entraînement.
Comparez cela à Larkfield. Les billets d’entraînement contiennent 269 231 jetons. 2 000 milliards de jetons représentent environ 7 millions de fois plus de texte, pour le plus petit modèle de la famille.
Priya a aussi mesuré la vitesse d’entraînement de SmolLM2-135M sur le Mac mini de ce cours. Il effectue 5 étapes d’entraînement sur un lot de 4 textes × 128 jetons, avec deux cœurs de processeur. Chaque étape prend environ 0,8 seconde : environ 600 jetons par seconde. À cette vitesse, 2 000 milliards de jetons prendraient environ 100 ans. La mémoire pose aussi problème, comme l’explique Les coûts de l’inférence. Les poids, les gradients et les deux moyennes mobiles d’Adam occupent environ 4 × 538 Mo = 2,2 Go pour ce petit modèle, avant les activations. Un modèle de 70 milliards de paramètres exige environ 1,1 To pour ces mêmes éléments.
Le préentraînement n’est pas non plus la dernière étape. Un modèle préentraîné ne fait que continuer du texte. L’ajustement aux instructions et l’entraînement sur les préférences en font ensuite un assistant. Les étapes de l’entraînement, dans le cours Les LLM : comment ils fonctionnent et comment en choisir un, explique ces étapes.
Larkfield ne devrait donc pas faire de préentraînement. Elle devrait partir d’un modèle que quelqu’un d’autre a préentraîné et l’adapter : faire son réglage fin sur les billets ou lui fournir une requête (prompt). Le module 7 exécute un tel modèle. L’étude de cas Faire le réglage fin d’un petit transformeur mesure l’apport du préentraînement par rapport au tout petit transformeur, avec les mêmes données.
Exercices guidés : construire les exemples dans le carnet
Section intitulée « Exercices guidés : construire les exemples dans le carnet »- Ouvrez le carnet à partir de l’encadré en haut de cette page et exécutez les cellules de préparation.
- Dans la section 1, créez les paires de prédiction du prochain jeton de la phrase de Larkfield. Vous voyez 10 lignes, de
my -> parcel(mon → colis) àlamp -> .(lampe → point). - Dans la cellule Your turn (à vous de jouer), mettez le nombre d’exemples de prédiction du prochain jeton des billets de validation dans
valid_examples: leur nombre de jetons moins un par billet. Exécutez la cellule de vérification. Elle afficheCheck passed.(vérification réussie). - Dans la section 2, créez la version masquée avec la graine 3. Vous voyez
[MASK]à la place de « broken » et de « lamp » (lampe). - Dans la section 3, entraînez les deux modèles de prédiction du prochain mot pendant 3 époques chacun. Vous voyez une perte d’entraînement plus faible sans masque.
- Mesurez les deux sur les données de validation avec le futur caché. Le modèle sans masque a la perte la plus élevée.
Afficher un exemple de réponse à l’étape 3
valid_examples = sum(len(tokenize(row["text"])) - 1 for row in valid_rows)print(valid_examples)Chaque billet de n jetons donne n − 1 exemples : chaque jeton après le premier sert de réponse une fois. La cellule de vérification compare votre nombre sans l’afficher.
À vous de jouer : une fenêtre plus longue
Section intitulée « À vous de jouer : une fenêtre plus longue »Changez une condition : les modèles de prédiction du prochain mot lisent les 96 premiers jetons de chaque billet au lieu de 48. Avant l’exécution, faites une prédiction : quelle est la forme des scores pour 2 billets? L’ancien modèle, conçu pour 48 positions, fonctionne-t-il encore? Le modèle sans masque semble-t-il encore meilleur pendant l’entraînement et moins bon avec le futur caché?
Afficher la réponse
seq_train, seq_valid = sequences(train_rows, length=96), sequences(valid_rows, length=96)try: NextWordModel(len(vocab))(seq_valid[:2, :-1]) # the position table has 48 rowsexcept IndexError as error: print("IndexError:", error)
def train_96(causal, epochs=3): set_seed(0) model = NextWordModel(len(vocab), length=96) opt = torch.optim.Adam(model.parameters(), lr=1e-3) for epoch in range(epochs): model.train() order = torch.randperm(len(seq_train)) for i in range(0, len(order), 32): opt.zero_grad() loss = next_word_loss(model, seq_train[order[i:i + 32]], causal=causal) loss.backward() opt.step() return model.eval(), round(loss.item(), 4)
honest96, last_honest = train_96(causal=True)cheater96, last_cheater = train_96(causal=False)with torch.no_grad(): print("scores", tuple(honest96(seq_valid[:2, :-1]).shape)) print("training loss, last step: with mask", last_honest, "| without mask", last_cheater) print("valid, future hidden: with mask", round(next_word_loss(honest96, seq_valid).item(), 3), "| without mask", round(next_word_loss(cheater96, seq_valid, causal=True).item(), 3))seq_train, seq_valid = sequences(train_rows), sequences(valid_rows) # back to 48 tokensIndexError: index out of range in selfscores (2, 95, 2864)training loss, last step: with mask 3.6497 | without mask 2.6696valid, future hidden: with mask 3.629 | without mask 5.624L’ancien modèle s’arrête : sa table de positions contient 48 lignes, et la position 48 n’existe pas. Avec une table de 96 lignes, les scores ont la forme (2, 95, 2864). Il y a une ligne de scores pour chaque position qui a un prochain jeton. La tendance reste la même. Sans masque, la perte d’entraînement est plus faible (2,67 contre 3,65). Avec le futur caché, la perte est plus élevée (5,624 contre 3,629). L’écart est plus petit qu’avec 48 jetons, car en 3 époques, ce modèle a moins bien appris à copier. Il apprend encore la mauvaise compétence.
Erreur courante
Entraîner un modèle de prédiction du prochain jeton sans masque causal
Symptôme : la perte d’entraînement descend vite et très bas : 0,54 contre 3,41 pour un modèle avec masque. Mais dans la boucle de génération, le modèle écrit « my parcel my problem problem problem … » (mon colis mon problème problème problème…). Sa perte avec le futur caché est de 11,345.
Cause : sans masque, la position i peut lire le jeton i + 1, qui est sa propre réponse. Le modèle apprend à copier cette réponse depuis l’entrée. Quand il écrit, aucun prochain jeton n’est disponible à copier. Il n’a donc rien appris d’utile.
Solution : utilisez le masque causal dans chaque modèle de prédiction du prochain jeton, pendant l’entraînement et la génération. Évaluez un décodeur dans ses conditions d’utilisation : avec le futur caché et quelques exemples générés, pas seulement la perte d’entraînement.
Ce que cela signifie pour un vrai système d’IA : une perte d’entraînement trop belle indique souvent que le modèle voit sa réponse. C’est la même fuite que lorsque des données de test passent dans l’entraînement. Voir Données d’entraînement, de validation et de test, dans le cours Apprentissage automatique : du problème à un modèle fiable.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Pourquoi la prédiction du prochain jeton et des jetons masqués n'exige-t-elle aucune étiquette humaine? Donnez les exemples d'entraînement que la phrase « parcel arrived late » fournit pour la prédiction du prochain jeton.
Afficher la réponse
Les deux objectifs cachent une partie d’un vrai texte et demandent au modèle de la prédire. La partie cachée est la bonne réponse. Le texte est donc son propre corrigé, et personne n’a à écrire d’étiquette. C’est l’apprentissage autosupervisé. Les trois jetons « parcel arrived late » (colis arrivé en retard) donnent deux exemples de prédiction du prochain jeton : « parcel » → « arrived » et « parcel arrived » → « late ». Pour la prédiction des jetons masqués, on pourrait cacher « arrived » et demander de le retrouver à partir de « parcel [MASK] late ».
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 dit : SmolLM2-135M tient dans 2,2 Go pour l'entraînement, donc Larkfield peut en préentraîner un sur un portable. Qu'oublie-t-il?
Afficher la réponse
La mémoire n’est qu’un coût; la quantité de texte et le temps de calcul sont les coûts les plus importants. La fiche du modèle indique 2 000 milliards de jetons de préentraînement sur 64 processeurs graphiques H100. Les billets d’entraînement de Larkfield contiennent 269 231 jetons, soit environ 7 millions de fois moins de texte. Sur le Mac mini du cours, SmolLM2-135M s’entraîne à environ 600 jetons par seconde. Ainsi, 2 000 milliards de jetons prendraient environ 100 ans. Les 2,2 Go comptent aussi seulement les poids, les gradients et les états d’Adam, sans les activations. Les modèles plus grands exigent bien plus : environ 1,1 To pour un modèle de 70 milliards de paramètres. Larkfield devrait plutôt adapter un modèle préentraîné.
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 entraînez un tout petit transformeur de classification et annotez ses formes d’entrée et de sortie. Suivez d’abord l’exemple détaillé, puis faites l’exercice par vous-même.
Exemple détaillé : les formes d’un transformeur plus petit
Section intitulée « Exemple détaillé : les formes d’un transformeur plus petit »Priya construit un TinyTransformer plus petit : 32 nombres par jeton, 2 têtes, 1 bloc. Elle y fait passer 4 billets de soutien de validation, étape par étape, et affiche la forme après chaque étape.
from ticketnet import TinyTransformer, token_ids, labels, train, count_parameters, TEAMS
set_seed(0)small = TinyTransformer(len(vocab), dim=32, heads=2, blocks=1)small.eval()ids = token_ids(valid_rows[:4], vocab)with torch.no_grad(): h = small.embed(ids) + small.position(torch.arange(ids.shape[1])) print("ids ", tuple(ids.shape)) print("token + position ", tuple(h.shape)) h = small.blocks(h, src_key_padding_mask=ids == PAD) print("after 1 block ", tuple(h.shape)) keep = (ids != PAD).unsqueeze(-1).float() mean = (small.norm(h) * keep).sum(1) / keep.sum(1) print("average per ticket", tuple(mean.shape)) print("scores ", tuple(small.out(mean).shape))print("parameters", count_parameters(small))La sortie :
ids (4, 93)token + position (4, 93, 32)after 1 block (4, 93, 32)average per ticket (4, 32)scores (4, 5)parameters 108421Ses annotations, une ligne par forme :
- (4, 93) : 4 billets, complétés par du remplissage jusqu’au plus long, soit 93 identifiants de jetons.
- (4, 93, 32) : chaque identifiant de jeton devient 32 nombres, son vecteur de jeton plus son vecteur de position.
- (4, 93, 32) : le bloc conserve la forme; le masque de remplissage cache le remplissage.
- (4, 32) : la moyenne des jetons réels de chaque billet élimine la dimension des jetons.
- (4, 5) : un score pour chacune des 5 équipes.
Votre tâche
Section intitulée « Votre tâche »Entraînez small sur les billets d’entraînement pendant 5 époques. Utilisez train(), une graine de 0 et un taux d’apprentissage de 0,001. Faites ensuite passer les 16 premiers billets de validation dans le modèle entraîné. Avant l’exécution, notez la forme des identifiants d’entrée et celle des scores, et annotez chacune.
Afficher le corrigé
ids_train, ids_valid = token_ids(train_rows, vocab), token_ids(valid_rows, vocab)y_train, y_valid = labels(train_rows), labels(valid_rows)set_seed(0)small = TinyTransformer(len(vocab), dim=32, heads=2, blocks=1)history = train(small, ids_train, y_train, ids_valid, y_valid, epochs=5, lr=1e-3, log=None)for row in history: print(row)
small.eval()batch16 = token_ids(valid_rows[:16], vocab)with torch.no_grad(): scores = small(batch16)print("ids", tuple(batch16.shape), "-> scores", tuple(scores.shape))print("predicted:", [TEAMS[i] for i in scores.argmax(1)[:4].tolist()])print("real: ", [row["team"] for row in valid_rows[:4]]){'epoch': 1, 'train_loss': 1.4929, 'train_acc': 0.3844, 'valid_loss': 1.4961, 'valid_acc': 0.355}{'epoch': 2, 'train_loss': 1.0709, 'train_acc': 0.5901, 'valid_loss': 1.0662, 'valid_acc': 0.6083}{'epoch': 3, 'train_loss': 0.6611, 'train_acc': 0.7621, 'valid_loss': 0.6765, 'valid_acc': 0.7417}{'epoch': 4, 'train_loss': 0.4474, 'train_acc': 0.8441, 'valid_loss': 0.4822, 'valid_acc': 0.81}{'epoch': 5, 'train_loss': 0.322, 'train_acc': 0.8963, 'valid_loss': 0.358, 'valid_acc': 0.8617}ids (16, 120) -> scores (16, 5)predicted: ['payment', 'delivery', 'delivery', 'delivery']real: ['payment', 'delivery', 'delivery', 'delivery']Formes : (16, 120) représente 16 billets complétés par du remplissage jusqu’à 120 identifiants de jetons. C’est parce qu’un de ces 16 billets atteint la limite de 120 jetons. À l’intérieur, les formes sont (16, 120, 32) après les plongements et après le bloc, puis (16, 32) après la moyenne. Les scores ont la forme (16, 5) : un score pour chaque équipe.
Résultat : après 5 époques, le petit modèle classe correctement 86,2 % des billets de validation, et sa perte diminue encore. Il compte 108 421 paramètres, moins que les 291 397 du tout petit transformeur, et il apprend encore. Comme le modèle plus grand, il commence sans aucune connaissance du langage.
Vérification des connaissances du module
Section intitulée « Vérification des connaissances du module »Vérifiez votre compréhension
Rappel : que contient un bloc de transformeur, et à quoi servent les deux masques d'un transformeur?
Afficher la réponse
Un bloc contient une attention multitête et une couche à propagation avant pour chaque jeton. Chacune possède une connexion résiduelle (ajouter la sortie de la partie à son entrée) et une normalisation de couche (remettre à l’échelle les nombres de chaque jeton). La forme d’entrée égale la forme de sortie, ce qui permet d’empiler les blocs. Le masque de remplissage cache les jetons de remplissage. Ainsi, le résultat d’un billet ne dépend pas de la longueur de son lot. Le masque causal cache tous les jetons après le jeton courant. Ainsi, un modèle qui prédit le prochain jeton ne peut pas le lire.
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 : vous mettez à zéro la table de positions du tout petit transformeur entraîné. Vous lui soumettez un billet, puis le même billet avec les mots mélangés. Qu'arrive-t-il aux scores, et pourquoi?
Afficher la réponse
Les deux ensembles de scores sont exactement les mêmes : dans la leçon, ils étaient −3,571, −2,937, 10,644, −2,797 et 0,005. L’attention compare chaque requête à chaque clé, peu importe leurs positions. La couche à propagation avant, les normalisations et la moyenne traitent tous les jetons de la même façon. Sans vecteurs de position, rien dans le modèle ne dépend de l’ordre. Mélanger les mots ne fait que mélanger les sorties des jetons, et leur moyenne reste la même.
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 pourquoi un modèle causal ne doit pas examiner les jetons futurs pendant l'entraînement.
Afficher la réponse
Un modèle causal apprend à prédire le prochain jeton. À chaque position, la réponse est le prochain jeton du même texte. Si une position pouvait lire les jetons qui la suivent, elle pourrait copier sa réponse dans l’entrée plutôt que la prédire. La perte d’entraînement semblerait très bonne, mais le modèle apprendrait à copier, pas à prédire. Quand le modèle écrit du texte, les jetons futurs n’existent pas encore. Il n’y a donc rien à copier, et le modèle échoue. Dans la leçon, le modèle entraîné sans masque a atteint une perte d’entraînement de 0,54, contre 3,41 avec le masque. Avec les jetons futurs cachés, sa perte de validation était de 11,345, pire qu’une réponse au hasard. Il a écrit « mon colis mon problème problème … ». Le masque causal fait correspondre l’entraînement à la génération : chaque position voit seulement ce qu’elle verra au moment d’écrire.
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 assemblé un transformeur et vu comment on l’entraîne :
- Un bloc contient une attention multitête et une couche à propagation avant, chacune avec une connexion résiduelle et une normalisation de couche; la forme reste la même, ce qui permet d’empiler les blocs.
- Les plongements de position donnent à l’attention l’ordre des mots. Le masque de remplissage cache le remplissage; le masque causal cache les jetons futurs. Les encodeurs lisent tout; les décodeurs lisent seulement le passé.
- Entraîné à partir de zéro sur 4 696 billets, le tout petit transformeur est le modèle d’acheminement le moins performant de Larkfield : 91,2 % sur les données de test, contre 99,2 % pour le modèle linéaire.
- La prédiction du prochain jeton et la prédiction des jetons masqués transforment du texte brut en exemples d’entraînement sans étiquettes. Sans masque causal, un modèle apprend à copier.
- Le préentraînement à l’échelle de SmolLM2-135M demande 2 000 milliards de jetons sur 64 processeurs graphiques de centre de données : environ 100 ans sur le Mac de ce cours. Adaptez plutôt un modèle préentraîné.
Réinitialisation : dans le carnet (notebook) Jupyter, sélectionnez Runtime › Disconnect and delete runtime (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, ou supprimez .venv et recréez-le comme dans Réinitialiser la configuration. Entraînez ensuite de nouveau le tout petit transformeur avec une graine de 0, comme dans Les blocs du transformeur : entraînez-le.
Ensuite, dans le module 7, vous chargez un modèle préentraîné par quelqu’un d’autre et examinez ses 30 blocs grâce à vos acquis.
Leçon suivante : Entraînement et inférence