CSC 8608 – Concepts avancés et applications du deep learning

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 les probabilités de génération d'une phrase à partir d'un modèle GPT-2 en utilisant la dérivation causale.
  • Expérimenter et comparer différentes méthodes de génération telles que le décodage glouton, les méthodes par échantillonnage (top-k, top-p, température) et la 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.

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.

Chargez le tokenizer GPT-2 depuis la bibliothèque Transformers. Écrivez ensuite le code Python permettant d'afficher les tokens générés pour la phrase suivante :
"Artificial intelligence is metamorphosing the world!"
Pourquoi certains tokens commencent-ils par un symbole spécial ?
from transformers import GPT2Tokenizer tokenizer = GPT2Tokenizer.from_pretrained('gpt2') phrase = "Artificial intelligence is metamorphosing the world!" # Tokenisation tokens = tokenizer.tokenize(phrase) print(tokens)

Utilisez maintenant le tokenizer pour obtenir les identifiants (token IDs) correspondant à chaque token. Affichez ces identifiants ainsi que leur décodage en texte afin de vérifier votre résultat.
# Obtention des identifiants des tokens token_ids = tokenizer.encode(phrase) print("Token IDs :", token_ids) # Décodage des identifiants vers le texte decoded_text = tokenizer.decode(token_ids) print("Texte décodé :", decoded_text)

Observez les tokens générés. Quels éléments remarquez-vous concernant la manière dont le tokenizer GPT-2 découpe les mots et la ponctuation ?

Le tokenizer GPT-2 découpe les mots fréquents en tokens entiers, tandis que les mots rares ou complexes sont divisés en sous-mots précédés de l'espace (ex : " artificielle"). De plus, la ponctuation est traitée comme des tokens séparés.

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" ?
nouvelle_phrase = "GPT models use BPE tokenization to process unusual words like antidisestablishmentarianism." nouveaux_tokens = tokenizer.tokenize(nouvelle_phrase) print(nouveaux_tokens)

On remarque que le mot "antidisestablishmentarianism" est découpé en plusieurs sous-mots. Cela est dû à sa rareté dans les données d'entraînement, forçant le tokenizer à le diviser en unités plus fréquentes et réutilisables.

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 utilisés par GPT-2.

Chargez le modèle GPT-2 et extrayez les encodages positionnels utilisés par le modèle. Affichez la taille de ces encodages positionnels.
from transformers import GPT2Model model = GPT2Model.from_pretrained('gpt2') position_embeddings = model.wpe.weight print("Taille des encodages positionnels :", position_embeddings.size())

Nous allons maintenant visualiser les encodages positionnels. Pour cela, nous utilisons la PCA (Principal Component Analysis), une technique statistique permettant de réduire la dimensionnalité d'un jeu de données tout en préservant au mieux les variations (ou informations importantes) contenues dans les données. PCA est utile ici car les embeddings positionnels possèdent une dimension élevée et sont difficiles à représenter directement. Nous utilisons la bibliothèque scikit-learn pour effectuer cette réduction dimensionnelle.

Visualisez les encodages positionnels en deux dimensions avec PCA en utilisant Plotly (par exemple). Sauvegardez le graphique sous forme de fichier HTML nommé positions_50.html pour les 50 premières positions.
import plotly.express as px from sklearn.decomposition import PCA positions = position_embeddings[:50].detach().numpy() # PCA pca = PCA(n_components=2) reduced_positions = pca.fit_transform(positions) # Visualisation avec Plotly fig = px.scatter(x=reduced_positions[:, 0], y=reduced_positions[:, 1], text=[str(i) for i in range(50)], title="Visualisation des encodages positionnels (PCA positions 0-50)", labels={"x": "PCA Composante 1", "y": "PCA Composante 2"}, color=range(50)) fig.write_html("positions_50.html")

Qu'observez-vous sur le résultat obtenu ? Expliquez brièvement la manière dont les positions sont représentées par les embeddings.

Les embeddings positionnels forment souvent des structures continues, où les positions proches dans une séquence (c'est-à-dire avec des indices proches) ont tendance à être proches également dans l'espace d'embedding. Cela aide le modèle à comprendre l'ordre relatif des mots dans une séquence. Cependant, cette représentation n'implique pas forcément une importance plus grande des positions initiales, mais indique simplement que les positions initiales sont souvent distinctes car elles posent les bases du contexte de la phrase.

Réalisez la même visualisation avec Plotly pour les positions 0 à 200 et sauvegardez le graphique dans un fichier HTML nommé positions_200.html. Commentez les différences observées par rapport au cas précédent (positions 0 à 50).
positions_200 = position_embeddings[:200].detach().numpy() # PCA reduced_positions_200 = pca.fit_transform(positions_200) # Visualisation avec Plotly fig = px.scatter(x=reduced_positions_200[:, 0], y=reduced_positions_200[:, 1], text=[str(i) for i in range(200)], title="Visualisation des encodages positionnels (PCA positions 0-200)", labels={"x": "PCA Composante 1", "y": "PCA Composante 2"}, color=range(200)) fig.write_html("positions_200.html")

On remarque que les positions plus élevées (au-delà de 50) continuent de former des motifs réguliers, mais peuvent être moins clairement regroupées que les positions initiales. Cela suggère que le modèle représente distinctement les positions initiales car elles servent souvent à établir le contexte initial, tandis que les positions plus éloignées varient plus subtilement pour représenter les détails ou nuances ultérieures dans la séquence.

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 mot (ou token) en fonction des mots précédents. On appelle cela la modélisation du langage causal (ou "causal language modeling").

Nous allons utiliser le modèle GPT-2 disponible via la bibliothèque Transformers pour observer les probabilités que le modèle donne à chaque mot dans une phrase donnée.

Chargez le modèle GPT-2 avec la classe GPT2LMHeadModel et le tokenizer correspondant. Calculez les probabilités du modèle pour chacun des mots de la phrase suivante :

"Artificial intelligence is fascinating."
import torch from transformers import GPT2LMHeadModel, GPT2Tokenizer # Chargement du modèle et tokenizer model = GPT2LMHeadModel.from_pretrained('gpt2') tokenizer = GPT2Tokenizer.from_pretrained('gpt2') phrase = "Artificial intelligence is fascinating." inputs = tokenizer(phrase, return_tensors="pt") # Calcul des logits outputs = model(**inputs, labels=inputs["input_ids"]) logits = outputs.logits # Calcul des probabilités probabilities = torch.softmax(logits, dim=-1) # Affichage des probabilités pour chaque mot for i, token_id in enumerate(inputs["input_ids"][0][1:]): prob = probabilities[0, i, token_id].item() token = tokenizer.decode(token_id) print(f"Mot: '{token}', Probabilité: {prob:.2E}")

Comment calcule-t-on la probabilité totale d'une phrase à partir des probabilités individuelles obtenues précédemment ? Écrivez le code qui permet de faire ce calcul.
import numpy as np # Calcul de la probabilité totale (produit des probabilités conditionnelles) probs = [probabilities[0, i, token_id].item() for i, token_id in enumerate(inputs["input_ids"][0][1:])] total_prob = np.prod(probs) print(f"Probabilité totale de la phrase : {total_prob:.10E}")

Observez les résultats obtenus. Qu'est-ce que cela nous apprend sur la manière dont GPT-2 évalue la plausibilité d'une phrase ?

La probabilité totale d'une phrase est calculée comme le produit des probabilités conditionnelles de chaque mot étant donné tous les mots précédents. Plus cette probabilité est élevée, plus le modèle considère la phrase comme plausible ou naturelle. Des phrases inhabituelles ou non grammaticales auront typiquement des probabilités très faibles, car le modèle n'aura pas vu de combinaisons similaires pendant son entraînement.

Comparez maintenant la probabilité totale obtenue précédemment avec la probabilité de la phrase suivante :

"Artificial fascinating intelligence is."

Que constatez-vous et pourquoi ?
phrase_non_grammaticale = "Artificial fascinating intelligence is." inputs_ng = tokenizer(phrase_non_grammaticale, return_tensors="pt") outputs_ng = model(**inputs_ng, labels=inputs_ng["input_ids"]) logits_ng = outputs_ng.logits probabilities_ng = torch.softmax(logits_ng, dim=-1) probs_ng = [probabilities_ng[0, i, token_id].item() for i, token_id in enumerate(inputs_ng["input_ids"][0][1:])] total_prob_ng = np.prod(probs_ng) print(f"Probabilité totale de la phrase non grammaticale : {total_prob_ng:.10E}")

La probabilité totale de la phrase non grammaticale est considérablement plus faible que celle de la phrase grammaticale. Cela montre que GPT-2 apprend effectivement des régularités grammaticales et contextuelles, attribuant des probabilités plus élevées aux séquences qui respectent l'ordre habituel et la syntaxe du langage naturel.

Comparez maintenant la probabilité totale obtenue précédemment avec la probabilité de la phrase en français suivante :

"L'intelligence artificielle est fascinante."

Que constatez-vous et pourquoi ?
phrase_francais = "L'intelligence artificielle est fascinante." inputs_ng = tokenizer(phrase_francais, return_tensors="pt") outputs_ng = model(**inputs_ng, labels=inputs_ng["input_ids"]) logits_ng = outputs_ng.logits probabilities_ng = torch.softmax(logits_ng, dim=-1) probs_ng = [probabilities_ng[0, i, token_id].item() for i, token_id in enumerate(inputs_ng["input_ids"][0][1:])] total_prob_ng = np.prod(probs_ng) print(f"Probabilité totale de la phrase en français : {total_prob_ng:.10E}")

La probabilité est beaucoup plus faible qu'avant. GPT-2 a été entraîné sur des textes anglais, et ne connaît pas le français.

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. Les méthodes que nous allons étudier sont les suivantes :

  • Décodage glouton (Greedy decoding)
  • Décodage par échantillonnage (Sampling) avec les paramètres : température, top-k, top-p (nucleus sampling) et pénalité de répétition.
  • Recherche par faisceau (Beam search)

Pour toutes ces expérimentations, nous utiliserons le modèle GPT-2 disponible dans la bibliothèque Transformers.

Chargez le modèle GPT-2 ainsi que le tokenizer associé. Générez une suite à partir du prompt suivant en utilisant le décodage glouton (greedy decoding) :

"The future of artificial intelligence is"

Utilisez une longueur maximale de 50 tokens.
from transformers import GPT2LMHeadModel, GPT2Tokenizer model = GPT2LMHeadModel.from_pretrained('gpt2') tokenizer = GPT2Tokenizer.from_pretrained('gpt2') prompt = "The future of artificial intelligence is" inputs = tokenizer(prompt, return_tensors="pt") outputs = model.generate(**inputs, max_length=50) text = tokenizer.decode(outputs[0], skip_special_tokens=True) print(text)

Refaites plusieurs fois la génération. Que constatez-vous ?
On obtient toujours la même sortie ! Glouton prend toujours le token le plus probable, il n'y a donc pas de variation.

Générez maintenant du texte avec un décodage par échantillonnage (sampling) en variant les paramètres suivants : température à 0.7, top-k à 50 et top-p à 0.95. Affichez le résultat et commentez brièvement les différences observées par rapport au décodage glouton.
outputs_sampling = model.generate( **inputs, max_length=50, do_sample=True, temperature=0.7, top_k=50, top_p=0.95 ) text_sampling = tokenizer.decode(outputs_sampling[0], skip_special_tokens=True) print(text_sampling)

Le décodage par échantillonnage produit généralement des textes plus variés et créatifs que le décodage glouton, qui tend à produire des textes plus répétitifs et prévisibles. Les paramètres de température et top-k/top-p permettent de contrôler le degré de créativité ou de diversité des phrases générées.

Générez maintenant du texte avec une pénalité de répétition élevée (repetition_penalty=2.0). Observez-vous une différence significative ? Expliquez.
outputs_rep_penalty = model.generate( **inputs, max_length=50, do_sample=True, temperature=0.7, top_k=50, top_p=0.95, repetition_penalty=2.0 ) text_rep_penalty = tokenizer.decode(outputs_rep_penalty[0], skip_special_tokens=True) print(text_rep_penalty)

La pénalité de répétition élevée empêche le modèle de répéter des phrases ou mots identiques trop fréquemment, rendant le texte généré souvent plus cohérent et moins répétitif, ce qui améliore la qualité globale du texte produit.

Essayez de donner une température très basse et une température très élevée. Qu'observez-vous ?
outputs_sampling = model.generate( **inputs, max_length=50, do_sample=True, temperature=0.0001, top_k=50, top_p=0.95 ) text_sampling = tokenizer.decode(outputs_sampling[0], skip_special_tokens=True) print(text_sampling) outputs_sampling = model.generate( **inputs, max_length=50, do_sample=True, temperature=1000.0, top_k=50, top_p=0.95 ) text_sampling = tokenizer.decode(outputs_sampling[0], skip_special_tokens=True) print(text_sampling)
Quand la température est basse, nous retrouvons la solution gloutonne. Quand la température est élevée, nous obtenons des phrases peu cohérentes.

Enfin, générez du texte en utilisant la recherche par faisceau (beam search) avec un faisceau (num_beams) égal à 5. Comparez le résultat avec les méthodes précédentes.
outputs_beam = model.generate( **inputs, max_length=50, num_beams=5, early_stopping=True ) text_beam = tokenizer.decode(outputs_beam[0], skip_special_tokens=True) print(text_beam)

La recherche par faisceau explore plusieurs chemins simultanément pour choisir une génération optimale en termes de probabilité globale. Ainsi, elle génère souvent un texte plus fluide et grammaticalement correct, mais peut être légèrement moins variée ou créative par rapport à l'échantillonnage.

Augmentez le nombre de beam. Qu'observez-vous ?
Les calculs deviennent très longs.