Correction Générateur automatique de texte
Extraction des n-grammes d'un texte
def extraire_ngrammes(txt, n):
"""Extrait la liste des n-grammes d'un texte.
Entrée : texte = le texte à analyser ( str )
n = la taille des n-grammes
Sortie : liste des n-grammes du fichier
"""
# suppression des retours à la ligne du texte ( remplacement par un espace )
txt = txt.replace('\n', ' ')
# séparation du texte en liste de tokens de longueur n :
ngrammes = []
i = 0
while i <= len(txt)-n: # tant qu'on n'a pas atteint la fin du texte - n caractères
ngramme = ''
for j in range(i, i+n): # extraction de n caractères
ngramme += txt[j]
ngrammes.append(ngramme)
i += n # "avance" de n caractères
return ngrammes
texte = "aabbababaaabcaabca"
n = 2
ngrammes = extraire_ngrammes(texte, n)
print(ngrammes)
Analyse du contexte de chaque ngramme
def analyse_contexte(ngrammes):
"""Renvoie les dictionnaires de contexte.
Entrée : nrammes = la liste des ngrammes extraits du corpus d'entraînement (list)
Sortie : contexte = dictionnaire des listes des ngrammes suivants chaque ngramme du corpus (dict)
compteur = dictionnaire indiquant combien de fois chaque ngramme de contexte apparaît dans le corpus
"""
contexte = {}
compteur = {}
# remplissage des dictionnaires de contexte pour chaque token
for i in range(1, len(ngrammes)):
precedent = ngrammes[i-1]
suivant = ngrammes[i]
if precedent not in contexte:
contexte[precedent] = []
contexte[precedent].append(suivant)
if (precedent, suivant) not in compteur:
compteur[(precedent, suivant)] = 0
compteur[(precedent, suivant)] += 1
return contexte, compteur
contexte, compteur = analyse_contexte(ngrammes)
print(contexte)
print(compteur)
Calcul des fréquences d'apparition
- le nombre de fois où un ngramme contexte donné apparaît est indiquée par le nombre d'éléments dans la liste de ce ngramme dans le dictionnaire contexte :
len(contexte[precedent]) - le nombre de fois où une paire (precedent, cible) apparaît est directement donné par
compteur[(precedent, cible)]
Première possibilité : parcours du dictionnaire contexte
L'idée : pour chaque clé du dictionnaire contexte ( donc pour chaque ngramme ), on parcours le tableau de ses suivants potentiels, et on calcule la fréquence d'apparition de la suite (precedent, suivant).
Cependant, cela suppose, pour chaque ngramme precedent, de "garder une trace" des fréquences déjà calculées, pour éviter de recalculer la fréquence d'un ngramme suivant déjà rencontré...
def calcul_frequences(contexte, compteur):
"""Fonction de calcul de la fréquence d'apparition de chaque suite de deux n-grammes possibles.
Entrées : contexte et compteur = les deux dictionnaires
Sortie : un dictionnaire fréquence dont les clés sont les n-grammes, et les valeurs, les listes des tuples (n-gramme suivant, fréquence).
"""
frequences = {}
for precedent in contexte:
suivants = [] # tableau qui gardera la "trace" des tuples (suivant, fréquence) déjà rencontrés pour la clé 'precedent', et donc des fréquences déjà calculées
for suivant in contexte[precedent]:
f = compteur[(precedent, suivant)]/len(contexte[precedent]) # calcul de la fréquence
if (suivant, f) not in suivants:
suivants.append((suivant, f))
frequences[precedent] = suivants
return frequences
frequences = calcul_frequences(contexte, compteur)
print(frequences)
Plus judicieux : parcours du dictionnaire compteur
Pour éviter le "problème" précédent, on a alors plutôt intérêt à parcourir le dictionnaire compteur : en effet, les clés de ce dictionnaire sont toutes les suites (precedent, suivant) possibles, qui n'apparaissent dans le dictionnaire qu'une seule fois par principe.
Cela n'utilise en plus qu'une seule boucle de parcours au lieu de deux imbriquées comme précédemment...
def calcul_frequences(contexte, compteur):
"""Fonction de calcul de la fréquence d'apparition de chaque suite de deux n-grammes possibles.
Entrées : contexte et compteur = les deux dictionnaires
Sortie : un dictionnaire fréquence dont les clés sont les n-grammes, et les valeurs, les listes des tuples (n-gramme suivant, fréquence).
"""
frequences = {}
for suite in compteur:
precedent = suite[0] # precedent = premier élément des tuples
suivant = suite[1] # suivant = deuxième élément
f = compteur[suite]/len(contexte[precedent])
if precedent not in frequences:
frequences[precedent] = []
frequences[precedent].append((suivant, f))
return frequences
frequences = calcul_frequences(contexte, compteur)
print(frequences)
Choix d'un n-gramme "candidat" dans un contexte donné
from random import random
def choisir_mot(candidats):
"""Renvoie un candidat probable dans une liste de candidats possibles.
Entrée : la liste des candidats sous forme (n-gramme, fréquence)
Sortie : le n-gramme sélectionné
"""
p = random() # tire aléatoirement un nombre entre 0 et 1.0
# tri de la liste des tokens suivants par ordre de proba décroissante
for i in range(len(candidats)):
max = i
for j in range(i+1, len(candidats)):
if candidats[i][1] < candidats[j][1]: # attention, on ne compare pas des valeurs, mais les tuples selon leur deuxième élément ( indice 1 ) !
max = j
if max != i:
candidats[i], candidats[max] = candidats[max], candidats[i]
# Choix du n-gramme
s = 0
for candidat, proba in candidats:
s += proba
if s >= p:
return candidat
n = choisir_mot(frequences['ab'])
print(n)
Génération de texte
def genere_texte(prompt, n, N):
"""Génère un texte aléatoire à partir d'un corpus contenu dans un fichie texte.
Entrée : prompt = le prompt initial (str), n = la taille des n-grammes (int), N = le nombre de mots à générer
Sortie : le texte généré (str)
"""
with open("les_miserables.txt", 'r') as f:
corpus = f.read()
# suppression des retours à la ligne
corpus = corpus.replace('\n', ' ')
ngrammes = extraire_ngrammes(corpus, n)
contexte, compteur = analyse_contexte(ngrammes)
frequences = calcul_frequences(contexte, compteur)
for i in range(N):
# token contexte courant ( = n derniers caractères du prompt )
ngramme = ''
for j in range(len(prompt)-n, len(prompt)):
ngramme += prompt[j]
candidat = choisir_mot(frequences[ngramme])
prompt += candidat
return prompt
n = 6
prompt = "Il était une"
print(genere_texte(prompt, n , 50))