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)
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)
len(contexte[precedent])
compteur[(precedent, cible)]
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)
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)
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)
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))