CSC 8614 – Modèles de langage

Portail informatique

CI4 : Modèles de langage

Dans ce TP, vous allez découvrir concrètement comment fonctionnent les modèles Transformers à travers la bibliothèque HuggingFace Transformers. Nous allons approfondir ensemble des notions fondamentales telles que la tokenisation, l'encodage positionnel, la génération de texte ainsi que les différentes méthodes de génération proposées par les modèles pré-entraînés comme GPT-2.

Objectifs du TP :

  • Comprendre le processus de tokenisation et notamment la méthode BPE (Byte Pair Encoding).
  • Visualiser et analyser le rôle des encodages positionnels dans un modèle Transformer.
  • Calculer des probabilités (et log-probabilités) de génération d'une phrase à partir d'un modèle GPT-2.
  • Expérimenter et comparer différentes méthodes de génération : décodage glouton, échantillonnage (top-k, top-p, température), recherche par faisceau (beam search).

Tout au long du TP, vous utiliserez des exemples pratiques et effectuerez des expérimentations pour renforcer votre compréhension.

Rendu (GitHub) et rapport Markdown — à lire avant de commencer

Pour ce TP, vous rendrez un dépôt GitHub contenant à la fois le code et un mini-rapport. Le rendu se fait via l’envoi de votre dépôt à l’enseignant.

Organisation attendue du dépôt

  • Créez un dossier TP1/ à la racine du dépôt.
  • Placez tout le code de ce TP dans TP1/ (scripts .py et/ou notebook .ipynb).
  • Créez un fichier de rapport Markdown : TP1/rapport.md.
  • Si vous ajoutez des images (captures), stockez-les dans TP1/img/ et référencez-les en chemins relatifs depuis le rapport.
  • Ajoutez un fichier TP1/requirements.txt (minimum : transformers, torch, plotly, scikit-learn ; ou bien un pip freeze si vous préférez).
  • Ajoutez un .gitignore (au moins : caches, environnements virtuels, fichiers volumineux non nécessaires).

Rapport (mini-rapport)

  • Le rapport TP1/rapport.md se remplit au fur et à mesure des exercices.
  • Vous devez y mettre : réponses courtes, résultats observés (copie de sorties), captures d’écran demandées, et une courte interprétation.
  • Ne collez pas des pages entières : soyez concis et sélectionnez les éléments pertinents.

Reproductibilité

  • Fixez un seed (au moins pour les expériences de génération) et notez-le dans le rapport.
  • Indiquez dans le rapport : version Python, OS, et versions des principales bibliothèques.

Dans TP1/rapport.md, ajoutez immédiatement un court en-tête (quelques lignes) contenant : (i) votre nom/prénom, (ii) la commande d’installation/activation d’environnement utilisée, (iii) les versions (Python + bibliothèques principales).

Ajoutez ensuite au fil du TP des sections/titres à votre convenance, tant que l’on peut retrouver clairement vos réponses et vos preuves d’exécution.

Découverte du tokenizer GPT-2

Dans cet exercice, vous allez découvrir le concept de tokenisation, qui est une étape fondamentale du traitement du langage naturel (NLP). La tokenisation consiste à découper une phrase ou un texte en unités élémentaires appelées tokens. Nous utiliserons un tokenizer pré-entraîné basé sur le mécanisme de Byte Pair Encoding (BPE). Le BPE est une méthode permettant de découper des mots en sous-mots en fonction de leur fréquence dans les données d'entraînement du modèle.

Pour cet exercice, nous utiliserons le tokenizer associé au modèle GPT-2 de Hugging Face. Il vous faudra pour cela installer la bibliothèque transformers. Vous pouvez consulter la documentation complète ici.

Créez un fichier TP1/ex1_tokenizer.py. Chargez le tokenizer GPT-2 depuis Transformers, puis affichez les tokens générés pour la phrase suivante :
"Artificial intelligence is metamorphosing the world!"

Gabarit de code (à compléter)

from transformers import GPT2Tokenizer tokenizer = GPT2Tokenizer.from_pretrained("gpt2") phrase = "Artificial intelligence is metamorphosing the world!" # TODO: tokeniser la phrase tokens = tokenizer.________(phrase) print(tokens)

Expliquez ensuite : pourquoi certains tokens commencent-ils par un symbole “spécial” ?

Dans votre rapport :

  • Copiez la sortie de votre programme (liste de tokens).
  • Donnez une explication en 3–6 lignes sur le rôle du préfixe/symbole observé.
  • Ajoutez une capture d’écran de l’exécution (terminal ou notebook).

Toujours dans TP1/ex1_tokenizer.py, obtenez maintenant les identifiants (token IDs) correspondant à chaque token. Affichez : (i) la liste des IDs, puis (ii) pour chaque ID, son décodage individuel (pour vérifier).

Gabarit de code (à compléter)

# TODO: obtenir les IDs token_ids = tokenizer.________(phrase) print("Token IDs:", token_ids) print("Détails par token:") for tid in token_ids: # TODO: décoder un seul token id txt = tokenizer.________([tid]) print(tid, repr(txt))

Dans votre rapport :

  • Ajoutez un petit tableau (Markdown) avec 6–10 lignes montrant : token (ou décodage), ID, remarque éventuelle (ex : espace, ponctuation).
  • Expliquez brièvement la différence entre tokens et token IDs.

Observez les tokens générés. Quelles remarques faites-vous sur la façon dont GPT-2 découpe : (i) les mots, (ii) les mots rares/longs, (iii) la ponctuation, (iv) les espaces ?

Dans votre rapport :

  • Donnez au moins 3 observations concrètes (basées sur vos sorties).
  • Reliez explicitement ces observations à l’intuition “BPE / sous-mots / réutilisation de fragments fréquents”.

Expérimentez maintenant avec la phrase suivante :

"GPT models use BPE tokenization to process unusual words like antidisestablishmentarianism."

Observez le résultat de la tokenisation. Que constatez-vous concernant le découpage du mot antidisestablishmentarianism ?

Gabarit de code (à compléter)

phrase2 = "GPT models use BPE tokenization to process unusual words like antidisestablishmentarianism." tokens2 = tokenizer.________(phrase2) print(tokens2) # TODO: extraire uniquement les tokens correspondant au mot long (optionnel mais recommandé) # TODO: compter le nombre de sous-tokens

Dans votre rapport :

  • Copiez la liste des tokens (ou un extrait centré sur le mot long).
  • Donnez le nombre de sous-tokens pour antidisestablishmentarianism.
  • Expliquez en 3–6 lignes pourquoi ce mot est découpé ainsi.

Analyse des encodages positionnels dans GPT-2

Dans ce deuxième exercice, nous allons nous intéresser à l'encodage positionnel (positional encoding), une caractéristique essentielle des Transformers. Contrairement aux modèles récurrents (RNN), les Transformers ne traitent pas les données de manière séquentielle. Ils utilisent plutôt un mécanisme d'attention qui ne prend pas en compte par défaut l'ordre des tokens dans une phrase. C'est pour cette raison qu'ils ont recours à l'encodage positionnel afin d'intégrer explicitement l'information de la position des mots.

Dans cet exercice, nous allons visualiser les encodages positionnels appris (learned positional embeddings) utilisés par GPT-2.

Créez un fichier TP1/ex2_positions.py. Chargez le modèle GPT-2 et extrayez les encodages positionnels. Affichez la taille (shape) de la matrice d’encodage positionnel, ainsi que deux champs de configuration utiles (par exemple : dimension d’embedding et longueur de contexte maximale).

Gabarit de code (à compléter)

from transformers import GPT2Model model = GPT2Model.from_pretrained("gpt2") # TODO: récupérer la matrice des embeddings positionnels position_embeddings = model._____._____ print("Shape position embeddings:", position_embeddings.size()) # TODO: afficher quelques infos de config utiles print("n_embd:", model.config.______) print("n_positions:", model.config.______)

Dans votre rapport :

  • Donnez la shape et interprétez-la (que représentent les deux dimensions ?).
  • Expliquez ce que signifie n_positions pour un modèle de langage causal (contexte maximum).

Visualisez les encodages positionnels en 2D via une PCA et Plotly. Sauvegardez le graphique sous forme de fichier HTML nommé TP1/positions_50.html pour les 50 premières positions.

Gabarit de code (à compléter)

import plotly.express as px from sklearn.decomposition import PCA # TODO: extraire les 50 premières positions (tensor -> numpy) positions = position_embeddings[:____].detach().cpu().numpy() pca = PCA(n_components=2) reduced = pca.fit_transform(positions) fig = px.scatter( x=reduced[:, 0], y=reduced[:, 1], text=[str(i) for i in range(len(reduced))], color=list(range(len(reduced))), title="Encodages positionnels GPT-2 (PCA, positions 0-50)", labels={"x": "PCA 1", "y": "PCA 2"} ) # TODO: sauver dans TP1/positions_50.html fig.write_html("TP1/__________.html")

Dans votre rapport :

  • Ajoutez une capture d’écran du nuage de points (ouvrez le HTML dans un navigateur).
  • Décrivez en 5–10 lignes ce que vous observez (continuité, regroupements, trajectoire, etc.).
  • Expliquez l’intérêt de la PCA ici (dimension originale vs visualisation).

Réalisez la même visualisation pour les positions 0 à 200 et sauvegardez le graphique dans TP1/positions_200.html.

Dans votre rapport :

  • Ajoutez une capture d’écran du nuage de points (positions 0–200).
  • Comparez (0–50) vs (0–200) : que change l’échelle ? voyez-vous une structure plus/moins lisible ?
  • Donnez une hypothèse (en 5–10 lignes) sur ce que cela implique pour la représentation des positions.

Probabilités et génération de texte avec GPT-2

Dans ce troisième exercice, nous allons étudier comment les modèles de type Transformer génèrent du texte. Plus précisément, nous allons analyser comment un modèle comme GPT-2 attribue des probabilités à chaque token en fonction des tokens précédents (modélisation causale).

Créez un fichier TP1/ex3_probs.py. Chargez GPT2LMHeadModel et le tokenizer correspondant. Calculez ensuite, pour chaque token de la phrase :

"Artificial intelligence is fascinating."

la probabilité conditionnelle attribuée par le modèle au token effectivement observé.

Gabarit de code (à compléter)

import torch from transformers import GPT2LMHeadModel, GPT2Tokenizer model = GPT2LMHeadModel.from_pretrained("gpt2") tokenizer = GPT2Tokenizer.from_pretrained("gpt2") phrase = "Artificial intelligence is fascinating." inputs = tokenizer(phrase, return_tensors="pt") with torch.no_grad(): outputs = model(**inputs) logits = outputs.logits # (1, seq_len, vocab) # TODO: convertir en probabilités (softmax) probs = torch.________(logits, dim=-1) # On affiche P(token_t | tokens_) pour t>=1 input_ids = inputs["input_ids"][0] for t in range(1, len(input_ids)): tok_id = input_ids[t].item() p = probs[0, t-1, tok_id].item() tok_txt = tokenizer.decode([tok_id]) print(t, repr(tok_txt), f"{p:.3e}")

Dans votre rapport :

  • Copiez un extrait représentatif de la sortie (pas forcément tout si c’est long).
  • Expliquez précisément l’alignement : pourquoi on lit la proba du token t dans les logits à la position t-1 ?

Calculez maintenant la log-probabilité totale de la phrase (somme des log-probas conditionnelles), puis la perplexité associée.

Conseil : évitez le produit direct des probabilités (sous-flux numérique).

Gabarit de code (à compléter)

import math import torch log_probs = torch.log_softmax(logits, dim=-1) input_ids = inputs["input_ids"][0] total_logp = 0.0 n = 0 for t in range(1, len(input_ids)): tok_id = input_ids[t].item() lp = log_probs[0, t-1, tok_id].item() total_logp += _________ n += __________ avg_neg_logp = - ________________ ppl = math.exp(avg_neg_logp) print("total_logp:", total_logp) print("avg_neg_logp:", avg_neg_logp) print("perplexity:", ppl)

Dans votre rapport :

  • Donnez la log-proba totale et la perplexité (valeurs numériques).
  • Expliquez en 5–10 lignes ce que mesure la perplexité (interprétation intuitive).

Comparez (log-proba et perplexité) entre :

"Artificial intelligence is fascinating."
"Artificial fascinating intelligence is."

Que constatez-vous ? Pourquoi ?

Dans votre rapport :

  • Donnez les deux perplexités et commentez l’écart.
  • Expliquez en 5–10 lignes le lien avec la grammaticalité / la distribution d’entraînement / les régularités apprises.

Comparez maintenant avec une phrase en français :

"L'intelligence artificielle est fascinante."

Que constatez-vous et pourquoi ?

Dans votre rapport :

  • Donnez la perplexité et comparez qualitativement aux phrases anglaises.
  • Proposez une explication courte : distribution des données, fréquence des tokens, mélange de langues, etc.

Pour le préfixe "Artificial intelligence is", affichez les 10 tokens les plus probables pour le token suivant, avec leurs probabilités.

Gabarit de code (à compléter)

prefix = "Artificial intelligence is" inp = tokenizer(prefix, return_tensors="pt") with torch.no_grad(): out = model(**inp) logits2 = out.logits # (1, seq_len, vocab) # TODO: récupérer la distribution pour le prochain token (dernier pas de temps) last_logits = logits2[0, __________, :] last_probs = torch.softmax(last_logits, dim=-1) topk = 10 vals, idx = torch.topk(last_probs, k=topk) for p, tid in zip(vals.tolist(), idx.tolist()): print(repr(tokenizer.decode([tid])), f"{p:.3e}")

Dans votre rapport :

  • Copiez les 10 propositions (token + proba).
  • Commentez brièvement : est-ce plausible ? voyez-vous des tokens d’espace/ponctuation ?

Exploration des méthodes de génération avec GPT-2

Dans ce quatrième exercice, nous allons explorer plusieurs méthodes de génération de texte à partir d'un modèle Transformer comme GPT-2. Nous analyserons comment différents paramètres et techniques influencent le texte produit par le modèle.

  • Décodage glouton (Greedy decoding)
  • Décodage par échantillonnage (Sampling) : température, top-k, top-p, pénalité de répétition
  • Recherche par faisceau (Beam search)

Créez un fichier TP1/ex4_generation.py. Chargez GPT-2 et le tokenizer. Fixez un seed pour rendre vos résultats reproductibles.

Gabarit de code (à compléter)

import torch from transformers import GPT2LMHeadModel, GPT2Tokenizer SEED = ________ # TODO torch.manual_seed(SEED) model = GPT2LMHeadModel.from_pretrained("gpt2") tokenizer = GPT2Tokenizer.from_pretrained("gpt2") prompt = "The future of artificial intelligence is" inputs = tokenizer(prompt, return_tensors="pt")

Dans votre rapport :

  • Indiquez le seed utilisé et pourquoi on le fixe ici.

Générez une suite avec décodage glouton (greedy decoding), longueur maximale 50 tokens.

Gabarit de code (à compléter)

outputs = model.generate( **inputs, max_length=___________, ) text = tokenizer.decode(outputs[0], skip_special_tokens=True) print(text)

Dans votre rapport :

  • Copiez le texte généré.
  • Relancez 3 fois : est-ce identique ? expliquez (2–5 lignes).
  • Ajoutez une capture d’écran d’une exécution.

Générez maintenant du texte avec sampling en utilisant : température = 0.7, top-k = 50, top-p = 0.95. Faites au moins 5 générations (en changeant le seed entre chaque, ou en utilisant un générateur).

Gabarit de code (à compléter)

def generate_once(seed): torch.manual_seed(seed) out = model.generate( **inputs, max_length=________, do_sample=True, temperature=________, top_k=________, top_p=________, ) return tokenizer.decode(out[0], skip_special_tokens=True) for s in [1, 2, 3, 4, 5]: print("SEED", s) print(generate_once(s)) print("-" * 40)

Dans votre rapport :

  • Copiez au moins 2 sorties différentes.
  • Comparez au greedy : diversité, cohérence, répétitions (5–10 lignes).
  • Expliquez qualitativement le rôle de température / top-k / top-p.

Ajoutez une pénalité de répétition élevée (repetition_penalty=2.0) et comparez.

Dans votre rapport :

  • Donnez au moins 1 sortie “avec pénalité” et 1 “sans pénalité” (mêmes paramètres, seed contrôlé).
  • Commentez : observe-t-on moins de répétitions ? y a-t-il des effets secondaires ?

Essayez une température très basse puis très élevée (par exemple 0.1 et 2.0). Gardez top-k=50, top-p=0.95, et comparez.

Dans votre rapport :

  • Donnez 1 sortie pour chaque température.
  • Expliquez le compromis “cohérence vs diversité” (5–10 lignes).

Génération avec beam search : num_beams=5, longueur max 50. Comparez avec greedy et sampling.

Gabarit de code (à compléter)

out_beam = model.generate( **inputs, max_length=________, num_beams=_______, early_stopping=True ) txt_beam = tokenizer.decode(out_beam[0], skip_special_tokens=True) print(txt_beam)

Dans votre rapport :

  • Copiez la sortie beam search.
  • Comparez qualitativement : “plus probable globalement”, “plus générique”, “moins divers”, etc.

Augmentez le nombre de beams (par exemple 10 puis 20). Mesurez le temps de génération (même approximatif) et commentez.

Dans votre rapport :

  • Donnez vos temps mesurés et les paramètres.
  • Expliquez pourquoi cela ralentit (complexité / exploration de chemins).