Magique, ChatGPT ? A partir d’un cours texte de départ ( le « prompt » ), « il » est capable de créer un discours très structuré et cohérent, et ce sur n’importe quel sujet !
Aucune magie, évidemment, contrairement à ce dont beaucoup de gens ( dont vous ne faites pas partie bien entendu ) sont persuadés…
ChatGPT est un agent conversationnel destiné à de la génération automatique de texte ( il existe également des générateurs d’images, comme Dall-E ).
Il est basé sur GPT, un ( il en existe d’autres ) grand modèle de langage ( en anglais : LLM = large Model Language ), c’est à dire une modélisation mathématique très poussée du
langage humain, modélisation issue de l’analyse d’un très grand corpus ( = ensemble ) de données, qui lui a permis de déterminer les « règles » que suit le langage humain
pour élaborer des phrases, des textes, etc.…
Pour faire simple, ChatGPT, comme la majorité des modèles de langage, ne fait qu’une seule chose ( mais il la fait bien ) : déterminer quel sera le mot le plus probable qui suivra un autre dans un texte donné.
Comment une machine arrive-t-elle à faire cela ?
Il y a une centaine d’années, le mouvement artistique dit des Surréalistes a inventé un « jeu » consistant à plusieurs personnes à
écrire une phrase, la première personne proposant un mot, puis la suivante un verbe, la suivante un adjectif, etc., et ce sans que chaque personne ne connaisse ce que les autres ont proposé au
préalable.
Ce jeu a été par la suite appelé le Cadavre Exquis, car la première phrase qui avait été « générée » selon ce principe était
: "Le cadavre - exquis - boira - le vin - nouveau."
Bien que du fait des règles du jeu, les phrases générées selon ce principe sont grammaticalement correctes, elles n’ont que très peu de sens au final...
La raison en est que chaque participant ne connaît pas le contexte dans lequel le mot qu’il doit proposer doit être, c’est à dire l’ensemble des mots qui vont précéder le sien
dans la phrase finale ; or, c’est ce contexte qui nous permet de saisir le sens d’une phrase.
Par exemple, si on commence à écrire les mots :
Je suis allé bronzer au ...
...on conçoit tout de suite que la suite sera probablement :
Je suis allé bronzer au soleil.
Écrire une phrase cohérente et sensée suppose donc, en plus de respecter les règles de grammaire, de connaître le contexte dans lequel la phrase se tient, et c’est tout le problème pour une machine de s'approprier ces concepts fondamentalement humains !
ChatGPT est basé sur des outils mathématiques très pointus pour analyser le contexte d’un texte, et il dispose d’une « fenêtre de contexte », c’est à dire le nombre de « mots » ( ce sont en fait
des suites de caractères de longueur variable appelés tokens en anglais ) sur lequel son analyse de contexte se fait, de plusieurs milliers de "mots" !
Bien entendu, il y a un code très technique ( utilisation de réseaux de neurones et de concepts informatiques très avancés ) à écrire, ainsi qu’un « entraînement » très lourd ( et très
énergivore ! ) du modèle sur un très large corpus avant qu'il soit opérationnel.
Or, il existe des modèles beaucoup plus simples ( mais également moins performants..), dit modèles de langages statistiques, qui sont basés sur la constatation que, pour bâtir une phrase sensée, il suffit de ne regarder que les quelques mots précédents du contexte ( 1, 2 ou trois, 5 au maximum ) pour déterminer les mots qui vont suivre alors.
Dans l’exemple précédent, le début de la phrase n’est pas indispensable pour saisir le contexte, il suffit en effet d’écrire :
"bronzer au ..."
...pour comprendre que la suite de la phrase sera :
"bronzer au soleil."
Cette constatation se généralise à une suite donnée de n-gramme; un n-gramme peut être un ensemble de n mots successifs dans un texte ( comme ci-dessus ), ou un ensemble de n caractères, comme dans l'approche que nous allons adopter ici.
Par exemple, pour la suite du n-gramme de 3 caractères ( = trigramme ) :
'bro'
... une personne proposera probablement :
'broche', 'bronche', 'broncher', 'bronzer', etc...
Dans un contexte de texte relatif aux vacances, le mot "candidat" serait alors probablement "bronzer"; mais comment faire comprendre ça à une machine ? Il va falloir lui apprendre !
Cette activité porte sur une méthode destinée à faire de la génération automatique de texte, en apprenant à une machine à prédire le n-gramme de caractères le plus susceptible d'en suivre un autre dans le texte à générer.
On considérera des n-grammes formés de n caractères contigus ( plus simple à coder qu'avec des n-grammes de mots ).
Prenons pour exemple le texte suivant :
Pour n = 2, les 2-grammes ( = digrammes ) de cette chaîne sont ( les espaces font partie des n-grammes ) :
Parmi tous les "contextes" possibles dans le texte, intéressons-nous à celui du digramme mu
( sans tenir compte de l'accentuation ) :
On constate que l'on trouve :
(mu, rs)
(mu, rm)
(mu, re)
→ le contexte mu
apparaît donc 4 fois dans le corpus; on peut alors déduire la fréquence avec laquelle chacun des digramme suivant apparaît ( la somme des fréquences
est égale à 1 ) :
rs
rm
re
Conséquence : on "apprend" alors à la machine que, dans un texte quelconque, le digramme le plus probable qui suivra le digramme mu
sera celui dont la
fréquence d'apparition dans le corpus d'apprentissage est la plus grande, c'est à dire ici le digramme re
.
Pour bâtir un texte complet, le générateur de texte, après la phase d’analyse du corpus, pourra ainsi choisir, n-gramme après n-gramme, ( donc à chaque changement de contexte ) un « candidat » probable
à ajouter à la suite des mots d’une phrase ( le début du texte, le "prompt", étant fourni par l'utilisateur ).
On verra cependant que, pour que le texte paraisse vraiment original et différent à chaque exécution, il ne faut pas choisir forcément le candidat le plus probable, mais plutôt le « piocher »
aléatoirement dans une liste de candidats possibles.
Le concept utilisé s'appelle une chaîne de Markov : c'est une suite d'éléments ( ici, des n-grammes ), dans laquelle la détermination d'un
élément donné ne nécessite pas de considérer l'ensemble de la chaîne, mais uniquement l'élément actuel , en utilisant la probabilité de passer de cet élément au suivant.
Ce principe pourrait être de la même manière appliqué à une suite de notes de musique, dans le but de produire des morceaux
aléatoires s'inspirant de morceaux connus !
C'est une méthode simple ( et donc limitée...), mais facile à coder, et surtout, qui ne nécessite par un énorme corpus d'apprentissage, du moins lorsque n reste petit.
On s'en doute, ce modèle souffre de plusieurs limitations :
Le travail sera fragmenté en différentes fonctions, que vous devrez compléter ou dont vous devrez écrire complètement le code.
Compléter le code de la fonction extraire_ngrammes
ci-dessous, qui :
Par exemple, avec texte = "aabbababaaabcaabca"
, et n = 2
, la fonction doit renvoyer la liste :
['aa', 'bb', 'ab', 'ab', 'aa', 'ab', 'ca', 'ab', 'ca']
Le gros du travail : à partir de la liste des n-grammes, établir dans quel "contexte" chacun se trouve, c'est à dire, pour un n-gramme donné dans la liste ( le "suivant" ), déterminer celui qui le précède ( le "précédent" ), et combien de fois la suite (précédent, suivant) apparaît dans le corpus.
Ce comptage permettra par la suite de calculer les fréquences avec lesquelles un n-gramme donné en suit un autre.
Pour réaliser ces calculs, le code devra remplir deux dictionnaires :
contexte = {
'aa': ['bb', 'ab'],
'bb': ['ab'],
'ab': ['ab', 'aa', 'ca', 'ca'],
'ca': ['ab']
}
Ce dictionnaire permettra :
compteur = {
('aa', 'bb'): 1,
('bb', 'ab'): 1,
('ab', 'ab'): 1,
('ab', 'aa'): 1,
('aa', 'ab'): 1,
('ab', 'ca'): 2,
('ca', 'ab'): 1
}
Ce dictionnaire permettra de déterminer le nombre de fois où une suite de deux n-grammes donnés (précédent, suivant) apparaît dans le corpus.Bien entendu, ces deux dictionnaires seront bien plus complets en réalité !
On donne ci-dessous l'algorithme à suivre pour remplir ces deux dictionnaires :
contexte ← dictionnaire vide
compteur ← dictionnaire vide
pour chaque n-gramme dans la liste:
précédent ← n-gramme précédent ( ou n-gramme courant )
suivant ← n-gramme courant ( ou n-gramme suivant )
si précédent n'est pas encore dans contexte:
on crée la clé précédent avec comme valeur la liste vide
on ajoute suivant à la liste associée à la clé précédent
si le tuple (précédent, suivant) n'est pas encore dans compteur:
on y créé une clé (précédent, suivant) avec comme valeur 0
on incrémente la valeur associée à la clé (précédent, suivant)
renvoi de contexte et de compteur
"
Écrire une fonction analyse_contexte
qui :
Le but est maintenant, à partir des dictionnaires contexte et compteur, de calculer la fréquence d'apparition de chaque suite de deux n-grammes.
Cette fréquence est calculée à partir de la formule :
calcul_frequences
, qui :
frequences = {
'aa': [('bb', 0.5), ('ab', 0.5)],
'bb': [('ab', 1.0)],
'ab': [('ab', 0.25), ('aa', 0.25), ('ca', 0.5)],
'ca': [('ab', 1.0)]
}
On a donc maintenant un moyen de connaître les n-grammes candidats possibles à la suite d'un autre n-gramme donné.
Il reste maintenant à choisir dans cette liste un candidat probable à chaque fois que nécessaire; le problème est, comme on l'a dit, que si on choisit toujours le plus fréquent, l'algorithme de génération de texte entrera rapidement dans une "boucle" : le texte généré deviendra répétitif et proposera en boucle les mêmes phrases...
Il faut donc choisir un candidat probable de manière "semi-aléatoire", c'est à dire tenir compte de la probabilité d'apparition des n-grammes, mais avec une marge de hasard...
Voila l'approche que l'on va suivre :
Écrire une fonction choisir_mot
qui, dans une liste de candidats, choisit un n-gramme selon le principe énoncé ci-dessus.
Pour le tri de la liste, on appliquera un des algorithmes vus en Première, comme par exemple le tri sélection.
Tester la fonction ( par exemple avec la liste des candidats suivant du n-gramme 'ab'
), et vérifier que le plus fréquent "sort" bien le plus souvent, mais pas toujours...
Tout est en place pour écrire la fonction principale du programme; elle devra :
Écrire une fonction genere_texte
, qui :
Vous pouvez utiliser ce fichier texte comme corpus d'apprentissage pour le modèle de langage ( qui sera donc capable d'écrire comme Victor Hugo 😲)
Appeler ensuite la fonction, en lui donnant divers valeurs pour n ( de 3 à 10 environ ).
Il était une fois observée. La chute de tout, autour de lui. Une troisième renversa en arrière Blanche au chemin d'Aubervilliers, en quatre mois d'agonie. Beaucoup de choses s'y étaient fermée par une grille dont il est vrai, livide, mais précise dans sa boîte fleurs. Tout autour de Jean Valjean reconnut Javert. --Voilà, grommela-t-il, de quoi fouiller la terr
Ok, ok, ce n'est pas ChatGPT...quelques pistes d'amélioration possibles :