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

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))